/* Tend — tendcouples.com
   The app's own design system, at marketing scale. Every value below is taken
   from tend-app/src/constants/{theme,words}.ts rather than invented here, so a
   token can be grepped across both repos and mean the same thing. */

/* ---------------------------------------------------------------- 1. TOKENS */

:root {
  /* Surfaces, and the ORDER MATTERS. Content sits ABOVE its page: --surface is
     1.119:1 brighter than --bg, and only genuinely recessed things (--well,
     --tile-empty) go below it.

     This is the retheme's central correction and it is easy to undo by accident.
     The retired cream palette stacked the other way — cards were darker than the
     page, which is the paper/parchment order — and that inversion, plus warm
     brown inks, is what read dated. If you add a surface, decide which side of
     the page it is on before picking a value. */
  --bg: #F1F2F5;
  --surface: #FFFFFF;  /* raised: every card, entry and sheet body */
  --well: #E4E7ED;     /* recessed: inputs, tracks, unselected chips */
  /* The empty calendar tile, and ONLY that. Deeper than --well because it has
     to clear the two palest words: `steady` reads 1.083:1 against it and
     `secure` 1.106:1, so a logged day never looks unlogged. The separation is
     HUE, not lightness — `close` and `warm` are both darker than this tile. */
  --tile-empty: #D8DCE5;

  --ink: #171A20;        /* 17.4:1 on surface, 15.6:1 on page */
  --ink-mid: #474E5C;    /*  8.4:1 / 7.5:1 */
  --ink-muted: #5E6675;  /*  5.8:1 on surface, 5.2:1 on page, 4.7:1 on well */
  --ink-faint: #AAB1BE;  /*  1.9:1 — DECORATIVE ONLY. Never put text in this. */
  --border: rgba(23, 26, 32, 0.10);

  /* Accents are fills, not text. The -text variant is a different colour, not a
     shade: use the base for fills and icons, the -text one for any glyph or
     word. The exception is the display italic, which is large enough (>=24px,
     700) to clear AA large text on --coral at 3.46:1 against this page. */
  --coral: #D85A30;   --coral-text: #A63A16;   --coral-light: #FBE7DE;
  --purple: #534AB7;  --purple-text: #4B42AD;  --purple-light: #ECEBFB;
  --teal: #2E8574;    --teal-text: #216056;    --teal-light: #E2F1EE;
  --amber: #BA7517;   --amber-text: #85520A;   --amber-light: #FBEDD7;
  --banner-accent: #F6C878;

  /* Device chrome. The drawn phone bezel — the only colour here that is
     neither a surface, an ink, nor product signal. */
  --device: #0C0E13;

  /* The eight words. Semantic product data ported from words.ts: each fill's
     meaning is fixed, so a fill may never be repurposed as a UI accent. The
     spectrum is double-encoded, warm-to-cool AND bright-to-dark, so it survives
     colourblindness and a squint; preserve both encodings anywhere it appears. */
  --w-cl: #EFA33C;  --k-cl: #43290A;  /* close        */
  --w-wa: #F6C878;  --k-wa: #4A3312;  /* warm         */
  --w-se: #F9E4C3;  --k-se: #6B4E23;  /* secure       */
  --w-st: #E8E4DC;  --k-st: #56493B;  /* steady       */
  --w-of: #C9A3A9;  --k-of: #472A31;  /* off          */
  --w-di: #7E94AC;  --k-di: #F5F8FB;  /* distant      */
  --w-te: #55476A;  --k-te: #EFE9F6;  /* tense        */
  --w-re: #46B09E;  --k-re: #0B3A32;  /* reconnecting */

  /* `distant` carries a near-white ink in the app — 2.9:1 on its own fill. The
     calendar keeps that value exactly, because the grid is a role="img" picture
     of the app with a full text alternative and its day numbers are pixels
     rather than text. Where a word name is REAL text on its own fill (the word
     ladder), this darker ink is used instead: 4.7:1, AA clear. Same fill, two
     inks, decided by whether a screen reader can reach it. */
  --k-di-text: #1B2A3A;

  /* Radius. Named literally because the app's scale is numeric — semantic names
     would need a mapping table, and the table is what drifts. The scale is now
     four steps: 8, 12 and 16 were consolidated away in the retheme.

     Type follows the app's ramp {11 12 13 14 16 19 22 26 32}px, plus two display
     steps this surface adds because the app's top step is sized for a phone and
     a marketing headline is not: 40px (h2 max) and 64px (h1 max). Every clamp
     endpoint lands on one of those values. */
  --r-10: 10px;  /* small — day tiles, swatches */
  --r-14: 14px;  /* medium — ladder bands, the day card's tile */
  --r-18: 18px;  /* large — cards */
  --r-20: 20px;  /* large + 2 — the one card that carries a 48pt tile */
  --r-pill: 100px;

  /* 4px base, as the app's Spacing scale. */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-8: 48px;
  --pad: clamp(1.25rem, 4vw, 3.5rem);

  --serif: Lora, Georgia, 'Times New Roman', serif;
  --sans: 'DM Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* The bloom, ported from tend-app/src/lib/bloom-motion.ts. Tiles fill in date
     order; each day's marks land a beat after its own tile, so the glyphs
     cascade with the sweep rather than arriving as a second event. */
  --tile-stagger: 24ms;
  --tile-ms: 195ms;
  --mark-offset: 55ms;
  --mark-ms: 255ms;
  --trip-ms: 300ms;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Elevation. `--e-card` is deliberately two layers and deliberately faint: with
     --surface sitting 1.119:1 above the page the fill already separates the card,
     and the shadow's only job is to stop that step reading as a flat colour
     change. If you can point at the shadow, it is too strong. */
  --e-card: 0 1px 2px rgba(23, 26, 32, 0.04), 0 6px 16px rgba(23, 26, 32, 0.05);
  --e-floating: 0 6px 20px rgba(23, 26, 32, 0.10);
  --e-dramatic: 0 16px 32px rgba(23, 26, 32, 0.22);
}

