/* ==========================================================================
   Enkindle AI — website styles
   V5 "Pinned Chapters" (2026-07-31). Zero build step, zero dependencies,
   zero external requests. One JS file, and it only adds reveal classes.
   Rules and rationale live in DESIGN.md; decisions live in BUILD-NOTES.md.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Poppins, self-hosted (Phase 4 — SEO/AEO audit #17)

   Through Phases 2–3.7 this was three <link>s to fonts.googleapis.com in every
   <head>: a render-blocking stylesheet on a third-party origin, and the only
   external request the site made. Two weights, ~16KB of woff2 between them — the
   dependency cost more than the bytes did. It was also the one launch-day failure
   mode owned by somebody else.

   `unicode-range` is copied verbatim from Google's own latin subset and is
   load-bearing, not decoration. Only the latin subset is shipped, because every
   glyph in the approved copy lives in it (including the em dash, en dash and
   curly apostrophe, all inside U+2000–206F). Keeping the descriptor means that if
   a latin-ext character ever does reach a heading, the browser skips Poppins for
   that one glyph and falls back to system-ui — a clean substitution rather than a
   missing-glyph box. Drop the descriptor and that failure becomes tofu.

   font-display: swap matches what the Google stylesheet was already requesting,
   so first-paint behaviour is unchanged: system-ui renders immediately, Poppins
   swaps in. The 700 face is preloaded from each <head> because it sets every
   heading including the hero <h1>; 600 is below-the-fold only and is not.

   Licence: SIL OFL 1.1, see assets/fonts/OFL.txt. Self-hosting is expressly
   permitted; the licence text ships alongside because the OFL requires it.
   -------------------------------------------------------------------------- */

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

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

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* --- Brand hexes (D0, frozen) --- */
  --navy:   #14293D;
  --cream:  #F1EAD3;
  --teal:   #6D93A3;

  /* Was #E67E2A. The site shipped two oranges — the CTA's #E67E2A and the
     flame's, which the lockup paints as #FF6708 on its two solid paths and
     #FF6608 on the gradient path. 2026-07-30 resolved it in favour of #FF6608,
     and the Brand Design Playbook's D0 table was updated the same day so the two
     do not drift.
     V5 promotes this from a control colour to a SURFACE colour — the hero spec
     band and the whole closing band are orange grounds now. The hard rule that
     survives that promotion unchanged: orange fill + navy ink only. Never orange
     with white (2.94:1), never orange text on cream (2.44:1), and never cream on
     an orange ground, which is why --focus flips to navy inside .band-orange. */
  --orange: #FF6608;

  /* Arrives with the flame lockup, which replaced the orange-diamond mark. Not
     part of the frozen four above — it is the mark's own cyan, lifted out of the
     SVG so the footer tagline can be set as text instead of baked-in paths.
     6.31:1 on --navy-deep, 5.44:1 on --navy — clears AA on both. */
  --cyan:   #00AAD4;

  /* --- Derivatives (white / near-black + two AA-forced teal shades) ---
     --navy-deep  near-black navy. The DEFAULT page ground as of V5.
     --teal-ink   brand teal darkened to clear WCAG AA (4.5:1) on cream.
                  Brand teal on cream is 2.75:1; the demo's on-light teal
                  (#4E7180) is 4.36:1 — both fail. #4A6875 = 4.95:1 on cream,
                  5.95:1 on white. Same hue (197.8), same saturation.
     --teal-lift  brand teal lightened by ~1% L to clear AA on navy
                  (#6D93A3 on navy = 4.49:1, one hundredth short). 4.62:1.
     See BUILD-NOTES.md for the full ratio table. */
  --white:     #FFFFFF;
  --navy-deep: #0E1C2B;
  --teal-ink:  #4A6875;
  --teal-lift: #7095A5;

  /* Hairlines / scrims — brand hexes at alpha, no new hues */
  --rule-on-light: rgba(20, 41, 61, 0.14);
  --rule-on-dark:  rgba(241, 234, 211, 0.18);
  --card-on-dark:  rgba(241, 234, 211, 0.06);

  /* --- Type (D3 / D3a) --- */
  --font-display: "Poppins", system-ui, sans-serif;   /* headings + display only */
  --font-body:    system-ui, sans-serif;              /* body, UI, forms         */
  --font-mono:    ui-monospace, "SF Mono", Menlo, monospace; /* .eyebrow only    */

  /* --- Display scale (V5) ---
     Every clamp here is measured from the approved reference, not invented, and
     each one belongs to exactly one place on the page. Two of them are new
     ceilings the old scale could not reach: the hero wall at 9rem and the
     closing band at 5rem.
     What is deliberately NOT here: a token for the chapter/proof/FAQ body copy.
     Those resolve to --size-lead, --size-body and --size-small below, which the
     reference's flat 1.375rem / 1.0625rem / 1rem values are the endpoints of. */
  /* Floor is 2.25rem, not the handoff's 2.75rem, and only the floor moved — the
     preferred and maximum terms are the reference's exactly, so this is
     identical at every width above ~428px where the floor stops binding.
     Measured: at 2.75rem the middle span "costing you hours" needs 364px of ink
     against 335px of column at 375px wide, so it wrapped and left "costing"
     alone on a line — a composed three-line headline turning into a four-line
     accident on the most common phone width. At 2.25rem the preferred term
     (39.5px) governs there instead and all three spans hold one line each.
     Handoff open item 1: mobile was not designed, and 2.75rem is a desktop
     number that happened to be written as a floor. */
  --size-hero-wall: clamp(2.25rem, 0.5rem + 8.4vw, 9rem);   /* hero <h1>        */
  --size-cta-wall:  clamp(2rem, 1rem + 4.6vw, 5rem);        /* #cta <h2>        */
  --size-h2-lg:     clamp(1.875rem, 1rem + 3vw, 3.5rem);    /* #work, page h1s  */
  --size-chapter:   clamp(1.75rem, 1rem + 2.4vw, 3rem);     /* chapters, spec   */
  --size-row-h3:    clamp(1.5rem, 1rem + 1.6vw, 2.25rem);   /* #svc rows        */
  --size-h2-sm:     clamp(1.25rem, 1rem + 1vw, 1.75rem);    /* FAQ questions    */
  --size-lead:      clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
  --size-body:      clamp(1.0625rem, 1rem + 0.25vw, 1.1875rem);
  --size-small:     clamp(1rem, 0.95rem + 0.2vw, 1.0625rem);   /* never below 16px */
  --size-label:     0.8125rem;

  --lh-heading: 1.14;
  --lh-body:    1.62;

  /* --- Space --- */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;
  --s-9: 6rem;

  --section-y:  clamp(3rem, 6vw, 5rem);
  --gutter:     clamp(1.25rem, 3vw, 2.5rem);

  /* The editorial spine — the eyebrow column and the gutter beside it. Named
     rather than typed inline because the pair defines where body copy starts
     wherever the spine is used (#about, #faq, the contact and case-study pages),
     and a value that decides a page-wide edge should be readable as one. */
  --spine-col: 15rem;
  --spine-gap: clamp(2rem, 4vw, 4rem);

  /* 80rem, up from 72rem in V5. Deliberately ONE container width for the whole
     site rather than the handoff's suggested --wrap / --wrap-wide pair: two
     widths would put the header's logo at a different left edge from the body
     copy on contact.html and case-study.html. Readability is protected by
     --measure, which is what actually caps running prose. */
  --wrap:       80rem;
  --measure:    62ch;

  /* 14px containers (proof cards, case-study figures). 10px now has exactly ONE
     consumer left — the dashed "screenshot pending" token — because V5 squares
     every button and chip-style control. It is kept, not deleted, and this is
     the note that says why it looks orphaned. */
  --radius:     14px;
  --radius-sm:  10px;

  /* The rendered height of the lockup. A token rather than a bare value in .logo
     so that --header-h below is a stated sum instead of a remembered one, and so
     that changing the lockup's size forces a look at both.
     Worth knowing when you do: the wordmark's baseline is the BOTTOM EDGE of
     this box — the glyph bottoms sit at y=225.81 in a viewBox that ends at
     225.8, and ENKINDLE has no descenders — which is what §6's nav links align
     themselves to. */
  --logo-h: 34px;

  /* Sticky-header height. Measured, not guessed: .header-home's padding-block
     (2 * --s-3) + --logo-h + the 1px bottom border = 12 + 34 + 12 + 1 = 59px.
     THREE consumers depend on it being true — §14b subtracts it from every
     pinned chapter, anchor targets use it as scroll-margin-top, and the
     thank-you page centres against it. Re-measure if the logo height or the
     header's padding changes.
     Unlike the pre-V5 header this does NOT step up at 45rem: the logo is 34px
     at every width now, so this is a single constant for the first time. */
  --header-h: 59px;

  /* --- Surface defaults ---
     V5 inverts the site: the default ground is navy-deep, and cream/white are
     the bands that interrupt it. Every one of these is re-pointed by a .band-*
     class below, so children never special-case their own surface. */
  --bg: var(--navy-deep);
  --fg: var(--cream);
  --eyebrow-color: var(--teal-lift);
  --ink-quiet: var(--teal-lift);
  --rule: var(--rule-on-dark);
  --focus: var(--cream);
}

