/* ==== 00-tokens.css ==== */
/* ============================================================================
   Tokens (R-VIS-1, R-VIS-3, R-VIS-26, R-VIS-27, R-VIS-36, R-MOT-6).

   The design system is "Folio, set on a grid" (docs/FOLIO_CLAUDE_PROPOSAL.html),
   carried by four print disciplines:

   1. One baseline. Client body is 18px on a 28px line (--u); every vertical
      distance is a multiple of the half-line --u2 (14px). Display sizes take
      leading that lands on the same half-unit.
   2. One scale: 14 / 16 / 18 / 24 / 32 / 48 / 56 / 72, plus the one poster
      size, 96, used once per page for the cover line. Nothing read runs below
      16px; 14 is only for single tracked caps labels.
   3. Side heads hang in a margin column (--side), so every block of content
      starts on one vertical axis.
   4. Three rule weights, from the 1994 Nutrition Facts panel: heavy 6px opens
      a division, medium 3px closes a total, hairline 1px separates rows.

   Blue means a link or the calorie target tick, nothing else. Red means "this
   failed and needs you", and over-target ink.

   The dark scheme follows prefers-color-scheme. There is no theme switch, so
   there is no data-theme override (R-VIS-27, Q-PAGE-7).
   ============================================================================ */
:root {
  color-scheme: light dark;

  /* Baseline and scale */
  --u: 1.75rem;            /* 28px: one line of 18px body */
  --u2: 0.875rem;          /* 14px: the half-line every gap is built from */
  --fs-14: 0.875rem;       /* caps labels only: single tracked words */
  --fs-16: 1rem;           /* the smallest running text */
  --fs-18: 1.125rem;
  --fs-24: 1.5rem;
  --fs-32: 2rem;
  --fs-48: 3rem;
  --fs-56: 3.5rem;
  --fs-72: 4.5rem;
  --fs-cover: 6rem;        /* 96px: the one poster size, on the band only */

  /* Grid */
  --side: 9.5rem;          /* the side-head column */
  --side-gap: 1.75rem;
  --gutter: clamp(1.25rem, 5vw, 4.5rem);
  --rule-heavy: 6px;
  --rule-mid: 3px;

  /* Faces: all self-hosted variable woff2 (01-base.css) */
  --font-sans: "Public Sans", "Helvetica Neue", Arial, sans-serif;
  --font-serif: "Source Serif 4", Georgia, serif;
  --font-mono: "Roboto Mono", "SFMono-Regular", Consolas, monospace;
  --font-display: "Fraunces", Georgia, serif;

  /* Folio neutrals are cool */
  --canvas: #e8ecef;
  --paper: #ffffff;
  --paper-subtle: #f2f4f5;
  --ink: #0a0c0e;
  --ink-muted: #4d5861;
  --ink-secondary: #454f57;   /* carries rank at small sizes: holds 7:1 on paper */
  --rule: #d3d8dc;
  --rule-strong: #68737c;

  /* The band: one black in both schemes (the paintings' ground) */
  --band: #07090b;
  --band-ink: #ffffff;
  --band-muted: #c7cfd5;
  --band-rule: #4a5157;

  /* Meanings */
  --accent: #005ea8;          /* links and the target tick */
  --accent-hover: #004a86;
  --focus: #a8326e;           /* one focus ring everywhere */
  --focus-on-band: #ff9ecf;
  --fail: #b3261e;            /* failed and needs you; over target */
  --fail-on-band: #ff7a6e;

  /* The coach: warm green neutrals */
  --coach-paper: #f6f6f1;
  --coach-rule: #d9dbcf;
  --coach-ink: #1f231d;
  --coach-placeholder: #5f645a;

  /* Charts */
  --hatch: #5b6168;
  --good-tint: rgba(10, 12, 14, 0.08);

  /* Motion: four durations, three curves (IBM Carbon's productive set) and
     one expressive token, spent at most once per event. */
  --dur-tap: 70ms;            /* press feedback */
  --dur-fade: 150ms;          /* fades, exits, every reduced-motion swap */
  --dur-move: 240ms;          /* a cell changes, a line is set, a row settles */
  --dur-arrive: 400ms;        /* the one expressive moment */
  --ease-standard: cubic-bezier(0.2, 0, 0.38, 0.9);
  --ease-enter: cubic-bezier(0, 0, 0.38, 0.9);
  --ease-exit: cubic-bezier(0.2, 0, 1, 0.9);
  --ease-expressive: cubic-bezier(0, 0, 0.3, 1);
  --cadence: 80ms;            /* one typeset line per beat */
}

/* Dark: the paper lifts to #16191c so the band stays a band; the coach's warm
   paper steps above the folio's, as it does in light. */
@media (prefers-color-scheme: dark) {
  :root {
    --canvas: #050607;
    --paper: #16191c;
    --paper-subtle: #1f2428;
    --ink: #f7f9fa;
    --ink-muted: #bbc4ca;
    --ink-secondary: #99a5ad;
    --rule: #343b40;
    --rule-strong: #8b969e;
    --accent: #8cc8ff;
    --accent-hover: #b8dcff;
    --focus: #ff9ecf;
    --fail: #ff7a6e;
    --coach-paper: #1f221d;
    --coach-rule: #383d36;
    --coach-ink: #e6e8e0;
    --coach-placeholder: #a3a897;
    --hatch: #9aa3ab;
    --good-tint: rgba(247, 249, 250, 0.1);
  }
}

/* Phone: the cover steps down; 56 becomes the poster size. */
@media (max-width: 48rem) {
  :root { --fs-72: 2.75rem; --fs-cover: var(--fs-56); }
}

/* ==== 01-base.css ==== */
/* ============================================================================
   Base: faces, the body, links, the focus ring (R-VIS-1, R-VIS-6, R-VIS-23,
   R-VIS-29, R-VIS-32).

   Fraunces (cover, side heads, step numerals, rail head), always SOFT 0 and
   WONK 0; Public Sans for all data, UI and labels; Source Serif 4 only for the
   coach's prose; Roboto Mono only for record ids and code. All self-hosted
   variable woff2 with font-display: swap. No licence-restricted face is ever
   served.
   ============================================================================ */
@font-face { font-family: "Public Sans"; src: url("/static/fonts/public-sans-var.woff2") format("woff2");
  font-style: normal; font-weight: 100 900; font-display: swap; }
@font-face { font-family: "Source Serif 4"; src: url("/static/fonts/source-serif-4-var.woff2") format("woff2");
  font-style: normal; font-weight: 200 900; font-display: swap; }
@font-face { font-family: "Source Serif 4"; src: url("/static/fonts/source-serif-4-italic-var.woff2") format("woff2");
  font-style: italic; font-weight: 200 900; font-display: swap; }
@font-face { font-family: "Roboto Mono"; src: url("/static/fonts/roboto-mono-var.woff2") format("woff2");
  font-style: normal; font-weight: 100 700; font-display: swap; }
@font-face { font-family: "Fraunces"; src: url("/static/fonts/fraunces-var.woff2") format("woff2");
  font-style: normal; font-weight: 100 900; font-display: swap; }
@font-face { font-family: "Fraunces"; src: url("/static/fonts/fraunces-italic-var.woff2") format("woff2");
  font-style: italic; font-weight: 100 900; font-display: swap; }

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  color: var(--ink);
  background: var(--canvas);
  font: 450 var(--fs-18)/var(--u) var(--font-sans);
  font-synthesis: none;
}

h1, h2, h3, h4 { margin: 0; text-wrap: balance; }
h1, h2 { font-optical-sizing: auto; }
p { margin: 0 0 var(--u2); }
/* Prose never ends on a one-word line. */
p, li { text-wrap: pretty; }
img, svg { max-width: 100%; }
[hidden] { display: none !important; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
a:hover { color: var(--accent-hover); }

/* One focus ring everywhere: 3px, offset 2px, never clipped. */
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* Placeholders hold contrast in both schemes. */
::placeholder { color: var(--ink-secondary); opacity: 1; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Tabular lining numerals wherever figures stack; proportional lining in
   running text. */
body { font-variant-numeric: proportional-nums lining-nums; }
code, kbd, samp, .record-id { font: 500 var(--fs-16)/var(--u) var(--font-mono); }

/* Forced colours: link-buttons keep an edge. */
@media (forced-colors: active) {
  button, .ink-button { border: 2px solid ButtonText; }
}

/* ==== 02-shell.css ==== */
/* ============================================================================
   The shell: the sheet, two panes, the band, the page-wide strip (R-PAGE-1,
   R-VIS-7..16, R-VIS-31, R-PAGE-44, R-ERR-18).

   <body> -> .page-strip (sticky, said once) -> #document-body.sheet ->
   .skip-link, main.folio (the band, then the page), aside.rail (the coach).
   ============================================================================ */

/* -- The sheet ------------------------------------------------------------- */
.sheet {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  align-items: stretch;
  width: 100%;
  max-width: 90rem;
  min-height: 100vh;
  margin-inline: auto;
  background: var(--paper);
  color: var(--ink);
}
.sheet--solo { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 48.01rem) {
  .sheet { width: calc(100% - 4rem); margin-block: 2rem; }
}
@media (max-width: 48rem) {
  body { background: var(--paper); }
}
/* Keep a whole dish-name word at the 24px cover floor on the narrowest screens, including reader spacing. */
@media (max-width: 18rem) {
  :root { --gutter: 0.5rem; }
}

/* -- The folio: about 2/3, scrolls with the page --------------------------- */
.folio {
  min-width: 0;
  padding: 0 var(--gutter) calc(var(--u) * 2);
}
/* Every folio ends on a printed 3px rule. */
.folio::after {
  content: "";
  display: block;
  margin-top: var(--u);
  border-top: var(--rule-mid) solid var(--ink);
}

/* -- The coach rail: about 1/3, the full sheet height ---------------------- */
.rail {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--u2);
  padding: var(--u2) clamp(1rem, 2.2vw, 2rem) var(--u);
  border-left: 1px solid var(--coach-rule);
  background: var(--coach-paper);
  color: var(--coach-ink);
}
/* The rail's title sits on the band's folio line: both panes share one top edge. */
.rail__title {
  margin: 0;
  padding-bottom: var(--u2);
  border-bottom: 1px solid var(--coach-rule);
  font: 650 var(--fs-24)/var(--u) var(--font-display);
  font-variation-settings: "SOFT" 0, "WONK" 0;
  color: var(--ink);
}
/* Stacked below 64rem: the coach follows the folio, opening on the heavy rule. */
@media (max-width: 64rem) {
  .sheet { grid-template-columns: minmax(0, 1fr); }
  .rail { border-left: 0; border-top: var(--rule-heavy) solid var(--ink); padding-inline: var(--gutter); }
}

/* -- The band: the masthead bleeds to the sheet's edge --------------------- */
.band {
  position: relative;
  margin: 0 calc(-1 * var(--gutter)) var(--u);
  padding: var(--u2) var(--gutter) calc(var(--u) * 1.5);
  background: var(--band);
  color: var(--band-ink);
  border-bottom: var(--rule-heavy) solid var(--accent);
}
.band--line { padding-bottom: 0; }
.band--line .folio-line { border-bottom: 0; }
/* On the black band the focus ring turns pink to hold contrast. */
.band :focus-visible { outline-color: var(--focus-on-band); }
.band a { color: var(--band-ink); text-decoration-thickness: 1px; text-underline-offset: 0.2em; }

/* The folio line: one pattern on every page -- context or back link left,
   page context and links in one right-hand group. */
.folio-line {
  display: flex;
  align-items: baseline;
  gap: 0 1.25rem;
  padding-bottom: var(--u2);
  border-bottom: 1px solid var(--band-rule);
  color: var(--band-muted);
  font: 650 var(--fs-16)/var(--u) var(--font-sans);
  letter-spacing: 0.02em;
}
.folio-line__left { display: flex; flex-wrap: wrap; gap: 0 1.25rem; min-width: 0; }
.folio-line__right {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 1.25rem;
  margin-left: auto;
}
.folio-line button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--band-ink);
  font: inherit;
  letter-spacing: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  cursor: pointer;
}
/* The install affordance takes no space until the browser offers it. */
.install-slot:empty { display: none; }

/* -- The cover -------------------------------------------------------------
   Fraunces at its sharp end: weight 650 lets the optical-size axis draw
   hairlines; SOFT 0 keeps the terminals cut. The 10ch measure breaks the line
   early so the band keeps an empty right half. Tight tracking also tightens
   the word space; give it back. No automatic hyphenation: 03-fit-cover.js
   steps the size instead. */
.band__title { padding-top: calc(var(--u) * 1.5); }
.band h1 {
  margin: 0;
  color: var(--band-ink);
  font: 650 var(--fs-cover)/calc(var(--u2) * 7) var(--font-display);   /* 98px on 96 */
  letter-spacing: -0.048em;
  word-spacing: 0.1em;
  font-variation-settings: "SOFT" 0, "WONK" 0;
  text-wrap: balance;
  overflow-wrap: break-word;
  hyphens: manual;
}
@media (min-width: 48.01rem) {
  .band h1 { max-width: 10ch; }
}
/* Longer lines step down; --min is reached only by measurement. */
.band h1.cover--long { font-size: var(--fs-72); line-height: calc(var(--u2) * 5); max-width: 15ch; }
.band h1.cover--longer { font-size: var(--fs-56); line-height: calc(var(--u2) * 4); max-width: 18ch; }
.band h1.cover--longest { font-size: var(--fs-48); line-height: calc(var(--u2) * 3.5); max-width: 22ch; }
.band h1.cover--min { font-size: var(--fs-32); line-height: calc(var(--u) * 1.5); max-width: none; }

