:root {
  /* A pitch at night, under floodlights.
     Green was previously doing six jobs at once — background, headings,
     numbers, buttons, dividers, accents — which is why it stopped meaning
     anything. Here it has two: the deep ground the scoreboard sits on, and
     "you're under" in the one place that reading matters. */
  --pitch: #1f9d52;
  --pitch-light: #2ec26b;
  --pitch-dark: #12602f;
  /* The header's ground, and the exact green of the QuicKcals mark, taken from
     the brand artwork itself — so the app icon on the home screen and the
     banner behind it are the same colour rather than two greens that nearly
     match. White on it measures 6.6:1, so the header text passes AA. */
  --brand: #176B3A;
  --brand-ink: #ffffff;
  --pitch-deep: var(--brand);
  --ink: #0f1417;
  --muted: #6e7b74;
  /* Pitch-marking white, warm-neutral. Not cream. */
  --bg: #f6f7f4;
  --card: #ffffff;
  --hairline: #e6ece8;
  --danger: #dc2626;
  --amber: #b06f05;
  --amber-light: #f59e0b;
  /* The floodlight: "over", and the one warm note against all that green. */
  --floodlight: #ffc53d;
  --gold: var(--amber);

  --radius-sm: 10px;
  --radius-md: 14px;
  --radius-lg: 22px;

  --shadow-sm: 0 1px 2px rgba(20, 30, 24, 0.06);
  --shadow-md: 0 4px 14px rgba(20, 30, 24, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --shadow-lg: 0 10px 30px rgba(20, 30, 24, 0.12);

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Barlow Condensed carries the headings and the numbers. It is narrow, and
     several headers are sized against that: swapping it for a normal-width
     face wraps the Today date onto two lines at every phone width. Montserrat
     is scoped to the wordmark instead, where it matches the logo exactly and
     costs no layout. Inter stays for body copy. */
  --font-heading: "Barlow Condensed", "Oswald", "Arial Narrow", -apple-system, BlinkMacSystemFont, sans-serif;
  /* The logo's own face, for the wordmark and nothing else. */
  --font-brand: "Montserrat", "Avenir Next", "Segoe UI", -apple-system, BlinkMacSystemFont, sans-serif;

  /* The raised "selected" pill shared by the stats tabs, the daily/weekly
     toggle and the units switch. A token because all three were the same
     hardcoded white gradient, which stayed white in dark mode. */
  --pill-active-bg: linear-gradient(160deg, #ffffff, #eefbf3);
  --pill-active-ink: var(--pitch-dark);
  --pill-active-shadow: 0 2px 6px rgba(18, 96, 47, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.9);

  /* The tooltip is a deliberate inversion of the page, so it can't just use
     --ink for its ground: in dark mode that's near-white, and the white text
     on it disappeared. */
  --tooltip-bg: var(--ink);
  --tooltip-ink: #fff;
}

* {
  box-sizing: border-box;
}

/* Author rules like `.settings-field { display: flex }` share specificity
   with the UA's `[hidden] { display: none }` and can win the cascade,
   making a hidden element visible again — force it explicitly instead. */
[hidden] {
  display: none !important;
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.banner h1,
h2,
.summary-total,
.balance-number,
.budget-title,
.auth-title {
  font-family: var(--font-heading);
}

/* Barlow Condensed runs narrow and short next to Poppins, so headings and
   figures are set larger and tighter than the old scale to hold the same
   presence. Numbers are tabular everywhere: a column of figures that
   shifts as it updates reads as sloppy. */
.summary-total,
.balance-number,
.stat-hero-number,
.weight-hero-number,
.tdee-value {
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.icon {
  display: inline-block;
  vertical-align: -3px;
  width: 1em;
  height: 1em;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

html {
  /* Guard against any single element pushing past viewport width on a small
     screen — nothing in this layout should need to scroll sideways.
     `clip`, not `hidden`, and not on <body>: a non-visible overflow on the
     root makes iOS Safari treat it as a scroll container, which stops
     position:fixed pinning to the viewport — the bottom tab bar drifts with
     the page instead of staying put. `clip` does the same job without
     creating that scroll container. Desktop browsers never showed it, which
     is why it survived this long. */
  overflow-x: clip;
  max-width: 100%;
}

body {
  margin: 0;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* The header is the scoreboard: a deep, flat touchline green rather than
   the three-layer gradient it replaces. The gradient was doing decorative
   work; a solid dark ground does structural work, by making everything
   below it read as the lit surface. */
.banner {
  position: relative;
  background: var(--pitch-deep);
  color: #fff;
  /* The page runs under the status bar (viewport-fit=cover), so the header's
     own padding has to clear it — otherwise the back arrow and the corner
     icons sit behind the clock on an installed iPhone app and can't be
     pressed at all. Zero on every device without a notch. */
  padding: calc(22px + env(safe-area-inset-top)) 20px 18px;
  text-align: center;
  overflow: hidden;
}
/* A single bright line along the bottom edge — the touchline. The only
   decoration the header gets. */
.banner::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 2px;
  background: linear-gradient(90deg, var(--pitch) 0%, var(--pitch-light) 50%, var(--pitch) 100%);
}

.settings-toggle,
.library-toggle,
.stats-toggle,
.library-back {
  position: absolute;
  top: calc(14px + env(safe-area-inset-top));
  width: 38px;
  height: 38px;
  min-height: 38px;
  padding: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.09);
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.92);
  font-size: 1.15rem;
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.22s var(--spring), background 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.settings-toggle:active,
.library-toggle:active,
.stats-toggle:active,
.library-back:active {
  transform: scale(0.9);
  background: rgba(255, 255, 255, 0.18);
}
.settings-toggle { right: 14px; }
.stats-toggle { right: 60px; }
.library-toggle { right: 106px; }
.library-back { left: 14px; }

/* Desktop nav bar: replaces the corner icon buttons/back chevron at wide
   widths (see the ≥1024px block below) — same four destinations, wired to
.banner h1 {
  margin: 0;
  padding: 0 92px;
  font-size: clamp(1.15rem, 5.4vw, 1.45rem);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.15;
}

/* On the diary the hierarchy inverts, deliberately: you open that screen to
   look at a week, not to be told the app's name. The wordmark drops to a
   tracked eyebrow and the fixture becomes the headline. Every other screen
   keeps its title at full size — there's no fixture there to take over, and
   an eyebrow alone leaves the header a large empty band. */
.banner--fixture h1 {
  margin: 0 0 20px;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.62);
  /* The clipping that keeps a long title on one line lives on the wordmark
     itself (see .brand-word), not here. On the heading it also clipped the
     mark, which now deliberately overflows this line box — a disc cropped
     into a horizontal band is not a logo. */
}
/* On the fixture screens the line the mark shares is a 0.7rem eyebrow, so
   there is much less line box to fall back into — the mark is a little
   smaller and the margin correspondingly larger to land on the same 28px. */
.banner--fixture h1 .brand-mark {
  width: 56px;
  height: 56px;
  margin-block: -14px;
}

/* Three header icons on the main app-shell banner (food library, stats,
   settings) need more clearance than the single-icon screens below it. */
/* The generic centered/symmetric-padding title above assumes clearance is
   needed on both sides. app-shell's icons are only ever on the right, so
   reserving 144px on the left too (to keep the text visually centered) was
   starving the title of width on a narrow phone and forcing it onto 3-4
   lines, ballooning the banner's height. Left-aligning with clearance only
   where the icons actually are fixes that. */
#app-shell .banner h1 {
  padding: 0 144px 0 4px;
  text-align: left;
}

.banner .muted {
  color: #d7ecdf;
  margin: 0;
}

/* Header wordmark: the same plate/fork/knife mark used as the home-screen
   app icon, reused as a small badge next to the title on every screen —
   gives the header an actual brand identity instead of plain bold text. */
.banner h1.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
}
#app-shell .banner h1.brand {
  justify-content: flex-start;
}
/* The mark used to be a green bubble on a white disc this stylesheet drew
   behind it — a badge on a badge, and the reason it read as a sticker stuck
   to the header rather than as the header's own mark.

   It is now inverted (icons/mark-inverse.svg): the bubble is the white shape
   and the cutlery is knocked out of it, so the banner shows through the K.
   That removes the need for the disc, the hairline and the shadow entirely —
   there is no plate to hold an edge, just the mark. It also hands the whole
   box back to the artwork: with no disc there is no padding to inset it, so
   the drawing is the full 68px rather than 52px of it.

   It is twice the size it was, and the header is exactly as tall as it was
   before. Those two facts are held together by the negative block margins:
   the banner's height is set by the text line the mark sits on, so the mark
   is allowed to overflow that line box into the padding the header already
   had rather than pushing it open. The margin is half the difference between
   the mark and the line it has to fit back into — change one and the other
   has to move with it, or the banner grows.

   The knockout is why this file names no colour for it. A cutout is
   transparent, so the mark takes whatever green is behind it and needs no
   second asset for the dark theme. The corollary: it is invisible on a light
   ground, which is why the sign-in card keeps its own green-on-white asset
   (.auth-brand-mark) rather than sharing this one. */
.brand-mark {
  display: block;
  width: 68px;
  height: 68px;
  margin-block: -18px;
  flex-shrink: 0;
}

/* The wordmark. "Quic" carries the weight and everything after it steps back:
   the K belongs with "cals" in the reading, not with the bold half, and
   bolding it made the join the loudest thing in the word. It stays split out
   because it is still the K the mark is built from and the k of kcal — it
   just doesn't shout about it. */
.brand-word {
  font-family: var(--font-brand);
  letter-spacing: -0.02em;
  /* Kept on one line, and truncated here rather than on the heading: the
     heading also holds the mark, which overflows its line box on purpose. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* A flex item won't shrink below its content without this, so without it
     the ellipsis never gets the chance to appear. */
  min-width: 0;
  /* Larger than the heading it sits in, so the name reads as the name rather
     than as more of the same text. */
  font-size: 1.35em;
}
/* Full white on the banner, not the 62% the fixture strap around it uses.
   That strap is a label and is meant to recede; the name of the app is not,
   and inheriting the label's colour was most of why the logo got lost.
   Scoped to the banner rather than set on .brand-word itself, because the
   same wordmark also sits on the white sign-in card — where white on white
   is no wordmark at all. */
.banner h1 .brand-word {
  color: #fff;
}
.brand-word-a {
  font-weight: 800;
}
.brand-word-k,
.brand-word-b {
  font-weight: 500;
}

.week-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 0;
}

/* The fixture. This is now the biggest thing in the header. */
.week-nav #week-range {
  min-width: 176px;
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #fff;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
}

.week-nav button {
  width: auto;
  min-height: 30px;
  padding: 4px 12px;
  font-size: 1.1rem;
  line-height: 1;
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.9);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 8px;
  box-shadow: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s var(--ease), transform 0.22s var(--spring), box-shadow 0.15s var(--ease);
}

.week-nav button:active {
  transform: scale(0.92);
  background: rgba(255, 255, 255, 0.18);
}

.week-nav button:disabled {
  opacity: 0.3;
}

main {
  max-width: 480px;
  margin: 0 auto;
  padding: 16px;
}

.card {
  background: var(--card);
  border-radius: var(--radius-lg);
  padding: 18px;
  margin-bottom: 16px;
  box-shadow: var(--shadow-md);
  animation: fadeInUp 0.35s var(--ease);
}

label {
  display: block;
  font-weight: 600;
  margin-bottom: 6px;
  font-size: 0.9rem;
}

textarea,
input[type="text"],
input[type="number"],
input[type="password"],
input[type="time"],
input[type="date"],
input[type="search"],
select {
  width: 100%;
  font-size: 1rem;
  padding: 10px 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  font-family: inherit;
  margin-bottom: 12px;
  resize: vertical;
  background: var(--card);
  color: var(--ink);
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

textarea:focus,
input:focus,
select:focus {
  outline: none;
  border-color: var(--pitch);
  box-shadow: 0 0 0 3px rgba(31, 157, 82, 0.15);
}

/* Native <select> chrome (the dropdown arrow) is the one input style that
   couldn't inherit the shared text-input look above — appearance:none
   strips it so a custom chevron (matching the app's inline-icon style)
   can take its place. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-size: 18px;
}

.photo-label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1.5px dashed var(--hairline);
  border-radius: var(--radius-sm);
  padding: 12px;
  margin-bottom: 12px;
  cursor: pointer;
  font-weight: 500;
  color: var(--muted);
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.photo-label .icon {
  font-size: 1.1rem;
}

.photo-label:active {
  border-color: var(--pitch);
  background: rgba(31, 157, 82, 0.04);
}

.photo-label input[type="file"] {
  display: none;
}

button {
  width: 100%;
  min-height: 48px;
  font-size: 1rem;
  font-weight: 600;
  border: none;
  border-radius: var(--radius-sm);
  background: linear-gradient(135deg, var(--pitch-light), var(--pitch));
  color: white;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(31, 157, 82, 0.28);
  transition: transform 0.25s var(--spring), box-shadow 0.12s var(--ease), opacity 0.12s var(--ease);
}

button:active {
  transform: scale(0.96);
  box-shadow: 0 1px 4px rgba(31, 157, 82, 0.28);
}

button:disabled {
  opacity: 0.55;
  box-shadow: none;
  transform: none;
}

button.ghost {
  background: transparent;
  color: var(--pitch);
  border: 1.5px solid var(--pitch);
  margin-top: 8px;
  box-shadow: none;
}

button.ghost:active {
  background: rgba(31, 157, 82, 0.08);
}

.button-link {
  display: inline-block;
  margin-top: 8px;
  padding: 10px 20px;
  font-size: 1rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1.5px solid var(--pitch);
  color: var(--pitch);
  text-decoration: none;
  transition: background 0.12s var(--ease), color 0.12s var(--ease), transform 0.2s var(--spring);
}

.button-link:active {
  /* --pitch rather than --pitch-dark: the latter inverts to a pale green in
     dark mode, which left white press text almost unreadable. */
  background: var(--pitch);
  color: #fff;
  transform: scale(0.98);
}

.error {
  color: #b3261e;
  font-size: 0.9rem;
}

.summary {
  text-align: left;
  background: var(--card);
}

/* Ink, not green. The form guide directly above it is the thing meant to
   carry the week's verdict; a second green statement two lines later was
   arguing with it. The number stays large and confident, it just stops
   competing on colour. */
.summary-total {
  font-size: 2.1rem;
  font-weight: 700;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1.05;
}

.week-net-summary {
  margin-top: 8px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--muted);
}
.week-net-summary--under {
  color: var(--pitch-dark);
}
.week-net-summary--over {
  color: var(--danger);
}

.day-totals {
  margin-top: 12px;
  text-align: left;
}

.day-total-row {
  display: flex;
  justify-content: space-between;
  font-size: 0.85rem;
  padding: 4px 0;
  border-bottom: 1px solid var(--hairline);
}

.day-total-row:last-child {
  border-bottom: none;
}

.day-total-row--today {
  background: var(--bg);
  border-bottom: none;
  border-radius: 8px;
  padding: 6px 8px;
  margin: 2px 0;
}

