/* OneList — landing page.
   -------------------------------------------------------------------------
   Separate from style.css on purpose. That stylesheet dresses /privacy and
   /support, where "system fonts, no third-party anything" is a correctness
   claim rather than a preference — a policy page that phones out to a font CDN
   while the policy says the app collects nothing is the one page where the
   contradiction would actually matter. This page relaxes exactly one clause of
   that doctrine: two woff2 files, served from this same origin. It still makes
   zero third-party requests, sets no cookie and loads no script it did not
   write.

   Every colour below is copied from Modules/Sources/DesignSystem/Tokens.swift
   and SlotStyle.swift. The app derives its palette in OKLCH and validates it
   with 592 colorimetric checks; these values are proven, not chosen. If one
   looks wrong here, reach for a different token rather than nudging a hex —
   the page and the product are supposed to be the same object.
   ------------------------------------------------------------------------- */

/* ---- Fraunces, self-hosted. SIL OFL 1.1, see /fonts/OFL.txt ------------- */

@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/fonts/fraunces-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/fonts/fraunces-latin-ext-var.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---- Tokens ------------------------------------------------------------- */

:root {
  /* **Light only, deliberately.** The captures are light-mode frames and they
     are now shown bare, without a bezel to separate them from the page — so a
     dark page would put a near-white rectangle on a near-black ground and the
     screenshot would read as a lightbox rather than as part of the page. The
     refusals band is still dark, but that is a designed tonal break with its
     own local tokens, not a theme. */
  color-scheme: light;

  /* Neutrals. Warm — tinted toward the accent hue at chroma 0.004–0.014, which
     is what makes the brand reach past the buttons. A cool grey ground is the
     fastest way to make this page stop looking like the app. */
  --paper: #f4f2ee;
  --paper-raised: #fdfcfa;
  --paper-sunken: #edebe7;
  --ink: #1a1917;
  --ink-muted: #6f6a62;
  --ink-faint: #8b877e;
  --rule: #d6d1c9;
  --shadow: rgb(0 0 0 / 0.14);

  /* Wine. Measured off the app icon rather than picked: the icon's gradient
     ends on L 0.328 and the palette's accent-offset rule lands on L 0.32. */
  --wine: #a82d4e;
  --wine-soft: rgb(168 45 78 / 0.12);
  --wine-line: rgb(168 45 78 / 0.28);
  --on-wine: #ffffff;
  --grad-wine: linear-gradient(160deg, #a83c56 0%, #802a43 52%, #591b34 100%);

  /* The eight meal-slot hues. In the app these identify meal slots; here each
     one owns a section. Light arm. */
  --amber: #c1852d;
  --clay: #ec6a45;
  --teal: #2ea28c;
  --indigo: #6a8df2;
  --iris: #b178e7;
  --olive: #769e36;
  --slate: #429bc0;
  --rose: #d76ac0;

  --font-display: "Fraunces", ui-serif, Georgia, "Times New Roman", serif;
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;

  /* Capped at 5rem rather than the 6.25 a single-column hero could carry: the
     hero is two columns, so the headline gets roughly half the width, and any
     larger breaks "Plan the week on a grid." into four lines. */
  --t-display: clamp(2.75rem, 6.5vw, 5rem);
  --t-h2: clamp(2.1rem, 5vw, 3.75rem);
  --t-h3: clamp(1.35rem, 2.4vw, 1.85rem);
  --t-lede: clamp(1.15rem, 1.5vw, 1.4rem);
  --t-body: 1.0625rem;
  --t-small: 0.9375rem;

  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-6: 24px;
  --s-8: 32px;
  --s-12: 48px;
  --s-16: 64px;
  --s-24: 96px;
  --s-32: 128px;

  --r-s: 6px;
  --r-m: 10px;
  --r-l: 16px;
  --r-xl: 22px;
  --r-2xl: 28px;

  --measure: 68rem;
  --measure-narrow: 38rem;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);

  /* Motion, ported from Token.Motion so the page and the product move the same
     way. One named curve per kind of change. */
  --ease-snappy: cubic-bezier(0.32, 0.72, 0, 1);
  --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
  --m-press: 150ms;
  --m-row: 200ms;
  --m-emphasis: 250ms;
  --m-reveal: 240ms;
  --m-screen: 300ms;

  /* Set per-section; every hue reference below reads this. */
  --hue: var(--wine);
}

/* ---- Reset -------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 84px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--t-body);
  line-height: 1.65;
  overflow-x: hidden;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: var(--wine);
  text-underline-offset: 0.18em;
}

:focus-visible {
  outline: 2px solid var(--wine);
  outline-offset: 3px;
  border-radius: var(--r-s);
}

/* ---- Shared shapes ------------------------------------------------------ */

.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.narrow {
  max-width: var(--measure-narrow);
}

/* The signature, ported straight from Typography.sectionHeader: uppercase and
   tracked is not a size, it is a different *kind* of label — which is why it
   can sit beside a 6rem headline without competing with it. */
.eyebrow {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--hue);
  margin: 0 0 var(--s-4);
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

.eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: currentColor;
  opacity: 0.3;
  max-width: 120px;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-optical-sizing: auto;
  margin: 0;
  text-wrap: balance;
}

h2 {
  font-size: var(--t-h2);
  letter-spacing: -0.02em;
  line-height: 1.02;
}

h3 {
  font-size: var(--t-h3);
  letter-spacing: -0.01em;
  line-height: 1.15;
}

p {
  margin: 0 0 var(--s-4);
  max-width: 40ch;
}

.lede {
  font-size: var(--t-lede);
  line-height: 1.55;
  color: var(--ink-muted);
  max-width: 34ch;
}

.bridge {
  color: var(--ink-faint);
  font-size: var(--t-small);
  margin-bottom: var(--s-6);
  max-width: 46ch;
}

.section {
  padding-block: var(--s-24);
  position: relative;
  isolation: isolate;
}

/* The hue reaches the section as a wash, never as a fill: 5% of a mark colour
   over warm paper still reads as paper, which is the point. */
.section::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--hue);
  opacity: 0.05;
  pointer-events: none;
}

.section--plain::before {
  display: none;
}

/* One measure for everything inside, so a lede at 34ch and a paragraph at 40ch
   stop disagreeing about where the centre line is. */
.centered {
  max-width: 46rem;
  margin-inline: auto;
  text-align: center;
}

.centered p,
.centered .lede,
.centered .bridge {
  max-width: 48ch;
  margin-inline: auto;
}

.centered .eyebrow {
  justify-content: center;
}

.centered .eyebrow::before {
  content: "";
  flex: 1;
  height: 1px;
  background: currentColor;
  opacity: 0.3;
  max-width: 80px;
}

.centered .eyebrow::after {
  max-width: 80px;
}

