/* ---------------------------------------------------------------------------
   SteadWell, on the web.

   THE PAGE IS BUILT AROUND ONE IDEA, TAKEN FROM THE LOGO. The mark is a check
   that sweeps beneath a leaf: a line that dips and then recovers. A real weight
   chart looks exactly like that, and the product's whole argument is that a bad
   day is not a trend. So the sweep is the spine of this page, drawn from the
   same path the app uses (mobile/src/brand.js SWEEP_PATH).

   Everything else follows the app's own rules, so the site and the product feel
   like one thing: type and space carry the hierarchy rather than coloured boxes,
   and the one saturated colour is reserved for things that mean something.

   NO THIRD-PARTY REQUESTS, deliberately. Fonts are served from /assets rather
   than a CDN. A page that makes a privacy claim while handing every visitor's
   IP to a font host is not making the claim honestly.
--------------------------------------------------------------------------- */

@font-face { font-family: 'Jakarta'; src: url('/assets/fonts/jakarta-500.ttf') format('truetype'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Jakarta'; src: url('/assets/fonts/jakarta-800.ttf') format('truetype'); font-weight: 800; font-display: swap; }
@font-face { font-family: 'SWMono'; src: url('/assets/fonts/mono-600.ttf') format('truetype'); font-weight: 600; font-display: swap; }

:root {
  /* The header's real height, border included. The driver re-measures it on load. */
  --hdr: 79px;
  /* SAMPLED FROM THE DESIGN COMP, not from the app. The hero art is a rectangular
     crop of that comp, so any difference between its background and the page's shows
     up as a visible panel behind the phone. The app's own appBg (#F6F7F2) is a touch
     greener and did exactly that. */
  --paper:   #F8F6F1;
  --card:    #ffffff;
  --ink:     #1a1714;
  --ink2:    #4b463f;
  --muted:   #6b655d;
  --faint:   #b4afa4;
  --hair:    #e6e2d8;
  /* The app's `muted2`, used only by the plan cards, which reproduce its paywall. */
  --muted2:  #8b857c;
  --green:   #16A34A;
  --green-d: #0f7a37;
  --green-bg:#eafaef;
  --measure: 1180px;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: 'Jakarta', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-weight: 500;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
/* THE PAPER GRAIN IS GONE, and it is worth knowing why rather than re-adding it.
   It was a fixed full-page noise overlay at 3.5% sitting at z-index 1, under the
   content at z-index 2. That is invisible over flat colour and very visible the
   moment a photographic asset lands on the page: the hero art (a crop of the design
   comp) rendered ABOVE the grain while the paper around it rendered below, so the
   image read as a lighter rectangular panel with a hard edge down its left side.
   The comp's background is flat, so matching it means no grain. */
.wrap { max-width: var(--measure); margin: 0 auto; padding: 0 28px; position: relative; z-index: 2; }
a { color: var(--green); text-decoration: none; }
a:hover { color: var(--green-d); }
/* `height: auto` is NOT optional here, and leaving it out was a real bug.
   The <img> tags carry width/height attributes so the browser can reserve space and
   avoid layout shift. Those attributes ALSO act as a presentational height, so
   constraining only the width leaves the height at the attribute's value and every
   screenshot renders squashed: the calorie dial came out as an ellipse and the hero
   phone was 1714px tall. This line is what keeps the aspect ratio honest. */
img { max-width: 100%; height: auto; display: block; }

.mono { font-family: 'SWMono', ui-monospace, monospace; font-weight: 600; }
.kicker {
  font-family: 'SWMono', ui-monospace, monospace; font-weight: 600;
  font-size: 12px; letter-spacing: .16em; text-transform: uppercase; color: var(--green);
}

/* ---- header ---- */
header {
  position: sticky; top: 0; z-index: 20;
  backdrop-filter: saturate(140%) blur(12px);
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  border-bottom: 1px solid transparent; transition: border-color .3s ease;
}
header.stuck { border-bottom-color: var(--hair); }
/* THE HEADER'S HEIGHT IS NOT A FREE NUMBER: the scroll story sticks to it, and the
   driver reads `header.offsetHeight` at load and writes it into `--hdr`, so changing
   this moves the pinned box with it and nothing needs editing in two places. The CSS
   default below it is only what applies before the first frame of JS. */
header .wrap { display: flex; align-items: center; justify-content: space-between; height: 78px; }
.brand { display: flex; align-items: center; gap: 12px; color: var(--ink); }
.brand img { width: 38px; height: 38px; border-radius: 11px; }
.brand span { font-weight: 800; font-size: 23px; letter-spacing: -.5px; }
nav { display: flex; gap: 28px; font-size: 14.5px; }
nav a { color: var(--ink2); }
nav a:hover { color: var(--green); }
@media (max-width: 720px) { nav { display: none; } }

/* ---- hero ----
   The headline and the status line must BOTH clear the fold on a 1280x800 laptop, which
   is the smallest screen anyone reviews a landing page on. That constrains the phone:
   a 2.16 aspect screenshot is tall, so it is capped by viewport height as well as width
   and the columns align to the top rather than centring (centring a 420px text block
   against a 650px phone pushed the headline 785px down the page). */
/* `overflow: hidden` CROPS THE SWEEP, and that is the point twice over: it is the same
   rule the app applies to the motif (a sweep that runs off its surface reads as
   something printed on it, one tucked fully inside reads as a sticker), and it stops the
   140%-wide bleed from widening the document. Relying on `overflow-x: hidden` on <body>
   alone did not: the page still measured wider than the viewport. */
/* `.hero` and `.hero-grid` are GONE: the hero is step one of `.hero-story` now, and
   its layout lives with the rest of that section at the bottom of this file. The
   h1, .lede and .status rules below are still the hero's and are still used. */
/* The art column is the wider one now: it carries the phone AND four pins hanging off
   its edges, while the copy column only has to hold a 30ch measure. */
h1 {
  /* THE CAP IS SET BY ONE LINE FITTING, not by taste. The copy column gave width to
     the art and its pins, so "The scale lied" has about 477px to live in: at 82 it
     wrapped and stranded "this" alone on a fourth line, and 78 is the largest size
     measured to still keep those three words together. Re-measure if the column
     split or the wording changes. */
  font-weight: 800; font-size: clamp(46px, 6.1vw, 78px); line-height: 1;
  letter-spacing: -.035em; margin: 0;
}
/* THE HAND-DRAWN UNDERLINE IS GONE, from "lied" here and from "foods" in the
   eating-out heading (where the comp has one). It was there to keep a single green
   word from being the only emphasis, which is the generic move; removing the
   emphasis entirely is simpler than replacing it, and at 78px the headline does not
   need help. */

.lede { font-size: clamp(17px, 1.6vw, 20px); color: var(--ink2); max-width: 30ch; margin: 22px 0 0; }
/* The hero art. It bleeds past its column on both sides, the way it does in the comp,
   and is allowed to run wider than the grid cell rather than being boxed into it.
   THE SIZING LIVES ON THE FIGURE, not the image, because the pins are positioned as
   percentages of this box and it therefore has to be exactly the art's box. */
/* The bleed is symmetric and modest now. It used to hang 22% into the left gutter,
   which was fine while the labels were baked into the picture and is not now: the
   pins hang OUTSIDE the phone, so the art needs clear space on both sides rather
   than maximum width. */
/* SIZED FROM MEASUREMENTS, not by eye, and the constraint is the pins rather than the
   picture. Each one hangs 148px of text plus a 16px stand-off outside the phone, so
   the art can only be as wide as the column minus roughly 330px if the left pin is to
   clear the copy and the right pin is to stay inside the page. At 1280 that lands at
   700px: still larger than the 640 it started at, and the widest that keeps all four
   labels on screen. */
/* THE TWO PHONES ARE THE SAME SIZE ON THE PAGE, and that is what these two widths are
   for. They are not equal numbers: the phone fills a different fraction of each comp
   (730px of a 906px crop in the hero, 823px of a 1016px crop in the eating-out one),
   so matching the DEVICE means the images differ. 645 and 649 put both phones at
   ~520px tall. Recompute if either crop changes:
     hero_h * (Wh / hero_w)  ==  eat_h * (We / eat_w) */
.art-figure {
  position: relative; margin: -26px 0 0 -20px;
  width: min(645px, 124%); max-width: none;
}
.art-photo { width: 100%; }

/* Sized by the same constraint as the hero, measured the same way: the left pin has
   to clear the copy column and the right pin has to stay inside the page, and each
   hangs 164px. That leaves about 600px at 1280, with the copy column narrowed to
   0.8fr to buy the art the room. Overflowing here is not a cosmetic problem: the
   first attempt pushed the document to 1308px wide and put "Menu analyzed" off the
   right edge of the screen. */
.eatout-figure { margin: 0 0 0 34px; width: min(649px, 100%); }
/* `.split.eatout-grid`, not `.eatout-grid`: `.split` sets its own 1fr 1fr further down
   this file, and at equal specificity the later rule wins. The copy column is narrower
   than the hero's (0.75fr) because this art needs more room: its pins sit further out
   from the phone than the hero's do. */
.split.eatout-grid { grid-template-columns: .75fr 1.25fr; gap: 40px; align-items: center; }
/* The logging figure. Its comp puts labels on BOTH sides of the phone and its nodes sit
   almost on the art's edges (6.4% and 92.1%), so the pins hang further out than any
   other section's: the margin-left is what buys the left pair room to clear the copy
   while keeping the right pair inside the page. */
.meallog-figure { margin: 0 0 0 130px; width: min(373px, 100%); }
.split.meallog-grid { grid-template-columns: .75fr 1.25fr; gap: 40px; align-items: center; }

/* The comp's closing line: the argument in one sentence, in ink rather than grey. */
.body.strong { color: var(--ink); font-weight: 800; font-size: 19px; margin-top: 22px; }

/* Each pin is a ZERO-SIZED anchor at the node's coordinates, with the text hung off
   one side of it. That is what keeps the green dot exactly on the point the comp's
   connector curve runs to: the text can be any length or wrap to any depth without
   moving the anchor by a pixel. */
.art-pin { position: absolute; left: var(--x); top: var(--y); width: 0; height: 0; }
.art-pin::after {
  content: ''; position: absolute; left: -4px; top: -4px; width: 8px; height: 8px;
  background: var(--green); border-radius: 50%;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--green) 16%, transparent);
}
.art-pin .t {
  position: absolute; top: 50%; transform: translateY(-50%);
  display: grid; gap: 2px; width: 148px;
  font-size: 12.5px; line-height: 1.38; color: var(--muted);
}
.art-pin b { font-weight: 800; font-size: 14px; color: var(--ink); letter-spacing: -.01em; line-height: 1.25; }
.art-pin.l .t { right: 16px; text-align: right; }
.art-pin.r .t { left: 16px; }

.status {
  display: inline-flex; align-items: center; gap: 9px; margin-top: 18px;
  border: 1px solid var(--hair); background: var(--card); border-radius: 999px;
  padding: 9px 16px 9px 13px; font-size: 13.5px; color: var(--muted);
}
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); position: relative; }
/* The ring no longer pulses, for the same reason the cue no longer nudges: on a
   screen whose premise is that only the words move, a scaling ring is motion the
   eye reads as the page being unsettled. The green dot carries the pill on its own. */
