/* Quota Vita Nutrition Coach — design system
   Warm cream ground, terracotta accent, one editorial serif for display.
   One stylesheet. No CSS is injected from JavaScript.

   Fonts are loaded from <link> in the document head, not with @import here.
   An @import inside a stylesheet cannot begin downloading until the stylesheet
   itself has arrived, so it puts the whole typeface behind a second round trip
   on the critical path. See head() in server/http.js. */

/* ---------------------------------------------------------------- tokens */

:root {
  color-scheme: light;
  /* Brand: #AC5C44 is the Quota Vita logotype terracotta, sampled from
     logo-horitzontal-quotaVita.png. The storefront is a white ground with
     near-black Inter text, so the Coach matches it. */
  --brand: #ac5c44;
  --brand-strong: #8f4a33;
  --brand-soft: #d9a494;
  --brand-tint: #f9ece7;

  --canvas: #eeebe4;
  --canvas-tint: #e6e2d9;
  --surface: #ffffff;
  --surface-sunk: #f3f0e9;

  --ink: #191614;
  --ink-soft: #6b625b;
  --ink-faint: #6f6862;

  --amber: #e8a33d;
  --sage: #2f6b4f;
  --plum: #6e2440;

  --line: rgba(25, 22, 20, 0.13);
  --line-strong: rgba(25, 22, 20, 0.26);

  /* Text that sits on a brand-filled surface. Hard-coded #fff worked only
     because the palette was light-only; the dark theme brightens the terracotta
     to stay visible on a dark ground, and white on that brighter fill falls
     below contrast. This token is the one place that decision is made. */
  --on-brand: #ffffff;

  /* Text and controls that sit on the landing photograph. Warmer and a shade
     brighter than --canvas on purpose: over a dark, warm image the shell cream
     goes grey, so the hero gets its own paper. It was written as the literal
     #fdfaf5 in twelve places across the hero block, which is why it is a token
     now — there is exactly one on-photograph cream and this is it. */
  --on-hero: #fdfaf5;

  /* The ground under the landing photograph, and — this is the part that was
     missing — the ground under the whole page while that photograph is on
     screen. See the note by `html[data-hero="1"]` at the end of this file. */
  --hero-ground: #241d18;
  --wash: rgba(43, 35, 32, 0.08);
  --topbar-bg: rgba(238, 235, 228, 0.88);
  --sheet-bg: rgba(255, 255, 255, 0.96);
  --scrim: rgba(43, 35, 32, 0.55);

  /* Tighter than before. Big soft corners read as a component library; the
     sites this was measured against sit between 6 and 14px and let the type
     and the spacing do the work. Pills stay pills — they are controls. */
  --radius-xs: 3px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* Structure comes from a hairline first and a shadow second. The previous
     shadow-2 spread 34px of soft brown under every card, which made everything
     hover a centimetre off the page and nothing look placed. */
  --ring: inset 0 0 0 1px rgba(43, 35, 32, 0.07);
  --shadow-1: 0 1px 1px rgba(43, 35, 32, 0.04), 0 2px 6px rgba(43, 35, 32, 0.05);
  --shadow-2: 0 1px 2px rgba(43, 35, 32, 0.05), 0 6px 16px rgba(43, 35, 32, 0.07);
  --shadow-3: 0 2px 4px rgba(43, 35, 32, 0.06), 0 12px 28px rgba(43, 35, 32, 0.10);

  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;
  --s8: 64px;

  --body: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* The display face is the single largest reason the Coach did or did not look
     like a product someone paid for. Setting --display to the UI sans meant
     every headline was Inter at 48px: correct, legible, and indistinguishable
     from an unstyled prototype. Fraunces is a warm optical-size serif — it
     belongs to food and to the Mediterranean, and it gives the page a voice at
     exactly the sizes where Inter has none. */
  --display: Fraunces, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;

  /* A type scale with real distance between steps. The old page ran a 3rem
     headline straight into 0.86rem body text with nothing in between, which
     reads as a template rather than a hierarchy. */
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.375rem;
  --text-2xl: 1.75rem;
  --text-3xl: clamp(2rem, 4.4vw, 2.6rem);
  --text-4xl: clamp(2.4rem, 6vw, 3.4rem);

  --topbar-h: 60px;
  /* What the header actually occupies on screen: its own height plus whatever
     the device puts above it. Anything that has to clear the header measures
     from this, not from --topbar-h. */
  --topbar-total: calc(var(--topbar-h) + env(safe-area-inset-top, 0px));
  --tabbar-h: 64px;
  --measure: 62ch;

  /* One column, one gutter, one edge — declared here so no box has to guess.
     --column is the content column every view is centred in; --gutter is the
     inset from that column to the type; --edge is the distance from the window
     to the type, which is the column's own margin plus the gutter, and never
     less than the gutter itself. The 100% resolves against whatever box uses
     it, so --edge only makes sense on boxes that span the window (.topbar and
     .hero do). Narrow phones change --gutter alone and everything follows. */
  --column: 1200px;
  --gutter: var(--s4);
  --edge: max(var(--gutter), calc((100% - var(--column)) / 2 + var(--gutter)));

  /* Compatibility aliases. The server-rendered intro styles in api/index.js
     still reference the older names; keep them resolving to the brand palette. */
  --shell: var(--canvas);
  --shell-raised: var(--canvas-tint);
  --shell-sunk: var(--canvas-tint);
  --card: var(--surface);
  --card-sunk: var(--surface-sunk);
  --on-shell: var(--ink);
  --on-shell-soft: var(--ink-soft);
  --accent: var(--brand);
  --accent-strong: var(--brand-strong);
  --accent-soft: var(--brand-strong);
  --logged: var(--sage);
  --gold: var(--amber);
  --line-on-shell: var(--line);
}

/* ------------------------------------------------------------------ base */

* { box-sizing: border-box; }

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

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--shell);
  color: var(--on-shell);
  font: 16px/1.55 var(--body);
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--display);
  /* Fraunces is an optical-size variable face: at display sizes it wants the
     higher optical axis and a touch less weight than a UI sans would. */
  font-optical-sizing: auto;
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1.06;
  text-wrap: balance;
}

h3, h4 { font-weight: 600; letter-spacing: -0.005em; line-height: 1.2; }

p { margin: 0 0 var(--s3); }
p:last-child { margin-bottom: 0; }
ul { margin: 0; padding: 0; list-style: none; }
img { max-width: 100%; display: block; }
a { color: inherit; }

button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.hidden { display: none !important; }
[hidden] { display: none !important; }

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

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

.main {
  flex: 1;
  width: 100%;
  padding: var(--s5) var(--s4) calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--s6));
  background:
    radial-gradient(120% 60% at 50% 0%, rgba(172, 92, 68, 0.07), transparent 62%),
    radial-gradient(90% 55% at 100% 100%, rgba(232, 163, 61, 0.09), transparent 60%);
}

@media (min-width: 900px) {
  .main {
    padding: var(--s6) var(--s6) var(--s8);
  }
}

.view {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
}

.view-head {
  margin: 0 0 var(--s5);
  max-width: var(--measure);
}

/* The old head ran to 3.1rem, and on a phone that pushed the first meal —
   the thing anyone opened the Coach to see — below the fold, behind an eyebrow,
   a headline and a lead. The title still sets the tone; it no longer owns the
   first screen. */
.view-head h1,
.view-head h2 {
  font-size: var(--text-3xl);
  margin: 0 0 var(--s2);
}

.view-lead {
  margin: 0;
  color: var(--ink-soft);
  font-size: var(--text-base);
  text-wrap: pretty;
}

.eyebrow {
  margin: 0 0 var(--s2);
  color: var(--ink-faint);
  font-family: var(--body);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.meta {
  color: var(--ink-soft);
  font-size: 0.86rem;
  line-height: 1.45;
}

.card .meta a { color: var(--brand-strong); }

/* ---------------------------------------------------------------- topbar */

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--topbar-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  padding-top: env(safe-area-inset-top);
}

.topbar-inner {
  display: flex;
  align-items: center;
  min-width: 0;
  gap: var(--s3);
  width: 100%;
  max-width: var(--column);
  min-height: var(--topbar-h);
  margin: 0 auto;
  padding: var(--s2) var(--gutter);
}

.brand {
  display: flex;
  align-items: center;
  min-width: 0;
  overflow: hidden;
  min-height: 44px;
  gap: var(--s2);
  margin-right: auto;
  color: var(--brand);
  font-family: var(--display);
  font-size: 1.06rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
}

.brand-mark {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  background: var(--brand);
  color: var(--on-brand);
  font-family: var(--body);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.02em;
}

.brand em {
  font-style: normal;
  font-weight: 500;
  color: var(--ink-soft);
}

.topnav { display: none; }

@media (min-width: 900px) {
  .topnav {
    display: flex;
    gap: var(--s1);
    margin: 0 var(--s3) 0 0;
  }

  .topnav-link {
    min-height: 40px;
    padding: 0 var(--s4);
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--ink-soft);
    font-size: 0.9rem;
    font-weight: 700;
    cursor: pointer;
  }

  .topnav-link:hover { color: var(--ink); background: var(--surface-sunk); }
  .topnav-link.is-active { background: var(--brand); color: var(--on-brand); }
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

.lang {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
}

.lang button {
  min-width: 44px;
  min-height: 36px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-soft);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  cursor: pointer;
}

.lang button.is-active {
  background: var(--ink);
  color: var(--surface);
}