/* Bands re-point the same six variables. --ink-quiet is the AA-corrected teal
   for whichever ground it lands on (link underlines, chip borders, qualifiers),
   and it exists so those consumers stop carrying per-band overrides of their own.
   --eyebrow-color tracks it everywhere EXCEPT orange, where a teal eyebrow on an
   orange ground would be both illegible and off-system: it goes navy instead. */
.band-deep {
  --bg: var(--navy-deep);
  --fg: var(--cream);
  --eyebrow-color: var(--teal-lift);
  --ink-quiet: var(--teal-lift);
  --rule: var(--rule-on-dark);
  --focus: var(--cream);
}

.band-navy {
  --bg: var(--navy);
  --fg: var(--cream);
  --eyebrow-color: var(--teal-lift);
  --ink-quiet: var(--teal-lift);
  --rule: var(--rule-on-dark);
  --focus: var(--cream);
}

.band-cream {
  --bg: var(--cream);
  --fg: var(--navy);
  --eyebrow-color: var(--teal-ink);
  --ink-quiet: var(--teal-ink);
  --rule: var(--rule-on-light);
  --focus: var(--navy);
}

.band-white {
  --bg: var(--white);
  --fg: var(--navy);
  --eyebrow-color: var(--teal-ink);
  --ink-quiet: var(--teal-ink);
  --rule: var(--rule-on-light);
  --focus: var(--navy);
}

/* --focus is NAVY here, not cream. The handoff says "cream on the dark and
   orange grounds"; cream on #FF6608 measures below the 3:1 WCAG 1.4.11 floor for
   non-text contrast — it is the same pair DESIGN.md already forbids for text.
   Navy on orange is 5.06:1 and clears it with room. */
.band-orange {
  --bg: var(--orange);
  --fg: var(--navy);
  --eyebrow-color: var(--navy);
  --ink-quiet: var(--teal-ink);
  --rule: var(--rule-on-light);
  --focus: var(--navy);
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--navy-deep);
  color: var(--cream);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: var(--lh-body);
  text-rendering: optimizeLegibility;
}

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--lh-heading);
  margin: 0;
  text-wrap: balance;
}

p { margin: 0; text-wrap: pretty; }

ul, ol { margin: 0; padding: 0; list-style: none; }

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

a { color: inherit; }

strong { font-weight: 700; }

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

/* Motion lives in §14, at the bottom of this file — including its
   `prefers-reduced-motion` handling, which sits beside the rules it turns off
   rather than up here, so you can never change one without seeing the other.
   Nothing between here and §14 animates, transforms or pins. */

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--section-y);
}

.section + .section { border-top: 1px solid var(--rule); }

/* Anchor targets clear the sticky header rather than landing underneath it.
   This is layout, not motion — it applies to instant jumps too, so it belongs
   here and not in §14 with the smooth-scroll gate. */
#main,
.section,
.chapters,
.chapter,
.work,
.svc,
.faq,
.cta { scroll-margin-top: var(--header-h); }

/* Editorial spine: eyebrow parks in a narrow left column from 62rem up. */
.layout > * + * { margin-top: var(--s-5); }

@media (min-width: 62rem) {
  .layout {
    display: grid;
    grid-template-columns: var(--spine-col) minmax(0, 1fr);
    column-gap: var(--spine-gap);
    align-items: start;
  }
  .layout > * + * { margin-top: 0; }
  .layout > .eyebrow { grid-column: 1; grid-row: 1; padding-top: 0.55rem; }
  .layout > .layout-body { grid-column: 2; grid-row: 1; }

  /* Blocks that need the whole measure — card grids, figure lists — break out
     of the spine and run the full container width on their own row.

     `grid-row: auto` is load-bearing, not tidy-up. An element carrying BOTH
     .layout-body and .span-full (case-study.html:275 is the one on the site)
     matches the rule above too, at equal specificity — so it correctly takes
     grid-column: 1 / -1 from here, but silently keeps grid-row: 1 from there and
     is placed ON TOP of the spine's first row. It rendered as two paragraphs
     overlapping, and it predates V5; it only became obvious once that section
     moved onto a cream band. Resetting the row here fixes the pair without
     making either rule care about the other. */
  .layout > .span-full { grid-column: 1 / -1; grid-row: auto; margin-top: var(--s-7); }
  .layout > .span-full + .span-full { margin-top: var(--s-6); }
}