.day-total-label {
  color: var(--muted);
}

.day-total-whoop {
  display: inline-block;
  font-size: 0.75rem;
  color: var(--muted);
  font-weight: 400;
  margin-top: 2px;
}

.day-total-net {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 600;
  margin-top: 2px;
  color: var(--muted);
}
.day-total-net--under {
  color: var(--pitch-dark);
}
.day-total-net--over {
  color: var(--danger);
}

.day-total-row--today .day-total-label,
.day-total-row--today .day-total-kcal {
  color: var(--pitch-dark);
  font-weight: 700;
}

.day-total-kcal {
  font-weight: 600;
}

.muted {
  color: var(--muted);
  font-size: 0.9rem;
}

/* Section eyebrows, set like the column headings on a team sheet. The
   little green bar that used to sit in front of every one of these is gone:
   it encoded nothing, it just repeated the brand colour eleven times down
   the page. That was the one accessory to take off before leaving. */
h2 {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: 0 6px 10px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.day-group {
  margin-bottom: 14px;
}

.day-heading {
  display: flex;
  justify-content: space-between;
  /* Centred, not baseline: the note button is a 28px box with no text in it,
     and baseline alignment hung it below the row. */
  align-items: center;
  gap: 8px;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin: 14px 6px 6px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--hairline);
}

/* Today is marked by ink and a solid rule, not another green wash. */
.day-heading--today {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

.log-week-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.log-week-btns {
  display: flex;
  gap: 6px;
}

.log-week-btn {
  width: auto;
  min-height: 36px;
  padding: 4px 14px;
  font-size: 0.9rem;
  font-weight: 500;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--hairline);
  border-radius: 8px;
}

.log-week-btn--active {
  background: var(--pitch);
  color: white;
  border-color: var(--pitch);
}

.week-select {
  flex: 1;
  min-width: 120px;
  margin-bottom: 0;
}

.entry-row {
  background: var(--card);
  border-radius: var(--radius-md);
  padding: 11px 14px;
  margin-bottom: 8px;
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  gap: 10px;
  animation: fadeInUp 0.3s var(--ease);
  transition: box-shadow 0.15s var(--ease), transform 0.22s var(--spring);
}

.entry-row:active {
  box-shadow: var(--shadow-sm);
  transform: scale(0.995);
}

.entry-main {
  flex: 1;
  min-width: 0;
}
.entry-time {
  color: var(--muted);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  margin-top: 1px;
}

.entry-label {
  font-size: 0.95rem;
  line-height: 1.25;
}

.entry-kcal {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  /* The provenance badge sits inside this cell, so it must never be split
     onto its own line away from the number it describes. */
  white-space: nowrap;
}

/* Icon-sized actions fit beside even a long label and a provenance badge on
   a narrow phone, so the row stays a single line. No min-width: 0 on the
   label — that would let it shrink past its longest word and break the text
   one character per line. */
.entry-actions {
  display: flex;
  flex-wrap: nowrap;
  gap: 4px;
  flex-shrink: 0;
  margin-left: auto;
}
.entry-actions button.entry-action-icon {
  width: 32px;
  padding: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.entry-actions button {
  width: auto;
  min-height: 32px;
  padding: 4px 10px;
  font-size: 0.85rem;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--hairline);
  box-shadow: none;
}

/* ── Editing an entry ──
   This was a flex row of bare boxes: a name, then 168, then 1, then a date,
   with nothing to say which number was which — and the macro boxes were a
   separate sibling of a flex parent, so they were laid out BESIDE the rest
   instead of under them and the form read as two unrelated halves.
   It is one grid now, every box captioned. */
.entry-edit {
  flex: 1 1 100%;
  min-width: 0;
}

.entry-edit-grid {
  display: grid;
  /* Six columns so a field can take a half (3), a third (2) or the lot (6)
     without a second set of nested containers. */
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 10px;
}

.entry-edit-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  margin: 0;
  font-weight: 400;
}
.entry-edit-field--full { grid-column: span 6; }
.entry-edit-field--half { grid-column: span 3; }
.entry-edit-field--third { grid-column: span 2; }

.entry-edit-caption {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.entry-edit-field input,
.entry-edit-field select {
  width: 100%;
  min-width: 0;
  margin-bottom: 0;
}

.entry-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 12px;
}
.entry-edit-actions button {
  width: auto;
  min-width: 92px;
}

.empty-state {
  text-align: center;
  color: var(--muted);
  padding: 20px;
}

.auth-screen:not([hidden]) {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background:
    radial-gradient(circle at 15% 10%, rgba(31, 157, 82, 0.1), transparent 45%),
    radial-gradient(circle at 85% 90%, rgba(217, 119, 6, 0.09), transparent 45%),
    var(--bg);
}

.auth-card {
  width: 100%;
  max-width: 360px;
  position: relative;
  overflow: hidden;
}
.auth-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 5px;
  background: linear-gradient(90deg, var(--pitch-light), var(--pitch), var(--amber));
}

.auth-brand-mark {
  display: block;
  margin: 0 auto 14px;
  border-radius: 18px;
  box-shadow: var(--shadow-md);
}

.auth-title {
  text-align: center;
  color: var(--pitch-dark);
  margin: 0 0 16px;
  font-size: 1.3rem;
}

.auth-toggle {
  text-align: center;
  margin-top: 4px;
}

.google-signin-btn:not([hidden]) {
  display: flex;
  justify-content: center;
  margin-bottom: 14px;
}

.auth-divider:not([hidden]) {
  display: flex;
  align-items: center;
  text-align: center;
  color: var(--muted);
  font-size: 0.85rem;
  margin: 0 0 14px;
}

.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  border-bottom: 1px solid var(--hairline);
}

.auth-divider span {
  padding: 0 10px;
}

.link-btn {
  width: auto;
  min-height: auto;
  background: none;
  border: none;
  padding: 0;
  color: var(--pitch);
  font-weight: 600;
  font-size: 0.9rem;
  text-decoration: underline;
}

#settings-card .muted {
  margin-bottom: 12px;
}

/* ── Settings fieldsets ── */
.settings-fieldset {
  border: 1px solid var(--hairline);
  border-radius: 10px;
  padding: 12px 14px 14px;
  margin: 0 0 14px;
}
.settings-fieldset legend {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0 4px;
}
.settings-row {
  display: flex;
  gap: 10px;
}
.settings-field {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.settings-field label {
  margin: 0;
  font-size: 0.8rem;
}
.settings-field input {
  margin: 0;
  padding: 8px 10px;
  font-size: 0.9rem;
}

.whoop-status-row {
  margin-bottom: 8px;
}
#whoop-connect-btn,
#whoop-disconnect-btn {
  width: auto;
  min-height: 36px;
  padding: 6px 14px;
  font-size: 0.85rem;
}

/* ── Budget widget ── */
.budget-widget {
  position: relative;
  overflow: hidden;
}

.budget-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 12px;
}
.budget-title {
  font-weight: 800;
  font-size: 1.02rem;
  letter-spacing: -0.01em;
}
.balance-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.balance-grid--single {
  grid-template-columns: 1fr;
}
.balance-cell {
  text-align: center;
  background: var(--bg);
  border-radius: var(--radius-sm);
  padding: 16px 8px;
  transition: transform 0.22s var(--spring);
}
.balance-number {
  display: block;
  font-size: 1.55rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.balance-caption {
  display: block;
  font-size: 0.75rem;
  color: var(--muted);
  margin-top: 3px;
  font-weight: 500;
}
.balance-cell--loss {
  background: rgba(31, 157, 82, 0.12);
}
.balance-cell--loss .balance-number {
  color: var(--pitch-dark);
}
.balance-cell--gain {
  background: rgba(220, 38, 38, 0.08);
}
.balance-cell--gain .balance-number {
  color: var(--danger);
}
.balance-cell--accent {
  background: rgba(217, 119, 6, 0.12);
}
.balance-cell--accent .balance-number {
  color: var(--amber);
}

/* ── Exercise section ── */
.section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}
.section-header h2 { margin: 0; }
.ghost-sm {
  width: auto;
  min-height: 32px;
  padding: 4px 12px;
  font-size: 0.85rem;
  background: transparent;
  color: var(--pitch);
  border: 1px solid var(--pitch);
  border-radius: 8px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.exercise-entry {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--hairline);
}
.exercise-entry:last-child { border-bottom: none; }
.exercise-icon {
  font-size: 1.1rem;
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: rgba(31, 157, 82, 0.1);
  color: var(--pitch-dark);
  display: flex;
  align-items: center;
  justify-content: center;
}
.exercise-label { flex: 1; font-size: 0.95rem; }
.exercise-time { display: inline-block; margin-top: 2px; font-size: 0.75rem; color: var(--muted); font-weight: 400; }
.exercise-kcal { font-weight: 700; color: var(--pitch); font-size: 0.9rem; white-space: nowrap; }
.exercise-del {
  width: auto;
  min-height: auto;
  padding: 2px 8px;
  font-size: 0.75rem;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 6px;
  color: var(--muted);
}
#exercise-list:empty::after {
  content: "No exercise logged this week.";
  display: block;
  text-align: center;
  color: var(--muted);
  padding: 16px;
  font-size: 0.9rem;
}
#entry-list:empty::after {
  content: "Nothing logged yet this week.";
  display: block;
  text-align: center;
  color: var(--muted);
  padding: 16px;
  font-size: 0.9rem;
}

/* ── Food library ── */
#food-search {
  margin: 0;
}
.food-row {
  background: var(--card);
  border-radius: var(--radius-md);
  padding: 11px 14px;
  margin-bottom: 8px;
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: flex-start;
  gap: 10px;
  animation: fadeInUp 0.3s var(--ease);
}
.food-star {
  width: 32px;
  min-height: 32px;
  padding: 0;
  flex-shrink: 0;
  background: transparent;
  border: none;
  color: var(--gold);
  font-size: 1.3rem;
  line-height: 1;
}
.food-info {
  flex: 1;
  min-width: 0;
}
.food-label {
  font-size: 0.95rem;
  font-weight: 600;
}
.food-meta {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 2px;
}
.food-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
  align-items: center;
}
.food-tag-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--bg);
  border-radius: 999px;
  padding: 2px 4px 2px 10px;
  font-size: 0.72rem;
  color: var(--pitch-dark);
}
.food-tag-pill button {
  width: auto;
  min-height: auto;
  padding: 0 6px;
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.6;
}
.food-tag-add {
  width: auto;
  min-height: auto;
  padding: 2px 8px;
  font-size: 0.72rem;
  background: transparent;
  border: 1px dashed var(--hairline);
  border-radius: 999px;
  color: var(--muted);
}
.food-tag-input {
  width: 90px;
  min-height: 24px;
  margin: 0;
  padding: 2px 8px;
  font-size: 0.72rem;
  border-radius: 999px;
}
.food-log-btn {
  width: auto;
  min-height: 32px;
  padding: 4px 12px;
  font-size: 0.85rem;
  flex-shrink: 0;
  align-self: center;
}
#food-all-list:empty::after,
#food-favorites-list:empty::after {
  content: "Nothing here yet.";
  display: block;
  text-align: center;
  color: var(--muted);
  padding: 16px;
  font-size: 0.9rem;
}

/* ── Stats / weigh-ins ── */
#weighin-chart {
  width: 100%;
  height: 150px;
  display: block;
}
.weighin-chart-line {
  fill: none;
  stroke: var(--pitch);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* Every line chart marks its actual data points, so it reads as measured
   readings joined by a line rather than an anonymous continuous squiggle.
   The visible dot is inert; a larger invisible circle on top of it is the
   tap target, so a fingertip doesn't have to land on a ~3px dot. */
.chart-dot {
  pointer-events: none;
  stroke: var(--card);
  stroke-width: 1.4;
}
/* Once points are packed tightly (a 30-day daily view), the card-coloured
   ring around each one punches through the line between them and the
   series reads as a dashed bead chain rather than a line with markers.
   Dense series drop the ring and shrink, so the points still read as
   discrete readings without breaking the line they sit on. */
.chart-dot--dense {
  stroke: none;
}
.chart-dot--weight {
  fill: var(--pitch-dark);
}
.chart-dot--in {
  fill: var(--pitch);
}
.chart-dot--out {
  fill: var(--amber);
}
.chart-hit {
  fill: transparent;
  pointer-events: all;
  cursor: pointer;
  transition: fill 0.12s var(--ease);
}
/* The browser's default focus ring on an SVG circle draws as a hard black
   rectangle over the chart. The active/focus fill below is the affordance
   instead — a soft round halo that actually follows the marker's shape. */
.chart-hit:focus {
  outline: none;
}
.chart-hit.tooltip-active,
.chart-hit:focus-visible {
  fill: rgba(20, 30, 24, 0.13);
}
.weighin-chart-grid {
  stroke: var(--hairline);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.weighin-chart-label {
  font-size: 10px;
  font-family: "Inter", sans-serif;
  fill: var(--muted);
}
.chart-area {
  stroke: none;
}
.chart-area.chart-animate {
  opacity: 0;
  animation: chart-fade 0.5s var(--ease) 0.35s forwards;
}
.chart-dot {
  transform-box: fill-box;
  transform-origin: center;
}
.chart-dot.chart-animate {
  opacity: 0;
  animation: chart-point-in 0.35s var(--spring) 0.4s forwards;
}
.chart-fade-in.chart-animate {
  opacity: 0;
  animation: chart-fade 0.5s var(--ease) 0.55s forwards;
}
@keyframes chart-fade {
  to {
    opacity: 1;
  }
}
@keyframes chart-point-in {
  from {
    opacity: 0;
    transform: scale(0.3);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
.weighin-row {
  align-items: center;
}
.weighin-del {
  flex-shrink: 0;
}
#weighin-trend-caption {
  margin: 10px 6px 0;
}

/* ── Calorie balance trend ── */
.card-heading-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  row-gap: 8px;
  margin: 0 6px 10px;
}
.card-heading-row h2 {
  margin: 0;
  white-space: nowrap;
}
.chart-legend {
  display: flex;
  gap: 12px;
}
.chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted);
}
.chart-legend-swatch {
  width: 9px;
  height: 9px;
  border-radius: 3px;
  display: inline-block;
}
.chart-legend-swatch--in {
  background: var(--pitch);
}
.chart-legend-swatch--out {
  background: var(--amber);
}
.balance-trend-controls {
  display: flex;
  align-items: center;
  gap: 12px;
}
.granularity-toggle {
  display: flex;
  gap: 2px;
  background: var(--bg);
  border-radius: 8px;
  padding: 2px;
  box-shadow: inset 0 1px 2px rgba(20, 30, 24, 0.07);
}
.granularity-btn {
  border: none;
  background: transparent;
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  min-height: 26px;
  width: auto;
  cursor: pointer;
  transition: transform 0.18s var(--spring), background 0.15s var(--ease), color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.granularity-btn--active {
  background: var(--pill-active-bg);
  color: var(--pill-active-ink);
  box-shadow: var(--pill-active-shadow);
}
.granularity-btn:active {
  transform: scale(0.94);
}

/* ── Stats tabs ── */
.stats-tabs {
  display: flex;
  gap: 4px;
  background: var(--bg);
  border-radius: 12px;
  padding: 4px;
  margin: 0 0 14px;
  box-shadow: inset 0 1px 3px rgba(20, 30, 24, 0.07);
}
.stats-tab-btn {
  flex: 1;
  border: none;
  background: transparent;
  border-radius: 9px;
  padding: 9px 8px;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--muted);
  min-height: 38px;
  width: auto;
  cursor: pointer;
  transition: transform 0.18s var(--spring), background 0.15s var(--ease), color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.stats-tab-btn--active {
  background: var(--pill-active-bg);
  color: var(--pill-active-ink);
  box-shadow: var(--pill-active-shadow);
}
.stats-tab-btn:active {
  transform: scale(0.96);
}
.stats-tab-panel > * + * {
  margin-top: 14px;
}

/* ── Tooltips ── */
.has-tooltip {
  cursor: pointer;
}
/* The whole breakdown-table row is the tappable target now, not just the
   date cell — the dotted underline stays on the date as the visual cue
   for "this row has more detail", and a press highlight makes the wider
   hit area obvious the first time it's tapped. */
tr.has-tooltip td:first-child {
  text-decoration: underline dotted var(--muted);
  text-underline-offset: 3px;
}
tr.has-tooltip {
  transition: background 0.12s var(--ease);
}
tr.has-tooltip:active {
  background: var(--bg);
}
.app-tooltip {
  position: fixed;
  z-index: 100;
  transform: translateX(-50%);
  max-width: 260px;
  width: max-content;
  background: var(--tooltip-bg);
  color: var(--tooltip-ink);
  font-size: 0.78rem;
  font-weight: 500;
  line-height: 1.35;
  padding: 8px 11px;
  border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.22);
  pointer-events: none;
  animation: tooltip-in 0.12s ease-out;
}
/* Caret pointing back at the tapped point. --caret-offset is set in JS to
   however far the box had to slide to stay on screen, so the caret keeps
   pointing at the point even when the box itself is nudged sideways. */