.icon-button {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

.icon-button:hover { background: var(--surface-sunk); }
.icon-button[aria-expanded="true"] { background: var(--ink); color: var(--surface); }

/* --------------------------------------------------------- overflow menu */

.overflow-menu {
  position: absolute;
  right: var(--s4);
  top: calc(100% - 4px);
  z-index: 40;
  width: min(300px, calc(100vw - var(--s6)));
  /* Taller than a small phone, so it scrolls inside itself rather than
     running its last items off the bottom of the screen. */
  max-height: calc(100dvh - var(--topbar-h) - env(safe-area-inset-top) - 28px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--s2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow-2);
  /* The menu grows with the app. Cap it against the viewport so the last group
     is reachable instead of falling off the bottom of a short screen. */
  max-height: calc(100dvh - var(--topbar-h) - var(--tabbar-h) - var(--s4));
  overflow-y: auto;
  overscroll-behavior: contain;
}

.overflow-menu h2 {
  margin: var(--s3) var(--s3) var(--s1);
  color: var(--brand-strong);
  font-family: var(--body);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.overflow-menu h2:first-child { margin-top: var(--s2); }

.menu-item {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: var(--s2) var(--s3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font-size: 0.94rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.menu-item:hover { background: var(--card-sunk); }
a.menu-item { box-sizing: border-box; text-decoration: none; line-height: 28px; }
.menu-item--danger { color: var(--brand-strong); }

/* ---------------------------------------------------------------- tabbar */

.tabbar {
  position: fixed;
  inset: auto 0 0;
  z-index: 30;
  display: flex;
  justify-content: space-around;
  gap: var(--s1);
  padding: var(--s1) var(--s2) calc(var(--s1) + env(safe-area-inset-bottom));
  background: var(--sheet-bg);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
}

.tab {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 56px;
  padding: var(--s1);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-faint);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
}

/* An inline SVG carries no intrinsic size. Inside a flex row it stretches to
   whatever space is available, which is how a 16px sync glyph rendered as a
   half-screen graphic on Today. Six of the seven icons in the app ship without
   a class, so the default belongs here rather than on each call site; the
   contexts below that want another size are more specific and still win. */
svg { width: 1.1em; height: 1.1em; flex: none; }

.tab svg { width: 22px; height: 22px; }
.tab.is-active { color: var(--brand); }
.tab.is-active svg { stroke-width: 2.2; }

@media (min-width: 900px) {
  .tabbar { display: none; }
  .main { padding-bottom: var(--s8); }
  .overflow-menu { max-height: calc(100dvh - var(--topbar-h) - var(--s5)); }
}

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

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: var(--s2) var(--s5);
  border: 1px solid var(--brand);
  border-radius: var(--radius-pill);
  background: var(--brand);
  color: var(--on-brand);
  font-size: 0.94rem;
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
}

.button:hover { background: var(--brand-strong); border-color: var(--brand-strong); }
.button:active { transform: translateY(1px); }

.button[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.button.quiet {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ink);
}

.button.quiet:hover { background: var(--card-sunk); border-color: var(--ink-soft); }

.on-shell .button.quiet,
.view > .actions .button.quiet {
  border-color: var(--line-strong);
  background: var(--surface);
  color: var(--ink);
}

.on-shell .button.quiet:hover,
.view > .actions .button.quiet:hover { background: var(--surface-sunk); }

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: var(--s4) 0 0;
}

/* ----------------------------------------------------------------- cards */

.card {
  padding: clamp(18px, 2.4vw, 30px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  box-shadow: none;
}

.card h2, .card h3 { color: var(--ink); }
.card .eyebrow { color: var(--ink-faint); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  background: var(--card-sunk);
  color: var(--ink-soft);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

.chip--logged { background: rgba(47, 107, 79, 0.13); color: var(--sage); }
.chip--restaurant { background: var(--brand-tint); color: var(--brand-strong); }
.chip--skipped { background: var(--wash); color: var(--ink-soft); }
.chip--live { background: rgba(232, 163, 61, 0.2); color: #8a6413; }

.status {
  margin: var(--s3) 0 0;
  padding: var(--s3) var(--s4);
  border-radius: var(--radius-sm);
  background: rgba(232, 163, 61, 0.15);
  border-left: 4px solid var(--amber);
  color: var(--ink);
  font-size: 0.9rem;
}

.status.error {
  background: var(--brand-tint);
  border-left-color: var(--brand);
  color: var(--brand-strong);
}

/* ------------------------------------------------------------ onboarding */

.view--setup { max-width: 760px; }
.view--progress { max-width: 820px; }
.view--setup .view-head { max-width: none; }

.setup { width: 100%; }

.stepper {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin: 0 0 var(--s4);
}

.stepper-track {
  position: relative;
  flex: 1;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunk);
  overflow: hidden;
}

.stepper-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: var(--radius-pill);
  background: var(--brand);
  transition: width 0.3s ease;
}

.stepper-label {
  color: var(--ink-soft);
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.chat {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s3);
  padding: clamp(16px, 2.2vw, 26px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  box-shadow: none;
}

.bubble {
  max-width: 90%;
  padding: var(--s3) var(--s4);
  border-radius: var(--radius-md);
  background: var(--card-sunk);
  color: var(--ink);
  font-size: 0.98rem;
}

.bubble .meta { display: block; margin-top: var(--s1); }
.bubble strong { font-weight: 700; }

.bubble.user {
  margin-left: auto;
  background: var(--brand);
  color: var(--on-brand);
  border-bottom-right-radius: var(--radius-xs);
}

.bubble.user .meta { color: color-mix(in srgb, var(--on-brand) 82%, transparent); }
.bubble.coach { border-bottom-left-radius: var(--radius-xs); }

.bubble.coach-intro {
  background: linear-gradient(135deg, var(--brand-tint), rgba(232, 163, 61, 0.16));
}

.bubble.full-card {
  max-width: 100%;
  background: transparent;
  padding: 0;
}

.composer {
  margin-top: var(--s1);
  padding: var(--s3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.composer-label {
  display: block;
  margin-bottom: var(--s1);
  color: var(--brand-strong);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.keyboard-hint {
  margin: 0 0 var(--s2);
  color: var(--ink-soft);
  font-size: 0.8rem;
}

@media (hover: none) {
  .keyboard-hint { display: none; }
  .shortcut-key { display: none; }
}

.quick-replies {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s2);
}

@media (min-width: 560px) {
  .quick-replies { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
}

.quick-replies button {
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: 100%;
  min-height: 52px;
  padding: var(--s2) var(--s4);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--card);
  color: var(--ink);
  font-size: 0.96rem;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

.quick-replies button:hover {
  border-color: var(--brand);
  background: var(--brand-tint);
}

.shortcut-key {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  border-radius: var(--radius-xs);
  background: var(--card-sunk);
  color: var(--ink-soft);
  font: 700 0.72rem/1 var(--body);
}

.chat-input {
  display: flex;
  gap: var(--s2);
  align-items: center;
}

.chat-input input {
  flex: 1;
  width: 100%;
  min-width: 0;
  min-height: 46px;
  padding: 0 var(--s3);
  border: 0;
  background: transparent;
  font-size: 1rem;
}

.chat-input input:focus { outline: none; }
.chat-input:focus-within { border-color: var(--brand); }

.field {
  display: grid;
  gap: var(--s1);
  font-size: 0.86rem;
  font-weight: 700;
  color: var(--ink);
}

.field input:not([type="checkbox"]),
.field select {
  min-height: 46px;
  padding: 0 var(--s3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-weight: 500;
}

.field small { color: var(--ink-soft); font-weight: 400; }

.consent-row {
  display: flex;
  gap: var(--s2);
  align-items: flex-start;
  margin: var(--s3) 0;
  color: var(--ink);
  font-size: 0.86rem;
  line-height: 1.45;
}

.consent-row input { margin-top: 3px; width: 20px; height: 20px; flex: 0 0 auto; }

.privacy {
  margin: var(--s5) 0 0;
  color: var(--ink-faint);
  font-size: 0.8rem;
}

.card .privacy { color: var(--ink-soft); }

/* ----------------------------------------------------------------- today */

.today { display: block; }
.today-aside { display: none; }

@media (min-width: 900px) {
  .today {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 330px;
    align-items: start;
    gap: var(--s5);
  }

  .today-aside {
    display: block;
    position: sticky;
    top: calc(var(--topbar-h) + var(--s4));
    align-self: start;
  }
}

.target {
  position: sticky;
  top: calc(var(--topbar-h) + env(safe-area-inset-top));
  z-index: 20;
  display: grid;
  gap: var(--s3);
  margin: 0 0 var(--s4);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  box-shadow: none;
  transition: padding 0.2s ease, gap 0.2s ease, border-radius 0.2s ease, box-shadow 0.2s ease;
}

/* ---------------------------------------------------------- stuck summary */

/* At rest this card is the day in full. Stuck to the top of a phone screen it
   was 330px of opaque white sitting over the meal list, slicing meal titles in
   half as they scrolled underneath — the single worst thing on the page. Stuck,
   it now collapses to one line: the number, the macros, nothing else. */

.target.is-stuck {
  gap: var(--s2);
  padding: var(--s2) var(--s3);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-3);
}

.target.is-stuck .target-figure b { font-size: var(--text-xl); }
.target.is-stuck .target-label,
.target.is-stuck .target-foot,
.target.is-stuck .chip { display: none; }
/* Collapsed, the row was showing a bare "0" with no label and no target: three
   naked zeroes that read as a rendering fault. One initial and the target keep
   it a sentence — P 0/122g — in the same width. */
.target.is-stuck .macro-row-label {
  overflow: hidden;
  max-width: 1.1ch;
  color: var(--ink-faint);
  white-space: nowrap;
}
.target.is-stuck .bar { height: 4px; }

.target.is-stuck .target-macros {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s3);
}

.target.is-stuck .macro-row {
  grid-template-columns: 1.4ch minmax(0, 1fr) auto;
  gap: 5px;
  font-size: var(--text-xs);
}

.target.is-stuck .macro-target { color: var(--ink-faint); }

@media (min-width: 900px) {
  .target.is-stuck { grid-template-columns: minmax(120px, auto) minmax(0, 1fr); }
}

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

.target-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
}

.target-figure {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
}

.target-figure b {
  font-family: var(--display);
  font-size: clamp(3.1rem, 12vw, 4.4rem);
  font-weight: 500;
  line-height: 0.92;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}

.target-figure span {
  color: var(--ink-soft);
  font-size: 0.82rem;
  font-weight: 700;
}

.target-label {
  color: var(--ink-faint);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.target-macros {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s2);
}

/* The target sits in a column whose width depends on whether the Coach panel is
   beside it, so it is sized against its container, not the window. Judging it by
   viewport width crushed the three macro rows into each other on a desktop that
   was wide overall but narrow here. */
.today-main { container-type: inline-size; container-name: plan; }

/* Macros share a row only when each one has room to be read. */
@container plan (min-width: 560px) {
  .target-macros { grid-template-columns: repeat(auto-fit, minmax(165px, 1fr)); gap: var(--s2) var(--s5); }
}

/* Wider still, the whole card becomes one summary bar. */
@container plan (min-width: 760px) {
  .target {
    grid-template-columns: minmax(160px, auto) minmax(0, 1fr);
    align-items: center;
    column-gap: var(--s6);
  }

  .target-head { grid-column: 1; grid-row: 1; display: block; }
  .target-head .chip { margin-top: var(--s2); }
  .target-macros { grid-column: 2; grid-row: 1; }
  .target-foot { grid-column: 1 / -1; grid-row: 2; }
}

/* Browsers without container queries keep the readable stacked layout. */
@supports not (container-type: inline-size) {
  @media (min-width: 1200px) {
    .target-macros { grid-template-columns: repeat(3, minmax(165px, 1fr)); gap: var(--s2) var(--s5); }
  }
}

.macro-row {
  display: grid;
  /* Sized to the longest label rather than a fixed 68px, which clipped
     "Carbohidrats" to "Carbohidrat…" in Catalan. */
  grid-template-columns: minmax(max-content, auto) minmax(24px, 1fr) auto;
  align-items: center;
  gap: var(--s2);
  font-size: 0.82rem;
}

.macro-row-label { color: var(--ink-soft); font-weight: 700; white-space: nowrap; }
.macro-row b { font-variant-numeric: tabular-nums; white-space: nowrap; }
.macro-row .macro-eaten { font-weight: 800; }
.macro-row .macro-target { color: var(--ink-faint); font-weight: 600; }

/* Reaching a macro is the good outcome; overshooting it is information, not a
   failure, so it warms rather than alarms. Colour is never the only signal —
   the pair of numbers beside the bar says the same thing. */
.macro-row.is-met .macro-eaten { color: var(--sage); }
.macro-row.is-over .macro-eaten { color: var(--amber); }

.bar {
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunk);
  box-shadow: var(--ring);
  overflow: hidden;
}

.bar i {
  display: block;
  height: 100%;
  min-width: 0;
  border-radius: var(--radius-pill);
  background: var(--brand);
  transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1), background-color 0.3s ease;
}

.bar--carbs i { background: var(--amber); }
.bar--fat i { background: var(--ink-faint); }

.macro-row.is-met .bar i { background: var(--sage); }
.macro-row.is-over .bar i { background: var(--amber); }

@media (prefers-reduced-motion: reduce) {
  .bar i { transition: none; }
}

.target-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
}

.target-foot .meta { flex: 1; min-width: 120px; }

.link-button {
  min-height: 44px;
  padding: 0 var(--s3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}

.link-button:hover { background: var(--card-sunk); }

/* ------------------------------------------------------------ meal cards */

.meal-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s4);
}