/* `minmax(0, 1fr)` rather than `1fr`, and `min-width: 0` on the children: a grid
   item's default `min-width: auto` lets a fixed-width device blow the column out
   past the wrap's padding, and the only symptom is five pixels of horizontal
   page scroll on a phone. */
.split {
  display: grid;
  gap: var(--s-12);
  align-items: center;
  grid-template-columns: minmax(0, 1fr);
}

.split__text,
.split .stage {
  min-width: 0;
}

/* ---- Buttons ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 44px;
  padding: 0 var(--s-6);
  border-radius: 999px;
  border: 1px solid transparent;
  font: inherit;
  font-size: var(--t-small);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--m-press) var(--ease-snappy),
    background-color var(--m-press) var(--ease-snappy);
}

.btn--primary {
  background: var(--wine);
  color: var(--on-wine);
}

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule);
}

.btn:active {
  transform: translateY(1px);
}

/* Apple's real badge is a licensed asset and must be their artwork at their
   minimum sizes; this is a placeholder with the same footprint so the layout
   is honest about the space it needs. Swap the inner markup, not the box. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 54px;
  padding: 0 var(--s-6) 0 var(--s-4);
  border-radius: var(--r-m);
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  transition: transform var(--m-press) var(--ease-snappy);
}

.badge:active {
  transform: translateY(1px);
}

.badge svg {
  width: 26px;
  height: 30px;
  flex: none;
}

.badge__lines {
  display: flex;
  flex-direction: column;
  line-height: 1.1;
}

.badge__small {
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
  opacity: 0.85;
}

.badge__big {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* ---- Nav ---------------------------------------------------------------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  backdrop-filter: blur(16px) saturate(1.4);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color var(--m-emphasis) var(--ease-smooth);
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  min-height: 64px;
}

.nav__mark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-decoration: none;
  margin-right: auto;
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

.nav__mark img {
  width: 28px;
  height: 28px;
  border-radius: 7px;
}

.nav__links {
  display: none;
  gap: var(--s-6);
  font-size: var(--t-small);
}

.nav__links a {
  color: var(--ink-muted);
  text-decoration: none;
}

.nav__links a:hover {
  color: var(--ink);
}

@media (min-width: 60rem) {
  .nav__links {
    display: flex;
  }
}

/* ---- Hero --------------------------------------------------------------- */

/* `overflow: hidden` is what lets the grid shot run off the right edge — see
   the note on `.hero__plan` about why the bleed is buying scale rather than
   drama. */
.hero {
  position: relative;
  padding-block: var(--s-16) var(--s-24);
  overflow: hidden;
}

/* Two very large, very soft washes. No mesh gradient, no animated blobs — the
   colour system on this page is the eight slot hues, and the hero's job is to
   introduce them, not to compete with them. */
.hero::before,
.hero::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  filter: blur(80px);
  pointer-events: none;
  z-index: -1;
}

.hero::before {
  width: 46rem;
  height: 46rem;
  top: -18rem;
  right: -14rem;
  background: var(--wine);
  opacity: 0.1;
}

.hero::after {
  width: 34rem;
  height: 34rem;
  bottom: -14rem;
  left: -12rem;
  background: var(--amber);
  opacity: 0.09;
}

.hero__grid {
  display: grid;
  gap: var(--s-16);
  align-items: center;
  grid-template-columns: 1fr;
}

.hero h1 {
  font-size: var(--t-display);
  line-height: 0.98;
  letter-spacing: -0.035em;
  margin: 0 0 var(--s-6);
  max-width: 16ch;
  /* Not `balance`. Balancing a two-sentence headline evens the line count and
     turns four lines into five, which costs the fold more than the ragged edge
     costs the eye. */
  text-wrap: pretty;
}

.hero h1 .accent {
  color: var(--wine);
  display: block;
}

.hero .lede {
  max-width: 42ch;
  margin-bottom: var(--s-8);
}

/* The palette stated as a legend. Eight segments, one per meal-slot hue, in
   the order the sections use them — the clearest single signal on the page
   that this is a system rather than a template. */
.chromabar {
  display: flex;
  gap: 3px;
  width: min(280px, 70%);
  margin: 0 0 var(--s-6);
}

.chromabar span {
  height: 4px;
  flex: 1;
  border-radius: 2px;
  background: var(--c);
  transform-origin: left center;
}

.hero__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
  margin-bottom: var(--s-6);
}

.trust {
  font-size: var(--t-small);
  color: var(--ink-faint);
  margin: 0;
  max-width: none;
}

/* ---- 3D devices --------------------------------------------------------- */

.stage {
  perspective: 1600px;
  perspective-origin: 50% 40%;
  position: relative;
}

/* **No bezel. The screenshot is the object.** A drawn device frame adds weight
   that competes with the UI inside it, and every one of these frames is here to
   be *read* — a walk order, a struck row, a mix card. Stripping the device to
   nothing makes the screenshot the hero, which is the standard treatment for
   dense feature content as opposed to a store listing. One consequence to
   respect: with no frame, the shot's own corner radius has to stand in for the
   device's screen radius, so it is derived rather than picked. */
.device {
  position: relative;
  transform-style: preserve-3d;
}

.device__screen {
  position: relative;
  border-radius: var(--shot-radius);
  overflow: hidden;
  background: var(--paper-raised);
  /* Four layers, and the order is the argument.
       1. A hairline. The captures are warm near-white and so is the page, so
          without an edge the shot dissolves into the paper. This is the one
          layer that is not about depth at all.
       2. Contact — tight, barely offset, the dark line where an object meets
          what it rests on.
       3. Key — the shadow the main light actually casts.
       4. Ambient — wide and faint, the room's own light filling in.
     Opacity falls and blur grows with distance, which is how light diffuses;
     a single shadow at any size reads as a sticker instead of an object.
     Tinted with the palette's warm near-black rather than pure black, for the
     same reason the neutrals are warm — a cold shadow on warm paper is the
     giveaway that two different systems drew this page. */
  box-shadow: 0 0 0 1px rgb(26 25 23 / 0.07), 0 1px 2px rgb(26 25 23 / 0.1),
    0 8px 20px rgb(26 25 23 / 0.1), 0 28px 60px rgb(26 25 23 / 0.13);
}

.device__screen img {
  width: 100%;
  height: auto;
}

/* Sized against the container, not the viewport, so a device inside a padded
   column can never reach past the gutter.

   **The radii are derived, not chosen.** With no bezel the shot's corner has to
   *be* the device's screen corner, so it is the real display radius scaled by
   how far the capture is being shrunk. iPhone 17 Pro Max: 62pt radius on a
   440pt-wide display, drawn 300px wide → 62 × (300/440) ≈ 42px. iPad Pro 13":
   18pt on a 1376pt-wide landscape display, drawn 880px wide → 18 × (880/1376)
   ≈ 12px. Pick these by eye and the phone stops looking like a phone. */
