/* ============================================================================
   SURF AID KIT — LANDING PAGE

   Layout, depth and motion. Colour and type come from tokens.css: every colour
   is a ROLE variable (--sak-foreground, --sak-primary, --sak-border …), never a
   ramp stop, so a section's data-scheme re-points the whole thing. Type rides
   the ready-made classes (.text-4xl, .text-base, .text-sm.uppercase, .text-xs).

   Four sections: hero (navy) · what it treats + what's inside, merged (bone) ·
   the kit (navy) · close (bone).

   tokens.css defines type + colour only — spacing, radius, shadow and motion
   are authored here.
   ============================================================================ */

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

:root {
  --pad-x: 22px;
  --gap: 16px;
  --measure: 1200px;

  --r-sm: 12px;
  --r-md: 18px;
  --r-lg: 28px;
  --r-pill: 999px;

  /* Elevation — warm, ink-tinted, layered. Reserved for product imagery now;
     the content surfaces are drawn with hairlines instead. */
  --shadow-sm: 0 1px 2px rgba(25,26,26,.06), 0 2px 8px rgba(25,26,26,.05);
  --shadow-md: 0 2px 6px rgba(25,26,26,.06), 0 12px 32px rgba(25,26,26,.10);
  --shadow-lg: 0 4px 12px rgba(25,26,26,.08), 0 28px 64px rgba(8,32,55,.18);

  --ease: cubic-bezier(.22,1,.36,1);
}

/* ---------------------------------------------------------------------------
   GLASS SURFACES

   One recipe for every panel and chip, so the input field, the image chips, the
   tiles and the price card all read as the same material: a graded translucent
   fill (never flat), a hairline in the same translucent tone so every edge
   carries identical weight, a top inset highlight where the light catches, and
   a soft drop to lift it off the ground. Looking through water, not a grey box.

   The values are scheme-scoped rather than global because "translucent" means
   the opposite tone on each ground.
   --------------------------------------------------------------------------- */

[data-scheme="navy"], [data-scheme="ink"] {
  /* Flat translucent fills — the gradient is on the stroke, not the surface. */
  --glass-fill: color-mix(in srgb, var(--sak-bone) 7%, transparent);
  /* Only the 1px stroke is graded: brighter along the top, falling away down
     the sides. That reads as an edge catching light, where the old inset
     highlight read as a thick decorative cap. */
  --glass-edge: linear-gradient(to bottom,
                color-mix(in srgb, var(--sak-bone) 30%, transparent) 0%,
                color-mix(in srgb, var(--sak-bone) 15%, transparent) 34%,
                color-mix(in srgb, var(--sak-bone) 8%, transparent) 100%);
  --glass-drop: 0 10px 28px rgba(2, 13, 27, .22);
}

[data-scheme="bone"] {
  --glass-fill: color-mix(in srgb, var(--sak-foreground) 4%, transparent);
  /* On the warm ground the stroke reads by getting darker at the top: a white
     highlight on bone just glowed, and beside the red button it competed. */
  --glass-edge: linear-gradient(to bottom,
                color-mix(in srgb, var(--sak-foreground) 20%, transparent) 0%,
                color-mix(in srgb, var(--sak-foreground) 13%, transparent) 40%,
                color-mix(in srgb, var(--sak-foreground) 9%, transparent) 100%);
  --glass-drop: 0 6px 18px rgba(25, 26, 26, .05);
}

/* The gradient stroke uses the two-background technique: a flat fill clipped to
   the padding box, the edge gradient clipped to the border box, so the 1px
   transparent border shows the gradient beneath. `border-image` does this in one
   line but cancels border-radius, which every surface here needs.

   linear-gradient(c, c) is how a flat colour becomes a background LAYER — a
   plain background-color can't be clipped separately from the border box. */
.glass {
  border: 1px solid transparent;
  border-radius: inherit;
  background:
    linear-gradient(var(--glass-fill), var(--glass-fill)) padding-box,
    var(--glass-edge) border-box;
  box-shadow: var(--glass-drop);
  /* Blur ONLY where a real photograph sits behind the surface — the image chips
     and the sign-up pill. Everywhere else there is nothing behind the glass but
     a decorative wash, and blurring a wash is what painted a diagonal gradient
     across every tile: the blur sampled the blob, which is brightest exactly
     where the grid starts, so each card came out light at one corner and dark at
     the other. A dozen live blurs is also real scroll cost on a mid phone. */
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  backdrop-filter: blur(16px) saturate(1.2);
}

/* ---------------------------------------------------------------------------
   PANELS — the content surfaces: tiles and the price card.

   Flat, opaque, one hairline. Not glass: a tile has nothing behind it worth
   looking through, so translucency bought an uneven colour cast and a live blur
   and gave back nothing. `--sak-card` is the role that already means "a panel
   raised off this ground" and it re-points per scheme, so the recipe is one
   line and the same one line works on navy, ink and bone.
   --------------------------------------------------------------------------- */
.panel {
  border: 1px solid var(--sak-border);
  border-radius: inherit;
  background: var(--sak-card);
}

@media (min-width: 900px) {
  :root { --pad-x: 48px; --gap: 20px; }
}