.meal-card {
  display: grid;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  box-shadow: none;
  scroll-margin-top: calc(var(--topbar-h) + 140px);
}

@media (min-width: 620px) {
  .meal-card { grid-template-columns: minmax(200px, 34%) minmax(0, 1fr); }
  .meal-media { height: 100%; }
}

.meal-media {
  position: relative;
  background: var(--card-sunk);
}

.meal-image {
  width: 100%;
  height: clamp(210px, 56vw, 330px);
  object-fit: cover;
}

@media (min-width: 620px) {
  .meal-image { height: 100%; min-height: 260px; }
}

.meal-body {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: clamp(16px, 2.2vw, 26px);
}

.meal-slot {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin: 0;
  color: var(--ink-faint);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.meal-title {
  margin: var(--s1) 0 0;
  font-size: clamp(1.3rem, 3.4vw, 1.7rem);
}

.macros {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: 0;
  padding: var(--s2) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.macros > div {
  display: flex;
  align-items: baseline;
  gap: 5px;
}

.macros dt {
  color: var(--ink-soft);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.macros dd {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.macros .macro--protein dd { color: var(--brand-strong); font-size: 1.05rem; }
.macros > div + div { padding-left: var(--s2); border-left: 1px solid var(--line); }

.meal-portions { margin: 0; font-size: 0.98rem; }
.meal-catalan { margin: 0; color: var(--ink-soft); font-size: 0.86rem; }
.meal-catalan strong { color: var(--ink); }

.milkshake {
  display: flex;
  flex-wrap: wrap;
  gap: 3px var(--s2);
  align-items: baseline;
  margin: 0;
  padding: var(--s2) var(--s3);
  border: 1px solid rgba(232, 163, 61, 0.5);
  border-radius: var(--radius-sm);
  background: rgba(232, 163, 61, 0.14);
  font-size: 0.84rem;
}

.milkshake strong { color: var(--brand-strong); }

.meal-actions {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s2);
  margin-top: auto;
  padding-top: var(--s2);
}

.meal-actions .button {
  width: 100%;
  padding: var(--s2) var(--s1);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

.meal-actions .button:only-child { grid-column: 1 / -1; }

.meal-card[data-status="eaten"] .meal-media,
.meal-card[data-status="restaurant"] .meal-media { filter: saturate(0.75); }

.meal-card[data-status="skipped"] { opacity: 0.72; }

.detail-toggle {
  display: none;
  align-self: flex-start;
  min-height: 40px;
  padding: 0 var(--s3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
}

.compact-view .detail-toggle { display: inline-flex; align-items: center; }
.compact-view .meal-card.is-collapsed .meal-media,
.compact-view .meal-card.is-collapsed .meal-details,
.compact-view .week-day.is-collapsed .weekly-card-visual,
.compact-view .week-day.is-collapsed .week-day-details { display: none; }
.compact-view .meal-card.is-collapsed { grid-template-columns: 1fr; }

.meal-details {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  flex: 1;
}

/* ------------------------------------------------------------ live coach */

.live-coach {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  box-shadow: none;
}

.live-coach-thread {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* Rows sized to their own text, packed at the top. A grid row stretches by
     default, so with the four suggested openings gone the single opening line
     became one bubble tall enough to fill the whole card — three lines of type
     in a 234px beige box. Packing at the *bottom* would read more like a chat,
     but `align-content: end` on a scroll container clips the top of the content
     once it overflows, and this one overflows as soon as a conversation
     starts. */
  align-content: start;
  gap: var(--s2);
  max-height: 46vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.live-coach .bubble { max-width: 100%; font-size: 0.92rem; }
.live-coach .composer { margin: 0; }

/* The Coach page is a conversation, not a document: it fills the space between
   the header and the tab bar, the thread scrolls inside itself, and the box you
   type into never leaves the screen. */
.view--coach {
  display: flex;
  flex-direction: column;
  height: calc(100dvh - var(--topbar-h) - var(--tabbar-h) - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 64px);
  min-height: 340px;
}

.view--coach .view-head { flex: 0 0 auto; margin-bottom: var(--s2); }
.coach-head-actions { flex: 0 0 auto; display: flex; margin: 0 0 var(--s3); }
.view--coach .live-coach { flex: 1 1 auto; min-height: 0; }
.view--coach .live-coach-thread { flex: 1 1 auto; min-height: 0; max-height: none; overflow-y: auto; }
.view--coach .live-coach .composer { flex: 0 0 auto; }
.view--coach .live-coach > .meta { flex: 0 0 auto; }

@media (max-width: 619px) {
  .view--coach .view-lead { display: none; }
  .view--coach .live-coach > .meta { display: none; }
  .view--coach .view-head h1 { font-size: 1.7rem; }
}

@media (min-width: 900px) {
  .view--coach { height: calc(100dvh - var(--topbar-h) - 104px); max-height: 820px; }
}

/* ------------------------------------------------------------ week + basket */

.week-grid {
  display: grid;
  gap: var(--s4);
  grid-template-columns: 1fr;
}

@media (min-width: 620px) { .week-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .week-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1400px) { .week-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.week-day {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  box-shadow: none;
}

.week-day > *:not(.weekly-card-visual) { padding-inline: var(--s4); }
.week-day > *:last-child { padding-bottom: var(--s4); }

.week-day h3 {
  margin-top: var(--s3);
  font-size: 1.25rem;
}

.week-day p { font-size: 0.9rem; }
.weekly-meal-image { width: 100%; height: clamp(140px, 24vw, 190px); object-fit: cover; }

.week-day-details { display: grid; gap: var(--s2); }
.week-day-details .meta { display: block; }

.basket {
  display: grid;
  gap: var(--s1);
  margin: 0 0 var(--s4);
}

@media (min-width: 620px) { .basket { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s1) var(--s5); } }

.basket li {
  display: flex;
  gap: var(--s2);
  align-items: baseline;
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.96rem;
}

.basket li strong {
  min-width: 62px;
  font-variant-numeric: tabular-nums;
}

.basket-costs { margin-top: var(--s4); }
.basket-costs h3 { font-size: 1.2rem; margin-bottom: var(--s1); }

.basket-costs ul { display: grid; gap: var(--s1); margin: var(--s3) 0; }

.basket-costs li,
.basket-costs-total {
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s1) 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.9rem;
}

.basket-costs-total {
  border-bottom: 0;
  border-top: 2px solid var(--ink);
  margin-top: var(--s2);
  padding-top: var(--s2);
  font-size: 1rem;
}

.basket-costs-disclaimer { color: var(--ink-soft); font-size: 0.8rem; }

.segmented {
  display: inline-flex;
  gap: 2px;
  margin: 0 0 var(--s4);
  padding: 3px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
}

.segmented button {
  min-height: 40px;
  padding: 0 var(--s4);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-soft);
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
}

.segmented button.is-active { background: var(--brand); color: var(--on-brand); }

/* ------------------------------------------------------------ methodology */

.method {
  margin: var(--s5) 0 0;
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--canvas-tint);
  color: var(--ink-soft);
  font-size: 0.86rem;
}

.method summary {
  color: var(--ink);
  font-weight: 700;
  cursor: pointer;
}

.method ul { margin: var(--s3) 0 0; padding-left: var(--s5); list-style: disc; }
.method li { margin-bottom: var(--s2); }
.method a { color: var(--brand-strong); }
.method strong { color: var(--ink); }

/* ---------------------------------------------------------------- modals */

.modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: var(--s4);
  background: var(--scrim);
  backdrop-filter: blur(4px);
  overflow-y: auto;
}

.modal-dialog {
  width: min(560px, 100%);
  max-height: calc(100dvh - var(--s7));
  overflow-y: auto;
  padding: clamp(20px, 3vw, 32px);
  border-radius: var(--radius-lg);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow-2);
}

.modal-dialog h2, .modal-dialog h3 { margin-bottom: var(--s2); font-size: 1.5rem; }
.modal-dialog video { width: 100%; border-radius: var(--radius-sm); background: var(--ink); }

body.modal-open { overflow: hidden; }

/* --------------------------------------------------------------- utility */

.loading-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s3);
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--brand-tint);
  color: var(--brand-strong);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}

.loading-chip::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--brand);
  animation: pulse 1.4s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 0.3; transform: scale(0.8); }
  50% { opacity: 1; transform: scale(1.15); }
}

@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;
  }
}

@media print {
  .topbar, .tabbar, .actions, .method, .modal { display: none !important; }
  body { background: var(--surface); color: #000; }
  .card, .meal-card, .week-day { box-shadow: none; border: 1px solid #ccc; }
}

/* ------------------------------------------------------ stacks + scores */

.stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s4); }

.scoreboard {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: var(--s3);
}

.score {
  display: grid;
  gap: 2px;
  padding: var(--s3);
  border-radius: var(--radius-sm);
  background: var(--card-sunk);
}

.score b {
  font-family: var(--display);
  font-size: 1.7rem;
  font-weight: 500;
  line-height: 1;
}

.score span { color: var(--ink-soft); font-size: 0.78rem; }

.tab-label { display: block; }

.today-main > .privacy { margin-top: var(--s5); }

/* Today's head is compact: the target card is the thing worth reading first. */
.view--today .view-head { margin-bottom: var(--s4); }
.view--today .view-head h1 { font-size: clamp(1.7rem, 5vw, 2.6rem); }

@media (max-width: 619px) {
  .view--today .view-head .view-lead { display: none; }
}

.view--coach .live-coach > .eyebrow { display: none; }

/* ------------------------------------------------------------------ shop */
/* The protein swap and the basket are where buying intent is highest, so the
   checkout sits inside them rather than in a separate store page. */

.shop-offer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) var(--s2);
  align-items: center;
  width: 100%;
  margin-top: var(--s2);
  padding-top: var(--s2);
  border-top: 1px solid var(--accent-soft);
}

/* The offer, in the order it should be read. "50 batuts · 1,44 € el batut" is
   the argument for a 71,90 EUR tub, so it is set in the display face at the
   size the rest of the app gives a heading — and the tub's own name, which
   used to lead, is now the caption it always was. */