/* ------------------------------------------------------- 2. RESET AND BASE */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 1rem/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* Clipped rather than pushed to left:-9999px. The off-screen trick leaves a real
   182px box in the layout, and some engines count it toward the document's
   scroll width — which showed up here as 35px of phantom horizontal scroll at
   one breakpoint and nowhere else. Clipping takes the box out entirely. */
.skip {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 20;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  padding: 0.7rem 1rem;
  background: var(--ink);
  color: var(--bg);
  border-radius: 0 0 var(--r-14) 0;
  font-weight: 500;
  text-decoration: none;
}
.skip:focus {
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
}

/* ------------------------------------------------------------------ 3. TYPE */

h1, h2, h3 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 700;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2rem, 6.4vw, 4rem);
  line-height: 1.04;
}

h2 {
  font-size: clamp(1.625rem, 4vw, 2.5rem);
  line-height: 1.1;
}

h3 {
  font-size: 1.1875rem;
  font-weight: 600;
  line-height: 1.3;
}

/* The App Store panels' signature: the sentence turns from upright black to
   coral italic at the phrase that carries the idea. One per heading. */
h1 em, h2 em {
  font-style: italic;
  color: var(--coral);
}

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(1rem, 1.6vw, 1.1875rem);
  line-height: 1.5;
  color: var(--ink-mid);
  text-wrap: pretty;
}

/* Lora italic. The app allows exactly one of these per screen — it is the voice
   of the product speaking, not a decorative treatment. Once per section here. */
.said {
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.1875rem, 1.7vw, 1.375rem);
  line-height: 1.45;
  color: var(--ink);
}

.section-label {
  font: 500 0.8125rem/1.3 var(--sans);
  color: var(--ink-muted);
}

.measure { max-width: 34rem; }
.measure p { color: var(--ink-mid); }

/* A word name inside running copy is a scrap of its own cloth. */
.w {
  font-style: normal;
  font-weight: 500;
  padding: 0.08em 0.4em 0.14em;
  border-radius: var(--r-10);
  background: var(--f);
  color: var(--k);
  white-space: nowrap;
}

/* ------------------------------------------------------------ 4. WORD FILLS */

/* One pair of rules per word, then a single gradient. When a day has no partner
   word --f2 is never declared and falls back to --f, so the gradient renders
   solid: the seam appears only where the two of them actually disagreed. */

[data-a="cl"], [data-w="cl"] { --f: var(--w-cl); --k: var(--k-cl); }
[data-a="wa"], [data-w="wa"] { --f: var(--w-wa); --k: var(--k-wa); }
[data-a="se"], [data-w="se"] { --f: var(--w-se); --k: var(--k-se); }
[data-a="st"], [data-w="st"] { --f: var(--w-st); --k: var(--k-st); }
[data-a="of"], [data-w="of"] { --f: var(--w-of); --k: var(--k-of); }
[data-a="di"], [data-w="di"] { --f: var(--w-di); --k: var(--k-di); }
[data-a="te"], [data-w="te"] { --f: var(--w-te); --k: var(--k-te); }
[data-a="re"], [data-w="re"] { --f: var(--w-re); --k: var(--k-re); }

[data-b="cl"] { --f2: var(--w-cl); }
[data-b="wa"] { --f2: var(--w-wa); }
[data-b="se"] { --f2: var(--w-se); }
[data-b="st"] { --f2: var(--w-st); }
[data-b="of"] { --f2: var(--w-of); }
[data-b="di"] { --f2: var(--w-di); }
[data-b="te"] { --f2: var(--w-te); }
[data-b="re"] { --f2: var(--w-re); }

/* Real text on a fill uses the AA-clear ink; see --k-di-text. */
.w[data-w="di"] { --k: var(--k-di-text); }

/* -------------------------------------------------------------- 5. CALENDAR */

/* A picture of the app's month view. Cells are placed explicitly rather than
   auto-flowing, because the trip outlines are explicitly placed and grid
   auto-placement would otherwise flow the cells around them. */

.cal {
  --gutter: 4px;
  --tile-r: var(--r-10);
  --dnum: 12px;
  --mk: 11px;
  margin: 0;
}

.cal--hero {
  --gutter: clamp(4px, 0.75vw, 8px);
  --tile-r: clamp(8px, 1.15vw, 13px);
  --dnum: clamp(11px, 1.05vw, 14px);
  --mk: clamp(11px, 1.05vw, 14px);
}

.cal-month {
  font-family: var(--serif);
  font-size: 1.1875rem;
  color: var(--ink-mid);
  margin-bottom: var(--s-3);
}

.cal-head,
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--gutter);
}

.cal-head {
  margin-bottom: var(--s-2);
}

.cal-head span {
  text-align: center;
  font: 500 clamp(11px, 0.85vw, 12px)/1.3 var(--sans);
  letter-spacing: 0.5px;
  color: var(--ink-muted);
}

.cal-grid {
  grid-template-rows: repeat(var(--rows, 5), auto);
  position: relative;
}

