/* Piehole UI — no build step, no external assets. */

:root {
  color-scheme: light;
  --surface-0: #f4f4f1;
  --surface-1: #fcfcfb;
  --surface-2: #eceae5;
  --border:    #dcdad3;
  --text-primary:   #0b0b0b;
  --text-secondary: #52514e;
  --text-muted:     #7a7873;

  /* KNOWN ISSUE (measured 2026-07-27) — this pair is not colourblind-safe.
     The comment that used to sit here described "slots 1 and 8 (blue, red),
     adjacent CVD ΔE 21.6 light / 19.2 dark". Slot 1 is no longer blue, and the
     numbers moved with it: green vs red measures ΔE 4.7 (deutan) in light and
     3.6 in dark, against a floor of 6. Deuteranopia is ~1 in 12 men, and it is
     the single most common way to be unable to read a chart.

     Affects every 2-series chart: BP (systolic/diastolic), macros, body
     composition. Re-stepping --series-1 to a blue fixes all of them at once.
     NOT done here because repainting every chart is a design decision, not a
     side effect. See CLAUDE.md "Open decisions".

     Until it is fixed, new charts must not depend on telling these two apart —
     see rangeBars() in app.js, which uses one hue and labels both ends. */
  --series-1: #1f9d5b;
  --series-2: #e34948;

  --good:     #0ca30c;
  --warning:  #fab219;
  --critical: #d03b3b;
  /* --warning is a FILL colour: #fab219 on white is about 1.9:1, nowhere near
     readable as small text. This is its text-safe partner (~5.9:1 on the light
     surface), for warning words rather than warning shapes. */
  --warning-text: #8a5a00;

  --radius: 10px;
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:where(:not([data-theme='light'])) {
    color-scheme: dark;
    --surface-0: #121211;
    --surface-1: #1a1a19;
    --surface-2: #252523;
    --border:    #383835;
    --text-primary:   #ffffff;
    --text-secondary: #c3c2b7;
    --text-muted:     #8e8d85;
    --series-1: #3bbd78;
    --series-2: #e66767;
    --warning-text: #fbbf24;
  }
}
:root[data-theme='dark'] {
  color-scheme: dark;
  --surface-0: #121211;
  --surface-1: #1a1a19;
  --surface-2: #252523;
  --border:    #383835;
  --text-primary:   #ffffff;
  --text-secondary: #c3c2b7;
  --text-muted:     #8e8d85;
  --series-1: #3bbd78;
  --series-2: #e66767;
  --warning-text: #fbbf24;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  background: var(--surface-0);
  color: var(--text-primary);
  font-size: 15px;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
  padding: 10px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
header h1 { font-size: 1rem; margin: 0; font-weight: 650; letter-spacing: -0.01em; }
header .who { color: var(--text-muted); font-size: 0.85rem; margin-left: auto; }

/* Bottom tab bar — the primary navigation. Fixed to the bottom of the viewport
   with the food-logging camera elevated in the centre. */
nav.tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 100;
  display: flex;
  justify-content: space-around;
  align-items: flex-end;
  gap: 2px;
  padding: 8px 6px calc(8px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--surface-1) 90%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--border);
}
nav.tabbar .tab {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.66rem;
  font-weight: 600;
  cursor: pointer;
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 4px 2px;
}
nav.tabbar .tab svg { width: 24px; height: 24px; }
nav.tabbar .tab[aria-selected='true'] { color: var(--series-1); }
/* The centre camera button: elevated, filled, the primary action. */
nav.tabbar .tab.snap {
  flex: 0 0 auto;
  color: #fff;
  transform: translateY(-14px);
}
nav.tabbar .tab.snap .snap-circle {
  width: 56px; height: 56px; border-radius: 20px;
  background: var(--series-1);
  display: grid; place-items: center;
  box-shadow: 0 8px 20px color-mix(in srgb, var(--series-1) 45%, transparent);
}
nav.tabbar .tab.snap[aria-selected='true'] { color: #fff; }
nav.tabbar .tab.snap svg { width: 28px; height: 28px; }

/* "More" bottom sheet for the secondary tabs. */
.sheet {
  position: fixed; inset: 0; z-index: 1100;
  background: rgba(0,0,0,.4);
  display: flex; align-items: flex-end;
}
.sheet-panel {
  width: 100%;
  background: var(--surface-1);
  border-radius: 20px 20px 0 0;
  border-top: 1px solid var(--border);
  padding: 8px 14px calc(20px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 4px;
}
.sheet-handle { width: 38px; height: 4px; border-radius: 99px; background: var(--border); margin: 6px auto 10px; }
.sheet-item {
  display: flex; align-items: center; gap: 14px;
  font: inherit; font-size: 1rem; text-align: left;
  padding: 14px 12px; border-radius: 12px;
  border: 0; background: transparent; color: var(--text-primary); cursor: pointer;
}
.sheet-item:hover { background: var(--surface-2); }
.sheet-item svg { width: 22px; height: 22px; color: var(--text-secondary); }

/* A phone-first app: constrain to a phone-width column and centre it, so on a
   desktop it reads as an app rather than a stretched, empty page. */
/* The bottom padding reserves the strip the fixed bar covers. --tabbar-h is
   measured in app.js (the "+" overflows above the bar, so the covered strip is
   taller than the element); the 96px literal is only the pre-measurement
   fallback, and 20px keeps the last control off the bar rather than against it. */
main {
  padding: 16px 16px calc(var(--tabbar-h, 96px) + 20px);
  max-width: 480px;
  margin: 0 auto;
}
header { max-width: 480px; margin: 0 auto; }
nav.tabbar { max-width: 480px; margin: 0 auto; }
.sheet-panel { max-width: 480px; margin: 0 auto; }

/* Tucked away while scrolling down a long page, back on the way up. The extra
   30px clears the "+", which sits above the bar's own box and would otherwise
   stay peeking over the bottom edge. */
nav.tabbar { transition: transform 0.22s ease; will-change: transform; }
nav.tabbar.is-tucked { transform: translateY(calc(100% + 30px)); }
@media (prefers-reduced-motion: reduce) {
  nav.tabbar { transition: none; }
}
section[hidden] { display: none; }
body { background: var(--surface-0); }

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
}
.card h2 { margin: 0 0 4px; font-size: 0.95rem; font-weight: 650; }
.card h2 + .sub { margin: 0 0 14px; color: var(--text-muted); font-size: 0.82rem; }

/* Stat tiles — a hero number needs no chart. */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.tile {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
}
.tile .label { color: var(--text-secondary); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; }
.tile .value { font-size: 1.9rem; font-weight: 620; letter-spacing: -0.02em; margin-top: 2px; font-variant-numeric: tabular-nums; }
.tile .value .unit { font-size: 0.9rem; font-weight: 450; color: var(--text-secondary); margin-left: 3px; }
.tile .meta { color: var(--text-muted); font-size: 0.76rem; margin-top: 4px; }

/* Expandable tiles — the day's individual readings behind a tapped tile. */
.tile.expandable { cursor: pointer; }
.tile.expandable:focus-visible { outline: 2px solid var(--series-1); outline-offset: 2px; }
.tile .caret { margin-left: 4px; display: inline-block; transition: transform 0.15s ease; }
.tile.open .caret { transform: rotate(180deg); }
.tile-readings { list-style: none; margin: 8px 0 0; padding: 8px 0 0; border-top: 1px solid var(--border); }
.tile-readings li {
  display: flex; justify-content: space-between; gap: 10px; padding: 3px 0;
  font-size: 0.78rem; font-variant-numeric: tabular-nums; color: var(--text-secondary);
}
.tile-readings li strong { font-weight: 600; color: var(--text-primary); }
/* An explicit display rule would beat the `hidden` attribute and leave the list
   permanently open — the same trap the More sheet fell into. */
.tile-readings[hidden] { display: none !important; }

/* Charts */
.chart-wrap { position: relative; overflow-x: auto; }
svg.chart { display: block; width: 100%; height: auto; touch-action: pan-y; }
svg.chart .grid line { stroke: var(--border); stroke-width: 1; }
svg.chart .axis text { fill: var(--text-muted); font-size: 11px; font-family: var(--font); }
svg.chart .line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
svg.chart .dot { stroke: var(--surface-1); stroke-width: 2; }
/* Daily readings behind a trend line: present, but clearly not the headline.
   Kept visible rather than dropped — hiding the scatter would overstate how
   precise the underlying measurements are. */
svg.chart .line.raw { stroke-width: 1.5; opacity: 0.28; }
svg.chart .dot.raw { stroke-width: 1.5; opacity: 0.4; }
/* Range bars. Values wear text tokens, never the series colour — the bar beside
   them carries identity, and a number tinted with the data reads as decoration. */
svg.chart .rb-label { fill: var(--text-primary); font-size: 12px; font-weight: 600; font-family: var(--font); }
svg.chart .rb-sub { fill: var(--text-muted); font-size: 10.5px; font-family: var(--font); }
svg.chart .rb-value { fill: var(--text-secondary); font-size: 11.5px; font-variant-numeric: tabular-nums; font-family: var(--font); }
svg.chart .crosshair { stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 3 3; }
svg.chart .endlabel { font-size: 11px; font-weight: 600; fill: var(--text-primary); font-variant-numeric: tabular-nums; }

.legend { display: flex; gap: 16px; flex-wrap: wrap; margin: 0 0 10px; padding: 0; list-style: none; }
.legend li { display: flex; align-items: center; gap: 6px; font-size: 0.82rem; color: var(--text-secondary); }
.legend .swatch { width: 11px; height: 11px; border-radius: 3px; flex: none; }

.tooltip {
  position: absolute;
  pointer-events: none;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 0.8rem;
  box-shadow: 0 4px 14px rgba(0,0,0,0.18);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.08s;
  z-index: 5;
}
.tooltip.on { opacity: 1; }
.tooltip .tt-date { color: var(--text-muted); margin-bottom: 3px; }
.tooltip .tt-row { display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
.tooltip .tt-trend { color: var(--text-muted); }

/* Forms */
label { display: block; font-size: 0.82rem; color: var(--text-secondary); margin-bottom: 4px; }
input, select, textarea {
  font: inherit;
  color: var(--text-primary);
  background: var(--surface-0);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  width: 100%;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--series-1); outline-offset: 1px; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.row > * { flex: 1 1 130px; }
.row > .narrow { flex: 0 1 100px; }
/* The rest of the .row vocabulary. These were style="flex:2 1 220px" and the
   like until it turned out the CSP drops inline style attributes, so none of
   them had ever taken effect. Named by role rather than by pixel value, which
   collapses three near-identical bases (180/200/220) into one. */
.row > .row-wide  { flex: 2 1 200px; }
.row > .row-fixed { flex: 0 0 auto; }
.row > .row-check { flex: 0 1 auto; padding-bottom: 8px; }
.row-grow { flex: 1; }
/* A checkbox must not stretch to the full width its input rule would give it. */
.chk { width: auto; }
/* Servings box on a recipe ingredient row. */
.serv-input { width: 70px; }

button.btn {
  font: inherit;
  font-weight: 560;
  cursor: pointer;
  border-radius: 8px;
  padding: 8px 16px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-primary);
  white-space: nowrap;
}
button.btn:hover { border-color: var(--text-muted); }
button.btn.primary { background: var(--series-1); border-color: var(--series-1); color: #fff; }
button.btn.primary:hover { filter: brightness(1.08); }
button.btn.danger { color: var(--critical); }
button.btn:disabled { opacity: 0.5; cursor: not-allowed; }

table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
th { text-align: left; font-weight: 600; color: var(--text-secondary); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; }
th, td { padding: 7px 8px; border-bottom: 1px solid var(--border); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
tr:last-child td { border-bottom: 0; }
.table-wrap { overflow-x: auto; }

.pill { display: inline-block; padding: 1px 8px; border-radius: 99px; font-size: 0.75rem; background: var(--surface-2); color: var(--text-secondary); }
.pass { color: var(--good); font-weight: 600; }
.fail { color: var(--text-muted); }

.results { list-style: none; margin: 10px 0 0; padding: 0; max-height: 280px; overflow-y: auto; }
.results li { display: flex; align-items: center; gap: 10px; padding: 8px; border-bottom: 1px solid var(--border); }
.results li:hover { background: var(--surface-2); }
.results .name { flex: 1; min-width: 0; }
.results .name small { display: block; color: var(--text-muted); }

.msg { padding: 10px 12px; border-radius: 8px; margin-bottom: 12px; font-size: 0.87rem; }
.msg.err { background: color-mix(in srgb, var(--critical) 12%, var(--surface-1)); border: 1px solid var(--critical); }
.msg.ok  { background: color-mix(in srgb, var(--good) 12%, var(--surface-1)); border: 1px solid var(--good); }
.empty { color: var(--text-muted); font-size: 0.87rem; padding: 12px 0; }
/* Plain-language summary under a chart — the sentence that says what the lines
   mean, for the reader who won't decode two trend lines themselves. */
.readout { padding: 4px 0 2px; font-size: 0.92rem; }
.readout p { margin: 0 0 4px; }
.readout .sub { color: var(--text-muted); font-size: 0.85rem; }
/* Ledger rows: label left, figure right, so the numbers form a scannable
   column rather than hiding inside sentences. */
.er-table { margin: 0 0 10px; }
.er-row { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0;
  font-variant-numeric: tabular-nums; }
.er-row + .er-row { border-top: 1px solid var(--border); }
.er-row.er-sep { border-top-width: 2px; }

.gate { max-width: 420px; margin: 12vh auto; }
.toggle-theme { background: none; border: 1px solid var(--border); border-radius: 8px; color: var(--text-secondary); cursor: pointer; padding: 4px 9px; font-size: 0.8rem; }

@media (max-width: 560px) {
  main { padding: 12px; }
  .tile .value { font-size: 1.6rem; }
  header .who { display: none; }
}

/* One-tap drink shortcuts. Sized for a thumb: the whole point is that logging
   a glass of water costs one deliberate tap and no decisions. */
.quick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 8px;
}
.quick-add {
  font: inherit;
  cursor: pointer;
  padding: 14px 10px;
  min-height: 60px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  text-align: center;
  line-height: 1.2;
}
.quick-add:hover { background: var(--surface-2); }
.quick-add:active { transform: scale(0.97); }
.quick-add .kcal { font-size: 0.75rem; color: var(--text-muted); }
/* Confirmation has to be visible without moving the thumb or reading text. */
.quick-add.logged {
  border-color: var(--series-1);
  background: var(--surface-2);
}

/* Fasting clock. Big enough to read at arm's length — it is the one number on
   this tab you check without intending to interact. */
.fasting-card { text-align: center; }
.fasting-label {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.fasting-value {
  font-size: 2.6rem;
  font-weight: 600;
  line-height: 1.1;
  margin: 6px 0 4px;
  font-variant-numeric: tabular-nums; /* digits must not jitter as it ticks */
}
.fasting-meta { font-size: 0.85rem; color: var(--text-secondary); }

/* Timeline of everything consumed. The rail is a single continuous line so
   gaps between entries read as elapsed time rather than as list spacing. */
.timeline { position: relative; margin: 0; padding: 0 0 0 62px; list-style: none; }
.timeline::before {
  content: '';
  position: absolute;
  left: 54px; top: 6px; bottom: 6px;
  width: 2px;
  background: var(--border);
}
.tl-item { position: relative; padding: 7px 0; }
.tl-time {
  position: absolute;
  left: -62px;
  width: 44px;
  text-align: right;
  font-size: 0.82rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.tl-item::before {
  content: '';
  position: absolute;
  left: -12px; top: 13px;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--series-1);
  border: 2px solid var(--surface-1);
}
.tl-item.drink::before { background: var(--text-muted); }
.tl-name { font-size: 0.95rem; }
.tl-sub { font-size: 0.78rem; color: var(--text-muted); }
/* A fasting window between two meals, shown on the rail itself. */
.tl-gap {
  position: relative;
  padding: 6px 0 6px 0;
  font-size: 0.78rem;
  color: var(--text-muted);
  font-style: italic;
}

/* Timeline rows are individually removable — a photographed plate often
   catches something you didn't actually eat. */
.tl-line { display: flex; align-items: center; gap: 8px; }
.tl-item-name { flex: 1 1 auto; }
.tl-del {
  flex: 0 0 auto;
  font: inherit;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  /* 30px is the smallest comfortable touch target that doesn't dominate the
     row; the padding does the work, not the glyph. */
  width: 30px;
  height: 30px;
  border-radius: 6px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-muted);
}
.tl-del:hover, .tl-del:focus-visible {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-primary);
}

/* Edit control sits opposite the time on a timeline row. Quiet until wanted —
   editing time/meal is occasional, not the primary action. */
.tl-item { display: flex; flex-wrap: wrap; }
.tl-edit {
  margin-left: auto;
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  padding: 2px 8px;
  border-radius: 6px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-muted);
}
.tl-edit:hover, .tl-edit:focus-visible {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-primary);
}
.tl-item .tl-name, .tl-item .tl-sub { flex-basis: 100%; }