.device--phone {
  --shot-radius: 42px;
  width: min(300px, 100%);
  transform: rotateY(-18deg) rotateX(6deg) rotateZ(-1.5deg);
}

.device--tablet {
  --shot-radius: 16px;
  width: min(560px, 100%);
  transform: rotateY(14deg) rotateX(4deg) rotateZ(1deg);
}

.device--flat {
  transform: none;
}

/* ---- Hero stage ---------------------------------------------------------
   **The screenshot is an object again, and that is the whole correction.** This
   hero spent several drafts as a background — the grid tilted back behind the
   copy, feathered, veiled, laid down like a floor. Every version failed the same
   way, and the reason is worth writing down so nobody spends the afternoon again:

   *A background must recede and evidence must dominate, and this image is
   evidence.* The grid earns its place by being read — Wednesday's dinner, the
   underline on today, Cook's day off on Friday. Push it back far enough to sit
   behind body copy and it stops being read; pull it forward far enough to be
   read and it fights the type. There is no setting in between, because the thing
   that would resolve it — tonal separation between picture and page — does not
   exist here: the captures are warm near-white and so is the paper.

   *And it costs the product its object-ness.* Bezel-less was always this page's
   treatment (see `.device__screen`), but a bezel-less screenshot with no edge,
   no shadow and no plausible physical presence is not a screen — it is
   wallpaper. The four-layer shadow below is not decoration; it is the only thing
   telling a visitor that this rectangle is a device.

   So: two shots, forward, lit, at a size where the cells are legible, in the
   gentle 3D the rest of the page already uses. No masks over text, no veil, no
   compromise on either side — because nothing is behind anything. */
.hero__stage {
  position: relative;
  perspective: 1700px;
  perspective-origin: 40% 45%;
}

/* The pair share the page's shadow stack, opened up one notch: these sit nearer
   the viewer than any other shot here, so key and ambient travel further. The
   hairline is the layer that matters most — a warm near-white capture on warm
   near-white paper has no edge of its own, and without it the grid dissolves
   into the page exactly the way the background drafts did. */
.hero__plan,
.hero__list {
  position: relative;
  margin: 0;
  border-radius: var(--shot-radius);
  overflow: hidden;
  background: var(--paper-raised);
  box-shadow: 0 0 0 1px rgb(26 25 23 / 0.07), 0 1px 2px rgb(26 25 23 / 0.1),
    0 14px 30px rgb(26 25 23 / 0.1), 0 44px 88px rgb(26 25 23 / 0.16);
}

.hero__plan img,
.hero__list img {
  width: 100%;
  height: auto;
}

/* **Sized to the cell, not to the column**, and the shot it is sizing changed
   in August: this is the whole iPad panel now — grid *and* grocery list — rather
   than the grid band cropped out of it. The hero's job moved with it. It used to
   say "plan the week on a grid" and leave the list to the phone in front; it now
   says both halves in one screenshot, which is the claim §4 is built on and the
   one thing no phone frame can carry.

   **The swap costs pixels per day column, and the width is what pays it back.**
   The band was 2070px across six columns — about 345 source pixels per day — and
   a meal name stops being readable somewhere under 150px of that on screen. The
   full panel spends a third of its width on the list, so at the old 128% the
   grid fell to roughly 100px per day, which is under the floor and was the
   original complaint about this hero.

   **This base value is the stacked layout's**, where the hero is one column and
   the stage has the full measure to itself, so the shot needs barely any bleed
   to run large — 112% keeps the grocery panel on screen, which is the half the
   old crop did not have and the reason the shot was swapped. The two-column
   desktop case re-fits it at `min-width: 64rem`, where the stage is competing
   with the copy for width.

   The radius is derived rather than picked, like every other shot on this page:
   18pt on a 1376pt-wide landscape display, drawn at roughly this size. */
.hero__plan {
  --shot-radius: 14px;
  width: 112%;
  transform: rotateY(-13deg) rotateX(4deg) rotateZ(-0.5deg);
}

/* **The status bar, masked.** The cropped band started below the navigation bar
   and so carried no capture date at all — the note above this rule used to say
   so. The full panel does carry one, reading 14 August beside a grid reading
   September, which is the one thing in these frames that is not true. Same
   numbers as `.device--tablet-landscape`, because it is the same capture at the
   same aspect ratio; the negative margin pulls the remaining pixels back up so
   the shot still starts at the top of its own rounded box. */
.hero__plan img {
  clip-path: inset(3.4% 0 0 0);
  margin-top: -2.5%;
}

/* The payoff, in front and to the left, where the eye lands after the headline.
   Small on purpose — a shopping list is a single column of short lines and stays
   legible at a third the width of the grid, so it can be the smaller object
   without becoming a thumbnail. `translateZ` rather than a bigger scale: the
   separation should read as *in front of*, which is depth, not size.

   62pt radius on a 440pt-wide display, scaled by how far the capture is shrunk. */
.hero__list {
  --shot-radius: 26px;
  position: absolute;
  z-index: 1;
  width: 33%;
  /* -5%, not -12%. In the stacked layout the stage has the full measure, so a
     percentage overhang is measured against a much wider box than it is at
     desktop — at -12% the phone hung 108px past the left edge and `.hero`'s
     `overflow: hidden` sliced it down the middle, which reads as a bug rather
     than a crop. The overhang that makes it read as *in front* is the vertical
     one below the iPad, and that is untouched. */
  left: -5%;
  bottom: -16%;
  /* **Clockwise, where it used to lean the other way.** Both shots leaned the
     same direction at -0.5deg and -2deg, so the phone read as a smaller copy of
     the iPad's angle rather than as a separate object in front of it. Turning
     this one the other way sets the two against each other, which is what makes
     the phone read as *held* — and it opens the wedge between the two near
     edges instead of running them parallel. `.pair`'s two devices already lean
     toward each other for the same reason. */
  transform: rotateY(-9deg) rotateX(3deg) rotateZ(3deg) translateZ(70px);
}

/* `.trust` keeps the one typographic change the background drafts forced, and it
   is a fix rather than a leftover: at `--ink-faint` it measured 3.2:1 on bare
   paper — the only string on this page that failed AA before any picture was
   involved. `.lede` goes back to its inherited `--ink-muted`, because nothing is
   behind it any more. */
.hero .trust {
  color: var(--ink-muted);
}

/* iPad held landscape — 2752 × 2064, ratio 1.333. Its own class rather than a
   width override: the corner radius of a 13" iPad reads differently across the
   long edge, and the status bar is a larger fraction of a shorter panel, so the
   date mask is not the same number. */
.device--tablet-landscape {
  --shot-radius: 12px;
  width: min(880px, 100%);
  transform: rotateY(-10deg) rotateX(4deg) rotateZ(0.5deg);
}