.app-tooltip::after {
  content: "";
  position: absolute;
  left: calc(50% + var(--caret-offset, 0px));
  transform: translateX(-50%);
  border: 6px solid transparent;
}
.app-tooltip--top::after {
  top: 100%;
  border-top-color: var(--tooltip-bg);
}
.app-tooltip--bottom::after {
  bottom: 100%;
  border-bottom-color: var(--tooltip-bg);
}
@keyframes tooltip-in {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(-3px);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}
#balance-trend-chart {
  width: 100%;
  height: 150px;
  display: block;
}
.balance-trend-line {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.balance-trend-line--in {
  stroke: var(--pitch);
}
.balance-trend-line--out {
  stroke: var(--amber);
}
#balance-trend-caption {
  margin: 10px 6px 0;
}

/* ── Insights ── */
.insights-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
/* Separated by a rule rather than by a filled box each. Two short
   observations in tinted panels with bullet dots read as a to-do list; they
   are sentences, and they should look like sentences. */
.insights-list li {
  padding: 10px 0;
  font-size: 0.88rem;
  line-height: 1.45;
  color: var(--ink);
}
.insights-list li + li {
  border-top: 1px solid var(--hairline);
}
.insights-list li:first-child {
  padding-top: 0;
}
.insights-list li:last-child {
  padding-bottom: 0;
}

/* ── Weekly breakdown ── */
.table-scroll {
  overflow-x: auto;
  margin: 0 -2px;
}
.breakdown-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
  white-space: nowrap;
}
.breakdown-table th {
  text-align: left;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  padding: 0 10px 8px;
}
.breakdown-table td {
  padding: 9px 10px;
  border-top: 1px solid var(--hairline);
  font-variant-numeric: tabular-nums;
}
.breakdown-table tbody tr:first-child td {
  border-top: none;
}
.breakdown-loss {
  color: var(--pitch-dark);
  font-weight: 700;
}
.breakdown-gain {
  color: var(--danger);
  font-weight: 700;
}

/* Hero stats: real hierarchy for the numbers that matter most (recovery,
   sleep) instead of every figure being the same tile size — a large,
   confident number with a small tracked-letter-spacing caption underneath,
   the same pattern premium fitness apps (Oura, WHOOP's own app) use. */
.hero-row {
  display: flex;
  justify-content: space-around;
  gap: 16px;
  text-align: center;
}
.stat-hero-number {
  display: block;
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: clamp(2.2rem, 9vw, 3rem);
  letter-spacing: -0.02em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.stat-hero-caption {
  display: block;
  margin-top: 4px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.stat-hero--good .stat-hero-number { color: var(--pitch-dark); }
.stat-hero--fair .stat-hero-number { color: var(--amber); }
.stat-hero--poor .stat-hero-number { color: var(--danger); }

/* ── Units toggle ── */
.units-toggle {
  display: flex;
  gap: 4px;
  background: var(--bg);
  border-radius: 10px;
  padding: 3px;
  box-shadow: inset 0 1px 3px rgba(20, 30, 24, 0.07);
}
.units-btn {
  flex: 1;
  border: none;
  background: transparent;
  border-radius: 8px;
  padding: 6px 12px;
  font-size: 0.85rem;
  color: var(--muted);
  font-weight: 500;
  min-height: 32px;
  width: auto;
  cursor: pointer;
  transition: transform 0.18s var(--spring), background 0.15s var(--ease), color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.units-btn--active {
  background: var(--pill-active-bg);
  color: var(--pill-active-ink);
  box-shadow: var(--pill-active-shadow);
}
.units-btn:active {
  transform: scale(0.95);
}
.ft-in-row {
  display: flex;
  gap: 5px;
}
.ft-in-row input { flex: 1; margin: 0; }

/* ── Capture shortcuts (scan / search / speak) ── */
.capture-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
/* Four buttons don't fit across a 390px phone, so they go two-by-two rather
   than shrinking until the labels clip. */
.capture-row .capture-btn {
  flex: 1 1 calc(50% - 4px);
  min-width: 0;
}
@media (min-width: 560px) {
  .capture-row .capture-btn { flex: 1 1 0; }
}
.capture-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: transparent;
  border: 2px dashed var(--hairline);
  color: var(--pitch-dark);
  font-size: 0.85rem;
  padding: 10px 6px;
  border-radius: 10px;
  min-height: 44px;
  width: auto;
}
.capture-btn .icon {
  font-size: 1rem;
  flex-shrink: 0;
}
.capture-btn:hover {
  border-color: var(--pitch);
  background: rgba(31, 157, 82, 0.04);
}
/* Listening is a state the user needs to see at a glance, so it goes solid
   and pulses rather than just changing its label. */
.capture-btn--listening {
  border-style: solid;
  border-color: var(--danger);
  color: var(--danger);
  background: rgba(220, 38, 38, 0.06);
  animation: listenPulse 1.4s ease-in-out infinite;
}
@keyframes listenPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.28); }
  50% { box-shadow: 0 0 0 6px rgba(220, 38, 38, 0); }
}

.scan-modal {
  position: fixed;
  inset: 0;
  background: #000;
  z-index: 200;
  display: flex;
  flex-direction: column;
}
.scan-modal[hidden] { display: none; }

.scan-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(16px + env(safe-area-inset-top)) 20px 16px;
  color: #fff;
  font-size: 1rem;
  font-weight: 600;
  flex-shrink: 0;
}
.scan-close-btn {
  width: 36px;
  height: 36px;
  min-height: 36px;
  padding: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.2);
  border: none;
  color: #fff;
  font-size: 1.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.scan-viewport {
  flex: 1;
  position: relative;
  overflow: hidden;
}
#scan-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.scan-reticle {
  position: absolute;
  inset: 25% 10%;
  border: 2px solid rgba(255, 255, 255, 0.9);
  border-radius: 8px;
  box-shadow:
    0 0 0 9999px rgba(0, 0, 0, 0.45);
}

.scan-status {
  flex-shrink: 0;
  text-align: center;
  color: rgba(255, 255, 255, 0.8);
  font-size: 0.9rem;
  padding: 14px 20px;
}

/* ── Secondary/icon buttons never inherit the primary CTA's gradient shadow ── */
.log-week-btn,
.ghost-sm,
.exercise-del,
.food-star,
.food-tag-pill button,
.food-tag-add,
.units-btn,
.capture-btn,
.scan-close-btn,
.entry-actions button {
  box-shadow: none;
  background-image: none;
}

.ghost-sm,
.food-log-btn {
  transition: transform 0.2s var(--spring), background 0.12s var(--ease);
}
.ghost-sm:active {
  background: rgba(31, 157, 82, 0.08);
  transform: scale(0.96);
}

.entry-actions button,
.exercise-del,
.food-tag-add {
  transition: background 0.12s var(--ease), transform 0.2s var(--spring);
}
.entry-actions button:active,
.exercise-del:active {
  background: var(--bg);
  transform: scale(0.94);
}

.food-star {
  transition: transform 0.22s var(--spring);
}
.food-star:active {
  transform: scale(1.25);
}

.units-btn {
  transition: color 0.15s var(--ease);
}

/* ── Empty states get a little more personality than a bare line of text ── */
.empty-state {
  font-size: 0.9rem;
}
#exercise-list:empty::after,
#food-all-list:empty::after,
#food-favorites-list:empty::after,
#entry-list:empty::after {
  border: 1.5px dashed var(--hairline);
  border-radius: var(--radius-md);
}

/* ── Responsive layout ──────────────────────────────────────────────────────
   Everything above this point was written mobile-only (no @media queries
   anywhere in the file). These two breakpoints keep every screen exactly as
   it is below 720px and progressively give it room to breathe above that,
   without changing the one-screen-at-a-time navigation model. */
@media (min-width: 720px) {
  main {
    max-width: 640px;
  }
}

@media (min-width: 1024px) {
  main {
    max-width: 900px;
  }

  /* The back chevron is a compact mobile affordance; at this width there's
     room for the tab bar to be the only navigation. */
  .library-back {
    display: none;
  }

  .banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    text-align: left;
    padding: 18px 32px;
    gap: 16px 24px;
  }
  .banner h1,
  #app-shell .banner h1 {
    padding: 0;
    margin: 0;
    flex-shrink: 0;
  }
  .week-nav {
    margin: 0;
    order: 3;
    flex-basis: 100%;
    justify-content: flex-start;
  }

  /* Settings: the fieldsets are already flat siblings inside one card, so a
     grid here lets them flow two-up without touching the markup — the
     account line, save button, error and logout stay full-width. */
  #settings-card {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px 24px;
    align-items: start;
  }
  #settings-card > .muted,
  #settings-card > .settings-group,
  #settings-card > #settings-save,
  #settings-card > #settings-error,
  #settings-card > #logout-btn,
  #settings-card > .settings-section--danger,
  #settings-card > .settings-legal {
    grid-column: 1 / -1;
  }

  /* Food library and food diary: same trick — the row/group containers
     already hold nothing but repeated same-shaped children. */
  #food-favorites-list,
  #food-all-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px 12px;
  }
  #food-favorites-list .food-row,
  #food-all-list .food-row {
    margin-bottom: 0;
  }
  #entry-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 24px;
    align-items: start;
  }

  /* Stats: summary tiles + trend chart sit side by side. */
  .stats-top-row {
    display: grid;
    grid-template-columns: 1fr 1.3fr;
    gap: 16px;
    align-items: start;
  }
  .stats-top-row .card {
    margin-bottom: 0;
  }
}

/* ── Current weight hero ── */
.weight-hero {
  text-align: center;
  padding: 4px 0 16px;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: 14px;
}
.weight-hero-number {
  display: block;
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: clamp(2.4rem, 11vw, 3.4rem);
  letter-spacing: -0.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--pitch-dark);
}
.weight-hero-caption {
  display: block;
  margin-top: 6px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted);
}
.weight-hero-note {
  display: block;
  margin-top: 8px;
  font-size: 0.8rem;
  color: var(--muted);
}

/* ── Goal progress ── */
.goal-progress {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline);
}
.goal-progress-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 8px;
}
.goal-progress-label {
  font-weight: 700;
  font-size: 0.92rem;
}
.goal-progress-eta {
  font-size: 0.8rem;
  color: var(--muted);
}
.goal-progress-track {
  height: 8px;
  border-radius: 999px;
  background: var(--bg);
  box-shadow: inset 0 1px 2px rgba(20, 30, 24, 0.09);
  overflow: hidden;
}
.goal-progress-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--pitch-light), var(--pitch));
  transition: width 0.6s var(--ease);
}

/* ── Adaptive TDEE ── */
.confidence-pill {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 4px 9px;
  border-radius: 999px;
  white-space: nowrap;
}
.confidence-pill--high {
  background: rgba(31, 157, 82, 0.14);
  color: var(--pitch-dark);
}
.confidence-pill--medium {
  background: rgba(217, 119, 6, 0.14);
  color: var(--amber);
}
.confidence-pill--low {
  background: rgba(107, 114, 128, 0.14);
  color: var(--muted);
}
#tdee-explain {
  margin: 10px 6px 0;
}
.tdee-warning {
  margin: 12px 0 0;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: rgba(217, 119, 6, 0.1);
  border-left: 3px solid var(--amber);
  font-size: 0.85rem;
  line-height: 1.45;
}

/* ── Data export/import ── */
.export-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 4px 0 14px;
}
.export-row .button-link {
  margin-top: 0;
  font-size: 0.85rem;
  padding: 8px 14px;
}
.import-label {
  display: block;
  margin-bottom: 6px;
}

/* ── Quick add ── */
.quick-add {
  padding-bottom: 12px;
}
.quick-add-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.quick-add-title {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.95rem;
}
.quick-add-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.quick-chip {
  width: auto;
  min-height: 42px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--card);
  background-image: none;
  color: var(--ink);
  box-shadow: var(--shadow-sm);
  font-size: 0.85rem;
  text-align: left;
  transition: transform 0.2s var(--spring), box-shadow 0.15s var(--ease), border-color 0.15s var(--ease);
}
/* Saved meals read as the deliberate, curated option; individual foods as
   the quick one. The tint is the only thing separating them. */
.quick-chip--meal {
  border-color: rgba(31, 157, 82, 0.35);
  background: rgba(31, 157, 82, 0.06);
  color: var(--pitch-dark);
}
.quick-chip:hover {
  border-color: var(--pitch);
  box-shadow: var(--shadow-md);
}
.quick-chip:active {
  transform: scale(0.95);
}
/* Picked, not yet logged. The filled state has to be unmistakable at a
   glance: an unselected chip and a selected one differ in border, fill and
   weight, not just a shade. */