/* Inline edit form — native datetime-local gives a real picker on iOS with no
   library, in the phone's own 12-hour format. */
.tl-edit-form { flex-basis: 100%; display: flex; flex-direction: column; gap: 10px; }
.tl-edit-names { font-size: 0.85rem; color: var(--text-secondary); }
.tl-edit-form label { display: flex; flex-direction: column; gap: 4px; font-size: 0.8rem; color: var(--text-muted); }
.tl-edit-form input, .tl-edit-form select {
  font: inherit;
  padding: 9px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
}
.tl-edit-actions { display: flex; gap: 8px; }

/* Count stepper on the photo-confirm screen. The user corrects what the model
   counted — a known fact, not a portion guess. */
.stepper { display: inline-flex; align-items: center; gap: 0; }
.step-btn {
  font: inherit;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  width: 32px; height: 32px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
}
.step-btn:first-child { border-radius: 8px 0 0 8px; }
.step-btn:last-child { border-radius: 0 8px 8px 0; }
.step-btn:active { background: var(--surface-2); }
.step-val {
  font: inherit;
  width: 44px;
  height: 32px;
  text-align: center;
  border: 1px solid var(--border);
  border-left: 0; border-right: 0;
  background: var(--surface-1);
  color: var(--text-primary);
  -moz-appearance: textfield;
}
.step-val::-webkit-outer-spin-button,
.step-val::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Shared macro footer under both consumption timelines (Today and Photo).
   Four equal cells so the day's numbers read at a glance. */
