/* ==========================================================================
   direction-d-v3.css - OWNED BY CALL INSIGHTS V3. Nothing else loads this file.

   A full copy of direction-d.css taken at the 8/21 fork, not an override layer.

   Brees, 8/21: "every version should have its own CSS because one of those
   versions is going to be the main." That is the whole rationale: the version
   that wins gets shipped as DE's real page, and a page that ships should not
   be wired into two pages that were rejected. V3 can therefore be edited
   freely - there is no shared selector to break, and no pin to keep complete.

   Scope: the page: sections, hero, connector, linework.

   RED FLAG - THIS IS THE COPY DECISION THIS PROJECT ONCE REVERSED. V2 was
   first built as a 3,600-line duplicate of direction-d.css and that duplicate
   was DELETED, because two copies mean a fix to one page silently misses the
   other. The risk did not go away; it was accepted on purpose, and it is
   bounded because two of these three pages get deleted. What replaces the old
   protection is scripts/css-divergence.js: it records the parent's hash at
   fork time and tells you when direction-d.css has changed since, so a shared fix that
   missed V3 is visible instead of silent. RUN IT BEFORE ANY RELEASE.
   ========================================================================== */

/* ==========================================================================
   Direction B — "The Connector"

   Thesis: the page is unmistakably Digital Empathy. It leans hardest into
   the new identity, using the brand's own routing-line graphic device as
   the page's spine.

   Colour weighting: oat base with genuine full-bleed colour blocks. Petal,
   lime and sky each own one major section. Indigo owns Pricing. Roughly
   55% oat/white, 45% colour.

   Section rhythm: oat / petal / oat / lime / oat / sky / indigo / oat.

   Signature: the connector line — see direction-b.js for its geometry.

   Media: the boldest of the three — a muted background video behind the
   hero and a still at the final CTA. B is the direction betting the new
   brand should be visible from the first scroll, so it is the one that
   puts motion on screen immediately.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. SHELL
   -------------------------------------------------------------------------- */

.container {
  position: relative;
  /* Content always rides above the connector. This must outrank .connector's
     z-index, not merely sit later in the DOM — see the note there. */
  z-index: 2;
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-pad);
}

#main {
  position: relative;
}

.section {
  position: relative;
  padding-block: var(--section-pad);
  background-color: var(--oat);
}

.section--white { background-color: var(--white); }
.section--petal { background-color: var(--petal); }
.section--lime  { background-color: var(--lime); }
.section--sky   { background-color: var(--sky); }

.section--indigo {
  background-color: var(--indigo);
  color: var(--oat);
}

/* Full-bleed blocks square off — radius 0, per the layout system. */
.section--petal,
.section--lime,
.section--sky,
.section--indigo {
  border-radius: var(--r-bleed);
}

/* --------------------------------------------------------------------------
   2. THE CONNECTOR
   Always behind content, never interactive, never announced.
   -------------------------------------------------------------------------- */

/* z-index 1, not 0.
   The brief specifies z-index 0, but every section that follows in the DOM
   is position:relative, so at 0 the line paints UNDERNEATH each section's
   background colour and is invisible on the page — which is what happened
   on the first build. Sitting at 1 puts it above the section fills and
   below .container (z-index 2), which is what "always behind content,
   never crossing text" actually asks for. The brief also explicitly permits
   the line to cross a full-bleed block, so drawing over the petal, lime and
   sky bands is intended, not a side effect. */
.connector {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity var(--d-reveal) var(--ease-out);
}

.connector[data-active="true"] {
  opacity: 1;
}

.connector svg {
  position: absolute;
  top: 0;
  left: 0;
}

.connector [data-nodes] {
  opacity: 0;
  transition: opacity var(--d-reveal) var(--ease-out);
}

/* -- the fallback spine --
   Shown below 768px, and whenever JS has not armed the real path. The brief
   is explicit that the full routing path must not be attempted on a narrow
   viewport, so this is the intended experience there, not a degraded one. */
.connector-rule {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 24px;
  z-index: 1;
  width: 3px;
  background-color: var(--petal);
  pointer-events: none;
}

/* 🔴 NO BREAKPOINT HERE ANY MORE.

   This was `@media (min-width: 768px) { html[data-js="on"] .connector-rule {
   display: none } }` — the same 768 that direction-d.js checked. The number
   lived in two files that had to agree, and the real condition was never a
   width: it is whether there is enough gutter beside the text column to route
   a line through without touching content.

   direction-d.js now measures that and sets `data-connector-path` on <html>.
   The fallback rule keys off the outcome, so the routing path and the gutter
   rule can no longer both be on, or both be off, at some width nobody tested.

   (The 768px floor in the script also stays as a cheap early bail — but it is
   now a floor, not the decision.) */
html[data-connector-path="on"] .connector-rule {
  display: none;
}

/* With JS off the SVG never gets a path, so hide the empty host and let the
   gutter rule carry the device at every width. */
html:not([data-js="on"]) .connector {
  display: none;
}

html:not([data-js="on"]) .connector-rule {
  display: block;
}

/* --------------------------------------------------------------------------
   3. AMBIENT SHAPES
   The overlapping-circles motif from the brand guide's Colour Palette page.
   Flat fills, no blur, no opacity below 100%. Always behind content, never
   over text. Never more than three visible in one viewport.
   -------------------------------------------------------------------------- */

.shape {
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  pointer-events: none;
}

.shape--petal-lg {
  width: clamp(280px, 34vw, 520px);
  aspect-ratio: 1;
  background-color: var(--petal);
  top: 4%;
  right: -6%;
}

.shape--sky-sm {
  width: clamp(140px, 16vw, 240px);
  aspect-ratio: 1;
  background-color: var(--sky);
  bottom: 8%;
  right: 26%;
}

/* The seam-mender - resized and repositioned 8/18. Brees: "make this pink dot
   much smaller and move it left so it sits over the line."

   🔴 73.75% IS NOT A GUESS. `linework-b.svg` draws its right-hand run as
   `M1180 -40 V180 ...`, i.e. a vertical line entering the top edge at x=1180 of
   a 1600 viewBox. The band is wider than the artwork's 1.78:1, so `cover`
   scales by WIDTH and maps SVG x 1:1 onto section width - 1180/1600 = 73.75%.
   The dot is centred on that column so it caps the line where it crosses the
   seam, and centred on the section's top edge so it straddles white and oat.

   ⏹ 8/24 — 73.75% IS RETIRED, AND THE DERIVATION ABOVE IS WHY IT COULD GO.
   That column pointed at `linework-b.svg`'s right-hand vertical, and the
   linework came off this section on 8/20 ("remove the background lined art.
   Leave the circles."). The dot was capping a line that no longer exists.
   Brees' 8/24 mock centres it, so it is centred - and the mapping note stays
   only as the record of where the old number came from.

   🔴 IT IS WHITE NOW, NOT PETAL, AND THAT IS THE WHOLE POINT. The section
   above is white and this one is sky, so a white disc reads as the white
   spilling down across the colour step - which is the mending job. A petal
   dot on a sky ground reads as a stray dot instead.
   Measured: white on sky is 1.35:1, exactly the mark floor this file holds
   decorative shapes to. Do not drop the ground any lighter than --sky without
   re-measuring, and never use --petal-soft here (1.25 on oat, under floor). */
.shape--seam {
  --seam-d: clamp(56px, 6.5vw, 120px);
  width: var(--seam-d);
  aspect-ratio: 1;
  background-color: var(--white);
  top: calc(var(--seam-d) / -2);
  left: 50%;
  right: auto;
  margin-left: calc(var(--seam-d) / -2);
}

/* The two big corner discs, re-cornered 8/24 to the mock: lime bleeding off
   the TOP-RIGHT, petal off the BOTTOM-LEFT.

   🔴 THE LEFT DISC CANNOT STAY SKY. It was `--sky` on an oat ground; the
   ground is sky now, so the same disc would be invisible. Petal is the mock's
   colour and the only one of the three that reads on this field.

   🔴 OFFSET BY THE DISC'S OWN DIAMETER, NEVER BY A PERCENTAGE OF THE SECTION.
   The rules these replace used `top: -14%` / `bottom: -18%`, i.e. a share of
   SECTION HEIGHT - so how much of each disc showed depended on how tall the
   copy happened to make the section. First pass here kept that and the petal
   disc surfaced at 13% down the section instead of 76%: same CSS, different
   section height. Multiplying the disc's own width keeps the crop fixed.

   Geometry is fitted from the mock's arcs, not eyeballed: lime centre
   (1386.5, 20) r=137 and petal centre (-87, 660) r≈250 at its 1430px width,
   which normalise to the multipliers below. */
.shape--lime-lg {
  --disc: clamp(180px, 17.5vw, 340px);
  width: var(--disc);
  aspect-ratio: 1;
  background-color: var(--lime);
  top: calc(var(--disc) * -0.43);
  right: calc(var(--disc) * -0.34);
  bottom: auto;
  left: auto;
}

/* ⚠️ NOT `.shape--petal-lg`. That name is already declared above (top: 4%;
   right: -6%) and nothing in this page uses it - so reusing it would have
   worked only by source order, and quietly shadowed a live rule the moment
   anything did use it. Named for the section instead. */
.shape--petal-cta {
  --disc: clamp(280px, 30vw, 600px);
  width: var(--disc);
  aspect-ratio: 1;
  background-color: var(--petal);
  bottom: calc(var(--disc) * -0.68);
  left: calc(var(--disc) * -0.62);
  top: auto;
  right: auto;
}

@media (max-width: 639px) {
  /* Keep the three-shapes-per-viewport ceiling honest on small screens. */
  .shape--sky-sm {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   4. BUTTONS
   -------------------------------------------------------------------------- */

.btn {
  display: inline-block;
  padding: 14px 28px;
  border-radius: var(--r-pill);
  font-size: var(--fs-body);
  font-weight: 600;
  text-decoration: none;
  transition: background-color var(--d-micro) var(--ease-out);
}

.btn--primary { background-color: var(--ink); color: var(--oat); }
.btn--primary:hover { background-color: #232328; }
.btn--onDark { background-color: var(--lime); color: var(--ink); }
.btn--onDark:hover { background-color: #c6cf63; }
/* --------------------------------------------------------------------------
   HEADER — an EXACT duplication of the DE brand homepage mockup's header.

   Replaces the previous .nav / .nav-overlay chrome wholesale. That markup is
   gone from the c* variants, so its CSS is deleted rather than left to rot.

   The device is the bundle's ArchLine, scaled by ONE number. Everything is
   derived from --arch-h so the lockup can never drift out of proportion:

       k        = --arch-h / 290      (viewBox is 200 x 290)
       stroke   = 20k
       rule y   = 190k                (where the horizontal rule sits)

   🔴 The petal path runs to H-1600 — a leader that deliberately extends far
   off the left edge of the screen, exactly as the mockup does. That requires
   overflow: visible on the SVG and no clipping ancestor. It overflows LEFT,
   which does not create a horizontal scrollbar in LTR, but the 320px sweep
   still checks for it.
   -------------------------------------------------------------------------- */
.hdr {
  --arch-h: 88px;
  --k: calc(var(--arch-h) / 290);
  --arch-stroke: calc(20 * var(--k));
  --arch-rule-y: calc(190 * var(--k));

  /* STICKY — Brees, 8/14: "it's going to be sticky as we scroll down the page."

     🔴 The header had to go FULL-WIDTH to do this. It was `max-width: 1240px;
     margin-inline: auto` with no background, which is invisible while static
     and breaks the moment it sticks: the page scrolls THROUGH it, and with no
     ground of its own the content shows straight through the header, plus the
     left and right gutters outside 1240px stay transparent at every viewport
     wider than the container.

     So the element is now full-bleed and carries the oat ground, and the
     1240px measure moves to its two children instead. The lockup's petal
     leader still escapes to the left edge through `overflow: visible` on the
     SVG, which is why that keeps working. */
  position: sticky;
  top: 0;
  z-index: 40;
  /* WHITE, not oat — Brees, 8/17, with the nav rebuild. The page's first
     section below is oat, so the header now reads as its own plane rather
     than merging into the hero. ⚠️ This changes the GROUND under the lockup:
     the petal leader and lime rule were chosen against oat, and moving a mark
     to a new ground is the §24 dot rule that has bitten four times. Measured
     after the change, not assumed: `widths.js` marks 0/28 and contrast 0/219
     at every width. */
  background-color: var(--white);
  /* Brees, 8/18: "the header just cuts right at the nav and it looks kind of
     off balance." The nav row had 10px under it, so the bar's edge sat almost
     on the type. Bottom padding now roughly matches the optical air above the
     lockup. ⚠️ Every increase here pushes --hdr-h AND --hdr-h-min down, which
     moves every pinned element on the page — see the A3 note below. */
  /* 30 -> 16 on top. Brees, 8/19: "lessen the padding on the top of the
     header", with the band above the lockup marked.

     Measured: the lockup starts at exactly the padding value, so this is the
     whole gap - the SVG carries no internal top space of its own. Header
     176 -> 162px.

     ⚠️ THE STICKY OFFSETS ARE NOT AFFECTED, and that is worth knowing before
     anyone "fixes" them to match. `.hdr.is-scrolled` sets its OWN
     `padding-top: 10px`, and direction-d.js measures the condensed bar to
     publish `--hdr-h-min` (114px here). Every pinned element on the page keys
     off that, not off this. Only `--hdr-h`, which drives `scroll-padding-top`,
     moves with this - and it should, because the unscrolled header really is
     shorter now.
     📌 The bottom stays 24. It was raised on 8/18 to roughly match the optical
     air above the lockup, so it is now the LARGER of the two; if the header
     starts reading bottom-heavy, that is the number to look at, not this one. */
  padding: 16px 0 24px;
}

/* 🔴 A STICKY HEADER BREAKS EVERY IN-PAGE JUMP UNLESS YOU PAY FOR IT.

   The skip link targets #main. With the header sticky, the browser scrolls
   #main to y=0 — which is UNDERNEATH the header, so the first thing a keyboard
   user lands on is hidden behind it. The skip link would look like it worked
   and would not have.

   This project has already shipped one skip link that was present, looked
   right, and did nothing (no tabindex="-1", fixed 8/11). Not doing that twice.
   scroll-padding-top offsets every anchored jump by the header's height.

   🔴 THE HEADER'S HEIGHT IS NOW MEASURED, NOT WRITTEN DOWN (8/17).

   It was 172px here and a hardcoded 176px in three sticky offsets, all
   assuming a 160px header. The nav rebuild took the bar from 7 flat links to
   9 items with chevrons, and **it wraps to a second row at 1024 and below —
   200px, not 162px.** Every one of those numbers silently became too small:
   the pinned proof section and the card tabs would tuck up under the header,
   and anchored jumps would land behind it.

   Nothing in CSS can know that number, because it depends on where the links
   happen to wrap. `direction-d.js` measures `.hdr` and publishes `--hdr-h`;
   the value below is only the desktop fallback for a no-JS load. */
:root {
  --hdr-h: 162px;
  /* The height the header settles at ONCE SCROLLED — see the condensing note
     below. Every sticky offset uses this, not --hdr-h. */
  --hdr-h-min: 102px;
  /* Air between the pinned headline and the first pinned card. */
  /* 52, up from 28. Brees, 8/18: "it's just nav title file, and it looks very
     crunched [...] this section needs a lot more breathing room."

     🔴 THE CRUNCH IS THE PINNED STATE, NOT THE STATIC ONE. Measured: in flow
     the gaps are 135px header->title and 64px title->card, which is roughly the
     screenshot he sent. Once the headline pins they collapse to 16px and 28px -
     header, title and card stacked with nothing between them. Only the pinned
     offsets needed moving; the static ones were already right. */
  --dl-head-gap: 52px;

  /* 🔴 ONE SOURCE FOR THE PINNED TITLE'S OFFSET, BECAUSE TWO DRIFTED.
     The headline's own `top` and the card's `top` calc each carried their own
     `--hdr-h-min + 16px`. Raising only the headline moved it down and left the
     cards where they were, so the gap SHRANK instead of growing - measured 28px
     when 52px was expected. Both now read this variable; change it here only. */
  --dl-head-top: calc(var(--hdr-h-min) + 40px);
}

/* --------------------------------------------------------------------------
   THE HEADER CONDENSES ON SCROLL (8/18).

   Brees asked for the deliverable headline to be sticky, which pushed every
   pinned card down by its 74px. The cards were ALREADY taller than a laptop
   viewport (trap #7 / A3), so paying for that out of the renders would have
   meant cutting ~31% of the content out of all three email renders — the
   page's only imagery. Condensing the header instead gives the space back to
   EVERY pinned element at once: the cards, the proof section, and both new
   sticky titles.

   ⚠️ HONEST ACCOUNTING: this claws back ~60px against the headline's 74px.
   It PAYS FOR the sticky headline. It does NOT fix A3, which was already
   failing before today and still needs its own decision.

   `shared.js` has toggled `.is-scrolled` on [data-nav] past 80px all along —
   this direction simply never styled it.

   🔴 STICKY OFFSETS USE --hdr-h-min, NOT --hdr-h. If they tracked the live
   height, every pinned card would JUMP by 60px the moment the header
   condensed, mid-scroll. Nothing is pinned until the reader has scrolled, and
   by then the header is condensed — so the condensed height is the correct
   constant for them. --hdr-h stays live for scroll-padding, which is
   evaluated at jump time.
   -------------------------------------------------------------------------- */
.hdr.is-scrolled {
  --arch-h: 52px;
  padding-top: 10px;
  padding-bottom: 18px;
}

.hdr.is-scrolled .hdr__wordmark { width: 168px; }

.hdr {
  transition: padding 180ms ease;
  /* 9/16, Brees: "Header needs to have a little shadow to show depth (it blends in
     when the section bg is white)." A hairline + a soft short drop, always on, so the
     white bar reads as a plane over white sections too. Kept small: the header is
     sticky and sits over every section, so a heavy shadow would muddy the grounds. */
  box-shadow: 0 1px 0 rgba(47, 47, 53, 0.06), 0 6px 18px -6px rgba(47, 47, 53, 0.16);
}

@media (prefers-reduced-motion: reduce) {
  .hdr { transition: none; }
}

html { scroll-padding-top: calc(var(--hdr-h) + 10px); }

/* The measure moved off .hdr and onto its children — see above. */
.hdr__lockup,
.hdr__nav {
  max-width: 1240px;
  margin-inline: auto;
}

.hdr__lockup { padding-inline: var(--container-pad); }

.hdr__lockup {
  display: flex;
  align-items: flex-start;
  width: 100%;
  text-decoration: none;
  /* The arch is the link's only large hit area; keep it from collapsing. */
  line-height: 0;
}

.hdr__arch {
  flex: 0 0 auto;
  display: block;
  width: calc(200 * var(--k));
  height: var(--arch-h);
  overflow: visible; /* the petal leader lives outside the viewBox */
}

/* The lime rule is a plain flexible bar, not part of the SVG, so it can run
   to whatever width the header happens to be. It is centred on the same
   baseline the SVG's own rule stops at, so the two read as one line. */
.hdr__rule {
  flex: 1 1 auto;
  min-width: 20px;
  height: var(--arch-stroke);
  margin-top: calc(var(--arch-rule-y) - (var(--arch-stroke) / 2));
  background-color: var(--lime);
}

.hdr__wordmark {
  flex: 0 0 auto;
  width: 220px;
  height: auto;
  margin-left: 32px;
  /* Optically centre the wordmark on the rule rather than baseline-align it. */
  margin-top: calc(var(--arch-rule-y) - 15px);
}

/* Nav sits BELOW the rule, left-aligned to the arch — and WRAPS rather than
   collapsing to a hamburger, which is what the mockup does. No overlay, no
   focus trap, no toggle.

   ⚠️ 8/17: DESKTOP ONLY FOR NOW, Brees' explicit call. The nav went from 7
   flat links to 9 items, 5 of which own submenus. Below the desktop width the
   row still just wraps, so the submenu buttons work but the result is not a
   designed phone experience. **A hamburger drawer is OWED before this ships.**
   Recorded as a decision, not overlooked. */
.hdr__nav {
  margin-top: 6px;
  /* The 42px is the optical inset that lines the nav up under the arch. Now
     that .hdr is full-bleed, the container gutter has to be added to it rather
     than replaced by it — the nav would otherwise sit 42px from the VIEWPORT
     edge instead of 42px from the content edge. */
  padding-left: calc(var(--container-pad) + 42px);
  padding-right: var(--container-pad);
}

.hdr__menu {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 28px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.hdr__nav a,
.hdr__toggle {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 400;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.hdr__nav a[aria-current="page"] { font-weight: 500; }

.hdr__nav a:hover,
.hdr__toggle:hover { color: var(--indigo); }

/* --------------------------------------------------------------------------
   THE SUBMENUS — click to open. Brees, 8/17, chose click over hover.

   🔴 A REAL <button>, NOT A LINK AND NOT A DIV. These items open a menu;
   they do not navigate anywhere. A link that does not link is a lie to a
   screen reader and a broken middle-click for everyone else. `aria-expanded`
   is kept in sync by direction-d.js, and the submenu carries `hidden` so it
   is out of the tab order when closed — a lesson from a previous build, where
   a closed off-screen menu kept 19 links tabbable.
   -------------------------------------------------------------------------- */

.hdr__item--sub { position: relative; }

.hdr__toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  line-height: inherit;
}

.hdr__chev {
  width: 10px;
  height: 6px;
  flex: 0 0 auto;
  transition: transform 160ms ease;
}

.hdr__toggle[aria-expanded="true"] .hdr__chev { transform: rotate(180deg); }

.hdr__sub {
  position: absolute;
  top: calc(100% + 12px);
  left: -16px;
  z-index: 5;
  min-width: 220px;
  margin: 0;
  padding: 10px 0;
  list-style: none;
  background-color: var(--white);
  border-radius: var(--r-render);
  /* The header ground is white too, so the panel needs an edge of its own or
     it dissolves into the bar above it. */
  box-shadow: 0 1px 0 rgba(47, 47, 53, 0.12), 0 12px 28px rgba(47, 47, 53, 0.14);
}

/* `hidden` must actually hide it — `display:flex` on a parent would override
   the UA rule, and this has bitten elsewhere on this account. */
.hdr__sub[hidden] { display: none; }

.hdr__sub a {
  display: block;
  padding: 8px 20px;
  white-space: nowrap;
}

.hdr__sub a:hover { background-color: var(--oat); }

/* --------------------------------------------------------------------------
   THE MEGA PANEL + THE CTA - 9/9, ROUND TWO

   Round one made the bar shorter. Brees: "this looks like a regular
   navigation, just short. What I'm looking for is something that is more
   visual... you see images and icons. It is structured nicely for the eye to
   easily scan and follow."

   So the Products panel becomes a real mega panel: a 2x3 grid of icon cards,
   each with the product's name and a line of description, plus a labelled
   audience rail down the right. Resources gets two labelled columns. About
   stays a plain list - two items do not need a grid, and giving it one would
   be decoration pretending to be structure.

   [i] THE DESCRIPTIONS ARE ALIE'S H1s, VERBATIM, lifted from each product's
   own page. Writing menu copy is writing copy. See sync-header.py.
   [i] THE ICONS ARE INLINE SVG drawn in the brand's line style - no product
   icons existed. currentColor, so they inherit the link colour and the hover
   state for free.
   -------------------------------------------------------------------------- */

/* The panel is wider than the bar item it hangs from, so it is pinned to the
   nav's own left edge rather than to the toggle - a panel anchored to
   "Products" would run off the right of the viewport at 1024. */
.hdr__mega {
  position: absolute;
  top: calc(100% + 12px);
  left: -16px;
  z-index: 5;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s-4);
  width: max-content;
  max-width: min(920px, calc(100vw - 2 * var(--container-pad)));
  padding: 20px;
  background-color: var(--white);
  border-radius: var(--r-render);
  box-shadow: 0 1px 0 rgba(47, 47, 53, 0.12), 0 12px 28px rgba(47, 47, 53, 0.14);
}

/* [!] `hidden` MUST STILL WIN. `display: grid` here overrides the UA rule for
   [hidden] exactly the way `display: flex` does - the trap this sheet already
   records once for .hdr__sub. Restated for every display the panels use. */
.hdr__mega[hidden] { display: none; }

.hdr__mega--cols { grid-template-columns: repeat(2, minmax(180px, 1fr)); }

/* -- the product cards ---------------------------------------------------- */

.hdr__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(240px, 1fr));
  gap: 2px 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* [!] `.hdr__nav a.hdr__card`, NOT `.hdr__card` - AND THIS IS THE SECOND TIME
   THIS EXACT TRAP HAS BITTEN IN ONE SESSION. `.hdr__nav a` is (0,2,0) and sets
   BOTH `color: var(--ink)` and `white-space: nowrap` for the top-level bar. A
   bare `.hdr__card` is (0,1,0) and loses, so:
     - the Get a Demo pill shipped ink-on-ink and its label was invisible;
     - and then every product description inherited `nowrap`, ran straight out
       of its own column, and the left and right cards printed on top of each
       other.
   The comment on the old rule said the nowrap "is cleared here". It was not.
   [i] ANY new rule targeting a link inside .hdr__nav must be at least (0,3,0)
   or it silently does nothing. Saying so once, here, because the failure is
   invisible in the CSS and obvious only on screen. */
.hdr__nav a.hdr__card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  padding: 12px;
  border-radius: 10px;
  white-space: normal;
}

.hdr__card:hover { background-color: var(--oat); }

.hdr__ico-wrap {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 9px;
  background-color: var(--sky-soft);
  color: var(--indigo);
}

.hdr__ico { width: 21px; height: 21px; }

.hdr__card-text { display: block; }

.hdr__card-name {
  display: block;
  font-weight: 500;
  line-height: 1.3;
}

/* [!] MEASURED, NOT EYEBALLED: --ink-muted on --oat (the hover ground, the
   worse of the two) is the pair that matters here, and 14px is body text, so
   it owes 4.5:1. The house muted ink clears it on both grounds; a lighter
   "menu grey" would not, which is why one is not invented here. */
.hdr__card-desc {
  display: block;
  margin-top: 3px;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--ink-muted);
}

/* -- the audience rail ---------------------------------------------------- */

.hdr__rail {
  min-width: 210px;
  padding-left: var(--s-3);
  border-left: 1px solid var(--ink-12);
}

.hdr__rail ul,
.hdr__col ul { margin: 0; padding: 0; list-style: none; }

.hdr__rail-head,
.hdr__col-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0 8px;
  padding: 0 12px;
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* 65% ink on white measures 4.9:1 - over the 4.5 floor for a 13px label. */
  color: var(--ink-muted);
}

.hdr__rail-head .hdr__ico { width: 16px; height: 16px; }

.hdr__nav .hdr__mega a {
  display: block;
  padding: 8px 12px;
  border-radius: 8px;
  white-space: normal;
}

.hdr__rail a:hover,
.hdr__col a:hover { background-color: var(--oat); }

/* -- round three, 9/15: Products without the rail, See Our Work underneath ----
   Alie's Navigation doc moved Who We're For onto the bar, so the Products panel
   is the card grid alone: one column of grid, not "grid + auto rail" (an empty
   auto column still takes a gap). "See Our Work" closes the panel as a single
   link under a hairline, visibly set apart from the five product cards.
   [!] (0,3,1) ON PURPOSE - `.hdr__nav .hdr__mega a` is (0,2,1) and sets
   display:block; see the trap recorded on .hdr__card above. */
.hdr__mega--products { grid-template-columns: minmax(0, 1fr); gap: var(--s-2); }
.hdr__foot { padding-top: 10px; border-top: 1px solid var(--ink-12); }
.hdr__nav .hdr__mega a.hdr__more {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  font-weight: 600;
}
.hdr__nav .hdr__mega a.hdr__more:hover { background-color: var(--oat); }
.hdr__more-arrow { width: 14px; height: 14px; flex: 0 0 auto; }

/* -- the Get a Demo button ------------------------------------------------
   [!] SELECTOR IS `.hdr__nav a.hdr__cta`, AND THAT IS LOAD-BEARING, NOT
   TIDINESS. `.hdr__nav a` sets `color: var(--ink)` at (0,2,0); a bare
   `.hdr__cta` is (0,1,0) and LOSES, so the pill shipped ink-on-ink and the
   words "Get a Demo" were invisible on a dark lozenge. No gate caught it -
   nothing measures "is this label the same colour as its own button". */
.hdr__item--cta { margin-left: auto; }

/* 🔁 9/17, Alie (Feedbucket 987558): "Can we make this button pop a bit more? Maybe orange instead of black?
   Or pink?" Brees chose MARIGOLD. 🔴 THE LABEL HAD TO FLIP TO INK — it is not a background swap. Oat/white on
   `--marigold #ff8c1a` is 2.33:1 and on `--petal` 1.70:1; both are illegible. Ink on marigold is 5.71:1.
   🔴 This rule paints on ALL 19 PAGES (sync-header.py writes the same header into every one), so it is a
   site-wide identity change, not a homepage tweak.
   ⚠️ The hover DARKENS the orange, which LOWERS contrast against an ink label — the opposite of the old
   ink→#232328 hover. #f5810c is as far as it can go and stay above 4.5 (5.08:1). Do not darken it further
   without re-measuring; the resting 5.71 is not a budget for the hover to spend. */
.hdr__nav a.hdr__cta {
  display: inline-block;
  padding: 9px 20px;
  border-radius: var(--r-pill);
  background-color: var(--marigold);
  color: var(--ink);
  font-size: 0.9375rem;
  font-weight: 600;
  white-space: nowrap;
  transition: background-color var(--d-micro) var(--ease-out);
}

.hdr__nav a.hdr__cta:hover { background-color: #f5810c; color: var(--ink); }

/* [!] THE PANEL'S ORIGIN CHANGES HERE, NOT ITS WIDTH. An earlier version set
   `left: 0` at this breakpoint and it was not enough: the panel is positioned
   against its own <li>, so `left: 0` still starts wherever "Products" happens
   to sit in the row. Measured with that fix in place, 1024px still pushed the
   document 61px sideways while 900 and below were clean - the failure moves
   with the bar's wrap point, which is why it has to be swept across widths
   rather than checked at one.
   Making the <li> static hands the containing block to .hdr__nav, so the panel
   spans the nav's own gutters at every width below the point where it fits. */
@media (max-width: 1180px) {
  .hdr__nav { position: relative; }
  .hdr__item--sub { position: static; }

  .hdr__sub,
  .hdr__mega {
    left: var(--container-pad);
    right: var(--container-pad);
    width: auto;
    max-width: none;
  }
}

@media (max-width: 900px) {
  .hdr__item--cta { margin-left: 0; }

  /* The origin fix is inherited from the 1180 query above. */
  /* One column, and the rail stops being a rail - a 1px border down the left
     of a stacked block reads as a stray line, so it becomes a rule above. */
  .hdr__mega,
  .hdr__mega--cols { grid-template-columns: minmax(0, 1fr); }
  .hdr__grid { grid-template-columns: minmax(0, 1fr); }
  .hdr__rail {
    min-width: 0;
    padding-left: 0;
    padding-top: var(--s-2);
    border-left: 0;
    border-top: 1px solid var(--ink-12);
  }
}

/* --------------------------------------------------------------------------
   6. HERO — 6/6, circles behind the render, background video slot.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
  padding-block: calc(var(--section-pad) * 0.75) var(--section-pad);
  /* 🔴 OAT -> WHITE, 8/19. Brees: "Change the hero bg from cream to white."
     Applies to BOTH product pages by his choice — this is the shared sheet and
     V2 does not override it.

     ⚠️ TWO THINGS THIS GROUND WAS QUIETLY DOING, both measured before the swap:
     1. The header is ALREADY pure white with a 0px bottom border, so the oat
        hero was the only thing drawing the line under the nav. White on white
        merges them into one field. Left seamless on purpose, pending Brees'
        look — if it needs an edge, a 1px `--line` under the header is the fix,
        NOT a tinted hero.
     2. The deck cards are white too, so on oat they read as cards and on white
        their ground disappears and the 2px deep-tier borders carry them alone.
        Still AA as graphic boundaries — the deep tier was chosen because it
        clears 3:1 on BOTH the white card and the page — but the read is now
        "outline", not "card". Re-measured after the swap, not assumed. */
  background-color: var(--white);
}

.hero__grid { display: grid; gap: var(--s-5); }

@media (min-width: 900px) {
  .hero__grid {
    /* 7/5 rather than 6/6. At 6fr the copy column was 524px, which forced the
       headline to six lines at any readable size — the column was as much to
       blame for the height as the type was. The mockup's copy column is ~31%
       of its viewport and its headline sits on four lines. */
    grid-template-columns: 7fr 5fr;
    gap: var(--s-5);
    align-items: center;
  }
}

/* 🔴 THE HERO HEADLINE IS SIZED OFF THE MOCKUP, NOT OFF --fs-display.

   Brees, 8/14: "I do want to tighten up the section a little bit because the
   title takes up roughly 60% of the viewport screen" — and separately, to make
   it "a little bit tight like" the mockup on brand-guide page four.

   MEASURED, both sides:
     · The mockup (physical p05, footer "logo suite | 04"): its headline is
       four lines occupying ~26% of the browser viewport, and its type is
       ~3.4% of the viewport width — about 48px at 1440.
     · d1 before this change at 1920x911: headline 485px tall = 53% of the
       viewport, at 72px. --fs-display is clamp(2.5rem, 5.5vw, 4.5rem), which
       is ~5% of viewport width. That gap is the whole complaint.

   ⚠️ --fs-display is NOT touched. It is a base.css token shared by all twelve
   directions, and retuning it here would silently resize eleven other pages.
   The override is local to this headline. */
.hero__headline {
  margin-bottom: var(--s-3);
  font-size: clamp(2rem, 3.2vw, 3.25rem);
}
.hero__line-1, .hero__line-2 { display: block; }
.hero__line-1 { margin-bottom: 0.12em; }
.hero__sub { margin-bottom: var(--s-3); max-width: 52ch; }
.hero__price { margin-bottom: var(--s-4); font-size: var(--fs-small); color: var(--ink-muted); }

.hero__render { position: relative; z-index: 1; }

html[data-js="on"] [data-hero-item] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--d-hero) var(--ease-out), transform var(--d-hero) var(--ease-out);
}

