/* components.css — build step 0.
   App shell, card, grouped list + row, buttons, chip, segmented control,
   bottom sheet, toast, skeleton, empty state, macro.
   Values come from tokens.css only. */

/* ---------------------------------------------------------------- reset */

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

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

/* ------------------------------------------------- which ink, and why
   On the dark theme the page is iron and the cards are butcher paper, so
   "secondary text" is two different colours depending on what is underneath.
   Rather than every rule knowing where it sits, three inherited variables
   carry the answer down the tree: the page points them at the on-bg family,
   and every surface re-points them at the on-surface family. A rule just asks
   for --t-2 and gets whichever is right.

   On the light theme both families hold the same values, so this costs
   nothing and changes nothing. */

:root {
  --t-1: var(--ink-on-bg);
  --t-2: var(--ink-on-bg-2);
  --t-3: var(--ink-on-bg-3);
  --t-accent: var(--accent);
  --t-line: var(--line-on-bg);
}

.card, .glist, .qcard, .sheet, .segmented, .chip, .build, .consequence,
.btn--quiet, .btn--destructive, .check, .field__input, .copybox, .wall__cell,
.nstep, .sheet__foot {
  --t-1: var(--ink);
  --t-2: var(--ink-2);
  --t-3: var(--ink-3);
  --t-accent: var(--accent-on-surface);
  --t-line: var(--line);
  color: var(--t-1);
}

/* Two surfaces that are neither the page nor paper: they carry their own. */
.toast { --t-1: var(--toast-ink); --t-2: var(--toast-ink); --t-3: var(--toast-ink); }
.tabbar { --t-1: var(--tab-ink); --t-2: var(--tab-ink-2); --t-3: var(--tab-icon); --t-line: var(--tabbar-line); }
.btn--primary { --t-1: var(--on-accent); --t-2: var(--on-accent); }

body {
  background: var(--bg);
  color: var(--t-1);
  font-family: var(--font);
  font-size: var(--size-body);
  line-height: var(--line-body);
  font-weight: var(--weight-regular);
  font-variant-numeric: tabular-nums;   /* tabular numerals everywhere */
  -webkit-font-smoothing: antialiased;
}

button, input, select, textarea { font: inherit; color: inherit; font-variant-numeric: inherit; }
button { background: none; border: 0; cursor: pointer; }
ul, ol { list-style: none; }

/* The ring is a fill, not text: the grease-pencil red reads on iron and on
   paper alike, so it stays --accent at both themes rather than deferring to
   whatever surface it happens to land on. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--sp-1);
}

/* -------------------------------------------------------------- type */

.t-display { font-size: var(--size-display); line-height: var(--line-display); font-weight: var(--weight-strong); letter-spacing: 0; }
.t-title   { font-size: var(--size-title);   line-height: var(--line-title);   font-weight: var(--weight-strong); letter-spacing: 0; }
.t-body    { font-size: var(--size-body);    line-height: var(--line-body);    font-weight: var(--weight-regular); }
.t-body-strong { font-size: var(--size-body); line-height: var(--line-body); font-weight: var(--weight-strong); }
.t-caption { font-size: var(--size-caption); line-height: var(--line-caption); font-weight: var(--weight-regular); }

.ink-2 { color: var(--t-2); }
.ink-3 { color: var(--t-3); }

/* ------------------------------------------------------------- shell */

.app {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.app__header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);
  border-bottom: var(--hairline) solid var(--t-line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  min-height: var(--tap);
}
.app__header-inner {
  width: 100%;
  max-width: var(--content);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}
.app__wordmark {
  font-size: var(--size-title);
  line-height: var(--line-title);
  font-weight: var(--weight-strong);
  color: var(--t-1);
}

.avatar {
  width: var(--tap);
  height: var(--tap);
  display: grid;
  place-items: center;
  margin-right: calc(var(--sp-2) * -1);   /* optical edge, keeps 44 px target */
}
.avatar__disc {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background: var(--avatar-bg);
  border: var(--hairline) solid var(--avatar-line);
  color: var(--avatar-ink);
  display: grid;
  place-items: center;
  font-size: var(--size-caption);
  line-height: 1;
  font-weight: var(--weight-strong);
}

.app__main {
  flex: 1;
  width: 100%;
  max-width: var(--content);
  margin: 0 auto;
  padding: var(--sp-4) var(--sp-4) calc(var(--tap) + var(--sp-8) + env(safe-area-inset-bottom));
}

/* Bottom tab bar — always at the bottom, at every width. On the dark theme it
   is the legacy nav: near-black #121316 under a 1 px #2E2F33 rule. */
.tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 30;
  background: var(--tabbar-bg);
  border-top: var(--hairline) solid var(--tabbar-line);
  box-shadow: var(--shadow-lift);
  padding-bottom: env(safe-area-inset-bottom);
}
.tabbar__inner {
  max-width: var(--content);
  margin: 0 auto;
  display: flex;
}
/* 3 px of transparent rule on every tab, so the active one's red line does not
   move the label by 3 px when it appears — the legacy nav's trick, kept. */
