/* Self-hosted typefaces. These were third-party requests to Google until
   2026-09-29; serving them ourselves removes the last two external origins,
   which is what lets the CSP in .htaccess say font-src 'self' and lets the
   privacy notice say the site makes no third-party requests at all.

   Both are variable fonts, latin subset only. The weight and optical-size
   ranges match what the page actually uses — see --font-heading and
   --font-body below.

   The src URLs are relative to THIS FILE, which is served from /assets/
   so "fonts/x.woff2", never "assets/fonts/x.woff2". */
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400 800;
  font-optical-sizing: auto;
  font-display: swap;
  src: url("fonts/newsreader-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

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

/* DsEarn — design tokens and base system.
   Palette, typography, spacing, and the .wrap/.section/.btn utilities that
   Tasks 5-7 build the marketing page on top of. No component beyond that
   contract lives here yet; the homepage sections define their own classes
   when the copy lands.

   Contrast notes (WCAG AA, verified against the fixed palette):
   - paper on ink   = 17.09:1  -> body copy on dark sections
   - slate on paper = 10.08:1  -> body copy on light sections (softer than
     pure ink, still comfortably AA/AAA)
   - teal on paper  =  4.66:1  -> passes AA for normal text; used for links
     and button labels on light backgrounds
   - teal on ink    =  3.66:1  -> FAILS AA for normal text, so teal is never
     used as body-sized text on a dark section. mint on ink = 10.81:1, so
     mint carries that role instead.
   - mist on paper  =  1.88:1  -> mist must never sit on paper as text; it
     is reserved for de-emphasised text on dark sections only.
*/

:root {
  --ink: #0E1614;
  --paper: #F6F7F5;
  --teal: #0A7D72;
  --teal-deep: #064B44;
  --mint: #7FD6C9;
  --slate: #33403C;
  --mist: #A9BABA;
  --coral: #CF6444;

  /* Newsreader is a variable serif (weight 400-800, optical size 6-72); we
     always request the full range rather than a fixed instance so the
     browser's automatic optical sizing (font-optical-sizing: auto, on by
     default) picks a heavier, more display-cut rendering at hero scale and
     a lighter text-cut rendering nearer the ~18px floor below which
     Newsreader is never used (see the small-text font-family overrides
     throughout this file). Never set below ~18px: a serif that thin reads
     spindly at footnote sizes, especially light-on-dark. */
  --font-heading: "Newsreader", Georgia, "Times New Roman", serif;
  --font-body: "Inter", ui-sans-serif, system-ui, sans-serif;

  /* Fluid type scale, ~1.2 ratio, tuned so a legal page reads calmly and a
     hero headline still has room to carry weight. */
  --fs-2xl: clamp(2.75rem, 2rem + 3vw, 4.5rem);
  --fs-xl: clamp(2.1rem, 1.7rem + 1.6vw, 3rem);
  --fs-lg: clamp(1.5rem, 1.3rem + 0.8vw, 1.875rem);
  --fs-md: clamp(1.25rem, 1.15rem + 0.4vw, 1.4rem);
  --fs-body-lg: 1.1875rem;
  --fs-body: 1rem;
  --fs-sm: 0.875rem;

  /* 8px spacing grid */
  --space-1: 0.5rem;
  --space-2: 0.75rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2rem;
  --space-6: 3rem;
  --space-7: 4rem;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;

  --measure: 62ch;
  /* A heading and its lead want a shorter line than body copy does, or an
     h2 runs the full 1192px of .wrap and reads as a banner rather than a
     sentence. These are the two the section heads use. */
  --measure-head: 26ch;
  --measure-lead: 54ch;

  /* One motion vocabulary. Before this, components each picked their own
     duration (0.1s / 0.15s / 0.2s / 0.25s) and all of them used `ease`,
     which decelerates lazily and is what made hovers feel soft rather than
     snappy. --ease front-loads the movement: a control responds at once and
     settles, which is the whole difference between crisp and sluggish. */
  --dur-press: 90ms;
  --dur-fast: 140ms;
  --dur: 220ms;
  --ease: cubic-bezier(0.2, 0.8, 0.3, 1);

  /* Card surfaces. Every panel on the page was inventing its own
     color-mix() inline, which is why no two cards on the same background
     matched. Two surfaces, two hairlines, one radius. */
  --surface: #FFFFFF;
  --surface-dark: #16211F;
  --hairline: color-mix(in srgb, var(--ink) 12%, transparent);
  --hairline-dark: color-mix(in srgb, var(--mint) 16%, transparent);
}

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

/* A `hidden` attribute must actually hide.

   The UA stylesheet's `[hidden] { display: none }` is the weakest rule in the
   cascade, so ANY class that sets `display` silently beats it. That is not
   hypothetical here: .btn sets `display: inline-flex`, so the mechanism map's
   replay button — which ships `hidden` and is meant to stay invisible until
   motion.js decides a replay would do something — rendered in full to every
   visitor. It is the same defect the waitlist form hit when
   `.waitlist-form { display: flex }` beat the rule that was supposed to hide
   it after a successful submit, and it will keep recurring as long as the
   attribute is the weaker party. This puts the attribute back in charge
   everywhere, once. */
[hidden] {
  display: none !important;
}

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

html,
body {
  max-width: 100%;
  /* `clip`, not `hidden` (Task 9): the CSS Overflow spec forces an axis
     left at its default 'visible' to compute as 'auto' whenever the other
     axis is anything other than visible/clip. With `overflow-x: hidden`
     here, both html and body silently became `overflow-y: auto` scroll
     containers — and since body never actually needs to scroll (the
     visual viewport/html already does), it became a dead scroll container
     sitting between the page and .site-header, which broke that header's
     new `position: sticky` (sticky pins to the nearest ancestor scroll
     container's scrollport, not to the viewport, and that ancestor never
     moved). `clip` prevents horizontal overflow exactly like `hidden` did,
     without being one of the values that triggers the other axis's
     visible -> auto promotion, so overflow-y on both elements stays at its
     default `visible` and the page's own vertical scrolling — and the
     sticky header riding on it — works as before. Verified with CDP: sticky
     header now stays pinned at top:0 while scrolling; no horizontal scroll
     reappeared at 360px. */
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--slate);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

/* The UA stylesheet gives every <figure> `margin: 1em 40px`, and this page is
   built almost entirely out of figures. The ones that set their own margin
   were fine; the cost chart and the competition figure were
   silently inset 40px from the column their own section's heading starts at,
   which reads as a mistake at desktop width and eats an eighth of the usable
   width on a phone. Components below set their own spacing. */
figure {
  margin: 0;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-heading);
  color: var(--ink);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-3);
}

h1 {
  font-size: var(--fs-xl);
}

h2 {
  font-size: var(--fs-lg);
  margin-top: var(--space-6);
}

h3 {
  font-size: var(--fs-md);
  margin-top: var(--space-5);
}

p,
li {
  max-width: var(--measure);
  margin: 0 0 var(--space-3);
}

ul,
ol {
  padding-left: 1.25em;
}

a {
  color: var(--teal);
  text-decoration-thickness: from-font;
  text-underline-offset: 3px;
  transition: color var(--dur-fast) var(--ease), transform var(--dur-press) var(--ease);
}

a:hover {
  color: var(--teal-deep);
}

/* Press feedback (Task 9): a 1px nudge on every link, on top of whatever
   color change its hover state already carries. Transform-only, so it
   still obeys the "animate only transform/opacity" rule below, and the
   global prefers-reduced-motion block already collapses its transition to
   effectively instant. */
a:active {
  transform: translateY(1px);
}

/* Body-sized teal text fails AA on the ink background (3.66:1); mint
   carries links and accents there instead (10.81:1). Excludes .btn: a
   `.btn--primary` anchor (e.g. the nav's "Join the waitlist") must keep its
   paper-on-teal button colour, not inherit mint-on-teal (2.95:1, fails both
   AA text and the 3:1 non-text floor). Fix the collision by excluding .btn
   here rather than out-ranking it with a higher-specificity override, so a
   future `<a class="btn">` added anywhere inside a dark section doesn't
   silently inherit the wrong colour again. */
.section--dark a:not(.btn) {
  color: var(--mint);
}

.section--dark a:not(.btn):hover {
  color: var(--paper);
}

:focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

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

.wrap {
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: 24px;
}

.section {
  padding-block: clamp(56px, 6vw, 96px);
}

.section--dark {
  background: var(--ink);
  color: var(--paper);
}

.section--dark h1,
.section--dark h2,
.section--dark h3,
.section--dark h4 {
  color: var(--paper);
}

