/* ============================================================================
   FARRO — marketing site design system
   ----------------------------------------------------------------------------
   The app's identity, restated at poster scale.

   The app is a WHITE room with gold ELEMENTS (DESIGN.md: "the page is white with
   a whisper of gold, gold is an element, never the room"). That rule was decided
   for a surface someone cooks inside for twenty minutes. A landing page is read
   for ninety seconds — so here the relationship inverts on purpose:

       GOLD IS THE ROOM. The white app-card is the object floating in it.

   Three surfaces, three jobs, and nothing else:
     .s-gold  poster / claim      ink on #F8D05C (12.19:1)
     .s-page  work / explanation  ink on #F7F4EC, white cards on warm shadow
     .s-ink   the turn / the ask  gold + white on #031826

   Every colour below is copied from lib/core/theme/farro_colors.dart, where each
   value was contrast-checked against its real background. Do not invent a new
   tint here: a colour that only exists on the website is a colour nobody checked.
   ========================================================================== */

/* ---------------------------------------------------------------- tokens -- */
:root {
  /* Brand — identical to FarroColors */
  --ink:        #031826;   /* 18.06:1 on white. Text, dark surfaces, the pen.  */
  --ink-soft:   #5F6B74;   /* 5.47:1                                          */
  --muted:      #657179;   /* 4.90 white / 4.56 page — the floor. Never lighter */
  --outline:    #888D91;   /* 3.05 — interactive borders (WCAG 1.4.11)        */
  --faint:      #B3B8BC;   /* decorative only, never text                     */
  --hairline:   #E2E5E7;
  --gold:       #F8D05C;   /* the accent. A FILL that carries ink — never text */
  --gold-deep:  #EABE3F;   /* gold one step down, for gold-on-gold separation  */
  --sheet:      #FFFFFF;
  --page:       #F7F4EC;   /* white with a whisper of gold (chroma .012 @ 90)  */
  --muted-gold: #5E5845;   /* 4.79:1 ON GOLD. --muted scores 3.14 there.      */

  /* On ink */
  --on-ink:      #F7F4EC;  /* 15.9:1 on ink                                    */
  --on-ink-soft: #A9B4BC;  /* 7.3:1  on ink — secondary, still well past AA    */

  /* Warm shadow. The card beats the page by 1.10:1, so the shadow is the only
     thing that makes it an object — and it is brown-gold, because a black
     shadow on a warm surface makes grey mud (DESIGN.md). */
  --sh-resting: 0 1px 2px rgba(107,74,18,.06), 0 2px 8px rgba(107,74,18,.05);
  --sh-raised:  0 2px 6px rgba(107,74,18,.07), 0 12px 28px rgba(107,74,18,.08);
  --sh-lifted:  0 4px 12px rgba(107,74,18,.09), 0 28px 60px rgba(107,74,18,.13);
  --sh-on-gold: 0 3px 10px rgba(90,60,0,.10),  0 24px 56px rgba(90,60,0,.16);

  /* Radius — card tops out at 16; 32+ is the round-everything reflex */
  --r-control: 7px;
  --r-card:   16px;
  --r-sheet:  28px;
  --r-pill:  100px;

  /* Space — the app's fixed scale, extended upward for a page you scroll */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 13px; --sp-4: 17px;
  --sp-5: 22px; --sp-6: 32px; --sp-7: 52px; --sp-8: 84px;
  --gutter: clamp(20px, 5vw, 40px);
  --band:   clamp(64px, 9vw, 132px);   /* vertical rhythm between sections */

  /* Motion — ease-out only, no bounce (DESIGN.md) */
  --e-out:  cubic-bezier(.22, 1, .36, 1);        /* quart-ish */
  --e-expo: cubic-bezier(.16, 1, .30, 1);
  --t-state: 160ms;                               /* state changes            */
  --t-move:  420ms;                               /* something travelling     */
  --t-enter: 700ms;                               /* something arriving       */

  /* Semantic z-scale — never arbitrary 999 */
  --z-sticky: 100;
  --z-nav:    200;
  --z-menu:   300;
  --z-toast:  400;
}

/* --------------------------------------------------------------- reset ---- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: "Bricolage Grotesque", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-variation-settings: "opsz" 14, "wdth" 100;
  font-weight: 500;
  font-size: 17px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* 🔴 `height: auto` is NOT optional here, and leaving it out is what shipped the
   phone screenshots squashed by 26%. Every <img> on this site carries width and
   height attributes (they reserve the space and stop layout shift). With
   `max-width: 100%` alone, the width shrinks to the container while the height
   stays at the ATTRIBUTE value — so a 1284×2778 screenshot drew at 321×936 and
   every app screen on the site was a quarter narrower than it really is.
   Measured, not guessed: rendered aspect 0.343 against a natural 0.462.
   `tools/audit.js` now fails on any image whose drawn aspect ratio drifts more
   than 2% from the file's own. */
img, svg, picture { max-width: 100%; height: auto; display: block; }
a { color: inherit; }
button { font: inherit; color: inherit; }

::selection { background: var(--gold); color: var(--ink); }

:where(a, button, summary, input):focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
  border-radius: var(--r-control);
}
.s-ink :where(a, button, summary):focus-visible,
.s-gold :where(a, button, summary):focus-visible { outline-color: currentColor; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: var(--z-toast);
  background: var(--ink); color: var(--on-ink);
  padding: 12px 18px; border-radius: 0 0 var(--r-card) 0;
}
.skip:focus { left: 0; }

/* --------------------------------------------------------- typography ----- */
/* Bricolage is not one typeface, it is a scale of them. `opsz` is pinned to the
   real rendered size on every style — leave it at default and you get one
   compromise shape everywhere: quirky where it should be quiet, tame where it
   should have character. Tracking floor is -0.04em. */

h1, h2, h3, h4 { margin: 0; text-wrap: balance; font-weight: 700; }
p  { margin: 0; text-wrap: pretty; }
ul, ol { margin: 0; padding: 0; }

/* ⚠️ The poster scale is sized against its COLUMN, not the viewport.
   The first cut used `clamp(2.85rem, 8.2vw, 5.6rem)` — 90px — inside a grid
   column about 560px wide. "shopping list again." is 20 characters: it needed
   900px, the column could not shrink it (grid items default to
   `min-width: auto`), so the row grew wider than the page and the auto margins
   split the overflow to BOTH sides. The headline ran off the left edge of the
   window with its first letter cut in half.
   The cap below is the largest size that still fits the narrower column at
   1250px, with the `min-width: 0` on the hero columns as the second defence. */
.t-poster {                                  /* the hero, and only the hero   */
  font-size: clamp(2.35rem, 4.6vw, 4.3rem);
  line-height: .96;
  letter-spacing: -.036em;
  font-variation-settings: "opsz" 72, "wdth" 100;
  font-weight: 700;
}
.t-display {                                 /* a section announces itself    */
  font-size: clamp(2.05rem, 4.6vw, 3.3rem);
  line-height: 1.02;
  letter-spacing: -.034em;
  font-variation-settings: "opsz" 48, "wdth" 100;
}
.t-head {                                    /* a beat inside a section       */
  font-size: clamp(1.5rem, 2.7vw, 2rem);
  line-height: 1.08;
  letter-spacing: -.028em;
  font-variation-settings: "opsz" 36, "wdth" 100;
}
.t-sub {
  font-size: 1.12rem;
  line-height: 1.25;
  letter-spacing: -.02em;
  font-weight: 700;
  font-variation-settings: "opsz" 24, "wdth" 100;
}
.t-lead {                                    /* carries a display heading.
                                                INK, not grey: a grey line
                                                under a headline reads as fine
                                                print, and this sentence sells */
  font-size: clamp(1.06rem, 1.45vw, 1.22rem);
  line-height: 1.45;
  letter-spacing: -.012em;
  font-weight: 500;
  font-variation-settings: "opsz" 18, "wdth" 100;
  max-width: 62ch;
}
.t-body { font-size: 1.02rem; line-height: 1.55; color: var(--muted); max-width: 68ch; }
.s-ink   .t-body { color: var(--on-ink-soft); }
.s-gold  .t-body { color: var(--muted-gold); }
.t-cap {
  font-size: .84rem; line-height: 1.35; font-weight: 700;
  letter-spacing: .01em; color: var(--muted);
  font-variation-settings: "opsz" 12, "wdth" 100;
}
.s-ink  .t-cap { color: var(--on-ink-soft); }
.s-gold .t-cap { color: var(--muted-gold); }

/* ALL CAPS exists in exactly two places in this brand: an aisle sign and the
   TODAY badge. A supermarket sign and a marker — never a section eyebrow. */
.t-aisle {
  font-size: .68rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--outline);
  font-variation-settings: "opsz" 12, "wdth" 100;
}