html[data-js="on"] [data-hero-item].is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html[data-js="on"] [data-hero-item] { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   7. THE PROBLEM — full-bleed petal, white cards on top.
   Text on petal is always ink. White cards carry ink text too.
   -------------------------------------------------------------------------- */

.problem__headline { max-width: 20ch; margin-bottom: var(--s-5); }

.problem__cards { display: grid; gap: var(--s-3); }

@media (min-width: 900px) {
  .problem__cards { grid-template-columns: repeat(3, 1fr); }
}

.problem__card {
  background-color: var(--white);
  border-radius: var(--r-card);
  padding: var(--s-4);
  box-shadow: var(--shadow-card);
}

/* The toggle chrome that used to live here — .problem__trigger, the plus/minus
   .problem__marker and its rotate-to-close state — is DELETED, not overridden.
   Brees removed the toggles on 8/13 and the markup no longer emits a button, so
   these rules matched nothing. Section 22.2 has what replaced them. */

.problem__text { padding-top: var(--s-2); }

/* --------------------------------------------------------------------------
   8. THE ARCH — the shared half-circle primitive.

   Brees, 2026-08-13: "Instead of boxes we have almost half circles."

   This is the DE homepage mockup's own device (its "Who we work with" trio)
   promoted to a general container, and it replaces the rounded rectangle
   everywhere a group of peers needs to be shown. It is also the house answer
   to feedback_no_ai_default_cards: the emphasis comes from a brand SHAPE that
   only DE owns, not from a generic bordered box.

   Geometry: 999px on the top two corners resolves to exactly half the
   element's width, which is what makes the dome a true semicircle at any
   column width. Do not substitute a fixed px radius — it stops being a
   semicircle the moment the column resizes.

   The dome is empty space by design: --arch-dome reserves the height the
   curve eats, so content never collides with the curve.
   -------------------------------------------------------------------------- */

.arch {
  --arch-dome: clamp(3.25rem, 8vw, 5rem);
  position: relative;
  background-color: var(--white);
  border-radius: 999px 999px var(--r-render) var(--r-render);
  padding: var(--arch-dome) var(--s-4) var(--s-4);
  box-shadow: var(--shadow-card);
}

/* --------------------------------------------------------------------------
   9. HOW IT WORKS — three arches, each numbered in its own brand colour.

   Brees has asked for the mockup's coloured 01/02/03 more than once. This is
   its home: the three steps are already a real, ordered sequence, so a numeral
   here carries meaning rather than decoration.

   🔴 The numerals use the --*-deep tier, NOT raw petal/lime/marigold. The
   mockup sets these in the raw pastels at 1.49–2.33:1. See the deep-tier note
   in base.css. They are set at --fs-h2 (>=30px) which is large text, so the
   3:1 floor applies and all three clear it.
   -------------------------------------------------------------------------- */

/* 🔴 CENTRED 8/20 — BREES, V1 ONLY: "In section three on version one, I want
   to centre the title and subtext."

   ⚠️ V1-ONLY IS NOT EXPRESSIBLE IN THIS FILE. Both pages carry the SAME
   `dir-b` body class and load this stylesheet; the only thing separating them
   is that V2 loads `direction-d-v2.css` afterwards. So a V1-only change is
   made here and then explicitly pinned back for V2 there. That is the
   project's documented mechanism, not a workaround - but it does mean this
   rule is the one that must be edited if the two are ever meant to agree
   again, and that the V2 pin has to be deleted at the same time.

   `margin-inline: auto` is required alongside `text-align`. The block is
   `max-width: 46ch`, so centring the TEXT inside a box still parked on the
   left just moves the ragged edge around. Both, or neither. */
.how__intro {
  max-width: 46ch;
  margin-bottom: var(--s-4);
}
.how__sub { margin-top: var(--s-2); color: var(--ink-muted); }

/* --------------------------------------------------------------------------
   SECTION 3's INTRO, V1 ONLY — centred, with the heading on ONE LINE.

   Brees, 8/20: "In section three on version one, I want to centre the title
   and subtext." Then 8/21: "can you make the title on one line."

   🔴 THIS REPLACES A PIN, AND THAT IS THE POINT. The centring was first done
   by changing the SHARED `.how__intro` and undoing it again in
   `direction-d-v2.css`. That worked but left a pin whose only job was to
   cancel a rule V2 never wanted — and a pin outliving its reason is
   indistinguishable from a deliberate difference. A V1-only CLASS says the
   same thing once, in the place it applies. **The V2 pin was deleted with
   this change; do not reinstate it.**

   ⚠️ `max-width: none` IS ON THE BLOCK, AND THE SUB TAKES THE 46ch INSTEAD.
   The heading cannot go to one line inside a 46ch box, but the sub still
   wants a measure — letting it run the full container would give it a
   90-character line. So the constraint moves down a level rather than
   disappearing.
   -------------------------------------------------------------------------- */
/* 🔴 `width: fit-content`, NOT `max-width: none` ALONE. The first attempt used
   only `max-width: none` and howline-check went from a 27px gap to **0px at
   every width**.

   Nothing had moved on screen. The BOX had: with the cap removed the block
   stretched to the full container, and although the type inside is centred and
   much narrower, `getBoundingClientRect()` reports the block. Section 3's line
   measures its clearance against that box, so it believed the copy now ran
   under its verticals.

   ⚠️ A CENTRED BLOCK IS NOT THE SAME SHAPE AS THE TEXT INSIDE IT, and anything
   measuring "where the copy is" reads the block. Making the box hug its
   content keeps the two the same thing. `max-width: 100%` stops a `nowrap`
   child from pushing it past the container. */
.how__intro--v1 {
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  text-align: center;
}

.how__intro--v1 .how__sub {
  max-width: 46ch;
  margin-inline: auto;
}

@media (min-width: 900px) {
  /* 🔴 `nowrap` ONLY FROM 900 UP, AND THE FLOOR IS NOT DECORATIVE. Below it
     the line cannot fit any type size worth reading, and `nowrap` on a box
     narrower than its content does not shrink the text — it overflows and
     produces a page-wide horizontal scrollbar. Measured across widths rather
     than assumed; see `scripts/widths.js` and the sweep in howline-check.

     ⚠️ `text-wrap: balance` from `.t-h2` HAS TO GO. It is a wrapping
     instruction, and with `nowrap` the two disagree — balance wins in some
     engines and reintroduces the second line. */
  .how__intro--v1 h2 {
    white-space: nowrap;
    text-wrap: nowrap;
    max-width: none;
  }
}

.how__track {
  position: relative;
  display: grid;
  gap: var(--s-3);
}

@media (min-width: 900px) {
  .how__track {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--s-3);
    align-items: start;
  }
}

.how__node { position: relative; z-index: 1; }

/* Numbered markers appear here and ONLY here. This is a real sequence. */
/* The type properties are gone: this element holds an <img>, not text, so
   font-family / font-size / weight / letter-spacing had nothing to style.
   `line-height: 0` stops the inline image adding a descender gap under it. */
.how__num {
  display: block;
  /* s-3, up from s-1. Brees: "the icon just needs a little more breathing room
     from the title." The 8px gap was not a design choice - a `.how__node-dot`
     used to sit between the numeral and the title and carried its own spacing,
     so deleting the dot silently closed the gap to the numeral's own 8px
     margin. This restores the air the dot used to occupy. */
  margin-bottom: var(--s-3);
  line-height: 0;
  text-align: center;
}

/* --------------------------------------------------------------------------
   THE NUMERALS ARE IMAGES CUT FROM BREES' ICON SHEET - 8/18.

   Source: `Pastel Icon Style Guide Grid.png`, Numbers row, cropped to
   assets/img/icons/num-1-v1 / num-2-v3 / num-3-v2. One SHARED vertical crop
   (y 762-852) across all three so their baselines line up when scaled, and
   white unpremultiplied to alpha so they recomposite exactly on the white card
   and degrade gracefully anywhere else.

   [X] DO NOT REBUILD THEM IN CSS. Two previous attempts did: ink text, then the
   digit drawn twice and offset to fake the two-tone. Brees: "I don't know why
   you're overlapping things. I gave you the file to use." The real glyphs are
   two-COLOURED - different strokes of one digit carry different hues, with a
   blend where they cross - which a duplicated-and-offset copy cannot produce.
   `.how__num-stack` / `-a` / `-b` are deleted.

   Colour is baked into the artwork, so there is nothing for variant-d.css to
   set here any more - the three `--num-a`/`--num-b` pairs are gone with it.

   Sized by HEIGHT so the three different glyph widths (41/65/60 at source)
   stay undistorted and optically consistent. Intrinsic width/height are on
   each <img> so the card reserves the box before they load. */
.how__num-img {
  display: inline-block;
  height: 56px;
  width: auto;
}

/* [X] The three single-colour `.how__num { color: ... }` rules are DELETED.
   They were superseded by the two-tone variant pairs in variant-d.css, and
   were dead anyway. The numerals are now IMAGES cut from Brees' icon sheet,
   so their colour is baked into the artwork and no CSS sets it at all. They
   were also colour sitting in the geometry file, which never belongs here. */

/* The dot that used to ride the rail now sits under the numeral, keeping the
   node language without needing the rail — the page connector already carries
   that job, and two rails competing in one section read as noise. Decorative,
   so it keeps the ORIGINAL pastel: it is not text and states nothing alone. */
/* [X] `.how__node-dot` IS DELETED - 8/18. Brees: "Remove the dots under the
   numbered icons in section three."

   Rules AND markup, because these served ONLY d1-signal: d3-document is the
   only other page loading this stylesheet and it has no how-section dots.
   arcade / b-connector / c1-c3 / field keep their own copies in their own
   files, untouched.

   ⚠️ `widths.js` still lists `.how__node-dot` in its mark selector, so its
   CHECKED count drops by 3 (39 -> 36). That is the elements going away, not
   marks going unmeasured - do not "restore" the number. */

.how__step-title { margin-bottom: var(--s-2); text-align: center; }

/* Body copy stays left-aligned at a readable measure. The numeral and title
   are centred in the dome because they are short; forty words of centred body
   text is not a style choice, it is a legibility cost. */
.how__node .t-body { font-size: var(--fs-small); }

/* --------------------------------------------------------------------------
   9. THE DELIVERABLE — three cards on lime, renders inset in white.
   -------------------------------------------------------------------------- */

/* This section was 1,779px in c2 — 16% of the whole page — because it stacked
   three full-fidelity email renders at full height. It is now three arches,
   each showing the subject line and the rows that carry the point, with the
   rest of the render expanding INLINE on demand.

   🔴 Not a slider, and not a tab strip. Brees vetoed a slider on 8/11 and the
   reasoning still holds: this is the section whose entire job is showing what
   the product produces, so hiding two-thirds of it behind an interaction
   defeats the section. The distinction here is that nothing is hidden by
   DEFAULT-STATE choice — every panel shows its substance up front, and the
   expand only adds depth. With JS off, .collapse is height:auto and all three
   renders are fully open, so the no-JS page is the complete page. */

/* Centred - Brees, 8/18: "make sure 'Three emails. One daily callback list.
   No dashboard to log into.' is centered."
   ⚠️ `margin-inline: auto` IS REQUIRED HERE, not decoration. Below 900 this
   box is capped at 24ch and sits at the left edge, so `text-align: center`
   alone would only centre the text INSIDE a left-hugging box and look
   untouched. Above 900 the cap is dropped (see the sticky block below) and the
   text-align does the work on its own. */
.deliverable__headline {
  max-width: 24ch;
  margin-inline: auto;
  margin-bottom: var(--s-4);
  text-align: center;
}

/* --------------------------------------------------------------------------
   THE DELIVERABLE HEADLINE — ONE LINE, AND IT STAYS PUT.

   Brees, 8/18: "I'd like to have 'Three emails. One daily callback list. No
   dashboard to log into.' on one line so it remains visible at all times."

   Two requests in one sentence: one LINE (it was max-width:24ch, i.e. three
   lines by design) and always VISIBLE (sticky, so it holds while the card
   stack plays underneath it).

   ⚠️ ONE LINE COSTS TYPE SIZE. 63 characters cannot sit on one line at
   --fs-h2 in a 1240px container at any width we support, so the size is
   driven by the viewport instead. It is deliberately NOT --fs-h2 any more.

   🔴 `white-space: nowrap` ONLY ABOVE 900px. Below that there is no readable
   size that fits one line, and nowrap would push the page into horizontal
   scroll — which widths.js would catch, but as an overflow failure rather
   than as the design decision it actually is. Under 900 it wraps as before.

   🔴 THE STICKY HEADLINE NEEDS AN OPAQUE GROUND. The card stack slides UP
   past it; without a ground of its own the cards would show straight through
   the type. Same failure the header had when it first went sticky.
   -------------------------------------------------------------------------- */
@media (min-width: 900px) {
  .deliverable__headline {
    max-width: none;
    /* ⚠️ RESTATED, NOT INHERITED - `margin-inline: auto` above is harmless
       here but the centring must not depend on a rule that a future edit to
       the base could drop. The clamp below is untouched: this line is tuned to
       fit the container exactly at 900 and there is no slack in it. */
    text-align: center;
    /* ⛔ `white-space: nowrap` REMOVED 9/22 - IT WAS TUNED TO A LINE THAT NO
       LONGER EXISTS. The note below is kept because it records the measurement:
       the clamp fits a 63-CHARACTER line at 900 and up. Alie's VCI copy v3 made
       this headline 80 characters ("The only training required is a skill your
       team already has: calling people back."), and nowrap does not wrap - it
       overflows and gets CLIPPED. Measured at 1440: 1321px of text in a 1112px
       box, right edge at 1485px against a 1440px viewport, cut mid-word. Also
       clipped at 1280 and 900. The page never scrolled sideways, so no overflow
       checker saw it; it only shows in a screenshot.
       Brees's call, 9/22: wrap to two lines rather than shrink the type or send
       the copy back. ⚠ If this headline is ever shortened to ~63 characters
       again, nowrap can come back - but re-measure, do not assume. */
    /* Tuned so the full 63-character line fits the container at 900 and up.
       Measured across widths, not guessed at. */
    font-size: clamp(1.0rem, 2.62vw, 2.3rem);
    line-height: 1.15;
    position: sticky;
    /* 40, up from 16 - the air between the condensed header and the pinned
       title. ⚠️ SCOPED TO THE HEADLINE. `.shape-field` uses the same
       `--hdr-h-min + 16px` expression for a different job; do not unify them. */
    top: var(--dl-head-top);
    z-index: 3;
    margin-bottom: var(--s-3);
  }

  /* The standfirst under the sticky headline. Brees, 9/22: "center the subtext
     for the card section." It arrived with Alie's VCI copy v3 and had no rule of
     its own, so it inherited .t-lead's left alignment and sat under a CENTRED
     headline - measured 833px wide, flush left at x=164 in an 1112px container.
     Centred here rather than on the base rule so it matches the headline, which
     only centres from 900 up; below that both stay left, as the rest of the page
     does. The max-width is left alone - it is a measure-of-line cap, and
     `margin-inline: auto` is what actually centres the block. */
  .deliverable__sub {
    text-align: center;
    margin-inline: auto;
  }

  /* 🔴 NO BACKGROUND ON THE STICKY HEADLINE. Brees, 8/18: "I don't want that
     in a green box."

     I had painted it `var(--lime)` on the reasoning that a sticky heading needs
     an opaque ground or content shows through it. Two things were wrong:

     1. **THE SECTION IS NOT LIME.** `variant-d.css` overrides
        `.section--lime` to **oat**. I picked the colour by reading the class
        NAME instead of the computed ground, so the "invisible" band I thought
        I was adding was a genuinely visible green rectangle on oat.
        📌 Same family as the §24 dot rule: never choose a colour from a token
        name — measure the ground you are actually sitting on.

     2. **NOTHING EVER PASSES BEHIND IT ANYWAY.** The cards pin at
        `--hdr-h-min + 16 + --dl-head-h + i * --stack-tab`, i.e. card 1 pins
        exactly at this headline's bottom edge. No card top can ever rise above
        it, so there was nothing to hide. The ground was solving a problem that
        did not exist.

     ⚠️ Do not "restore" a background here. If overlap ever becomes real, the
     correct colour is the SECTION's computed ground, not the class name's. */

  /* THE STICKY WINDOW ENDS WITH THE STACK.

     Brees, 8/18: once the third card is reached, "it should just scroll up
     like normal ... it should just scroll the title and the three cards
     together." A sticky element releases at its containing block's bottom edge;
     the headline's block used to be `.container`, which runs to the end of the
     section — so it stayed pinned while the released cards slid up past it,
     which reads as the title scrolling OVER the cards. Sharing a wrapper with
     `.stack` makes it release exactly when the cards do. */
  .deliverable__pin {
    position: relative;

    /* 🔴 THE TITLE AND THE CARDS MUST RELEASE AT THE SAME MOMENT, AND SHARING
       A WRAPPER IS NOT ENOUGH TO DO IT.

       A sticky element clamps at `containingBlockBottom - itsOwnHeight`. The
       cards are ~754px tall and the title ~74px, so in one shared container
       the cards let go a full 680px of scroll BEFORE the title does. Measured:
       at scrollY 4553 the cards were at -154 (gone) while the title was still
       pinned at 118, and it stayed pinned to 5253. The title alone on screen
       with nothing under it is exactly the "title scrolls over the cards"
       Brees reported — the wrapper alone only shortened the gap.

       The fix uses the box-model quirk this project already learned the hard
       way, in reverse. From the 8/17 card-dwell work: **a parent's padding is
       INVISIBLE to a sticky child's clamp — only its CONTENT box counts.**

       So: pull the wrapper's CONTENT box up by (cardHeight - titleHeight) with
       a negative margin on the stack, then hand the same amount straight back
       as PADDING. Flow height is unchanged, nothing moves on the page, but the
       title's clamp now lands at the cards' release point.

       ⚠️ The negative margin is on `.stack`, which changes only how the PARENT
       computes its content height. `.stack`'s own content box is untouched, so
       the cards' clamping is unaffected. Do not move it onto a card.

       Both values are published by direction-d.js. With JS off they fall back
       to 0, which simply restores the old shared-container behaviour rather
       than breaking the layout. */
    /* 🔴 `+ --dl-head-gap`, NOT `- --dl-head-h`. THE OLD FORMULA WAS WRONG, AND
       IT WAS WRONG BEFORE 8/18 - raising the gap only made it visible.

       It compensated for the HEIGHT difference between title and cards and
       ignored that the two also pin at different OFFSETS. Solved properly:

         title releases when  S < titleTop + pinShorten + headH
         card  releases when  S < cardTop  + cardH
         cardTop - titleTop = headH + gap
         => pinShorten = cardH - headH + (headH + gap) = cardH + gap

       The headH terms cancel, which is why it never belonged in the formula.
       With the old value the cards let go ~94px of scroll BEFORE the title, so
       they slid up past it and out of view while the title stayed pinned -
       Brees: "Front desk file disappears when I scroll" and "section title is
       also overlapping". Verify by measuring both release points, never by
       reasoning: they must go within a few px of each other. */
    /* 🔴 `+ (n-1) * tab` WAS ADDED 8/18 AND THE FORMULA IS WRONG WITHOUT IT.
       The card release point moved when the cards were given the bottom margins
       that stop them collapsing on exit (see the `.stack__card` note). Every
       card now releases at

           cardTop(0) + cardH + (n-1)*tab

       instead of `cardTop + cardH`, i.e. two tabs later. The title's release
       was left on the old value, so it let go first and the cards slid up over
       it - Brees: "the title overlaps", the same symptom as the original L2b
       bug, reintroduced by the fix for a different one.

           title releases when  S < titleTop + pinShorten + headH
           cards release when   S < titleTop + headH + gap + cardH + (n-1)*tab
           => pinShorten = cardH + gap + (n-1)*tab

       ⚠️ THESE TWO NUMBERS ARE COUPLED. Any change to the card margins, the
       tab, or the card count moves the release point and this must move with
       it. Verify by MEASURING both release points, never by reasoning. */
    --pin-shorten: max(0px, calc(
      var(--stack-card-h, 0px)
      + var(--dl-head-gap)
      + (var(--stack-n) - 1) * var(--stack-tab)
    ));
    padding-bottom: var(--pin-shorten);
  }

  .deliverable__pin > .stack {
    margin-bottom: calc(-1 * var(--pin-shorten));
  }
}

@media (prefers-reduced-motion: reduce) {
  .deliverable__headline { position: relative; top: auto; }
}

.deliverable__cards { display: grid; gap: var(--s-3); }

@media (min-width: 900px) {
  .deliverable__cards { grid-template-columns: repeat(3, 1fr); align-items: start; }
}

/* The arch dome carries the audience label; the render sits in the straight
   part below it. A render is a rectangle and fights a curve, so it never
   enters the dome. */
/* The three-arch treatment that used to live here — .deliverable__card and its
   dome sizing, .deliverable__who, .deliverable__inset, and the whole
   .deliverable__more expand control — is DELETED, not overridden. The section
   is a tabbed panel now (section 22.4) and none of these selectors appear in
   the markup. Keeping them would leave a reader unsure which of two layouts
   the file describes. */

/* --------------------------------------------------------------------------
   10. WHAT CHANGES — sky full-bleed, 5/7 on every row.
   (Was "alternating 7/5". The alternation was never live and was rejected on
   sight when it finally rendered on 8/20. All three rows are title-left.)
   -------------------------------------------------------------------------- */

.changes__block { display: grid; gap: var(--s-3); padding-block: var(--s-5); }

/* ⏹ THE WHITE SHAPES ARE DELETED - F2, 8/18.

   B3 (8/17) turned these three blocks into white shapes with arched ends,
   replacing a hairline rule. Brees, 8/18: "we're going to remove the white
   boxes and remove the blue background."

   DELETED, NOT COMMENTED OUT. Two competing treatments left in the file is how
   a later session ends up reviving the wrong one - the `.changes__num` trap
   already recorded here. Gone: the white fill, `padding-inline`, the elliptical
   `--changes-curve` radii on `--curve-r` / `--curve-l`, and their padding.

   The blocks are plain content on the section ground again. Separation is by
   SPACE - `padding-block: --s-5` on each plus the gap below - not by a box and
   not by a rule. If they ever read as one undifferentiated run, that is a
   spacing decision to take to him, not a reason to put the box back.

   🔴 THIS COMMENT WAS UNTERMINATED FROM 8/18 UNTIL 8/20 AND ATE 50 LINES OF
   LIVE CSS. It opened at "THE WHITE SHAPES ARE DELETED" and was never closed, so
   the parser ran on to the next one it found - the close of the HORIZONTAL
   RULES comment below - swallowing the `@media` block that follows and
   `.changes__lead { position: relative; }` with it.

   Nothing looked broken, which is why it survived two days: a LATER media
   block (section 23) re-declares `.changes__block`'s 5fr/7fr columns, so the
   layout kept working. The only visible casualty was `--flip`, whose `order`
   rules exist ONLY here. Block 2 carried the class the whole time and rendered
   identical to blocks 1 and 3.

   📌 HOW IT WAS FOUND: not by reading the CSS, which looks fine, but by asking
   the browser which rules actually match `.changes__block--flip` and getting
   an EMPTY LIST. A rule that is inside a comment is not a weak rule, it is an
   absent one - and no specificity or cascade reasoning will ever explain it. */

@media (min-width: 900px) {
  .changes__block { grid-template-columns: 5fr 7fr; gap: var(--s-5); }
  /* ⏹ `--flip` IS GONE - 8/20, AND DO NOT REBUILD IT. Deleted, not commented
     out, per this file's standing rule that two competing treatments is how a
     later session revives the wrong one.

     The history, because it is genuinely confusing: the modifier existed from
     8/18 and NEVER ONCE RENDERED. Its `order` rules sat inside an unterminated
     comment (see above), so block 2 carried the class for two days and looked
     identical to blocks 1 and 3. On 8/20 the comment was fixed, the flip
     appeared for the first time, was completed (tracks swapped, lead ranged
     right, icon pushed over) - and Brees looked at it and said "undo the flip,
     it doesn't look good."

     📌 So the rejection is of the REAL thing, not of the broken version. All
     three blocks now run title-left / body-right. The section heading below
     still said "alternating 7/5" and has been corrected; it described an
     intention that never shipped. */
}

.changes__lead { position: relative; }

/* --------------------------------------------------------------------------
   THE HORIZONTAL RULES - 8/18. Brees marked them on a screenshot: a full-width
   line between each block.

   📌 THIS SUPERSEDES THE F2 NOTE ABOVE, WHICH SAID SEPARATION HERE IS BY SPACE
   ALONE. That note ended "if they ever read as one undifferentiated run, that
   is a spacing decision to take to him" - he has now taken it, and the answer
   was a rule. It is a hairline row separator, which is the standing exception
   to the no-boxes rule, NOT a return of the F2 white panels.

   🔴 IT IS THE RULE-AND-DOT DEVICE, NOT A BORDER. First build read "add
   horizontal lines" literally and shipped `border-top: 1px solid var(--line)`.
   That ignored what the screenshot's arrow was doing: it points FROM the
   little rule-with-a-dot above the pullquote DOWN to the line he drew, and the
   line he drew has a dot on its right end. He was naming the device to use,
   not just the position. Same construction as `.pullquote::before/::after`
   below - 3px --line bar, 10px petal dot centred on it - stretched full width.

   BETWEEN blocks only, never above the first or below the last: a trailing
   rule would read as the section's own bottom edge and collide with the
   proof section's ground change. `+` gives exactly the two inner gaps.

   ⚠️ BOTH PSEUDO-ELEMENTS MUST STAY `position: absolute`. `.changes__block` is
   `display: grid`, so an in-flow ::before/::after becomes a GRID ITEM and gets
   its own cell - which at >=900 would push the 5fr/7fr two-column layout into
   a third and fourth track and silently wreck the section. Absolute takes them
   out of grid flow entirely. Measured after: still exactly 2 grid children. */
.changes__block + .changes__block {
  position: relative;
}

.changes__block + .changes__block::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  /* Stops short of the dot so the bar runs INTO it rather than under it. */
  right: 12px;
  height: 3px;
  border-radius: 2px;
  background-color: var(--line);
}

.changes__block + .changes__block::after {
  content: "";
  position: absolute;
  /* -3.5px against a 3px bar centres the 10px dot on it - the exact offset
     `.pullquote::after` uses. Kept identical so the two devices read as one. */
  top: -3.5px;
  right: 0;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: var(--petal);
}

/* -- The BOTTOM rule puts its dot on the LEFT - 8/20 ------------------------
   Brees: "both rules have the dot on the right but the bottom one should have
   the dot on [the left]", and when the flip was undone: "you can leave the
   horizontal rows as it is." So the two rules stay deliberately opposite even
   though the rows they separate no longer alternate.

   🔴 RE-KEYED FROM `.changes__block--flip + .changes__block` TO `:last-of-type`
   ON THE SAME DAY, AND THAT IS LOAD-BEARING. It was first tied to the flip so
   the dot would follow the mirrored row. Undoing the flip removed the class,
   which would have silently taken this rule with it and sent the dot back to
   the right - the change would have looked like it simply did not happen.

   ⚠️ THIS IS NOW A POSITIONAL SELECTOR AND CARRIES THE USUAL COST: it means
   "the last block", so ADDING A FOURTH BLOCK MOVES THE LEFT DOT TO IT. There
   is no longer a semantic hook to hang it on, because the thing it described
   is gone. If blocks are ever added here, decide deliberately which rule the
   dot belongs to rather than letting the selector choose.

   Both offsets are set on each pseudo-element, never just one. `left: 0`
   without `right: auto` leaves BOTH live and the dot stretches the full width
   - the partial-override trap this project has now hit five times. */
.changes__block:last-of-type::before {
  left: 12px;
  right: 0;
}

.changes__block:last-of-type::after {
  left: 0;
  right: auto;
}

/* --------------------------------------------------------------------------
   THE MARK BESIDE EACH BLOCK - full history, because it has flipped five times.

   2026-08-11  `.changes__num` (an ink circle with 1/2/3) was REPLACED by two
               overlapping brand-coloured discs with a glyph over them. Brees
               wanted "icons there and not just different colors".
   08-17       Numerals restored from a tracked-edit list, killed again: "I
               don't want the number icons."
   08-18 (a)   Glyphs -> plain ink numerals on the same discs.
   08-18 (b)   Discs removed, two-tone sheet numerals stood alone.
   08-18 (c)   CURRENT - REVERTED TO THE 08-11 DEVICE. "In the What Changes
               section we weren't supposed to change the icons so we need to
               put back the circles." Discs + glyphs, exactly as they were.

   So (a) and (b) are both undone here. The two-tone sheet numerals now live in
   `.how__num` ("Three things. Zero setup headaches."), which is where Brees
   put them and the only section they measure clean in - see the note there.

   WHY THIS PAIRING SURVIVES AND THE NUMERALS DID NOT: the discs are decorative
   MARKS (1.35 floor) and the glyph over them is full ink, 5.63 at worst
   against the multiply wedge. A two-tone numeral is TEXT (3:1 floor) in a
   mid-tone colour, and on a mid-tone disc it measured as low as 1.49.
   -------------------------------------------------------------------------- */
.changes__icon {
  position: relative;
  display: block;
  width: 72px;
  height: 56px;
  margin-bottom: var(--s-2);
}

.changes__disc {
  position: absolute;
  top: 0;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  mix-blend-mode: multiply;
}

.changes__disc--a { left: 0; }
.changes__disc--b { left: 24px; }

/* [X] `.changes__glyph` IS DELETED - 8/18. Brees, with a crop of the section:
   "Remove the black icons from this section."

   The mark is now the two overlapping discs ALONE. Rules and markup both, and
   safe to delete outright: these served only d1-signal (d3-document is the
   only other page loading this stylesheet and has no What Changes section);
   direction-b / -c / -arcade / -d2 keep their own copies for their own pages.

   NOTE: this returns the discs to differing only in hue, which is what the
   8/11 note objected to when it replaced `.changes__num` with disc+glyph. That
   objection is now overruled by instruction - see the five-step history above
   `.changes__icon`, of which this is step six. Do NOT reinstate a glyph to
   "fix" the hue-only distinction. */

/* The glyph sits centred on the OVERLAP, not on the icon box.

   First attempt drew it at 40px spanning 16→56 across a 24px overlap window,
   so it straddled both discs and the wedge and read as a smudge rather than
   as an icon sitting in a circle. Verified on screen at 1:1 zoom, not assumed.

   28px centred on x=36 (the middle of the two 48px discs) keeps the whole
   glyph inside the combined mass, and centring it vertically on the discs
   rather than pinning it to the top stops it riding high. */




/* [X] `.changes__num` IS DELETED FROM THIS FILE - 8/18. It was the old
   ink-filled badge with oat text, kept "for any direction still using the
   numeral form". No such direction loads this stylesheet: d1-signal is back
   on discs + glyphs and d3-document has no What Changes section at all.
   direction-b / -c / -arcade keep their own copies, untouched. Two numeral
   classes in one file is the exact revival trap recorded above. */

.changes__body p + p { margin-top: var(--s-3); }

/* Inherited from direction-c with a coloured LEFT border, which is a standing
   house no (feedback_no_left_borders). Replaced with the brand's own device:
   a short routing rule with a dot on its end, set above the quote. Same job —
   marking the quote as set apart — in the vocabulary the page already speaks. */
.pullquote {
  position: relative;
  margin-block: var(--s-4);
}

/* [X] THE RULE-AND-DOT ABOVE THE PULLQUOTE IS DELETED - 8/18. Brees crossed it
   out on a screenshot: "remove that line."

   Both pseudo-elements are gone (the 56px --line bar and the 10px petal dot),
   along with the padding-top that existed only to make room for them.

   ⚠️ THE SAME DEVICE STILL SERVES AS THE SECTION SEPARATOR on
   `.changes__block + .changes__block`, which he asked for and kept. Removing
   it here does not retire the device - do not delete that one too. */

.pullquote p {
  font-size: var(--fs-lead);
  font-style: italic;
  line-height: 1.5;
  max-width: 52ch;
}

/* --------------------------------------------------------------------------
   11. MEDIA SLOTS
   -------------------------------------------------------------------------- */

.media-slot { margin-top: var(--s-4); }

/* 🔴 THE HERO VIDEO SLOT RULES WERE DELETED HERE ON 8/14, NOT LEFT TO ROT.

   `.media-slot--video-bg` and `.hero__slot-note` matched nothing the moment
   the hero's background-video slot came out of the markup (Brees: no product
   video exists, and the phone render IS the product shot).

   About 2.4KB of rules and their commentary went with them. A stylesheet where
   half the rules are silently outranked or unmatched is how the
   variant-vs-arch border-radius bug happened on this project.

   For the record, since the commentary is going too: that slot was `inset: 0`
   over the whole hero with a 1px dashed frame, and its top edge was what Brees
   described as "a horizontal line that looks like maybe a border shadow on the
   bottom of the header." The header never had a border. */


.media-slot__frame {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  aspect-ratio: 16 / 9;
  border: 1.5px dashed var(--ink-10);
  border-radius: var(--r-card);
  background-color: var(--oat);
  text-align: center;
  padding: var(--s-3);
}

.media-slot__label {
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-muted);
}

.media-slot__note { font-size: var(--fs-small); color: var(--ink-muted); max-width: 38ch; }

/* --------------------------------------------------------------------------
   12. THE PROOF — cards on oat with node dots aligned to each figure.
   -------------------------------------------------------------------------- */

.proof__sub { margin-top: var(--s-2); margin-bottom: var(--s-5); color: var(--ink-muted); }

.proof__cards { display: grid; gap: var(--s-3); }

@media (min-width: 900px) { .proof__cards { grid-template-columns: repeat(3, 1fr); } }

.proof__card {
  position: relative;
  background-color: var(--white);
  border-radius: var(--r-card);
  padding: var(--s-4);
  box-shadow: var(--shadow-card);
}

.proof__node {
  position: absolute;
  top: -8px;
  left: var(--s-4);
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background-color: var(--marigold);
  border: 3px solid var(--oat);
}

.proof__card:nth-child(2) .proof__node { background-color: var(--clay); }

/* --------------------------------------------------------------------------
   E6 (8/18) — THE CARD READS TITLE, INFORMATION, THEN THE MONEY.

   Brees: "the dollar amount is more about what was saved and it's not really
   the biggest call to action here. I think what we need to do is reorganize
   the information inside of those boxes. We should have: the title, the
   information, the dollar amount with 'invoiced' under it."

   The figure used to open the card, so the number was the first thing read and
   the claim came second.

   📌 IT ALSO REMOVES A DUPLICATION NOBODY HAD SPOTTED. Every amount appeared
   TWICE — once as `.proof__figure` at the top and again as `<strong>$2,391</strong>`
   inside the body sentence. Splitting the sentence at the number resolves it:
   the info line keeps Alie's words up to the amount, and the amount plus its
   "invoiced …" tail become the block beneath. **No new copy — her sentence is
   regrouped, not rewritten.**

   ⚠️ HIS REFERENCE IMAGE IS A LAYOUT, NOT A STYLE. "It shouldn't be styled
   this way. Maybe we should use the same colors and fonts." So the figure
   keeps ink rather than the image's orange, and the cards stay centred rather
   than adopting the image's left alignment. Only order and grouping changed.
   -------------------------------------------------------------------------- */

.proof__figure {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--ls-display);
}

/* `margin-top: auto` inside the card's flex column pins the money to the
   bottom, so the three figures line up across cards of unequal text length
   instead of floating wherever the copy happens to end. */
.proof__amount {
  margin-top: auto;
  padding-top: var(--s-3);
}

.proof__invoiced {
  display: block;
  margin-top: 6px;
  font-size: var(--fs-small);
  color: var(--ink-muted);
}

.proof__title { margin-bottom: var(--s-1); }

/* --------------------------------------------------------------------------
   13. PRICING
   -------------------------------------------------------------------------- */