/* :where() contributes zero specificity, so this rule is (0,1,0) instead of
   the (0,1,1) a bare `.section--dark p, .section--dark li` would be. It
   still targets exactly p/li inside a dark section and still beats the
   bare `p`/`li` element rules ((0,0,1)), so nothing currently paper stops
   being paper — but any component class defined later in this file (e.g.
   `.field__hint`, `.waitlist-form__fineprint`, `.site-footer__copyright`,
   all of which set `color: var(--mist)` for de-emphasised text) now wins
   on source order instead of silently losing to this rule on specificity.
   Same bug class as the `.section--dark a:not(.btn)` / wordmark collision
   documented above — a `.section--dark <element>` rule out-ranking a
   component's own single-class rule regardless of where it's defined. */
.section--dark :where(p, li) {
  color: var(--paper);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  /* Body font, not the heading serif: at --fs-body (16px) this sits below
     the ~18px floor where Newsreader stays legible, and a button label is
     a UI control, not a headline. */
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--fs-body);
  padding: 0.85rem 1.75rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
    transform var(--dur-press) var(--ease);
}

.btn--primary {
  background: var(--teal);
  color: var(--paper);
}

.btn--primary:hover {
  background: var(--teal-deep);
}

/* The third button weight. The page had two states — filled primary and
   nothing — so every control that was not a conversion CTA had to borrow the
   conversion CTA's authority. That is why the map's replay control read as
   the page's main action. Quiet is a real control that is visibly not the
   thing the page is asking you to do.

   Border is full currentColor rather than a tint: a 40% tint of --teal on
   --paper measures under the 3:1 WCAG 1.4.11 floor for a UI component
   boundary, and this border IS the component's boundary. */
.btn--quiet {
  background: transparent;
  border-color: currentColor;
  color: var(--teal);
  /* Smaller than a primary on purpose. --fs-sm is 14px, still normal-text
     size for contrast purposes, so the 4.66:1 teal-on-paper figure applies
     unchanged. */
  font-size: var(--fs-sm);
  padding: 0.6rem 1.1rem;
}

/* teal on ink is 3.66:1 and fails AA. Same substitution the rest of this
   file makes for dark sections. */
.section--dark .btn--quiet {
  color: var(--mint);
}

.btn--quiet:hover {
  background: color-mix(in srgb, currentColor 10%, transparent);
}

.btn__arrow {
  transition: transform var(--dur-fast) var(--ease);
}

.btn--quiet:hover .btn__arrow,
.btn--quiet:focus-visible .btn__arrow {
  transform: translateX(2px);
}

/* Press feedback (Task 9), same transform-only nudge as plain links. */
.btn:active {
  transform: translateY(1px);
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: 0.001ms !important;
    animation-duration: 0.001ms !important;
  }
}

/* ---------------------------------------------------------------------
   Homepage: nav, hero, problem, how-it-works (Task 5).

   Rhythm: the header and hero share `.section--dark` so they read as one
   continuous ink-coloured opening move instead of a light nav bar bolted
   onto a dark hero. From there the page zebra-stripes light/dark per
   section, which is already how the copy brief ordered the sections.

   Recurring motif: a connecting line is used exactly twice, only where the
   content is a real sequence or hand-off — the problem section's
   Orders -> Inventory -> Fulfillment flow (the gap is the point: it's
   where a person currently bridges the systems by hand) and the four
   numbered how-it-works steps. Everywhere else (the three "why it stays
   broken" reasons) is an unordered set and stays unnumbered.
   ------------------------------------------------------------------- */

/* Scoped to .site-header/.site-footer (not just `a.wordmark`) so its
   specificity ties `.section--dark a:not(.btn)` — :not()'s argument counts
   toward specificity, so that rule is (0,2,1), one class higher than a bare
   `a.wordmark` (0,1,1) would be. Source order then settles the tie in the
   wordmark's favour, since this rule comes later in the file. Without the
   qualifier the wordmark would silently inherit mint like any other
   dark-section link, which isn't the brand treatment we want. The footer
   (Task 7) carries the same wordmark on the same ink background as the
   header, so it needs the identical fix — listed here rather than left to
   fall through to `.section--dark a:not(.btn)` (mint) or the base `a` rule
   (teal-on-ink, 3.66:1, fails AA). */
.site-header a.wordmark,
.site-footer a.wordmark {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--fs-md);
  color: var(--paper);
  text-decoration: none;
}

.site-header a.wordmark:hover,
.site-footer a.wordmark:hover {
  color: var(--mint);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  padding-block: var(--space-4);
}

.site-nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-5);
}

.site-nav__links {
  display: flex;
  list-style: none;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  gap: var(--space-4);
}

/* Measured at every breakpoint from 360 to 1440: each of these was a 20px-tall
   tap target, less than half the 44px a finger reliably hits, and they sit
   next to each other in a wrapping row where a near miss lands on the wrong
   section. The padding grows the hit area without moving the text, and the
   negative inline margin keeps the visual gap between links as designed. */
.site-nav__links a {
  display: inline-block;
  padding-block: 0.75rem;
  margin-block: -0.75rem;
  text-decoration: none;
  font-weight: 500;
}

.site-nav__links a:hover {
  text-decoration: underline;
}

.hero {
  padding-top: clamp(48px, 7vw, 96px);
}

.hero__content {
  max-width: 44rem;
}

.hero__title {
  font-size: var(--fs-2xl);
}

.lead {
  font-size: var(--fs-body-lg);
}

.hero__capture {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

.hero__capture input {
  flex: 1 1 16rem;
}

/* Below the width where the field and the button stop sharing a line, a
   half-width button under a full-width field reads as an unfinished
   layout. They match. */
@media (max-width: 620px) {
  .hero__capture {
    flex-direction: column;
    gap: var(--space-1);
  }

  .hero__capture .btn {
    justify-content: center;
  }

  /* `flex: 0 0 auto` FIRST, and it is the whole fix. The row layout gives
     this input `flex: 1 1 16rem`, where 16rem is a basis along the main
     axis — and once the container turns into a column, the main axis is
     vertical. The field stopped being 16rem wide and became 16rem TALL: a
     256px box under a two-line headline, which is the largest object above
     the fold on a phone and exactly the thing this block was added to fix.
     Measured at 390px before the reset: 250px tall.

     Then the smaller padding, because this one sits under a hero headline
     rather than in a form. */
  .hero__capture input {
    flex: 0 0 auto;
    width: 100%;
    padding: 0.7rem 0.9rem;
    font-size: var(--fs-sm);
  }
}

.hero__fineprint {
  margin-top: var(--space-2);
  margin-bottom: 0;
  font-size: var(--fs-sm);
  color: var(--mist);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* This rule cannot hide a <table> directly, and that is not a detail.
   A table's used width is the GREATER of its declared width and its
   min-content width, so `width: 1px` can never shrink one — measured, the
   chart's screen-reader data table computed to 343.562px with the rule fully
   applied, `table-layout: fixed` included. Being `position: absolute` with no
   positioned ancestor, it then sized against the initial containing block and
   pushed the document to 406px at a 360px viewport. Nothing scrolled, because
   html and body carry `overflow-x: clip` — so the only symptom was content
   quietly cut off at the right edge of a phone.

   So the table is wrapped in a <div class="visually-hidden"> instead. A block
   box does honour `width: 1px`, and its `overflow: hidden` clips the table
   inside it. Wrap any future table the same way rather than putting the class
   on the table itself. */

/* Border is --teal, not --mist: mist-on-paper measures 1.88:1, well under
   the 3:1 WCAG 1.4.11 floor for a UI component's visual boundary. --teal is
   4.66:1 against the field's own --paper background (documented at the top
   of this file), so it clears the requirement while staying inside the
   existing brand palette rather than inventing a new grey. This is the
   shared rule for every text-style field on the page, including the hero
   email input, so the fix lands everywhere at once. */
input[type="email"],
input[type="text"],
textarea {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  padding: 0.85rem 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--teal);
  background: var(--paper);
  color: var(--ink);
  transition: border-color var(--dur-fast) var(--ease);
}

/* Hover feedback (Task 9) — a field's own :focus-visible ring already
   signals "active"; this signals "interactive" before that, on the same
   --teal-deep already used for hovered links/buttons. */
input[type="email"]:hover,
input[type="text"]:hover,
textarea:hover {
  border-color: var(--teal-deep);
}

/* On ink, a --paper field measures 17:1 against the section behind it, so
   six empty boxes were the brightest objects in the viewport and the
   waitlist read as holes punched in the page rather than as a form. A
   surface just above the section's own background, with the same mint
   hairline every other dark-section panel uses, puts the emphasis back on
   the labels and the submit button. The typed value is --paper at 17:1, so
   what the visitor writes is still the highest-contrast thing in the
   field — which is the only part of it that carries information. */
.section--dark input[type="email"],
.section--dark input[type="text"],
.section--dark textarea {
  background: var(--surface-dark);
  border-color: color-mix(in srgb, var(--mint) 28%, transparent);
  color: var(--paper);
}

.section--dark input[type="email"]:hover,
.section--dark input[type="text"]:hover,
.section--dark textarea:hover {
  border-color: color-mix(in srgb, var(--mint) 55%, transparent);
}

/* ::placeholder inherits the field's colour at full opacity in some
   engines and at 54% in others, so it is stated rather than left to them —
   --mist on --surface-dark is 6.9:1, comfortably legible without competing
   with a real value. */
.section--dark input::placeholder,
.section--dark textarea::placeholder {
  color: var(--mist);
  opacity: 1;
}

/* Every section heading that opens a section already gets breathing room
   from .section's own block padding; without this reset h2/h3's global
   margin-top would stack on top of that and double the gap. */
.wrap > h2:first-child,
.wrap > h3:first-child {
  margin-top: 0;
}

/* --- The flow, and the arrows that don't arrive ----------------------

   The old version stacked a dashed rule, a loose token and two captions in
   a centred column beside the node chips. Because a column's height is its
   content's height and the row was centred on that, the rule landed ABOVE
   the chips' midline rather than on it, the token floated free of the line
   it was meant to be travelling, and the two captions collided on a phone.

   The fix is one deterministic track height, --flow-track, shared by the
   chips and by each gap's first row. Row 1 is exactly a chip tall, so the
   arrow is centred on the chips' midline no matter how long the caption
   below it wraps; row 2 holds the caption and cannot push the arrow off
   that line. Nothing here depends on measuring anything.
   -------------------------------------------------------------------- */

.flow {
  --flow-track: 3.5rem;
  display: flex;
  align-items: start;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block: var(--space-6);
}

.flow__node {
  display: flex;
  align-items: center;
  min-height: var(--flow-track);
  border: 2px solid var(--ink);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-4);
  /* Body font: this chip renders at the base 16px, below the ~18px floor. */
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--ink);
}