/* The iPad status bar carries the real capture date beside a grid reading
   14 September — the one thing in these frames that is not true. Masked here
   rather than re-shot; documented in docs/design/05-asset-index.md so the App
   Store upload gets the same treatment. */
.device--tablet .device__screen img {
  clip-path: inset(2.6% 0 0 0);
  margin-top: -1.1%;
}

.device--tablet-landscape .device__screen img {
  clip-path: inset(3.4% 0 0 0);
  margin-top: -2.5%;
}

.pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s-8);
  justify-items: center;
  max-width: 720px;
  margin-inline: auto;
}

.pair .device--phone:first-child {
  transform: rotateY(12deg) rotateX(4deg) rotateZ(1deg);
}

.pair .device--phone:last-child {
  transform: rotateY(-12deg) rotateX(4deg) rotateZ(-1deg);
}

figure {
  margin: 0;
}

figcaption {
  margin-top: var(--s-6);
  font-size: var(--t-small);
  color: var(--ink-faint);
  text-align: center;
}

/* ---- Bento -------------------------------------------------------------- */

.bento {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: 1fr;
  margin-top: var(--s-8);
}

.tile {
  position: relative;
  overflow: hidden;
  padding: var(--s-8);
  border-radius: var(--r-2xl);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
}

.tile::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 40%;
  background: linear-gradient(to bottom, var(--hue), transparent);
  opacity: 0.09;
  pointer-events: none;
}

.tile__link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-6);
  font-size: var(--t-small);
  font-weight: 600;
  color: var(--hue);
  text-decoration: none;
}

.tile__link::after {
  content: "↓";
}

.tile__num {
  position: absolute;
  top: var(--s-4);
  right: var(--s-6);
  font-family: var(--font-display);
  font-size: 2.5rem;
  font-weight: 700;
  color: var(--hue);
  opacity: 0.28;
  line-height: 1;
}

/* Room for the numeral in the top-right corner, which is absolutely positioned
   and would otherwise sit on top of a two-word title. */
.tile h3 {
  margin-bottom: var(--s-3);
  max-width: 18ch;
  padding-right: 3rem;
}

.tile p {
  color: var(--ink-muted);
  margin: 0;
  max-width: 44ch;
}

/* ---- Chapters ----------------------------------------------------------

   The page argues two things, so it has two chapters and everything else is a
   card inside one of them. A chapter opens full-width with its own device frame
   running larger than anything else on the page, then drops into a card grid
   whose tiles sit on the chapter's washed ground — so they read as *inside* the
   chapter rather than as a fresh stack of sections. */

.chapter__lead {
  max-width: 46rem;
  margin-inline: auto;
  text-align: center;
}

.chapter__lead .eyebrow {
  justify-content: center;
}

.chapter__lead .eyebrow::before {
  content: "";
  flex: 1;
  height: 1px;
  background: currentColor;
  opacity: 0.3;
  max-width: 80px;
}

.chapter__lead .eyebrow::after {
  max-width: 80px;
}

.chapter__lead .lede {
  max-width: 52ch;
  margin: var(--s-6) auto 0;
}

/* The chapter's own frame. Larger than any other device on the page, because
   this is the one that has to land. */
.chapter__hero {
  margin: var(--s-12) auto 0;
  max-width: 54rem;
}

.chapter__hero figcaption {
  margin-top: var(--s-4);
}

/* Explicit column counts rather than `auto-fit`: four cards in an auto-fit grid
   land 3 + 1 at desktop widths and leave the fourth stranded on its own row.
   Chapter one is a 2 × 2; chapter two is a 2 × 3 whose wide card takes the
   second slot of row one. */
.cards {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--s-12);
}

@media (min-width: 48rem) {
  .cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 64rem) {
  .cards--3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.card {
  position: relative;
  overflow: hidden;
  padding: var(--s-6);
  border-radius: var(--r-l);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
}

.card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: var(--hue);
  opacity: 0.85;
}

.card h3 {
  font-size: 1.2rem;
  margin-bottom: var(--s-3);
  max-width: 22ch;
}

.card p {
  margin: 0;
  font-size: var(--t-small);
  color: var(--ink-muted);
  max-width: none;
}

/* **Cards are lists, not paragraphs.** A card is a scannable unit by definition,
   and a card body written as one 60-word paragraph is an essay in a box — the
   reader has to parse it to find out whether it is relevant. One bullet, one
   fact, twelve words at the outside. The marker takes the card's hue, which is
   the fifth and last place that hue is allowed to appear. */
.bullets {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-2);
  font-size: var(--t-small);
  color: var(--ink-muted);
}

/* **A block with an absolute marker, not a flex row.** Flex makes every inline
   child its own flex item, so a bullet containing <strong> or <em> silently
   breaks into columns — the emphasis stops being emphasis and becomes a layout.
   Absolute positioning keeps the li an ordinary block, where inline elements
   flow the way inline elements should. */
.bullets li {
  position: relative;
  padding-left: var(--s-4);
  max-width: 46ch;
}

.bullets li::before {
  content: "";
  position: absolute;
  left: 0;
  /* On the first line's optical centre, so a wrapping bullet keeps its marker
     beside the first word rather than beside the middle of the paragraph. */
  top: 0.62em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--hue);
}

.bullets em {
  font-style: italic;
}

.bullets strong {
  color: var(--ink);
  font-weight: 600;
}

.card strong {
  color: var(--ink);
  font-weight: 600;
}

/* The differentiator gets the most room in the grid. */
@media (min-width: 48rem) {
  .card--wide {
    grid-column: span 2;
  }

  .card--wide h3 {
    font-size: var(--t-h3);
    max-width: 26ch;
  }
}

/* A compact band for something real that is nonetheless not one of the two
   things this page argues. An h3 rather than an h2, no wash, no numeral. */
.strip {
  padding-block: var(--s-16);
  border-top: 1px solid var(--rule);
}

.strip h3 {
  font-size: clamp(1.5rem, 3vw, 2.1rem);
}

/* A tall phone beside a wide tablet. Equal columns centre the short one and
   leave a column of dead space above and below it, so the tablet gets the
   larger share and both sit on the same bottom edge — which is also how two
   devices actually stand on a desk. */
.figure-pair {
  display: grid;
  gap: var(--s-8);
  grid-template-columns: minmax(0, 1fr);
  align-items: end;
  justify-items: center;
}

@media (min-width: 56rem) {
  .figure-pair {
    grid-template-columns: minmax(0, 5fr) minmax(0, 9fr);
  }

  .figure-pair .device--phone {
    justify-self: center;
  }
}

/* ---- The three steps ---------------------------------------------------

   The whole product as one screen: meals → grid → list → trip, three phones in
   a row, one per step. This replaced four separate chapter-length sections. The
   pattern earns its place because the product *is* a pipeline — showing it as
   three stills in sequence is closer to the truth than describing each stage. */

.steps {
  display: grid;
  gap: var(--s-12) var(--s-8);
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--s-16);
}