.kicker {
  margin: 0 0 var(--u2);
  color: var(--band-muted);
  font: 700 var(--fs-14)/var(--u) var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.dek,
.orienting {
  margin: var(--u2) 0 0;
  color: var(--band-muted);
  font: 650 var(--fs-18)/var(--u) var(--font-sans);
}
/* The fact line: figures in band ink, units muted; it breaks only between
   middot clauses. */
.facts {
  margin: var(--u) 0 0;
  color: var(--band-muted);
  font: 500 var(--fs-18)/var(--u) var(--font-sans);
}
.band__title--recipe .facts { margin-top: calc(var(--u) * 2); }
.fact { white-space: nowrap; }
.fig--band .fig__n { color: var(--band-ink); font-weight: 650; }
.fig--band .unit { color: var(--band-muted); }

/* The painting stands beside the headline block: it spans the block without
   adding height, stands on the fact line, and its right edge is the band's
   measure. Its ground is the band's black, so its edges are the dish's. */
.band__title--art {
  display: grid;
  grid-template-columns: minmax(0, 1fr) min(18rem, 36%);
  column-gap: var(--u);
}
.band__title--art h1 { max-width: none; }
/* At the smallest desktop step a long word plus reader spacing needs more of the headline measure. */
@media (min-width: 48.01rem) {
  .band__title--art.band__title--compact { grid-template-columns: minmax(0, 1fr) min(12rem, 25%); }
}
.band__art {
  align-self: stretch;
  width: 100%;
  height: 0;
  min-height: 100%;
  object-fit: contain;
  object-position: right bottom;
}

/* -- The skip link to the coach: first in the tab order, hidden until
   focused; focused, it lies over the band's top-left line on the band's own
   background, so nothing moves. */
.skip-link {
  position: absolute;
  z-index: 2;
  top: var(--u2);
  left: var(--gutter);
  padding: 0 calc(var(--u2) / 2);
  background: var(--band);
  color: var(--band-ink);
  font: 650 var(--fs-16)/var(--u) var(--font-sans);
  letter-spacing: 0.02em;
}
.skip-link:focus-visible { outline-color: var(--focus-on-band); }
.skip-link:not(:focus) {
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* -- Page-wide states, said once: a sticky strip of band black above the
   page (offline, signed out, read-only). Empty, it takes no space. */
.page-strip {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--band);
  color: var(--band-ink);
}
.page-strip:empty { display: none; }
.page-strip__line {
  margin: 0;
  padding: var(--u2) var(--gutter);
  font: 400 var(--fs-16)/var(--u) var(--font-sans);
}
.page-strip__line + .page-strip__line { padding-top: 0; }
.page-strip a { color: var(--band-ink); font-weight: 650; }
.page-strip :focus-visible { outline-color: var(--focus-on-band); }

/* -- Phone ------------------------------------------------------------------ */
@media (max-width: 48rem) {
  .folio-line, .folio-line__right { flex-wrap: wrap; justify-content: flex-start; gap: 0 var(--u2); }
  .folio-line__right { margin-left: auto; }
  .band h1 { line-height: calc(var(--u2) * 4); }
  .band h1.cover--long,
  .band h1.cover--longer { font-size: var(--fs-48); line-height: calc(var(--u2) * 3.5); }  /* 56 is the full size */
  .band h1.cover--longest { font-size: var(--fs-32); line-height: calc(var(--u) * 1.5); }
  .band h1.cover--min { font-size: var(--fs-24); line-height: var(--u); }
  /* The painting stands above the kicker, on the same left edge. */
  .band__title--art { grid-template-columns: minmax(0, 1fr); }
  .band__art {
    order: -1;
    justify-self: start;
    width: min(62%, 40vh);
    height: auto;
    min-height: 0;
    margin: 0 0 var(--u);
    object-position: left bottom;
  }
  .band__title--recipe .band__art { margin-bottom: calc(var(--u) * 2); }
  /* The coach's own heavy rule closes the folio on a phone. */
  .sheet:not(.sheet--solo) .folio { padding-bottom: var(--u); }
  .sheet:not(.sheet--solo) .folio::after { display: none; }
}
/* A stacked painting is a figure, not the first screen: capped on tablets. */
@media (min-width: 37.5rem) and (max-width: 48rem) {
  .band__art { height: 17.5rem; }
}
/* Let the last cover step use the full band before breaking a word at the reader's chosen spacing. */
@media (max-width: 18rem) {
  .band h1.cover--min { margin-inline: calc(-1 * var(--gutter)); }
}
/* On a short, narrow screen (200% zoom on 720p) the painting yields to the dish name. */
@media (max-width: 48rem) and (max-height: 30rem) {
  .band__art { display: none; }
}

/* -- Forced colours: the band turns to the system ground; a painting made
   for black would show as a smudge, so it steps aside. */
@media (forced-colors: active) {
  .band__art { display: none; }
}

/* Public, visible discovery inside each page's main content. */
.agent-discovery {
  margin-block: var(--u) var(--u2);
  font-size: var(--fs-16); line-height: var(--u); color: var(--ink-secondary);
}

/* ==== 03-section.css ==== */
/* ============================================================================
   Sections: side heads, three rules, ruled lists, labels (R-VIS-5, R-VIS-10,
   R-VIS-11, R-VIS-19, R-SURF-50).

   Every section is a two-column row: the head in the margin column, the
   content on one shared axis, the two sharing a first baseline. The hairline
   opens an ordinary section; the heavy rule opens a division; the medium
   rule closes a total. No boxes, cards, radii, shadows, pills or gradients.
   ============================================================================ */
.side,
.doc-section {
  display: grid;
  grid-template-columns: var(--side) minmax(0, 1fr);
  column-gap: var(--side-gap);
  align-items: first baseline;
  margin: 0;
  padding: var(--u2) 0 calc(var(--u) * 1.5);
  border-top: 1px solid var(--ink);
}
.side--heavy { border-top-width: var(--rule-heavy); }
.side--mid { border-top-width: var(--rule-mid); }

/* The side head: Fraunces 650 24px, hung in the margin. */
.side-head {
  grid-column: 1;
  margin: 0;
  color: var(--ink);
  font: 650 var(--fs-24)/1.15 var(--font-display);
  font-variation-settings: "SOFT" 0, "WONK" 0;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.side-body,
.doc-section > :not(.side-head) { grid-column: 2; min-width: 0; }
.side-body > :last-child { margin-bottom: 0; }
.side-body > .lead:first-child { margin-top: var(--u2); }

/* The generic resource page (R-SURF-50): its prose, links and forms sit on
   the content axis, under the side heads' margin. */
.region > p,
.region > ul,
.region > ol,
.doc-links,
.doc-actions { margin-left: calc(var(--side) + var(--side-gap)); }
.doc-links,
.doc-actions { padding: var(--u2) 0; border-top: 1px solid var(--ink); }
.doc-actions > form + form { margin-top: var(--u); }

/* A ruled list: hairlines between rows. A row that carries a word-action
   sets it at the row's end. */
.ruled { margin: 0; padding: 0; list-style: none; }
.ruled > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 var(--u2);
  padding: calc(var(--u2) / 2) 0;
}
.ruled > li + li { border-top: 1px solid var(--rule); }
.ruled + form { margin-top: var(--u); }

/* A label: tracked caps inside a section, the only text below 16px. */
.label {
  margin: 0 0 calc(var(--u2) / 2);
  color: var(--ink);
  font: 700 var(--fs-14)/var(--u) var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
/* A field group opens with an 18px bold head. */
.field-head {
  margin: var(--u) 0 calc(var(--u2) / 2);
  color: var(--ink);
  font: 700 var(--fs-18)/var(--u) var(--font-sans);
}
.side-body > .field-head:first-child { margin-top: 0; }
/* Explanatory prose under a head recedes. */
.lead {
  max-width: 36em;
  margin: 0 0 var(--u);
  color: var(--ink-muted);
  font: 400 var(--fs-18)/var(--u) var(--font-sans);
}
.side-body p { max-width: 36em; }

/* Figures: ink at full weight, the unit in secondary ink (R-VIS-22). */
.fig__n { font-weight: inherit; }
.unit { color: var(--ink-secondary); }
.fig--tab { font-variant-numeric: tabular-nums lining-nums; }

/* -- Phone: side heads fold into run-in heads above their content. -------- */
@media (max-width: 48rem) {
  .side, .doc-section { display: block; }
  .side-head { margin-bottom: var(--u2); }
  .region > p, .region > ul, .region > ol, .doc-links, .doc-actions { margin-left: 0; }
}

/* ==== 04-forms.css ==== */
/* ============================================================================
   Forms on the same system (R-PAGE-52, R-PAGE-55, R-ERR-20, R-ERR-21,
   R-SURF-58, R-VIS-24).

   A form is a ruled page, not a set of boxes: labels 16px ink, inputs 18px on
   one ruled line (a bottom hairline, no box, no radius), textareas ruled above
   and below, selects ruled with a chevron. Every Save, Send and "Ask your
   coach" is the same ink-filled button; destructive and minor actions are
   underlined words whose confirm step stays a button.

   Markup comes from app.core.html.form(): <form class="write [cls]"> with its
   hidden fields, the body, then <button class="ink">.
   ============================================================================ */
form.write {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--u2) var(--u2);
  margin: 0;
  min-width: 0;
  max-width: 100%;
}
form.write > * { flex: 1 1 100%; min-width: 0; }
form.write > button { flex: 0 1 auto; }
/* A one-field form sets its button on the field's line; the field fills the
   measure. */
form.inline > .ruled-input,
form.inline > label { flex: 1 1 min(14rem, 50%); }

/* Labels and fields */
.ruled-input,
.folio form label { display: block; color: var(--ink); font: 500 var(--fs-16)/var(--u) var(--font-sans); }
.ruled-input__label { display: block; }
.folio input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]),
.folio textarea,
.folio select {
  display: block;
  width: 100%;
  min-height: 3rem;
  margin: 0;
  padding: 0;
  border: 0;
  border-bottom: 1px solid var(--ink);
  border-radius: 0;
  background: none;
  color: var(--ink);
  font: 400 var(--fs-18)/var(--u) var(--font-sans);
}
/* A textarea is ruled above and below, like the composer; no side borders. */
.folio textarea { padding-block: calc(var(--u2) / 2); border-top: 1px solid var(--ink); resize: vertical; }
/* Selects: one line underneath and a chevron that turns light with the scheme. */
.folio select {
  padding-right: 2.5rem;
  appearance: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%230a0c0e' stroke-width='1.6'/%3E%3C/svg%3E") no-repeat right 0.25rem center / 0.75rem;
}
@media (prefers-color-scheme: dark) {
  .folio select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23f7f9fa' stroke-width='1.6'/%3E%3C/svg%3E"); }
}
/* One focus ring: the field's own rule would hide a thinner ring. */
.folio input:focus-visible, .folio textarea:focus-visible, .folio select:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
fieldset { min-inline-size: 0; margin: 0; padding: 0; border: 0; }
legend { padding: 0; font: 700 var(--fs-18)/var(--u) var(--font-sans); }

/* -- The ink button: Save, Add, Send, the warm-up action ------------------- */
button.ink,
.ink-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  max-width: 100%;
  margin: 0;
  padding: 0 1rem;
  border: 2px solid transparent;
  border-radius: 0;
  background: var(--ink);
  color: var(--paper);
  font: 700 var(--fs-18)/1 var(--font-sans);
  text-decoration: none;
  white-space: normal;
  overflow-wrap: anywhere;
  cursor: pointer;
}
.ink-button:hover { color: var(--paper); }
button:disabled { opacity: 0.55; cursor: default; }

/* -- A word-action: an underlined word, not a box ("Remove") -------------- */
form.word-action { display: inline-flex; flex: 0 1 auto; gap: 0; max-width: 100%; }
form.word-action > button.ink {
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-secondary);
  font: 600 var(--fs-16)/var(--u) var(--font-sans);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/* -- "Ask your coach to ...": every one looks the same -- ink-filled, 16px,
   verb first, square corners, set like Send. It is a real form. */
form.coach-say { display: inline-block; margin: var(--u2) 0 0; }
form.coach-say > button.ink {
  min-height: calc(var(--u) * 1.5);
  padding: 0 var(--u2);
  border: 1px solid var(--ink);
  font: 650 var(--fs-16)/var(--u) var(--font-sans);
}
form.coach-say > button.ink:hover { background: none; color: var(--ink); }

/* -- Search (progress?query=, conversations?query=) ----------------------- */
form.search { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--u2); }
form.search > label { flex: 1 1 14rem; }

/* The auto-submit form carries no control. */
form.auto { display: none; }

/* ==== 05-errors.css ==== */
/* ============================================================================
   When things go wrong (proposal §06; R-ERR-15..18, R-ERR-23, R-ERR-24,
   R-SURF-57, R-SURF-58).

   One treatment for every failure. An error is set, not boxed: no side bars,
   tints, pills, cards or yellow banners. Red means "this failed and needs
   you", and it never appears alone: a solid square on the baseline, then the
   words, in ink. "Couldn't be read" takes a hollow square. Waiting, stale,
   declined and informational states stay in ink with no mark.
   ============================================================================ */
.fail,
.note-line,
.save-error {
  margin: var(--u2) 0;
  color: var(--ink);
  font: 400 var(--fs-16)/var(--u) var(--font-sans);
}
.fail > b, .note-line > b { font-weight: 650; }

/* The mark: an empty aria-hidden span (ui.fail) or, on the server's
   "Couldn't save" note, a box drawn before the words. */
.mark,
.save-error::before {
  content: "";
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  margin-inline-end: 0.55rem;
  vertical-align: 0.08em;
  background: var(--fail);
}
.fail--unknown .mark { background: none; box-shadow: inset 0 0 0 1.5px currentColor; }
.band .mark, .page-strip .mark { background: var(--fail-on-band); }

/* A failed save goes after its control's form, across the full width, and
   stays there through polls until that control is submitted again. */
.save-error { flex: 1 1 100%; }

/* A plan's failure status: a run-in head, never a pill. */
.status-head {
  margin: 0 0 var(--u2);
  color: var(--ink);
  font: 650 var(--fs-18)/var(--u) var(--font-sans);
}

/* Stale data says how old it is, in secondary ink, on a hairline, at full
   opacity: the region keeps its last values beneath it. */
.stale-caption {
  margin: 0 0 var(--u2);
  padding-bottom: calc(var(--u2) / 2);
  border-bottom: 1px solid var(--rule);
  color: var(--ink-secondary);
  font: 400 var(--fs-16)/var(--u) var(--font-sans);
}

/* The page-wide signed-out line carries the failed mark on the band. */
.page-strip__line--fail::before {
  content: "";
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  margin-inline-end: 0.55rem;
  vertical-align: 0.08em;
  background: var(--fail-on-band);
}

/* -- Error pages: the band carries the heading; the sentence and the one way
   forward sit on the page. */
.error-page__body { max-width: 36em; }
.error-page__body > p:first-child { font-size: var(--fs-18); }
/* A body handed over whole (core error pages before they pass their parts to
   ui.shell.error_page): the heading is set at the page's display size. */
.error-page > h1 {
  margin: var(--u) 0 var(--u2);
  font: 650 var(--fs-56)/calc(var(--u2) * 4) var(--font-display);
  font-variation-settings: "SOFT" 0, "WONK" 0;
  letter-spacing: -0.04em;
}
.error-page > .dek { color: var(--ink-secondary); }
.error-page > p { max-width: 36em; }

@media (forced-colors: active) {
  .mark, .save-error::before, .page-strip__line--fail::before { forced-color-adjust: none; background: CanvasText; }
  .fail--unknown .mark { background: none; box-shadow: inset 0 0 0 1.5px CanvasText; }
}

/* ==== 06-charts.css ==== */
/* ============================================================================
   Charts: the house sparkline and the part-of-whole bar (R-VIS-25, R-VIS-32,
   R-VIS-34, R-VIS-35).
   ============================================================================ */