.dot::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; border: 1px solid var(--green); opacity: .3; }

/* The full-bleed brand sweep that used to run across the hero is GONE. The hero art
   is a crop of the design comp and carries its own background curves, and the two
   sets of lines read as clutter over each other. The motif still appears where it
   belongs: as the underline on the headline. */
@keyframes draw { to { stroke-dashoffset: 0 } }

/* THE CSS PHONE FRAME IS GONE (Aug 21). `.phone` was a bezel drawn in CSS around a
   flat screenshot, and the last thing using it was the accountability group. That
   section is a step of the top story now, with its own comp render, so every device
   on this page comes out of a design comp and none is rebuilt in CSS. Its screenshot,
   `shots/group.webp`, is unused with it: the shots folder now feeds nothing on the
   page and exists for the capture scripts. */

/* ---- the scroll cue ----
   THE BLURRED BAND (`.veil`) IS GONE (Aug 20), and it is worth knowing why, because
   it was carefully tuned and it still had to go. It was a fixed, blurred, masked
   cloud across the bottom of the first screen, fogging whatever the fold cut through.
   Its profile was tuned against a STATIC hero whose content ended well above the
   fold. The top of the page is a scroll story now: a sticky box that centres its
   content in the viewport, so on a shorter window the copy, the button and the
   status pill all sit exactly where that band was. The mask edge is invisible over
   flat paper and very visible over a saturated object, which is what got reported,
   correctly, as "a weird grey line around half of the button": measured on an 800px
   window the button's green ran (22,163,74) at the top and (57,172,98) at the bottom,
   washed out by the band crossing it, and on a 720px one the whole button sat inside
   it. Raising the button above it with z-index does NOT work and was tried: the story
   step is inside a `position: sticky` box, which creates its own stacking context, so
   nothing inside it can outrank a fixed element that sits above that box.
   The arrow stays. It was always the part that says "there is more below", it fades
   over the first 170px of scroll the same as before, and it sits low enough on the
   screen to be clear of the story's content. */
