/* Component styles — reference tokens only (see tokens.css). Mobile-first. */

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--ds-font-body);
  font-size: var(--ds-fs-body);
  line-height: var(--ds-lh-body);
  color: var(--ds-text);
  background: var(--ds-bg);
  -webkit-text-size-adjust: 100%;
}

a { color: var(--ds-link); text-decoration: none; }
a:hover { text-decoration: underline; }

.numeric { font-variant-numeric: tabular-nums; }

/* ---- App shell ---- */
.app-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--ds-surface);
  border-bottom: 1px solid var(--ds-border);
  box-shadow: var(--ds-shadow-sm);
}
.app-header__bar {
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
  max-width: var(--ds-maxw);
  margin: 0 auto;
  padding: var(--ds-space-3) var(--ds-space-4);
}
.wordmark {
  font-size: var(--ds-fs-display);
  font-weight: var(--ds-fw-bold);
  letter-spacing: -0.02em;
  color: var(--ds-ink);
}
.wordmark__dot { color: var(--ds-copper); }
/* The ᛘ rune, drawn rather than typed (Runic has patchy system-font coverage
   and remote fonts are forbidden). Sized to the wordmark's own font-size so it
   scales with it, and it inherits currentColor so dark mode needs no rule. */
.wordmark { display: inline-flex; align-items: center; gap: 0.06em; }
.wordmark__rune { width: 1.02em; height: 1.02em; flex: none; }
/* One-tap shortcut into Chat, beside the wordmark. A glyph, so the global
   link underline on hover would look wrong — swap it for a pill highlight. */
.wordmark__chat {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--ds-tap);
  min-height: var(--ds-tap);
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-fs-h2);
  line-height: 1;
}
.wordmark__chat:hover { background: var(--ds-surface-alt); text-decoration: none; }

/* Account menu: pushed to the right corner of the header bar. It reuses the
   .nav__group dropdown markup (see below), so it opens/closes with the pill-row
   groups — the toggle script queries .nav__group document-wide. */
.app-header__account { margin-left: auto; }
.app-header__account .nav__menu { left: auto; right: 0; }

/* ---- Nav (wrapping pill row, grouped into dropdowns) ---- */
/* The row wraps rather than scrolls: the sections collapse into a handful of
   dropdown groups (see below), so it stays short, and — crucially — the nav
   must keep `overflow: visible` or it would clip the absolutely-positioned
   dropdown menus. */
.nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ds-space-1);
  row-gap: var(--ds-space-1);
  max-width: var(--ds-maxw);
  margin: 0 auto;
  padding: 0 var(--ds-space-4) var(--ds-space-2);
}
/* Menu links are matched on their own too (`.nav__menu a`), not only through
   `.nav`, because the account dropdown sits in the header bar — outside the
   pill row — and must still render as pills. */
.nav a,
.nav__menu a {
  flex: 0 0 auto;
  min-height: var(--ds-tap);
  display: inline-flex;
  align-items: center;
  padding: var(--ds-space-2) var(--ds-space-3);
  border-radius: var(--ds-radius-pill);
  color: var(--ds-muted);
  font-size: var(--ds-fs-sm);
  font-weight: var(--ds-fw-medium);
  white-space: nowrap;
}
.nav a:hover,
.nav__menu a:hover { background: var(--ds-surface-alt); text-decoration: none; }
.nav a.is-active,
.nav__menu a.is-active { background: var(--ds-primary-soft); color: var(--ds-link); }

/* ---- Nav dropdown groups ---- */
/* A group collapses several sections behind one pill so the row stays short.
   <details>/<summary> gives an accessible, JS-optional disclosure; the menu is
   absolutely positioned so opening it never reflows the row. */
.nav__group { position: relative; flex: 0 0 auto; }
.nav__group > summary {
  list-style: none;                 /* hide the default disclosure triangle */
  min-height: var(--ds-tap);
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  padding: var(--ds-space-2) var(--ds-space-3);
  border-radius: var(--ds-radius-pill);
  color: var(--ds-muted);
  font-size: var(--ds-fs-sm);
  font-weight: var(--ds-fw-medium);
  white-space: nowrap;
  cursor: pointer;
}
.nav__group > summary::-webkit-details-marker { display: none; }
.nav__group > summary::after {
  content: "▾";
  font-size: 0.7em;
  color: var(--ds-muted);
}
.nav__group > summary:hover { background: var(--ds-surface-alt); }
/* Pill-row groups only: tighter horizontal padding + caret gap so the five
   groups sit closer and stay on one line in the common viewport. Keeps the
   44px min-height tap target; the corner Account pill and the dropdown menu
   items (matched under .app-header__account / .nav__menu) are untouched. */
.nav > .nav__group > summary {
  gap: 0;
  padding-left: var(--ds-space-2);
  padding-right: var(--ds-space-2);
}
.nav > .nav__group > summary::after { margin-left: 0.15em; }
.nav__group.is-active > summary { background: var(--ds-primary-soft); color: var(--ds-link); }
.nav__group[open] > summary { background: var(--ds-surface-alt); }
.nav__menu {
  position: absolute;
  top: calc(100% + var(--ds-space-1));
  left: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-1);
  min-width: 12rem;
  padding: var(--ds-space-1);
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-md);
  box-shadow: var(--ds-shadow-md);
}
/* Right-align the last group's menu so it never spills off the viewport edge. */
.nav__group:last-of-type .nav__menu { left: auto; right: 0; }
/* Divider inside a menu (Sign out, under the Account links). */
.nav__menu-sep {
  height: 0;
  margin: var(--ds-space-1) var(--ds-space-2);
  border: 0;
  border-top: 1px solid var(--ds-border);
}

/* ---- Content ---- */
main { max-width: var(--ds-maxw); margin: 0 auto; padding: var(--ds-space-5) var(--ds-space-4) var(--ds-space-8); }
h1 { font-size: var(--ds-fs-display); color: var(--ds-ink); margin: 0 0 var(--ds-space-4); line-height: var(--ds-lh-tight); }
h2 { font-size: var(--ds-fs-h2); color: var(--ds-ink); margin: var(--ds-space-6) 0 var(--ds-space-3); }

.card {
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-sm);
  padding: var(--ds-space-5);
}

/* ---- KPI tile ---- */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--ds-space-3); }
.kpi { background: var(--ds-surface); border: 1px solid var(--ds-border); border-radius: var(--ds-radius-md); padding: var(--ds-space-4); }
.kpi__label { font-size: var(--ds-fs-xs); text-transform: uppercase; letter-spacing: 0.04em; color: var(--ds-muted); }
.kpi__value { font-size: var(--ds-fs-kpi); font-weight: var(--ds-fw-bold); color: var(--ds-ink); font-variant-numeric: tabular-nums; }

/* ---- Source legend chips (chart source filter) ---- */
.chip { display: inline-flex; align-items: center; gap: var(--ds-space-1); font-size: var(--ds-fs-xs); color: var(--ds-muted); }
.chip::before { content: ""; width: 10px; height: 10px; border-radius: 2px; background: var(--dot, var(--ds-faint)); }

/* ---- Buttons ---- */
.btn {
  min-height: var(--ds-tap);
  display: inline-flex; align-items: center; justify-content: center;
  padding: var(--ds-space-2) var(--ds-space-4);
  border-radius: var(--ds-radius-md);
  border: 1px solid var(--ds-primary);
  background: var(--ds-primary);
  color: #fff;
  font-weight: var(--ds-fw-semibold);
  cursor: pointer;
}
.btn:hover { background: var(--ds-primary-strong); border-color: var(--ds-primary-strong); text-decoration: none; }
.btn--ghost { background: transparent; color: var(--ds-link); }

/* ---- Forms (real labels, visible focus) ---- */
label { display: block; font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-medium); color: var(--ds-text); margin-bottom: var(--ds-space-1); }
input, select, textarea {
  width: 100%; min-height: var(--ds-tap);
  padding: var(--ds-space-2) var(--ds-space-3);
  font: inherit; color: var(--ds-text);
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-md);
}
/* A checkbox or radio is a CONTROL, not a field: the full-width rule above is
   for things you type into. Left to inherit it, a radio stretches to the row
   width and — inside a flex row like the goal picker — squeezes its own label
   to zero width, so the text wraps a word per line and is clipped away. That
   rendered the goal cards as a tall empty box with one floating circle. Reset
   here rather than per-template, which is why several forms carried an inline
   `style="width:auto; min-height:0"` workaround. The hidden-input selectors
   further down (.seg--radio, .chip-check) match with equal specificity and
   come later, so they still win. */
input[type="checkbox"], input[type="radio"] {
  width: auto; min-height: 0; flex: 0 0 auto;
  padding: 0; margin: 0;
  accent-color: var(--ds-primary);
}
:focus-visible { outline: 3px solid var(--ds-link); outline-offset: 2px; }

/* ---- Table ---- */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: var(--ds-fs-sm); }
th, td { text-align: left; padding: var(--ds-space-2) var(--ds-space-3); border-bottom: 1px solid var(--ds-border); }
th { color: var(--ds-muted); font-weight: var(--ds-fw-semibold); }
tbody tr:nth-child(even) { background: var(--ds-surface-alt); }

/* Food log: each item is a data row plus a small edit/delete row beneath it, so
   the actions don't need their own column and the table fits a phone. The two
   rows read as one group (no zebra; the border sits under the actions row). */
.log-table tbody tr { background: none; }
.log-table td { border-bottom: 0; }
.log-table tr.log-actions td {
  border-bottom: 1px solid var(--ds-border);
  padding-top: 0; padding-bottom: var(--ds-space-3);
}
.log-table tr.log-actions:last-child td { border-bottom: 0; }
.log-actions td { display: flex; gap: var(--ds-space-3); align-items: center; font-size: var(--ds-fs-xs); }
.log-actions a { color: var(--ds-muted); }
.log-actions form { display: inline; }
.log-actions .linklike { font-size: var(--ds-fs-xs); }
/* Tighten side padding on phones so the five data columns fit without scroll. */
@media (max-width: 480px) {
  .table-wrap.card { padding-left: var(--ds-space-2); padding-right: var(--ds-space-2); }
  .log-table th, .log-table td { padding-left: var(--ds-space-2); padding-right: var(--ds-space-2); }
}