/* The marker under a phrase. Painted as a background gradient rather than an
   absolutely-positioned bar, with `box-decoration-break: clone`, so it survives
   the phrase wrapping onto two lines. The bar version needed `white-space:
   nowrap` to look right — and that nowrap is half of why the headline could not
   shrink and ran off the page. A decoration must never be able to break a
   layout. */
.hl {
  color: var(--ink);
  background-image: linear-gradient(var(--gold), var(--gold));
  background-repeat: no-repeat;
  background-size: 100% .30em;
  background-position: 0 calc(100% - .06em);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.s-gold .hl { background-image: linear-gradient(var(--sheet), var(--sheet)); }
.s-ink  .hl { background-image: linear-gradient(var(--gold), var(--gold)); }

/* --------------------------------------------------------- structure ------ */
.wrap { width: min(1180px, 100% - var(--gutter) * 2); margin-inline: auto; }
.wrap-narrow { width: min(760px, 100% - var(--gutter) * 2); margin-inline: auto; }
.band { padding-block: var(--band); }

.s-page { background: var(--page); color: var(--ink); }
.s-gold { background: var(--gold); color: var(--ink); }
.s-ink  { background: var(--ink);  color: var(--on-ink); }
.s-sheet{ background: var(--sheet); color: var(--ink); }

/* ------------------------------------------------------------ buttons ----- */
.btn {
  display: inline-flex; align-items: center; gap: 10px;
  min-height: 52px; padding: 0 26px;
  border: 0; border-radius: var(--r-pill);
  font-weight: 700; font-size: 1rem; letter-spacing: -.01em;
  font-variation-settings: "opsz" 18, "wdth" 100;
  text-decoration: none; cursor: pointer;
  transition: transform var(--t-state) var(--e-out),
              box-shadow var(--t-state) var(--e-out),
              background-color var(--t-state) var(--e-out);
}
.btn:hover  { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

.btn-ink   { background: var(--ink);  color: var(--on-ink); box-shadow: var(--sh-raised); }
.btn-ink:hover { box-shadow: var(--sh-lifted); }
.btn-gold  { background: var(--gold); color: var(--ink); box-shadow: var(--sh-on-gold); }
.btn-sheet { background: var(--sheet); color: var(--ink); box-shadow: var(--sh-on-gold); }
.btn-line  { background: transparent; color: currentColor; box-shadow: inset 0 0 0 2px currentColor; }
.btn-line:hover { background: currentColor; }
.btn-line:hover > * { mix-blend-mode: difference; }

.btn .arw { transition: transform var(--t-state) var(--e-out); }
.btn:hover .arw { transform: translateX(4px); }

/* ------------------------------------------------- pre-launch (TEMPORARY) -- */
/* 🔴 REMOVE THIS BLOCK ON LAUNCH DAY. Both store pages return 404 today
   (checked 2026-07-31), so every store badge on this site was a dead link —
   the worst possible state for the only call to action on the page.
   Until they resolve the badges stay visible — the logos are what a reader
   recognises — but they are not links.
   ⚠️ There is deliberately NO waitlist. One was built and removed the same
   evening: there is no mailing list, no sender and no automation behind it, so
   the form promised an email nobody could send. A promise you cannot keep is
   the same defect as an invented review, and this site does not carry those.
   To go live: delete `.launch`, restore the two <a class="store-badge"> pairs,
   and delete this CSS block. Nothing else references it. */
.launch { display: flex; flex-direction: column; gap: var(--sp-4); align-items: flex-start; }
.launch-flag {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 7px 15px 7px 12px; border-radius: var(--r-pill);
  background: var(--ink); color: var(--gold);
  font-size: .78rem; font-weight: 700; letter-spacing: .04em;
  font-variation-settings: "opsz" 12, "wdth" 100;
}
.launch-flag::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--gold); flex: none;
  animation: pulse 2.4s var(--e-out) infinite;
}
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }
.stores--soon { opacity: .5; filter: grayscale(1); pointer-events: none; }

.launch-note { font-size: .88rem; font-weight: 600; color: var(--muted-gold); max-width: 46ch; }
.s-page .launch-note { color: var(--muted); }
.s-ink  .launch-note { color: var(--on-ink-soft); }
.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;
}

/* store badges — real SVG, not a coloured rectangle */
.stores { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
.store-badge {
  display: block; border-radius: 10px;
  transition: transform var(--t-state) var(--e-out), filter var(--t-state) var(--e-out);
}
.store-badge img { height: 54px; width: auto; }
.store-badge:hover { transform: translateY(-2px); }

/* ---------------------------------------------------------------- nav ----- */
.nav {
  position: sticky; top: 0; z-index: var(--z-nav);
  background: var(--gold);
  transition: background-color var(--t-move) var(--e-out),
              box-shadow var(--t-move) var(--e-out);
}
.nav.is-stuck { box-shadow: 0 1px 0 rgba(3,24,38,.10), 0 10px 30px rgba(107,74,18,.10); }
.nav-in { display: flex; align-items: center; gap: var(--sp-5); min-height: 74px; }
.brand {
  display: flex; align-items: center; gap: 10px;
  text-decoration: none; font-weight: 700; font-size: 1.22rem;
  letter-spacing: -.03em; font-variation-settings: "opsz" 24, "wdth" 100;
}
.brand img { width: 34px; height: 34px; border-radius: 9px; box-shadow: var(--sh-resting); }
.nav-links { display: flex; gap: 4px; margin-inline-start: auto; }
.nav-links a {
  text-decoration: none; font-weight: 700; font-size: .96rem;
  padding: 9px 14px; border-radius: var(--r-pill);
  font-variation-settings: "opsz" 14, "wdth" 100;
  transition: background-color var(--t-state) var(--e-out);
}
.nav-links a:hover { background: rgba(3,24,38,.09); }
.nav .btn { min-height: 44px; padding: 0 20px; font-size: .95rem; }
.nav-toggle {
  display: none; margin-inline-start: auto;
  background: none; border: 0; padding: 10px; cursor: pointer; border-radius: var(--r-control);
}
@media (max-width: 900px) {
  .nav-links { display: none; }
  .nav-toggle { display: block; }
  .nav .btn-ink { display: none; }
  .nav.is-open .nav-links {
    display: flex; position: absolute; inset: 74px 0 auto 0;
    flex-direction: column; gap: 0; margin: 0;
    background: var(--gold); padding: var(--sp-3) var(--gutter) var(--sp-5);
    box-shadow: 0 20px 40px rgba(107,74,18,.16);
  }
  .nav.is-open .nav-links a { padding: 15px 0; font-size: 1.15rem; }
}

/* ---------------------------------------------------------------- hero ---- */
/* ⚠️ The headline must be inside the first screenful. The first cut aligned the
   grid to `end` so the phone sat on the baseline — which pushed the h1 below a
   736px-tall phone and off a 900px window entirely. Lorenzo opened it and asked
   where the title was. The fold is not a place the headline can be moved out of:
   centre the row, cap the phone, and let the phone bleed off the bottom edge
   (which is what makes it read as a device rather than a picture of one). */
.hero {
  position: relative; overflow: hidden;
  padding-block: clamp(28px, 4vw, 52px) 0;
  min-height: min(90svh, 820px);
  display: flex; align-items: center;
}
/* One soft sun behind the phone. Not a mesh of three blobs — one light source,
   the same one the store panels use. */
.hero::before {
  content: ""; position: absolute; z-index: 0;
  width: 120vmax; height: 120vmax; right: -34vmax; top: -46vmax;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(255,255,255,.62) 0%, rgba(255,255,255,.22) 45%, rgba(255,255,255,0) 68%);
  pointer-events: none;
}
.hero-grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1.22fr) minmax(0, .78fr);
  gap: clamp(24px, 4vw, 64px); align-items: center;
}
/* `minmax(0, …)` above and this below are the same fix said twice, because a
   grid item's default `min-width: auto` refuses to shrink below its longest
   word and pushes the whole row wider than the page. Without them, one long
   headline silently breaks the entire fold. */
.hero-grid > * { min-width: 0; }
.hero-copy .t-lead { margin-top: var(--sp-5); color: var(--ink); }
.hero-cta { margin-top: var(--sp-6); display: flex; flex-direction: column; gap: var(--sp-4); align-items: flex-start; }
.hero-note { display: flex; align-items: center; gap: 9px; font-size: .92rem; font-weight: 600; color: var(--muted-gold); }
.hero-note .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink); flex: none; }

/* The phone: a white object floating in the gold room. It is CLIPPED by the
   hero rather than fitted inside it — a device that runs off the bottom edge
   reads as a real one; a whole phone centred in a box reads as a picture of a
   phone. `.hero` owns the overflow: hidden that makes that safe. */