@media (min-width: 60rem) {
  .steps {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.step {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.step__n {
  font-family: var(--font-display);
  font-size: 2.25rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--hue);
  font-variant-numeric: tabular-nums;
}

.step h3 {
  font-size: 1.35rem;
  margin: 0;
  max-width: 16ch;
}

/* The shot sits under its copy, not beside it: three columns of text over three
   phones reads as one sequence, where alternating text/image would read as
   three unrelated blocks. */
.step .stage {
  margin-top: auto;
  padding-top: var(--s-4);
}

.step .device--phone {
  width: min(240px, 100%);
  transform: rotateY(-9deg) rotateX(3deg);
}

/* ---- The definition ----------------------------------------------------

   The page's argument opens by taking something away: "meal planner" makes a
   visitor project photos, steps and cook times onto the product, and every
   screen after this is easier to read once that expectation is gone. */

/* **Set the words as if they were staying, then strike them.** The faint-grey
   version of this read as decoration: pale text under a pale hairline is a
   whisper, and the section's whole job is a firm no. Full-ink words at display
   weight are something a reader takes seriously — and the drama is the tension
   between typesetting them that way and crossing them out anyway.

   Hierarchy survives on size alone: the h2 above tops out at 3.75rem and this at
   2.4rem, so it never competes for first read despite the matching colour. */
.strike {
  list-style: none;
  margin: var(--s-8) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  /* More air than a list needs. Crammed, six struck words read as one texture;
     spaced, each one is its own refusal. */
  gap: var(--s-3) var(--s-4);
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2.9vw, 1.85rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  /* Tight, so three rows of two read as one block being crossed out rather than
     as three separate lines of text. Also the divisor the bar's 56% is measured
     against — changing it moves the strike. */
  line-height: 1.15;
  color: var(--ink);
}

/* **A drawn bar, not `text-decoration`.** Fraunces sets its line-through metric
   low — near the baseline rather than through the middle of the word — so at
   this size the default reads as an underline that has drifted up rather than as
   a strike. `text-underline-offset` cannot move it; only `underline` responds.
   So the semantics stay on the `<s>` element and the mark is drawn.

   It also buys two things the property could not: an overhang past both ends, so
   the stroke looks made across the word rather than fitted to it, and immunity
   from `skip-ink` breaking the line around the descenders in Steps, Servings and
   Ratings. */
/* `inline-block` so the bar has a box with a known height to sit in. On a plain
   inline element the containing block is the font's content area, whose top is
   an ascent the stylesheet cannot see — the position then has to be guessed, and
   a guess lands on the cap line. Here the box is `line-height` tall, so a
   percentage is arithmetic: the baseline is about 78% down, the strike wants to
   cross half an x-height above it, and 56% is that point. */
.strike s {
  position: relative;
  display: inline-block;
  text-decoration: none;
}

.strike s::after {
  content: "";
  position: absolute;
  left: -0.09em;
  right: -0.09em;
  top: 59%;
  height: 0.075em;
  /* `currentColor`, so the stroke is the same ink as the word it crosses. A
     contrasting rose line read as a highlighter drawn over the type — a second
     object on top of the first. In the text's own colour it reads as the word
     being struck, which is the thing the section is claiming. It also means the
     bar follows the colour automatically if this list is ever re-toned. */
  background: currentColor;
  border-radius: 0.04em;
}

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

   **Three cards, not four.** Lifetime is not a fourth tier — it is Household,
   bought once instead of yearly, and the entitlement it unlocks is identical.
   Giving it a column of its own implied a ladder with four rungs and made the
   real axis (sharing, not seats) harder to see, so it sits inside the Household
   card as an alternative price.

   **No button on any card.** Purchases happen inside the app, through Apple —
   a Buy button here would either lie about what it does or bounce to the same
   App Store link three times. The tier list is information, and the page has one
   call to action. */

.plans {
  display: grid;
  gap: var(--s-4);
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--s-12);
  align-items: stretch;
}

@media (min-width: 56rem) {
  .plans {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--s-8) var(--s-6) var(--s-6);
  border-radius: var(--r-2xl);
  border: 1px solid var(--rule);
  background: var(--paper-raised);
}

.plan--best {
  border-color: var(--wine);
  /* A second ring rather than a thicker border, so the recommended card does not
     sit 1px taller than the two beside it. */
  box-shadow: 0 0 0 1px var(--wine);
}

.plan__ribbon {
  position: absolute;
  top: calc(var(--s-3) * -1);
  left: var(--s-6);
  padding: 3px var(--s-3);
  border-radius: 999px;
  background: var(--wine);
  color: var(--on-wine);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.plan__name {
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 var(--s-2);
}

.plan__price {
  font-family: var(--font-display);
  font-size: 2.4rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
  margin: 0;
}

.plan__per {
  font-family: var(--font-ui);
  font-size: var(--t-small);
  font-weight: 400;
  color: var(--ink-faint);
  letter-spacing: 0;
}

/* The lifetime price, inside the Household card. */
.plan__alt {
  margin: var(--s-2) 0 0;
  font-size: var(--t-small);
  color: var(--wine);
  font-variant-numeric: tabular-nums;
}

.plan ul {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-3);
  font-size: var(--t-small);
  color: var(--ink-muted);
}

.plan li {
  position: relative;
  padding-left: var(--s-6);
}

/* A character, not a colour: the tick is read out, and it survives with styling
   off. */
.plan li::before {
  content: "✓";
  position: absolute;
  left: 0;
  top: -0.05em;
  color: var(--wine);
  font-weight: 700;
}

.plan li[data-mark="—"]::before {
  content: "—";
  color: var(--ink-faint);
  font-weight: 400;
}

.plan li[data-mark="—"] {
  color: var(--ink-faint);
}

/* ---- Details, quotes, asides ------------------------------------------- */

.details {
  list-style: none;
  margin: var(--s-8) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-4);
}

.details li {
  padding-top: var(--s-4);
  border-top: 1px solid var(--hue);
  border-image: none;
  font-size: var(--t-small);
  color: var(--ink-muted);
}

.details strong {
  color: var(--ink);
  font-weight: 600;
}

/* The 2px coloured rail is the planner cell's own motif — every placement in
   the app carries one down its leading edge. Repeating it here is the cheapest
   way to make the page feel like it came out of the same drawing. */
.quote {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--hue);
  border-left: 3px solid var(--hue);
  padding-left: var(--s-6);
  margin: var(--s-8) 0 0;
  max-width: 26ch;
}

.aside {
  margin-top: var(--s-8);
  padding: var(--s-6);
  border-radius: var(--r-l);
  background: var(--paper-sunken);
  border: 1px solid var(--rule);
}

.aside h3 {
  font-size: 1.15rem;
  margin-bottom: var(--s-2);
}