/* ---- User-authored text: never let one token widen the page --------------
   A name the user typed — or that a label scan read — can arrive as a single
   long token with no break opportunity: "THORNE-RESEARCH-BASICNUTRIENTS-2/DAY",
   a pasted URL, a run-on description. With `overflow-wrap: normal` that token
   sets the MIN-CONTENT width of its entire ancestor chain, so <main> grows past
   the viewport.

   The visible symptom is not on the page that overflows — it is the BOTTOM TAB
   BAR. That bar is `position: fixed; left: 0; right: 0`, so it sizes to the
   layout viewport; a document wider than the screen widens that viewport on
   mobile, and the bar stretches with it until its tabs march off the right
   edge. Reproduced at 320px: one 59-character food name took the document to
   741px, and one supplement name took it to 353px.

   `anywhere`, not `break-word`: these names sit in flex rows, and only
   `anywhere` also shrinks the min-content size the flex row measures against.
   `break-word` would still let the token dictate the row's minimum.

   Any NEW element rendering a user-entered name belongs in this list. Elements
   that already clip (`.pill__val`) or scroll (`.table-wrap`) do not — they are
   contained by other means. Guarded by tests/test_text_overflow_css.py. */
.fooditem__name,
.check-name { overflow-wrap: anywhere; }

.muted { color: var(--ds-muted); }
.scaffold-note {
  border-left: 3px solid var(--ds-copper);
  background: var(--ds-primary-soft);
  color: var(--ds-link);
  padding: var(--ds-space-3) var(--ds-space-4);
  border-radius: var(--ds-radius-md);
  font-size: var(--ds-fs-sm);
}

/* ---- Flash messages ---- */
.flash { padding: var(--ds-space-3) var(--ds-space-4); border-radius: var(--ds-radius-md);
  margin-bottom: var(--ds-space-4); font-size: var(--ds-fs-sm); border: 1px solid transparent; }
.flash--success { background: var(--ds-success-soft); color: var(--ds-success); border-color: var(--ds-success); }
.flash--warning { background: var(--ds-warning-soft); color: var(--ds-warning); border-color: var(--ds-warning); }

/* ---- Layout helpers ---- */
.grid-2 { display: grid; grid-template-columns: 1fr; gap: var(--ds-space-4); margin-top: var(--ds-space-4); }
@media (min-width: 640px) { .grid-2 { grid-template-columns: 1fr 1fr; } }
.section-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--ds-space-3); margin-bottom: var(--ds-space-3); }
.section-head h1, .section-head h2 { margin: 0; }
.kpi__sub { font-size: var(--ds-fs-xs); color: var(--ds-muted); margin-top: var(--ds-space-1); }
.kpi__unit { font-size: 1rem; color: var(--ds-muted); }
.tidy-list, .task-list, .check-list { list-style: none; padding: 0; margin: 0; }
.tidy-list li { padding: var(--ds-space-1) 0; }

/* ---- Badges ---- */
.badge { display: inline-block; padding: 0.1rem 0.5rem; border-radius: var(--ds-radius-pill);
  font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold); background: var(--ds-surface-alt);
  color: var(--ds-muted); border: 1px solid var(--ds-border); white-space: nowrap; }
.badge--phigh { color: var(--ds-danger); border-color: var(--ds-danger); }
.badge--pmed  { color: var(--ds-warning); border-color: var(--ds-warning); }
.badge--plow  { color: var(--ds-muted); }
.badge--tag   { color: var(--ds-accent); border-color: var(--ds-accent); }
.badge--src[data-src="manual"]      { color: var(--ds-series-manual); }
.badge--src[data-src="renpho"]      { color: var(--ds-series-renpho); border-color: var(--ds-series-renpho); }
.badge--src[data-src="dexa"]        { color: var(--ds-series-dexa); border-color: var(--ds-series-dexa); }
.badge--src[data-src="apple_health"]{ color: var(--ds-series-apple); }
.badge--src[data-src="google_fit"]  { color: var(--ds-series-google); }

/* ---- Forms ---- */
.form-grid { display: grid; grid-template-columns: 1fr; gap: var(--ds-space-3); }
@media (min-width: 560px) { .form-grid { grid-template-columns: 1fr 1fr; } }
.form-actions { display: flex; gap: var(--ds-space-3); margin-top: var(--ds-space-4); }
.filter-bar { display: flex; flex-wrap: wrap; gap: var(--ds-space-4); margin-bottom: var(--ds-space-4); }
.filter-bar label { display: inline-flex; flex-direction: column; }
.filter-bar select { width: auto; min-width: 160px; }
.card h2:first-child { margin-top: 0; }

/* ---- Measurements: RENPHO-style body-part card grid + waist-hip gauge ---- */
.measure-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ds-space-3);
  margin-top: var(--ds-space-4); }
.measure-grid--custom { grid-template-columns: 1fr; }
@media (min-width: 560px) { .measure-grid--custom { grid-template-columns: repeat(3, 1fr); } }
.measure-heading { margin-top: var(--ds-space-5); margin-bottom: var(--ds-space-1); }
.measure-hint { margin-top: 0; }
.measure-card { display: flex; flex-direction: column; gap: 2px;
  padding: var(--ds-space-3); border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-md); background: var(--ds-surface-alt); }
.measure-card__label { font-size: var(--ds-fs-xs); text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ds-muted); }
/* --ds-fs-h does not exist (the scale is display/h2/h3/body/sm/xs), so this
   silently had no font-size and the value inherited body size. h2 is the right
   step: it is the prominent-number size .mcard__value uses, and this input IS
   the card's value. */
.measure-card__input { font-size: var(--ds-fs-h2); font-weight: var(--ds-fw-semibold);
  font-variant-numeric: tabular-nums; border: 0; background: transparent; padding: 0;
  min-height: 0; width: 100%; color: var(--ds-ink); }
.measure-card__input:focus { outline: none; }
.measure-card:focus-within { border-color: var(--ds-primary); }
.measure-card__unit { font-size: var(--ds-fs-xs); color: var(--ds-muted); }
.measure-card__last { font-size: var(--ds-fs-xs); color: var(--ds-muted); }
.measure-card__name { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold);
  border: 0; border-bottom: 1px dashed var(--ds-border); background: transparent;
  padding: 0 0 2px; min-height: 0; width: 100%; color: var(--ds-ink); }
.measure-card__name:focus { outline: none; border-bottom-color: var(--ds-primary); }

.measure-derived { display: grid; grid-template-columns: 1fr; gap: var(--ds-space-3);
  margin-top: var(--ds-space-4); }
@media (min-width: 560px) { .measure-derived { grid-template-columns: 1fr 1fr; } }
.measure-derived .estimate-box { margin-top: 0; }
.whr-card { padding: var(--ds-space-4); border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-md); background: var(--ds-surface-alt); }
.whr-gauge { margin-top: var(--ds-space-2); }
.whr-gauge__labels { display: flex; justify-content: space-between;
  font-size: var(--ds-fs-xs); color: var(--ds-muted); }
/* Scale numbers sit above the band seams (53.3% / 66.7% = the 0.96 / 1.00
   cutoffs on the 0.80–1.10 visible track), positioned from inline `left`. */
.whr-gauge__scale { position: relative; height: 1.1em;
  font-size: var(--ds-fs-xs); color: var(--ds-muted); }
.whr-gauge__scale span { position: absolute; transform: translateX(-50%); }
.whr-gauge__track { position: relative; height: 8px; margin: 4px 0;
  border-radius: var(--ds-radius-pill);
  background: linear-gradient(to right,
    var(--ds-info) 0%, var(--ds-info) 53%,
    var(--ds-success) 53%, var(--ds-success) 67%,
    var(--ds-warning) 67%, var(--ds-warning) 100%); }
.whr-gauge__marker { position: absolute; top: 50%; left: 50%; width: 14px; height: 14px;
  border-radius: 50%; background: var(--ds-surface); border: 3px solid var(--ds-ink);
  transform: translate(-50%, -50%); transition: left 0.15s ease; }
.whr-gauge__marker[data-band="low"] { border-color: var(--ds-info); }
.whr-gauge__marker[data-band="standard"] { border-color: var(--ds-success); }
.whr-gauge__marker[data-band="high"] { border-color: var(--ds-warning); }

/* ---- Reminder opt-in (Tasks page) ---- */
.reminder-toggle {
  display: inline-flex; align-items: center; gap: var(--ds-space-2);
  font-weight: var(--ds-fw-regular); margin: 0 var(--ds-space-3) 0 0;
}
.reminder-toggle input { width: auto; margin: 0; }

/* ---- Checklist (supplements) + task rows ---- */
.check-row, .task { display: flex; align-items: flex-start; gap: var(--ds-space-3);
  padding: var(--ds-space-3) 0; border-bottom: 1px solid var(--ds-border); }
.check-row:last-child, .task:last-child { border-bottom: 0; }
.check-btn { flex: 0 0 auto; width: 40px; height: 40px; min-height: 40px; border-radius: var(--ds-radius-md);
  border: 2px solid var(--ds-border); background: var(--ds-surface); color: transparent;
  font-size: 1.25rem; font-weight: 700; cursor: pointer; line-height: 1; }
