/* ═══════════════════════════════════════════════════════════════════════════
   CrewDocket marketing site — CARBON.

   SOURCE OF TRUTH: v2/src/app.css (the app's token sheet, "THIS FILE IS THE
   LAW"). Every value in the token block below was READ from that file on
   2026-08-02 and copied verbatim, with the app.css line noted. This sheet
   does NOT import from v2 — the site and the app share a design language,
   not a codebase — so drift is possible and this header is how it stays
   visible. If a value here disagrees with app.css, app.css is right.

   THE LAW, carried over without exception (docs/design-system.md):
     · Surfaces are FLAT. Separation is fill-step + hairline. Never a
       gradient, never a glow, never a halo.
     · ONE cobalt accent. There is no second accent colour on this site.
     · ZERO charts, zero sparklines, zero plots.
     · Shadows live on overlays only. A resting surface gets a hairline.
     · Sentence case everywhere. Uppercase survives nowhere.
     · Weight never exceeds 650.
     · Figures are tabular.
     · No emoji, no stock photography, no hand-drawn fake dashboards.
     · Default section is an OPEN SECTION: content on the canvas with a
       hairline rule, not a box. Card chrome is rationed.

   STATED DEVIATIONS FROM THE APP, and why (a marketing page is not a
   cockpit; these are deliberate, not drift):
     1. Body type is 17px/1.55, not the app's 14px/1.45. The buyer reads this
        standing in a driveway in daylight at arm's length, not seated at a
        2552px monitor. docs/marketing-site.md §4.
     2. The type ramp gains --t-h1 (34px phone / 56px at 1100), above the
        app's --t-3xl. A landing headline is the one place size does the work.
        Exact values from docs/marketing-site.md §4.
     3. Three breakpoints — 0 / 768 / 1100 — not the app's 960 / 720 pair.
        A marketing page has no chrome to collapse. §4: "Breakpoints: three,
        and only three."
     4. Semantic colour (green/amber/red/gold) is NOT ported. Nothing on this
        site reads it, and app.css's own meta-rule is "A TOKEN EXISTS ONLY IF
        SOMETHING READS IT." Omitting them also makes the one-accent law
        unbreakable by accident.
     5. No web font is loaded. app.css names Geist and v2/index.html pulls it
        from Google Fonts render-blocking — 300–600ms of nothing on rural
        LTE, which §4 calls out as a defect. The stack below still NAMES
        Geist first, so if a self-hosted WOFF2 ever lands in site/static/
        fonts/ this sheet picks it up with no edit. Until then the system
        stack renders instantly and costs zero bytes.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ─── Fill ladder — four steps, and only four. app.css:62-65 ─────────── */
  --bg-0: #09090B;   /* canvas         — true neutral near-black          */
  --bg-1: #121215;   /* first surface  — panels, cards, list surfaces     */
  --bg-2: #17171B;   /* raised surface — hero panel, menus, popovers      */
  --bg-3: #1D1D21;   /* highest step   — inputs, hover fills, chips       */

  /* ─── Hairlines. app.css:93-97 ───────────────────────────────────────── */
  --line:       rgba(255, 255, 255, 0.07);
  --line-2:     rgba(255, 255, 255, 0.11);
  --line-3:     rgba(255, 255, 255, 0.16);
  --panel-line: rgba(255, 255, 255, 0.08);
  --edge:       rgba(255, 255, 255, 0.05);

  /* ─── Ink ramp. app.css:103-116. Measured against #09090B in the app's
     2026-07-30 a11y pass: ink-0 18.3:1 · ink-1 12.8:1 · ink-2 7.6:1 ·
     ink-3 5.0:1. All clear AA for normal text on every fill step.
     Sunlight rule for this site: nothing load-bearing goes below ink-1.
     ink-2 carries labels; ink-3 carries the footer's quietest line and
     nothing a buyer has to read to decide. ───────────────────────────── */
  --ink-0: #f5f5f8;
  --ink-1: rgba(245, 245, 248, 0.84);
  --ink-2: rgba(245, 245, 248, 0.64);
  --ink-3: rgba(245, 245, 248, 0.50);

  /* ─── The one accent. app.css:150-151, :201-212.
       --accent       #3b82f6  every RAIL, DOT, SEAM, UNDERLINE, cobalt label
       --accent-solid #2563eb  any SOLID fill under a white label (5.17:1 AA)
       --accent-2     #60a5fa  focus ring ONLY (7.8:1 on canvas). Not a
                               second accent — a luminance step, because a
                               ring drawn in --accent beside a cobalt button
                               measured 1.45:1 against the thing it outlined.
     ─────────────────────────────────────────────────────────────────────── */
  --accent:             #3b82f6;
  --accent-2:           #60a5fa;
  --accent-solid:       #2563eb;
  --accent-solid-hover: #2b6fd9;
  --accent-solid-press: #1d4ed8;
  --accent-fill:        color-mix(in srgb, var(--accent) 12%, transparent);
  --accent-edge:        color-mix(in srgb, var(--accent) 38%, transparent);
  --focus-ring:         var(--accent-2);

  /* ─── Type. Stack from app.css:707-709, minus the network request. ───── */
  --font-sans: 'Geist', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
               system-ui, Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* Ramp — app.css:232-249 for --t-sm … --t-3xl; --t-h1 is site-only (§4). */
  /* 14px is the FLOOR. Nothing on this site renders below it — the buyer
     reads this standing outside in daylight, and the quiet steps carry their
     quietness in ink and weight, which is the law anyway. --t-sm is kept as a
     name because faq / pricing / privacy / terms all read it. */
  --t-sm:   14px;
  --t-base: 14px;
  --t-md:   16px;
  --t-body: 17px;   /* site body — see deviation 1 */
  --t-lg:   19px;
  --t-xl:   23px;
  --t-2xl:  28px;
  --t-3xl:  36px;
  --t-h1:   34px;   /* phone. Steps to 44 at 768, 56 at 1100 */

  /* ─── Spacing. app.css:252-253 ───────────────────────────────────────── */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px;
  --s-5: 20px; --s-6: 28px; --s-7: 36px; --s-8: 48px; --s-9: 64px;

  /* ─── Radii. app.css:257. Panels 14, controls 10 — machined, not bubbly. */
  --r-sm: 6px; --r-md: 10px; --r-lg: 12px; --r-xl: 14px; --r-pill: 999px;

  /* ─── ELEVATION — how a thing sits ABOVE the page without a shadow ─────
     THE PROBLEM. CARBON forbids the two devices every other site reaches for:
     a gradient and a blurred drop shadow. Take both away and most pages
     collapse into one flat slab, which is exactly what this one was: two
     fills across eleven thousand pixels, so every section read as the same
     sheet of paper.

     WHAT IS LEFT IS ENOUGH, and it is three things working together at two
     distances:

       FAR FIELD (across the room, where a 1px line is invisible)
         · the fill step. bg-0 canvas → bg-1 surface → bg-2 raised → bg-3
           control. Four steps is a real ladder if you actually climb it.
         · the air around the object. A raised thing has more margin than a
           flush thing. That reads at ten feet; a hairline does not.
       NEAR FIELD (arm's length, where the craft is)
         · the bevel below.

     THE BEVEL. These are HAIRLINES, not shadows — 1px, zero blur, zero
     spread, drawn inset. --panel-edge in app.css:115 is the same declaration
     and the same reasoning. Light in this room comes from above, so the top
     edge of a raised object catches it (--e-catch) and the bottom edge is the
     object's own underside (--e-seat). A recessed object is the exact inverse
     (--e-sunk): dark at the top, catch-light along the bottom. That inversion
     is the whole reason a well reads as sunk and a card reads as raised at
     the identical fill.

     app.css:116 also declares --panel-shadow (0 1px 2px, a 2px contact
     shadow). It is deliberately NOT ported. The law here is "a resting
     surface gets a hairline", the site has no overlays at all, and a blurred
     edge is the one thing a 1px line cannot be mistaken for.
     ─────────────────────────────────────────────────────────────────────── */
  --e-catch: inset 0 1px 0 rgba(255, 255, 255, 0.045);
  --e-seat:  inset 0 -1px 0 rgba(0, 0, 0, 0.45);
  --e-sunk:  inset 0 1px 0 rgba(0, 0, 0, 0.45),
             inset 0 -1px 0 rgba(255, 255, 255, 0.03);
  /* The response vocabulary. app.css:715-723 is the same pair — a 4% wash and
     a 2px cobalt rail — and it is the house hover for every row in the
     product. Nothing in it moves. */
  --e-rail:     inset 2px 0 0 var(--accent);
  --fill-hover: rgba(255, 255, 255, 0.04);
  --fill-press: rgba(255, 255, 255, 0.075);
  /* The focus ring's inner gap, painted in canvas so the ring reads the same
     on bg-0, on a bg-2 card and on a cobalt button. See FOCUS below. */
  --focus-gap:  0 0 0 2px var(--bg-0);

  /* THE ONE REAL SHADOW ON THIS SITE, and it exists so the law stays true
     rather than merely unbroken. "Shadows live on overlays only" is only a
     meaningful rule if there is an overlay to prove it on. There is exactly
     one on this page: the skip link, which is absolutely positioned above the
     header and is the first thing a keyboard user ever sees. Nothing else may
     read this token — a resting surface takes the bevel above.
     app.css:318 --shadow-2, verbatim. */
  --shadow-overlay: 0 1px 0 rgba(255, 255, 255, 0.04) inset,
                    0 6px 20px -8px rgba(0, 0, 0, 0.60);

  /* ─── Motion. --ease-out / --d-fast / --d-norm are app.css:274-286.
     The entrance tokens are site-only, and app.css has none because a cockpit
     has no entrance: a tool opened forty times a day must never make you wait
     for it. A page a stranger reads once is the opposite case — a page where
     everything is simply already there reads as a document, and a page where
     things arrive reads as something someone made.

     EVERY NUMBER HERE IS A TOKEN so the budget is countable in one place.
     The whole budget, per element, once: 380ms of arrival and at most 220ms
     of queue behind it. Nothing repeats, nothing loops, nothing plays twice.
     --ease-enter has a long tail and NO overshoot — a bounce is a toy. ──── */
  --ease-out:   cubic-bezier(0.22, 1, 0.36, 1);
  --ease-enter: cubic-bezier(0.16, 1, 0.30, 1);
  --d-fast: 120ms;      /* direct feedback under a finger or a cursor */
  --d-norm: 220ms;
  --d-enter:    380ms;  /* one thing arriving */
  --d-stagger:  55ms;   /* between siblings — capped at 4 steps, see MOTION */
  --enter-lift: 10px;   /* how far it travels. Ten pixels, not forty. */

  /* ─── Safe areas. app.css:382-385. THE INSET LAW applies to any element
     that touches a viewport edge. On a desktop browser these resolve to 0px,
     which is exactly why a resized window can never verify them —
     docs/mobile-why-it-keeps-missing.md. ───────────────────────────────── */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  /* ─── Site layout. The app caps at 1760 because it renders dashboards;
     a page of prose caps far tighter so the measure stays under 68
     characters at every width (§4). ───────────────────────────────────── */
  --site-max:    1080px;
  /* MEASURED, not estimated. This token was 34em and the comment beside it
     claimed "≈ 62–68 characters" — the intent was right and the number did
     not deliver it. Counting real rendered characters per real rendered line
     in Chrome on 2026-08-03, across every prose block on index.html at 768 /
     900 / 1280 / 1440:

         34em → median 73, p95 79, longest 84.  16 lines over 75.
         32em → median 70, p95 75, longest 78.   2 lines over 75.
         31em → median 66, p95 73, longest 76.   1 line  over 75.
         30em → median 64, p95 70, longest 75.   0 lines over 75, 0 under 45.

     30em is the first value where the WHOLE distribution sits inside the
     45–75 band, and it lands the median on 64 — a hair under the classic 66.
     Long measure is the most common way a text-heavy page reads cheap, and
     this page is almost entirely text.

     It changes nothing on a phone: 30em is 510px at --t-body and the gutters
     already hold prose to 335px at 375, so the cap never binds below 768.
     Re-measure with site/scripts/verify-assets.mjs's sibling harness if the
     font stack ever changes — character width is a property of the FONT, so
     shipping a webfont invalidates this number. */
  --measure-max: 30em;
  --gutter:      20px;
}