.tab {
  flex: 1;
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-1) var(--sp-2);
  border-top: 3px solid transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  text-decoration: none;
  color: var(--t-2);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  font-weight: var(--weight-regular);
  transition: color var(--dur-fast) var(--ease-out);
}
.tab svg { width: 22px; height: 22px; color: var(--t-3); transition: color var(--dur-fast) var(--ease-out); }
.tab[aria-current="page"] { color: var(--t-1); font-weight: var(--weight-strong); border-top-color: var(--tab-rule); }
.tab[aria-current="page"] svg { color: var(--t-1); }

/* --------------------------------------------------------- macro */

.macro {
  display: inline;
  font-variant-numeric: tabular-nums;
  color: var(--t-2);
  white-space: normal;
}
.macro--caption { font-size: var(--size-caption); line-height: var(--line-caption); }
.macro--body    { font-size: var(--size-body);    line-height: var(--line-body); }
.macro__kcal { color: var(--t-1); font-weight: var(--weight-strong); }
/* --ink-3 is for large text and icons only; every part of the macro is small
   text, so units and separators stay on --ink-2 (6.2:1). */
.macro__sep  { color: var(--t-2); padding: 0 2px; }
.macro__unit { color: var(--t-2); }
.macro__label { color: var(--t-2); }

/* ---------------------------------------------------------- card */

.card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-4);
  box-shadow: var(--shadow);
}
.card + .card { margin-top: var(--sp-3); }

/* A card that is itself the button. The colour is restated rather than
   inherited: a <button> resets its own colour, and the card's ink comes from
   the surface it is, not from the page under it. */
button.card, a.card {
  display: block;
  width: 100%;
  text-align: left;
  text-decoration: none;
  color: var(--t-1);
  transition: background-color var(--dur-fast) var(--ease-out);
}
button.card:active, a.card:active { background: var(--surface-2); }

.card__cta {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: var(--hairline) solid var(--t-line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  color: var(--t-accent);
}

/* ---------------------------------------------- grouped list + row */

.section { margin-top: var(--sp-8); }
.section:first-child { margin-top: 0; }
.section__head {
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  font-weight: var(--weight-regular);   /* two weights only; h2 default 700 is not one */
  color: var(--t-2);
  margin: 0 var(--sp-1) var(--sp-2);
}

/* Inside a 16 px stack the section's own 32 px would compound to 48. */
.stack-4 > .section { margin-top: var(--sp-4); }
.stack-4 > .section:first-child { margin-top: 0; }

.glist {
  background: var(--surface-2);
  border-radius: var(--radius);
  border: var(--hairline) solid var(--t-line);
  overflow: hidden;
}
.glist--plain { background: var(--surface); }
.glist--warn { border-color: var(--danger); }
.glist--warn .row__title { color: var(--danger); }

.row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: var(--row-min);
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
  color: var(--t-1);
  text-decoration: none;
  border-top: var(--hairline) solid var(--t-line);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.row:first-child { border-top: 0; }
button.row:active, a.row:active { background: var(--press); }

.row__lead {
  flex: 0 0 auto;
  min-width: 24px;
  color: var(--t-3);
  display: flex;
  align-items: center;
  justify-content: center;
}
.row__main  { flex: 1 1 auto; min-width: 0; }
.row__title { font-size: var(--size-body); line-height: var(--line-body); font-weight: var(--weight-strong); }
.row__title--regular { font-weight: var(--weight-regular); }
.row__meta  { font-size: var(--size-caption); line-height: var(--line-caption); color: var(--t-2); margin-top: 2px; }
.row__macro { margin-top: 2px; }
.row__trail { flex: 0 0 auto; color: var(--t-3); display: flex; align-items: center; gap: var(--sp-2); }
.row__trail .t-caption { color: var(--t-2); }
.chevron { width: 18px; height: 18px; }

/* ------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-control);
  font-size: var(--size-body);
  line-height: var(--line-body);
  font-weight: var(--weight-strong);
  transition: background-color var(--dur-fast) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}
.btn--block { display: flex; width: 100%; }

.btn--primary { background: var(--accent); color: var(--on-accent); }
.btn--primary:active { opacity: .88; }

.btn--quiet {
  background: var(--surface);
  color: var(--t-1);
  border: var(--hairline) solid var(--t-line);
}
.btn--quiet:active { background: var(--surface-2); }

.btn--destructive {
  background: var(--surface);
  color: var(--danger);
  border: var(--hairline) solid var(--t-line);
}
.btn--destructive:active { background: rgba(166, 61, 44, .06); }

.btn[disabled] { opacity: .5; cursor: default; }

/* --------------------------------------------------------- chip */

.chip {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--radius-full);
  background: var(--surface);
  border: var(--hairline) solid var(--t-line);
  color: var(--t-1);
  font-size: var(--size-body);
  line-height: var(--line-body);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.chip[aria-pressed="true"], .chip--selected {
  background: var(--accent-soft);
  border-color: var(--t-accent);
  color: var(--t-accent);
  font-weight: var(--weight-strong);
}
.chip--static { min-height: 28px; padding: var(--sp-1) var(--sp-3); font-size: var(--size-caption); line-height: var(--line-caption); color: var(--t-2); cursor: default; }

.chiprow { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* --------------------------------------------- segmented control */

.segmented {
  display: flex;
  gap: var(--sp-1);
  padding: var(--sp-1);
  background: var(--surface-2);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius-control);
}
.segmented__seg {
  flex: 1;
  min-height: var(--tap);   /* Apple HIG 44 pt, so the control stands at 52 */
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--sp-1);
  color: var(--t-2);
  font-size: var(--size-body);
  line-height: var(--line-body);
  transition: background-color var(--dur) var(--ease-in-out),
              color var(--dur) var(--ease-in-out);
}
.segmented__seg[aria-selected="true"] {
  background: var(--surface);
  color: var(--t-accent);
  font-weight: var(--weight-strong);
  box-shadow: var(--shadow);
}