.layout-body > * + * { margin-top: var(--s-5); }

.prose { max-width: var(--measure); }
.prose > * + * { margin-top: var(--s-4); }

/* --------------------------------------------------------------------------
   4. Eyebrow labels (D3a — the ONLY monospace on the site)
   -------------------------------------------------------------------------- */

/* font-weight and text-wrap are pinned here, not inherited: most eyebrows are
   real <h2>s (they are their section's heading), so without these they would
   pick up the base h1,h2,h3 rule's 700 weight and balanced wrapping and stop
   matching the <p> eyebrows beside them. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--size-label);
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0.02em;
  color: var(--eyebrow-color);
  margin: 0;
  text-wrap: wrap;
}

/* Visually hidden, still read by screen readers and parsers.
   Added 2026-07-30 (SEO/AEO audit #7). Section eyebrows carry two spans: the
   visible <token>, marked aria-hidden, and a plain-language heading in here, so
   a screen reader never announces "<how-it-works>" as a heading and a parser
   gets something it can key off.
   Do NOT swap this for display:none — that would hide it from assistive tech and
   search engines alike, which is the opposite of the point. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   5. Buttons + links

   V5 squares the buttons and replaces the fill/text inversion with a single
   instant hover shadow. Both are deliberate breaks with the pre-V5 system and
   both are written up in DESIGN.md rather than left to be "fixed" later:
     • radius 0, not 10px — the 10px control radius now has one consumer left,
       the dashed pending token, and that is documented at --radius-sm.
     • one hover box-shadow, no transition, no transform, no colour change. The
       Flat Ground Rule was amended to permit exactly this and nothing else;
       shadow still never expresses elevation anywhere on the site.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-block;
  font-family: var(--font-body);
  font-size: var(--size-small);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.01em;
  text-decoration: none;
  padding: 1rem 1.6rem;
  border-radius: 0;
  /* Transparent rather than absent so .btn-primary and .btn-ghost share an
     identical box and never shift by 4px when one replaces the other. */
  border: 2px solid transparent;
  cursor: pointer;
}

/* Header CTA. 0.65rem, not the reference's 0.6rem, and the 0.05rem is the whole
   point: at 0.6rem this control computes to 43.6px tall at the 16px type floor,
   just under the 44px touch target the rest of the site holds. 0.65rem lands it
   at exactly 44px. Two-tenths of a millimetre of visual difference, one
   accessibility floor kept. */
.btn-compact { padding: 0.65rem 1.1rem; }

/* Primary — orange fill + navy ink, 5.06:1. */
.btn-primary {
  background: var(--orange);
  color: var(--navy);
}

.btn-primary:hover { box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45); }

/* On the orange bands an orange button is not a button. The band re-points it
   to a navy fill with cream ink rather than the page introducing a second
   primary-button variant — same mechanism every other surface uses. */
.band-orange .btn-primary {
  background: var(--navy);
  color: var(--cream);
}

.band-orange .btn-primary:hover { box-shadow: 0 8px 22px rgba(14, 28, 43, 0.5); }

/* Secondary — deliberately subordinate, and never orange. No shadow: a shadow
   under a transparent fill reads as a bug, and the amended Flat Ground Rule
   permits the hover shadow on primary buttons only. The border carries it. */
.btn-ghost {
  background: transparent;
  color: var(--fg);
  border-color: var(--ink-quiet);
}

.btn-ghost:hover { border-color: currentColor; }

.text-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--ink-quiet);
  text-underline-offset: 0.28em;
  text-decoration-thickness: 2px;
}

.text-link:hover { text-decoration-color: currentColor; }

/* Unfilled copy tokens (R0.7) — must never ship silently.
   check-placeholders.sh fails the build on this class, so it is a deploy gate
   with a paint job, not decoration. */
.placeholder {
  background: transparent;
  color: inherit;
  font-weight: 700;
  font-size: 0.94em;
  letter-spacing: 0.02em;
  padding: 0.1em 0.45em;
  border: 1px dashed var(--ink-quiet);
  border-radius: 5px;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   6. Skip link + header + footer
   -------------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: var(--s-4);
  top: -100%;
  z-index: 10;
  background: var(--orange);
  color: var(--navy);
  font-weight: 600;
  font-size: var(--size-small);
  padding: 0.75rem 1.1rem;
  text-decoration: none;
}

.skip-link:focus { top: var(--s-4); }

/* The skip link lands on the navy-deep header, where a navy ring would vanish —
   the cream halo keeps the indicator visible on either surface. This box-shadow
   is a FOCUS-VISIBILITY device, not elevation, and remains the documented
   exception it has always been. */
.skip-link:focus-visible {
  outline: 3px solid var(--navy);
  outline-offset: 0;
  box-shadow: 0 0 0 6px var(--cream);
}

/* The z-index is the one exception to §14b's "paint order is DOM order" rule and
   it is required: the pinned chapters come later in the DOM, so without it they
   would paint straight over the header they are supposed to tuck under.
   5 keeps it below .skip-link's 10, so the skip link still lands on top of the
   header when focused. */
.site-header {
  position: sticky;
  top: 0;
  padding: 6px 0 12px 0;
  z-index: 5;
  background: var(--navy-deep);
  color: var(--cream);
  border-bottom: 1px solid var(--rule-on-dark);
  --focus: var(--cream);
  --ink-quiet: var(--teal-lift);
}

/* padding-block: 0 is deliberate and is what makes --header-h come out at 59px.
   The 12px top and bottom the header reads as live on .header-home and on the
   nav links instead, NOT here. Put them back on this element and every nav link
   gains 24px it does not need while the bar grows to 83px — which is exactly
   what the pre-V5 header did. The reference reaches 59px by giving its nav links
   no padding at all; that costs the >=44px touch target, so the padding moved
   rather than disappeared. */
.site-header .wrap {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  padding-block: 0;
}

.logo { height: var(--logo-h); width: auto; }

/* 12px + 34px + 12px = 58px, + the 1px border = the 59px in --header-h.
   This element is the header's height. Change its padding or .logo's height and
   --header-h is wrong until you re-measure it. */
.header-home { display: inline-block; padding-block: var(--s-3); }

/* The two <nav> landmarks read as one nav block. They stay separate elements —
   "Sections" and "Primary" are distinct landmarks and screen-reader users
   navigate by that distinction — so this wrapper does the visual grouping the
   markup must not.
   flex: 1 lets the section links sit just right of the lockup while the Contact
   button is pushed hard right by its own margin-left. The margin, not
   justify-content: space-between, is what makes this survive the section nav
   disappearing below 68rem — with one child, space-between would park Contact
   next to the logo. */