/* ═══ RESET ═══════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }

html {
  color-scheme: dark;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  scroll-behavior: smooth;
  /* THE OVERSCROLL FILL. The background of the ROOT element — not body's — is
     what paints the rubber-band strip above the header and below the footer
     when a thumb drags past the end of the document, and the strip behind a
     desktop scrollbar track. Only body carried a fill, so both of those
     surfaces fell to the UA's own dark-scheme grey: a lighter, bluer,
     off-brand band that appears every single time anyone flicks this page on
     a phone. One declaration, and the page has no visible end. */
  background-color: var(--bg-0);
}

body {
  margin: 0;
  background: var(--bg-0);
  color: var(--ink-1);
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: 1.55;
  font-weight: 400;
  overflow-x: hidden;
  /* CARBON has no ambient backdrop. The canvas is a pure, still surface. */
}

h1, h2, h3, h4 {
  margin: 0;
  color: var(--ink-0);
  font-weight: 600;          /* never above 650 */
  letter-spacing: -0.015em;
  text-wrap: balance;
}
p  { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg { max-width: 100%; height: auto; display: block; }

a {
  color: var(--accent);
  text-decoration: none;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: color var(--d-fast) var(--ease-out);
}
a:hover { text-decoration: underline; }
/* THE TAP RECEIPT. html sets -webkit-tap-highlight-color: transparent, which
   removes the grey flash mobile browsers draw on every tapped link and puts
   nothing in its place — so an inline link in running prose currently gives a
   phone user no signal at all that the tap registered. --accent-2 is the same
   cobalt one luminance step up (it is the focus-ring token, and this is the
   same job: say "this one, right here"). Wrapped in :where() so its
   specificity stays at the level of `.btn-primary`'s own colour rather than
   above it — an un-wrapped `a:active` outranks every class on the page and
   would turn the primary button's white label cobalt on press. */
:where(a):active { color: var(--accent-2); }

/* Kill the 350ms double-tap-zoom wait on everything control-shaped so taps
   fire the moment the finger lifts. Panning and pinch-zoom survive. */
:where(a, button, [role="button"], summary, label, input, select, textarea) {
  touch-action: manipulation;
}

/* ═══ FOCUS ═══════════════════════════════════════════════════════════════
   Visible, always, on every interactive thing. The 2px offset puts canvas
   between ring and control so the pair reads 7.8:1 / 3.9:1 on both sides
   (app.css:207-212). :focus-visible only — a mouse click does not draw it,
   a Tab key always does.

   THE GAP IS PAINTED, and this is the part that was missing. `outline-offset`
   leaves a 2px transparent gap, so the ring's inner neighbour is whatever
   happens to be behind the control: canvas behind a link, bg-1 behind a card's
   button, cobalt behind the primary CTA — three different rings depending on
   where you tabbed to, and against the cobalt button the gap disappeared
   entirely and the ring read as a fat halo. --focus-gap fills that gap with
   canvas on every surface, so ONE ring is drawn everywhere on the site and it
   measures the same everywhere: 7.8:1 outward to bg-0, 3.9:1 inward to the
   canvas gap.

   NOTHING FADES IN. A focus ring is the answer to a key that was just
   pressed; a 120ms ramp on it is 120ms of a keyboard user not knowing where
   they are. Motion is for arrival, not for orientation. */
:where(a, button, [role="button"], summary, input, select, textarea):focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--r-sm);
  box-shadow: var(--focus-gap);
}