.flow__gap {
  flex: 1 1 9rem;
  max-width: 15rem;
  display: grid;
  grid-template-rows: var(--flow-track) auto;
  align-items: center;
  justify-items: center;
}

/* The arrow itself: a stem the full width of the gap, an arrowhead at the
   far end pointed at the next system, and the "gap" chip sitting on the
   stem punching a real hole through it. The hole IS the argument — the
   line starts, and then stops being a line. */
.flow__arrow {
  position: relative;
  display: block;
  width: 100%;
  height: 2px;
}

.flow__gap-line {
  display: block;
  width: 100%;
  height: 0;
  border-top: 2px dashed color-mix(in srgb, var(--slate) 55%, transparent);
}

/* A solid head, so the stem reads as aimed rather than merely stopping.
   Drawn with borders rather than a glyph: a character would sit on a text
   baseline and need re-centring at every font size. */
.flow__arrow::after {
  content: "";
  position: absolute;
  top: 50%;
  right: -1px;
  width: 0;
  height: 0;
  transform: translateY(-50%);
  border-left: 7px solid color-mix(in srgb, var(--slate) 55%, transparent);
  border-block: 5px solid transparent;
}

/* Centred ON the stem, with the section's own background behind it, which
   is what breaks the dashes rather than merely overlapping them. */
.flow__gap-label {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding-inline: var(--space-1);
  background: var(--paper);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  line-height: 1;
  font-weight: 600;
  color: var(--slate);
}

/* --- The flow on a phone --------------------------------------------
   Same arrow, rotated. The chips go full width so they stop being ragged,
   and each gap becomes a fixed-width vertical stem on the left with its
   caption centred beside it — which is what stops "gap" and "someone
   re-types it here" sharing a line and missing each other's baseline.
   -------------------------------------------------------------------- */
@media (max-width: 640px) {
  .flow {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
  }

  .flow__node {
    justify-content: flex-start;
  }

  .flow__gap {
    max-width: none;
    grid-template-rows: none;
    grid-template-columns: 4.5rem 1fr;
    justify-items: start;
    column-gap: var(--space-3);
  }

  .flow__arrow {
    justify-self: center;
    width: 2px;
    height: var(--flow-track);
  }

  .flow__gap-line {
    width: 0;
    height: 100%;
    border-top: none;
    border-left: 2px dashed color-mix(in srgb, var(--slate) 55%, transparent);
  }

  /* The head points down the column instead of along the row. */
  .flow__arrow::after {
    top: auto;
    bottom: -1px;
    right: 50%;
    transform: translateX(50%);
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 7px solid color-mix(in srgb, var(--slate) 55%, transparent);
    border-bottom: none;
  }

  /* Same two-element trick on the other axis: the token is now as tall as
     the stem, so translateY's percentage is a percentage of the stem. */
  .flow__token {
    top: 0;
    left: 50%;
    width: 9px;
    height: 100%;
    margin: 0 0 0 -4.5px;
    transform: translateY(24%);
  }

  .flow__token::before {
    top: -4.5px;
    left: 0;
  }
}

@media (max-width: 640px) and (prefers-reduced-motion: no-preference) {
  .flow__token {
    animation-name: flow-stall-down;
  }
}

/* 24%, where the horizontal keyframe uses 36%: the rotated stem is one
   --flow-track tall (56px) against roughly 200px of width for the
   horizontal one, and the "gap" chip's height does not shrink with it. The
   fraction that parks the token against the chip's edge is therefore not
   the same on the two axes, and cannot be shared. */
@keyframes flow-stall-down {
  0% { transform: translateY(0); opacity: 0; }
  12% { transform: translateY(0); opacity: 1; }
  45%, 80% { transform: translateY(24%); opacity: 1; }
  100% { transform: translateY(24%); opacity: 0; }
}

.reasons {
  margin: 0;
}

/* The sentence the section lands on. It was a bare <p> with no rule at all,
   so it inherited a 16px bottom margin and no top one — which read fine at
   1440, where the four-across reasons grid ends well above it, and ran
   straight into the last reason's text at 360, where that grid is a single
   column. It is the section's conclusion, so it is set apart and given the
   lead's size rather than left to look like a fifth reason. */
.closing-line {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-body-lg);
  text-wrap: pretty;
}

.section--dark .closing-line {
  border-top-color: var(--hairline-dark);
}

.reasons__item {
  margin-bottom: var(--space-4);
}

.reasons__item:last-child {
  margin-bottom: 0;
}

.reasons__item dt {
  /* Body font: renders at the base 16px, below the ~18px floor. */
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 2px;
}

.reasons__item dd {
  margin: 0;
  color: var(--slate);
}

/* --ink on --ink. The two rules above were written for #gap, which is a
   light section; #status reuses the same component on the ink background,
   where the dt rendered as invisible-on-invisible and the dd as slate on
   ink (1.6:1). Four items — "Maps the process", "Writes the integration",
   "Tests before launch", "Runs it" — were simply not on the page.

   The `:where()` on .section--dark contributes no specificity, so these
   rules are (0,2,0) — one class above the (0,1,1) rules they correct, and
   above the (0,1,0) `.section--dark :where(p, li)` rule, without needing a
   third qualifier. Same failure mode as every other dark-section colour
   collision documented in this file: a component that sets an explicit
   light-section colour and is then reused on ink. */
:where(.section--dark) .reasons__item dt {
  color: var(--paper);
}

:where(.section--dark) .reasons__item dd {
  color: var(--mist);
}


.status-body > .reasons--tight {
  margin-bottom: var(--space-6);
}

.status-next {
  padding-top: var(--space-5);
  border-top: 1px solid var(--teal-deep);
}

/* The prose keeps a readable measure inside a band that is now the full
   width of the wrap; the two short blocks sit side by side above the
   numbered questions, which are the section's actual conclusion. */
.status-next__cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: var(--space-4) var(--space-6);
}

.status-next__cols h3 {
  margin-top: 0;
}

/* `:first-child`, not every h3. `.status-next h3` is (0,1,1) and the
   `.status-next__heading { margin-top: var(--space-5) }` rule further down
   this file is (0,1,0), so the blanket reset silently beat it and all three
   of this block's headings sat flush against the paragraph above them —
   "Who we're looking for" touching the sentence that ends the paragraph
   before it. Only the first heading needs its top margin removed, because
   only that one sits directly under the block's own border-top. */