.veil-cue {
  position: fixed; left: 50%; bottom: 26px; z-index: 16;
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--hair); background: color-mix(in srgb, var(--card) 82%, transparent);
  backdrop-filter: blur(4px); color: var(--muted);
  transform: translateX(-50%);
  transition: opacity .25s ease, color .2s ease, border-color .2s ease;
  /* THE NUDGE IS GONE. It was a 5px vertical loop, and the first screen is a scroll
     story whose whole premise is that nothing moves except the words: a cue bobbing
     up and down at the bottom of it reads as the PAGE drifting, which is exactly how
     it was reported. The cue still fades out over the first 170px of scroll, which
     is what tells you it is about scrolling. */
}
.veil-cue:hover { color: var(--green); border-color: color-mix(in srgb, var(--green) 40%, var(--hair)); }
/* GONE WHEN THE STORY IS NOT STICKY. Its whole job is "there is more below this
   pinned screen"; stacked, the page just scrolls and the arrow is a floating circle
   sitting on top of the hero art, which is how it read on a phone. */
@media (max-width: 1249px) { .veil-cue { display: none; } }
.veil-cue svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* ---- sections ---- */
section { padding: 96px 0; position: relative; }
/* Section labels ("The idea", "Targets that move") are GONE, along with the rule
   that ran off them. They were captions on sections that already announce
   themselves in a heading, and stacking a small green label over every h2 is one
   of the things that made the page read as generated. The kicker style stays
   because the hero still uses it once. */
h2 { font-weight: 800; font-size: clamp(31px, 3.8vw, 47px); line-height: 1.06; letter-spacing: -.03em; margin: 0 0 18px; max-width: 20ch; }
h3 { font-weight: 800; font-size: 21px; letter-spacing: -.02em; margin: 0 0 8px; }
/* Body copy runs a little larger than is usual for a marketing page, because there
   is no longer a label, a pull quote and a stat block competing with it. */
.body { color: var(--ink2); font-size: 18px; line-height: 1.62; max-width: 54ch; }
.body + .body { margin-top: 16px; }

/* ---- the third section's figure ----
   The old `.annotated` / `.pin` system lived here: a flat screenshot in a CSS phone
   frame with pins positioned against a wrapper box. It is GONE, and the section now
   uses the same `.art-figure` / `.art-pin` machinery as the other two, because a
   third annotation mechanism for the same job is how a page ends up with three
   slightly different design systems in it.
   This figure is CENTRED under full-width copy rather than sitting in a split, so
   its own width is all that positions it; the pins hang into the space either side. */
.targets-figure { margin: 46px auto 0; width: min(390px, 62vw); }

/* The connector curves for the third figure, drawn because that render has none baked
   in. Faint on purpose: in the other two comps these are background lines you notice
   only after the phone, and a crisp pointer would look like a diagram. */
.art-lines { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2; }
.art-lines path {
  fill: none; stroke: var(--green); stroke-width: 1; opacity: .3;
  stroke-linecap: round; vector-effect: non-scaling-stroke;
}

.split { display: grid; grid-template-columns: 1fr 1fr; gap: 68px; align-items: center; }
.split.flip > :first-child { order: 2; }



/* ---- plans ----
   THESE ARE THE APP'S PAYWALL CARDS, rebuilt for the web from
   `mobile/src/screens/paywall.js`: same rows, same words, same order, same icon tile
   beside a bold title and a one-line benefit. Two differences, both deliberate and
   both noted in the markup: the cards sit side by side because a page has the width a
   phone does not, and neither carries the app's purchase button because there is
   nothing to buy from a browser.
   Colours come from `mobile/src/theme.js` rather than this page's palette wherever
   the app's own surface is being reproduced: the gradient is `C.grad`
   (#22C55E -> #15803D), the tile behind a dark-card icon is white at 20%, and the
   light card's tile is `C.greenBg` (#eafaef). The Pro card's surface is the DARK END
   of the app's gradient, flat: see the note on `.plan.pro` below. */
/* STRETCHED, not top-aligned. The app stacks these so their different heights never
   meet; side by side, a short card next to a tall one reads as one of them being
   unfinished rather than as one plan having fewer features. */