/* Resting opacity lives on the cell, animated opacity on the tile, so the two
   multiply: a spill tile blooms 0 -> 0.55 rather than 0 -> 1. The generator
   emits only one of these classes — future wins over adjacent-month and the two
   dims never compound, exactly as the app's ternary does it. */
.cell { position: relative; aspect-ratio: 1; }
.cell.is-spill { opacity: 0.55; }
.cell.is-future { opacity: 0.5; }

.tile {
  position: absolute;
  inset: 0;
  border-radius: var(--tile-r);
  background: var(--tile-empty);
}

.cell[data-a] .tile {
  background: linear-gradient(
    135deg,
    var(--f) 0,
    var(--f) 49.75%,
    var(--f2, var(--f)) 50.25%,
    var(--f2, var(--f)) 100%
  );
}

.dnum {
  position: absolute;
  top: 0.42em;
  left: 0.5em;
  font: 500 var(--dnum)/1 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--k, var(--ink-faint));
}
.cell.is-empty.is-today .dnum { color: var(--ink); }

.marks {
  position: absolute;
  right: 0.28em;
  bottom: 0.18em;
  display: flex;
  gap: 1px;
  align-items: center;
  font-size: var(--mk);
  line-height: 1;
}

/* The cream halo is what keeps ⚑ legible on `tense`. */
.mk {
  font-style: normal;
  text-shadow: 0 0 2px var(--bg), 0 0 2px var(--bg);
}
.mk-grat { color: var(--coral); }
.mk-mom  { color: var(--purple); }
.mk-flag { color: var(--ink); }
.mk-res  { color: var(--ink); }
.mk-mile { color: var(--amber); }
.mk-plan { color: var(--teal); }
.mk-more { color: var(--ink-muted); font-size: 0.8em; }

.ring {
  position: absolute;
  inset: 0;
  border: 2px solid var(--ink);
  border-radius: var(--tile-r);
  pointer-events: none;
}

/* A trip is not a badge. It is an outline spanning a run of days, broken into
   one segment per week row; where it wraps, the vertical edge is omitted so it
   reads as continuing rather than as two separate trips. */
.trip {
  pointer-events: none;
  z-index: 2;
  border-block: 1.5px solid rgba(23, 26, 32, 0.55);
}
.trip.is-start {
  border-left: 1.5px solid rgba(23, 26, 32, 0.55);
  border-start-start-radius: var(--tile-r);
  border-end-start-radius: var(--tile-r);
}
.trip.is-end {
  border-right: 1.5px solid rgba(23, 26, 32, 0.55);
  border-start-end-radius: var(--tile-r);
  border-end-end-radius: var(--tile-r);
}

.cal-note {
  margin-top: var(--s-4);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-muted);
}

/* ---------------------------------------------------------------- 6. BLOOM */

/* Nothing lives in a `to` block. Keyframes carry the `from` state and any
   intermediate deviation; every final value is the element's ordinary declared
   style. That is what makes `animation: none` under reduced motion provably the
   finished state rather than a duration trick that happens to land there — and
   why a browser that never runs the animation still shows a whole calendar. */

.tile {
  animation: tile-bloom var(--tile-ms) var(--ease) backwards;
  animation-delay: calc(var(--i, 0) * var(--tile-stagger));
}
@keyframes tile-bloom {
  from { opacity: 0; transform: scale(0.9); }
}

.marks {
  animation: mark-land var(--mark-ms) var(--ease) backwards;
  animation-delay: calc(var(--i, 0) * var(--tile-stagger) + var(--mark-offset));
}
@keyframes mark-land {
  from { opacity: 0; transform: translateY(-4px); }
  35%  { opacity: 1; }
  70%  { transform: translateY(1px); }
}

.ring {
  animation: ring-in var(--tile-ms) var(--ease) backwards;
  animation-delay: calc(var(--i, 0) * var(--tile-stagger));
}
@keyframes ring-in {
  from { opacity: 0; }
}

/* clip-path rather than scaleX: scaling would stretch the 1.5px borders and
   smear the rounded end caps as the outline draws. */
.trip {
  animation: trip-draw var(--trip-ms) linear backwards;
  animation-delay: calc(var(--i, 0) * var(--tile-stagger) + var(--mark-offset));
}
@keyframes trip-draw {
  from { clip-path: inset(0 100% 0 0); }
}

/* ------------------------------------------------------------ 7. PRIMITIVES */

/* THE PAGE NARROWS AS IT MOVES TOWARD THE ASK: 78rem for the three sections
   that show the product, 46rem at the fire, 34rem at the close. Every section
   used to be 78rem with identical padding and a near-50/50 split, which is one
   topology and one interval repeated until nothing led. Width is now the page's
   primary rhythm instrument, and `.band` and `.close` below re-time the padding
   to match — generous at the pause, tight at the ask. */
.section {
  max-width: 78rem;
  margin-inline: auto;
  padding: clamp(3.5rem, 8vw, 7rem) var(--pad);
}

/* More space above a heading than below it, once, everywhere. */
.section-head { margin-bottom: clamp(2.25rem, 4.5vw, 3.5rem); }
.centred {
  max-width: 44rem;
  margin-inline: auto;
  text-align: center;
}
h2 + .lede,
h2 + p { margin-top: 0.9em; }

/* Body copy that belongs to the artifact above it rather than to a copy column
   facing it. Proximity does the grouping, so no container has to. */