.stage { position: relative; display: flex; justify-content: center; align-items: center; }
.phone {
  position: relative; width: min(302px, 74vw);
  border-radius: 42px; padding: 8px;
  background: var(--ink);
  box-shadow: var(--sh-on-gold);
}
.phone img { border-radius: 35px; display: block; }
.phone--tilt { transform: rotate(-2.2deg); }
/* Hero only: let it bleed. Feature sections show the phone whole. */
.hero .phone { margin-bottom: clamp(-190px, -14vh, -90px); }

/* floating proof stickers — the same voice as the store panels */
.sticker {
  position: absolute; z-index: 2;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 17px; border-radius: var(--r-pill);
  font-size: .92rem; font-weight: 700; letter-spacing: -.015em;
  font-variation-settings: "opsz" 14, "wdth" 100;
  box-shadow: var(--sh-on-gold); white-space: nowrap;
}
.sticker--ink   { background: var(--ink);  color: var(--gold); }
.sticker--sheet { background: var(--sheet); color: var(--ink); }
.sticker--a { left: -6%;  top: 26%; transform: rotate(-3deg); }
.sticker--b { right: -4%; bottom: 22%; transform: rotate(2.5deg); }
@media (max-width: 1080px) { .sticker--a { left: -2%; } .sticker--b { right: 0; } }

@media (max-width: 860px) {
  .hero-grid { grid-template-columns: 1fr; }
  .hero-copy { padding-bottom: var(--sp-6); }
  .hero-cta { align-items: stretch; }
  .hero-cta .stores { justify-content: flex-start; }
  .sticker { font-size: .82rem; padding: 9px 14px; }
}

/* ------------------------------------------------------------ tag pill ---- */
/* The store panels' own device: a small ink capsule carrying one gold word,
   sitting above a two-weight headline. It is the ONE place caps are allowed on
   this site besides an aisle sign — a marker, not a section eyebrow, and it
   names the job (Plan / Shop / Keep) rather than labelling the section. */
.tag-pill {
  display: inline-block; margin-bottom: var(--sp-4);
  padding: 7px 15px; border-radius: var(--r-pill);
  background: var(--ink); color: var(--gold);
  font-size: .74rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  font-variation-settings: "opsz" 12, "wdth" 100;
}

/* The paragraph that lands after the sticky stage. It needs real clearance:
   a sticky element releases at the end of its rail, and anything sitting tight
   underneath reads as collided even when it technically is not. */
.derive-outro { padding-block: clamp(40px, 7vh, 84px) var(--band); }

/* -------------------------------------------------------------- cards ----- */
.card {
  background: var(--sheet); border-radius: var(--r-card);
  box-shadow: var(--sh-resting); padding: var(--sp-5);
}
/* 1px border + wide soft shadow on the same element is the "ghost card" tell.
   Cards here get the shadow and nothing else. */

/* -------------------------------------------------- the derive sequence --- */
/* The one thing every competitor claims and none of them shows: the plan
   BECOMING the list. Scroll drives it. */
.derive { position: relative; }
.derive-rail { height: 260vh; }                 /* the scroll budget          */
/* ⚠️ `overflow: hidden` is not decoration — it is the guarantee.
   A sticky box whose content is taller than the box does not scroll: it spills,
   and because it is sticky it paints ON TOP of whatever section follows. That
   is exactly what shipped — the shopping-list sheet was drawn straight across
   the paragraph below it. Two defences, both needed: the content is sized to
   fit inside the stage, and the stage clips anything that ever stops fitting
   (a longer translation, a bigger default font, a shorter window). */
.derive-stage {
  position: sticky; top: 0;
  height: 100svh; max-height: 100vh; overflow: hidden;
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(20px, 4vw, 56px); align-items: center;
  padding-block: clamp(88px, 11vh, 108px) clamp(28px, 6vh, 56px);
}
.derive-col { min-width: 0; }
.derive-col h3 { margin-bottom: var(--sp-4); }

/* Everything below is sized in vh as well as px: six meal rows AND a nine-line
   list have to clear a laptop window that still has browser chrome on it. */
.meal-row {
  display: flex; align-items: baseline; gap: 12px;
  padding: clamp(9px, 1.4vh, 13px) 16px; margin-bottom: clamp(6px, .9vh, 9px);
  background: var(--sheet); border-radius: var(--r-card);
  box-shadow: var(--sh-resting);
  transition: opacity var(--t-move) var(--e-out), transform var(--t-move) var(--e-out),
              box-shadow var(--t-move) var(--e-out);
}
.meal-row .day { font-size: .76rem; font-weight: 700; color: var(--outline); width: 3.1em; flex: none;
                 font-variation-settings: "opsz" 12, "wdth" 100; }
.meal-row .dish { font-size: clamp(.94rem, 1.55vh, 1.05rem); font-weight: 700; letter-spacing: -.022em;
                  font-variation-settings: "opsz" 24, "wdth" 100; }
.meal-row.is-lit { box-shadow: var(--sh-raised); transform: translateX(6px); }
.meal-row.is-lit .dish { color: var(--ink); }

.list-sheet {
  background: var(--sheet); border-radius: var(--r-sheet);
  box-shadow: var(--sh-lifted);
  padding: clamp(14px, 2.2vh, 22px) var(--sp-5) clamp(10px, 1.6vh, 17px);
  overflow: hidden;
}
.list-aisle + .list-aisle { margin-top: clamp(7px, 1.5vh, 15px); }
.list-aisle .t-aisle { display: block; margin-bottom: 3px; }
/* Visible by default — the shopping list is the CONTENT of this section, and
   content never waits for JavaScript to exist. `.is-armed` is added by main.js
   from inside a real animation frame, and only then do the rows start hidden
   and arrive on scroll. */
.list-item {
  display: flex; align-items: center; gap: 12px;
  padding: clamp(4px, .75vh, 7px) 0;
  font-size: clamp(.94rem, 1.55vh, 1.05rem); font-weight: 700; letter-spacing: -.022em;
  font-variation-settings: "opsz" 24, "wdth" 100;
}
.derive-rail.is-armed .list-item { opacity: 0; transform: translateY(10px);
  transition: opacity 320ms var(--e-out), transform 320ms var(--e-out); }
.derive-rail.is-armed .list-item.is-in { opacity: 1; transform: none; }
.list-item .box {
  width: 20px; height: 20px; flex: none; border-radius: 6px;
  border: 2px solid var(--outline);
}
.list-item .qty { margin-inline-start: auto; font-size: .88rem; font-weight: 600; color: var(--muted); }
.derive-progress { height: 6px; border-radius: 3px; background: rgba(3,24,38,.10); overflow: hidden;
                   margin-bottom: clamp(9px, 1.7vh, 17px); }
.derive-progress i { display: block; height: 100%; width: 0; background: var(--ink); border-radius: 3px;
                     transition: width var(--t-move) var(--e-out); }

/* ⚠️ The mobile flattening of this section used to live HERE, in the middle of
   the stylesheet, and it was incomplete in a way that only showed on a phone.
   It has moved to the MOBILE block at the end of the file, where the rest of the
   responsive rules live. See the note there. */

/* ------------------------------------------------------------ showcase ---- */
/* One carousel, one feature per slide — and the explanation lives INSIDE the
   slide, next to its own screenshot. The first cut kept the captions in a
   separate block above the track and swapped them as you scrolled: two things
   that had to agree, so they could disagree, and they did.
   The track is a native scroll-snap row: the finger drags the real thing with
   the platform's own momentum. JavaScript only lights the dots. */
.showcase { overflow: hidden; }
.show-head { text-align: center; margin-bottom: clamp(22px, 3vw, 38px); }

.show-track {
  display: flex; gap: clamp(16px, 2vw, 28px);
  overflow-x: auto; scroll-snap-type: x mandatory;
  padding-block: 6px 10px;
  padding-inline: var(--gutter);
  scrollbar-width: none; -ms-overflow-style: none;
  overscroll-behavior-x: contain;
}
.show-track::-webkit-scrollbar { display: none; }

.show-slide {
  flex: 0 0 auto; scroll-snap-align: center;
  width: min(1080px, calc(100vw - var(--gutter) * 2));
  background: var(--sheet); border-radius: var(--r-sheet);
  box-shadow: var(--sh-raised);
  padding: clamp(24px, 3.4vw, 52px);
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 300px);
  gap: clamp(24px, 4vw, 64px); align-items: center;
}
.show-slide .copy { min-width: 0; }
.show-slide .tag-pill { margin-bottom: var(--sp-4); }
.show-slide h3 {
  font-size: clamp(1.6rem, 3vw, 2.5rem); line-height: 1.04; letter-spacing: -.032em;
  font-variation-settings: "opsz" 36, "wdth" 100;
}
.show-slide p { margin-top: var(--sp-4); font-size: 1.05rem; line-height: 1.5;
                color: var(--muted); max-width: 46ch; }