.status-next h3:first-child {
  margin-top: 0;
}

.status-next h3 {
  color: var(--paper);
}

.status-next p {
  margin-bottom: var(--space-3);
}

.status-next > :last-child {
  margin-bottom: 0;
}

/* --- Who's building it ------------------------------------------------

   The bios had no card around them at all: two columns of loose text on
   the page background, of different lengths, so the section ended in a
   hole under the shorter one and neither bio had an edge that said where
   it began. They are cards now — one surface, one hairline, equal height,
   the photo and the name on one line at the top of each.
   -------------------------------------------------------------------- */

.team-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-6);
}

@media (max-width: 720px) {
  .team-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 480px) {
  .team-card {
    padding: var(--space-4);
  }
}

.team-card {
  display: flex;
  flex-direction: column;
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  /* Hover feedback (Task 9): a small lift, transform-only. The card isn't
     a link (no href), so there's no :focus-visible state to add here —
     hover is the applicable "real interaction state" for a static card. */
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.team-card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--teal) 35%, transparent);
}

.team-card h3 {
  margin: 0;
  font-size: var(--fs-md);
}

/* Photo beside the name, both vertically centred on each other, so the two
   cards' heads line up whatever the name's length. */
.team-card__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.team-card__ident {
  min-width: 0;
}

/* Task 9: Chase's photo was replaced with a studio headshot at 540x675,
   close to Brendan's 540x694 — both are the same style of centred headshot
   (face in the upper third, shoulders at the bottom edge), so the
   per-photo object-position tuning this rule used to need no longer
   applies. The modifier classes stay on the <img> elements as a hook for a
   future photo that does need one.

   Two things changed here after rendering the pair side by side. The 176px
   square made two headshots the largest objects in a section about who
   writes the software — an avatar at the head of a card is the right
   weight for a founder line on a pre-launch site. And the photographs do
   not match: one is daylight in front of trees, the other a black studio
   backdrop, so at full saturation they read as two unrelated pictures
   rather than as a pair. The circle crops most of each background away and
   the grade closes what is left of the distance. This is a treatment, not
   a fix — matching headshots would be the fix. */
.team-card__photo {
  width: 84px;
  height: 84px;
  object-fit: cover;
  border-radius: 50%;
  background: var(--mist);
  flex: 0 0 auto;
  filter: saturate(0.82) contrast(1.04);
  box-shadow: 0 0 0 1px var(--hairline);
}

.team-card__role {
  margin: var(--space-1) 0 0;
  font-family: var(--font-body);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--teal);
}

.team-card__credential {
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--hairline);
  font-size: var(--fs-sm);
  color: var(--slate);
  /* Task 9: both credential lines grew long enough to wrap on a narrow
     card (checked at 360px). Progressive enhancement only — unsupported
     browsers keep the plain wrap, which is still legible. */
  text-wrap: pretty;
}

.team-card__bio {
  margin-bottom: 0;
  text-wrap: pretty;
}

.waitlist-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 34rem;
  margin-top: var(--space-6);
}

/* Two-class selector so this reliably beats `.waitlist-form { display: flex }`
   on specificity regardless of source order — a plain `[hidden]` attribute
   loses that fight (an author `display` rule always outranks the UA
   stylesheet's `[hidden] { display: none }`), which is exactly the bug this
   replaces. Toggled via classList, not the `hidden` property. */
.waitlist-form.is-hidden {
  display: none;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.field label {
  /* Body font: --fs-sm is 14px, below the ~18px floor. */
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--fs-sm);
}

.field input,
.field textarea {
  width: 100%;
  resize: vertical;
}

.field__hint {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--mist);
}

/* The honeypot must be reachable by a form-filling bot's DOM parser (so it
   gets autofilled) but never seen or tabbed to by a real visitor. Clipped
   off-screen rather than `display:none`/`visibility:hidden`, which some
   autofill heuristics skip; `type="hidden"` is avoided for the same
   reason — browsers never autofill hidden inputs, so it would catch
   nothing. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.waitlist-form__submit {
  align-self: flex-start;
  margin-top: var(--space-1);
}

.form-status {
  margin: 0;
  min-height: 1.5em;
  font-weight: 600;
}

.form-status.status--success {
  color: var(--mint);
}

.form-status.status--error {
  color: var(--paper);
}

.waitlist-form__fineprint {
  margin-top: var(--space-4);
  font-size: var(--fs-sm);
  color: var(--mist);
  max-width: 34rem;
}

.site-footer {
  padding-block: var(--space-6);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-5) var(--space-7);
  padding-bottom: var(--space-5);
  border-bottom: 1px solid var(--teal-deep);
}

.site-footer__brand {
  max-width: 26rem;
}

.site-footer__brand p {
  margin-top: var(--space-2);
  margin-bottom: 0;
  color: var(--mist);
}

/* Below 900px this is the only navigation left on the page (see the
   @media (max-width: 900px) block on .site-nav), so it carries the section
   links the header no longer does. */
.site-footer__nav {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2) var(--space-5);
  align-content: start;
  margin-top: var(--space-4);
}

@media (max-width: 620px) {
  .site-footer__nav {
    display: none;
  }
}

/* Same 44px floor as the primary nav and the contact links: below 900px
   these are the only navigation on the page.

   color is stated explicitly rather than left to inherit from
   `.section--dark a:not(.btn)`. That rule happens to win today — its
   specificity (0,2,1) beats the bare `a { color: var(--teal) }` (3.66:1,
   fails AA) — but a single class selector like this one only has to gain
   one more class before a same-or-higher-specificity component rule could
   out-rank it and silently fall back to teal, exactly the collision this
   stylesheet already documents on the wordmark rule above. Stating mint
   here means that can't happen by accident on the page's only navigation
   below 900px. */
.site-footer__nav a {
  display: inline-block;
  padding-block: 0.75rem;
  margin-block: -0.75rem;
  text-decoration: none;
  font-weight: 500;
  color: var(--mint);
}

.site-footer__nav a:hover {
  text-decoration: underline;
}

.site-footer__contact {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
}

/* Same 44px floor as the nav: these were 17px-tall targets, and two of them
   are email addresses someone is trying to tap on a phone. Padding grows the
   hit area, the negative margin keeps the designed gap between rows. */
.site-footer__contact a {
  display: inline-block;
  padding-block: 0.85rem;
  margin-block: -0.85rem;
}

.site-footer__copyright {
  margin: var(--space-4) 0 0;
  font-size: var(--fs-sm);
  color: var(--mist);
}

/* ---------------------------------------------------------------------
   Editorial motion (Task 9).

   Scope: scroll reveals, a condensing sticky nav, and the cost chart's
   bars drawing in. Only transform and opacity are ever animated here —
   no layout property (width, height, padding, margin) is transitioned.

   Safety model for the reveal system and the cost bars, both of which
   have a "pre-reveal hidden" state:
   - The static HTML ships `<html class="no-js">`. assets/motion.js (the
     only thing that can hide anything) is the sole script that swaps
     `no-js` for `js` — every hidden-state selector below is scoped under
     `html.js`, so if that script never runs (disabled, blocked, a CDN
     hiccup, an exception before that line), the selector never matches
     and every element in question sits in its plain, always-visible
     default state. Nothing is ever hidden by default in markup or by a
     rule that isn't conditioned on JS having actually executed.
   - The same script withholds `js` entirely on a browser with no
     IntersectionObserver. The observer is the only thing that adds
     `.is-revealed`, so entering the hidden state without it would hide
     every revealable block on the page forever. The class and the
     mechanism that undoes it are switched on together, or not at all.
   - motion.js additionally never sets up the reveal observer at all when
     `prefers-reduced-motion: reduce` is set, so under that preference
     `.is-revealed` is simply never needed.
   - Belt and suspenders: the `@media (prefers-reduced-motion: reduce)`
     rules below re-declare the same `html.js` hidden-state selectors, at
     the same specificity, later in the file — so even if the JS guard
     above had a bug, the cascade still resolves to fully visible with no
     transition. Each pair of rules is written to match in specificity on
     purpose; don't raise one without raising the other.
   ------------------------------------------------------------------- */

