/* ============================================================================
   The PLANNER's design layer. Loaded after the app's own stylesheet so it can
   restate the material of surfaces the app already lays out correctly.

   THE RULE THIS FILE OBEYS: the planner is the 65% side. It gets the same
   materials as the homepage - the same glass, the same light, the same press,
   the same type ramp - and almost none of its theatre. Nothing here has a
   cinematic duration. Nothing here animates on scroll. A person opens this
   screen several times a day, and the measure of success is that it feels
   fast and obvious, with the craft showing in how things RESPOND rather than
   in how much they move.

   The three places the Lusion half is allowed in, and only these three:
     1. the intelligence light, when the product is actually working (§26)
     2. one spatial lift on the big theme cards, which are chosen once (§29)
     3. the recommendation reveal, which is a moment worth marking (§40)
   ========================================================================= */

/* ---- 1. THE PAGE ---------------------------------------------------------
   The same two pools of light as the hero, at roughly a third of the
   strength. This is what makes the app feel like the same product as the
   homepage rather than a different site behind a login: continuity of LIGHT,
   not a repeated logo. */
/* E5: THE FIELD, and the two pools of light survive ON it rather than being
   replaced by it. The pools were the continuity argument above and they still
   are, but at 5% over a white ground they were invisible tints and over the
   peacock they are what stops a flat dark rectangle reading as dead. Raised to
   10% and switched to the family's own lights, Aqua at the foot and Lagoon at
   the shoulder, so the field is lit rather than painted. */
body {
  background: var(--field-lit);
  background-attachment: fixed;
}

/* THE BAR IS SOLID.
   This rule used to say "matched to the homepage's so crossing between them is
   seamless" and set a 72% wash of the ink. The homepage's bar had since become SOLID
   (home.css section 12, under the owner's own review of the same defect: "any
   transparency lets white bleed through"), so the match this comment claimed
   had been false for months, and the planner shipped a bar you could read the
   page through: scrolled, the meal chips and the current-meal line sat legibly
   behind the wordmark. Found in a screenshot; invisible to every check, because
   overflow measures geometry and truncation measures clipped text, and both are
   satisfied by text laid out perfectly under a pane on top of it.
   Solid rather than frosted, because a backdrop blur would also have hidden the
   words but the measured-paper pass deleted every blur from the chrome, and
   tests/s08 pins that ("the nav is PAPER now, not glass"). Opaque is the fix
   that agrees with the rest of the system instead of reopening it.
   scripts/topbar-glass.test.mjs measures what is readable through the bar:
   .72 scored 65, this scores 4.

   E5 TURNS THE BAR INTO SECTION GLASS, and the paragraph above is why that is
   safe now when it was not then. The defect was never translucency by itself: it
   was translucency with NO BLUR, because the measured-paper pass had deleted
   every blur from the chrome, so .72 white was a clear pane and you could read
   the meal chips through the wordmark. Section glass restores the blur the
   original fix could not use, and a 16px blur destroys legibility in a way
   opacity alone never did. The check still governs the outcome rather than the
   mechanism, so it is re-run rather than reasoned about. */
.topbar {
  /* D1: the bar wears the SOLID frost pair, not the panel frost. Panel frost
     is .66/.52 white and measured structure 31-39 through the bar even
     blurred; the dialog-grade pair at .92/.94 plus the section blur puts the
     planner back under the check's budget of 20. */
  background: var(--glass-dialog);
  backdrop-filter: blur(var(--glass-blur-section));
  -webkit-backdrop-filter: blur(var(--glass-blur-section));
  border-bottom: 1px solid var(--glass-edge);
}
/* NEVER FALL BACK TO WHITE (E5 §1). Without backdrop-filter the same gradient
   goes fully opaque, which is the one fallback that keeps the bar reading as the
   same object instead of reopening the leak the comment above describes. */
@supports not (backdrop-filter: blur(2px)) {
  .topbar {
  }
}

/* ---- 2. SURFACES ---------------------------------------------------------
   Every card in the app was a 1px border and a single soft shadow, which is
   the flat-card look the brief calls generic SaaS. The elevation tokens give
   each one a contact shadow AND an air shadow, and a lit top edge. Same
   layout, different material. */
.card,
.dash-card,
.na-shell .na-card {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  border-color: var(--border);
}
/* E6 §1 + §3: WHICH SELECTOR IS WHICH TIER, and the first attempt had it
   backwards. `.card` is this app's OUTER container and `[data-surface]` marks
   the panels nested inside it - the Today's Meals strip, the add-meal row - so
   assigning section to [data-surface] put the dimmer tier INSIDE the brighter
   one and inverted the ladder. Measured at the time: a nested panel came back
   the same grey as its parent, delta 1.000:1.
   Outer container = section, nested panel = card, doors = primary. */
.card {
  border: 1px solid var(--glass-edge);
  border-top-color: var(--glass-rim);
}
[data-surface],
.dash-card,
.dtoday {
}
/* A lit top edge, as a 1px inset highlight rather than a white wash over the
   whole card: the first version used a mix-blend-mode overlay across the full
   height, which bleached the illustrations inside the theme cards instead of
   lighting their edge. */
.card,
.dash-card,
.na-shell .na-card {
  background-clip: padding-box;
}
.na-shell .na-card,
.dash-card {
  position: relative;
  box-shadow:
    var(--shadow-sm),
    inset 0 1px 0 color-mix(in srgb, var(--text) 90%, transparent);
}

/* ---- 3. THE THEME CARDS (§29) -------------------------------------------
   The one spatial moment in the product. These are the three big choices on
   the dashboard, pressed once per session at most, so they can afford a real
   lift; a food row pressed forty times cannot, and does not get one. */
.na-shell .na-card {
  transition:
    transform var(--motion-medium) var(--ease-out),
    box-shadow var(--motion-medium) var(--ease-out),
    border-color var(--motion-fast) var(--ease-standard);
}
.na-shell .na-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-sm);
  border-color: var(--accent-soft);
}
.na-shell .na-card:active {
  transform: translateY(-1px) scale(0.994);
  box-shadow: var(--shadow-sm);
  transition-duration: var(--motion-press);
  transition-timing-function: var(--ease-in);
}
/* the art field carries the luminous wash rather than a flat tint, so the
   illustration sits in light instead of on a coloured rectangle */
.na-shell .na-art {
  position: relative;
  overflow: hidden;
}
.na-shell .na-art::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      80% 70% at 30% 18%,
      color-mix(in srgb, var(--text) 10%, transparent) 0%,
      transparent 62%
    ),
    var(--glass-card);
  pointer-events: none;
}

/* ---- 4. QUANTITY CONTROLS (§33) -----------------------------------------
   These are the most-pressed objects in the product, so they get the press
   physics and nothing else. tabular-nums is not decoration: without it the
   value jumps sideways as digits change width, which reads as the layout
   flinching every time somebody adds a katori. */
.stepper {
  border-radius: var(--radius-sm);
  border-color: var(--border-strong);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.stepper .step-input {
  font-variant-numeric: tabular-nums;
  color: var(--foreground);
}
.stepper button {
  background: var(--background-secondary);
  color: var(--foreground);
  border: none;
  min-width: 44px; /* the 44px touch target, which several of these were under */
  font-size: 18px;
  font-weight: 700;
  cursor: pointer;
  transition:
    background-color var(--motion-fast) var(--ease-standard),
    transform var(--motion-release) var(--ease-spring);
}
.stepper button:hover {
  background: var(--blue-300);
}
.stepper button:active {
  transform: scale(0.9);
  background: var(--blue-400);
  transition-duration: var(--motion-press);
  transition-timing-function: var(--ease-in);
}

/* ---- 5. CHIPS ------------------------------------------------------------
   The old chip was a tinted gradient with three inset shadows and a blur: a
   lot of material for a word. A chip is a word you can press. */
.chip {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--foreground);
  border-radius: var(--radius-pill);
  padding: 8px 14px;
  font-weight: 600;
  font-size: var(--text-caption);
  /* no shadow: a chip with a hairline does not also need to float. The Build
     screen renders 110 of these at once, and this one declaration was most of
     that screen's "141 shadows" in the step-1 audit. The backdrop-filter
     override is gone too; the glass rule it overrode no longer exists. */
  transition:
    transform var(--motion-release) var(--ease-spring),
    background-color var(--motion-fast) var(--ease-standard),
    border-color var(--motion-fast) var(--ease-standard);
}
.chip:hover {
  border-color: var(--accent-soft);
}
.chip:active {
  transform: scale(0.96);
  transition-duration: var(--motion-press);
  transition-timing-function: var(--ease-in);
}

/* ---- 6. THE INTELLIGENCE LIGHT (§26, §27) -------------------------------
   The product's signature "I am working this out" state, and the one place
   the full cyan-blue-violet-pink ramp appears inside the planner.

   It replaces the boot spinner, which was a rotating border: a spinner says
   "wait", and this says "the plate is being worked out", which is both truer
   and calmer. The light travels UNDER the plate outline rather than on top of
   it, because the thing being thought about is the meal. */
.boot-spin {
  width: 84px;
  height: 84px;
  border: none;
  border-radius: 50%;
  animation: none;
  position: relative;
  background:
    radial-gradient(circle at 50% 50%, var(--surface) 54%, transparent 55%), var(--accent-cyan);
  background-size:
    100% 100%,
    260% 100%;
  animation: thinkSweep 2.4s linear infinite;
  box-shadow: var(--shadow-sm);
}
@keyframes thinkSweep {
  from {
    background-position:
      0 0,
      0% 50%;
  }
  to {
    background-position:
      0 0,
      260% 50%;
  }
}
body.booting .boot-loader {
  color: var(--foreground-muted);
  font-weight: 600;
  font-size: var(--text-ui);
}