/* -------------------------------------------------- bottom sheet */

.sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--scrim);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}
.sheet-scrim[data-open="true"] { opacity: 1; }

.sheet {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 41;
  margin: 0 auto;
  max-width: var(--content);
  background: var(--surface);
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: var(--shadow-lift);
  display: flex;
  flex-direction: column;
  max-height: 92dvh;
  transform: translateY(var(--sheet-travel));
  opacity: 0;
  transition: transform var(--dur-slow) var(--ease-out),
              opacity var(--dur-slow) var(--ease-out),
              height var(--dur-slow) var(--ease-in-out);
}
.sheet[data-open="true"] { transform: translateY(0); opacity: 1; }
.sheet[data-dragging="true"] { transition: none; }

.sheet__grip {
  flex: 0 0 auto;
  padding: var(--sp-2) 0 var(--sp-1);
  display: grid;
  place-items: center;
  touch-action: none;
  cursor: grab;
}
.sheet__handle {
  width: 36px;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--t-line);
}
.sheet__head {
  flex: 0 0 auto;
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
  border-bottom: var(--hairline) solid var(--t-line);
}
.sheet__body {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: var(--sp-4) var(--sp-4) calc(var(--sp-8) + env(safe-area-inset-bottom));
}
.sheet__foot {
  flex: 0 0 auto;
  padding: var(--sp-3) var(--sp-4) calc(var(--sp-4) + env(safe-area-inset-bottom));
  border-top: var(--hairline) solid var(--t-line);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* --------------------------------------------------------- toast */

.toast-layer {
  position: fixed;
  left: 0; right: 0;
  /* Clears the tab bar normally; an open sheet overrides --toast-bottom so the
     toast clears that sheet's footer instead. */
  bottom: var(--toast-bottom, calc(var(--tap) + var(--sp-3) + env(safe-area-inset-bottom)));
  z-index: 50;
  transition: bottom var(--dur) var(--ease-out);
  padding: 0 var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  align-items: center;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  width: 100%;
  max-width: calc(var(--content) - var(--sp-8));
  background: var(--toast-bg);
  color: var(--toast-ink);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow);
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--size-body);
  line-height: var(--line-body);
  opacity: 0;
  transform: translateY(var(--sp-2));
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
.toast[data-open="true"] { opacity: 1; transform: translateY(0); }
.toast__msg { flex: 1; min-width: 0; }
.toast__action {
  flex: 0 0 auto;
  min-height: var(--tap);
  padding: 0 var(--sp-3);
  color: var(--toast-ink);
  font-weight: var(--weight-strong);
  border-radius: var(--sp-1);
}
.toast__action:focus-visible { outline-color: var(--toast-ink); }

/* ------------------------------------------------------ skeleton */

/* Static by design: the motion rules forbid loops, so skeletons do not shimmer. */
/* --t-line, not --track: a skeleton stands in for text, so it takes the
   hairline of whatever it is standing on — a soft brown bar on paper, a
   #2E2F33 bar on iron — rather than one dark bar everywhere. */
.sk { background: var(--t-line); border-radius: var(--sp-1); height: 12px; }
.sk--line   { height: 12px; }
.sk--body   { height: 16px; }
.sk--title  { height: 20px; }
.sk--display{ height: 26px; border-radius: var(--sp-2); }
.sk + .sk { margin-top: var(--sp-2); }
.sk--w40 { width: 40%; } .sk--w55 { width: 55%; } .sk--w70 { width: 70%; }
.sk--w85 { width: 85%; } .sk--w100 { width: 100%; }

/* --------------------------------------------------- empty state */

