/* ==========================================================================
   seo-content.css — page rules for /products/seo-content/

   SCOPE: this sheet is linked by ONE page. It is loaded after
   direction-d-v3.css and variant-d-v3.css and adds only what those two do not
   already provide.

   🔴 WHY A SEPARATE SHEET RATHER THAN MORE OF direction-d-v3.css.
   That file is 276KB and is a PAGE stylesheet for Call Insights, not a
   component library — roughly 80% of it is keyed to that page's element names.
   Adding a second page's rules to it would make every future edit a
   two-page-blast-radius edit, which is the exact hazard the V1/V2/V3 fork was
   created to avoid. Chrome and primitives are shared; page rules are not.

   ⚠️ WHAT THIS PAGE BORROWS AND MUST NOT REDEFINE:
     .container .section .section--white .section--indigo
     .btn .btn--primary .btn--onDark
     .arch                    (the white dome-topped card)
     .hdr* .footer* .skip-link .visually-hidden
     .collapse .faq*          (the accordion, incl. its JS contract)
     .final-cta* .cta-quote*  (the closing ask and the quote pattern)
     .reveal [data-hero-item] (the two animation systems)
     .hero .hero__grid .hero__copy .hero__headline .hero__sub .hero__discs
     all t-* type primitives

   🔴 CLASS NAMES DELIBERATELY AVOIDED. These are live roots in
   direction-d-v3.css / variant-d-v3.css and reusing the word would inherit
   layout, colour or a JS hook silently:
     .proof     — position:sticky + queried by initProofPin(). This page's
                  case study is .proofcase for exactly that reason.
     .changes   — repainted white by variant-d-v3.css
     .deliverable / .stack — sticky pin system, needs --i and --stack-n
     .how__*    — queried by track-v3.js
     .v3-seq    — ONLY track-v3.js adds .is-in to it, and this page does not
                  load track-v3.js. A .v3-seq element here would be
                  opacity:0 forever. Every reveal on this page is .reveal.
     .section--lime — variant-d-v3.css repaints it oat, so it is a lie here.

   TOKENS ONLY. verify.js fails a stray hex, any gradient, blur, infinite
   animation, and the override keyword outside the reduced-motion block.
   ========================================================================== */


/* ==========================================================================
   1. HERO STAGE

   .hero__stage is NOT reused: it hard-codes min-height:520px to hold the Call
   Insights email deck, which would leave a 520px hole on this page. This is
   the same grid cell with its own height.
   ========================================================================== */

.seo-stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 340px;
}

/* ==========================================================================
   THE HERO ART — a search line and the appointments it produced.

   Replaced the two stacked "surfaces" on 8/27 (see the long note in the
   markup, including why invented UI copy is allowed inside this figure and
   invented PROSE is not).

   🔴 EVERY COLOUR HERE IS ON WHITE, WHICH IS THE EASY GROUND — but the accent
   bars and avatar tints are DECORATIVE and the text on them is not. Measured
   on white: --ink 12.6:1 on every soft tint (petal-soft, lime-soft, sky-soft),
   so the avatar initials clear AA comfortably. The bars themselves carry no
   information — the row's own text does — so they sit under the 1.35
   decorative floor, not 3:1.
   ========================================================================== */

/* 🔴 min-width: 0 ON EVERY LINK IN THIS CHAIN, AND IT IS NOT DEFENSIVE
   BOILERPLATE — IT FIXES A MEASURED DEFECT. The address bar sets
   `white-space: nowrap` so it keeps looking like an address bar, which gives
   it a min-content width of ~402px. Grid and flex items default to
   `min-width: auto`, i.e. they refuse to shrink below their content, so that
   402 propagated up and the whole figure held a 450px floor.

   At a 430px viewport the art measured 448 wide sitting at left 22 — its right
   edge 40px PAST the screen, with the card cut off. The page reported
   `scrollWidth - clientWidth === 0` the whole time, because an ancestor clips:
   the overflow check said clean while content was being sliced off. A width
   guard that only watches the document is blind to a clipped child.

   With min-width: 0 the chain can shrink and the bar's own ellipsis does the
   truncating, which is what it was written for. */
.seo-art {
  position: relative;
  z-index: 1;
  display: grid;
  gap: var(--s-2);
  width: 100%;
  min-width: 0;
  max-width: 28rem;
  margin: 0;
}

/* 🔴 AN ADDRESS BAR. The first build made this bare text and wrote a comment
   defending it ("deliberately OUTSIDE the card and unboxed"). Brees, 8/27:
   it is meant to read as a browser's address bar. See the note in the markup
   for why the supplied artboard shows it unboxed and why that render is
   suspect.

   ⚠️ THIS IS A BOX AROUND COPY, WHICH THE HOUSE RULE FORBIDS — and it is
   allowed for the same reason the appointment rows carry coloured left
   borders one element below: this whole figure is an aria-hidden MOCK-UP of
   software. The pill is drawing a browser chrome, not framing an argument.
   Do not take this pattern out into the page.

   It sits ABOVE the card and FLUSH with its edges, the way browser chrome sits
   above the window it belongs to. It was inset by --s-3 for one pass; that cost
   48px of line and the sentence ellipsised at every width down to 1920 —
   measured, the text needed 321px and had 308. Flush, it has 356.
   ⚠️ The ellipsis stays as the mobile fallback; it just no longer fires on
   desktop, which is where it was reading as a defect rather than a fallback. */
.seo-art__bar {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: var(--s-2);
  padding: 10px var(--s-3);
  background-color: var(--white);
  border: 1px solid var(--ink-12);
  border-radius: var(--r-pill);
  font-size: var(--fs-small);
  line-height: 1.3;
  color: var(--ink-muted);
  /* Softer than the card's own elevation on purpose: the bar is behind and
     above it in the stack, not a second card competing for the same plane. */
  box-shadow: 0 1px 2px rgba(47, 47, 53, 0.06);
}

.seo-art__bar-icon {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--ink-muted);
}

/* ⚠️ Truncates rather than wraps. An address bar that runs to two lines stops
   looking like an address bar, and the string is decorative — the figcaption
   carries the meaning for anyone who cannot see it. */
.seo-art__bar-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.seo-art__card {
  min-width: 0;
  padding: var(--s-3);
  background-color: var(--white);
  border-radius: var(--r-render);
  box-shadow: var(--shadow-card);
}

.seo-art__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  margin-bottom: var(--s-3);
}

.seo-art__label {
  margin: 0;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.seo-art__chip {
  margin: 0;
  padding: 3px 10px;
  background-color: var(--sky-soft);
  border-radius: var(--r-pill);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  color: var(--ink);
}

.seo-art__rows {
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 🔴 THE ACCENT IS A LEFT BORDER, AND THIS IS THE ONE PLACE THE STANDING
   "no coloured left borders" RULE DOES NOT APPLY. That rule is about cards and
   callouts carrying COPY — a decorative stripe standing in for emphasis the
   type should be doing. These rows are a mock-up of a product UI inside an
   aria-hidden figure; the bar is drawing a screenshot, not decorating an
   argument. It is also what the supplied design shows.
   ⚠️ Do not copy this pattern out into the page proper. */
.seo-art__row {
  display: grid;
  min-width: 0;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) 0 var(--s-2) var(--s-2);
  border-left: 3px solid var(--ink-12);
}

.seo-art__row--petal { border-left-color: var(--petal); }
.seo-art__row--lime  { border-left-color: var(--lime-deep); }
.seo-art__row--sky   { border-left-color: var(--sky-deep); }

.seo-art__avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
  background-color: var(--ink-08);
}