html { background: var(--sak-bone); scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--sak-font-sans);
  color: var(--sak-foreground);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }
picture { display: block; }
img { display: block; max-width: 100%; height: auto; }
svg { display: block; }

/* The `hidden` attribute must win over any `display` set below it — the form's
   success state relies on it. */
[hidden] { display: none !important; }

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

::selection { background: var(--sak-red-200); color: var(--sak-blue-900); }

.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  border: 0; clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   THE NAVY FIELD

   The first three sections are one continuous navy ground — there is no divider
   between the hero and §2 — so the depth washes belong to the FIELD, not to any
   one section inside it. Scoping a wash to §2 and clipping it with that
   section's `overflow: hidden` is what cut a straight bright line across the
   hero boundary: the blob's top sat above the section, so the clip ran through
   the part of the circle that was still opaque.

   Two rules keep a wash from ever showing a seam:

   1. The blob's box lives entirely inside its clipping container, so no clip
      edge can land in the middle of it. The only clipping left is at the
      viewport's own left and right edges, where there is nothing beyond the cut
      to compare against and the seam cannot be perceived.
   2. `radial-gradient(closest-side, colour, transparent)` on a SQUARE box. The
      gradient is then the circle inscribed in that box, so every point on the
      box's boundary — sides and corners alike — is already fully transparent.

   The sections inside give up their own background so the field's washes show
   through; the wrapper carries `data-scheme="navy"` and paints the ground for
   all three. `[data-scheme]`'s background rule in tokens.css is (0,1,0), so the
   two-class selector below wins without !important.
   --------------------------------------------------------------------------- */

/* One source of truth for the hero's height: the hero sizes itself from it, and
   the left wash below positions itself from it. They must agree — see the wash. */
.field { --hero-h: 100svh; position: relative; isolation: isolate; overflow: hidden; }
.field > .hero, .field > .section { background-color: transparent; }

.field::before, .field::after {
  content: "";
  position: absolute; z-index: -1;
  aspect-ratio: 1; border-radius: 50%;
  pointer-events: none;
}
/* Left: the light the tiles sit into. It used to straddle the hero → §2
   boundary, which was right when the hero was navy and the wash could run
   continuously through it. The hero is now an opaque photograph, so the wash
   cannot show there at all — it only lightens the navy BELOW the photo, and that
   difference lands on a straight horizontal line exactly at the hero's foot.
   That was the seam. Starting the wash below the hero removes the step; keyed to
   --hero-h so the two can never drift apart. */
.field::before {
  width: min(1040px, 108vw); left: -24%;
  top: calc(var(--hero-h) + 40px);
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--sak-blue-700) 42%, transparent), transparent);
}
/* Right and low, under the price card. */
.field::after {
  width: min(1160px, 120vw); right: -22%; bottom: 200px;
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--sak-blue-700) 46%, transparent), transparent);
}

@media (max-width: 899px) {
  /* `top` is keyed to --hero-h now, so only the low right wash needs moving. */
  .field::after { bottom: 120px; }
}

/* ---------------------------------------------------------------------------
   SECTIONS
   --------------------------------------------------------------------------- */

.section { padding: 76px var(--pad-x); }
.section > .inner { max-width: var(--measure); margin-inline: auto; }

/* Adjacent sections on the same ground get a hairline, or the boundary
   disappears once the whole top of the page runs navy.

   Both sides must be `.section` — which deliberately excludes the hero → §2
   boundary, since the hero carries `.hero` and not `.section`. The hero should
   flow straight into what it treats as one continuous field; §2 → §3 still
   needs the divider. */
.section[data-scheme="navy"] + .section[data-scheme="navy"],
.section[data-scheme="bone"] + .section[data-scheme="bone"] {
  border-top: 1px solid var(--sak-border);
}

@media (min-width: 900px) {
  .section { padding: 128px var(--pad-x); }
}

/* Eyebrow — a hairline pill carrying a small accent dot, after IntegratedBio's
   `▪ WHAT WE DO`. Bare letterspaced caps floating in space read as a label
   nobody drew; the pill makes it an object, and the dot is where the accent
   goes so the label itself can stay calm.

   IntegratedBio's mark is a SQUARE, and it was one here too — which put two
   different chip languages side by side in the hero, since the spec chips
   below carry a round dot. Rounded is also what the rest of the page is made
   of: the wordmark's KIT pill, the CTA, the form. The dot matches `.fact`
   exactly — same 6px, same radius, same accent role.

   Used twice, on the two places that name a category: the hero and the price
   card. */
.eyebrow {
  /* Block-level `flex` + `fit-content` rather than `inline-flex`: an
     inline-level pill sits in a line box and inherits the parent's leading,
     which puts a few unaskable pixels under it. */
  display: flex; width: fit-content;
  align-items: center; gap: 9px;
  padding: 7px 15px 6px;
  /* The highlight role, mixed down. Solid it reads as a drawn rule demanding
     attention; at 34% it reads as an edge catching light, which is what a pill
     around a label should be. Same token the placeholder text uses at full
     strength — one decision, two applications. */
  border: 1px solid color-mix(in srgb, var(--sak-highlight) 34%, transparent);
  border-radius: var(--r-pill);
  color: var(--sak-foreground);
  letter-spacing: .14em;
}
/* Over the photograph a hairline alone cannot hold: measured against the
   brightest water behind it, a bone stroke needs ~75% opacity to clear 3:1, and
   at that strength it stops being a hairline and becomes a white outline.

   So the hero pill borrows the form's material instead of shouting louder — a
   translucent fill with the same backdrop blur. The blur is what does the work:
   it flattens the water behind the pill into an even tone, so a soft edge reads
   cleanly against it. Same justification as the form, and the same condition
   that kept blur on these two components when it was stripped from the tiles:
   there is a real photograph behind them. */