.check-row.is-taken .check-btn, .task.is-done .check-btn {
  background: var(--ds-primary); border-color: var(--ds-primary); color: #fff; }
/* Dose stepper: the checkbox's replacement on a supplement taken more than once
   a day. Occupies the same leading slot as .check-btn so the rows in one list
   still line up whether they are ticked or counted. */
.dose-stepper { flex: 0 0 auto; display: flex; align-items: center; gap: var(--ds-space-1); }
.dose-btn { width: 34px; height: 40px; min-height: 40px; border-radius: var(--ds-radius-md);
  border: 2px solid var(--ds-border); background: var(--ds-surface); color: var(--ds-ink);
  font-size: 1.1rem; font-weight: 700; cursor: pointer; line-height: 1; }
.dose-btn--add { border-color: var(--ds-primary); background: var(--ds-primary); color: #fff; }
.dose-btn[disabled] { opacity: 0.4; cursor: default; }
.dose-count { min-width: 2.5rem; text-align: center; font-weight: var(--ds-fw-medium);
  color: var(--ds-ink); font-size: var(--ds-fs-sm); }
.check-body, .task-body { flex: 1; min-width: 0; }
.check-name, .task-title { font-weight: var(--ds-fw-medium); color: var(--ds-ink); }
.check-meta, .check-note, .task-note { font-size: var(--ds-fs-sm); }
.check-note, .task-note { margin-top: var(--ds-space-1); }
.task.is-done .task-title { text-decoration: line-through; color: var(--ds-muted); }
.task .badge { margin-right: var(--ds-space-2); }

/* ---- Equipment ---- */
.equip-edit { display: flex; flex-wrap: wrap; gap: var(--ds-space-2); align-items: center; }
.equip-edit input[type="text"] { flex: 1 1 8rem; min-width: 8rem; }
.equip-num { width: 5rem; flex: 0 0 auto; }
.preset-list { list-style: none; margin: 0; padding: 0; }
.preset-row { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-space-3);
  padding: var(--ds-space-2) 0; border-bottom: 1px solid var(--ds-border); }
.preset-row:last-child { border-bottom: 0; }
.preset-info { min-width: 0; display: flex; flex-direction: column; }
.preset-name { font-weight: var(--ds-fw-medium); color: var(--ds-ink); }
.preset-trains { font-size: var(--ds-fs-xs); }
.preset-row form { flex: 0 0 auto; }

/* ---- Nutrients-today tally ---- */
.nutrient-list { list-style: none; margin: 0; padding: 0; }
.nutrient-row { padding: var(--ds-space-3) 0; border-bottom: 1px solid var(--ds-border); }
.nutrient-row:last-child { border-bottom: 0; }
.nutrient-main { display: flex; align-items: center; gap: var(--ds-space-2); flex-wrap: wrap; }
.nutrient-name { font-weight: var(--ds-fw-medium); color: var(--ds-ink); }
.nutrient-amt { font-size: var(--ds-fs-sm); margin-top: var(--ds-space-1); }
.nutrient-row.is-over { border-left: 3px solid var(--ds-danger); padding-left: var(--ds-space-3); }
.nutrient-row.is-near { border-left: 3px solid var(--ds-warning); padding-left: var(--ds-space-3); }

/* ---- Rx detail pills (tap to copy / call / email) ---- */
.rx-details { margin-top: var(--ds-space-2); }
.rx-details > summary { display: inline-flex; align-items: center; gap: var(--ds-space-1);
  cursor: pointer; font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold);
  color: var(--ds-link); list-style: none; padding: var(--ds-space-1) 0; min-height: var(--ds-tap); }
.rx-details > summary::-webkit-details-marker { display: none; }
.rx-details > summary::before { content: "▸"; display: inline-block; transition: transform 0.15s ease; }
.rx-details[open] > summary::before { transform: rotate(90deg); }
.pill-row { display: flex; flex-wrap: wrap; gap: var(--ds-space-2); margin-top: var(--ds-space-2); }
.pill { display: inline-flex; align-items: center; gap: var(--ds-space-1);
  min-height: var(--ds-tap); padding: var(--ds-space-1) var(--ds-space-3);
  border: 1px solid var(--ds-border); border-radius: var(--ds-radius-pill);
  background: var(--ds-surface-alt); color: var(--ds-text); font: inherit;
  font-size: var(--ds-fs-sm); line-height: var(--ds-lh-tight); cursor: pointer;
  text-decoration: none; max-width: 100%; text-align: left; transition: background 0.12s ease, border-color 0.12s ease; }
.pill:hover { background: var(--ds-primary-soft); border-color: var(--ds-primary); }
.pill:focus-visible { outline: 2px solid var(--ds-link); outline-offset: 2px; }
.pill__ico { flex: 0 0 auto; font-size: var(--ds-fs-xs); }
.pill__label { flex: 0 0 auto; color: var(--ds-muted); font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-semibold); text-transform: uppercase; letter-spacing: 0.02em; }
.pill__val { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pill--action { color: var(--ds-link); border-color: var(--ds-accent); }
.pill--action:hover { background: var(--ds-accent-soft); border-color: var(--ds-accent); }
.pill.is-copied { background: var(--ds-success-soft); border-color: var(--ds-success); color: var(--ds-success); }
.pill.is-copied .pill__label, .pill.is-copied .pill__val { color: inherit; }
.pill.is-copied .pill__val::after { content: " ✓ Copied"; font-weight: var(--ds-fw-semibold); }
.pill.is-copyfail { background: var(--ds-danger-soft); border-color: var(--ds-danger); color: var(--ds-danger); }
/* "More Rx detail" disclosure inside the add/edit form grid */
.rx-more { grid-column: 1 / -1; }
.rx-more > summary { cursor: pointer; color: var(--ds-link); font-weight: var(--ds-fw-semibold);
  font-size: var(--ds-fs-sm); padding: var(--ds-space-2) 0; min-height: var(--ds-tap);
  display: flex; align-items: center; }
.rx-more > .form-grid { margin-top: var(--ds-space-3); }

/* ---- Estimate box (measurements) ---- */
.estimate-box { margin-top: var(--ds-space-4); padding: var(--ds-space-4);
  border: 1px solid var(--ds-primary); border-left-width: 4px; border-radius: var(--ds-radius-md);
  background: var(--ds-primary-soft); }

/* ---- Progress bar ---- */
.progress { height: 12px; border-radius: var(--ds-radius-pill); background: var(--ds-surface-alt);
  overflow: hidden; border: 1px solid var(--ds-border); }
.progress__bar { height: 100%; background: var(--ds-primary); border-radius: var(--ds-radius-pill); }

/* ---- Table row actions ---- */
.row-actions { display: flex; gap: var(--ds-space-3); align-items: center; white-space: nowrap; }
.row-actions form { display: inline; }
/* Lookup result row (label + "Use"), built in JS on the Food page. The wrapping
   counterpart to .row-actions: the label is a THIRD PARTY's food name ("Chicken,
   broilers or fryers, breast, meat only, cooked, roasted") plus its macros, so
   nowrap would push the button off the side of the card exactly as it did to
   the Recent-foods chips. The label wraps; the button keeps its own line-end. */
.lookup-row { display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--ds-space-2) var(--ds-space-3); }
.lookup-row > span { flex: 1 1 12rem; min-width: 0; overflow-wrap: anywhere; }
.linklike { background: none; border: 0; padding: 0; min-height: 0; color: var(--ds-danger);
  cursor: pointer; font: inherit; text-decoration: underline; }

/* ---- Chat (conversational entry) ---- */
.chat { display: flex; flex-direction: column; gap: var(--ds-space-3); }
.chat__log {
  display: flex; flex-direction: column; gap: var(--ds-space-2);
  max-height: 60vh; overflow-y: auto; padding-right: var(--ds-space-1);
}
.chat__msg {
  max-width: 85%; padding: var(--ds-space-2) var(--ds-space-3);
  border-radius: var(--ds-radius-lg); font-size: var(--ds-fs-sm);
  line-height: var(--ds-lh-body); white-space: pre-wrap; word-wrap: break-word;
}
.chat__msg--me { align-self: flex-end; background: var(--ds-primary); color: #fff; border-bottom-right-radius: var(--ds-radius-sm); }
.chat__msg--bot { align-self: flex-start; background: var(--ds-surface-alt); color: var(--ds-text); border: 1px solid var(--ds-border); border-bottom-left-radius: var(--ds-radius-sm); }
.chat__action {
  align-self: flex-start; font-size: var(--ds-fs-xs); color: var(--ds-success);
  background: var(--ds-success-soft); border: 1px solid var(--ds-success);
  border-radius: var(--ds-radius-pill); padding: var(--ds-space-1) var(--ds-space-3);
}
.chat__compose { display: flex; gap: var(--ds-space-2); align-items: stretch; }
.chat__compose input { flex: 1 1 auto; }
.chat__mic {
  flex: 0 0 auto; min-height: var(--ds-tap); min-width: var(--ds-tap);
  border: 1px solid var(--ds-border); background: var(--ds-surface);
  border-radius: var(--ds-radius-md); cursor: pointer; font-size: 1.1rem;
}
.chat__mic.is-listening { background: var(--ds-primary-soft); border-color: var(--ds-primary); }
.chat__mic:disabled { opacity: 0.5; cursor: not-allowed; }
.chat__hint { font-size: var(--ds-fs-xs); margin-top: var(--ds-space-2); }
/* Report-scan confirm card (review before logging) */
.chat__confirm { max-width: 100%; }
.chat__fields { display: grid; grid-template-columns: auto 1fr; gap: var(--ds-space-1) var(--ds-space-3);
  margin: var(--ds-space-2) 0; font-size: var(--ds-fs-sm); }
.chat__fields dt { color: var(--ds-muted); }
.chat__fields dd { margin: 0; color: var(--ds-ink); font-weight: var(--ds-fw-medium); }
.chat__confirm-actions { display: flex; gap: var(--ds-space-2); margin-top: var(--ds-space-2); }

/* ---- Markdown (plan) ---- */
.markdown :first-child { margin-top: 0; }
.markdown h3 { font-size: var(--ds-fs-h3); color: var(--ds-ink); margin: var(--ds-space-4) 0 var(--ds-space-2); }
.markdown ul { padding-left: 1.25rem; }
.markdown li { margin: var(--ds-space-1) 0; }
.markdown strong { color: var(--ds-ink); }
.markdown code { background: var(--ds-surface-alt); padding: 0.1em 0.35em; border-radius: var(--ds-radius-sm); font-family: var(--ds-font-mono); font-size: 0.9em; }
canvas { max-width: 100%; }

/* A responsive Chart.js canvas (responsive + maintainAspectRatio:false) sizes
   itself to its parent's height. If that parent's height is auto — driven by the
   canvas — the two feed back on each other and the canvas grows without bound
   every resize, eventually hanging the page. The .chart-box gives the canvas a
   parent with a DEFINITE height (set per instance), which breaks the loop. */
.chart-box { position: relative; width: 100%; }
.chart-box > canvas { position: absolute; inset: 0; width: 100%; height: 100%; }

/* ---- Help guide ---- */
.help-toc { margin-bottom: var(--ds-space-6); }
.help-toc__head { font-size: var(--ds-fs-sm); text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ds-muted); margin: 0 0 var(--ds-space-2); }
.help-toc ul { list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--ds-space-1) var(--ds-space-3); }
.help-toc a { display: block; min-height: var(--ds-tap); display: flex; align-items: center;
  font-size: var(--ds-fs-sm); }