/* ==========================================================================
   E2 — THE RULE DEVICE, PORTED FROM d3-document (8/18)

   Brees: "on the one plan everything included section, there's this line that
   goes across and then it converts into a blue line. I'd like to get that
   added into the same section on our D1 signal."

   The device is one 56px box drawing an L: `::before` runs in from off-screen
   left along the BOTTOM in petal, turns up through a 26px radius at the
   content's left edge, and `::after` carries on along the TOP to off-screen
   right in sky. The colour change at the corner is the "converts into a blue
   line" he described.

   🔴 `overflow: hidden` ON `.pricing` IS LOAD-BEARING, NOT TIDINESS. The arms
   are -50vw wide; without the clip they create horizontal scroll at every
   width. d3 carries the same clip on `.doc` for the same reason.

   ⚠️ SAFE HERE, AND THAT WAS CHECKED. An ancestor with any `overflow` other
   than `visible` disables `position: sticky` for its descendants — that is how
   `overflow: hidden` on `.deliverable` silently killed the card stack on 8/17.
   `.pricing` contains no sticky descendant, and `.proof` (which IS sticky) is
   a SIBLING, not a child, so its pinning is untouched. The pricing-slides-over
   -proof effect relies on paint order, which a clip does not change.

   Colours are the `--onDark` pairing because this band is indigo: petal 4.74:1
   and sky 5.97:1 there, both clear of the 3:1 graphic-boundary floor. On the
   light grounds elsewhere these same pastels measure 1.0-2.3 — this indigo
   band is the one place they are legal as marks.
   ========================================================================== */

.pricing { overflow: hidden; }

/* THE GAP BELOW IS s-5, NOT s-3. Brees: "the pink line in the one plan is
   really close and doesn't have a lot of breathing room when there's just so
   much breathing room on the right and left." Measured: the rule was 56px tall
   with 24px under it, inside a section with 86px of padding and ~340px of empty
   space either side - the vertical proportions were fighting the horizontal
   ones. 64px under it settles that without moving the section's own rhythm. */
.pricing__rule {
  position: relative;
  height: 56px;
  margin-bottom: var(--s-5);
  /* Matches the reveal state above so the device reads identically with JS
     off, where `.reveal` never gets its overrides. */
  transform: scaleY(-1);
}

/* The closing half. `rotate(180deg)` point-mirrors the opening device: the
   corner moves to the RIGHT edge and the two colours swap ends, so the pair
   brackets the section. Brees picked this over a plain vertical flip.

   WARNING: the ::before/::after run to -50vw, so after rotation they extend off
   the OPPOSITE side. `.pricing { overflow: hidden }` above is what keeps that
   from becoming page-level horizontal scroll - do not remove it. */
.pricing__rule--end {
  /* 🔴 scaleX(-1), NOT a 180 rotation - corrected 8/18. Brees, with the corner
     circled: "the bottom line should go down instead of up." A 180 flips BOTH
     axes, which put the sky arm left-LOW and the pink arm right-HIGH so the
     stroke ASCENDED. A horizontal mirror keeps each arm's HEIGHT and only swaps
     their ends: sky enters left HIGH, the corner turns DOWN, pink exits right
     LOW. Paired with the opening device's scaleY(-1), both strokes now descend.

     📌 IT DOES NOT CHANGE THE DRAW DIRECTION. clip-path resolves in local space
     and any x-flip maps local right-to-left onto visual left-to-right, so this
     line still draws left-to-right as asked. The two transforms are identical
     on that axis and differ only vertically. */
  transform: scaleX(-1);
  margin-top: var(--s-5);
  margin-bottom: 0;
}

/* --------------------------------------------------------------------------
   THE RULES DRAW THEMSELVES WHEN THEY ENTER - 8/18.

   Brees: "once it enters the viewport, then the line just draws itself [...]
   the top one going from the right to the left and then the bottom one going
   from the left to the right."

   Entry-triggered, NOT scroll-linked: it reuses the existing `.reveal`
   IntersectionObserver in shared.js, which fires once at 15% visibility and
   then unobserves. Nothing is tied to scroll position, so the draw always
   completes even if the reader stops moving.

   [!] THE CLIP IS ON THE PSEUDO-ELEMENTS, NEVER ON `.pricing__rule` ITSELF.
   The arms run to -50vw, far outside the element's own border box, and
   `inset()` resolves against that box - clipping the parent would amputate
   both arms at the content column. Each pseudo-element draws its borders
   INSIDE its own box, so clipping each one locally is exact.

   ONE ANIMATION SERVES BOTH DIRECTIONS. `clip-path` applies in the element's
   own coordinate space and the transform maps it afterwards, so the identical
   reveal read through `rotate(180deg)` comes out mirrored - the top device
   draws right-to-left and the bottom one left-to-right for free. Do not add a
   second, hand-mirrored animation for the end rule.

   The sky arm (::after, the right-hand run) draws first and the petal arm
   (::before, which carries the corner) follows on a delay, so the line reads
   as one continuous stroke turning the corner rather than two lines appearing.

   ⚠️ `.reveal` FIGHTS THE ROTATION AND HAS TO BE OVERRIDDEN TWICE. Its base
   state is `translateY(20px)` and `.is-visible` is `transform: none` - either
   would drop `rotate(180deg)` off the end rule. base.css's reduced-motion
   block ALSO forces `transform: none` on `.reveal`, so the override is
   repeated inside a reduced-motion query below. Remove either and the bottom
   device silently un-rotates.
   -------------------------------------------------------------------------- */
html[data-js="on"] .pricing__rule.reveal,
html[data-js="on"] .pricing__rule.reveal.is-visible {
  /* These draw; they never slide or fade.

     🔴 scaleY(-1), NOT rotate(180deg) - 8/18. Brees: "instead of the pink line
     going up I would like to flip that [...] the blue line stays but instead of
     going up it goes down for the pink."

     A 180 rotation flips BOTH axes and would carry the sky arm over to the
     LEFT, which is the one thing he said should stay put. A vertical flip
     leaves the horizontal arrangement alone - sky still the right-hand
     outgoing run - and only inverts the corner, so the stroke now DESCENDS
     into the blue instead of rising into it.

     It also leaves the draw direction untouched: scaleY does not touch x, so
     the reveal still runs right-to-left, which is the motion he liked. */
  opacity: 1;
  transform: scaleY(-1);
}

/* Order matters: same specificity as the pair above, so it must come after.
   Must stay in step with the base rule - see the scaleX note there. */
html[data-js="on"] .pricing__rule--end.reveal,
html[data-js="on"] .pricing__rule--end.reveal.is-visible {
  transform: scaleX(-1);
}

html[data-js="on"] .pricing__rule.reveal::before,
html[data-js="on"] .pricing__rule.reveal::after {
  /* Anchored at the element's own RIGHT edge, zero width. */
  clip-path: inset(0 0 0 100%);
}

html[data-js="on"] .pricing__rule.reveal.is-visible::after {
  clip-path: inset(0);
  /* 8/19: 620ms -> `--d-reveal`. Was the last hardcoded draw duration on
     the page; see the token's note in base.css. The 460ms offset on ::before
     below is KEPT — it is the two-stage sequence, not a duration. */
  transition: clip-path var(--d-reveal) var(--ease-out);
}

html[data-js="on"] .pricing__rule.reveal.is-visible::before {
  clip-path: inset(0);
  transition: clip-path var(--d-reveal) var(--ease-out) 460ms;
}

@media (prefers-reduced-motion: reduce) {
  /* shared.js adds `is-visible` immediately here and base.css zeroes every
     transition-duration, so the lines land fully drawn with no motion. The
     mirror override is restated because base.css's reduced-motion
     `.reveal { transform: none }` would otherwise win over the rule above. */
  html[data-js="on"] .pricing__rule--end.reveal,
  html[data-js="on"] .pricing__rule--end.reveal.is-visible {
    transform: scaleX(-1);
  }
}

/* The incoming arm: along the bottom from off-canvas left, turning up. */
.pricing__rule::before {
  content: "";
  position: absolute;
  left: -50vw;
  bottom: 0;
  width: calc(50vw + 3px);
  height: 100%;
  border-bottom: 3px solid var(--petal);
  border-right: 3px solid var(--petal);
  border-bottom-right-radius: 26px;
}

/* The outgoing run: along the top, off-canvas right, in sky. */
.pricing__rule::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  right: -50vw;
  height: 3px;
  background-color: var(--sky);
}

/* ⏹ `.pricing__inner`'s 760px CAP IS DELETED - 8/20. Brees: "the one plan
   everything included, can we make sure that it is site width", on both V1
   and V2.

   The element carries `container pricing__inner`, and `.container` already
   sets `max-width: var(--container-max)` (1240px). This rule existed only to
   pull the pricing band 480px narrower than every other section on the page.
   Removing it is the whole change - the band now inherits the site width like
   everything else, rather than being given a second, competing number.

   ⚠️ THE CLASS STAYS IN THE MARKUP. It is still the hook the price, the rule
   device and the plan rows are scoped through; only the width override is
   gone. Do not "tidy up" by deleting the attribute.

   📌 Both pages share this markup, so one deletion covers both. No V2 pin is
   wanted here - he asked for the change on BOTH versions. */
.pricing__price { margin-block: var(--s-3) var(--s-4); }

.pricing__amount {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--ls-display);
}

.pricing__per { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500; }
.pricing__terms { display: block; margin-top: var(--s-1); font-size: var(--fs-small); color: var(--oat-75); }

.pricing__table th {
  padding: 14px 0;
  border-bottom: 1px solid var(--oat-14);
  font-weight: 400;
  text-align: left;
}

.pricing__table td {
  padding: 14px 0;
  border-bottom: 1px solid var(--oat-14);
  text-align: right;
  white-space: nowrap;
  font-size: var(--fs-small);
  color: var(--oat-75);
}

.check { display: inline-block; vertical-align: -2px; margin-right: 6px; }

.pricing__closer { margin-block: var(--s-4); font-size: var(--fs-lead); max-width: 46ch; }

/* --------------------------------------------------------------------------
   14. FAQ
   -------------------------------------------------------------------------- */

/* ⏹ THE TWO-COLUMN STICKY FAQ IS REVERTED - F4, 8/18.

   Built earlier today at his request ("title on the left and the FAQs on the
   right, the title should stay sticky"), then: *"instead of having it in two
   columns, let's just go ahead and put it in a single column like how it was
   before. I think I like that view better."*

   DELETED, NOT COMMENTED OUT - the grid, the sticky headline and its offset,
   the `.faq__list` cap and the reduced-motion rule that existed only to unstick
   it. Leaving a dormant two-column layout in the file is how a later session
   revives the wrong one; the `.changes__num` trap is already recorded here.

   📌 Three fixes made while the two-column version existed are NOT lost,
   because they were never part of it:
     - "Frequently Asked Questions" (E3) is copy and stays.
     - The headline's `overflow: auto` scrollbar bug was FIXED BY DELETION on
       8/18. Nothing here reintroduces it. **Never put `overflow` on this
       heading** - a 2px sub-pixel rounding difference paints a full scrollbar.
     - `.faq__marker` rotates its ARMS, not its box, so the marker no longer
       reports 4px of phantom horizontal overflow. Unrelated to layout.

   Back to the single 780px reading column with the headline stacked above it,
   exactly as it was before. */
/* NO WIDTH CAP - 8/18. Brees: "FAQs right now, they're really constricted. I
   wanna make those site-wide so the width on that section should get a little
   bit larger." It was 780px inside a ~1112px container, which read as a narrow
   inset against every neighbouring section.

   WARNING: THE ROWS GO FULL WIDTH, THE PROSE DOES NOT. `.faq__a` keeps its
   `68ch` cap, so the rules and questions span the container while answer line
   length stays readable. Removing that cap too would push answers past 110
   characters. (The F4 single-column revert above is intact - only the 780px
   cap is gone.)

   [X] THE RULE IS DELETED, NOT SET TO `none`. `.faq__inner` also carries
   `.container`, so `max-width: none` did not "restore the container width" - it
   OVERRODE the container's own 1240px cap and blew the section out to the full
   1440px viewport, measured. Removing the declaration lets `.container` govern,
   which is what "site-wide" means here. */
/* Centred 8/18 - Brees: "center the frequently asked questions title".
   ⚠️ TEXT-ALIGN ONLY. Never put `overflow` on this element: a 2px sub-pixel
   rounding difference paints a full scrollbar (fixed by deletion on 8/18). */
.faq__headline { margin-bottom: var(--s-4); text-align: center; }

/* 🔁 EVERY FAQ IS AT LEAST 80vh — Brees, 9/15: "FAQ sections on all pages should
   be at least 80vh". One rule for every page: .faq (Call Insights, SEO,
   Websites, Google Ads, the homepage) and .si-faq (Smart Intake).
   - + var(--corner): the closing CTA pulls up over the FAQ's bottom by one
     corner, so that strip is never seen. Adding it keeps the VISIBLE height at
     80vh. Google Ads uses its own --gads-corner; google-ads.css overrides this.
   - Flex column + centred, so the extra height opens above and below the
     questions instead of collecting under them. Padding is untouched, so the
     corner pad-out that corner-check asserts is unchanged. */
.faq,
.si-faq {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: calc(80vh + var(--corner, 0px));
}

.faq__item { border-top: 1px solid var(--ink-10); }
.faq__item:last-child { border-bottom: 1px solid var(--ink-10); }
.faq__q { font-size: var(--fs-body); font-weight: 500; }

/* ⏹ ALTIVO, NOT THE DISPLAY FACE — 8/24, Brees. The questions were set in
   All Round Gothic at `--fs-h3`, which made every row of the accordion read as
   a heading; they are controls, and the body face sits them back down.

   📌 THE NEGATIVE TRACKING WENT WITH IT. `-0.01em` was chosen for All Round
   Gothic, a rounded display face that takes tightening at size. Altivo does
   not — carrying the old value over would have been a leftover of a typeface
   that is no longer there.
   ⏹ WEIGHT 400, not 500 — Brees, same day. Both are real loaded Altivo faces
   (400/500/700 measure 432.1/436.9/441.3px for the same question), so this is
   a genuine face, not a synthesised one. What now separates a question from
   its answer is size and the rule between rows, not weight. */
.faq__q button {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
  width: 100%;
  padding-block: var(--s-3);
  font-family: var(--font-body);
  font-size: var(--fs-h3);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.3;
}

.faq__marker {
  position: relative;
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-top: 6px;
}

.faq__marker::before,
.faq__marker::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  background-color: var(--ink);
  border-radius: 1px;
  /* The transition moved here with the rotation. */
  transition: transform var(--d-marker) var(--ease-out);
}

.faq__marker::before { width: 14px; height: 2px; transform: translate(-50%, -50%); }
.faq__marker::after  { width: 2px; height: 14px; transform: translate(-50%, -50%); }

/* 🔴 ROTATE THE ARMS, NOT THE BOX.

   This was `transform: rotate(45deg)` on `.faq__marker` itself. An 18px square
   rotated 45 degrees has a bounding box of 18 x root2 = **25px**, so every
   expanded row reported 4px of horizontal overflow on `.faq__item` — nothing
   visible (the rotated arms only span ~10px), but it dirtied scrollWidth on
   the list and its ancestors.

   Same family as the `rotate(-4deg)` phone false positives already documented
   for overlap.js: a rotated element's BOUNDING BOX is not its visual footprint.
   Rotating the two pseudo-element arms instead gives an identical "x" while the
   marker's own box stays exactly 18px. */
.faq__q button[aria-expanded="true"] .faq__marker::before,
.faq__q button[aria-expanded="true"] .faq__marker::after {
  transform: translate(-50%, -50%) rotate(45deg);
}

.faq__a { padding-bottom: var(--s-3); max-width: 68ch; }

/* --------------------------------------------------------------------------
   15. TESTIMONIAL + FINAL CTA
   -------------------------------------------------------------------------- */

/* ⏹ THE WHOLE `.testimonial__*` BLOCK IS DELETED — 8/24. It styled the white
   panel, the client logo and the panel's attribution; Brees' mock takes the
   panel and the logo out, and nothing on this page carries those classes any
   more (checked: zero matches in the page). The replacement is `.cta-quote`,
   in section 15 below. The 8/18 reasoning it carried is not lost — the part
   that still binds (why the practice name is not said twice, why a hairline
   does the separating) moved into the new block and into the markup.
*/

/* --ink-muted is calibrated against oat and white. Petal, lime and sky are
   darker grounds, so muted ink drops under 4.5:1 on them — the attribution
   measured 4.09:1 on petal. On any colour block, secondary text goes solid
   ink and takes its hierarchy from size and weight instead.
   B is the only direction this applies to: it is the one that puts body
   copy on full-bleed colour.

   NOT .changes__num: in this direction that is an ink-filled badge with oat
   text, so forcing it to ink made it ink-on-ink at 1:1. It carries its own
   background and was already compliant. Only text sitting DIRECTLY on a
   colour block belongs in this list. */
.section--petal .cta-quote__name,
.section--petal .t-eyebrow,
.section--lime .t-eyebrow,
.section--sky .t-eyebrow {
  color: var(--ink);
}

/* 🔴 `overflow-x: clip`, NOT `overflow: hidden` - 8/18. Brees: "i don't like
   the abruptness here."

   THE SEAM WAS TWO EDGES ON ONE LINE. `.shape--sky-lg` sits at `top: -14%`,
   i.e. drawn to BLEED off the top - but `overflow: hidden` cut it flat exactly
   where the white -> oat colour step happens. A circle sliced flat, landing on
   the colour change, is what made the edge read hard.

   `overflow-x: clip` keeps the horizontal containment that was the whole point
   of the original `hidden` (these discs are far wider than their offsets and
   WILL cause horizontal scroll unchecked) while letting the vertical axis stay
   visible, so the disc crosses the seam instead of stopping at it.

   ⚠️ `hidden` CANNOT DO THIS. `overflow-x: hidden` forces the other axis to
   `auto`; only `clip` leaves `visible` alone. Do not "tidy" it to a shorthand.

   📌 It paints over the FAQ because of DOM ORDER, not z-index - this section
   comes later. Adding a z-index would fight the connector, which owns 1.

   Verified: no horizontal scroll at 900/1280/1440/1600, and no overlap with
   any VISIBLE FAQ content. ⚠️ Measure that overlap against `.faq__q` and the
   `.collapse` boxes, NOT `.faq__a` - a collapsed answer keeps its height
   inside a clipped wrapper and reports a false overlap. */
.final-cta {
  position: relative;
  /* `isolation: isolate` is inherited business from `.linework`, which this
     section carried until 8/20. Brees: "remove the background lined art.
     Leave the circles." Dropping the class also drops the isolate, and that
     is a stacking context, not decoration — the three `.shape` circles sit at
     z-index 0 and `position: relative` alone (z-index auto) does NOT create
     one. Kept explicitly so removing the artwork changes the artwork and
     nothing else. Verified: all three circles land on the same pixel as
     before the change. */
  isolation: isolate;
  overflow-x: clip;
  overflow-y: visible;
  /* 8/24 — sky, from Brees' mock. Sampled off the image at #c6e2ee, which is
     this page's own `--sky` to the byte, so the mock is quoting the palette
     back rather than introducing a colour. */
  background-color: var(--sky);
}

/* --------------------------------------------------------------------------
   E4 (8/18) — THE CLOSING BLOCK IS TWO COLUMNS, LIKE THE MOCKUP.

   The testimonial moved here out of "Three Calls Almost Lost". The mockup's
   closing section is the ask on the left (headline, button) and the quote on
   the right in its own panel, both riding the circle field — so this is that,
   not a centred stack with a quote bolted underneath.

   ⚠️ The block stops being CENTRED as a result. `text-align: center` was right
   for one sentence and one action; with two columns it left every line ragged
   on both sides. Left-aligned per column.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   V3 ROUND 10 (8/24) — THE CLOSING BLOCK IS ONE CENTRED COLUMN AGAIN.

   ⏹ The 8/18 two-column split (ask left, quote panel right) is GONE. It was
   built from the homepage mockup; Brees' 8/24 mock for THIS page centres the
   ask and runs the quote full width underneath a hairline, so the grid, the
   `align-items: center` and the per-column left alignment all come out.

   The block is capped at 54rem rather than the container's 1100px: in the
   mock the rule, the headline's second line and the quote row all share one
   measure (~820px at its 1430px width, i.e. 57% of the viewport). Capping the
   inner rather than each child is what keeps them agreeing at every width.
   -------------------------------------------------------------------------- */
.final-cta__inner {
  /* 62rem, not 54: `--container-pad` takes 4rem off each side at desktop, so
     54rem left only 736px of text and `text-wrap: balance` broke the headline
     into THREE lines where the mock has two. The measure that matters is the
     TEXT width (~830px), not the box. */
  max-width: 62rem;
  padding-block: var(--s-4);
  text-align: center;
}

/* Bigger than `--fs-h2`, which is capped at 48px. The mock sets this ~1.3x
   that, and it is the last thing on the page that has to carry a question -
   so it gets its own step between h2 and display rather than borrowing the
   hero's `--fs-display` (72px), which would have the closing ask shouting as
   loud as the page's own h1. Lands at 64px from 1455px up. */
.final-cta__headline {
  font-size: clamp(2.25rem, 4.4vw, 4rem);
  margin-bottom: var(--s-4);
}

/* 🔴 SHARED, BECAUSE THE MARKUP IS SHARED — moved here 9/2 from
   seo-content.css, where it had been written as a page-local rule.

   The supporting line under the closing headline is optional: Call Insights
   and Google Ads have headline + button only, SEO added a sub, and Websites
   later copied SEO's MARKUP without its stylesheet. So on Websites the sub had
   no margin at all and the button sat flush against the last line of copy —
   measured 0px. Brees: "spacing issue."

   ⚠️ A rule that belongs to a SHARED block does not belong in a page sheet.
   Nothing warned: four gates were green, because none of them measures the gap
   between two ordinary elements. scripts/rhythm-sweep.js does now. */
.final-cta__sub {
  max-width: 54ch;
  margin: 0 auto var(--s-4);
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   THE QUOTE, OUT OF ITS PANEL (8/24)

   🔴 THE WHITE CARD IS GONE. The 8/18 panel was the documented exception to
   the standing no-boxes rule; the mock drops it, which puts this block back
   ON the rule rather than beside it - the quote sits straight on the ground
   and a hairline does the separating. Nothing here should grow a background,
   a radius or a shadow again.

   Left-aligned inside a centred section on purpose: three lines of quotation
   centred would rag on both edges, which is exactly the reason the 8/18 note
   gave for un-centring the old two-column version.
   -------------------------------------------------------------------------- */
.cta-quote {
  margin: var(--s-5) 0 0;
  padding: var(--s-4) var(--s-4) 0;
  border-top: 1px solid var(--line-ink);
  text-align: left;
}

@media (min-width: 720px) {
  .cta-quote {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 12rem;
    gap: var(--s-4);
    align-items: start;
  }
}

.cta-quote__text {
  margin: 0;
}

.cta-quote__text p {
  font-size: var(--fs-lead);
  line-height: 1.5;
}

/* The attribution, behind a vertical rule - the same hairline device the page
   already uses between the What Changes blocks, and the reason this block
   needs no box. */
.cta-quote__by {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line-ink);
}

@media (min-width: 720px) {
  .cta-quote__by {
    margin-top: 0;
    padding: 0 0 0 var(--s-3);
    border-top: 0;
    border-left: 1px solid var(--line-ink);
  }
}

/* The person's name leads, the practice lockup sits under it (8/24, Brees).

   ⏹ `.cta-quote__practice` IS GONE. It set the practice name as bold text for
   one round; the lockup carries the practice again, so styling that name in
   type as well would be the same words twice in two treatments - the exact
   thing the 8/18 note took out.

   📌 SOLID INK, NOT `--ink-muted`, AND THAT IS A DELIBERATE CHANGE FROM THE
   ROUND BEFORE. Muted measured 4.64:1 on --sky: passing, but by 0.14, and
   this file's own note says secondary text on a colour ground goes solid ink
   and takes its hierarchy from size and weight instead. As the TOP line of
   the attribution it is no longer secondary anyway. Now 9.83:1. */
.cta-quote__name {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
}

/* Sized by HEIGHT so a future client lockup with a different aspect drops in
   without re-measuring; `width: auto` keeps it undistorted, and the intrinsic
   width/height on the <img> reserve the box so it cannot shift the row as it
   loads. `max-width: 100%` is what stops a wide lockup from pushing out of
   the 12rem column. */
.cta-quote__logo {
  display: block;
  /* 34px, not the panel's old 30: the lockup renders 172px wide here against a
     192px column, so it reads at the size the name above it does without
     touching the column's right edge (38px would fill it exactly). */
  height: 34px;
  width: auto;
  max-width: 100%;
  margin-top: var(--s-2);
}

/* --------------------------------------------------------------------------
   16. FOOTER
   -------------------------------------------------------------------------- */

.footer {
  position: relative;
  /* 🔴 ABOVE EVERY SECTION, ADDED 9/2. The closing CTA carries a large --petal
     disc that bleeds past its own box; while that section was raised to win a
     stacking fight further up the page, the disc stopped being covered and
     painted a pink circle across the footer. Brees found it. The footer is the
     last thing on the page and nothing above it should ever paint over it, so
     it stops depending on DOM order and says so. */
  z-index: 4;
  padding-block: var(--s-6) var(--s-4);
  background-color: var(--ink);
  color: var(--oat);
}

/* The arch-figure frieze across the top of the footer.

   First attempt used <img> + object-fit: cover at a fixed height, which
   BEHEADED the figures — the 1600×170 strip got cropped to a 74px band and
   only the dot heads and the tops of the arches survived. Verified on screen,
   not assumed.

   The brand's own BrandPattern component sets `repeat-x` with
   `background-size: auto 100%`, which scales the artwork by HEIGHT and tiles
   horizontally. The figures keep their proportions at any viewport width and
   the row simply gets longer, which is what a frieze is supposed to do.
   Following the component rather than re-inventing it. */
.footer__frieze {
  display: block;
  width: 100%;
  height: 58px;
  background-image: url("../img/brand/footer-frieze.png");
  background-repeat: repeat-x;
  background-size: auto 100%;
  background-position: left bottom;
  margin-bottom: var(--s-5);
}

@media (min-width: 900px) {
  .footer__frieze { height: 74px; }
}

/* --------------------------------------------------------------------------
   FOOTER — the mockup's structure. Brees, 8/18: "I like the footer on
   Digital Empathy Homepage.html more."

   Adopted: the full lockup instead of a lone 48px arch mark, the four-column
   1.8/1/1.2/1.4 grid, blurb + tagline paired in the wide column, links split
   into Pages and Resources, a newsletter signup, and a hairline over a
   copyright row carrying the phone on the right.

   🔴 ITS TEXT COLOURS WERE NOT ADOPTED, ON PURPOSE. The mockup runs
   rgba(oat, .5) for the eyebrows and the copyright row; that measures ~4.2:1
   on ink, under the 4.5 floor. Importing the mockup's text tokens is the
   documented trap on this project — it previously measured 18 failures across
   119 pairs. --oat-75 (7.3:1) and --oat-60 (5.24:1) give the same three-tier
   hierarchy and pass. Do not "restore" the mockup's values.
   -------------------------------------------------------------------------- */

/* 🔴 THE LOCKUP DERIVES FROM --arch-h, EXACTLY LIKE THE HEADER.

   First build hardcoded `width: 50px`, `height: 5px`, `margin-top: 33px`. The
   arch is an SVG on a 200x290 viewBox whose lime leg exits the right edge at
   y = 190, so the bar that continues it must sit at `190 * k` where
   `k = --arch-h / 290`. At the footer's size that is ~47px — the hardcoded 33
   put the rule **~14px above the line it is supposed to continue**, which is
   the break Brees saw. Same three tokens as `.hdr`; change --arch-h alone and
   everything follows. */
/* [X] THE FOOTER LOCKUP IS DELETED - 8/18. Brees: "remove the digital empathy
   icon line and logo [...] Instead add this logo above the text on the left."

   Gone with it: `.footer__arch` (the petal/lime arch linework), `.footer__rule`
   (the bar that continued the SVG's own rule), `.footer__wordmark`, and the
   `--arch-h / --k / --arch-stroke / --arch-rule-y` token set they shared.
   Rules and markup both. `.hdr` keeps its own copy of that token set - this
   deletion does not touch the header.

   The replacement is Alie's primary logo, OAT cut, from Drive. Oat because the
   footer ground is near-black; the standard mark would vanish. Sized by width
   so the lockup's own proportions survive, with intrinsic width/height on the
   <img> so the footer reserves its box before it loads. */
.footer__logo {
  display: block;
  width: clamp(180px, 18vw, 240px);
  height: auto;
  margin-bottom: var(--s-3);
}

.footer__grid { display: grid; gap: var(--s-4); }

/* ⚠️ FOUR COLUMNS NEED REAL ROOM. Holding the 1.8/1/1.2/1.4 split down to
   900px left the newsletter column ~120px wide, which is where the email
   field collapsed. Two columns carry the middle band instead. */
@media (min-width: 900px) {
  .footer__grid { grid-template-columns: repeat(2, 1fr); gap: var(--s-4) var(--s-5); }
}

@media (min-width: 1200px) {
  .footer__grid { grid-template-columns: 1.8fr 1fr 1.2fr 1.4fr; gap: var(--s-4); }
}

.footer__brand { display: grid; gap: var(--s-3); align-content: start; }

.footer__desc {
  font-size: var(--fs-small);
  color: var(--oat-75);
  max-width: 36ch;
  line-height: 1.62;
}

/* [X] `.footer__tagline` IS DELETED - 8/18. Brees: "Remove where it says
   'Empathy engineered for Growth'." Its nowrap media query and the
   `-em` accent rule went with it.

   📌 THAT REMOVES THE PAGE'S ONLY USE OF --font-accent (Andora). The licence
   item is NOT closed by this: Andora is still loaded by `fonts.css` and D1 in
   Alie's sitemap doc still needs the one-time-licence swap before this serves
   a public page. Do not tick it off on the strength of this deletion. */




.footer__col { display: grid; gap: 10px; align-content: start; }

.footer__eyebrow {
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* The mockup uses oat at 50% here. That fails; 75% carries the same
     "quieter than the links" read and passes at 7.3:1. */
  color: var(--oat-75);
}

.footer__col a,
.footer__bottom a {
  font-size: var(--fs-small);
  color: var(--oat-75);
  text-decoration: none;
}

.footer__col a:hover,
.footer__bottom a:hover { color: var(--oat); text-decoration: underline; }

.footer__news { display: flex; gap: 8px; min-width: 0; flex-wrap: wrap; }

/* 🔴 `flex: 1 1 0` + `width: 0` + `min-width: 0` LET THIS SHRINK TO NOTHING.
   Measured: the placeholder needs 133px and the field offered 127px at 1600
   and **9px at 900** — truncated at every single width, reading as
   "you@practice.con". A flex basis it can actually wrap on is the fix: below
   ~170px the Join button drops to the next line instead of crushing the
   field. */
.footer__input {
  flex: 1 1 170px;
  min-width: 170px;
  background-color: rgba(243, 238, 232, 0.08);
  border: 1px solid rgba(243, 238, 232, 0.35);
  border-radius: var(--r-pill);
  padding: 10px 16px;
  color: var(--oat);
  font-family: var(--font-body);
  font-size: var(--fs-small);
}

/* The placeholder is decoration, not the label — but it still has to be
   readable, and a 1px hairline border has to clear 3:1 as a UI boundary. */
.footer__input::placeholder { color: var(--oat-60); opacity: 1; }

/* 🔴 RESPONSIVE PASS 9/17 — iOS ZOOMS THE PAGE INTO ANY FIELD UNDER 16px.
   --fs-small computes to 15px here, so tapping the newsletter field on an iPhone zoomed the whole page in and left
   it zoomed after the keyboard closed — on all 19 pages, because this is the footer. Only on touch-first devices,
   where the zoom happens; a mouse user keeps the 15px that matches the Join button beside it. */
@media (pointer: coarse) {
  .footer__input { font-size: max(16px, var(--fs-small)); }
}

.footer__join { padding: 10px 22px; font-size: var(--fs-small); }

/* ============================================================ FOOTER NEWSLETTER - THE REAL FORM (9/21)
   Brees: "I'd like it to be embedded on there as it currently is." The dead placeholder field + Join button
   that used to live here (.footer__input / .footer__join, kept above - the button class is still used
   elsewhere) is replaced by JotForm 262634500920046 in an iframe, written by scripts/sync-footer-newsletter.py.

   🔴 NOTHING HERE CAN STYLE THE FORM. The iframe is cross-origin: these rules size the frame, and the form's
      own look comes from ITS custom CSS inside JotForm. Do not add field styling here expecting it to land.
   ⚠️ min-height is only the pre-handler moment. JotForm's embed handler sets the height inline once it loads,
      and again when the thank-you replaces the fields - so the footer reflows a little on submit, by design.
   [i] A Perspectives page embeds the same form in its close panel, so its footer gets .footer__news--link
       instead: one form per page, no duplicate ids. */
.footer__news--embed { display: block; width: 100%; min-width: 0; }
.footer__news-frame {
  display: block;
  width: 100%;
  /* [!] 9/21: WAS 280px, AND THAT WAS 118px OF DEAD SPACE ON ALL 21 EMBED PAGES. Measured with playwright at
     six widths: the handler sets 162px when the pills stack and 52px when they sit on one row (the form flips
     at a FRAME width of ~600px, its own breakpoint). min-height is a floor, so 280 beat the handler at every
     width and reserved a hole nobody had measured. It was hidden until the social row moved out from under it
     on 9/21 - the icons had been filling the gap. The floor now matches the shape at each width.
     In this footer the frame is narrow (a column) EXCEPT in the one-column band 670-899px, where it stretches
     past 600px and the form goes to one row. */
  min-height: 162px;   /* stacked: the pills + the button, in a column this narrow */
  border: 0;
  border-radius: var(--r-md, 12px);
  background-color: transparent;
  color-scheme: light;
}
.footer__news-fallback { margin: 0; font-size: var(--fs-small); color: var(--oat-75); }
@media (min-width: 670px) and (max-width: 899px) {   /* one-column footer, frame past ~600px: one row */
  .footer__news-frame { min-height: 52px; }
}

/* The Perspectives shape: a plain link up to the panel that already carries the form. */
.footer__news--link { display: block; }
.footer__news-jump {
  font-size: var(--fs-small);
  color: var(--oat-75);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.footer__news-jump:hover { color: var(--oat); }

.footer__social {
  display: flex;
  /* 14 -> 6. The 44px tap targets below now supply the space the gap used to;
     at 14 the row read as three floating boxes. */
  gap: 6px;
  flex-wrap: wrap;
  margin: 0;
  /* Pulls the row back to the column's text edge, cancelling the tap target's
     own left padding so the first mark still lines up with the copy above. */
  margin-left: -12px;
  padding: 0;
  list-style: none;
}

/* The three text links became icons - 8/18.

   🔴 THE TAP TARGET IS THE POINT OF THIS RULE, NOT THE ICON SIZE. As
   words they measured 69x20 / 71x20 / 61x20 - already under the 24px AA
   minimum and far under 44px. A bare 20px glyph would have made that WORSE.
   The 44px box is padding around a 20px mark: the target grows, the artwork
   stays small. */
.footer__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
}

.footer__social-icon {
  width: 20px;
  height: 20px;
  /* ⚠️ `currentColor`, NOT A HARDCODED FILL. It inherits the footer link
     colour, which already measures ~7.2:1 on the #2F2F35 ground - and as
     non-text graphics these only need 3:1. A literal colour here would have to
     be re-measured every time the footer's text tier moves. */
  fill: currentColor;
}