.quick-chip--selected {
  border-color: var(--pitch);
  background: rgba(31, 157, 82, 0.14);
  color: var(--pitch-dark);
  font-weight: 700;
  box-shadow: 0 0 0 2px rgba(31, 157, 82, 0.22);
}
.quick-chip-star {
  color: var(--gold);
  display: inline-flex;
  flex-shrink: 0;
}
.quick-chip-star .icon {
  width: 13px;
  height: 13px;
}
.quick-chip-text {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
}
.quick-chip-label {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 40vw;
}
.quick-chip-sub {
  font-size: 0.7rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ── Where a calorie figure came from ── */
.entry-source {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 4px;
  border-radius: 5px;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  vertical-align: 1px;
  cursor: help;
}
.entry-source--est {
  background: rgba(217, 119, 6, 0.13);
  color: var(--amber);
}
.entry-source--verified {
  background: rgba(31, 157, 82, 0.13);
  color: var(--pitch-dark);
}

/* ── Plan and allowance ───────────────────────────────────────────────────
   Deliberately quiet. What plan someone is on is a fact about their account,
   not a thing to sell at them every time they open Settings. */
.plan-card {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  margin-bottom: 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.plan-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.plan-name {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.05rem;
}
.plan-price {
  font-size: 0.85rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.plan-usage {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}
.plan-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.plan-options:not(:empty) {
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
}
.plan-option {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.plan-option-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.plan-option-name {
  font-weight: 700;
  font-size: 0.9rem;
}
.plan-option-price {
  font-size: 0.82rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.plan-option-tagline {
  margin: 0;
  font-size: 0.8rem;
  color: var(--muted);
}
.plan-option-list {
  margin: 2px 0 0;
  padding-left: 18px;
  font-size: 0.8rem;
  color: var(--muted);
}
.plan-option-list li {
  margin-bottom: 2px;
}

.plan-buy {
  width: auto;
  align-self: flex-start;
  min-height: 38px;
  margin-top: 6px;
  padding: 8px 16px;
  font-size: 0.85rem;
}
.plan-manage {
  width: auto;
  align-self: flex-start;
  margin-bottom: 4px;
}

/* ── Invites ──────────────────────────────────────────────────────────────
   The one card in Settings with a colour behind it, because it is the only
   one making an offer rather than reporting a state. A tinted panel in the
   brand green rather than a second white box in a column of white boxes. */
.referral-card {
  border: 1px solid color-mix(in srgb, var(--pitch) 28%, var(--hairline));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--pitch) 7%, var(--card));
  padding: 12px 14px;
  margin-bottom: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.referral-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.referral-title {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.05rem;
}
.referral-reward {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--pitch);
  font-variant-numeric: tabular-nums;
}
.referral-pitch {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}
.referral-code-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
  flex-wrap: wrap;
}
/* The code has to survive being read aloud and typed on a phone, so it gets
   the widest tracking in the app and a monospaced face. */
.referral-code {
  flex: 1 1 120px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: 6px 12px;
  border: 1px dashed color-mix(in srgb, var(--pitch) 40%, var(--hairline));
  border-radius: var(--radius-sm);
  background: var(--card);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  /* The tracking above adds a gap after the last character, which reads as
     off-centre unless it is taken back. */
  text-indent: 0.18em;
  color: var(--ink);
}
.referral-share {
  width: auto;
  flex: 0 0 auto;
  min-height: 40px;
  padding: 8px 16px;
  font-size: 0.85rem;
}
.referral-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 2px;
  padding-top: 10px;
  border-top: 1px solid color-mix(in srgb, var(--pitch) 20%, var(--hairline));
}
.referral-stat {
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: center;
}
.referral-stat-value {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 1.15rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.referral-stat-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

/* The line on the sign-in card naming who invited them. */
.auth-invite {
  margin: 0 0 4px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--pitch) 10%, var(--card));
  border: 1px solid color-mix(in srgb, var(--pitch) 25%, var(--hairline));
  font-size: 0.85rem;
  color: var(--ink);
  text-align: center;
}

/* ── The admin screen ─────────────────────────────────────────────────────
   A working screen rather than a designed one: dense, scannable, and the only
   place in the app that shows money. */