.seo-art__row--petal .seo-art__avatar { background-color: var(--petal-soft); }
.seo-art__row--lime  .seo-art__avatar { background-color: var(--lime-soft); }
.seo-art__row--sky   .seo-art__avatar { background-color: var(--sky-soft); }

.seo-art__body { min-width: 0; }

.seo-art__title {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
}

.seo-art__meta {
  display: block;
  margin-top: 1px;
  font-size: var(--fs-eyebrow);
  line-height: 1.35;
  color: var(--ink-muted);
}

.seo-art__when {
  flex: none;
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}


/* 🔁 9/16 (later) — THE AI SEARCH RENDER (see the markup note). The bar, card, head, label, chip and source rows are
   the rules above, reused; these are the only new pieces. All on white: ink 12.6, ink-muted above 5, indigo name 10.3. */
.seo-art__bar-icon--spark { color: var(--indigo); }
.seo-art__bar-text { color: var(--ink); }
.seo-art__label { display: inline-flex; align-items: center; gap: 7px; }
.seo-art__spark { width: 8px; height: 8px; border-radius: 50%; background-color: var(--sky-deep); }
.seo-art__answer {
  margin: 0 0 var(--s-3);
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--ink);
}
.seo-art__name { font-weight: 600; color: var(--indigo); }
.seo-art__sup { margin-left: 2px; font-size: 0.7em; font-weight: 600; color: var(--ink-muted); }
.seo-art__sources {
  margin: 0 0 var(--s-2);
  padding-top: var(--s-2);
  border-top: 1px solid var(--ink-08);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.seo-art__avatar { font-size: var(--fs-eyebrow); width: 26px; height: 26px; }
/* compact: the render was 631px tall and pushed the hero ~300px taller than Call Insights' */
.seo-art__card .seo-art__head { margin-bottom: var(--s-2); }
.seo-art__card .seo-art__rows { gap: 4px; }
.seo-art__card .seo-art__row { padding-top: 6px; padding-bottom: 6px; }
@media (min-width: 900px) { .seo-art .seo-art__card { padding: var(--s-3) var(--s-4); } }
.seo-art__cite {
  padding: 3px 9px;
  border-radius: var(--r-pill);
  background-color: var(--lime-soft);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}

@media (min-width: 900px) {
  /* 🔴 520, NOT 420 — this number IS the hero height match with Call Insights.
     Brees, 8/25: the two heroes must be the same height. Measured at 1440x900
     before the change: .hero 724 (vci) vs 624 (seo) — the 100px he was seeing.
     The hero grid takes the taller of copy and stage, and on BOTH pages the
     stage is the taller one, so the stage's min-height governs the whole hero.
     .hero__stage is 520; matching it here lands both heroes at 724.

     ⚠️ ONLY at >=900, where the hero is two columns. Below that both pages
     stack and the hero art is ~350px intrinsically — a 520 floor there would
     be 170px of empty air above the fold on a phone, and it would match
     nothing: below 1024 Call Insights' own hero is governed by its deck's
     INTRINSIC height (876 at 1024, 935 at 900), not by its min-height, so
     there is no number that matches it there without inventing air. */
  .seo-stage { min-height: 520px; }

  /* The art grows with the box rather than floating in it. The reasoning is
     unchanged from the panels this replaced: Call Insights' deck is 437x466
     inside its 520 stage (54px of slack), so this is sized to sit at the same
     density, not merely to be centred. */
  .seo-art { max-width: 30rem; gap: var(--s-3); }
  .seo-art__card { padding: var(--s-4); }
}


/* ==========================================================================
   2. THE THREE ASPECTS

   Three .arch cards. .arch supplies the white ground, the 999px dome and the
   one permitted elevation (--shadow-card); everything here is layout.

   🔴 THE NUMERALS ARE THE SAME ARTWORK AND THE SAME TREATMENT AS .how__node,
   ON PURPOSE — Brees' call on 8/25, chosen from four rendered options in
   _wip-s2/aspects-dome-options.html (small disc / shorter dome / bigger mark /
   numerals). The two product pages now share the numbered-arch device, which
   is what makes them read as one system.

   ⚠️ THE TRADEOFF WAS PUT TO HIM AND TAKEN KNOWINGLY, so do not "fix" it: the
   numerals imply a SEQUENCE, and these three aspects are parallel facets in
   Alie's copy — classic results, Maps, AI answers — not steps 1-2-3. If the
   copy is ever reworked into an order, this stops being a tradeoff.

   [X] DO NOT REBUILD THE NUMERALS IN CSS. direction-d-v3.css:902 records two
   previous attempts (ink text, then the digit drawn twice and offset to fake
   the two-tone), both rejected. They are images cut from Brees' icon sheet,
   share one vertical crop so their baselines line up, and their colour is
   baked into the artwork — no CSS sets it.
   ========================================================================== */

/* 🔴 THIS SECTION MIRRORS THE CALL INSIGHTS "how" SECTION RULE FOR RULE.
   It was NOT matched before: the intro was left-aligned at 52ch with a 64px
   bottom margin, the sub had no muted colour and the wrong top margin, the
   card titles were left-aligned, the columns were `start` (so short cards did
   not grow) and the column gap was 24 instead of 44. Every value below is the
   one direction-d-v3.css uses — copied, not re-derived, so the two product
   pages cannot drift apart by taste.

   `.how__intro` is 46ch / `--s-4`; `.how__intro--v1` then centres it with
   `width: fit-content; max-width: 100%; margin-inline: auto`, and re-caps the
   sub at 46ch centred. Both are folded together here.

   ⚠️ ONE RULE IS DELIBERATELY NOT COPIED: `.how__intro--v1 h2` takes
   `white-space: nowrap` above 900 so its heading sits on one line. That
   heading is five words. This one is fourteen, and `nowrap` on a box narrower
   than its content does not shrink type — it overflows, and this page has a
   hard no-horizontal-scroll rule. */
.aspects__intro {
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
  margin-bottom: var(--s-4);
  text-align: center;
}

.aspects__sub {
  max-width: 46ch;
  margin-top: var(--s-2);
  margin-inline: auto;
  color: var(--ink-muted);
}

.aspects__track {
  position: relative;
  display: grid;
  gap: var(--s-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 900px) {
  .aspects__track {
    grid-template-columns: repeat(3, 1fr);
    /* ⚠️ STRETCH, NOT START. `start` lets each card take its own height, so
       three cards with different copy lengths end at three different depths
       and the line's baseline has nothing straight to run along. The dome
       stays a true semicircle either way because its radius is driven by
       WIDTH, not height — which is exactly why direction-d-v3.css overrides
       its own earlier `start` to `stretch`. */
    align-items: stretch;
  }
}

@media (min-width: 1024px) {
  /* 44, not 24. The outer gap has to read as clearly wider than the gaps
     inside a card; at 24 Brees called out the mismatch on the Call Insights
     page. The line MEASURES this gap rather than carrying its own constant, so
     changing it moves the line too — do not introduce a second number. */
  .aspects__track { column-gap: 44px; }
}

.aspects__node {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
}

/* IN FLOW, not absolutely positioned — this mirrors .how__num exactly rather
   than approximating it. The arch's own `padding-top: var(--arch-dome)` drops
   the numeral into the dome, and because the dome is a 999px radius the curve
   spans roughly the top half of the card, so an in-flow numeral lands inside
   it at every column width. Positioning it absolutely would pin it to a fixed
   offset the dome no longer follows when the column resizes. */
.aspects__num {
  display: block;
  margin-bottom: var(--s-3);
  line-height: 0;
  text-align: center;
}

/* 56px, the same height .how__num-img uses. The three source files have
   different intrinsic widths (41 / 65 / 60) and one shared vertical crop, so
   sizing by HEIGHT is what keeps their baselines aligned. */
.aspects__num-img {
  display: inline-block;
  height: 56px;
  width: auto;
}

/* Centred, like `.how__step-title`. The numeral and the title are centred in
   the dome because they are short; the body stays left at a readable measure,
   because forty words of centred type is a legibility cost, not a style. */
.aspects__title { margin-bottom: var(--s-2); text-align: center; }

.aspects__node .t-body {
  margin: 0;
  font-size: var(--fs-small);
}


/* ==========================================================================
   2b. THE LINE

   Host is absolutely positioned over the section. z-index 1 puts it above the
   section's ground and below `.container`'s z-index 2, so the copy is always
   over the line.

   The stroke colour is the existing track token — an alpha on a brand token,
   not a new hex. Measured 2.07:1 on oat, over the 1.35 decorative floor.
   ========================================================================== */

.seotrack {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.seotrack svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.seotrack__rail { stroke: var(--v3-track-on-oat); }


/* ==========================================================================
   2c. .seo-seq — THIS PAGE'S REVEAL CLASS

   🔴 DELIBERATELY NOT .v3-seq. That class is styled in direction-d-v3.css
   but only track-v3.js ever adds `.is-in` to it, and track-v3.js is not loaded
   here — an element carrying it on this page would be opacity:0 forever. A
   separate name makes that impossible to get wrong, and follows the same
   ownership rule that gave V3 its own sheets.

   🔴 AND DELIBERATELY NOT .reveal. `.reveal` fires from shared-v3.js's
   IntersectionObserver the moment an element enters the viewport, which is a
   DIFFERENT CLOCK from the line's. With both attached the cards appear first
   and the line arrives at boxes that are already there — the opposite of the
   effect, and a bug already paid for once on the Call Insights deliverable
   section.

   ⚠️ HIDDEN ONLY UNDER html[data-js="on"]. With JS off nothing draws the
   line, so nothing would ever add `.is-in` and the three cards would be blank.
   ========================================================================== */

html[data-js="on"] .seo-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"] .seo-seq.is-in {
  opacity: 1;
  transform: none;
}


/* ==========================================================================
   3. FOUNDATION / STRATEGY — two columns, one rule between them

   🔴 THE .arch PANELS ARE GONE (8/27, new layout from Brees). This section has
   now been three things in three days and the history matters, because two of
   the three were his explicit call:

     was  two full-width stacked rows split by a hairline   ("NO BOXES")
     8/26 two white .arch cards on the indigo                (he asked for
          containers "so it's easier to read and see the comparison")
     8/27 two plain columns split by a vertical rule         (this)

   ⏹ SO THE "no AI-default cards" HOUSE RULE IS BACK IN FORCE HERE, and the
   8/26 note arguing the panels-as-a-system exemption no longer applies. Do not
   restore the cards from that note — it was true when it was written and the
   layout moved on.

   📌 WHAT THE COLUMNS GAINED INSTEAD: an eyebrow each, which is what now
   carries the "these two are different kinds of thing" job the white cards
   were doing. Emphasis from type, which is what the rule wanted all along.
   ========================================================================== */

/* 🔴 LEFT, NOT CENTRED. The 8/26 build centred this to match .aspects__intro.
   The new layout sets it left and lets it wrap over three lines — it is the
   section's opening statement, not a label over a row of cards. */
/* 🔴 rem, NOT ch — AND THE ch VERSION WAS A REAL BUG, NOT A TASTE CALL.
   This read `max-width: 22ch`. `ch` resolves against the font-size of the
   element it is DECLARED ON, and this is a wrapper <div> inheriting 16px body
   type — not the 48px display h2 inside it. 22ch computed to 232px and the
   headline wrapped to EIGHT lines.

   ⚠️ SAME CLASS OF MISTAKE AS THE `100%` INSIDE A CUSTOM PROPERTY on the case
   study: a relative unit read in the wrong context, producing a legal value
   that nothing fails on. If a cap is meant for the CHILD's type, either put it
   on the child or use a unit that does not depend on font-size.

   Measured at 1920, 48px display type: 30rem -> 4 lines, 34rem -> 3,
   42rem -> 3, 46rem -> 2. 38rem sits mid-band so the third line survives a
   font swap or a small copy edit either way. */
.services__intro {
  max-width: 38rem;
  margin-bottom: var(--s-5);
}

/* The turn in the sentence gets the turn in colour. --petal on --indigo
   measures 4.74:1 — clears AA for body text, and this is display size, so it
   clears the 3:1 large-text floor several times over. */
.services__accent { color: var(--petal); }

.services__pair {
  display: grid;
  gap: var(--s-5);
}

@media (min-width: 900px) {
  .services__pair {
    grid-template-columns: 1fr 1fr;
    gap: var(--s-5);
    align-items: start;
  }

  /* 🔴 THE RULE GOES ON THE SECOND COLUMN, NOT IN THE GAP. A separator drawn
     as a third grid track cannot stretch to the taller column's height without
     a fixed height somewhere; a border on the second block is exactly as tall
     as the row and needs nothing measured.
     ⚠️ `align-items: start` means the two columns are their own heights, so
     the rule is as tall as the RIGHT column. That is correct here — the right
     column is the longer one. If the copy ever flips, switch to `stretch`. */
  .services__block--second {
    padding-left: var(--s-5);
    border-left: 1px solid var(--oat-14);
  }
}

/* ⏹ .services__eyebrow AND .services__eyebrow--lime DELETED 8/28 — "No
   eyebrows" is a sitewide rule in the Websites page Build Notes and Brees
   applied it here too. Both elements are gone from the markup, so the rules
   went with them rather than sitting orphaned: dead CSS is how a class name
   comes back later attached to something it was never measured for.
   For the record, since the numbers were hard-won: the base eyebrow was
   --oat-75 (4.78:1 on indigo) and NEVER --oat-60 (3.69:1 at 13px, a fail),
   and the --lime modifier measured 5.44:1 on indigo. */

.services__head {
  max-width: none;
  margin-bottom: var(--s-2);
  color: var(--white);
}

.services__body {
  max-width: 46ch;
  margin: 0;
  color: var(--oat-75);
}


/* ==========================================================================
   4. PRICING — a featured band and three cards

   🔴 THIS REPLACED A FOUR-COLUMN TABLE ON 8/27 (Brees, from a screenshot).
   The table and everything built for it is gone: .costs__scroll, .costs__table,
   the cell labels, the offering-major phone transpose, and the ruled grid added
   the day before.

   ⏹ SO IS ITS GATE. scripts/costs-grid-check.js measured that grid's column
   rules and their absence in the stacked layout. With no table it had no
   subject, and a gate whose target no longer exists is worse than none — it
   would either fail forever or be quietly relaxed until it passed. Deleted,
   not disabled. What it caught (a desktop-only rule leaking into a stacked
   layout) is recorded in this file's history if the pattern returns.

   ⚠️ WHY THE SHAPE CHANGED, IN ONE LINE. Alie's "When you need it" row makes
   Technical SEO "You already have it" — not a decision — while the other three
   are choices. Four equal columns said the opposite.

   📌 A WHOLE SECTION OF PANELS, WHICH IS THE NAMED EXEMPTION to the house
   "copy never goes in a box" rule — the same one the arch pair uses two
   sections up. Three cards as a system, not a box around a paragraph.
   ========================================================================== */

/* 🔴 THE HEAD BLOCK SITS ON THE RIGHT — Brees, 9/2: "position text on the
   right instead of the left."

   Both blocks are measure-capped (24ch and 46ch) inside a full-width
   container, so they were anchored left and left the right half of the band
   empty. `margin-inline-start: auto` pushes each to the container's right
   edge; their RIGHT edges now line up and the ragged edge falls on the left.

   📌 AND THE TEXT IS RIGHT-ALIGNED TOO — Brees, 9/2, circling the ragged right
   edge: "needs to be right aligned." Moving the block right was not enough on
   its own; with the copy still ragged-right the two blocks lined up on their
   BOX edges while the words did not, which is what he was looking at. The
   ragged edge now falls on the left.
   ⚠️ No breakpoint needed: once the container is narrower than the measure,
   the cap stops binding and the auto margin resolves to zero on its own. It
   is only ever asymmetric where there is real space to be asymmetric in.
   ⚠️ .costs carries the top-RIGHT corner. The head clears it — the section's
   top padding is --section-pad + corner, so the copy starts below the arc. */
.costs__head {
  max-width: 24ch;
  margin-bottom: var(--s-2);
  margin-inline-start: auto;
  text-align: right;
}

.costs__lead {
  max-width: 46ch;
  margin: 0 0 var(--s-5);
  margin-inline-start: auto;
  text-align: right;
  color: var(--ink-muted);
}

/* -- the already-yours band --------------------------------------------- */

.costs__featured {
  display: grid;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
  padding: var(--s-4);
  /* 🔴 --sky, NOT --indigo — Brees, 8/28: "change that from the indigo to
     light blue so it matches the CTA part in that section." The match target
     is .final-cta, which is already var(--sky) #c6e2ee.

     ⚠️ THIS INVERTED EVERY INK ON THE BAND. It was built light-on-dark and
     every colour in it was picked FOR indigo; on sky they measure:
       oat body 1.17 · oat-75 desc 1.17 · oat-60 markers · lime pill fill 1.10.
     All four were invisible and all four are re-derived below.

     📌 --ink, NOT --ink-muted, for everything secondary. This project has
     already hit that: variant-d-v3.css records --ink-muted at 4.31:1 on sky,
     under the 4.5 floor — "--ink-muted ONLY works on oat and white."
     Full --ink on sky is 9.83.

     ⚠️ KNOWN TRADE, NOT AN OVERSIGHT: the band sits on a --white section, and
     sky-on-white measures 1.35:1 — exactly the project's shape floor. As
     indigo it was 8.09 and read as the dark floor everything else sits on;
     as sky it is a soft panel. That is the requested change, not a bug, but
     it IS the reason the band no longer anchors the section. */
  background-color: var(--sky);
  border-radius: var(--r-render);
  color: var(--ink);
}

@media (min-width: 900px) {
  .costs__featured {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--s-5);
    padding: var(--s-5);
    align-items: start;
  }
}

/* THE PILL STAYS GREEN ON THE NEW GROUND — Brees, 8/28: "change the pill
   from indigo to the green." It was briefly --indigo when the band went sky.

   ✅ THE TEXT IS FINE: --ink on --lime measures 8.95:1, well over the 4.5
   body floor (the pill is eyebrow-sized, so it gets the body floor, not the
   3:1 large-text one). On the old indigo band this pair was 12.4:1.

   ⚠️ KNOWN, ACCEPTED TRADE — the pill's SHAPE is soft. --lime on --sky is
   1.10:1, under this project's ~1.35 shape floor, so the badge's edge barely
   separates from the ground; it reads closer to green text than to a filled
   pill. That is a legibility-neutral, aesthetic cost and it was the explicit
   request. If the badge should read as a hard shape again, the fix is a
   ground change or an outline — NOT a darker green.

   🔴 DO NOT "FIX" THIS WITH --lime-deep. It looks like the obvious answer
   (2.57:1 shape, better separation) but its TEXT fails both ways on this
   fill: --oat 3.02:1, --ink 3.82:1, both under 4.5. The pre-existing warning
   on this rule said the same thing for the indigo band: lime-deep is the tier
   for lime on WHITE and it goes muddy here. */
.costs__pill {
  display: inline-block;
  margin: 0 0 var(--s-3);
  padding: 5px var(--s-2);
  background-color: var(--lime);
  border-radius: var(--r-pill);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
}

.costs__featured-name { margin: 0 0 var(--s-2); }

.costs__featured-desc {
  max-width: 44ch;
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.55;
  /* 🔴 SOLID --ink (9.83:1 on sky), NOT a muted tier — 8/28, with the ground.
     This read --oat-75, which was 4.78:1 on indigo and is 1.17:1 on sky.
     --ink-muted is NOT the light-ground equivalent here: it measures 4.31:1
     on sky (variant-d-v3.css), under the floor. Hierarchy comes from size
     and weight on this band, not from a lighter ink. */
  color: var(--ink);
}

.costs__featured-list {
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-small);
  line-height: 1.5;
}