html {
  scroll-behavior: smooth;
  /* .site-header is `position: sticky; top: 0` and opaque, so without an
     offset every one of the five nav anchors — and waitlist.js's
     scrollIntoView — parks the target's own <h2> underneath the bar.
     scroll-padding on the scroll container (html, which is the scroller
     here: see the overflow: clip note on body) covers both, where
     scroll-margin on each target would have to be remembered per section.

     Header heights, from the rules further down this file:
       normal    2 x var(--space-4) = 3rem, plus the CTA button
                 (1.6rem line box + 2 x 0.85rem padding + 2 x 1px border
                 = 3.4rem)              -> ~6.4rem, ~103px
       condensed 2 x var(--space-2) = 1.5rem, plus the same button
                                        -> ~4.9rem,  ~79px
     7rem (112px) clears the taller state with ~9px of air, and leaves a
     comfortable ~33px gap in the condensed state, which is the one a nav
     click actually lands in. */
  scroll-padding-top: 7rem;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/* Scroll reveals: a `<section>` gets [data-reveal="section"], and at most
   one clearly-major child inside it gets [data-reveal="item"] (the cost
   figure, the steps list, the team grid, ...) so a section reads as one
   considered layered reveal rather than a cascade of individually
   animating pieces. Reveal is one-shot — motion.js unobserves an element
   as soon as it has revealed once. */
html.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}

html.js [data-reveal="item"] {
  transition-delay: 0.08s;
}

html.js [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  html.js [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Condensing nav: the header is already opaque --ink at all times (see
   the Task 5 comment on .section--dark), so pinning it is what makes
   "stays legible over both light and dark sections" true — without
   `position: sticky` it just scrolls away like any other section and is
   never over the content below it. The condensed state trims its own
   padding once motion.js reports the hero has scrolled past; that swap
   is an instant, untransitioned step (not an animation) so this pass
   stays to transform/opacity only, same as everywhere else on this page. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
}

.site-header--condensed .site-header__inner {
  padding-block: var(--space-2);
}

/* Below 900px the header is a wordmark bar and nothing else.

   Measured at 390x844 before this rule: the sticky header was ~500px tall —
   60% of the viewport, permanently — because the wordmark, two wrapped rows
   of links and a full-weight CTA all stacked. Every anchor jump then landed
   underneath it, since scroll-padding-top was tuned to the desktop header.

   No disclosure menu, by decision: the footer carries the section links
   instead, and the hero's own capture field is the conversion path. */
@media (max-width: 900px) {
  .site-nav {
    display: none;
  }

  .site-header__inner {
    padding-block: var(--space-2);
    flex-wrap: nowrap;
  }

  html {
    /* ~48px of header plus air, against 7rem for the desktop bar. */
    scroll-padding-top: 4rem;
  }
}


/* F2. Fill is NOT set here: it resolves from the section below, because the
   same figure classes are used on both light and dark sections. */
.fig__label {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
}

.fig__value {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

.fig__axis {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* `.section .fig__label` and `.section--dark .fig__label` are BOTH (0,2,0),
   so specificity cannot separate them and source order decides. The dark
   rules therefore come second, and must stay second. This is the same
   collision class already documented for `.section--dark a:not(.btn)` and
   the wordmark further up this file. */
.section .fig__label { fill: var(--ink); }
.section .fig__value,
.section .fig__axis { fill: var(--slate); }

.section--dark .fig__label { fill: var(--paper); }
.section--dark .fig__value,
.section--dark .fig__axis { fill: var(--mist); }

/* F4. paint-order: stroke paints the outline UNDER the glyph, so a label
   that crosses a data line keeps a background-coloured gap around itself
   instead of becoming unreadable. The stroke colour must match the section
   background, so it is set per section for the same reason as fill. */
.fig__halo {
  paint-order: stroke;
  stroke-width: 3px;
  stroke-linejoin: round;
}

.section .fig__halo { stroke: var(--paper); }
.section--dark .fig__halo { stroke: var(--ink); }

/* F5. Anything in a figure that is not data draws at exactly one of these
   two weights. */
.fig__axis-line {
  stroke: var(--mist);
  stroke-width: 1;
  fill: none;
}

.fig__grid-line {
  stroke: color-mix(in srgb, var(--mist) 55%, transparent);
  stroke-width: 1;
  fill: none;
}

/* The controls stopped being a card. A grey panel was the only piece of UI
   chrome on an editorial page, and three 1,420px tracks with their values
   flung to the far right edge is what made this section read as a form. */
.field--slider {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  /* The 44px touch target lives HERE, on the wrapper, not on the track. A
     44px-tall range input is what a form looks like; a 44px-tall row with a
     4px track inside it is what a control looks like, and a finger cannot
     tell the difference. */
  min-height: 44px;
  column-gap: var(--space-3);
  row-gap: var(--space-1);
  margin-bottom: var(--space-3);
  padding-top: var(--space-1);
  border-top: 1px solid color-mix(in srgb, var(--slate) 22%, transparent);
}

.field--slider label {
  grid-column: 1;
  font-size: var(--fs-sm);
}

/* On the same line as its label, not 1,400px away from it. */
.field--slider output {
  grid-column: 2;
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--teal);
}

.field--slider input[type="range"] {
  grid-column: 1 / -1;
  width: 100%;
  accent-color: var(--teal);
}

/* Range inputs lose their focus ring in several browsers once accent-color
   is set. Put it back explicitly — this is the only keyboard affordance
   these controls have. */
.field--slider input[type="range"]:focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 4px;
}

/* Status section additions (Task 9). #status is a .section--dark, so text
   here must use paper/mist, never slate or teal — see the contrast notes
   at the top of this file. */
.status-next__heading { margin-top: var(--space-5); }

.status-questions {
  max-width: var(--measure);
  padding-left: 1.25rem;
  margin-top: var(--space-2);
}

.status-questions li {
  margin-top: var(--space-1);
}

.status-questions li::marker {
  color: var(--mint);
  font-variant-numeric: tabular-nums;
}

/* A work item that visibly stalls in each gap (Task 10). Purely
   illustrative — the token is aria-hidden and the section's prose already
   says what it means, so a visitor who never sees it moving loses nothing.

   Coral, not mint. The token is the page's third and last coral, and it
   means exactly what the other two mean: work that is waiting on a person.
   A parcel of work that leaves Orders, runs down the wire and stops dead
   because a human has to carry it the rest of the way IS that, drawn. Mint
   would have said the opposite — mint is what the product does by itself.

   The element is the full width of the stem and the visible 9px dot is its
   ::before, so `translateX(42%)` is 42% OF THE STEM. Sized directly, a
   percentage translate would have been 42% of the dot's own 9px — under
   4px of travel — which is the trap this two-element shape exists to
   avoid. It also keeps the whole effect on transform and opacity, per the
   rule the rest of this file follows.

   It starts parked AT the stall point rather than at the beginning, so the
   figure still says "it stops here" for a visitor with reduced motion set,
   a dropped stylesheet-animation, or a first paint before the loop runs. */
.flow__token {
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 9px;
  margin-top: -4.5px;
  transform: translateX(36%);
}

.flow__token::before {
  content: "";
  position: absolute;
  top: 0;
  left: -4.5px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--coral);
}

@media (prefers-reduced-motion: no-preference) {
  .flow__token {
    animation: flow-stall 3.6s var(--ease) infinite;
  }

  /* The second gap runs half a cycle behind the first, so the two read as
     one process moving through the business rather than as a pair of
     synchronised decorations. */
  .flow__gap:nth-of-type(4) .flow__token { animation-delay: 1.8s; }
}

/* 36%, not 100%: it never reaches the next system. That is the whole
   figure, so it is the one number here that is not rounded for looks — the
   "gap" chip is centred at 50% and paints over the stem, so the token has
   to stop short of the chip's own edge or it parks underneath it. The hold
   is longer than
   the travel, because a handoff waiting on a person waits far longer than
   it moves. */
@keyframes flow-stall {
  0% { transform: translateX(0); opacity: 0; }
  12% { transform: translateX(0); opacity: 1; }
  45%, 80% { transform: translateX(36%); opacity: 1; }
  100% { transform: translateX(36%); opacity: 0; }
}

.flow__gap-why {
  text-wrap: pretty;
}

/* ---------------------------------------------------------------------
   SP-C: the same teaching treatment on the figures the page already had.
   Additive, motion-gated, no new colour.
   --------------------------------------------------------------------- */

/* The gap already showed that information stalls. It never said what the
   stalling costs, which is the sentence this whole section is making. */
.flow__gap-why {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--fs-sm);
  color: var(--slate);
  text-align: center;
}

/* A 2px read-progress rail across the sticky header. Decoration: it is
   aria-hidden and duplicates nothing a reader needs, it only says there is
   more of the argument below. .site-header is already position: sticky,
   which makes it the containing block this positions against. */