/* any surface can declare itself busy and get the same light beneath it */
.is-thinking {
  position: relative;
  overflow: hidden;
}
.is-thinking::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--accent-cyan);
  background-size: 220% 100%;
  opacity: 0.14;
  animation: thinkBand 2.2s linear infinite;
  pointer-events: none;
}
@keyframes thinkBand {
  from {
    background-position: 0% 50%;
  }
  to {
    background-position: 220% 50%;
  }
}

/* ---- 7. THE RECOMMENDATION REVEAL (§40) ---------------------------------
   A recommendation is the product being useful, so it is allowed one moment.
   The light traces the edge of the card once as it arrives and then stops:
   a permanent glowing border is a notification that never goes away. */
.reco {
  position: relative;
}
.reco::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background: var(--accent-cyan);
  -webkit-mask:
    linear-gradient(var(--shade) 0 0) content-box,
    linear-gradient(var(--shade) 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  animation: recoTrace var(--motion-cinematic) var(--ease-out) 1 both;
  pointer-events: none;
}
@keyframes recoTrace {
  0% {
    opacity: 0;
  }
  40% {
    opacity: 1;
  }
  100% {
    opacity: 0.28;
  }
}

/* ---- 8. TYPE -------------------------------------------------------------
   The planner keeps the calm register. The ONE change is that screen titles
   get real display weight and tracking, because "What sounds good today?" was
   set at the same size as the paragraph under it and read as a label. */
.na-title,
.card .title {
  font-size: clamp(1.45rem, 2.1vw, 2rem);
  letter-spacing: var(--track-tight);
  line-height: 1.12;
  font-weight: 750;
}
.eyebrow,
.na-shell .na-eyebrow {
  font-size: var(--text-caption);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--foreground-muted);
}

/* ---- 9. REDUCED MOTION --------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .na-shell .na-card:hover,
  .na-shell .na-card:active,
  .stepper button:active,
  .chip:active {
    transform: none;
  }
  .boot-spin,
  .is-thinking::after,
  .reco::before {
    animation: none;
  }
  /* still legible as "working": a still band of the same light */
  .is-thinking::after {
    opacity: 0.12;
  }
  .reco::before {
    opacity: 0.28;
  }
}

/* ---- 10. GUIDANCE IS NOT A WARNING --------------------------------------
   .lock is the panel that says "pick a cuisine above to open the categories".
   It was painted in the WARN family: an amber wash, an amber border and amber
   text, which is the same treatment the product uses for an allergen conflict.
   Two problems, one visual and one honest. Visually it was the only warm
   object on a cool screen and it read as an error. More seriously, spending
   the warning colour on ordinary guidance is how a real warning stops being
   noticed. Guidance gets the accent's quiet end; amber stays reserved for
   things that can hurt somebody. */
.lock {
  background: var(--sky-100);
  border: 1px solid var(--sky-300);
  color: var(--accent-text);
  border-radius: var(--radius-md);
  font-weight: 650;
}

/* Chips inside a TINTED panel cannot use the white fill above: a white chip on
   a tinted section is paler than the thing holding it, which is the inversion
   scripts/no-inverted-tiles exists to stop (it caught exactly this on five
   surfaces). On those panels the chip goes a step DEEPER than its holder
   instead, which is the same rule the rest of the product follows. */
.pf-sec .chip,
.grad-sec .chip {
  background: var(--sky-300);
  border-color: var(--sky-400);
  color: var(--sec-ink);
}
.pf-sec .chip:hover,
.grad-sec .chip:hover {
  border-color: var(--accent);
}

/* ---- 11. THE SUGGESTIONS ROW --------------------------------------------
   The dashboard's "for you" strip. Three cards, generous, pressable, and
   quiet: this is the first thing a returning person sees, so it earns its
   place by being useful rather than by being animated. The only luminous
   element is the badge, and it only appears when the strategy is genuinely
   personalised, so the light itself carries the claim. */
.dash-sugg {
  margin: 0 0 18px;
  padding: 18px 20px 20px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
}
.dash-sugg-hd {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  /* same reason as .lrn-scale: the badge beside the title is a fixed-width pill
     and the pair measured 226px in a 220px box at 360px, clipping the badge */
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 14px;
}
.dash-sugg-ttl {
  font-size: 1.05rem;
  font-weight: 750;
  letter-spacing: var(--track-tight);
  color: var(--foreground);
}
.dash-sugg-note {
  margin: 3px 0 0;
  font-size: var(--text-caption);
  color: var(--foreground-muted);
  max-width: 52ch;
}
.dash-sugg-badge {
  flex: none;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--on-accent);
  background: var(--accent);
  border-radius: var(--radius-pill);
  padding: 4px 10px;
  box-shadow: var(--shadow-sm);
}
.sugg-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.sugg-card {
  display: grid;
  gap: 4px;
  text-align: left;
  font: inherit;
  cursor: pointer;
  padding: 14px 15px 15px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--background-secondary);
  color: var(--foreground);
  transition:
    transform var(--motion-release) var(--ease-spring),
    border-color var(--motion-fast) var(--ease-standard),
    box-shadow var(--motion-fast) var(--ease-standard);
}
.sugg-card:hover {
  border-color: var(--accent-soft);
  box-shadow: var(--shadow-sm);
}
.sugg-card:active {
  transform: scale(0.978);
  transition-duration: var(--motion-press);
  transition-timing-function: var(--ease-in);
}
.sugg-name {
  font-weight: 700;
  font-size: var(--text-ui);
  line-height: 1.25;
}
.sugg-why {
  font-size: var(--text-caption);
  color: var(--accent-text);
  font-weight: 600;
}
.sugg-meta {
  font-size: 11.5px;
  color: var(--foreground-muted);
}
@media (max-width: 760px) {
  .sugg-row {
    grid-template-columns: 1fr;
  }
}

/* ---- 12. THE APPLICATION SHELL'S NAVIGATION -----------------------------
   These read as app destinations, not as website links: sentence case rather
   than the marketing header's uppercase tracking, and an active state, because
   inside the product "where am I" is a question the nav has to answer. */
.app-nav-item {
  font: inherit;
  font-size: 14.5px;
  font-weight: 650;
  letter-spacing: var(--track-tight);
  color: var(--foreground-muted);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  transition:
    color var(--motion-fast) var(--ease-standard),
    background-color var(--motion-fast) var(--ease-standard),
    transform var(--motion-release) var(--ease-spring);
}
.app-nav-item:hover {
  color: var(--foreground);
  background: var(--background-secondary);
}
.app-nav-item:active {
  transform: scale(0.96);
  transition-duration: var(--motion-press);
  transition-timing-function: var(--ease-in);
}
.app-nav-item[aria-current='page'] {
  color: var(--accent-text);
  background: var(--sky-100);
}
.nav-drawer .app-nav-item {
  text-align: left;
  font-size: 16px;
  padding: 11px 2px;
}
@media (prefers-reduced-motion: reduce) {
  .app-nav-item:active {
    transform: none;
  }
}