/* -- The house sparkline (ui.charts.sparkline) ----------------------------- */
.spark { margin: 0 0 var(--u); }
.spark__readout {
  margin: 0 0 calc(var(--u2) / 2);
  font: 400 var(--fs-16)/var(--u) var(--font-sans);
  font-variant-numeric: tabular-nums lining-nums;
}
.spark__label { font-weight: 700; }
.spark__readout .fig__n { font-size: var(--fs-18); font-weight: 650; }
.spark__when, .spark__against { display: inline-block; color: var(--ink-secondary); white-space: nowrap; }
.spark__against b { color: var(--ink); font-weight: 600; }
.spark__svg { display: block; width: 100%; height: auto; overflow: visible; }
.spark__trend { fill: none; stroke: var(--ink); stroke-width: 1.6; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.spark__ref { fill: none; stroke: var(--ink-secondary); stroke-width: 1.2; stroke-dasharray: 2 3; vector-effect: non-scaling-stroke; }
.spark__area--good { fill: var(--good-tint); stroke: none; }
.spark__area--over, .spark__area--under { stroke: none; }
.spark__hatch { stroke-width: 1.6; }
/* Over target is red and hatched (R-VIS-25); under protein takes the caution
   hatch. The verdict is the texture, never the hue alone. */
.spark__hatch--over { stroke: var(--fail); }
.spark__hatch--under { stroke: var(--hatch); }
.spark__dot { fill: var(--ink); }

/* -- The part-of-whole bar (ui.charts.part_bar) ----------------------------
   Solid = eaten or held, hatched = planned (dense = the darker hatch),
   outlined = missing, next = outlined with a 3px inset rule. Each segment
   starts on an ink division; the full measure is a hairline, so room under
   the target reads as empty space. The target is a blue tick, a half-line
   above the bar; past it the bar is red and hatched. */
.bar { position: relative; isolation: isolate; margin: var(--u2) 0 0; container-type: inline-size; }
.bar__track { position: relative; height: var(--u); }
.bar__track::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset: auto 0 0 0;
  border-bottom: 1px solid var(--ink-secondary);
}
.bar__seg {
  position: absolute;
  inset-block: 0;
  left: min(var(--at, 0%), calc(100% - 1px));
  width: max(1px, var(--w));
  /* A proportional inset leaves even a tiny nonzero segment visible. */
  clip-path: inset(0 min(2px, 10%) 0 0);
  border-left: 1.5px solid var(--ink);
  background: repeating-linear-gradient(-45deg, var(--hatch) 0 2px, transparent 2px 9px);
}
.bar__seg--solid { background: var(--ink); }
.bar__seg--dense { background: repeating-linear-gradient(-45deg, var(--ink) 0 4px, transparent 4px 8px); }
.bar__seg--outlined { background: none; box-shadow: inset 0 0 0 1.5px var(--ink); }
.bar__seg--next { box-shadow: inset 0 0 0 3px var(--ink); }
.bar__target {
  position: absolute;
  z-index: 1;
  top: calc(-1 * var(--u2) / 2);
  left: clamp(0px, calc(var(--at) - 1.5px), calc(100% - 3px));
  width: 3px;
  height: calc(var(--u) + var(--u2));
  background: var(--accent);
}
.bar__over {
  position: absolute;
  inset: 0 0 0 var(--at);
  background-color: var(--paper);
  background-image: repeating-linear-gradient(45deg, var(--fail) 0 2px, transparent 2px 6px);
  box-shadow: inset 0 0 0 1px var(--fail);
}
.bar--empty .bar__track { border: 1px solid var(--ink-secondary); background: var(--good-tint); }
/* Labels keep their names; neighbours never touch. */
.bar__labels { display: flex; gap: 0; margin-top: calc(var(--u2) / 2); }
.bar__labels > span {
  flex: 0 0 var(--w);
  min-width: 0;
  overflow-wrap: anywhere;
  padding-right: var(--u2);
  color: var(--ink);
  font: 500 var(--fs-16)/1.3 var(--font-sans);
  font-variant-numeric: proportional-nums lining-nums;
}
.bar__labels b { display: block; padding-right: 4px; font-weight: 650; }
/* Small segments (or a narrow column) turn the labels into a hairline ledger
   under the bar: names never truncate or collide. */
.bar--ledger .bar__labels { display: block; margin-top: var(--u2); }
.bar--ledger .bar__labels > span {
  display: flex;
  justify-content: space-between;
  gap: var(--u2);
  align-items: baseline;
  padding: calc(var(--u2) / 2) 0;
  border-top: 1px solid var(--rule);
  font-variant-numeric: tabular-nums lining-nums;
}
.bar--ledger .bar__labels b { display: inline; padding: 0; }
@container (max-width: 30rem) {
  .bar__labels { display: block; margin-top: var(--u2); }
  .bar__labels > span {
    display: flex;
    justify-content: space-between;
    gap: var(--u2);
    align-items: baseline;
    padding: calc(var(--u2) / 2) 0;
    border-top: 1px solid var(--rule);
    font-variant-numeric: tabular-nums lining-nums;
  }
  .bar__labels b { display: inline; padding: 0; }
}

.bar__labels > span > span { min-width: 0; }
.bar--ledger .bar__labels > span > span { flex: 0 0 auto; max-width: 50%; text-align: right; }
@container (max-width: 30rem) {
  .bar__labels > span > span { flex: 0 0 auto; max-width: 50%; text-align: right; }
}

/* -- Forced colours: draw the charts in system colours. ------------------- */
@media (forced-colors: active) {
  .bar__seg, .bar__target, .bar__over { forced-color-adjust: none; }
  .bar__seg { border-left-color: CanvasText; background: repeating-linear-gradient(-45deg, CanvasText 0 2px, transparent 2px 9px); }
  .bar__seg--solid { background: CanvasText; }
  .bar__seg--dense { background: repeating-linear-gradient(-45deg, CanvasText 0 4px, transparent 4px 8px); }
  .bar__seg--outlined { background: none; box-shadow: inset 0 0 0 1.5px CanvasText; }
  .bar__seg--next { box-shadow: inset 0 0 0 3px CanvasText; }
  .bar__target { background: Highlight; }
  .bar__over { background: repeating-linear-gradient(45deg, GrayText 0 1px, transparent 1px 4px); }
  .spark__trend, .spark__hatch--over, .spark__hatch--under { stroke: CanvasText; }
  .spark__ref { stroke: GrayText; }
  .spark__dot { fill: CanvasText; }
  .spark__area--good { fill: none; }
}

/* -- Print: fine hatching rasterises unevenly, so planned segments print as a
   flat light tone; solid stays solid, outlined stays outlined. */
@media print {
  .bar, .spark { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .bar__seg:not(.bar__seg--solid):not(.bar__seg--outlined) { background: #d3d7da; }
}

/* ==== 07-motion.css ==== */
/* ============================================================================
   Motion: flip, set and fade (R-MOT-1..12). Only what changed moves, like a
   split-flap board; nothing travels across the page. Motion is driven by the
   difference between the old and new server renderings (02-flap.js,
   04-record-motion.js), never by who made the change.
   ============================================================================ */

/* Split-flap (02-flap.js): the old top half folds down over a hinge, then the
   new bottom half falls into place. Flat ink on its own ground; the hinge is a
   hairline. */
.flap { font-variant-numeric: tabular-nums lining-nums; }
.flap__cell { display: inline-block; }
.flap__box { position: relative; display: inline-block; perspective: 300px; vertical-align: top; }
.flap--cell > .flap__box { display: block; }
.flap__sizer { display: block; visibility: hidden; }
.flap__face:not(.flap__sizer) { position: absolute; inset: 0; background: var(--flap-ground); }
.flap__top { clip-path: inset(0 0 50% 0); transform-origin: 50% 50%; }
.flap__bottom { clip-path: inset(50% 0 0 0); transform-origin: 50% 50%; }
.flap__box::after {
  content: "";
  position: absolute;
  z-index: 1;
  left: 0;
  right: 0;
  top: calc(50% - 0.5px);
  border-top: 1px solid var(--flap-ground);
}

/* A replated cover line is set word by word on the beat. */
.cover-set {
  display: inline-block;
  white-space: pre;
  animation: set-line var(--dur-move) var(--ease-enter) both;
  animation-delay: calc(var(--i) * var(--cadence));
}
/* Replaced content is set fresh, line by line, one line per beat, after the
   old line clears. */
.is-set {
  animation: set-line var(--dur-move) var(--ease-enter) both;
  animation-delay: calc(var(--dur-fade) + var(--i, 0) * var(--cadence));
}
/* Departing rows are held through a fade, never collapsed to auto height. */
.is-leaving { animation: fade-out var(--dur-fade) var(--ease-exit) forwards; }
/* A bar segment whose state changed fills in place from the left. */
.bar__seg.is-changing { position: relative; }
.bar__seg--solid.is-changing { background: none; }
.bar__seg--solid.is-changing::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ink);
  transform-origin: left;
  animation: seg-fill var(--dur-move) var(--ease-enter) both;
}
button:active { transform: translateY(1px); transition: transform var(--dur-tap) var(--ease-standard); }

@keyframes set-line { from { opacity: 0; transform: translateY(0.5em); } }
@keyframes seg-fill { from { transform: scaleX(0); } }
@keyframes fade-in { from { opacity: 0; } }
@keyframes fade-out { to { opacity: 0; } }

/* Reduced motion: nothing changes position. Flips are instant value changes
   (the scripts check matchMedia themselves); set lines become 150ms fades. */
@media (prefers-reduced-motion: reduce) {
  .cover-set, .is-set { animation: fade-in var(--dur-fade) linear both; animation-delay: 0s; }
  .bar__seg--solid.is-changing::before { animation: fade-in var(--dur-fade) linear both; }
  button:active { transform: none; }
}

/* ==== 10-auth.css ==== */
/* ============================================================================
   The front door, the roster, the invite confirmation and the two Settings
   sections auth owns (R-AUTH-12, R-AUTH-13, R-AUTH-29..53, R-PAGE-57).

   All of it is set on the shared system: side heads, ruled lists, ruled
   inputs, ink buttons and word-actions, the house sparkline. Nothing here is a
   box, card, pill or shadow. A state that needs the trainer (gone dark, a
   decline, a failed plan) is set bold beside its glyph, never in colour alone;
   waiting states are plain ink with their glyph (R-AUTH-49).
   ============================================================================ */

/* -- The front door (R-AUTH-13) -------------------------------------------- */
.front { max-width: 44rem; padding: var(--u) 0 calc(var(--u) * 2); }
.front-lead { margin: 0 0 var(--u); font: 400 var(--fs-24)/calc(var(--u) * 1.25) var(--font-serif); }
.front-notice { margin: 0 0 var(--u2); color: var(--ink); font: 700 var(--fs-18)/var(--u) var(--font-sans); }
.front-steps {
  margin: calc(var(--u) * 1.5) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
  border-top: var(--rule-heavy) solid var(--ink);
}
.front-step {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr);
  column-gap: var(--u2);
  padding: var(--u2) 0;
  counter-increment: step;
}
.front-step + .front-step { border-top: 1px solid var(--rule); }
.front-step::before {
  content: counter(step);
  grid-row: span 2;
  font: 650 var(--fs-32)/1 var(--font-display);
}
.front-step h3 { margin: 0; font: 700 var(--fs-18)/var(--u) var(--font-sans); }
.front-step p { margin: 0; color: var(--ink-muted); font: 400 var(--fs-18)/var(--u) var(--font-sans); }
.front-footer {
  margin: calc(var(--u) * 1.5) 0 0;
  padding-top: var(--u2);
  border-top: 1px solid var(--ink);
  color: var(--ink-secondary);
  font: 400 var(--fs-16)/var(--u) var(--font-sans);
}
.front-footer__agents { display: block; }

/* -- The sign-out word in the folio line ---------------------------------- */
.folio-line form.sign-out > button.ink { color: var(--band-muted); }
.folio-line form.sign-out > button.ink:hover { color: var(--band-ink); }

/* -- The roster (R-AUTH-47..52) ------------------------------------------- */
.roster-own {
  margin: var(--u) 0 0 calc(var(--side) + var(--side-gap));
  font: 600 var(--fs-18)/var(--u) var(--font-sans);
}
.roster-list > li.roster-row { display: block; padding: var(--u2) 0; }
.roster-row__main {
  display: grid;
  grid-template-columns: minmax(10rem, 1fr) minmax(0, 2fr) minmax(9rem, auto);
  align-items: start;
  gap: 0 var(--u);
}
.roster-row__main > .roster-row__name { grid-column: 1; }
.roster-row__main > .roster-sparklines { grid-column: 2; }
.roster-row__main > .roster-facts { grid-column: 3; }
.roster-row__name { margin: 0; font: 650 var(--fs-24)/calc(var(--u) * 1.25) var(--font-display); }
.roster-row__name a { color: var(--ink); text-decoration: none; }
.roster-row__name a:hover { text-decoration: underline; text-decoration-thickness: 1px; }
.roster-sparklines { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 0 var(--u); }
.roster-sparklines .spark { margin: 0; }
.roster-facts { margin: 0; }
.roster-fact { display: grid; grid-template-columns: 7rem minmax(0, 1fr); gap: 0 var(--u2); }
.roster-fact > dt {
  color: var(--ink);
  font: 700 var(--fs-14)/var(--u) var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.roster-fact > dd { margin: 0; font: 400 var(--fs-18)/var(--u) var(--font-sans); }
.roster-fact > dd > form.word-action { margin-left: var(--u2); }
.invite-actions > form.word-action { margin-left: 0; }
/* At a glance the fact is a label over its value, set at the row's end. */
.roster-row__main .roster-fact { grid-template-columns: minmax(0, 1fr); white-space: nowrap; }
.roster-state { color: var(--ink); }
.roster-state--caution { font-weight: 700; }

/* The Details disclosure: a word that opens, not a box. */
.roster-row__detail { margin-top: calc(var(--u2) / 2); }
.roster-row__detail > summary,
details.confirm > summary {
  display: inline-block;
  color: var(--ink-secondary);
  font: 600 var(--fs-16)/var(--u) var(--font-sans);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  cursor: pointer;
  list-style: none;
}
.roster-row__detail > summary::-webkit-details-marker,
details.confirm > summary::-webkit-details-marker { display: none; }
.roster-row__detail[open] > summary { color: var(--ink); }
.roster-row__detail > .roster-facts { margin-top: var(--u2); }
.roster-row__detail > .roster-facts > .roster-fact + .roster-fact { border-top: 1px solid var(--rule); }
.roster-notes { margin: var(--u2) 0 0; color: var(--ink); font: 400 var(--fs-18)/var(--u) var(--font-sans); }
.roster-notes > .label { display: inline; margin-right: var(--u2); }

/* Invite actions (R-AUTH-38): the mail and copy controls on one line, the link
   printed in full beneath, breaking anywhere rather than overflowing. */
.invite-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--u2); margin: var(--u2) 0; }
.ink-button--quiet {
  min-height: calc(var(--u) * 1.5);
  padding: 0 var(--u2);
  border: 1px solid var(--ink);
  background: none;
  color: var(--ink);
  font: 650 var(--fs-16)/var(--u) var(--font-sans);
}
.ink-button--quiet:hover { background: var(--ink); color: var(--paper); }
.copy-status { color: var(--ink-secondary); font: 400 var(--fs-16)/var(--u) var(--font-sans); }
.invite-link { margin: 0; overflow-wrap: anywhere; }
.invite-link > .label { display: inline; margin-right: calc(var(--u2) / 2); }
.invite-link__url { font: 400 var(--fs-16)/var(--u) var(--font-mono); }