.nav-group {
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--s-5);

  /* Stretched so it is the header's content box (58px), not just as tall as its
     own tallest child. That box bottom is what the text links below align to;
     without it, flex-end means "the bottom of a 46px group floating in a 58px
     bar" and the baselines miss by 5.96px. */
  align-self: stretch;
}

/* display: flex + stretch for the same reason as .nav-group: the <ul> has to BE
   58px tall before align-self: flex-end inside it can mean anything. A plain
   block <ul> would sit at its own content height and take its children with it. */
.site-nav {
  margin-left: auto;
  display: flex;
  align-self: stretch;
}

.section-nav ul { display: flex; gap: var(--s-5); }

/* Primary nav is a row too now that "Case study" sits beside the Contact button.
   align-items: center is what keeps the button optically centred in the bar
   while the text link beside it drops to the baseline. A filled button aligns by
   its box, not by its baseline; bottom-aligning it would hang it off the
   header's bottom rule. */
.site-nav ul { display: flex; align-items: center; gap: var(--s-5); }

/* THE BASELINE ALIGNMENT. These two rules bottom-flush both text-link groups
   with the header's content box; the padding-block trick below then puts their
   baselines var(--s-3) above their own bottom edge, which is exactly where the
   wordmark's baseline sits. Full derivation and the measured deltas are in the
   note on .section-nav a. */
.section-nav { align-self: flex-end; }
.nav-page { align-self: flex-end; }

/* --- Text nav links ----------------------------------------------------------
   ONE mechanism, two payoffs. --nav-descent is exactly the space the line box
   leaves under the baseline, moved out of the bottom padding into the top, so
   the link's baseline sits var(--s-3) above its own bottom edge.

   1. THE LINK SITS ON THE LOGO'S BASELINE. The wordmark's baseline is the
      bottom edge of its box — glyph bottoms at y=225.81 in a viewBox ending at
      225.8, and ENKINDLE has no descenders — and .header-home's var(--s-3) of
      bottom padding puts that baseline var(--s-3) above the header's content
      box. Both boxes therefore carry their baseline the same distance above
      their own bottom, so bottom-flushing them (the align-self: flex-end rules
      above) lands the two baselines on one line. No magic number, and it holds
      at every step of the --size-small clamp.
      Measured delta to the wordmark baseline: 0.000px at 1440, 1280, 1088 and
      1024, where --size-small is pinned at its 17px ceiling. It opens to
      0.227px at 768 and 0.133px at 480 — that is --nav-descent's third decimal
      showing through once the clamp lands on a fractional font size, and it is
      the same residual the underline gap carries at those widths. Before these
      two align-self rules, centred: 5.961px.
      Remove either one and the link re-centres in the bar, putting its baseline
      back ~5.96px ABOVE the wordmark's.

   2. THE UNDERLINE SITS 4px UNDER THE TEXT. The ::after rule is positioned from
      that same known baseline rather than from the padding box — a
      border-bottom would draw ~12px under the text and read as a detached rule.
      Subtracting the gap AND the rule's own height is the part that is easy to
      get wrong: `bottom` positions the box's bottom edge, so subtracting only
      the gap lands the visible top edge at 2px instead of 4px and cuts the
      descenders. 4px, not 3px: the deepest descender here is the 'g' of
      "Beyond", measured at 3.19px below the baseline at 17px.

   --nav-descent is measured in-browser, not derived: at line-height 1.3 the line
   box leaves 4.592px below the baseline at 17px, i.e. 0.270em. Set in em so it
   tracks the --size-small clamp rather than being right at one size only.
   line-height must stay explicit; inheriting --lh-body (1.62) puts the number
   back to 7.54px and the rule back under the descenders.
   Total padding-block is 24px either way, so the >=44px hit area survives:
   1.3em + 24px = 44.8px at the 16px floor, 46.1px at 17px. Keeping the box this
   tight is also what lets §5's `outline-offset: 3px` focus ring stay a ring —
   grow the box to the full 58px bar instead and the ring's top edge is drawn
   above y=0, where the sticky header clips it away at every scroll position. */
.section-nav a,
.nav-link {
  --nav-descent: 0.270em;
  --rule-h: 2px;      /* the underline's own thickness */
  --rule-gap: 4px;    /* clear air between the baseline and the top of the rule */
  position: relative;
  display: inline-block;
  font-size: var(--size-small);
  font-weight: 400;
  line-height: 1.3;
  color: var(--cream);
  text-decoration: none;
  white-space: nowrap;
  padding-block: calc(var(--s-3) + var(--nav-descent)) calc(var(--s-3) - var(--nav-descent));
}

.section-nav a::after,
.nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--s-3) - var(--rule-gap) - var(--rule-h));
  height: var(--rule-h);
  background: transparent;
}

.section-nav a:hover::after,
.section-nav a[aria-current="page"]::after,
.nav-link:hover::after,
.nav-link[aria-current="page"]::after { background: var(--teal-lift); }

/* Hidden below 68rem. Four section links plus the Contact button plus a 151px
   lockup do not fit narrower than this, and there is no hamburger by design —
   Contact never disappears, so the header always has its one real destination. */
.section-nav { display: none; }

@media (min-width: 68rem) {
  .section-nav { display: block; }
}

/* "Case study" is a cross-page link, so unlike the section anchors it is worth
   carrying below 68rem — it is the only header route to and from case-study.html
   on the pages that have no section nav. It cannot go all the way down, though:
   at 375px the lockup (128px) + the Contact button (100px) + the wrap's own
   gutters already spend 288px of a 335px content box, and the link needs 82px
   plus a 24px gap. 30rem is the narrowest width where it measures clean —
   verified as zero horizontal overflow at 320px with the link correctly absent.
   Below that the route is still there, just in the content rather than the
   chrome: index.html's proof figure links into the teardown and case-study.html's
   footer links back out.
   The gate is on the <li>, not on the .nav-link inside it: .site-nav ul is a
   flex row with a gap, and hiding only the anchor would leave an empty item
   still spending var(--s-5) of the header's tightest width. */
.nav-page { display: none; }

@media (min-width: 30rem) {
  .nav-page { display: block; }
}

.site-footer {
  background: var(--navy-deep);
  color: var(--cream);
  border-top: 1px solid var(--rule-on-dark);
  padding-block: var(--s-7);
  --focus: var(--cream);
  --ink-quiet: var(--teal-lift);
}

.site-footer .wrap {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}

/* 40px, NOT the handoff's 44px, and the 4px is a real decision. .footer-tagline
   below is tracked to end flush with this wordmark's right edge, and that
   tracking was measured against a 40px lockup. 44px widens the mark and pulls
   the tagline out of alignment for no gain. The handoff's own §9 also says the
   footer is "unchanged from the current site", which this is and 44px is not.
   Re-tune --footer-tagline tracking before changing this. */