/* The words carried a hover underline; a filled mark cannot, so the hover is
   the same colour lift the neighbouring text links already use. */
.footer__social a:hover .footer__social-icon { fill: var(--oat); }

.footer__divider {
  height: 1px;
  background-color: rgba(243, 238, 232, 0.14);
  margin-top: var(--s-5);
}

.footer__bottom {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding-top: var(--s-3);
  font-size: var(--fs-small);
  color: var(--oat-60);
}

.footer__bottom p { margin: 0; }

/* --------------------------------------------------------------------------
   17. CARD HOVER
   -------------------------------------------------------------------------- */

@media (hover: hover) {
  .problem__card,
  .proof__card,
  .deliverable__card {
    transition: transform var(--d-micro) var(--ease-out);
  }

  .problem__card:hover,
  .proof__card:hover,
  .deliverable__card:hover {
    transform: translateY(-4px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .problem__card:hover,
  .proof__card:hover,
  .deliverable__card:hover { transform: none; }

  .connector,
  .connector [data-nodes] { transition: none; }
}

/* --------------------------------------------------------------------------
   THE TRAVELLING HEAD — the connector's "you are here" marker.

   Rides the current scroll position along the connector path (see the note in
   direction-c.js). The drawn line still uses a high-water mark; only this
   marker moves back up with you.

   Fill is set per variant, because how loud this reads is part of the colour
   question these three mockups are asking. The white ring keeps it legible
   wherever the line crosses a dark section — the same reason the focus ring
   on this project has a white halo, which must never be removed.
   -------------------------------------------------------------------------- */
.connector__head {
  fill: var(--ink);
  stroke: var(--white);
  stroke-width: 3;
  transition: opacity var(--d-micro) var(--ease-out);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .connector__head { display: none; }
}

/* --------------------------------------------------------------------------
   THE DELIVERABLE CARDS — structure only.

   Brees, 2026-08-11, on this section: "very hard to read because of how long
   it is. I don't know if this would be better in a slider."

   The problem is not the column count — it is that three dense email renders
   share ONE unbroken lime field, so the eye gets a single long beat instead of
   three. A slider was considered and argued against: it hides two thirds of
   the content behind an interaction, on the section whose entire job is
   showing what the product actually produces, and it drags in focus
   management and live-region work on a page whose ADA pass is still owed.

   Two structures are provided; each variant picks one.

     (default stack)  three cards, each able to carry its own ground
     .deliverable__cards--rail  a scroll-snap rail — swipeable and
                                keyboard-reachable, with the neighbouring
                                cards still partly visible so nothing is
                                hidden. Slider feel, no hidden state.

   CARD GROUNDS ARE NOT SET HERE. Which card gets which colour is the axis
   under review and lives in variant-1/2/3.css.
   -------------------------------------------------------------------------- */


/* ==========================================================================
   17. THE LINE PATTERN — the motif, promoted to structure.

   Brees, 2026-08-13: "one of the circles has a pattern in it. It's a bunch of
   lines with dots and that really is kind of the motif and the feel that we're
   going through [...] I also want to add in more lines and more dots. This
   really needs to feel like a premium brand."

   The asset is DE's own Line Pattern graphic, lifted from the brand homepage
   mockup bundle (76c93859.png -> assets/img/brand/line-pattern.png). It is a
   1100x1100 seamless tile on transparency: warm neutral routing lines, some
   solid and some dotted, hollow ring nodes at the junctions, and colour ONLY
   in the dots — marigold, sky and petal.

   That is the same rule base.css records for the connector: the line is a
   quiet neutral and all the colour lives in the dots. The pattern and the
   connector are the same idea at two scales, which is why the page can carry
   both without them competing.

   devices.md lists this file as "7200x7201, circular hero image". It is
   neither — it is 1100x1100 and it is the tiling line pattern. Corrected here.

   Only 4.3% of the tile's pixels are opaque, so it reads as texture rather
   than as content. It is always decorative and always aria-hidden.
   ========================================================================== */

/* `.pattern-field` and its tiled background lived here and are DELETED.
   Superseded by the composed `.linework` drawings in section 23.1 — a tile
   has no composition, which was the actual complaint. The `.pattern-circle`
   below survives: inside a 420px disc a tile is the right tool, because the
   frame is small enough that a composed drawing would show only a fragment. */

/* The hero device: a large disc filled with the pattern. This is the circle
   Brees pointed at in the mockup. The tile is clipped by border-radius rather
   than masked, so it stays crisp at any size and needs no SVG. */
.pattern-circle {
  position: absolute;
  border-radius: 50%;
  background-image: url("../img/brand/line-pattern.png");
  background-repeat: repeat;
  background-size: 340px;
  background-color: var(--sky-soft);
  pointer-events: none;
}

/* ==========================================================================
   18. THE PHONE — the hero composition, from the DE homepage mockup.

   Brees, 2026-08-13: "on the top right hand we have a phone with circles.
   That's really important to integrate in there."

   The mockup's hero puts a tilted phone over overlapping flat circles, with
   the pattern disc behind. Here the phone shows the actual product — the
   morning callback email — rather than a client site, so the hero does two
   jobs at once: it carries the brand composition AND it is the product shot.

   Built in CSS, not an image: the render inside it is real text, so it stays
   selectable, translatable, and readable by a screen reader.
   ========================================================================== */

.hero__stage {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 520px;
}

/* 🔴 THE RENDER NOW NEEDS ITS OWN WIDTH AND STACKING — the frame was supplying
   both. Measured immediately after the frame came out: the render computed to
   **2px wide by 2567px tall**. As a flex item with `container-type: inline-size`
   and no width of its own it shrank to min-content, which stretched the stage
   and pushed the discs 2,400px down the page.

   ⚠️ `width: 100%` here means the STAGE's width, which is the hero's right
   column — not the viewport. The stage keeps `min-height` and its centring.
   ⚠️ `z-index: 2` restores what `.phone` used to carry: the render sits ABOVE
   the pattern disc behind it. Without it the disc paints over the product. */
/* --------------------------------------------------------------------------
   THE HERO DECK — three short cards instead of one tall render.

   Brees, 8/18: "we'll have three boxes with the renders because one long one
   isn't working."

   ⏹ WHAT THIS REPLACES. The single render was capped at 320px wide and pushed
   right, chosen from a measured table trading its height against how much of
   the background circles it covered. At that width it stood **882px tall** —
   and that height IS the thing he rejected. The whole trade is moot once the
   content is three short cards, so the table is not carried forward.
   ⚠️ Do NOT reinstate `justify-content: flex-end` on the stage. It existed to
   free space on the left for the discs, which the deck no longer eats.

   The stagger is negative margin, NOT absolute positioning. Absolute would take
   the cards out of flow and leave the stage with a collapsed height — the exact
   failure that put the hero at 2,567px when the phone frame came out. In flow,
   the deck's height is always the sum of what is actually in it.
   -------------------------------------------------------------------------- */

.hero__deck {
  position: relative;
  z-index: 2;
  display: grid;
  width: 100%;
  margin: 0;
}

.hero__deck-card {
  position: relative;
  /* 🔴 300 -> 345 -> 400px, 8/19. Brees: "I'd like to see it fill more of the
     space." MEASURED FIRST, and the diagnosis was not what it looked like:
     the deck already spanned 101% of its column, so there was nothing left to
     take sideways. What it did not use was HEIGHT — 334px of a 520px stage,
     91px of dead ground above and 94px below. The dial that fills that is the
     overlap below, not this width.

     ⚠️ 400px IS NEAR THE CEILING, AND THE CEILING IS THE COLUMN. `min(100%,
     …)` resolves against the 437px stage, so any value past ~437 changes
     nothing on screen. That is part of why 345 read as "no change" — and why
     the answer to "bigger" is never just a bigger number here. Going wider
     than the column means widening the grid, which costs a fourth headline
     line (+55px of hero) and would break the pink-strip match to V2.

     ⚠️ WIDER CARDS ARE SHORTER CARDS: less wrapping, less height. Measured at
     440px the deck was 299px tall against 378px at 300px. A bigger card never
     pushes this hero taller.

     🔴 92%, NOT 100%, AND THE 8% IS LOAD-BEARING. The stagger below is built
     out of the SLACK between the card and its column — `justify-self: end`
     can only move card 2 by what is left over. At `min(100%, 400px)` the
     column caps the card below ~1368px, slack goes to zero, and cards 1 and 2
     land on the same x: measured 0/-1 at 1024 and 0/-2 at 900. That is the
     same defect that killed card 3's offset, one card over. 92% guarantees
     slack at every width, and it is deliberately the value that changes
     NOTHING on desktop: 92% of the 437px column is 402px, so the 400px cap
     still wins and 1440/1600/1920 measure byte-identical either way.
     ⚠️ Raising this toward 100% silently re-flattens the deck below 1368px.

     ⚠️ V1 ONLY IN PRACTICE, even though this rule lives in the shared sheet:
     V2's hero replaced the deck with the card wall, so `.hero__deck-card` does
     not exist on that page (grepped: 0 occurrences). Do not "sync" this to V2. */
  width: min(92%, 400px);
}

/* Each card sits above the one before it, so the overlap reads as a deck with a
   front and a back rather than as three cards fighting.

   🔴 THE OVERLAP IS THE FILL DIAL, 8/19. -54px -> -8px took the deck from
   334px to 428px — 64% -> 82% of the stage — without touching the hero's
   height. It also stopped the overlap eating the cards: at -54px the back two
   were 28% and 47% hidden, at -8px they are 8% and 15%. Still a real overlap,
   so it still reads as a deck with a front and a back.
   ⚠️ These are px, so they do NOT scale with the card. Re-measure, never assume.

   🔴 CARD 3 IS -8%, AND THE SIGN IS THE WHOLE POINT. Brees: "the orange card
   should be staggered better" — and it was not staggered badly, it was not
   staggered AT ALL. Card 2 is `justify-self: end` and card 3 was `margin-left:
   8%`; at 352px cards those resolved ~50px apart, but 407px cards leave only
   30px of slack in a 437px column, so both landed at 35px and 34px — ONE PIXEL
   apart, the orange card parked flush under the green one.
   ⚠️ A POSITIVE OFFSET CANNOT FIX THIS: 30px of slack is the cap. The stagger
   only exists if card 3 sits OUTSIDE the column, hence the negative value.
   Measured at -8%: card x = -3 / 35 / -36, a 71px step off card 2, and still
   28px clear of the copy column with 0px² of overlap. */
/* 🔴 THE CARD SCALES; THE BACKGROUND DOES NOT MOVE AT ALL — 8/24. Brees:
   "it does need to be a little bit larger. The background should not move at
   all and just the card itself should get larger."

   `transform` is what makes that a guarantee rather than a hope: it is not a
   layout property, so the discs, the pattern circle, the stage and the hero's
   own height cannot respond to it. MEASURED at five scales — the petal disc,
   the lime disc and the hero height came back byte-identical every time.

   🔴 `transform-origin: left center` IS LOAD-BEARING. With the default centre
   origin the card grows BOTH ways and eats the gap to the headline: 57px at
   1.0, 29px at 1.14, 4px at 1.26. Anchored left it grows into the empty right
   side instead, and that gap holds at a constant 64px from 1280 to 1920 while
   the right margin never drops below 41px.

   ⚠️ 1.2 IS THE MEASURED CEILING FOR THE RANGE, not a round number. At 1.28
   the card runs off the right edge at 1200 and 1024 (-15px and -12px), which
   is why the scale is gated at 1280 and the smaller widths keep 1.0.
   ⏹ The -2deg tilt is gone: the option Brees picked was the straight one. */
.hero__deck-card--1 { z-index: 1; transform: none; }

/* 🔴 THE WHOLE CARD NOW FITS INSIDE 520px OF LAYOUT, AND THAT IS THE POINT.

   Brees, 8/24: the hero card should carry everything the product screenshot
   does — the three buttons and the two links — the inbox chrome above it can
   go, and "if you need to make the card just a little bit wider, just a smidge
   bit wider, you can do that too."

   THE CONSTRAINT THAT SHAPES ALL OF IT: `.hero__stage` is `min-height: 520px`
   and the discs live INSIDE it, so the moment the deck's LAYOUT height passes
   520 the stage grows and the background moves — the one thing he ruled out
   when the card was first enlarged. Adding the buttons and links costs ~100px.
   Measured, unmitigated: deck layout 517 -> 617, hero 724 -> 821, both discs
   down 97px. That was caught by measuring, not by looking.

   THREE LEVERS, ALL MEASURED, none of them font-size alone:
     · WIDER  — 470px instead of 400 at >=1440, so the ask, the Reason line and
       the flag wrap into fewer lines. This is the "smidge wider" he offered,
       and it is also what pays for the buttons.
     · TIGHTER — the card's internal rhythm comes in a few px. The type is not
       what shrank; the space between blocks is.
     · SMALLER TYPE, SCALED BACK UP — 11px at scale 1.22 renders at 13.4px,
       larger on screen than the 12px it replaces, while costing less layout.

   Result at >=1440: deck layout 478 of 520 (42px spare), card draws 533x583,
   and hero / stage / both discs / the pattern circle all come back
   byte-identical to before the buttons existed.

   ⚠️ 1280-1439 IS A SEPARATE BAND AND NEEDS TO BE. There is no room to widen
   there — at 1280 the stage's right edge leaves 84px, so a 470px card at 1.22
   ran 12px off the viewport. That band keeps the 437px column and scales 1.12,
   which lands 32px clear at 1280 and 75px at 1366.
   📌 Re-run `C:/tmp/sweep2.js`-style measurement if the card's copy changes:
   these numbers are a fit, not a margin. */
@media (min-width: 1280px) {
  .hero__deck-card--1 {
    transform: scale(1.12);
    transform-origin: left center;
  }

  /* The internal rhythm, tightened. */
  .hero__deck .callback--real { padding: 14px 16px; }
  .hero__deck .callback__ask,
  .hero__deck .callback__summary { margin-top: 10px; padding: 9px 11px; }
  .hero__deck .callback__facts { margin-top: 10px; line-height: 1.5; }
  .hero__deck .callback__flag { margin-top: 8px; line-height: 1.4; }
  .hero__deck .callback__actions { margin-top: 12px; gap: 8px; }
  .hero__deck .callback__btn { padding: 7px 12px; }
  .hero__deck .callback__links { margin-top: 10px; }
  .hero__deck .deck-hdr { margin-bottom: 10px; }
  .hero__deck .deck-hdr__practice { margin-top: 6px; }
  .hero__deck .deck-hdr__tabs { margin-top: 8px; padding-top: 7px; }
  .hero__deck .deck-hdr__tab { padding-bottom: 6px; }

  .hero__deck .callback--real,
  .hero__deck .callback--real .callback__meta,
  .hero__deck .callback--real .callback__phone,
  .hero__deck .callback--real .callback__ask,
  .hero__deck .callback--real .callback__facts,
  .hero__deck .callback--real .callback__flag { font-size: 11px; }
  .hero__deck .callback--real .callback__caller { font-size: 13.5px; }
  /* The buttons and links were left at their base sizes on the first pass
     and that alone put the deck at 515 of its 520 budget — 5px of
     headroom, which one word of copy would have blown. */
  .hero__deck .callback__btn,
  .hero__deck .callback__links { font-size: 11px; }
}

@media (min-width: 1440px) {
  /* The column stays 437 — only the card overflows it. Resizing the column
     would move the discs AND cost the headline a fourth line, both recorded
     in the notes above. */
  .hero__deck-card { width: min(100%, 470px); }
  .hero__deck { margin-right: -33px; }
  .hero__deck-card--1 { transform: scale(1.22); }
}

/* ⏹ `--2` and `--3` are gone with their markup — see the note in index.html.
   Their `--deck-line` colours below are dead too, left in place only because
   the deck may grow a second card again. */

/* 🔴 THE CARDS NEED AN EDGE OF THEIR OWN. Brees, 8/18: "I like the D1 signal
   hero version the best. It's just the cards blend into the bg."

   A white card on the oat hero ground is **1.48:1** — and the render's default
   border is `--ink-12`, a hairline meant for separating rows INSIDE a card, not
   for holding one against a page. Behind the deck sits the pattern circle,
   which gives the eye even more to compete with.

   Same answer he chose for the mockup ("let's just borders"), and the same
   tier: the raw pastels measure 1.35 / 1.49 / 1.70 on white and worse on oat —
   the invisible-mark failure this project has hit four times. The deep tier
   clears **3:1 on BOTH the white card and the oat page**, which is the non-text
   graphic standard rather than the 1.35 mark floor.

   ⚠️ 2px, not 1. At 1px the deep tier reads as a thin dark line rather than as
   the card's edge, and these cards are only ~300px wide. */
/* ⏹ NO BORDER — 8/24. Brees: "remove the border around the card and just add
   a drop shadow." The 2px sky-deep outline was there to separate three
   overlapping cards from each other; with one card there is nothing to
   separate, and on a white hero the outline read as a frame around the email
   rather than the email itself sitting on the page.

   📌 THE SHADOW IS THE MENU DROPDOWN'S, NOT A NEW ONE. That panel solves the
   identical problem — white on white, needs an edge of its own or it
   dissolves — and this file's token note says --shadow-card is "the one
   permitted elevation", which at 0 2px 24px / 6% is far too faint to hold a
   card this size off a white ground. Reusing the one existing heavier value
   keeps the page at two elevations rather than three. The hairline in its
   first layer is what replaces the border: it defines the edge without
   drawing a coloured frame.
   ⚠️ `--deck-line` is left declared — a second card would want its edge back. */
.hero__deck-card .render--deck {
  border: 0;
  box-shadow: 0 1px 0 rgba(47, 47, 53, 0.12), 0 12px 28px rgba(47, 47, 53, 0.14);
}

.hero__deck-card--1 { --deck-line: var(--sky-deep); }
.hero__deck-card--2 { --deck-line: var(--lime-deep); }
.hero__deck-card--3 { --deck-line: var(--clay); }

/* 🔴 EACH CARD IS ~10% TALLER — Brees, 8/19: "make the cards like 10% taller,
   each one." The emphasis is on EACH, and that is why the padding is per-card
   rather than one shared value.

   ⚠️ A SINGLE PADDING VALUE CANNOT DO THIS. The three cards are 220 / 112 /
   133px, so any uniform bump grows the short ones far more in percentage
   terms — measured, +4px on the chrome and body gave **7% / 14% / 12%**. Ten
   percent of each card is 22 / 12 / 14px, which is 11 / 6 / 7 per side.
   Measured after: **10.0% / 10.7% / 10.5%**.

   ⚠️ 44px OF HEADROOM LEFT, AND SPENDING IT MOVES THE HERO. The deck is now
   476px inside a 520px stage. Past 520 the stage grows, the hero grows with
   it, and the pink strip stops matching V2 — measured, a +10px uniform bump
   took the hero to 744 and the strip with it. Any further height on these
   cards has to be paired with a re-measure of the pink on both pages. */
.hero__deck-card--1 .render--deck { padding-block: 11px; }
.hero__deck-card--2 .render--deck { padding-block: 6px; }
.hero__deck-card--3 .render--deck { padding-block: 7px; }

/* The render at deck size. `.render` carries `container-type: inline-size`, so
   the real component reflows on its own — these only step the type down. */
.render--deck {
  font-size: 0.6875rem;
  line-height: 1.45;
}

.render--deck .render__chrome { padding: 8px 10px; gap: 2px 8px; }
.render--deck .render__body { padding: 10px; }
.render--deck .render__from,
.render--deck .render__subject { font-size: 0.6875rem; }
.render--deck .render__intro { font-size: 0.6875rem; margin-bottom: 8px; }
.render--deck .callback { padding: 8px 10px; }
.render--deck .callback__name { font-size: 0.75rem; }
.render--deck .callback__value,
.render--deck .callback__pet,
.render--deck .callback__line { font-size: 0.625rem; }

.render--deck .render__table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.625rem;
}

.render--deck .render__table td { padding: 4px 0; }
.render--deck .render__table td:last-child { text-align: right; font-weight: 600; }
.render--deck .render__table tr + tr td { border-top: 1px solid var(--ink-12); }

/* Below the two-column breakpoint the stage is full width and a rotated,
   overlapping deck reads as a mistake at that size. Straighten and separate. */
@media (max-width: 899px) {
  .hero__deck-card { width: 100%; transform: none; }
  .hero__deck-card--2,
  .hero__deck-card--3 { margin-top: var(--s-3); margin-left: 0; justify-self: stretch; }
}

/* The flat overlapping discs, straight from the mockup's hero: a petal and a
   lime circle overlapping at the lower left, mix-blend multiply so the overlap
   makes the tan the brand's logo makes. Same trick as the ArchLine. */
/* 🔴 THE DISCS GREW AND DROPPED, 8/19 — AND SIZE ALONE WOULD NOT HAVE WORKED.
   Brees: "enlarge the pink and green circle in the hero because they are
   covered." Measured first: at 120px they were **2% visible**, and the deck is
   what buries them.

   ⚠️ ENLARGING ALONE BARELY MOVES THE NUMBER, because the deck covers the
   whole stage horizontally (deck 1036..1510 against a stage of 1072..1509).
   A circle grown from a buried anchor is still buried — measured, holding the
   position fixed:

       120px -> petal  2% visible        240px -> petal  4%
       180px -> petal  2%                360px -> petal  3%   <- 3x the size, still gone

   The only free ground is the 151px band BELOW the deck, so the pair had to
   move as well as grow. Sweeping size x bottom:

       size 200  bottom -12% -> 57%      size 240  bottom -12% -> 45%
       size 200  bottom -18% -> 76%      size 240  bottom -18% -> 61%   <- chosen
       size 200  bottom -24% -> 85% (6% clipped by the hero)

   240 / -18% doubles them and takes visibility 2% -> 61%, with 0% clipped by
   the hero edge and 0px² over the copy column.

   🔴 THE RATIO LIVES IN ONE PLACE NOW. The box width, the disc and the lime's
   offset were three hand-tuned px values (190 / 120 / 70) that only make the
   multiply overlap read correctly in that exact proportion — change one and
   the tan wedge where they cross goes wrong. They are all derived from
   `--disc` here, so the pair can be resized without re-deriving the overlap.
   The clamp keeps the mass off small screens, where the deck stacks and the
   discs no longer have a band to sit in. */
/* 🔴 REPOSITIONED 8/19 FROM BREES' ANNOTATED SCREENSHOT — he drew a circle
   outline where each one should sit: both higher, and pulled APART to either
   side of the deck's lower edge rather than overlapping in the middle.

   The marks were converted, not eyeballed. His screenshot is 1.60x smaller
   than the rendered page, derived from two landmarks present in both (the CTA
   button's box and the h1's cap line), which put his circle centres at
   **(1092, 706)** and **(1405, 714)** against the old (1192, 743) and
   (1339, 736) — the petal 100px left and 37px up, the lime 66px right and
   22px up.

   ⚠️ THE OFFSETS ARE FRACTIONS OF `--disc`, NOT PIXELS, so the pair keeps its
   arrangement when the clamp shrinks them on narrow screens. -0.417 and
   +0.888 are those measured pixel offsets divided by the 240px disc.

   🔴 THIS SEPARATES THEM, AND THAT COSTS THE TAN WEDGE. The two discs are
   `mix-blend-mode: multiply` specifically so their overlap makes the tan the
   brand's logo makes. At these centres they are 313px apart with 240px discs
   — a 73px GAP, so there is no overlap and no tan. His drawing shows them
   apart, so this follows the drawing; closing the gap ~75px would bring the
   wedge back. Flagged to him rather than silently split the difference. */
.hero__discs {
  --disc: clamp(156px, 14vw, 250px);
  position: absolute;
  z-index: 1;
  /* An ANCHOR POINT, not a box: zero-sized, pinned to the stage's bottom-left
     corner. Each disc states its own centre as an offset from here, so the
     pair's arrangement is one coordinate system instead of a box whose width
     had to be re-derived every time a disc changed size. */
  left: 0;
  bottom: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

/* 🔴 PLACEMENT "D" — THE DECK LANDS ON THE PAIR. Brees, 8/19, on the previous
   arrangement: "not happy with the pink and green circle placements. Doesn't
   feel balanced or intentional." He was right, and measuring said why:

     1. Both circles sat in the 143px strip under the deck — the most crowded
        part of the hero — at 22% and 40% visible, while the band from
        x1510 to x1905 (395px, full height) was completely EMPTY.
     2. They no longer touched. These are `mix-blend-mode: multiply` so their
        overlap makes the tan the logo makes; apart, they are just two circles
        and the device is gone.
     3. 160 vs 187 is a 14% size difference — too small to read as hierarchy,
        too big to read as a pair.

   D fixes all three: the overlap returns (48px), the pair moves into the empty
   band, and it sits under the orange card's bottom-right corner so the stack
   rests ON it. That contact is the point — it is what gives the circles a
   reason to be where they are, which is what "intentional" meant here.

   🔴 EACH DISC STATES ITS CENTRE, NOT ITS CORNER, and that is what keeps this
   stable. `left/top` = centre offset − `--d`/2, so changing a disc's SIZE can
   never move it; only changing its centre does. Every earlier round broke on
   exactly this — shrinking the petal pulled it up and left by half the
   difference, off the point that had been chosen.

       centres, in `--disc` units from the stage's bottom-left corner
         petal   +1.632, -0.028   ->  (1480, 600) at 1920
         lime    +2.352, -0.148   ->  (1660, 570) at 1920
       sizes     petal 0.84 -> 210px    lime 1.0 -> 250px

   ⚠️ Sizes are 210/250 — a 19% step, deliberately past the 14% that read as a
   near-miss. */
.hero__disc {
  --d: var(--disc);
  position: absolute;
  width: var(--d);
  height: var(--d);
  border-radius: 50%;
  mix-blend-mode: multiply;
}

.hero__disc--petal {
  --d: calc(var(--disc) * 0.84);
  left: calc(var(--disc) * 1.632 - var(--d) / 2);
  top: calc(var(--disc) * -0.028 - var(--d) / 2);
  background-color: var(--petal);
}

.hero__disc--lime {
  --d: var(--disc);
  left: calc(var(--disc) * 2.352 - var(--d) / 2);
  top: calc(var(--disc) * -0.148 - var(--d) / 2);
  background-color: var(--lime);
}

/* ==========================================================================
   AMBIENT DRIFT ON THE HERO SHAPES — 8/19, AND IT LOOPS FOREVER ON PURPOSE.

   Brees: "have the shapes in s1 slightly animate in the background." He was
   shown the three compliant alternatives — scroll-linked drift, a single
   settle on entry, and loop+scroll together — and chose the ambient loop, the
   same call he made for V2's marquee.

   🔴 THIS IS THE SECOND DOCUMENTED EXEMPTION TO "NOTHING MAY LOOP FOREVER."
   `verify.js` strips exactly the region between the `loop-exempt` markers
   below, prints what it stripped, and still fails on an `infinite` anywhere
   else. Do not widen the markers to cover anything but these three rules.

   ⚠️ `prefers-reduced-motion` IS NOT PART OF THE EXEMPTION and is enforced
   separately below. Looping forever is allowed here. Ignoring a user who
   asked for less motion is not.

   🔴 THE THREE DURATIONS ARE MUTUALLY PRIME (23 / 29 / 37s), WHICH IS THE
   WHOLE TRICK. Ambient motion reads as mechanical the moment the viewer can
   feel the cycle, and shapes on equal or harmonic durations re-sync every
   period and give exactly that. At 23/29/37 the three only return to their
   shared starting phase every lcm = 24,679s — about 6.9 hours — so there is
   no perceptible loop point in any real visit.

   ⚠️ TRAVEL IS 8-11px, DELIBERATELY SMALL. "Slightly" was the word. The discs
   sit against the deck's hard edges, so anything larger reads as drift
   against a stationary card rather than as atmosphere.

   ⚠️ `translate3d`, not `top`/`left`: these composite on the GPU and never
   trigger layout. Animating the offsets instead would relayout the stage on
   every frame, three times over, for the life of the page.

   ⚠️ The discs carry `mix-blend-mode: multiply`, and a transform makes an
   element a stacking context — the tan wedge where they cross was re-checked
   in the browser after this, not assumed.
   ========================================================================== */

@keyframes hero-drift-petal {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(-7px, 10px, 0); }
}
@keyframes hero-drift-lime {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(9px, -8px, 0); }
}
@keyframes hero-drift-pattern {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50%      { transform: translate3d(-6px, -11px, 0); }
}

/* loop-exempt:start — hero ambient drift, Brees 8/19 */
.hero__disc--petal { animation: hero-drift-petal 23s ease-in-out infinite; }
.hero__disc--lime  { animation: hero-drift-lime 29s ease-in-out infinite; }
.hero__pattern     { animation: hero-drift-pattern 37s ease-in-out infinite; }
/* loop-exempt:end */

@media (prefers-reduced-motion: reduce) {
  .hero__disc--petal,
  .hero__disc--lime,
  .hero__pattern { animation: none; }
}

/* ==========================================================================
   19. THE PLAN — pricing as dotted rows.

   Brees, 2026-08-13: "I love the 'What We're Good At' section on the digital
   home pages file, how it uses the different colors for each of the dots."

   That device is hairline rows, each led by a brand-coloured dot. It belongs
   on the pricing list, which is nine peer items that all resolve to the same
   answer — exactly the shape the mockup uses it for, and a third of the height
   of the bordered table it replaces.

   Hairline row separators are the one container the house rules permit
   (feedback_no_ai_default_cards names them explicitly), so this stays onside.

   The dots are decorative: every row states "Included" in text as well, so no
   information is carried by colour alone (1.4.1). They therefore keep the
   ORIGINAL pastels at full strength — on indigo they are a bright accent.
   ========================================================================== */

.plan__list { margin-block: var(--s-4); }

.plan__row {
  display: grid;
  grid-template-columns: 10px 1fr auto;
  align-items: baseline;
  gap: var(--s-2);
  padding: 13px 0;
  border-bottom: 1px solid var(--oat-14);
}

.plan__row:first-child { border-top: 1px solid var(--oat-14); }

.plan__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  transform: translateY(-2px);
}

/* The five brand accents cycling, so no two adjacent rows share a colour and
   the sequence never repeats within a screen. */
.plan__row:nth-child(5n + 1) .plan__dot { background-color: var(--petal); }
.plan__row:nth-child(5n + 2) .plan__dot { background-color: var(--lime); }
.plan__row:nth-child(5n + 3) .plan__dot { background-color: var(--sky); }
.plan__row:nth-child(5n + 4) .plan__dot { background-color: var(--marigold); }
/* Clay, not petal-soft. The tint was chosen to keep the cycle gentle and it
   read as a washed-out near-white against indigo — a fifth colour that did not
   look like a colour. Clay is the palette's fifth real accent and holds its
   own on the dark ground. */
.plan__row:nth-child(5n + 5) .plan__dot { background-color: var(--clay); }

.plan__name { font-size: var(--fs-body); }

.plan__val {
  font-size: var(--fs-small);
  color: var(--oat-75);
  white-space: nowrap;
}

/* The hero's pattern disc. Sized and placed here rather than on
   .pattern-circle so the primitive stays reusable elsewhere on the page.
   Offset up-and-right of the phone, echoing the mockup's composition. */
/* 🔴 GROWN 420 -> 620px AND MOVED UP-RIGHT, 8/19. Brees asked whether the blue
   circle should be bigger; the honest answer was that at 420 it could not do
   its job — measured, only **9% of it cleared the deck**. What you saw was a
   thin blue rind around the cards rather than a shape.

   It matters more now than it used to: placement D deliberately weights the
   pink/lime pair LOW-RIGHT, and this circle is the only element positioned to
   put weight back into the upper right.

       420  right  -8%  top   2%  ->   9% visible
       620  right -32%  top  -8%  ->  38% visible, 0px² over copy   <- chosen
       720  right -50%  top -16%  ->  54% visible, 16px² over copy
       820  right -52%  top -18%  ->  58%, but 13,769px² over live copy

   ⚠️ THE MOVE IS NOT DECORATION — IT PAYS FOR THE SIZE. A circle grows from
   its centre, so scale alone drove it leftward under the copy: at 620 held at
   the old -8%/2% it put 2,611px² over live text. The -32%/-8% shift is what
   buys the size back at zero cost.

   ⚠️ Only the CAP changed, not the 32vw. Below ~1300 the circle was never
   hitting 420 anyway, so it still scales with the viewport exactly as before
   and simply stops growing later.

   ⏹ 720 WAS OFFERED AND DECLINED — Brees: "I like 720 but I don't like the
   extra space", circling the empty blue between the deck's right edge and the
   circle's rim. Do not raise this cap without solving that gap first; the fix
   would be pushing the circle far enough right to bleed off the hero edge, not
   simply making it larger. */
.hero__pattern {
  z-index: 0;
  width: clamp(260px, 32vw, 620px);
  aspect-ratio: 1;
  top: 2%;
  right: -8%;
}

/* 🔴 THE UP-AND-RIGHT SHIFT IS TWO-COLUMN ONLY, AND A WIDTH SWEEP IS WHY.
   -32% is measured against the hero's two-column geometry, where the stage is
   a 437px right-hand column. Below 900 the hero stacks and the stage becomes
   FULL WIDTH, so the same -32% resolves against a box three times wider and
   throws the circle off the page: measured at 767, **72% of it bled past the
   hero edge**, against 12% at 900 and 0% at 1440. Applied globally it would
   have quietly deleted the shape on tablet.
   ⚠️ The width cap is left global on purpose — 32vw only reaches 620px above
   ~1940px, so narrow viewports never see the new ceiling anyway. */
@media (min-width: 900px) {
  .hero__pattern {
    top: -8%;
    right: -32%;
  }
}

@media (max-width: 639px) {
  /* Keeps the three-shapes-per-viewport ceiling honest on small screens:
     the pattern disc plus two hero discs is already three. */
  .hero__pattern { width: 240px; right: -18%; }
}

/* ==========================================================================
   20. THE LENGTH PASS

   Brees, 2026-08-13: "what I'd like to see is how we make this page not feel
   so long because right now it's really long."

   c2 measured 11,259px — 12.4 screens. Restructuring the deliverable into
   arches and folding the testimonial into Proof took it to 10,612px, which is
   11.6 screens and nowhere near enough. The structural edits were the visible
   work; they were not where the height actually lived.

   Where it actually lived: --section-pad is clamp(4rem, 8vw, 8rem), which
   resolves to 128px at desktop. Nine sections x two edges = 2,304px of the
   page — roughly a fifth of the whole thing — was vertical padding. Nothing in
   the section content had to change to get most of it back.

   📌 Worth remembering: the section that FELT longest (three emails) was not
   the one costing the most height. Restructuring it was still right, because
   the complaint was about density as much as pixels — but the measurement is
   what found the rest.

   These overrides are scoped to this stylesheet so the c* variants keep the
   rhythm they were approved with.
   ========================================================================== */

:root {
  /* 128px -> 88px at desktop. Still a generous band; the sections read as
     separate without each one costing a third of a screen in air. */
  --section-pad: clamp(3rem, 6vw, 5.5rem);
}