.shop-figure {
  width: 100%;
  font-family: var(--display);
  font-size: 1.28rem;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.shop-figure b { font-weight: 600; }

.shop-line {
  color: var(--ink-soft);
  font-size: 0.8rem;
}

/* The cheaper first order, kept visually below the recommendation rather than
   beside it: it is an alternative, not a competing headline. */
.shop-starter {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
  align-items: center;
  width: 100%;
}

.button.shop-buy {
  padding: var(--s1) var(--s3);
  font-size: 0.84rem;
  text-decoration: none;
}

/* The flavour row drops to its own line: it is a second decision, taken after
   the format one, and it should not compete with the buy button for the eye. */
.shop-flavours {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
  align-items: center;
  width: 100%;
}

.shop-flavours-label {
  color: var(--ink-soft);
  font-size: 0.76rem;
}

.shop-flavour {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35em;
  padding: 0.25em 0.7em;
  border: 1px solid var(--accent-soft);
  border-radius: var(--radius-pill);
  color: var(--brand-strong);
  font-size: 0.78rem;
  font-weight: 600;
  text-decoration: none;
}

.shop-flavour:hover,
.shop-flavour:focus-visible {
  background: var(--brand-tint);
}

.shop-flavour-price {
  font-weight: 400;
}

.shop-flavour-note {
  color: var(--ink-soft);
  font-size: 0.7rem;
  font-weight: 400;
}

.shop-block {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) var(--s3);
  align-items: baseline;
  margin-top: var(--s4);
  padding: var(--s4);
  border: 1px solid var(--accent-soft);
  border-radius: var(--radius-sm);
  background: rgba(224, 169, 75, 0.14);
}

/* Who made it, above what it costs. Set as an eyebrow rather than a sentence:
   three claims the reader is about to see again on the storefront, in the same
   words, so the handoff reads as one company. */
.shop-provenance {
  width: 100%;
  margin: 0 0 var(--s2);
  color: var(--brand-strong);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

/* The block is flex with `p { flex: 1 1 220px }`, so a plain <p> would try to
   share a line with the body copy. This one is its own row. */
.shop-block .shop-provenance { flex: 0 0 100%; }

.shop-block h2 {
  width: 100%;
  margin: 0;
  color: var(--accent-strong);
  font-size: 1.05rem;
}

/* The tub has had time to run out. Same block, one degree warmer — the accent
   fill goes from a wash to something that reads as a prompt, without becoming
   a different component or introducing a colour the palette does not have. */
.shop-block--reorder {
  border-color: var(--brand);
  background: var(--brand-tint);
}

.shop-block p {
  flex: 1 1 220px;
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.86rem;
}

.shop-block .shop-offer {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s3);
}

/* --------------------------------------------------------------- account */

.account-consent {
  display: flex;
  gap: var(--s2);
  align-items: flex-start;
  margin: var(--s4) 0;
  color: var(--ink-soft);
  font-size: 0.88rem;
  line-height: 1.45;
  cursor: pointer;
}

.account-consent input {
  flex: none;
  margin-top: 3px;
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
}

.account-state {
  margin: 0 0 var(--s2);
  color: var(--logged);
  font-weight: 600;
}

/* ------------------------------------------------------------- progress */

.streak-chips {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 40px;
  padding: 4px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

.streak-chips:hover { background: var(--surface-sunk); }

.streak-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.86rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.streak-chip--xp { color: var(--brand-strong); }

.xp-ring {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: conic-gradient(var(--brand) calc(var(--p) * 1%), var(--surface-sunk) 0);
  box-shadow: inset 0 0 0 3px var(--surface);
}

/* Between the tab bar breakpoint and a roomy desktop the header carries five
   nav items plus brand, streak, XP, language and menu: tighten it there. */
@media (min-width: 900px) and (max-width: 1199px) {
  .topbar-inner { gap: var(--s2); }
  .topnav-link { padding: 0 var(--s3); font-size: 0.84rem; }
  .lang button { min-width: 40px; }
}

/* The header holds brand, streak, XP and the menu. Below tablet the language
   switch moves into the overflow menu so nothing has to be squeezed. */
@media (max-width: 1099px) {
  .topbar-actions > .lang { display: none; }
  .topbar-inner { gap: var(--s2); }

  /* ...except on the landing and during setup, where there is no overflow menu
     to hide it in. Choosing your language is the first thing a Catalan speaker
     needs, and on a phone it was nowhere at all. */
  [data-hero="1"] .topbar-actions > .lang,
  [data-view="setup"] .topbar-actions > .lang { display: flex; }
}

.menu-lang { display: grid; }
.menu-lang .menu-item.is-active { background: var(--brand-tint); color: var(--brand-strong); font-weight: 800; }

@media (max-width: 420px) {
  .streak-chips { padding: 4px 8px; gap: 6px; }
  .streak-chip { font-size: 0.8rem; }
  .brand em { display: none; }
}

/* On the narrowest phones the mark carries the brand on its own, so the
   wordmark gives its width to the streak, search and menu controls. */
@media (max-width: 400px) {
  /* The logotype stays; "Coach" gives up its width to the streak, search and
     menu controls. */
  .brand em { display: none; }
  .brand { gap: 0; }
  /* The gutter tightens for every box at once — header and hero both read it,
     so neither can drift out of line with the other. An earlier fix wrote the
     hero's inset here directly and it never applied: .hero's own `padding`
     shorthand is declared 800 lines further down the file, and at equal
     specificity the later rule wins. The variable is not order-sensitive. */
  :root { --gutter: var(--s3); }
  .topbar-inner { gap: var(--s1); }
}

.level-card .level-head,
.goal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s3);
}

.level-name { font-size: 1.5rem; }

.level-xp,
.goal-count {
  font-size: 0.9rem;
  font-weight: 800;
  color: var(--brand-strong);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.level-card .bar,
.goal-head + .bar { height: 12px; margin-bottom: var(--s3); }

.streak-week {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--s1);
  margin: var(--s4) 0 var(--s3);
}

.streak-day {
  display: grid;
  gap: 4px;
  justify-items: center;
  padding: var(--s2) 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--canvas-tint);
  font-size: 0.7rem;
  font-weight: 800;
  color: var(--ink-faint);
  text-transform: uppercase;
}

.streak-day i { font-style: normal; font-size: 1rem; line-height: 1; min-height: 1rem; }
.streak-day.is-done { background: rgba(232, 163, 61, 0.18); border-color: rgba(232, 163, 61, 0.5); color: var(--ink); }
.streak-day.is-today { outline: 2px solid var(--brand); outline-offset: 1px; }

.quest-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2); }

.quest {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--canvas-tint);
}

.quest-check {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  border: 2px solid var(--line-strong);
  border-radius: 50%;
  color: var(--on-brand);
  font-size: 0.8rem;
  font-weight: 800;
}

.quest-text { flex: 1; font-size: 0.94rem; font-weight: 600; }
.quest-xp { color: var(--brand-strong); font-size: 0.8rem; font-weight: 800; white-space: nowrap; }
.quest.is-done { background: rgba(47, 107, 79, 0.1); border-color: rgba(47, 107, 79, 0.35); }
.quest.is-done .quest-check { background: var(--sage); border-color: var(--sage); }
.quest.is-done .quest-text { text-decoration: line-through; color: var(--ink-soft); }

.badge-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--s3);
}

.badge {
  display: grid;
  gap: 2px;
  justify-items: center;
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--canvas-tint);
  text-align: center;
  opacity: 0.5;
  filter: grayscale(1);
}

.badge.is-earned { opacity: 1; filter: none; background: var(--brand-tint); border-color: rgba(172, 92, 68, 0.35); }
.badge-icon { font-size: 1.7rem; line-height: 1.2; }
.badge strong { font-size: 0.86rem; }
.badge span { color: var(--ink-soft); font-size: 0.74rem; line-height: 1.3; }

/* -------------------------------------------------------- xp feedback */

.toast-host {
  position: fixed;
  inset: auto 0 calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 12px);
  z-index: 70;
  display: grid;
  justify-items: center;
  gap: var(--s2);
  pointer-events: none;
}

@media (min-width: 900px) { .toast-host { inset: auto 0 24px; } }

.toast {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s2);
  margin: 0;
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  /* Fixed rather than themed. `background: var(--ink)` inverted with the palette,
     so in dark mode the toast became a cream pill with cream-adjacent text and
     an amber highlight that no longer had a dark ground to sit on. A toast is a
     momentary overlay; it is allowed to be the same dark pill in both themes. */
  background: #221b17;
  color: #f7f1e8;
  box-shadow: var(--shadow-3), inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  font-size: var(--text-sm);
  animation: toast-in 0.28s ease both, toast-out 0.3s ease 2.2s both;
}

@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

.toast b { color: #f0b45f; font-weight: 800; }
.toast span { opacity: 0.85; font-size: 0.8rem; }

@keyframes toast-in { from { opacity: 0; transform: translateY(12px) scale(0.96); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(-8px); } }

.celebrate {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  background: var(--scrim);
  backdrop-filter: blur(3px);
  pointer-events: none;
  animation: fade-in 0.2s ease both;
}

.celebrate.is-leaving { animation: fade-in 0.35s ease reverse both; }

.celebrate-card {
  display: grid;
  gap: var(--s1);
  justify-items: center;
  padding: var(--s6) var(--s7);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-2);
  text-align: center;
  animation: pop 0.42s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}

.celebrate-icon { font-size: 3rem; line-height: 1; }
.celebrate-card strong { font-size: 1.3rem; }
.celebrate-card span:last-child { color: var(--ink-soft); font-size: 0.9rem; }

@keyframes fade-in { from { opacity: 0; } }
@keyframes pop { from { opacity: 0; transform: scale(0.82); } }

/* --------------------------------------------------------------- notices */

.notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3);
  margin: 0 0 var(--s4);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  box-shadow: none;
}

.notice > div { flex: 1 1 260px; display: grid; gap: 3px; }
.notice strong { color: var(--ink); font-size: 0.98rem; }
.notice span { color: var(--ink-soft); font-size: 0.88rem; line-height: 1.45; }
.notice .button { flex: 0 0 auto; }