.empty {
  text-align: center;
  padding: var(--sp-8) var(--sp-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
}
.empty__line { color: var(--t-2); max-width: 32ch; }

/* -------------------------------------------------------- misc */

.stack   { display: flex; flex-direction: column; gap: var(--sp-2); }
.stack-3 { display: flex; flex-direction: column; gap: var(--sp-3); }
.stack-4 { display: flex; flex-direction: column; gap: var(--sp-4); }
.hr { height: var(--hairline); background: var(--t-line); border: 0; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* First render: rows settle, 20 ms apart. */
@media (prefers-reduced-motion: no-preference) {
  .settle > * {
    animation: settle var(--dur) var(--ease-out) both;
    animation-delay: calc(var(--i, 0) * var(--stagger));
  }
  @keyframes settle {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
}

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

.screenhead { min-width: 0; }
.screenhead__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: var(--tap);
  margin-bottom: calc(var(--sp-2) * -1);
}

.stepper {
  flex: 0 0 auto;
  display: flex;
  gap: var(--sp-1);
  margin-right: calc(var(--sp-2) * -1);
}
.stepper__btn {
  min-width: var(--tap);
  min-height: var(--tap);
  width: var(--tap);
  height: var(--tap);
  display: grid;
  place-items: center;
  border-radius: var(--radius-control);
  color: var(--t-2);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.stepper__btn svg { width: 20px; height: 20px; }
.stepper__btn:active { background: var(--surface-2); }
.stepper__btn[disabled] { color: var(--t-3); opacity: .45; cursor: default; }

/* -------------------------------------------------------- checkbox */

.check {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-control);
  border: 1.5px solid var(--t-3);
  background: var(--surface);
  display: grid;
  place-items: center;
  color: var(--on-accent);
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.check[data-checked="true"] { background: var(--accent); border-color: var(--accent); }
.check svg { width: 16px; height: 16px; }

/* A row whose item is in the basket: text recedes, the number goes with it. */
.row--got .row__title,
.row--got .row__meta { color: var(--t-3); }
.row--got .row__title { text-decoration: line-through; text-decoration-thickness: 1px; }

/* ------------------------------------------------------ text field */

.field { display: flex; flex-direction: column; gap: var(--sp-1); }
.field__label { color: var(--t-2); }
.field__input {
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius-control);
  font-size: var(--size-body);
  line-height: var(--line-body);
  color: var(--t-1);
}
.field__input::placeholder { color: var(--t-3); }
/* A typed figure with its unit beside it. The unit is not part of the field:
   it is a label, so the tap target stays the input itself. */
.field__pair { display: flex; align-items: center; gap: var(--sp-2); }
.field__pair .field__input { flex: 1 1 auto; min-width: 0; }
.field__unit { color: var(--t-2); flex: 0 0 auto; }
.field__input--num { font-variant-numeric: tabular-nums; }
.field__input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
.field__hint { color: var(--t-2); }

/* ------------------------------------------------------------ week */

/* Leading slot: the day itself. Fixed width so seven rows line up. */
.daylead {
  width: 34px;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.15;
}
.daylead__wd { font-size: var(--size-caption); color: var(--t-2); }
.daylead__n  { font-size: var(--size-body); font-weight: var(--weight-strong); color: var(--t-1); }

.row--today { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }
.row--today .row__title { color: var(--t-accent); }
.row--today .daylead__wd { color: var(--t-accent); }
.row--today .daylead__n  { color: var(--t-accent); }

/* The doc asks for --ink-3 on past rows, but --ink-3 is documented as large
   text and icons only and these rows are 13 px and 16 px. Past rows recede to
   --ink-2 (6.2:1) instead: against today's accent and the future's --ink they
   still read as done, and they stay legible. */
.row--past .row__title,
.row--past .row__meta,
.row--past .daylead__wd,
.row--past .daylead__n { color: var(--t-2); }
.row--past .row__title { font-weight: var(--weight-regular); }

/* Not a row: a note that sits inside a grouped list where a row would. */
.note {
  padding: var(--sp-3) var(--sp-4);
  border-top: var(--hairline) solid var(--t-line);
  color: var(--t-2);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
}
.note:first-child { border-top: 0; }
.note--done { color: var(--t-accent); }

/* A quiet strip that states a consequence before it is applied. */
.consequence {
  background: var(--surface-2);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius-control);
  padding: var(--sp-3);
  color: var(--t-2);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
}
.consequence b { color: var(--t-1); font-weight: var(--weight-strong); }

/* The copy-out fallback, for browsers that refuse the clipboard. */
.copybox {
  width: 100%;
  min-height: 220px;
  padding: var(--sp-3);
  background: var(--surface-2);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius-control);
  font-family: var(--font);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  color: var(--t-1);
  resize: vertical;
}

/* Week is a scan view: a long dish name is clamped to two lines there. The
   Day sheet and Tonight always show it whole. */
.row__title--clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* A note the engine attached to a trip: something it could not do for you.
   Informational, not destructive — the palette has no warning colour and this
   is not the place to invent one. */
.note--warn {
  color: var(--t-2);
  box-shadow: inset 2px 0 0 var(--t-3);
  background: var(--surface-2);
}

.screenhead__eyebrow { color: var(--t-2); }

/* A row that is listed but cannot be used yet. */
.row--disabled .row__title { color: var(--t-2); }
.row--disabled .row__meta  { color: var(--t-3); }

/* ------------------------------------------------------ caption card
   The quote and a screen's standing subtitle. A card with no shadow and no
   action: it is prose, not a control, so it must not look pressable. */

.qcard {
  background: var(--surface-2);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius);
  padding: var(--sp-4);
}
.qcard__eyebrow { margin-bottom: var(--sp-1); }
.qcard__line { color: var(--t-2); }

/* ------------------------------------------------------- disclosure
   A row that opens in place. The summary carries the row's own metrics so a
   grouped list of rows and disclosures lines up. */