.aside p {
  margin: 0;
  font-size: var(--t-small);
  color: var(--ink-muted);
  max-width: 48ch;
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--s-6);
  margin-top: var(--s-8);
}

.stat b {
  display: block;
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--hue);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.stat span {
  font-size: var(--t-small);
  color: var(--ink-faint);
}

/* The learned order is invisible in a screenshot — there is no pixel that says
   "this order was learned". The walk annotation is the mitigation. */
.walk {
  list-style: none;
  margin: var(--s-8) 0 0;
  padding: 0 0 0 var(--s-6);
  border-left: 2px solid var(--hue);
  display: grid;
  gap: var(--s-3);
  font-size: var(--t-small);
  color: var(--ink-muted);
  counter-reset: walk;
}

.walk li {
  position: relative;
}

.walk li::before {
  counter-increment: walk;
  content: counter(walk);
  position: absolute;
  left: calc(var(--s-6) * -1 - 9px);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--hue);
  color: var(--paper);
  font-size: 0.625rem;
  font-weight: 700;
  display: grid;
  place-items: center;
  top: 0.35em;
}

/* ---- Refusals: the page's one dark band --------------------------------- */

/* The page's one tonal break, and the only dark surface on it — this is where
   the argument turns from what the app does to what it refuses to do, and the
   ground turning over under the reader is the cheapest way to say so. Local
   token overrides rather than a theme: the page has one theme, and this is a
   designed inversion inside it. The screenshot beside it keeps its hairline, so
   a warm near-white capture on near-black still reads as an object rather than
   as a hole in the page. */
.refusals {
  background: #0f0e0d;
  color: #f4f1ed;
  --ink: #f4f1ed;
  --ink-muted: #a8a19a;
  --ink-faint: #8b847c;
  --rule: #36312b;
  --hue: #f485db;
  border-block: 1px solid rgb(244 133 219 / 0.22);
}

.refusals::before {
  display: block;
  background: radial-gradient(
    120% 80% at 80% 20%,
    rgb(244 133 219 / 0.14),
    transparent 60%
  );
  opacity: 1;
}

.refuse-list {
  list-style: none;
  margin: var(--s-8) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-4);
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.2vw, 1.6rem);
  font-weight: 500;
  letter-spacing: -0.01em;
}

.refuse-list li {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
}

.refuse-list li::before {
  content: "✕";
  color: var(--hue);
  opacity: 0.65;
  font-family: var(--font-ui);
  font-size: 0.85em;
  flex: none;
}

/* ---- Pricing ------------------------------------------------------------ */

.tiers {
  display: grid;
  gap: var(--s-4);
  margin-top: var(--s-12);
  grid-template-columns: 1fr;
}

.tier {
  position: relative;
  padding: var(--s-8) var(--s-6);
  border-radius: var(--r-2xl);
  border: 1px solid var(--rule);
  background: var(--paper);
}

.tier--best {
  background: var(--paper-raised);
  border-color: var(--wine-line);
  box-shadow: 0 1px 0 var(--wine-line);
}

.tier__ribbon {
  position: absolute;
  top: calc(var(--s-3) * -1);
  left: var(--s-6);
  padding: 2px var(--s-3);
  border-radius: 999px;
  background: var(--wine);
  color: var(--on-wine);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.tier h3 {
  font-size: 1.3rem;
  margin-bottom: var(--s-1);
}

.tier__price {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

.tier__per {
  font-family: var(--font-ui);
  font-size: var(--t-small);
  font-weight: 400;
  color: var(--ink-faint);
}

.tier ul {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-3);
  font-size: var(--t-small);
}

.tier li {
  display: flex;
  gap: var(--s-3);
  align-items: flex-start;
  color: var(--ink-muted);
}

/* Never colour alone: the mark is a character, and it is read out. */
.tier li::before {
  content: attr(data-mark);
  flex: none;
  width: 1em;
  color: var(--wine);
  font-weight: 700;
}

.tier li[data-mark="—"]::before {
  color: var(--ink-faint);
}

.tier li[data-mark="—"] {
  opacity: 0.6;
}

.promise {
  margin-top: var(--s-12);
  padding: var(--s-8);
  border-radius: var(--r-2xl);
  background: var(--paper-raised);
  border: 1px solid var(--rule);
}

/* The card's list is a `.bullets`, which brings its own marker. These rules used
   to add a second one — a flex `●` on top of the absolutely-positioned dot — and
   the pair rendered as one smeared blob rather than as two obvious mistakes,
   which is why it survived a screenshot. Left as a comment rather than deleted
   silently: if a `.promise` ever holds a plain `<ul>` again it needs its own
   marker back, and it should not be this one. */

.finequote {
  margin-top: var(--s-8);
  font-size: var(--t-small);
  color: var(--ink-faint);
  max-width: 60ch;
}

/* ---- FAQ ----------------------------------------------------------------

   **A drawer, and the page's only collapsed content.** Everything else here is
   visible on arrival; this is deliberately not, and the trade is what lets the
   section exist at all — 450 words of answers cost a scanner ten lines. See the
   comment above the section in `index.html` for why that trade is the point
   rather than a compromise.

   **Native `<details>`, no script.** Keyboard operable, announced, and correct
   with JavaScript off. `name="faq"` gives an exclusive accordion with no code;
   a browser that does not know the attribute lets two stand open, which is a
   worse layout and not a broken one. */

.faq__list {
  max-width: 46rem;
  margin: var(--s-12) auto 0;
  border-top: 1px solid var(--rule);
}

.faq__item {
  border-bottom: 1px solid var(--rule);
}

.faq__q {
  position: relative;
  /* `block`, not `list-item`: that is what removes the disclosure triangle in
     every engine, and it is also what makes `position: relative` on the summary
     behave like an ordinary box. `list-style: none` and the WebKit pseudo below
     cover the engines that draw a marker anyway. */
  display: block;
  list-style: none;
  /* Right padding clears the chevron; the negative inline margin lets the hover
     tint bleed past the text edge, so the highlight reads as the row's hit area
     rather than as a box drawn around the words. */
  padding: var(--s-6) var(--s-8) var(--s-6) var(--s-3);
  margin-inline: calc(var(--s-3) * -1);
  border-radius: var(--r-s);
  cursor: pointer;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.9vw, 1.25rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--ink);
  transition: background-color var(--m-press) var(--ease-snappy);
}

.faq__q::-webkit-details-marker {
  display: none;
}

/* A chevron drawn from two borders. **The hue lives here and nowhere else in
   this section** — slot hues are never used for text (`04-page-spec.md`), which
   is what keeps the page's contrast true without a per-pairing audit, so the
   question stays full `--ink` and the mark carries the colour. */