/* Each section is a stop on a long scroll, so give it a visible top edge and
   enough scroll-margin that an anchor jump clears the sticky header. */
.help-section { scroll-margin-top: 5.5rem; padding-top: var(--ds-space-5);
  border-top: 1px solid var(--ds-border); margin-top: var(--ds-space-6); }
.help-section:first-of-type { border-top: 0; margin-top: var(--ds-space-4); padding-top: 0; }
.help-section h2 { margin-top: 0; }
.help-section h3 { font-size: var(--ds-fs-h3); color: var(--ds-ink);
  margin: var(--ds-space-5) 0 var(--ds-space-2); }
.help-section ul { padding-left: 1.15rem; }
.help-section li { margin: var(--ds-space-2) 0; }
.help-section strong { color: var(--ds-ink); }

/* Phone captures: cap the width so they read as a device shot rather than a
   full-bleed image, and keep them centred once the column outgrows them. */
.help-shot { margin: var(--ds-space-4) 0; }
.help-shot img { display: block; width: 100%; max-width: 20rem; height: auto; margin: 0 auto;
  border: 1px solid var(--ds-border); border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-md); background: var(--ds-surface); }

/* ---- Reports ---- */
/* Shared date-range controls sit in a row that wraps on narrow screens; the
   preset buttons and actions stay tappable. */
.report-controls { display: flex; flex-wrap: wrap; gap: var(--ds-space-4); align-items: flex-end; }
.report-controls label { margin-bottom: 0; }
.report-presets { display: flex; gap: var(--ds-space-2); flex-wrap: wrap; }
.report-presets .btn { padding: var(--ds-space-2) var(--ds-space-3); }
.report-actions { display: flex; flex-wrap: wrap; gap: var(--ds-space-3);
  margin-top: var(--ds-space-4); }
.report-graph { margin-top: var(--ds-space-4); }
.report-graph__head { display: flex; justify-content: space-between; align-items: center;
  gap: var(--ds-space-3); margin-bottom: var(--ds-space-3); }
.report-graph__foot { margin-top: var(--ds-space-3); }
.report-metrics { min-height: 11rem; }

/* ============================================================
   DESIGN-SYSTEM COMPONENTS (UI adoption — docs/IMPLEMENTATION_HANDOFF.md §2)

   The eleven stamped components from the Personal Design System, expressed as
   namespaced classes. Rules for this block:

     * TOKEN-ONLY. Every colour / space / radius / shadow is a var(--ds-*). The
       only literal here is `#fff`, and only where it sits on --ds-primary or
       the copper gradient — solid fills that stay dark in BOTH themes, so white
       text on them always passes AA (see the two-copper-roles rule in
       tokens.css). rgb(255 255 255 / N%) on the copper hero is the same case.
     * TWO COPPER ROLES. Fills use --ds-primary; text / links / active nav use
       --ds-link. Never swap them.
     * DARK PARITY IS FREE. These reference tokens that already flip on
       [data-theme] and prefers-color-scheme, so no component carries its own
       dark block — adding one would be the bug, not the fix.
     * TAP TARGETS. Anything interactive is >= var(--ds-tap) (44px).
   ============================================================ */

/* ---- 1. Bottom tab bar + centre Log FAB ---------------------------------
   The primary nav on a phone: fixed to the bottom where the thumb reaches, with
   the centre slot raised into a FAB because "log something" is the one action
   worth a permanent oversized target. Rendered from `primary_nav` in
   app/__init__.py — a second *rendering* of the existing nav data, never a
   second source of truth. Desktop (>= 900px) hides it and keeps the pill row. */
.tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 50;
  display: flex;
  align-items: flex-start;
  justify-content: space-around;
  gap: var(--ds-space-1);
  padding: var(--ds-space-2) var(--ds-space-2) 0;
  /* Clear the iOS home indicator without a magic number. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
  min-height: 4.5rem;
  background: var(--ds-surface);
  border-top: 1px solid var(--ds-border);
  box-shadow: 0 -4px 16px rgb(15 23 42 / 6%);
}
.tabbar__item {
  flex: 1 1 0;
  min-width: 0;
  min-height: var(--ds-tap);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: var(--ds-space-1);
  padding-top: var(--ds-space-1);
  font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-semibold);
  color: var(--ds-faint);
  text-align: center;
  border-radius: var(--ds-radius-md);
}
.tabbar__item:hover { text-decoration: none; color: var(--ds-muted); }
/* The active tab is copper-as-TEXT, so --ds-link — not --ds-primary. */
.tabbar__item[aria-current="page"] { color: var(--ds-link); }
.tabbar__ico { width: 23px; height: 23px; display: block; }
.tabbar__ico svg { width: 100%; height: 100%; display: block;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }
.tabbar__label { display: block; max-width: 100%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }

/* The FAB breaks the row's baseline on purpose — the negative margin is what
   makes it read as raised rather than as a wider fifth tab. */
.tabbar__fab {
  flex: 0 0 auto;
  width: 3.75rem;
  margin-top: -1.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.tabbar__fab:hover { text-decoration: none; }
.tabbar__fab-btn {
  width: 3.75rem; height: 3.75rem;
  border-radius: var(--ds-radius-pill);
  background: linear-gradient(135deg, var(--ds-copper), var(--ds-primary-strong));
  border: 4px solid var(--ds-surface);
  box-shadow: var(--ds-shadow-lg);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
}
.tabbar__fab-btn svg { width: 26px; height: 26px; fill: none; stroke: currentColor;
  stroke-width: 2.5; stroke-linecap: round; }
.tabbar__fab-label { margin-top: var(--ds-space-1); font-size: var(--ds-fs-xs);
  font-weight: var(--ds-fw-bold); color: var(--ds-link); }

/* Reserve room so the last card in <main> is never trapped under the bar. */
body.has-tabbar main { padding-bottom: calc(6rem + env(safe-area-inset-bottom, 0px)); }
@media (min-width: 900px) {
  .tabbar { display: none; }
  body.has-tabbar main { padding-bottom: var(--ds-space-8); }
}
/* Mirror image: below 900px the header pill row is redundant with the tab bar,
   so it collapses to just the wordmark + account corner. */
@media (max-width: 899px) {
  body.has-tabbar .app-header > .nav { display: none; }
}

/* ---- 2. Headers: copper hero (Home / onboarding) vs calm detail ----------
   Home and onboarding get the copper gradient — they are arrival screens and
   the brand moment belongs there. Every drill-in gets .detail-header instead: a
   calm surface with copper only as *detailing* (back chevron + a short accent
   rule), so the colour keeps its meaning instead of becoming wallpaper. */
.hero-header {
  background: linear-gradient(135deg, var(--ds-copper), var(--ds-primary-strong));
  color: #fff;
  padding: var(--ds-space-5) var(--ds-space-4) var(--ds-space-6);
  margin: calc(var(--ds-space-5) * -1) calc(var(--ds-space-4) * -1) var(--ds-space-5);
}
.hero-header__row { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ds-space-3); max-width: var(--ds-maxw); margin: 0 auto; }
.hero-header__greet { font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-semibold); opacity: 0.9; }
.hero-header__name { font-size: var(--ds-fs-display); font-weight: var(--ds-fw-bold);
  line-height: var(--ds-lh-tight); }
.hero-header__avatar {
  width: var(--ds-tap); height: var(--ds-tap); flex: 0 0 auto;
  border-radius: var(--ds-radius-pill);
  background: rgb(255 255 255 / 20%);
  border: 2px solid rgb(255 255 255 / 50%);
  display: flex; align-items: center; justify-content: center;
  font-weight: var(--ds-fw-bold); color: #fff;
}
.hero-header__avatar:hover { text-decoration: none; background: rgb(255 255 255 / 30%); }
/* Goal chip — names the primary goal on the hero, so the framing is explicit
   rather than implied by which tiles happen to be on top. */
.goalchip {
  display: inline-flex; align-items: center; gap: var(--ds-space-2);
  margin-top: var(--ds-space-3);
  padding: var(--ds-space-1) var(--ds-space-3);
  min-height: 2rem;
  border-radius: var(--ds-radius-pill);
  background: rgb(255 255 255 / 16%);
  color: #fff; font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold);
}
.goalchip:hover { text-decoration: none; background: rgb(255 255 255 / 26%); }
.goalchip::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: #fff; }

.detail-header {
  display: flex; align-items: center; gap: var(--ds-space-3);
  position: relative;
  margin: calc(var(--ds-space-5) * -1) calc(var(--ds-space-4) * -1) var(--ds-space-5);
  padding: var(--ds-space-3) var(--ds-space-4);
  background: var(--ds-surface);
  border-bottom: 1px solid var(--ds-border);
}
.detail-header__back, .detail-header__action {
  flex: 0 0 auto;
  min-width: var(--ds-tap); min-height: var(--ds-tap);
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; cursor: pointer;
  color: var(--ds-link); font: inherit; font-size: var(--ds-fs-h2);
  border-radius: var(--ds-radius-md);
}
.detail-header__back:hover, .detail-header__action:hover {
  text-decoration: none; background: var(--ds-surface-alt);
}
.detail-header__title { flex: 1; min-width: 0; margin: 0;
  font-size: var(--ds-fs-h2); font-weight: var(--ds-fw-bold); color: var(--ds-ink); }