.aside {
  margin: 0;
  max-width: 30rem;
  color: var(--ink-mid);
  font-size: 0.9375rem;
  line-height: 1.6;
}

.note {
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-muted);
}
.note-center { text-align: center; margin-top: var(--s-6); }

.stores {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
}
.stores-center { justify-content: center; }

/* Both badges are official art and neither may be redrawn or recoloured. Apple
   and Google each normalise on HEIGHT, which leaves the Apple badge about 13%
   narrower than Google's — correct per both guidelines, but the pair reads as
   mismatched. Giving each the same box and centring the art inside fixes the
   footprint without touching either asset. */
.store {
  display: grid;
  place-items: center;
  width: 9.5rem;
  height: 2.75rem;
  border-radius: var(--r-10);
  transition: opacity 0.2s;
}
.store:hover { opacity: 0.86; }
.store img { height: 100%; width: auto; }

.pill {
  display: inline-block;
  padding: 0.34em 0.85em;
  border-radius: var(--r-pill);
  font: 500 0.8125rem/1.3 var(--sans);
  color: var(--bg);
}
.pill-coral { background: var(--coral); }
.pill-teal { background: var(--teal); }

/* --------------------------------------------------------------- 8. THE HERO */

/* The bottom padding used to be 0 because a field of loose rotated tiles filled
   the seam into the page. They were decoration — the calendar above already
   stands for every day they could have stood for — so the hero closes on its
   own whitespace now, and `overflow: hidden` went with them. */
.hero {
  padding: clamp(1.75rem, 4vw, 3.5rem) var(--pad) clamp(2rem, 5vw, 4rem);
}

.hero-inner {
  max-width: 78rem;
  margin-inline: auto;
  display: grid;
  gap: clamp(2.25rem, 5vw, 4.5rem);
  align-items: center;
}
/* The calendar leads. At 1fr/1.02fr the two halves were the same size, so the
   proof and the pitch argued for attention; the artifact should win the first
   viewport outright. */
@media (min-width: 1000px) {
  /* Sized by the phone, not by a share of the row: a fractional column left a
     320px device floating in 694px of nothing. The copy takes what is left. */
  .hero-inner { grid-template-columns: minmax(0, 1fr) auto; }
  /* Bigger here than anywhere else on the page. This is the first viewport's
     whole proof that there is an app to install. */
  .hero-cal .phone { --pw: clamp(17rem, 24vw, 22rem); }
}

.hero-copy { max-width: 34rem; }
.hero-copy h1 { margin-bottom: 0.42em; }

@media (max-width: 999px) {
  .hero-inner { justify-items: center; text-align: center; }
  .hero-copy { margin-inline: auto; }
  .stores { justify-content: center; }
}

/* ------------------------------------------------------ 9. THE PRIVATE PAIR */

/* Two people choosing at the same moment, neither able to see the other. The
   section stages the mechanic rather than describing it, which is why the two
   devices sit at equal weight either side of a seam: any arrangement where one
   leads would make it a demo of one person's screen instead of a picture of
   two private choices.

   Both phones hold real captures of the picker — one with a word chosen and
   grown, one untouched — so the difference between them IS the state change. */
.pair {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: clamp(0.75rem, 2.5vw, 2rem);
  max-width: 46rem;
  margin-inline: auto;
}

.pair-side {
  margin: 0;
  display: grid;
  justify-items: center;
  gap: var(--s-3);
}
.pair-side .phone { --pw: clamp(8.5rem, 17vw, 13rem); }

/* Two words, doing a job no copy could do more briefly: saying which screen
   belongs to whom. Not a caption on a picture — a label on a column. */
.pair-who {
  margin: 0;
  font: 500 0.8125rem/1 var(--sans);
  color: var(--ink-muted);
}

/* The seam is the mechanic, so it is drawn rather than left as a gap: a
   hairline running the height of the devices with the seal sitting on it. */
.pair-seam {
  position: relative;
  align-self: stretch;
  display: grid;
  place-items: center;
  min-height: 100%;
}
.pair-seam::before {
  content: '';
  position: absolute;
  inset-block: 12%;
  width: 1px;
  background: linear-gradient(var(--border), var(--border));
}
.pair-seal {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: var(--e-card);
  color: var(--ink-muted);
}
.pair-seal svg { width: 1.125rem; height: 1.125rem; }

/* Under 620px the seam stops being a divider between columns and becomes a
   rule between rows, which is the same idea rotated rather than a fallback. */
@media (max-width: 619px) {
  .pair { grid-template-columns: minmax(0, 1fr); gap: var(--s-4); }
  .pair-seam { min-height: 0; padding: var(--s-1) 0; }
  .pair-seam::before {
    inset-block: auto;
    inset-inline: 12%;
    width: auto;
    height: 1px;
  }
}

/* The day the two choices produce. Centred under the pair, because it belongs
   to both of them rather than to either column. */
.today-stage {
  max-width: 30rem;
  margin: clamp(2.5rem, 5vw, 4rem) auto 0;
  text-align: center;
}
.today-stage .today-card { text-align: left; }


/* -------------------------------------------------------- 10. THE DAY CARD */

/* The day the hold produces. No border. The card is separated by its fill sitting above the page plus
   --e-card; outlining it on top of that reads as boxing everything in. */
.today-card {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3);
  border-radius: var(--r-20);
  background: var(--surface);
  box-shadow: var(--e-card);
}