.admin-card {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  margin-bottom: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.admin-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.admin-title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.05rem;
}
.admin-summary {
  display: grid;
  /* Two across on a phone. Four squeezed "AI this month" onto three lines
     beside captions that fitted on one, which made the row read as broken
     rather than dense. */
  grid-template-columns: repeat(2, 1fr);
  gap: 10px 12px;
}
@media (min-width: 520px) {
  .admin-summary { grid-template-columns: repeat(4, 1fr); }
}
.admin-stat {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.admin-stat-number {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
}
.admin-stat-caption {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
/* A margin that has gone negative is the one thing on this screen that has to
   be impossible to scroll past. */
.admin-stat--bad .admin-stat-number { color: var(--danger); }

.admin-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.admin-row-label {
  font-size: 0.85rem;
  font-weight: 600;
}
.admin-signups {
  flex: 0 0 auto;
}
.admin-counts {
  margin: 0 0 4px;
  font-size: 0.78rem;
}
.admin-users {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 340px;
  overflow-y: auto;
}
.admin-user {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "name plan" "meta plan";
  align-items: center;
  gap: 2px 10px;
  padding: 7px 9px;
  border-radius: var(--radius-sm);
  background: var(--bg);
}
.admin-user--at-cap {
  box-shadow: inset 3px 0 0 var(--floodlight);
}
.admin-user-name {
  grid-area: name;
  font-size: 0.85rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.admin-user-meta {
  grid-area: meta;
  font-size: 0.72rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.admin-user-plan {
  grid-area: plan;
  /* Wide enough for the longest plan name plus the dropdown arrow. Left to
     size itself, the arrow sat on top of the text. */
  width: auto;
  min-width: 96px;
  min-height: 34px;
  margin: 0;
  padding: 4px 26px 4px 10px;
  font-size: 0.8rem;
}
.admin-pill {
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--pill-active-bg);
  color: var(--pill-active-ink);
}

/* ── Suggestions, in the admin card ───────────────────────────────────────*/
.admin-suggestions {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 320px;
  overflow-y: auto;
}
.suggestion {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  background: var(--bg);
}
/* Still readable, visibly done. Dropping it out of the list entirely would
   mean the only way to check what was decided is the database. */
.suggestion--handled { opacity: 0.55; }
.suggestion-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.suggestion-kind {
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--pill-active-bg);
  color: var(--pill-active-ink);
}
/* A problem is the one worth spotting in a list of ideas. */
.suggestion-kind--problem {
  background: rgba(217, 119, 6, 0.15);
  color: var(--amber);
}
.suggestion-who {
  font-size: 0.72rem;
  color: var(--muted);
}
.suggestion-body {
  margin: 0;
  font-size: 0.85rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.suggestion-done {
  width: auto;
  align-self: flex-end;
}

/* ── The ad slot ──────────────────────────────────────────────────────────
   Below everything on Today, labelled, and with the way out attached. An ad
   between someone and their own diary is the kind that makes people leave. */
.ad-slot {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
  padding: 10px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--card);
}
.ad-label {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.ad-unit {
  min-height: 90px;
  overflow: hidden;
}
.ad-remove {
  align-self: flex-end;
  font-size: 0.78rem;
}

/* The line under the log button. Amber rather than red: running low is not
   an error, and colouring it like one would make the app look broken. */
.plan-allowance {
  margin: 8px 0 0;
  font-size: 0.8rem;
  color: var(--amber);
  text-align: center;
}

/* ── Matches as you type ──────────────────────────────────────────────────
   Sits directly under the description box, so the answer arrives where the
   question is being asked rather than at the bottom of the screen. */
.text-suggestions {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: -4px 0 12px;
  animation: fadeInUp 0.16s var(--ease);
}
.text-suggestion {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  text-align: left;
  min-height: 44px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hairline);
  background: var(--card);
  background-image: none;
  box-shadow: none;
  color: var(--ink);
  transition: background 0.12s var(--ease), border-color 0.12s var(--ease);
}
.text-suggestion:hover,
.text-suggestion:focus-visible {
  border-color: var(--pitch);
  background: rgba(31, 157, 82, 0.05);
}
.text-suggestion-name {
  font-size: 0.9rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
.text-suggestion-meta {
  font-size: 0.75rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* ── Food database search ── */
.food-search-card #food-search-query {
  margin-bottom: 10px;
}
.food-search-results {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 320px;
  overflow-y: auto;
}
.food-search-result {
  width: 100%;
  /* Badge on the left, name and figures stacked beside it. */
  display: flex;
  align-items: flex-start;
  gap: 10px;
  text-align: left;
  background: var(--bg);
  background-image: none;
  color: var(--ink);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  box-shadow: none;
  padding: 9px 12px;
  min-height: 44px;
  transition: background 0.12s var(--ease), border-color 0.12s var(--ease);
}
.food-search-result:hover {
  border-color: var(--pitch);
  background: rgba(31, 157, 82, 0.05);
}
.food-search-result-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.food-search-result-name {
  font-size: 0.9rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.food-search-result-meta {
  font-size: 0.75rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Saved meals & recipes ── */
.meal-row {
  background: var(--card);
  border-radius: var(--radius-md);
  padding: 11px 14px;
  margin-bottom: 8px;
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: flex-start;
  gap: 10px;
  animation: fadeInUp 0.3s var(--ease);
}
.meal-info {
  flex: 1;
  min-width: 0;
}
.meal-name {
  font-size: 0.95rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
}
.meal-kind-pill {
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: 999px;
}
.meal-kind-pill--template {
  background: rgba(31, 157, 82, 0.13);
  color: var(--pitch-dark);
}
.meal-kind-pill--recipe {
  background: rgba(217, 119, 6, 0.13);
  color: var(--amber);
}
.meal-meta {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
.meal-items-line {
  font-size: 0.76rem;
  color: var(--muted);
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meal-actions {
  display: flex;
  gap: 5px;
  flex-shrink: 0;
}
/* Four actions beside the name squeezed the meal's own figures into a column
   two words wide on a small phone — "435 kcal per portion" over four lines,
   with the ingredients truncated to nothing. Below the width where they both
   fit, the buttons take a line of their own underneath. */
@media (max-width: 460px) {
  .meal-row {
    flex-wrap: wrap;
  }
  .meal-info {
    flex: 1 0 100%;
  }
  .meal-actions {
    width: 100%;
    justify-content: flex-end;
  }
}
.meal-actions button {
  width: auto;
  min-height: 32px;
  padding: 4px 10px;
  font-size: 0.85rem;
  background: transparent;
  background-image: none;
  color: var(--muted);
  border: 1px solid var(--hairline);
  box-shadow: none;
  transition: background 0.12s var(--ease), transform 0.2s var(--spring);
}
.meal-actions button:active {
  background: var(--bg);
  transform: scale(0.94);
}

/* Was scoped to the meal editor, so everywhere else in settings this was an
   unstyled span: a heading with no weight and no space under it, which ran
   "How much to add" straight into the buttons below it and made a whole
   settings block read as one clump. */
.field-label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  margin: 18px 0 6px;
}
/* Nothing to separate from at the top of a section. */
.settings-section-body > .field-label:first-child,
.meal-editor .field-label:first-child {
  margin-top: 0;
}
/* A caption belongs to the heading above it, not to the gap. */
.field-label + .muted {
  margin-top: -2px;
  margin-bottom: 10px;
}
.meal-editor .field-label {
  margin: 0 0 6px;
}
/* The control under a heading needs air beneath it before the next label. */
.units-toggle {
  margin-bottom: 14px;
}
.meal-kind-btns {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}
.meal-kind-btn {
  flex: 1;
  width: auto;
  min-height: 40px;
  border-radius: 10px;
  border: 1px solid var(--hairline);
  background: var(--card);
  background-image: none;
  color: var(--muted);
  box-shadow: none;
  font-size: 0.88rem;
  font-weight: 600;
  transition: color 0.12s var(--ease), border-color 0.12s var(--ease), background 0.12s var(--ease);
}
.meal-kind-btn--active {
  border-color: var(--pitch);
  background: rgba(31, 157, 82, 0.08);
  color: var(--pitch-dark);
}
.meal-kind-note {
  font-size: 0.78rem;
  margin: 0 0 12px;
}
.meal-servings-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.meal-servings-row label {
  margin: 0;
  font-size: 0.85rem;
  flex: 1;
}
.meal-servings-row input[type="number"] {
  margin: 0;
  width: 90px;
  flex-shrink: 0;
}
.meal-item-row {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
}
.meal-item-row input[type="text"],
.meal-item-row input[type="number"] {
  margin: 0;
}
.meal-item-row input[type="text"].meal-item-label {
  flex: 1;
  width: auto;
  min-width: 0;
}
.meal-item-row input[type="number"].meal-item-kcal {
  width: 78px;
  flex: 0 0 78px;
}
.meal-item-remove {
  width: 34px;
  min-height: 34px;
  flex-shrink: 0;
  padding: 0;
  background: transparent;
  background-image: none;
  border: 1px solid var(--hairline);
  color: var(--muted);
  box-shadow: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.meal-add-item {
  margin-bottom: 14px;
}

@media (max-width: 380px) {
  /* Three capture buttons plus their labels won't fit side by side on the
     narrowest phones, so the labels go and the icons carry the meaning. */
  .capture-btn {
    font-size: 0;
    gap: 0;
    padding: 10px 4px;
  }
  .capture-btn .icon {
    font-size: 1.1rem;
  }
}

/* ── Offline ── */
.offline-banner {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 14px;
  margin-bottom: 12px;
  border-radius: var(--radius-md);
  background: rgba(217, 119, 6, 0.1);
  border: 1px solid rgba(217, 119, 6, 0.3);
  color: var(--amber);
  font-size: 0.83rem;
  line-height: 1.35;
  animation: fadeInUp 0.3s var(--ease);
}
.offline-banner .icon {
  font-size: 1rem;
  flex-shrink: 0;
}
/* Work is safely stored and on its way out — reassurance, not a warning. */
.offline-banner--waiting {
  background: rgba(31, 157, 82, 0.09);
  border-color: rgba(31, 157, 82, 0.28);
  color: var(--pitch-dark);
}

/* ── First-run wizard ── */
.onboarding-screen:not([hidden]) {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background:
    radial-gradient(circle at 15% 10%, rgba(31, 157, 82, 0.1), transparent 45%),
    radial-gradient(circle at 85% 90%, rgba(217, 119, 6, 0.09), transparent 45%),
    var(--bg);
}
.onboarding-card {
  width: 100%;
  max-width: 460px;
}
.onboarding-progress {
  display: flex;
  gap: 6px;
  margin-bottom: 18px;
}
.onboarding-dot {
  flex: 1;
  height: 4px;
  border-radius: 999px;
  background: var(--hairline);
  transition: background 0.25s var(--ease);
}
.onboarding-dot--active {
  background: var(--pitch);
}
.onboarding-step h2 {
  margin-top: 0;
}
.onboarding-step > .muted {
  margin-bottom: 16px;
  font-size: 0.88rem;
  line-height: 1.45;
}
.onboarding-list {
  margin: 0 0 14px;
  padding-left: 18px;
  font-size: 0.88rem;
  line-height: 1.5;
}
.onboarding-list li {
  margin-bottom: 8px;
}
.onboarding-list strong {
  color: var(--pitch-dark);
}

/* ── Weekly review ── */
.summary-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
}
.summary-actions .ghost-sm,
.summary-actions .button-link {
  flex: 1 1 0;
  min-width: 0;
  max-width: 180px;
  margin-top: 8px;
  min-height: 44px;
  padding: 10px 12px;
  font-size: 0.95rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1.5px solid var(--pitch);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.review-range {
  text-align: center;
  margin: 0 0 12px;
  font-size: 0.85rem;
}
.review-comparison {
  margin: 12px 0 0;
  text-align: center;
  font-size: 0.83rem;
  line-height: 1.4;
}
.review-highlight {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hairline);
}
.review-highlight:last-child {
  border-bottom: none;
}
.review-highlight-label {
  font-size: 0.78rem;
  color: var(--muted);
  flex-shrink: 0;
}
.review-highlight-value {
  font-size: 0.88rem;
  font-weight: 600;
  text-align: right;
}
.review-section {
  padding: 0 0 4px 12px;
  margin-bottom: 16px;
  /* A coloured rule rather than a coloured block: four tinted panels in a
     row would fight each other, a rule just labels the section. */
  border-left: 3px solid var(--hairline);
}
.review-section:last-child {
  margin-bottom: 0;
}
.review-section h3 {
  margin: 0 0 6px;
  font-family: var(--font-heading);
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.review-section ul {
  margin: 0;
  padding-left: 18px;
  font-size: 0.9rem;
  line-height: 1.5;
}
.review-section li {
  margin-bottom: 6px;
}
.review-section li:last-child {
  margin-bottom: 0;
}
.review-section--good { border-left-color: var(--pitch); }
.review-section--warn { border-left-color: var(--amber); }
.review-section--note { border-left-color: var(--muted); }
.review-section--win  { border-left-color: var(--pitch-light); }

/* ── Focus ── */
/* Keyboard users need to see where they are; a mouse click shouldn't leave a
   ring behind. :focus-visible draws that line for us. */
:focus-visible {
  outline: 3px solid var(--pitch);
  outline-offset: 2px;
  border-radius: 4px;
}
.banner :focus-visible {
  outline-color: #fff;
}

/* ── Reduced motion ── */
/* Honours the OS-level setting rather than animating regardless. Durations
   go to near-zero instead of zero so transitionend listeners still fire. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Dark ── */
/* Only the tokens are redefined; every rule above is written against them,
   so nothing else needs a dark variant. Two selectors, because the theme has
   three states: "system" sets no attribute and follows the media query,
   while an explicit choice stamps data-theme and must win either way. */
:root[data-theme="dark"] {
  --pitch: #35b96a;
  --pitch-light: #4fd684;
  /* Inverted from light mode: "dark" has to read as *lighter* here, since it
     is used for text and icons sitting on a dark ground. */
  --pitch-dark: #7fe3a8;
  --ink: #e8ecea;
  --muted: #9aa5a0;
  --bg: #121614;
  --card: #1b211e;
  --hairline: #2c342f;
  --danger: #f87171;
  --amber: #fbbf24;
  --amber-light: #fcd34d;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.55);
  --pill-active-bg: linear-gradient(160deg, #2f3a34, #24312a);
  --pill-active-ink: #9fe9bf;
  --pill-active-shadow: 0 2px 6px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --tooltip-bg: #f2f5f3;
  --tooltip-ink: #14181a;
  /* The header ground has to stay clearly above the page in dark mode; the
     light-mode value sits at almost the same luminance as the dark page and
     the scoreboard would dissolve into it. */
  --brand: #124F2B;
  --pitch-deep: var(--brand);
  --floodlight: #e8a91f;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --pitch: #35b96a;
    --pitch-light: #4fd684;
    --pitch-dark: #7fe3a8;
    --ink: #e8ecea;
    --muted: #9aa5a0;
    --bg: #121614;
    --card: #1b211e;
    --hairline: #2c342f;
    --danger: #f87171;
    --amber: #fbbf24;
    --amber-light: #fcd34d;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.04);
    --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.55);

    --pill-active-bg: linear-gradient(160deg, #2f3a34, #24312a);
    --pill-active-ink: #9fe9bf;
    --pill-active-shadow: 0 2px 6px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.06);
    --tooltip-bg: #f2f5f3;
    --tooltip-ink: #14181a;
    /* The header ground has to stay clearly above the page in dark mode; the
       light-mode value sits at almost the same luminance as the dark page and
       the scoreboard would dissolve into it. */
    --brand: #124F2B;
  --pitch-deep: var(--brand);
    --floodlight: #e8a91f;
  }
}

/* Tells the browser to draw its own furniture (scrollbars, form controls,
   the space beyond the page) to match. */
:root[data-theme="dark"] { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
}

/* ── Health export help ── */
.health-help {
  margin-bottom: 12px;
}
.health-help summary {
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--pitch-dark);
  padding: 4px 0;
}
.health-help p {
  margin: 8px 0 0;
  font-size: 0.82rem;
  line-height: 1.45;
}

/* ── Quick add, now behind a button ── */
.entry-form-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}
.entry-form-header label {
  margin: 0;
}
.quick-add {
  /* Inside the form card now rather than a card of its own, so it reads as
     a drawer on the thing it fills in. */
  background: var(--bg);
  border-radius: var(--radius-md);
  padding: 12px;
  margin-bottom: 12px;
  animation: fadeInUp 0.22s var(--ease);
}

/* ── Info buttons ── */
.card-header-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.card-header-row h2 {
  margin: 0;
}
.averages-window {
  margin-left: auto;
  font-size: 0.75rem;
}
.info-btn {
  width: 22px;
  height: 22px;
  min-height: 22px;
  padding: 0;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  background-image: none;
  box-shadow: none;
  color: var(--muted);
  cursor: help;
  transition: color 0.12s var(--ease), transform 0.2s var(--spring);
}
.info-btn .icon {
  font-size: 1rem;
}
.info-btn:hover {
  color: var(--pitch);
}
.info-btn:active {
  transform: scale(0.88);
}
.info-btn.tooltip-active {
  color: var(--pitch);
}

.averages-grid-secondary {
  margin-top: 8px;
}

/* ── Weekly table drill-down ── */
.breakdown-toggle-cell {
  width: 34px;
  padding-right: 0 !important;
}
.breakdown-toggle {
  width: 26px;
  height: 26px;
  min-height: 26px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--hairline);
  border-radius: 7px;
  background: var(--card);
  background-image: none;
  box-shadow: none;
  color: var(--muted);
  transition: color 0.12s var(--ease), border-color 0.12s var(--ease), transform 0.2s var(--spring);
}
.breakdown-toggle .icon {
  font-size: 0.85rem;
}
.breakdown-toggle:hover {
  color: var(--pitch);
  border-color: var(--pitch);
}
.breakdown-toggle:active {
  transform: scale(0.9);
}
.breakdown-detail-row td {
  padding: 0 !important;
  background: var(--bg);
}
.breakdown-detail-loading {
  margin: 0;
  padding: 12px 14px;
  font-size: 0.82rem;
}
.week-days {
  padding: 6px 14px 10px;
  animation: fadeInUp 0.22s var(--ease);
}
.week-day {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--hairline);
}
.week-day:last-child {
  border-bottom: none;
}
.week-day-label {
  font-size: 0.8rem;
  color: var(--muted);
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
}
/* Marks the two days a match week only half-owns. */
.week-day-half {
  font-size: 0.72rem;
  color: var(--pitch);
  cursor: help;
}
.week-day-value {
  font-size: 0.85rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.week-day-value--empty {
  font-weight: 400;
  color: var(--muted);
}

/* ── Settings: the burn-estimate fallback ── */
.body-stats-note {
  font-size: 0.8rem;
  line-height: 1.45;
  margin: 0 0 12px;
}
.estimate-fields {
  margin-bottom: 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
}
.estimate-fields > summary {
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--pitch-dark);
}
.estimate-fields[open] > summary {
  margin-bottom: 8px;
}
.estimate-fields > .muted {
  font-size: 0.78rem;
  line-height: 1.4;
  margin: 0 0 10px;
}

/* ── The form guide ──
   The signature element. Everything else on this screen is deliberately
   quieter so this can be the one thing you remember. */
.form-guide {
  margin: 0 0 18px;
  padding: 0 0 16px;
  border-bottom: 1px solid var(--hairline);
}
.form-guide-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}
.form-guide-title {
  font-family: var(--font-heading);
  font-size: 0.9rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}
.form-guide-record {
  font-family: var(--font-heading);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.form-guide-record--good { color: var(--pitch-dark); }
.form-guide-record--bad { color: var(--amber); }

.form-guide-days {
  display: grid;
  /* Column count is set in JS from the day count — a match week touches 8
     calendar days, opening and closing part-way through a Monday. */
  grid-template-columns: repeat(8, 1fr);
  gap: 5px;
}
.form-day {
  aspect-ratio: 1;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: transform 0.2s var(--spring), box-shadow 0.15s var(--ease);
}
.form-day:active { transform: scale(0.92); }
.form-day.tooltip-active { box-shadow: 0 0 0 2px var(--pitch); }

/* Under your burn: the win. Solid pitch green, the one place the brand
   colour is allowed to be a fill rather than an accent. */
.form-day--under {
  background: var(--pitch);
  color: #fff;
}
/* Over: the floodlight amber. Not red — going over on one day is a result,
   not a failure, and the palette shouldn't shout about it. */
.form-day--over {
  background: var(--floodlight);
  color: #4a3300;
}
/* Logged, but no burn data to judge it against yet. */
.form-day--logged {
  background: rgba(31, 157, 82, 0.14);
  color: var(--pitch-dark);
}
/* Not yet played. */
.form-day--none {
  background: var(--bg);
  color: var(--muted);
  box-shadow: inset 0 0 0 1px var(--hairline);
}
.form-day--today {
  box-shadow: inset 0 0 0 2px var(--ink);
}
.form-day--today.tooltip-active {
  box-shadow: inset 0 0 0 2px var(--ink), 0 0 0 2px var(--pitch);
}
.form-day-letter {
  line-height: 1;
}

/* ── Deficit streak ── */
.streak-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.streak-cell {
  background: var(--bg);
  border-radius: var(--radius-md);
  padding: 14px 12px;
  text-align: center;
}
.streak-number {
  display: block;
  font-family: var(--font-heading);
  font-size: 2.3rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* A run that's still alive is the one thing here worth colouring — green
   keeps meaning "you're under", same as it does on the form guide. */
.streak-number--live {
  color: var(--pitch-dark);
}
.streak-caption {
  display: block;
  margin-top: 6px;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--muted);
}
.streak-note {
  margin: 12px 0 0;
  font-size: 0.78rem;
  line-height: 1.45;
}

/* ── Water ── */
.water-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.water-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.water-title {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.water-total {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--pitch-dark);
}
.water-buttons {
  display: flex;
  gap: 8px;
}
.water-buttons .water-btn {
  flex: 1 1 0;
}

/* ── Entry photos ── */
.entry-thumb {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  min-height: 0;
  box-shadow: none;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: none;
  cursor: pointer;
}
.entry-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.entry-qty {
  margin-left: 6px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* Narrower than the label and kcal fields it sits between — a quantity is
   one or two characters, and giving it an equal share squeezed both. */
.photo-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 24px;
  background: rgba(8, 14, 11, 0.92);
}
.photo-modal img {
  max-width: 100%;
  max-height: 78vh;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}
.photo-modal-caption {
  margin: 0;
  color: #fff;
  font-size: 0.9rem;
  text-align: center;
}
.photo-modal-close {
  position: absolute;
  top: calc(16px + env(safe-area-inset-top));
  right: 16px;
  width: 40px;
  height: 40px;
  min-height: 0;
  box-shadow: none;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  cursor: pointer;
}

/* ── Day notes ── */
.day-note-btn {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  min-height: 0;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  box-shadow: none;
  color: var(--muted);
  cursor: pointer;
}
/* A day with a note reads differently before you tap anything — that's the
   whole point of putting the marker on the heading. */
.day-note-btn--set {
  color: var(--pitch);
  background: rgba(31, 157, 82, 0.12);
}
.day-note {
  margin: 0;
  padding: 8px 12px;
  font-size: 0.85rem;
  font-style: italic;
  color: var(--muted);
  border-left: 3px solid var(--hairline);
}
.day-note-editor {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
}
.day-note-editor textarea {
  width: 100%;
  margin: 0;
}
.day-note-editor button {
  align-self: flex-start;
}

/* ── Copy a day ── */
.copy-day-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.copy-day-fields {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.copy-day-fields .settings-field {
  flex: 1 1 140px;
}

/* ── Measurements ── */
.measurement-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 12px;
}
.measurement-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--hairline);
}
.measurement-row:last-child {
  border-bottom: none;
}
.measurement-date {
  flex: 0 0 auto;
  min-width: 72px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.measurement-values {
  flex: 1 1 auto;
  font-size: 0.85rem;
  color: var(--muted);
}
.measurement-del {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  min-height: 0;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: none;
  box-shadow: none;
  color: var(--muted);
  cursor: pointer;
}

/* ── Progress photos ── */
.progress-photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 10px;
  margin-top: 12px;
}
.progress-photo {
  position: relative;
  margin: 0;
}
.progress-photo-btn {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  min-height: 0;
  box-shadow: none;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: none;
  cursor: pointer;
}
.progress-photo-btn img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.progress-photo figcaption {
  margin-top: 4px;
  font-size: 0.75rem;
  color: var(--muted);
  text-align: center;
}
.progress-photo-del {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 26px;
  height: 26px;
  min-height: 0;
  box-shadow: none;
  display: grid;
  place-items: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(8, 14, 11, 0.6);
  color: #fff;
  cursor: pointer;
}