/* the undo offer under Fix. Quiet: it is a safety net, not a call to action */
.fix-undo {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.fix-undo-tx {
  font-size: var(--text-caption);
  color: var(--foreground-muted);
}

/* ---- THE BEFORE/AFTER ROWS ----------------------------------------------
   What the optimiser did, one line per portion it moved. It takes the whole
   width under the summary and the Undo button, so the flex row above stays a
   single line and this reads as its detail rather than as more furniture
   competing with it.

   A GRID, NOT A FLEX ROW, and that is what stops it truncating. The dish name
   is the only elastic column; the two amounts and the arrow are sized to their
   own content, so "1½ cups" can never be squeezed to "1½ c…" no matter how
   long the dish is. The name gets the ellipsis instead, which is the one place
   a cut is recoverable because the dish is on the plate above.
   Both amounts are TABULAR: a column of numbers that jitters as the digits
   change reads as noise, and this list exists to be compared down its columns. */
.fix-diff {
  flex: 1 0 100%;
  list-style: none;
  margin: 2px 0 0;
  padding: 0;
  display: grid;
  gap: 3px;
}
.fd {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: baseline;
  gap: 8px;
  font-size: var(--text-caption);
  color: var(--foreground-muted);
}
.fd-name {
  color: var(--foreground);
  font-weight: 650;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* the meal number, only present when the day has more than one meal, so the
   same dish in two meals is two readable rows rather than one ambiguous pair */
.fd-name small {
  font-weight: 550;
  color: var(--foreground-muted);
  margin-left: 6px;
}
.fd-was,
.fd-now {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* the OLD amount is struck through rather than merely faded: this is the one
   distinction in the row that must survive being read at a glance, and a
   lighter grey against a grey label is not a distinction */
.fd-was {
  text-decoration: line-through;
  opacity: 0.75;
}
.fd-now {
  font-weight: 750;
  color: var(--foreground);
}
.fd-arw {
  opacity: 0.5;
}
/* direction is said by the arrow's colour AND by which way the amounts move,
   never by colour alone: the struck-out old value and the bold new one are
   legible with no colour perception at all */
.fd-up .fd-arw {
  color: var(--accent-text);
}
.fd-down .fd-arw {
  color: var(--foreground-muted);
}
.fd-more {
  display: block;
  font-style: italic;
  opacity: 0.8;
}

/* ---- 13. THE PROFILE PAIR, DEMOTED --------------------------------------
   Goal, diet, meals a day and cuisine are facts the person already entered.
   Restating them in two full-width cards at the top of the screen every day
   is the product talking about the user instead of about their food. They
   keep their place, one step quieter and below the ways in. */
.dash-row-compact {
  margin-top: 18px;
}
.dash-row-compact .dash-card {
  padding: 14px 16px;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}
.dash-row-compact .dash-card-hd {
  font-size: var(--text-ui);
}
.dash-row-compact .sub,
.dash-row-compact .dash-progress-tx {
  font-size: var(--text-caption);
}
.dash-row-compact dl,
.dash-row-compact .dash-facts {
  font-size: var(--text-caption);
}

/* ---- 12b. THE ACCOUNT MENU ----------------------------------------------
   WHAT THIS REPLACED. The header used to end in four separate things: a green
   tick, the person's name, an "Account" button and a "Sign out" button, all at
   the same visual weight as the navigation itself. Sign out was therefore one
   stray click from Today, and the row was so wide that the destinations had to
   be squeezed. Everything about the person now lives behind one 34px circle,
   which is both the smallest possible header footprint and the convention
   every user already knows.

   The menu keeps the glass EDGE - the 1px light border and the highlight - but
   takes the opaque relative's fill. At 0.72 alpha a screenshot showed the
   dashboard's "Meals selected" and its star icon reading straight through the
   word "Preferences". system.css says it in one line: glass over an unknown
   background cannot be trusted to carry text, and a menu is nothing but text. */
.acct-bar {
  position: relative;
}
.avatar-btn {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background:
    radial-gradient(120% 120% at var(--px, 50%) var(--py, 40%), var(--sky-200) 0%, transparent 70%),
    var(--sky-100);
  color: var(--accent-text);
  font: inherit;
  font-size: 14px;
  font-weight: 750;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transform: scale(calc(1 - var(--press, 0) * 0.06));
  transition:
    box-shadow var(--motion-fast) var(--ease-standard),
    transform var(--motion-release) var(--ease-spring);
}
.avatar-btn:hover {
  box-shadow: var(--shadow-sm);
}
.avatar-btn[aria-expanded='true'] {
  border-color: var(--accent);
  box-shadow: var(--shadow-sm);
}
.avatar-menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 60;
  min-width: 218px;
  padding: 8px;
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
}
.avatar-menu[hidden] {
  display: none;
}
.am-head {
  padding: 8px 10px 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
  display: grid;
  gap: 2px;
}
.am-head b {
  font-size: 14px;
  color: var(--foreground);
}
/* the email is the identity check, not a label: it may be long, and wrapping
   it would push the menu wider than the header it hangs from */
.am-head small {
  font-size: var(--text-caption);
  color: var(--foreground-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 200px;
}
.am-item {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--foreground);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  padding: 9px 10px;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-standard);
}
.am-item:hover {
  background: var(--background-secondary);
}
.am-sep {
  height: 1px;
  background: var(--border);
  margin: 4px 2px;
}
/* Sign out is separated and tinted because it is the one item in this menu
   that cannot be undone by clicking the next thing */
.am-out {
  color: var(--bad);
}

/* The primary action, standing apart from the destinations. It is a filled
   button in a row of text links precisely so it does not read as a fourth
   place to go. */
.nav-build {
  white-space: nowrap;
}
.drawer-build {
  margin-top: 10px;
  align-self: flex-start;
}
@media (max-width: 620px) {
  .nav-build {
    display: none;
  }
}

/* THE RIGHT RAIL SHOWS AT EVERY WIDTH.
   It used to be display:none from 641px up, under a comment that justified it
   as "everything it held is reachable without it: Meals and Insights from the
   header (or the drawer), Preferences from the avatar menu". That sentence was
   false when it was written and got worse afterwards: the header has three
   items - Today, Meals and Build my plate - and NONE of them opened Progress.
   So on any tablet or desktop the rail was the only route to Progress, and it
   was the thing being hidden. My plan, Progress and Preferences measured 0x0
   at 768, 1024 and 1280, which is what the owner reported twice as "I still
   cannot see my bio and blueprint buttons".
   The detached look the rule was reaching for is a layout problem and is
   handled by layout: body.fnav-docked reserves the rail's lane so no panel can
   run under it. Hiding a control is not a fix for it sitting in the wrong
   place. scripts/float-rail.test.mjs measures the orbs at every viewport, and
   scripts/nav.test.mjs now pins that Progress has a header route too, so the
   rail is a shortcut rather than the only door. */

/* ---- THE FOCUS RING, AS A BASELINE ---------------------------------------
   A dozen components had a hand-written :focus-visible rule and everything
   else had nothing, which is the shape this defect always takes: the parts
   somebody remembered are covered and the rest are not. scripts/a11y.test.mjs
   measured it by focusing a sample and comparing computed style against the
   resting state: 18 of 22 controls in the meal builder looked IDENTICAL
   focused. A keyboard user was moving an invisible cursor through the product.

   :focus-visible and not :focus, so a mouse click on a button does not leave a
   ring behind it. The browser's own heuristic decides, which is the whole
   point of the pseudo-class.

   The three rules below it are not duplication: .stepper .step-input,
   .gate-card and .fp-search input each set `outline: none` at a specificity
   this cannot reach, two of them with no replacement at all. They are restated
   here rather than edited in place because the outline they remove is wanted
   for the MOUSE - a ring on a number field you just clicked is noise - and
   only the keyboard case needs it back. */
a[href]:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.stepper .step-input:focus-visible,
.gate-card:focus-visible,
.fp-search input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---- THE MOBILE MEAL TRAY (§32) ------------------------------------------
   The same #platePeek, presented as a bottom sheet under 900px. One bar in
   reach at all times saying what is on the plate; tapping it raises the whole
   editor over the food list instead of scrolling away from it.

   WHY CSS AND NOT A SECOND COMPONENT. A tray built as its own markup is a
   second plate to keep in step with the first, and this codebase has already
   paid for that once: the composer and the menu grew separate category lists
   and drifted. There is one plate, one render and one set of handlers here;
   only the box they sit in changes.

   The translate is of the element's OWN height, so the sheet parks with
   exactly the bar showing whatever it grows to. */
.tray-bar {
  display: none;
}
@media (max-width: 900px) {
  .plate-inline.show {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 70;
    display: flex;
    flex-direction: column;
    max-height: min(74vh, 560px);
    background: var(--surface);
    border-top: 1px solid var(--border);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: var(--shadow-sm);
    transform: translateY(calc(100% - var(--tray-bar-h)));
    transition: transform var(--motion-medium) var(--ease-out);
  }
  .plate-inline.show.tray-open {
    transform: none;
  }
  .tray-bar {
    --tray-bar-h: 58px;
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: var(--tray-bar-h);
    padding: 10px 16px;
    border: none;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    background: var(--accent);
    color: var(--on-accent);
    font: inherit;
    font-weight: 750;
    text-align: left;
    cursor: pointer;
  }
  .plate-inline.show {
    --tray-bar-h: 58px;
  }
  .tb-ic {
    font-size: 19px;
  }
  .tb-tx {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.25;
  }
  /* the count is the reason the bar exists, so it is never the thing that
     gets cut: the label above it wraps to nothing and this line owns the row */
  .tb-tx small {
    font-weight: 600;
    font-size: var(--text-caption);
    color: var(--on-accent-soft);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .tb-chev {
    flex: none;
    font-size: 20px;
    line-height: 1;
    transition: transform var(--motion-medium) var(--ease-out);
  }
  .tray-open .tb-chev {
    transform: rotate(180deg);
  }
  /* the card scrolls inside the sheet; the sheet itself never does, so the
     bar cannot be scrolled off the screen it is anchored to */
  .plate-inline.show .peek-card {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    border-radius: 0;
  }
  .plate-inline.show .peek-body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  /* the orb is a scroll shortcut to a plate that is now always on screen */
  .plate-orb-wrap {
    display: none !important;
  }
  /* so the last row of the page is not parked under the bar */
  body.has-tray {
    padding-bottom: 64px;
  }
}

/* ---- WHAT CURRYPLATE IS LEARNING ----------------------------------------
   The behaviour read, moved out of the account modal and rebuilt as a product
   surface. It used to be `label, bar, number` nine times inside a settings
   drawer; it is now one composition of three strands.

   THREE STRANDS, ONE SURFACE, and that is the whole difference. Three cards
   would say "here are three reports"; three columns inside one lightly-lit
   panel, divided by hairlines, say "here is what we noticed about you". The
   panel carries the same pale-blue light as the rest of the product, at a
   third of the strength, so it reads as part of the page rather than as a
   widget dropped onto it. */
.lrn {
  position: relative;
  margin-top: clamp(28px, 4vh, 46px);
  padding: clamp(22px, 3vw, 34px);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background:
    radial-gradient(
      120% 90% at 8% 0%,
      color-mix(in srgb, var(--accent) 5.5%, transparent) 0%,
      transparent 58%
    ),
    radial-gradient(
      90% 70% at 100% 100%,
      color-mix(in srgb, var(--accent-emerald) 5%, transparent) 0%,
      transparent 60%
    ),
    var(--surface);
  box-shadow:
    var(--shadow-sm),
    inset 0 1px 0 color-mix(in srgb, var(--text) 90%, transparent);
}
.lrn-hd {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: clamp(16px, 3vw, 40px);
  flex-wrap: wrap;
}
.lrn-title {
  margin: 0;
  font-size: var(--text-feature);
  letter-spacing: var(--track-display);
}
.lrn-sub {
  margin: 6px 0 0;
  max-width: 46ch;
  color: var(--foreground-muted);
  font-size: var(--text-ui);
  line-height: var(--leading-body);
}
/* how much it has learned, said as two plain counts. Never a percentage: a
   product that reports "personalisation 83.7%" has invented a precision it
   does not have and given somebody a number they cannot act on. */
.lrn-scale {
  display: flex;
  /* IT WRAPS, AND IT IS ALLOWED TO SHRINK SO THAT WRAP CAN FIRE. The two counts
     measure 242px side by side; inside the dashboard card a 360px phone gives
     this row 216px, so it ran 26px outside its own header and the second count
     was cut. It was already wrapping onto its own line under the title and was
     still too wide there, because `flex: none` sizes an item to max-content -
     it never gets narrow enough for its own flex-wrap to engage. Shrinking is
     what lets it become two rows; the counts themselves still never squash,
     because wrapping happens between them rather than inside them. */
  flex-wrap: wrap;
  gap: clamp(18px, 3vw, 34px);
  margin: 0;
  flex: 0 1 auto;
  min-width: 0;
}
.lrn-scale dd {
  margin: 0;
  font-size: var(--text-feature);
  font-weight: 750;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
}
.lrn-scale dt {
  margin-top: 4px;
  font-size: var(--text-caption);
  color: var(--foreground-muted);
  white-space: nowrap;
}

/* WHAT WE HAVE NOTICED: the behavioural read, above the tally.
   These are sentences, not charts, and the styling has one job - to make them
   read as findings rather than as another row of statistics. So: no bars, no
   numerals set large, and a left rule in the accent instead of a card border,
   which marks each one as a quotation of the person's own behaviour without
   adding a third box to a page that already has two.

   The rule is 2px and the padding is generous because the alternative tried
   first, a bordered card per finding, put three boxes inside a box and made the
   section look like an analytics widget - the exact thing the panel below it
   was rebuilt to stop being. */
.lrn-noticed {
  margin-top: clamp(20px, 3vh, 30px);
  padding-top: clamp(18px, 2.6vh, 26px);
  border-top: 1px solid var(--border);
}
.lrn-noticed-h {
  margin: 0 0 clamp(12px, 1.8vh, 18px);
  font-size: var(--text-caption);
  font-weight: 750;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--foreground-muted);
}
.lrn-cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* one column on a phone, and never three: a finding is a sentence and three
     sentence columns at 1440px give each one a 30ch measure that breaks mid
     clause. Two is the most this content takes.
     THE min(..., 100%) IS LOAD-BEARING, and its absence was a real defect. A
     bare minmax floor of 260px is a floor even when the container is narrower:
     these cards sit two levels inside a padded dashboard card, so at a 375px
     phone the grid gets 231px and laid out a 260px track anyway. The 29px of
     card ran outside its own box and the sentences were cut mid-word. Nothing
     caught it: the document did not scroll, because an ancestor absorbed the
     overflow, and the truncation sweep measures height. It was visible the
     moment somebody screenshotted a phone. */
  grid-template-columns: repeat(auto-fit, minmax(min(clamp(260px, 32vw, 420px), 100%), 1fr));
  gap: clamp(14px, 2vw, 22px);
}
/* THE ODD ONE OUT FILLS THE ROW. INSIGHT_SHOWN is three, so two columns leave a
   card alone beside a hole in the NORMAL case rather than an edge one - and a
   hole the size of a card reads as something that failed to load, which is what
   the screenshot showed. Spanning it is what a wrapped list of findings should
   do; at one column the rule is a no-op, and at two cards there is no orphan. */