/* THE OTHER HALF OF THE SAME DECISION. scripts/site.js stamps tabindex="-1"
   onto whatever an in-page link points at and moves focus there, so the
   keyboard follows the scroll instead of staying at the top of the document.
   Those targets are SECTIONS AND CONTAINERS, not controls. Engines disagree
   about whether to draw the default ring on a programmatically focused
   tabindex="-1" element, and where one does, clicking "Or talk to me first"
   paints a 2px box around an entire full-width section — which is not focus
   indication, it is a rendering artifact, and it reads as a bug.
   `main:focus` was already handled below; this is the same call applied
   consistently to every element site.js can touch, instead of just the one
   somebody happened to hit first. Real controls are untouched: the rule above
   still fires on every one of them, and the user's next Tab lands on a
   properly outlined control inside the region they just jumped to. */
:where(main, section, article, aside, header, footer, div, dl, ol, ul, p, figure)[tabindex="-1"]:focus {
  outline: none;
}

/* ═══ TOUCH FLOOR ═════════════════════════════════════════════════════════
   44×44 CSS px minimum on every interactive target, with ≥8px between.
   Keyed to INPUT type, not viewport width — an iPad at 1024px is touch-
   primary and fell straight through width-keyed floors in the app
   (app.css:472-495). Bare `a` is excluded because prose links are inline in
   running text and a 44px min-height there blows up paragraph leading;
   .link-lg is the opt-in for standalone prose links.

   (hover: none) ALONE IS NOT ENOUGH, and this is the gap that matters here.
   An iPad with a Magic Keyboard, a Surface, a touchscreen laptop and a phone
   with a Bluetooth mouse all report (hover: hover) — the pointing device
   wins the query — while the screen is still a screen a finger lands on.
   (any-pointer: coarse) asks the only question that matters: is a coarse
   input available AT ALL. Measured 2026-08-03: a 1440px desktop with a mouse
   reports any-pointer:coarse FALSE, so nothing here reaches a real desktop. */
@media (hover: none), (any-pointer: coarse) {
  :where(button, [role="button"], summary, .btn, .link-lg) {
    min-height: 44px;
  }
  :where(.btn, .link-lg) { display: inline-flex; align-items: center; }
}

/* ═══ MOTION — THE FLOOR ══════════════════════════════════════════════════
   No parallax, no scroll-jacking, no count-up animations — §4. A count-up
   on a real number still reads as a fake number.

   THIS AUDIT LINE USED TO READ "exactly two transitions in the whole site"
   and it was accurate on 2026-08-03. It is no longer, and pretending
   otherwise would make this the first comment in the file that lies. What is
   here now, counted 2026-08-03 after the depth-and-motion pass, is:

     · direct feedback on a thing under a cursor or a finger — the button,
       the FAQ chevron, the .rail response, the press fills. --d-fast.
     · ONE entrance per element, 380ms, the first time it scrolls into view
       and never again. The MOTION SYSTEM block below.

   STILL ZERO: parallax, scroll-jacking, count-ups, loops, infinite
   animations, anything that repeats, and anything that moves under a cursor.
   That last one is the load-bearing rule — a card that jumps when the pointer
   crosses it is a card you have to chase, and it is the single most common
   way a hover state makes a page feel cheap rather than alive.

   WHY AN ENTRANCE IS ALLOWED WHEN A HOVER LIFT IS NOT: an entrance happens
   once, to something you have not read yet, and it finishes before you get
   there. A hover lift happens every time you pass, to something you are
   trying to read or click, and it never finishes.

   ── AND WHEN SOMEBODY ASKS FOR LESS ──────────────────────────────────────
   DELAYS ARE FLATTENED TOO, and they were not before. A near-zero duration
   with a 400ms delay still makes someone who asked for less motion sit and
   wait for something to happen; the duration is what they see, the delay is
   what they feel. The negative animation-delay lands a paused animation on
   its final frame rather than its first, so nothing gets stuck mid-gesture.

   THE STILL PAGE IS NOT A DEGRADED PAGE. The entrance is the only thing here
   with a state to be stuck in, and it is built so that its OFF state is the
   page exactly as it shipped before this pass: scripts/site.js checks
   prefers-reduced-motion before it stamps anything, so under reduce nothing
   is ever hidden, there is nothing to reveal, and no element is left at an
   opacity waiting for an observer that will not fire. The guard inside this
   block is the second belt, for the visitor who turns the setting ON while
   the page is already open. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: -0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }

  /* THE SECOND BELT. Flattening a duration to 0.01ms is not enough on its own
     for an entrance: the element still starts at opacity 0 and still needs an
     IntersectionObserver callback to reach opacity 1, so a visitor who
     enables the setting mid-page — or a browser that re-evaluates the query
     after site.js has already stamped html.reveal-on — would be left staring
     at invisible sections that snap in as they scroll. Not a still page: a
     broken one. These two rules put every reveal target at its FINAL state
     unconditionally, which is the page with no entrance in it at all. */
  .reveal-on [data-reveal],
  .reveal-on [data-reveal-seq] > * {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ═══ LAYOUT ══════════════════════════════════════════════════════════════ */

/* The one horizontal frame. Every section's content sits in a .wrap; the
   gutters carry the landscape safe-area insets, because a phone in a truck
   cradle is landscape and has ~47px of cutout on BOTH sides (app.css THE
   INSET LAW, rule 5). Applied once per chain, here and nowhere inside. */
.wrap {
  width: 100%;
  max-width: calc(var(--site-max) + (var(--gutter) * 2));
  margin-inline: auto;
  padding-inline: calc(var(--gutter) + var(--safe-l))
                  calc(var(--gutter) + var(--safe-r));
}

/* An OPEN SECTION: content directly on the canvas, separated by air and a
   hairline. Not a box. Card chrome is rationed to the few objects that earn
   it (docs/design-system.md §8). */
.section {
  padding-block: var(--s-8);
  border-top: 1px solid var(--line);
}
/* Sections that want air instead of a rule. There is no third way — a
   section separates with a solid --line rule or with air (design-system §2).
   The hero deliberately does NOT carry .section: it is the first thing on the
   canvas and has nothing above it to separate from. */
.section--open { border-top: 0; }

.measure { max-width: var(--measure-max); }

/* Skip link — real, keyboard-reachable, and it lands on a focusable main. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--s-3) var(--s-4);
  background: var(--bg-2);
  color: var(--ink-0);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  font-size: var(--t-base);
  font-weight: 500;
  /* The site's only overlay, and therefore the site's only shadow. */
  box-shadow: var(--shadow-overlay);
}
.skip:focus {
  left: calc(var(--gutter) + var(--safe-l));
  top: calc(var(--s-3) + var(--safe-t));
  text-decoration: none;
  /* Composed by hand: the generic :where(...):focus-visible rule sets
     box-shadow to --focus-gap alone, which would drop the overlay shadow off
     the one element that is entitled to it. */
  box-shadow: var(--shadow-overlay), var(--focus-gap);
}