.disclosure { display: block; }
.disclosure__sum {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: var(--row-min);
  padding: var(--sp-3) var(--sp-4);
  border-top: var(--hairline) solid var(--t-line);
  cursor: pointer;
  list-style: none;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.disclosure__sum::-webkit-details-marker { display: none; }
.disclosure:first-child > .disclosure__sum { border-top: 0; }
.disclosure__sum:active { background: var(--press); }
.disclosure__chev {
  transition: transform var(--dur) var(--ease-out);
}
.disclosure[open] > .disclosure__sum .disclosure__chev { transform: rotate(90deg); }
.disclosure__body {
  padding: 0 var(--sp-4) var(--sp-4);
  border-top: var(--hairline) solid var(--t-line);
  padding-top: var(--sp-3);
}
.disclosure__body > * + * { margin-top: var(--sp-3); }

/* --------------------------------------------------- block progress
   Day 29 of 100, and how much of the wall is crossed off. A caption line and
   a 4 px rule — no percentage badge, no celebration. */

.progress { display: flex; flex-direction: column; gap: var(--sp-2); }
.progress__line { color: var(--t-2); }
.progress__line b { color: var(--t-1); font-weight: var(--weight-strong); }
.progress__bar {
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--track);
  overflow: hidden;
}
.progress__fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-full);
  transition: width var(--dur-slow) var(--ease-in-out);
}

/* ------------------------------------------------------------- wall
   The 100-day grid the legacy app calls the wall, as a jump control. Seven
   columns of 44 px targets: 7 x 44 + 6 x 4 = 332, inside 358 at 390 px. */

.wall { display: flex; flex-direction: column; gap: var(--sp-3); }
.wall__head {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--sp-1);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  color: var(--t-2);
  text-align: center;
}
.wall__block { display: flex; flex-direction: column; gap: var(--sp-1); }
.wall__label {
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  color: var(--t-2);
}
.wall__row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--sp-1);
}
.wall__cell {
  min-height: var(--tap);
  min-width: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius-control);
  border: var(--hairline) solid var(--t-line);
  background: var(--surface);
  color: var(--t-1);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.wall__cell:active { background: var(--surface-2); }
.wall__cell[data-done="true"] {
  color: var(--t-2);
  background: var(--surface-2);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
.wall__cell[aria-current="date"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: var(--weight-strong);
}
.wall__pad { min-height: var(--tap); }

/* -------------------------------------------------- the day's totals
   A grouped list whose rows are one label and one macro. The sum row and the
   target row carry the strong weight; the remainder is the only coloured one
   and it is coloured by nothing — "over" is a fact, not a failure. */

.totals .row__title { font-weight: var(--weight-regular); }
.totals .row--sum .row__title { font-weight: var(--weight-strong); }
.totals .row--sum { background: var(--surface); }

.macro--left .macro__kcal { font-weight: var(--weight-strong); }

/* ------------------------------------------------------ recipe book
   Meal detail's own blocks: the macro line as published, the plate line, and
   the two weigh-order build lines. All quoted, never re-formatted. */

.mline {
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  color: var(--t-2);
}
.mline b { color: var(--t-1); font-weight: var(--weight-strong); }

.build {
  background: var(--surface-2);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius-control);
  padding: var(--sp-3);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  color: var(--t-2);
}
.build__lead { color: var(--t-2); }
.build b { color: var(--t-1); font-weight: var(--weight-strong); }
.build__tare { color: var(--t-3); padding: 0 var(--sp-1); }
.build + .build { margin-top: var(--sp-2); }

/* One loading-chart entry: name, the three loads, the build line under it. */
.ld { padding: var(--sp-3) var(--sp-4); border-top: var(--hairline) solid var(--t-line); }
.ld:first-child { border-top: 0; }
.ld__name { font-size: var(--size-body); line-height: var(--line-body); font-weight: var(--weight-strong); }
.ld__nums { font-size: var(--size-caption); line-height: var(--line-caption); color: var(--t-2); margin-top: 2px; }
.ld__nums b { color: var(--t-1); font-weight: var(--weight-strong); }
.ld__build { font-size: var(--size-caption); line-height: var(--line-caption); color: var(--t-2); margin-top: var(--sp-1); }

/* Prose inside a sheet: the protocol, the loading-chart notes. */
.prose > p { font-size: var(--size-body); line-height: var(--line-body); color: var(--t-2); }
.prose > p + p { margin-top: var(--sp-3); }
.prose b { color: var(--t-1); font-weight: var(--weight-strong); }

/* The worked day template inside its disclosure: same shape as the totals
   list, but its figures are the page's own strings, not macro components. */
.tmpl .row__title { font-weight: var(--weight-regular); }
.tmpl .row--sum .row__title { font-weight: var(--weight-strong); }
.tmpl .row--sum { background: var(--surface); }
.tmpl .row__trail .t-caption { color: var(--t-2); }
.tmpl .row--sum .row__trail .t-caption { color: var(--t-1); font-weight: var(--weight-strong); }

/* Which meal a build line is for. The engine's line says how to weigh, not
   what for, so the label sits above it rather than inside the quote. */
.build__cap { margin-bottom: var(--sp-1); }

/* ------------------------------------------- the day sheet timeline
   Tonight's "Today" list, in the order the legacy day sheet prints it. One
   row per slot: the slot name in the leading column, what is eaten, its macro,
   and whatever the card carries underneath — the weigh order, a second item in
   the same slot, or the note that day adds.

   The row is not the button. Most slots have nothing to open, and the ones
   that do open a recipe — a side errand, not the row's purpose — so the link
   is an explicit 44 px target in the trailing slot rather than the whole row.
   That also keeps a sub-item's own link out of a nested button. */