.faq__q::after {
  content: "";
  position: absolute;
  right: var(--s-2);
  /* The first line's optical centre, the same reasoning as `.bullets li::before`
     — a question that wraps keeps its chevron beside the first line rather than
     beside the middle of the block. */
  top: calc(var(--s-6) + 0.65em);
  width: 9px;
  height: 9px;
  border-right: 2px solid var(--hue);
  border-bottom: 2px solid var(--hue);
  transform: translateY(-50%) rotate(45deg);
  transition: transform var(--m-row) var(--ease-snappy);
}

.faq__item[open] > .faq__q::after {
  transform: translateY(-50%) rotate(-135deg);
}

/* Pointer devices only. On a touchscreen `:hover` sticks after a tap, which
   would leave the last-tapped question permanently tinted. */
@media (hover: hover) {
  .faq__q:hover {
    background: color-mix(in srgb, var(--hue) 8%, transparent);
  }
}

/* Flush with the question above it, not indented under it. The summary's own
   `--s-3` of left padding is cancelled by an equal negative margin — it exists
   to give the hover tint somewhere to bleed, not to set a text position — so an
   answer matching that padding would sit 12px to the right of every question on
   a page that is otherwise ruthlessly aligned. */
.faq__a {
  padding: 0 var(--s-8) var(--s-6) 0;
}

.faq__a p {
  margin: 0 0 var(--s-3);
  font-size: var(--t-small);
  color: var(--ink-muted);
  /* Wider than the page's 40ch default: this column is 46rem and an answer set
     to 40ch in it would rag down the middle of an empty half. */
  max-width: 58ch;
}

.faq__a p:last-child {
  margin-bottom: 0;
}

.faq__a strong {
  color: var(--ink);
  font-weight: 600;
}

.faq__a em {
  font-style: italic;
}

/* Interface strings quoted as themselves — *We keep this at home*, *You already
   have these* — are set in italic rather than in quotes, matching how `.bullets`
   already sets *Ground beef* and *Chicken with rice* in §2. */

.faq__more {
  max-width: 46rem;
  margin: var(--s-8) auto 0;
  font-size: var(--t-small);
  color: var(--ink-faint);
}

/* The open/close animation, and it is opt-in twice over. `interpolate-size` is
   what makes `height: auto` a value a transition can reach; without it the
   whole block is skipped and `<details>` snaps open, which is the behaviour the
   element has had for twenty years. `content-visibility` needs
   `allow-discrete`, or the content vanishes on frame one of the close. */
@supports (interpolate-size: allow-keywords) {
  :root {
    interpolate-size: allow-keywords;
  }

  .faq__item::details-content {
    block-size: 0;
    overflow: hidden;
    transition: block-size var(--m-reveal) var(--ease-snappy),
      content-visibility var(--m-reveal) var(--ease-snappy) allow-discrete;
  }

  .faq__item[open]::details-content {
    block-size: auto;
  }
}

/* ---- Closer + footer ---------------------------------------------------- */

.closer {
  background: var(--grad-wine);
  color: #fff;
  text-align: center;
  padding-block: var(--s-24);
}

.closer h2 {
  color: #fff;
  margin-bottom: var(--s-4);
}

.closer p {
  margin-inline: auto;
  color: rgb(255 255 255 / 0.82);
}

.closer .badge {
  background: #fff;
  color: #1a1917;
  margin-top: var(--s-6);
}

.closer__icon {
  width: 96px;
  height: 96px;
  border-radius: 22px;
  margin: 0 auto var(--s-8);
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.28);
}

.footer {
  background: var(--paper-sunken);
  border-top: 1px solid var(--rule);
  padding-block: var(--s-12);
  font-size: var(--t-small);
  color: var(--ink-muted);
}

.footer__grid {
  display: grid;
  gap: var(--s-6);
  grid-template-columns: 1fr;
}

.footer a {
  color: var(--ink-muted);
}

.footer p {
  max-width: 40ch;
}

/* ---- Responsive --------------------------------------------------------- */

@media (min-width: 48rem) {
  .bento {
    grid-template-columns: repeat(2, 1fr);
  }

  .tiers {
    grid-template-columns: repeat(2, 1fr);
  }

  .footer__grid {
    grid-template-columns: 2fr 1fr 1fr;
  }

  .details {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 64rem) {
  /* **250%, and the number is the whole argument of this hero.** The grid band
     carries about 345 source pixels per day column; a meal name stops reading
     somewhere under 150px of that on screen. Fitted inside its grid column the
     shot lands at 115px per day — unreadable, and the exact complaint that
     started this redesign. 250% puts it at 226px, which is why the bleed exists.
     What runs off the right is Friday and Saturday; what stays is Monday with
     today's underline, Wednesday's three-side dinner and Cook's day off.

     `margin-left` slides the grid clear of the phone in front of it, and 24% is
     measured rather than eyeballed: the rail column is 72 of the capture's 2070
     pixels, which is 47px on screen here, so the overlap has to come in under
     that. At 17% it was 52px and BREAKFAST, LUNCH and DINNER were all buried —
     and the rails are the "meals down the side" half of the headline. At 24% the
     overlap is 19px and all three read.

     The copy column gives up a little width here (0.92fr) so the stage has room
     for both objects. The headline still breaks in four lines. */
  .hero__grid {
    grid-template-columns: 0.92fr 1.08fr;
  }

  .hero {
    padding-bottom: var(--s-32);
  }

  .hero__plan {
    width: 126%;
    margin-left: 32%;
  }

  /* Big enough to read, floored in px so a narrow desktop cannot shrink it into
     a thumbnail. Two measured constraints hold it in place:

     Its left edge clears the longest copy line — 37px at 1440, and 6px at 1024,
     which is the tightest the layout ever gets. Measured against the *glyphs*
     with a Range rather than against the column box, because the trust line
     wraps well short of its column and the box would have lied by ~90px.

     `bottom` is bounded by the hero clipping it. The overhang below the grid is
     what makes the list read as in front rather than beside, but `.hero` has
     `overflow: hidden`, so too much of it and the phone is sliced off
     mid-list — which looks like a bug rather than a crop. -14% leaves 35px of
     overhang and 38px of clearance inside the hero at every width from 1024 up;
     -46% cut it by 190px. */
  .hero__list {
    width: max(42%, 205px);
    left: -8%;
    bottom: -14%;
  }

  .split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s-16);
  }

  /* Alternate the rail so the eye zig-zags down the page rather than scanning
     one edge. */
  .split--flip .split__text {
    order: 2;
  }

  .section {
    padding-block: var(--s-24);
  }

  .tiers {
    grid-template-columns: repeat(4, 1fr);
  }

  /* **A phone shot sets its row's height; the copy no longer does.** Once the
     cards became bullets, text stopped being what makes a section tall — a
     330px-wide capture is 717px tall on its own, so every split row was that
     tall whatever sat beside it. 270px keeps every meal name legible and takes
     roughly 150px off each of the four rows that carry one. */
  .device--phone {
    width: min(270px, 26vw);
  }

  .pair .device--phone {
    width: min(250px, 22vw);
  }
}