.show-slide ul { list-style: none; margin-top: var(--sp-5); display: grid; gap: 11px; }
.show-slide li { display: flex; gap: 12px; align-items: flex-start; font-size: 1rem; font-weight: 500; }
.show-slide li .tick {
  flex: none; width: 23px; height: 23px; border-radius: 7px;
  background: var(--gold); color: var(--ink);
  display: grid; place-items: center; font-size: .74rem; font-weight: 700;
  box-shadow: var(--sh-resting);
}
.show-slide .shot { justify-self: center; }
.show-slide .phone { width: min(272px, 68vw); }
@media (max-width: 820px) {
  .show-slide { grid-template-columns: 1fr; text-align: left; }
  .show-slide .shot { order: -1; }
  .show-slide .phone { width: min(232px, 62vw); }
}

.show-nav { display: flex; gap: 14px; justify-content: center; align-items: center;
            margin-top: clamp(20px, 2.6vw, 32px); }
.show-arrow {
  width: 46px; height: 46px; flex: none; border: 0; border-radius: 50%;
  background: var(--sheet); color: var(--ink); box-shadow: var(--sh-resting);
  display: grid; place-items: center; cursor: pointer;
  transition: transform var(--t-state) var(--e-out), box-shadow var(--t-state) var(--e-out),
              opacity var(--t-state) var(--e-out);
}
.show-arrow:hover { transform: translateY(-2px); box-shadow: var(--sh-raised); }
.show-arrow[disabled] { opacity: .3; cursor: default; transform: none; }
.show-dots { display: flex; gap: 9px; align-items: center; }
.show-dots button {
  position: relative;
  width: 9px; height: 9px; padding: 0; border: 0; border-radius: 50%;
  background: var(--outline); cursor: pointer;
  transition: all var(--t-state) var(--e-out);
}
/* A 9px dot is a 9px target, which is a fifth of what a thumb needs. The dot
   stays 9px and the HIT AREA becomes 45px — measured on a 375px viewport,
   where the audit flagged all three at 9px high. */
.show-dots button::before { content: ""; position: absolute; inset: -18px; border-radius: 50%; }
.show-dots button[aria-selected="true"] { background: var(--ink); width: 28px; border-radius: var(--r-pill); }
@media (prefers-reduced-motion: reduce) {
  .show-arrow:hover { transform: none; }
}

/* 🔴 THE GAP ABOVE THE FOOTER.
   Reported four separate times and never actually measured: an article ended
   with a button and the dark footer began on the very next pixel, so the text
   sat welded to the black band. Article pages have no closing section to give
   them room, so the room is declared here, once, for every page that ends in
   content. `tools/audit.js` now fails if any page's last content element sits
   closer than 48px to the footer. */
/* ⚠️ Scoped to ARTICLE pages on purpose. Put on `main` it also applied to the
   home page and the blog index, which end on a dark closing band — so a strip
   of page colour appeared between that band and the dark footer, and it read as
   a hole in the page. Recipes and guides end in content and need the room;
   pages that close on a band already have it. */
main > article { padding-bottom: clamp(56px, 8vw, 112px); }

/* -------------------------------------------------------------- refuse ---- */
/* What Farro refuses to do. The most ownable page-section this product has —
   every line is a competitor's one-star review. */
.refuse-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1px;
               background: rgba(247,244,236,.18); border-radius: var(--r-card); overflow: hidden; }
.refuse-cell { background: var(--ink); padding: var(--sp-5) var(--sp-5) var(--sp-6); }
.refuse-cell .no {
  display: inline-block; font-size: .74rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--gold); margin-bottom: 12px;
  font-variation-settings: "opsz" 12, "wdth" 100;
}
.refuse-cell h4 { font-size: 1.16rem; letter-spacing: -.026em; font-variation-settings: "opsz" 24, "wdth" 100; }
.refuse-cell p { margin-top: 9px; font-size: .96rem; line-height: 1.5; color: var(--on-ink-soft); }

/* ---------------------------------------------------------------- FAQ ----- */
.faq { display: grid; gap: 10px; }
.faq-item { background: var(--sheet); border-radius: var(--r-card); box-shadow: var(--sh-resting); overflow: hidden; }
.faq-item summary {
  cursor: pointer; list-style: none; padding: var(--sp-4) var(--sp-5);
  font-size: 1.08rem; font-weight: 700; letter-spacing: -.022em;
  font-variation-settings: "opsz" 24, "wdth" 100;
  display: flex; align-items: center; gap: var(--sp-4);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: ""; flex: none; margin-inline-start: auto;
  width: 13px; height: 13px; border-right: 2.5px solid var(--ink); border-bottom: 2.5px solid var(--ink);
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform var(--t-state) var(--e-out);
}
.faq-item[open] summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.faq-item .a { padding: 0 var(--sp-5) var(--sp-5); }
.faq-item .a p { color: var(--muted); font-size: 1rem; line-height: 1.55; }

/* --------------------------------------------------------- blog cards ----- */
.post-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: var(--sp-5); }
.post-card {
  display: flex; flex-direction: column; text-decoration: none;
  background: var(--sheet); border-radius: var(--r-card); overflow: hidden;
  box-shadow: var(--sh-resting);
  transition: transform var(--t-move) var(--e-out), box-shadow var(--t-move) var(--e-out);
}
.post-card:hover { transform: translateY(-4px); box-shadow: var(--sh-lifted); }
.post-card .shot { aspect-ratio: 16 / 10; overflow: hidden; background: var(--page); }
.post-card .shot img { width: 100%; height: 100%; object-fit: cover;
                       transition: transform 600ms var(--e-out); }
.post-card:hover .shot img { transform: scale(1.04); }
.post-card .body { padding: var(--sp-5); display: flex; flex-direction: column; gap: 9px; flex: 1; }
.post-card h3 { font-size: 1.18rem; letter-spacing: -.028em; line-height: 1.14;
                font-variation-settings: "opsz" 24, "wdth" 100; }
.post-card .excerpt { font-size: .96rem; color: var(--muted); line-height: 1.5; }
.post-card .meta { margin-top: auto; padding-top: 6px; font-size: .8rem; font-weight: 700; color: var(--outline);
                   font-variation-settings: "opsz" 12, "wdth" 100; }
.pill-tag {
  display: inline-block; padding: 5px 11px; border-radius: var(--r-pill);
  background: var(--gold); color: var(--ink); font-size: .72rem; font-weight: 700;
  letter-spacing: .02em; font-variation-settings: "opsz" 12, "wdth" 100;
}

/* ------------------------------------------------------------- article ---- */
.article { padding-block: var(--band); }
.article-head { width: min(760px, 100% - var(--gutter) * 2); margin-inline: auto; }
.article-head h1 { margin-top: var(--sp-4); }
.article-meta { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: var(--sp-4);
                font-size: .88rem; font-weight: 600; color: var(--muted); }
.article-hero { margin: var(--sp-6) auto 0; width: min(1000px, 100% - var(--gutter) * 2); }
.article-hero img { width: 100%; border-radius: var(--r-sheet); box-shadow: var(--sh-raised); aspect-ratio: 16/9; object-fit: cover; }
.prose { width: min(680px, 100% - var(--gutter) * 2); margin: var(--sp-7) auto 0; font-size: 1.09rem; line-height: 1.68; }
.prose > * + * { margin-top: 1.15em; }
.prose h2 { font-size: clamp(1.5rem, 3vw, 1.95rem); letter-spacing: -.03em; line-height: 1.1;
            margin-top: 2.1em; font-variation-settings: "opsz" 36, "wdth" 100; }
.prose h3 { font-size: 1.24rem; letter-spacing: -.026em; margin-top: 1.7em;
            font-variation-settings: "opsz" 24, "wdth" 100; }
.prose p, .prose li { color: #1E3140; }         /* 12.4:1 on page — prose ink  */
.prose ul, .prose ol { padding-inline-start: 1.3em; display: grid; gap: .5em; }
.prose li::marker { color: var(--outline); }
.prose a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--gold);
           text-decoration-thickness: 3px; text-underline-offset: 3px; }
.prose a:hover { background: var(--gold); text-decoration-color: transparent; }
.prose blockquote {
  margin-block: 1.6em; padding: var(--sp-5) var(--sp-5) var(--sp-5) 0;
  border: 0; font-size: 1.28rem; line-height: 1.35; font-weight: 700; letter-spacing: -.026em;
  font-variation-settings: "opsz" 36, "wdth" 100;
}
.prose blockquote p { color: var(--ink); }
.prose img { border-radius: var(--r-card); box-shadow: var(--sh-resting); margin-block: 1.6em; }
.prose figcaption { font-size: .86rem; color: var(--muted); margin-top: .6em; }
.prose hr { border: 0; height: 2px; background: var(--hairline); margin-block: 2.4em; }