.lrn-card:last-child:nth-child(odd) {
  grid-column: 1 / -1;
}
/* and the measure is capped INSIDE the card rather than by the column, so the
   spanning one does not hand somebody a 120-character line to read */
.lrn-card > * {
  max-width: 62ch;
}
.lrn-card {
  min-width: 0;
  padding: clamp(14px, 1.8vw, 18px) clamp(16px, 2vw, 22px);
  border-left: 2px solid color-mix(in srgb, var(--accent) 42%, transparent);
  border-radius: var(--radius-sm);
  background: linear-gradient(
    100deg,
    color-mix(in srgb, var(--accent) 5%, transparent) 0%,
    transparent 62%
  );
}
.lrn-card-head {
  margin: 0;
  font-size: var(--text-body);
  font-weight: 650;
  line-height: 1.32;
  letter-spacing: var(--track-tight);
}
/* THE EVIDENCE, set quieter than the claim and never hidden. It is the reason
   the claim is allowed to exist, so a layout that dropped it on a phone would
   leave an assertion with nothing behind it. */
.lrn-card-ev {
  margin: 8px 0 0;
  font-size: var(--text-caption);
  line-height: var(--leading-body);
  color: var(--foreground-muted);
  font-variant-numeric: tabular-nums;
}
.lrn-card-imp {
  margin: 8px 0 0;
  font-size: var(--text-ui);
  line-height: var(--leading-body);
}
.lrn-card-act {
  margin-top: 12px;
}
/* the cards arrive with the rest of the panel, one after another. Same trail
   timing as the strands below, so the section reads as one reveal. */
.lrn .lrn-card {
  opacity: 0;
  transform: translateY(6px);
}
.lrn.in .lrn-card {
  animation: lrnCardIn var(--motion-medium) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--motion-instant));
}
@keyframes lrnCardIn {
  to {
    opacity: 1;
    transform: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .lrn .lrn-card {
    opacity: 1;
    transform: none;
  }
  .lrn.in .lrn-card {
    animation: none;
  }
}

/* THE THREE STRANDS. auto-fit rather than a fixed three, so the tablet width
   drops to two and the phone to one without a second set of rules; the hairline
   is a border on the strand itself so it disappears with the column. */
.lrn-grid {
  margin-top: clamp(20px, 3vh, 30px);
  display: grid;
  /* min(..., 100%) for the reason spelled out on .lrn-cards: a bare minmax floor
     overflows any container narrower than it, and this one does at 360px. */
  grid-template-columns: repeat(auto-fit, minmax(min(clamp(220px, 24vw, 300px), 100%), 1fr));
  gap: clamp(18px, 2.4vw, 34px);
}
.lrn-strand {
  min-width: 0;
  padding-left: clamp(18px, 2.4vw, 34px);
  border-left: 1px solid var(--border);
}
.lrn-strand:first-child {
  padding-left: 0;
  border-left: none;
}
.lrn-strand h4 {
  margin: 0 0 12px;
  font-size: var(--text-caption);
  font-weight: 750;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--foreground-muted);
}
.lrn-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 9px;
}
/* THE ROW IS A GRID, WHICH IS WHAT STOPS IT TRUNCATING. The name is the only
   elastic column and the count is sized to its own digits, so "Seeraga samba
   chicken biryani" wraps or ellipsises on its own line while the number beside
   it never moves. A flex row here squeezed the count to nothing at 375px. */
.lrn-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 4px 10px;
  font-size: var(--text-ui);
}
.lrn-nm {
  min-width: 0;
  font-weight: 650;
  color: var(--foreground);
  overflow-wrap: anywhere;
}
.lrn-n {
  font-variant-numeric: tabular-nums;
  font-weight: 750;
  color: var(--accent-text);
  white-space: nowrap;
}
.lrn-n small {
  font-weight: 600;
  color: var(--foreground-muted);
  margin-left: 1px;
}
/* the trail spans both columns under the name: a proportional mark rather than
   a chart, 3px tall, so five of them read as a shape at a glance and never as
   a bar chart demanding to be studied */
.lrn-trail {
  grid-column: 1 / -1;
  display: block;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--background-secondary);
  overflow: hidden;
}
.lrn-trail i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
  /* the reveal when the numbers CHANGE. A width transition, so a repaint with
     the same numbers does not replay it. */
  transition: width var(--motion-slow) var(--ease-out);
  transform-origin: 0 50%;
}
/* THE REVEAL WHEN THE SECTION ARRIVES. The trails were already at their length
   the moment the panel painted, so scrolling down to a finished chart was the
   whole experience - nothing said "this was measured from you". They grow from
   nothing once, over one --motion-slow beat, staggered a fifth of a beat apart
   down each strand.
   `.in` is added by an IntersectionObserver ONCE per mount (see renderLearning)
   and never removed, so a repaint that changes a count animates the width and
   does not replay the growth. scaleX rather than width, because this composes
   with the width transition above instead of fighting it. */
.lrn.in .lrn-trail i {
  animation: lrnFill var(--motion-slow) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--motion-instant));
}
@keyframes lrnFill {
  from {
    transform: scaleX(0);
    opacity: 0.3;
  }
  to {
    transform: scaleX(1);
    opacity: 1;
  }
}
.lrn-act {
  margin-top: 14px;
}
.lrn-lead {
  margin: 0;
  font-size: var(--text-feature);
  font-weight: 750;
  line-height: 1.1;
  letter-spacing: var(--track-display);
}
.lrn-lead-n,
.lrn-note,
.lrn-none {
  margin: 6px 0 0;
  color: var(--foreground-muted);
  font-size: var(--text-caption);
  line-height: var(--leading-body);
}
.lrn-note {
  margin-top: 10px;
  font-size: var(--text-ui);
  color: var(--foreground);
}
.lrn-foot {
  margin-top: clamp(18px, 2.6vh, 28px);
  padding-top: 16px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.lrn-fine {
  margin: 0;
  max-width: 64ch;
  color: var(--foreground-muted);
  font-size: var(--text-caption);
}
/* the expanded read is the same section showing every row, not a second page */
.lrn-all .lrn-list {
  gap: 11px;
}

.lrn-empty {
  text-align: center;
}
.lrn-empty .lrn-hd {
  justify-content: center;
}
.lrn-empty .lrn-sub {
  margin: 8px auto 18px;
}

/* POINTER-AWARE DEPTH, on the composition and not on its rows. The ambient
   light shifts a few pixels with the pointer so the panel feels like a surface
   being looked at; individual rows do not move, because a list that dodges the
   cursor is a list nobody can read. --pnx/--pny come from pointer.js and are
   absent on touch and under reduced motion, where this costs nothing. */
.lrn::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(
    38% 46% at calc(50% + var(--pnx, 0) * 26%) calc(38% + var(--pny, 0) * 22%),
    color-mix(in srgb, var(--accent-cyan) 8%, transparent) 0%,
    transparent 70%
  );
  transition: background-position var(--motion-slow) var(--ease-out);
}
@media (max-width: 720px) {
  .lrn-strand {
    padding-left: 0;
    border-left: none;
    border-top: 1px solid var(--border);
    padding-top: 18px;
  }
  .lrn-strand:first-child {
    border-top: none;
    padding-top: 0;
  }
}