.macro-footer {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.macro-cell { text-align: center; }
.macro-val { font-size: 1.3rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.macro-unit { font-size: 0.8rem; font-weight: 400; color: var(--text-muted); margin-left: 1px; }
.macro-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-top: 2px;
}

/* History: one tappable row per day. Calories lead; macros trail small. */
.history-row {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  padding: 12px 14px;
  margin-bottom: 8px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
}
.history-row:hover, .history-row:focus-visible { background: var(--surface-2); }
.history-date { font-weight: 600; }
.history-meta { font-size: 0.8rem; color: var(--text-muted); margin: 2px 0 6px; }
.history-macros {
  display: flex; gap: 12px; align-items: baseline;
  font-size: 0.82rem; color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.history-kcal { font-size: 1.05rem; font-weight: 600; color: var(--text-primary); }
.history-kcal small { font-size: 0.72rem; font-weight: 400; color: var(--text-muted); }

/* Meal thumbnail on a timeline row. Small by default; tap to view larger via
   the browser's own image controls (it's already low-res, so this is enough). */
.tl-photo {
  display: block;
  margin-top: 8px;
  max-width: 140px;
  width: 100%;
  border-radius: 8px;
  border: 1px solid var(--border);
}

/* "fix" — correct a wrong food match on a logged item. */
.tl-fix {
  flex: 0 0 auto;
  font: inherit;
  font-size: 0.76rem;
  cursor: pointer;
  padding: 2px 8px;
  border-radius: 6px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-muted);
}
.tl-fix:hover, .tl-fix:focus-visible {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-primary);
}
.tl-fix-box { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.tl-fix-input {
  flex: 1 1 180px;
  font: inherit;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
}
.tl-fix-results { flex-basis: 100%; display: flex; flex-direction: column; gap: 4px; }
.tl-fix-hit {
  text-align: left;
  font: inherit;
  cursor: pointer;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-primary);
}
.tl-fix-hit:hover { background: var(--surface-2); }
.tl-fix-hit small { color: var(--text-muted); }

/* "+ add photo" on a meal that has none yet. */
.tl-addphoto {
  margin-top: 8px;
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
  padding: 6px 12px;
  border-radius: 8px;
  border: 1px dashed var(--border);
  background: transparent;
  color: var(--text-secondary);
}
.tl-addphoto:hover { background: var(--surface-2); color: var(--text-primary); }