/* ── Diagnostics ── */
.diagnostics-errors {
  margin-top: 10px;
  /* Capped and scrolled: a run of failures overnight otherwise pushes Save,
     Log out and the delete panel several screens down, which is exactly when
     you most need to reach them. */
  max-height: 320px;
  overflow-y: auto;
}
.diagnostics-row {
  display: grid;
  grid-template-columns: auto auto 1fr;
  gap: 4px 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 0.8rem;
}
.diagnostics-when {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.diagnostics-context {
  font-weight: 600;
  color: var(--amber);
  white-space: nowrap;
}
.diagnostics-message {
  grid-column: 1 / -1;
  color: var(--muted);
  /* A stack trace or a long API error must not push the settings card
     sideways on a phone. */
  overflow-wrap: anywhere;
}

/* ── Danger zone ── */
.settings-fieldset--danger {
  border-color: var(--danger);
}
.settings-fieldset--danger legend {
  color: var(--danger);
}
.danger-btn {
  width: 100%;
  background: var(--danger);
  color: #fff;
  border: none;
  /* The shared button rule glows green; on a delete button that reads as
     reassurance, which is the opposite of what this one is. */
  box-shadow: 0 2px 8px rgba(220, 38, 38, 0.28);
}
.danger-btn:hover {
  filter: brightness(0.94);
}

.settings-legal,
.auth-legal {
  margin-top: 16px;
  text-align: center;
  font-size: 0.82rem;
}

/* The day's total stays hard right whatever sits between it and the label. */
.day-heading-label {
  min-width: 0;
}
.day-heading-kcal {
  margin-left: auto;
  white-space: nowrap;
}

/* ── Macros ────────────────────────────────────────────────────────────────
   The three rows sit directly under the ring, in the same card. They used to
   have a card of their own headed "Against target", which repeated the
   calorie bar the ring already draws — one card, one answer to "where is
   today".

   Each macro gets its own hue so the rows are distinguishable at a glance
   without reading the labels, but all three stay inside the app's existing
   palette rather than introducing a fourth accent — protein takes the pitch
   green, carbs the floodlight amber, fat a muted slate. */
.macro-today {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.macro-row {
  display: grid;
  /* Fixed label and figure columns so the three bars line up with each other
     rather than each starting wherever its own label ends. */
  grid-template-columns: 48px 1fr 88px;
  align-items: center;
  gap: 10px;
}
.macro-name {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
}
.macro-track {
  height: 6px;
  border-radius: 999px;
  background: var(--hairline);
  overflow: hidden;
}
.macro-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  transition: width 400ms var(--ease);
}
.macro-row[data-macro="protein"] .macro-fill { background: var(--pitch); }
.macro-row[data-macro="carbs"] .macro-fill { background: var(--amber); }
.macro-row[data-macro="fat"] .macro-fill { background: var(--muted); }
/* Colour tracks whether the day is on the right side of the target, which is
   not the same as whether the bar is full: clearing a protein floor fills the
   bar and is good, passing a carb ceiling fills it and is not. */
.macro-fill--good { background: var(--pitch) !important; }
.macro-fill--over { background: var(--floodlight) !important; }
/* Two lines: what the day is at, then how far that is from the target. The
   gap used to be all that was shown, which answered "how much more" without
   ever answering "how much so far". */
.macro-figures {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.25;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.macro-total {
  font-size: 0.78rem;
  font-weight: 700;
}
.macro-delta {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--muted);
}
/* The bracket picks up the bar's verdict, so a glance down the column tells
   you which macros are where they should be without reading the words. */
.macro-delta--good { color: var(--pitch-dark); }
.macro-delta--over { color: var(--amber); }
.macro-today-note {
  margin: 4px 0 0;
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.4;
}
@media (prefers-reduced-motion: reduce) {
  .macro-fill { transition: none; }
}

/* Macros on an entry row, under the label. */
.entry-macros {
  margin-top: 2px;
  font-size: 0.72rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  /* Kept on one line: a long food name squeezes this column, and wrapping
     split "F 12g" across two lines rather than shortening the name above. */
  white-space: nowrap;
}

/* The global input[type="number"] rule sets width:100%, which is what's
   wanted in the edit grid — but it also outranks a plain class selector, so
   anything that needs to differ has to be at least as specific. */
.entry-edit-field input[type="number"],
.entry-edit-field input[type="date"],
.entry-edit-field input[type="text"] {
  min-height: 40px;
  padding: 7px 10px;
  font-size: 0.95rem;
}

/* Settings: the two target forms. */
.macro-targets {
  margin-top: 10px;
}

/* One row per macro: name, how to read it, the figure. Three columns rather
   than the old side-by-side fields, because each target now carries an
   operator and three of those across a phone screen doesn't fit. */
.macro-target-row {
  display: grid;
  grid-template-columns: 62px 1fr 92px;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.macro-target-name {
  font-size: 0.85rem;
  font-weight: 600;
}
/* The shared select and input rules set width:100% and their own margins;
   these need to sit flush in a grid cell, and both selectors have to be at
   least as specific as the element-based ones they're overriding. */
.macro-target-row select {
  width: 100%;
  margin: 0;
  min-height: 42px;
}
.macro-target-value {
  display: flex;
  align-items: center;
  gap: 4px;
}
.macro-target-value input[type="number"] {
  width: 100%;
  margin: 0;
  min-height: 42px;
  text-align: right;
}
.macro-target-value span {
  font-size: 0.85rem;
  color: var(--muted);
}
/* ── Bottom tab bar ────────────────────────────────────────────────────────
   Five destinations, always reachable. Replaces the corner icons, which put
   three of the five behind an unlabelled glyph and the other two nowhere at
   all. Fixed to the bottom at every width rather than becoming a header nav
   on desktop: one navigation that behaves the same everywhere beats two that
   have to be kept in step. */
.tab-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
  background: var(--pitch-deep);
  box-shadow: 0 -6px 20px rgba(11, 58, 34, 0.22);
}
/* The shared button rule sets width:100%, a 48px floor, a green gradient and
   a glow — all four wrong here, and all four need overriding explicitly
   rather than hoping a later rule wins. */
.tab-bar .tab-btn {
  width: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 8px 2px 6px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  box-shadow: none;
  color: rgba(255, 255, 255, 0.62);
  font-family: var(--font-heading);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}
.tab-bar .tab-btn .icon {
  width: 21px;
  height: 21px;
  stroke-width: 1.9;
}
.tab-bar .tab-btn span {
  line-height: 1;
  /* "Settings" is wider than a fifth of a 360px screen; letting the label
     shrink keeps all five on one row rather than wrapping the bar to two. */
  font-size: clamp(0.58rem, 2.5vw, 0.72rem);
}
.tab-bar .tab-btn--active {
  color: #fff;
  background: rgba(255, 255, 255, 0.14);
}
.tab-bar .tab-btn:active {
  transform: none;
}
.tab-bar .tab-btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: -2px;
}

/* Every screen has to clear the bar. Applied to the scrolling containers
   rather than to body, so a header still sits flush at the top. */
#today-screen main,
#app-shell main,
#stats-screen main,
#settings-screen main,
#food-library-screen main,
#review-screen main {
  padding-bottom: calc(84px + env(safe-area-inset-bottom));
}

@media (min-width: 720px) {
  /* Centred and detached on wider screens, so it reads as a control rather
     than a strip welded across a very wide window. */
  .tab-bar {
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    bottom: 16px;
    width: min(560px, calc(100% - 32px));
    border-radius: var(--radius-lg);
    padding: 8px;
  }
  /* The selection bar follows it up and in, so the two stay stacked rather
     than the bar stretching the full width above a centred tab bar. */
  .select-bar {
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    width: min(560px, calc(100% - 32px));
  }
}

/* ── Today ─────────────────────────────────────────────────────────────── */
/* Borrows the week nav's arrows so stepping through days feels the same as
   stepping through weeks — same control, same place, one width down. */
.day-nav {
  margin-top: 4px;
}
.today-date {
  margin: 0;
  min-width: 210px;
  font-family: var(--font-heading);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.82);
}

.today-hero-row {
  display: flex;
  align-items: center;
  gap: 18px;
}
.today-ring-wrap {
  position: relative;
  flex: 0 0 auto;
  width: 132px;
  height: 132px;
}
.today-ring {
  width: 100%;
  height: 100%;
  /* Starts the fill at 12 o'clock rather than 3, which is where every ring
     gauge anyone has already used starts. */
  transform: rotate(-90deg);
}
/* fill:none matters — an SVG circle defaults to filled black, which is a
   solid disc rather than a ring. */
.today-ring-track {
  fill: none;
  stroke: var(--hairline);
  stroke-width: 10;
}
.today-ring-fill {
  fill: none;
  stroke: var(--pitch);
  stroke-width: 10;
  stroke-linecap: round;
  /* The sweep is driven frame by frame in JS (see animateRing in app.js) so it
     stays locked to the counting figure — a CSS transition here would chase
     each frame's target and lag behind the number. Only the colour, which JS
     doesn't touch, still transitions. */
  transition: stroke 200ms var(--ease);
}
.today-ring-fill--over {
  stroke: var(--floodlight);
}
.today-ring-centre {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
}
.today-hero-number {
  font-family: var(--font-heading);
  font-size: 2.3rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.today-hero-unit {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.today-hero-side {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Caption left, figure hard right, filling the width beside the ring.
   Stacked value-over-caption left the right-hand third of the card empty and
   the three numbers ragged; as rows they line up in a column you can read
   down, and the space is used. row-reverse rather than reordering the markup,
   so the figure is still announced before its caption. */
.today-stat {
  display: flex;
  flex-direction: row-reverse;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.today-stat-value {
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}
.today-stat-value--under { color: var(--pitch-dark); }
.today-stat-value--over { color: var(--amber); }
.today-stat-caption {
  font-size: 0.8rem;
  color: var(--muted);
  min-width: 0;
}
.today-empty {
  padding: 18px 0;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .today-ring-fill { transition: none; }
}

/* ── Confirm sheet ───────────────────────────────────────────────────────── */
/* Everything the app guessed passes through here before it reaches the diary.
   It sits above the tab bar (z-index 40) so nothing behind it is tappable
   while a decision is pending. */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(8, 14, 11, 0.55);
  backdrop-filter: blur(2px);
}
.sheet-panel {
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px 18px calc(20px + env(safe-area-inset-bottom));
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--card);
  box-shadow: var(--shadow-lg);
  animation: sheet-rise 0.22s var(--ease);
}
@keyframes sheet-rise {
  from { transform: translateY(14px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .sheet-panel { animation: none; }
}
@media (min-width: 720px) {
  .sheet { align-items: center; }
  .sheet-panel { border-radius: var(--radius-lg); }
}
.sheet-head {
  display: flex;
  /* Source order is title-then-badge for screen readers; reversing here puts
     the badge above it visually without a second markup order to keep in step. */
  flex-direction: column-reverse;
  align-items: flex-start;
  gap: 6px;
}
.sheet-title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.4rem;
  letter-spacing: 0.01em;
}
/* Says where the figures came from, because "from the packet" and "AI
   estimate" deserve different amounts of scrutiny. */
.sheet-source {
  flex: none;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--pill-active-bg);
  color: var(--pill-active-ink);
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.sheet-note { margin: 0; font-size: 0.88rem; }
.sheet-actions {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.confirm-items {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.confirm-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--bg);
}
.confirm-item-top {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}
.confirm-label-field {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0;
  font-size: 0.74rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.confirm-item input { margin-bottom: 0; }
.confirm-label { flex: 1 1 auto; min-width: 0; }
/* The calorie box gets the same caption treatment as the macro boxes — a bare
   number next to three labelled ones is the one field you have to guess at. */
.confirm-kcal-field {
  flex: 0 0 84px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0;
  font-size: 0.74rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.confirm-kcal {
  width: 100%;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* The shared button rule would make this full width, 48px tall and green. */
.confirm-remove {
  flex: none;
  width: 34px;
  height: 34px;
  min-height: 0;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: var(--card);
  color: var(--muted);
  box-shadow: none;
  cursor: pointer;
}
.confirm-remove:hover { color: var(--danger); border-color: var(--danger); }
.confirm-remove .icon { width: 16px; height: 16px; }
.confirm-serving {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: 0.85rem;
  color: var(--muted);
}
.confirm-serving input {
  flex: 0 0 96px;
  font-variant-numeric: tabular-nums;
}
.confirm-macros {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.confirm-macro-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0;
  font-size: 0.74rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.confirm-macro-field input {
  font-variant-numeric: tabular-nums;
}
.confirm-total {
  margin: 0;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ── Toast ───────────────────────────────────────────────────────────────── */
/* Confirmation for the one-tap saves, which have no sheet of their own. Sits
   clear of the tab bar so it never covers the thing you just tapped. */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(78px + env(safe-area-inset-bottom));
  z-index: 80;
  transform: translate(-50%, 12px);
  opacity: 0;
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease);
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: min(92vw, 460px);
  padding: 11px 14px;
  border-radius: var(--radius-md);
  /* The pair that inverts against the page in both themes. It used to be
     var(--ink) with white text, which is fine in light mode and white-on-white
     in dark, where --ink IS the near-white text colour — so every "Logged"
     confirmation was invisible. */
  background: var(--tooltip-bg);
  color: var(--tooltip-ink);
  box-shadow: var(--shadow-lg);
  font-size: 0.9rem;
}
.toast--shown {
  opacity: 1;
  transform: translate(-50%, 0);
}
@media (prefers-reduced-motion: reduce) {
  .toast { transition: none; }
}
.toast-text { flex: 1 1 auto; }
/* Another one the shared button rule would turn into a full-width green CTA. */
.toast-action {
  flex: none;
  width: auto;
  min-height: 0;
  margin: 0;
  padding: 5px 10px;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: transparent;
  box-shadow: none;
  color: var(--tooltip-ink);
  opacity: 0.86;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}
.toast-action:hover { opacity: 1; }

/* Stops the page behind the sheet scrolling under it on iOS. */
body.sheet-open { overflow: hidden; }

/* ── Quick add: select, then log ─────────────────────────────────────────── */
.quick-add-hint {
  margin: 8px 0 0;
  font-size: 0.8rem;
}
.quick-add-log {
  margin-top: 10px;
}

/* ── "That saved" flash ──────────────────────────────────────────────────── */
/* A green disc sweeps out from the left of the control and a tick lands in
   the middle of it. Adapted from the interactive-hover-button motion the user
   sent — same idea (a small dot growing to fill the control while an icon
   slides in), rebuilt in plain CSS because this app has no React, Tailwind or
   shadcn to drop that component into. Here it fires on a real save rather
   than on hover, so the animation means something. */
.saved-tick {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: inherit;
  /* Its own solid ground, so the white check is never briefly white-on-white
     over a button that happens to be pale — which is what the sweep growing
     out from a dot used to leave it as, at both ends of the animation.
     A fixed green rather than --pitch, which lightens in dark mode to the
     point where a white tick on it measures 2.5:1. This is 5:1 in both. */
  background: #15803d;
  color: #fff;
  opacity: 0;
  pointer-events: none;
}
.saved-tick .icon {
  width: 22px;
  height: 22px;
  stroke-width: 3;
  transform: scale(0.4);
}
.is-saved {
  position: relative;
  overflow: hidden;
}
.is-saved .saved-tick {
  animation: saved-tick-in 1.1s var(--ease) forwards;
}
.is-saved .saved-tick .icon {
  position: relative;
  animation: saved-check-in 1.1s var(--spring) forwards;
}
@keyframes saved-tick-in {
  0% { opacity: 0; }
  12% { opacity: 1; }
  82% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes saved-check-in {
  0% { transform: scale(0.3); opacity: 0; }
  40% { transform: scale(1); opacity: 1; }
  82% { transform: scale(1); opacity: 1; }
  100% { transform: scale(1); opacity: 0; }
}
/* Reduced motion still gets the confirmation, just without the sweep. */
@media (prefers-reduced-motion: reduce) {
  .is-saved .saved-tick,
  .is-saved .saved-tick .icon {
    animation: none;
  }
  .is-saved .saved-tick {
    opacity: 1;
    background: #15803d;
  }
  .is-saved .saved-tick .icon { transform: scale(1); }
}

/* Shown in place of the log form when looking back at an earlier day. */
.day-past-note {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: flex-start;
}
.day-past-note p {
  margin: 0;
  font-size: 0.9rem;
}

/* ── Weigh-in and measurement forms ──────────────────────────────────────── */
/* The Save button sat flush against the fields above it, and the primary
   button's green glow spilled up over the date input — which reads as the
   boxes overlapping. The date and number inputs also came out 2px different
   in height, because a date input carries its own picker chrome, so the two
   never lined up. */
#weighin-form .settings-row,
#measurement-form .settings-row {
  margin-bottom: 14px;
}
#weighin-form input,
#measurement-form input {
  min-height: 44px;
  margin-bottom: 0;
}

/* ── Editing a food ──────────────────────────────────────────────────────── */
.food-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}
/* The shared button rule would make this full width, 48px tall and green. */
.food-edit-btn {
  width: auto;
  min-height: 0;
  padding: 6px 10px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--card);
  background-image: none;
  box-shadow: none;
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}
.food-edit-btn:hover { color: var(--pitch-dark); border-color: var(--pitch); }
.food-edited {
  align-self: flex-start;
  margin-top: 2px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--pill-active-bg);
  color: var(--pill-active-ink);
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.food-editor {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin: 0 0 10px;
  padding: 14px;
}
.food-editor-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0;
  font-size: 0.72rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.food-editor-field--wide { grid-column: 1 / -1; }
.food-editor-field input {
  margin-bottom: 0;
  min-height: 42px;
  font-variant-numeric: tabular-nums;
}
.food-editor-note,
.food-editor .error {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.78rem;
}
.food-editor-actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.food-editor-actions button { width: auto; flex: 1 1 auto; }

/* Explains why a field is being asked for, under the field itself. */
.settings-hint {
  margin: 4px 0 0;
  font-size: 0.76rem;
  line-height: 1.4;
}

.onboarding-hint {
  margin: 10px 0 0;
  font-size: 0.78rem;
  line-height: 1.45;
}

/* ── What still fits ── */
/* The suggestions card on Today. Rows are read down the left and acted on at
   the right, so the label, figures and reason stack in one column with the
   Log button pinned beside them. */

/* The whole header is the toggle, so the tap target is the width of the card
   rather than a small chevron. Every one of these declarations is undoing the
   global button rule — full width is the one thing it gets right here. */
.what-now-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  padding: 0;
  background: none;
  box-shadow: none;
  color: inherit;
  text-align: left;
  font-weight: inherit;
  font-size: inherit;
}
.what-now-toggle-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.what-now-title {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
}
.what-now-chevron {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  color: var(--pitch);
  transition: transform 0.2s var(--ease);
}
.what-now-chevron .icon {
  width: 20px;
  height: 20px;
}
.what-now-card--open .what-now-chevron {
  transform: rotate(180deg);
}
.what-now-body {
  margin-top: 12px;
}
.what-now-basis {
  margin: 0 0 12px;
  font-size: 0.78rem;
  line-height: 1.45;
}
.what-now-room {
  font-size: 0.85rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.what-now-note {
  margin: 0 0 12px;
  font-size: 0.88rem;
  line-height: 1.45;
}
.what-now-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.what-now-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  background: var(--bg);
  border-radius: var(--radius-sm);
}
.what-now-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}
/* Name and calories share a line, with the figure hard right so a column of
   them reads down the card. */
.what-now-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.what-now-label {
  font-weight: 600;
  font-size: 0.95rem;
  line-height: 1.3;
  min-width: 0;
}
.what-now-kcal {
  flex: 0 0 auto;
  font-size: 0.85rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.what-now-why {
  font-size: 0.8rem;
  color: var(--muted);
  line-height: 1.4;
}
/* Every one of these four fights the global button rule, which would
   otherwise make this a full-width green slab with a glow. */
.what-now-log {
  width: auto;
  /* Comfortably tappable: this is the one control on the row, and the iPhone
     feedback earlier was all about targets being hard to hit. */
  min-height: 40px;
  flex: 0 0 auto;
  padding: 4px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  background: transparent;
  color: var(--pitch);
  border: 1px solid var(--pitch);
  border-radius: 8px;
  box-shadow: none;
  position: relative;
  overflow: hidden;
}
.what-now-skipped {
  margin: 12px 0 0;
  font-size: 0.8rem;
  line-height: 1.45;
}

/* Screen-reader-only label. Kept off-screen rather than display:none so it is
   still announced. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Typing the numbers under the barcode, offered once scanning has clearly not
   worked. Sits inside the dark scanner overlay, so it carries its own light
   colours rather than the page's. */
.scan-manual-row {
  flex-shrink: 0;
  padding: 0 16px 16px;
}
.scan-manual-form {
  display: flex;
  gap: 8px;
  align-items: center;
}
.scan-manual-form input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  padding: 0 12px;
  font-size: 1rem;
  color: #fff;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: var(--radius-sm);
}
.scan-manual-form input::placeholder {
  color: rgba(255, 255, 255, 0.6);
}
/* Undoes the global button rule, which would make this full width and push the
   input onto its own line. */
.scan-manual-submit {
  width: auto;
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 16px;
  font-size: 0.9rem;
  box-shadow: none;
}

/* ── Food search ── */
/* Two labels only, on the rows where provenance is worth knowing: something
   they have eaten before, and something off a menu. A packet needs no label —
   nobody searching for hobnobs wants to be told which database they came from. */
.food-badge {
  flex: 0 0 auto;
  align-self: flex-start;
  margin-top: 2px;
  padding: 2px 7px;
  /* One width for both, so the names line up down the list instead of stepping
     in and out with the length of the label. */
  min-width: 54px;
  text-align: center;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  border-radius: 999px;
  white-space: nowrap;
}
.food-badge--yours {
  background: var(--pitch);
  color: #fff;
}
.food-badge--menu {
  background: var(--amber);
  color: #fff;
}
.food-search-estimate {
  margin-top: 10px;
  width: 100%;
  justify-content: center;
}

/* ── Share card ── */
/* Full-screen so the preview is big enough to judge before it's posted. */
.share-modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(7, 20, 13, 0.72);
  overflow-y: auto;
  /* Without this, scrolling past the end of the panel hands the gesture to the
     page underneath and the background slides around behind the modal. */
  overscroll-behavior: contain;
}
/* Belt and braces with the above: the body itself is frozen for as long as any
   modal is open, so nothing behind it can move even where scroll chaining is
   not what's driving it (iOS Safari being the usual culprit). The scroll
   position is held rather than reset, so closing the modal puts the page back
   exactly where it was. */