main:focus { outline: none; }

/* ═══ TYPE PRIMITIVES ═════════════════════════════════════════════════════
   Hierarchy comes from colour steps (ink-0 → ink-3) and weight steps
   (600 → 500 → 450), not from caps or bold. */

.h1 {
  font-size: var(--t-h1);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 600;
}
.h2 {
  font-size: var(--t-2xl);
  line-height: 1.15;
  letter-spacing: -0.025em;
  font-weight: 600;
}
.h3 {
  font-size: var(--t-lg);
  line-height: 1.25;
  letter-spacing: -0.02em;
  font-weight: 600;
}

/* The lede — the sentence under a headline. Body size, ink-1, held to
   measure. */
.lede {
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--ink-1);
  max-width: var(--measure-max);
  /* sections.css already sets this on nine of its own prose selectors; the
     shared primitives were the ones left out, so the same paragraph could
     wrap two different ways depending on which sheet had styled it. Measured
     gain on its own is small — one block on the page — but a primitive that
     disagrees with everything built on top of it is the kind of inconsistency
     that accumulates. Unsupported engines ignore it and wrap as before. */
  text-wrap: pretty;
}

/* Quiet label. Sentence case. Never uppercase, never letter-spaced open. */
.label {
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--ink-2);
  letter-spacing: 0.01em;
}

/* Figures are tabular, everywhere, always. */
.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }

/* ═══ THE PROOF RAIL ══════════════════════════════════════════════════════
   The honest-proof line. A 2px cobalt RAIL is sanctioned accent vocabulary
   (app.css:141-149: rails, dots, seams, underlines). This is the only place
   in the shell that carries the accent as ink, and it carries the one claim
   on this page that no competitor can copy: the software runs a real
   business. */
/* SET AT BODY SIZE, NOT ONE STEP DOWN. It was --t-md/1.5 — 16px against the
   17px --t-body of the .lede it sits directly beneath in the hero, and of the
   .flow-p it sits beneath in step one. A 1px difference is not a step; it is
   below the threshold at which anyone perceives a step, so it read as a
   mistake rather than as hierarchy, and a page full of near-misses is exactly
   what "not best in class" feels like. The rail and the ink do the
   differentiating here, which is the law anyway: hierarchy comes from colour
   and weight, not from size. */
.proof {
  border-left: 2px solid var(--accent);
  padding-left: var(--s-4);
  color: var(--ink-1);
  font-size: var(--t-body);
  line-height: 1.55;
  max-width: var(--measure-max);
  text-wrap: pretty;
}
.proof strong { color: var(--ink-0); font-weight: 600; }

/* ═══ CONTROLS ════════════════════════════════════════════════════════════
   Buttons are --r-md rectangles, not pills. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: 0 var(--s-5);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-family: inherit;
  font-size: var(--t-md);
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--d-fast) var(--ease-out),
              border-color var(--d-fast) var(--ease-out),
              box-shadow var(--d-fast) var(--ease-out);
}
.btn:hover { text-decoration: none; }
.btn:active { opacity: 0.9; }

/* THE KEY. A solid cobalt rectangle on a near-black page is a coloured
   rectangle; the same rectangle with a 4.5% white hairline along its top edge
   is a physical key with light falling on it. One inset line, 1px, no blur,
   no spread, no gradient — the same --panel-edge trick app.css:115 uses on
   every panel in the product, spent here on the one control this page is
   built around. It is the cheapest elevation on the site and the most
   visible: it is the thing your eye lands on first. */
.btn-primary {
  background: var(--accent-solid);   /* white label measures 5.17:1 ✓ AA */
  color: #ffffff;
  box-shadow: var(--e-catch);
}
.btn-primary:hover  { background: var(--accent-solid-hover); }
.btn-primary:active { background: var(--accent-solid-press); }
/* The FOCUS block's --focus-gap is set on a :where() selector, so it carries
   the specificity of `:focus-visible` alone — exactly equal to `.btn-primary`,
   which is declared later and would silently win. The result would be a
   primary button that is the ONLY control on the site with no painted focus
   gap, and it is the control keyboard users reach for. Composed explicitly so
   the button keeps its catch-light AND draws the same ring as everything
   else. Same reason, same fix, on .rail below. */
.btn-primary:focus-visible { box-shadow: var(--e-catch), var(--focus-gap); }

.btn-quiet {
  background: transparent;
  border-color: var(--line-2);
  color: var(--ink-0);
}
.btn-quiet:hover { border-color: var(--line-3); background: var(--bg-1); }
/* PRESS, AND WHY IT IS NOT JUST THE HOVER AGAIN. `-webkit-tap-highlight-color:
   transparent` is set on html, which means a tap on a phone currently produces
   NOTHING — no flash, no fill, no acknowledgement of any kind between the
   finger landing and the next page painting. On a slow connection that is a
   second or more of a button that appears not to have worked, and the second
   tap is the one that double-submits. A press fill is not decoration; it is
   the receipt. bg-3 is the ladder's control step, which is what a pressed
   quiet button is. */
.btn-quiet:active { background: var(--bg-3); border-color: var(--line-3); }

/* THE SECOND DOOR, as a link and not a button.
   Every CTA on this page now offers two things: start the product, or talk to
   a person. The second must be findable and must NOT compete -- two buttons
   side by side make a visitor choose before they have decided anything, and
   the design law allows exactly one cobalt accent per view, already spent on
   the primary. So this is a quiet text link with a 44px touch target rather
   than a second button. */
.cta-alt {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink-2);
  font-size: var(--t-md);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  /* The border is declared transparent at rest ON PURPOSE, not just so it can
     appear: it reserves the pixel, so the hairline arriving on hover cannot
     shift the baseline of the label beside it. Nothing moves under a cursor,
     and that includes moving by one pixel. */
  transition: color var(--d-fast) var(--ease-out),
              border-color var(--d-fast) var(--ease-out);
}
.cta-alt:hover  { color: var(--ink-0); border-bottom-color: var(--line-3); }
.cta-alt:active { color: var(--ink-0); border-bottom-color: var(--accent); }
.cta-alt:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; border-radius: 2px; }

/* Stack the two actions on a phone; sit them side by side once there is room. */
.hero-actions { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3); }
@media (min-width: 768px) {
  .hero-actions { flex-direction: row; align-items: center; gap: var(--s-5); }
}

/* The page's primary action on a phone: full width, 52px, generous. §4. */
.btn-lg {
  min-height: 52px;
  width: 100%;
  font-size: var(--t-md);
  font-weight: 500;
}
@media (min-width: 768px) {
  .btn-lg { width: auto; min-width: 18rem; }
}