.hero .eyebrow {
  border-color: color-mix(in srgb, var(--sak-highlight) 46%, transparent);
  background: color-mix(in srgb, var(--sak-bone) 12%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
}
.eyebrow::before {
  content: ""; flex: none;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--sak-accent);
}

/* ---------------------------------------------------------------------------
   REVEAL
   --------------------------------------------------------------------------- */

[data-reveal] {
  opacity: 0; transform: translateY(22px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
[data-reveal].in { opacity: 1; transform: none; }
[data-reveal][data-d="1"] { transition-delay: .07s; }
[data-reveal][data-d="2"] { transition-delay: .14s; }
[data-reveal][data-d="3"] { transition-delay: .21s; }
[data-reveal][data-d="4"] { transition-delay: .28s; }

/* The reveal itself is already honoured in the script — it adds `.in` to
   everything up front and never observes. This covers the motion the script
   can't reach: the smooth scroll, the tile lift, the button's press. The
   reveal is restated here so the page is still readable if the script fails. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  .group, .signup-btn { transition: none; }
  .group:hover { transform: none; }
  .signup-btn:active { transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   1 · HERO
   ═══════════════════════════════════════════════════════════════════════════ */

/* The hero is a full-bleed photograph with the copy sitting on it — the product
   frame is gone. `isolation` gives the section its own stacking context so the
   shot can sit at z-index -1 behind the copy without the field's depth washes
   (also z-index -1, in the FIELD's context) punching through it.

   Mobile-first: the copy stacks at the FOOT of the frame, so the surfer stays
   readable in the upper half where there is no type over him. `svh`, not `vh` —
   `vh` is the tallest-toolbar value on mobile Safari, which pushes the form
   below the fold on first paint, and the form is the only metric this page has. */
.hero {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  min-height: var(--hero-h);
  display: flex; flex-direction: column;
  padding: 0 var(--pad-x) 40px;
}

/* The photograph is MASKED out at its foot rather than covered with navy.
   Painting an opaque navy fade over it leaves a visible step at the hero → §2
   boundary, because §2 below is navy PLUS the field's depth wash and the hero is
   navy alone — two different colours meeting on a straight line. Masking removes
   the photo instead, so what shows through underneath is the field's own ground,
   wash included, and there is nothing left to form an edge. */
.hero-shot {
  position: absolute; inset: 0; z-index: -1;
  -webkit-mask-image: linear-gradient(to top, transparent 0%, rgba(0,0,0,.5) 9%, #000 26%);
  mask-image: linear-gradient(to top, transparent 0%, rgba(0,0,0,.5) 9%, #000 26%);
}
.hero-shot picture { display: block; width: 100%; height: 100%; }
/* Below 900 the file itself is already the portrait art-direction cut, framed on
   the action, so it wants no offset. The landscape file above 900 does. */
.hero-shot img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; }
@media (min-width: 900px) {
  .hero-shot img { object-position: 62% 46%; }
}

/* ONE directional gradient, behind the copy only — not a global darkening pass.
   Bottom-up on mobile because that is where the copy sits. Multi-stop with an
   eased alpha ramp: a two-stop gradient across this distance bands visibly on an
   8-bit panel, which is what read as a seam. The tail also dissolves the hero →
   §2 boundary, so the navy field below still starts without a visible edge. */
.hero::after {
  content: "";
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    /* Behind the copy. Measured, not guessed: bone on the brightest whitewater
       under the headline is 1.9:1 ungraded, and reaching AA-large there needs
       ~42% navy — ~66% under the 12px eyebrow. The curve holds those values to
       ~68% of the height and then falls away fast, so the board's upper two
       thirds stay clear. That trade is the whole design: the copy band is
       protected, the subject is not veiled. */
    linear-gradient(to top,
      var(--sak-blue-900) 0%,
      color-mix(in srgb, var(--sak-blue-900) 96%, transparent) 20%,
      color-mix(in srgb, var(--sak-blue-900) 90%, transparent) 35%,
      color-mix(in srgb, var(--sak-blue-900) 80%, transparent) 50%,
      color-mix(in srgb, var(--sak-blue-900) 68%, transparent) 62%,
      color-mix(in srgb, var(--sak-blue-900) 46%, transparent) 72%,
      color-mix(in srgb, var(--sak-blue-900) 22%, transparent) 82%,
      color-mix(in srgb, var(--sak-blue-900) 7%, transparent) 92%,
      transparent 100%),
    /* Behind the masthead. The wordmark sits over sunlit rock at up to
       rgb(215,207,197) — bone on bone, 1.35:1. Short and local. */
    linear-gradient(to bottom,
      color-mix(in srgb, var(--sak-blue-900) 82%, transparent) 0%,
      color-mix(in srgb, var(--sak-blue-900) 62%, transparent) 45%,
      color-mix(in srgb, var(--sak-blue-900) 28%, transparent) 75%,
      transparent 100%) 0 0 / 100% 17% no-repeat;
}

/* `width: 100%` is load-bearing now the hero is a flex column: a flex item's
   base size is its content, so without it the masthead shrinks to the wordmark
   and `margin-inline: auto` centres that instead of spanning the measure. */
.masthead {
  position: relative;
  width: 100%;
  max-width: var(--measure); margin-inline: auto;
  padding: 22px 0 18px;
  border-bottom: 1px solid color-mix(in srgb, var(--sak-bone) 22%, transparent);
}
/* `color` picks the wordmark variant, so it is set explicitly per placement
   rather than inherited: this mark is the PRIMARY artwork and must stay bone
   even if something above it changed scheme. Height drives the size and the
   viewBox supplies the width — 247 / 41, so nothing has to restate the ratio. */
.wordmark { height: 22px; width: auto; color: var(--sak-bone); }

@media (min-width: 900px) {
  .wordmark { height: 26px; }
}

/* Every grid below uses minmax(0, …) rather than a bare `1fr`. A bare fr track
   has an `auto` minimum, i.e. min-content, which can push a column wider than
   its container — long headlines and nowrap pills both do it. */
/* `margin-top: auto` is what pins the copy to the floor of the photograph. */
.hero-inner {
  position: relative;
  width: 100%;
  max-width: var(--measure); margin-inline: auto; margin-top: auto;
  padding-top: 40px;
  display: grid; gap: 36px;
  grid-template-columns: minmax(0, 1fr);
}

.hero-eyebrow { margin-bottom: 20px; }
.hero h1 { max-width: 14ch; }
/* Over a photograph the muted tone loses the ground it was measured against, so
   the hero's sub steps up to full foreground. Everywhere else muted is correct. */
.hero-sub {
  color: var(--sak-foreground);
  max-width: 32ch; margin-top: 18px;
}
.hero .signup-note { color: var(--sak-foreground); }

/* The product frame — now §3 only, since the hero photograph runs full bleed. */
.kit-media { position: relative; }
.kit-media .frame {
  position: relative; border-radius: var(--r-lg); overflow: hidden;
  box-shadow: var(--shadow-lg);
  border: 1px solid color-mix(in srgb, var(--sak-bone) 12%, transparent);
}

/* One aligned spec row, after Titan Intake's `+SAVE TIME · +INCREASE REVENUE ·
   +ACCELERATE CARE`. The previous version put a chip in three different corners
   of the render, which covered the product and read as three unrelated labels.

   Mobile is the default: a plain wrapped row under the image. Three chips need
   ~371px and the frame is 331px at 375, so inside the render they wrap to two
   rows and cover a third of the kit — and the type can't shrink to fix it,
   because text-sm at 12px is the floor for anything meant to be read. */
.facts {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: 12px;
}

.fact {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 13px; border-radius: var(--r-pill);
  color: var(--sak-foreground);
  white-space: nowrap;
  --glass-fill: color-mix(in srgb, var(--sak-blue-950) 56%, transparent);
}
.fact::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--sak-accent);
}

@media (max-width: 899px) {
  .facts { gap: 7px; }
  .fact { padding: 7px 11px; gap: 6px; }
  .fact::before { width: 5px; height: 5px; }
}

/* IN THE FRAME FROM 940 UP, UNDER IT BELOW.

   940, deliberately NOT the 900 layout breakpoint. The only question that
   matters is whether three chips fit on ONE row inside the frame: they measure
   403px and the 14px insets take the requirement to 431px. Two rows of chips
   stacked over the render cover too much of the kit even at 56% opacity, so a
   single row is the whole condition.

   At 900 the frame is 459px — 28px of headroom, and Chrome measures width media
   queries against innerWidth, so a classic scrollbar eats 15px of the budget
   before the layout sees it. That margin is inside what a copy edit or a
   font-rendering difference can consume, and the failure mode is the two-row
   overlay we just took out. 940 gives 52px.

   Between 900 and 939 the section is already two columns while the chips are
   still underneath the render. That is intentional: it is the same arrangement
   as mobile, just in a narrower column, and it costs nothing.

   The blur follows the chips. Inside the frame there is a render to sample;
   underneath they sit on flat navy, where a backdrop-filter buys nothing and
   costs a composite layer on exactly the phones this page is built for. */
@media (min-width: 940px) {
  .kit-media .frame::after {
    content: "";
    position: absolute; inset: auto 0 0 0; height: 46%;
    z-index: 1; pointer-events: none;
    background: linear-gradient(to top, rgba(2, 13, 27, .68), transparent);
  }
  .facts {
    position: absolute; z-index: 2;
    left: 14px; right: 14px; bottom: 14px;
    margin-top: 0;
  }
}
@media (max-width: 939px) {
  .fact { -webkit-backdrop-filter: none; backdrop-filter: none; }
}

.hero-signup { margin-top: 34px; }

/* From 900 up the copy moves off the floor and into the left half, and the
   gradient turns with it: bottom-up becomes left-to-right, because the subject
   now has room to sit clear on the right. The vertical tail is kept, at much
   lower strength, purely to dissolve the hero → §2 boundary. */
@media (min-width: 900px) {
  /* The masthead stays pinned to the top and the copy centres in what is left:
     `margin-block: auto` on the inner does both. `justify-content: center` does
     not — it centres masthead and copy together as one group, which pushes the
     mark down the page and crowds it against the eyebrow. */
  .field { --hero-h: min(88svh, 780px); }
  .hero { padding-bottom: 0; }
  .hero-inner {
    margin-block: auto; padding-top: 0;
    grid-template-columns: minmax(0, 1fr);
  }
  .hero-copy { max-width: 33rem; }
  .hero-signup { margin-top: 36px; }

  /* Desktop turns the gradient with the layout: the copy sits in the left half
     and the subject has room on the right, so one directional ramp does the job
     the two stacked ramps do on a phone. */
  .hero::after {
    background:
      linear-gradient(100deg,
        color-mix(in srgb, var(--sak-blue-900) 94%, transparent) 0%,
        color-mix(in srgb, var(--sak-blue-900) 89%, transparent) 20%,
        color-mix(in srgb, var(--sak-blue-900) 76%, transparent) 34%,
        color-mix(in srgb, var(--sak-blue-900) 54%, transparent) 46%,
        color-mix(in srgb, var(--sak-blue-900) 30%, transparent) 57%,
        color-mix(in srgb, var(--sak-blue-900) 12%, transparent) 68%,
        transparent 82%),
      /* Light vertical assist only — the mask on .hero-shot is what actually
         dissolves the hero → §2 boundary. */
      linear-gradient(to top,
        color-mix(in srgb, var(--sak-blue-900) 40%, transparent) 0%,
        color-mix(in srgb, var(--sak-blue-900) 18%, transparent) 9%,
        transparent 20%),
      /* Masthead band: the mark runs the full measure, so its right-hand end
         reaches past where the directional ramp has already faded out. */
      linear-gradient(to bottom,
        color-mix(in srgb, var(--sak-blue-900) 60%, transparent) 0%,
        color-mix(in srgb, var(--sak-blue-900) 38%, transparent) 50%,
        transparent 100%) 0 0 / 100% 14% no-repeat;
  }
}

/* ---------------------------------------------------------------------------
   SIGN-UP FORM

   Desktop: one pill, 6px of padding, the field inside it and the button pill
   sitting in the right-hand end.

   Stacked (<560px): the OUTER pill's chrome moves onto the field, so both
   controls are pills of exactly the same height with one consistent gap.
   Everything is driven off --field-h so the two can't drift apart.

   THE PILL'S MATERIAL IS THREE VARIABLES, not a set of rules per breakpoint.
   Whichever element is acting as the pill — the row on desktop, the field when
   stacked — takes the same fill, the same edge and the same blur, so the two
   layouts cannot look like different components. They used to: the stacked
   field wore a GRADIENT edge (--glass-edge) while the desktop row wore a flat
   one, which is why the mobile field read as a different treatment even though
   nothing behind it had changed.

   It also retires a real trap. The old code had to repeat
   `[data-scheme="navy"] .signup-row` inside the media query, because a media
   query adds no specificity and the bare `.signup-row` reset lost to it. Custom
   properties inherit rather than cascade by specificity, so the scheme sets the
   value once on .signup and every breakpoint just reads it.
   --------------------------------------------------------------------------- */

.signup {
  --field-h: 52px;
  max-width: 30rem;
  --pill-fill: color-mix(in srgb, var(--sak-foreground) 6%, transparent);
  --pill-edge: var(--sak-border);
  --pill-blur: blur(16px) saturate(1.2);
}
[data-scheme="navy"] .signup,
[data-scheme="ink"] .signup {
  --pill-fill: color-mix(in srgb, var(--sak-bone) 8%, transparent);
}
/* On the photograph the divider tone disappears, so the hero's pill takes the
   highlight role — the same token, at the same strength, as the eyebrow beside
   it. Fill and blur are untouched: a near-opaque navy was tried here and
   rejected, it reads as a slab and loses the material. */
.hero .signup { --pill-edge: color-mix(in srgb, var(--sak-highlight) 46%, transparent); }

.signup-row {
  display: flex; gap: 8px;
  padding: 6px; border-radius: var(--r-pill);
  border: 1px solid var(--pill-edge);
  background: var(--pill-fill);
}
.signup-row:focus-within { border-color: var(--sak-accent); }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.signup-input {
  flex: 1; min-width: 0;
  height: var(--field-h);
  border: 0; background: transparent; outline: none;
  border-radius: var(--r-pill);
  padding: 0 8px 0 16px;
  color: var(--sak-foreground);
  font-family: var(--sak-font-sans);
  /* 16px on purpose — iOS zooms the page on focus below this, and a zoom jump
     inside an in-app browser is a real way to lose someone. */
  font-size: var(--sak-text-base);
  letter-spacing: var(--sak-tracking-base);
}
/* The highlight role at full strength. `muted-foreground` is blue-300 and read
   as an unintentional light blue in the field — it is a body-copy tone doing a
   UI job. Highlight is blue-100 on navy: brighter, clearly a placeholder rather
   than entered text, and the same token as the edges around it. */
.signup-input::placeholder { color: var(--sak-highlight); opacity: 1; }

.signup-btn {
  flex: none;
  height: var(--field-h);
  border: 0; border-radius: var(--r-pill); cursor: pointer;
  padding: 0 22px;
  background: var(--sak-primary); color: var(--sak-primary-foreground);
  font-family: var(--sak-font-sans); letter-spacing: .1em;
  /* Heavier than the 500 that `.text-sm` carries. The scale has no 600 — the
     weights are 400 / 500 / 700 only, and 700 is documented as held in reserve.
     This is its first use, which is a small system decision worth knowing about:
     the alternative was introducing a 600 that exists nowhere else. */
  font-weight: var(--sak-font-weight-bold);
  transition: background .2s var(--ease), transform .2s var(--ease);
}
.signup-btn:hover { background: var(--sak-red-600); }
.signup-btn:active { transform: translateY(1px); }
.signup-btn:disabled { opacity: .7; cursor: default; }

@media (max-width: 559px) {
  /* The row stops being the pill and becomes a plain stack; the field takes over
     the pill's fill, edge, radius and blur — the same three variables, so the
     stacked field is the desktop pill, not a lookalike. One selector does it:
     the scheme no longer sets a background here, it sets --pill-fill on .signup,
     which inherits and so needs no specificity match. */
  .signup-row {
    flex-direction: column; gap: 10px;
    padding: 0; border: 0; background: transparent; border-radius: 0;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    /* .glass's drop shadow would otherwise sit behind the stack. */
    box-shadow: none;
  }

  .signup-input {
    /* flex:none is load-bearing. Once the row is a column, `flex: 1` applies its
       0% basis to HEIGHT, which overrides `height` and collapses the field to
       its text. Opting out of flex sizing restores --field-h. */
    flex: none;
    height: var(--field-h);
    padding: 0 20px;
    border: 1px solid var(--pill-edge);
    background: var(--pill-fill);
    -webkit-backdrop-filter: var(--pill-blur);
    backdrop-filter: var(--pill-blur);
  }
  .signup-input:focus { border-color: var(--sak-accent); }
  /* Full width, same height, same padding — only the width changes. */
  .signup-btn { width: 100%; }
}

.signup-note { margin-top: 14px; padding-left: 4px; color: var(--sak-muted-foreground); }
.signup-done {
  margin-top: 14px; padding: 14px 18px; border-radius: var(--r-md);
  background: color-mix(in srgb, var(--sak-accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--sak-accent) 30%, transparent);
  display: flex; align-items: flex-start; gap: 10px;
}
.signup-done::before { content: "✓"; color: var(--sak-accent); font-weight: 700; }

/* ═══════════════════════════════════════════════════════════════════════════
   2 · WHAT IT TREATS + WHAT'S INSIDE (merged)
   Grouped by function. Outlined tiles, no fill — a light-blue panel on warm
   bone read as a different palette; a hairline on bone reads as the brand.
   ═══════════════════════════════════════════════════════════════════════════ */

.treats .inner { position: relative; }

.treats-head { max-width: 20ch; text-wrap: balance; }
.treats-lead { margin-top: 28px; max-width: 26ch; }

/* ---------------------------------------------------------------------------
   THE CAROUSEL

   Cards are sized in fractions of the TRACK, not the viewport, so the peek is
   the same proportion at every width: --per is how many cards are fully visible,
   and the remainder is what shows of the next one. That single number is the
   whole responsive story.

   Scroll-padding + scroll-snap-align: start means a snapped card lands flush
   with the section's left edge rather than centred, so the peek is always on the
   right — one consistent direction to swipe, which is what makes it read as
   "there is more" instead of "this is off-centre".
   --------------------------------------------------------------------------- */

.carousel { margin-top: 44px; position: relative; }

.groups {
  --per: 1.12;          /* 1 card + a 12% sliver of the next */
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--gap) * (var(--per) - 1))) / var(--per));
  gap: var(--gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  /* Let the cards run to the section edge instead of stopping at the measure,
     so the peeked card is cut by the viewport — the cue reads as "the row
     continues", not "the row ended awkwardly". */
  margin-inline: calc(var(--pad-x) * -1);
  padding-inline: var(--pad-x);
  scroll-padding-inline-start: var(--pad-x);
  padding-block: 4px;   /* room for the hover lift, which overflow would clip */
  scrollbar-width: none;
}
.groups::-webkit-scrollbar { display: none; }
.groups > .group { scroll-snap-align: start; }