/* Steep perspective on a narrow viewport wastes the width and makes the
   screenshot unreadable — which defeats the entire reason it is there. */
@media (max-width: 48rem) {
  .device--phone,
  .device--tablet,
  .pair .device--phone:first-child,
  .pair .device--phone:last-child {
    transform: rotateY(-7deg) rotateX(3deg);
  }

}

/* Phones. The grid still has to bleed — six columns across a 390px screen is
   65px each, which is a picture of a grid with nothing readable in it — so the
   shot runs well past the viewport and shows about three days.

   **The panel is deliberately off-screen here**, which is the one breakpoint
   where the swapped shot gives up its second half. A 390px screen cannot hold a
   grid and a list side by side at any size worth reading, and the hero already
   has a phone in front of it showing the list; §4 carries the pairing instead.
   That is also why the number went up rather than down: the new capture spends a
   third of its width on the panel, so matching the old ~186px per day column
   across the *grid* costs proportionally more bleed.

   The turn eases to 6° because a hard yaw on a narrow frame spends width without
   buying depth. */
@media (max-width: 32rem) {
  .hero__plan {
    width: 340%;
    transform: rotateY(-6deg) rotateX(3deg);
  }

  /* The list moves to the right edge and tucks under. On the left it would sit
     on the rails, and there is no gutter to hide in at this width. */
  .hero__list {
    width: 44%;
    left: auto;
    right: -4%;
    bottom: -12%;
    transform: rotateY(-5deg) rotateX(2deg) rotateZ(2deg) translateZ(40px);
  }
}

/* ---- Motion ------------------------------------------------------------- */

/* One orchestrated load sequence, not scattered micro-interactions. 14px and
   60ms of stagger, and the same 14px is reused by every scroll reveal — so the
   page has one motion signature rather than two. */
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes rise-device {
  from {
    opacity: 0;
    transform: translateY(26px) rotateY(-26deg) rotateX(9deg) rotateZ(-3deg);
  }
}

@keyframes draw {
  from {
    transform: scaleX(0);
  }
}

.load > * {
  animation: rise 600ms var(--ease-smooth) both;
}

.load > *:nth-child(1) {
  animation-delay: 0ms;
}
.load > *:nth-child(2) {
  animation-delay: 60ms;
}
.load > *:nth-child(3) {
  animation-delay: 120ms;
}
.load > *:nth-child(4) {
  animation-delay: 180ms;
}
.load > *:nth-child(5) {
  animation-delay: 240ms;
}

.chromabar span {
  animation: draw 600ms var(--ease-smooth) both;
}
.chromabar span:nth-child(1) {
  animation-delay: 90ms;
}
.chromabar span:nth-child(2) {
  animation-delay: 130ms;
}
.chromabar span:nth-child(3) {
  animation-delay: 170ms;
}
.chromabar span:nth-child(4) {
  animation-delay: 210ms;
}
.chromabar span:nth-child(5) {
  animation-delay: 250ms;
}
.chromabar span:nth-child(6) {
  animation-delay: 290ms;
}
.chromabar span:nth-child(7) {
  animation-delay: 330ms;
}
.chromabar span:nth-child(8) {
  animation-delay: 370ms;
}

/* The pair arrive turned a few degrees further and settle, the grid first and
   the list a beat behind it — so the hero reads in the order it argues: plan,
   then shop. A bare `from` on purpose, so the resting `to` resolves to whatever
   the cascade set at this breakpoint; writing the `to` out would pin one
   breakpoint's transform onto all of them. */
.hero__plan {
  animation: rise-hero-shot 800ms var(--ease-smooth) 240ms both;
}

.hero__list {
  animation: rise-hero-shot 800ms var(--ease-smooth) 400ms both;
}

@keyframes rise-hero-shot {
  from {
    opacity: 0;
    transform: translateY(26px) rotateY(-22deg) rotateX(7deg);
  }
}

/* Native scroll-driven animations. No observer library — and the fallback for
   a browser without them is "the content is visible", which is correct. */
@supports (animation-timeline: view()) {
  .reveal {
    animation: rise linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 30%;
  }

  .section .device--phone,
  .section .device--tablet {
    animation: turn linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 55%;
  }

  /* The paired devices lean *toward each other*, so they need their own two
     keyframes — the generic turn would flatten both the same way and the mirror
     would be lost. Higher specificity than the rule above, so order is not
     load-bearing here. */
  .pair .device--phone:first-child {
    animation: turn-pair-left linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 55%;
  }

  .pair .device--phone:last-child {
    animation: turn-pair-right linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 55%;
  }

  @keyframes turn-pair-left {
    from {
      transform: translateY(22px) rotateY(20deg) rotateX(7deg) rotateZ(2deg);
    }
    to {
      transform: translateY(0) rotateY(12deg) rotateX(4deg) rotateZ(1deg);
    }
  }

  @keyframes turn-pair-right {
    from {
      transform: translateY(22px) rotateY(-20deg) rotateX(7deg) rotateZ(-2deg);
    }
    to {
      transform: translateY(0) rotateY(-12deg) rotateX(4deg) rotateZ(-1deg);
    }
  }

  @keyframes turn {
    from {
      transform: translateY(22px) rotateY(-24deg) rotateX(8deg) rotateZ(-2.5deg);
    }
    to {
      transform: translateY(0) rotateY(-9deg) rotateX(3deg) rotateZ(-0.5deg);
    }
  }

  .split--flip .device--phone,
  .split--flip .device--tablet {
    animation-name: turn-flip;
  }

  @keyframes turn-flip {
    from {
      transform: translateY(22px) rotateY(24deg) rotateX(8deg) rotateZ(2.5deg);
    }
    to {
      transform: translateY(0) rotateY(9deg) rotateX(3deg) rotateZ(0.5deg);
    }
  }

  @media (max-width: 48rem) {
    .section .device--phone,
    .section .device--tablet,
    .split--flip .device--phone,
    .split--flip .device--tablet {
      animation: none;
    }
  }
}

/* Everything must be complete and legible with all motion removed. A section
   that only makes sense once it has animated is broken. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 160ms !important;
  }

  .device--phone,
  .device--tablet,
  .pair .device--phone:first-child,
  .pair .device--phone:last-child {
    transform: none !important;
    animation: none !important;
  }

  /* **The hero pair keep their pose, and that is a deliberate exception to the
     rule above.** Everywhere else on this page the 3D transform is the resting
     end of a scroll-driven turn, so removing motion and removing the transform
     are the same edit. Here nothing turns — the pose is static, and with no
     bezel it is part of what says "device" rather than "picture". Flattening it
     would not remove motion. The entry animation is still dropped, which is the
     part this query is about. */
  .hero__plan,
  .hero__list {
    animation: none !important;
  }
}