.tl .row { align-items: flex-start; }

.row__lead--slot {
  min-width: var(--slot);
  justify-content: flex-start;
  padding-top: 3px;
  color: var(--t-2);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
}

/* A second thing eaten in the same slot: the preworkout under breakfast, the
   lunch shake, the cappuccino. Set in from the meal it belongs to, under the
   accent rule the legacy card puts down the side of its build blocks. */
.tl__sub {
  margin-top: var(--sp-2);
  padding: var(--sp-1) 0 var(--sp-1) var(--sp-3);
  box-shadow: inset 2px 0 0 var(--accent-on-surface);
}
.tl__sub-title { font-size: var(--size-body); line-height: var(--line-body); }
.tl__note { margin-top: var(--sp-2); color: var(--t-2); }
.tl .build { margin-top: var(--sp-2); }

/* The legacy "recipe" link: underlined, accent-coloured, and a real target. */
.tl__link {
  min-height: var(--tap);
  display: inline-flex;
  align-items: center;
  padding: 0 var(--sp-2);
  color: var(--t-accent);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  font-weight: var(--weight-strong);
  text-decoration: underline;
  text-underline-offset: 3px;
  border-radius: var(--sp-1);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.tl__link:active { background: var(--press); }
.tl__sub .tl__link { margin-left: calc(var(--sp-2) * -1); }

/* The dark theme's quote card is the legacy sermon: paper with a
   grease-pencil rule down its left edge. */
[data-theme="dark"] .qcard { border-left: 3px solid var(--accent); }

/* The recipe link on its own line under the meal it belongs to. */
.tl .row__main > div > .tl__link,
.tl .row__main > .tl__link { margin-left: calc(var(--sp-2) * -1); margin-top: var(--sp-1); }

/* ============================================================ five tabs
   Today · Meal plan · Groceries · Recipes · Settings. At 360 px each tab has
   72 px, so the label must never wrap and the padding gives way before the
   type does — 13 px is the floor the type scale sets and nothing here goes
   under it. */

.tab span { white-space: nowrap; }

@media (max-width: 420px) {
  .tab { padding-left: 2px; padding-right: 2px; gap: 1px; }
  .tab svg { width: 20px; height: 20px; }
}

/* ------------------------------------------------- a header's own action
   The Meal plan's jump grid and Groceries' "This week", on the eyebrow line
   beside the title. A quiet button, 44 px, never wrapping. */

.headact { flex: 0 0 auto; display: flex; gap: var(--sp-2); margin-right: calc(var(--sp-2) * -1); }
.headact .btn { white-space: nowrap; }

/* --------------------------------------------------- a row's caption line
   The Meal plan's "D29 · Mon 8/17", above the dish it names. */

.row__above {
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  color: var(--t-2);
}
.row--today .row__above { color: var(--t-accent); }

/* ------------------------------------------------------- week blocks
   Both list pages are one scroll of week blocks. Meal plan's head is a plain
   band above its seven rows; Groceries' sticks under the app header while you
   are inside that week, so the aisles always say which week they belong to. */

.wkblock { margin-top: var(--sp-8); }
.wkblock:first-child { margin-top: 0; }
.stack-4 > .wkblock { margin-top: var(--sp-6); }

.wkhead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-1) var(--sp-2);
  padding: 0 var(--sp-1) var(--sp-2);
}
.wkhead__t { color: var(--t-1); }
.wkhead__r { color: var(--t-2); }
.wkhead__now,
.wkbar__now {
  color: var(--on-accent);
  background: var(--accent);
  border-radius: var(--radius-full);
  padding: 1px var(--sp-2);
}
.wkhead--now .wkhead__t { color: var(--t-accent); }

.wkbar {
  position: sticky;
  z-index: 10;
  top: calc(var(--tap) + var(--sp-4) + var(--hairline));
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-1) var(--sp-2);
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-1);
  background: var(--bg);
  border-bottom: var(--hairline) solid var(--t-line);
}
.wkbar__t { color: var(--t-1); }
.wkbar--now .wkbar__t { color: var(--t-accent); }
.wkblock__dinners { margin: var(--sp-3) var(--sp-1) 0; }

/* --------------------------------------------------------------- a trip
   One shop, inside a week block: its label, what it covers, its aisles, and
   the two things you can do with it. */

.trip { margin-top: var(--sp-6); }
.trip__head { color: var(--t-1); margin: 0 var(--sp-1); }
.trip__covers { margin: var(--sp-1) var(--sp-1) var(--sp-4); }
.trip__staples { margin: var(--sp-3) var(--sp-1) 0; }
.trip__acts { margin-top: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); }
.trip .section { margin-top: var(--sp-4); }

/* ------------------------------------------------------- back to today
   Tonight, standing on some other day. An accent rule down the left edge, the
   way the quote card carries one — it is a way back, not a warning. */

.backrow { box-shadow: inset 3px 0 0 var(--accent); }

/* ------------------------------------------------------------ settings
   A control that belongs to a row sits under it, full width, rather than in
   the trailing slot: chips and a segmented control both need the whole line
   at 360 px. */