@media (min-width: 700px) {
  /* Two columns, as in the screenshot. auto-fit rather than a fixed 2 so a
     sixth item would not orphan. */
  .costs__featured-list { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: var(--s-2) var(--s-4); }
}

.costs__featured-list li {
  position: relative;
  padding-left: 14px;
}

.costs__featured-list li::before {
  content: "\00b7";
  position: absolute;
  left: 0;
  /* was --oat-60 (built for indigo); 1.17:1 on sky. Full ink is 9.83:1. */
  color: var(--ink);
}

/* -- the three cards ----------------------------------------------------- */

.costs__cards {
  display: grid;
  gap: var(--s-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 860px) {
  .costs__cards { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-3); }
}

/* 🔴 A HAIRLINE, NOT A SHADOW. --shadow-card is this page's one permitted
   elevation and it is spent on the arch panels; three more elevated cards in
   the next section would flatten that distinction. The screenshot's cards are
   outlined, not raised.

   ⚠️ flex column + the footer's margin-top:auto is what gives the three cards a
   straight bottom edge when their bullet lists differ in length (3, 4 and 3
   items). Without it the "When:" lines sit at three different heights. */
.costs__card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: var(--s-4);
  background-color: var(--white);
  border: 1px solid var(--ink-10);
  border-radius: var(--r-render);
}