/* The hero used to keep MORE room than the rest, on the reasoning that it is
   the one section that earns its height. That was true when it also had to
   carry a full-bleed video slot and its caption. Both are gone (Brees, 8/14 —
   no product video exists), and with the header now sticky, every pixel of
   hero padding is competing with a header that never leaves the screen.

   🔴 The header is sticky now, so it costs its height on EVERY screen, not
   just the first. Hero padding had to come down to pay for it. */
/* 🔴 THE HERO'S BOTTOM PADDING CARRIES A +96px TERM — 8/19, AND IT IS
   MEASURED, NOT A ROUND NUMBER.

   Brees: "V1 should have the same amount of pink showing as V2." The strip of
   section 2's petal ground visible under the hero is `viewport - heroBottom`,
   so matching the two pages means landing their hero bottoms on the same
   pixel. Measured at 1920x907:

       V1 hero ended 792  ->  115px of pink
       V2 hero ended 888  ->   19px of pink

   V2's hero is 96px taller, 77px of that from its own content (the card wall
   against V1's deck) and 19px from its own padding override. So V1 needs
   +96px here to finish where V2 finishes and show the same 19px.

   ⚠️ V2 IS NOT AFFECTED BY THIS LINE. `direction-d-v2.css` sets
   `padding-bottom` as a longhand and loads after this file, so it wins there.
   Changing this value moves V1 only — which is the intent, but it means the
   two pages' hero heights are now tuned in two different files. If one moves,
   RE-MEASURE THE OTHER; they are matched by pixel, not by a shared rule.

   ⚠️ Archived directions under `_archive/` also load this sheet and will
   pick up the extra 96px. They are out of the build, so this is noted rather
   than worked around.

   🔴 THE SPLIT WAS REBALANCED 8/19 — SAME SUM, DIFFERENT DISTRIBUTION.
   Brees: "the content doesn't look visually vertically centered, seems like
   more space on the bottom." He was right, and by a lot. Measured at 1920:
   padding was **44px top against 162px bottom**, and the content sat
   correspondingly high — header to h1 was **65px** while CTA to the section
   edge was **151px**. The +96px above went entirely onto the bottom, which
   is what tipped it.

   The content block runs h1 top (227) to CTA bottom (737). Slack above 65 +
   slack below 151 = 216, so centred is 108 a side — a **43px** shift down.
   Hence +43 on the top term and -43 on the bottom (96 -> 53).

   ⚠️ THE SUM IS UNCHANGED AT EVERY VIEWPORT, AND THAT IS THE WHOLE TRICK.
   0.5sp+43 + 0.75sp+53 == 0.5sp + 0.75sp+96. The hero's HEIGHT does not move,
   so it still ends on 888 and still shows the same 19px of pink as V2. Moving
   content without moving the box is the only way to fix this without
   re-opening the pink-strip match.
   ⚠️ Re-measure the balance if the hero's CONTENT height ever changes — this
   is a pixel centring of today's content, not a rule that adapts.

   🔴 AND THE OLD NOTE ABOVE WAS HALF WRONG: "V2 IS NOT AFFECTED BY THIS LINE"
   holds only for the BOTTOM half. `padding-block` sets both, and V2 overrides
   only `padding-bottom`, so the top term DOES reach V2 — +43px there would
   have grown V2's hero and eaten its pink strip. `direction-d-v2.css` now
   pins its own `padding-top` to the pre-shift value. */
.hero { padding-block: calc(var(--section-pad) * 0.5 + 43px) calc(var(--section-pad) * 0.75 + 53px); }

/* -- What changes ----------------------------------------------------------
   Three blocks at --s-5 top and bottom is 128px between each. Since 8/17 the
   blocks are white SHAPES on the sky ground, so their own edges already
   separate them and the air is doing a job the shape has done. (This note used
   to credit a hairline rule; that rule is gone — B3.) */
.changes__block { padding-block: var(--s-4); }

/* -- FAQ -------------------------------------------------------------------
   Seven collapsed rows should not cost 1,155px. The per-item padding is the
   whole story here.

   ⚠️ This was first written as `.faq__item { --faq-pad: var(--s-2) }`, which
   was a silent no-op — no such custom property exists anywhere in the project.
   It changed nothing and reported nothing. The real padding is on the button
   and the answer, below. */
.faq__q button { padding-block: var(--s-2); }
.faq__a { padding-bottom: var(--s-2); }

/* -- Final CTA -------------------------------------------------------------
   1,114px for a headline, a button and one image slot. The slot was sized as
   a full 16:9 at container width; capped, it stops dominating a section whose
   job is one sentence and one action. */
.final-cta .media-slot { max-width: 560px; margin-inline: auto; margin-top: var(--s-4); }
.final-cta__inner { padding-block: var(--s-2); }

/* -- Pricing ---------------------------------------------------------------
   Nine rows at 13px x 2 plus the surrounding blocks. Tightening the row alone
   is worth ~90px and costs nothing in legibility. */
.plan__row { padding: 10px 0; }

/* ==========================================================================
   21. TEXT ON THE PATTERN — a real contrast fix, found by measurement.

   🔴 The rendered contrast sweep passed this page 198/198. It was WRONG about
   the patterned sections, and predictably so: the sweep resolves an element's
   backdrop by climbing ancestors for a non-transparent `background-color`. The
   pattern is a background IMAGE on a ::before pseudo-element, so the sweep
   never sees it and measures every one of these strings against plain oat.

   📌 This is the same shape as the failure already recorded twice on this
   account — a previous build's "0 axe violations" that were really `incomplete`, and
   this project's own three faked contrast sweeps. A green sweep is evidence
   about what the sweep can see, not about the page.

   Measured by hand against the tile's actual marks, blended at the pattern's
   real 0.55 opacity over oat:

                        ink-muted (0.72)   full ink
       line #ddd8cf           4.76           10.31
       line #eae4db           4.92           10.97
       dot sky                4.82           10.56
       dot petal              4.49  ✗         9.23
       dot marigold           4.06  ✗         7.67
       dot clay               3.59  ✗         6.22

   So --ink-muted at normal size fails on three of the six marks. The dots are
   sparse, which makes this INTERMITTENT rather than absent — a line of muted
   text fails only where a dot happens to fall behind it. That is worse than a
   consistent failure, not better: it is invisible in a screenshot and it moves
   when the layout reflows.

   Dropping the pattern's opacity was the obvious fix and it is the wrong one.
   It would have to fall to 0.19 for --ink-muted to clear AA on oat, and at
   0.19 there is essentially no pattern left — trading away the exact thing
   this section was added for. Brees asked for MORE lines and dots.

   So the pattern stays at full strength and the TEXT changes instead: inside a
   .pattern-field, secondary copy takes full ink. It loses a little tonal
   hierarchy and keeps all of its legibility, which is the right way round —
   and base.css already argues this exact position, that hierarchy should come
   from size, weight and letter-spacing rather than from washing contrast out.
   ========================================================================== */


/* The one place muted text may still sit on the pattern is inside a container
   that paints its own solid ground — an arch, a proof card, a render. Those
   are unaffected by the rule above and keep the muted tone, because the
   pattern is not behind them at all. */

/* ==========================================================================
   22. ROUND 2 — Brees' feedback, 2026-08-13 (second pass).

   Everything below either overrides or replaces something above it. Where a
   rule from an earlier section is now dead, it is deleted rather than left to
   rot — a stylesheet where half the rules are silently outranked is how the
   variant-vs-arch border-radius bug happened in the first place.
   ========================================================================== */

/* --------------------------------------------------------------------------
   22.1 THE PATTERN — superseded by 23.1.

   This block scaled the tile from 620px to 1280px in answer to "it feels
   extremely busy". It made the marks bigger and left the wallpaper problem
   untouched, and Brees came back on the same point. The whole approach was
   replaced rather than tuned again; only .pattern-circle keeps a tile.
   -------------------------------------------------------------------------- */

.pattern-circle { background-size: 420px; }

/* --------------------------------------------------------------------------
   22.2 THE PROBLEM — no toggles.

   "I don't like that those three boxes are toggles [...] There's not a lot of
   text so I want to remove the toggles from that section."

   The three answers are 9, 9 and 11 words. All of .problem__trigger,
   .problem__marker and .problem__label's button styling is now dead and has
   been deleted from section 7 rather than left in place.
   -------------------------------------------------------------------------- */

.problem__cards { align-items: stretch; }

.problem__card {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

/* The dot replaces the toggle marker: it keeps the row identifiable as one of
   three peers without implying anything is operable. Decorative, so it takes
   the full-strength pastel and states nothing on its own. */
.problem__dot {
  width: 10px;
  height: 10px;
  margin-bottom: var(--s-1);
  border-radius: 50%;
}

/* petal / lime / marigold, matching How It Works and Proof, so the page has ONE
   three-colour sequence rather than a different trio per section.

   Card 1 was --sky, which at 1.35:1 on white was the palest mark on the page by
   some distance and read as a printing error next to the lime and marigold. It
   is decorative and carries no contrast requirement, but "no requirement" is
   not the same as "should be nearly invisible" — the icon-disc bug in the next
   section was the same instinct taken one step further. */
.problem__card:nth-child(1) .problem__dot { background-color: var(--petal); }
.problem__card:nth-child(2) .problem__dot { background-color: var(--lime); }
.problem__card:nth-child(3) .problem__dot { background-color: var(--marigold); }

/* 9/10 type rule: h3 takes the BODY face. See base-v3.css .t-h3. */
.problem__label {
  font-family: var(--font-body);
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: 0;
}

.problem__text { padding-top: 0; color: var(--ink-muted); }

/* --------------------------------------------------------------------------
   22.3 HOW IT WORKS — the three arches are now identical in size.

   "The boxes, the round boxes in that section, look good but I'd like all of
   them to be the same size and height. Right now number one is larger."

   Measured before the fix: 435 / 410 / 415px. Step one simply has more words.

   Grid items stretch by default, so the cause was `align-items: start` on the
   track, added to stop short cards growing. Removing it makes all three take
   the tallest one's height, and the dome stays a true semicircle because its
   radius is driven by WIDTH, not height.
   -------------------------------------------------------------------------- */

@media (min-width: 900px) {
  .how__track { align-items: stretch; }
}

.how__node { display: flex; flex-direction: column; }


/* --------------------------------------------------------------------------
   22.5 WHAT CHANGES — the "Word doc" fix.

   "I think the format needs to be a little bit better. It just kind of feels
   like it's like a Word doc and I think we can do better with that."

   The diagnosis was right. Each block was a short heading in a left column and
   a long paragraph in a right column, so the heading column ran out after ~150px
   and left roughly 300px of dead blue underneath it, three times. Nothing
   anchored the eye, and the alternating flip made the emptiness alternate sides.

   Three changes:
     1. The lead column is vertically CENTRED against the body, so the heading
        sits with its text instead of floating at the top of a void.
     2. The icon roughly doubles and its glyph is no longer clipped by the discs.
     3. Each block gets an arch-cropped media slot under the heading — which
        both fills the dead column and reserves media this page owes anyway.
   -------------------------------------------------------------------------- */

@media (min-width: 900px) {
  /* 🔴 `start`, NOT `center` - 8/18. Brees sent a screenshot with a red line
     drawn along the TOP EDGE of the overlapping discs, running across into the
     body column: the paragraph should begin level with the discs.

     `center` was the cause, and it made the error DIFFERENT IN EVERY BLOCK
     because each column pair has a different height. Measured icon-top minus
     first-paragraph-top before the change:

         block 1  +60px   (paragraph starts ABOVE the discs - his screenshot)
         block 2  +14px
         block 3  -40px   (paragraph starts BELOW them)

     Aligning to `start` puts both columns' content boxes on one line, so the
     discs - the first thing in the lead column - and the body's first line
     share a top edge in all three.

     ⚠️ IT HOLDS THROUGH THE FLIP. `--flip` swaps the ORDER of lead and
     body, and `align-items` applies to every track equally, so block 2 lines
     up the same way round. A margin nudge would have had to be written twice
     and would have drifted again the moment the copy changed. */
  .changes__block {
    grid-template-columns: 5fr 7fr;
    gap: var(--s-5);
    align-items: start;
  }
}

.changes__lead { display: flex; flex-direction: column; gap: var(--s-3); }

/* The icon was 48px with the glyph sitting partly behind the second disc. */
.changes__icon {
  width: 96px;
  height: 56px;
}

.changes__disc { width: 56px; height: 56px; }
.changes__disc--b { left: 40px; }


/* 🔴 `t-h3` ON THE ELEMENT, NOT `t-h2` — 8/18. Brees: "Make sure h3 sizes
   (unless intentially large)."

   These three carried `t-h2` and measured **48px at 1440 — the exact size of
   this section's own `<h2>` above them** (`.changes__heading`), so the heading
   and its three subheadings were typographically indistinguishable. That is
   the same defect E1 fixed in section 2 on 8/18, where his words were "some of
   the titles are larger than others."

   The token scale IS the standard here: display 72 / h2 48 / h3 24. An `<h3>`
   now takes the h3 token.

   ⏹ `.problem__label` IS DELIBERATELY LEFT AT 32px. It is the other outsized
   h3 on the page (its own `clamp(1.375rem, 2.4vw, 2rem)`, not a borrowed
   class), but it reads as an intentional emphasis pass — the surrounding notes
   call those three reasons "the payload of this section" and bumped their body
   copy to `--fs-lead` to match. Brees also said of section 2 the same day:
   "we'll leave it as is, just change the route of the line." Do not shrink it
   without asking. */
.changes__title { margin-bottom: 0; }

/* The arch media slot. Uses the page's own arch geometry so it reads as part
   of the system rather than as a generic image box. */
/* 🔴 `.changes__media` IS DELETED — rules and markup both. Tracked edit #6,
   Brees: remove image slots D3/D4/D5, no images in this section. The old
   260px-vs-340px note went with it; that trade no longer exists.

   ⚠️ THE STANDING MEDIA BASELINE IS NOW UNMET ON THIS PAGE BY INSTRUCTION.
   CLAUDE.md requires 3+ content images and a hero video on every page. The
   hero video slot went on 8/14 (no product footage exists) and these three
   went today. Only D2 in the final CTA remains, and #10 asks whether that goes
   too — which would leave the page with ZERO media. Recorded as a decision,
   NOT rationalised as a design choice. */

/* --------------------------------------------------------------------------
   22.6 THE PROOF — one alignment, not two.

   "Look at the formatting in this. We've got text that's left-centered and the
   boxes probably should be centered."

   The section mixed both: heading and sub left-aligned, cards left-aligned,
   and then the testimonial centred underneath. Two alignments in one section
   with nothing distinguishing them reads as an accident, because it was one.

   Centred throughout. The cards suit it — each is a figure, a three-word title
   and two lines — and it matches the arch language in How It Works, where the
   numeral and title are also centred.
   -------------------------------------------------------------------------- */

.proof { text-align: center; }
.proof__sub { margin-inline: auto; }

.proof__cards { align-items: stretch; }

/* --------------------------------------------------------------------------
   THE PROOF CARDS TAKE THE ARCH, LIKE "THREE THINGS".

   Brees, 8/18: "add round corners on the featured boxes in the 'Three Calls
   Almost Lost' section, just like we did in the 'Three Things' section."

   The How It Works cards are `.arch` — a full dome on top, --r-render at the
   bottom. These were a flat --r-card (24px) all round. Same geometry and the
   SAME TOKENS are reused rather than a second set of numbers invented, so the
   two sections cannot drift apart later.

   ⚠️ --arch-dome is padding, not decoration. A 999px top radius resolves to a
   semicircle of half the card's width (~190px at three columns), and content
   sitting in that band would be clipped by the curve — the dome is empty space
   by design. Same reason `.arch` carries it.

   These cards are centred (below), so the reserved band reads as a symmetric
   crown; the node dot then sits just under the apex where the numerals sit in
   Three Things. */
.proof__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* 52px at desktop, down from 80. Brees: "There's a lot of room above the
     icon [...] maybe we can tighten up the boxes a little bit."

     THIS IS ALSO WHAT BUYS THE BLUE AT THE BOTTOM. `.proof` is sticky at
     top:130 and `.pricing` slides over it, so the section's 86px bottom
     padding is covered before it is ever seen - measured, only 35px of sky
     showed below the cards against 86px above. The padding is ALREADY
     symmetric; the sticky window is the constraint, so do not "fix" this by
     adding bottom padding. Every px taken off a card becomes a px of visible
     sky, which is why tightening the card answers both halves of his note.

     ⚠️ THE FLOOR IS THE ARCH, NOT TASTE. The card top is a dome of radius =
     half the card width, so content sitting y px down has only
     2*sqrt(r^2-(r-y)^2) of width available. At 52px on a ~350px card that is
     ~294px against a 270px content box - it clears. Going much below puts the
     TITLE into the curve, not the icon; the icon is narrow and misleads.
     Re-measure before reducing further.

     ⚠️ SCOPED TO `.proof__card` ON PURPOSE. `.arch` declares its own
     --arch-dome at the top of this file and still uses the 80px value; the
     how-section cards depend on it. Do not consolidate these two. */
  --arch-dome: clamp(2.5rem, 5vw, 3.25rem);
  border-radius: 999px 999px var(--r-render) var(--r-render);
  padding: var(--arch-dome) var(--s-4) var(--s-4);
}

/* The node dot was pinned to the card's top-left corner to meet the connector.
   Centred cards need it centred too, or it reads as a stray mark.

   ⚠️ NOT DEAD CODE, AND NOT A DORMANT DUPLICATE. d1-signal no longer has a
   `.proof__node` element - it was replaced by `.proof__icon` below on 8/18.
   This rule and the two at ~1062 survive because `direction-d.css` is ALSO
   loaded by `directions/d3-document/`, which still ships the node markup and
   has no `.proof__node` rule of its own. Deleting them would silently strip
   that page's dots. Nothing on d1 can revive these: the elements are gone. */
.proof__node {
  position: static;
  margin-bottom: var(--s-2);
}

/* --------------------------------------------------------------------------
   THE PROOF ICONS - 8/18.

   Brees: "we're gonna remove the dots that are inside of the boxes" and put
   three icons from his sheet in instead, with an explicit variant each:
     card 1  Almost walked, then stayed  ->  Single Person, VARIANT 1 (petal + lime)
     card 2  No appointment offered      ->  X,             VARIANT 3 (marigold + clay)
     card 3  Fully booked                ->  Calendar,      VARIANT 2 (sky + indigo)

   🔴 MEASURED ON THE WHITE CARD, NOT ASSUMED. `.proof__card` is `--white`,
   sitting on the sky section ground F3 gave it. Against that white:
     indigo 8.09 · clay 3.54 · marigold 2.33 · petal 1.70 · lime 1.49 · sky 1.35
   The project's mark floor is 1.35, so literal --sky would have landed EXACTLY
   on it with zero margin, and the six calendar dots visibly washed out in the
   render - the flag was not theoretical. Brees' call, 8/18: the calendar's blue
   half uses --sky-deep (3.47 on white) instead. Still Variant 2 - indigo frame,
   blue accents - with the blue at the deep tier, which is the standing rule for
   a pastel mark on any light ground.

   ⚠️ THE REMAINING THIN ONE IS VARIANT 1'S LIME AT 1.49. If this card fill ever
   moves off --white, re-measure all three before shipping; do not reason about
   it. sky-soft/lime on a sky ground runs to 1.10.

   Colours are set INLINE on the SVG paths rather than in variant-d.css because
   they are the icon's identity from the sheet, not a colourway of this page -
   a variant file swapping them would produce an icon that is no longer the
   variant Brees named. -------------------------------------------------- */
.proof__icon {
  display: block;
  /* 64, not 48. At 48 they read as a quiet accent lost in the arch dome's
     empty top - Brees sized them up on 8/18 to match the prominence they
     have on his sheet. */
  width: 64px;
  height: 64px;
  margin-bottom: var(--s-2);
}

.proof__glyph {
  display: block;
  width: 100%;
  height: 100%;
  /* Multiply gives the crossing wedge on the X and the head-over-shoulder
     overlap on the person - the brand's own two-tone construction, the same
     trick `.changes__disc` and the arch mark use. */
  mix-blend-mode: multiply;
}

.proof__card .t-body { margin-inline: auto; }


/* ⏹ The two `.proof .testimonial__inner` rules are DELETED — E4 moved the
   quote into the closing CTA, so there is no testimonial inside .proof to
   space or centre any more. Left in place they would be dead weight that
   reads as if the quote still lives there. */

/* ==========================================================================
   23. ROUND 3 — Brees' second review, 2026-08-13.
   ========================================================================== */

/* --------------------------------------------------------------------------
   23.1 LINEWORK replaces the tiled pattern.

   "I know I asked you to use that background for the background but it's too
   busy. I don't know if we necessarily need to use that same background image
   but the idea was to give you a general creative direction as to what I mean
   by lines, etc. I would like to see that polished a little bit more."

   Round 2 answered "too busy" by scaling the tile from 620px to 1280px. That
   made the marks bigger; it did not make the composition better, because a
   tile has no composition. It repeats on a grid, it has no relationship to the
   content in front of it, and every section using it looked identical.

   `.pattern-field` and its 1280px tile are DELETED. In their place, two
   composed SVGs — linework-a and linework-b — drawn once at page scale, with
   long routing runs that enter and leave the frame and never repeat. Same
   grammar as the connector and the ArchLine: right angles, 40px arcs, colour
   only in the dots.

   📌 The instruction to read here was "lines, etc." as a DIRECTION, not
   "use this asset". Round 2 took the asset literally and tuned a number.
   -------------------------------------------------------------------------- */

.linework {
  position: relative;
  isolation: isolate;
}

.linework::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-repeat: no-repeat;
  background-position: center;
  /* `cover` on a composed drawing, not a tile — it is meant to fill the
     section once, the way artwork does. */
  background-size: cover;
  /* 🔴 0.3 — BREES' EXPLICIT CHOICE, 8/19, OVER THE MEASURED OBJECTION BELOW.
     He asked for "the bg be .3 opacity" on both linework sections across V1
     and V2, was shown the history in this comment and the predicted number,
     and chose it anyway. It is his call and it stays until he says otherwise.

     ⚠️ WHAT IT COSTS, MEASURED NOT GUESSED: the #d2cabe stroke at 0.3 over
     oat blends to about #e9e3db, which is **1.11:1** against the section
     ground. That is the same contrast as the old tile that was thrown out for
     being invisible, and below the 0.7 attempt described next.

     ⏹ THE HISTORY THIS REPLACES — do not lose it, it is why the value was 1:
     At 0.7 the #d9d3ca stroke blended to roughly 1.05:1 against oat — the
     drawing loaded, sized and positioned correctly and was still invisible.
     That is the opposite of "too busy" and no better.
     📌 The old TILE's individual line measured 1.11:1. It only ever read as
     busy through DENSITY — dozens of near-invisible marks per screen. Sparse
     linework has no density to hide behind, so each line has to carry itself:
     #d2cabe measures 1.41:1 on oat, which is present without being loud.

     ⚠️ IF HE WANTS IT SOFTER BUT STILL VISIBLE, opacity is the wrong dial —
     that is the lesson of the 0.7 attempt. Change the STROKE COLOUR in the
     two SVGs instead, which is how it was solved last time.

     ⏹ 8/20, FINAL — BACK TO 0.3 ON THE ORIGINAL STROKE. Read this before
     touching the dial again; it went round three times in one session.

     The round: Brees asked for 0.1. Applied and measured it came out at
     1.067:1 on oat, which is the binned 0.7 attempt over again. Shown that,
     he chose "darken the stroke instead" and it went to #D2CABE at opacity 1
     (1.41:1). Seeing it, he asked for "0.3 again" — so the 8/19 appearance is
     restored exactly: #B2A695 at 0.3, which measures 1.220:1.

     ⚠️ "0.3 again" MEANT THE OLD LOOK, NOT THE NEW STROKE AT 0.3. Those are
     different pictures: #D2CABE at 0.3 measures 1.105:1, fainter than
     anything he had seen and fainter than the 0.1 he rejected. The stroke was
     reverted with the opacity for that reason. If the dial is ever revisited,
     move BOTH or neither.

     📌 So the standing position is the one this comment always described:
     1.220:1 is below the 1.35 decorative floor and he knows it, has been
     shown the number three times, and wants it that soft. It is his call.
     What is NOT open: reaching for opacity to make it "soft but visible".
     That has now failed at 0.7, at 0.1, and would fail again. Stroke colour
     is the dial; #D2CABE at opacity 1 is the tested 1.41:1 option sitting
     ready if he ever wants it to actually read.

     ✅ The circles are gone for good regardless of the dial — both groups
     removed from linework-a.svg on 8/20, asserted by verify.js. */
  opacity: 0.3;
  pointer-events: none;
}

/* 🔴 THE CTA'S LINEWORK IS BACK, AND IT USES PLAIN `cover` - 8/18.
   Brees: "I want the line graphic behind the CTA like it was."

   "Like it was" means the ORIGINAL cover behaviour, NOT the ratio/bleed fix
   that briefly lived here. That fix was right about the cause - the 1600x900
   drawing loses 187-331px to a 2.4-2.8:1 band, which is why the routing line
   appears to start in mid-air - but giving it full height pushed the artwork UP
   THROUGH THE FAQ, where the dotted run and the vertical leg crossed live
   questions. He crossed both out. **Do not reintroduce that override.**

   So the crop is a KNOWN, ACCEPTED tradeoff now, not an unnoticed bug: the
   drawing is background texture here and its cut ends are the cost of it.

   ⚠️ `.shape--seam` STAYS. He asked for the circle to "mend the sections" and
   has not withdrawn it; it is doing a different job from the linework and the
   two coexist. Remove it only if he says so.

   ⚠️ NOTHING IS DECLARED HERE ON PURPOSE - the generic `.linework::before`
   (inset: 0, cover) is what applies. An `inset: 0` box cannot overflow the
   section, so `overflow-y: visible` is safe alongside it. */

.linework--a::before { background-image: url("../img/brand/linework-a.svg"); }
.linework--b::before { background-image: url("../img/brand/linework-b.svg"); }

/* 🔴 The contrast reasoning from section 21 still applies and still cannot be
   measured by the sweep, because this is still a background IMAGE on a
   pseudo-element.

   It is now much safer by construction: the marks are sparse, and the runs are
   composed to leave the middle of each frame open, which is where the text
   sits. But "safer" is not "checked", so secondary copy on a linework section
   keeps taking full ink exactly as it did on the pattern. The worst mark is
   the same clay dot at 6.22:1 against full ink.

   Do not relax this because the drawing looks emptier. */
.linework .how__sub,
.linework .proof__sub,
.linework .cta-quote__name,
.linework .t-eyebrow {
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   23.2 THE PROBLEM SECTION — give it weight.

   "This is the second section and really the section that's going to draw the
   person in and make them feel like we heard their pain points. While I like
   that there are no toggles here, I do think that this section is just missing
   a piece that doesn't make it feel important. I almost want to just scroll
   over this section and not even stop to read it."

   Diagnosis: the headline was --fs-h2 and the three reasons were small white
   rounded boxes. Boxes are the house's least favourite device precisely
   because they flatten everything to the same importance
   (feedback_no_ai_default_cards), and three of them on a pink field read as
   UI, not as an argument. Nothing on screen said "this is the part that is
   about you."

   Rebuilt so the emphasis comes from TYPE, which is the house position:

     - the headline goes to --fs-display, the size the hero uses. It is the
       page's second-most-important sentence and it now looks like it.
     - the three reasons lose their boxes entirely and become large type on
       hairline rows, each led by its brand dot. Same device as the pricing
       list, which Brees approved.
     - the reason LABEL is display-face and large; the explanation sits under
       it in muted body. Two clear levels instead of one flat card.
   -------------------------------------------------------------------------- */

/* 🔴 NO SIZE OVERRIDE — E1, 8/18. This carried `font-size: var(--fs-display)`,
   which rendered section 2's H2 at **72px against 48px everywhere else**. It is
   what Brees spotted: "some of the titles are larger than others, especially in
   section two." The audit found four different H2 sizes on one page (72 / 56 /
   48 / 36.8) with the H1 smaller than two of them.

   The token scale IS the standard — display 72 / h2 48 / h3 24. `.t-h2` on the
   element now supplies size, line-height and letter-spacing; the last two were
   restating what `.t-h2` already sets, so they went with it.

   ⚠️ `max-width` is in `ch`, which resolves against the element's OWN font
   size — trap #6, already recorded here. Dropping 72px → 48px shrinks 17ch from
   ~700px to ~470px, so the cap is re-set in px to hold the same measure the
   layout was built around rather than silently narrowing the column. */
.problem__headline {
  max-width: 700px;
  margin-bottom: var(--s-5);
}

/* 🔴 NO CARDS. The white box, its radius, its padding and its shadow are all
   gone. If this ever starts looking weak again, the fix is type and space —
   not a container. */
.problem__cards {
  display: grid;
  gap: 0;
  border-top: 1px solid rgba(47, 47, 53, 0.22);
}

@media (min-width: 900px) {
  .problem__cards { grid-template-columns: 1fr; }
}

.problem__card {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: var(--s-2) var(--s-3);
  align-items: baseline;
  padding-block: var(--s-3);
  background: none;
  border: 0;
  border-bottom: 1px solid rgba(47, 47, 53, 0.22);
  border-radius: 0;
  box-shadow: none;
}

@media (min-width: 760px) {
  /* Label left, explanation right — so the three labels stack into a readable
     column of their own and the eye can take the argument in at a glance. */
  .problem__card { grid-template-columns: 14px minmax(0, 22ch) minmax(0, 1fr); }
}

.problem__dot {
  width: 14px;
  height: 14px;
  margin-bottom: 0;
  border-radius: 50%;
  transform: translateY(-0.35em);
}

.problem__label {
  font-size: clamp(1.375rem, 2.4vw, 2rem);
  font-weight: 500;
  line-height: 1.15;
}

.problem__text {
  font-size: var(--fs-lead);
  /* On the petal field --ink-muted is 4.6:1, which passes, but the reasons are
     the payload of this section and they read better at full strength. */
  color: var(--ink);
  max-width: 44ch;
}

#epanel-1 { --panel-accent: var(--petal); }
#epanel-2 { --panel-accent: var(--lime); }
#epanel-3 { --panel-accent: var(--marigold); }


/* ==========================================================================
   24. THE DOT RULE — pastels on indigo, deep tier everywhere else.

   🔴 THIS IS THE THIRD TIME THE SAME MISTAKE HAS SHIPPED IN TWO DAYS.

     1. `variant-2.css` put a --sky icon disc on a --sky section.      1.00:1
     2. Round 2 put a --sky dot on white, which read as a printing error.
     3. Round 2 then "fixed" it by switching that dot to --petal — on the
        PETAL section. Also 1.00:1. Brees' screenshot shows two dots and a
        gap where the first one should be.

   Each time the colour was chosen from the palette on its own merits and
   never checked against the ground it lands on. So here is the measurement,
   once, for every ground this page uses:

                     petal   sky    oat    white   indigo
       petal          1.00   1.26   1.48   1.70    4.74
       lime           1.15   1.00   1.29   1.49    5.44
       sky            1.26   1.00   1.17   1.35    5.97
       marigold       1.37   1.72   2.02   2.33    3.47
       ---- deep tier ----
       petal-deep     2.03   2.56   3.01   3.47    2.33
       lime-deep      2.04   2.57   3.02   3.48    2.32
       marigold-deep  2.03   2.56   3.00   3.47    2.33

   Read the top block: the brand pastels are unusable as marks on every light
   ground the page has. They work in exactly ONE place — on indigo, where they
   clear 3.47 to 5.97. That is why the pricing list looks right and why every
   other dot on the page has looked either weak or absent.

   THE RULE, and it is not a judgement call:
     - on INDIGO, dots take the full-strength pastels
     - on every light ground, dots take the --*-deep tier

   A decorative dot has no formal contrast requirement — it states nothing on
   its own. "No requirement" is not "may be invisible": if it cannot be seen it
   is not decoration, it is a missing element.
   ========================================================================== */

/* -- Problem: petal ground -------------------------------------------------- */
.problem__card:nth-child(1) .problem__dot { background-color: var(--petal-deep); }
.problem__card:nth-child(2) .problem__dot { background-color: var(--lime-deep); }
.problem__card:nth-child(3) .problem__dot { background-color: var(--marigold-deep); }

/* -- Problem: INDIGO ground (was petal) — 8/20 ------------------------------
   Brees: "change the pink in sec2 on both v1 and v2 to #4C4E68". That hex is
   already this system's `--indigo`, so the section takes `section--indigo`
   rather than a one-off hex, and inherits its `color: var(--oat)` flip.

   🔴 A GROUND THIS DARK IS NOT A ONE-LINE SWAP. Everything below was tuned
   against the petal field and reverses on indigo:
     - `.problem__text` was `--ink` at full strength. Its own comment says the
       reasons "are the payload of this section and they read better at full
       strength" — so it becomes full-strength `--oat`, NOT `--oat-75`. The
       intent was emphasis, not a colour.
     - the row hairline was `rgba(47,47,53,0.22)` — ink on ink, invisible here.
       It takes `--oat-14`, the value the pricing band (already indigo) uses
       for exactly this.
     - the dots were the `--*-deep` tier, chosen to carry on a pale petal
       field. On indigo that tier collapses: lime-deep measures 2.32:1 against
       this ground where the plain pastel measures 5.44:1. base.css already
       says decorative dots keep the ORIGINAL pastels; on a dark ground that
       is not a preference, it is the only readable option.
   `.problem__label` sets no colour, so it inherits the oat flip on its own. */
.section--indigo .problem__text { color: var(--oat); }
.section--indigo .problem__card { border-bottom-color: var(--oat-14); }
.section--indigo .problem__card:nth-child(1) .problem__dot { background-color: var(--petal); }
.section--indigo .problem__card:nth-child(2) .problem__dot { background-color: var(--lime); }
.section--indigo .problem__card:nth-child(3) .problem__dot { background-color: var(--marigold); }

/* -- How It Works: white arches on an oat section --------------------------- */
/* (the deep-tier dot colours went with the device - see the note above) */

#epanel-1 { --panel-accent: var(--petal-deep); }
#epanel-2 { --panel-accent: var(--lime-deep); }
#epanel-3 { --panel-accent: var(--marigold-deep); }

/* -- Pricing: indigo, and the ONE place the pastels belong ------------------
   Left exactly as it was. Brees called this section out as looking good, and
   the table above says why: on indigo the pastels are the strongest marks on
   the page. Do not "make it consistent" with the rule above. */


/* ==========================================================================
   25. THE CARD STACK — the three emails
   Replaces the tabbed panel. Brees, 8/14: "Maybe instead of tabs we do
   animated cards?", pointing at a card-stack scroll animation.

   🔴 THIS REVERSES THE 8/13 TABS DECISION ON PURPOSE. Tabs were chosen by
   Brees from three options and the notes marked the question closed. He
   re-opened it himself. Recorded here so a future reader does not "restore"
   the tabs thinking something drifted.

   MECHANIC — pure CSS, no JavaScript:
   Each card is `position: sticky` with a top offset that increments per card
   (`--i`). As you scroll, each card pins under the header and the next slides
   up over it, so the previous cards' top edges accumulate into a visible
   stack.

   🔴 WHY THIS IS NOT THE 8/11 SLIDER IN DISGUISE. The slider was vetoed
   because it hid two thirds of the section whose entire job is showing what
   the product produces. Nothing here is hidden: all three emails are in the
   DOM, in reading order, always rendered, and reachable by ordinary scrolling.
   There is no wheel hijacking, no scroll snapping and no JS. Turn styles off
   entirely and it is three panels in a column.

   ⚠️ IT COSTS HEIGHT. A stack of three cards needs roughly the sum of their
   heights in scroll distance. That is inherent to the pattern, and it is worth
   saying out loud on a page where Brees has twice asked for less length.
   ========================================================================== */

.stack {
  /* flow-root so the cards' margins do not collapse out of the section and
     take the stack's scroll distance with them. */
  display: flow-root;
  margin-top: var(--s-5);
}

.stack__card {
  position: sticky;
  /* 🔴 THE OFFSET HAS TO CLEAR THE STICKY HEADER.
     The header became `position: sticky` earlier on 8/14 and measures 160px on
     desktop. A card pinned at top:0 would slide underneath it and its own
     heading would be the first thing hidden. 176px = header + 16px of air.
     The 12px per card is what makes the stack legible as a stack — without it
     every card pins at the same y and you see one card, not a deck. */
  top: calc(var(--hdr-h-min) + 16px + (var(--i) * 12px));
  border-radius: var(--r-card);
  /* 🔴 VERTICAL PADDING IS TIGHTER THAN HORIZONTAL ON PURPOSE, AND MEASURED.
     At an even --s-4 the weekly card came out 745px against 735px of usable
     height (911 viewport minus the 176px offset) — 10px over, which does not
     sound like anything until you remember a pinned card taller than the
     viewport has its bottom edge permanently cut off. Dropping the vertical
     padding one step buys 32px and puts all three cards inside the budget.
     Re-measure if the header height or the renders change. */
  padding: var(--s-3) var(--s-4);
  margin-bottom: var(--s-4);
}

.stack__card:last-child { margin-bottom: 0; }

/* 🔴 NO DROP SHADOW. The 8/13 note on this exact section was that arch >
   inset > render, three containers deep with a shadow on the outer one, "makes
   it look like this is done in 1990". The cards separate by solid ground and
   the accumulating offset instead — which is also why each card MUST keep an
   opaque background (see variant-d.css). A translucent card would let the one
   underneath show through and the stack would read as a rendering fault. */

.stack__inner {
  display: grid;
  gap: var(--s-3);
  align-items: start;
}

@media (min-width: 960px) {
  .stack__inner {
    grid-template-columns: 4fr 7fr;
    gap: var(--s-4);
  }
}

/* -- The audience label ----------------------------------------------------
   This is the line the tab used to carry. Brees noticed the selected tab read
   as solid and wondered whether the ground should be too — it now is, and the
   label sits on it rather than above it. */
.stack__who {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: var(--s-2);
  font-family: var(--font-display);
  font-size: var(--fs-small);
  font-weight: 600;
}

.stack__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
}