.today-tile {
  width: 48px;
  height: 48px;
  flex: none;
  border-radius: var(--r-14);
  background: linear-gradient(
    135deg,
    var(--f) 0,
    var(--f) 49.75%,
    var(--f2, var(--f)) 50.25%,
    var(--f2, var(--f)) 100%
  );
}
/* A recessed well, not a dashed outline.
   The dashed version reasoned that an outline reads as "not yet" where a filled
   neutral reads as a colour you might have picked. That held while the fill
   would have been beige — one more warm swatch among eight. It stopped holding
   when the chrome went cool: --tile-empty is a cool neutral where all eight
   words are saturated, so nothing on the grid reads as unpicked-but-coloured.
   The separation is hue, not lightness — `close` and `warm` are both darker
   than this tile. */
.today-tile.is-sealed { background: var(--tile-empty); }

.today-text { min-width: 0; flex: 1; }
.today-text .section-label { margin-bottom: 0.35rem; }
.today-text .said { margin: 0; }

.reveal-btn {
  margin-top: var(--s-4);
  padding: 0.7rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: none;
  color: var(--ink-mid);
  font: 500 0.875rem/1 var(--sans);
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s;
}
.reveal-btn:hover { border-color: var(--ink-muted); color: var(--ink); }

/* ----------------------------------------------------------- 11. THE ENTRIES */