.costs__card-name {
  margin: 0 0 var(--s-4);
  color: var(--ink);
}

.costs__card-price {
  margin: 0 0 var(--s-3);
  line-height: 1.1;
}

.costs__card-amount {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.costs__card-unit {
  font-size: var(--fs-small);
  color: var(--ink-muted);
}

.costs__card-what {
  margin: 0 0 var(--s-3);
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--ink);
}

.costs__card-list {
  margin: 0 0 var(--s-4);
  padding: 0;
  list-style: none;
  font-size: var(--fs-small);
  line-height: 1.5;
}

.costs__card-list li {
  position: relative;
  padding-left: 14px;
  margin-bottom: 6px;
  color: var(--ink);
}

.costs__card-list li:last-child { margin-bottom: 0; }

.costs__card-list li::before {
  content: "\00b7";
  position: absolute;
  left: 0;
  color: var(--ink-muted);
}

/* The footer band. margin-top:auto pins it to the bottom of every card
   whatever the list length; the negative inline margins let its tint run to
   the card's own edges without a second wrapper. */
.costs__card-when {
  margin: auto calc(var(--s-4) * -1) calc(var(--s-4) * -1);
  padding: var(--s-3) var(--s-4);
  background-color: var(--sky-soft);
  border-bottom-left-radius: var(--r-render);
  border-bottom-right-radius: var(--r-render);
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--ink);
}