.notice--care { border-color: rgba(179, 38, 30, 0.4); background: #fdf1ef; }
.notice--care strong { color: #8f2019; }
.notice--freeze { border-color: rgba(232, 163, 61, 0.5); background: rgba(232, 163, 61, 0.12); }
.notice--demo { border-color: rgba(172, 92, 68, 0.4); background: var(--brand-tint); }

.button--danger { background: #a3271f; border-color: #a3271f; }
.button--danger:hover { background: #8a1f18; border-color: #8a1f18; }

.plain-list { display: grid; gap: var(--s1); margin: var(--s3) 0; padding-left: 1.1em; list-style: disc; }
.plain-list li { color: var(--ink-soft); font-size: 0.92rem; }

/* ----------------------------------------------------------- quest strip */

.quest-strip {
  margin: 0 0 var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.quest-strip-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3); }
.quest-strip-head .label { color: var(--ink-faint); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
.quest-strip-count { font-size: 0.8rem; font-weight: 800; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.quest-strip ul { display: grid; gap: 6px; margin-top: var(--s2); }

.quest-strip li > button,
.quest-strip li > span {
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: 100%;
  min-height: 44px;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  font-size: 0.88rem;
  color: var(--ink);
  text-align: left;
}

.quest-strip li > button { cursor: pointer; }
.quest-strip li > button:hover { border-color: var(--brand); background: var(--brand-tint); }
.quest-strip li > button::after { content: "›"; margin-left: 2px; color: var(--brand-strong); font-weight: 700; }
.quest-strip li b { margin-left: auto; color: var(--brand-strong); font-size: 0.78rem; }
.quest-strip li .quest-check { flex: 0 0 auto; width: 20px; height: 20px; border-width: 1.5px; font-size: 0.66rem; }
.quest-strip li.is-done { color: var(--ink-soft); }
.quest-strip li.is-done > span > span:nth-child(2) { text-decoration: line-through; }
.quest-strip li.is-done .quest-check { background: var(--sage); border-color: var(--sage); }

.meal-card.is-flagged { outline: 3px solid var(--brand); outline-offset: 3px; }

/* ------------------------------------------------------------- day close */

.day-done {
  margin: 0 0 var(--s4);
  padding: clamp(18px, 2.4vw, 28px);
  border: 1px solid rgba(47, 107, 79, 0.35);
  border-radius: var(--radius-lg);
  background: linear-gradient(160deg, rgba(47, 107, 79, 0.1), var(--surface) 60%);
}

.day-done h2 { font-size: clamp(1.3rem, 3.6vw, 1.7rem); margin-bottom: var(--s3); text-wrap: balance; }

.day-done-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: var(--s3);
  margin: var(--s4) 0;
}

.day-done-stats div { display: grid; gap: 2px; }
.day-done-stats b { font-size: 1.8rem; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.day-done-stats span { color: var(--ink-soft); font-size: 0.78rem; }

.tomorrow { margin-top: var(--s4); padding-top: var(--s3); border-top: 1px solid var(--line); }
.tomorrow .label { display: block; margin-bottom: var(--s2); color: var(--ink-faint); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
.tomorrow-choices { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s2); }

.tomorrow-choice {
  min-height: 44px;
  padding: 0 var(--s4);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 0.86rem;
  font-weight: 700;
  cursor: pointer;
}

.tomorrow-choice:hover { border-color: var(--brand); background: var(--brand-tint); }
.tomorrow-choice.is-active { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }

/* ---------------------------------------------------------------- extras */

.skip-offer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.86rem;
}

.greeting {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin: 0 0 var(--s4);
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink);
}

.bubble--problem { background: var(--brand-tint); color: var(--brand-strong); font-weight: 600; }

.field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--s4); }

.landing { display: grid; gap: var(--s4); }
.view--landing { max-width: 780px; }
.landing-card h2 { font-size: 1.35rem; margin-bottom: var(--s3); }

.steps { display: grid; gap: var(--s3); margin: 0 0 var(--s4); padding: 0; counter-reset: step; }

.steps li {
  display: grid;
  gap: 2px;
  padding-left: 38px;
  position: relative;
  counter-increment: step;
}

.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 1px;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-pill);
  background: var(--brand-tint);
  color: var(--brand-strong);
  font-size: 0.8rem;
  font-weight: 800;
}

.steps strong { font-size: 1rem; }
.steps span { color: var(--ink-soft); font-size: 0.92rem; }

.week-day.is-today { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand-tint), var(--shadow-1); }
.chip--today { background: var(--brand); color: var(--on-brand); margin-left: var(--s2); vertical-align: middle; }
.week-day-use { align-self: flex-start; margin-bottom: var(--s1); }

.logged-toggle:not(:empty) { display: flex; justify-content: center; margin: 0 0 var(--s4); }
.show-logged { width: 100%; max-width: 420px; }

/* ================================================================= icons */

/* One inline SVG set replaces the emoji that used to stand in for icons.
   Sized in em so an icon always matches the text it sits beside, and stroked in
   currentColor so it inherits state — earned, done, brand — without a variant. */

.icon {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  vertical-align: -0.125em;
}

.badge-icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunk);
  color: var(--ink-faint);
  font-size: 1.35rem;
  line-height: 1;
}

.badge.is-earned .badge-icon {
  background: var(--brand);
  color: var(--on-brand);
}

.streak-chip .icon { font-size: 1rem; color: var(--amber); }
.greeting .icon { color: var(--amber); margin-right: 0.35em; }
.streak-day i { display: grid; place-items: center; font-size: 0.95rem; min-height: 1rem; color: var(--amber); }
.celebrate-icon { display: grid; place-items: center; font-size: 2.6rem; line-height: 1; color: var(--brand); }
.celebrate-icon .icon { stroke-width: 1.4; }

/* ========================================================== install offer */

/* Docked above the tab bar rather than dropped over the page: an install offer
   that covers the thing it is trying to get you to install is self-defeating. */

.install-bar {
  position: fixed;
  z-index: 40;
  left: max(var(--s3), env(safe-area-inset-left));
  right: max(var(--s3), env(safe-area-inset-right));
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--s3));
  display: flex;
  align-items: center;
  gap: var(--s3);
  max-width: 560px;
  margin: 0 auto;
  padding: var(--s3) var(--s3) var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--sheet-bg);
  backdrop-filter: blur(12px);
  box-shadow: var(--shadow-3);
  animation: install-rise 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Above 900px the tab bar is replaced by the top navigation, so reserving its
   height at the bottom parked the offer in the middle of the quest list. */
@media (min-width: 900px) {
  .install-bar {
    left: auto;
    right: var(--s5);
    bottom: var(--s5);
    width: min(420px, calc(100vw - var(--s6)));
    margin: 0;
  }
}

.install-bar-text { display: grid; gap: 2px; min-width: 0; }
.install-bar-text strong { font-size: var(--text-sm); }
.install-bar-text span { color: var(--ink-soft); font-size: var(--text-xs); line-height: 1.35; }
.install-bar-actions { display: flex; align-items: center; gap: var(--s2); margin-left: auto; }

@keyframes install-rise {
  from { opacity: 0; transform: translateY(12px); }
}

@media (prefers-reduced-motion: reduce) {
  .install-bar { animation: none; }
}

/* ------------------------------------------------------------- small bits */

.button--sm {
  min-height: 36px;
  padding: 0 var(--s3);
  font-size: var(--text-sm);
}

.icon-button--sm { width: 36px; height: 36px; min-height: 36px; }
.icon-button--sm svg { width: 16px; height: 16px; }

/* The storage line on the daily plan. Signed in it is reassurance; signed out
   it is an offer, and an offer needs to look clickable. */
.privacy--synced { display: flex; align-items: center; gap: 0.45em; color: var(--sage); }
.privacy--synced svg { width: 1.05em; height: 1.05em; }
.privacy--offer .privacy-action {
  color: var(--brand-strong);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}

/* ============================================================ goal chips */

/* The weekly goal opens with the answers people actually give, rather than a
   blank box. The Coach conversation used to do the same, with four suggested
   openings — but four chips that each want their own line took 200px out of a
   352px card on an iPhone SE, and the Coach's opening line had nothing left to
   render into. The field is the whole invitation there now. */

.goal-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: var(--s3) 0;
}

.goal-chip {
  min-height: 40px;
  padding: 0 var(--s4);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.16s ease, border-color 0.16s ease, transform 0.16s ease;
}

.goal-chip:hover { border-color: var(--brand); background: var(--brand-tint); }
.goal-chip:active { transform: translateY(1px); }
.goal-chip.is-active { border-color: var(--brand); background: var(--brand); color: var(--on-brand); }

@media (prefers-reduced-motion: reduce) {
  .goal-chip { transition: none; }
  .goal-chip:active { transform: none; }
}

/* ============================================================ danger zone */

/* An irreversible action does not belong in a row of ordinary ones, and does
   not get to wear the same quiet grey as "Back to today". */

.danger-zone {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--line);
}

.button.danger {
  border-color: color-mix(in srgb, var(--plum) 45%, transparent);
  background: transparent;
  color: var(--plum);
}

.button.danger:hover {
  border-color: var(--plum);
  background: color-mix(in srgb, var(--plum) 10%, transparent);
}

/* The marker watchTargetPanel() measures against. Zero height, no box, no
   effect on layout — it only needs to keep scrolling once the card has stopped. */
.target-sentinel { height: 0; margin: 0; padding: 0; }

/* --------------------------------------------------------- button weight */

/* Three equal pills gave "I'll eat this", "Restaurant" and "Skip" the same
   visual claim, so the card asked a question instead of proposing an answer.
   The plan's own suggestion leads, the alternative is available, and skipping
   is a way out rather than a third option of equal standing. */

.button.ghost {
  border-color: transparent;
  background: transparent;
  color: var(--ink-soft);
  font-weight: 600;
}

.button.ghost:hover {
  border-color: var(--line);
  background: var(--wash);
  color: var(--ink);
}

.meal-actions { grid-template-columns: 1.35fr 1fr 0.75fr; }

@media (max-width: 480px) {
  .meal-actions { grid-template-columns: 1.3fr 1fr 0.7fr; }
}

/* ------------------------------------------------------------- menu rows */

.menu-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  min-height: 44px;
  padding: var(--s2) var(--s3);
  font-size: 0.94rem;
  font-weight: 600;
  color: var(--ink);
}

.menu-choice { display: flex; gap: 2px; padding: 2px; border: 1px solid var(--line-strong); border-radius: var(--radius-pill); }
.menu-choice span { display: contents; }

.menu-choice button {
  min-width: 46px;
  min-height: 36px;
  padding: 0 var(--s3);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.76rem;
  font-weight: 800;
  cursor: pointer;
}

.menu-choice button.is-active { background: var(--brand); color: #fff; }

.menu-links { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); padding: var(--s2) var(--s3) var(--s3); }
.menu-links a { color: var(--ink-soft); font-size: 0.84rem; text-decoration: none; }
.menu-links a:hover { color: var(--brand-strong); text-decoration: underline; }

/* ----------------------------------------------------------------- search */

.search-panel {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: clamp(12px, 5vh, 72px) var(--s4) var(--s4);
  background: rgba(43, 35, 32, 0.5);
  backdrop-filter: blur(4px);
}

.search-sheet {
  display: flex;
  flex-direction: column;
  width: min(620px, 100%);
  max-height: min(76dvh, 640px);
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-2);
}

.search-bar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: 0 0 auto;
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line);
}

.search-bar svg { width: 20px; height: 20px; flex: 0 0 auto; color: var(--ink-faint); }

.search-bar input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  border: 0;
  background: transparent;
  font-size: 1.05rem;
  color: var(--ink);
}

.search-bar input:focus { outline: none; }
.search-bar input::-webkit-search-cancel-button { display: none; }

.search-close {
  flex: 0 0 auto;
  min-height: 40px;
  padding: 0 var(--s3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}

.search-close:hover { background: var(--surface-sunk); color: var(--ink); }

.search-results { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--s2); }
.search-hint { margin: 0; padding: var(--s4); color: var(--ink-faint); font-size: 0.9rem; text-align: center; }