.entries {
  display: grid;
  gap: var(--s-3);
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

/* Five entries into a six-column field: two wide, then three narrow. An auto-fit
   track lands them 4 + 1 with an orphan, and five cards at identical width read
   as a wall of boxes rather than a collection — the flag and the gratitude carry
   the most text, so they take the room and set up a change of scale beneath. */
@media (min-width: 900px) {
  .entries { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .entry { grid-column: span 2; }
  .entry:nth-child(-n + 2) { grid-column: span 3; }
}

.entry {
  padding: var(--s-4);
  border-radius: var(--r-18);
  background: var(--surface);
  box-shadow: var(--e-card);
}

.entry-kind {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 0.6rem;
  font: 500 0.8125rem/1.3 var(--sans);
  color: var(--ink-muted);
}
.entry-coral { color: var(--coral-text); }
.entry-purple { color: var(--purple-text); }
.entry-teal { color: var(--teal-text); }
.entry-amber { color: var(--amber-text); }
.entry-kind .mk { font-size: 1rem; text-shadow: none; }

.entry-body {
  font-family: var(--serif);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
}
/* The flag's I-statement scaffolding. The app italicises the three connectives
   because they are structure handed to the writer, not emphasis. */
.entry-body i { font-style: italic; color: var(--ink-mid); }

/* -------------------------------------------------------- 12. THE DARK BAND */

/* The app ships one dark surface and one only: the wellness hearth. This page
   gets exactly one too. Redefining the surface tokens locally means every
   component inside — the mark halos included — picks up the night ground.

   These stay WARM while the rest of the page went cool-neutral, and that is
   deliberate rather than a spot the retheme missed: this is firelight, and a
   cool-grey dark would read as a different app's empty state. The contrast
   between warm-dark here and cool chrome elsewhere is the intended effect.
   Note --surface is ABOVE the page here too, and --well below it — the same
   relationship as in light, which is the point of the names. */
.band {
  /* The hearth glow is an absolutely-positioned child with negative insets, and
     those still count toward the document's scroll width — it was adding up to
     36px of horizontal scroll at mid widths. `clip` confines it to the section
     without creating a scroll container, and the glow sits nowhere near the
     section edge, so nothing visible is lost. */
  overflow: clip;
  --bg: #191410;
  --surface: #2A2219;
  --well: #100C09;
  --ink: #F1EAD9;
  --ink-mid: #CDBFA9;
  --ink-muted: #9C8D76;
  --ink-faint: #82745F;
  --border: rgba(241, 234, 217, 0.14);
  --amber-text: #E8B368;
  /* Firelight, sampled from the hearth artwork itself rather than picked: the
     Lottie renders these two, so the glow spilling from the device is the same
     light as the fire inside it. Scoped to the band; there is no firelight
     anywhere else on the page. */
  --fire-glow: #E8B368;
  --fire-core: #D94945;
  max-width: none;
  background: var(--bg);
  color: var(--ink);
  /* The most generous interval on the page. Three full-width sections, then the
     ground goes dark and the measure halves — the pause before the ask. */
  padding-block: clamp(4.5rem, 10vw, 9rem);
}
/* 46rem against the 78rem above it. The width IS the change of pace; a second
   two-column split here would have been the page's fourth. */
.band-inner {
  max-width: 46rem;
  margin-inline: auto;
}
.band p { color: var(--ink-mid); }
/* Coral goes muddy on the night ground; the app's own banner accent carries the
   turn here instead. Same job, legible material. */
.band h2 em { color: var(--amber-text); }

/* The five hearth states were a row of pills here. They are app vocabulary a
   visitor has no use for before installing, and the quote does the same work
   with feeling instead of a taxonomy. Amber stays reserved for the hearth. */
.hearth {
  margin: var(--s-6) 0 0;
  padding-left: var(--s-4);
  border-left: 2px solid rgba(232, 179, 104, 0.4);
}
.hearth cite {
  display: block;
  margin-top: var(--s-3);
  font: 400 0.8125rem/1.4 var(--sans);
  font-style: normal;
  color: var(--ink-muted);
}

/* -------------------------------------------------- 13. THE CLOSE AND FOOTER */

/* The narrowest measure on the page and the tightest interval. After the fire's
   long exhale the ask arrives quickly, which is the whole reason the widths
   step down rather than holding at 78rem to the end. */
.close {
  text-align: center;
  padding-block: clamp(3rem, 6vw, 5rem);
}
.close .measure { margin-inline: auto; }

footer {
  padding: clamp(2.5rem, 5vw, 4rem) var(--pad) clamp(2.5rem, 5vw, 3.5rem);
  border-top: 1px solid var(--border);
  text-align: center;
}
.wordmark {
  margin: 0;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 500;
  font-size: 1.625rem;
  letter-spacing: -0.01em;
}
.dot { color: var(--coral); font-style: normal; }
.tagline {
  margin: 0.35rem 0 var(--s-5);
  color: var(--ink-muted);
  font-size: 0.875rem;
}
.legal { display: flex; justify-content: center; gap: var(--s-5); flex-wrap: wrap; }
.legal a {
  font-size: 0.875rem;
  color: var(--ink-muted);
  text-decoration-color: var(--border);
  text-underline-offset: 3px;
}
.legal a:hover { color: var(--ink); }

/* A real screenshot inside a device. The capture is 1206x2622 and the frame is
   1080x2340 — 0.3% apart, so `cover` crops nothing anyone can see. */
.screenshot {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* ------------------------------------------------------------- 24. SOCIALS */

.socials {
  display: flex;
  justify-content: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}
.social {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--r-pill);
  color: var(--ink-muted);
  background: var(--surface);
  box-shadow: var(--e-card);
  transition: color 0.2s var(--ease), transform 0.2s var(--ease);
}
.social svg { width: 1.125rem; height: 1.125rem; }
.social:hover { color: var(--ink); transform: translateY(-1px); }

/* ----------------------------------------------------------- 14. RESPONSIVE */

@media (max-width: 620px) {
  .entries { grid-template-columns: minmax(0, 1fr); }
  .legal { gap: var(--s-3); }
}

/* ------------------------------------------------------ 15. REDUCED MOTION */

/* The calendar opts out explicitly rather than riding a global duration reset.
   Because no keyframe above carries a final value, `animation: none` renders
   the element's ordinary declared style — which is, by construction, the
   finished state. A whole calendar, instantly, with no flash of an empty grid. */
@media (prefers-reduced-motion: reduce) {
  .tile,
  .marks,
  .ring,
  .trip { animation: none; }

  *, *::before, *::after { transition-duration: 0.01ms !important; }
}

/* --------------------------------------------------------------- 18. THE PHONE */

/* A drawn iPhone, not a photograph of one. The point is not realism — it is that
   a visitor who has read nothing still knows within a second that this is an app
   they can install, which a bare grid on a page never says on its own.
   What sits inside is live markup at the size the app actually draws it, so it
   stays sharp at any density and animates like the real thing. */

.phone {
  --pw: clamp(15rem, 25vw, 20rem);
  position: relative;
  width: var(--pw);
  aspect-ratio: 1080 / 2340;
  padding: calc(var(--pw) * 0.03);
  border-radius: calc(var(--pw) * 0.15);
  background: var(--device);
  /* A hairline of lighter metal on the rim, then the drop. Without the inset the
     bezel reads as a flat black rectangle rather than as an edge. */
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.14),
    0 2px 4px rgba(23, 26, 32, 0.10),
    0 24px 48px -20px rgba(23, 26, 32, 0.45);
  flex: none;
}

.phone-screen {
  position: relative;
  height: 100%;
  overflow: hidden;
  border-radius: calc(var(--pw) * 0.125);
  background: var(--bg);
  display: flex;
  flex-direction: column;
}

/* The island. Sits over the screen, so it is a child of the frame rather than
   of the content it covers. */
.phone::after {
  content: '';
  position: absolute;
  z-index: 3;
  top: calc(var(--pw) * 0.055);
  left: 50%;
  transform: translateX(-50%);
  width: calc(var(--pw) * 0.26);
  height: calc(var(--pw) * 0.075);
  border-radius: var(--r-pill);
  background: var(--device);
}

/* Status bar. Time and the three indicators, drawn rather than lettered, because
   a real-looking status bar with invented carrier text is a detail nobody asked
   for and one more thing to get wrong. */
.phone-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(var(--pw) * 0.055) calc(var(--pw) * 0.075) calc(var(--pw) * 0.02);
  font: 500 calc(var(--pw) * 0.045) / 1 var(--sans);
  color: var(--ink);
  flex: none;
}
.phone-status i {
  display: inline-block;
  width: calc(var(--pw) * 0.13);
  height: calc(var(--pw) * 0.032);
  border-radius: var(--r-pill);
  background: var(--ink);
  opacity: 0.85;
}

.phone-body {
  flex: 1;
  min-height: 0;
  padding: 0 calc(var(--pw) * 0.055) calc(var(--pw) * 0.055);
  display: flex;
  flex-direction: column;
  gap: calc(var(--pw) * 0.035);
}

/* The app's own home chrome, at phone scale. Every value here is a fraction of
   --pw rather than a fixed size, so one variable moves the whole screen. */
.app-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2);
}
.app-title {
  margin: 0;
  font-family: var(--serif);
  font-size: calc(var(--pw) * 0.075);
  line-height: 1.2;
  letter-spacing: -0.01em;
}
.app-title span { color: var(--coral); }
.app-month {
  font: 500 calc(var(--pw) * 0.042) / 1 var(--sans);
  color: var(--ink-muted);
  white-space: nowrap;
}