.costs__card-when-label { font-weight: 600; }

.costs__note {
  margin-top: var(--s-4);
  font-size: var(--fs-small);
}


/* ==========================================================================
   5. CASE STUDY — dark, split, stat ROWS

   🔴 REBUILT 8/26 FROM A MOCKUP BREES SUPPLIED. It was --sky-soft with the
   three figures in a light row. It is now indigo, split into a left rail and
   a right panel, with the stats as full-width ROWS and the quote beneath them.

   ⏹ THE OLD CONTRAST NOTE IS DEAD AND MUST NOT BE PUT BACK. It read:
   "Secondary text stays SOLID --ink here rather than --ink-muted: --ink-muted
   is verified on oat and white only (5.05 / 5.40) and drops to 4.31 on full
   --sky." There is no ink on this section any more — the ground is indigo and
   every value below is a light tier. The equivalent care is spent on those.

   🔴 EVERY COLOUR ON THIS SECTION, MEASURED AGAINST --indigo #4c4e68:
        --lime      #d2db75   5.44:1   AA text
        --sky       #c6e2ee   5.97:1   AA text
        --marigold  #ff8c1a   3.47:1   AA LARGE ONLY  <- see below
        --oat       #f3eee8   7.01:1   AA text
        --white     #ffffff   8.09:1   AA text

   ⚠️ MARIGOLD IS THE ONE TO WATCH. 3.47 clears the 3:1 large-text floor and
   nothing else. It is only ever used on .proofcase__figure, which is display
   size — if that colour is ever moved onto a body-sized element it FAILS AA
   and --marigold-soft (#ffc081, 5.05) is the passing member of the same hue.
   ========================================================================== */

/* .section--indigo supplies the ground and color:--oat.

   🔴 THE SECTION'S OWN BLOCK PADDING IS REMOVED AND HANDED TO THE COLUMNS.
   .section sets padding-block; with the rail full-bleed, that padding would
   sit OUTSIDE the dark panel and the two-tone band would be a floating stripe
   with indigo above and below it. The columns pad themselves instead, so the
   rail's fill runs the entire height of the section. */
.proofcase { padding-block: 0; }

/* 🔴 `clip`, NOT `hidden`. .section--indigo carries border-radius: --r-bleed,
   and without clipping the rail's square corner pokes out past the rounded
   section edge. `overflow: hidden` would ALSO make this element a scroll
   container, which silently kills `position: sticky` on the rail below — the
   whole point of variant 2b. `clip` rounds the corner and leaves sticky
   working. Verified by measuring that the rail still pins, not by assuming. */
.proofcase { overflow: clip; }

/* 🔴 THE SPLIT IS ON THE PAGE'S OWN CONTAINER NOW. TWO EARLIER ATTEMPTS AT
   THIS WERE BOTH WRONG, AND THE SECOND ONE ONLY LOOKED LIKE A TASTE PROBLEM.

   Attempt 1 tried to re-create .container's edge on a full-bleed child:

       --proofcase-gutter: max(var(--container-pad),
         calc((100% - var(--container-max)) / 2 + var(--container-pad)));

   A custom property substitutes its TEXT, and the percentage resolves at the
   USE SITE. In the rail's padding `100%` resolved against the rail's own grid
   area (591px at 1920), not the section — the calc went negative, `max()`
   picked --container-pad, and the inset was 64px instead of the ~400 it
   claimed. Nothing failed: it produced a legal value.

   Attempt 2 replaced it with `clamp(1.5rem, 7vw, 9rem)` = 134px at 1920.
   Brees: "still feels stretched." He was right, and MEASURING SAID WHY —
   this was never about the number:

       .container, every other section   left 340   right 340   width 1240
       .proofcase content                left 134   right 134   width 1652
       quote line                                               width 1131

   The section was 400px wider than the rest of the page and the quote ran to
   ~100 characters. No inset value fixes "does not match the page"; it has to
   BE the container.

   🔴 SO THE CONTENT USES .container's OWN GEOMETRY AND THE RAIL'S FILL BLEEDS
   OUT FROM UNDER IT. --container-max + auto margins + --container-pad, exactly
   like every other section; the dark panel is painted by a pseudo-element that
   runs off the left edge and is cut by the section's `overflow: clip`. That is
   why the clip is load-bearing twice over — see the note on .proofcase. */
.proofcase__split {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-pad);
  display: grid;
}

.proofcase__rail,
.proofcase__panel { padding-block: var(--section-pad); }

.proofcase__rail { padding-inline-end: var(--s-4); }