/* Tap a meal thumbnail to view the full-resolution original. */
.tl-photo { cursor: zoom-in; }
.lightbox {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0,0,0,0.9);
  display: flex; align-items: center; justify-content: center;
  padding: 16px; cursor: zoom-out;
}
.lightbox img { max-width: 100%; max-height: 100%; border-radius: 6px; }
.lightbox-msg { color: #fff; font-size: 0.9rem; }
/* Sits over the photo rather than beside it: the image should still get the
   whole screen, which is the reason for opening it. cursor:auto because the
   overlay is zoom-out and these are not. */
.lb-tools {
  position: absolute; left: 0; right: 0; bottom: 20px;
  display: flex; gap: 8px; justify-content: center; align-items: center;
  cursor: auto;
}
.lb-tools .btn { background: rgba(255,255,255,0.92); color: #111; border: 0; }
.lb-state { color: #fff; font-size: 0.85rem; display: flex; gap: 8px; }

/* Timeline filter: All / Meals / Exercise. */
.tl-filter { display: flex; gap: 6px; margin-bottom: 12px; }
.tl-filter button {
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
  padding: 5px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-1);
  color: var(--text-secondary);
}
.tl-filter button.active {
  background: var(--series-1);
  border-color: var(--series-1);
  color: #fff;
}
/* Pure CSS filtering — no re-render, just hide the type not wanted. */
.tl-wrap.filter-meals .tl-event.exercise { display: none; }
.tl-wrap.filter-exercise .tl-event.meal,
.tl-wrap.filter-exercise .tl-gap,
.tl-wrap.filter-exercise .macro-footer { display: none; }
/* Exercise events get a distinct rail dot. */
.tl-item.exercise::before { background: var(--series-2); }

/* ============================================================================
   v2 redesign — photo-forward, SnapCalorie-inspired. Appended so these win the
   cascade without surgically editing the rules above.
   ============================================================================ */
:root { --radius: 18px; }

/* A card that carries a title but no box — lets its children (meal cards) be
   the visual units, sitting on the page background. */
.card.bare { background: transparent; border: 0; box-shadow: none; padding: 0; }
.card.bare > h2 { padding: 0 4px; }
.card.bare > .sub { padding: 0 4px; }

/* Timeline: a stack of cards, not a dotted rail. */
.timeline { padding: 0; display: flex; flex-direction: column; gap: 12px; }
.timeline::before { display: none; }
.tl-item::before { display: none; }
.tl-item {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 8px 20px rgba(0,0,0,.05);
}
.tl-time {
  position: static; left: auto; width: auto; text-align: left;
  font-size: 0.8rem; font-weight: 700; color: var(--text-secondary);
}
.tl-edit { margin-left: auto; }
.tl-gap { text-align: center; color: var(--text-muted); padding: 0; margin: -2px 0; }

/* Macro colours (protein / carbs / fats), used by the chips and bars. */
:root { --protein: #e0574c; --carbs: #2f9dc4; --fats: #e2a63a; }

/* Meal card: a small left thumbnail, calories, macro chips — then the item
   list (with fix/delete) below. Matches the reference. */
.tl-item.meal { padding: 12px; }
.mc-head { display: flex; align-items: center; gap: 12px; width: 100%; }
.mc-thumb {
  width: 64px; height: 64px; flex: 0 0 auto;
  border-radius: 14px; overflow: hidden; position: relative;
}
.mc-thumb .tl-photo {
  width: 100%; height: 100%; object-fit: cover; margin: 0; border: 0; border-radius: 14px;
  max-width: none;
}
.mc-empty {
  display: grid; place-items: center; cursor: pointer;
  background: var(--surface-2); color: var(--text-muted); border: 1px dashed var(--border);
}
.mc-empty svg { width: 22px; height: 22px; }
.mc-time {
  position: absolute; left: 4px; bottom: 4px;
  font-size: 9px; font-weight: 700; color: #fff;
  background: rgba(0,0,0,.5); padding: 1px 5px; border-radius: 5px;
}
.mc-main { flex: 1 1 auto; min-width: 0; }
.mc-cal { font-size: 0.98rem; font-weight: 800; letter-spacing: -.01em; }
.mc-sub { font-size: 0.75rem; color: var(--text-muted); margin-top: 1px; }
.mc-chips { display: flex; gap: 10px; margin-top: 7px; }
.chip { display: inline-flex; align-items: center; gap: 4px; font-size: 0.74rem; font-weight: 700; color: var(--text-secondary); }
.chip b { width: 16px; height: 16px; border-radius: 50%; color: #fff; font-size: 9px; display: grid; place-items: center; }
.chip.p b { background: var(--protein); }
.chip.c b { background: var(--carbs); }
.chip.f b { background: var(--fats); }
.tl-item.meal .tl-edit { align-self: flex-start; flex: 0 0 auto; }
/* Item list under the summary. */
.mc-items { display: flex; flex-direction: column; gap: 2px; margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); }
.mc-items:empty { display: none; }
.mc-items .tl-line { font-size: 0.85rem; }
.tl-item.drink .mc-items { border-top: 0; margin-top: 0; padding-top: 0; }
.tl-addphoto { margin-top: 10px; }

/* Fasting hero — big and green, the first thing on Today. */
.fasting-card { padding: 26px 20px 24px; }
.fasting-label { color: var(--series-1); }
.fasting-value { font-size: 3.1rem; line-height: 1; color: var(--series-1); margin: 8px 0 6px; }

/* Round, prominent snap button; slimmer bar. */
nav.tabbar { padding: 6px 4px calc(6px + env(safe-area-inset-bottom)); }
nav.tabbar .tab { font-size: 0.6rem; gap: 2px; }
nav.tabbar .tab svg { width: 23px; height: 23px; }
nav.tabbar .tab.snap { transform: translateY(-16px); }
nav.tabbar .tab.snap .snap-circle { width: 60px; height: 60px; border-radius: 50%; }

/* More sheet: hug its content, don't sprawl. */
.sheet-panel { padding: 6px 14px calc(14px + env(safe-area-inset-bottom)); }
.sheet-item { padding: 13px 12px; font-size: 0.98rem; }

/* Stat tiles a touch rounder to match. */
.tile { border-radius: var(--radius); }

/* Label under the "+" so it reads as the log action. */
nav.tabbar .tab.snap .snap-lbl {
  display: block; font-size: 0.6rem; font-weight: 700; color: var(--series-1); margin-top: 3px;
}

/* Prominent "Log a meal" action on Today — logging must never depend on the
   bottom bar, which iOS Safari's toolbar can obscure. */
.log-cta {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; margin: 4px 0 16px;
  font: inherit; font-size: 1rem; font-weight: 700;
  padding: 15px; border-radius: var(--radius); border: 0; cursor: pointer;
  background: var(--series-1); color: #fff;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--series-1) 40%, transparent);
}
.log-cta svg { width: 20px; height: 20px; }

/* Keep the More sheet compact — it only holds a few items. */
.sheet-item { padding: 12px; gap: 14px; }
.sheet-panel { gap: 2px; }

/* More: a compact popup above the bar, NOT a half-screen sheet that buries the
   page behind it. Sized to its few items; tap anywhere off it to dismiss. */
.sheet { background: transparent; }
.sheet-panel {
  position: absolute; bottom: 84px; right: 12px; left: auto;
  width: auto; min-width: 220px; max-width: 260px; margin: 0;
  border: 1px solid var(--border); border-radius: 16px;
  box-shadow: 0 12px 34px rgba(0,0,0,.28); padding: 6px;
}
.sheet-handle { display: none; }
.sheet-item { padding: 11px 12px; font-size: 0.92rem; gap: 12px; }
.sheet-item svg { width: 20px; height: 20px; }

/* CRITICAL: `.sheet { display: flex }` overrode the `hidden` attribute, so the
   More popup never hid — it sat over the page and its invisible layer ate every
   tap (that's why "Log a meal" did nothing). Make hidden actually hide it. */
.sheet[hidden] { display: none !important; }

/* ── Fasting gauge hero + macro bars ─────────────────────────────────────── */
.fasting-hero { text-align: center; padding: 22px 20px 20px; }
.gauge { position: relative; width: 240px; max-width: 90%; margin: 0 auto; }
.gauge-svg { width: 100%; height: auto; display: block; overflow: visible; }
.gauge-track { fill: none; stroke: var(--surface-2); stroke-width: 15; stroke-linecap: round; }
.gauge-fill {
  fill: none; stroke: var(--series-1); stroke-width: 15; stroke-linecap: round;
  transition: stroke-dasharray 0.6s ease;
}
.gauge-center { position: absolute; left: 0; right: 0; top: 46%; }
.fasting-hero .fasting-value {
  /* As big as fits: scales with the phone width but capped, so a double-digit
     "13h 20m" never overgrows the gauge. */
  font-size: clamp(1.9rem, 8.5vw, 2.35rem); line-height: 1; color: var(--series-1);
  font-weight: 800; letter-spacing: -0.03em; font-variant-numeric: tabular-nums;
  margin: 0; white-space: nowrap;
}
/* Small, muted "h"/"m" units keep the digits the focus and the string compact. */
.fasting-value .u {
  font-size: 0.5em; font-weight: 700; letter-spacing: 0;
  opacity: 0.6; margin-left: 0.04em; margin-right: 0.02em;
}
.gauge-cap {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-muted); margin-top: 4px;
}
.gauge-goal { font-size: 0.8rem; color: var(--text-muted); margin-top: 4px; }

.macbars { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 20px; }
.mb { text-align: center; }
.mb-g { font-size: 1rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.mb-g span { font-size: 0.72rem; font-weight: 600; color: var(--text-muted); margin-left: 1px; }
/* The target, beside the running total. A bar with no limit on it is a shape. */
.mb-of { font-size: 0.72rem; font-weight: 600; color: var(--text-muted); margin-left: 1px; }
.mb-track { height: 9px; border-radius: 99px; overflow: hidden; margin: 6px 0; }
/* Unfilled track is a lighter step of the fill's own hue, not neutral grey, so
   the whole bar reads as one meter rather than a bar sitting on a background. */
.mb.p .mb-track { background: color-mix(in oklab, var(--protein) 18%, var(--surface-2)); }
.mb.c .mb-track { background: color-mix(in oklab, var(--carbs) 18%, var(--surface-2)); }
.mb.f .mb-track { background: color-mix(in oklab, var(--fats) 18%, var(--surface-2)); }
.mb-fill {
  height: 100%; border-radius: 99px; width: 0;
  /* Eases out so it decelerates into place — the meter arrives rather than
     stopping dead. Slow enough to read as movement after logging a meal. */
  transition: width 0.65s cubic-bezier(0.22, 0.61, 0.36, 1), background-color 0.3s ease;
}
.mb.p .mb-fill { background: var(--protein); }
.mb.c .mb-fill { background: var(--carbs); }
.mb.f .mb-fill { background: var(--fats); }
/* Past target: the bar is full either way, so the colour has to carry it. */
.mb.over .mb-fill { background: var(--warning); }
.mb.over .mb-g b { color: var(--warning); }
.mb-lbl { font-size: 0.72rem; font-weight: 700; color: var(--text-secondary); }

/* A meter that animates is a nicety; motion sickness is not. */
@media (prefers-reduced-motion: reduce) {
  .mb-fill { transition: none; }
}

/* Goal reference line on the calories chart — dashed and muted so it reads as
   a benchmark rather than another data series. */
.chart .goal-line {
  stroke: var(--text-muted); stroke-width: 1.5;
  stroke-dasharray: 5 4; opacity: 0.7;
}
.chart .goal-label {
  fill: var(--text-muted); font-size: 10px; font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase;
}

/* Login / signup gate: the mode-switch line under the button. */
.gate-switch { margin: 14px 0 0; font-size: 0.9rem; color: var(--text-muted); text-align: center; }
.gate-switch a { color: var(--series-1); font-weight: 600; text-decoration: none; margin-left: 4px; }
.gate-switch a:hover { text-decoration: underline; }
.gate .card label { display: block; margin: 10px 0 4px; font-weight: 600; font-size: 0.85rem; }

/* "Add ingredient" control at the bottom of a logged meal tile. */
.mc-add { margin-top: 8px; }
.tl-addfood {
  background: none; border: 1px dashed var(--border); color: var(--text-muted);
  border-radius: 8px; padding: 5px 10px; font-size: 0.82rem; font-weight: 600;
  cursor: pointer; width: 100%;
}
.tl-addfood:hover { color: var(--series-1); border-color: var(--series-1); }
.mc-addform { display: flex; gap: 6px; align-items: center; }
.mc-add-input { flex: 1; min-width: 0; }
.btn.ghost { background: none; color: var(--text-muted); }

/* Per-ingredient price and meal-cost total. */
.tl-line { display: flex; align-items: center; gap: 8px; }
.tl-item-name { flex: 1; min-width: 0; }
.tl-price { color: var(--text-muted); font-variant-numeric: tabular-nums; font-size: 0.85rem; white-space: nowrap; }
.mc-price { color: var(--text-muted); font-weight: 600; }
/* "Add ingredient" as the final line of the item list. */
.tl-addline { padding-top: 2px; }
.tl-addline .tl-addfood { width: auto; }

/* Tap an ingredient name to reveal its macros (P/C/F). */
.tl-item-name {
  flex: 1; min-width: 0; text-align: left; background: none; border: none;
  padding: 0; font: inherit; color: inherit; cursor: pointer;
}
.tl-item-name::after { content: ' ›'; color: var(--text-muted); }
.tl-item-name[aria-expanded="true"]::after { content: ' ⌄'; }
.tl-detail {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 2px 0 8px 2px; margin-top: -2px;
}
.tl-detail-kcal { color: var(--text-muted); font-size: 0.8rem; font-variant-numeric: tabular-nums; }

/* The hidden attribute must always win over a component's display rule. Without
   this, any element given `display: flex/grid/block` shows even when [hidden] —
   the trap that made .tl-detail (per-item macros) render permanently expanded,
   and .sheet before it. A global rule fixes the whole class of bug. */
[hidden] { display: none !important; }

/* Per-meal collapse/expand-all toggle, above the first ingredient. */
.mc-toggle { padding-bottom: 4px; }
.tl-toggle-all {
  background: none; border: none; padding: 0; font: inherit; cursor: pointer;
  color: var(--series-1); font-weight: 600; font-size: 0.82rem;
}
.tl-toggle-all:hover { text-decoration: underline; }

/* The camera is the primary action on the Log tab, so it gets a target sized to
   be hit with a thumb while holding a plate — full width, not a text-sized
   button. Everything else in the card is secondary to it. */
.capture-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  padding: 16px;
  font-size: 1.02rem;
  border-radius: var(--radius);
}
.capture-btn svg { width: 21px; height: 21px; flex: none; }
.capture-btn:active { transform: scale(0.985); }

/* The library import and the meal label sit on one quiet line under it: both
   are corrections to the default path, not choices to make every time. */
.capture-alt {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  margin-top: 10px;
}
.capture-alt .btn.ghost { padding-left: 0; border-color: transparent; }

/* Present to the layout engine, invisible to the eye. Used for the camera file
   input, which stops honouring capture= if it is display:none. */
.vis-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.capture-meal { flex: 0 1 130px; }

/* "This looks like a meal you have logged before." Green because it is a good
   outcome — the person's own corrected items, no estimate to re-check — but
   quiet, because it still has to be read and agreed with rather than assumed. */
.match-note {
  border: 1px solid var(--series-1);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--series-1) 9%, transparent);
  padding: 12px 14px;
  margin: 12px 0 4px;
}
.match-why { font-weight: 600; font-size: 0.92rem; }
.match-when { color: var(--text-muted); font-size: 0.8rem; margin-top: 3px; }
.match-note .btn.ghost { margin-top: 8px; padding-left: 0; border-color: transparent; }