/* The copper detailing: one short rule under the back control, nothing more. */
.detail-header::after {
  content: ""; position: absolute; left: var(--ds-space-4); bottom: -1px;
  width: 34px; height: 3px; border-radius: 2px; background: var(--ds-primary);
}

/* ---- 3. Metric list card (.mcard) --------------------------------------
   One tracked value per row: icon, label, big value, timestamp, quick-add. */
.mcard-list { display: flex; flex-direction: column; gap: var(--ds-space-3); }
.mcard {
  display: flex; align-items: center; gap: var(--ds-space-3);
  padding: var(--ds-space-3) var(--ds-space-4);
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-sm);
  color: inherit;
}
a.mcard:hover { text-decoration: none; border-color: var(--ds-primary); }
.mcard__ico {
  flex: 0 0 auto; width: 46px; height: 46px;
  border-radius: 50%; border: 2px solid var(--ds-border);
  background: var(--ds-surface-alt); color: var(--ds-muted);
  display: flex; align-items: center; justify-content: center; font-size: var(--ds-fs-h3);
}
.mcard__ico svg { width: 22px; height: 22px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* Icon accents carry a role, not decoration: copper = the goal metric, teal =
   intake, amber = something due, danger = out of range. */
.mcard__ico--copper { border-color: var(--ds-primary); background: var(--ds-primary-soft); color: var(--ds-link); }
.mcard__ico--teal   { border-color: var(--ds-accent);  background: var(--ds-accent-soft);  color: var(--ds-accent); }
.mcard__ico--amber  { border-color: var(--ds-warning); background: var(--ds-warning-soft); color: var(--ds-warning); }
.mcard__ico--danger { border-color: var(--ds-danger);  background: var(--ds-danger-soft);  color: var(--ds-danger); }
.mcard__mid { flex: 1; min-width: 0; }
.mcard__label { font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-semibold); color: var(--ds-muted); }
.mcard__value { font-size: var(--ds-fs-h2); font-weight: var(--ds-fw-bold); color: var(--ds-ink);
  line-height: 1.15; font-variant-numeric: tabular-nums; }
.mcard__unit { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold); color: var(--ds-muted); }
.mcard__ts { font-size: var(--ds-fs-xs); color: var(--ds-faint); margin-top: var(--ds-space-1); }
.mcard__add {
  flex: 0 0 auto;
  min-width: var(--ds-tap); min-height: var(--ds-tap);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--ds-border); border-radius: 50%;
  background: transparent; color: var(--ds-link);
  font: inherit; font-size: var(--ds-fs-h2); font-weight: var(--ds-fw-bold);
  line-height: 1; cursor: pointer;
}
.mcard__add:hover { background: var(--ds-primary-soft); border-color: var(--ds-primary);
  text-decoration: none; }
/* A due / overdue reminder earns a warning edge — the one variant in the spec. */
.mcard--reminder { border-left: 3px solid var(--ds-warning); }

/* ---- 4. Stat delta tile (.stat) ---------------------------------------- */
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ds-space-3); }
@media (min-width: 640px) { .stat-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } }
.stat {
  background: var(--ds-surface); border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg); box-shadow: var(--ds-shadow-sm);
  padding: var(--ds-space-3) var(--ds-space-4);
}
.stat__label { display: flex; align-items: center; gap: var(--ds-space-2);
  font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold); color: var(--ds-muted); }
.stat__value { display: flex; align-items: baseline; gap: var(--ds-space-1);
  margin-top: var(--ds-space-2); font-size: var(--ds-fs-h2); font-weight: var(--ds-fw-bold);
  color: var(--ds-ink); line-height: 1; font-variant-numeric: tabular-nums; }
.stat__unit { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold); color: var(--ds-muted); }
/* The delta pill carries DIRECTION, and direction is goal-relative — a falling
   weight is `is-good` under weight loss and `is-warn` under healthy gain. The
   template picks the class (services/goals.py decides); this file only paints. */
.stat__delta {
  display: inline-flex; align-items: center; gap: var(--ds-space-1);
  margin-top: var(--ds-space-2); padding: 0.1rem var(--ds-space-2);
  border-radius: var(--ds-radius-pill);
  font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold);
  background: var(--ds-surface-alt); color: var(--ds-muted);
}
.stat__delta.is-good { color: var(--ds-success); background: var(--ds-success-soft); }
.stat__delta.is-warn { color: var(--ds-danger);  background: var(--ds-danger-soft); }
.stat__delta.is-flat { color: var(--ds-muted);   background: var(--ds-surface-alt); }

/* ---- 5. Grouped menu list (.glist) ------------------------------------- */
.glist-label { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold);
  letter-spacing: 0.07em; text-transform: uppercase; color: var(--ds-muted);
  margin: var(--ds-space-5) var(--ds-space-2) var(--ds-space-2); }
.glist { background: var(--ds-surface); border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg); overflow: hidden; box-shadow: var(--ds-shadow-sm); }
.glist__row {
  display: flex; align-items: center; gap: var(--ds-space-3);
  min-height: 56px; padding: var(--ds-space-3) var(--ds-space-4);
  border-bottom: 1px solid var(--ds-border);
  color: inherit;
}
.glist__row:last-child { border-bottom: 0; }
a.glist__row:hover, button.glist__row:hover { background: var(--ds-surface-alt); text-decoration: none; }
button.glist__row { width: 100%; border-left: 0; border-right: 0; border-top: 0;
  background: transparent; font: inherit; text-align: left; cursor: pointer; }
.glist__tile {
  flex: 0 0 auto; width: 32px; height: 32px;
  border-radius: var(--ds-radius-md);
  display: flex; align-items: center; justify-content: center;
  background: var(--ds-surface-alt); color: var(--ds-muted); font-size: var(--ds-fs-sm);
}
.glist__tile svg { width: 19px; height: 19px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.glist__tile--copper { background: var(--ds-primary-soft); color: var(--ds-link); }
.glist__tile--teal   { background: var(--ds-accent-soft);  color: var(--ds-accent); }
.glist__tile--amber  { background: var(--ds-warning-soft); color: var(--ds-warning); }
.glist__tile--slate  { background: var(--ds-surface-alt);  color: var(--ds-muted); }
.glist__label { flex: 1; min-width: 0; font-weight: var(--ds-fw-semibold); color: var(--ds-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.glist__value { flex: 0 0 auto; max-width: 40%; font-size: var(--ds-fs-sm); color: var(--ds-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.glist__chev { flex: 0 0 auto; color: var(--ds-faint); font-size: var(--ds-fs-h3); line-height: 1; }
a.glist__row .glist__chev { color: var(--ds-link); }

/* ---- 6. Selectors: .seg / .chips / .selectfield + .sheet / .toggle-row ---
   These replace every wrapping <select> row. The segmented control SCROLLS
   horizontally rather than wrapping: a wrapped segmented control stops reading
   as one control, which was exactly the bug in the old period pickers. */
.seg {
  display: flex; gap: var(--ds-space-1);
  padding: var(--ds-space-1);
  background: var(--ds-surface-alt);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-md);
  overflow-x: auto;
  scrollbar-width: none;
}
.seg::-webkit-scrollbar { display: none; }
.seg > a, .seg > button {
  flex: 0 0 auto;
  min-height: var(--ds-tap);
  display: inline-flex; align-items: center; justify-content: center;
  padding: var(--ds-space-2) var(--ds-space-4);
  border: 0; background: transparent;
  border-radius: var(--ds-radius-sm);
  font: inherit; font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-bold);
  color: var(--ds-muted); white-space: nowrap; cursor: pointer;
}
.seg > a:hover, .seg > button:hover { text-decoration: none; color: var(--ds-ink); }
/* A selected segment is a solid FILL, so --ds-primary + white text. */
.seg > .is-on, .seg > [aria-pressed="true"], .seg > [aria-current="true"] {
  background: var(--ds-primary); color: #fff; box-shadow: var(--ds-shadow-sm);
}

.chips { display: flex; gap: var(--ds-space-2); overflow-x: auto;
  padding-bottom: var(--ds-space-1); scrollbar-width: none; }
.chips::-webkit-scrollbar { display: none; }
/* A CSRF-protected quick-add posts, so its button must sit inside a <form>.
   That makes the button a GRANDchild, which a direct-child selector misses --
   it rendered as a bare grey rectangle. Match both depths. */
.chips > a, .chips > button, .chips > form > button {
  flex: 0 0 auto;
  min-height: var(--ds-tap);
  display: inline-flex; align-items: center; gap: var(--ds-space-2);
  padding: var(--ds-space-2) var(--ds-space-4);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-surface); color: var(--ds-text);
  font: inherit; font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-semibold);
  white-space: nowrap; cursor: pointer;
}
.chips > a:hover, .chips > button:hover, .chips > form > button:hover {
  text-decoration: none; border-color: var(--ds-accent); }
.chips > form { flex: 0 0 auto; display: flex; }
.chips > .is-on, .chips > [aria-pressed="true"] {
  border-color: var(--ds-accent); background: var(--ds-accent-soft); color: var(--ds-accent-strong);
}
/* .chips--wrap: the same chip row, wrapped instead of scrolled.
   .chips scrolls and never wraps because a picker has to keep reading as ONE
   control. Quick-add / Recent foods are not a picker: the set is of unknown
   length and every label is USER TEXT, so a single entry can be a sentence
   ("McDouble with extra mustard, diced onions, salt, 2 half strips of bacon").
   Nowrap sent that one chip straight off the side of the card. Labels wrap
   onto lines here, and the pill radius softens to --ds-radius-md because a
   multi-line pill reads as a lozenge, not a chip. */
.chips--wrap { flex-wrap: wrap; overflow-x: visible; padding-bottom: 0; }
.chips--wrap > a, .chips--wrap > button, .chips--wrap > form > button {
  max-width: 100%; min-width: 0;
  white-space: normal; overflow-wrap: anywhere; text-align: left;
  border-radius: var(--ds-radius-md);
}
/* The label is ONE flex child (see food.html): a chip button is a flex row, so
   a name and its date as two children lay out as two columns. */
.chips--wrap > a > span, .chips--wrap > button > span { min-width: 0; }

.selectfield {
  display: flex; align-items: center; justify-content: space-between; gap: var(--ds-space-3);
  width: 100%; min-height: var(--ds-tap);
  padding: var(--ds-space-3) var(--ds-space-4);
  background: var(--ds-surface); border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-md); font: inherit; text-align: left; cursor: pointer;
}
.selectfield:hover { border-color: var(--ds-primary); text-decoration: none; }
.selectfield__label { font-size: var(--ds-fs-sm); color: var(--ds-muted); }
.selectfield__value { display: flex; align-items: center; gap: var(--ds-space-2);
  font-weight: var(--ds-fw-bold); color: var(--ds-ink); }