/* the in-article conversion unit: the reason the recipe posts exist */
/* .import-cta is styled in the composed block above */
.import-cta h3 { font-size: clamp(1.4rem, 3vw, 1.85rem); letter-spacing: -.03em; line-height: 1.06;
                 font-variation-settings: "opsz" 36, "wdth" 100; }
.import-cta p { margin-top: 12px; color: var(--muted-gold); font-size: 1rem; line-height: 1.5; }
.import-steps { list-style: none; counter-reset: s; margin-top: var(--sp-5); display: grid; gap: 12px; }
.import-steps li { display: flex; gap: 13px; align-items: flex-start; font-weight: 600; font-size: 1rem; }
.import-steps li::before {
  counter-increment: s; content: counter(s);
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  background: var(--ink); color: var(--gold);
  display: grid; place-items: center; font-size: .82rem; font-weight: 700;
}
.import-url {
  display: flex; align-items: center; gap: 10px; margin-top: var(--sp-5);
  background: var(--sheet); border-radius: var(--r-pill); padding: 8px 8px 8px 18px;
  box-shadow: var(--sh-resting);
}
.import-url code { flex: 1; min-width: 0; font-family: inherit; font-size: .92rem; font-weight: 600;
                   color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.import-url button { flex: none; min-height: 40px; padding: 0 18px; border-radius: var(--r-pill);
                     background: var(--ink); color: var(--gold); border: 0; font-weight: 700; font-size: .9rem; cursor: pointer; }

/* ---------------------------------------------------------- dish cards ---- */
/* The five dinners inside a week article. Each one is a LINK to its own page,
   because one page carries one importable recipe — so the card is the object
   that makes that architecture visible instead of a rule in a README. */
.dish-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
  gap: var(--sp-5); margin-top: var(--sp-7);
}
.dish-card {
  display: flex; flex-direction: column; text-decoration: none;
  background: var(--sheet); border-radius: var(--r-card); overflow: hidden;
  box-shadow: var(--sh-resting);
  transition: transform var(--t-move) var(--e-out), box-shadow var(--t-move) var(--e-out);
}
.dish-card:hover { transform: translateY(-4px); box-shadow: var(--sh-lifted); }
.dish-card .shot { aspect-ratio: 16 / 10; overflow: hidden; background: var(--page); }
.dish-card .shot img { width: 100%; height: 100%; object-fit: cover;
                       transition: transform 600ms var(--e-out); }
.dish-card:hover .shot img { transform: scale(1.04); }
.dish-card .body { padding: var(--sp-5); display: flex; flex-direction: column; gap: 9px; flex: 1; }
.dish-card h3 { font-size: 1.24rem; letter-spacing: -.03em; line-height: 1.1;
                font-variation-settings: "opsz" 24, "wdth" 100; }
.dish-card .excerpt { font-size: .96rem; color: var(--muted); line-height: 1.5; }
.dish-card .meta { margin-top: auto; padding-top: 8px; font-size: .82rem; font-weight: 700;
                   color: var(--ink); font-variation-settings: "opsz" 12, "wdth" 100; }
.dish-card:hover .meta .arw { transform: translateX(4px); }
.dish-card .meta .arw { display: inline-block; transition: transform var(--t-state) var(--e-out); }

/* ------------------------------------------------------- recipe page ------ */
/* Rebuilt: a gold masthead with the three facts on it, a full-bleed photograph,
   a short piece of writing about the dish, then ingredients and method side by
   side so you can read one while doing the other — which is how a recipe is
   actually used and is the whole reason the two-column form exists in print. */
.recipe-head { padding-block: clamp(40px, 6vw, 76px) clamp(34px, 5vw, 60px); }
.recipe-head h1 { margin-top: var(--sp-2); max-width: 18ch; }
.recipe-head .t-lead { color: var(--ink); font-size: clamp(1.1rem, 1.6vw, 1.3rem); }
.recipe-head .tag-pill { transition: transform var(--t-state) var(--e-out); }
.recipe-head .tag-pill:hover { transform: translateY(-2px); }

.fact-strip {
  list-style: none; display: flex; flex-wrap: wrap; gap: clamp(20px, 4vw, 52px);
  margin-top: var(--sp-6); padding-top: var(--sp-5);
  border-top: 2px solid rgba(3,24,38,.14);
}
.fact-strip b { display: block; font-size: clamp(1.3rem, 2.2vw, 1.75rem); letter-spacing: -.03em;
                line-height: 1; font-variation-settings: "opsz" 36, "wdth" 100; }
.fact-strip span { display: block; margin-top: 4px; font-size: .78rem; font-weight: 700;
                   letter-spacing: .07em; text-transform: uppercase; color: var(--muted-gold); }

.recipe-shot { margin: 0; }
.recipe-shot img { width: 100%; aspect-ratio: 21 / 9; object-fit: cover; }
@media (max-width: 700px) { .recipe-shot img { aspect-ratio: 4 / 3; } }

/* the story sits large and dark — it is writing, not a caption */
.recipe-story { margin-top: clamp(34px, 5vw, 64px); }
.recipe-story p { font-size: clamp(1.12rem, 1.6vw, 1.28rem); line-height: 1.6; color: var(--ink); }
.recipe-story p::first-letter {
  float: left; font-size: 3.1em; line-height: .82; padding: .06em .1em 0 0;
  font-weight: 700; font-variation-settings: "opsz" 72, "wdth" 100;
}

.recipe-cols {
  display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
  gap: clamp(20px, 3vw, 42px); align-items: start;
}
@media (max-width: 820px) { .recipe-cols { grid-template-columns: 1fr; } }
.recipe-panel { background: var(--sheet); border-radius: var(--r-sheet);
                box-shadow: var(--sh-resting); padding: clamp(22px, 3vw, 36px); }
.recipe-panel--ing { background: var(--ink); color: var(--on-ink); box-shadow: var(--sh-raised); }
.recipe-panel h2 { margin-bottom: var(--sp-5); }
.recipe-panel ul { list-style: none; display: grid; gap: 11px; }
.recipe-panel li { display: flex; gap: 12px; align-items: flex-start; font-size: 1.02rem;
                   font-weight: 700; letter-spacing: -.022em; line-height: 1.35;
                   font-variation-settings: "opsz" 24, "wdth" 100; }
.recipe-panel--ing li .box { width: 19px; height: 19px; flex: none; margin-top: 3px;
  border-radius: 6px; border: 2px solid rgba(247,244,236,.42); }
.recipe-panel--ing .t-cap { color: var(--on-ink-soft); }
.recipe-panel ol { padding-inline-start: 0; list-style: none; counter-reset: step;
                   display: grid; gap: var(--sp-5); }
.recipe-panel ol li { display: grid; grid-template-columns: 34px 1fr; gap: 15px;
                      font-size: 1.04rem; font-weight: 500; line-height: 1.55;
                      letter-spacing: 0; font-variation-settings: "opsz" 18, "wdth" 100; }
.recipe-panel ol li::before {
  counter-increment: step; content: counter(step);
  width: 34px; height: 34px; border-radius: 50%; background: var(--gold); color: var(--ink);
  display: grid; place-items: center; font-size: .92rem; font-weight: 700;
  font-variation-settings: "opsz" 14, "wdth" 100;
}

/* --------------------------------------------------------- guide format -- */
/* ONE shape for every guide, forever: the question, the short answer in a gold
   box, numbered sections, FAQ. The point is that writing guide number forty
   requires no layout decisions — the format is the product of the pillar, not
   of each article. */
.answer-box {
  width: min(760px, 100% - var(--gutter) * 2);
  margin: calc(var(--band) * -0.45) auto 0; position: relative; z-index: 2;
  background: var(--sheet); border-radius: var(--r-sheet);
  box-shadow: var(--sh-lifted); padding: clamp(22px, 3vw, 36px);
}
.answer-box .t-aisle { display: block; margin-bottom: 10px; }
.answer-box p { font-size: clamp(1.08rem, 1.5vw, 1.24rem); line-height: 1.55; color: var(--ink); }