/* The track is focusable so a keyboard can scroll it; give that a visible ring
   without the 3px offset, which would sit under the neighbouring card. */
.groups:focus-visible { outline: 2px solid var(--sak-ring); outline-offset: -2px; border-radius: var(--r-md); }

.carousel-nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-top: 22px;
}
/* The counter only earns its place while ONE card is visible — there it tells
   you where you are in a set you cannot see. From 620 up two or more cards are
   on screen, the position is self-evident, and the counter can only express it
   as a range, which reads as a bug rather than information. Hidden, and the
   arrows take the row on their own. */
.carousel-count { color: var(--sak-muted-foreground); letter-spacing: .12em; }
@media (min-width: 620px) {
  .carousel-count { display: none; }
  .carousel-nav { justify-content: flex-end; }
}
.carousel-arrows { display: flex; gap: 8px; }
.carousel-btn {
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--sak-highlight) 34%, transparent);
  background: transparent;
  color: var(--sak-foreground);
  transition: border-color .2s var(--ease), background-color .2s var(--ease);
}
.carousel-btn svg { width: 15px; height: 15px; }
.carousel-btn[data-carousel-prev] svg { transform: scaleX(-1); }
.carousel-btn:hover { border-color: var(--sak-accent); }
.carousel-btn:disabled { opacity: .35; cursor: default; }
.carousel-btn:disabled:hover { border-color: color-mix(in srgb, var(--sak-highlight) 34%, transparent); }