.selectfield__value::after { content: "▾"; color: var(--ds-link); font-size: 0.8em; }
.sheet { margin-top: var(--ds-space-2); background: var(--ds-surface);
  border: 1px solid var(--ds-border); border-radius: var(--ds-radius-lg);
  overflow: hidden; box-shadow: var(--ds-shadow-md); }
.sheet__head { padding: var(--ds-space-3) var(--ds-space-4) var(--ds-space-2);
  font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold); text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--ds-muted); }
.sheet__opt { display: flex; align-items: center; justify-content: space-between;
  min-height: var(--ds-tap); padding: var(--ds-space-3) var(--ds-space-4);
  border-top: 1px solid var(--ds-border); color: var(--ds-ink); }
.sheet__opt:hover { background: var(--ds-surface-alt); text-decoration: none; }
.sheet__opt.is-on { font-weight: var(--ds-fw-bold); color: var(--ds-link); }
.sheet__opt.is-on::after { content: "✓"; }

.toggle-row {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--ds-space-3);
  padding: var(--ds-space-3) var(--ds-space-4);
  background: var(--ds-surface); border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg); margin-bottom: var(--ds-space-2);
}
.toggle-row__text { flex: 1; min-width: 0; }
.toggle-row__title { font-weight: var(--ds-fw-bold); color: var(--ds-ink); }
.toggle-row__sub { font-size: var(--ds-fs-sm); color: var(--ds-muted); margin-top: var(--ds-space-1); }
/* A real <button aria-pressed>, not a styled checkbox, so it announces state. */
.switch {
  flex: 0 0 auto; position: relative;
  width: 48px; height: 28px; padding: 0;
  border: 0; border-radius: var(--ds-radius-pill);
  background: var(--ds-faint); cursor: pointer;
  transition: background 0.16s ease;
}
.switch::after { content: ""; position: absolute; top: 3px; left: 3px;
  width: 22px; height: 22px; border-radius: 50%; background: var(--ds-surface);
  box-shadow: var(--ds-shadow-sm); transition: left 0.16s ease; }
.switch[aria-pressed="true"] { background: var(--ds-accent); }
.switch[aria-pressed="true"]::after { left: 23px; }
.switch--copper[aria-pressed="true"] { background: var(--ds-primary); }
/* The visible control is 28px tall; this grows the HIT area to 44px without
   inflating the track, so the a11y floor holds without a design compromise. */
.switch::before { content: ""; position: absolute; inset: -8px; }

/* ---- 7. Gauge bar (.gauge) — a value on a clinical range ---------------
   Body composition: the useful reading is not the number, it is where the
   number falls between "low" and "high". The track ramp is passed per instance
   as --gauge-ramp so the DOMAIN owns the colour meaning, not this file. */
.gauge { margin-bottom: var(--ds-space-4); }
.gauge__top { display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--ds-space-3); margin-bottom: var(--ds-space-2); }
.gauge__name { font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-bold); color: var(--ds-ink); }
.gauge__value { font-weight: var(--ds-fw-bold); color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.gauge__value .u { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold); color: var(--ds-muted); }
.gauge__track { position: relative; height: 12px; border-radius: var(--ds-radius-pill);
  background: var(--gauge-ramp, var(--ds-surface-alt)); }
.gauge__mark { position: absolute; top: 50%; left: var(--gauge-pos, 50%);
  transform: translate(-50%, -50%);
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--ds-surface); border: 3px solid var(--ds-ink);
  box-shadow: var(--ds-shadow-sm); }
.gauge__scale { display: flex; justify-content: space-between;
  font-size: var(--ds-fs-xs); color: var(--ds-faint); margin-top: var(--ds-space-1); }
.gauge__tag { display: inline-block; padding: 0.1rem var(--ds-space-2);
  border-radius: var(--ds-radius-pill); font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold); }
.gauge__tag.is-good { color: var(--ds-success); background: var(--ds-success-soft); }
.gauge__tag.is-warn { color: var(--ds-warning); background: var(--ds-warning-soft); }
.gauge__tag.is-high { color: var(--ds-danger);  background: var(--ds-danger-soft); }
.gauge__tag.is-info { color: var(--ds-info);    background: var(--ds-info-soft); }

/* ---- 8. Ring gauge (.ring) — inline SVG, no JS -------------------------
   The template sets stroke-dasharray on .ring__bar; there is nothing to
   initialise, so it renders identically with JS off. */
.ring { display: block; flex: 0 0 auto; width: var(--ring-size, 66px); height: var(--ring-size, 66px); }
.ring__track { fill: none; stroke: var(--ds-surface-alt); }
.ring__bar { fill: none; stroke: var(--ds-primary); stroke-linecap: round;
  transform: rotate(-90deg); transform-origin: 50% 50%; }
.ring__bar--teal { stroke: var(--ds-accent); }
.ring__bar--success { stroke: var(--ds-success); }
.ring__bar--warning { stroke: var(--ds-warning); }
.ring__value { fill: var(--ds-ink); font-size: 15px; font-weight: var(--ds-fw-bold);
  text-anchor: middle; dominant-baseline: central; }
.ring__cap { fill: var(--ds-muted); font-size: 8px; font-weight: var(--ds-fw-semibold);
  text-anchor: middle; dominant-baseline: central; }

/* ---- 9. Progress fill bars (.fill) — macros, adherence ----------------- */
.fill-list { display: flex; flex-direction: column; gap: var(--ds-space-3); }
.fill__top { display: flex; justify-content: space-between; align-items: baseline; gap: var(--ds-space-3); }
.fill__name { font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-bold); color: var(--ds-ink); }
.fill__val { font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-semibold); color: var(--ds-muted);
  font-variant-numeric: tabular-nums; }
.fill__track { height: 9px; margin-top: var(--ds-space-2); border-radius: var(--ds-radius-pill);
  background: var(--ds-surface-alt); overflow: hidden; }
.fill__bar { height: 100%; width: var(--fill-pct, 0%); border-radius: var(--ds-radius-pill);
  background: var(--ds-primary); transition: width 0.3s ease; }
.fill__bar--teal { background: var(--ds-accent); }
.fill__bar--success { background: var(--ds-success); }
.fill__bar--warning { background: var(--ds-warning); }
.fill__bar--danger { background: var(--ds-danger); }
/* Stacked protein / carb / fat ratio bar — one row, segment widths set inline. */
.pcf { display: flex; height: 8px; margin: var(--ds-space-2) 0;
  border-radius: var(--ds-radius-pill); overflow: hidden; background: var(--ds-surface-alt); }
.pcf > span { display: block; height: 100%; }
.pcf__p { background: var(--ds-primary); }
.pcf__c { background: var(--ds-accent); }
.pcf__f { background: var(--ds-warning); }
/* What the ratio bar could not count, and the offer to fill it in. Muted and
   one line: the bar is only ever drawn over entries carrying all three macros
   (services/food.macro_coverage), so its basis has to be sayable — but an
   unrecorded macro is a gap, not a failing, and this must never read as one. */
.macrogap { margin: 0 0 var(--ds-space-2); font-size: var(--ds-fs-xs);
  color: var(--ds-muted); line-height: var(--ds-lh-body); }

/* ---- 10. Grouped bar chart (.chart--grouped) + trend (.chart--trend) ----
   Both are Chart.js canvases; these classes own only SIZING, because the
   colours come from static/js/charts.js reading --ds-* off computed style
   (Chart.js cannot resolve a CSS variable itself). Both need a parent with a
   DEFINITE height for the same reason .chart-box does — see the note there. */
.chart--grouped, .chart--trend { position: relative; width: 100%; }
.chart--grouped > canvas, .chart--trend > canvas {
  position: absolute; inset: 0; width: 100%; height: 100%; }
.chart--grouped { height: 200px; }
.chart--trend { height: 220px; }
.chart-legend { display: flex; flex-wrap: wrap; gap: var(--ds-space-3);
  margin-top: var(--ds-space-3); font-size: var(--ds-fs-xs); color: var(--ds-muted); }
.chart-legend__key { display: inline-flex; align-items: center; gap: var(--ds-space-1); }
.chart-legend__key::before { content: ""; width: 10px; height: 10px; border-radius: 2px;
  background: var(--key, var(--ds-faint)); }

/* ---- 11. Calendar heatmap (.heat) -------------------------------------
   Logging consistency at a glance. The four levels are a SEQUENTIAL ramp of one
   hue, so they read as more/less rather than as four categories. The ramp
   itself is a domain colour and lives in app-tokens.css. */
.heat { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.heat__cell { aspect-ratio: 1; border-radius: var(--ds-radius-sm); background: var(--ds-heat-0); }
.heat__cell.l0 { background: var(--ds-heat-0); }
.heat__cell.l1 { background: var(--ds-heat-1); }
.heat__cell.l2 { background: var(--ds-heat-2); }
.heat__cell.l3 { background: var(--ds-heat-3); }
.heat__legend { display: flex; align-items: center; justify-content: flex-end; gap: var(--ds-space-1);
  margin-top: var(--ds-space-3); font-size: var(--ds-fs-xs); color: var(--ds-faint); }
.heat__legend i { width: 11px; height: 11px; border-radius: var(--ds-radius-sm); display: inline-block; }

/* ---- Supporting pieces the reskinned screens share --------------------- */
/* Small uppercase caption above a card's content. */
.card-title { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ds-muted);
  margin: 0 0 var(--ds-space-3); }