.setrow__ctl { margin-top: var(--sp-2); }
.setrow__ctl .chiprow { gap: var(--sp-2); }
.setrow__conseq { margin: 0 var(--sp-1); }

/* ======================================================================== */
/* v2 — the generated block                                                 */
/* ======================================================================== */

/* -------------------------------------------------------------- stepper
   Minus, the value, plus. Both buttons are the 44 px minimum and the value
   between them is tabular enough that the row does not jitter as it changes. */

.nstep {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--surface-2);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius);
  padding: var(--sp-2);
}
.nstep__btn {
  flex: 0 0 auto;
  width: var(--tap);
  height: var(--tap);
  display: grid;
  place-items: center;
  border-radius: var(--radius-control);
  border: var(--hairline) solid var(--t-line);
  background: var(--surface);
  color: var(--t-1);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.nstep__btn:active { background: var(--surface-2); }
.nstep__btn[disabled] { color: var(--t-3); opacity: .45; cursor: default; }
.nstep__glyph { font-size: var(--size-title); line-height: 1; font-weight: var(--weight-strong); }
.nstep__mid {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--sp-2);
  text-align: center;
}
.nstep__val { color: var(--t-1); font-weight: var(--weight-strong); }
.nstep__unit { color: var(--t-2); }

/* A pair of steppers side by side — feet and inches. */
.nstep-pair { display: flex; gap: var(--sp-2); }
.nstep-pair > * { flex: 1 1 0; min-width: 0; }

/* --------------------------------------------------------- toggle row */

.row--toggle { width: 100%; text-align: left; }

/* ----------------------------------------------------------- onboarding
   One question a screen. The question is the only thing on it, so it gets the
   display size and the answer sits under it with the whole width. The footer
   is fixed: Back and Next never move between screens. */

.onb { display: flex; flex-direction: column; min-height: 100%; gap: var(--sp-6); }
.onb__prog { color: var(--t-2); }
.onb__bar {
  height: 3px;
  border-radius: var(--radius-full);
  background: var(--track);
  overflow: hidden;
  margin-top: var(--sp-2);
}
.onb__fill { height: 100%; background: var(--accent); border-radius: var(--radius-full); }
.onb__q { color: var(--t-1); }
.onb__hint { color: var(--t-2); margin-top: var(--sp-2); max-width: 36ch; }
.onb__body { display: flex; flex-direction: column; gap: var(--sp-4); }
.onb__foot { display: flex; gap: var(--sp-2); margin-top: var(--sp-4); }
.onb__foot .btn { flex: 1 1 0; justify-content: center; }
.onb .chiprow .chip { min-height: var(--tap); }

/* The review screen's per-person card. */
.pcard { display: flex; flex-direction: column; gap: var(--sp-3); }
.pcard__name { color: var(--t-1); }
.pcard__note { color: var(--t-2); }

/* --------------------------------------------------------- the wall
   The countdown grid, under the counter on Today the way the legacy board
   carried it. At 360 px the seven columns need a 2 px gutter to keep every
   cell at least 44 px wide: (328 - 12) / 7 = 45. */

.wall--compact .wall__row,
.wall--compact .wall__head { gap: 2px; }
.wall--compact .wall__block { gap: 2px; }
.wall--compact .wall__cell { font-size: var(--size-caption); }
.wall__cell[data-kind="bonus"] { border-style: dashed; }
.wall__cell[data-browsing="true"] {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}

/* ------------------------------------------------------- member switch
   Two people, one plate on screen. The switch sits directly under the card it
   governs, the way Tonight's did. */

.memberbar { margin-top: var(--sp-2); }

/* -------------------------------------------------------- target diff
   "Re-estimate" shows what would change BEFORE it is applied: the figure now,
   the figure after, and nothing else. */

.diff { display: flex; flex-direction: column; gap: var(--sp-2); }
.diff__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-top: var(--hairline) solid var(--t-line);
}
.diff__row:first-child { border-top: 0; }
.diff__k { color: var(--t-2); font-size: var(--size-caption); line-height: var(--line-caption); }
.diff__v { color: var(--t-1); font-weight: var(--weight-strong); }
.diff__v .diff__was { color: var(--t-3); font-weight: var(--weight-regular); text-decoration: line-through; margin-right: var(--sp-2); }

/* ------------------------------------------------------------- prose note
   The uncertainty note. It is the one paragraph the product must not bury, so
   it reads at body size on the paper rather than as a caption. */

.unote { color: var(--t-2); }

/* ------------------------------------------------------------- the plate
   How much of the dish goes on this person's plate: the grams, the macros
   under them, what it covers of the day, and the clamp when it bound. The
   cap line is quiet on purpose: it is a fact about the recipe, not a warning
   about the reader. */

.plate { display: flex; flex-direction: column; gap: var(--sp-1); }
.plate__head { color: var(--t-1); }
.plate__covers { margin: 0; }
.plate__cap { margin: 0; color: var(--t-2); }
.row__macro + .plate__covers { margin-top: var(--sp-1); }

/* --------------------------------------------------------- the check-in
   One card, at the top of Today, on the day the weigh-in is owed. It is a
   card rather than a banner because it asks for something. */