/* Flat panel, one hairline, and a lot of air. All six are identical — the field
   guide used to carry an accent border to single it out, and against five plain
   neighbours that read as a state rather than as emphasis. */
.group {
  display: flex; flex-direction: column;
  border-radius: var(--r-md);
  padding: 24px 24px 26px;
  transition: border-color .3s var(--ease), transform .3s var(--ease),
              background-color .3s var(--ease);
}
.group:hover {
  border-color: color-mix(in srgb, var(--sak-accent) 55%, transparent);
  transform: translateY(-3px);
}

/* Icon chip left, index right — IntegratedBio numbers each panel in the corner
   opposite its mark, which is what gives a grid of six its order. */
.group-top {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 22px;
}
.group-n { color: var(--sak-subtle-foreground); letter-spacing: .12em; }

/* A simple light chip: flat fill, UNIFORM hairline. The graded stroke the large
   surfaces use is wrong at 46px — over that short a drop it reads as a border
   that got lighter by accident, not as an edge catching light. */
.group .ico {
  width: 46px; height: 46px; border-radius: 14px;
  display: grid; place-items: center;
  color: var(--sak-accent);
  background: color-mix(in srgb, var(--sak-bone) 9%, transparent);
  border: 1px solid var(--sak-border);
}
.group .ico svg { width: 23px; height: 23px; }
.group h3 { margin-bottom: 8px; }
.group p { color: var(--sak-muted-foreground); }