.search-hit {
  display: grid;
  gap: 2px;
  width: 100%;
  padding: var(--s3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.search-hit:hover, .search-hit:focus-visible { background: var(--brand-tint); }
.search-hit-group { color: var(--ink-faint); font-size: 0.66rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; }
.search-hit-label { color: var(--ink); font-size: 0.98rem; font-weight: 600; }
.search-hit-detail { color: var(--ink-soft); font-size: 0.84rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.week-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
  margin: 0 0 var(--s5);
}

.week-actions .button { flex: 0 1 auto; }

@media (max-width: 480px) {
  .week-actions .button { flex: 1 1 100%; }
}

/* ------------------------------------------------------- basket sections */

.basket-total {
  display: grid;
  gap: 2px;
  justify-items: center;
  margin: 0 0 var(--s5);
  padding: var(--s4);
  border-radius: var(--radius-md);
  background: var(--canvas-tint);
  text-align: center;
}

.basket-total .label { color: var(--ink-faint); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
.basket-total b { font-size: clamp(2.1rem, 7vw, 2.8rem); font-weight: 700; line-height: 1.05; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }

.basket-list { display: grid; gap: var(--s5); }
.basket-section h3 { margin: 0 0 var(--s2); color: var(--ink-faint); font-size: 0.7rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
.basket-section ul { display: grid; gap: 0; }

.basket-section li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.96rem;
}

.basket-section li:last-child { border-bottom: 0; }
.basket-item b { font-variant-numeric: tabular-nums; }
.basket-price { flex: 0 0 auto; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The shop leaves the app, so it is marked as a link rather than a destination. */
.tab--shop, .topnav-link--shop { text-decoration: none; }
.tab--shop { color: var(--brand-strong); }
.tab--shop:hover { color: var(--brand); }
.topnav-link--shop { display: inline-flex; align-items: center; color: var(--brand-strong); font-weight: 700; }
.topnav-link--shop:hover { background: var(--brand-tint); }

/* Six destinations have to fit the narrowest phone still in use. */
@media (max-width: 400px) {
  .tabbar { gap: 0; padding-inline: var(--s1); }
  .tab { min-width: 0; padding: var(--s1) 2px; font-size: 0.6rem; letter-spacing: -0.01em; }
  .tab svg { width: 20px; height: 20px; }
  .tab-label { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

@media (max-width: 340px) {
  .tab { font-size: 0.55rem; }
  .tab svg { width: 18px; height: 18px; }
}

.day-done-badge { display: inline-flex; }
.day-done-badge svg { width: 30px; height: 30px; }

/* ------------------------------------------------ the expensive pass, 2026

   Read against the current Awwwards winners (Miu Miu "A House that we shaped",
   LIKOVA, Oimachi) the Coach was doing four things that read as cheap:
   the page and the cards were two per cent apart so nothing looked placed on
   anything; the accent was on every label, so it emphasised nothing; the one
   number people open the app for was smaller than the headline above it; and
   the food photography — the best asset in the product — was cropped small.
   What those sites share is commitment: one ground, one voice, one accent used
   once, and an image given room. None of them decorate.                     */

/* Photography runs to the edge of its card. */
.meal-card { overflow: hidden; }
.meal-media { background: var(--surface-sunk); }

/* Rhythm: the page breathes between blocks rather than stacking flush cards. */
.target { margin-bottom: var(--s5); }
.quest-strip { margin-bottom: var(--s5); }
.meal-list { gap: var(--s5); }
.today-main > .privacy { margin-top: var(--s6); }

/* Section labels are set small, wide and quiet — the size and spacing carry
   them, not colour. */
.eyebrow,
.label,
.target-label,
.quest-strip-head .label,
.basket-section h3,
.search-hit-group {
  letter-spacing: 0.16em;
}

/* The headline and the figure are the two voices on the screen. Everything
   else steps back. */
.view-head h1 { letter-spacing: -0.035em; }
.view-lead { max-width: 46ch; }

/* One accent, and it lands on the thing you press. */
.button { letter-spacing: -0.005em; }

/* Hairlines define the cards now, so the borders have to be visible. */
.target, .quest-strip, .meal-card, .week-day, .live-coach, .card {
  border-color: var(--line);
}

/* ─────────────────────────────────────────────────────────── landing hero

   The one screen where the Awwwards treatment belongs outright: a photograph
   at full bleed, a headline as large as the viewport allows, and almost
   nothing else. The daily app has to work in a supermarket; the landing has
   one job, which is to be worth six questions.                              */

[data-hero="1"] .topbar {
  position: absolute;
  inset: 0 0 auto;
  background: transparent;
  border-bottom: 0;
}

[data-hero="1"] .brand { color: var(--on-hero); }
[data-hero="1"] .brand em { color: rgba(253, 250, 245, 0.72); }
[data-hero="1"] .lang { border-color: rgba(253, 250, 245, 0.4); }
[data-hero="1"] .lang button { color: rgba(253, 250, 245, 0.78); }
[data-hero="1"] .lang button.is-active { background: var(--on-hero); color: #17140f; }
[data-hero="1"] .main { padding: 0; background: none; }
/* [data-hero] is set by mount() whenever the hero layout is on screen, which
   covers the landing and the six questions that continue it. */

/* One screen, and only one. Before, the footer sat below a hero that already
   claimed the full viewport, so the page scrolled — and on a real phone the
   headline slid up underneath the sticky header. Now the view is exactly the
   height that is actually available (--vvh, which unlike dvh accounts for the
   keyboard) and the footer is part of it. */
/* min-height, never height, and never overflow:hidden. A fixed height plus
   clipping is how the hero disappeared: on a real iPhone Safari's chrome leaves
   about 660px, the footer took 135 of them, and the panel that did not fit in
   what was left was simply cut off rather than shown. The screen still wants to
   be one screen — hence min-height and a compact footer below — but if the
   content genuinely needs more room it scrolls, because a page that scrolls is
   always better than a page with its middle removed. */
/* Two earlier attempts got this wrong, so it is worth stating plainly.
   `100dvh - topbar` subtracted the header twice — .main already grows into
   whatever the header leaves — and left exactly one header of cream below the
   footer. `min-height: 100%` then resolved to auto, because a percentage needs
   a definite parent height and .main's comes from flex-grow, which made the
   gap bigger. Stretching is the mechanism that does not need either: .main
   becomes a column and the view is the item that grows. */
.view--landing {
  max-width: none;
  margin: 0;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  /* A flex item defaults to min-height:auto and refuses to shrink below its
     content. Every level between the shell and the transcript therefore has to
     opt out, or the whole column overflows the keyboard-shrunk viewport — which
     is precisely how the answer field ended up under the keyboard again. */
  min-height: 0;
}

[data-hero="1"] .main { display: flex; flex-direction: column; min-height: 0; }

.view--landing .hero { flex: 1 1 auto; min-height: 0; }
/* The hero is the whole view now — there is no band under it to leave room
   for. See the note above heroFrame() in coach.js for why the footer went. */

/* The gutter is the page's, not the hero's own. A hand-picked
   clamp(20px, 5vw, 72px) put the headline 72px from the window edge while the
   logotype above it sat inside the centred content column — 172px in on a
   laptop — so the landing read as two layouts stacked on one screen. Both now
   measure from --edge, so "Menja segons" and the age field start exactly under
   "Quota Vita" at every width, phones included. */
.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: 0;
  padding: var(--topbar-total) var(--edge) clamp(28px, 5vw, 64px);
  overflow: hidden;
  isolation: isolate;
  background: var(--hero-ground);
}

.hero-media { position: absolute; inset: 0; z-index: 0; }
.hero-media img { width: 100%; height: 100%; object-fit: cover; object-position: 60% 50%; }

/* Two scrims: one along the bottom for the type, one from the left so the
   headline has a quiet field on the side the photograph is emptiest. */
/* Measured, not guessed. The first scrim left the headline at 2.18:1 over the
   olive-oil bottle — a bright band of the photograph — which fails even the 3:1
   floor for large text. The gradient is now heaviest exactly where the words
   are and stays light on the right, so the food is still the picture. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(18, 13, 9, 0.42) 0%, rgba(18, 13, 9, 0.34) 26%, rgba(18, 13, 9, 0.74) 58%, rgba(18, 13, 9, 0.92) 100%),
    linear-gradient(100deg, rgba(18, 13, 9, 0.72) 0%, rgba(18, 13, 9, 0.34) 48%, rgba(18, 13, 9, 0.06) 100%);
}

.hero-body {
  position: relative;
  z-index: 2;
  width: min(660px, 100%);
  color: var(--on-hero);
}

.hero-title {
  margin: 0 0 var(--s4);
  font-family: var(--display);
  /* 11vw alone measured only the width, so on a 1280x800 laptop the two lines
     came to 274px, the column overflowed the hero, and "Menja segons" was cut
     off by the top of the window. The height gets a vote. */
  font-size: clamp(2.9rem, min(11vw, 9vh), 6.2rem);
  font-weight: 600;
  line-height: 0.92;
  letter-spacing: -0.045em;
  text-wrap: balance;
  color: var(--on-hero);
  text-shadow: 0 1px 30px rgba(12, 8, 5, 0.55);
}

.hero-lead {
  margin: 0 0 clamp(22px, 4vw, 34px);
  max-width: 44ch;
  font-size: clamp(1rem, 2.2vw, 1.2rem);
  line-height: 1.5;
  color: rgba(253, 250, 245, 0.9);
}

/* The Coach is already talking, and the first answer is given here. */
.hero-chat {
  padding: clamp(16px, 3vw, 22px);
  border: 1px solid rgba(253, 250, 245, 0.24);
  border-radius: var(--radius-lg);
  background: rgba(22, 17, 13, 0.55);
  backdrop-filter: blur(22px) saturate(1.2);
  -webkit-backdrop-filter: blur(22px) saturate(1.2);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hero-chat { background: rgba(22, 17, 13, 0.86); }
}

.hero-bubble {
  margin: 0 0 var(--s3);
  font-size: clamp(0.98rem, 2.1vw, 1.08rem);
  line-height: 1.45;
  color: var(--on-hero);
}

.hero-composer {
  display: flex;
  gap: var(--s2);
  align-items: center;
  padding: 5px 5px 5px var(--s4);
  border: 1px solid rgba(253, 250, 245, 0.34);
  border-radius: var(--radius-pill);
  background: rgba(253, 250, 245, 0.1);
}

.hero-composer:focus-within { border-color: rgba(253, 250, 245, 0.72); }

.hero-composer input {
  flex: 1;
  min-width: 0;
  min-height: 46px;
  border: 0;
  background: transparent;
  color: var(--on-hero);
  font-size: 1.05rem;
}

.hero-composer input::placeholder { color: rgba(253, 250, 245, 0.62); }

/* The native number stepper put a pair of grey arrows in the middle of the
   hero. The keyboard still gets a numeric pad from inputmode. */
.hero-composer input { appearance: textfield; -moz-appearance: textfield; }
.hero-composer input::-webkit-outer-spin-button,
.hero-composer input::-webkit-inner-spin-button { appearance: none; margin: 0; }
.hero-composer input:focus { outline: none; }
.hero-composer .button { flex: 0 0 auto; background: var(--on-hero); border-color: var(--on-hero); color: #17140f; }
.hero-composer .button:hover { background: #fff; border-color: #fff; }

.hero-note {
  margin: var(--s3) 0 0;
  font-size: 0.84rem;
  color: rgba(253, 250, 245, 0.72);
}

.hero-secondary {
  margin-top: var(--s4);
  padding: 0;
  border: 0;
  background: transparent;
  color: rgba(253, 250, 245, 0.86);
  font: inherit;
  font-size: 0.92rem;
  text-decoration: underline;
  text-underline-offset: 4px;
  cursor: pointer;
}

.hero-secondary:hover { color: #fff; }

/* The disclaimer and the four site links, set as fine print on the photograph
   rather than on a cream band of their own: 55px of a 667px screen recovered,
   and the panel above it gets them. Landing only — during the questions this
   is not rendered at all. */
.hero-fineprint {
  display: flex;
  flex-wrap: wrap;
  gap: 2px var(--s3);
  align-items: baseline;
  margin: var(--s3) 0 0;
  font-size: 0.72rem;
  line-height: 1.4;
  color: rgba(253, 250, 245, 0.66);
}

.hero-fineprint a {
  color: rgba(253, 250, 245, 0.82);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.hero-fineprint a:hover { color: #fff; }

/* Who makes this, one line above the legal line. Brighter than the fine print
   and dimmer than the lead: it is not a disclaimer and it is not a headline. */
.hero-provenance {
  display: flex;
  flex-wrap: wrap;
  gap: 2px var(--s3);
  align-items: baseline;
  margin: var(--s4) 0 0;
  font-size: 0.78rem;
  letter-spacing: 0.01em;
  color: rgba(253, 250, 245, 0.78);
}

.hero-provenance a {
  color: var(--on-hero);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.hero-provenance a:hover { color: #fff; }

@media (min-width: 900px) {
  .hero-media img { object-position: 72% 46%; }
  .hero-body { padding-bottom: var(--s5); }
}

/* ── the landing on a desktop ───────────────────────────────────────────────

   Below 900px this file is the record of a lot of measurement — --vvh, the
   keyboard, the composer that must never move — and none of it is touched
   here. Above 900px there was no composition at all: .hero-body is capped at
   660px and pinned to --edge, so on a 1440px laptop the headline, the lead and
   the panel ran down the left quarter and the right half of the photograph
   carried nothing. The column was a phone layout that a desktop was allowed to
   watch.

   Two columns instead: the words on the left where the scrim is heaviest, the
   panel on the right where the photograph is emptiest, both still measured
   from --edge so the headline stays under the logotype. Rows are assigned
   explicitly rather than by flow, because the panel is third in the DOM (it
   has to be, for the phone) and second on the screen.

   The questions do NOT become a grid. Their column is where the keyboard work
   lives — min-height:0 all the way down, justify-content:flex-end, a transcript
   with a flex-basis that gives way before anything else does — and a grid row
   track does not shrink the way a flex item does. They keep the mechanism that
   was measured and get the *position* instead: the same panel width, on the
   same side, so answering the first question moves nothing.                   */
@media (min-width: 900px) {
  .view--landing .hero-body {
    /* One width, two layouts. The landing spends it as a grid track and the
       questions as a plain width, which is the only reason the panel does not
       jump between step one and step two. */
    --hero-panel: clamp(340px, 42%, 520px);
    width: 100%;
  }

  .view--landing:not(.view--asking) .hero-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--hero-panel);
    column-gap: clamp(32px, 5vw, 72px);
    align-content: end;
  }

  .view--landing:not(.view--asking) .hero-title { grid-column: 1; grid-row: 1; align-self: end; }
  .view--landing:not(.view--asking) .hero-lead { grid-column: 1; grid-row: 2; }
  .view--landing:not(.view--asking) .hero-provenance { grid-column: 1; grid-row: 3; }
  .view--landing:not(.view--asking) .hero-fineprint { grid-column: 1; grid-row: 4; }

  /* The panel spans the three rows the words occupy and sits on the baseline
     of the last of them, so its bottom edge and the fine print line up however
     long the headline wraps. */
  .view--landing:not(.view--asking) .hero-chat { grid-column: 2; grid-row: 1 / span 3; align-self: end; }
  .view--landing:not(.view--asking) .hero-secondary { grid-column: 2; grid-row: 4; justify-self: start; margin-top: var(--s3); }

  /* Still a flex column, still shrinking the way it was measured to — only
     narrower, and on the right. */
  .view--asking .hero-chat { align-self: flex-end; width: var(--hero-panel); }
  .view--asking .hero-title { max-width: calc(100% - var(--hero-panel) - clamp(32px, 5vw, 72px)); }
}

/* The Catalan question would not fit beside the button, so this used to stack
   the two — which made the composer 113px tall and left the field looking like
   a centred caption rather than something you type in. The question was never
   the placeholder's job: it is asked in the bubble above, and the field only
   has to hold a two-digit number. One line, left-aligned, thumb-sized. */
@media (max-width: 460px) {
  .hero-composer { padding: 5px 5px 5px var(--s3); }
  .hero-composer input { min-height: 50px; text-align: start; }
  .hero-composer .button { min-height: 50px; }
  .hero-title { font-size: clamp(3rem, 13vw, 4.2rem); }
  .hero { padding-bottom: clamp(24px, 6vw, 40px); }
}

/* The question, asked out loud above the field it is answered in. */
.hero-ask {
  margin: 0 0 var(--s3);
  font-size: clamp(1.02rem, 2.2vw, 1.12rem);
  font-weight: 600;
  color: var(--on-hero);
}

/* ───────────────────────────────────────────────────────────── brand mark

   The real logotype, in its two official cuts: the terracotta one for the
   light chrome and the negative one for the photographic landing. Both are
   trimmed to the artwork and served at 120px tall, which is more than any
   header asks for.                                                          */

.brand-logo {
  display: block;
  width: auto;
  height: 21px;
  flex: 0 0 auto;
}

.brand-logo--light { display: none; }

/* Over the hero photograph the negative cut takes over. */
[data-hero="1"] .brand-logo--ink { display: none; }
[data-hero="1"] .brand-logo--light { display: block; }

/* "Coach" is set beside the logotype, not inside it, so the mark is never
   redrawn or restyled. */
.brand em {
  font-family: var(--display);
  font-size: 1.02rem;
  font-style: normal;
  font-weight: 500;
  color: var(--ink-soft);
  white-space: nowrap;
}

[data-hero="1"] .brand em { color: rgba(253, 250, 245, 0.82); }

@media (max-width: 460px) {
  .brand-logo { height: 19px; }
}

/* ── Onboarding, held inside the landing design ──────────────────────────────
   Three faults, one shape. The questions used to render as a plain cream form,
   so the site appeared to become a different product the moment you answered.
   The transcript grew downward, pushing the input further down the page with
   every answer — by the third the composer had left the screen. And on iOS the
   numeric keypad has no return key at all, so an unreachable Send button meant
   there was no way to submit.

   The panel is now a fixed frame: progress on top, a transcript that scrolls
   inside itself, and the composer pinned beneath it. The composer sits in the
   same place at question six as at question one. */

/* Bottom-anchored, like the landing it continues from. Centring the panel made
   it grow in both directions: the five-way training question ended up 863px tall
   on an 844px screen, with its last option off the bottom. Anchored to the
   bottom, the composer's baseline is fixed and the conversation grows upward —
   which is what "keep the bar still and stack the answers above it" means. */
/* Let flexbox measure the space rather than a hand-written calc. Subtracting
   the paddings by hand ignored the headline above the panel, so the composer
   sat a few pixels under the fold on every question — pinned, but pinned just
   off the screen. Stretching the body to the hero's content box and laying it
   out bottom-up means the panel can only ever be as tall as what is actually
   left, and the transcript is the part that gives way. */
.view--asking .hero { align-items: stretch; }

.view--asking .hero-body {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 0;
}

.hero-chat--asking { min-height: 0; }

/* The headline stays — this is still the same page — but yields room once the
   conversation is the thing you are actually doing. */
.view--asking .hero-title {
  font-size: clamp(1.5rem, 4.6vw, 2.4rem);
  line-height: 1.05;
  margin-bottom: var(--s3);
}

.hero-chat--asking { display: flex; flex-direction: column; gap: var(--s3); }

/* Three short labels on one line at 375px. Wrapped, "PAS 5 DE 7" and
   "RECOMENÇA" each became two lines and the row was taller than the question
   under it. Nothing here is long enough to need a second line, so none of it
   gets one. */
.hero-progress {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: rgba(253, 250, 245, 0.72);
}

.hero-step-back {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: rgba(253, 250, 245, 0.82);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.hero-step-back:hover { color: #fff; }

/* The scrolling region. Capped against the viewport so the composer below it
   is always on screen, including with a keyboard open. */
/* A *fixed* height, not a maximum. With a maximum the box still grew for the
   first three answers, and the composer travelled ~140px down the screen before
   settling — which is exactly the drift being complained about, just smaller.
   Constant height means the bar is in the same place at question seven as at
   question one.

   The inner wrapper is what lets the content sit at the bottom of that box:
   `justify-content: flex-end` directly on a scroll container clips the top of
   the content once it overflows, so the alignment is done one level in. */
.hero-transcript {
  /* The history is what yields space first: flex-basis 0 so it takes only what
     is left over once the live question and the composer have what they need. */
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  /* A bubble cut off by the top edge of the scroller looked like broken text.
     Faded, it reads as what it is: the part of the conversation you have
     already had. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 24px);
  mask-image: linear-gradient(180deg, transparent 0, #000 24px);
}

.hero-transcript-inner {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--s2);
  min-height: 100%;
}

.hero-transcript .bubble {
  margin: 0;
  padding: var(--s3) var(--s4);
  border-radius: var(--radius-md);
  font-size: 0.98rem;
  line-height: 1.45;
}

.hero-transcript .bubble.coach {
  background: rgba(253, 250, 245, 0.12);
  color: var(--on-hero);
  align-self: flex-start;
}

.hero-transcript .bubble.user {
  background: var(--on-hero);
  color: #17140f;
  align-self: flex-end;
}

.hero-transcript .bubble .meta {
  display: block;
  margin-top: var(--s1);
  font-size: 0.85rem;
  color: rgba(253, 250, 245, 0.72);
}

.hero-transcript .bubble.user .meta { color: rgba(23, 20, 15, 0.66); }
.hero-transcript .bubble--problem { background: rgba(214, 122, 90, 0.24); }

/* Choice questions occupy the composer's slot, so nothing shifts between a
   typed answer and a tapped one. */
.hero-replies { display: flex; flex-direction: column; gap: var(--s2); }
.hero-replies .composer-label {
  font-size: 0.82rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(253, 250, 245, 0.72);
}
.hero-replies .quick-replies { display: flex; flex-wrap: wrap; gap: var(--s2); }
.hero-replies .quick-replies button {
  padding: var(--s3) var(--s4);
  border: 1px solid rgba(253, 250, 245, 0.38);
  border-radius: var(--radius-pill);
  background: rgba(253, 250, 245, 0.1);
  color: var(--on-hero);
  font: inherit;
  cursor: pointer;
  min-height: 44px;
}
.hero-replies .quick-replies button:hover {
  background: rgba(253, 250, 245, 0.2);
  border-color: rgba(253, 250, 245, 0.6);
}

/* Back and Start over sit in the progress row, so both escape hatches are
   away from the thumb and neither costs the question any height. */
.hero-step-links { display: flex; gap: var(--s3); align-items: center; }

/* The Send button is the submit control on a phone, so it never shrinks below
   a thumb-sized target. */
.hero-composer .button { min-height: 44px; padding-inline: var(--s4); }
.hero-composer input { min-height: 44px; }

@media (max-width: 640px) {
  /* Of the space actually visible, not of the screen: with the keyboard up,
     30dvh was nearly a third of a viewport that is no longer there. */
  .hero-transcript { flex-basis: min(calc(var(--vvh, 100dvh) * 0.3), 240px); }
}

/* On a short screen the headline and a full-size transcript together leave the
   four- and five-option questions no room, and the last option lands under the
   fold — the one thing this layout exists to prevent. Below 720px of height the
   photograph and the panel carry the continuity on their own, and the
   transcript keeps just enough depth to show the exchange in progress. */
@media (max-height: 720px) {
  .view--asking .hero-title { display: none; }
  .view--asking .hero-transcript { flex-basis: 20dvh; min-height: 58px; }
  .view--asking .hero-chat--asking { gap: var(--s2); }
}

/* ── With the keyboard up ────────────────────────────────────────────────────
   The screenshot that started this: you answer the first question, the second
   one renders, and it is entirely behind the keyboard. --vvh shrinks the view
   to the space the keyboard leaves, and everything that is not the exchange in
   progress gets out of the way, so the question and its field stay visible. */
.keyboard-open .view--asking .hero-title,
.keyboard-open .view--asking .hero-lead,
.keyboard-open .view--landing .hero-fineprint { display: none; }

/* And the header leaves with them. It is the one thing on screen that is not
   part of the exchange, and keeping it costs twice: 60px reserved so the panel
   can clear it is 60px the question does not get, and not reserving them is
   how the panel ended up underneath it. On a phone at question two, with the
   keyboard up, "Pas 2 de 7" and the top of the panel were behind the logotype
   and cut off by .hero's overflow. The logotype comes back the moment the
   field is dismissed. */
.keyboard-open[data-hero="1"] .topbar { display: none; }
.keyboard-open .view--landing .hero,
.keyboard-open .view--asking .hero { padding-top: var(--s2); }
.keyboard-open .view--asking .hero { padding-bottom: var(--s2); }
.keyboard-open .view--asking .hero-transcript { flex-basis: auto; min-height: 44px; }
.keyboard-open .view--landing .hero-bubble,
.keyboard-open .view--landing .hero-secondary,
.keyboard-open .view--landing .hero-note { display: none; }

/* ── Thumb-sized hit areas for visually small controls ───────────────────────
   The phone QA found the CA/EN switch at 36px, "Enrere" at 20px and the
   example-day link at 23px. Inflating them would wreck a deliberately compact
   header and turn quiet text links into buttons, so the *visual* size stays and
   the touch area is extended to 44px with a transparent overlay centred on the
   control. Apple's and Google's guidance is about the target, not the ink. */
.lang button,
.hero-secondary,
.hero-step-back { position: relative; }

.lang button::after,
.hero-secondary::after,
.hero-step-back::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

/* Phone QA, round two: several real controls measured 40px — the goal chips on
   the week screen, the one-day/seven-day toggle on the basket, the streak chip
   in the header. Four pixels short of the 44px minimum is still short. */
@media (max-width: 700px) {
  .view button:not(.hero-step-back):not(.tab):not(.detail-toggle),
  .view select { min-height: 44px; }
}

/* Two more that must not grow visually: the header chip, where 4px would push
   the bar taller, and the "keep it across devices" link, which is a sentence.
   Both get the transparent 44px overlay instead.

   Deliberately not extended: citation links inside running prose (the
   methodology sources). WCAG's target-size rule exempts links inline in a
   sentence, and an overlay there would sit on top of the words around it. */
.streak-chips,
.privacy-action { position: relative; }

.streak-chips::after,
.privacy-action::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

/* The landing footer was 135px on a phone — a fifth of the screen for a
   disclaimer and four links, taken directly out of the photograph's height. */
@media (max-width: 640px) {
  /* Short screens: the headline yields before the photograph or the field do. */
  .view--landing .hero { padding-top: var(--s3); }
}

@media (max-width: 640px) and (max-height: 740px) {
  .view--landing .hero-title { font-size: clamp(2.1rem, 9vw, 2.8rem); }
  .view--landing .hero-lead { font-size: 0.92rem; margin-bottom: var(--s3); }
}

/* A *definite* height, not a minimum. With only min-height the column had
   nothing stopping it: the activity question's hint is longer than the four
   options it explains, so on a 360x600 phone the hero grew to 754px and the
   last option fell below the fold. Given a real height, the blocks inside
   shrink in the order set below — history first, then the question scrolls
   inside itself — and the options always fit. */
[data-hero="1"] .app { height: 100dvh; }

/* With the keyboard up the shell shrinks to the space that is left, so filling
   the parent means filling the *visible* area, not the screen behind it. */
.keyboard-open .app { min-height: var(--vvh, 100dvh); height: var(--vvh, 100dvh); }

/* iOS floats an AutoFill bar (key / card / pin) above the keyboard, and it is
   not always subtracted from visualViewport — on the real device it sat on top
   of the answer field and the Envia button. Reserve its height. */
.keyboard-open .view--landing .hero,
.keyboard-open .view--asking .hero { padding-bottom: 52px; }

/* The question being answered: never clipped.

   `flex: 0 1 auto` was not enough, and the measurement says why. Flexbox
   shrinks every shrinkable item at once, in proportion to its basis — so the
   question started losing lines while the transcript above it still had 229px
   of history on a 932px iPhone. The question was clipped mid-sentence on a
   screen with room to spare, which is exactly what was being complained about.

   It does not shrink at all now. The history is the only thing that gives way,
   down to nothing if that is what it takes. The cap is a backstop for a screen
   so short that even the shortened hints will not fit: it is a fraction of the
   *visible* viewport, so it tightens with the keyboard rather than measuring a
   screen that is half covered. */
.hero-ask-block {
  flex: 0 0 auto;
  min-height: 0;
  max-height: calc(var(--vvh, 100dvh) * 0.42);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.hero-ask-block .bubble {
  margin: 0;
  padding: var(--s3) var(--s4);
  border-radius: var(--radius-md);
  background: rgba(253, 250, 245, 0.14);
  color: var(--on-hero);
  font-size: 0.98rem;
  line-height: 1.45;
}

.hero-ask-block .bubble .meta {
  display: block;
  margin-top: var(--s1);
  font-size: 0.85rem;
  color: rgba(253, 250, 245, 0.74);
}

.hero-ask-block .bubble--problem { background: rgba(214, 122, 90, 0.28); }

/* With the keyboard up the history can disappear entirely — the question and
   the field are the only things that matter at that moment. */
.keyboard-open .hero-transcript { flex-basis: 0; min-height: 0; }

/* Priority when space runs out: the answer controls never shrink, the question
   scrolls inside its own block, and the history goes first. On a 360x600 phone
   the four-option activity question has a hint longer than the options it
   explains, and without this the fourth option fell off the screen. */
.hero-replies,
.hero-composer { flex: 0 0 auto; }

/* ── The cream band under the hero ──────────────────────────────────────────

   Reported as "a cream footer appears and breaks the design" the moment you
   start answering. It was there all along; the keyboard is just what made it
   visible.

   The shell is sized to the space the keyboard leaves — `.keyboard-open .app
   { height: var(--vvh) }` — but `body` is still `min-height: 100dvh` and still
   painted `--shell`. Measured on the running app: with --vvh at 420px in a
   679px window, `.app` ended at 420 and 259px of cream sat below it. With the
   keyboard actually up that band is behind the keyboard, which is why it
   survived so long — but the keyboard opens and closes over about 250ms, and
   for that quarter of a second, every time, a cream strip slides up under a
   dark photograph. On Android, where the resize lands at a different moment,
   it can simply stay there.

   Sizing the body to --vvh as well would be the fix that fights: two elements
   both chasing a number that changes mid-animation. The ground is the honest
   fix. While the hero is on screen the page is dark all the way down, so a gap
   of any height, at any moment in the animation, is invisible. Nothing needs
   to be measured and nothing can be off by a frame.

   `data-hero` is on <html>, so this has to be written as an attribute selector
   on the root — `.keyboard-open [data-hero="1"]` would be a descendant
   selector and match nothing, the same trap the topbar rule fell into. */
html[data-hero="1"],
html[data-hero="1"] body { background-color: var(--hero-ground); }

/* The landing's fine print, on a phone. The disclaimer keeps the half that is
   the disclaimer; the qualifier in front of it is what pushed the line to two.
   Above 640px there is room for both and both are shown. */
@media (max-width: 640px) {
  .fineprint-qualifier { display: none; }
}

/* ── A meal card with no photograph ─────────────────────────────────────────

   Meal images are generated per meal per day by an image provider, against
   titles a model writes fresh each morning, so there is no fixed set of
   pictures that could ship with the app. When the provider is down — as it was
   when this was written — every card got a grey rectangle reading "Imatge no
   disponible" and a Try again button that could not succeed, three times on the
   Today screen and seven more down the week.

   The placeholder is gone, so the card has to hold together without a media
   column: above 620px it stops being a two-column grid and the text takes the
   whole card, instead of leaving a 34% band of empty sunk cream beside it. */
.meal-card--textonly .meal-body { padding-top: var(--s4); }

@media (min-width: 620px) {
  .meal-card--textonly { grid-template-columns: minmax(0, 1fr); }
}

/* ── The landing, vertically ────────────────────────────────────────────────

   The hero bottom-anchors its content: `.hero { align-items: flex-end }`, and
   during the questions `.hero-body { justify-content: flex-end }`. On a phone
   that is the whole point — the answer field belongs under the thumb, and with
   the keyboard up the panel has to grow upward from a fixed floor.

   On a desktop it has no such job, and the result was a headline and a panel
   pressed into the bottom third of a 1300px-tall window with the upper half
   carrying nothing. Reported as "not centered", and that is exactly what it is:
   the block is centred horizontally, between --edge on both sides, and sat at
   the bottom vertically.

   So above 900px wide AND 760px tall — the height condition matters, because a
   short laptop window has no slack to centre into and clipping the top of a
   headline is worse than a low one — the hero centres what it holds. Every
   measured phone rule below those thresholds is untouched.

   Written with both classes so it beats `.view--asking .hero { align-items:
   stretch }`, which is a later rule of equal weight. */
@media (min-width: 900px) and (min-height: 760px) {
  .view--landing .hero { align-items: center; }
  .view--landing:not(.view--asking) .hero-body { align-content: center; }

  .view--landing.view--asking .hero { align-items: center; }
  .view--landing.view--asking .hero-body { justify-content: center; }
}

/* ── The shortest phones, during the questions ──────────────────────────────

   Found by the twenty-persona sweep: on a 320x568 iPhone SE, questions three
   through six put the top of the panel above the bottom of the logotype. Both
   languages, so it is height and not Catalan's longer words.

   The arithmetic is unforgiving. 568px, minus 60 reserved for the header, minus
   the hero's bottom padding, leaves about 480 for a panel holding a progress
   row, a scrolling transcript, the question with its hint, and a composer with
   44px targets. The `max-height: 720px` block above already hides the headline
   and caps the transcript at 20dvh; on a 568px screen that is still not enough,
   and the panel — anchored to the bottom and growing upward — grows under an
   absolutely-positioned header that costs no layout height.

   The same answer as the keyboard case, for the same reason: the header is the
   one thing on screen that is not part of the exchange, so it leaves, and the
   60px it was reserving goes to the question. It comes back on the landing and
   the moment the plan appears, because `data-view` follows the view.

   Written as an attribute selector on the root: `data-view` sits on <html>
   alongside `data-hero`, so a descendant selector would match nothing — the
   trap the topbar rule fell into once already. */
@media (max-height: 640px) {
  html[data-view="setup"] .topbar { display: none; }
  html[data-view="setup"] .hero { padding-top: var(--s3); }
}