.progress-rail {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
}

.progress-rail__fill {
  display: block;
  width: 0;
  height: 100%;
  background: var(--mint);
}

/* ---------------------------------------------------------------------
   The tablet band (SP-D).

   The stylesheet had breakpoints at 720, 640 and 480 only, so every width
   from 721 to 1024 — which is every iPad in portrait — silently took the
   full desktop layout with no rule written for it and nothing ever
   rendered there to check. The economics panel's two sliders are the
   component that needs its own sizing there: a label and a value fight
   over a 14rem track well before the 720px cutover.
   --------------------------------------------------------------------- */
@media (max-width: 1024px) {
  /* Two sliders and their readouts in one column each, rather than a
     label and a value fighting over a 14rem track. */
  .field--slider {
    grid-template-columns: 1fr;
  }

  .field--slider output {
    grid-column: 1;
    text-align: left;
  }
}

/* ---------------------------------------------------------------------
   Pitch rebuild (2026-09-30).

   The page this replaces was an essay: one narrow left column inside a
   full-bleed section, eight times over, with every figure captioned and
   footnoted. It read as a document, not a product. What follows is the
   layout system for the rebuilt spine — hero, run canvas, and the
   scannable blocks between them.

   The governing rule: a text column is capped at a readable measure, but
   FIGURES ARE NOT. Anything that carries data spans the full wrap. That
   single change is most of why the old page looked empty at 1440.
   ------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--space-2) var(--space-3);
  background: var(--paper);
  color: var(--ink);
  border-radius: 0 0 var(--radius-sm) 0;
}

.skip-link:focus {
  left: 0;
}

.eyebrow {
  margin: 0 0 var(--space-3);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mint);
}

/* Every section opens the same way — heading plus at most one sentence —
   so the eye learns the rhythm and can skip straight to the figure. The
   measure is deliberately tighter than --measure: a lead that runs the
   full width reads as a paragraph to be studied rather than a caption to
   be glanced at. */
/* A heading and its lead take a shorter line than body copy does. At
   48rem the h2s broke wherever the width ran out — "Others charge you in
   engineers, or in every run." set as one 1000px line — and the leads
   wrapped at a different width again, so no two section openings on the
   page had the same shape. Two measures, stated once, and every section
   opens the same way. */
.section__head {
  max-width: 48rem;
  margin-bottom: clamp(32px, 4vw, 56px);
}

.section__head h2 {
  max-width: var(--measure-head);
  text-wrap: balance;
}

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

.section__head > :last-child {
  margin-bottom: 0;
}

.section__head .lead {
  margin-bottom: 0;
  color: var(--slate);
}

.section--dark .section__head .lead {
  color: var(--mist);
}

/* --- Header ---------------------------------------------------------- */

.site-header__cta {
  flex: 0 0 auto;
}

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

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(32px, 5vw, 72px);
}

.hero__content {
  max-width: 36rem;
}

.hero__title {
  font-size: clamp(2.25rem, 1.5rem + 2.2vw, 3.5rem);
  line-height: 1.06;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.hero .lead {
  color: var(--mist);
  max-width: 32rem;
}

@media (max-width: 960px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .hero__content {
    max-width: none;
  }
}

/* --- Run canvas ------------------------------------------------------
   One frame: a seven-step process executing, and the two monthly costs
   filling beside it. Ships finished; runcanvas.js rewinds and replays it
   once on view. Every state below is reachable with JS off, because the
   markup carries `is-settled` and `is-done` from the server.
   -------------------------------------------------------------------- */

.runcanvas {
  --rc-fill: 0.787%;
  /* The thread inside switches axis on the CANVAS's width, not the
     window's — see the container query below. The hero canvas is ~560px
     wide at a 1440px viewport, which is narrower than the phone breakpoint
     the thread used to key off, so a viewport query left seven columns of
     ~62px each and wrapped "Reserve the items" onto three lines on a
     desktop. Container queries are a smaller ask than color-mix(), which
     this file already depends on in forty places. */
  container-type: inline-size;
  container-name: runcanvas;
  margin: 0;
  padding: clamp(20px, 2.4vw, 32px);
  background: var(--surface-dark);
  border: 1px solid var(--hairline-dark);
  border-radius: var(--radius-lg);
}

.section:not(.section--dark) .runcanvas {
  background: var(--surface);
  border-color: var(--hairline);
}

/* The hero's canvas. The modifier was in the markup with no rule behind
   it, so the card took the full half of the hero grid — 780px wide for a
   thread that is 200px of text, and the emptiness read as a rendering
   fault rather than as space. Capped and centred in its column instead.

   `justify-self` and an explicit `width: 100%`, NOT `margin-inline: auto`.
   Two rules combine here and the combination is a trap. A grid item with
   an auto margin in an axis is not stretch-aligned in that axis — it is
   sized to fit-content and then centred. And .runcanvas is a
   `container-type: inline-size` size container, which by definition
   computes its inline size WITHOUT looking at its contents. So
   fit-content was the padding and nothing else: measured, the hero's
   canvas collapsed to a 61px sliver with the whole thread wrapping one
   word per line. A definite `width: 100%` sidesteps the intrinsic sizing
   entirely, and justify-self does the centring without the auto margin. */
.runcanvas--compact {
  width: 100%;
  max-width: 30rem;
  justify-self: center;
}

.runcanvas--full {
  margin-bottom: clamp(32px, 4vw, 56px);
}

.runcanvas__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  font-size: var(--fs-sm);
}

.runcanvas__title {
  font-weight: 600;
  letter-spacing: 0.01em;
}

.runcanvas__state {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: 0.75rem;
  color: var(--teal);
}

.section--dark .runcanvas__state {
  color: var(--mint);
}

.runcanvas__state::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 0.5em;
  border-radius: 50%;
  background: currentColor;
  vertical-align: middle;
}

.runcanvas.is-running .runcanvas__state::before {
  animation: rc-pulse 0.9s ease-in-out infinite;
}

@keyframes rc-pulse {
  50% { opacity: 0.25; }
}

/* The steps. A run is a sequence, so they are drawn as one connected
   track rather than seven separate chips — the connection is the claim. */
/* --- The invoice thread ----------------------------------------------

   Seven underlined labels in a row said the steps had finished. It never
   said they were a THREAD — that the invoice is one piece of work being
   carried from step to step — which is the claim the whole canvas exists
   to make. So the same arrow language the gap figure uses runs through it:
   a chevron in each seam, pointing at the step it feeds, lighting as the
   run reaches it. In the gap figure an arrow is what stalls; here it is
   what arrives. Two readings of one shape, which is the point of using one
   shape for both.
   -------------------------------------------------------------------- */

.runcanvas__steps {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  column-gap: 18px;
  margin: 0 0 var(--space-5);
  padding: 0;
  list-style: none;
}