.guide-body { width: min(680px, 100% - var(--gutter) * 2); margin: clamp(40px, 6vw, 76px) auto 0; }
.guide-step + .guide-step { margin-top: clamp(32px, 4.5vw, 56px); }
.guide-step h2 {
  display: flex; gap: 15px; align-items: baseline;
  font-size: clamp(1.35rem, 2.4vw, 1.8rem); line-height: 1.1; letter-spacing: -.03em;
  font-variation-settings: "opsz" 36, "wdth" 100; margin-bottom: var(--sp-4);
}
.guide-step h2 .n {
  flex: none; width: 34px; height: 34px; border-radius: 50%;
  background: var(--gold); color: var(--ink);
  display: grid; place-items: center; font-size: .92rem;
  font-variation-settings: "opsz" 14, "wdth" 100;
}
.guide-step p { font-size: 1.06rem; line-height: 1.62; color: #1E3140; }
.guide-step p + p { margin-top: 1em; }

/* --------------------------------------------------------- blog index ----- */
/* One grid and a row of chips, not stacked sections with headings nobody could
   decode. The chips filter in place, the URL keeps the choice (?show=recipes)
   so a filtered view can be linked and shared, and with JavaScript off every
   card is simply present. */
.chips { display: flex; flex-wrap: wrap; gap: 9px; justify-content: center;
         margin-bottom: clamp(26px, 3.4vw, 44px); }
.chip {
  border: 0; cursor: pointer; min-height: 44px; padding: 0 20px;
  border-radius: var(--r-pill); background: var(--sheet); color: var(--ink);
  font-weight: 700; font-size: .96rem; letter-spacing: -.01em;
  font-variation-settings: "opsz" 14, "wdth" 100;
  box-shadow: var(--sh-resting);
  transition: transform var(--t-state) var(--e-out), background var(--t-state) var(--e-out),
              color var(--t-state) var(--e-out), box-shadow var(--t-state) var(--e-out);
}
.chip:hover { transform: translateY(-2px); }
.chip[aria-pressed="true"] { background: var(--ink); color: var(--gold); box-shadow: var(--sh-raised); }
.chip .n { opacity: .55; margin-inline-start: 6px; font-size: .86em; }

/* the featured card: the newest thing, given the room it deserves */
.feature-card {
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  background: var(--sheet); border-radius: var(--r-sheet); overflow: hidden;
  box-shadow: var(--sh-raised); text-decoration: none;
  margin-bottom: clamp(20px, 2.6vw, 34px);
  transition: transform var(--t-move) var(--e-out), box-shadow var(--t-move) var(--e-out);
}
.feature-card:hover { transform: translateY(-4px); box-shadow: var(--sh-lifted); }
.feature-card .shot { aspect-ratio: 4 / 3; overflow: hidden; }
.feature-card .shot img { width: 100%; height: 100%; object-fit: cover;
                          transition: transform 700ms var(--e-out); }
.feature-card:hover .shot img { transform: scale(1.04); }
.feature-card .body { padding: clamp(24px, 3vw, 44px); display: flex; flex-direction: column;
                      justify-content: center; gap: 12px; }
.feature-card h3 { font-size: clamp(1.5rem, 2.6vw, 2.2rem); line-height: 1.06; letter-spacing: -.032em;
                   font-variation-settings: "opsz" 36, "wdth" 100; }
.feature-card .excerpt { color: var(--muted); font-size: 1.02rem; line-height: 1.5; }
.feature-card .meta { font-size: .82rem; font-weight: 700; color: var(--outline);
                      font-variation-settings: "opsz" 12, "wdth" 100; }
@media (max-width: 780px) { .feature-card { grid-template-columns: 1fr; } .feature-card .shot { aspect-ratio: 16/10; } }

/* a recipe card wears its numbers; a guide card wears its reading time */
.post-card .shot { position: relative; }
.post-card .stamp {
  position: absolute; left: 12px; top: 12px; z-index: 1;
  background: var(--ink); color: var(--gold);
  padding: 6px 12px; border-radius: var(--r-pill);
  font-size: .72rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  font-variation-settings: "opsz" 12, "wdth" 100;
}
.post-card.is-hidden { display: none; }
.blog-empty { text-align: center; color: var(--muted); padding-block: var(--sp-8); }

/* ------------------------------------------------------------- final CTA -- */
.cta-final { position: relative; overflow: hidden; text-align: center; }
.cta-final::before {
  content: ""; position: absolute; inset: auto 50% -60vmax auto;
  width: 110vmax; height: 110vmax; transform: translateX(50%); border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,.5) 0%, rgba(255,255,255,0) 62%);
  pointer-events: none;
}
.cta-final .wrap { position: relative; z-index: 1; }
.cta-final .t-lead { margin: var(--sp-5) auto 0; color: var(--ink); }
.cta-final .stores { justify-content: center; margin-top: var(--sp-6); }

/* --------------------------------------------------------------- footer --- */
.footer { background: var(--ink); color: var(--on-ink); padding-block: var(--sp-8) var(--sp-6); }
.footer-grid { display: grid; grid-template-columns: 1.6fr repeat(3, 1fr); gap: var(--sp-6); }
.footer h5 { font-size: .8rem; letter-spacing: .07em; text-transform: uppercase; color: var(--gold);
             margin: 0 0 var(--sp-4); font-variation-settings: "opsz" 12, "wdth" 100; }
.footer ul { list-style: none; display: grid; gap: 11px; }
.footer a { text-decoration: none; color: var(--on-ink-soft); font-size: .97rem; }
.footer a:hover { color: var(--gold); }
.footer .brand { color: var(--on-ink); }
.footer .blurb { margin-top: var(--sp-4); font-size: .97rem; color: var(--on-ink-soft); max-width: 40ch; line-height: 1.55; }
.footer-bottom { margin-top: var(--sp-8); padding-top: var(--sp-5); border-top: 1px solid rgba(247,244,236,.16);
                 display: flex; flex-wrap: wrap; gap: var(--sp-4); justify-content: space-between; align-items: center;
                 font-size: .88rem; color: var(--on-ink-soft); }
@media (max-width: 760px) { .footer-grid { grid-template-columns: 1fr 1fr; } }

/* ------------------------------------------------------------- reveals ---- */
/* Reveals ENHANCE an already-visible default: without JS (or in a headless
   renderer, or on a hidden tab) `.js` is never set and everything paints
   normally. Gating visibility on a transition is how a section ships blank. */
.js .reveal { opacity: 0; transform: translateY(18px); }
.js .reveal.is-in { opacity: 1; transform: none;
  transition: opacity var(--t-enter) var(--e-expo), transform var(--t-enter) var(--e-expo); }
.js .reveal.d1.is-in { transition-delay: 90ms; }
.js .reveal.d2.is-in { transition-delay: 180ms; }
.js .reveal.d3.is-in { transition-delay: 270ms; }
/* The guarantee. `.is-in` animates TO visible, which needs a running
   compositor; `.is-shown` simply IS visible, with no transition left to
   advance. main.js forces it 2s after load so no renderer can leave the page
   blank. Nothing else may use !important in this file. */
.js .reveal.is-shown { opacity: 1 !important; transform: none !important; transition: none !important; }

@media (prefers-reduced-motion: reduce) {
  .js .reveal, .js .reveal.is-in { opacity: 1; transform: none; transition: opacity 200ms linear; }
  .btn:hover, .post-card:hover, .store-badge:hover { transform: none; }
  .post-card:hover .shot img { transform: none; }
  .derive-rail .list-item { opacity: 1 !important; transform: none !important; transition: none; }
  .meal-row.is-lit { transform: none; }
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
}

/* ----------------------------------------------------------------- misc --- */
.center { text-align: center; }
.center .t-lead, .center .t-body { margin-inline: auto; }
.stack-4 > * + * { margin-top: var(--sp-4); }
.stack-5 > * + * { margin-top: var(--sp-5); }
.stack-6 > * + * { margin-top: var(--sp-6); }
[hidden] { display: none !important; }

/* ------------------------------------------------------ the reading key --- */
/* Three blocks that only Farro can write, because they come from knowing what a
   recipe costs at the shop rather than how it is cooked. They are the answer to
   "why would anyone read our recipes instead of the ten thousand others". */
.verdict {
  margin-top: calc(var(--band) * -0.42); position: relative; z-index: 2;
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--hairline); border-radius: var(--r-sheet); overflow: hidden;
  box-shadow: var(--sh-lifted);
}
.verdict > div { background: var(--sheet); padding: clamp(18px, 2.4vw, 28px); }
.verdict > div:last-child { background: var(--page); }
.verdict p { margin-top: 8px; font-size: 1.04rem; line-height: 1.45; letter-spacing: -.018em;
             font-weight: 600; color: var(--ink); }
@media (max-width: 640px) { .verdict { grid-template-columns: 1fr; } }

.cupboard { list-style: none; display: grid; gap: 7px; margin-top: 4px; }
.cupboard li { font-size: .96rem; font-weight: 500; color: var(--on-ink-soft);
               letter-spacing: 0; font-variation-settings: "opsz" 14, "wdth" 100; }