@media (min-width: 900px) {
  .proofcase__split {
    /* The mockup's rail edge sits at ~31% of the section. 4/9 lands at 30.8%.
       The stat rows need the remaining width for figure + meaning on one line. */
    grid-template-columns: minmax(0, 4fr) minmax(0, 9fr);
    /* 🔴 NO align-items: start HERE. It was set, and it broke BOTH halves of
       variant 2b at once: the rail cell shrank to its content, so the dark
       panel ended 140px short of the section (measured 683 of 823), and the
       sticky inner block had no travel because its containing block was the
       same height as itself. Default stretch is load-bearing — the cell must
       be full height for the fill to reach and for the pin to have room. */
  }

  .proofcase__panel { padding-inline-start: var(--s-5); }
}

/* -- left rail ----------------------------------------------------------- */

/* 🔴 THE DARKER RAIL IS REAL NOW. An earlier pass declined to build it and
   said so in this comment, on the grounds that two indigos a few percent apart
   read as a rendering seam. That was wrong about variant 2b: the rail is a
   full-height panel with a hard vertical edge, not a tint, and at this
   darkness the edge reads as intentional.

   ⚠️ DEFINED LOCALLY, NOT IN base-v3.css. This is ink at 22% over --indigo,
   which resolves to about #46475d. It exists on one section of one page;
   adding a global token would put a colour in the shared palette that the
   brand does not have. Same reasoning as --costs-hover on the pricing table.

   📌 IT DARKENS, SO EVERY CONTRAST FIGURE IN THIS SECTION'S HEADER NOTE IS A
   FLOOR, NOT AN EXACT VALUE — text on the rail measures HIGHER than the same
   text on the panel. The sweep checks both grounds independently. */
/* ⚠️ THE FLEX COLUMN IS ON THE RAIL, NOT THE INNER BLOCK. The mix is pushed to
   the foot with `margin-top: auto`, and an auto margin only consumes FREE
   SPACE — so its parent has to be the full-height cell. On the content-sized
   inner block it collapsed to nothing, which is why the pills sat directly
   under the sub instead of at the bottom of the panel. */
.proofcase__rail {
  --proofcase-rail: rgba(47, 47, 53, 0.22);
  position: relative;
  /* Its own stacking context, so the bleeding pseudo-element can sit at
     z-index -1 without falling behind the SECTION's background and vanishing. */
  isolation: isolate;
  display: flex;
  flex-direction: column;
}

/* 🔴 THE FILL IS A PSEUDO-ELEMENT, NOT A background-color ON THE RAIL. The
   rail is now inside the 1240px container, so its own box stops 340px short of
   the viewport edge at 1920 — a background-color would end there and the
   two-tone band would be a floating stripe instead of running off the side.

   `left: -100vw` is deliberately far more than needed and is CUT by
   `overflow: clip` on .proofcase. Computing the exact bleed distance would
   mean (100vw - container)/2 arithmetic that has already been got wrong twice
   on this section; over-reaching and clipping cannot be off by a pixel.

   ⚠️ inset-block: 0 makes it full height even though the rail is a flex column
   — the fill tracks the CELL, which stretches, so it always reaches the
   section's top and bottom edges. */
.proofcase__rail::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset-block: 0;
  right: 0;
  left: -100vw;
  background-color: var(--proofcase-rail);
}

/* ⏹ THE RAIL IS NOT STICKY, AND MUST NOT BE MADE STICKY AGAIN WITHOUT ASKING.
   Variant 2b in the canvas is named "Sticky rail — practice pinned left", so
   it was built that way first. Brees, 8/26: "I don’t think the title needs to
   be sticky here."

   📌 THE MEASUREMENT AGREED WITH HIM. At 1920x911 the section is 823px — it
   fits entirely on screen — so the pin had only ~140px of travel and did
   almost nothing. At the end of that travel the headline slid up under the
   fixed header: standard sticky behaviour, but it read as a defect.

   ⚠️ AN INNER WRAPPER WENT WITH IT. Sticky needed `align-self: start`, which
   shrank the grid cell and took the dark fill with it (measured: the panel
   ended 140px short of the section bottom), so the pin had to live on a child
   element. With no pin, that child had no reason to exist. If sticky ever
   comes back, the wrapper comes back too — one cell cannot both stretch and
   pin.

   ⚠️ .proofcase__rail KEEPS `display: flex; flex-direction: column`. That is
   what lets .proofcase__mix push itself to the foot with margin-top:auto — it
   is not a leftover of the sticky build. */


.proofcase__intro { max-width: 30ch; }

.proofcase__sub {
  margin-top: var(--s-3);
  margin-bottom: 0;
  color: var(--oat-75);
}

/* -- the mix ------------------------------------------------------------- */

/* Pushed to the foot of the rail, as in the mockup: the mix is a credit line
   for the work, not a second heading under the intro. auto margin rather than
   a fixed gap so it sits at the bottom whatever the headline wraps to. */
.proofcase__mix { margin-top: auto; padding-top: var(--s-5); }

.proofcase__mix-label {
  margin: 0 0 var(--s-2);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* 🔴 --oat-75, NOT --oat-60. Measured: oat-60 on indigo is 3.69:1 at this
     element's 13px, and 13px is body size, so the floor is 4.5 not 3. The
     contrast sweep caught it — it was picked to sit quieter than the chips
     and "quieter" ran straight past AA. oat-75 measures 4.78 — which clears 4.5 but not by much, so do NOT
     lighten this element’s ground or shrink its type without re-measuring. */
  color: var(--oat-75);
}

.proofcase__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ⚠️ PILLS HERE ARE THE MOCKUP'S CALL AND THEY ARE TAGS, NOT COPY. The house
   rule is that COPY never goes in a bordered box; these are four labels naming
   services, which is the "set of chips as a system" case — the same exemption
   the arch panels use two sections up. Outlined, never filled: a filled pill
   would read as a button and two of these are not links. */
.proofcase__chip {
  display: inline-block;
  padding: 6px var(--s-2);
  border: 1px solid var(--oat-14);
  border-radius: var(--r-pill);
  font-size: var(--fs-small);
  line-height: 1.3;
  color: var(--oat);
  text-decoration: none;
}

.proofcase__chip--link:hover,
.proofcase__chip--link:focus-visible {
  border-color: var(--oat);
}

/* 🔴 RESPONSIVE PASS 9/17 — THE TWO LINKED CHIPS WERE 34px THUMB TARGETS.
   "Google Ads" and "Homepage Copywriting" measured 114x34 / 199x34 at every phone width. Growing the pill would
   make the two links taller than the two plain tags beside them, which would stop the four reading as one set —
   so the PILL stays 34 and only the hit area grows: an invisible 5px above and below makes the target 44.
   ⚠️ responsive-all.js measures the element box, not this pseudo-element, so it keeps reporting these at 34.
   That is a known false report; the measured target is the ::after. Touch only — a mouse needs none of it. */
@media (pointer: coarse) {
  .proofcase__chip--link { position: relative; }
  .proofcase__chip--link::after { content: ""; position: absolute; inset: -5px 0; }
}

/* -- stat rows ----------------------------------------------------------- */

/* ⏹ NO RULE ABOVE THE FIRST ROW (Brees, 8/26). It read `border-top` here,
   which put a hairline above +54% with nothing above it to divide — a rule
   that separates a row from the section's own padding is drawing a box, not a
   separator. The row rules below still divide each stat from the next, and the
   last one carries into the quote. */