/* Hero "today" card — big number beside a ring. Dashboard, Water, Food. */
.hero-card { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ds-space-4); }
.hero-card__big { display: flex; align-items: baseline; gap: var(--ds-space-2);
  margin-top: var(--ds-space-2); font-size: var(--ds-fs-kpi); font-weight: var(--ds-fw-bold);
  color: var(--ds-ink); line-height: 1; font-variant-numeric: tabular-nums; }
.hero-card__big .u { font-size: var(--ds-fs-body); font-weight: var(--ds-fw-semibold); color: var(--ds-muted); }
.hero-card__big .ar { font-size: var(--ds-fs-h3); color: var(--ds-success); }
.hero-card__big .ar.is-warn { color: var(--ds-danger); }
.hero-card__sub { font-size: var(--ds-fs-sm); color: var(--ds-muted); margin-top: var(--ds-space-2); }
/* Triad — three equal readings sharing one card (BP systolic / diastolic / pulse). */
.triad { display: flex; text-align: center; }
.triad__cell { flex: 1; min-width: 0; }
.triad__cell + .triad__cell { border-left: 1px solid var(--ds-border); }
.triad__key { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold); color: var(--ds-muted);
  margin-bottom: var(--ds-space-2); }
.triad__value { font-size: var(--ds-fs-kpi); font-weight: var(--ds-fw-bold); color: var(--ds-ink);
  line-height: 1; font-variant-numeric: tabular-nums; }
.triad__unit { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold); color: var(--ds-muted);
  margin-top: var(--ds-space-1); }
/* Status chip — a categorical clinical label (Normal / Elevated / Stage 1 …). */
.statuschip { display: inline-flex; align-items: center; gap: var(--ds-space-1);
  padding: var(--ds-space-1) var(--ds-space-3); border-radius: var(--ds-radius-pill);
  font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold); }
.statuschip.is-good { color: var(--ds-success); background: var(--ds-success-soft); }
.statuschip.is-warn { color: var(--ds-warning); background: var(--ds-warning-soft); }
.statuschip.is-high { color: var(--ds-danger);  background: var(--ds-danger-soft); }
.statuschip.is-info { color: var(--ds-info);    background: var(--ds-info-soft); }
/* Record row — one compact reading in a list (BP history, fingersticks). */
.rec { display: flex; align-items: center; gap: var(--ds-space-3);
  padding: var(--ds-space-3) 0; border-top: 1px solid var(--ds-border); }
.rec:first-of-type { border-top: 0; }
.rec__value { flex: 0 0 auto; min-width: 5rem; font-size: var(--ds-fs-h3);
  font-weight: var(--ds-fw-bold); color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.rec__mid { flex: 1; min-width: 0; }
.rec__state { font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-bold); }
.rec__ts { font-size: var(--ds-fs-xs); color: var(--ds-faint); margin-top: var(--ds-space-1); }
.rec__dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 auto; }
/* Two small readings side by side under a chart. */
.mini-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ds-space-3); }
.mini { background: var(--ds-surface); border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg); box-shadow: var(--ds-shadow-sm); padding: var(--ds-space-3); }
.mini__key { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold); color: var(--ds-muted); }
.mini__value { font-size: var(--ds-fs-h2); font-weight: var(--ds-fw-bold); color: var(--ds-ink);
  margin-top: var(--ds-space-1); font-variant-numeric: tabular-nums; }
.mini__value .u { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold); color: var(--ds-muted); }
/* Full-width primary action at the foot of a form / log sheet. */
.btn--block { width: 100%; border-radius: var(--ds-radius-pill); padding: var(--ds-space-3);
  font-size: var(--ds-fs-h3); font-weight: var(--ds-fw-bold); }
/* Vertical rhythm for a column of cards, and the caption above one. */
.stack { display: flex; flex-direction: column; gap: var(--ds-space-4); }
.eyebrow { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-bold);
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--ds-muted);
  margin: var(--ds-space-5) var(--ds-space-1) var(--ds-space-2); }
.eyebrow:first-child { margin-top: 0; }

/* ---- Tips & tricks flyovers (static/js/tips.js) ------------------------
   A coach-mark spotlights ONE control and explains ONE idea. Token-only like
   everything above, so it is dark-safe for free.

   The overlay is built by JS and only exists while a tip is open — with
   scripting off none of this is ever in the DOM, which is why there is no
   `display: none` fallback to maintain. */
.tip-overlay {
  position: fixed; inset: 0; z-index: 200;
  /* Deliberately NOT a token: this is a scrim, and a scrim has to be the same
     dark wash in both themes or the spotlight stops reading as a spotlight.
     Slate-900 at 55%, matching the shadow tint used throughout tokens.css. */
  background: rgb(15 23 42 / 55%);
  animation: tip-fade 0.15s ease;
}
@keyframes tip-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .tip-overlay { animation: none; }
  .tip-card { animation: none; }
}

/* The spotlight is a ring around the target, not a hole punched in the scrim:
   a clip-path cut-out needs the target's exact geometry on every frame and
   breaks the moment the page scrolls. */
.tip-spot {
  position: fixed;
  border-radius: var(--ds-radius-lg);
  border: 2px solid var(--ds-copper);
  box-shadow: 0 0 0 9999px rgb(15 23 42 / 30%), var(--ds-shadow-lg);
  pointer-events: none;
}

.tip-card {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  width: min(22rem, calc(100vw - 2 * var(--ds-space-4)));
  padding: var(--ds-space-4);
  background: var(--ds-surface);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-shadow-md);
  animation: tip-rise 0.18s ease;
}
@keyframes tip-rise { from { opacity: 0; transform: translate(-50%, 6px); } }
/* The little copper arrow pointing back at the spotlit control. */
.tip-card::before {
  content: ""; position: absolute; left: 50%; margin-left: -7px;
  border: 7px solid transparent;
}
.tip-card--below::before { top: -14px; border-bottom-color: var(--ds-copper); }
.tip-card--above::before { bottom: -14px; border-top-color: var(--ds-copper); }
.tip-card__title { margin: 0 0 var(--ds-space-2);
  font-size: var(--ds-fs-h3); font-weight: var(--ds-fw-bold); color: var(--ds-ink); }
.tip-card__text { margin: 0; font-size: var(--ds-fs-sm); color: var(--ds-text);
  line-height: var(--ds-lh-body); }
.tip-card__actions { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ds-space-2); margin-top: var(--ds-space-4); }
.tip-card__off { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-semibold);
  padding-left: 0; padding-right: 0; border-color: transparent; color: var(--ds-muted); }
.tip-card__off:hover { background: transparent; color: var(--ds-link); text-decoration: underline; }
.tip-card__ok { flex: 0 0 auto; }

/* The "?" affordance — a persistent way back to the tips, so turning them off
   is never a one-way door. Sits clear of the tab bar. */
.tip-fab {
  position: fixed; right: var(--ds-space-4); z-index: 40;
  bottom: calc(5.5rem + env(safe-area-inset-bottom, 0px));
  width: 2.5rem; height: 2.5rem;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--ds-border); border-radius: 50%;
  background: var(--ds-surface); color: var(--ds-link);
  font: inherit; font-size: var(--ds-fs-h3); font-weight: var(--ds-fw-bold);
  box-shadow: var(--ds-shadow-md); cursor: pointer;
}
.tip-fab:hover { background: var(--ds-primary-soft); border-color: var(--ds-primary); }
/* The visible button is 40px; this lifts the HIT area to the 44px floor
   without making the affordance itself heavier than the content it sits over. */
.tip-fab::after { content: ""; position: absolute; inset: -2px; border-radius: 50%; }
@media (min-width: 900px) {
  .tip-fab { bottom: var(--ds-space-5); }
}

/* ---- Appearance toggle (light / dark / system) -------------------------- */
.theme-toggle { display: flex; gap: var(--ds-space-1); }
.theme-toggle > button { flex: 1 1 0; }

/* ---- Radio / checkbox variants of the selectors -------------------------
   Where a selector posts a value it is a real <input>, not a styled <button>:
   a radio group already means "one of N", already posts, and already works
   with a keyboard and a screen reader. These rules put the segmented / chip
   look on top of that, rather than reimplementing it in JS. */

/* Screen-reader-only, for a <legend> that would be redundant on screen. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* A <fieldset> wearing .seg. Browsers give fieldset its own default box, so
   these have to be reset before the flex layout reads correctly. */
.seg--radio { border: 1px solid var(--ds-border); margin: 0; }
.seg--radio > label {
  flex: 1 0 auto;
  min-height: var(--ds-tap);
  display: inline-flex; align-items: center; justify-content: center;
  padding: var(--ds-space-2) var(--ds-space-4);
  border-radius: var(--ds-radius-sm);
  font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-bold);
  color: var(--ds-muted); white-space: nowrap; cursor: pointer;
  margin-bottom: 0;
}
/* The input is hidden visually but stays focusable, so the focus ring lands on
   the label via :focus-within — never `display: none`, which removes it from
   the tab order entirely. */