.footer-logo { height: 40px; }

.footer-brand {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  align-items: flex-start;
}

/* The tagline used to be baked into the SVG as paths, 20.5 units tall against a
   100.1-unit wordmark — about 2px on screen, so it never resolved at any size the
   header could carry. Set as text here instead, where the footer has the room and
   the size, tracking and colour are all ours to pick. */
.footer-tagline {
  margin: 0;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.212em;
  text-transform: uppercase;
  color: var(--cyan);
  /* letter-spacing also applies after the final S, so the text block ends one
     space wider than its ink. Pulled back so the tagline's right edge lands on
     the wordmark's, not one gap past it. */
  margin-right: -0.212em;
}

.footer-meta {
  font-size: var(--size-small);
  color: var(--cream);
  opacity: 0.82;
}

@media (min-width: 45rem) {
  .site-footer .wrap { flex-direction: row; align-items: center; justify-content: space-between; }
}

/* --------------------------------------------------------------------------
   7. Hero — wall of type, with the orange spec band as its footing
   -------------------------------------------------------------------------- */

.hero {
  background: var(--bg);
  color: var(--fg);
}

.hero > .wrap { padding-block: clamp(2.5rem, 5vw, 4rem) 0; }

/* Three block spans stepping right. The indents are vw so the stagger scales
   with the headline instead of pinching at one width — and they are dropped
   entirely below 48rem, where 18vw of a 375px screen is 67px of a 320px-wide
   text column and the third line would have nowhere to go. */
.hero-wall {
  font-size: var(--size-hero-wall);
  line-height: 0.9;
  letter-spacing: -0.042em;
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
}

.hero-wall > span { display: block; }

/* case-study.html runs the same wall at a lower ceiling. --size-hero-wall is a
   :root token consumed only by .hero-wall, so scoping it on an ancestor is the
   whole mechanism — no override of .hero-wall itself, and the stagger, the
   line-height and the tracking all still come from the rules above.
   Same reasoning as the §13b .contact-head note this replaced: an interior page
   should read as the same system as the homepage without competing with its
   9rem hero. It is also what the composed headline needs — "I ran my CRO
   teardown" is 21 characters against the homepage's longest span at 17, so past
   ~5.5rem the first line outruns the 80rem wrap and the composed three lines
   become an accidental four.

   All three terms move, not just the ceiling, and the floor is the one that
   actually matters. --size-hero-wall's own floor is 2.25rem, sized in §1 for a
   17-character longest span; this headline's is 21 ("I ran my CRO teardown"),
   which measures 10.918em wide. Against the 335px text column a 375px phone
   leaves, 2.25rem needs 393px and wraps — the exact four-line accident §1's
   note describes, one page over. 1.5rem/0.25rem+5.9vw holds all three spans on
   one line each from 320px up, with the floor binding only below ~339px.
   At 1424px and wider this is identical to the ceiling-only version; the slope
   is ~6% shallower in between, which suits an interior page anyway. */
.hero-compact { --size-hero-wall: clamp(1.5rem, 0.25rem + 5.9vw, 5.5rem); }

/* The one orange element above the spec band. Inside the last span, not a
   sibling of it, so it tracks the final word rather than orphaning onto its own
   line when the headline wraps.
   Homepage only, by rule — DESIGN.md's Rationed Ember Rule allows exactly one
   orange character on the site. case-study.html's wall deliberately has none. */
.hero-mark { color: var(--orange); }

.hero-row {
  display: grid;
  gap: clamp(2rem, 4vw, 4rem);
  margin-top: clamp(1.5rem, 5vw, 5rem);
}

/* Poppins on a <p>. This is display type that happens to be marked up as a
   paragraph — a single sub-headline sentence, not running prose — which is the
   line DESIGN.md's Poppins-Never-Speaks Rule actually draws. .svc-price is the
   same case. Neither is a licence to set body copy in Poppins. */
.hero-lead {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.25rem, 1rem + 1.2vw, 1.875rem);
  line-height: 1.14;
  /* max-width: 28ch; */
}

.hero-note { font-size: var(--size-body); }

/* Full-bleed by being a full-width child of a full-width section. No
   `width: 100vw; margin-left: calc(50% - 50vw)` breakout, which the reference
   uses and which overflows the page horizontally by the scrollbar's width on
   any platform that still paints one. */
.spec {
  background: var(--bg);
  color: var(--fg);
  margin-top: clamp(2rem, 4vw, 3rem);
}

/* align-items is `start` here and only becomes `end` at 48rem. The reference
   specifies `end`, and at 48rem+ every label fits on one line so the two are
   identical — but in the 2x2 mobile grid the labels wrap unevenly ("person
   doing the work" takes two lines, "to keep, either way" one), and bottom-
   aligning them staggers the numerals beside them by a whole line. The numerals
   are the thing being read; they line up. */
.spec-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-5);
  align-items: start;
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
}

.spec-num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--size-chapter);
  line-height: 1;
  letter-spacing: -0.02em;
}

.spec-label {
  margin-top: 0.35rem;
  font-size: var(--size-small);
  font-weight: 600;
}

@media (min-width: 48rem) {
  .spec-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); align-items: end; }
  .hero-row { grid-template-columns: minmax(0, 1fr) minmax(0, 26rem); align-items: start; }
}

/* The stagger is opt-IN at 48rem rather than opt-out below it, so flush-left is
   the default and no reset rule (or !important) is needed on small screens.
   Handoff open item 1: mobile was not designed, and 18vw of a 375px screen would
   leave the third line 67px of a ~320px text column to live in. */
@media (min-width: 48rem) {
  .hero-wall > span:nth-child(2) { padding-left: 7vw; }
  .hero-wall > span:nth-child(3) { padding-left: 18vw; }
}

/* --------------------------------------------------------------------------
   8. The process — three full-bleed chapters (the signature moment)

   Static layout only. The pinning that makes them slide over one another is in
   §14b and this section is complete and readable without it.
   -------------------------------------------------------------------------- */

.chapter {
  position: relative;
  background: var(--bg);
  color: var(--fg);
  padding: clamp(2rem, 6vw, 5rem) clamp(1.25rem, 4vw, 3rem);
}

/* Numeral above the heading below 62rem — the two-column layout needs width the
   narrow viewports do not have, and a 7rem numeral beside a wrapped heading
   leaves the copy in a 12-character column. */
.chapter-num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(3rem, 8vw, 7rem);
  line-height: 0.8;
  letter-spacing: -0.04em;
  /* Ink is the panel's own text colour at low alpha, never a separate grey —
     so it re-points with the band like everything else. */
  color: currentColor;
  opacity: 0.22;
}