/* ============================================================================
   MY ACCOUNT: a workspace, not a settings form.

   WHAT WAS WRONG. Six bordered rectangles stacked inside a 520px column, one
   scrollbar for the lot, and 2,400px of travel from the first name field to the
   delete button. On a 1440px screen two thirds of the width was empty while the
   health chips wrapped four rows deep inside the narrow one. Every heading was
   an emoji from a different illustrator, and "Insulin resistance / prediabetes"
   was the widest label in the product inside the narrowest box in the product.

   WHAT THIS IS. A fixed shell with three bands - who you are, the work, the
   commit - and the middle band split into a section rail and the one panel it
   selects. The SHELL NEVER SCROLLS: only .ac2-work does, so the close button,
   the rail and Save are always where you left them. Each section is about one
   screenful, which is what makes the rail worth having.

   THE MATERIAL is the product's pale blue over white, at the same tilt the rest
   of the app uses: a translucent white sheet, a wash of --sky over it, a thin
   near-white edge and a large radius. No grey, no dark surface, no new hue.
   ========================================================================== */
.ac2-wrap {
  padding: clamp(0px, 3vw, 34px);
  /* the SHARED scrim alone. It used to add a backdrop blur; measured paper
     has no glass, and a scrim's job - dim what is behind the sheet - is done
     by the tint without frosting it. */
}
/* IT ARRIVES. The sheet used to appear at full size in one frame, which on
   something this large reads as a jump cut. It settles instead: a 12px rise and
   1.5% of scale over one medium beat, which is short enough not to be waited
   for and long enough to feel like a surface moving into place. No bounce - the
   ease is --ease-out, not the spring. Closing is not animated, because the
   dialog stack removes the node synchronously and delaying that would change
   the behaviour of all nineteen dialogs to decorate one. */
.modal.ac2 {
  animation: ac2Open var(--motion-medium) var(--ease-out) both;
  width: min(92vw, 1280px);
  max-width: 1280px;
  /* Sized to the WORK, not to the screen. At min(90vh, 880px) the profile
     section - four short fields - sat above 400px of empty sheet on a 1440
     display, which is the "giant empty areas" the old modal was rebuilt to
     escape. This is roughly the tallest section's natural height; the two long
     ones scroll their own region, which is what the region is for. */
  height: min(86vh, 700px);
  min-height: min(86vh, 520px);
  max-height: 86vh;
  padding: 0;
  overflow: hidden;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  border-radius: var(--radius-xl);
  border: 1px solid color-mix(in srgb, var(--text) 72%, transparent);

  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--text) 90%, transparent),
    var(--shadow-sm);
}

/* ---- who you are --------------------------------------------------------- */
.ac2-hd {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: clamp(18px, 2.4vw, 26px) clamp(18px, 3vw, 34px);
  padding-right: 64px; /* clear of the close button, which is absolute */
  border-bottom: 1px solid color-mix(in srgb, var(--text) 80%, transparent);
}
.ac2-av {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 900;
  font-size: var(--text-ui);
  letter-spacing: var(--track-tight);
  box-shadow: var(--shadow-sm);
}
.ac2-who {
  min-width: 0;
}
.modal.ac2 .ac2-name {
  font-size: var(--text-lead);
  font-weight: 900;
  letter-spacing: var(--track-tight);
  margin: 0;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.ac2-mail {
  margin: 2px 0 0;
  font-size: var(--text-caption);
  font-weight: 600;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* ---- the rail and the work ----------------------------------------------- */
.ac2-body {
  display: grid;
  grid-template-columns: minmax(0, 248px) minmax(0, 1fr);
  min-height: 0;
}
.ac2-rail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 16px 12px;
  border-right: 1px solid color-mix(in srgb, var(--text) 80%, transparent);
  overflow-y: auto;
}
.ac2-nav {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  text-align: left;
  padding: 11px 13px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font-size: var(--text-ui);
  font-weight: 700;
  cursor: pointer;
}
.ac2-nav .ac2-ic {
  flex: 0 0 auto;
  opacity: 0.9;
}
.ac2-navtx {
  min-width: 0;
  /* the rail labels are the one place a truncation would be invisible: the
     section is still reachable, so nothing looks broken while "Health &
     allergies" reads "Health & aller...". They wrap instead. */
  overflow-wrap: anywhere;
}
.ac2-nav:hover {
  background: var(--glass-card);
  color: var(--ink);
}
.ac2-nav.on {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--text) 10%, transparent), var(--sky-200)),
    var(--glass-card);
  border-color: color-mix(in srgb, var(--text) 90%, transparent);
  color: var(--accent-text);
  font-weight: 800;
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--text) 90%, transparent),
    var(--shadow-sm);
}
.ac2-work {
  min-width: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: clamp(20px, 2.6vw, 32px) clamp(18px, 3vw, 38px) 30px;
}
.ac2-panel {
  display: none;
  max-width: 860px;
}
.ac2-panel.on {
  display: block;
  /* the panel arrives rather than appearing: a 12px lift over one medium beat,
     which is the same entrance the planner's cards use */
  animation: ac2In var(--motion-medium) var(--ease-out) both;
}
@keyframes ac2Open {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes ac2In {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.ac2-panel:focus-visible {
  outline: none;
}
.ac2-ph {
  margin-bottom: 18px;
}
.ac2-pt {
  font-size: var(--text-feature);
  font-weight: 900;
  letter-spacing: var(--track-display);
  line-height: 1.1;
  margin: 0;
  color: var(--ink);
}
.ac2-ps {
  margin: 6px 0 0;
  font-size: var(--text-ui);
  font-weight: 600;
  color: var(--muted);
  max-width: 56ch;
}

/* ---- the content inside a panel ------------------------------------------ */
.ac2-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr));
  gap: 14px 18px;
}
.modal.ac2 .ac2-grid .field {
  margin-bottom: 0;
}
.ac2-note {
  margin: 14px 0 0;
  font-size: var(--text-caption);
  font-weight: 600;
  line-height: 1.55;
  color: var(--muted);
  max-width: 62ch;
}
.ac2-fine {
  margin: 22px 0 0;
  font-size: var(--text-caption);
  font-weight: 600;
  color: var(--muted);
}
.ac2-fieldset {
  position: relative;
  padding: 16px 18px;
  margin-bottom: 16px;
}
.ac2-legend {
  display: block;
  font-size: var(--text-caption);
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 11px;
}
.ac2-danger {
  border-color: color-mix(
    in srgb,
    var(--bad) 30%,
    color-mix(in srgb, var(--text) 90%, transparent)
  );
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--text) 10%, transparent), var(--danger-soft)),
    var(--glass-card);
}
/* the cultural-rhythm section: three sub-groups inside one fieldset, so each
   gets a quieter heading than the fieldset's own legend */
.ac2-sublegend {
  display: block;
  font-size: var(--text-caption);
  font-weight: 700;
  color: var(--muted);
  margin: 12px 0 7px;
}
.ac2-sublegend:first-child {
  margin-top: 0;
}
.ac2-fasts {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
  display: grid;
  gap: 6px;
}
.ac2-fast {
  font-size: var(--text-ui);
  color: var(--foreground);
}
.ac2-fastadd {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.ac2-fastadd select {
  flex: 1 1 120px;
  min-width: 0;
}
.modal.ac2 .ac2-fieldset .field {
  margin-bottom: 0;
}

/* the bio, as facts. Two columns of label-over-value on a wide panel, one on a
   phone; the label is small and quiet so the value is what the eye lands on. */
.ac2-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  /* The rules between facts are each tile's OUTLINE, meeting in a 1px gap,
     rather than a --border ground showing through that gap. The ground version
     drew correctly until the fact count stopped filling the last row, at which
     point the empty grid cell showed as a grey block - visible at 1024px, where
     nine facts wrap to three columns. An outline paints only where a tile is. */
  gap: 1px;
  margin: 0 0 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--sky-100);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--text) 90%, transparent);
}
.ac2-fact {
  padding: 12px 16px;
  background: linear-gradient(180deg, var(--sky-100), var(--sky-200));
  outline: 1px solid var(--border);
  min-width: 0;
}
.ac2-fact dt {
  font-size: var(--text-caption);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.ac2-fact dd {
  margin: 3px 0 0;
  font-size: var(--text-ui);
  font-weight: 800;
  color: var(--ink);
  /* a goal reading "Lose weight, to 75 kg" is longer than its column at 375px
     and must wrap rather than elide */
  overflow-wrap: anywhere;
}
.ac2-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 16px;
}
.ac2-row .ac2-note {
  margin: 0;
  flex: 1 1 260px;
}
.ac2-empty {
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-md);
  background: linear-gradient(180deg, var(--sky-100), var(--sky-200));
  padding: 22px;
  text-align: center;
}
.ac2-empty p {
  margin: 0 0 14px;
  font-size: var(--text-ui);
  font-weight: 700;
  color: var(--muted);
}

/* current planning mode: a readout, not another card of the same weight as the
   sections around it */