/* .leaves is styled in the composed block above */
.leaves .t-cap { color: var(--muted-gold); }
.leaves ul { list-style: none; margin-top: var(--sp-5); display: grid; gap: 12px; }
.leaves li { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: baseline;
             padding-top: 12px; border-top: 1px solid rgba(3,24,38,.16); }
.leaves b { font-size: 1.04rem; letter-spacing: -.024em; font-variation-settings: "opsz" 24; }
.leaves a { margin-inline-start: auto; font-weight: 700; font-size: .95rem;
            text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px; }
.leaves a:hover .arw { transform: translateX(3px); }
.leaves .arw { display: inline-block; transition: transform var(--t-state) var(--e-out); }

/* ============================================================================
   THE RECIPE PAGE, COMPOSED
   ----------------------------------------------------------------------------
   The page had grown to thirteen stacked panels, each with its own width (1000,
   760, 680, wrap) and its own card. Individually fine; together it read as a
   pile of boxes — "una serie di blocchi, tutto molto random", and correctly so.
   The fix is not more styling, it is composition. Three decisions:

     ONE MEASURE. Every zone on this page is --measure wide. Varying widths is
     what made it feel assembled by accident; a single column edge running down
     the whole page is what makes a long page feel edited.

     ONE SURFACE PER ZONE, not per block. The five reference sections stop being
     five cards and become ruled rows on one surface, under one lede. Cards are
     for things you choose between; these are things you read in order.

     THE RAIL. What you buy pins itself while the method scrolls, because that
     is how the page is used with hands in the food.
   ========================================================================== */
:root { --measure: min(880px, 100% - var(--gutter) * 2); }

/* every zone, the same edge */
.recipe-story, .recipe-cols, .verdict, .why, .panel-wide, .leaves, .import-cta {
  width: var(--measure); margin-inline: auto;
}

/* ---- the cook: ingredients pinned, method beside them ------------------- */
.recipe-cols { grid-template-columns: minmax(0, .78fr) minmax(0, 1.22fr);
               gap: clamp(18px, 2.4vw, 34px); margin-top: clamp(34px, 4.5vw, 60px); }
.recipe-panel--ing { position: sticky; top: 88px; }
@media (max-width: 900px) { .recipe-panel--ing { position: static; } }

/* ---- the reference zone: ONE surface, ruled rows -------------------------- */
/* .why and the five .panel-wide sections are siblings in the markup, so they are
   welded here into a single object: same background, no shadows between them, a
   rule where each begins. Nothing floats. */
.why, .panel-wide {
  background: var(--sheet); box-shadow: none; border-radius: 0;
  padding: clamp(22px, 3vw, 34px) clamp(22px, 3vw, 38px);
  margin-top: 0; border-top: 1px solid var(--hairline);
}
.why { margin-top: clamp(40px, 6vw, 72px); border-top: 0;
       border-radius: var(--r-sheet) var(--r-sheet) 0 0; box-shadow: var(--sh-resting); }
.panel-wide:last-of-type { border-radius: 0 0 var(--r-sheet) var(--r-sheet);
                           box-shadow: var(--sh-resting); }
.why h2, .panel-wide h2 { margin-bottom: var(--sp-4); }
.panel-wide .panel-p, .why + .panel-wide .panel-p { margin-top: 0; }

/* the numbered "why" rows lose their circles: inside a reference panel a number
   in a disc is decoration, and the section already has three of everything */
.why ul { counter-reset: w; gap: 11px; }
.why li { grid-template-columns: 1.4em 1fr; gap: 10px; }
.why li::before { width: auto; height: auto; background: none; color: var(--outline);
                  border-radius: 0; font-size: .95rem; place-items: start;
                  content: counter(w) "."; }

.swaps > div { grid-template-columns: minmax(0, .34fr) minmax(0, .66fr); padding: 12px 0; }
.swaps > div:first-child { border-top: 0; padding-top: 0; }
.swaps > div:last-child { border-bottom: 0; padding-bottom: 0; }
.variations { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
              gap: 1px; background: var(--hairline); border-radius: var(--r-card);
              overflow: hidden; margin-top: var(--sp-4); }
.variations b { display: block; font-size: 1.04rem; letter-spacing: -.026em; margin-bottom: 7px;
                font-variation-settings: "opsz" 24, "wdth" 100; }
.variations p { font-size: .95rem; line-height: 1.5; color: var(--muted); }
.variations > div { background: var(--sheet); box-shadow: none; border-radius: 0; }
.panel-wide .faq { margin-top: var(--sp-4); }
.panel-wide .faq-item { box-shadow: none; background: var(--page); }

/* ---- the app zone: one gold object, not three ---------------------------- */
/* Scaling, leftovers and importing are the same argument — this is the part a
   person should stop doing by hand — so they stop being three panels. */
.scale, .leaves, .import-cta {
  border-radius: 0; box-shadow: none; margin-top: 0;
  padding: clamp(22px, 3vw, 34px) clamp(22px, 3vw, 38px);
}
.scale { background: var(--gold); color: var(--ink);
         margin-top: clamp(40px, 6vw, 72px);
         border-radius: var(--r-sheet) var(--r-sheet) 0 0; box-shadow: var(--sh-raised); }
.scale .panel-p { color: var(--muted-gold); }
.scale .panel-p strong { color: var(--ink); }
.leaves { background: var(--gold); border-top: 1px solid rgba(3,24,38,.16); }
.import-cta { background: var(--gold); border-top: 1px solid rgba(3,24,38,.16);
              border-radius: 0 0 var(--r-sheet) var(--r-sheet); box-shadow: var(--sh-raised); }

/* ------------------------------------------------- the editorial blocks --- */
/* Five question classes carry almost all long-tail search on a dish, and each
   one is also a candidate for the block an AI Overview quotes. They are laid out
   as panels rather than more prose so the page stays scannable at 1,800 words. */