/* The goals and details editor (R-AUTH-51): two plain forms, one Save each. */
.roster-editor { margin-top: var(--u2); padding-top: var(--u2); border-top: 1px solid var(--ink); }
.roster-goals__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0 var(--u);
}
.meal-structure { margin-top: var(--u2); }
.meal-structure .meal-row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(5rem, 1fr) minmax(8rem, 1.2fr) minmax(7rem, 1fr);
  gap: 0 var(--u2);
}
@media (max-width: 48rem) { .meal-structure .meal-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.ruled-input--area textarea { min-height: calc(var(--u) * 3); }

/* -- Confirm steps (remove a trainer, revoke access) ---------------------- */
details.confirm { flex: 0 0 auto; }
details.confirm[open] { flex: 1 1 100%; }
details.confirm > p { max-width: 36em; margin: calc(var(--u2) / 2) 0; color: var(--ink); }
details.confirm[open] > form.word-action > button.ink {
  min-height: calc(var(--u) * 1.5);
  padding: 0 var(--u2);
  border: 1px solid var(--fail);
  color: var(--fail);
  text-decoration: none;
}

/* -- Settings: your trainers, AI assistant access (R-AUTH-32, R-AUTH-35) -- */
.trainer__email, .token__when { color: var(--ink-secondary); font-size: var(--fs-16); }
.trainer__name, .token__name { font-weight: 600; }

/* -- The invite confirmation (R-AUTH-40..42) ------------------------------ */
.join-confirm { max-width: 40rem; padding: var(--u) 0 calc(var(--u) * 2); }
.join-confirm__lead { margin: 0 0 var(--u2); overflow-wrap: anywhere; font: 400 var(--fs-24)/calc(var(--u) * 1.25) var(--font-serif); }
.join-confirm__warning { margin: 0 0 var(--u); font: 700 var(--fs-18)/var(--u) var(--font-sans); }
.join-confirm__answers { display: flex; flex-wrap: wrap; gap: var(--u2); }
.join-confirm__answers > form.write { flex: 0 1 auto; }
form.ink-quiet > button.ink { border-color: var(--ink); background: none; color: var(--ink); }

/* -- Narrow measures ------------------------------------------------------ */
@media (max-width: 48rem) {
  .roster-own { margin-left: 0; }
  .roster-row__main { grid-template-columns: minmax(0, 1fr); }
  .roster-row__main > .roster-row__name,
  .roster-row__main > .roster-sparklines,
  .roster-row__main > .roster-facts { grid-column: 1; }
  .roster-fact { grid-template-columns: minmax(0, 1fr); }
  .roster-row__main .roster-fact { grid-template-columns: auto minmax(0, 1fr); }
}

@media print {
  .invite-actions, .roster-editor, details.confirm, .folio-line form.sign-out { display: none; }
  .roster-row__detail > summary { display: none; }
}

/* ==== 20-kitchen.css ==== */
/* ============================================================================
   The kitchen: "Cooking today" and one inventory ledger (kitchen;
   R-PAGE-45..51, R-PIC-19). Ported from docs/folio-claude/folio.css ("The
   kitchen: one ledger", item icons, the phone and narrowest blocks) by wave
   0b; owned by kitchen from wave 1.

   Port notes: the page body's top padding is the band's own margin
   (02-shell.css), so .front-body is not carried. Category heads are
   ui.section-style caps labels in the margin column.
   ============================================================================ */
#pantry-panel:empty { display: none; }

/* -- "Cooking today": a second column only when each holds 16rem; rows close
   with a hairline, so no column opens on a doubled rule. */
.today-ingredients { columns: 2 16rem; column-gap: var(--u); }
.today-ingredients-row {
  display: grid;
  grid-template-columns: 5.25rem minmax(0, 1fr);
  column-gap: var(--u2);
  align-items: baseline;
  padding: calc(var(--u2) / 2) 0;
  border-bottom: 1px solid var(--rule);
  break-inside: avoid;
  font-size: var(--fs-18);
}
.today-ingredients-row__amount { text-align: right; font-weight: 650; white-space: nowrap; }
.today-ingredients-row__amount .unit { color: var(--ink-muted); font-weight: 500; }
/* The same hanging indent as the recipe's list. */
.today-ingredients-row__name {
  position: relative;
  display: block;
  padding-left: calc(1.75rem + var(--u2) / 2);
  overflow-wrap: anywhere;
  hyphens: auto;
}
.today-ingredients-row__name .item-icon { position: absolute; left: 0; top: 0.15em; margin: 0; }

/* -- The inventory: it opens with the heavy rule and its head. */
.ledger-summary {
  margin: 0;
  padding-block: var(--u2) var(--u);
  border-top: var(--rule-heavy) solid var(--ink);
  font: 650 var(--fs-24)/1.15 var(--font-display);
  font-variation-settings: "SOFT" 0, "WONK" 0;
  letter-spacing: -0.02em;
  font-variant-numeric: lining-nums;
}
/* Each category hangs in the margin beside its first item, a caps label that
   recedes so the foods lead. */
.pantry-group {
  display: grid;
  grid-template-columns: var(--side) minmax(0, 1fr);
  column-gap: var(--side-gap);
  align-items: first baseline;
  margin: 0;
  border-top: 1px solid var(--ink);
}
.pantry-group-heading {
  grid-column: 1;
  margin: 0;
  padding-block: var(--u2);
  color: var(--ink-secondary);
  font: 600 var(--fs-14)/1.3 var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-wrap: balance;
}
.pantry-item { grid-column: 2; border-top: 1px solid var(--rule); }
.pantry-group-heading + .pantry-item { border-top: 0; }
/* The row itself is the toggle: an underline on hover, a silent marker when
   closed. Icons take a column of their own so names keep one left edge. */
.pantry-item__summary {
  position: relative;
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr) auto;
  column-gap: var(--u2);
  row-gap: 0.2rem;
  padding: var(--u2) 0;
  list-style: none;
  overflow-wrap: anywhere;
  hyphens: auto;
  cursor: pointer;
}
.pantry-item__summary::-webkit-details-marker { display: none; }
.pantry-item__summary:hover .pantry-item__name { text-decoration: underline; text-underline-offset: 0.2em; text-decoration-thickness: 1px; }
.pantry-item__name { position: relative; grid-column: 2; grid-row: 1; font: 700 var(--fs-18)/var(--u) var(--font-sans); }
.pantry-item__name::before { content: "\25B8" / ""; position: absolute; margin-left: -0.8em; color: var(--ink-secondary); }
.pantry-item[open] > .pantry-item__summary .pantry-item__name::before { display: none; }
.pantry-item__name .item-icon { position: absolute; left: calc(-3.5rem - var(--u2)); width: 3.5rem; height: 3.5rem; }
.pantry-item__qty {
  grid-column: 3;
  grid-row: 1;
  text-align: right;
  font: 650 var(--fs-18)/var(--u) var(--font-sans);
  font-variant-numeric: tabular-nums;
}
/* Macros on one 16px line with their basis; figures in ink, units grey;
   missing data shows as missing, never zeros. */
.pantry-macros {
  grid-column: 2 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--u2);
  color: var(--ink-secondary);
  font-size: var(--fs-16);
  line-height: 1.4;
}
.pantry-macros b { color: var(--ink); font-weight: 600; }
.pantry-macros > :first-child { color: var(--ink); }
/* Direct item resources retain their transport forms; the pantry disclosure has none. */
.pantry-item__controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--u2);
  min-width: 0;
}
.pantry-item__controls > form:last-child { grid-area: 1 / 2; }

/* -- An open card: Nutrition Facts, then the measurement explanation. */
.pantry-item__body {
  display: grid;
  gap: var(--u2);
  padding: 0 0 var(--u);
  margin-left: calc(3.5rem + var(--u2));
}
.pantry-facts { margin: 0; color: var(--ink-secondary); font-size: var(--fs-16); line-height: 1.4; }
/* Stock, unit and cap share one line where the measure allows; Save closes it. */
form.pantry-edit > label { flex: 1 1 9rem; }
form.pantry-edit > button { align-self: flex-end; }
/* The photo attach sits under the edit line: the file picker, then Attach. */
.pantry-item__controls > form.pantry-photo { grid-column: 1; }
form.pantry-photo > label { flex: 1 1 14rem; }
form.pantry-photo input[type="file"] { max-width: 100%; }
form.pantry-photo > button { align-self: flex-end; }

/* -- The Nutrition Facts insert: the one deliberate box, a quoted printed
   label (proposal §08), drawn with the system's own sizes, inks and three
   rule weights, so it follows the scheme like the rest of the folio. */
.nutrition-card {
  max-width: 22rem;
  padding: calc(var(--u2) / 2) var(--u2) var(--u2);
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
}
.nutrition-card__title {
  margin: 0 0 calc(var(--u2) / 2);
  padding-bottom: calc(var(--u2) / 2);
  border-bottom: var(--rule-heavy) solid var(--ink);
  font-size: var(--fs-32);
  font-weight: 900;
  line-height: 1.05;
}
.nutrition-card__basis { margin: 0; padding-bottom: calc(var(--u2) / 2); border-bottom: 1px solid var(--ink); font-size: var(--fs-16); }
.nutrition-card__calories {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  padding-block: calc(var(--u2) / 2);
  border-bottom: var(--rule-heavy) solid var(--ink);
}
.nutrition-card__calories-label { font-size: var(--fs-18); font-weight: 800; }
.nutrition-card__calories-value { font-size: var(--fs-32); font-weight: 900; }
.nutrition-card__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  padding-block: calc(var(--u2) / 2);
  border-bottom: 1px solid var(--ink);
}
.nutrition-card__row--protein { border-bottom: var(--rule-mid) solid var(--ink); }
.nutrition-card__row-label { font-weight: 700; }
.nutrition-card__value { text-align: right; }
.nutrition-card__range { display: block; font-size: var(--fs-16); font-weight: 400; color: var(--ink-secondary); }
.nutrition-card__footnote { margin: calc(var(--u2) / 2) 0 0; color: var(--ink-secondary); font: 400 var(--fs-16)/1.4 var(--font-sans); }

/* -- "Recently removed": name, amount, Undo on one ruled line. */
.removed-recap li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--u2); }
.removed-recap__name { font-weight: 700; }
.removed-recap__qty { color: var(--ink-secondary); }

/* -- One food's page: stock and macros lead, the icon beside them. */
.pantry-item__lead { display: flex; align-items: center; gap: var(--u2); margin: 0; }
.pantry-item__lead .item-icon { width: 3.5rem; height: 3.5rem; }

/* -- Item icons: two cuts of one painting. The light scheme (the friendly
   living cookbook) shows the whole painting; the dark scheme (the regular who
   wants less on the page) shows the main item alone. Both are in the HTML;
   the stylesheet picks (R-PIC-19, R-VIS-28). */
.item-icon { width: var(--u); height: var(--u); vertical-align: bottom; object-fit: contain; }
.icon--plain { display: none; }
@media (prefers-color-scheme: dark) {
  .icon--full { display: none; }
  .icon--plain { display: revert; }
}

@media (min-width: 48.01rem) { .today-ingredients { border-top: 0; } }
@media (max-width: 48rem) {
  .pantry-group { display: block; }
  .pantry-group-heading { padding-bottom: 0; }
}
/* Narrowest: the quantity goes under the name; the icon shrinks to 2.5rem. */
@media (max-width: 22.5rem) {
  .pantry-item__body { margin-left: 0; }
  .pantry-item__summary { grid-template-columns: 2.5rem minmax(0, 1fr); }
  .pantry-item__name .item-icon { width: 2.5rem; height: 2.5rem; left: calc(-2.5rem - var(--u2)); }
  .pantry-item__qty { grid-column: 2; grid-row: 2; text-align: left; }
  .pantry-macros { grid-row: 3; }
  .today-ingredients-row { grid-template-columns: minmax(0, 1fr); }
  .today-ingredients-row > * { grid-column: 1; grid-row: auto; }
  .today-ingredients-row__amount { text-align: left; }
}

/* The inventory has one action; each disclosure is a read-only food label. */
form.pantry-delete { display: block; }
form.pantry-delete > button { margin: 0 0 var(--u2); }
.pantry-select { position: absolute; right: 0; top: var(--u2); margin: 0;
  width: 1.2rem; height: 1.2rem; accent-color: var(--ink); cursor: pointer; }
.pantry-item__summary--selectable { padding-right: 2rem; }
.pantry-measurement { max-width: 34em; margin: var(--u2) 0 0; color: var(--ink-secondary);
  font: 400 var(--fs-16)/1.5 var(--font-sans); }

@media print {
  .pantry-select { display: none !important; }
  .pantry-item__summary--selectable { padding-right: 0; }
}

/* ==== 30-settings.css ==== */
/* ============================================================================
   Settings: forms on the same system (goals; R-PAGE-52..55, R-GOAL-*).
   Ported from docs/folio-claude/folio.css ("Settings: forms on the same
   system", the phone and narrowest blocks, the form-value error) by wave 0b;
   owned by goals from wave 1.

   Port notes: ruled inputs, textareas, selects with their chevron, ink Save
   buttons, field-group heads and word-actions are shared (03-section.css,
   04-forms.css) and not repeated here. The macro split is ui.charts.part_bar
   (protein solid, carbs dense, fat hatched). The long field label that spans
   both target columns carries .span-2 from the server (no :has()).
   ============================================================================ */

/* -- Targets: a compact two-by-two grid of right-aligned figures. */
.goal-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 12rem)); gap: var(--u2) var(--u); }
.goal-fields input { text-align: right; }
.goal-fields > label { position: relative; padding-bottom: var(--u); }
.goal-fields .span-2 { grid-column: span 2; }
.goals-feedback { font-size: var(--fs-16); }
.goal-actions { margin-top: var(--u2); }
/* Nothing forces the page wider than a 150%-zoomed phone (about 250px). */
.goals-fields { min-width: 0; }
/* "Auto-adjusted" tags (R-GOAL-18): a single tracked caps word. */
.field-adjusted-tag { position: absolute; bottom: 0; left: 0; font-size: var(--fs-14); text-transform: uppercase;
  letter-spacing: 0.08em; }

/* -- Meal structure is a table: its column labels print once, on the first
   row. Each meal is two lines: name, share, its calories and Remove; then the
   day it belongs to and its usual time. Remove and "Add a meal" are
   underlined words (shown by 30-goals.js; without JS a blank row is ignored). */