/* 🔴 FULL INK, NOT --ink-muted. The card grounds went to full-strength brand
   colour on 8/14, and --ink-muted is calibrated for oat and white only
   (5.05 / 5.40). On petal it measures 4.09:1 and on sky 4.31:1 — both under
   the 4.5 floor. Every piece of secondary text on these cards therefore takes
   full ink. Do not "soften" it back for hierarchy; hierarchy here comes from
   size, weight and case. */
.stack__when {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink);
}

.stack__title { margin-bottom: var(--s-2); }

/* Full ink for the same reason as .stack__when above. */
.stack__body {
  margin: 0;
  color: var(--ink);
  max-width: 46ch;
}

/* 🔴 THE TEXT-INVERSION BLOCK IS DELETED, NOT MOVED — 8/14.

   There was a `.stack__card--N .stack__title { color: var(--oat) }` group here
   for whichever card was indigo. It moved from --3 to --1 when the order
   changed, and is now gone entirely: the grounds are sky / lime / marigold and
   NONE of them is dark. All three take ink — sky 9.83, lime 8.95,
   marigold 5.71.

   ⚠️ IF A DARK CARD EVER COMES BACK, THIS BLOCK COMES BACK WITH IT, on the
   matching card number. Ink on indigo is 1.64:1 — not weak, unreadable — and
   nothing in the build fails when it is wrong. Only the contrast sweep catches
   it, and only if that card is actually being measured.

   The full-ink rules above exist for the sky and marigold grounds, where
   --ink-muted measures 4.31 and ~4.1 and fails the 4.5 floor. */

.stack__render {
  margin: 0;
  width: 100%;
}

/* -- Reduced motion --------------------------------------------------------
   Sticky is layout rather than animation, so this is not strictly required by
   2.3.3. It is here because a deck that assembles itself under a fixed header
   is exactly the kind of movement that makes some people queasy, and the
   fallback costs nothing: three panels in a column, in order, fully readable.

   🔴 Do not "optimise" this away. The stack is decoration; the emails are the
   content, and they survive without it. */
@media (prefers-reduced-motion: reduce) {
  .stack__card {
    position: static;
    top: auto;
  }
}

/* Below the two-column breakpoint the render needs the full width, and a
   pinned stack on a short phone screen leaves almost nothing visible once the
   160px header is deducted. So the stack becomes a plain column. */
@media (max-width: 959px) {
  .stack__card {
    position: static;
    top: auto;
    padding: var(--s-3);
  }
}


/* ==========================================================================
   26. S2 PROBLEM — the three reasons sit BESIDE the headline, not under it.

   Brees, 8/14, edit #1 on the tracked list: "move the three items beside the
   title, not stacked under it."

   Before: headline (full width, 17ch, 5 lines at 72px) with a 64px gap, then
   three full-width hairline rows underneath. Section height 1029px.

   The stacked version wasted the right half of the headline block and then
   wasted the left half of every row — the label column was 22ch of a 1136px
   row, so the explanation floated in the middle of an empty field. Beside
   each other, both halves are doing work at the same time.

   🔴 THE HEADLINE KEEPS --fs-display. The 8/13 note is explicit that the
   display size is what stopped this section reading as skippable ("I almost
   want to just scroll over this section"). It is NOT stepped down to make the
   two-column fit easier. The columns were sized around the type instead:
   at 1920 the headline column lands 520px, which is ~14 characters of 72px
   display face — six lines, 458px measured. The list column lands 496px:
   rows measure 171 / 208 / 171, so labels 1 and 3 hold one line and "Price
   shoppers comparing" takes two. List total 551px against a 458px headline.

   Section height 1029px → 727px, and the page with it, 11,711 → 11,409.
   That is the first time this page has got SHORTER; Brees has asked twice.

   Breakpoint is 1000px, not the 900px the old grid used: below that the
   headline column drops under ~430px and "happening." starts to crowd the
   measure. Under 1000px this reverts to the stacked layout exactly as it was.
   -------------------------------------------------------------------------- */

@media (min-width: 1000px) {
  .problem .container {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    column-gap: var(--s-6);
    align-items: start;
  }

  /* 17ch on a 72px display face computes to ~700px and would fight the track.
     The column is the measure now. (Trap #6 — `ch` resolves against the
     element's OWN font, which is why the old cap was so much wider than it
     looked.) */
  .problem__headline {
    max-width: none;
    margin-bottom: 0;
  }

  /* 🔴 FULL RE-DECLARATION, not a partial override. Trap #5 on this project:
     the 760px rule sets a THREE-track `grid-template-columns` and leaving it
     in force here would put a 22ch label track inside a 496px column. The dot
     track stays; label and text stack. */
  .problem__card {
    grid-template-columns: 14px minmax(0, 1fr);
  }

  /* The explanation no longer sits in its own track, so it needs to clear the
     dot gutter and sit under its label. */
  .problem__text {
    grid-column: 2;
    max-width: none;
  }
}


/* ==========================================================================
   27. THE PARKED HEAD AND ITS SECTION LABEL

   Brees, 8/17: "I'd like for the dot to kind of overlap sections one and two
   and then show by name and then as I scroll that's when the dot should move
   through the new section."

   His annotation pin sat at x≈378 on a 1908px shot. The innerL lane is at 365
   and the hero/problem seam is that lane's corner — so the mark is the
   junction itself, not a nearby spot. Read literally, per the standing rule
   that an annotated screenshot is a spec.

   The dot ALREADY straddles the two sections for free: the connector now
   enters along the problem section's top boundary (§26 / edit #2), so the
   whole entry run lies exactly on the seam. Nothing had to be positioned.

   HOW THE PARKING WORKS — and why it is not a snap.
   The obvious build is "if the head is within N px of a junction, clamp it
   there." That produces a JUMP of N px the moment it leaves the band. Instead
   direction-d.js remaps travel between junctions through a smoothstep, which
   has ZERO DERIVATIVE AT BOTH ENDS: the head decelerates into every junction,
   rests, and accelerates away. Continuous, no jump, and it is literally
   "parks at the seam, then moves through the section as you scroll."

   🔴 THE LABEL COLOUR IS COMPUTED AT RUNTIME, NOT WRITTEN DOWN. It is set
   from the measured luminance of the ground the label actually lands on —
   ink on light, oat on dark. Hard-coding ink would fail on the indigo pricing
   seam (1.64), which is the same mistake §24 documents. It also has to keep
   working when the card grounds change, and that decision is still open.
   -------------------------------------------------------------------------- */

/* 🔴 THE LABEL IS GONE. `.connector__label` and its reduced-motion rule used
   to live here. Brees, 8/17: "I also don't want the title. Right now it says
   [The problem]. I don't want that. It should not say that."

   Deleted, not hidden — and with it went `data-connector-name` on the seven
   sections, `readableOn()` in direction-d.js, and the runtime label-colour
   logic. That also closes the open copy question those names had created:
   they were placeholder wording that had never been near Alie, and there is
   now no unapproved copy on the page from this device.

   ⚠️ Do not re-add a name here as a "small touch". It was asked for on 8/17
   and removed on 8/17. */



/* ==========================================================================
   28. WHAT CHANGES — the section title and Alie's numerals

   Two tracked edits, both restoring copy the build had dropped rather than
   adding anything new:

     #7  the section had NO title. Three numbered statements sat directly on
         the sky ground under nothing. "What Changes When You Turn It On" is
         Alie's own heading.
     #9  Alie numbers those three items 1. / 2. / 3. and the page had dropped
         the numerals.

   ⚠️ HEADING LEVEL: the three block titles moved from <h2> to <h3> when the
   section title arrived, because the section title is now the h2 and skipping
   a level is a real a11y defect. They KEEP `class="t-h2"`, so nothing about
   the design changed — the class carries the look, the element carries the
   structure. Do not "fix" the mismatch by putting them back to h2.
   -------------------------------------------------------------------------- */

.changes__heading {
  /* ⏹ THE BESPOKE 56px CLAMP IS GONE — E1, 8/18. It was "one step above the
     block titles", i.e. a THIRD H2 size on a page that already had two. That
     reasoning depended on S2 being a display moment, and S2 has now come back
     to the token. `.t-h2` supplies the size.

     ⚠️ Its 18ch cap had the same `ch`-against-own-font problem as S2 — it is
     px now so the measure does not shrink with the type.

     ⏹ AND THE 620px CAP IS NOW GONE TOO - 8/18. Brees: "this should be one
     line." That cap was the ONLY thing wrapping it; no type change was needed.
     Measured at `.t-h2` before touching anything: the line needs 792px at
     desktop against 1112px of content box, and 535 against 836 at 900 - it
     fits everywhere from 900 up with room to spare.

     ⚠️ NO `white-space: nowrap` ON PURPOSE. Unlike the deliverable headline
     (63 characters, which genuinely needed forcing and a tuned clamp), this
     line is 31 characters and fits on its own. Leaving it free means it still
     WRAPS below 900 where the container gets narrow, instead of overflowing.
     Do not "finish the job" by adding nowrap. */

  /* ⏹ AND ON 8/24 IT WENT BACK TO TWO LINES, RIGHT-ALIGNED. Brees: "right
     align this title and put it on two lines: What Changes When / You Turn It
     On." That reverses the 8/18 "this should be one line" above — kept rather
     than deleted, because the measurements under it are still the reason the
     line CAN sit on one, and someone will otherwise re-derive them.

     ⚠️ The break is a `<br>` in the markup, not a width cap. A cap would put
     the fold wherever the font happened to land it and would move with any
     type change; he named the two lines. `text-wrap: balance` from `.t-h2`
     has nothing left to balance once the break is explicit.

     📌 LEFT, NOT RIGHT. It was right-aligned for one round on 8/24 and he
     corrected it the same day: "i meant to left align that title." Written
     explicitly rather than deleted, so the next person does not read the two
     forced lines as a leftover of a right-aligned layout and try to
     "restore" it. */
  text-align: left;
  margin-bottom: var(--s-5);
}

/* [X] NUMERALS CAME AND WENT AGAIN ON 8/18 - AND THIS SECTION ENDED UP BACK
   WHERE IT STARTED. DO NOT ACT ON ANY EARLIER VERSION OF THIS NOTE.

   This block has now said three different things. It once read "NO NUMERALS.
   DO NOT REVIVE THEM - this is the SECOND time" (they were killed on 8/11 and
   8/17). It was then rewritten to say numerals were back, because Brees asked
   for them on 8/18. He then corrected that: "we weren't supposed to change the
   icons so we need to put back the circles."

   CURRENT AND SETTLED: What Changes uses the two overlapping discs with a
   glyph, the 08-11 device. The two-tone numerals from his icon sheet live in
   `.how__num` instead.

   WARNING: THE CLASS-NAME LESSON STILL STANDS. Numerals here once picked up
   `.changes__num` by accident and rendered as the ink badge he had killed.
   That rule is deleted from this file. If numerals are ever tried here again,
   they need their own name AND a fresh contrast measurement against the discs -
   the last attempt failed at 1.49:1 against a 3:1 floor. */

@media (max-width: 899px) {
  .changes__heading { margin-bottom: var(--s-4); }
}


/* ==========================================================================
   29. THE PALETTE-PAGE CIRCLES — "Three emails" section

   Brees, 8/17, pointing at guide page 11 (= `p12.webp`; the filenames run one
   ahead of the guide's own numbering): "you will see these circles that are
   complementing the background."

   Read off the page rather than remembered:
     - FLAT fills. No blur, no gradient. (Gradients are a standing no.)
     - TINTS, not full strength. Every circle on that page is the pale end of
       its hue — which is why §3's existing `.shape--*-lg` rules are the wrong
       tool here: they use `var(--petal)` etc. at full strength, and four of
       those behind a card stack would shout over the renders.
     - WILDLY varying sizes, scattered, not arranged on a grid.
     - Overlapping, with a visibly DARKER WEDGE where two cross.
     - Some cropped by the page edge.

   🔴 THE WEDGE IS `mix-blend-mode: multiply`, NOT A THIRD HAND-PICKED COLOUR.
   The guide's darker crossing is what two translucent inks do on paper.
   Multiply reproduces it for any pair, so the wedge stays correct if the
   grounds change — and the card-stack grounds are still an open decision.
   It is also the same trick the arch mark already uses to make its tan
   capsule, so the page is not learning a new device.

   ⚠️ These sit at z-index 0, behind `.container` (z-index 2) AND behind the
   opaque cards. What reads is the part that clears the stack. That is the
   guide's own behaviour — it lets them run off the page edge rather than
   fitting them into the layout.
   -------------------------------------------------------------------------- */

/* 🔴 `overflow: hidden` MUST NOT GO ON `.deliverable`.

   That is what I did first, to crop the circles at the section edge, and it
   silently BROKE THE CARD STACK: an ancestor with any `overflow` other than
   `visible` disables `position: sticky` for its descendants. Measured — the
   cards stopped pinning entirely (card 1 top at −594 instead of 176), so the
   file-folder tabs he had just asked for were dead. Nothing errored; the cards
   simply scrolled like normal blocks.

   The clip belongs on a wrapper that is NOT the sticky ancestor. */
/* 🔴 THE CIRCLE FIELD MUST NOT LEAVE THIS SECTION — 9/2. Brees: "those shapes
   shouldn't go into this section", pointing at the pale petal and lime circles
   showing over the recovered-revenue section below.

   `.shape-field` is `position: sticky` with `height: 100vh` and
   `margin-bottom: -100vh` — a viewport-tall box pinned just under the header so
   the circles hold still while the cards move over them. Its own
   `overflow: hidden` clips the circles to that box, but NOTHING clipped the box
   to this section, so while it is still stuck it paints over whatever follows.

   ⚠️ IT ONLY SHOWS AT SHORT VIEWPORTS, WHICH IS WHY IT SURVIVED SO LONG.
   Measured: no overlap at all from 900px tall upward — at 700-780px the lime
   circle crosses into the next section by up to 400px. This exact trap has
   shipped before: "Hero overlap lives on viewport
   HEIGHT, not width ... 1440x900-only checks hid it while 20 live pages
   overlapped." Every sweep I ran for this was 900-1000px tall.

   🔴 `clip`, NEVER `hidden`. This section is the sticky card stack AND the
   sticky field; `overflow: hidden` makes a scroll container and silently kills
   position:sticky on every descendant. `clip` does not. */
.deliverable { position: relative; overflow: clip; }

/* 🔴 THE CIRCLES DO NOT TRAVEL WITH THE CARDS.

   Brees, 8/17: "the circle shapes, they're still moving with the cards. They
   shouldn't be moving. They should stay where they are."

   Absolutely positioned inside the section, they were pinned to the SECTION
   box — so they scrolled with it, and against the pinned cards they read as
   sliding. Sticky + a 100vh box holds them still in the viewport for as long
   as the section is on screen, so the cards move over a stationary field.

   `margin-bottom: -100vh` keeps that 100vh box out of the section's height —
   without it the section would grow by a full viewport. */
/* 🔴 THE CIRCLES MUST SETTLE BEFORE ANYTHING ELSE LOCKS — E5, 8/18.

   `top: 0` made them pin only once their own top reached the viewport top,
   which is LATER than the title (pins at --hdr-h-min + 16) and the first card.
   Measured at 1600: at scrollY 2431 the title was locked at 130 and card 1 at
   200 while the circles were still at 26, only reaching 0 at 2581. So for
   ~150px of scroll everything was frozen and the field was still sliding —
   Brees: "as soon as the blue card scrolls up then the shapes, the circles,
   also scroll up. I don't want those moving."

   Pinning them at the SAME offset the title uses makes them settle ~100px of
   scroll EARLIER than anything else, so by the time the stack locks the field
   has already stopped. A larger `top` pins a sticky element sooner, not later
   — that is the part that reads backwards.

   ⚠️ Derived from --hdr-h-min, not a number, so it tracks the condensing
   header like every other pinned offset on this page. */
/* 🔴 THE FIELD LIVES INSIDE `.deliverable__pin` SO IT RELEASES WITH THE STACK.

   E5 pinned the circles so they stop drifting, and that was right. But as a
   direct child of `.deliverable` its sticky constraint resolved against the
   SECTION's bottom, and `margin-bottom: -100vh` makes that constraint fall on
   the element's TOP rather than its bottom — so it stayed pinned ~700px after
   the cards had gone. Measured: card 3 released around 4400 while the field
   held at 130 until 5012, and `.changes` slid over the pinned circles and
   cropped them. Brees: "as the following section rolls in we end up with
   cut-off shapes [...] it looks like section four is parallaxing."

   Sharing the stack's wrapper gives it the stack's end point, so the circles
   travel out with their own section. No parallax.

   ⚠️ FULL-BLEED HAS TO BE RESTORED BY HAND. `.deliverable__pin` sits inside
   `.container` (1240 + padding), and every circle is positioned in PERCENT of
   this field — constrained to the container they would all move inward.
   `width: 100vw` + `margin-inline: calc(50% - 50vw)` puts it back on the
   viewport so the percentages resolve exactly as before. */
.shape-field {
  position: sticky;
  top: calc(var(--hdr-h-min) + 16px);
  /* 🔴 `100vw` MINUS THE SCROLLBAR, NOT PLAIN `100vw`. Plain 100vw includes
     the vertical scrollbar and the page's client width does not, so the field
     was ~8px wider than the page could hold and produced a real horizontal
     scrollbar. `--sbw` is set by `initScrollbarWidth()` in direction-d.js and
     falls back to 0, which is correct for overlay-scrollbar browsers.
     ⚠️ The centring expression has to use the SAME corrected width or the
     field goes off-centre by half a scrollbar. */
  width: calc(100vw - var(--sbw, 0px));
  margin-inline: calc(50% - (100vw - var(--sbw, 0px)) / 2);
  height: 100vh;
  margin-bottom: -100vh;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  /* 🔴 ISOLATE THE BLEND GROUP. Without this, `mix-blend-mode: multiply`
     composites against the nearest isolating ancestor — which was the root, so
     the circles were free to tint anything they were layered against,
     including the sticky header. Isolation keeps the multiply doing the one
     job it was added for: the darker wedge where two circles cross. */
  isolation: isolate;
}

.shape--soft {
  mix-blend-mode: multiply;
  aspect-ratio: 1;
}

/* Sizes deliberately span 4.4x, smallest to largest — the page's own range is
   wider than anything a "set of decorative circles" would default to. */
/* 🔴 BOTH DROPPED DOWN THE FIELD — 8/19. Brees: "this area feels really busy
   with the shapes, title and files. Can we maybe move the shapes down a little
   so this section feels a bit more open."

   Measured in the PINNED state, which is the only state that matters here —
   the field is `position: sticky` and everything crowds once it parks:

       field 130-876 · title 154-196 · first card top 248

   The petal circle's top sat at **175 — 22px ABOVE the title's bottom edge**,
   so it was in the title's own band, not merely near it. The lime followed 45px
   behind it. Both masses were stacked into the one strip that already holds the
   pinned headline and the three card tabs.

   petal 6% -> 18% and lime 12% -> 27% put the circles below the title band and
   let the top of the section breathe. New tops land at ~264 and ~331, i.e. 68px
   and 135px BELOW the title's bottom.

   ⚠️ MEASURE IN THE PINNED STATE, NOT IN FLOW. In flow these sit far apart and
   look fine; the crowding only exists once the section parks under the header.
   ⚠️ Bottom clearance was checked too — the field is `overflow: hidden` and a
   circle pushed too far down gets flat-cut, which is the G1 failure. At 27% the
   lime's bottom lands ~140px clear of the field's bottom edge. */
/* 🔴 ANCHORED IN px AND HELD OUT OF THE COPY COLUMN — fixed 8/24.

   These were `top: 27%` / `top: 18%`, a share of a box whose height depends on
   the cards inside it. Removing the renders' chrome bars the same day took
   ~159px out of that box, both circles rose by a proportional amount, and they
   landed ON the first card's body copy — "Prioritized by potential revenue…"
   was partly behind a lime disc at 1280x800 AND at 1440x950.

   THIS IS THE SECOND TIME TODAY THE SAME TRAP HAS FIRED. The closing CTA's
   discs were offset by a percentage of section height and moved when the
   section's height changed; the note there says to multiply the shape's own
   size instead. Percentages of a content-driven box are not a position, they
   are a promise that the content will never change.

   ⚠️ A DECORATIVE SHAPE MUST NOT SIT OVER BODY COPY HERE, and it is not a
   z-index question: the cards carry opaque backgrounds, so pushing the discs
   behind them would not put them behind the TEXT, it would delete them from
   the card entirely. They are held clear of the copy column instead.
   📌 `scripts/dl-shape-clear.js` measures it — run it if either value moves. */
.shape--dl-lime {
  width: clamp(220px, 26vw, 400px);
  background-color: var(--lime-soft);
  top: 380px;
  left: -22%;
}

.shape--dl-petal {
  width: clamp(120px, 14vw, 210px);
  background-color: var(--petal-soft);
  top: 268px;
  left: -8%;
}

/* 🔴 HALF IN VIEW, HALF OFF SCREEN - 8/27, Brees' words. This is the circle
   that lived here until 8/19, went to `.changes` so it could bleed across the
   seam, and came back when that section was deleted.

   ⚠️ IT IS THE SAME CIRCLE THE 8/18 NOTE BELOW SAYS MUST NOT BE CROPPED. That
   note is now historical: he asked for the crop this time. Both requests are
   recorded so neither reads as a mistake later.

   `right: calc(var(--d) / -2)` is exactly half the diameter, and `.shape-field`
   is `overflow: hidden` at exactly `100vw - scrollbar` — so the cut lands on
   the viewport edge to the pixel, and no horizontal scrollbar can appear. A
   negative offset on the SECTION instead would widen the document. */
.shape--dl-sky-half {
  --d: clamp(180px, 20vw, 320px);
  width: var(--d);
  background-color: var(--sky-soft);
  top: 18%;
  right: calc(var(--d) / -2);
}

/* 🔴 THIS ONE IS DELIBERATELY *NOT* EDGE-CROPPED - 8/18.

   Brees: "on the right-hand side is an orange and a blue circle but the blue
   circle is still cut off so we need that to not be cut off."

   It was `bottom: -12%; right: -6%` against the field's `overflow: hidden`,
   which is a HARD STRAIGHT EDGE on two sides at once. Measured before the fix:
   right 96px + bottom 108px clipped at 1600, right 77 + bottom 108 at 1280,
   right 54 + bottom 108 at 900 - the bottom crop was a constant 108px because
   -12% resolves against the field's fixed 100vh height, not the viewport width.

   Two clipped edges meeting at a corner is what made it read as broken rather
   than as the guide's bleed. Now inset so the full curve is inside the field.

   ⚠️ THE OTHER THREE KEEP THEIR CROPS ON PURPOSE. `--dl-lime` still runs off
   the left edge by ~27%, which is the brand guide p11 behaviour ("some cropped
   by the page edge"). Only this one was called out, and only this one moved. */
/* [X] THE SKY AND MARIGOLD CIRCLES LEFT THIS FIELD - 8/19. Brees: "can we move
   the blue and orange circle down so it bleeds into the following section".

   They now live on `.changes` as `.shape--ch-*`. They could not bleed from
   here: this field is `overflow: hidden`, and even with that freed, `.changes`
   is a LATER section with an opaque ground and paints straight over anything
   the deliverable owns. Measured before moving them - the shape cleared the
   boundary by 31px and `elementFromPoint` at that spot still returned the next
   section.

   [!] The cost: the pair no longer travels with the pinned field during the
   card-stack scroll. The lime and petal shapes still do. */

/* The straddling pair, owned by the section BELOW the seam so DOM order lets it
   paint upward across the boundary - the same mechanism `.shape--seam` uses at
   the FAQ -> CTA seam, and for the same reason recorded there: by DOM order,
   never by z-index.

   [!] `.changes` must keep `overflow: visible`. It has no overflow rule today
   and must not be given one, or these are clipped back inside it.
   [!] Held to the right margin on purpose. A positioned shape paints ABOVE
   non-positioned text in the same stacking context, so these must not sit over
   the section's copy - measured, not assumed. */
/* [!] THE SIZE IS BOUNDED BY THE EMPTY BAND AT THE SEAM, NOT BY TASTE.

   A positioned shape paints ABOVE non-positioned text in the same stacking
   context, so these sit in FRONT of both sections' copy - they cannot be
   tucked behind it. The first attempt kept the field's 480px disc and it
   covered the recap card's figures.

   Measured at the junction: **169px of empty band above the seam** (the
   deliverable's last content is `.render__footnote`) and **88px below** (the
   next content is the section heading) - 257px total, across the full width.
   A circle that fits inside that band cannot overlap anything at any x.

   `--d * -0.6` puts 60% of the disc above the seam and 40% below, which lands
   inside the band at every size in the clamp. Re-measure the band before
   growing these; the padding is vw-based and moves with the viewport. */
.shape--ch-sky {
  --d: clamp(150px, 16vw, 230px);
  width: var(--d);
  background-color: var(--sky-soft);
  top: calc(var(--d) * -0.6);
  right: 3%;
}

.shape--ch-marigold {
  --d: clamp(64px, 7vw, 104px);
  width: var(--d);
  background-color: var(--marigold-soft);
  top: calc(var(--d) * -0.45);
  right: 14%;
}

@media (max-width: 899px) {
  /* Below the two-column breakpoint the right margin is gone and these would
     sit on the copy. */
  .shape--ch-sky,
  .shape--ch-marigold { display: none; }
}

@media (max-width: 899px) {
  /* The deliverable field's standing ceiling is three shapes per viewport;
     below 900px the section is one narrow column. */
  .shape--dl-petal { display: none; }
  /* Same three-shape ceiling as its neighbours: below 900 the section is one
     narrow column and a half-cropped circle would sit on the copy. */
  .shape--dl-sky-half { display: none; }
}


/* ==========================================================================
   30. THE CARD STACK AS FILE-FOLDER TABS

   Brees, 8/17, with a screenshot: "when the cards stop, I'd like for them to
   stack like this: a file tab so you can always see the front desk, manager,
   and then the last one at all times. I think though the table on the right
   [...] needs to be pushed down, maybe aligned with the title of the card so
   that way it's not visible in the folder layout."

   TWO NUMBERS, BOTH DERIVED:

   `--stack-who` is the audience line's own height, off its own token
   (--fs-small 15px x 1.65 line-height = 24.75px measured). Everything else
   hangs off it, so if the label's type changes the tab and the render follow.

   `--stack-tab` = card padding-top + that line + 4px of air. That is the
   minimum strip that shows "● The front desk  EVERY MORNING" in full. It was
   12px, which is why the pinned cards read as a deck of edges rather than as
   tabs you can read.

   The render drops by the whole who block (line + its margin) so its top lands
   on the TITLE's top — his suggestion, and it is what keeps the "Vet Call
   Insights ..." chrome row out of the tab strip.

   🔴 THIS MAKES EVERY CARD 41px TALLER, AND THAT IS NOT FREE.
   The render is the taller column in all three cards (651/665/568 against
   257/201/257 of copy), so pushing it down grows the card by exactly the push.
   Combined with a 52px strip instead of 12px, the pinned cards no longer fit
   the viewport budget the old numbers were tuned to. Measured consequences are
   recorded in session-notes.md — this is trap #7 (a pinned card taller than
   the viewport loses its bottom edge forever) and it is REAL here, not
   theoretical. The fix is to shorten the renders or condense the header on
   scroll; both are Brees' calls, so neither is done here.
   -------------------------------------------------------------------------- */

/* 🔴 DECLARED ON `.deliverable`, NOT ON `.stack` — 8/18. `--pin-shorten` is
   computed on `.deliverable__pin`, which is the stack's PARENT, and custom
   properties inherit DOWN. With these on `.stack` the pin could not read the
   tab, and its formula silently fell back — which is what let the cards ride
   over the pinned title. `.stack` inherits them from here unchanged.
   ⚠️ Safe because `d3-document`, the only other page loading this file, has no
   `.stack` and no `.deliverable` at all (checked, not assumed).

   📌 AND THAT SAFETY IS A LIVE CONDITION, NOT A ONE-OFF CHECK — proved 9/8.
   Smart Intake briefly adopted `.stack` for "Where Spotlight shines". It has no
   `.deliverable`, so these three inherited nothing, the `top` calc below
   contained an undefined term, the whole declaration was invalid, `top`
   computed to **auto**, and `position: sticky` did nothing at all — silently,
   with no error and no visual clue while the page sat still. That section was
   reverted, so the claim above is true again. Any FUTURE page adopting `.stack`
   must declare --stack-who / --stack-tab / --stack-n for itself. */
.deliverable {
  --stack-who: calc(var(--fs-small) * 1.65);
  --stack-tab: calc(var(--s-3) + var(--stack-who) + 4px);
  --stack-n: 3;
}

/* --------------------------------------------------------------------------
   🔴 THE CARDS MUST LEAVE TOGETHER, NOT ONE AT A TIME - 8/18.

   Brees: "When I start scrolling down after the last card - they start to
   collapse and they shouldn't." Measured at 1440x690: the tab gaps went
   **53 / 53 -> 19 / 40 -> 0 / 40** on the way out. Cards 1 and 2 ended up
   EXACTLY on top of each other, which is the overlapping header strips in his
   screenshot.

   THE CAUSE IS ARITHMETIC, NOT TASTE. A sticky element is released when the
   containing block's bottom can no longer hold its margin box:

       released when   containerBottom < stickyTop(i) + cardHeight + marginBottom(i)

   The cards deliberately pin at staggered tops - `i * --stack-tab` - so with
   equal heights and no margins those thresholds differ by one tab per card.
   Card 3 is let go first, then 2, then 1, and each one catches up with the one
   above it. The stagger that builds the deck is the same thing that collapses
   it.

   Giving card i a bottom margin of `(n - 1 - i) * tab` makes the sum constant:

       stickyTop(i) + cardH + (n-1-i)*tab
         = base + i*tab + cardH + (n-1-i)*tab
         = base + cardH + (n-1)*tab        <- identical for every card

   so all three hit the limit on the same pixel and travel out as a block with
   their tabs intact.

   ⚠️ THE NEGATIVE TOP MARGIN IS NOT DECORATION - IT PAYS THE FLOW BACK.
   Those bottom margins would otherwise add ~158px of dead space inside the
   stack and slow the whole section's pacing. Each card after the first cancels
   the one above it. Sticky uses `top` for the PINNED position, so shifting the
   static position like this does not move the deck.

   ⚠️ `--stack-n` MUST MATCH THE NUMBER OF CARDS. Add a fourth card and this
   silently mis-tunes - the last card would get a margin it should not have.

   ⏹ `--pin-shorten` IS NOT THE LEVER AND WAS TESTED, NOT ASSUMED: swept at
   0 / 120 / 240 / 360 against its 657px, the worst gap stayed 0 at every value
   and every reduction broke L2b's guarantee that the title clears the cards.
   Its formula syncs the TITLE to the cards and says nothing about card-to-card
   convergence. Do not spend another pass on it.
   -------------------------------------------------------------------------- */
.stack__card {
  margin-bottom: calc((var(--stack-n) - 1 - var(--i)) * var(--stack-tab));
}

.stack__card + .stack__card {
  margin-top: calc(-1 * (var(--stack-n) - var(--i)) * var(--stack-tab));
}

.stack__card {
  /* + --dl-head-h: the pinned headline above owns that band now, and a card
     pinning into it would slide under the type. Published by direction-d.js
     because the headline's height depends on the viewport-driven font size. */
  /* 🔴 --dl-head-gap IS THE AIR UNDER THE PINNED TITLE, AND IT USED TO BE
     ZERO. Card 1 pinned at exactly the headline's bottom edge, so in the
     pinned state the title sat directly on the first tab — Brees, 8/18: "the
     title and the header all look very squished and there should be a lot more
     space in between them", with the mark landing in that gap.

     ⚠️ It is NOT the headline's margin-bottom. That governs the FLOW gap only;
     the pinned position is computed from --dl-head-h, which is a border-box
     height and excludes margin. Setting margin here changes the unpinned view
     and nothing else — which is why the pinned state looked squished while the
     top of the page looked fine. */
  top: calc(var(--dl-head-top) + var(--dl-head-h, 0px) + var(--dl-head-gap) + (var(--i) * var(--stack-tab)));
}

@media (min-width: 960px) {
  /* Only in the two-column layout. Below 960 the render is stacked under the
     copy, so there is nothing in the tab strip to push out of the way. */
  .stack__render {
    margin-top: calc(var(--stack-who) + var(--s-2));
  }
}