/* A standalone prose link that must clear the touch floor. */
.link-lg {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--accent);
  font-size: var(--t-md);
  font-weight: 500;
  /* The underline is drawn as a BORDER, not text-decoration, because a border
     can be given a colour that animates and a decoration cannot be given a
     resting position. It sits transparent at rest and takes 120ms to arrive,
     which is the difference between a link that flickers and a link that
     answers. text-decoration is suppressed so the two do not both draw. */
  border-bottom: 1px solid transparent;
  padding-bottom: 1px;
  transition: color var(--d-fast) var(--ease-out),
              border-color var(--d-fast) var(--ease-out);
}
.link-lg:hover {
  text-decoration: none;
  border-bottom-color: var(--accent-edge);
}
/* On a phone there is no hover, so this is the ONLY feedback a link gives
   between the tap and the next page. --accent-2 is a luminance step of the
   same cobalt, not a second colour. */
.link-lg:active { color: var(--accent-2); border-bottom-color: var(--accent); }

/* ═══ HEADER ══════════════════════════════════════════════════════════════
   Deliberately NOT sticky. The above-the-fold budget on a 375×812 screen is
   ~600 CSS px and it buys four things (§S1); a sticky bar spends part of it
   forever. There is also no mobile nav toggle, because there is no nav —
   which is what keeps this page's JS at almost nothing. */
.site-head {
  border-bottom: 1px solid var(--line);
  padding-top: var(--safe-t);   /* EDGE CONTACT PAYS — inset law rule 1 */
}
.head-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  min-height: 60px;
  padding-block: var(--s-3);
}

/* Type-only wordmark with the brand thread as its mark. One solid accent
   hairline — app.css:81-90 flattened the last gradient in the token layer
   and this is the same mark, at the same ink weight. */
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 44px;
  color: var(--ink-0);
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.02em;
  /* THE BLEED, and it costs nothing geometrically. A negative inline margin of
     8px with 8px of inline padding puts the CONTENT box back exactly where it
     was — the tick does not move a pixel and stays flush with the gutter the
     page's prose is aligned to — while the element's FILL now extends 8px past
     the text on both sides. That is the only way to give a bar item a hover
     surface without either shifting it or painting a fill that hugs the
     letters. Used again on .foot-meta a and it is what .rail --bleed does. */
  padding-inline: var(--s-2);
  margin-inline: calc(var(--s-2) * -1);
  border-radius: var(--r-sm);
  transition: background-color var(--d-fast) var(--ease-out);
}
/* The wordmark had a declared hover that resolved to the resting state — a
   rule that exists to do nothing. It reads as a decision until you check it. */
.wordmark:hover  { text-decoration: none; color: var(--ink-0); background: var(--bg-1); }
.wordmark:active { background: var(--bg-2); }
.wordmark::before {
  content: '';
  width: 3px;
  height: 20px;
  border-radius: 2px;
  background: var(--accent);
  flex: none;
}

/* The header CTA appears only where there is room for it. On a phone the
   hero's own button is 300px below and full-width; a second one in the bar
   would be spending the fold budget twice. */
.head-cta { display: none; }
@media (min-width: 768px) {
  .head-cta {
    display: inline-flex;
    min-height: 44px;
    font-size: var(--t-base);
  }
}

/* ═══ HERO ════════════════════════════════════════════════════════════════
   Above the fold on a phone holds EXACTLY four things: the claim, one line
   of proof, one button, and the top edge of a real screenshot. §S1.
   No eyebrow. No announcement strip. No live-count badge. No trust chips.
   No floating CTA, no scroll-progress bar, no nav-dot rail. */
.hero {
  padding-top: var(--s-7);
  padding-bottom: var(--s-8);
}
.hero-stack {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  align-items: flex-start;
}
.hero h1 {
  font-size: var(--t-h1);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 600;
  max-width: 15ch;   /* holds two lines at 34px on a 375px screen */
}
.hero .lede { color: var(--ink-1); }
.hero-actions {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);   /* ≥8px between targets */
  align-items: stretch;
  width: 100%;
  padding-top: var(--s-1);
}

/* The hero screenshot slot. It is EMPTY on purpose and it stays empty until
   a real screenshot exists — see index.html. Nothing draws a fake dashboard
   here. When the demo build ships (docs/marketing-site.md §6), the <img>
   drops in with explicit width/height and no layout shift. */
.hero-shot {
  margin: 0;
  width: 100%;
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  background: var(--bg-2);
  /* A screenshot is a WINDOW ONTO ANOTHER SCREEN, so its frame is the most
     raised object the page will ever hold. The seat line matters more than the
     catch here: it is what stops a bright screenshot from looking like a hole
     cut in the page. */
  box-shadow: var(--e-catch), var(--e-seat);
  overflow: hidden;
}
.hero-shot img { width: 100%; height: auto; display: block; }

/* ═══ CTA SECTION ═════════════════════════════════════════════════════════
   Exactly ONE call to action on this page. §2.1. Card chrome is spent here
   because this is an object, not a region. */
/* Raised, by the ELEVATION rules at the top of this file: one fill step up
   from the canvas, a hairline border, and the bevel pair. See .raise. */
.cta-card {
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  box-shadow: var(--e-catch), var(--e-seat);
  padding: var(--s-6) var(--s-5);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  align-items: flex-start;
}
.cta-fine {
  font-size: var(--t-base);
  color: var(--ink-2);
  max-width: var(--measure-max);
  text-wrap: pretty;
}

/* Hairline-separated rows. No ✓ icons, no green checkmarks, no emoji —
   ink-2 labels, ink-0 values. §5.3. Provided here as shared vocabulary for
   the sections that mount between the hero and the CTA. */
.rows { width: 100%; }
.rows li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  padding-block: var(--s-3);
  border-top: 1px solid var(--line);
  font-size: var(--t-md);
}
.rows li:first-child { border-top: 0; }
.rows .row-k { color: var(--ink-2); }
.rows .row-v { color: var(--ink-0); font-variant-numeric: tabular-nums; }
/* Something we do not have yet, printed next to the things we do. §5.3:
   "Printing your own gaps next to your price is the most persuasive thing
   on the page, because nobody fakes that." */
.rows .row-gap { color: var(--ink-3); }

/* ═══ FOOTER ══════════════════════════════════════════════════════════════ */
.site-foot {
  border-top: 1px solid var(--line);
  padding-top: var(--s-7);
  /* EDGE CONTACT PAYS — the footer is last in the column, so it touches the
     bottom edge and owns that inset (inset law rule 1: "touches the edge" is
     about geometry, not the position property). */
  padding-bottom: calc(var(--s-7) + var(--safe-b));
  color: var(--ink-2);
  font-size: var(--t-base);
}
.foot-stack {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}
.foot-line {
  color: var(--ink-1);
  font-size: var(--t-md);
  line-height: 1.5;
  max-width: var(--measure-max);
  text-wrap: pretty;
}
.foot-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-5);
  color: var(--ink-3);
  font-size: var(--t-sm);
}
/* A standalone contact link, not a link inside a sentence — so it takes the
   44px floor unconditionally. Measured at 375px it was 22px tall. */
.foot-meta a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink-2);
  /* Same zero-cost bleed as .wordmark — the fill extends past the address,
     the address itself does not move. */
  padding-inline: var(--s-2);
  margin-inline: calc(var(--s-2) * -1);
  border-radius: var(--r-sm);
  transition: color var(--d-fast) var(--ease-out),
              background-color var(--d-fast) var(--ease-out);
}
.foot-meta a:hover  { color: var(--ink-0); background: var(--bg-1); text-decoration: none; }
.foot-meta a:active { color: var(--ink-0); background: var(--bg-2); }