.meal-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 6.5rem 6rem auto;
  grid-template-areas: "name share cal remove" "schedule schedule schedule schedule";
  gap: 0 var(--u2);
  align-items: end;
}
.meal-row + .meal-row { margin-top: calc(var(--u2) / 2); }
.meal-row__name { grid-area: name; }
.meal-row__share { grid-area: share; }
.meal-row__cal { grid-area: cal; }
.meal-row__remove { grid-area: remove; }
.meal-row__day { grid-area: day; }
.meal-row__at { grid-area: at; max-width: 10rem; }
.meal-row__label { display: block; white-space: nowrap; color: var(--ink); font-size: var(--fs-16); }
.meal-row + .meal-row > label .meal-row__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}
.meal-row__day .meal-row__label, .meal-row__at .meal-row__label { color: var(--ink-secondary); }
.meal-row__schedule { grid-area: schedule; color: var(--ink-secondary); font-size: var(--fs-16); }
.meal-row__schedule > summary, .meal-options > summary { cursor: pointer; }
.meal-row__timing { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--u2); padding-top: var(--u2); }
.meal-row__timing > label { grid-area: auto; }
.meal-options { margin-top: var(--u2); font-size: var(--fs-16); }
.meal-options > .goal-fields { margin-top: var(--u2); }
.meal-row__share-input { display: flex; align-items: baseline; gap: 0.25rem; }
.meal-row__share-input input { min-width: 0; text-align: right; }
.meal-row__pct { color: var(--ink-secondary); }
/* Calories and Remove centre on the input line: both exactly its height. */
.meal-row__cal, .meal-row__remove {
  display: inline-flex;
  align-items: center;
  height: 3rem;
  font-size: var(--fs-16);
  white-space: nowrap;
}
.meal-structure__controls {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--u2);
  margin: var(--u2) 0;
}
.meal-total { margin: 0 0 0 auto; font-size: var(--fs-18); }

/* An underlined word that acts in the page (Remove a row, Add a meal, Start at):
   plain text, no box (R-PAGE-55). */
.word-button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-16);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  cursor: pointer;
}
.word-button:hover { text-decoration-thickness: 2px; }

/* The goals form spans two sections (targets, then Meal structure) with one
   Save under the table; the first continues "Your goals" above it. */
.side--cont { border-top: 0; padding-top: 0; }
.settings-block > .field-head:first-child { margin-top: 0; }
form.goals-form > button.ink { margin-inline-start: calc(var(--side) + var(--side-gap)); margin-bottom: var(--u); }
.goal-text input { text-align: left; }
.macro-split { margin: var(--u) 0; }
.macro-split__total { margin: 0 0 calc(var(--u2) / 2); font-size: var(--fs-24); font-weight: 650; }
.goal-start { min-height: calc(var(--u) * 2); }
.goal-start > .lead { margin-bottom: calc(var(--u2) / 2); }
.checkbox-line { display: flex; gap: 0.5rem; align-items: baseline; margin: var(--u2) 0 0; font-size: var(--fs-16); }
/* The 30/40/30 proposal's line keeps its space while off, so nothing jumps when it appears (Q-GOAL-5). */
.goal-proposal[data-off] { visibility: hidden; }
/* Days with no meals: seven checkboxes on as few lines as fit. */
.meal-days-off { border: 0; margin: var(--u2) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0 var(--u2); }
.meal-days-off > legend { padding: 0; margin-bottom: calc(var(--u2) / 2); }
.meal-days-off > .checkbox-line { margin: 0; }
.weight-proj { margin: 0 0 var(--u2); }
.weight-proj svg { display: block; width: 100%; max-width: 24.5rem; height: 6rem; color: var(--ink); }
.weight-proj figcaption { font-size: var(--fs-16); color: var(--ink-secondary); }
.weight-proj__ends { color: var(--ink); }
.recipe-preference { padding-bottom: var(--u); }
.recipe-preference > form.word-action { margin-top: var(--u2); }
form.about-form { margin-bottom: var(--u2); }

/* A meal-structure value that can't be used: the reason under the field,
   marked (the server writes it; R-ERR-15). */
.meal-structure-error { font-size: var(--fs-16); line-height: var(--u); color: var(--ink); }
.meal-structure-error::before {
  content: "";
  display: inline-block;
  width: 0.55rem;
  height: 0.55rem;
  margin-inline-end: 0.55rem;
  vertical-align: 0.08em;
  background: var(--fail);
}

/* -- Trainers and agent tokens: rows ruled in the three weights. */
.token-list { border-top: 1px solid var(--ink); }
.weight-proj--empty { padding: 0; }
.weight-proj--empty > p { margin-top: 0; }

/* At 1025-1280px beside the rail, and on phones, calories and Remove take a
   row of their own so they never collide under text spacing (R-PAGE-54). */
@media (min-width: 64.01rem) and (max-width: 80rem), (max-width: 48rem) {
  .meal-row {
    grid-template-columns: minmax(0, 1fr) min(8rem, 46%);
    grid-template-areas: "name share" "cal remove" "schedule schedule";
  }
  .meal-row__remove { justify-self: end; }
  .meal-row__at { max-width: none; }
}
@media (max-width: 48rem) {
  form.goals-form > button.ink { margin-inline-start: 0; }
  .meal-row__label { white-space: normal; }
}
@media (max-width: 22.5rem) {
  .goal-fields { grid-template-columns: minmax(0, 1fr); }
  .goal-fields .span-2 { grid-column: auto; }
}

/* Personal details have their own section; the embedded resource's field head
   is redundant with that section's heading. */
#about-you .settings-block > .field-head:first-child { display: none; }
.goal-restore { display: block; margin: 0 0 var(--u2); text-align: left; }
.equipment-list { margin: var(--u2) 0 var(--u); }
.equipment-list > summary { cursor: pointer; font-size: var(--fs-18); font-weight: 650; }
.equipment-list[open] > summary { margin-bottom: var(--u2); }
.equipment-list .ruled { margin: 0; }

/* ==== 35-history.css ==== */
/* ============================================================================
   History: the diary list, the 30-day trend and search (diary; R-LOG-23,
   R-LOG-24, R-PAGE-57). Trend visuals are the house sparkline
   (ui.charts.sparkline, 06-charts.css) and search is app.core.html.search_form
   (04-forms.css); this partial holds only what the history page adds. The
   proposal redrew no history page, so it inherits the band, side heads, scale
   and rules, with no sizes of its own.
   ============================================================================ */
.history-trend > .spark:last-child { margin-bottom: 0; }

.history-status { margin: var(--u2) 0 0; color: var(--ink-secondary); }

/* Days, newest first: a bold date head, then that day's meals as a ruled
   list. The meal slot sits in a column of its own so dishes align. */
.history-days { margin: var(--u) 0 0; padding: 0; list-style: none; }
.history-day + .history-day { margin-top: var(--u); }
.history-day__date {
  margin: 0;
  padding-bottom: calc(var(--u2) / 2);
  border-bottom: 1px solid var(--ink);
  color: var(--ink);
  font: 700 var(--fs-16)/var(--u) var(--font-sans);
}
.history-day__when { color: var(--ink-secondary); font-weight: 400; }