.chapter-head {
  margin-top: var(--s-4);
  font-size: var(--size-chapter);
  line-height: 1.08;
  letter-spacing: -0.02em;
}

.chapter-copy {
  margin-top: var(--s-4);
  font-size: var(--size-lead);
  max-width: 52ch;
}

/* Below 62rem the label is an ordinary eyebrow above chapter 01 (handoff open
   item 3). It only becomes the absolutely-positioned corner mark once there is
   a full-height panel with a corner to put it in. */
.chapter-label { margin-bottom: var(--s-5); }

@media (min-width: 62rem) {
  .chapter {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: clamp(1.5rem, 4vw, 3.5rem);
    row-gap: var(--s-4);
    align-content: center;
  }

  /* Out of flow, so it does not shift the centred grid it sits on top of.
     Its top offset is measured from the chapter's own top edge — which, once
     §14b pins the chapter at --header-h rather than 0, is a point the reader
     can actually see. At the reference's top: 0 this label sits underneath the
     opaque header and is invisible for the entire time chapter 01 is pinned. */
  .chapter-label {
    position: absolute;
    top: clamp(1.25rem, 3vw, 2rem);
    left: clamp(1.25rem, 4vw, 3rem);
    margin-bottom: 0;
  }

  .chapter-num { grid-row: 1 / 3; }
  .chapter-head,
  .chapter-copy { margin-top: 0; }
}

/* --------------------------------------------------------------------------
   9. Proof of work — real screenshots in navy cards, or an honest empty state
   -------------------------------------------------------------------------- */

.work {
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--section-y);
}

/* No max-width, deliberately. This wraps onto two composed lines via its own
   spans; capping it would re-break them. */
.work-head {
  font-size: var(--size-h2-lg);
  line-height: 1.06;
  letter-spacing: -0.024em;
}

.work-head span { display: block; }

.work-intro {
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
  max-width: var(--measure);
  font-size: var(--size-body);
}

.proof-cards {
  margin-top: var(--s-7);
  display: grid;
  gap: var(--s-5);
}

.proof-card {
  background: var(--navy);
  color: var(--cream);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* A navy object on a cream band is the one documented exception to the
     Full-Bleed Rule: it is meant to read as a discrete artefact. It carries its
     own on-dark ink tokens because it is a surface, not a band. */
  --ink-quiet: var(--teal-lift);
  --focus: var(--cream);
}

.proof-card figure { margin: 0; display: contents; }

.proof-shot {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top center;
  background: var(--navy-deep);
  border-bottom: 1px solid var(--rule-on-dark);
}

/* Honest empty state — used only where no real screenshot exists yet.
   UNUSED as of 2026-08-01: the bookkeeping card was the last holdout and now has a real
   screenshot, so nothing in the markup carries this class. Kept, not deleted, for two
   reasons: it is the pattern the site commits to whenever a proof slot outruns the evidence
   (borrow nothing, say so on the card), and `.proof-pending span` below is the last
   --radius-sm on the site. Delete both together with the token, or not at all. */
.proof-pending {
  aspect-ratio: 16 / 10;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--s-5);
  background: var(--navy-deep);
  border-bottom: 1px solid var(--rule-on-dark);
}

/* The last --radius-sm on the site, and the reason that token still exists. */
.proof-pending span {
  font-size: var(--size-small);
  color: var(--teal-lift);
  border: 1px dashed var(--teal-lift);
  border-radius: var(--radius-sm);
  padding: var(--s-3) var(--s-4);
}

.proof-card figcaption {
  padding: var(--s-5);
  font-size: var(--size-small);
  line-height: 1.55;
}

@media (min-width: 48rem) {
  .proof-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* --------------------------------------------------------------------------
   10. Beyond the teardown — the paid work as line items
   -------------------------------------------------------------------------- */

.svc {
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--section-y);
  border-bottom: 1px solid var(--rule);
}

.svc-rows { margin-top: var(--s-7); }

.svc-row {
  padding-block: var(--s-6);
  border-top: 1px solid var(--rule);
}

.svc-row:last-child { border-bottom: 1px solid var(--rule); }

.svc-head {
  font-size: var(--size-row-h3);
  line-height: 1.1;
  letter-spacing: -0.018em;
}

.svc-copy {
  margin-top: var(--s-4);
  font-size: var(--size-body);
}

/* Poppins on a <p> — a price, not prose. See the note on .hero-lead. */
.svc-price {
  margin-top: var(--s-4);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.25rem;
  line-height: 1.2;
}

.svc-qual {
  display: block;
  margin-top: var(--s-1);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--size-small);
  line-height: 1.4;
  color: var(--ink-quiet);
}

@media (min-width: 62rem) {
  .svc-row {
    display: grid;
    grid-template-columns: minmax(0, 24ch) minmax(0, 1fr) 11rem;
    column-gap: clamp(1.5rem, 3vw, 3rem);
    align-items: start;
  }
  .svc-copy,
  .svc-price { margin-top: 0; }
  .svc-price { text-align: right; }
}

/* --------------------------------------------------------------------------
   11. FAQ
   -------------------------------------------------------------------------- */

.faq {
  background: var(--bg);
  color: var(--fg);
  padding-block: var(--section-y);
}

@media (min-width: 62rem) {
  .faq-grid {
    display: grid;
    grid-template-columns: var(--spine-col) minmax(0, 1fr);
    column-gap: var(--spine-gap);
    align-items: start;
  }
}

.faq-list { margin-top: var(--s-5); }

@media (min-width: 62rem) {
  .faq-list { margin-top: 0; }
}

/* Native <details>/<summary>, so the disclosure is the browser's own and still
   works with JavaScript off. The <h3> lives inside the <summary>, which the HTML
   spec allows.
   The .faq-item class is a CONTRACT, not a style hook: check-faq-drift.py selects
   on it to compare all eleven questions against the FAQPage JSON-LD. Renaming it
   breaks the deploy gate. */
.faq-item {
  border-top: 1px solid var(--rule);
  padding-block: var(--s-5);
}

.faq-list > .faq-item:last-child { border-bottom: 1px solid var(--rule); }

.faq-item summary {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  cursor: pointer;
  list-style: none;          /* Firefox's default marker */
}

.faq-item summary::-webkit-details-marker { display: none; }

.faq-item h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--size-h2-sm);
  line-height: 1.14;
  max-width: 38ch;
  flex: 1 1 auto;
}

/* The marker is drawn from two gradient bars, not typed — a literal "+" would
   be a character on the page, and every character here is locked copy. Open
   state simply drops the vertical bar, leaving a minus. No transform, no
   transition: this stays outside §14 and needs no reduced-motion gate. */