body.modal-open {
  position: fixed;
  width: 100%;
  overflow: hidden;
}
.share-panel {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  background: var(--card);
  border-radius: var(--radius-lg);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.32);
}
.share-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: var(--font-heading);
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* Undoes the global button rule for the icon-only close. */
.share-close-btn {
  width: auto;
  min-height: 36px;
  padding: 0 6px;
  background: none;
  box-shadow: none;
  color: var(--muted);
}
.share-preview {
  border-radius: var(--radius-sm);
  overflow: hidden;
  line-height: 0;
}
/* The canvas is drawn at 1080x1350 so the exported file is sharp; on screen it
   scales down to whatever the panel is. */
.share-preview canvas {
  width: 100%;
  height: auto;
}
.share-status {
  margin: 0;
  font-size: 0.85rem;
}
.share-actions {
  display: flex;
  gap: 8px;
  align-items: center;
}
.share-actions button {
  flex: 1;
}
.share-actions .ghost-sm {
  flex: 0 0 auto;
}

/* ── The day's note ── */
.today-note-card textarea {
  width: 100%;
  resize: vertical;
  /* Room for two lines of the placeholder at the size it actually renders,
     so the hint doesn't have to be scrolled to be read. */
  min-height: 76px;
  line-height: 1.45;
}
.today-note-status {
  font-size: 0.78rem;
}
.today-note-save {
  /* Appears only once there is an unsaved change, so it sits under the box
     rather than beside it — no reserved gap when there is nothing to save. */
  margin-top: 10px;
  position: relative;
  overflow: hidden;
}

/* ── Settings sections ── */
/* Five headings over thirteen closed panels, instead of thirteen open ones in
   a single scroll. */
.settings-group {
  margin: 22px 0 10px;
  font-family: var(--font-heading);
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.settings-group:first-of-type {
  margin-top: 14px;
}
.settings-section {
  border: 1px solid var(--hairline);
  border-radius: 10px;
  margin: 0 0 10px;
  overflow: hidden;
}
/* The whole header row is the target, not a chevron the size of a fingernail.
   Every declaration here is undoing the global button rule. */
.settings-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: 48px;
  padding: 10px 14px;
  background: none;
  box-shadow: none;
  color: var(--ink);
  text-align: left;
  font-size: 0.95rem;
  font-weight: 600;
  border-radius: 0;
}
.settings-section-chevron {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  color: var(--muted);
  transition: transform 0.2s var(--ease);
}
.settings-section-chevron .icon {
  width: 18px;
  height: 18px;
}
.settings-section--open .settings-section-chevron {
  transform: rotate(180deg);
  color: var(--pitch);
}
.settings-section-body {
  padding: 0 14px 14px;
  animation: section-open 0.22s var(--ease);
}
@keyframes section-open {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}
/* ── Jump to a date ── */
/* The input sits invisible on top of the icon rather than beside it: tapping
   the label is what opens the OS date picker on a phone, and every attempt to
   drive one from a button ends up fighting a different browser's idea of
   showPicker(). The icon is what you see; the input is what you press. */
.date-jump {
  position: relative;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  margin: 0;
  border-radius: 999px;
  color: inherit;
  cursor: pointer;
}
.date-jump .icon {
  width: 19px;
  height: 19px;
  pointer-events: none;
}
.date-jump input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  opacity: 0;
  cursor: pointer;
}

/* The confirm sheet's meal row wraps the same way the log form's does. */
#confirm-meal-row {
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}
#confirm-meal-row .log-week-btns {
  flex-wrap: wrap;
}

/* A file input dressed as a button — tapping the label opens the camera, the
   same trick the log form's photo field uses. */
.meal-scan-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin: 0;
  cursor: pointer;
  overflow: hidden;
}
.meal-scan-btn input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

/* ── A meal, logged as one thing ── */
.meal-group-row {
  cursor: pointer;
  user-select: none;
}
.meal-group-chevron {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 24px;
  color: var(--muted);
}
.meal-group-chevron .icon {
  width: 18px;
  height: 18px;
}
/* Inset and quieter, so the items read as belonging to the line above rather
   than as four more entries in the day. */
.entry-row--in-group {
  margin-left: 16px;
  background: var(--bg);
  box-shadow: none;
  border: 1px solid var(--hairline);
}

/* ── Picking entries to clump into a meal ── */
.section-header-side {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* While selecting, the whole row is the target — a checkbox beside a row is a
   fiddle to hit on a phone, and the row is already the thing being chosen. */
.entry-row--pick {
  cursor: pointer;
  user-select: none;
}
.entry-pick {
  flex: 0 0 auto;
  order: -1;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid var(--hairline);
  color: transparent;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease);
}
.entry-pick .icon {
  width: 15px;
  height: 15px;
}
.entry-row--picked .entry-pick {
  background: var(--pitch);
  border-color: var(--pitch);
  color: #fff;
}
.entry-row--picked {
  border: 1px solid var(--pitch);
}

/* Sits above the tab bar rather than in the page, so it stays reachable
   however far down the list the last item was picked. */
.select-bar {
  position: fixed;
  left: 12px;
  right: 12px;
  /* 84px is the app's own figure for the tab bar — it is what main already
     pads its bottom by, so the bar clears it by construction rather than by a
     number guessed to look right. */
  bottom: calc(92px + env(safe-area-inset-bottom));
  z-index: 55;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px 10px 16px;
  background: var(--card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  animation: fadeInUp 0.2s var(--ease);
}
.select-count {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.select-bar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}
.select-bar-actions button {
  width: auto;
  min-height: 40px;
  padding: 6px 14px;
}
.select-bar-actions button:disabled {
  opacity: 0.5;
}

/* ── Where the calories go ── */
.meal-breakdown {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 4px 0 10px;
}
.meal-breakdown-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.meal-breakdown-name {
  font-weight: 600;
  font-size: 0.95rem;
}
.meal-breakdown-value {
  font-family: var(--font-heading);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.meal-breakdown-track {
  height: 8px;
  margin: 6px 0 4px;
  border-radius: 999px;
  background: var(--bg);
  overflow: hidden;
}
.meal-breakdown-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--pitch);
  /* The first paint animates from nothing, so the card reads as a comparison
     being drawn rather than four bars that were always there. */
  animation: meal-bar-in 0.5s var(--ease);
}
@keyframes meal-bar-in {
  from { transform: scaleX(0); transform-origin: left; }
  to { transform: none; }
}
.meal-breakdown-sub {
  font-size: 0.78rem;
  color: var(--muted);
}

/* ── Meal tags ── */
/* The tag on an entry row: quiet, because the food and the figure are what
   the row is for — the tag is only there to say which meal it landed in. */
.entry-meal-tag {
  align-self: flex-start;
  margin-top: 2px;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--hairline);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--muted);
}

/* Under a meal heading the chip only repeats it, so it comes off there. */
.meal-grouped .entry-meal-tag {
  display: none;
}

/* The heading above each meal's items, once the day is grouped by meal. */
.meal-group-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 16px 2px 6px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.meal-group-heading:first-child {
  margin-top: 4px;
}
.meal-group-total {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* Entry times are hidden with a body class rather than by not rendering them:
   the preference flips instantly, with no re-fetch and no list rebuild. */
body.hide-entry-times .entry-time {
  display: none;
}

/* Five tags plus the blank one will not sit on one line of a phone, so the row
   wraps and the label sits above rather than beside them. */
#meal-tag-row {
  flex-wrap: wrap;
  gap: 8px;
}
#meal-tag-row .log-week-btns {
  flex-wrap: wrap;
}

/* ── Setting toggles ── */
/* A checkbox drawn as a switch. The whole row is the label, so the text is as
   tappable as the switch itself — on a phone the switch alone is a poor
   target. */
.setting-toggle {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0 0 14px;
  font-weight: 400;
  cursor: pointer;
}
.setting-toggle input {
  appearance: none;
  flex: 0 0 auto;
  width: 44px;
  height: 26px;
  margin: 2px 0 0;
  padding: 0;
  border-radius: 999px;
  background: var(--hairline);
  border: none;
  position: relative;
  transition: background 0.18s var(--ease);
  cursor: pointer;
}
.setting-toggle input::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: transform 0.18s var(--ease);
}
.setting-toggle input:checked {
  background: var(--pitch);
}
.setting-toggle input:checked::after {
  transform: translateX(18px);
}
.setting-toggle input:focus-visible {
  outline: 2px solid var(--pitch);
  outline-offset: 2px;
}
.setting-toggle-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.setting-toggle-label {
  font-weight: 600;
  font-size: 0.95rem;
}
.setting-toggle-note {
  font-size: 0.8rem;
  color: var(--muted);
  line-height: 1.4;
}

/* The same open/close behaviour inside a card, where the card already supplies
   the border and the padding — so the head loses the panel chrome and keeps
   only the hit area, and the title carries a heading's weight rather than a
   settings row's. */
.card-collapse-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.card-collapse-head {
  flex: 1 1 auto;
  justify-content: flex-start;
  gap: 8px;
  min-height: 34px;
  padding: 0;
}
.card-collapse-head .settings-section-title {
  font-family: var(--font-heading);
  font-size: 1.05rem;
  font-weight: 700;
}
.card-collapse-body {
  padding: 12px 0 0;
}