.runcanvas__step {
  position: relative;
  padding: var(--space-2) 0 0;
  border-top: 2px solid color-mix(in srgb, currentColor 16%, transparent);
  font-size: 0.8125rem;
  line-height: 1.25;
  color: inherit;
  opacity: 0.4;
  transition: opacity var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

/* The seam chevron. It belongs to the step it points AT, so it lights on
   the same class change that lights the step — no second state to keep in
   sync with runcanvas.js, and no rule that has to look backwards up the
   sibling chain at the step before it. */
.runcanvas__step + .runcanvas__step::before {
  content: "";
  position: absolute;
  top: -5px;
  left: -13px;
  width: 0;
  height: 0;
  border-left: 6px solid currentColor;
  border-block: 4px solid transparent;
  color: color-mix(in srgb, currentColor 16%, transparent);
  transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.runcanvas__step.is-done {
  opacity: 1;
  border-top-color: var(--teal);
}

.runcanvas__step.is-done + .runcanvas__step::before,
.runcanvas__step.is-done::before {
  color: var(--teal);
}

.section--dark .runcanvas__step.is-done {
  border-top-color: var(--mint);
}

.section--dark .runcanvas__step.is-done + .runcanvas__step::before,
.section--dark .runcanvas__step.is-done::before {
  color: var(--mint);
}

/* The head of the thread: a filled dot on the step currently executing,
   and a nudge forward on the chevron feeding it, so the eye can see WHERE
   the work is rather than only how far it has got. */
.runcanvas__step.is-active::after {
  content: "";
  position: absolute;
  top: -5px;
  left: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--teal);
}

.section--dark .runcanvas__step.is-active::after {
  background: var(--mint);
}

@media (prefers-reduced-motion: no-preference) {
  .runcanvas__step.is-active::before {
    transform: translateX(3px);
  }
}

/* --- The thread when the canvas is narrow -----------------------------
   The seven steps used to pair off into two columns below 720px of
   VIEWPORT, which broke the sequence into a reading order nobody follows
   and left "Close the order" orphaned in a row of its own — and did
   nothing at all for the hero's canvas, which is that narrow on a desktop.
   One column, the rail down the left, the chevrons rotated to point at the
   next step down it, whenever the canvas itself is under 34rem.
   -------------------------------------------------------------------- */
@container runcanvas (max-width: 34rem) {
  .runcanvas__steps {
    grid-template-columns: minmax(0, 1fr);
    padding-left: var(--space-4);
  }

  .runcanvas__step {
    padding: var(--space-1) 0 var(--space-3);
    border-top: none;
    border-left: 2px solid color-mix(in srgb, currentColor 16%, transparent);
    padding-left: var(--space-3);
  }

  .runcanvas__step:last-child {
    padding-bottom: 0;
    /* The rail stops with the work, rather than trailing past the last
       step into nothing. */
    border-left-color: transparent;
  }

  .runcanvas__step.is-done {
    border-top-color: transparent;
    border-left-color: var(--teal);
  }

  .section--dark .runcanvas__step.is-done {
    border-left-color: var(--mint);
  }

  .runcanvas__step:last-child.is-done {
    border-left-color: transparent;
  }

  .runcanvas__step + .runcanvas__step::before {
    top: -7px;
    left: -6px;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 6px solid currentColor;
    border-bottom: none;
  }

  .runcanvas__step.is-active::after {
    top: 4px;
    left: -5px;
  }

  @media (prefers-reduced-motion: no-preference) {
    .runcanvas__step.is-active::before {
      transform: translateY(3px);
    }
  }
}

/* The two meters. The agent's track is always full — it is the reference
   the other is read against — and DsEarn's is set to the true ratio by
   economics-views.js, which at the default is under one percent. The
   minimum width keeps that sliver visible without overstating it; the
   dollar figure beside it carries the precision the bar cannot. */
.runcanvas__meters {
  display: grid;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.runcanvas__meter {
  display: grid;
  grid-template-columns: minmax(7rem, 11rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
}

.runcanvas__meter-label {
  font-size: var(--fs-sm);
  color: var(--slate);
}

.section--dark .runcanvas__meter-label {
  color: var(--mist);
}

.runcanvas__track {
  height: 14px;
  border-radius: 3px;
  background: color-mix(in srgb, currentColor 8%, transparent);
  overflow: hidden;
}

.runcanvas__track > i {
  display: block;
  height: 100%;
  border-radius: 3px;
  transition: width 0.5s cubic-bezier(0.22, 0.75, 0.3, 1);
}

.runcanvas__track--agent > i {
  width: 100%;
  background: var(--coral);
}

.runcanvas__track--dsearn > i {
  width: var(--rc-fill);
  min-width: 4px;
  background: var(--teal);
}

.section--dark .runcanvas__track--dsearn > i {
  background: var(--mint);
}

.runcanvas__meter-value {
  font-family: var(--font-heading);
  font-size: var(--fs-md);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
  min-width: 5.5ch;
}

.runcanvas__verdict {
  margin: 0;
  font-size: var(--fs-body);
  color: var(--slate);
}

.section--dark .runcanvas__verdict {
  color: var(--mist);
}

.runcanvas__verdict b {
  font-family: var(--font-heading);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--teal);
}

.section--dark .runcanvas__verdict b {
  color: var(--mint);
}

/* In the full canvas the multiple IS the section's headline figure, so it
   is set at display scale rather than run into a sentence. */
.runcanvas__verdict--hero {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid color-mix(in srgb, currentColor 12%, transparent);
}

.runcanvas__verdict--hero b {
  font-size: clamp(2.75rem, 2rem + 3vw, 4rem);
  line-height: 1;
  letter-spacing: -0.02em;
}

.runcanvas__controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid color-mix(in srgb, currentColor 12%, transparent);
}

/* --- Two-column split ------------------------------------------------ */

.split {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(32px, 4vw, 64px);
  align-items: start;
}

@media (max-width: 860px) {
  .split {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- Stages (describe / build / run / improve) ------------------------ */

.stages {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-5);
}

.stages__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
}

.stages__num {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
  font-family: var(--font-heading);
  font-size: var(--fs-body);
  font-weight: 600;
}

.stages__item h3 {
  margin: 0 0 var(--space-1);
  font-size: var(--fs-md);
}

.stages__item p {
  margin: 0;
  font-size: var(--fs-body);
}

/* --- App shot: the product, drawn in markup -------------------------- */

.appshot {
  margin: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--mint) 20%, transparent);
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.6);
}

.appshot__chrome {
  display: flex;
  gap: 6px;
  padding: 10px 12px;
  background: color-mix(in srgb, var(--ink) 8%, var(--paper));
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
}

.appshot__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 20%, transparent);
}

.appshot__body {
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
}

.appshot__rail {
  padding: var(--space-3);
  background: var(--ink);
}

.appshot__brand {
  margin: 0 0 var(--space-3);
  font-family: var(--font-heading);
  font-weight: 700;
  color: var(--paper);
  font-size: var(--fs-body);
}

.appshot__rail-group {
  margin: var(--space-3) 0 var(--space-1);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mist);
}

.appshot__rail-item {
  margin: 0 0 4px;
  padding-left: var(--space-2);
  border-left: 2px solid transparent;
  font-size: 0.8125rem;
  color: var(--paper);
}

.appshot__rail-item.is-current {
  border-left-color: var(--mint);
  font-weight: 600;
}

.appshot__main {
  padding: var(--space-4);
  min-width: 0;
}

.appshot__kicker {
  margin: 0 0 var(--space-3);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--slate);
}

.appshot__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
}

.appshot__name {
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--ink);
}

.appshot__badge {
  flex: 0 0 auto;
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--teal);
}

.appshot__badge--wait {
  color: var(--coral);
}

.appshot__stats {
  display: flex;
  gap: var(--space-5);
  margin: var(--space-2) 0 var(--space-4);
}

.appshot__stats dt {
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--slate);
}

.appshot__stats dd {
  margin: 2px 0 0;
  font-family: var(--font-heading);
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink);
}

.appshot figcaption {
  padding: var(--space-3) var(--space-4);
  background: var(--ink);
  color: var(--mist);
  font-size: var(--fs-sm);
}

@media (max-width: 480px) {
  .appshot__body {
    grid-template-columns: minmax(0, 1fr);
  }

  .appshot__rail {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: baseline;
  }

  .appshot__rail-group {
    display: none;
  }

  .appshot__rail-item {
    margin: 0;
    border-left: 0;
    padding-left: 0;
  }

  .appshot__stats {
    gap: var(--space-4);
  }
}

/* --- Pull quote ------------------------------------------------------ */


/* --- Stat row -------------------------------------------------------- */

.statrow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.statrow__item {
  padding-top: var(--space-3);
  border-top: 2px solid color-mix(in srgb, currentColor 18%, transparent);
}

.statrow__item--us {
  border-top-color: var(--teal);
}

.statrow__value {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--fs-xl);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.statrow__label {
  margin: var(--space-1) 0 0;
  font-size: var(--fs-sm);
  color: var(--slate);
}

.section--dark .statrow__label {
  color: var(--mist);
}

.statrow--dark .statrow__item {
  border-top-color: color-mix(in srgb, var(--mint) 40%, transparent);
}

/* --- Footnote disclosure --------------------------------------------- */

.footnote {
  margin-top: var(--space-4);
  max-width: var(--measure);
  font-size: var(--fs-sm);
}

.footnote > summary {
  cursor: pointer;
  color: var(--teal);
  font-weight: 500;
}

.section--dark .footnote > summary {
  color: var(--mint);
}

.footnote p {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-sm);
  color: var(--slate);
}

.section--dark .footnote p {
  color: var(--mist);
}

/* --- The statement band ----------------------------------------------

   One sentence, alone, on mint. Three of these now carry the page's claims;
   each one used to be the last paragraph of a section, which is where a
   claim goes to be missed — the final [data-reveal] block in a section
   arrives exactly when the reader has decided the section is over and
   started scrolling. The band carries no other content on purpose: the
   pause IS the component.

   Mint, not paper and not ink. A band always lands between a light section
   and a dark one, so any band painted like either merges with one of its
   neighbours — measured, teal-deep against ink is 1.83:1, which is no
   boundary at all. Mint is the page's one saturated colour and reads as a
   different surface from both: --ink on mint is 10.81:1 for the text, and
   the band against a --section--dark neighbour is the same 10.81:1. Against
   paper the luminance step is only 1.58:1, but the hue shift across a
   full-bleed field is what separates them there, not luminance.
   -------------------------------------------------------------------- */