/* One item swapped for the food this person keeps correcting it to. Muted, but
   present: a substitution nobody can see is a substitution nobody can undo. */
.swapped { color: var(--series-1); }

/* "Possibly the same food as X." Warning-coloured, because unlike a swap this
   is something to act on — a double-counted meat inflates the whole plate. */
.dupe { color: var(--warning-text); font-weight: 560; }

/* ── The recipe library ───────────────────────────────────────────────────
   Image-led and two across. A recipe is recognised by its picture long before
   its name is read, which is why every recipe app that is pleasant to use looks
   like this. Adapted from a reference design whose warmth came from a coral
   brand; here the same shapes are built out of --series-1, so it reads fresh
   rather than warm and belongs to this app instead of borrowing another's. */

/* Header. color-mix over the surface rather than a second hard-coded colour,
   so it follows the theme into dark mode without a second definition. */
.rx-head {
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--series-1) 22%, var(--surface-1)),
    color-mix(in srgb, var(--series-1) 6%, var(--surface-1)));
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 16px;
  margin-bottom: 14px;
}
.rx-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.rx-hello { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.01em; }
.rx-sub { color: var(--text-secondary); font-size: 0.82rem; margin-top: 2px; }
.rx-icon {
  flex: none; width: 40px; height: 40px; border-radius: 14px; font-size: 1.05rem;
  border: 1px solid var(--border); background: var(--surface-1); cursor: pointer;
}
.rx-search {
  margin-top: 14px; display: flex; align-items: center; gap: 9px;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: 14px; padding: 0 12px;
}
.rx-search svg { width: 17px; height: 17px; color: var(--text-muted); flex: none; }
.rx-search input { border: 0; background: none; padding: 11px 0; flex: 1; font: inherit; }
.rx-search input:focus { outline: none; }

/* "From a recipe" on the Log screen. Full width so it reads as the action of
   its card, but a plain .btn rather than .primary — the camera above it is the
   primary action on that screen and there must be only one. */
#photo-from-recipe { width: 100%; }

/* Sort chips. The reference filters by cuisine; there is no such taxonomy here,
   so these order by figures the app actually records. */
.rx-chips { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; margin-bottom: 14px; }
.rx-chip {
  flex: none; font: inherit; font-size: 0.83rem; font-weight: 560; cursor: pointer;
  padding: 8px 14px; border-radius: 99px;
  border: 1px solid var(--border); background: var(--surface-1); color: var(--text-secondary);
}
.rx-chip.on { background: var(--series-1); border-color: var(--series-1); color: #fff; }

/* The grid. The card is a button: the whole tile opens the recipe, and logging
   lives on the recipe rather than competing with the photo for space. */
.rx-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.rx-card {
  display: flex; flex-direction: column; align-items: stretch; text-align: left;
  border: 0; background: none; padding: 0; font: inherit; color: inherit; cursor: pointer;
}
.rx-card:active { transform: scale(0.985); }
.rx-photo {
  position: relative; aspect-ratio: 1; border-radius: 18px; overflow: hidden;
  background: var(--surface-2); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.10);
  display: flex; align-items: center; justify-content: center;
}
.rx-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The tile that matters most. The designs this borrows from are entirely
   photography and never have to show absence — most of this library is in that
   state, so an empty tile is drawn as an invitation rather than a grey hole. */