/* ═══ SURFACES — THE LADDER, APPLIED ══════════════════════════════════════
   The tokens are at the top of this file; these are the four things you can
   actually put on the page, and there is no fifth. A surface that is none of
   these is an OPEN SECTION — content on the canvas — which is still the
   default and still what most of this page should be. Card chrome is
   rationed; that has not changed. What has changed is that when a thing does
   earn chrome, there is now a correct way to draw it instead of an ad-hoc
   fill and a border.

       .band      a whole section, one step up from the canvas
       .panel     an object resting ON the page
       .raise     an object ABOVE the page
       .well      something set INTO a surface

   READ THEM AS A LADDER, because that is what stops this becoming decoration:
   each one is exactly one --bg step from its neighbour, and NOTHING may skip a
   rung or double back. A .raise inside a .band is not bg-2 sitting on bg-2 —
   the band re-bases the ladder and the raise steps to bg-3. That re-basing is
   the whole system and it is at the bottom of this block.

   HOW THE 11,813px PROBLEM GETS FIXED. It is not the panels. This page is
   nine sections of prose and only two of them will ever hold an object, so
   card chrome cannot possibly carry it. .band is what carries it: a full
   section one step up, which at reading distance is the only fill difference
   a person actually perceives on a page this long. Two or three bands across
   nine sections gives the scroll a rhythm — a reader can feel where they are
   without reading. Alternate every section and you have stripes, which is
   worse than flat because it is flat AND busy. */

/* A whole section, one step up from the canvas. Full-bleed: the fill runs to
   the viewport edge while the content inside stays in its .wrap, which is
   what makes it read as the page stepping rather than as a very wide card.
   The catch-light lands on the seam where the step happens. */
.section--band {
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--e-catch);
  /* MORE AIR THAN AN OPEN SECTION, and this is the half of elevation that
     survives distance. A hairline is invisible from across the room; the size
     of the gap around a thing is not. A band that steps up in fill but keeps
     the same padding reads as a mistake in the fill, not as a raised region. */
  padding-block: calc(var(--s-8) + var(--s-3));
}
/* .section already draws a top rule, and a band draws a bottom one. Two bands
   in a row, or a band followed by an open section, would stack them into a
   2px double line — the classic tell of a system nobody checked. */
.section--band + .section { border-top: 0; }

/* An object RESTING on the page. One step up, hairline, no bevel: a resting
   surface gets a hairline and nothing else (the law, and app.css agrees). */
.panel {
  background: var(--bg-1);
  border: 1px solid var(--panel-line);
  border-radius: var(--r-xl);
  padding: var(--s-5);
}

/* An object ABOVE the page. The difference from .panel is deliberately made of
   four things at once, because ONE of them is never enough to read across a
   room: a brighter fill, a brighter border, the catch-light on the top edge,
   the seat line on the bottom. Light falls from above; the top edge takes it
   and the underside does not. That is the entire trick, it costs two 1px
   inset lines, and it is why the app looks machined and a flat card does not. */
.raise {
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  box-shadow: var(--e-catch), var(--e-seat);
  padding: var(--s-5);
}

/* Something set INTO a surface — a quoted message, a read-out, a figure slot.
   The bevel is the EXACT INVERSE of .raise: dark along the top inner edge,
   catch-light along the bottom. Nothing else distinguishes them, and nothing
   else has to: flip those two lines and a card becomes a hole. Tighter radius,
   because a recess is cut and a card is placed. */
.well {
  background: var(--bg-0);
  border: 1px solid var(--edge);
  border-radius: var(--r-md);
  box-shadow: var(--e-sunk);
  padding: var(--s-4);
}

/* THE RE-BASING. Without this the ladder is a lie: drop a .panel into a .band
   and you get bg-1 on bg-1 — an object that is invisible on exactly the
   sections that were given a fill to make objects visible. Each nesting steps
   the child up one rung so the RELATIONSHIP is preserved wherever it is used,
   which is the difference between a system and four classes. A .well inside a
   band stays on bg-0 on purpose: a recess is meant to go down to the canvas,
   and it is the one place the ladder is allowed to descend. */
.section--band .panel { background: var(--bg-2); border-color: var(--line-2); }
.section--band .raise { background: var(--bg-3); }
.section--band .well  { background: var(--bg-0); }

/* THE SEAM — a 2px cobalt rule, drawn ABOVE a section that has to be read.
   Sanctioned accent vocabulary (rails, dots, seams, underlines), and it is the
   one device on this page that is visible from across the room without being
   loud. RATION IT: one per page. Two seams is two headlines, which is none. */
.seam { position: relative; }
.seam::before {
  content: '';
  position: absolute;
  top: -1px;                 /* sits on the section's own hairline, not below */
  left: 0;
  width: 64px;
  height: 2px;
  background: var(--accent);
}


/* ═══ THE RESPONSE — WHAT AN ELEMENT DOES WHEN IT IS TOUCHED ══════════════
   THE LAW FIRST: nothing moves under a cursor. No lift, no scale, no shift,
   not by one pixel. A row that jumps when the pointer crosses it is a row you
   have to chase, and chasing is the single most common way a hover state makes
   an expensive page feel cheap.

   WHAT IS LEFT IS BETTER ANYWAY, and it is exactly what the product does
   (app.css:715-723, the house hover on every row in the app): a 4% white wash
   and a 2px cobalt rail down the leading edge. The rail is the part that
   matters. A wash alone says "the pointer is somewhere near here"; a rail
   says "THIS ROW", and it says it at the left edge where the eye starts the
   line rather than under the word the pointer happens to be over.

   THREE STATES, NOT ONE:
     hover  — gated to (hover: hover). A phone must never get a stuck hover
              fill left behind after a tap, which is what happens when an
              ungated :hover meets a touchscreen.
     active — NOT gated, because on a phone it is the ONLY feedback there is.
              html sets -webkit-tap-highlight-color: transparent, so without
              this a tap produces nothing at all.
     focus  — the same fill and the same rail as hover, plus the ring. A
              keyboard user should see the row light up the way a mouse user
              does, not just get an outline drawn around it in mid-air. This
              is where craft is most visible and most often skipped.

   THE TRANSITION LIVES ON THE RESTING RULE, not inside :hover, so the surface
   eases back out on mouse-leave too. A one-way transition snaps on exit and
   feels broken — this is the same note app.css:707 carries. */
.rail {
  /* The bleed: 16px of inline padding against 16px of negative inline margin.
     The content does not move by a pixel — see .wordmark — but the fill and
     the rail now have somewhere to be that is not underneath the text. */
  padding-inline: var(--s-4);
  margin-inline: calc(var(--s-4) * -1);
  border-radius: var(--r-sm);
  transition: background-color var(--d-fast) var(--ease-out),
              box-shadow var(--d-fast) var(--ease-out);
}
/* For a target that already carries its own inline padding — a summary inside
   a card, a row in a .panel — where a second bleed would punch through the
   container's edge. */
.rail--flush { padding-inline: 0; margin-inline: 0; }