.statement-band {
  padding-block: clamp(56px, 6vw, 96px);
  background: var(--mint);
}

.statement {
  max-width: 30ch;
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--fs-xl);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--ink);
  text-wrap: balance;
}

/* The band is the one place on the page where a paragraph is the whole
   content, so it carries the wrap's measure rather than the body measure —
   `p { max-width: 62ch }` would otherwise win and the sentence would run
   the full column. */
.statement-band .wrap > .statement {
  max-width: 30ch;
}

@media (max-width: 620px) {
  .statement {
    max-width: none;
    font-size: var(--fs-lg);
  }
}

/* --- Reasons grid ---------------------------------------------------- */

.reasons--grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-5);
}

.reasons--tight {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--space-4);
}

/* --- Why we win ------------------------------------------------------ */

.why-win {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-5);
  margin-top: clamp(32px, 4vw, 56px);
}

.why-win__item h3 {
  margin: 0 0 var(--space-1);
  font-size: var(--fs-md);
}

.why-win__item p {
  margin: 0;
}

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

.tiers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-4);
}

.tier {
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
}

.tier__kicker {
  margin: 0;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--slate);
}

.tier__value {
  margin: var(--space-2) 0 0;
  font-family: var(--font-heading);
  font-size: var(--fs-xl);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.tier__unit {
  margin: 4px 0 var(--space-3);
  font-size: var(--fs-sm);
  color: var(--slate);
}

.tier__note {
  margin: 0;
  font-size: var(--fs-body);
}

.pricing__note {
  margin: clamp(28px, 3vw, 44px) 0 0;
  max-width: var(--measure);
  color: var(--slate);
}

/* --- Two doors ------------------------------------------------------- */

.doors {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
  gap: clamp(32px, 4vw, 64px);
  /* `start`, deliberately. The waitlist form is five fields tall and the
     founders' card is four lines; stretching the card to match moved the
     emptiness from under the card to INSIDE it, which reads as a panel
     that failed to load rather than as space. A short card is a short
     card. */
  align-items: start;
}

@media (max-width: 860px) {
  .doors {
    grid-template-columns: minmax(0, 1fr);
  }
}

.door__kicker {
  margin: 0;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mint);
}

.door h3 {
  margin: var(--space-2) 0 var(--space-2);
  font-size: var(--fs-lg);
}

.door--quiet {
  display: flex;
  flex-direction: column;
  padding: var(--space-5);
  border: 1px solid var(--hairline-dark);
  border-radius: var(--radius-lg);
  background: var(--surface-dark);
}

.door--quiet .door__aside {
  padding-top: var(--space-2);
}

.door__contacts {
  list-style: none;
  margin: var(--space-4) 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}

.door__contacts a {
  display: block;
  font-weight: 500;
}

.door__contacts span {
  display: block;
  font-size: var(--fs-sm);
  color: var(--mist);
}

.door__aside {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--mist);
}

/* --- Mobile conversion bar -------------------------------------------
   The header has no room for a CTA at phone widths and the page is
   several screens long, so the only route to the waitlist was to scroll
   the whole thing. aria-hidden + tabindex=-1 because the same link is
   already in the header and the footer: this is a visual shortcut, not a
   third copy in the accessibility tree or the tab order.
   -------------------------------------------------------------------- */

.mobilebar {
  display: none;
}

@media (max-width: 720px) {
  .mobilebar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 40;
    display: block;
    padding: var(--space-2) var(--space-3)
             calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
    background: color-mix(in srgb, var(--ink) 92%, transparent);
    backdrop-filter: blur(8px);
    border-top: 1px solid color-mix(in srgb, var(--mint) 18%, transparent);
  }

  .mobilebar .btn {
    width: 100%;
    justify-content: center;
  }

  /* Stowed while the hero's own capture field is still on screen — see
     motion.js. Moved rather than hidden, so the bar slides out of the way
     instead of blinking, and `visibility` rather than `display` so the
     transition has something to run on. pointer-events goes too: a bar
     parked off-screen must not still be taking taps at the bottom edge. */
  .mobilebar {
    transition: transform var(--dur) var(--ease),
      visibility 0s linear var(--dur-press);
  }

  .mobilebar.is-stowed {
    transform: translateY(100%);
    visibility: hidden;
    pointer-events: none;
  }

  /* Clear the bar so the footer's last line is never trapped under it. */
  .site-footer {
    padding-bottom: 5rem;
  }
}

/* --- Narrow-screen navigation ----------------------------------------
   No hamburger: a disclosure menu is one more thing to break in front of
   an investor, and the link set is short. Below 900px the links become a
   single scrollable row that keeps its 44px tap targets, and the header
   CTA stays put at every width.
   -------------------------------------------------------------------- */

@media (max-width: 900px) {
  .site-header__inner {
    flex-wrap: nowrap;
    gap: var(--space-3);
  }

  .site-nav {
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .site-nav::-webkit-scrollbar {
    display: none;
  }

  .site-nav__links {
    flex-wrap: nowrap;
    gap: var(--space-4);
  }

  .site-nav__links a {
    white-space: nowrap;
  }
}

@media (max-width: 620px) {
  .site-nav {
    display: none;
  }
}

/* --- Narrow-screen typography ----------------------------------------
   The old page set body copy at --fs-body-lg everywhere, which at 390px
   put roughly six words on a line and ran the page to 13,000px. Dropping
   the lead to body size at phone widths is worth about two screens.
   -------------------------------------------------------------------- */

@media (max-width: 620px) {
  .lead {
    font-size: var(--fs-body);
  }

  .runcanvas__meter {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-1) var(--space-3);
  }

  .runcanvas__meter-label {
    grid-column: 1;
    grid-row: 1;
  }

  .runcanvas__meter-value {
    grid-column: 2;
    grid-row: 1;
  }

  .runcanvas__track {
    grid-column: 1 / -1;
    grid-row: 2;
  }
}

/* --- Narrow-screen density -------------------------------------------
   Measured at 390px: the rebuilt page still ran to 12,542px because every
   auto-fit grid on it collapses to ONE column there — a 13rem minimum
   cannot fit twice in 342px of content width, so nine short stat blocks,
   four one-line reasons and three price tiles each took a full-width row
   of their own. Short, scannable items pair up instead; only the ones
   whose text genuinely needs the width stay full-bleed.
   -------------------------------------------------------------------- */

@media (max-width: 620px) {
  .reasons--tight {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3) var(--space-4);
  }

  /* Both stat rows on this page carry THREE items, so pairing them up left
     the third alone in a half-width cell with an empty cell beside it —
     a hole, in the middle of the section that states the company's goals.
     One column of three, each a figure with its label set beside it rather
     than under it, is shorter than the two-column version was and has no
     orphan. */
  .statrow {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
  }

  .statrow__item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline;
    gap: var(--space-3);
  }

  .statrow__value {
    font-size: var(--fs-lg);
  }

  .statrow__label {
    margin: 0;
  }

  /* Three price tiles stack, but without the card padding doubling the
     height they cost. */
  .tier {
    padding: var(--space-3);
  }

  .tiers {
    gap: var(--space-3);
  }

  /* The four stages are a sequence and keep their order, but the numbered
     disc and its text share a line's worth of leading rather than sitting
     in a tall two-row block. */
  .stages {
    gap: var(--space-4);
  }

  .stages__num {
    width: 1.75rem;
    height: 1.75rem;
    font-size: var(--fs-sm);
  }

  .section__head {
    margin-bottom: var(--space-5);
  }

  .why-win,
  .reasons--grid {
    gap: var(--space-4);
  }

  .door--quiet {
    padding: var(--space-4);
  }
}

/* The waitlist form is the page's one conversion and stays legible, but
   five stacked label-over-input pairs is 600px of phone. The two short
   identity fields share a row. */
@media (max-width: 620px) {
  .waitlist-form {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
  }

  .waitlist-form > .field:first-child,
  .waitlist-form > .field:nth-child(4),
  .waitlist-form > .field:nth-child(5),
  .waitlist-form > .form-status,
  .waitlist-form > .waitlist-form__submit,
  .waitlist-form > .hp {
    grid-column: 1 / -1;
  }
}