.proofcase__stats {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 🔴 ROWS, NOT COLUMNS — the mockup's structure and the better one. As three
   columns the labels wrapped to two and three lines at different points and
   the row had no common baseline. As rows each figure gets a fixed column and
   its meaning sits beside it on one line at every width above 600. */
.proofcase__stat {
  display: grid;
  gap: var(--s-1) var(--s-4);
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--oat-14);
}

@media (min-width: 600px) {
  .proofcase__stat {
    /* Fixed figure column so the three labels start at the SAME x. With
       auto the "+54%" and "#1" columns differ by ~90px and the labels stagger,
       which is what made the old row read as three unrelated things. */
    grid-template-columns: 11rem minmax(0, 1fr);
    align-items: center;
  }
}

.proofcase__figure {
  font-family: var(--font-display);
  /* Bigger than the old --fs-h2: these are the payload of the section and the
     mockup sets them as the largest thing on the panel. */
  font-size: clamp(2.5rem, 4.5vw, 3.5rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.02em;
}

.proofcase__figure--lime     { color: var(--lime); }
.proofcase__figure--marigold { color: var(--marigold); }
.proofcase__figure--sky      { color: var(--sky); }

.proofcase__label {
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--oat-75);
}

/* -- the quote ----------------------------------------------------------- */

/* .cta-quote is built for a light ground: --line-ink rules and ink text. On
   indigo every one of those has to be restated, and they are restated HERE
   rather than in the shared sheet because Call Insights' quote is still on a
   light section and must not move. */
.proofcase__quote {
  margin-top: 0;
  padding: var(--s-4) 0 0;
  border-top: 0;
}

.proofcase__quote .cta-quote__text p { color: var(--oat); }

.proofcase__quote .cta-quote__by {
  margin-top: var(--s-3);
  padding: var(--s-3) 0 0;
  border-top: 1px solid var(--oat-14);
  border-left: 0;
}

@media (min-width: 720px) {
  /* The shared rule puts the attribution in a 12rem right column behind a left
     rule. In this panel the quote is already narrow, so it stacks — and the
     left rule is explicitly cleared, both because there is no column beside it
     and because of the standing no-left-borders rule. */
  .proofcase__quote { display: block; }
  .proofcase__quote .cta-quote__by {
    margin-top: var(--s-3);
    padding: var(--s-3) 0 0;
    border-top: 1px solid var(--oat-14);
    border-left: 0;
  }
}

.proofcase__quote .cta-quote__name { color: var(--white); }

/* The attribution is lockup + text, side by side, as in variant 2b. */
.proofcase__quote .cta-quote__by {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}

/* 🔴 A WHITE PLATE, BECAUSE THE MARK IS DARK. See the note in the markup —
   the lockup measures 1.10:1 on this section's indigo. This is the one filled
   box on the section and it is not copy in a box: it is the ground a
   third-party mark needs in order to be legible at all.

   ⚠️ The padding is generous on purpose. A logo cropped tight to a plate reads
   as a favicon; the clear space is part of the lockup. */
.proofcase__lockup {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-2) var(--s-3);
  background-color: var(--white);
  border-radius: var(--r-render);
}

.proofcase__lockup img {
  display: block;
  width: auto;
  /* Height-driven, not width-driven: the mark is a fixed-aspect wordmark and
     capping its HEIGHT is what keeps it optically the same size as the
     attribution beside it. */
  height: 34px;
}

.proofcase__by-text { display: block; }

.proofcase__role {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-small);
  color: var(--oat-75);
}


/* ==========================================================================
   4b. THE PRICING TABLE ON A PHONE

   🔴 THE PROBLEM THIS SOLVES. At 430px the table is 960px inside a 387px box.
   It scrolled correctly and the page never overflowed — but the only thing
   visible was "Technical SEO", and the other THREE priced offerings sat off
   screen with nothing to suggest they existed. A pricing table that hides
   three quarters of the prices is worse than no table.

   ⚠️ WHY `order` AND NOT A SECOND BLOCK OF MARKUP. The DOM is attribute-major
   (a row per attribute) and the phone layout is offering-major (a block per
   offering) — a transpose, which CSS cannot do to a table. The usual fix is to
   ship the copy twice, once as a table and once as a stack, and hide one. That
   is two places to update every price, and this project has been bitten by
   drift more than once. So the SAME cells are re-sequenced with `order`:
   offering c takes orders c*10 (its name) then c*10+1..4 (its four values).

   ⚠️ CHANGING A TABLE'S `display` STRIPS ITS TABLE SEMANTICS in every browser.
   That is accepted here rather than worked around, because below this
   breakpoint it genuinely is not a table any more — it is four labelled
   blocks. What it must NOT lose is the labelling, which is why every cell
   carries a real `.costs__cell-label` span in the markup. It is a span, not a
   `::before` with `content: attr(data-label)`, because generated content is
   not reliably announced and these labels are the only thing telling a phone
   user whether "$299" is a price or a page count.

   📌 The labels are STAMPED FROM each row's own `<th scope="row">` by script,
   never hand-typed, so the phone label and the desktop row header cannot say
   different things.
   ========================================================================== */

/* On desktop the row header already names the value, so the in-cell label is
   for assistive tech and the phone layout only. Clipped, not display:none —
   display:none would take it from screen readers too. */
.costs__cell-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (max-width: 759px) {
  /* Nothing scrolls any more, so the horizontal scroller stands down. */
  .costs__scroll { overflow-x: visible; }


  .costs__table {
    display: grid;
    grid-template-columns: 1fr;
    min-width: 0;
  }

  .costs__table thead,
  .costs__table tbody,
  .costs__table tr { display: contents; }

  /* The corner cell and the attribute row-headers carried the labelling on
     desktop; in this layout each cell carries its own. */
  .costs__corner,
  .costs__table tbody th[scope="row"] { display: none; }

  /* Each offering's name becomes the heading of its block. */
  .costs__table thead th {
    padding: var(--s-4) 0 var(--s-2);
    border-bottom: 1px solid var(--line-ink);
  }

  .costs__table td {
    padding: var(--s-2) 0;
    border-bottom: 1px solid var(--ink-10);
  }

  .costs__table tbody tr:last-child td { border-bottom: 0; }

  .costs__cell-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0 0 4px;
    overflow: visible;
    clip-path: none;
    display: block;
    font-size: var(--fs-eyebrow);
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-muted);
  }

  /* offering c: name at c*10, then its four values at c*10 + 1..4.
     Written out rather than generated, so the sequence is readable in one
     glance and a mis-ordered cell is a visible diff. */
  .costs__table thead th:nth-child(2) { order: 10; }
  .costs__table thead th:nth-child(3) { order: 20; }
  .costs__table thead th:nth-child(4) { order: 30; }
  .costs__table thead th:nth-child(5) { order: 40; }

  .costs__table tbody tr:nth-child(1) td:nth-child(2) { order: 11; }
  .costs__table tbody tr:nth-child(2) td:nth-child(2) { order: 12; }
  .costs__table tbody tr:nth-child(3) td:nth-child(2) { order: 13; }
  .costs__table tbody tr:nth-child(4) td:nth-child(2) { order: 14; }

  .costs__table tbody tr:nth-child(1) td:nth-child(3) { order: 21; }
  .costs__table tbody tr:nth-child(2) td:nth-child(3) { order: 22; }
  .costs__table tbody tr:nth-child(3) td:nth-child(3) { order: 23; }
  .costs__table tbody tr:nth-child(4) td:nth-child(3) { order: 24; }

  .costs__table tbody tr:nth-child(1) td:nth-child(4) { order: 31; }
  .costs__table tbody tr:nth-child(2) td:nth-child(4) { order: 32; }
  .costs__table tbody tr:nth-child(3) td:nth-child(4) { order: 33; }
  .costs__table tbody tr:nth-child(4) td:nth-child(4) { order: 34; }

  .costs__table tbody tr:nth-child(1) td:nth-child(5) { order: 41; }
  .costs__table tbody tr:nth-child(2) td:nth-child(5) { order: 42; }
  .costs__table tbody tr:nth-child(3) td:nth-child(5) { order: 43; }
  .costs__table tbody tr:nth-child(4) td:nth-child(5) { order: 44; }
}