.app-chips { display: flex; gap: calc(var(--pw) * 0.028); }
.app-chip {
  flex: 1;
  display: flex;
  align-items: center;
  gap: calc(var(--pw) * 0.028);
  padding: calc(var(--pw) * 0.035) calc(var(--pw) * 0.045);
  border-radius: var(--r-14);
  background: var(--surface);
  box-shadow: var(--e-card);
  font: 500 calc(var(--pw) * 0.04) / 1.25 var(--sans);
  color: var(--ink-muted);
  min-width: 0;
}
.app-chip b {
  font-family: var(--serif);
  font-weight: 600;
  font-size: calc(var(--pw) * 0.062);
  color: var(--amber-text);
}
.app-chip .flame { color: var(--amber); font-size: calc(var(--pw) * 0.055); }
.app-chip span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The app's TodayCard, shrunk. Same anatomy as the full-size one further down
   the page: tile, two lines, circled arrow. */
.app-today {
  display: flex;
  align-items: center;
  gap: calc(var(--pw) * 0.035);
  padding: calc(var(--pw) * 0.038);
  border-radius: var(--r-18);
  background: var(--surface);
  box-shadow: var(--e-card);
}
.app-today .today-tile {
  width: calc(var(--pw) * 0.115);
  height: calc(var(--pw) * 0.115);
  border-radius: var(--r-10);
}
.app-today-text { flex: 1; min-width: 0; }
.app-today-text b {
  display: block;
  font: 500 calc(var(--pw) * 0.036) / 1.3 var(--sans);
  color: var(--ink-muted);
  margin-bottom: 1px;
}
.app-today-text i {
  font-family: var(--serif);
  font-style: italic;
  font-size: calc(var(--pw) * 0.047);
  line-height: 1.3;
  color: var(--ink);
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-arrow {
  flex: none;
  width: calc(var(--pw) * 0.08);
  height: calc(var(--pw) * 0.08);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--ink);
  color: var(--bg);
  font-size: calc(var(--pw) * 0.045);
  line-height: 1;
}

/* The calendar at the size the app draws it. --tile-r stays on the scale rather
   than scaling with the phone: a day tile is 10px in the app and looks wrong
   rounded any harder, even at 40px across. */
.cal--app {
  --gutter: calc(var(--pw) * 0.012);
  --tile-r: var(--r-10);
  --dnum: calc(var(--pw) * 0.036);
  --mk: calc(var(--pw) * 0.036);
  margin-top: calc(var(--pw) * 0.01);
}
.cal--app .cal-month { display: none; }
.cal--app .cal-head {
  margin-bottom: calc(var(--pw) * 0.018);
}
.cal--app .cal-head span {
  font-size: calc(var(--pw) * 0.034);
  letter-spacing: 0.4px;
}

/* The floating tab bar, the last thing that says "app" rather than "web page". */
.app-tabs {
  margin-top: auto;
  display: flex;
  gap: calc(var(--pw) * 0.02);
  padding: calc(var(--pw) * 0.022);
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: var(--e-floating);
}
.app-tab {
  flex: 1;
  text-align: center;
  padding: calc(var(--pw) * 0.032) 0;
  border-radius: var(--r-pill);
  font: 500 calc(var(--pw) * 0.038) / 1 var(--sans);
  color: var(--ink-muted);
}
.app-tab.is-on { background: var(--ink); color: var(--bg); }

/* ------------------------------------------------------- 19. THE RECAP SCREEN */

/* The recap phone's contents. Cards on the app's own surface, scrolled to where
   the interesting part is rather than starting at the top of the screen. */
.app-recap { gap: calc(var(--pw) * 0.03); overflow: hidden; }