/* One number changes per breakpoint: how many cards are fully in view. The peek
   is whatever is left over, so it never has to be tuned separately. */
@media (min-width: 620px) { .groups { --per: 2.15; } }
@media (min-width: 900px) {
  .groups { --per: 3.2; }
  .carousel { margin-top: 60px; }
  .treats-lead { margin-top: 32px; }
  .group { padding: 28px 28px 30px; min-height: 276px; }
  /* The copy sits on the floor of the tile and the mark on the ceiling, so six
     panels of unequal text length still share two alignments instead of none.
     `auto` on the first text element pushes it and everything after it down. */
  .group h3 { margin-top: auto; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   3 · THE KIT ITSELF
   Product photo, one headline, one subtitle, the price, then highlights as
   pills (Superpower's product page). One level of hierarchy per element.
   ═══════════════════════════════════════════════════════════════════════════ */

.kit .inner {
  position: relative;
  display: grid; gap: 40px; grid-template-columns: minmax(0, 1fr);
}

/* Frame styling is defined with the product frame above. */

.kit-head { max-width: 22ch; text-wrap: balance; }
.kit-sub { margin-top: 18px; max-width: 34ch; color: var(--sak-muted-foreground); }

/* ---------------------------------------------------------------------------
   THE PLAN CARD (Function's pricing card)

   Price and inclusions in ONE decision unit, rather than a floating checklist
   with a price bolted underneath. Left: the number and its caveat. Right: what
   you get. A hairline between them on desktop, above them when stacked.
   --------------------------------------------------------------------------- */

.plan {
  /* Golden ratio against the content measure — the card takes the major part,
     1200 / 1.618 ≈ 742px. `width` not `max-width`, because this is a grid item
     and an auto inline margin disables grid's stretch, which drops the item back
     to fit-content (it sized itself to its longest list row, not to its cap). */
  width: min(100%, calc(var(--measure) / 1.618));
  margin: 52px auto 0;
  border-radius: var(--r-lg);
  padding: 30px 28px 26px;
}

.plan-price {
  display: flex; align-items: baseline; gap: 10px;
  margin-top: 20px;
}
.plan-price .amount { line-height: 1; }
.plan-price .unit { color: var(--sak-muted-foreground); }

.plan-note {
  margin-top: 16px;
  color: var(--sak-muted-foreground);
}

.plan-list {
  margin-top: 26px; padding-top: 12px;
  border-top: 1px solid var(--sak-border);
}
/* Each inclusion divided by its own hairline rather than floating in a stack —
   Orchid's spec rows. It turns five statements into a list you can count. */
.plan-list li {
  display: flex; align-items: flex-start; gap: 13px;
  padding: 12px 0;
  border-bottom: 1px solid var(--sak-border);
}
.plan-list li:last-child { border-bottom: 0; padding-bottom: 0; }
.plan-list .tick {
  flex: none; width: 21px; height: 21px; margin-top: 1px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--sak-accent) 42%, transparent);
  background: color-mix(in srgb, var(--sak-accent) 13%, transparent);
  color: var(--sak-accent);
  display: grid; place-items: center;
}
.plan-list .tick svg { width: 11px; height: 11px; }

@media (min-width: 900px) {
  .plan {
    padding: 40px 44px 38px;
    /* Same ratio inside: the price is the minor column, the inclusions the
       major one — 38.2 / 61.8. */
    display: grid;
    grid-template-columns: minmax(0, 0.382fr) minmax(0, 0.618fr);
    align-items: center;
  }
  .plan-main { padding-right: 40px; }
  .plan-list {
    margin-top: 0; padding-top: 0;
    padding-left: 40px;
    border-top: 0;
    border-left: 1px solid var(--sak-border);
  }
}



/* Inline check row — for a short run of highlights or standards worth
   emphasising, at a point where reassurance matters. */
.checkrow {
  display: flex; flex-wrap: wrap; gap: 10px 24px;
  margin-top: 20px;
}
.checkrow li {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--sak-muted-foreground);
}
.checkrow svg { width: 13px; height: 13px; color: var(--sak-accent); flex: none; }