.faq-item summary::after {
  content: "";
  flex: 0 0 auto;
  width: 0.85rem;
  height: 0.85rem;
  margin-top: 0.4em;
  background:
    linear-gradient(var(--eyebrow-color), var(--eyebrow-color)) center/100% 2px no-repeat,
    linear-gradient(var(--eyebrow-color), var(--eyebrow-color)) center/2px 100% no-repeat;
}

.faq-item details[open] summary::after {
  background:
    linear-gradient(var(--eyebrow-color), var(--eyebrow-color)) center/100% 2px no-repeat;
}

.faq-item summary:hover h3 { color: var(--eyebrow-color); }

.faq-item p {
  margin-top: var(--s-4);
  max-width: var(--measure);
  font-size: var(--size-body);
}

/* --------------------------------------------------------------------------
   12. Closing band
   -------------------------------------------------------------------------- */

.cta {
  background: var(--bg);
  color: var(--fg);
  padding-block: clamp(3.5rem, 7vw, 6rem);
}

.cta-head {
  margin-top: var(--s-5);
  font-size: var(--size-cta-wall);
  line-height: 1;
  letter-spacing: -0.032em;
  max-width: 24ch;
}

/* 2px, and solid navy rather than the hairline alpha used everywhere else: on an
   orange ground a 14%-alpha navy rule is invisible, and this divider is doing
   structural work under a 5rem headline. */
.cta-row {
  margin-top: clamp(2rem, 4vw, 3rem);
  padding-top: var(--s-5);
  border-top: 2px solid var(--navy);
  display: grid;
  gap: var(--s-6);
}

.cta-copy {
  max-width: 56ch;
  font-size: var(--size-body);
}

.cta-action .btn { white-space: nowrap; }

@media (min-width: 48rem) {
  .cta-row {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
    gap: var(--s-6);
  }
}

/* --------------------------------------------------------------------------
   13a. Case study — before/after comparison figures (case-study.html only)

   Each source composite is a single 1924x822 image with BEFORE and AFTER already
   side by side inside it, so this deliberately does NOT build a two-column grid —
   the comparison is baked into the asset, and splitting it would fight the image.
   The job here is only: full measure width, no layout shift, readable caption.

   aspect-ratio is pinned to the real 1924/822 so the box is reserved before the
   image decodes. The explicit width/height attributes in the markup do the same
   job for browsers that ignore the CSS; both are present on purpose.

   The white fill matters more since V5: these are six light screenshots on a
   dark page now, and the matte is what stops them bleeding into the band.
   -------------------------------------------------------------------------- */

.compare-list {
  list-style: none;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-7);
}

.compare-item figure { margin: 0; }

.compare-shot {
  width: 100%;
  height: auto;
  aspect-ratio: 1924 / 822;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  background: var(--white);
}

.compare-item figcaption {
  margin-top: var(--s-4);
  font-size: var(--size-small);
  line-height: 1.55;
  max-width: var(--measure);
}

.compare-item figcaption strong { display: block; }

/* The case study's closing section. Body stack, not Poppins: this is 100+ words
   of running prose and the Poppins-Never-Speaks Rule holds — weight comes from
   the band and the size step instead.
   No max-width here on purpose; it inherits .prose's --measure rather than
   overriding it, and --measure can never bind at this column width anyway. The
   homepage's closing band is a different component (.cta, §12) — these two are
   not the same moment and deliberately do not share a class. */
.final .prose {
  font-size: clamp(1.25rem, 1.05rem + 0.9vw, 1.75rem);
  line-height: 1.45;
}

.final-cta { margin-top: var(--s-7); }

/* --------------------------------------------------------------------------
   13b. Contact + thank-you pages — the form works with JS off and CSS off
   -------------------------------------------------------------------------- */

/* Scaled-down wall treatment, so the page a CTA lands on is recognisably the
   same system as the page it came from without competing with the homepage's
   9rem hero. */
.contact-head h1,
.thanks-head h1 {
  font-size: var(--size-h2-lg);
  line-height: 1.06;
  letter-spacing: -0.024em;
  max-width: 22ch;
  margin-top: var(--s-4);
}

/* Its content is three short lines; without this the footer lands mid-screen on
   any normal display and a confirmation page reads as a page that failed to
   finish loading. Same 100vh-then-100svh fallback pair the pinned chapters use,
   for the same Safari reason. */
.thanks-head {
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
  display: grid;
  align-content: center;
}

.thanks-cta { margin-top: var(--s-7); }

.contact-grid { margin-top: var(--s-8); }

.convo {
  font-size: var(--size-lead);
  line-height: 1.6;
  max-width: 46rem;
}

.convo-line + .convo-line { margin-top: var(--s-6); }

.convo label { font-weight: 400; }

/* Each "sentence fragment + its blank" is one segment: stacked on small
   screens, run together into a sentence from 48rem up. */
.convo-seg { display: block; }
.convo-seg + .convo-seg { margin-top: var(--s-4); }

.convo-fill {
  display: flex;
  align-items: baseline;
  gap: 0.15rem;
  margin-top: var(--s-2);
  max-width: 24rem;
}

/* White fill, no border except the 2px underline, zero radius — the form reads
   as a sentence with blanks in it rather than a stack of fields. The ink tokens
   inside are the ON-LIGHT ones regardless of the band, because the inside of the
   field is white no matter what the page around it is doing.
   Font size is pinned at exactly 1rem: 16px prevents iOS zoom-on-focus and must
   not drop below it. The padding holds the control at >=44px for touch. */
.field {
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.4;
  color: var(--navy);
  background: var(--white);
  border: 0;
  border-bottom: 2px solid var(--teal-ink);
  border-radius: 0;
  padding: 0.7rem 0.65rem;
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
}

.field::placeholder { color: var(--teal-ink); opacity: 1; }

.field:hover { border-bottom-color: var(--navy); }

textarea.field { min-height: 6.5rem; line-height: 1.55; resize: vertical; }

.convo-fill-wide { max-width: 34rem; }
.convo-fill-block { display: block; max-width: 34rem; }

@media (min-width: 48rem) {
  .convo { line-height: 2.2; }
  .convo-seg { display: inline; }
  .convo-seg + .convo-seg { margin-top: 0; }
  .convo-fill {
    display: inline-flex;
    vertical-align: baseline;
    width: 20rem;
    margin-top: 0;
  }
  .convo-fill-wide { width: 26rem; }
  .convo-fill-block { display: block; width: 100%; margin-top: var(--s-3); }
}

/* Toggle — two radios styled as chips. The inputs stay in the a11y tree and
   stay focusable; only their painted box is clipped away. Never replace the
   input with a <div>. */