.rc {
  padding: calc(var(--pw) * 0.045);
  border-radius: var(--r-18);
  background: var(--surface);
  box-shadow: var(--e-card);
  flex: none;
}
.rc-label {
  font: 500 calc(var(--pw) * 0.038) / 1.3 var(--sans);
  color: var(--ink-muted);
  margin: 0 0 calc(var(--pw) * 0.028);
}
.rc-body {
  margin: 0;
  font-family: var(--serif);
  font-style: italic;
  font-size: calc(var(--pw) * 0.044);
  line-height: 1.5;
  color: var(--ink);
}
.rc-stat {
  display: flex;
  align-items: baseline;
  gap: calc(var(--pw) * 0.03);
  margin: 0 0 calc(var(--pw) * 0.03);
}
.rc-stat b {
  font-family: var(--serif);
  font-weight: 600;
  font-size: calc(var(--pw) * 0.105);
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.rc-stat span {
  font: 400 calc(var(--pw) * 0.038) / 1.35 var(--sans);
  color: var(--ink-muted);
}

/* Track and fill, matching the app: the well says how much of the week there
   was to match, the purple says how much matched. */
.rc-bars {
  display: flex;
  align-items: stretch;
  gap: calc(var(--pw) * 0.018);
  height: calc(var(--pw) * 0.16);
}
.rc-bars i {
  flex: 1;
  display: flex;
  align-items: flex-end;
  border-radius: var(--r-10);
  background: var(--well);
  overflow: hidden;
}
.rc-bars i::after {
  content: '';
  display: block;
  width: 100%;
  height: var(--h);
  border-radius: var(--r-10);
  background: var(--purple);
}

.rc-shape { display: flex; justify-content: space-between; gap: 2px; }
.rc-shape span {
  display: grid;
  justify-items: center;
  gap: calc(var(--pw) * 0.022);
  font: 500 calc(var(--pw) * 0.034) / 1 var(--sans);
  color: var(--ink-muted);
}
.rc-shape i {
  width: calc(var(--pw) * 0.032);
  height: calc(var(--pw) * 0.032);
  border-radius: 50%;
  background: var(--w-te);
  opacity: var(--o);
}

.rc .cal--sm { --gutter: calc(var(--pw) * 0.014); --tile-r: var(--r-10); }
.rc .cal--sm .cal-head { margin-bottom: calc(var(--pw) * 0.014); }
.rc .cal--sm .cal-head span { font-size: calc(var(--pw) * 0.032); }

/* ------------------------------------------------------------ 20. THE HEARTH */

/* The commissioned Lottie from the app, played on the site. 30KB over the wire
   once Netlify has brotli'd it, and the player is fetched only when the section
   is close to view — nothing above the fold waits on either. */
.hearth-phone { --pw: clamp(13rem, 22vw, 17rem); }
/* The glow is a negative-z child, so its parent needs its own stacking
   context or it renders behind the section rather than behind the frame. */
.band .showcase-phone { isolation: isolate; }

/* Firelight spilling out of the device. A near-black phone on a near-black band
   had almost nothing to separate it from the ground; this reads as the screen
   lighting the room rather than as a drop shadow, which is also why it is warm
   and centred on the fire rather than offset below the phone.
   Sits behind the frame (negative z-index) so it never washes the screen. */
.hearth-phone::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: -18% -34%;
  background:
    radial-gradient(50% 42% at 50% 56%, color-mix(in srgb, var(--fire-glow) 30%, transparent), transparent 70%),
    radial-gradient(38% 30% at 50% 60%, color-mix(in srgb, var(--fire-core) 22%, transparent), transparent 72%);
  filter: blur(28px);
  pointer-events: none;
}
/* #1C1C1C, not the band's #191410. The Lottie paints its own background solid
   at this value, and it is what the app's wellness screen actually renders —
   measured off a device capture, where theme.ts's Colors.dark.background says
   #191410. Matching the artwork rather than the token is what stops a hairline
   seam showing at the poster's edge before the animation swaps in. */
.hearth-phone .phone-screen { background: #1C1C1C; }

.hearth-stage {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}
.hearth-stage svg,
.hearth-stage canvas,
.hearth-stage img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The poster is the resting state: it ships in the markup and the animation
   replaces it only once the player has actually loaded. That way no-JS,
   reduced-motion and a failed CDN all land on a drawn scene rather than a hole. */
.hearth-poster { z-index: 1; }
.hearth-phone.is-playing .hearth-poster { display: none; }

.hearth-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--pw) * 0.075);
  z-index: 2;
  text-align: center;
  padding: 0 calc(var(--pw) * 0.08);
}
.hearth-caption b {
  display: block;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: calc(var(--pw) * 0.075);
  color: #F1EAD9;
}
.hearth-caption span {
  display: block;
  margin-top: calc(var(--pw) * 0.02);
  font: 400 calc(var(--pw) * 0.042) / 1.35 var(--sans);
  color: #CDBFA9;
}

/* ------------------------------------------------- 21. SHOWCASE AND BENEFITS */

/* A phone beside the reason to care about what is on it. The phone leads on
   desktop because the section's job is to prove there is an app; the words
   lead on mobile, where the phone would otherwise push them below the fold. */
.showcase {
  display: grid;
  gap: clamp(2rem, 4.5vw, 4rem);
  align-items: center;
  justify-items: center;
}
@media (min-width: 900px) {
  .showcase {
    grid-template-columns: auto minmax(0, 1fr);
    justify-items: start;
  }
}

.showcase-phone {
  margin: 0;
  display: grid;
  justify-items: center;
  gap: var(--s-4);
}
.showcase-phone .note { text-align: center; max-width: 20rem; }

.benefits {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.25rem);
  max-width: 30rem;
}
.benefits h3 { margin-bottom: 0.4rem; }
.benefits p {
  margin: 0;
  color: var(--ink-mid);
  line-height: 1.55;
}

/* The store row directly under the payoff. Its own breathing room, because it
   is a second ask rather than a footnote to the section above it. */
.section .stores-center { margin-top: clamp(2.5rem, 5vw, 4rem); }

/* --------------------------------------------------- 22. THE BAND, TWO UP */

/* The fire section stops being one tight column now that it carries the
   animation. The copy keeps its measure; the phone sits beside it. */
.band-inner {
  display: grid;
  gap: clamp(2rem, 4.5vw, 4rem);
  align-items: center;
  justify-items: center;
}
.band-copy { max-width: 34rem; }
@media (min-width: 900px) {
  .band-inner {
    max-width: 68rem;
    grid-template-columns: minmax(0, 1fr) auto;
    justify-items: start;
  }
}

/* The bezel is near-black in both palettes, so on the night ground it needs its
   rim highlight lifted or the phone dissolves into the section. */
.band .phone {
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.22),
    0 24px 48px -20px rgba(0, 0, 0, 0.6);
}

/* ----------------------------------------------------- 23. HERO, WITH PHONE */

/* The hero column is now the width of a phone rather than of a calendar. */
.hero-cal {
  width: auto;
  margin: 0;
  display: grid;
  justify-items: center;
  gap: var(--s-4);
}
.hero-cal .cal-note {
  margin-top: 0;
  max-width: 22rem;
  text-align: center;
}

@media (max-width: 899px) {
  .showcase-phone { order: -1; }
  .band-copy { text-align: left; }
}