.history-meals > .history-meal { flex-wrap: nowrap; }
.history-meal__what {
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  column-gap: var(--u2);
  align-items: first baseline;
  flex: 1 1 auto;
  min-width: 0;
}
.history-meal__slot { color: var(--ink-secondary); overflow-wrap: anywhere; }
.history-meal__dish { grid-column: 2; color: var(--ink); }
.history-meal__dish--blank { color: var(--ink-secondary); }
.history-meal__rating {
  grid-column: 2;
  color: var(--ink-secondary);
  font: 700 var(--fs-14)/var(--u) var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.history-meal__figs {
  flex: 0 0 auto;
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums lining-nums;
}

.history-meal__fig { white-space: nowrap; }   /* a figure breaks only between clauses */

.history-more { display: flex; flex-wrap: wrap; gap: 0 var(--u); margin: var(--u) 0 0; }

/* Phone: the slot column narrows and the figures drop under the dish. */
@media (max-width: 48rem) {
  .history-meals > .history-meal { flex-wrap: wrap; }
  .history-meal__what { grid-template-columns: 5.5rem minmax(0, 1fr); flex-basis: 100%; }
  .history-meal__figs {
    flex: 1 1 100%; min-width: 0; box-sizing: border-box;
    padding-left: calc(5.5rem + var(--u2)); text-align: left; white-space: normal;
  }
}

/* Leave the slot's hanging indent when it would leave less than a figure's measure. */
@media (max-width: 22.5rem) {
  .history-meal__figs { padding-left: 0; }
}

@media print {
  .history-day { break-inside: avoid; }
  #history-diary form.search, .history-more { display: none; }
}

/* ==== 40-recipe.css ==== */
/* ============================================================================
   The recipe spread and Record (plan; R-PAGE-9..15, R-PAGE-42, R-PAGE-56).
   Ported from docs/folio-claude/folio.css ("The recipe spread", "The
   recording ballot", the phone and narrowest blocks, print) by wave 0b;
   owned by plan from wave 1, which writes the markup these class names
   describe (plan.api.spread, plan.api.ballot, the recipe page).

   Port notes: the proposal styled two legacy containers (the workspace's
   "#cook .briefing__body" and the recipe page's
   ".recipe-body:has(#recipe-ingredients)"); both are the one ".spread" here,
   with no :has(). "Healthy is colourless" is the template's job: it emits no
   per-row status when everything is in stock (the :has() fake is gone). The
   ballot's phone rules duplicated its container query and are dropped: the
   container query alone stacks it.
   ============================================================================ */

/* -- The spread: ingredients in a narrow column, method beside them on the
   same baseline, on the timetable's tracks (ingredients over the margin and
   the first slot column, method from the second to the edge). Always four
   tracks, so a two-slot week never squeezes the method. */
.spread {
  --spread: 4;
  display: grid;
  grid-template-columns: calc(var(--side) + var(--side-gap)) repeat(var(--spread), minmax(0, 1fr));
  column-gap: 0;
  /* The band already supplies one baseline of separation. */
  padding-top: var(--u2);
}
.spread__col { display: block; padding: 0; border-top: var(--rule-mid) solid var(--ink); }
.spread__col > .label {
  padding-top: var(--u2);
  margin-bottom: 0;
  line-height: 1.3;
}
.spread__ingredients { grid-column: 1 / 3; margin-right: var(--u2); }
.spread__method { grid-column: 3 / -1; }
/* Package names need more of the measure than the proposal's short food names. Keep both columns readable. */
.spread--long { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.spread--long .spread__ingredients { grid-column: 1; }
.spread--long .spread__method { grid-column: 2; }
/* Side by side at 1025-1280px, with the rail open, the columns stack so
   neither is squeezed into splitting words. */
@media (min-width: 64.01rem) and (max-width: 80rem) {
  .spread .spread__ingredients, .spread .spread__method { grid-column: 1 / -1; margin-right: 0; }
  .spread__method { margin-top: var(--u); }
}

/* -- Ingredient rows: read from arm's length at the stove, 24px. Amounts take
   the width they need; a figure and its unit never part; a name holds its
   line under a hanging indent, the icon in the indent. */
/* One amount column for the whole list (subgrid), so a long amount ("0.529 oz") never pushes its own name off the
   shared axis. */
.ingredient-list {
  display: grid;
  grid-template-columns: minmax(5.25rem, max-content) minmax(0, 1fr);
  column-gap: var(--u2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.ingredient-row {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: baseline;
  padding: calc(var(--u2) / 2) 0;
  border-bottom: 1px solid var(--rule);
  font-size: var(--fs-24);
  line-height: var(--u);
}
.ingredient-row:last-child { border-bottom: 0; }
.ingredient-row__amount { text-align: right; font-size: var(--fs-24); font-weight: 650; white-space: nowrap; }
.ingredient-row__amount .unit { font-size: var(--fs-18); }
.ingredient-row__name {
  position: relative;
  display: block;
  padding-left: calc(1.75rem + var(--u2) / 2);
  overflow-wrap: anywhere;
  hyphens: auto;
}
.ingredient-row__name .item-icon { position: absolute; left: 0; top: 0.15em; margin: 0; }
/* A shortfall is the only per-row availability mark. */
.ingredient-row__status { grid-column: 2; font-size: var(--fs-16); line-height: var(--u2); }

/* -- Method: 18/28 at most 34em; step numerals hang in the margin at display
   size, cut like the cover. */
.recipe-method { margin: 0; padding: 0; list-style: none; counter-reset: step; }
.recipe-method > li {
  counter-increment: step;
  display: grid;
  grid-template-columns: var(--u) minmax(0, 1fr);
  column-gap: var(--u2);
  max-width: 34em;
  padding-block: calc(var(--u2) / 2);
  font-size: var(--fs-18);
  line-height: var(--u);
}
.recipe-method > li::before {
  content: counter(step);
  justify-self: end;
  color: var(--ink);
  font: 650 var(--fs-32)/var(--u) var(--font-display);
  font-variation-settings: "SOFT" 0, "WONK" 0;
  letter-spacing: -0.03em;
  font-variant-numeric: lining-nums;
}

/* -- Record: once, on the recipe being cooked. It opens on the heavy rule
   with its own side head; the answer is an open strip of three peer choices,
   hairlines above and below, each cell its label's width with the spare room
   shared, inverted to ink on hover, no box. */
.record {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: var(--side) minmax(0, 1fr);
  column-gap: var(--side-gap);
  align-items: first baseline;
  margin-top: calc(var(--u) * 1.5);
  padding: var(--u2) 0 0;
  border-top: var(--rule-heavy) solid var(--ink);
  container: ballot / inline-size;
}
.record__choices {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 0;
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
}
.record__ballot { margin-top: 1.25rem; container-type: inline-size; }
#recipe-main .record > .side-head { padding-top: var(--u2); }
.record__choices > form { display: contents; }
.record__choices button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: calc(var(--u) * 2.5);
  padding: 0 var(--u2);
  border: 0;
  border-left: 1px solid var(--ink);
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
  font: 700 var(--fs-24)/var(--u) var(--font-sans);
  letter-spacing: -0.01em;
  text-align: center;
}
.record__choices > form:first-child button { border-left: 0; }
.record__choices button:hover { background: var(--ink); color: var(--paper); }
.record__choices button:focus-visible { position: relative; z-index: 1; }
/* A failed record goes under the strip, across its full width. */
.record__choices > .save-error { grid-column: 1 / -1; order: 99; margin: var(--u2) 0 0; }
/* The strip stacks only when its three labels can't share one line. */
@container ballot (max-width: 34rem) {
  .record__choices { grid-template-columns: 1fr; border-top-width: var(--rule-mid); border-bottom: 0; }
  .record__choices button {
    justify-content: space-between;
    padding-left: 0;
    border-left: 0;
    border-bottom: 1px solid var(--ink);
    text-align: left;
  }
  .record__choices > form:last-of-type button { border-bottom: var(--rule-mid) solid var(--ink); }
  .record__choices button::after { content: "\2192" / ""; font-weight: 400; color: var(--ink-secondary); }
}

/* Seasonings, without grams, under the ingredients. */
.spread__optional { margin-top: var(--u); }
.recipe-optional { display: flex; flex-wrap: wrap; gap: 0 0.3em; margin: 0; padding: 0; list-style: none;
  font-size: var(--fs-18); line-height: var(--u); }
.recipe-optional > li:not(:last-child)::after { content: ","; }

/* A recorded meal: "Recorded · Loved it" and Undo on one ruled line, where the strip was (R-PAGE-15). */
.record__done {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--u2);
  padding: var(--u2) 0;
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
}
.record__state { margin: 0; font-size: var(--fs-24); line-height: var(--u); }
.record__state b { font-weight: 700; }
.record__done > .save-error { flex-basis: 100%; }

/* -- Phone: ingredients, then method, then Record. */
@media (max-width: 48rem) {
  .spread { display: block; padding-top: 0; }
  .spread__col + .spread__col { margin-top: var(--u); }
  .spread__ingredients { margin-right: 0; }
  .record { display: block; }
  .record .side-head { margin-bottom: var(--u2); }
}
/* Narrowest: the amount sits above its ingredient, so the name keeps the
   measure. One column means one column. */
@media (max-width: 22.5rem) {
  .ingredient-list, .ingredient-row { grid-template-columns: minmax(0, 1fr); }
  .ingredient-row > * { grid-column: 1; grid-row: auto; }
  .ingredient-row__amount { text-align: left; }
}

/* -- Print: one Letter page in light ink; Record and per-row status don't
   print; rows and steps never split. */
@media print {
  .record, .ingredient-row__status { display: none !important; }
  .ingredient-row, .recipe-method > li { break-inside: avoid; }
  .spread__col > .label { break-after: avoid; }
}

/* ==== 41-week.css ==== */
/* ============================================================================
   The week: a timetable (plan; R-PAGE-22..30, R-MOT-18). Ported from
   docs/folio-claude/folio.css ("The week: a timetable", "The week as a list",
   the narrowest block, "Today's dishes in the timetable", print) by wave 0b;
   owned by plan from wave 1 (plan.api.timetable writes the markup).

   Port notes: there is no separate six-slot ledger variant. More than four
   slots, a narrow week column or a phone all give the same day-by-day list,
   from one container query on .plan-panel (the timetable's own wrapper, on the plan page and in the workspace).
   The proposal's phone copy of the list rules is dropped (the container query
   applies on every phone), and so is its separate ledger variant. A day whose meals are all past carries
   .week-day--past from the server (no :has()).
   ============================================================================ */
.plan-panel { container: week / inline-size; }
.week-menu {
  display: grid;
  grid-template-columns: calc(var(--side) + var(--side-gap)) repeat(var(--slots, 4), minmax(0, 1fr));
  column-gap: calc(var(--u2) / 2);
  border-bottom: var(--rule-mid) solid var(--ink);
}
/* Slot names print once, as column heads on the rule they label (16px, 600,
   secondary ink); the stub holds the side head and the unit key. */
.week-head { display: contents; }
.week-head > span {
  align-self: end;
  padding: 0 var(--u2) calc(var(--u2) / 2) 0;
  color: var(--ink-secondary);
  font: 600 var(--fs-16)/1.25 var(--font-sans);
}
.week-head__stub { display: flex; flex-direction: column; justify-content: end; }
.week-head__stub .side-head { white-space: nowrap; }
.week-key { display: block; color: var(--ink-secondary); font: 500 var(--fs-16)/1.4 var(--font-sans); }
.week-day { display: contents; }
.week-day__name {
  grid-column: 1;
  margin: 0;
  padding: var(--u2) 0;
  border-top: 1px solid var(--rule);
  font: 700 var(--fs-16)/1.3 var(--font-sans);
}
.week-day__today { display: block; font-weight: 500; }

/* Past days fold into one "Earlier this week" disclosure at the foot, on the
   same columns. The disclosure marks are silent. */
.week-earlier { grid-column: 1 / -1; }
.week-earlier > summary {
  display: flex;
  gap: var(--u2);
  padding: var(--u2) 0;
  list-style: none;
  color: var(--ink-secondary);
  font: 700 var(--fs-16)/1.4 var(--font-sans);
  cursor: pointer;
}
.week-earlier > summary::-webkit-details-marker { display: none; }
.week-earlier > summary::before { content: "▸" / ""; color: var(--ink); }
.week-earlier[open] > summary::before { content: "▾" / ""; }
.week-earlier > summary span { font-weight: 500; }
.week-earlier__days {
  display: grid;
  grid-template-columns: calc(var(--side) + var(--side-gap)) repeat(var(--slots, 4), minmax(0, 1fr));
  column-gap: calc(var(--u2) / 2);
}

/* A cell: a plate, the dish, its figures. Every cell is a link; the whole
   cell is the target, with no box and no underline until hover. */
.meal-line {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--u2) var(--u2) var(--u2) 0;
  border-top: 1px solid var(--rule);
  container-type: inline-size;
}
/* Each cell says its slot in the text flow (the column heads are
   aria-hidden): spoken, never seen, as an inline run of zero size. */
.meal-line__meal {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  color: var(--ink-secondary);
  font: 500 var(--fs-16)/var(--u2) var(--font-sans);
}
.meal-line__badge { display: inline; font-size: 0; line-height: 0; }
.meal-line__dish {
  font: 500 var(--fs-16)/1.4 var(--font-sans);
  text-wrap: pretty;
  overflow-wrap: anywhere;
  hyphens: manual;
}
.meal-line__dish a { color: var(--ink); text-decoration: none; }
.meal-line__dish a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
.meal-line__dish a::after { content: ""; position: absolute; inset: 0; }
/* "556 · 31g": the figure is ink; units print once, in the key. */
.meal-line__nutri {
  margin-top: 2px;
  color: var(--ink);
  font-size: var(--fs-16);
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.meal-line__nutri b { font-weight: 500; }

/* Past meals recede by secondary ink only, never opacity. Today is set bold, a
   size up, under the 3px rule; today's eaten meal steps back with a spoken
   tick. The next meal carries the page's only 6px tab, over the cell's text. */
.slot-past .meal-line__nutri, .slot-past .meal-line__nutri b, .slot-past .meal-line__dish a { color: var(--ink-secondary); }
.week-day--today .week-day__name,
.week-day--today .meal-line { border-top: var(--rule-mid) solid var(--ink); }
.week-day--today .meal-line__dish {
  font-size: var(--fs-18);
  font-weight: 700;
  line-height: 1.33;
  text-wrap: balance;
  transition: color var(--dur-move) var(--ease-standard), font-size var(--dur-move) var(--ease-standard);
}
.week-day--today .meal-line--eaten .meal-line__dish {
  color: var(--ink-secondary);
  font-size: var(--fs-16);
  font-weight: 500;
  line-height: 1.4;
}
.week-day--today .meal-line--eaten .meal-line__dish a { color: var(--ink-secondary); }
.meal-line--eaten .meal-line__dish::before {
  content: "✓\00a0" / "Eaten: ";
  color: var(--ink);
  animation: fade-in var(--dur-move) linear both;
}
.meal-line--next::before {
  content: "";
  position: absolute;
  top: calc(-1 * var(--rule-mid));
  left: 0;
  right: 0;
  height: var(--rule-heavy);
  background: var(--ink);
}
/* A cell narrower than 8rem sets today's dish at 16px, whatever the count. */
@container (max-width: 8rem) {
  .week-day--today .meal-line__dish { font-size: var(--fs-16); line-height: 1.35; }
}

/* Today's dishes: the band's own transparent paintings, whole, up to 7rem.
   Later days stay a quiet list on desktop; past paintings are muted. */
.week-thumb {
  display: block;
  width: min(100%, 7rem);
  height: auto;
  aspect-ratio: 1;
  margin: 0 0 calc(var(--u2) / 2);
  object-fit: contain;
}
.week-day:not(.week-day--today) .week-thumb { display: none; }
.slot-past .week-thumb { opacity: 0.55; }

/* The week's foot: the adjust caption (the only route to changing the plan),
   then the kitchen line, both on the content axis. */
.week-foot {
  margin: calc(var(--u2) / 2) 0 0 calc(var(--side) + var(--side-gap));
  font: 500 var(--fs-18)/var(--u) var(--font-sans);
}

/* -- The week as a list: a narrow week column, a phone, or more than four
   slots. Day by day from today, one ink rule between days (3px over today),
   the slot name per row, a 2.5rem painting, then dish and figures. A week
   with more than four slots (.plan-panel--many, from the server) sets in the
   content column only, which narrows its container into the same list; its
   head keeps the page edge. */
@media (min-width: 48.01rem) {
  .plan-panel.plan-panel--many { padding-left: calc(var(--side) + var(--side-gap)); }
  .plan-panel.plan-panel--many .week-head__stub {
    position: absolute;
    top: 0;
    left: calc(-1 * (var(--side) + var(--side-gap)));
    width: var(--side);
  }
  .plan-panel.plan-panel--many .week-menu { position: relative; }
  .plan-panel.plan-panel--many .week-head__stub .side-head { white-space: normal; }
}
@container week (max-width: 40rem) {
  .week-menu, .week-earlier__days { display: block; }
  .week-head > span:not(.week-head__stub) { display: none; }
  .week-head__stub { padding-bottom: var(--u2); }
  .week-day { display: block; padding: 0 0 var(--u2); }
  .week-day--past { display: none; }
  .week-earlier .week-day--past { display: block; }
  .week-day__name { border-top: 1px solid var(--ink); padding-top: var(--u2); }
  .week-day--today .week-day__name { border-top-width: var(--rule-mid); }
  .meal-line {
    display: grid;
    grid-template-columns: 6rem 3rem minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    column-gap: var(--u2);
    padding: calc(var(--u2) / 2) 0;
    border-top: 1px solid var(--rule);
  }
  .week-day--today .meal-line { border-top: 1px solid var(--rule); }
  .meal-line__meal {
    position: static;
    grid-row: 1 / span 2;
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
    line-height: 1.4;
  }
  .meal-line__dish, .meal-line__nutri { grid-column: 3; }
  .meal-line__nutri { align-self: start; }
  .meal-line--next::before { top: -1px; left: calc(6rem + var(--u2)); }
  .week-thumb,
  .week-day:not(.week-day--today) .week-thumb {
    display: block;
    grid-column: 2;
    grid-row: 1 / span 2;
    width: 2.5rem;
    height: 2.5rem;
    margin: 0;
  }
  .week-foot { margin-left: 0; }
}
@media (max-width: 48rem) {
  .week-foot { margin-left: 0; }
}
/* Narrowest: the slot name sits above its dish, so the dish keeps the measure. */
@media (max-width: 22.5rem) {
  .meal-line { grid-template-columns: 2.5rem minmax(0, 1fr); grid-template-rows: auto auto 1fr; }
  .meal-line__meal { grid-column: 1 / -1; grid-row: 1; }
  .week-thumb, .week-day:not(.week-day--today) .week-thumb { grid-column: 1; grid-row: 2 / span 2; }
  .meal-line__dish, .meal-line__nutri { grid-column: 2; }
  .meal-line--next::before { left: 0; }
  .week-earlier > summary { flex-wrap: wrap; }
}

/* A past slot that was never recorded: an inert badge, in words (R-PAGE-26). */
.unlogged-badge {
  align-self: start;
  margin-top: 2px;
  color: var(--ink-secondary);
  font: 600 var(--fs-14)/1.4 var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
/* A meal a finished replan changed says so, in words, even when its title and figures stayed (R-PLAN-58). */
.meal-line__replanned {
  align-self: start;
  color: var(--ink-secondary);
  font: 600 var(--fs-14)/1.4 var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
/* A skipped meal leaves the grid: the day's quiet recap carries it, with Undo (R-LOG-18, Q-PAGE-2). */
.week-recap {
  grid-column: 2 / -1;
  margin: 0;
  padding: 0 0 var(--u2);
  color: var(--ink-secondary);
  font: 500 var(--fs-16)/1.4 var(--font-sans);
}
.week-recap form.word-action { display: inline-flex; }
@container week (max-width: 40rem) {
  .week-recap { padding: calc(var(--u2) / 2) 0 0; }
}

/* The plan's state and notices sit on the content axis above the week: still sentences in ink, a failure with
   its mark, never boxed (R-ERR-15, R-ERR-16, R-PLAN-47, R-PLAN-48). */
.plan-status, .plan-notices { margin: var(--u2) 0 var(--u) calc(var(--side) + var(--side-gap)); }
.plan-status > p, .plan-notices p { margin: 0 0 calc(var(--u2) / 2); max-width: 36em; font-size: var(--fs-18);
  line-height: var(--u); }
.plan-notice + .plan-notice, .plan-notices > * + * { margin-top: var(--u2); }

/* Batch prep: the task, its session, and the meals it feeds, one ruled row each (R-PLAN-49). */
.prep > li { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--u2); }
.prep__task { font-weight: 600; }
.prep__when { color: var(--ink-secondary); text-align: right; }
.prep__for { grid-column: 1 / -1; color: var(--ink-secondary); font-size: var(--fs-16); }

.week-foot p { margin: 0 0 calc(var(--u2) / 2); }

@media (max-width: 48rem) {
  .plan-status, .plan-notices { margin-left: 0; }
}
@media print {
  .plan-notices form, .week-recap form { display: none !important; }
}

@media (forced-colors: active) {
  .meal-line--next::before { forced-color-adjust: none; background: CanvasText; }
}

@media print {
  .week-earlier, .week-foot { display: none !important; }
  .meal-line, .week-day { break-inside: avoid; }
  .week-day__name { break-after: avoid; }
}

/* ==== 45-planning.css ==== */
/* ============================================================================
   The planning view: the search drawn over the week being tried (planview;
   R-MOT-13..24). Ported from docs/folio-claude/folio.css ("Planning") by wave
   0b; owned by planview from wave 1. planview.html writes the markup, the
   board is plan's own timetable (41-week.css), and 45-planning.js draws the
   tree into the never-morphed <svg>.

   A route's tone is its recorded A* priority (a costlier route is lighter);
   the selected route is the heaviest line on the page. Strategies are line
   textures, not panels or colours.
   ============================================================================ */
.planning { container: planning / inline-size; }
.planning__state {
  margin: 0 0 var(--u2);
  white-space: normal;
  font: 650 var(--fs-32)/calc(var(--u) * 1.5) var(--font-display);
  font-variation-settings: "SOFT" 0, "WONK" 0;
  letter-spacing: -0.02em;
}
.planning__state:not(:empty) { animation: set-line var(--dur-move) var(--ease-enter) both; }
.planning__note { max-width: 36em; margin: 0 0 var(--u); }
/* The journal page's events, one line each, as its Markdown lists them (R-PLAN-18). */
.journal { margin-top: var(--u); color: var(--ink-secondary); font-size: var(--fs-16); }
.journal > summary { cursor: pointer; }
.journal__events { margin: calc(var(--u2) / 2) 0 0; padding: 0; list-style: none; font-variant-numeric: tabular-nums; }
.journal__events li { overflow-wrap: anywhere; }
.planning__failure { margin: 0 0 var(--u); }
.planning__failure .coach-say { margin-top: var(--u2); }

/* SVG elements get no user-agent rule for the hidden attribute. */
.planning__tree [hidden] { display: none; }
.planning__tree { display: block; width: 100%; height: auto; margin: 0 0 var(--u); overflow: visible; }
.planning__edge {
  fill: none;
  stroke: color-mix(in srgb, var(--ink) var(--tone, 100%), var(--paper));
  stroke-width: 1.6;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.planning__edge.is-active { stroke: var(--ink); stroke-width: 2.4; }
/* After an ending the last route rests in secondary ink; only a found week keeps full ink. */
[data-planning]:not([data-outcome="live"]) .planning__edge.is-active:not(.is-selected) { stroke: var(--ink-secondary); }
/* Each fallback strategy has its own line, as a map draws road, track and path. */
.planning__edge[data-pass="multi_meal"] { stroke-dasharray: 7 4; stroke-linecap: butt; }
.planning__edge[data-pass="freeform"] { stroke-dasharray: 0.1 4.5; stroke-width: 2.2; }
.planning__edge.is-active[data-pass="freeform"] { stroke-width: 3.2; }
.planning__edge.is-selected { stroke-width: 4; }
.planning__reveal { fill: none; stroke: #fff; stroke-width: 16; stroke-linecap: round; }
.planning__node { fill: color-mix(in srgb, var(--ink) var(--tone, 100%), var(--paper)); }
.planning__ring { fill: var(--paper); stroke: var(--ink); stroke-width: 2.5; vector-effect: non-scaling-stroke; }
.planning__dot { fill: var(--ink); }
.planning__sweep { fill: none; stroke: var(--ink); stroke-linecap: round; stroke-linejoin: round; }
.planning__glint { fill: none; stroke: var(--paper); stroke-width: 3; stroke-linecap: round; stroke-dasharray: 0.13 1; }
.planning__ray { stroke: var(--ink); stroke-linecap: round; }
.planning__ring-out { fill: none; stroke: var(--ink); stroke-width: 2; }

/* The one-line key, server-rendered once the planner falls back. */
.planning__key { display: flex; flex-wrap: wrap; gap: 0 var(--u); margin: 0 0 var(--u2); font: 500 var(--fs-16)/var(--u) var(--font-sans); }
.planning__key { animation: set-line var(--dur-move) var(--ease-enter) both; }
.planning__key-item { display: inline-flex; align-items: center; gap: calc(var(--u2) / 2); color: var(--ink-secondary); }
.planning__key-item svg { overflow: visible; }
.planning__key-item .is-sample { stroke: currentColor; stroke-width: 2.4; }
.planning__key-item:is([data-mode="live"], [data-mode="won"]) { color: var(--ink); }
.planning__key-item:is([data-mode="live"], [data-mode="won"]) .planning__key-name { font-weight: 700; }

/* -- The board is the settled plan's own timetable (41-week.css), with its
   own container so it becomes the day-by-day list in a narrow column, like
   the settled week. Tentative meals are set light; the found week inks in
   cell by cell and its closing rule thickens from medium to heavy. */
.planning__board { container: week / inline-size; }
.planning__board .meal-line--pending .meal-line__dish { opacity: 0.6; }
.planning__board .meal-line--pending .meal-line__dish.is-final { opacity: 1; }
.planning__board .week-menu { transition: border-bottom-width var(--dur-arrive) var(--ease-expressive); }
.planning__board.is-selected .week-menu { border-bottom-width: var(--rule-heavy); }

@media (prefers-reduced-motion: reduce) {
  .planning__state:not(:empty), .planning__key { animation: fade-in var(--dur-fade) linear both; }
  .planning__board .week-menu { transition: none; }
}
@media print {
  .planning__tree { display: none; }
}

/* ==== 46-fix.css ==== */
/* ============================================================================
   "What would work": the proof, the follow-ups, the foods (planview;
   R-PLAN-59, R-PLAN-60, R-PLAN-66..70, R-MOT-21). Ported from
   docs/folio-claude/folio.css ("After a failure", "The follow-ups", "Foods
   that would make it work") by wave 0b; owned by planview from wave 1.

   Port notes: the proof's have-against-need bar is ui.charts.part_bar (solid
   held, outlined missing: the one part-of-whole idiom), so .proof__bar is not
   carried.
   ============================================================================ */
.proof, .fixes { margin: 0 0 var(--u); border-top: var(--rule-heavy) solid var(--ink); }
.proof__head, .fixes__head {
  margin: calc(var(--u2) / 2) 0 0;
  font: 700 var(--fs-14)/var(--u) var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.proof__row, .fixes__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0 var(--u);
  padding: calc(var(--u2) / 2) 0 var(--u2);
  border-top: 1px solid var(--rule);
}
.proof__label, .fixes__name { font-weight: 700; }
.proof__figures { text-align: right; font-variant-numeric: tabular-nums lining-nums; }
.proof__row > .bar, .proof__shortfall { grid-column: 1 / -1; }
.proof__shortfall { color: var(--ink-secondary); }

/* The follow-ups: a ruled table with a status column, like a departure
   board's; a status word flips when its finding lands (data-flip-cell). */
.fixes__status { text-align: right; font: 700 var(--fs-14)/var(--u) var(--font-sans); letter-spacing: 0.12em; text-transform: uppercase; }
.fixes__body { grid-column: 1 / -1; }
.fixes__body p { max-width: 36em; margin: 0; }
.fixes__pending, .fixes__when { color: var(--ink-secondary); }
.fixes__row:not([data-status="checking"]) .fixes__body > * { animation: set-line var(--dur-move) var(--ease-enter) both; }
.fixes__body > :nth-child(2) { animation-delay: var(--cadence); }
.fixes__body > :nth-child(3) { animation-delay: calc(2 * var(--cadence)); }
.fixes__body > :nth-child(n + 4) { animation-delay: calc(3 * var(--cadence)); }
/* Diagnostic detail stays out of the reading flow, behind a disclosure (R-PLAN-70). */
.fixes__detail { margin-top: calc(var(--u2) / 2); color: var(--ink-secondary); font-size: var(--fs-16); }
.fixes__detail > summary { cursor: pointer; }
.fixes__detail p { margin: calc(var(--u2) / 4) 0 0; }

/* Foods that would make it work: one group per kind of food the kitchen is
   short on, its alternatives laid out as foods with "or" between them. */
.foods__lead { margin: 0 0 var(--u2); }
.foods { border-top: 1px solid var(--rule); }
.foods__group {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr);
  gap: 0 var(--side-gap);
  padding: var(--u2) 0;
  border-bottom: 1px solid var(--rule);
}
.foods__need { font-weight: 700; }
.foods__gloss { display: block; color: var(--ink-secondary); font-size: var(--fs-16); font-weight: 400; line-height: 1.35; }
.foods__options { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: var(--u2) var(--u); }
.food { display: grid; grid-template-columns: 3rem minmax(0, 1fr); align-items: center; gap: 0 calc(var(--u2) / 2); }
.food__icon { grid-column: 1; width: 3rem; height: 3rem; object-fit: contain; }  /* both cuts share the cell; the scheme shows one (R-PIC-19) */
.food__text { grid-column: 2; display: flex; flex-direction: column; line-height: 1.3; }  /* a food with no painting keeps the name column */
.food__name { font-weight: 600; }
.food__amount { color: var(--ink-secondary); font-size: var(--fs-16); font-variant-numeric: tabular-nums lining-nums; }
.food__tested {
  align-self: flex-start;
  margin-top: 2px;
  padding: 0 5px;
  border: 1.5px solid var(--ink);
  font: 700 var(--fs-14)/1.4 var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.foods__note { grid-column: 2; margin: calc(var(--u2) / 2) 0 0; color: var(--ink-secondary); }
.foods__more { grid-column: 1 / -1; }
.foods__more > summary, .foods__more-groups > summary { color: var(--ink-secondary); cursor: pointer; }
.foods__more[open] > summary { display: none; }
.foods__more-groups > summary { padding: var(--u2) 0; border-bottom: 1px solid var(--rule); }

@media (prefers-reduced-motion: reduce) {
  .fixes__row:not([data-status="checking"]) .fixes__body > * { animation: fade-in var(--dur-fade) linear both; }
}
/* Findings are set line by line as they land (07-motion.css .is-set). */
@container planning (max-width: 34rem) {
  .foods__group { grid-template-columns: 1fr; }
  .foods__note { grid-column: 1; }
}

/* ==== 50-workspace.css ==== */
/* ============================================================================
   The workspace: "How today's going", the pace tick, the protein channel, the
   warm-up, the week's foot and the kitchen line, the sparklines (workspace;
   R-PAGE-3..7, R-PAGE-16..21, R-LOG-26). Ported from docs/folio-claude/
   folio.css ("The day plan", "Warm-up", the closing-section fix, the phone
   and narrowest blocks, print) by wave 0b; owned by workspace from wave 1.

   Today's plan bar and the warm-up's day bar are ui.charts.part_bar
   (06-charts.css), the one part-of-whole idiom (R-VIS-35). The workspace's
   sections are ui.section.side.
   ============================================================================ */

/* -- How today's going, set like a Nutrition Facts panel: "Calories" left,
   the total right ("0 / 2,100": eaten 24px 650 tabular, the target receding
   in secondary ink). */
.today-meter__row { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: var(--u2); }
.today-meter__label { color: var(--ink); font: 700 var(--fs-18)/var(--u) var(--font-sans); }
.today-meter__total {
  font: 650 var(--fs-24)/var(--u) var(--font-sans);
  font-variant-numeric: tabular-nums lining-nums;
  white-space: nowrap;
}
.today-meter__target { color: var(--ink-secondary); font-weight: 500; }
.today-meter__total .unit { color: var(--ink-secondary); font-weight: 500; }

/* The pace tick: expected-by-now, an ink rule a half-line taller than the
   bar, only when the client's clock is known (R-PAGE-20). It sits on the
   track, under the target's blue tick. */
.day-bar--pace .bar__track::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: calc(-1 * var(--u2) / 4);
  bottom: calc(-1 * var(--u2) / 4);
  left: clamp(0px, calc(var(--pace) - 1px), calc(100% - 2px));
  width: 2px;
  background: var(--ink);
}

/* The line under the bar keeps one size and one place in every state, and its
   clauses never break inside. The verdict is said once, here. */
.today-summary { display: flex; flex-direction: column; align-items: flex-start; margin-top: var(--u2); }
.today-summary p { margin: 0; font: 650 var(--fs-18)/var(--u) var(--font-sans); }
.today-summary .today-verdict { font-weight: 700; }
.today-summary .clause { white-space: nowrap; }

/* Protein speaks the bar's language: a 14px ruled channel, an ink fill. */
.today-protein { margin-top: var(--u); }
.protein-track { height: var(--u2); margin-block: calc(var(--u2) / 2) 0; border: 1px solid var(--ink-secondary); background: var(--good-tint); }
.protein-track__fill { height: 100%; width: var(--w); background: var(--ink); }

/* -- The week: plan's timetable answers its container queries from here, as
   it does from #plan-panel on the plan page. */
.ws-week {
  container: week / inline-size;
  padding-top: var(--u2);
  border-top: 1px solid var(--ink);
}

/* The week closes on the kitchen line. */
.week-foot { margin-top: var(--u2); }
.kitchen-line { margin: calc(var(--u2) / 2) 0 0; color: var(--ink-secondary); font: 500 var(--fs-18)/var(--u) var(--font-sans); }
/* Without a week to close, the line stands alone on the content axis; the next section's rule separates it. */
.region > .kitchen-line { margin: 0 0 0 calc(var(--side) + var(--side-gap)); padding: var(--u2) 0 var(--u); }

/* -- The cook spread sits directly under the band; the all-clear is one line. */
.cook { margin: 0; }
.all-clear { margin: 0; font: 650 var(--fs-18)/var(--u) var(--font-sans); }

/* -- A failed plan in the plan area: the run-in status head (05-errors.css),
   the cause in ink, the coach-say button. */
#plan-failure .status-detail { margin: var(--u2) 0; max-width: 34em; font: 400 var(--fs-18)/var(--u) var(--font-sans); }

/* -- Warm-up (before the first plan): the cover line is the next action; the
   days print as a part-of-whole bar. */
.warmup-goal { margin: 0 0 var(--u2); color: var(--ink-secondary); font: 500 var(--fs-18)/var(--u) var(--font-sans); }
.warmup-lead { margin: 0; font: 650 var(--fs-24)/var(--u) var(--font-sans); }
.warmup-note { max-width: 34em; margin: var(--u) 0 var(--u2); font: 400 var(--fs-18)/var(--u) var(--font-sans); }

/* -- How you've been doing: the two sparklines side by side, then the link. */
.ws-sparks { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); gap: var(--u2) var(--u); }
.ws-sparks .spark { margin: 0; }
.history-link { margin: var(--u2) 0 0; font: 500 var(--fs-18)/var(--u) var(--font-sans); }

/* Narrowest: a plan clause may wrap; figures still keep their units. */
@media (max-width: 22.5rem) {
  .today-summary .clause { white-space: normal; }
}

@media (max-width: 48rem) {
  .spread__col > .label { margin-bottom: var(--u2); }
}

/* Print: the head and today's recipe on page 1; the ledger and the week on
   page 2. Live-only lines drop out. */
@media print {
  .today-meter, #today { break-inside: avoid; }
  #today-panel { break-before: page; }
  .kitchen-line, .history-link { display: none !important; }
}

/* Keep the graph's meaning beside it, including one-meal and empty intake days. */
.day-keyline { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; margin-top: var(--u2);
  color: var(--ink-secondary); font: 500 var(--fs-16)/1.5 var(--font-sans); }
.day-key { display: inline-flex; align-items: center; gap: 0.4rem; }
.day-key::before { content: ""; width: 0.8rem; height: 0.65rem; flex: none; }
.day-key--eaten::before { background: var(--ink); }
.day-key--next::before { border: var(--rule-mid) solid var(--ink);
  background: repeating-linear-gradient(-45deg, var(--hatch) 0 1px, transparent 1px 4px); }
.day-key--planned::before { background: repeating-linear-gradient(-45deg, var(--hatch) 0 2px, transparent 2px 5px); }
.day-key--target::before, .day-key--pace::before { width: 3px; height: 0.9rem; margin-inline: 0.25rem; background: var(--accent); }
.day-key--pace::before { width: 2px; background: var(--ink); }
.today-summary .clause { white-space: normal; }

/* ==== 60-coach.css ==== */
/* ============================================================================
   The coach rail: correspondence on warm paper (rail; R-PAGE-32..41,
   R-ERR-19). Ported from docs/folio-claude/folio.css ("The coach", "Stacked
   and short viewports", the phone block, failed receipts and photos) by wave
   0b; owned by rail from wave 1, whose @panel("rail") renders inside the
   shell's <aside class="rail"> (02-shell.css draws the pane and its title).

   Port notes: ".workspace-coach" is the shell's ".rail". The letter-style
   message with no speaker line carries .msg--letter from the server (no
   :has()); Photos shows its focus ring with :focus-within. A failed receipt
   uses the shared mark (05-errors.css). The message and photo forms are two
   real forms laid out as one composer row.
   ============================================================================ */

/* -- The transcript, typeset like an edited interview: speaker labels hang in
   a 4.5rem gutter, the speech flush-and-hang beside them on a shared first
   baseline. Two voices, two faces: the coach in the serif, the reader in the
   sans; never by position alone. */
.chat-log { display: block; }
.msg {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  column-gap: var(--u2);
  align-items: first baseline;
  padding: var(--u2) 0 0;
}
.msg + .msg { margin-top: calc(var(--u2) / 2); }
.msg > :is(.msg-images, .msg-bubble) { grid-column: 2; }
.msg--letter > :is(.msg-images, .msg-bubble) { grid-column: 1 / -1; }
.msg-meta { display: block; margin: 0; }
.msg-meta__author {
  display: block;
  color: var(--ink);
  font: 700 var(--fs-14)/var(--u) var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.msg-meta__time {
  display: block;
  color: var(--ink-secondary);
  font: 500 var(--fs-16)/1.3 var(--font-sans);
  font-variant-numeric: tabular-nums;
}
.msg-user .msg-bubble { color: var(--ink); font: 500 var(--fs-18)/var(--u) var(--font-sans); }
.msg-assistant .msg-bubble {
  color: var(--coach-ink);
  font: 400 var(--fs-18)/var(--u) var(--font-serif);
  font-variant-numeric: oldstyle-nums proportional-nums;
}
.msg-assistant .msg-bubble p { margin: 0 0 calc(var(--u2) / 2); }
.msg-assistant .msg-bubble p:last-child { margin-bottom: 0; }
.msg-assistant .msg-bubble strong { font-weight: 650; }

/* -- Act receipts: stage directions -- italic serif, indented past the
   speech, a hairline at the left, no label, mark and words on one line. */
.receipts { margin: calc(var(--u2) / 2) 0 0 calc(4.5rem + var(--u2)); padding: 0 0 0 var(--u2); border-left: 1px solid var(--ink); }
.receipt { display: flex; flex-wrap: nowrap; gap: 0.4em; }
.receipt__mark { flex: none; color: var(--ink); font: 700 var(--fs-16)/var(--u) var(--font-sans); }
.receipt__text { color: var(--coach-ink); font: italic 400 var(--fs-18)/var(--u) var(--font-serif); }
/* A failed receipt: the red square hangs, the reader sentence in upright bold
   wraps under itself, never into a sliver. */
.receipt--failed { display: grid; grid-template-columns: auto 1fr; align-items: baseline; }
.receipt--failed > :not(.mark) { grid-column: 2; }
.receipt--failed .receipt__text { font-style: normal; font-weight: 650; color: var(--ink); }

/* -- Tables in replies: booktabs, figures aligned. */
.coach-html table { width: 100%; margin: var(--u2) 0; border-collapse: collapse; font: 500 var(--fs-16)/1.4 var(--font-sans); font-variant-numeric: tabular-nums lining-nums; }
.coach-html thead th { padding: calc(var(--u2) / 2) var(--u2) calc(var(--u2) / 2) 0; border-top: var(--rule-mid) solid var(--ink); border-bottom: 1px solid var(--ink); text-align: left; font-weight: 700; }
.coach-html td { padding: calc(var(--u2) / 2) var(--u2) calc(var(--u2) / 2) 0; border-bottom: 1px solid var(--coach-rule); }
.coach-html tbody tr:last-child td { border-bottom: 1px solid var(--ink); }

/* -- The composer: an input with a visible caps label, a ruled writing line
   spanning the rail, not a box. Send leads, in ink; Photos follows as a text
   button. The message and the photo are two real forms; the composer lays
   their parts out as one row (the message form's box is "contents"), and the
   whole composer sticks to the rail's foot on desktop. */
.composer {
  position: sticky;
  bottom: 0;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--u2) var(--u);
  margin-top: var(--u);
  padding-block: var(--u2);
  border-top: 1px solid var(--coach-rule);
  background: var(--coach-paper);
}
.composer > form.chat-form { display: contents; }
.composer-label {
  flex: 1 1 100%;
  color: var(--ink);
  font: 700 var(--fs-14)/var(--u) var(--font-sans);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.chat-form textarea {
  flex: 1 1 100%;
  min-height: calc(var(--u) * 3);
  padding: calc(var(--u2) / 2) 0;
  border: 0;
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font: 500 var(--fs-18)/var(--u) var(--font-sans);
  resize: none;
  field-sizing: content;
}
.chat-form textarea::placeholder { color: var(--coach-placeholder); opacity: 1; }
.chat-form textarea:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.chat-form button[type="submit"]:disabled { opacity: 0.45; cursor: not-allowed; }
.chat-form button[type="submit"] { padding: 0.75rem 1rem; font-weight: 750; line-height: 1.2; }
.composer > form.photos-form { display: inline-flex; align-items: center; gap: var(--u2); }
.photos {
  position: relative;
  color: var(--ink);
  font: 650 var(--fs-18)/var(--u) var(--font-sans);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}
.photos input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.photos:focus-within { outline: 3px solid var(--focus); outline-offset: 3px; }
.composer-js .photos-form > button[type="submit"] { display: none; }
/* A waiting photo's Remove form: its chip's button submits it, so the form
   itself shows nothing (its refusal note, a sibling, still does). */
.composer > form.photo-remove { display: none; }
.composer > .save-error { flex: 1 1 100%; order: 5; margin: 0; }
.composer-hint { margin: var(--u2) 0 0; color: var(--ink-secondary); font: 450 var(--fs-16)/1.4 var(--font-sans); }
.composer-readonly { margin: var(--u2) 0 0; padding-top: var(--u2); border-top: 1px solid var(--coach-rule); font-size: var(--fs-16); line-height: 1.4; }
.chat-feedback { margin: calc(var(--u2) / 2) 0 0; font: 500 var(--fs-16)/1.4 var(--font-sans); }
.chat-feedback:empty { display: none; }

/* -- Attachment chips: name, real size, status in words, then the actions as
   text buttons. A failed one carries the red square and its sentence. */
.attachments { display: flex; flex: 1 1 100%; flex-direction: column; gap: calc(var(--u2) / 2); }
.attachments:empty { display: none; }
.chip { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--u2); font: 500 var(--fs-16)/1.4 var(--font-sans); }
.chip__name { font-weight: 600; overflow-wrap: anywhere; }
.chip__size { color: var(--ink-secondary); font-variant-numeric: tabular-nums; }
.chip__status { color: var(--ink-secondary); }
.chip--failed .chip__status { color: var(--ink); font-weight: 650; }
.chip__action { min-height: 0; padding: 0; border: 0; background: none; color: var(--ink); font: inherit; font-weight: 650; text-decoration: underline; text-underline-offset: 0.2em; cursor: pointer; }
.chip__note { flex: 1 1 100%; margin: 0; max-width: 30rem; }

/* -- Citations: a lookup is a stage direction too, set where it happened;
   a failed one carries the red square. */
.citations { margin: calc(var(--u2) / 2) 0 0 calc(4.5rem + var(--u2)); padding: 0 0 0 var(--u2); border-left: 1px solid var(--ink); }
.citation { margin: 0; color: var(--coach-ink); font: italic 400 var(--fs-18)/var(--u) var(--font-serif); }
.citation__lead { font-style: italic; }
.citation--failed .fail { font-style: normal; }

/* -- A turn's state at its message: still words while it works, the failure
   where it happened. */
.turn-status, .turn-failed, .turn-interrupted { margin: calc(var(--u2) / 2) 0 0 calc(4.5rem + var(--u2)); font: 500 var(--fs-16)/1.4 var(--font-sans); }
.turn-status { color: var(--ink-secondary); }
/* The coach's thinking while it works: its newest words under the status,
   live only (the page makes it; a reload never shows one). Never a scroll
   box (R-VIS-33): the script keeps it to about four lines. */
.turn-thinking { margin: calc(var(--u2) / 2) 0 0 calc(4.5rem + var(--u2)); padding-left: calc(var(--u2) / 2); border-left: 1px solid var(--rule); color: var(--ink-secondary); font: 400 var(--fs-16)/1.4 var(--font-sans); white-space: pre-wrap; overflow-wrap: anywhere; }
.turn-failed .fail { margin: 0; }
.msg--live[hidden] { display: none; }
.msg-user .msg-bubble { white-space: pre-wrap; overflow-wrap: anywhere; }
.msg-assistant + .receipts + .msg-assistant .msg-meta,
.msg-assistant + .msg-assistant .msg-meta { visibility: hidden; }

/* -- Photos in a message take the text column, beside the words. */
.msg-images { display: flex; flex-wrap: wrap; gap: var(--u2); margin: 0 0 calc(var(--u2) / 2); padding: 0; list-style: none; }
.msg-image a, .msg-image--gone { display: grid; gap: 0.2rem; max-width: 7.5rem; color: var(--ink); font: 500 var(--fs-16)/1.3 var(--font-sans); text-decoration: none; overflow-wrap: anywhere; }
.msg-image img { width: 7.5rem; height: 7.5rem; object-fit: cover; border: 1px solid var(--coach-rule); }
.vision-which summary { cursor: pointer; font: 500 var(--fs-16)/var(--u) var(--font-sans); }
.vision-which ul { margin: 0; padding-left: 1.2em; font: 500 var(--fs-16)/1.4 var(--font-sans); }
.receipt { margin: 0; }
.receipt__mark--running::before { content: "…"; }
.receipt--partial .receipt__text { font-style: normal; }

/* -- Earlier correspondence: a section head and a ledger, like the folio. No
   scroll box inside a scrolling page (a magnifier trap at 200%). Each row is
   a real link that opens the conversation in the viewer below. */
.archive { max-height: none; overflow: visible; }
.archive__head {
  margin: var(--u) 0 var(--u2);
  padding-top: var(--u2);
  border-top: 1px solid var(--ink);
  font: 650 var(--fs-24)/var(--u) var(--font-display);
  font-variation-settings: "SOFT" 0, "WONK" 0;
  letter-spacing: -0.02em;
}
.archive-list { margin: 0; padding: 0; list-style: none; }
a.archive-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0 var(--u2);
  padding: calc(var(--u2) / 2) 0;
  border-bottom: 1px solid var(--coach-rule);
  color: var(--ink);
  text-decoration: none;
}
a.archive-row:hover .archive-row__title { text-decoration: underline; text-underline-offset: 0.2em; }
.archive-row__title { grid-column: 1; grid-row: 1; font: 600 var(--fs-18)/var(--u) var(--font-sans); }
.archive-row__date { grid-column: 1; grid-row: 2; color: var(--ink-secondary); font: 500 var(--fs-16)/1.3 var(--font-sans); }
.archive-row__current { grid-column: 2; grid-row: 1; color: var(--ink-secondary); font: 500 var(--fs-16)/var(--u) var(--font-sans); }
form.new-conversation { display: block; }
form.new-conversation > button.ink {
  min-height: 0;
  padding: var(--u2) 0 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: 650 var(--fs-18)/var(--u) var(--font-sans);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
form.new-conversation > button.ink:hover { background: none; color: var(--accent); }
.archive-viewer:empty { display: none; }
.archive-viewer { margin-top: var(--u); padding-top: var(--u2); border-top: 1px solid var(--ink); }
.archive-viewer__head { margin: 0; font: 650 var(--fs-18)/var(--u) var(--font-sans); }
.archive-viewer__date { color: var(--ink-secondary); font-weight: 500; }

/* -- The conversations page's folio: search, then the hits or the ledger. */
.conversations-page { padding-block: var(--u); }
.hits { margin: var(--u) 0 0; padding: 0; list-style: none; }
.hit__head { margin: 0; font: 600 var(--fs-18)/var(--u) var(--font-sans); }
.hit__lines { margin: calc(var(--u2) / 2) 0 0; padding: 0; list-style: none; }
.hit-line { font: 500 var(--fs-16)/1.4 var(--font-sans); color: var(--ink-secondary); }
.hit-line--match { color: var(--ink); }
.hit-line__who { font: 700 var(--fs-14)/var(--u) var(--font-sans); letter-spacing: 0.12em; text-transform: uppercase; }

/* -- Stacked below the folio, or in a short window, nothing sticks. */
@media (max-width: 64rem), (max-height: 40rem) {
  .composer { position: static; }
}
/* Phone: the speaker label sits above the speech, so the coach's lines run
   about 40 characters instead of 29. Send is sized to its word. */
@media (max-width: 48rem) {
  .msg { grid-template-columns: minmax(0, 1fr); }
  .msg > :is(.msg-images, .msg-bubble) { grid-column: 1; }
  .msg-meta { display: flex; gap: 0.5em; align-items: baseline; }
  .receipts, .citations, .turn-status, .turn-thinking, .turn-failed, .turn-interrupted { margin-left: 0; }
  .chat-form button[type="submit"] { flex: none; width: auto; padding: 0 var(--u); }
}

/* ==== 70-public.css ==== */
/* ============================================================================
   Legal pages and the install affordance (platform; R-AUTH-54, R-PLAT-3).

   The legal pages are the shell's bare page (no rail): the folio line, the
   page's own heading and dek, then side-head sections of prose on the content
   axis. The install affordance lives in the shell's #install-slot at the end
   of the band's folio line; it takes no space until 70-install.js fills it
   (02-shell.css), and it never prints (90-print.css).
   ============================================================================ */

/* -- Legal pages ------------------------------------------------------------ */
.legal__up { margin: 0 0 var(--u2); font: 650 var(--fs-16)/var(--u) var(--font-sans); letter-spacing: 0.02em; }
.legal { margin-top: var(--u); }
.legal .side-body > p { max-width: 36em; margin: 0 0 var(--u2); }
.legal .side-body strong { font-weight: 700; }
.legal__foot { margin: var(--u) 0 0; color: var(--ink-secondary); }

/* -- The install affordance -------------------------------------------------- */
.install-slot { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.6em; }
.install-slot__hint { color: var(--band-muted); }

/* ==== 90-print.css ==== */
/* ============================================================================
   Print: Letter pages common to every page (R-PAGE-56, R-PAGE-1).

   Paper is light whatever the screen's scheme. The black band becomes the
   page's head on the paper itself: the heaviest rule opens it, a 3px rule
   closes it, the type is black, and a painting on its transparent ground sits
   on the paper like gouache. The coach, the page strip and the folio line's
   links and forms don't print. Each partial adds its own page-specific rules.
   ============================================================================ */
@media print {
  @page { size: letter; margin: 14mm 16mm; }
  :root {
    color-scheme: light;
    --canvas: #ffffff;
    --paper: #ffffff;
    --paper-subtle: #f2f4f5;
    --ink: #0a0c0e;
    --ink-muted: #4d5861;
    --ink-secondary: #454f57;
    --rule: #d3d8dc;
    --rule-strong: #68737c;
    --accent: #005ea8;
    --fail: #b3261e;
    --hatch: #5b6168;
    --gutter: 0px;   /* the page margin is the gutter */
  }
  body, .sheet { min-height: 0; height: auto; background: #fff; }
  .sheet { display: block; width: auto; margin: 0; }
  .rail,
  .page-strip,
  .skip-link,
  .install-slot,
  .folio-line a,
  .folio-line form,
  form.write,
  form.coach-say,
  .save-error { display: none !important; }
  .folio { padding: 0; }
  .band {
    margin: 0 0 var(--u);
    padding: var(--u2) 0 var(--u);
    background: none;
    color: #000;
    border-top: var(--rule-heavy) solid #000;
    border-bottom: var(--rule-mid) solid #000;
  }
  .folio-line { color: #000; border-bottom-color: #000; }
  .band h1, .facts, .fig--band .fig__n, .dek, .kicker { color: #000; }
  .fig--band .unit { color: var(--ink-secondary); }
  /* One cover size on paper, whatever step the screen chose, so a recipe holds
     to one page at every window width. */
  .band h1 { font-size: var(--fs-56) !important; line-height: 56px !important; max-width: none; }
  .side, .doc-section, .ruled > li, .bar, .spark { break-inside: avoid; }
  .side-head { break-after: avoid; }
}