@media (hover: hover) {
  .rail:hover { background-color: var(--fill-hover); box-shadow: var(--e-rail); }
}
.rail:active { background-color: var(--fill-press); box-shadow: var(--e-rail); }
/* Composed by hand for the same specificity reason as .btn-primary: the
   generic focus rule would replace box-shadow outright and take the rail with
   it, leaving keyboard users the one state with no fill and no rail. */
.rail:focus-visible {
  background-color: var(--fill-hover);
  box-shadow: var(--e-rail), var(--focus-gap);
}

/* Native form controls: the caret and the tick take the accent. Wrapped in
   :where() so any sheet loaded after this one — sections.css owns the actual
   fields — wins without a fight. */
:where(input, select, textarea) {
  transition: border-color var(--d-fast) var(--ease-out),
              background-color var(--d-fast) var(--ease-out);
}


/* ═══ THE ENTRANCE ════════════════════════════════════════════════════════
   Eight sections arrive; the hero does not. Everything above the fold is
   already there when the page paints, because the four things in the hero are
   the four things §S1 spends the fold budget on and none of them may be
   withheld for 380ms to look clever.

   THE SHAPE: 10px up, opacity 0 → 1, 380ms, --ease-enter, ONCE. The observer
   unobserves on the first intersection, so scrolling back up and down again
   replays nothing. Ten pixels and not forty — at forty the eye tracks the
   movement and reads the animation; at ten it reads the arrival and not the
   motion, which is the entire difference between considered and decorative.

   ── THE THREE WAYS THIS BREAKS, AND WHAT STOPS EACH ──────────────────────
   1. NO JAVASCRIPT. Every rule here is nested under .reveal-on, a class only
      scripts/site.js sets. No script, no class, no rule matches, and the page
      is the page — fully rendered, nothing hidden. The law that this site
      renders completely with JS off is not bent by a single pixel.
   2. REDUCED MOTION. site.js checks the query BEFORE stamping and does not
      stamp. Same outcome as no-JS: nothing hides, nothing to reveal, nothing
      to get stuck at opacity 0. The still version is not a fallback, it IS
      the page — which is the only honest test of a reduced-motion path. The
      MOTION block above carries the second belt for a setting flipped
      mid-session.
   3. THE FLASH. A deferred script can run after first paint on a slow
      connection, which would paint a section, hide it, then re-enter it. That
      is worse than no entrance. site.js marks everything already inside the
      viewport as arrived in the SAME synchronous block that adds .reveal-on,
      so an element that has been painted is never taken away.

   Nothing here has a will-change. Thirty promoted layers to save 380ms of
   compositing on eight opacity fades is a worse trade than the fades. */
.reveal-on [data-reveal],
.reveal-on [data-reveal-seq] > * {
  opacity: 0;
  transform: translate3d(0, var(--enter-lift), 0);
  transition: opacity   var(--d-enter) var(--ease-enter),
              transform var(--d-enter) var(--ease-enter);
}
.reveal-on [data-reveal].is-in,
.reveal-on [data-reveal-seq].is-in > * {
  opacity: 1;
  transform: none;
}

/* THE QUEUE. The container is what gets observed; its children arrive behind
   each other so a section assembles rather than appearing as a slab. Capped
   at four steps — 220ms — because the fifth child of a section is below the
   fold anyway and a queue you can count is a queue that is too slow. The cap
   is a rule and not a hope: nth-child(n+5) pins every remaining child to step
   four, so a fourteen-row spec list cannot run to 770ms. */
.reveal-on [data-reveal-seq].is-in > *:nth-child(2) { transition-delay: calc(var(--d-stagger) * 1); }
.reveal-on [data-reveal-seq].is-in > *:nth-child(3) { transition-delay: calc(var(--d-stagger) * 2); }
.reveal-on [data-reveal-seq].is-in > *:nth-child(4) { transition-delay: calc(var(--d-stagger) * 3); }
.reveal-on [data-reveal-seq].is-in > *:nth-child(n+5) { transition-delay: calc(var(--d-stagger) * 4); }


/* ═══ TYPE MOMENTS ════════════════════════════════════════════════════════
   The ramp above is correct and it is undesigned: every block on this page is
   set by the same three rules, so nothing on it is ever TREATED. These are the
   three places where type should do the work, and they are three because a
   fourth would make them all ordinary again.

   None of them introduces a size the ramp does not already have, a weight
   above 650, an uppercase, or a second family. The treatment is in the
   LEADING, the MEASURE and the SPACE — which is where typographic craft
   actually lives and is exactly what a scale alone cannot give you. */

/* ─── 1. .display — the paragraph set like a headline that is not one ─────
   For the ONE block on the page that has to land before the reader decides
   whether to keep going: the buyer's own Sunday night, read back to him.

   IT IS SET LOOSER THAN THE RULES SAY, on purpose. A heading at 36px takes
   1.15 because it is two or three words and tight leading makes it a unit. A
   PARAGRAPH at 36px taking 1.15 is a wall — the lines fuse and the eye cannot
   find the next one. 1.26 at the top size is the number that keeps it a
   paragraph while it is being read at a headline's size. The measure comes in
   as the size goes up, for the same reason: line length is measured in
   CHARACTERS, so a fixed em measure would let a 36px line run past 75. */
.display {
  font-size: var(--t-xl);
  line-height: 1.35;
  letter-spacing: -0.018em;
  color: var(--ink-0);
  font-weight: 400;
  max-width: 20em;
  text-wrap: balance;
}

/* ─── 2. .turn — the one-line pivot ──────────────────────────────────────
   A single sentence that turns the page from the reader's problem to the
   answer. It is not a heading — it is a sentence, and making it a heading
   would flatten it back into the outline with every other heading.

   THE TREATMENT IS THE SPACE AND THE MARK. A short cobalt rule above it, a
   lot of air, and then one line at ink-0. The rule is a seam — sanctioned
   vocabulary — and it does what a heading would have done without pretending
   the sentence is a label. This is the loudest thing on the page that is not
   a button, and there is exactly one of it. */
.turn {
  margin-top: var(--s-8);
  font-size: var(--t-lg);
  line-height: 1.35;
  letter-spacing: -0.015em;
  color: var(--ink-0);
  font-weight: 500;
  max-width: 24ch;
  text-wrap: balance;
}
.turn::before {
  content: '';
  display: block;
  width: 40px;
  height: 2px;
  margin-bottom: var(--s-5);
  background: var(--accent);
}

/* ─── 3. .sig — the signature ────────────────────────────────────────────
   "— Sam, Collingwood". The whole argument of this page is that a person
   wrote the software and answers the phone, and that line is the only place
   the person actually signs. Set as a signature and not as a caption.

   THE CRAFT IS THE HANGING DASH. An em dash is a wide, visually light
   character; leave it in the text column and the signature's first letter
   starts a full em to the right of every line above it, so it reads as
   indented and slightly wrong without anyone being able to say why. Negative
   text-indent with matching padding hangs the dash into the margin and lands
   the S of "Sam" on the same vertical as the paragraph above. Padding rather
   than a negative margin so the dash hangs INSIDE the container and can never
   be clipped by the gutter on a 320px screen. */
.sig {
  display: block;
  margin-top: var(--s-4);
  text-indent: -1.05em;
  padding-inline-start: 1.05em;
  color: var(--ink-2);
  font-size: var(--t-md);
  line-height: 1.5;
}