.ac2-mode {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background:
    linear-gradient(180deg, var(--sky-100), var(--sky-200)),
    radial-gradient(90% 120% at 100% 0%, var(--sky-300) 0%, transparent 62%);
  padding: 20px 22px;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--text) 90%, transparent);
}
.ac2-eyebrow {
  display: block;
  font-size: var(--text-caption);
  font-weight: 900;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.ac2-modename {
  display: block;
  margin: 6px 0 4px;
  font-size: var(--text-lead);
  font-weight: 900;
  letter-spacing: var(--track-tight);
  color: var(--ink);
}
.ac2-modesub {
  margin: 0 0 16px;
  font-size: var(--text-ui);
  font-weight: 600;
  color: var(--muted);
  max-width: 54ch;
}

/* ---- the multi-selects --------------------------------------------------- */
/* NOTHING HERE MAY TRUNCATE. "Insulin resistance / prediabetes" and "Type 1 or
   insulin-treated diabetes" are real options a real person has to be able to
   read before ticking; a chip that elides them is a medical setting the user
   cannot verify. Height is free, width is not, so the chips wrap their text. */
.ac2 .chip {
  white-space: normal;
  text-align: left;
  overflow-wrap: anywhere;
  max-width: 100%;
  line-height: 1.35;
}
.ac2 .chips {
  gap: 9px;
}
.ac2 #acHealth .hl-group {
  margin-bottom: 18px;
}
.ac2 .hl-label {
  display: block;
  font-size: var(--text-caption);
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 9px;
}
/* SELECTED IS A STATE, NOT A CALL TO ACTION. The house chip turns into a solid
   accent slab with a coloured drop shadow, which at nine selected conditions
   reads as nine primary buttons shouting at once. In here a selection is a
   tinted glass tile with a blue edge and one restrained ring - clearly chosen,
   still an option. */
.ac2 .chip.active {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--text) 10%, transparent), var(--sky-300)),
    var(--glass-card);
  color: var(--accent-text);
  border-color: var(--accent);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--text) 90%, transparent),
    0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
}
.ac2 .hl-text {
  width: 100%;
}

/* ---- the commit band ----------------------------------------------------- */
.ac2-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding: 14px clamp(18px, 3vw, 34px);
  border-top: 1px solid color-mix(in srgb, var(--text) 80%, transparent);
}
.modal.ac2 .ac2-msg {
  margin: 0;
  min-width: 0;
  flex: 1 1 200px;
  font-size: var(--text-caption);
  font-weight: 700;
}
.modal.ac2 .ac2-msg:empty {
  display: none;
}
.ac2-acts {
  display: flex;
  gap: 10px;
  margin-left: auto;
}
/* unsaved work says so on the button as well as in the status line: the status
   line is at the far end of a 1340px band from where the pointer is */
.ac2-dirty #ac_save {
  box-shadow: var(--shadow-sm);
}

/* ---- the close button ---------------------------------------------------- */
/* A 36px square with a 15px glyph, on a sheet this size, read as a stray form
   control. This is a glass disc that belongs to the material around it. */
.modal.ac2 .modal-x {
  top: clamp(14px, 1.6vw, 20px);
  right: clamp(14px, 1.6vw, 20px);
  width: 34px;
  height: 34px;
  color: var(--muted);
  font-size: var(--text-caption);
  transition:
    color var(--motion-fast) var(--ease-out),
    box-shadow var(--motion-fast) var(--ease-out);
}
.modal.ac2 .modal-x:hover {
  color: var(--ink);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--text) 90%, transparent),
    var(--shadow-sm);
}
.modal.ac2 .modal-x:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* ---- tablet: the rail keeps its labels, the sheet keeps its edges --------- */
@media (max-width: 1024px) {
  .modal.ac2 {
    width: 94vw;
    height: min(86vh, 760px);
  }
  .ac2-body {
    grid-template-columns: minmax(0, 216px) minmax(0, 1fr);
  }
}

/* ---- phone: a sheet, and the rail becomes a strip ------------------------- */
@media (max-width: 760px) {
  .ac2-wrap {
    padding: 0;
  }
  .modal.ac2 {
    width: 100vw;
    max-width: none;
    height: 100dvh;
    max-height: none;
  }
  .ac2-hd {
    padding: 16px 60px 14px 18px;
  }
  .ac2-body {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }
  /* a vertical rail on a phone is six rows of chrome above the first field.
     Horizontal, it is one row that scrolls, and the selected pill is dragged
     into view by the same code that switches the panel. */
  .ac2-rail {
    flex-direction: row;
    gap: 8px;
    padding: 10px 14px;
    border-right: none;
    border-bottom: 1px solid color-mix(in srgb, var(--text) 80%, transparent);
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  .ac2-rail::-webkit-scrollbar {
    display: none;
  }
  .ac2-nav {
    width: auto;
    flex: 0 0 auto;
    border-radius: var(--radius-pill);
    padding: 10px 14px;
    /* 44px of target height at a phone's font size, which is the floor every
       other control in the app is held to */
    min-height: 44px;
  }
  .ac2-navtx {
    white-space: nowrap;
  }
  .ac2-work {
    padding: 20px 18px 26px;
  }
  .ac2-foot {
    padding: 12px 18px calc(12px + env(safe-area-inset-bottom, 0px));
  }
  .ac2-acts {
    width: 100%;
  }
  .ac2-acts .btn {
    flex: 1 1 0;
    min-height: 46px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ac2-panel.on,
  .modal.ac2 {
    animation: none;
  }
}

/* the email change: field and action on ONE line, the button matched to the
   input's own height so the pair reads as a single control. It stacked when the
   old .ac-email-row rule went with the old modal, which left "Send link" as a
   loose grey pill floating under the address. */
.ac2 .ac-email-row {
  display: flex;
  gap: 10px;
  align-items: stretch;
}
.ac2 .ac-email-row input {
  flex: 1 1 auto;
  min-width: 0;
}
.ac2 .ac-email-row .btn {
  flex: 0 0 auto;
  white-space: nowrap;
}
@media (max-width: 520px) {
  .ac2 .ac-email-row {
    flex-wrap: wrap;
  }
  .ac2 .ac-email-row .btn {
    width: 100%;
  }
}

/* ---- the two quick additions on Main dishes ------------------------------
   Curd and a boiled egg are added, not browsed for: nobody opens a category
   called "Curd & fermented sides" to put curd beside a sambar rice. The strip
   sits under the dish chips on Main dishes and writes through composeToggle,
   so it is the same record the category's own chip writes. Quiet by design -
   it is an afterthought to the main choice, not a second one. */
.cmp-quick {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--line);
}
.cmp-quick-lbl {
  display: block;
  font-size: var(--text-caption);
  font-weight: 900;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}

/* THE PLATTER IS ITS OWN SECTION inside Vegetables & greens, and it has to
   read as one: it was a dashed rule under the dish list, which on a long
   category looked like the list continuing. A tinted panel with its own edge
   says "this is a different thing you can build here". */
.cmp-sub.substep {
  margin-top: 18px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-top: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: linear-gradient(180deg, var(--sky-100), var(--sky-200));
}
.cmp-sub.substep h4 {
  margin-bottom: 6px;
}

/* ---- THE PLATE ------------------------------------------------------------
   The product is named after this and until now it was a table of rows with
   steppers on them. A correct editor, and not a plate.

   THE ONE RULE THE DESIGN ENFORCES: two claims, drawn in two places that
   cannot be confused for each other.

     INSIDE THE RIM is food, and a blob's area is its share of the GRAMS. It is
     not calories and it is emphatically not macros - a plate drawn as a third
     "protein" would be saying that a third of what you are looking at is
     protein, which is not what a third of a plate of food means.

     OUTSIDE THE RIM is the halo: three rings for protein, carbohydrate and fat,
     filling toward this meal's share of the day. A ring cannot be misread as a
     quantity of food, which is the entire reason the macros are out there.

   ONE COLOUR FOR ALL THE FOOD. Tinting the dishes differently would invite
   somebody to decode the colours, and the only code available is the one the
   halo already owns. Size and labels tell the dishes apart.

   The three ring hues are the product's own intelligence ramp, cyan to blue to
   violet - the only place three distinguishable accents exist in the approved
   palette. Nothing new is introduced for this. */
.pv {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: center;
  gap: clamp(16px, 3vw, 30px);
  padding: clamp(14px, 2vw, 20px) clamp(16px, 2.4vw, 24px);
  border-bottom: 1px solid var(--border);
}
.pv-stage {
  width: clamp(150px, 22vw, 210px);
  justify-self: center;
}
.pv-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}
/* the china. A plate, not a pie chart: a soft dish with a lit edge and an inner
   rim, the way a real plate has a well.

   IT IS THE CREAM, NOT THE WHITE, and that is not a taste call. The card behind
   it is --surface, so a --surface plate on a --surface card is a white disc
   with a hairline round it: in the first screenshot the plate was invisible and
   the food looked like it was floating on the card. The page's own background
   tone gives the china something to be. */
.pv-china {
  fill: var(--background);
  stroke: var(--border-strong);
  stroke-width: 1.2;
  filter: drop-shadow(0 6px 14px color-mix(in srgb, var(--accent) 12%, transparent));
}
.pv-rim {
  fill: none;
  stroke: var(--border-strong);
  stroke-width: 0.7;
  opacity: 0.55;
}
.pv-food {
  fill: var(--sky-300);
  stroke: var(--sky-500);
  stroke-width: 0.8;
}
/* FOOD SETTLES, it does not appear. Each item lands after the one before it,
   in the order it went onto the plate, and a portion CHANGE transitions the
   radius rather than replaying the drop - which is why the animation is on the
   group and the transition is on the circle. */
.pv-item {
  animation: pvSettle var(--motion-medium) var(--ease-spring) both;
  animation-delay: calc(var(--i, 0) * 60ms);
}
/* a dish that was already on the plate is not arriving, it is changing size.
   Set by renderPlatePeek on the blobs it carried across a rebuild. */