/* width and rhythm come from the composed block above — one measure, one edge */
.panel-wide .panel-p { margin-top: var(--sp-4); font-size: 1.04rem; line-height: 1.6; color: #1E3140; }

/* .why is styled in the composed block above */
.why ul { list-style: none; margin-top: var(--sp-5); display: grid; gap: 14px; counter-reset: w; }
.why li { display: grid; grid-template-columns: 30px 1fr; gap: 14px; align-items: baseline;
          font-size: 1.02rem; line-height: 1.55; color: #1E3140; }
.why li::before {
  counter-increment: w; content: counter(w);
  width: 30px; height: 30px; border-radius: 50%; background: var(--ink); color: var(--gold);
  display: grid; place-items: center; font-size: .84rem; font-weight: 700;
  font-variation-settings: "opsz" 12, "wdth" 100;
}

.swaps { display: grid; gap: 0; margin-top: var(--sp-5); }
.swaps > div { display: grid; grid-template-columns: minmax(0, .38fr) minmax(0, .62fr);
               gap: clamp(12px, 2.4vw, 30px); padding: 14px 0; border-top: 1px solid var(--hairline);
               align-items: baseline; }
.swaps > div:last-child { border-bottom: 1px solid var(--hairline); }
.swaps dt { font-size: 1.02rem; font-weight: 700; letter-spacing: -.024em;
            font-variation-settings: "opsz" 24, "wdth" 100; }
.swaps dd { margin: 0; font-size: .99rem; line-height: 1.5; color: var(--muted); }
.swaps--wrong dt { color: var(--ink); }
@media (max-width: 640px) { .swaps > div { grid-template-columns: 1fr; gap: 4px; } }

/* .variations is styled once, in the composed block. Declaring the same
   selector twice in one stylesheet is a bug with a delay: the later copy
   silently won and turned a 1px rule into fat grey bars. */

/* The scale block is where the app stops being a claim and becomes the obvious
   answer — placed late, after the reader has decided to cook the thing. */
/* .scale is styled in the composed block above */

/* ============================================================================
   THE COMPARISON TABLE
   ----------------------------------------------------------------------------
   A comparison page's whole payload is a table, and prose is the wrong shape for
   it twice over: a reader deciding between two apps scans rather than reads, and
   an answer engine lifts a table verbatim while it has to paraphrase a
   paragraph. This is the only table on the site, so it is styled once, here.

   It scrolls INSIDE its own box (overflow-x on the wrapper, never on the page)
   because a four-column table at 375px either scrolls or lies — and a page that
   scrolls sideways is a broken page.
   ========================================================================== */
/* ============================================================================
   THE TEXT CARD — an article with no photograph
   ----------------------------------------------------------------------------
   Five food photos were being reused across twenty articles, so a guide about
   shopping once a week wore the same chicken as a guide about cooking for one
   and as a comparison of recipe apps. A decorative picture that has nothing to
   do with the words is worse than no picture: it tells the reader the page was
   assembled rather than written, before they have read a sentence.

   So a guide gets a shape a reader recognises as WRITING instead. It has to hold
   its own beside a photo card in the same grid, which it does by being taller in
   the type and quieter in the surface — the topic sits flat at the top where the
   photograph's stamp used to be, and the title is given the room the image had.
   ========================================================================== */
.post-card--text .body { padding: var(--sp-6) var(--sp-5) var(--sp-5); gap: 11px; }
.post-card--text h3 { font-size: 1.42rem; line-height: 1.1; letter-spacing: -.032em;
                      font-variation-settings: "opsz" 32, "wdth" 100; }
.post-card--text .excerpt { font-size: 1rem; }
/* The gold hairline is the whole difference between "a card" and "a card that
   lost its image". It is the top edge the photograph used to be. */
.post-card--text { border-top: 3px solid var(--gold); }
/* ⚠️ `.post-card--text .stamp--flat`, not `.stamp--flat`. The photo card's badge
   is `.post-card .stamp { position: absolute; top: 12px; left: 12px }` — it sits
   ON the photograph — and that selector scores 0,2,0 against a bare class's
   0,1,0, so it wins no matter how far down the file the override is written.
   Shipped as a bare class, the chip inherited `position: absolute`, found no
   positioned ancestor (a text card has no `.shot` to anchor to), and resolved
   against the page: measured at y = -4355, four thousand pixels above its own
   card and invisible on every guide. Caught by farroClippedText() on its first
   working run, not by looking. */
.post-card--text .stamp--flat {
  position: static; align-self: flex-start;
  background: var(--ink); color: var(--on-ink);
  border-radius: var(--r-pill); padding: 5px 12px 6px;
  font-size: .68rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  font-variation-settings: "opsz" 12, "wdth" 100;
}

/* ⚠️ NOT var(--measure). That is 880px — the width of the RECIPE page's zones —
   and this component lives on a GUIDE page, whose answer box is 760 and whose
   body is 680. Set to the measure it was written next to, the table hung 60px
   past the answer box on both sides and sat on an edge nothing else on the page
   shared, which reads as breakage even though nothing overflows. A component
   inherits the measure of the page it is ON, not the one it was styled beside. */
.cmp { width: min(760px, 100% - var(--gutter) * 2); margin: var(--sp-7) auto 0; }
.cmp-scroll {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  background: var(--sheet); border-radius: var(--r-card);
  box-shadow: var(--sh-raised);
}
.cmp table { width: 100%; border-collapse: collapse; min-width: 460px; }
/* The caption is a SIBLING of the scroll box, not a <caption> inside the table.
   A real caption scrolls away sideways with the table it labels — so the one
   line telling you what you are looking at is the first thing to leave. */
.cmp-cap {
  display: block; margin: 0 0 var(--sp-3);
  font-size: .68rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--outline);
  font-variation-settings: "opsz" 12, "wdth" 100;
}
.cmp th, .cmp td {
  text-align: left; padding: 14px clamp(12px, 2vw, 20px);
  border-bottom: 1px solid var(--hairline); vertical-align: top;
}
.cmp thead th {
  font-size: .95rem; font-weight: 700; letter-spacing: -.02em; color: var(--ink);
  font-variation-settings: "opsz" 18, "wdth" 100;
  background: var(--page);
}
/* The first column is the question being asked; the rest are the answers. */
.cmp tbody th { font-weight: 700; font-size: .95rem; color: var(--ink); width: 30%; }
.cmp td { font-size: .95rem; line-height: 1.5; color: var(--muted); }
.cmp tbody tr:last-child th, .cmp tbody tr:last-child td { border-bottom: 0; }
.cmp-note { font-size: .86rem; color: var(--muted); margin-top: var(--sp-4); }

/* ----------------------------------------------------------------------------
   A GUIDE PAGE IS NARROWER THAN A RECIPE PAGE, and the shared blocks must know.
   `.import-cta` is in the 880px group at the top of this file because that is
   right on a recipe. On a guide it sat 120px wider than the FAQ column above it,
   hanging 60px past it on both sides, with a ZERO vertical gap — so the gold
   block appeared to be eating the last question. Measured, not guessed.
   Everything scoped to .guide-page overrides the shared width for this page only.
   -------------------------------------------------------------------------- */
.guide-page .import-cta { width: min(760px, 100% - var(--gutter) * 2); margin-top: var(--band); }

/* ============================================================================
   MOBILE — and this block is LAST in the file on purpose.
   ----------------------------------------------------------------------------
   Three times now a responsive rule has been silently beaten by a declaration
   written later in the stylesheet: the recipe page kept its two-column layout at
   367px and rendered the ingredient list as a black column three words wide.
   A media query does not win because it is a media query — it wins on the
   normal cascade, so every responsive override on this site now lives here,
   after everything it overrides. Do not add layout rules below this line.
   ========================================================================== */
@media (max-width: 900px) {
  /* ---- THE DERIVE SEQUENCE STOPS BEING A SEQUENCE ---------------------------
     On a phone the two columns stack, so the scene is ~970px of content and the
     scroll-pinning has nothing to pin. Flattening it means undoing FOUR
     declarations, and the first version undid two: it reset `height` and
     `position` and left `max-height: 100vh` and `overflow: hidden` standing.
     `height: auto` then resolved to the real 971px, `max-height` clamped it back
     to the viewport's 844, and `overflow: hidden` guillotined the difference —
     so the shopping list, which is the entire point of this section, was cut
     off mid-row through "Cilantro" and three of its nine lines never existed on
     a phone. Found by Lorenzo on a real phone, not by any check here.

     The lesson worth keeping: when a rule sets a CLUSTER of properties to make
     one guarantee, the override has to undo the whole cluster. Overriding the
     obvious half leaves the guarantee half-standing, which is worse than
     leaving it alone, because it now guards nothing and clips everything. */
  .derive-rail { height: auto; }
  .derive-stage {
    position: static; height: auto; max-height: none; overflow: visible;
    grid-template-columns: 1fr; padding-block: 0;
  }
  .derive-rail.is-armed .list-item { opacity: 1; transform: none; }

  /* the cook stacks: method first is wrong, you buy before you cook */
  .recipe-cols { grid-template-columns: 1fr; gap: var(--sp-5); }
  .recipe-panel--ing { position: static; }
  /* a phone is held one-handed in a kitchen — the ingredient lines are the
     thing read at arm's length, so they get the room, not a narrow column */
  .recipe-panel li { font-size: 1.06rem; }
  .recipe-panel ol li { grid-template-columns: 30px 1fr; gap: 12px; }
  .recipe-panel ol li::before { width: 30px; height: 30px; font-size: .86rem; }

  .show-slide { grid-template-columns: 1fr; }
  .show-slide .shot { order: -1; }
  .hero-grid { grid-template-columns: 1fr; }
  .feature-card { grid-template-columns: 1fr; }
  .object, .loop-in { grid-template-columns: 1fr; }
}

@media (max-width: 700px) {
  /* Long unbreakable strings are the classic phone bug: a URL or a
     "Recipes → Add → paste a link" run pushes the whole page sideways. */
  .import-url { flex-wrap: wrap; padding: 12px; border-radius: var(--r-card); }
  .import-url code { flex: 1 1 100%; white-space: normal; overflow-wrap: anywhere;
                     font-size: .84rem; }
  .import-url button { flex: 1 1 100%; min-height: 46px; }
  .import-steps li { align-items: flex-start; }
  .import-steps li strong { overflow-wrap: anywhere; }

  .verdict { grid-template-columns: 1fr; }
  .swaps > div, .ledger .line { grid-template-columns: 1fr; gap: 5px; }
  .variations { grid-template-columns: 1fr; }
  .leaves li { flex-direction: column; align-items: flex-start; gap: 4px; }
  .leaves a { margin-inline-start: 0; }
  .fact-strip { gap: var(--sp-5) var(--sp-6); }
  .footer-grid { grid-template-columns: 1fr 1fr; }

  /* Nothing on a phone should need a horizontal drag except the carousel. */
  .prose, .guide-body, .answer-box, .recipe-story { overflow-wrap: break-word; }
}

@media (max-width: 460px) {
  /* The tightest real phone. Type comes down one step, never below the
     legibility floor — "hard to read" is the complaint we exist to beat. */
  .t-display { font-size: clamp(1.75rem, 7.4vw, 2.1rem); }
  .recipe-head h1 { max-width: none; }
  .fact-strip { gap: var(--sp-4) var(--sp-5); }
  .fact-strip b { font-size: 1.15rem; }
  .why li, .swaps dt, .recipe-panel li { font-size: 1rem; }
  .post-grid, .dish-grid { grid-template-columns: 1fr; }
  .chips { gap: 7px; }
  .chip { padding: 0 15px; font-size: .9rem; }
}