.plans { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin-top: 42px; align-items: stretch; }
.plan {
  position: relative; border-radius: 22px; padding: 26px 24px;
  border: 1px solid var(--hair); background: var(--card);
}
/* FLAT, NOT A GRADIENT, AND A POINT ALONG THE APP'S OWN RAMP. The app's Pro card runs
   #22C55E to #15803D across the diagonal. This was the gradient, then its dark end
   (#15803D, the bottom right corner), then this: 30 percent of the way back up that
   ramp, because the dark end read as too dark on a page this light.
   WHITE TEXT ON GREEN IS A CONTRAST BUDGET, and this is where it is spent: #199547
   gives 3.87:1 against white, which clears WCAG AA for the 13.5px titles (large text,
   3:1) but not the 12.5px descriptions (4.5:1). Those are pushed to nearly solid white
   to claw back what opacity was costing. If a strict AA pass is ever wanted, #178841
   is the lightest point on this ramp that gets there, at 4.53:1. Anything lighter,
   including the brand's own #16A34A at 3.30:1, is worse rather than better. */
.plan.pro {
  border: 0; color: #fff;
  background: #199547;
  box-shadow: 0 18px 44px -28px rgba(22,163,74,.5);
}
.plan h3 { font-size: 19px; margin: 0; letter-spacing: -.01em; }
.plan.pro h3 { color: #fff; }
.plan-price { margin: 2px 0 16px; font-weight: 800; font-size: 34px; letter-spacing: -.02em; line-height: 1.1; }
.plan-price span { font-size: 14px; font-weight: 800; color: var(--muted); margin-left: 5px; letter-spacing: 0; }
.plan.pro .plan-price { color: #fff; }
.plan.pro .plan-price span { color: rgba(255,255,255,.94); }
.plan.basic .plan-price { font-size: 28px; }
/* The app puts a white pill in the Pro card's top right corner. */
.plan-badge {
  position: absolute; top: 22px; right: 22px;
  background: #fff; color: var(--green); border-radius: 999px;
  padding: 4px 11px; font-size: 10.5px; font-weight: 800; letter-spacing: .04em;
}

.plan-features { list-style: none; padding: 0; margin: 0; display: grid; gap: 13px; }
.plan-features li { display: flex; align-items: center; gap: 11px; }
/* The icon tile: 30px, radius 9, exactly as the app draws it. */
.plan-features .fi {
  flex: none; width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; background: var(--green-bg);
}
.plan.pro .plan-features .fi { background: rgba(255,255,255,.20); }
.plan-features .fi svg {
  width: 17px; height: 17px; fill: none; stroke: var(--green);
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.plan.pro .plan-features .fi svg { stroke: #fff; }
.plan-features .ft { display: grid; gap: 1px; font-size: 12.5px; line-height: 1.35; color: var(--muted2, var(--muted)); }
.plan-features .ft b { font-size: 13.5px; font-weight: 800; color: var(--ink); letter-spacing: -.01em; }
.plan.pro .plan-features .ft { color: rgba(255,255,255,.94); }
.plan.pro .plan-features .ft b { color: #fff; }

/* ---- footer ---- */
footer { border-top: 1px solid var(--hair); padding: 54px 0 70px; margin-top: 40px; }
.foot { display: flex; justify-content: space-between; gap: 30px; flex-wrap: wrap; align-items: flex-start; }
/* On a phone the flex row wrapped into a lopsided 2 + 1 + 1: the brand column took
   the width and the link columns squeezed against the right edge. Two even columns
   with the brand across the top reads as a footer instead. */
@media (max-width: 620px) {
  .foot { display: grid; grid-template-columns: 1fr 1fr; gap: 30px 24px; }
  .foot .col:first-child { grid-column: 1 / -1; }
  .foot .col:last-child { grid-column: 1 / -1; }
}
.foot a { color: var(--ink2); font-size: 14.5px; }
.foot a:hover { color: var(--green); }
.foot .col { display: grid; gap: 10px; }
.legalnote { color: var(--faint); font-size: 12.5px; margin-top: 34px; max-width: 62ch; line-height: 1.65; }

/* ---- reveal on scroll ---- */
.rise { opacity: 0; transform: translateY(18px); transition: opacity .7s cubic-bezier(.22,1,.36,1), transform .7s cubic-bezier(.22,1,.36,1); }
.rise.in { opacity: 1; transform: none; }

@media (max-width: 900px) {
  /* `.split.eatout-grid` has to be named again here: it is a two-class selector, so it
     outranks a bare `.split` even inside this media query, and the eating-out section
     stayed in two columns on a phone until it was. */
  .split, .split.eatout-grid, .split.meallog-grid { grid-template-columns: 1fr; gap: 44px; }
  .eatout-figure { margin: 4px -8% 0; }
  .meallog-figure { margin: 4px auto 0; width: min(300px, 74vw); }
  .split.flip > :first-child { order: 0; }
  .plans { grid-template-columns: 1fr; }
  section { padding: 68px 0; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .rise { opacity: 1; transform: none; transition: none; }
  .cta { transition: none; }
  /* Every self-drawing line settles at its finished state instead of never arriving:
     a dashoffset left at its start value is an INVISIBLE graphic, not a still one. */
  h1 .dip .ul path { animation: none; stroke-dashoffset: 0; }
}

/* ---------------------------------------------------------------------------
   THE SCROLL STORY (#story)
   The only scroll-driven section on the page. A sticky row holds two things: a
   single reading position on the left that the four feature texts travel UP
   through, and one phone on the right that never moves while its screen
   crossfades to whatever is being read.

   Four rules this section is built on, each of which is the difference between
   a product on a desk and a slideshow:
     - THE DEVICE IS INERT. No transform, no scale, no parallax, no rotation
       change between steps. It is `.phone` at rest, and the only thing that
       animates inside it is the opacity of four stacked <img>.
     - THE TEXT SHARES ONE POSITION. Every step is absolutely placed at the same
       centre line and offset from it by scroll, so the active one always lands
       in the same spot. Steps in separate places, faded in and out, is the thing
       this is not.
     - THE STICKY BOX CLEARS THE HEADER. The header is `position: sticky` at 68px,
       so this sits at 68 and is 68 shorter than the viewport, or the phone's top
       runs under it.
     - IT IS DESKTOP ONLY. Under 900px, and under prefers-reduced-motion, the
       whole mechanism is turned off rather than compromised: the steps become a
       plain list, each with its own screenshot in a phone frame.

   The heights: `--steps` (set on the section in markup) drives the track, at one
   viewport-and-a-bit per step. Less than that and the copy is gone before it is
   read, which was the first version's problem.
--------------------------------------------------------------------------- */
.story-track { height: calc(var(--steps) * 105vh); margin-top: 30px; }
.story-sticky {
  /* --hdr, not 68: `header .wrap` is 68px and the header carries a 1px bottom border,
     so the sticky box's natural position is 69 and sticking at 68 made it jump a pixel
     the moment the page moved. On a section whose premise is that nothing moves, one
     pixel is the difference between still and settling. The driver reads the header's
     real height and keeps this variable in step. */
  position: sticky; top: var(--hdr, 69px); height: calc(100vh - var(--hdr, 69px));
  display: grid; grid-template-columns: 1fr 1fr; gap: 68px; align-items: center;
}
/* Stretched to the full height of the sticky row, not centred as a 340px block:
   the ticks are pinned to the BOTTOM of this column, and against an auto-height box
   "the bottom" was the bottom of the reading window, which is exactly where the
   headlines travel. The reading window is centred inside it instead. */
.story-copy { position: relative; align-self: stretch; display: flex; align-items: center; }

/* The single reading window. Fixed height so the four steps, which are different
   lengths, all pivot around the same centre line instead of each finding its own. */
.story-text { position: relative; height: 340px; width: 100%; }
.story-step {
  position: absolute; left: 0; right: 0; top: 50%;
  /* --ty is the scroll-driven travel, set by JS in px; the -50% is what centres a
     block of unknown height on the reading line. Transform and opacity only: both
     are composited, so a 4,000px-tall section scrolls without a layout pass. */
  transform: translate3d(0, calc(-50% + var(--ty, 0px)), 0);
  opacity: var(--op, 0);
  will-change: transform, opacity;
  /* An invisible step must not eat clicks or land in the tab order of the one on
     top of it. THE ACTIVE ONE MUST, though, and a flat `pointer-events: none` here
     was a real bug: the steps are stacked on the same square, so the rule was written
     to stop the hidden ones swallowing clicks, and it silently made the hero's button
     unclickable as well. The driver sets `--pe: auto` on whichever step is the one
     you can read (opacity at or above a half, which is exactly one of them at any
     scroll position, the fades being complementary). */
  pointer-events: var(--pe, none); visibility: var(--vis, hidden);
}
.story-step .body { max-width: 46ch; }

@media (max-width: 900px) {
  /* Mechanism off, not shrunk. Each step is an ordinary block with its own phone
     under it, which is the same shape the rest of the page takes at this width. */
  .story-track { height: auto; margin-top: 8px; }
  .story-sticky { position: static; height: auto; display: block; }
  .story-text { height: auto; }
  .story-copy { display: block; }
  .story-step {
    position: static; transform: none; opacity: 1; visibility: visible;
    pointer-events: auto; margin-top: 54px;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Same fallback, for the same reason: every feature stays readable, nothing is
     driven by scroll. JS also stops writing the custom properties. */
  .story-track { height: auto; margin-top: 8px; }
  .story-sticky { position: static; height: auto; display: block; }
  .story-text { height: auto; }
  .story-copy { display: block; }
  .story-step {
    position: static; transform: none; opacity: 1; visibility: visible;
    pointer-events: auto; margin-top: 54px;
  }
}

/* ---------------------------------------------------------------------------
   THE TOP STORY (#eatout, `.hero-story`)
   The hero and the eating-out section are two STEPS of one scroll interaction
   now, sharing the machinery above: the copy travels through a single reading
   position on the left, and the whole comp art crossfades in a fixed stage on
   the right.

   The one rule this section breaks on purpose: the DEVICE CHANGES between steps
   here, because the two pictures are different comps of different devices at
   different angles, with their own baked-in background curves and their own
   pins. The phone-screen story further down is the opposite case (one frame,
   four screens) and keeps the frame nailed down. Both are crossfades of the
   media only; neither ever moves, scales or rotates anything.

   The stage is a fixed box with both figures centred inside it, so a 906x911
   crop and a 1016x928 one occupy the same optical centre and nothing shifts as
   one replaces the other.
--------------------------------------------------------------------------- */
/* NO `overflow: hidden` HERE, and this was a real bug worth naming: the old `.hero`
   carried it to crop the art's bleed, and an ancestor with a clipping overflow makes
   `position: sticky` stick to a scrollport that does not scroll, i.e. not at all. The
   symptom is the whole story scrolling past as one block with a screenful of blank
   paper under it. The bleed is contained by the stage's own widths instead, and the
   document was measured at 1280 and 432 to prove it. */
.hero-story { padding: 0; }
/* A little longer per step than the phone story: there is more to read on the
   first step (headline, lede and the status pill) and only two steps to spend it
   on, so a short track makes the hero feel like it is being taken away. */
/* `--tail` is extra track AFTER the last step lands (in steps). Without it the sticky
   released at the exact moment the final feature arrived, so the page dropped into the
   pricing section the instant that copy was readable. The driver runs `t` past the last
   index by the same amount, so the words keep rising through it. */
.hero-story .story-track { height: calc((var(--steps) + var(--tail, 0)) * 118vh); margin-top: 0; }
/* The art column is the wider one, exactly as the old hero grid had it: it
   carries the device AND four pins hanging off its edges, while the copy column
   only has to hold a 30ch measure. */
.hero-story .story-sticky { grid-template-columns: .88fr 1.12fr; gap: 40px; }
/* Taller reading window than the phone story's: an 78px headline over a lede and
   a pill is about 400px of block. */
.hero-story .story-text { height: 430px; }

.story-stage { position: relative; height: 100%; }
.story-stage > .art-figure {
  position: absolute; top: 50%; left: 50%;
  /* THE DEVICE IS CENTRED, NOT THE PICTURE, and that distinction is the whole
     quality of the crossfade. Each comp frames its phone differently inside its own
     crop (the hero's device centre sits at 46.5% of the art's height, the
     eating-out one's at 51.8%), so four pictures centred on the same point put four
     phones in four different places: measured, 33px of vertical jump and 15px of
     horizontal between steps one and two, which reads as the device hopping as it
     dissolves. Each figure is offset by ITS OWN device centre instead, so all four
     phones sit on one point and the crossfade happens in place.
     `--cx`/`--cy` are measured device-bbox centres as a fraction of the art; the
     translate is in percentages of the FIGURE, which is the same thing once the
     figure is the art's box. See the widths below for the numbers and how they were
     taken. This transform is still a CONSTANT: nothing in the scroll handler ever
     touches it, so no picture moves, scales or rotates. */
  transform: translate(calc(var(--cx, .5) * -100%), calc(var(--cy, .5) * -100%));
  margin: 0; opacity: 0; will-change: opacity;
}
/* The first picture is opaque in the markup, so the page is correct before a line
   of JS runs and stays correct if none ever does. */
.story-stage > .art-figure.on { opacity: 1; }
/* THE TWO PHONES ARE THE SAME SIZE ON THE PAGE, which is not the same as the two
   images being the same width: the device fills a different fraction of each comp
   (730px of a 906px crop, 823px of a 1016px one). 645 and 649 put both devices at
   about 520px tall. The third term caps each by VIEWPORT HEIGHT, which the old
   stacked hero did not need and a sticky box does: there is no scrolling past a
   picture that is taller than the box it is pinned in. Each uses its own aspect
   (906/911 and 1016/928) so the cap lands on the same height for both. */
/* WIDTH holds the device SIZE equal, `--cx`/`--cy` hold its POSITION equal. Both
   sets of numbers come from the same measurement, run over the built .webp files: the
   device's bounding box is the dark-bezel pixels, and the four came out 729, 968, 917
   and 933 art-px tall inside arts of 906x911, 965x1130, 690x1060 and 670x995. Each
   width is `520 * art_w / device_h`, which is the relation this page has always held
   (all four phones ~520px on screen); each --cx/--cy is that bbox's centre over the
   art's size. RE-MEASURE ALL EIGHT NUMBERS if any comp is re-exported. */
.hero-story .hero-figure    { --cx: .4895; --cy: .4649; width: min(645px, 108%, calc((100vh - 200px) * 0.994)); }
/* 508, not 649: the comp behind this one was replaced on Aug 20 with a tighter
   render (the device is 983px tall in a 960px crop, against 823 in a 1016px one), so
   the width that lands the DEVICE at the same ~520px as the other three came down
   with it. 520 * 960 / 983 = 508. The height cap uses this art's own aspect. */
.hero-story .eatout-figure  { --cx: .5280; --cy: .4991; width: min(518px, 109%, calc((100vh - 200px) * 0.854)); }
/* The third and fourth comps are narrower crops of taller renders, which is why
   these two numbers are so much smaller than the first two and still put the DEVICE
   at the same height. The relation to hold if a crop ever changes is
   `phone_h * (W / art_w)` equal across all four. The height cap uses each art's own
   aspect (690/1060 and 670/995) so it lands on the same height for every one. */
.hero-story .targets-figure { --cx: .4783; --cy: .4835; width: min(390px, 100%, calc((100vh - 200px) * 0.651)); }
.hero-story .meallog-figure { --cx: .5090; --cy: .5020; width: min(373px, 100%, calc((100vh - 200px) * 0.673)); }
/* The fifth, the group. Bare render like the targets one, so its numbers come from
   the same measurement: 800x1205 art, device 1093px tall centred at (395, 623.5). */
.hero-story .group-figure   { --cx: .4938; --cy: .5174; width: min(381px, 100%, calc((100vh - 200px) * 0.664)); }

/* THE PINS SIT TIGHTER TO THE PHONE IN THIS SECTION, and it is the second comp that
   sets the number. Its left node is the furthest out from its device of the four, and
   with the page's usual 148px label and 16px stand-off that label landed at x=547
   against copy whose INK reaches 549 (measured with Range client rects, not element
   boxes: an h1 is a full-width block, so its box tells you nothing about where the
   letters stop). 138 and 10 pull every label in by 16px on both sides, which clears
   the copy by 14 and brings the right-hand ones 16px further inside the page.
   Uniform across all four figures on purpose: doing it to one changes how that one
   picture's labels wrap, and the difference shows in a crossfade. Re-check the left
   pin against the copy's ink if either the art or these numbers change. */
.hero-story .art-pin .t { width: 138px; }
.hero-story .art-pin.l .t { right: 10px; }
.hero-story .art-pin.r .t { left: 10px; }

/* The lede runs a little wider here than the 30ch it inherits: it carries the app's
   whole argument now rather than one sentence about the trend line, and at 30ch that
   is seven short lines stacked under a 78px headline. */
.hero-story .lede { max-width: 36ch; }

/* ---- the launch CTA ----
   The one solid green thing on the page, which is the whole reason it reads as the
   button: everywhere else the green is a rule, a node or a word. It is a MAILTO, not
   a form, because there is no waitlist behind it (see the markup).
   Restrained on purpose: a pill in the brand green, the same radius as the status
   pill under it, no shadow and no gradient. The hover darkens and does not move,
   because this sits inside a section whose premise is that nothing moves except the
   words. */
.cta {
  /* THE UA BUTTON RESET, and leaving it out was a visible bug. This rule was written
     when the CTA was an <a>; it became a <button> with the dialog, and a bare button
     carries `border: 2px outset ButtonBorder`, which paints light on the top left and
     DARK ON THE BOTTOM RIGHT. Reported exactly that way, "a weird dark line around the
     bottom half of the button", and measured: a 2px ring of rgb(168,168,168) above and
     rgb(84,84,84) below. `font: inherit` belongs here for the same reason: a button
     does not inherit the page's family on its own. */
  appearance: none; -webkit-appearance: none; border: 0; cursor: pointer;
  font-family: inherit; line-height: 1.2; text-align: center;
  display: inline-flex; align-items: center; gap: 10px;
  margin-top: 26px; padding: 14px 24px;
  border-radius: 999px; background: var(--green); color: #fff;
  font-weight: 800; font-size: 15.5px; letter-spacing: -.01em;
  transition: background .2s ease;
}
.cta:hover { background: var(--green-d); color: #fff; }
.cta[disabled] { opacity: .6; }
.cta::after {
  /* A small arrow, drawn rather than an icon font, so the button still says "go"
     without another asset on a page that loads nothing from anywhere. */
  content: ''; width: 7px; height: 7px; margin-left: 1px;
  border: 2px solid currentColor; border-left: 0; border-bottom: 0;
  transform: rotate(45deg);
}

/* ---- the waitlist dialog ----
   A `<dialog>`, so the backdrop, the focus trap, ESC-to-close and the top layer are
   the browser's job rather than a z-index negotiation with a sticky header and a
   fixed veil. Styled as the page's own paper card: hairline border, the same radius
   family as the panels, no shadow stack and no gradient. */
.sheet {
  border: 1px solid var(--hair); border-radius: 22px;
  padding: 34px 32px 30px; width: min(420px, calc(100vw - 40px));
  background: var(--card); color: var(--ink);
  box-shadow: 0 40px 90px -30px rgba(26,23,20,.35);
}
.sheet::backdrop { background: color-mix(in srgb, var(--ink) 34%, transparent); }
.sheet h3 { margin: 0 0 8px; font-size: 24px; letter-spacing: -.02em; max-width: 15ch; }
.sheet-lead { margin: 0 0 20px; color: var(--ink2); font-size: 15px; line-height: 1.55; }
.sheet-x {
  position: absolute; top: 14px; right: 16px;
  border: 0; background: none; cursor: pointer;
  font-size: 26px; line-height: 1; color: var(--faint); padding: 4px 8px;
  transition: color .2s ease;
}
.sheet-x:hover { color: var(--ink); }
.sheet input[type="email"] {
  width: 100%; padding: 13px 18px; border-radius: 999px;
  border: 1px solid var(--hair); background: var(--paper); color: var(--ink);
  font: inherit; font-size: 15.5px;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.sheet input[type="email"]::placeholder { color: var(--faint); }
.sheet input[type="email"]:focus {
  outline: none; border-color: var(--green);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 14%, transparent);
}
.sheet .cta { margin-top: 12px; width: 100%; justify-content: center; }
.notify-msg { margin: 12px 0 0; font-size: 13.5px; color: var(--muted); min-height: 1px; }
.notify-msg.bad { color: #c02626; }
.notify-msg.bad a { color: #c02626; text-decoration: underline; }
.notify-done { margin: 0 0 18px; font-weight: 800; font-size: 15.5px; color: var(--green-d); }
/* The honeypot. Off-screen rather than display:none, because some submitters skip
   fields that are not rendered at all, and this one is meant to be filled in. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.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;
}

/* The status pill sits under the CTA on the first step, as it did in the hero. */
.hero-story .status { margin-top: 18px; }


/* ---------------------------------------------------------------------------
   THE TOP STORY NEEDS 1250px, and that is a measurement rather than a taste.
   Its four comps are ANNOTATED: every pin hangs 148px of text plus a 16px
   stand-off outside the phone, on both sides, and the last two crops put their
   nodes almost on the art's edge (4.3% and 92.1% on the logging one). Fitting a
   phone at the size the others are drawn at, plus 164px of label either side,
   inside one half of the page needs about 1250px of window: at 1100 the document
   measured 1119 with the right-hand labels hanging off the screen, and at 1000 it
   measured 1040. It was 1180 until the eating-out comp was replaced (Aug 20): the new
   render puts its nodes further out from the phone (87.1% of the crop against 76.8%),
   and "Menu analyzed" measured 21px off the right edge at a 1180 window. Every other
   way of buying that room was tried on paper first and each one costs something real:
   a narrower figure breaks the device-size parity between the four steps, shifting it
   left runs the left-hand pins into the copy column (they clear it by 9px as it is),
   and a narrower pin box changes how the labels wrap on ONE of the four pictures,
   which is visible in a crossfade.

   So below 1250 this section is the stacked layout, where the pins stop being
   pins and become a two-up legend under each picture. That is the same fallback
   the phones have always used, just reached earlier, and these rules were moved
   here from the 900px block WHOLESALE: after the restructure, every `.art-figure`
   on the page belongs to this section, so nothing else is affected by the move.
   The phone story further down keeps 900, having no labels to fit.
--------------------------------------------------------------------------- */
@media (max-width: 1249px) {
  /* Mechanism off, not shrunk, and the ORDER is the point: stacked, each step has
     to be followed by its own picture, but in the DOM the two pictures live
     together in the stage. `display: contents` dissolves the three wrappers so
     every step and every figure becomes a direct child of one flex column, which
     can then interleave them without duplicating any markup. */
  /* `.hero-story .story-track` has to be named: it is a two-class selector, so the
     bare `.story-track { height: auto }` above loses to it even inside this query,
     and the section kept its 236vh of track with nothing in the bottom 200 of it. */
  .hero-story .story-track { height: auto; margin-top: 0; }
  .hero-story .story-sticky { position: static; height: auto; display: flex; flex-direction: column; }
  .hero-story .story-copy, .hero-story .story-text, .hero-story .story-stage { display: contents; }
  .hero-story .story-step {
    position: static; transform: none; opacity: 1; visibility: visible; margin: 0;
    pointer-events: auto;
  }
  /* STACKED SIZES ARE CAPPED IN PIXELS AND CENTRED. The bleed belongs to a phone
     screen and nothing else: `width: 116%` here meant that at 1179, where this
     layout now starts, the pictures rendered 1180px wide and a single phone filled
     the window. Capped and centred, then allowed to bleed into the gutter (and
     exactly as far as the gutter, 28px) only under 700px, where a picture really
     does want the whole screen. The last two comps are narrower crops than the
     first two, which is why their caps are smaller: it keeps all four DEVICES the
     same size, the same relation the desktop widths hold. */
  .hero-story .story-stage > .art-figure {
    position: static; transform: none; opacity: 1;
    width: min(100%, 560px); margin: 4px auto 0;
  }
  .hero-story .story-stage > .targets-figure,
  .hero-story .story-stage > .meallog-figure,
  .hero-story .story-stage > .group-figure { width: min(100%, 330px); }
  .hero-story .story-step[data-step="0"] { order: 1; }
  .hero-story .story-stage > .art-figure:nth-of-type(1) { order: 2; }
  .hero-story .story-step[data-step="1"] { order: 3; margin-top: 54px; }
  .hero-story .story-stage > .art-figure:nth-of-type(2) { order: 4; }
  .hero-story .story-step[data-step="2"] { order: 5; margin-top: 54px; }
  .hero-story .story-stage > .art-figure:nth-of-type(3) { order: 6; }
  .hero-story .story-step[data-step="3"] { order: 7; margin-top: 54px; }
  .hero-story .story-stage > .art-figure:nth-of-type(4) { order: 8; }
  .hero-story .story-step[data-step="4"] { order: 9; margin-top: 54px; }
  .hero-story .story-stage > .art-figure:nth-of-type(5) { order: 10; }
  .hero-story { padding: 40px 0 0; }
  /* The pins cannot float beside a phone that already fills the screen, so they stop
     being pins and become a two-up legend under it, the same move the targets figure
     makes at this width. */
  .art-figure { display: grid; }
  .art-pin {
    position: static; width: auto; height: auto;
    display: flex; align-items: baseline; gap: 9px;
  }
  /* `order: -1` because ::after is generated AFTER the text, so in a flex row the
     node would otherwise trail the label it belongs to. */
  .art-lines { display: none; }
  .art-pin::after { position: static; flex: none; order: -1; margin-top: 6px; }
  /* The .l/.r selectors that align the desktop pins outward are more specific than a
     bare `.art-pin .t`, so they have to be named here or the left column stays
     right-aligned in the stacked legend. */
  .art-pin.l .t, .art-pin.r .t {
    position: static; transform: none; width: auto; text-align: left;
  }
  .art-figure { grid-template-columns: 1fr 1fr; gap: 16px 20px; }
  .art-photo { grid-column: 1 / -1; }
}


/* Under 700px a picture does want the whole screen, so the two wide comps bleed into
   the gutter and exactly as far as the gutter. 28px is the `.wrap` padding, so the
   art lands on the viewport edge rather than a percentage of a column, which is what
   used to drag the two-up legend off the left of the screen. */
@media (max-width: 700px) {
  .hero-story .story-stage > .hero-figure,
  .hero-story .story-stage > .eatout-figure {
    width: calc(100% + 56px); margin: 4px -28px 0;
  }
  .hero-story .story-stage > .targets-figure,
  .hero-story .story-stage > .meallog-figure,
  .hero-story .story-stage > .group-figure { width: min(100%, 300px); }
}

@media (prefers-reduced-motion: reduce) {
  /* Same fallback for the same reason: both features stay readable and nothing is
     driven by scroll. The JS stops writing its custom properties too. */
  /* `.hero-story .story-track` has to be named: it is a two-class selector, so the
     bare `.story-track { height: auto }` above loses to it even inside this query,
     and the section kept its 236vh of track with nothing in the bottom 200 of it. */
  .hero-story .story-track { height: auto; margin-top: 0; }
  .hero-story .story-sticky { position: static; height: auto; display: flex; flex-direction: column; }
  .hero-story .story-copy, .hero-story .story-text, .hero-story .story-stage { display: contents; }
  .hero-story .story-step {
    position: static; transform: none; opacity: 1; visibility: visible; margin: 0;
    pointer-events: auto;
  }
  /* STACKED SIZES ARE CAPPED IN PIXELS AND CENTRED. The bleed belongs to a phone
     screen and nothing else: `width: 116%` here meant that at 1179, where this
     layout now starts, the pictures rendered 1180px wide and a single phone filled
     the window. Capped and centred, then allowed to bleed into the gutter (and
     exactly as far as the gutter, 28px) only under 700px, where a picture really
     does want the whole screen. The last two comps are narrower crops than the
     first two, which is why their caps are smaller: it keeps all four DEVICES the
     same size, the same relation the desktop widths hold. */
  .hero-story .story-stage > .art-figure {
    position: static; transform: none; opacity: 1;
    width: min(100%, 560px); margin: 4px auto 0;
  }
  .hero-story .story-stage > .targets-figure,
  .hero-story .story-stage > .meallog-figure,
  .hero-story .story-stage > .group-figure { width: min(100%, 330px); }
  .hero-story .story-step[data-step="0"] { order: 1; }
  .hero-story .story-stage > .art-figure:nth-of-type(1) { order: 2; }
  .hero-story .story-step[data-step="1"] { order: 3; margin-top: 54px; }
  .hero-story .story-stage > .art-figure:nth-of-type(2) { order: 4; }
  .hero-story .story-step[data-step="2"] { order: 5; margin-top: 54px; }
  .hero-story .story-stage > .art-figure:nth-of-type(3) { order: 6; }
  .hero-story .story-step[data-step="3"] { order: 7; margin-top: 54px; }
  .hero-story .story-stage > .art-figure:nth-of-type(4) { order: 8; }
  .hero-story .story-step[data-step="4"] { order: 9; margin-top: 54px; }
  .hero-story .story-stage > .art-figure:nth-of-type(5) { order: 10; }
  .hero-story { padding: 40px 0 0; }
}