.pv-item[data-kept] {
  animation: none;
  opacity: 1;
  transform: none;
}
.pv-food {
  transition: r var(--motion-medium) var(--ease-out);
}
@keyframes pvSettle {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
/* the halo. Rotated so an arc starts at the top and travels clockwise, which is
   the direction a person reads a dial. */
.pv-rings {
  transform: rotate(-90deg);
}
.pv-track {
  fill: none;
  stroke: var(--blue-400);
  stroke-width: 4;
  stroke-linecap: round;
}
.pv-arc {
  fill: none;
  stroke-width: 4;
  stroke-linecap: round;
  transition: stroke-dasharray var(--motion-slow) var(--ease-out);
}
.pv-cyan {
  stroke: var(--accent-cyan);
}
.pv-blue {
  stroke: var(--accent-blue);
}
.pv-violet {
  stroke: var(--accent-emerald);
}
/* MET IS NOT A REWARD AND ABOVE IS NOT A TELLING-OFF. The only difference
   between the three states is weight: a met ring reads solid, a short one is
   drawn lighter because there is less of it to see. No green, no red - this is
   progress against a plan the person set, not a verdict on their eating. */
.pv-arc.is-short {
  opacity: 0.72;
}
.pv-arc.is-met,
.pv-arc.is-above {
  opacity: 1;
}
.pv-fill {
  margin: 10px 0 0;
  text-align: center;
  font-size: var(--text-caption);
  color: var(--foreground-muted);
  font-variant-numeric: tabular-nums;
}
.pv-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
  min-width: 0;
}
.pv-key {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 8px;
  font-size: var(--text-caption);
}
.pv-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  align-self: center;
}
.pv-cyan .pv-dot {
  color: var(--accent-cyan);
}
.pv-blue .pv-dot {
  color: var(--accent-text);
}
.pv-violet .pv-dot {
  color: var(--accent-text-alt);
}
.pv-key-n {
  color: var(--foreground);
  font-weight: 600;
}
.pv-key-v {
  color: var(--foreground);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* the target, set quieter than the number reached but never dropped: a figure
   with nothing to be measured against is not progress, it is a total */
.pv-key-v i {
  font-style: normal;
  font-weight: 500;
  color: var(--foreground-muted);
}
/* the phone puts the plate above its legend rather than beside it. A 150px
   plate next to three rows of text leaves both too narrow to read. */
@media (max-width: 560px) {
  .pv {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
  }
  .pv-legend {
    grid-template-columns: minmax(0, 1fr);
  }
}
@media (prefers-reduced-motion: reduce) {
  .pv-item {
    animation: none;
  }
  .pv-food,
  .pv-arc {
    transition: none;
  }
}

/* ---- FIX MY PLATE: the before, leaving ------------------------------------
   The new size is already on screen - the blob's radius transitions - so what
   was missing was the OLD one. Each portion that moved gets an outline at the
   size it used to be, drawn over the new blob and fading as it goes. That is
   the whole before-and-after in one picture: no second plate to compare
   against, and no need to remember what it looked like a second ago.

   IT IS AN OUTLINE, NOT A FILL. A filled ghost reads as more food; a ring
   reads as a size that used to be there, which is what it is. It shrinks
   toward the new blob when the portion went down and grows out to it when the
   portion went up, so the direction is legible without a label. */
.pv-ghost {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.6;
  stroke-dasharray: 3 3;
  animation: pvGhost var(--motion-slow) var(--ease-out) both;
  pointer-events: none;
}
/* IT HOLDS BEFORE IT FADES. The first version went from 0.85 to 0 on an
   ease-out, which front-loads the decay: a screenshot 150ms into a 520ms
   animation caught it at 0.24 opacity, and a 1.2px dashed line at a quarter
   opacity is not a thing anybody sees. The ring now stays legible for the
   first half and leaves over the second, which is the same total time and a
   completely different picture. */
@keyframes pvGhost {
  0% {
    opacity: 0.9;
  }
  45% {
    opacity: 0.9;
  }
  100% {
    opacity: 0;
  }
}
/* the blob that changed gets one beat of the accent, so the eye is sent to the
   dishes that moved rather than having to scan all of them */
.pv-food.moved-up,
.pv-food.moved-down {
  animation: pvMoved var(--motion-slow) var(--ease-out) both;
}
@keyframes pvMoved {
  0% {
    stroke: var(--accent);
    stroke-width: 2.4;
  }
  100% {
    stroke: var(--sky-500);
    stroke-width: 0.8;
  }
}

/* the receipt. What moved, by how much, and the way back - because "your rice
   got smaller" is not something a person should have to measure off a circle,
   and an action that changed their food has to be one tap from reversed. */
.pfx {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 10px clamp(12px, 2vw, 20px);
  margin-top: clamp(12px, 2vh, 18px);
  padding-top: clamp(12px, 2vh, 16px);
  border-top: 1px solid var(--border);
}
.pfx-hd {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--text-caption);
  font-weight: 650;
  color: var(--foreground);
}
.pfx-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
  min-width: 0;
}
.pfx-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: baseline;
  font-size: var(--text-caption);
  color: var(--foreground-muted);
}
.pfx-n {
  min-width: 0;
  overflow-wrap: anywhere;
}
.pfx-a {
  font-variant-numeric: tabular-nums;
  color: var(--foreground);
  white-space: nowrap;
}
.pfx-a i {
  font-style: normal;
  margin: 0 6px;
  color: var(--foreground-muted);
}
/* up and down are told apart by the arrow and the numbers, not by colour: a
   bigger portion is not a warning and a smaller one is not a success */
.pfx-up .pfx-a,
.pfx-down .pfx-a {
  color: var(--foreground);
}
@media (prefers-reduced-motion: reduce) {
  .pv-ghost,
  .pv-food.moved-up,
  .pv-food.moved-down {
    animation: none;
  }
  .pv-ghost {
    opacity: 0;
  }
}

/* ---- THE DAY AROUND THE PLATE ---------------------------------------------
   The halo says how this meal is doing against its share. It cannot say how the
   DAY is going, and a meal exactly on target is not reassuring if the two
   before it were double. This strip is that second question, above the plate,
   so the plate reads as one meal inside a day rather than as the whole picture.

   FOUR STATES, DRAWN DIFFERENTLY, and that is the feature rather than
   decoration. The one that matters most is `reported`: a meal the person says
   they ate and never entered. Its calories are unknown, which is precisely why
   a progress bar wants to drop it - and dropping it would tell somebody they
   have a meal's worth of room they have already spent. It is drawn as an
   outline with a dash where the number would be, which is exactly what is
   known about it.

   NO GREEN AND NO RED anywhere in here. Being over the day's figure is a fact
   the sentence states, not a verdict the colour delivers. */
.dsp {
  padding: clamp(12px, 1.8vw, 16px) clamp(16px, 2.4vw, 24px) 0;
}
.dsp-row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 6px;
}
.dsp-seg {
  flex: 1 1 0;
  min-width: 0;
  display: grid;
  gap: 5px;
}
/* E6 §4.2: the strip's TRACK. It was --blue-400, a mid blue from the white
   palette's ramp, which on the field read as a stray bright blue outline across
   the top of Today. A track is the unfilled part of a bar and belongs to the
   family: a translucent white over whatever panel it sits on, so it reads as an
   absence rather than as a second colour. */
.dsp-bar {
  height: 6px;
  border-radius: var(--radius-pill);
  /* D1: on a jewel the unfilled track is translucent white, not ink */
  background: var(--track);
}
/* THE MEAL'S NAME. Breakfast, Lunch, Dinner - what somebody actually calls the
   thing, rather than a segment they have to count along to identify. The word
   comes from src/domain/day-progress.js, which refuses to print two identical
   names and falls back to clock times when the day's rhythm would produce them.

   IT WRAPS RATHER THAN CLIPPING. Five meals across a 375px phone give each
   segment about 60px, and "Breakfast" does not fit on one line of that: with
   nowrap and an ellipsis it reads "Breakf...", which is the truncation the
   brief rules out by name. Two lines is fine; a clipped meal name is not. */
.dsp-nm {
  font-size: var(--text-caption);
  line-height: 1.25;
  color: var(--on-accent-soft);
  text-align: center;
  overflow-wrap: anywhere;
}
/* E6 §4.4: the EMPTY label wraps, because the nowrap rule above was written for
   a NUMBER ("a long number must not widen its own segment and squeeze its
   neighbours") and this is a sentence. At 360px with 120% text "Not logged" is
   7px wider than its segment and the ellipsis clipped it, which truncation
   caught. Two short lines is fine; a clipped word is not. */