.seg--radio input, .chip-check input {
  position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0;
}
.seg--radio > label:has(input:checked),
.seg--radio > label.is-on { background: var(--ds-primary); color: #fff; box-shadow: var(--ds-shadow-sm); }
.seg--radio > label:focus-within { outline: 3px solid var(--ds-link); outline-offset: 2px; }

/* A checkbox wearing .chips — used by Menu -> Customize navigation. */
.chip-check {
  flex: 0 0 auto;
  min-height: var(--ds-tap);
  display: inline-flex; align-items: center; gap: var(--ds-space-2);
  padding: var(--ds-space-2) var(--ds-space-4);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-surface); color: var(--ds-text);
  font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-semibold);
  white-space: nowrap; cursor: pointer; margin-bottom: 0;
}
.chip-check:has(input:checked) {
  border-color: var(--ds-accent); background: var(--ds-accent-soft); color: var(--ds-accent-strong);
}
.chip-check:has(input:checked) span::before { content: "✓ "; }
.chip-check:focus-within { outline: 3px solid var(--ds-link); outline-offset: 2px; }
/* `display: inline-flex` above beats the UA's `[hidden] { display: none }`, so a
   chip hidden by script would otherwise stay on screen. The goal picker hides
   the chip for whichever goal is currently primary. */
.chip-check[hidden] { display: none; }
/* At the cap, the remaining chips read as unavailable rather than unresponsive.
   The already-checked ones stay live so a swap is always one tap away. */
.chip-check:has(input:disabled) { opacity: 0.45; cursor: not-allowed; }

/* ---- Goal picker row ---------------------------------------------------
   A .glist__row that is a <label> wrapping a radio, so the whole 56px row is
   the tap target rather than the 20px control. */
.goal-option { cursor: pointer; align-items: flex-start; margin-bottom: 0; }
/* The row is the tap target, so the control itself only has to be legible.
   The nudge lines the circle up with the first line of the label rather than
   the top of the (two-line) block. */
.goal-option input[type="radio"] { width: 20px; height: 20px; margin-top: 3px; }
.goal-option:has(input:checked) { background: var(--ds-primary-soft); }
.goal-option:has(input:checked) .glist__label { color: var(--ds-link); }
.goal-option:focus-within { outline: 3px solid var(--ds-link); outline-offset: -3px; }
.goal-option .glist__label { white-space: normal; font-weight: var(--ds-fw-bold); }
.goal-option__blurb { display: block; margin-top: var(--ds-space-1);
  font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-regular); color: var(--ds-muted);
  white-space: normal; }

/* :has() is well supported in every browser this app targets, but a browser
   without it would show no selected state at all. This keeps the radio itself
   visible there, so the control is still usable rather than silently mute. */
@supports not (selector(:has(*))) {
  .seg--radio input, .chip-check input {
    position: static; opacity: 1; width: auto; height: auto;
  }
}

/* ---- Goal-led report sections ------------------------------------------
   The primary goal's section leads at full size; the secondaries share a row
   under it, one cell each. The proportion is the point — a secondary is a
   glance, not a second report — so the halves are sized against the primary
   rather than by their own content: `--goalsec-h` is the primary's minimum
   height and the row takes half of it.

   Two across on a phone as well as a desktop. At 393px each cell is ~170px,
   which still holds a label, a number and one line of context; stacking them
   would cost the side-by-side comparison that is the whole reason they share
   a row. */
.goalsec { --goalsec-h: 15rem; margin-bottom: var(--ds-space-4); }
.goalsec__primary { min-height: var(--goalsec-h); margin-bottom: var(--ds-space-3); }
.goalsec__row {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--ds-space-3);
}
.goalsec__secondary {
  min-height: calc(var(--goalsec-h) * 0.45);
  display: flex; flex-direction: column; margin: 0;
}
.goalsec__head { display: flex; align-items: center; gap: var(--ds-space-3); }
.goalsec__ico {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: var(--ds-radius-md);
  display: flex; align-items: center; justify-content: center;
  background: var(--ds-primary-soft); color: var(--ds-link);
}
.goalsec__ico--sm { width: 28px; height: 28px; background: var(--ds-accent-soft); color: var(--ds-accent); }
.goalsec__ico svg { width: 22px; height: 22px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.goalsec__ico--sm svg { width: 16px; height: 16px; }
/* The goal's NAME, in sentence case. An `.eyebrow` above it already carries the
   uppercase label, and two uppercase lines stacked read as one shouted block
   with no hierarchy — which is exactly how it looked before. */
.goalsec__name { font-size: var(--ds-fs-h2); font-weight: var(--ds-fw-bold);
  color: var(--ds-ink); margin: 0; line-height: 1.2; }
.goalsec__name--sm { font-size: var(--ds-fs-base); }
.goalsec__value { font-size: var(--ds-fs-display); font-weight: var(--ds-fw-bold);
  color: var(--ds-ink); margin: var(--ds-space-3) 0 0; line-height: 1.1; }
/* The number and its unit stay on one line: "128/82" wrapping away from "mmHg"
   reads as two facts rather than one reading. The cell is sized to hold it. */
.goalsec__value--sm { font-size: var(--ds-fs-h2); margin-top: var(--ds-space-2);
  white-space: nowrap; }
.goalsec__value .u { font-size: var(--ds-fs-sm); font-weight: var(--ds-fw-medium); color: var(--ds-muted); }
/* The tone classes are the stat tile's, so "good" means what the GOAL says it
   means — `goals.delta_class()` decides, never this stylesheet. */
.goalsec__sub { font-size: var(--ds-fs-sm); color: var(--ds-muted); margin: var(--ds-space-1) 0 0; }
.goalsec__sub.is-good { color: var(--ds-success); }
.goalsec__sub.is-warn { color: var(--ds-danger); }
.goalsec__focus { font-size: var(--ds-fs-xs); margin: var(--ds-space-2) 0 0; }
.goalsec__tiles {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--ds-space-2);
  margin-top: var(--ds-space-4);
}
.goalsec__tiles:empty { display: none; }
.goalsec__tile {
  background: var(--ds-surface-alt); border-radius: var(--ds-radius-md);
  padding: var(--ds-space-2) var(--ds-space-3); display: flex; flex-direction: column;
}
.goalsec__tile-label { font-size: var(--ds-fs-xs); color: var(--ds-muted);
  text-transform: uppercase; letter-spacing: 0.05em; }
.goalsec__tile-value { font-size: var(--ds-fs-h3); font-weight: var(--ds-fw-bold); color: var(--ds-ink); }
.goalsec__tile-value .u { font-size: var(--ds-fs-xs); font-weight: var(--ds-fw-regular); color: var(--ds-muted); }
.goalsec__tile-sub { font-size: var(--ds-fs-xs); color: var(--ds-faint); }
@media (min-width: 700px) {
  .goalsec__tiles { grid-template-columns: repeat(4, 1fr); }
}

/* ---- Food item row -----------------------------------------------------
   Three stacked lines rather than one flex row. At 390px a description, its
   macro breakdown, a calorie figure and two actions cannot share a line: the
   text ends up wrapping around the buttons. Stacking keeps the name and the
   calories on the same baseline (the pairing people scan for) and drops
   everything else beneath. */
.fooditem { padding: var(--ds-space-3) 0; border-top: 1px solid var(--ds-border); }
.fooditem:first-of-type { border-top: 0; }
.fooditem__top { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--ds-space-3); }
.fooditem__name { min-width: 0; font-weight: var(--ds-fw-semibold); color: var(--ds-ink); }
.fooditem__kcal { flex: 0 0 auto; font-weight: var(--ds-fw-bold); color: var(--ds-link);
  white-space: nowrap; }
.fooditem__meta { margin-top: var(--ds-space-1); font-size: var(--ds-fs-sm);
  color: var(--ds-muted); }
.fooditem__actions { display: flex; align-items: center; gap: var(--ds-space-4);
  margin-top: var(--ds-space-1); font-size: var(--ds-fs-xs); }
.fooditem__actions a { color: var(--ds-muted); }
.fooditem__actions form { display: inline; }
.fooditem__actions .linklike { font-size: var(--ds-fs-xs); }
/* The one action in this row that is an offer rather than housekeeping, so it
   is copper-as-text against Edit/Delete's muted grey. Matched under
   .fooditem__actions to beat that rule's colour, not by shouting !important. */
.fooditem__actions .fooditem__gap { color: var(--ds-link); }

/* ---- Print — the clinician one-pager -----------------------------------
   The Reports page IS the handout: a "Print / Save as PDF" button hands it to
   the browser's own PDF engine, which renders the real Chart.js canvases. A
   server-side renderer (WeasyPrint) would not — it runs no JavaScript, so
   every graph would print as an empty box.

   Ink, not theme. A printed page is read on white paper by someone who is not
   the user, so this block deliberately IGNORES the dark/light tokens and forces
   black-on-white. That is the one place a `[data-theme]`-independent override
   is correct, and it is why these rules live under an @media block rather than
   in a component: nothing here restyles a component for the screen. */
@media print {
  /* Chrome/Safari drop background colours by default; without this the tokens'
     dark surfaces would print as invisible-on-invisible when a user overrides. */
  :root, [data-theme] {
    --ds-bg: #fff; --ds-surface: #fff; --ds-ink: #000; --ds-text: #000;
    --ds-muted: #444; --ds-border: #999;
  }
  body { background: #fff; color: #000; }
  /* Navigation, actions and anything that only exists to be tapped. The page
     is a document now — a control that cannot be operated is noise on paper. */
  .app-header, .nav, .tabbar, .flash, .btn, .linklike, button,
  .no-print { display: none !important; }
  body.has-tabbar { padding-bottom: 0; }
  main { padding: 0; max-width: none; }
  /* Cards become plain sections: a shadow prints as a grey smear. */
  .card, .goalsec__primary, .goalsec__secondary {
    box-shadow: none; border: 1px solid #999; break-inside: avoid;
  }
  .chart-box, .goalsec__primary, .goalsec__secondary { break-inside: avoid; }
  /* Charts are canvases — let them size to the paper, not the viewport. */
  .chart-box { height: auto; max-height: 8cm; }
  canvas { max-width: 100% !important; height: auto !important; }
  /* A URL a clinician cannot click is noise; the report's own address is
     already printed in the header block. */
  a[href]::after { content: none !important; }
  /* Print-only context the screen doesn't need — who, what range, generated
     when — so a loose page on a desk is still identifiable. */
  .print-only { display: block !important; }
}
/* Print-only blocks are hidden on screen; the @media block above reveals them. */
.print-only { display: none; }