.rx-noimg {
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--series-1) 14%, var(--surface-2)),
    color-mix(in srgb, var(--series-1) 4%, var(--surface-2)));
  border: 1px dashed color-mix(in srgb, var(--series-1) 35%, var(--border));
  box-shadow: none; flex-direction: column; gap: 6px; color: var(--series-1);
}
.rx-noimg svg { width: 30px; height: 30px; }
.rx-noimg-cap { font-size: 0.72rem; font-weight: 600; }

.rx-name { font-weight: 640; font-size: 0.88rem; line-height: 1.25; margin-top: 8px; }
.rx-meta { color: var(--text-muted); font-size: 0.78rem; margin-top: 2px; }
.rx-meta b { color: var(--text-primary); font-variant-numeric: tabular-nums; }
/* Cost per serving is the figure a recipe app usually cannot show at all, so it
   keeps the accent. */
.rx-cost { color: var(--series-1); }
/* "from Chris" on a recipe somebody sent. Quiet — it is provenance, not a
   warning — but always present, because a copy arrives with no accept step and
   an unmarked one is indistinguishable from something you wrote yourself. */
.rx-from {
  display: block; margin-top: 3px;
  font-size: 0.74rem; font-weight: 600;
  color: var(--series-1);
}
/* Diet tags. Quiet on a card — they are a filter aid, not the headline — and
   capped at three so a dish that fits everything does not become a wall. */