.dsp-n.is-empty {
  white-space: normal;
  text-overflow: clip;
  overflow: visible;
  overflow-wrap: anywhere;
  color: var(--on-accent-soft);
  font-variant-numeric: normal;
}
.dsp-n {
  font-size: var(--text-caption);
  font-variant-numeric: tabular-nums;
  color: var(--on-accent-soft);
  text-align: center;
  /* a long number must not widen its own segment and squeeze its neighbours */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* THE MEAL HAPPENING NOW, which is the one the whole page is about. Marked on
   the NAME rather than by recolouring the bar: the bar already carries the
   meal's state (logged, reported, plate, todo) and a second meaning on the same
   pixel would make "current" and "on the plate" indistinguishable. */
.dsp-seg.is-now .dsp-nm {
  color: var(--on-accent);
  font-weight: 700;
}
/* the ring was --accent, royal blue, and it is the "stray bright blue progress
   outline" E6 §4.2 names. Aqua is the family's own focus light. */
.dsp-seg.is-now .dsp-bar {
  box-shadow:
    0 0 0 2px var(--surface),
    0 0 0 3px var(--aqua);
}
/* eaten and counted: solid, and the number is real */
/* E6 §4.2: Lagoon fill, not the royal blue */
.dsp-seg.is-logged .dsp-bar {
  /* D1 progress: mint-to-aqua fill; lagoon-ink would sink into the jewel */
  background: var(--grad-fill);
}
.dsp-seg.is-logged .dsp-n {
  color: var(--on-accent);
  font-weight: 650;
}
/* eaten and never entered. The dash IS the content: there is no number to show
   and inventing one is the thing this whole strip exists to refuse. */
.dsp-seg.is-reported .dsp-bar {
  background: repeating-linear-gradient(90deg, var(--accent-soft) 0 4px, transparent 4px 8px);
  box-shadow: inset 0 0 0 1px var(--accent-soft);
}
/* being built right now. Outlined rather than filled, because a plate is a plan
   and a filled segment would read as a meal already decided. */
.dsp-seg.is-plate .dsp-bar {
  background: var(--sky-300);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.dsp-seg.is-plate .dsp-n {
  color: var(--on-accent);
  font-weight: 650;
}
.dsp-seg.is-todo .dsp-bar {
  /* R1: on the maroon jewel the navy ramp tint read as dark slots cut into
     the card; a todo is an absence, so it wears the same whisper-light track
     as the base bar, one step dimmer */
  background: color-mix(in srgb, var(--track) 70%, transparent);
}
.dsp-line {
  margin: 10px 0 0;
  font-size: var(--text-caption);
  color: var(--on-accent-soft);
  font-variant-numeric: tabular-nums;
}
/* past the day's figure the sentence carries the weight, not a colour: this is
   a number somebody set for themselves, and a red bar is a verdict on dinner */
.dsp-line.is-over {
  color: var(--on-accent);
  font-weight: 600;
}

/* ---- THE SOURCED READ ------------------------------------------------------
   Measured against a target, with the document behind the number. Held apart
   from the pattern cards below it because it is a different KIND of claim: those
   describe somebody to themselves and rest on nothing but their own log, while
   this one brings in an outside figure and has to carry the source with it.
   A stronger left edge and a quieter body, so it reads as a reference rather
   than as a verdict. */
.gdn {
  margin: 0 0 clamp(16px, 2.4vh, 24px);
}
.gdn-h {
  margin: 0 0 clamp(10px, 1.4vh, 14px);
  font-size: var(--text-caption);
  font-weight: 750;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--foreground-muted);
}
.gdn-cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* min(..., 100%) for the reason spelled out on .lrn-cards, and this grid is
     where that defect was actually seen: the citation line was cut mid-source. */
  grid-template-columns: repeat(auto-fit, minmax(min(clamp(260px, 32vw, 420px), 100%), 1fr));
  gap: clamp(14px, 2vw, 22px);
}
/* the same orphan rule the pattern cards needed: two nutrients in a two-column
   grid is fine, one or three is not */
.gdn-card:last-child:nth-child(odd) {
  grid-column: 1 / -1;
}
.gdn-card {
  min-width: 0;
  padding: clamp(14px, 1.8vw, 18px) clamp(16px, 2vw, 22px);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--background-secondary);
}
.gdn-card > * {
  max-width: 62ch;
}
.gdn-head {
  margin: 0 0 6px;
  font-size: var(--text-ui);
  font-weight: 700;
  color: var(--foreground);
}
.gdn-ev {
  margin: 0 0 8px;
  font-size: var(--text-caption);
  color: var(--foreground-muted);
}
/* the one line that says what to do about it */
.gdn-act {
  margin: 0 0 8px;
  font-size: var(--text-ui);
  color: var(--foreground);
}
/* THE CITATION, deliberately small and deliberately present. Small because it is
   not the message; present because a figure whose source is elsewhere is a
   figure the product is claiming as its own. */
.gdn-src {
  margin: 0;
  font-size: var(--text-caption);
  line-height: 1.4;
  color: var(--foreground-muted);
}
.gdn-note {
  margin: clamp(10px, 1.4vh, 14px) 0 0;
  font-size: var(--text-caption);
  color: var(--foreground-muted);
  max-width: 62ch;
}

/* ---- SECTION 2 OF TODAY: THE DAY'S MEALS ----------------------------------
   The strip, framed and given a heading, at the top of the page rather than only
   above the plate. It is here so the workspace's "one meal, sized to what is
   left of today" is read with what is left of today directly above it. */
.dtoday {
  margin-top: clamp(12px, 1.8vh, 18px);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  padding-bottom: clamp(12px, 1.8vw, 16px);
}
.dtoday-hd {
  margin: 0;
  padding: clamp(12px, 1.8vw, 16px) clamp(16px, 2.4vw, 24px) 0;
  font-size: var(--text-caption);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--foreground-muted);
}
/* the strip's own top padding is there to clear the plate card's header; under
   a heading of its own that gap is a second one, so it closes */
.dtoday .dsp {
  padding-top: 8px;
}
/* the §8 day-state note (a declared fast): quiet, above the strip, because it
   changes how the numbers under it should be read */
.dtoday-note {
  margin: 8px 0 0;
  padding: 0 clamp(16px, 2.4vw, 24px);
  font-size: var(--text-ui);
  color: var(--foreground-muted);
}

/* ---- SECTION 6: THE SECONDARY FOLD ----------------------------------------
   How the product works, folded away. It was a full band across the page above
   the learning read, which put a marketing explainer between a returning person
   and their own food. Closed by default: somebody on their fortieth visit does
   not need the three steps restated, and somebody on their first can open it. */
.na-next-fold {
  margin-top: clamp(12px, 1.8vh, 18px);
}
.na-next-fold > summary {
  cursor: pointer;
  padding: 10px 4px;
  font-size: var(--text-ui);
  font-weight: 600;
  color: var(--foreground-muted);
}
.na-next-fold > summary:hover {
  color: var(--foreground);
}

/* ---- THE MEAL WORKSPACE ---------------------------------------------------
   One surface, three modes, and the two things that are actions.

   WHAT IT REPLACED. Three large cards presenting themselves as three products.
   They were three answers to one question - what am I doing with food right
   now - and giving them equal weight made the standing plan, which somebody
   sets up once and never opens again, the same size as the thing they use
   every day.

   THE MODES ARE THE SAME SIZE AS EACH OTHER and none of them is the size of the
   old cards. They are a choice of what you are doing, not three destinations
   being sold, so they get a segmented row rather than three pieces of
   illustrated real estate.

   THE ACTIONS SIT BELOW, quieter, in the order that says what they are: Pick
   for me, and then the same thing made standing. */
.ws {
  margin-top: clamp(16px, 2.4vh, 24px);
  padding: clamp(14px, 2vw, 20px);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background:
    radial-gradient(
      120% 90% at 6% 0%,
      color-mix(in srgb, var(--accent) 5%, transparent) 0%,
      transparent 60%
    ),
    var(--surface);
  box-shadow: var(--shadow-sm);
}
/* THE TWO DOORS (v3 §1): the home screen is exactly two ways in.
   Two peers, equal weight, side by side wherever two comfortable tap targets
   fit and stacked on phones. A grid of exactly two cannot strand a card the
   way the three-mode row this replaces once did at iPad portrait. Depth and
   press come from the same bevel/shadow tokens every raised control uses, so
   the doors feel like the buttons they are, not cards that happen to click. */
.doors {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(10px, 1.6vw, 16px);
  margin-top: clamp(10px, 1.4vh, 14px);
}
@media (min-width: 720px) {
  .doors {
    grid-template-columns: repeat(2, 1fr);
  }
}
.door {
  min-width: 0;
  text-align: left;
  display: grid;
  gap: 6px;
  padding: clamp(16px, 2.2vw, 24px);
  /* the CTA's blue-into-green at tint strength (owner's note: the home
     matches the colour combo as a LIGHTER gradient, never a flat fill); the
     bevel highlight over the sweep is what makes the tile read as glass.
     COLOR AND IMAGE SET SEPARATELY: the shorthand resets background-color
     to transparent, and s08's grounded-door guard (rightly) refuses to
     measure text contrast against a control that paints no ground. */
  /* E6 §3: THE DOORS ARE THE BRIGHTEST SURFACES ON THE PAGE. Importance maps to
     luminance, so the primary tier lives here and nowhere dimmer. Measured
     1.366:1 above the card tier and 2.24:1 above the field, which is what keeps
     them from sinking into the panel behind them in a grayscale capture. */
  color: var(--on-accent);
  cursor: pointer;
  /* rim first, then the cast, then the door's own colour glow (E6 §2) */
  transition:
    border-color var(--motion-fast) var(--ease-standard),
    background-color var(--motion-fast) var(--ease-standard),
    box-shadow var(--motion-fast) var(--ease-standard),
    transform var(--motion-release) var(--ease-spring);
}
.door b {
  /* R1: the doors are the room's two big handles; they take the small
     readout size instead of UI size so they read from across the room */
  font-size: var(--text-readout-sm);
  font-weight: 700;
  letter-spacing: var(--track-tight);
}
.door small {
  font-size: var(--text-caption);
  line-height: var(--leading-body);
  color: var(--on-accent-soft);
}
.door:hover {
  border-color: var(--glass-rim);
  /* E6 §2: interactive glass BRIGHTENS ~6% under the hand. On a dark ladder the
     old rule (deepen) moved the door toward the panel behind it, which is the
     opposite of what hover should say. */
  background-image: var(--glass-primary-hover);
  transform: translateY(-1px);
  box-shadow: var(--glass-inset), var(--glass-cast), var(--glass-glow-primary);
}
.door:active {
  transform: translateY(1px) scale(0.985);
  box-shadow: var(--bevel-pressed);
  transition-duration: var(--motion-press);
  transition-timing-function: var(--ease-in);
}
.door:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* deliveries and preferences: reachable from the home, loud nowhere (v3 §1).
   Plain secondary buttons in a quiet row, never a third door-sized card. */
.door-quiet {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: clamp(10px, 1.4vh, 14px);
  padding-top: clamp(10px, 1.4vh, 14px);
  border-top: 1px solid var(--border);
}
/* the unfinished-plan count, on the mode that owns a multi-meal cart */
.ws-badge {
  justify-self: start;
  margin-top: 4px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--sky-200);
  color: var(--accent-text);
  font-size: var(--text-caption);
  font-weight: 700;
}