.toggle-set {
  position: relative;
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.toggle-set legend { padding: 0; }

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

/* Square as of V5, matching the buttons. The border colour comes from
   --ink-quiet so the chip is correct on whatever band the form lands on. */
.toggle-label {
  display: inline-block;
  font-size: 1rem;
  line-height: 1.3;
  padding: 0.7rem 1.1rem;
  margin: var(--s-2) var(--s-2) 0 0;
  border: 2px solid var(--ink-quiet);
  border-radius: 0;
  cursor: pointer;
}

.toggle-label:hover { border-color: currentColor; }

.toggle-input:checked + .toggle-label {
  background: var(--cream);
  color: var(--navy);
  border-color: var(--cream);
}

.toggle-input:focus-visible + .toggle-label {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
}

/* Conditional branches. With CSS disabled both branches simply render, each
   with its own visible label — the form stays complete and submittable. */
.branch { display: none; margin-top: var(--s-5); }

#path-store:checked ~ .branch-store { display: block; }
#path-process:checked ~ .branch-process { display: block; }

/* Honeypot. Deliberately NOT the .sr-only clip pattern used elsewhere:
   .sr-only keeps an element in the accessibility tree, which is exactly wrong for a
   decoy — a screen reader user would be offered a field that fails their submission
   if they fill it. display:none removes it from the tab order and the a11y tree at
   once, and a bot parsing the HTML still sees it, which is the entire mechanism.
   It still submits (display:none suppresses painting, not the value), which is what
   Netlify checks. */
.honeypot { display: none; }

.convo-note { margin-top: var(--s-6); }

.form-actions { margin-top: var(--s-8); }

/* Phase 5. The data sentence, directly under the submit button. --s-4 rather than --s-5 so
   it reads as attached to the button rather than as a new block, and the 54ch measure
   matches .microcopy so the two small-type blocks on this page agree. */
.form-privacy {
  margin-top: var(--s-4);
  font-size: var(--size-small);
  line-height: 1.6;
  max-width: 54ch;
}

.microcopy {
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
  font-size: var(--size-small);
  line-height: 1.6;
  max-width: 54ch;
}

@media (min-width: 64rem) {
  .contact-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
    column-gap: clamp(2.5rem, 5vw, 5rem);
    align-items: start;
  }
  .microcopy {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
    border-left: 2px solid var(--ink-quiet);
    padding-left: var(--s-5);
  }
}

/* --------------------------------------------------------------------------
   14. Motion — the whole moving layer, in one place

   Two effects and nothing else: a fade-up on entry, and the pinned chapter
   stack. Both are additions to a page that is complete without them. Delete
   this section and `js/motion.js` and the static page is back, intact.
   -------------------------------------------------------------------------- */

/* --- 14a. Reveal on entry ---------------------------------------------------
   The hidden state is gated behind `.js`, which js/motion.js adds at runtime —
   base CSS never hides content, so JS-off renders everything. That file also
   adds `.reveal` to its targets and `.is-in` once they arrive; elements already
   on screen get both in the same task, so nothing animates on load. */

.js .reveal {
  opacity: 0;
  transform: translateY(14px);
}

/* The transition sits on the revealed state, not the hidden one: hiding is
   instant, only the arrival eases. 520ms, ease-out, once per element. */
.js .reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 520ms cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 520ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* --- 14b. The pinned chapter stack ------------------------------------------
   Each chapter holds at the top of the viewport while the next — later in the
   DOM, so painted above it, and opaque — scrolls up and covers it. Pure
   `position: sticky`, no JavaScript involved at any point.

   `top: var(--header-h)` is what sequences them: a top-anchored sticky element
   stays in normal flow until its own top edge reaches that offset, and only then
   pins. Chapter 2 cannot reach it until chapter 1 has scrolled up, so they lock
   one at a time — never all at once, the trap a bottom-anchor version falls into.
   Sticky also clamps to its containing block (.chapter-list), so the last chapter
   has nothing after it to pin against and the stack releases into #work on its own.

   WHY --header-h AND NOT 0. The handoff specifies `top: 0`, on the reasoning that
   every chapter is a full 100svh and its copy fits. The copy does fit — but the
   header is opaque and sticky at z-index 5, so at `top: 0` the chapter's first
   59px sit underneath it. Two things break: the <the-process> label, which the
   handoff positions ~20–32px from the chapter's top edge, is invisible for the
   entire time chapter 01 is pinned (verified in-browser against the reference);
   and align-content: center centres the copy over 59px the reader cannot see, so
   every chapter reads low. Subtracting the header from both the offset and the
   height fixes both and costs nothing.

   PAINT ORDER. Among the chapters it is pure DOM order — no z-index on any of
   them. The one z-index on the page belongs to .site-header (§6), which precedes
   them in the DOM and would otherwise be painted over.

   NO TALL-CHAPTER OFFSET. The pre-V5 stack needed `top: min(...)` plus a
   --panel-min because the flagship panel's copy ran past a viewport. None of
   these three does — chapter 02 is the longest at ~350px of copy against a
   ~700px+ box. If chapter 02's copy ever grows past one viewport, reinstate that
   `min(var(--header-h), calc(100svh - var(--chapter-h)))` form rather than
   letting its last line fall below the fold and then get covered unread.

   THE GATE, all four conditions doing separate jobs:
     • min-width 62rem — matches §8's two-column chapter layout; narrower than
       this the chapters are stacked bands and pinning them would be wrong.
     • hover:hover + pointer:fine — no pin on touch, including touch laptops and
       tablets whatever their width.
     • prefers-reduced-motion:no-preference — this is where the pin is disabled
       for reduced motion; a media query that never applies beats one undone
       afterwards, so 14c only has the reveal left to neutralise. Note the
       two-column LAYOUT in §8 is gated on width alone, so a reduced-motion
       reader on a desktop still gets the designed composition, just still. */

@media (min-width: 62rem) and (hover: hover) and (pointer: fine)
       and (prefers-reduced-motion: no-preference) {

  .chapter {
    position: sticky;
    top: var(--header-h);
    min-height: calc(100vh - var(--header-h));    /* fallback: Safari < 15.4 */
    min-height: calc(100svh - var(--header-h));
  }
}

/* --- 14c. Reduced motion ----------------------------------------------------
   Zero animation, zero pinning. Pinning is excluded by 14b's own media query (a
   rule that never applies beats a rule undone afterwards), so all that is left
   to neutralise here is the reveal. */

@media (prefers-reduced-motion: reduce) {
  .js .reveal,
  .js .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* --- 14d. Smooth anchor scrolling -------------------------------------------
   The section nav jumps between sections, and smoothing that jump is motion, so
   it lives here with the rest rather than beside the nav's own styles. Gated the
   way the pin is — a media query that never applies, rather than a rule undone
   afterwards — which is why 14c above still only has the reveal to neutralise.
   The offset keeping the target clear of the header is scroll-margin-top in §3;
   that is layout, and applies to instant jumps too. */

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