/* ==========================================================================
   31. PROOF PINS, PRICING SLIDES OVER IT

   Brees, 8/17: "when we're scrolling from 'Three Calls Almost Lost', I'd like
   that to stay sticky and then I would like One Plan Everything Included to
   scroll up over the previous section."

   🔴 NO z-index IS SET HERE, ON PURPOSE, AND THAT IS THE WHOLE TRICK.
   The obvious build is `.proof { z-index: 0 }` + `.pricing { z-index: 1 }`.
   That breaks the connector: the line sits at z-index 1 (§2 of this file, and
   deliberately not 0 — see the note there), so lifting pricing to 1 would put
   an opaque indigo band on top of it in DOM order and the routing line would
   vanish for that whole section.

   Left alone, both sections are `position: relative` with `z-index: auto` from
   `.section`, so they paint in DOM ORDER — pricing is after proof, therefore
   pricing paints over it. Sticky positioning does not change that. The
   connector, at z-index 1, still paints above both. Everything lands in the
   right order without touching a single z-index.

   ⚠️ `.section` already sets an opaque background, which is what makes the
   slide-over read as a slide-over rather than as two things overlapping. If a
   translucent section ground is ever introduced, this effect breaks.

   ⚠️ The containing block is <main>, so proof stays pinned for the rest of the
   page — sitting unseen behind pricing, FAQ and the final CTA. That is
   harmless while those are opaque, and it is why no unstick logic is needed.
   -------------------------------------------------------------------------- */

/* 🔴 THE SLIDE-OVER STAYS. Brees, 8/18: "I liked the paralax."

   It was briefly removed the same day on a misread of "Can the one price
   section not move parallax like it did?" — that was him asking why it had
   STOPPED, not asking for it to stop. It had stopped because of a
   `@media (max-height: 899px)` unpin added an hour earlier, which was invisible
   on a tall monitor and total on a laptop. **Restored.**

   ⚠️ THE UNPIN WAS FIXING A REAL BUG AND THAT BUG IS BACK WITH THIS RULE.
   A pinned section can only ever show `viewport - top` pixels, and this one
   needs 859. Measured at a 690px viewport: the proof section's dollar figures
   sit at y696-799 and are visible for **0px at every scroll position** — the
   thing he sent a screenshot of. The two complaints are in direct tension and
   the resolution is his to make; see `scripts/sticky-height.js` for the table.
   **Do not silently re-add the max-height unpin — it takes the parallax with
   it on every laptop.** */

.proof {
  position: sticky;
  /* 🔴 THE PIN IS LATE WHEN THE SECTION DOES NOT FIT — 8/18. Brees: "that
     bug needs to be fixed. not having the text show is unacceptable. Why can
     we do both" — and both IS possible; the two requests were never actually
     in conflict.

     THE DEFAULT below clears the sticky header, and is what tall displays get.
     `--proof-pin-top` is set by `initProofPin()` in direction-d.js to
     `min(default, viewportHeight - sectionHeight)`, which is NEGATIVE when the
     section is taller than the viewport.

     WHY THAT FIXES IT. Pinning at +130 immediately parks the section with its
     bottom 129px below the fold, and because the containing block is <main> it
     NEVER releases — so the dollar figures were unreachable at every scroll
     position, not merely crowded. Measured at 690px: amounts at y716-819,
     **0px visible, ever**. A negative top lets the section scroll until its
     BOTTOM reaches the fold and parks it there instead, so the reader has seen
     the whole card by the time it holds — and it still holds, so `.pricing`
     still rides up over it. Measured after: amounts **103/103 visible**, and
     21 scroll steps of slide-over while pinned. Nothing is traded.

     ⚠️ ON TALL VIEWPORTS THE VALUE IS THE DEFAULT AND NOTHING CHANGES.
     `min()` picks the default whenever the section fits.
     ⚠️ DO NOT REPLACE THIS WITH `bottom: 0`. It was measured: the amounts
     become visible but the section then hangs at the viewport bottom and
     `.pricing` never rides over it at all — slide-over steps drop to ZERO.
     ⏹ DO NOT re-add a `@media (max-height:...)` unpin. That was tried the same
     day and it silently removed the parallax on every laptop, which is what he
     noticed. */
  top: var(--proof-pin-top, calc(var(--hdr-h-min) + 16px));
}

@media (prefers-reduced-motion: reduce) {
  /* A section that parks while another slides over it is scroll-coupled
     motion. Reduced motion gets the plain stacked reading. */
  .proof { position: relative; top: auto; }
}

@media (max-width: 959px) {
  /* Below the two-column breakpoint proof is taller than most phone viewports,
     so pinning it would cut its own bottom off and never give it back. */
  .proof { position: relative; top: auto; }
}


/* ==========================================================================
   32. THE LAST CARD NEEDS ROOM TO ARRIVE

   Brees, 8/17, with an arrow drawn from the orange card's top edge up to just
   under the manager's tab: "the orange one should scroll up a bit more before
   you start to leave the section."

   🔴 IT WAS NEVER REACHING ITS PIN AT ALL. Measured across the stack:

       scrollY 3740 -> tops 176, 229, 552
       scrollY 3960 -> tops 176, 196, 332   <- card 2 already UNSTICKING
       scrollY 4180 -> tops -10, -24, 112

   `reached282: false`. Card 3's sticky top is 282 and the best it ever managed
   was 332, because it is `:last-child` with `margin-bottom: 0` — a sticky
   element can only travel while its containing block still has room beneath
   it, and there was none. Card 2 was being dragged off its own pin at the same
   moment, which is the "leaves too early" he is describing.

   So the fix is not a sticky offset, it is SCROLL DISTANCE: give the last card
   room after it so it can rise to its tab position and hold there.

   ⚠️ COST: this adds `--stack-dwell` to the page height, on a page whose
   standing complaint is that it is too long. It is the price of the third tab
   arriving at all. Tune the one variable — do not "fix" this by changing the
   sticky tops, which would break the tab rhythm §30 sets up.
   -------------------------------------------------------------------------- */

.stack {
  --stack-dwell: 300px;
  /* 🔴 THE DWELL IS PADDING ON THE CONTAINER, NOT MARGIN ON THE CARD.

     First attempt put `margin-bottom: var(--stack-dwell)` on
     `.stack__card:last-child`. Every precondition then measured CORRECT —
     position sticky, top 281.5px, margin-bottom 300px, last child, 300px of
     room below it, no overflow/transform/contain anywhere in the ancestor
     chain, no competing rule — and card 3 STILL refused to pin. Measured at
     scrollY 4150: tops 176 / 229 / **142**, i.e. it sailed straight past its
     282 threshold and ended up above the other two.

     The reason: a sticky element is constrained to its containing block's
     PADDING BOX, and it is the element's MARGIN BOX that must stay inside it.
     Putting the 300px on the card's own margin grew the card's margin box and
     the stack's height by the same 300px — slack of exactly ZERO. The card was
     already flush against its limit and could not be offset at all.

     🔴 AND PADDING ON `.stack` DOES NOT WORK EITHER — that was attempt two.
     A block child's containing block is its parent's CONTENT box, and sticky
     is clamped to that. Padding sits outside it, so it bought nothing. Proved
     by measurement: with `padding-bottom: 300px` the cards clamped at doc
     4268, which is exactly stackBottom(5362) − 300 − 794. The padding was
     invisible to the clamp.

     What actually extends the content box is a real in-flow child. `::after`
     is one, and costs no markup. */
}

.stack::after {
  content: "";
  display: block;
  height: var(--stack-dwell);
}

/* 🔴 ALL CARDS SHARE ONE HEIGHT — THIS IS WHAT MASKS THE CARD BELOW.

   Brees, 8/17, with a red X over the green card's tail showing beneath the
   orange one: "the green card should be masked after the orange one goes on
   top of it."

   The cards measure 739 / 753 / 657. Because each pins 53px lower than the
   last, a shorter card on top can never cover a taller one beneath it — card
   2's bottom simply protrudes. Equal heights fix it by construction: with
   every card the same height and each offset DOWN by one tab, a later card's
   bottom always sits below the previous card's bottom, so the one underneath
   is covered completely except for its tab.

   The height is measured at run time (see direction-d.js) rather than written
   down, so it follows the renders instead of drifting away from them. */
.stack__card {
  min-height: var(--stack-card-h, auto);
}

.stack__card:last-child {
  /* Explicitly zero — §25's base rule already does this, but the failed
     margin-based dwell above must not creep back in. */
  margin-bottom: 0;
}

@media (max-width: 959px) {
  /* The stack is a plain column below 960 (§25) — nothing pins, so the dwell
     is dead space and the shared height is dead weight. */
  .stack::after { height: 0; }
  .stack__card { min-height: 0; }
}

/* --------------------------------------------------------------------------
   SECTION 3 — THE ROUTING LINE THAT INTRODUCES THE THREE STEPS. V1 only.

   Brees, 8/20: "the line comes down and then once it gets to the number one
   featured box, that's when the box appears and then the line ... comes out
   from number one and then as it's going towards, number two appears and same
   thing. This should all happen automatically. It's not on a scroll."

   🔴 IT IS DELIBERATELY NOT THE CONNECTOR, AND THAT IS THE WHOLE DESIGN.
   The page's main connector is SCROLL-LINKED: draw() paces it against the
   reader's scroll position and `maxProgress` never undraws. This sequence is
   TIME-based and plays itself. Driving both from one mechanism would have
   meant either making the connector time-aware everywhere or scrubbing this
   with the wheel, and the wheel is exactly what he ruled out. So this is a
   separate, self-contained SVG that starts once and runs.

   ⚠️ V1 ONLY, VIA MARKUP. Both stylesheets and both scripts are shared with
   V2. The opt-in is the `data-howline` element existing at all; V2 has no such
   element, so every rule here is inert there. Do not add a body-class sniff.

   Sits at z-index 1: above the section ground, BELOW `.container` (z-index 2),
   so the line passes behind the copy and behind the white arches - which is
   what makes it read as coming OUT of a card rather than over it.
   -------------------------------------------------------------------------- */
.howline {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
}

.howline svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* 🔴 THE SAME COLOUR AS THE CONNECTOR, AND THAT IS THE WHOLE POINT — 8/21.
   Brees asked for section 2's line to CONNECT to section 3's. Making them
   meet geometrically was only half of it: the connector renders at
   `--line-deep` (#B2A695, measured off the live page) while this drew in
   petal pink, so the join was a colour change at the section seam and the two
   still read as separate lines that happened to touch.

   ⚠️ THE TOKEN, NOT THE HEX. `--line-deep` was added to base.css precisely
   because this value had been living in three places and drifting. The `d`
   attribute in the markup carries #B2A695 as a no-CSS fallback; if the token
   is ever retuned, that fallback is the second place to change. */
.howline [data-howline-path] {
  stroke: var(--line-deep);
}

/* The three steps are revealed BY THE LINE, not by the scroll observer.

   🔴 THEY NO LONGER CARRY `.reveal`, ON PURPOSE. `.reveal` fires from
   shared.js's IntersectionObserver the moment each card enters the viewport,
   which is a different clock from the line's. With both attached the cards
   appeared first and the line arrived at boxes that were already there,
   which is the opposite of what was asked for.

   ⚠️ THE `html[data-js="on"]` GUARD IS LOAD-BEARING, exactly as it is on
   `.reveal`. Without it a JS-off reader gets three permanently invisible
   cards. Hidden state is only ever asserted when something is able to unhide
   it. */
html[data-js="on"] .how__node--seq {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--d-reveal) var(--ease-out),
              transform var(--d-reveal) var(--ease-out);
}

html[data-js="on"] .how__node--seq.is-in {
  opacity: 1;
  transform: none;
}

/* Reduced motion gets the finished state, not a faster animation. The line is
   drawn complete and the cards are simply present - matching how the rest of
   this page treats the preference. */
@media (prefers-reduced-motion: reduce) {
  html[data-js="on"] .how__node--seq {
    opacity: 1;
    transform: none;
    transition: none;
  }
}


/* ==========================================================================
   V3 — SECTIONS 2 + 3, ONE SCROLL-SCRUBBED TRACK

   From Brees' canvas mock `Connecting Sections.dc.html`, 8/21. The line runs
   down the indigo problem section, spurs out to each problem in its own
   colour, crosses the section boundary on a dome, then TRACES each step card:
   up one side, over the arch, down the other. The card outline and the line
   are the same stroke, which is why the arch reads as drawn rather than as a
   border-radius.

   🔴 THE TRACK IS A SIBLING OF THE TWO SECTIONS, NOT A CHILD OF EITHER.
   It has to cross the boundary, and `.section` sets `overflow` in places; a
   child of section 2 would be clipped at the seam, which is precisely where
   the dome lives. It is absolutely positioned inside `main` and sized to span
   both sections from JS.

   ⚠️ z-index 1 IS LOAD-BEARING AND IS NOT ARBITRARY. `.section` carries
   `position: relative` with no z-index, so it is a positioned element at
   auto. A track at auto would paint UNDER section 2's indigo ground - it sits
   earlier in the DOM - and the line would simply vanish over the indigo half
   while looking perfect over the oat half. `.container` is z-index 2 and
   keeps the copy above the line, which is the layering the mock shows.
   ========================================================================== */

.v3track {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  z-index: 1;
  pointer-events: none;
}

.v3track svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

[data-v3-spine="on-indigo"] { stroke: var(--v3-track-on-indigo); }
[data-v3-spine="on-oat"]    { stroke: var(--v3-track-on-oat); }

/* The three spurs carry the same accent as the dot they run to, so the line
   and the marker are visibly one thing. Order matches the markup, which
   matches the section's own dot colours in direction-d-v3.css. */
[data-v3-spur="0"] { stroke: var(--petal); }
[data-v3-spur="1"] { stroke: var(--lime); }
[data-v3-spur="2"] { stroke: var(--marigold); }

/* --------------------------------------------------------------------------
   LINE-DRIVEN REVEALS

   🔴 ONE MECHANISM FOR BOTH SECTIONS. V1 had two clocks here: section 2's
   cards rode the shared `.reveal` observer while section 3's rode
   `.how__node--seq`. With the line now introducing all six, two clocks means
   elements appearing BEFORE the line reaches them - which is the exact bug
   the 8/20 note records for V1's section 3, where `.reveal` and the sequence
   were both attached and the cards won.

   ⚠️ HIDDEN ONLY UNDER `html[data-js="on"]`. With JS off nothing draws the
   line, so nothing would ever add `.is-in` and the whole of sections 2 and 3
   would be blank text-less panels. The guard is what keeps a no-JS reader
   seeing the finished page.
   -------------------------------------------------------------------------- */

html[data-js="on"] .v3-seq {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--d-reveal) var(--ease-out),
              transform var(--d-reveal) var(--ease-out);
}

html[data-js="on"] .v3-seq.is-in {
  opacity: 1;
  transform: none;
}

/* Reduced motion gets the FINISHED state, not a faster animation: the line is
   drawn complete and all six elements are simply present. Matches how the rest
   of this page treats motion, and it is why track-v3.js checks the same query
   before it scrubs anything. */
@media (prefers-reduced-motion: reduce) {
  html[data-js="on"] .v3-seq {
    opacity: 1;
    transform: none;
    transition: none;
  }
}


/* --------------------------------------------------------------------------
   V3 SECTION 2 — THE ROWS LOSE THEIR SEPARATORS

   The canvas mock has no rule under each problem; the coloured spur arriving
   from the spine is what marks the row now. Left in place, the 1px border ran
   the full width of the right column and read as a second, competing
   horizontal at exactly the height the spur arrives - two lines saying the
   same thing, one of them by accident.

   📌 Appended rather than edited in place at line ~3538. V3 owns this sheet so
   either would work, but keeping V3's decisions in one block at the end is
   what lets `css-divergence.js` be read as "here is what V3 changed".
   -------------------------------------------------------------------------- */

.problem .problem__card {
  border-bottom: 0;
}

/* ⚠️ AND THE RULE ABOVE THE FIRST ROW, WHICH IS A DIFFERENT ELEMENT. Removing
   the card borders left one hairline still drawn - a `border-top` on the LIST,
   not on any card. It survived the first pass because it looks identical to
   the ones that were removed and sits where a row border would. Found by
   asking the page for every non-zero border in the section rather than by
   reading the stylesheet for the selector I expected. */
.problem .problem__cards {
  border-top: 0;
}

/* The canvas sets the row dot slightly larger, which it can carry now that it
   is the only mark on the row. */
.problem .problem__dot {
  width: 12px;
  height: 12px;
}


/* ==========================================================================
   V3 — WHAT CHANGES: A WHITE PANEL, HAIRLINE ROWS

   From `What Changes Section.dc.html`. The section becomes one white field on
   oat, and the three shifts become rows separated by a hairline: icon, title,
   body. The overlapping discs V1 already had ARE the canvas's row markers -
   that device did not need rebuilding, only re-placing.
   ========================================================================== */

/* 🔴 NOT A BOX. Brees, 8/21: "shouldn't be a box. It should just be the text
   like we had it and we can have the text reach site width."

   The canvas mock draws this section as a white card floating on grey, and I
   built the card. That was reading the mock too literally: the panel is how a
   CANVAS artboard shows a section in isolation - it is the artboard's own
   frame - not a component the page needs. Building it cost width twice over,
   once to the panel's own padding and again to the section padding outside it,
   so the copy sat narrower than site width for no reason anyone asked for.

   ⚠️ THE WRAPPER STAYS AS A HOOK, DOING NOTHING VISUAL. Removing the div means
   re-editing markup that is otherwise correct; neutralising it here keeps the
   change in one place and leaves an obvious place to hang a future treatment.
   It must NOT reintroduce padding - that is the whole defect. */
.changes__panel {
  background: none;
  border-radius: 0;
  padding: 0;
  box-shadow: none;
}

/* 🔴 V1'S HORIZONTAL RULES ARE KEPT, NOT THE CANVAS'S HAIRLINE. Brees, 8/21:
   "for the What Changes section I want to use the horizontal rules just like we
   have them on version one."

   An earlier pass here replaced V1's 3px bar and its 10px petal dot with the
   canvas mock's 1px hairline, and also hid the `:last-of-type` pair. That
   second part was the worse mistake: those rules MIRROR the device on the
   final block - bar and dot swap sides - and their own note records the
   partial-override trap ("`left: 0` without `right: auto` leaves BOTH live")
   that this project has hit five times. Hiding them threw away a deliberate
   flip, not a leftover.

   ⏹ Nothing is overridden here now. The rules in section 22 of this sheet are
   the whole treatment. Do not re-add a hairline alongside them - two
   separators saying one thing is what the problem rows had before their border
   came off.
*/

@media (min-width: 1024px) {
  /* Icon | title | body. The title column is fixed so the three titles stack
     into a readable left edge instead of each one being as wide as its text. */
  .changes__block {
    grid-template-columns: 76px 288px minmax(0, 1fr);
    column-gap: var(--s-4);
    align-items: start;
  }

  .changes__lead {
    display: contents;
  }

  .changes__body {
    max-width: 62ch;
  }
}


/* ==========================================================================
   V3 — THE PROOF: COPY LEFT, RECOVERED CALLS AS ROWS

   From `Recovered Calls Section.dc.html`. The three figures stop being a row
   of three cards and become three rows on a soft field, each reading
   left-to-right: what happened, then what it was worth. The amount lands in a
   consistent right-hand column, which is what lets the three be compared at a
   glance - as a row of cards they were three separate objects.
   ========================================================================== */

@media (min-width: 1024px) {
  .proof .container {
    display: grid;
    grid-template-columns: minmax(0, 380px) minmax(0, 1fr);
    column-gap: var(--s-6);
    align-items: center;
  }

  /* 🔴 LEFT, NOT CENTRED. V1 centres this section's copy because it sat above
     a centred row of three cards. Beside a left-edged column of rows, centred
     type gives the section two competing left edges and neither reads as the
     start of the line. The mock sets it left; so does this. */
  .proof__col {
    text-align: left;
  }

  .proof__col .proof__sub {
    margin-inline: 0;
  }
}

.proof__field {
  background-color: var(--sky-soft);
  border-radius: var(--s-4);
  padding: clamp(1rem, 2.5vw, 2rem);
}

/* 🔴 ONE COLUMN, NOT THREE. `.proof__cards` is a three-across grid in V1 and
   that rule has to be beaten explicitly - left alone it silently wins inside
   the new field and the rows come back as squashed cards. */
.proof__field .proof__cards {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-2);
}

.proof__field .proof__card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  column-gap: var(--s-3);
  align-items: center;
  text-align: left;
  background-color: var(--white);
  border-radius: var(--s-3);
  padding: var(--s-3) var(--s-4);
  box-shadow: 0 18px 40px -28px rgba(35, 60, 80, 0.45);
}

.proof__field .proof__title {
  margin: 0;
  /* Slightly tighter than the h3 token so the row titles stop wrapping to two
     lines at 1024-1440, which was pushing every row taller than the mock's. */
  font-size: clamp(1.0625rem, 1.4vw, 1.25rem);
  grid-column: 2;
  font-size: var(--fs-h3);
}

.proof__field .proof__info {
  grid-column: 2;
  margin: 0;
  font-size: var(--fs-small);
}

/* The icon spans both text rows so it centres against the pair rather than
   sitting on the title's baseline. */
.proof__field .proof__icon {
  grid-column: 1;
  grid-row: 1 / span 2;
}

/* The amount right-aligned across both rows: the column the eye runs down. */
.proof__field .proof__amount {
  grid-column: 3;
  grid-row: 1 / span 2;
  margin: 0;
  text-align: right;
}

.proof__field .proof__invoiced {
  display: block;
  font-size: var(--fs-small);
}


/* --------------------------------------------------------------------------
   V3 SECTION 3 — THE COLUMN GAP IS PART OF THE LINE, NOT JUST THE LAYOUT

   🔴 The track draws an arch 10px outside each card, so the visible gap
   BETWEEN two arches is the grid gap minus 20. At the inherited 24px that left
   **4px** - the arches almost touched, while the line's entry run beside card 1
   was 26px. Brees saw the mismatch immediately: "triple or four or five times
   what it is inside the inside columns."

   44 here makes the arch-to-arch gap a readable 24, and `track-v3.js` MEASURES
   that gap for its entry run rather than carrying its own constant. Change
   this number and the line follows; do not re-introduce a second one.
   -------------------------------------------------------------------------- */

@media (min-width: 1024px) {
  .how__track {
    column-gap: 44px;
  }
}


/* --------------------------------------------------------------------------
   V3 — THE MEASURING STATE

   🔴 `getBoundingClientRect()` INCLUDES TRANSFORMS, AND `.v3-seq` IS
   TRANSFORMED WHILE HIDDEN. `track-v3.js` builds its path at load, when every
   card still carries `translateY(14px)` from its un-revealed state - so every
   card measured 14px LOWER than where it settles, and the whole path was built
   against positions that no longer existed once the cards arrived.

   Brees saw both halves of that on 8/21 and reported them as separate bugs:
   the verticals ran ~25px past the bottom of the columns, and at the rounded
   top "the line goes behind the column". One cause, two symptoms - the arch was
   drawn 14px low, so it cut into the dome at the top and hung below at the base.

   ⚠️ THE TRANSITION IS KILLED TOO, not just the transform. Without that,
   removing this class animates the hidden cards back down over 750ms and any
   measurement taken in that window is wrong in a new way.
   -------------------------------------------------------------------------- */

html.v3-measuring .v3-seq {
  transform: none !important;
  transition: none !important;
}


/* --------------------------------------------------------------------------
   V3 — THE PROOF ROWS TAKE THE PAGE'S NUMERALS

   Brees, 8/21: use the numbered icons already on the page rather than the
   canvas mock's person / cross / calendar glyphs.

   The artwork is 91px tall at natural size, which is the height section 3
   wants inside a 440px arch. In a proof ROW it has to sit against two lines of
   type, so it is scaled by HEIGHT and left to find its own width - the three
   glyphs are 41, 65 and 60 wide, and forcing a common width would squash the
   1 and stretch nothing else.
   -------------------------------------------------------------------------- */

.proof__icon--num {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 3rem;
}

.proof__num-img {
  display: block;
  width: auto;
  height: clamp(2.25rem, 3.2vw, 3rem);
}


/* ==========================================================================
   V3 — THE PRICING RULES ARE THE LOGO

   Brees, 8/21: keep the animation, but the line should draw the logo's shape.
   Then, on seeing it draw: "when it comes to doing the actual rounded corners
   of the M, it gets very janky."

   🔴 THE ARMS ARE ELEMENTS, THE LOCKUP IS AN SVG, AND THAT SPLIT IS THE FIX.
   Putting the long arms inside the path meant animating `stroke-dashoffset`
   across a box ~8,000 units wide with a blend mode on it - a full repaint and
   re-composite every frame - while the arches, the part being looked at, got
   ~5% of the distance and drew in a blink. Arms now animate with `scaleX`,
   which the compositor can do without repainting; the svg's box is small.

   ⏹ The old CSS corner device (`::before` / `::after` borders) stays
   NEUTRALISED rather than deleted - those rules carry the reduced-motion and
   no-JS contracts documented beside them.
   ========================================================================== */

.pricing__rule--logo::before,
.pricing__rule--logo::after {
  content: none;
}

.pricing__rule--logo {
  /* Every measurement below is a fraction of this, so the device scales as one
     object: the lockup's 290-unit viewBox maps to exactly this height. */
  --rule-h: 88px;
  --lock-x: 16%;
  --lock-w: calc(var(--rule-h) * 240 / 290);
  --stroke: calc(var(--rule-h) * 20 / 290);

  position: relative;
  height: var(--rule-h);
  transform: none;
  overflow: visible;
}

/* 🔴 scaleX(-1) IS KEPT ON THE END RULE, AND STILL FOR THE 8/18 REASON: the
   pair should bracket the section with the closing device MIRRORED, not
   rotated. A 180 would stand the arches on their heads. The mirror also
   reverses the draw direction for free, which is what Brees asked for
   originally - top device one way, bottom device the other. */
.pricing__rule--logo.pricing__rule--end {
  transform: scaleX(-1);
}

.pricing__lockup {
  position: absolute;
  top: 0;
  left: var(--lock-x);
  height: var(--rule-h);
  width: var(--lock-w);
  overflow: visible;
}

/* --------------------------------------------------------------------------
   THE ARMS

   Positioned at the two heights the lockup itself leaves at: the petal arm at
   y=272 of 290, the lime arm at y=190. Those are the logo's own numbers, not
   chosen values - it is why the line enters low and leaves high.
   -------------------------------------------------------------------------- */

.pricing__arm {
  position: absolute;
  height: var(--stroke);
  border-radius: 999px;
  /* scaleX is composited; width/left animation would repaint every frame. */
  transform: scaleX(0);
}

/* 🔴 THE ARMS BLEED PAST THE CONTAINER, AS THEY DID WHEN THEY LIVED IN THE
   PATH. Moving them out of the svg parked them inside `.container` (max-width
   1240), so they stopped at the text column instead of running to the edge of
   the screen - the full-bleed run is the whole character of the device.

   `50% - 50vw` is the idiom this section already used for the old corner
   device's arms, and `.pricing { overflow: hidden }` is what stops it becoming
   page-level horizontal scroll. That rule now carries three devices. */
.pricing__arm--l {
  left: calc(50% - 50vw);
  right: calc(100% - var(--lock-x));
  width: auto;
  top: calc(var(--rule-h) * 272 / 290 - var(--stroke) / 2);
  background-color: var(--petal);
  /* 🔴 GROWS FROM THE SCREEN EDGE INWARD. Brees, 8/24: "the pink line draws
     first and then the green but it should be one line. It should start from
     the left side of the screen and go right."

     This was `100% 50%`, and the comment that sat here argued for it - the arm
     grew OUTWARD from the lockup toward the edge. That is the wrong end: the
     stroke appeared to run away from the mark, finish, and then a separate
     green line appeared on the far side. Two lines, not one, even though the
     timing was already continuous. Direction is what makes a sequence read as
     a single travelling stroke; the order of the segments alone does not. */
  transform-origin: 0 50%;
}

.pricing__arm--r {
  left: calc(var(--lock-x) + var(--lock-w));
  right: calc(50% - 50vw);
  width: auto;
  top: calc(var(--rule-h) * 190 / 290 - var(--stroke) / 2);
  background-color: var(--lime);
  transform-origin: 0 50%;
}

/* --------------------------------------------------------------------------
   THE SEQUENCE

   arm in -> up the leg -> first arch -> second arch -> out the right arm, with
   the dots landing last.

   🔴 THE ARCHES GET MORE TIME THAN THEIR LENGTH DESERVES, DELIBERATELY. Paced
   purely by distance they would flick past - they are a few hundred units
   against arms that span most of the section. The arms are travel; the arches
   are the logo. This is the same lesson `howline.js` records for section 3:
   "the pacing problem was the easing, not the duration", where one ease across
   a whole path spent its speed in the wrong place.

   📌 `pathLength="100"` NORMALISES EVERY SEGMENT, so these numbers are timing
   decisions rather than consequences of how long each curve happens to be.
   -------------------------------------------------------------------------- */

html[data-js="on"] .pricing__rule--logo .pricing__seg {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}

html[data-js="on"] .pricing__rule--logo.is-visible .pricing__arm--l {
  transition: transform 460ms cubic-bezier(0.33, 0, 0.2, 1);
  transform: scaleX(1);
}

html[data-js="on"] .pricing__rule--logo.is-visible .pricing__seg--leg-a {
  transition: stroke-dashoffset 240ms linear 430ms;
  stroke-dashoffset: 0;
}

/* Linear inside each arch: a constant travel speed is what reads as fluid.
   An ease per segment would accelerate and brake four times over. */
html[data-js="on"] .pricing__rule--logo.is-visible .pricing__seg--arch-a {
  transition: stroke-dashoffset 420ms linear 660ms;
  stroke-dashoffset: 0;
}

html[data-js="on"] .pricing__rule--logo.is-visible .pricing__seg--arch-b {
  transition: stroke-dashoffset 420ms linear 1075ms;
  stroke-dashoffset: 0;
}

html[data-js="on"] .pricing__rule--logo.is-visible .pricing__seg--leg-b {
  transition: stroke-dashoffset 160ms linear 1490ms;
  stroke-dashoffset: 0;
}

html[data-js="on"] .pricing__rule--logo.is-visible .pricing__arm--r {
  transition: transform 460ms cubic-bezier(0.33, 0, 0.2, 1) 1640ms;
  transform: scaleX(1);
}

html[data-js="on"] .pricing__rule--logo .pricing__lockup-dot {
  opacity: 0;
}

html[data-js="on"] .pricing__rule--logo.is-visible .pricing__lockup-dot {
  transition: opacity 300ms var(--ease-out) 1500ms;
  opacity: 1;
}

/* ⚠️ `.reveal` FIGHTS THE MIRROR, EXACTLY AS IT DID WITH THE OLD DEVICE. Its
   base state is `translateY(20px)` and `.is-visible` is `transform: none` -
   either would drop `scaleX(-1)` off the end rule and un-mirror it silently.
   Repeated in the reduced-motion block for the same reason. */
html[data-js="on"] .pricing__rule--logo.reveal,
html[data-js="on"] .pricing__rule--logo.reveal.is-visible {
  transform: none;
}

html[data-js="on"] .pricing__rule--logo.pricing__rule--end.reveal,
html[data-js="on"] .pricing__rule--logo.pricing__rule--end.reveal.is-visible {
  transform: scaleX(-1);
}

@media (prefers-reduced-motion: reduce) {
  html[data-js="on"] .pricing__rule--logo .pricing__seg {
    stroke-dashoffset: 0;
    transition: none;
  }
  html[data-js="on"] .pricing__rule--logo .pricing__arm {
    transform: scaleX(1);
    transition: none;
  }
  html[data-js="on"] .pricing__rule--logo .pricing__lockup-dot {
    opacity: 1;
    transition: none;
  }
  html[data-js="on"] .pricing__rule--logo.reveal,
  html[data-js="on"] .pricing__rule--logo.reveal.is-visible {
    transform: none;
  }
  html[data-js="on"] .pricing__rule--logo.pricing__rule--end.reveal,
  html[data-js="on"] .pricing__rule--logo.pricing__rule--end.reveal.is-visible {
    transform: scaleX(-1);
  }
}


/* ==========================================================================
   34. THREE CALLS — THE FULL-WIDTH REBUILD (8/24)

   Lifted out of `_compare/three-calls-v2.html` after Brees signed off on it
   there. Replaces the section that was built from the same canvas comp on
   8/21: the copy column and a rounded field of three rows inside `.container`.

   WHAT CHANGED, AND WHOSE WORDS EACH ONE IS:
     · full-width instead of site-width — "im thinking more fullwidth than site"
     · mirrored, field left / copy right — "can we flip it horizontally?"
     · the cards sit inside the shape    — "the boxes shouldn't be going
       outside of the shape"
     · no eyebrow, no total card         — "remove the box on the right 'one
       month, one practice' and remove the eyebrow"

   🔴 THE SECTION KEEPS ITS `.proof` CLASS AND MUST. `.proof` is
   `position: sticky` with a pin top computed by `initProofPin()`, which finds
   it with `document.querySelector(".proof")` — that is the parallax where
   `.pricing` rides up over this section, and it is a behaviour Brees has
   already complained about twice from both directions. Renaming the section
   would have deleted it silently, with every gate still green.

   🔴 THE INNER CLASSES ARE `.recovered__*`, NOT `.proof__*`, ON PURPOSE. The
   old element rules (`.proof__field`, `.proof__cards`, `.proof__card`, the
   hover rules near section 18, the `--num` icon rules) are still in this file
   and would have styled the new markup into a mixture of the two layouts.
   A separate namespace means nothing old can reach the new section and
   nothing new can disturb the old.
   ⏳ THOSE OLD `.proof__*` RULES ARE NOW DEAD — nothing in the page carries
   those classes any more. They are NOT deleted in this pass: they are spread
   across five blocks, some interleaved with the `.proof` section-level rules
   that are still live (sticky, pin, text-align), and unpicking them is its
   own change with its own verification. Left as tracked debt, not as
   something anyone should assume is in use.

   📌 THE GEOMETRY IS MEASURED, NOT STYLED BY EYE. `scripts/recovered-fit.js`
   walks every card corner against the field's real shape at twelve widths.
   Run it after touching the radius, either padding, or the card copy.
   ========================================================================== */
/* 🔴 THE FIELD IS DEEPER THAN THE GROUND HERE, WHICH IS THE OTHER WAY ROUND
   FROM WHAT IS LIVE. The comp's ground samples #CFE5F1 and its field #BCDAEC
   — the field is the DARKER of the two. The live build has it inverted:
   --sky ground with a lighter --sky-soft field. Neither comp colour is a
   token, so this maps the RELATIONSHIP onto tokens that exist rather than
   inventing two new ones: --sky-soft ground, --sky field. No new colour.
   📌 It also separates this section from the closing CTA, which went --sky
   earlier today — two full --sky bands on one page would have flattened it. */