.rx-diets { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.rx-diet {
  font-size: 0.68rem; font-weight: 640; padding: 2px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--series-1) 14%, var(--surface-2));
  color: var(--series-1); white-space: nowrap;
}
/* On the detail sheet each tag carries its reasoning, so it needs a row. */
.rx-diet-list { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.rx-diet-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.rx-diet-row small { color: var(--text-muted); font-size: 0.77rem; }
/* Muted to match the reasoning beside a tag: both are the small print under a
   row, and the "not answered" note is the more important of the two. */
.rx-diet-note { margin-top: 4px; color: var(--text-muted); font-size: 0.77rem; }

.rx-pill-from { background: color-mix(in srgb, var(--series-1) 14%, var(--surface-2)); }

/* "Add to my recipes" on a catalogue card. Full width because taking a copy is
   the only thing this card is for. */
.rx-take { width: 100%; margin-top: 8px; padding: 8px; font-size: 0.8rem; }

/* Where a recipe of yours stands with the catalogue. Pending is the common
   case and deliberately calm — waiting is not a problem. */
.rx-state {
  display: flex; align-items: center; gap: 7px;
  margin-top: 10px; padding: 9px 12px; border-radius: 12px;
  font-size: 0.79rem; line-height: 1.35;
  background: var(--surface-2); color: var(--text-muted);
}
.rx-state b { color: var(--text-primary); font-weight: 640; }
.rx-state-live { background: color-mix(in srgb, var(--series-1) 12%, var(--surface-2)); }
.rx-state-live b { color: var(--series-1); }
/* A rejection carries a reason and needs to be read, so it is the one state
   that is allowed to look like something happened. */
.rx-state-no { background: color-mix(in srgb, var(--series-2) 12%, var(--surface-2)); }
.rx-state-no b { color: var(--series-2); }

/* ── Moderation — a work queue, so rows rather than cards ───────────────── */
.mod-row {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 0; border-top: 1px solid var(--border);
}
.mod-row:first-child { border-top: 0; }
.mod-main { flex: 1; min-width: 0; }
.mod-main .rx-name { margin-top: 0; }
.mod-row .btn { flex: none; padding: 7px 12px; font-size: 0.78rem; }
.mod-flag { color: var(--series-2); }

/* The row is a summary; this opens the thing being judged. Understated — it is
   a step in a decision, not the decision. */
.mod-open {
  margin-top: 5px; padding: 0; border: 0; background: none;
  font: inherit; font-size: 0.76rem; font-weight: 640;
  color: var(--accent); text-decoration: underline; cursor: pointer;
}
.mod-full {
  padding: 12px 0 16px; border-top: 1px dashed var(--border);
  margin-bottom: 4px;
}
/* Contained rather than full-bleed: a reviewer needs to SEE the photo, and a
   picture that fills the screen in a queue is harder to work through, not
   easier. Capped so a tall image cannot push the buttons out of reach. */
.mod-photo {
  border-radius: 14px; overflow: hidden; background: var(--surface-2);
  max-height: 260px; display: flex; align-items: center; justify-content: center;
}
.mod-photo img { width: 100%; max-height: 260px; object-fit: contain; display: block; }
.mod-facts {
  display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 10px;
  font-size: 0.79rem; color: var(--text-muted);
}
.mod-facts b { color: var(--text-primary); }
.mod-notes { margin: 8px 0 0; font-size: 0.82rem; line-height: 1.45; }
.mod-ings { list-style: none; margin: 10px 0 0; padding: 0; }
.mod-ings li {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 5px 0; border-bottom: 1px solid var(--border); font-size: 0.81rem;
}
.mod-ings li:last-child { border-bottom: 0; }
.mod-ings span { color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* What somebody objected to, in their words. A count cannot carry it, and it is
   the whole reason a live recipe is in front of a reviewer. */
.mod-reports {
  margin-top: 12px; padding: 10px 12px; border-radius: 12px;
  background: color-mix(in srgb, var(--series-2) 10%, var(--surface-2));
  font-size: 0.79rem;
}
.mod-reports ul { margin: 6px 0 0; padding-left: 18px; }

/* Save/Undo for a turned photo. Appears only once something has actually been
   turned, so it never sits there as a pair of dead buttons. */
#recipe-rot-actions:not(:empty) { display: flex; gap: 8px; margin-top: 8px; }
#recipe-rot-actions .btn { flex: 1; }

/* Sits directly under the cost tick-box, so it loses the paragraph gap. */
.rx-cost-note { margin: 8px 0 0; }

/* ── One recipe: hero photo with the sheet riding up over it ────────────── */
.rx-detail {
  border: 1px solid var(--border); border-radius: 20px; overflow: hidden;
  margin-top: 16px; background: var(--surface-1);
}
.rx-hero {
  position: relative; background: var(--surface-2);
  display: flex; align-items: center; justify-content: center;
  /* Phones shoot 4:3 and 3:4, never 1:1. A fixed 210px strip cropped the top
     and bottom off every portrait plate — the food, usually, since people
     centre it. The frame takes the shape of the photo in it instead.

     This 4:3 is only the value before the photo arrives, chosen because it is
     what a camera gives by default; `shapeHero()` replaces it with the image's
     OWN ratio on load, so the frame ends up exactly the shape of what is in it.
     It has to be set from script — one ratio per photograph is per-datum, and a
     stylesheet cannot hold it. Three bucketed classes used to stand in for that
     and left up to a quarter of the frame empty. */
  aspect-ratio: 4 / 3;
  /* A portrait photo is 33% taller than a landscape one, so without a ceiling
     the sheet, the name and the Log button all get pushed off a phone screen.
     When it bites, the frame keeps the ratio and narrows instead — so it is
     centred, rather than left sitting against one edge. */
  max-height: 60vh;
  margin-inline: auto;
  overflow: hidden;
}
/* `contain`, not `cover`: with the frame already matching the photo they are
   equivalent, and if the measurement is ever wrong contain shows the whole
   dish with a margin rather than silently cutting a piece off. Which failure
   you get when you are wrong is a design decision, not an accident. */
.rx-hero img { width: 100%; height: 100%; object-fit: contain; display: block; }
.rx-hero.rx-noimg { border: 0; border-radius: 0; }
.rx-hero.rx-noimg svg { width: 46px; height: 46px; }
.rx-hero-btn {
  position: absolute; top: 12px; width: 36px; height: 36px; border-radius: 50%;
  border: 0; cursor: pointer; background: rgba(255, 255, 255, 0.92);
  color: #111; font-size: 1.3rem; line-height: 1;
}
.rx-back { left: 12px; }
/* Rides up over the hero, which is the move that makes the reference feel like
   an app rather than a page. */
.rx-sheet {
  position: relative; margin-top: -22px; border-radius: 22px 22px 0 0;
  background: var(--surface-1); padding: 18px 16px 16px;
}
.rx-title { margin: 0; font-size: 1.05rem; font-weight: 700; line-height: 1.25; letter-spacing: -0.01em; }
.rx-pills { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
.rx-pill {
  font-size: 0.78rem; padding: 6px 11px; border-radius: 99px;
  background: var(--surface-2); color: var(--text-secondary);
}
.rx-pill b { color: var(--text-primary); font-variant-numeric: tabular-nums; }
.rx-intro { margin: 14px 0 0; font-size: 0.9rem; }
.rx-sec h3 { font-size: 0.9rem; margin: 18px 0 8px; }
.rx-ing {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 9px 0; border-top: 1px solid var(--border); font-size: 0.88rem;
}
.rx-g { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.rx-steps { padding-left: 20px; margin: 0; font-size: 0.9rem; }
.rx-steps li { margin-bottom: 6px; }
.rx-log { width: 100%; margin-top: 16px; padding: 14px; border-radius: 14px; font-size: 1rem; }
/* Below the action people came for, and quiet: a delete that looks as important
   as "Log 1 serving" gets tapped by accident on a phone. */
.rx-del { display: block; width: 100%; margin-top: 8px; color: var(--critical); }

/* Share and Publish, side by side under the primary action.

   The labels WRAP here, against `button.btn`'s global nowrap. `flex: 1` sets a
   basis of 0 but leaves min-width at auto, which floors each button at the
   intrinsic width of a line that refuses to break: "Publish to the catalogue"
   measures 241px, so the pair needs 416px and every phone narrower than that
   clipped it — 20px lost at 393 (iPhone 15), 53px at 360, 93px at 320. It read
   as "Publish to the catalogu", which is the one word in it that matters.
   Wrapping to two lines keeps both labels whole; the row stretches to match. */
.rx-share { display: flex; gap: 8px; margin-top: 14px; }
.rx-share .btn { flex: 1; min-width: 0; white-space: normal; }
.rx-link {
  margin-top: 12px; padding: 12px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-2);
}
.rx-link .sub { margin: 0 0 10px; }
/* The URL is shown, selectable, before it goes anywhere — a link that works for
   anyone should be seen by the person handing it over. */
.rx-link-url { width: 100%; font-size: 0.85rem; font-family: var(--mono); }
.rx-link-actions { display: flex; gap: 8px; margin-top: 10px; }
.rx-link-actions .btn { flex: 1; }

/* ── The public share page (/r/<token>) ──────────────────────────────────
   Served by the same app under the same strict CSP, so it links this
   stylesheet and uses classes — a style attribute would be dropped. It reuses
   the recipe detail's own look, so a shared recipe looks like the app it came
   from rather than like a printout. */
.share-main { max-width: 480px; margin: 0 auto; padding: 16px; }
.share-chips { margin-top: 12px; }
.share-foot {
  margin: 18px 0 0; padding-top: 14px;
  border-top: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.8rem;
}
.share-foot a { color: var(--series-1); }

/* "Save as recipe" on a logged plate. Sits opposite "+ Add ingredient" and is
   styled the same quiet way: both are things you occasionally do to a meal, not
   the reason the row exists. */
/* Edit moved down here when "Save as recipe" took the top slot. Quiet, and to
   the right, so the row reads: add something, or fix something. */
.tl-editlink {
  margin-left: auto;
  font: inherit; font-size: 0.82rem; font-weight: 560;
  border: 0; background: none; cursor: pointer; padding: 0;
  color: var(--text-muted);
}
.tl-editlink:hover { text-decoration: underline; color: var(--text-primary); }
.mc-keepform { display: flex; align-items: center; gap: 6px; width: 100%; flex-wrap: wrap; }
.mc-keepform .mc-add-input { flex: 3 1 140px; }
.keep-servings { flex: 0 0 64px; }
#recipe-dish-photo { margin-top: 14px; }

/* "Also save as a recipe". A whole-row tap target, because a bare checkbox on a
   phone is a 13px target next to a label that does nothing. */
.recipe-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--text-primary);
}
.recipe-toggle input { width: auto; margin: 0; flex: none; }
.recipe-toggle:has(input:checked) {
  border-color: var(--series-1);
  background: color-mix(in srgb, var(--series-1) 7%, transparent);
}
#photo-recipe-fields { margin-top: 10px; }

/* The small preview beside the recipe-draft form. Bounded HERE and not in a
   style attribute: the CSP is `style-src 'self'` with no 'unsafe-inline', which
   blocks inline style ATTRIBUTES as well as <style> blocks, and this carried
   the only size limit its image had — a 1024px dish photo rendered at full
   width, overflowed its card and pushed the rest of the recipe off the screen.

   `.recipe-photo-img` used to share this rule, from when the dish photo WAS a
   small preview in the sheet. It has since moved to the hero at the top of the
   detail, where it is sized by `.rx-hero img` — but the 220px ceiling came with
   it and kept winning, because a max-width beats a width: 100% every time. So
   the hero opened at the full shape of the photo and the photo inside it was
   never larger than 220px, marooned in the middle of a frame up to 489px tall.
   That is what "the photo is undersized for its box" was. Bound an image where
   it is USED; a rule that follows one somewhere else is a rule that outlives
   the reason for it. */
.recipe-draft-photo {
  max-width: 160px;
  max-height: 160px;
  border-radius: 10px;
  display: block;
  margin: 2px 0 12px;
}

/* The tooltip legend swatch. Its colour comes from the series and is applied
   through the CSSOM in app.js, which the CSP does allow. */
.tt-row .swatch { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }

/* Meal-photo preview during identification: a small thumbnail, never full-size.
   Bounded in CSS (not just inline) so nothing can render it large. The AI still
   receives the full-resolution image; this only limits what's shown. */
#photo-preview { max-width: 160px; }
#photo-preview img {
  display: block; max-width: 160px; max-height: 160px; width: auto; height: auto;
  border-radius: 10px; border: 1px solid var(--border);
}

/* Daily cost on a history summary row. */
.history-cost { color: var(--series-1); font-weight: 700; font-variant-numeric: tabular-nums; }
/* A cost covering only some of the day's items reads muted, so it does not
   carry the same authority as a complete one. The "+" does the real work. */
.history-cost.partial { color: var(--text-muted); font-weight: 600; }

/* "Forgot password?" link on the login gate. */
.gate-forgot { margin: 10px 0 0; text-align: center; font-size: 0.85rem; }
.gate-forgot a { color: var(--text-muted); text-decoration: none; }
.gate-forgot a:hover { text-decoration: underline; }

/* ── Onboarding wizard (new signups) ─────────────────────────────────────── */
.onboard {
  position: fixed; inset: 0; z-index: 100;
  background: var(--surface-0);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
.onboard-card {
  width: 100%; max-width: 440px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px 20px 18px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
}
.onboard-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.ob-dots { display: flex; gap: 6px; }
.ob-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--surface-2); }
.ob-dot.on { background: var(--series-1); }
.onboard-skip { background: none; border: none; color: var(--text-muted); font-size: 0.85rem; cursor: pointer; padding: 4px; }
#ob-body h2 { margin: 4px 0 6px; font-size: 1.3rem; }
#ob-body .sub { margin: 0 0 14px; color: var(--text-secondary); font-size: 0.9rem; }