.checkin { display: flex; flex-direction: column; gap: var(--sp-3); }
.checkin__when { margin: 0; }
.checkin__done { margin: 0; color: var(--t-1); }
.checkin__entry { margin-top: var(--sp-1); }
.checkin__assess { margin-top: var(--sp-1); }

/* ------------------------------------------------------ the daily weight
   The first row of Today's group. It is a row, not a card: a weight is a
   figure the day needs, not an event. */

.weighrow .weighrow__entry { margin-top: var(--sp-2); }
.weighrow__trend { margin-top: 2px; font-variant-numeric: tabular-nums; }
.weighrow__how { margin-top: var(--sp-1); }
.weighrow .row__trail .btn { margin-left: var(--sp-2); }

/* -------------------------------------------------------- the day's rows
   engine2.dayBudget's own rows, in the member's own order. The source word
   is quiet: it says where a figure came from, not what to do. */

.srcword { color: var(--t-3); }
.row--floor { background: var(--surface); }
.row--floor[data-floor="short"] .row__title { color: var(--danger); }

/* ------------------------------------------------------------ the slots
   The member's day, in order. The name is its own button — moving a meal and
   editing it are different intentions, so they are different targets. */

.slotrow { gap: var(--sp-2); }
.slotrow__main {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  background: none;
  border: 0;
  padding: 0;
  color: inherit;
  text-align: left;
}
.slotrow__main--fixed { cursor: default; }
.slotrow__ctl { flex: 0 0 auto; display: flex; gap: var(--sp-1); }
.slotrow__btn {
  width: var(--tap);
  height: var(--tap);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius-control);
  color: var(--t-1);
}
.slotrow__btn:active { background: var(--press); }
.slotrow__btn[disabled] { color: var(--t-3); opacity: .45; cursor: default; }
.slotrow__btn svg { width: 18px; height: 18px; }
.slotmac { margin-top: var(--sp-1); }

/* ------------------------------------------------------------ the beta
   One screen of plain sentences and one button that copies a diagnostic. */

.betaline { margin-top: var(--sp-1); }
.installhint { margin-top: var(--sp-1); }
.diagbox {
  width: 100%;
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  background: var(--surface);
  border: var(--hairline) solid var(--t-line);
  border-radius: var(--radius-control);
  color: var(--t-2);
  font-size: var(--size-caption);
  line-height: var(--line-caption);
  font-family: inherit;
}

/* ------------------------------------------------- the dinner, and again
   Today's dinner card IS a button, so the second action cannot live inside
   it. It sits directly under the card with the tight gap that reads as "part
   of the same thing" rather than "the next thing down". */

.dinner { display: flex; flex-direction: column; gap: var(--sp-2); }
.dinner__reroll { display: block; }

/* How big the pan is tonight, under the plate on the card and on the recipe
   card. One quiet line: it is a fact about the cooking, not an instruction. */
.cookline { margin: var(--sp-2) 0 0; color: var(--t-2); }

/* ------------------------------------------------------------ the reroll
   One dish, dealt. The name leads, the plate follows, the consequence sits
   under both, and the tier note is quiet because a repeated vegetable is a
   fact about the week rather than a warning. */

.reroll__name { margin: 0; }
.reroll__tier { margin: var(--sp-1) 0 0; color: var(--t-2); }
.reroll__spent { margin: 0; color: var(--t-2); }

/* The trip that is being bought for more mouths than there are targets. */
.trip__scaled { margin: calc(0px - var(--sp-3)) var(--sp-1) var(--sp-4); color: var(--t-2); }

/* ------------------------------------------------------- the assistant
   A conversation on the iron wall. Both sides are PAPER — the same surface
   every card in this app is — because a second material would be a second
   thing to explain. The reader's own questions sit right and carry the
   accent's hairline; the answers sit left and carry the plain one. Neither
   is coloured: the colour in this product means "accent", and an answer is
   not an accent.

   The bubbles are 88% wide rather than 100 so the alignment is legible at
   360 px, where a full-width bubble would read as a card and lose the sense
   that one side is asking and the other answering. */

.chat__honest { margin: calc(0px - var(--sp-2)) 0 0; }

.chat__thread {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.chatmsg {
  --t-1: var(--ink);
  --t-2: var(--ink-2);
  --t-3: var(--ink-3);
  --t-accent: var(--accent-on-surface);
  --t-line: var(--line);
  color: var(--t-1);
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow);
  max-width: 88%;
  min-width: 0;
}

.chatmsg--you {
  align-self: flex-end;
  background: var(--surface-2);
  border: var(--hairline) solid var(--t-line);
}

.chatmsg--bot { align-self: flex-start; }

.chatmsg__who {
  color: var(--t-2);
  margin: 0 0 var(--sp-1);
}

.chatmsg__body { display: flex; flex-direction: column; gap: var(--sp-1); }
.chatmsg__line { margin: 0; overflow-wrap: anywhere; }
.chatmsg__line--quiet { color: var(--t-2); }

/* The ways on. Quiet buttons, wrapped, each keeping its own 44 px. */
.chatmsg__acts {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: var(--hairline) solid var(--t-line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* The opening card is an answer nobody asked for, so it is drawn as one. */
.chat__open .chatmsg { max-width: 100%; }

.chat__composer { margin-top: var(--sp-2); }