/* [!] A FULL VIEWPORT BEFORE THE NEXT SECTION SCROLLS - BREES, 9/9:
   "it needs to be 100% before the next section with the individual backgrounds
   starts scrolling." Measured 717px at 1440x900 = 80vh, not the ~70% it reads
   as.

   [!] min-height, NOT PADDING. corner-check lists `.proof.recovered` in
   `noPadOut` - it is asserted to show a bottom-padding delta of ZERO, so that
   "no pad-out by decision" cannot quietly become "pad-out forgotten". Height
   bought with padding fails that assertion.
   [i] Scoped to >=900px: below that the section stacks and a 100vh floor would
   strand the cards in dead space on a phone. */
@media (min-width: 900px) {
  .proof.recovered {
    min-height: 100vh;
    align-content: center;
  }
}

.proof.recovered {
  background-color: var(--sky-soft);
  /* 🔴 THIS SECTION IS SHORTER THAN THE OTHERS ON PURPOSE — 8/24. Brees: "the
     indigo section should not scroll in until the light blue touches the
     bottom of the header."

     It could not, and the reason was height, not an offset. `initProofPin()`
     keeps the CSS default — `--hdr-h-min + 16`, i.e. parked just under the
     header — ONLY while `viewport - sectionHeight >= that default`. Otherwise
     it pins at `viewport - sectionHeight` so the section's own bottom stays
     reachable, which is the 8/18 fix for "the dollar figures are unreachable
     at every scroll position". At 863px tall in a 900px viewport that clamp
     gave 37px, so the light blue slid 93px UNDER the header before it parked,
     and the indigo started arriving over it.

     So the section loses the page's standard `--section-pad`. Nothing about
     the pin changed — the clamp still protects reachability, it just stops
     being the branch that fires at ordinary desktop heights. */
  /* Trimmed with the horizontal tightening, 8/24 — the section was 637px tall
     at 1920 around a 509px pill. Still well inside the pin threshold. */
  padding-block: clamp(2.5rem, 4vw, 3.25rem);
  /* 🔴 BOTH DECLARATIONS NEED THE DOUBLE CLASS, FOR TWO DIFFERENT REASONS.
     `variant-d-v3.css` sets `.proof { background-color: var(--sky) }` and it
     is the LAST sheet in the chain, so a single `.recovered` here would lose
     to it on source order — the same half-override that had the CTA's lime
     disc drawing at one size and positioned for another earlier today.
     And `.proof { text-align: center }` (section 25) still applies because
     this section KEEPS its .proof class; left-aligned is what the two-column
     layout needs. `.proof.recovered` (0,2,0) wins both regardless of order. */
  text-align: left;
}

/* 🔴 FULL-WIDTH, NOT SITE-WIDTH — Brees, 8/24: "im thinking more fullwidth
   than site."

   So this section does NOT use `.container`. Every other section on the page
   does, and that cap (1240px) is what made the cards narrow enough to wrap
   card 1's title onto two lines at 1440. Here the whole composition runs to
   the viewport with one edge inset, measured off his comp: the left column
   starts 129px into a 1897px shot and the cards end 123px from the other
   side — 6.8% and 6.5%, i.e. ONE symmetric inset, which is what `--edge` is.

   ⚠️ This is the ONLY section on the page that breaks the container rhythm
   every other one keeps. That is deliberate and it is Brees' call, made on
   the comp — but it means a future "why doesn't this use .container like
   everything else" is answered here, not by adding one. */
.recovered {
  overflow-x: clip;
  --edge: clamp(1.25rem, 6.75vw, 8rem);

  /* 🔴 THE COMPOSITION IS CAPPED; THE FIELD STILL BLEEDS. 8/24, second pass —
     "still feels stretched" after closing the gap by 68px, which was the right
     diagnosis of the wrong thing. Closing the gap does not help when the whole
     composition is 1664px wide: at 1920 the cards and the copy were simply at
     opposite ends of the screen, and no amount of gap tuning fixes span.

     So the CONTENT gets a measure and the FIELD keeps running off the left
     edge — which is what "more fullwidth than site" asked for. The blue still
     bleeds; the reading does not stretch with the monitor.

     --run is the distance from the viewport's left edge to the content's, and
     it is used twice: once as the field's negative margin (to reach the edge)
     and once as its left padding (to put the cards back on the content edge).
     One expression, so the two cannot drift. */
  --cap: 1560px;
  --run: calc(var(--edge) + max(0px, (100vw - var(--cap)) / 2));
}

.recovered__inner {
  display: grid;
  gap: var(--s-5);
  align-items: center;
  max-width: var(--cap);
  margin-inline: auto;
  padding-inline: var(--edge);
}

/* MIRRORED 8/24 — Brees: "can we flip it horizontally?" Field left, copy
   right, and the pill's cap and bleed swap sides with them.

   🔴 PLACED, NOT REORDERED. The copy stays FIRST in the DOM and is moved to
   column 2 by grid; the field stays second and is pulled to column 1. Moving
   the markup instead would put a field of dollar figures ahead of the heading
   that explains them for anyone reading in source order — a screen reader, or
   this page with its CSS off. Nothing in this section is focusable, so tab
   order is not in play; reading order is, and it stays intact. */
@media (min-width: 900px) {
  .recovered__inner {
    /* ⏹ TIGHTENED 8/24 — Brees: "i want to tighten this section a little. it
       feels stretched." Measured at 1920 before the change: 212px of dead
       space between the cards' right edge and the copy's left, and a copy
       column only 593px wide inside a 1920px section. The comp's own 1:0.6
       came from ITS width; at 1920 that ratio buys air, not composition.

       1 : 0.68 with a 3vw gap instead of 4.5: the middle closes by ~44px and
       the copy column gains ~58, so the two halves read as one composition
       rather than two things at opposite ends of the screen.

       ⚠️ THE PILL'S OWN PADDING IS NOT PART OF THIS. Its vertical padding and
       its cap-side padding are the two terms in the cards-inside-the-shape
       clearance; tightening either is what breaks containment, not what
       tightens the layout. Narrowing the field DOES re-wrap the card text, so
       `scripts/recovered-fit.js` still has to pass after this. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.68fr);
    gap: clamp(1.5rem, 3vw, 3.5rem);
  }

  .recovered__field { grid-column: 1; grid-row: 1; }
  .recovered__col { grid-column: 2; grid-row: 1; }
}

/* -- the ask column ------------------------------------------------------- */

/* ⏹ THE EYEBROW AND THE TOTAL CARD ARE BOTH GONE — Brees, 8/24: "remove the
   box on the right 'one month, one practice' and remove the eyebrow."

   🟢 THAT CLOSES THE OPEN COPY QUESTION BY DELETION. The "$3,281 / One month,
   one practice" card was the last of the three pieces of canvas copy waiting
   on Brees AND Alie — the sum was sound, the framing claimed one practice in
   one month, which the page does not claim. It is not resolved, it is out.
   If it ever comes back, it comes back with Alie on the wording first.

   ⏹ Their CSS is deleted rather than left dangling: `.recovered__eyebrow`,
   `.recovered__dot`, `.recovered__total*` and `.recovered__badge` — including the
   measured note about the badge's olive text failing at 3.81:1 on --lime-soft,
   which no longer applies to anything on this page.

   The column is now the two things it started as: the headline and the line
   under it. */
.recovered__head { margin: 0 0 var(--s-3); }
.recovered__sub { margin: 0; color: var(--ink-muted); }

/* -- the field ------------------------------------------------------------ */

/* 🔴 THE BLEED IS PAINTED, NOT LAID OUT — AND THE FIRST ATTEMPT PROVED WHY.

   `margin-right: calc(50% - 50vw)` was the obvious move and it was wrong
   twice over. `50%` on a grid ITEM resolves against its grid AREA, not the
   container, so the field overshot the viewport by 242px at 1440 — and
   because the field's BOX moved, its children went with it: the cards ran off
   the right edge and every dollar figure was cut in half. The width checks
   still said "hscroll 0" the whole time, because `overflow-x: clip` was
   faithfully hiding the damage. A clean overflow number is not a clean
   layout.

   Painting the extension with a pseudo-element fixed the clipping but left
   the cards where they were, so the pill had a 207px empty blue gutter down
   its right side — the comp has them running almost to the edge. So the box
   DOES bleed.

   ⏹ THE CONTAINER-DERIVED BLEED IS GONE with the container itself (8/24,
   full-width). It used to be `(100vw - --container-max)/2 + --container-pad`;
   now the only thing between the field and the viewport edge is `--edge`, so
   that is the whole bleed, and the field's right PADDING puts it straight
   back — which is what lands the cards on the same inset as the copy on the
   left. One number, used twice, symmetric by construction.

   Still MEASURED at five widths: every card and every dollar figure has to
   end inside the viewport. The first attempt's "hscroll 0" while the amounts
   were cut in half is why that check exists.
   ⚠️ `overflow-x: clip` on the section is REQUIRED, not decorative. */
.recovered__field {
  position: relative;
  /* --sky, NOT --sky-deep. `--sky-deep` is #3893bb — a strong mid blue meant
     for marks that must clear 3:1, not for a quiet field; it would have put
     the white cards on a saturated blue slab. --sky (#c6e2ee) against the
     --sky-soft ground is the comp's own light/dark relationship. */
  background-color: var(--sky);
  /* Cap on the RIGHT, bleed off the LEFT — the mirror of the first build. */
  border-radius: 0 var(--s-4) var(--s-4) 0;
  padding: clamp(1.25rem, 2.4vw, 2.25rem) clamp(1.5rem, 3vw, 3rem)
           clamp(1.25rem, 2.4vw, 2.25rem) var(--edge);
  margin-left: calc(-1 * var(--run));
}

/* 🔴 THE PILL IS CONDITIONAL, BECAUSE THE GEOMETRY SAYS IT HAS TO BE.
   Brees, 8/24: "the boxes shouldn't be going outside of the shape."

   A stadium's cap is NARROWEST at the top and bottom, which is exactly where
   cards 1 and 3 sit — so rectangular cards of equal width poke out of it even
   though the middle one clears easily. Measured overhang before this fix:
   50px at 1920, 83px at 1440, 158px at 1024, 276px at 900.

   The clearance a card needs is  cap - sqrt(cap² - (cap - padV)²)  where the
   cap radius is half the field's height. That grows as the field gets TALLER,
   and the field gets taller at narrow widths as every card's text wraps — at
   900px it wants 287px of clearance inside a field only ~500px wide. No
   padding can satisfy that, so the pill is not a shape this layout can hold
   at every width.

   ⏹ SO: stadium at >= 1280 only, where the numbers work, and the rounded
   rectangle above it everywhere below. Checked by `inside.js`, which walks
   every card corner against the real shape rather than trusting the picture. */
@media (min-width: 1280px) {
  .recovered__field {
    /* 🔴 18rem, NOT 999px — AND THAT IS THE ACTUAL FIX, NOT THE PADDING.
       With 999px the used radius is always HALF THE FIELD'S HEIGHT, so the
       clearance the cards need grew every time their text wrapped one line
       more: 1280 wanted 12px more than any padding I gave it, and every wide
       width was one copy edit away from the same problem. Bounding the radius
       breaks that link — clearance now depends only on the radius and the
       vertical padding, both fixed here, so it cannot drift with content.
       At >= 1500 the field is shorter than 36rem anyway, so the browser
       clamps to height/2 and the end is a true semicircle exactly as in the
       comp. Below that it keeps a short straight run in the middle, which is
       the difference between a shape that holds its cards and one that does
       not. */
    /* ⏹ 15rem, DOWN FROM 18 — 8/24, and this is what actually tightened the
       section. The 184px of dead space between the cards and the copy was not
       the grid gap (56px of it); it was the pill's cap-side padding, and that
       padding is not decoration — it is the clearance the cards need to stay
       inside the arc:  R - sqrt(R^2 - (R - padV)^2).

       At R=288 that demands ~98px. At R=240 it demands ~69, so the padding can
       come in to 5.5rem and the cards get 40px of width back, which also un-
       wraps a line of card text that the first tightening pass had pushed to
       two. Smaller radius, tighter composition, shorter cards — one number.
       ⚠️ Still bounded, and still for the original reason: an unbounded 999px
       radius tracks the field's height, so the clearance would drift every
       time the card copy changed. */
    border-radius: 0 15rem 15rem 0;
    /* 🔴 BOTH PADDINGS ARE FIXED, AND NEITHER SCALES WITH vw. That is the
       whole trick. The clearance a card needs is

           R - sqrt(R² - (R - padV)²)

       — radius and vertical padding only. It does NOT depend on the viewport,
       so scaling the cap padding with vw (the previous `max(8.5rem, 10vw)`)
       spent 192px at 1920 to satisfy a 66px requirement and left a slab of
       empty blue between the cards and the cap. Fixed at 6rem / 8rem, the
       requirement is a constant 73px and the spare is a constant 55px at
       every width in the pill range, and the cards get the 64px back.

       ⚠️ Changing EITHER value changes the clearance. Re-run `inside.js`. */
    /* 4.5rem, not 6: the other 24px a side that brings the section under the
       pin threshold at 800px-tall viewports too. ⚠️ The vertical padding is
       half of the cards-inside-the-pill equation (clearance is
       R - sqrt(R^2 - (R - padV)^2)), so this number cannot move without
       re-running `scripts/recovered-fit.js`. At 4.5rem the requirement is
       ~98px against the 8rem on the cap side: still inside. */
    padding: 4.5rem 5.5rem 4.5rem var(--run);
  }
}

@media (max-width: 899px) {
  /* Stacked, the field is a card again rather than a band running off the
     page: no bleed, no pill, and the left padding comes back to normal. */
  .recovered__field {
    border-radius: var(--s-4);
    margin-left: 0;
    padding-left: clamp(1.5rem, 3vw, 3rem);
  }
}

.recovered__cards { display: grid; gap: var(--s-2); }

/* 🔴 THE CARD'S INTERNAL BUDGET IS MEASURED, NOT EYEBALLED — 8/24. Capping
   the composition narrowed the cards to 655px and card 1's title started
   wrapping. Measured: the title needed 328px and had 309, i.e. it was 19px
   short, and the 655 was spent as 80 padding + 48 icon + 48 in two gaps +
   170 amount + 309 title.

   Padding 40 -> 32, gaps 24 -> 20 and the icon column 3rem -> 2.5rem give the
   title 32px back, which clears 328 with room rather than by a pixel.
   ⚠️ The amount column is 170px because "invoiced over two weeks" sets it. If
   a longer caption ever appears, this budget is what gives — re-measure. */
.recovered__card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  column-gap: 20px;
  align-items: center;
  background-color: var(--white);
  border-radius: var(--s-3);
  padding: var(--s-3) 32px;
  box-shadow: 0 18px 40px -28px rgba(35, 60, 80, 0.45);
}

.recovered__icon {
  grid-column: 1;
  grid-row: 1 / span 2;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 2.5rem;
}

/* Scaled by HEIGHT, width auto: the three glyphs are 41, 65 and 60 wide at
   natural size, so a common width would squash the 1 and stretch nothing. */
.recovered__num {
  display: block;
  width: auto;
  height: clamp(2.25rem, 3.2vw, 3rem);
}

/* 9/10 type rule: h3 takes the BODY face. See base-v3.css .t-h3. */
.recovered__title {
  grid-column: 2;
  margin: 0 0 var(--s-1);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--fs-h3);
  line-height: 1.25;
  letter-spacing: 0;
}

.recovered__info {
  grid-column: 2;
  margin: 0;
  font-size: var(--fs-small);
  color: var(--ink-muted);
}

.recovered__amount {
  grid-column: 3;
  grid-row: 1 / span 2;
  margin: 0;
  text-align: right;
}

.recovered__figure {
  display: block;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.5rem, 2.2vw, 2rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.recovered__invoiced {
  display: block;
  margin-top: 0.2em;
  font-size: var(--fs-small);
  color: var(--ink-muted);
}


/* ==========================================================================
   35. THE STACK RELAXES ON SHORT VIEWPORTS (8/24)

   Brees: "I want to show more of the renders (especially in laptop) [...] only
   in laptop view and whatever else, tablet if appropriate. Only those in
   desktop, large view, I'd like it pinned."

   MEASURED FIRST, because the pinned arrangement was cutting content at EVERY
   size, not just laptops. Worst card, before this block:

       1920x1080  112px cut      1280x800  389px cut
       1440x900   292px cut      1366x768  423px cut
       1536x700   492px cut  (1080p at 125% scaling — a common laptop)

   THE THREE THINGS EATING THE HEIGHT, in order of size:
     1. The section headline is PINNED, so every card clears it: the cards pin
        at 248px, not at the header's 130. That band is charged to every card
        at every viewport, for a line the reader takes in once.
     2. `--stack-card-h` equalises all three cards to the tallest — 838px —
        so the daily card carries 159px of dead space and the monthly 94px.
        Natural heights: daily 679, weekly 838, monthly 744.
     3. The pin is a fixed offset, so whatever does not fit is simply
        unreachable while the card is pinned.

   Above the threshold all three stay exactly as they were: pinned headline,
   equal cards, the accumulating deck. Below it the section becomes an ordinary
   scrolling stack — which is not a new layout, it is the SAME fallback the
   `max-width: 959px` block already uses for narrow screens.

   ⚠️ 860px, AND IT IS NOW MEASURED RATHER THAN GUESSED. The first pass used
   940 on the assumption that a maximised 1080p desktop lands near 950. It does
   not: Brees' own 1920x1080 desktop reports **innerHeight 911** — Chrome's
   chrome plus the Windows taskbar take 169px — so his desktop fell into the
   relaxed branch and the title stopped pinning on the machine the pinning is
   FOR. He spotted it within minutes of the deploy.

   The split now sits between the two real cases:
       1920x1080 desktop, maximised   -> 911  PINNED
       1440x900 laptop  (~169 chrome) -> 731  relaxed
       1366x768 laptop                -> ~600 relaxed
   📌 THE LESSON IS THE CATEGORY, NOT THE NUMBER: a screen resolution is not a
   viewport. Measure `innerHeight` on the actual machine before picking a
   height breakpoint.

   ⏹ THIS IS THE `@media (max-height: ...)` UNPIN THAT SECTION 31 FORBIDS —
   deliberately, and it is a different thing. That prohibition is about the
   PROOF section, where an unpin was added silently and removed the parallax on
   every laptop without anyone deciding to. This one is asked for, scoped to
   the stack, and the parallax it costs is the parallax Brees is trading for
   the renders being readable. Do not read the two notes as contradicting.
   ========================================================================== */

@media (max-height: 859px) {
  /* The headline scrolls with the section instead of holding a band. */
  .deliverable__headline {
    position: relative;
    top: auto;
  }

  /* ⚠️ The `--pin-shorten` compensation goes with it. It exists only to make a
     STICKY title release at the same moment as the sticky cards; left behind
     it is padding under a section that no longer pins, i.e. a gap. */
  .deliverable__pin {
    padding-bottom: 0;
  }

  .deliverable__pin > .stack {
    margin-bottom: 0;
  }

  /* 🔴 THE CARDS STAY STICKY. ONLY THE TITLE LETS GO.

     A first pass here made `.stack__card { position: static }` as well, which
     threw away the deck — Brees, immediately: "the files should have stayed
     sticky. The only thing that wasn't supposed to stay sticky was the section
     title." The point of this block is to stop the headline reserving a band
     at the top of the viewport, so the cards can pin at the header (130px)
     instead of below the title (248px). That is +118px of render on every
     card, with the stack behaving exactly as it does on a large screen. */
  .stack__card {
    /* 🔴 AND THE CARDS HAVE TO STOP RESERVING THE TITLE'S BAND TOO. Unpinning
       the headline in CSS is not enough on its own: the card's own `top` is
       `--dl-head-top + --dl-head-h + --dl-head-gap + i * --stack-tab`, and
       `--dl-head-h` is published to the root by direction-d-v3.js from the
       headline's measured height whether or not it is sticky. Measured after
       unpinning the title but before this line: the pin was still ~259px and
       the worst cut had barely moved, 389 -> 297. The band was gone from the
       screen and still charged to every card.

       Same formula, minus the two head terms. The stagger stays, so the deck
       still reads as a deck. */
    top: calc(var(--dl-head-top) + (var(--i) * var(--stack-tab)));

    /* Each card at its own height rather than all three padded out to the
       tallest. Not part of the pinning — it is the other 159px, and the daily
       card is the one that gets it back. Revert this line alone if the deck's
       uniform tabs matter more than the extra render. */
    min-height: 0;
  }
}

/* ==========================================================================
   THE DRAWER — the phone/tablet menu. 9/16, the site-wide responsive pass.

   B4, owed since 8/17 ("desktop only for now"). Below 900px the bar stopped
   being a bar: at 390 the lockup overran the screen and the nav stacked into
   three wrapped rows plus the CTA — ~230px of header before any content, and
   the document scrolled 33px sideways at 320.

   The measured single-row limit is 900px (nav + CTA fit beside nothing but
   their own gutters down to there), which is also this sheet's own layout
   breakpoint — so the drawer owns everything below it and 900+ is untouched.

   🔴 JS-GATED ON PURPOSE. With JS off the burger is hidden and the nav shows
   inline exactly as before: a wrapped menu is ugly, a drawer nobody can open
   is broken.

   🔴 THE NAV STAYS ONE ELEMENT. The drawer is .hdr__nav itself, fixed to the
   viewport, NOT a duplicated copy — sync-header.py already had to exist once
   because copies drift. The submenus keep their own buttons and their own JS
   (click to open, one at a time, Escape); they simply lay out as accordions.

   [!] SPECIFICITY: every link rule is ≥ (0,2,2) — `.hdr__nav a` is (0,1,1)
   and `.hdr__nav a.hdr__cta` is (0,2,1). See the trap recorded on .hdr__card.
   ========================================================================== */

.hdr__burger { display: none; }

@media (max-width: 899px) {
  html[data-js="on"] .hdr {
    --arch-h: 60px;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    column-gap: 12px;
    padding: 14px 0 16px;
  }

  html[data-js="on"] .hdr.is-scrolled {
    --arch-h: 44px;
    padding: 8px 0 10px;
  }

  /* The lockup and the button sit ABOVE the open drawer, which is a fixed
     layer inside the same header stacking context. */
  html[data-js="on"] .hdr__lockup {
    position: relative;
    z-index: 2;
    min-width: 0;
    max-width: none;
    margin: 0;
    padding-right: 0;
  }

  /* Wordmark: 30/440 is its own aspect, so the half-height that centres it on
     the rule is width × 0.0341. */
  html[data-js="on"] .hdr__wordmark {
    --wm: clamp(128px, 36vw, 184px);
    width: var(--wm);
    margin-left: 12px;
    margin-top: calc(var(--arch-rule-y) - var(--wm) * 0.0682);
  }

  html[data-js="on"] .hdr.is-scrolled .hdr__wordmark { --wm: 124px; width: var(--wm); }

  html[data-js="on"] .hdr__burger {
    position: relative;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 10px;
    height: 44px;
    min-width: 44px;
    margin-right: var(--container-pad);
    margin-top: calc(var(--arch-rule-y) - 22px);
    padding: 0 16px 0 18px;
    border: 1.5px solid var(--ink);
    border-radius: var(--r-pill);
    background-color: var(--white);
    color: var(--ink);
    font-family: var(--font-display);
    font-size: 0.9375rem;
    font-weight: 500;
    line-height: 1;
    cursor: pointer;
    transition: background-color var(--d-micro) var(--ease-out), color var(--d-micro) var(--ease-out);
  }

  html[data-js="on"] .hdr__burger[aria-expanded="true"] {
    background-color: var(--ink);
    color: var(--white);
  }

  .hdr__burger-icon {
    position: relative;
    display: block;
    width: 18px;
    height: 10px;
    flex: 0 0 auto;
  }

  .hdr__burger-icon span {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    border-radius: 2px;
    background-color: currentColor;
    transition: top 200ms var(--ease-out), transform 200ms var(--ease-out);
  }

  .hdr__burger-icon span:first-child { top: 0; }
  .hdr__burger-icon span:last-child { top: 8px; }
  .hdr__burger[aria-expanded="true"] .hdr__burger-icon span:first-child { top: 4px; transform: rotate(45deg); }
  .hdr__burger[aria-expanded="true"] .hdr__burger-icon span:last-child { top: 4px; transform: rotate(-45deg); }

  /* -- the drawer itself -------------------------------------------------- */
  html[data-js="on"] .hdr__nav {
    position: fixed;
    inset: 0;
    z-index: 1;
    max-width: none;
    margin: 0;
    padding: calc(var(--drawer-top, 96px) + 8px) var(--container-pad) 40px;
    overflow-y: auto;
    overscroll-behavior: contain;
    background-color: var(--white);
    visibility: hidden;
    opacity: 0;
    transform: translateY(-6px);
  }

  /* 🔴 THE CLOSING FADE ONLY EXISTS AFTER THE FIRST OPEN. With the transition
     on the base rule, Firefox animated FROM the pre-JS inline nav TO the hidden
     drawer when data-js flipped on — a full-screen menu flashing over every
     page load for 220ms. `.drawer-ready` is added by the first open. */
  html[data-js="on"] .hdr.drawer-ready .hdr__nav {
    transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out), visibility 0s linear 220ms;
  }

  html[data-js="on"] .hdr.drawer-ready.is-open .hdr__nav,
  html[data-js="on"] .hdr.is-open .hdr__nav {
    visibility: visible;
    opacity: 1;
    transform: none;
    transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out), visibility 0s;
  }

  html.nav-open,
  html.nav-open body { overflow: hidden; }

  html[data-js="on"] .hdr__menu {
    display: block;
    max-width: 560px;
    margin: 0 auto;
  }

  html[data-js="on"] .hdr__menu > li { border-bottom: 1px solid var(--ink-12); }
  html[data-js="on"] .hdr__menu > li.hdr__item--cta { border-bottom: 0; margin: 0; padding-top: 32px; }

  html[data-js="on"] .hdr__nav .hdr__menu > li > a:not(.hdr__cta),
  html[data-js="on"] .hdr__nav .hdr__menu > li > .hdr__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* The row reaches 12px past the gutter on each side so the focus ring has
       air around the words, while the words stay on the page's content edge. */
    width: calc(100% + 24px);
    min-height: 64px;
    margin: 0 -12px;
    padding: 0 12px;
    border-radius: 12px;
    font-size: 1.25rem;
    text-align: left;
  }

  html[data-js="on"] .hdr__nav .hdr__toggle .hdr__chev { width: 14px; height: 8px; }

  html[data-js="on"] .hdr__nav .hdr__menu a.hdr__cta {
    display: block;
    width: 100%;
    padding: 17px 24px;
    font-size: 1rem;
    text-align: center;
    white-space: normal;
  }

  /* Submenus become accordions: in flow, no card, no shadow. */
  html[data-js="on"] .hdr__nav .hdr__sub,
  html[data-js="on"] .hdr__nav .hdr__mega {
    position: static;
    left: auto;
    right: auto;
    width: auto;
    min-width: 0;
    max-width: none;
    margin: 0;
    padding: 0 0 18px;
    background-color: transparent;
    border-radius: 0;
    box-shadow: none;
  }

  html[data-js="on"] .hdr__nav .hdr__sub a {
    display: flex;
    align-items: center;
    min-height: 48px;
    padding: 8px 12px;
    border-radius: 10px;
    font-size: 1.0625rem;
    white-space: normal;
  }

  html[data-js="on"] .hdr__nav .hdr__mega a.hdr__card { padding: 10px 12px; }
  html[data-js="on"] .hdr__nav .hdr__mega a.hdr__more { min-height: 48px; }
}

/* The label goes on phones narrow enough that it would crowd the wordmark;
   the button keeps "Menu" as its accessible name either way. */
@media (max-width: 419px) {
  html[data-js="on"] .hdr__nav .hdr__menu a.hdr__cta { padding: 16px 12px; font-size: 0.9375rem; }
  html[data-js="on"] .hdr__burger { width: 44px; padding: 0; justify-content: center; }
  html[data-js="on"] .hdr__burger-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
}

/* Product descriptions are Alie's H1s: two lines each in a 350px drawer turns
   five products into a scroll. Names + icons carry the scan on phones. */
@media (max-width: 599px) {
  html[data-js="on"] .hdr__nav .hdr__card-desc { display: none; }
  html[data-js="on"] .hdr__nav .hdr__mega a.hdr__card { align-items: center; }
}

@media (prefers-reduced-motion: reduce) {
  html[data-js="on"] .hdr__nav,
  html[data-js="on"] .hdr.is-open .hdr__nav,
  .hdr__burger-icon span { transition: none; }
}

/* ==========================================================================
   RESPONSIVE PASS — SHARED PHONE FIXES (9/16)

   Found by scripts/responsive-all.js (every page x Chromium/Firefox/WebKit x
   16 viewports) and by LOOKING at the 390px screenshots of all ten pages.
   Each rule names what it fixes; each is scoped below the width it breaks at
   so nothing at 900+ moves.
   ========================================================================== */

/* Vet Call Insights, "Your phone is leaking...": below 760 the row is
   [14px dot | 1fr], and the explanation — the third child — auto-placed into
   the 14px DOT column. Measured 57px wide, one word per line. */
@media (max-width: 759px) {
  .problem__text { grid-column: 2; }
}

/* The closing CTA's corner discs are sized for desktop (lime floor 180px,
   petal 280px). On a phone that put the lime disc straight through the
   headline on every page. Smaller discs, same crop logic (offsets are still
   multiples of the disc's own diameter). */
@media (max-width: 639px) {
  .final-cta .shape--lime-lg {
    --disc: 128px;
    top: calc(var(--disc) * -0.5);
    right: calc(var(--disc) * -0.42);
  }
  .final-cta .shape--petal-cta {
    --disc: 190px;
    bottom: calc(var(--disc) * -0.7);
    left: calc(var(--disc) * -0.58);
  }
}

/* Vet Call Insights, "Three calls almost lost": the card is
   [icon | story | amount]. On a phone the amount column is sized by
   "invoiced over two weeks" and crushed the story to ~57px — one word per
   line, overlapping the amount. Below 600 the amount drops under the story. */
@media (max-width: 599px) {
  .recovered__card {
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 16px;
    padding: var(--s-3) 24px;
  }
  .recovered__icon { grid-row: 1 / span 3; align-self: start; padding-top: 4px; }
  .recovered__amount {
    grid-column: 2;
    grid-row: 3;
    margin-top: var(--s-2);
    text-align: left;
  }
  .recovered__figure { display: inline; }
  .recovered__invoiced { display: inline; margin: 0 0 0 0.4em; }
}

/* The accumulating card deck (Call Insights' three emails, Smart Intake's two
   visit panels) tunes a margin-bottom / negative margin-top pair so pinned
   cards converge at desktop. Below 960 the cards are static, and the SAME pair
   nets to exactly 0 — measured: every card sat flush against the next on a
   phone. Real gaps where the deck is not a deck. */
@media (max-width: 959px) {
  .stack__card { margin-bottom: 0; }
  .stack__card + .stack__card { margin-top: var(--s-3); }
  /* Static cards are non-positioned, so the section's sticky .shape-field
     (z-index 0) painted its soft discs OVER the front-desk card's text. A
     relative card stays in flow and sits above the field again. */
  /* 🔴 top: auto IS LOAD-BEARING. The sticky `top` (header + 16 + i*12) is
     inert on a static card and becomes a ~120px downward shove on a relative
     one — measured: every card slid into the next and the last card ran 185px
     past its section. Same trap as the Smart Intake headline in this pass. */
  .stack__card { position: relative; top: auto; z-index: 1; }
}

/* Call Insights plan price: "$399/month" plus "· Month-to-month · Cancel
   anytime" wrapped on a phone with the separator starting the new line. The
   terms take their own line there and the leading dot steps out. */
@media (max-width: 599px) {
  .pricing__terms { display: block; margin-top: var(--s-1); }
  .pricing__sep { display: none; }
}

/* 🔴 THE FOOTER OUTRANKS EVERY SECTION, AGAIN. section-corners.css records the
   first time a raised .final-cta put its petal disc over the footer (fixed by
   giving the footer z-index 4). Launching then raised .lp-cta to 6 for its
   corner and the pink disc came back over the footer frieze. 10 is above
   every section ladder on the site (max 6) and well under the header's 40. */
.footer { z-index: 10; }

/* Footer on touch widths: the links were 25px-tall targets 10px apart —
   legal under WCAG 2.5.8's 24px floor, by one pixel, and fiddly for a thumb.
   Each link becomes a 44px row with the same visual rhythm (padding replaces
   the grid gap). The eyebrow goes 11 -> 12px everywhere: it was the only
   running text on the site under 12px. */
.footer__eyebrow { font-size: 0.75rem; }
@media (max-width: 899px) {
  .footer__col { gap: 0; }
  .footer__col .footer__eyebrow { margin-bottom: 4px; }
  .footer__col a { display: block; padding-block: 10px; }
  /* 🔴 RESPONSIVE PASS 9/17 — NOT THE SOCIAL ICONS. The row above also matched `.footer__social a` (same
     specificity, later in the sheet), turning each 44px icon box from centred inline-flex into a block, so the
     glyph sat at the box's LEFT edge. With the row's -12px pull-back that put Facebook 8px from the screen
     edge, 12px outside the column every other footer line starts on — on all 19 pages, at every phone width. */
  /* 🔄 9/21: RE-SCOPED off `.footer__col`. The social row moved into .footer__brand (Brees: "move the
     social icons to left under the paragraph of text"), where a `.footer__col`-scoped guard would have matched
     nothing - and the block rule above would have flattened the icon boxes again the moment the row landed.
     `.footer__social a` ties the guard to the thing it protects, wherever that thing lives; same specificity
     as the rule above, later in the sheet, so it still wins if the row ever moves back. */
  .footer__social a { display: inline-flex; padding-block: 0; }
}

/* ==========================================================================
   RESPONSIVE PASS (9/16) — SECTION RHYTHM ON PHONES AND TABLETS

   `clamp(3rem, 6vw, 5.5rem)` (the 8/19 length cut, above) is right at desktop
   — 88px from ~1470px up — but 6vw falls through the floor early: a 768px
   tablet got 46px, the same as a 390px phone, and the sweep flagged ~45px
   section tops on every page at 768-820. Brees, 9/16: "everything has
   breathing room."

   A linear ramp with a raised floor instead: 390 -> 56px, 768 -> 71px,
   1024 -> 81px, 1100+ -> 88px (the desktop value is unchanged, so the 8/19
   page-length saving stands where it was measured).
   ========================================================================== */
:root {
  --section-pad: clamp(3.5rem, 2.5rem + 4vw, 5.5rem);
}

/* Tablet arches (600-899). The three-step arch cards (Websites "How it
   works", Call Insights "Three things", SEO "three aspects") stack one per row
   below 900 and stretched to the full ~700px column: the arch became a flat
   dome and the body ran 90+ characters a line. Capped and centred, they keep
   the proportion they have on a phone and at desktop. */
@media (min-width: 600px) and (max-width: 899px) {
  .arch { width: 100%; max-width: 32rem; margin-inline: auto; }
}

/* Smallest phones (<375): the full-width pills ("Book Your Free Strategy
   Session", "See the work: Browse our portfolio →") broke onto two lines in a
   280px button. A tighter inset and one type step keep them on one line. */
@media (max-width: 374px) {
  .btn { padding-inline: 18px; font-size: 0.9375rem; }
  .hp-bar .cf { font-size: 1.75rem; }
}
@media (max-width: 374px) {
  .hp-bar .cf { white-space: normal; }
}