/* ==========================================================================
   6. CLOSING CTA

   .final-cta and .final-cta__headline are inherited whole. This page adds a
   supporting line the Call Insights version does not have.
   ========================================================================== */

/* ⏹ .final-cta__sub MOVED TO direction-d-v3.css, 9/2. It was written here as a
   page-local rule; the Websites page then copied the markup without this sheet
   and shipped the button flush against the copy. The block is shared, so the
   rule is shared. Do not re-add a page-local copy. */


/* ==========================================================================
   7. REDUCED MOTION

   Everything that moves on this page moves via .reveal or [data-hero-item],
   and both are already handled in base-v3.css — reduced motion gets the
   FINISHED state, never a faster animation.

   This block exists for the one thing this sheet adds on its own: the
   horizontal scroll on the pricing table. Nothing here animates, so there is
   no transition to null out; smooth scrolling is the only motion and it is
   turned off.

   ⚠️ verify.js splits each stylesheet at the first
   `@media (prefers-reduced-motion` and rejects that keyword BEFORE it. Keep
   this block last.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .costs__scroll { scroll-behavior: auto; }

  /* Reduced motion gets the FINISHED state, never a faster animation. An
     element that starts at opacity:0 would otherwise simply stay invisible —
     a duration override alone is not sufficient. track-seo.js checks the same
     query and draws the whole line at once. */
  html[data-js="on"] .seo-seq {
    opacity: 1;
    transform: none;
    transition: none;
  }
}


/* ==========================================================================
   8. THE MEASURING PROTOCOL — MUST STAY BELOW THE REDUCED-MOTION BLOCK

   🔴 WHY THIS EXISTS. `.seo-seq` sits at `translateY(14px)` until the line
   reveals it, and `getBoundingClientRect()` REPORTS TRANSFORMED GEOMETRY — so
   a path measured at load is built 14px below the settled layout. On Call
   Insights that single error produced two separate visual bugs before it was
   found. track-seo.js adds this class for the duration of every measure.

   ⚠️ THE TRANSITION IS KILLED TOO, not just the transform. Without that,
   removing the class animates the hidden cards back down over 750ms, and any
   measurement taken inside that window is wrong in a new way.

   ⚠️ verify.js splits each stylesheet at the first
   `@media (prefers-reduced-motion` and rejects the override keyword BEFORE it.
   This block is legal only because it sits after. Keep it last.
   ========================================================================== */

html.seo-measuring .seo-seq {
  transform: none !important;
  transition: none !important;
}

/* RESPONSIVE PASS (9/16) — below 900.
   1. "What SEO costs…" keeps its right-aligned desktop composition only where
      there is a left column for it to answer. Stacked on a phone the ragged
      left edge read as a mistake; it aligns with every other heading.
   2. The Sonoran Sky case: rail and panel each carry a full --section-pad top
      AND bottom, so stacked they left two paddings (~120-170px) of indigo
      between the chips and "+54%". The panel keeps its bottom pad and takes a
      normal content gap on top. */
@media (max-width: 899px) {
  .costs__head,
  .costs__lead { margin-inline-start: 0; text-align: left; }
  .proofcase__panel { padding-top: var(--s-4); }
}

/* ==========================================================================
   9/16 (later) — "WHAT SEO COSTS" AS FOUR PRICING COLUMNS (concept 10A, on white)
   Brees picked 10A from the Claude Design canvas, "on a white bg not light blue". Presentation only — the markup
   and every word are unchanged:
     - the heading and lead centre above the row (this supersedes the right-aligned desktop head and the left-aligned
       phone rule above)
     - from 1100px, Technical SEO becomes the first of FOUR columns: an indigo card with its lime "Already yours"
       pill; the three priced services are the other three
     - checks instead of middots on every list; the "When:" line closes each card under a hairline
   Contrast: white on indigo 8.09 · oat on indigo 7.01 · ink on lime 8.95 · lime on indigo 5.44 (checks, graphic)
   · lime-deep on white 3.48 (checks, graphic 3:1) · ink on white 13.3 · ink-muted on white above 5.
   ========================================================================== */
.costs__head,
.costs__lead { margin-inline: auto; text-align: center; }
.costs__head { max-width: 22ch; }
.costs__lead { margin-bottom: var(--s-5); }

.costs__featured {
  background-color: var(--indigo);
  color: var(--white);
  border-radius: 28px;
}
.costs__featured-name { color: var(--white); }
.costs__featured-desc { color: var(--oat); }
.costs__featured-list { color: var(--white); }
.costs__featured-list li,
.costs__card-list li { padding-left: 26px; }
.costs__featured-list li::before,
.costs__card-list li::before {
  content: "\2713";
  top: 0;
  font-weight: 700;
}
.costs__featured-list li::before { color: var(--lime); }
.costs__card-list li::before { color: var(--lime-deep); }

.costs__card {
  border: 0;
  border-radius: 28px;
  box-shadow: inset 0 0 0 1.5px var(--ink-12);
}
.costs__card-amount { font-size: clamp(2.25rem, 3.2vw, 2.75rem); }
.costs__card-when {
  margin: auto 0 0;
  padding: var(--s-3) 0 0;
  border-top: 1px solid var(--ink-12);
  border-radius: 0;
  background-color: transparent;
}

@media (min-width: 1100px) {
  .costs > .container {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 3fr);
    column-gap: var(--s-3);
    align-items: stretch;
  }
  .costs__head,
  .costs__lead { grid-column: 1 / -1; }
  .costs__featured {
    grid-column: 1;
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
    gap: var(--s-3);
    margin: 0;
    padding: var(--s-4);
  }
  .costs__featured-list { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .costs__cards { grid-column: 2; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-3); }
  .costs__featured-name { font-family: var(--font-body); font-size: var(--fs-h3); font-weight: 500; }
}