.ob-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.ob-goal {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  padding: 14px 12px; text-align: left;
  background: var(--surface-0); border: 1.5px solid var(--border);
  border-radius: var(--radius); cursor: pointer;
  font-size: 0.9rem; font-weight: 600; color: var(--text-primary);
}
.ob-goal-ic { font-size: 1.5rem; }
.ob-goal.sel { border-color: var(--series-1); background: color-mix(in srgb, var(--series-1) 12%, var(--surface-0)); }

.ob-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.ob-chip {
  padding: 8px 14px; border-radius: 999px;
  background: var(--surface-0); border: 1.5px solid var(--border);
  cursor: pointer; font-size: 0.9rem; font-weight: 600; color: var(--text-primary);
}
.ob-chip.sel { border-color: var(--series-1); background: color-mix(in srgb, var(--series-1) 14%, var(--surface-0)); color: var(--series-1); }

/* ── About you / Your body ──────────────────────────────────────────────────
   Same soft cards as the goal grid. Sex is three options, so it gets its own
   single column rule rather than being squeezed into the two-up grid. */
.ob-sex { grid-template-columns: 1fr 1fr 1fr; }
.ob-sex .ob-goal { padding: 14px 6px; font-size: 0.78rem; }
.ob-sex .ob-goal-ic { font-size: 1.35rem; }

.ob-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 18px;
}
.ob-label {
  display: block; margin-top: 18px; margin-bottom: 6px;
  font-size: 0.82rem; font-weight: 640;
}
.ob-row .ob-label { margin: 0; }
.ob-opt { font-weight: 500; color: var(--text-muted); }

/* Big numeric fields: this is a phone, and a number pad on a small target is
   the difference between typing a height and giving up on it. */
.ob-field { display: flex; align-items: center; gap: 8px; }
.ob-num {
  flex: 1; min-width: 0; width: 100%;
  padding: 14px 12px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--surface-0);
  font: inherit; font-size: 1.25rem; font-weight: 640;
  text-align: center; font-variant-numeric: tabular-nums;
}
.ob-num:focus { outline: none; border-color: var(--series-1); }
.ob-unit-static { color: var(--text-muted); font-size: 0.85rem; flex: none; }

/* Unit toggle — a segmented control, so both options are always visible and
   it is obvious which one the number is in. */
.ob-toggle {
  display: flex; border: 1px solid var(--border); border-radius: 999px;
  overflow: hidden; flex: none;
}
.ob-toggle button {
  border: 0; background: none; font: inherit; font-size: 0.76rem; font-weight: 640;
  padding: 6px 14px; color: var(--text-muted); cursor: pointer;
}
.ob-toggle button.on { background: var(--series-1); color: #fff; }

.ob-note { margin-top: 10px; }

/* A vertical list where the choices need a sentence each — five activity levels
   will not fit in a two-up grid without truncating the part that makes them
   distinguishable. */
.ob-list { display: flex; flex-direction: column; gap: 8px; }
.ob-listitem {
  display: flex; align-items: center; gap: 12px; text-align: left;
  padding: 13px 14px; border-radius: 16px;
  border: 1px solid var(--border); background: var(--surface-0);
  font: inherit; color: inherit; cursor: pointer; width: 100%;
}
.ob-listitem.sel {
  border-color: var(--series-1);
  background: color-mix(in srgb, var(--series-1) 12%, var(--surface-0));
}
.ob-li-ic { font-size: 1.35rem; flex: none; }
.ob-li-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ob-li-text b { font-size: 0.88rem; font-weight: 640; }
.ob-li-text small { font-size: 0.76rem; color: var(--text-muted); }

/* A single button that names the CURRENT unit and swaps on tap. The segmented
   pair is right in onboarding, where the choice is new; here the value is
   already on record and the question is only what to show it in. */
.ob-toggle-single {
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface-2);
  font: inherit; font-size: 0.74rem; font-weight: 640; color: var(--text-muted);
  padding: 6px 12px; cursor: pointer; flex: none; min-width: 46px;
}
#p-target { margin-top: 14px; }
#p-target .btn { width: 100%; margin-top: 8px; }
#p-activity { margin-bottom: 4px; }

/* The payoff line on the last screen. The numbers are the point, so they are
   the only thing at full contrast. */
.ob-summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px;
  margin: 14px 0 4px; padding: 14px;
  border-radius: 16px;
  background: color-mix(in srgb, var(--series-1) 10%, var(--surface-2));
  font-size: 0.86rem; color: var(--text-muted);
}
.ob-summary b {
  color: var(--series-1); font-size: 1.05rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.ob-sep { color: var(--border); }

.onboard-foot { display: flex; gap: 10px; margin-top: 18px; }
.onboard-foot .btn { flex: 1; }
.onboard-foot .btn.primary { flex: 2; }

/* ── Spacing and colour the CSP will not take from a style="" attribute ──────

   `style-src 'self'` drops inline style ATTRIBUTES, not only <style> blocks, so
   every `style="margin-top:10px"` written into a template literal was thrown
   away silently — laid out wrong, with the reason visible only in a console
   nobody was watching. Twenty-three of them were found on 2026-07-31 by
   grepping for the pattern that had already cost the chart legends their
   colour. These rules are what those attributes were asking for.

   Nothing here is new design: each is the declaration that was already written
   at the point of use. Anything genuinely per-datum cannot live in a
   stylesheet at all — hand it over as `data-` and assign through the CSSOM,
   the way the chart legend and tooltip do. */

/* Muted sub-text on the line below a value: a macro breakdown under a calorie
   count, a serving description under a food name. */
.muted { color: var(--text-muted); }

/* "Add a missed ingredient…" — an input and its button on one line, under a
   table of what was found. The note that sometimes follows clears it. */
.add-row { display: flex; gap: 8px; margin-top: 10px; }
.add-row + .sub { margin-top: 10px; }

/* Save / discard under a draft. `.actions` is the plainer case: a <p> holding
   the buttons rather than a flex row, so it takes the margin and nothing else —
   restoring the attribute, not redesigning the pair. */
.form-actions { display: flex; gap: 8px; margin-top: 14px; }
p.actions { margin-top: 12px; }

/* The dish name a photo was identified as, above its item table. */
.dish-name { margin: 12px 0 4px; }

/* A "no match" line inside the food-search drop-down sits tighter than the
   same message would at page level. */
.tl-fix-results .empty { margin: 6px 0; }

/* MEDAS: the score tile, the partial-week line that sometimes follows it, and
   the footnote under the rules table. */
#medas .tile,
#medas .tile + .sub { margin-bottom: 14px; }
#medas .table-wrap + .sub { margin-top: 10px; }

/* The date heading under "‹ All days". */
#history-detail h2 { margin-top: 10px; }

/* Recipe-from-a-photo draft: the card itself, and its ingredient table. */
#recipe-photo-results .card { margin-top: 6px; }
#recipe-photo-results .table-wrap { margin-top: 10px; }

/* Macro chips on the recipe detail sheet stand off the pills above them by
   more than the timeline's own chips do. */
.rx-sheet .mc-chips { margin-top: 12px; }

/* The same attributes, in index.html rather than a template literal — nine of
   them, all equally dropped. */

/* Range and metric pickers above the trend charts. */
.trend-controls { margin-bottom: 12px; }

/* The chosen photo, between the camera buttons and the results table. */
#photo-preview { margin-top: 12px; }

/* "New recipe by hand": a collapsed builder under the recipe library. The
   summary is the whole affordance for opening it, so it needs to look like
   one — it had been rendering as unweighted body text with a default cursor. */
#recipe-builder-card { margin-top: 16px; }
#recipe-builder > summary { cursor: pointer; font-weight: 600; font-size: 0.9rem; }

/* Its rows: the name/servings row stands off the summary a little more than
   the ingredient rows stand off each other. */
#recipe-builder .row { margin-top: 12px; }
#recipe-builder .row ~ .row { margin-top: 10px; }
#recipe-builder .row-check label { margin-bottom: 8px; }
#r-ingredients { margin-top: 10px; }