.settings-section--danger {
  border-color: var(--danger);
}
.settings-section--danger .settings-section-head {
  color: var(--danger);
}

/* ── Layout editor ── */
.layout-screen + .layout-screen {
  margin-top: 18px;
}
.layout-screen-title {
  margin: 0 0 8px;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--muted);
}
.layout-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.layout-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 10px;
  background: var(--bg);
  border-radius: var(--radius-sm);
}
.layout-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  font-size: 0.9rem;
  cursor: pointer;
}
.layout-toggle input {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  accent-color: var(--pitch);
}
.layout-moves {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}
.layout-move {
  width: 36px;
  min-height: 36px;
  padding: 0;
  display: grid;
  place-items: center;
  background: var(--card);
  color: var(--pitch);
  border: 1px solid var(--hairline);
  box-shadow: none;
  border-radius: 8px;
}
.layout-move:disabled {
  opacity: 0.35;
}
.layout-move .icon {
  width: 18px;
  height: 18px;
}

/* A card the user turned off. Off the screen, not deleted — what it was
   showing is exactly where it was. */
.module--off {
  display: none !important;
}

/* ── Motion ─────────────────────────────────────────────────────────────────
   Two things, both tied to something actually happening rather than added for
   their own sake: cards arriving on a screen that has just been opened, and
   rows arriving in a list that has just been filled.

   Everything here is a one-shot entrance, never a loop. A card that keeps
   moving is a card you can't read, and this is an app people open twenty times
   a day — an animation that is charming the first time is an obstacle the
   twentieth. */
@keyframes card-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}
@keyframes row-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* Staggered by position, so a screen resolves top-down the way it is read
   rather than every card appearing at once. Capped at six: past that the last
   card would be waiting on half a second of choreography. */
#today-screen main > .card,
#today-screen main > section,
#today-screen main > form,
#stats-screen main > .card {
  animation: card-in 0.32s var(--ease) both;
}
#today-screen main > *:nth-child(1) { animation-delay: 0ms; }
#today-screen main > *:nth-child(2) { animation-delay: 40ms; }
#today-screen main > *:nth-child(3) { animation-delay: 80ms; }
#today-screen main > *:nth-child(4) { animation-delay: 120ms; }
#today-screen main > *:nth-child(5) { animation-delay: 160ms; }
#today-screen main > *:nth-child(n + 6) { animation-delay: 200ms; }

.what-now-row,
.food-search-result,
.insights-list li {
  animation: row-in 0.26s var(--ease) both;
}
.what-now-row:nth-child(2) { animation-delay: 40ms; }
.what-now-row:nth-child(3) { animation-delay: 80ms; }
.what-now-row:nth-child(4) { animation-delay: 120ms; }
.what-now-row:nth-child(n + 5) { animation-delay: 160ms; }

/* The macro bars fill from empty rather than snapping to their width, so the
   day's progress is something you watch arrive. */
.macro-fill {
  transition: width 520ms var(--ease), background 200ms var(--ease);
}

/* Pressing something should feel like pressing something. */
.what-now-log:active,
.layout-move:active,
.settings-section-head:active {
  transform: scale(0.96);
}

/* Anyone who has asked their device to stop moving things gets a still app —
   the entrances become instant rather than merely faster, because "less
   motion" is not what that setting asks for. */
@media (prefers-reduced-motion: reduce) {
  #today-screen main > .card,
  #today-screen main > section,
  #today-screen main > form,
  #stats-screen main > .card,
  .what-now-row,
  .food-search-result,
  .insights-list li,
  .settings-section-body {
    animation: none;
  }
  .macro-fill {
    transition: none;
  }
  .what-now-log:active,
  .layout-move:active,
  .settings-section-head:active {
    transform: none;
  }
}

/* ── Spacing ────────────────────────────────────────────────────────────────
   One scale, applied consistently: 16px between cards, 18px inside them, and
   16px down the sides. The gaps that had drifted were mostly inside cards
   whose contents were added at different times. */
.card > .section-header:first-child {
  margin-top: 0;
}
.card > *:last-child {
  margin-bottom: 0;
}

/* ── Settings footer ── */
/* Save, Log out and Delete account were three siblings with no margins, so
   the delete panel sat flush against the log-out button — two very different
   consequences a fingernail apart. */
#settings-save {
  margin-top: 20px;
}
#logout-btn {
  margin-top: 10px;
}
/* Set apart from everything above it, because it is not like anything above
   it. */
.settings-section--danger {
  margin-top: 28px;
}
#settings-error:not([hidden]) {
  margin: 10px 0 0;
}

/* ── What the day is measured against ── */
/* The cog sits in the hero card's corner rather than in a header row: the card
   has no heading to hang it off, and the corner is out of the way of the ring
   and the figures beside it. A cog rather than an (i) because the panel behind
   it is a setting to change, not a paragraph to read. */
.today-hero--anchored {
  position: relative;
}
.burn-settings-btn {
  position: absolute;
  top: 6px;
  right: 6px;
  /* 40px, not 34: the cog is the only control on this card and it was landing
     a thumb's width from the figures. */
  width: 40px;
  min-height: 40px;
  padding: 0;
  display: grid;
  place-items: center;
  background: none;
  box-shadow: none;
  color: var(--muted);
  border-radius: 999px;
}
.burn-settings-btn .icon {
  width: 19px;
  height: 19px;
}
.burn-settings-btn:hover,
.burn-settings-btn:focus-visible {
  color: var(--pitch);
}
/* The cog is absolutely positioned, so without a band reserved for it the
   first stat's figure ran straight underneath — the top-right of the card had
   a number and an icon fighting for the same corner. The row starts below it
   instead. */
.today-hero--anchored .today-hero-row {
  margin-top: 22px;
}
.burn-choices {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 10px 0 4px;
}
/* Each option carries its own one-line explanation, because "measured" and
   "estimate" mean nothing on their own to someone deciding between them, and
   a tick on the right so the chosen one reads at a glance. */
.burn-choice {
  width: 100%;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  min-height: 0;
  padding: 11px 13px;
  text-align: left;
  background: var(--bg);
  background-image: none;
  color: var(--ink);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  box-shadow: none;
  font-weight: 500;
}
.burn-choice-text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.burn-choice-mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  color: var(--pitch);
}
.burn-choice-mark .icon {
  width: 18px;
  height: 18px;
}
.burn-choice--on {
  border-color: var(--pitch);
  background: rgba(31, 157, 82, 0.08);
}
.burn-choice-label {
  font-size: 0.92rem;
  font-weight: 600;
}
.burn-choice--on .burn-choice-label {
  color: var(--pitch-dark);
}
.burn-choice-note {
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.4;
  font-weight: 400;
}
.burn-modal-note {
  margin: 4px 0 0;
  font-size: 0.8rem;
}

/* ── Choosing what a row says ──────────────────────────────────────────────
   A wrapping row of toggles rather than a list of checkboxes: there are eight
   of them, they are all one or two words, and a column of eight would push
   everything below it off the screen. */
.nutrient-show-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}
/* Same on/off language as the meal-kind buttons, so a toggle looks like a
   toggle wherever it appears in settings. */
.nutrient-show-btn {
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 7px 13px;
  min-height: 0;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--card);
  background-image: none;
  box-shadow: none;
  color: var(--muted);
  cursor: pointer;
  width: auto;
  margin: 0;
  transition: color 0.12s var(--ease), border-color 0.12s var(--ease), background 0.12s var(--ease);
}
.nutrient-show-btn--on {
  border-color: var(--pitch);
  background: rgba(31, 157, 82, 0.08);
  color: var(--pitch-dark);
}
/* Separates the macro targets above from the rest of the label below — they
   are two different questions inside one settings section. */
.settings-rule {
  border: none;
  border-top: 1px solid var(--hairline);
  margin: 20px 0 16px;
}

/* The four label nutrients have longer names than "Protein" — "Saturated fat"
   wrapped to two lines in the macro rows' 62px name column. */
.macro-targets--nutrients .macro-target-row {
  grid-template-columns: 88px 1fr 88px;
}

/* ── The target proposal ───────────────────────────────────────────────────
   Present only when there is something to answer, so it can afford to look
   like an interruption rather than another permanent card. */
.target-review-card {
  border: 1px solid var(--pitch);
  background: rgba(31, 157, 82, 0.05);
}
.target-review-title {
  font-size: 1rem;
  margin: 0 0 6px;
}
.target-review-body {
  margin: 0 0 14px;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--muted);
}
.target-review-body strong {
  color: var(--ink);
  font-weight: 600;
}
.target-review-actions {
  display: flex;
  gap: 8px;
}
.target-review-actions button {
  flex: 1;
  width: auto;
  margin: 0;
  min-height: 42px;
  font-size: 0.9rem;
}

/* ── The eating window ─────────────────────────────────────────────────────
   A single line and a bar. The retrospective version of this figure already
   has a card on Stats; this is the live one, and it only needs to answer
   "how long have I got" at a glance. */
/* ── Net carbs, under keto ────────────────────────────────────────────────
   The one number keto is about, given the weight the macro row can't: a hero
   figure against its ceiling rather than a third bar of three. */
.keto-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.keto-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.keto-title {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--muted);
}
.keto-note {
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.keto-figures {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.keto-eaten {
  font-family: var(--font-heading);
  font-size: 2.1rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.keto-limit {
  font-size: 0.9rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.keto-track {
  height: 6px;
  border-radius: 999px;
  background: var(--hairline);
  overflow: hidden;
}
.keto-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--pitch);
  transition: width 0.3s var(--ease);
}
/* Past the ceiling, in the same colour every other ceiling on the screen
   turns — reported, not scolded. */
.keto-fill--over { background: var(--floodlight); }

.fasting-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.fasting-main {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.fasting-state {
  font-size: 0.95rem;
  font-weight: 600;
}
.fasting-detail {
  font-size: 0.82rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.fasting-track {
  height: 6px;
  border-radius: 999px;
  background: var(--hairline);
  overflow: hidden;
}
.fasting-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--pitch);
  transition: width 0.3s var(--ease);
}
/* The fast itself fills the other way — toward a target rather than up to a
   ceiling — so it gets its own colour rather than borrowing the warning one. */
.fasting-fill--fasting { background: var(--amber); }
/* Target reached. The green the rest of the screen uses for a figure that has
   landed where it was meant to. */
.fasting-fill--done { background: var(--pitch); }
/* Before the first thing is eaten there is nothing to fill, and an empty
   track reads better than a bar claiming zero progress. */
.fasting-fill--waiting { background: var(--hairline); }

/* ── The team table ────────────────────────────────────────────────────────
   A league table, so it reads as rows of a table: a position, a name, and the
   two figures that decide the order. */
.team-setup-row {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}
.team-setup-row input {
  flex: 1;
  margin: 0;
}
.team-setup-row button {
  flex: 0 0 auto;
  align-self: stretch;
}
.team-rows {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 12px;
}
.team-row {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 10px;
  padding: 9px 4px;
  border-bottom: 1px solid var(--hairline);
}
.team-row:last-child { border-bottom: none; }
/* The viewer's own row, so a table of twelve names can be read at a glance. */
.team-row--you {
  background: rgba(31, 157, 82, 0.06);
  border-radius: var(--radius-sm);
  border-bottom-color: transparent;
}
.team-pos {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.team-name {
  font-size: 0.92rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.team-days {
  display: block;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--muted);
}
.team-change {
  font-size: 0.92rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.team-change--down { color: var(--pitch-dark); }
.team-change--up { color: var(--amber); }
.team-change--none { color: var(--muted); font-weight: 400; }
.team-change-kg {
  display: block;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--muted);
}

/* ── Eating out ────────────────────────────────────────────────────────────
   A chain is picked before its menu is read, so the brand row is a heading
   you tap rather than another result competing with the food. */
.food-menu-back {
  align-self: flex-start;
  margin-bottom: 8px;
}
.food-menu-chains {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 4px 0 10px;
}
.food-menu-chain {
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 7px 13px;
  min-height: 0;
  width: auto;
  margin: 0;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--card);
  background-image: none;
  box-shadow: none;
  color: var(--muted);
  cursor: pointer;
}
.food-brand-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  text-align: left;
  font: inherit;
  padding: 12px 14px;
  margin: 0 0 6px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--card);
  background-image: none;
  box-shadow: none;
  color: var(--ink);
  cursor: pointer;
}
.food-brand-name {
  font-size: 0.95rem;
  font-weight: 600;
}
.food-brand-meta {
  display: block;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--muted);
}
.food-brand-chevron {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
}

/* A list that runs on past the fold gets a page at a time rather than an
   endless scroll: the Food screen carries three lists stacked, and one of them
   running to two hundred rows buries the others. */
.list-more {
  width: 100%;
  margin-top: 8px;
}

/* The search card sits last on the Today screen, and opening it used to ask
   the page to scroll it up into view. That only ever worked if there was
   enough room below it to scroll into, and with the keyboard up on a phone
   there isn't — so the box stayed down at the bottom, under the keyboard.
   It is now lifted out of the page: while it is open it is a fixed panel over
   the screen, so where it lands owes nothing to how far the page can scroll or
   to what the keyboard has done to the viewport. Searching becomes its own
   view, which is what it already was in everything but layout. */
/* .card is named in the selector on purpose: the Today screen's entrance rule
   (#today-screen main > .card) would otherwise out-specify a plain id here and
   hand the card back its in-flow animation and transform. */
#food-search-card.card:not([hidden]) {
  position: fixed;
  inset: 0;
  z-index: 70;
  margin: 0;
  border-radius: 0;
  box-shadow: none;
  display: flex;
  flex-direction: column;
  /* The panel itself doesn't scroll: the header and the query box stay put and
     only the results move, so the box you are typing in can't drift away. */
  overflow: hidden;
  padding: calc(14px + env(safe-area-inset-top)) 16px calc(14px + env(safe-area-inset-bottom));
  animation: search-rise 0.18s var(--ease);
}
#food-search-card.card:not([hidden]) .food-search-results {
  flex: 1;
  min-height: 0;
  max-height: none;
  overscroll-behavior: contain;
}
/* The estimate fallback is the last resort under the results, not something to
   push off the bottom of a full-height panel. */
#food-search-card.card:not([hidden]) .food-search-estimate {
  flex: none;
}
@keyframes search-rise {
  from { transform: translateY(10px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  #food-search-card.card:not([hidden]) { animation: none; }
}
/* With room to spare it goes back to being a dialog over the page rather than
   taking the whole window, so the diary stays visible behind it. */
@media (min-width: 720px) {
  #food-search-card.card:not([hidden]) {
    inset: 5vh auto auto 50%;
    transform: translateX(-50%);
    width: min(560px, calc(100vw - 32px));
    height: min(680px, 84vh);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: 18px;
    animation: search-fade 0.18s var(--ease);
  }
  @keyframes search-fade {
    from { opacity: 0; }
    to { opacity: 1; }
  }
  body.search-open::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 69;
    background: rgba(8, 14, 11, 0.45);
  }
}