/* ─── The opening treatment ──────────────────────────────────────────────
   ::first-line is the cheapest real typographic device there is: the first
   line of a block is set at ink-0 and the rest at its own colour, so the
   paragraph has an opening without a drop cap, a lead-in or a single extra
   byte of markup. It re-computes on every reflow, so it is correct at every
   width by construction. COLOUR ONLY — a weight or size change on ::first-line
   alters the line's own break point and the browser re-breaks it, which
   produces a visible jitter as the box is resized. */
.opener::first-line { color: var(--ink-0); }


/* ═══ THE EDGE — SELECTION, CARET, SCROLLBAR ══════════════════════════════
   Nobody names these and everybody feels them. A default selection highlight
   is the loudest cheap signal available on a dark page: the UA's is a strong
   flat blue that is NOT this cobalt, and the first time a reader drags across
   a sentence — which is what people do when they are considering buying — the
   page shows them a colour that belongs to no part of the design. */
::selection {
  background: var(--accent-solid);   /* white on this measures 5.17:1 ✓ AA */
  color: #ffffff;
  text-shadow: none;
}

:root {
  /* Native controls — the radios in the founding-operator form. One line, and
     the browser's default blue tick never appears. */
  accent-color: var(--accent);
  /* The caret. Inherited, so every field gets it. --accent-2 rather than
     --accent because a 1px bar needs the extra luminance to be findable
     against a bg-3 input at arm's length in daylight. */
  caret-color: var(--accent-2);
}

/* THE SCROLLBAR — DESKTOP ONLY, and the gate is the point. Phones draw a
   transient overlay scrollbar that is already correct and already respects
   the platform; restyling it does nothing but risk making it worse. Both
   queries have to pass: a touchscreen laptop reports (hover: hover) and would
   otherwise be caught here. */
@media (hover: hover) and (pointer: fine) {
  /* Firefox. Two properties, no pseudo-elements, and it is the whole API. */
  html {
    scrollbar-color: var(--bg-3) var(--bg-0);
    scrollbar-width: thin;
  }
  /* Chromium / WebKit. */
  ::-webkit-scrollbar { width: 12px; height: 12px; }
  ::-webkit-scrollbar-track { background: var(--bg-0); }
  ::-webkit-scrollbar-thumb {
    background: var(--bg-3);
    /* The border is drawn in the TRACK colour, not the thumb's, which insets
       the thumb to 6px without a second element and without the rounded ends
       clipping. The result is a thin bar with air around it instead of a
       12px slab jammed against the window edge. */
    border: 3px solid var(--bg-0);
    border-radius: var(--r-pill);
  }
  ::-webkit-scrollbar-thumb:hover {
    background: color-mix(in srgb, var(--ink-0) 16%, var(--bg-3));
  }
  ::-webkit-scrollbar-corner { background: var(--bg-0); }
}


/* ═══ BREAKPOINTS — three, and only three: 0 · 768 · 1100 ═════════════════
   Anything that needs a fourth is a section that should be simpler. §4. */

@media (min-width: 768px) {
  :root {
    --gutter: 32px;
    --t-h1: 44px;
  }
  .section     { padding-block: var(--s-9); }
  .hero        { padding-top: var(--s-8); padding-bottom: var(--s-9); }
  .hero-stack  { gap: var(--s-6); }
  .hero h1     { max-width: 18ch; }
  .hero-actions {
    flex-direction: row;
    align-items: center;
    gap: var(--s-4);
    width: auto;
  }
  .cta-card    { padding: var(--s-7) var(--s-6); }
  .foot-stack  { gap: var(--s-6); }

  /* Surfaces. The band keeps its extra air relative to a plain section at
     every width — the ratio is the signal, not the absolute number. */
  .section--band { padding-block: calc(var(--s-9) + var(--s-4)); }
  .panel, .raise { padding: var(--s-6); }
  .seam::before  { width: 80px; }

  /* Type moments step with the ramp. The measure comes IN as the size goes
     up, because line length is counted in characters and a fixed em measure
     would let a 28px line run past 75 of them. */
  .display { font-size: var(--t-2xl); line-height: 1.3; max-width: 19em; }
  .turn    { font-size: var(--t-xl); max-width: 26ch; }
  .turn::before { width: 56px; }
}

@media (min-width: 1100px) {
  :root {
    --gutter: 40px;
    --t-h1: 56px;
  }
  .hero        { padding-top: var(--s-9); }
  .hero h1     { line-height: 1.05; max-width: 17ch; }
  .hero .lede  { font-size: var(--t-lg); }

  /* 1.26 at 36px, and it is the number this whole class exists for: a
     paragraph set at heading size takes heading leading and fuses into a
     block a reader's eye slides off. Loosening it is the treatment. */
  .display { font-size: var(--t-3xl); line-height: 1.26; letter-spacing: -0.024em; max-width: 17em; }
  .turn    { font-size: var(--t-2xl); line-height: 1.3; max-width: 24ch; }
  .seam::before { width: 96px; }
}

/* Landscape phone in a truck cradle — ~430×932 rotated, so ~390px of
   height. Not a fourth breakpoint: it keys off orientation and a height
   ceiling, not a width, and it only tightens vertical rhythm so the button
   stays reachable without a scroll. */
@media (orientation: landscape) and (max-height: 460px) {
  .hero { padding-top: var(--s-5); padding-bottom: var(--s-6); }
  .hero-stack { gap: var(--s-4); }
  .section { padding-block: var(--s-6); }
}

/* ═══ PRINT ═══════════════════════════════════════════════════════════════
   Somebody will print this to show a partner. Invert to paper. */
@media print {
  :root {
    --bg-0: #ffffff; --bg-1: #ffffff; --bg-2: #ffffff; --bg-3: #ffffff;
    --ink-0: #000000; --ink-1: #1a1a1a; --ink-2: #444444; --ink-3: #666666;
    --line: #d5d5d5; --line-2: #c0c0c0; --panel-line: #d5d5d5;
  }
  body { background: #fff; color: #000; font-size: 11pt; }
  .site-head, .head-cta, .skip { display: none; }
  .btn { border: 1px solid #000; color: #000; background: transparent; }
  a[href^="http"]::after,
  a[href^="mailto"]::after { content: " (" attr(href) ")"; font-size: 9pt; }

  /* THE ENTRANCE MUST NOT REACH PAPER. Print fires no IntersectionObserver
     callbacks for anything below the first screen, so a page printed straight
     after load would come out with page one and eight blank pages behind it.
     This is not a nicety — it is the difference between a document and a
     bug, and nobody would find it until they had already handed the paper to
     a partner. Force every reveal target to its final state. */
  .reveal-on [data-reveal],
  .reveal-on [data-reveal-seq] > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  /* Every device in the ELEVATION system is made of light — a catch-light, a
     seat line, a wash, a step in a dark fill ladder. On white paper all four
     are invisible at best and grey smears at worst, and printers do not
     render backgrounds by default anyway. On paper the separator is the
     hairline, which is what it was before there were fills. */
  .panel, .raise, .well, .cta-card, .hero-shot, .btn-primary {
    box-shadow: none !important;
    background: transparent !important;
  }
  .section--band { box-shadow: none; padding-block: var(--s-6); }
  .rail { margin-inline: 0; padding-inline: 0; }
  /* The seam and the turn's rule are the two accent marks that survive: a
     black rule reads on paper and carries the same hierarchy it did on
     screen. */
  .seam::before, .turn::before { background: #000; }
  ::selection { background: transparent; color: inherit; }
}