.close .checkrow { justify-content: center; }

.kit-top { display: grid; gap: 32px; grid-template-columns: minmax(0, 1fr); }

/* MOBILE: the render is a full-width block, edge to edge, with no radius and no
   frame. The rounded card was a product-shot device carried over from when this
   image sat in a small column; at full width it just fences the photograph in.
   The negative margin cancels the section's padding to reach the viewport edge.
   Aspect ratio is the file's own: nothing is cropped, the element is just bigger.

   It also sits FLUSH under the section divider. A full-bleed image separated
   from the rule above it by 76px of empty navy reads as two unfinished things
   rather than one edge — the divider needs something to divide. The air that
   was there moves up to §2, where it does the job it was meant to do: letting
   the carousel finish before the next section starts. */
@media (max-width: 899px) {
  .kit { padding-top: 0; }
  .treats { padding-bottom: 100px; }
  .kit-media .frame {
    margin-inline: calc(var(--pad-x) * -1);
    border-radius: 0;
    border: 0;
    box-shadow: none;
  }
  /* The image now carries the top of the section on its own, so the copy below
     needs slightly less air than it did under a floating card. */
  .kit-top { gap: 26px; }
}

@media (min-width: 900px) {
  /* The media column takes the major share — 1.25 / 0.75 against a tighter gap.
     The copy is a headline and one line of subtitle, so it needs far less room
     than an even split was giving it, and every pixel it gives back makes the
     render larger at its natural ratio. */
  .kit-top {
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr);
    align-items: center; gap: 56px;
  }
  .kit-copy { order: 2; }
  .kit-media { order: 1; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   4 · CLOSE
   ═══════════════════════════════════════════════════════════════════════════ */

.close { text-align: center; }
.close-head { max-width: 18ch; margin-inline: auto; text-wrap: balance; }
.close-sub {
  color: var(--sak-muted-foreground);
  max-width: 46ch; margin: 20px auto 0;
}
.close .signup { margin: 36px auto 0; }
.close .signup-note { text-align: center; padding-left: 0; }

/* Mark and copyright share one baseline, on one line. */
.foot {
  text-align: left; margin-top: 84px; padding-top: 26px;
  border-top: 1px solid var(--sak-border);
  display: flex; flex-wrap: wrap;
  align-items: baseline; justify-content: space-between;
  gap: 14px 24px;
}

/* The INVERSE artwork, on this bone ground — blue-800 lettering, which is the
   value the supplied file carries. Not full-bleed: 420px is where the mark's
   cap height lands on the 90px the text-5xl placeholder set, so it keeps the
   optical weight that was signed off without stretching to the measure. */
.foot-mark {
  display: block; flex: none;
  width: min(100%, 420px); height: auto;
  color: var(--sak-blue-800);
}

/* Flexbox takes a replaced element's baseline to be its bottom margin edge, but
   the wordmark's type baseline sits at 38.81 of its 41-unit viewBox — so the box
   bottom falls 5.34% of the rendered height BELOW where the letters actually
   sit. Aligned naively the copyright drops by that much and reads as a mistake.
   At the 420px mark that is 420 x 41/247 x 0.0534 = 3.7px, so lift it back.
   A percentage can't express this: in translateY, % resolves against the
   element's OWN height, not the mark's. Keep it in step with --foot-mark-w. */
.foot { --foot-mark-w: 420px; }
.foot-mark { width: min(100%, var(--foot-mark-w)); }
.foot-copy {
  flex: none;
  color: var(--sak-muted-foreground);
}
/* Only while the two actually share a line. Measured: the mark at its 420px cap
   plus the 24px gap plus the copyright needs a 525px container — about 569px of
   viewport — and below that the copyright wraps to its own row, where the lift
   would do nothing but eat 3.7px out of the row gap. */
@media (min-width: 580px) {
  .foot-copy { transform: translateY(calc(var(--foot-mark-w) * -0.00886)); }
}
