/* ============================================================================
   18-cta-expand.css — the expanding card, EXPAND half.
   Shared with sections/19-cta-contract.css via the `.mk-ctacard` base; that
   file carries only the contract row's own overrides. Everything structural
   lives here so the two halves cannot drift.

   ----------------------------------------------------------------------------
   §1 · THE FOUR CLIP NUMBERS — full derivation, because these are the only
        invented-looking values on the page and none of them is invented.

   The rest-state card is 1040 x 402 with a 4 px radius, inside a 1280 x 900 row.

   HORIZONTAL — 9.375%
     120 / 1280. MEASURED off the @2x Figma exports, not derived from a stated
     margin. In figma-truth/sections-en/18-cta-a.png and 20-cta-c.png the card's
     non-#f5f5f5 bounding box runs device x 240..2319 in BOTH files — 120.0 CSS
     px of page ground on each side, identical to the pixel in both keyframes.
     (salient-truth/MECHANIC-expanding-card.md §8.2 flagged this number as
     derived-not-measured. It is measured now, and it agrees.)

   RADIUS — 4px
     `cornerRadius: 4` in the payload, confirmed against the pixels. For a 4 CSS
     px radius (8 device px at @2x) the circle predicts the first covered pixel
     at dx~5 on the corner's first row, reaching dx~0 by row 4. The export gives
     4 and 0 — consistent once antialiasing bias toward the outer envelope is
     accounted for. Salient's own demo uses 20px; that is a demo value.

   VERTICAL — 29.7778% top / 25.5556% bottom
     = 268/900 and 230/900. These are NOT the 50% that
     MECHANIC-expanding-card.md §6 derived from the 450 / 900 frame heights, and
     §8.2 correctly refused to stand behind that derivation. The real constraint
     is the copy, which a clip-path CANNOT move: it is fixed at frame B's own
     position — which is NOT the frame centre, see §1.1 — and must still land
     inside the card at rest.

       frame A (18-cta-a.spec.md §1.1)   card top abs 48
                                         overline abs y 168  -> card-rel 120
                                         btns abs y 360 h 40 -> card-rel 312..352
       frame B (19-cta-b.spec.md §2.1)   overline row y 388
                                         btns row y 580 h 40 -> 580..620

       copy block height   352 - 120 = 232 = 620 - 388        <- agrees
       card top   in row   388 - 120 = 268
       card bottom in row  620 + (402 - 352) = 670
       268 + 402 = 670                                        <- agrees

     Two independent anchors — the copy's top edge and its bottom edge — pin the
     same window to the pixel. So the rest-state window is row y 268..670, the
     copy sits inside it at exactly frame A's relative offsets, and 50 px of card
     remains below the button row. Nothing is ever clipped and both pills stay
     hit-testable at every progress, which is what makes `config: []` safe.

   ----------------------------------------------------------------------------
   §1.1 · THE COPY IS 54 px BELOW THE FRAME CENTRE, AND THE ROW PADDING IS THE
          ONLY THING PUTTING IT THERE. This whole derivation rests on the copy
          stack sitting at row y 388..620. It is NOT centred: (900 - 232)/2 is
          334, and 388 is 54 px lower. figma-truth/19-cta-b.spec.md §3.4 gives
          the mechanism — `padding-top: 120` against `padding-bottom: 12` with
          `justify-content: center` displaces the stack by (120 - 12)/2 = 54 —
          and says in as many words to implement it as the asymmetric padding
          rather than as a hand-tuned offset. That is the `padding: 120px 120px
          12px` on `.mk-ctacard__row` below.

          IT IS DEFEATABLE FROM THE MARKUP, and it was. Salient emits an inline
          `padding-top: 0px; padding-bottom: 0px` on every row whose padding
          setting is "none", the lift carried it, and an inline declaration beats
          this stylesheet: only the horizontal 120s survived, the stack centred
          at 334, and all three keyframes were wrong at once — frame B measured
          3.83 % instead of 0.842 %, and the rest card carried its copy at
          card-relative 66..298 instead of frame A's 120..352. Both rows'
          `style` attributes now carry `z-index` only. If this number ever drifts
          again, check the markup before changing anything here.

          `box-sizing: border-box` is what keeps this height-neutral: the row is
          `aspect-ratio: 64/45` at width 1280, so 900 INCLUDES the padding and
          the content box is 900 - 132 = 768, exactly the spec's table. Adding
          the padding moved no downstream offset — sum(data-mk-h) is still 19200.

   WHAT THIS COSTS, recorded rather than hidden: keyframe A shows sky and
   keyframe C shows pool deck, which reads as the card sitting at the TOP of the
   photo in A and the BOTTOM in C. It does not. Figma re-cover-fits the image
   into each keyframe's own 1040 x 1040 "Text area" square and then clips it —
   the visible 402 is the top of that square in A and the bottom of it in C. A
   1D profile match of keyframe C against frame B puts C's band at ~0.79x frame
   B's scale, i.e. a different zoom, so C's pixels are not a clip window onto
   frame B's photo and cannot be reproduced by one. A clip-path never rescales.
   The card at rest therefore shows the row y 268..670 band of the single
   frame-B plate — the horizon band — in both halves. This is the one place the
   built card cannot match the A and C keyframes pixel-for-pixel, and it is a
   property of the mechanic the founder confirmed, not a shortcut.

   ----------------------------------------------------------------------------
   §2 · NO `transition` ON clip-path, EVER. The scrub must land exactly on the
        seeked value each frame. A transition turns every frame into a chase and
        also breaks the ownership readback in js/mk-expanding-card.js, which
        compares the inline value against the string it wrote last frame.

   §3 · NO STATIC `border-radius` ON THE ROW. The rest-state radius arrives
        through the clip string's `round 4px` and animates to 0 with it. A
        `border-radius: 4px` sitting underneath would round the full-bleed state
        too — which frame B does not do — and would fight the clip at every
        intermediate value.
   ========================================================================== */

/* --------------------------------------------------------------------------
   BASE — shared by both halves.
   -------------------------------------------------------------------------- */
.mk-ctacard {
  /* The page ground the card floats on at rest. Figma paints #f5f5f5 behind the
     card in keyframes A and C; that is this token. The section itself is
     otherwise inert — all paint lives on the row. */
  background-color: var(--mk-surface-plain);

  /* Frame B's 546px text measure. Kept as a custom property so the ES build can
     override it in one place, exactly as 19-cta-b.css did. */
  --mk-ctacard-measure: 546px;
}
/* The Spanish 740px measure moved to build/v2-es/css/lang-es.css (2026-09-02):
   per-language sizing lives only in that sheet, never in shared CSS. */

/* ==========================================================================
   §4 · R8-16 (FT-20260821-jehnean-round8) — THE FULL-SCREEN HOLD.
   Full RCA, the child-combinator check and the rhythm.css/_band-xl.css
   tension: sections/18-cta-expand.html's own header comment and
   founder-trace/helix/R8-16.md. Summary of the shape only, here:

     `.mk-ctacard__stage` (new) is the STAGE — `data-myk-stage`, picked up by
     04-ecology.css §G1b's page-wide `[data-myk-hold] [data-myk-stage]` rule
     (`position: sticky; top: 0`) and sized below. `.mk-ctacard__row` (the
     existing expand row, UNCHANGED apart from how it is positioned) becomes
     the stage's absolute fill — same shape sections/02-who-why-b.css uses
     for `.mk-whowhyb__card` inside `.mk-whowhyb__stage`.

     BELOW 1000px the same stage/hold model now applies — see §6 (mobile
     round): the founder asked for the full-screen immersive expand on
     mobile too, so the pre-§6 arrangement (stage keeps the row's OLD
     `aspect-ratio: 64/45`, row back in normal flow at <=600px) is retired
     rather than preserved. §6 carries the derivation.

     AT/ABOVE 1000px the stage becomes `height: 100svh` (aspect-ratio
     released) and the SECTION grows to `100svh + N * 100svh`, giving the
     sticky stage N screens of pinned travel (N = `--myk-cta-hold-screens`,
     see the R10-08 addendum immediately below for its current value and
     derivation). `js/mk-expanding-card.js` needs no change: once the stage
     is pinned the row's on-screen position freezes at rowTop 0, so the clip
     driver's own clamp-at-s=1 behaviour holds it at `inset(0px)` — full
     bleed — for the whole budget, for free.

   ----------------------------------------------------------------------------
   §4a · R10-08 (FT-20260822-jehnean-round10) — PERSIST LONGER, 0.85 -> 0.95.
   Full record: founder-trace/helix/R10-08.md.

   HER WORDS, this round: "doesn't persist it long enough when I'm scrolling
   ... all these things need to persist long enough for me to read even if I
   keep scrolling" — named by the round's own spec as the SAME persist-
   duration class as R10-01 (black-dress).

   R8-16.md's own MINOR FINDING 2 already flagged a live coherence question:
   0.85 here was reused verbatim from 02-who-why-b's PRE-R8-11 value; R8-11
   (same round 8) then moved that sibling to 0.95 for the identical "persist
   a little bit longer" ask, and 18-cta-expand was left at the old 0.85,
   "named here so the founder coherence question ... is visible to whoever
   reads this next." Round 10 answers that question and satisfies R10-08 in
   the same edit: 0.85 -> 0.95 closes the gap AND extends this section's own
   persist, using the exact +11.8% ratio R8-11 already used (and, by
   implication, already had reasoned about) for the identical complaint on
   the sibling full-screen-hold mechanic — the least-invented number
   available, not a fresh guess.

   PREDICATE P-R8-11 REUSED, NOT RE-DERIVED: "a PERSIST LONGER request on a
   hold mechanic built from a single screens constant scales the whole arc,
   not just the plateau." `--myk-cta-hold-screens` is exactly that single
   constant here too — §5 below's SAINT-BARTH choreography reads `p` (0..1,
   a FRACTION of the pinned travel, written by 04-ecology.html's page-wide
   reader from live geometry), never an absolute pixel value, so scaling the
   screens constant scales the fade-in/hold/fade-out windows' PX WIDTH
   proportionally while their 40/40/20 ratio (and the fail-open 0.5) is
   untouched by this edit — same reasoning R8-11 used on 02-who-why-b, same
   result shape here.

   `js/mk-expanding-card.js` needs no change for this edit either, for the
   same structural reason R8-16 named originally: the row's screen position
   still freezes at rowTop 0 for the whole (now longer) pinned duration, and
   R8-22's own cache-poison fix (`founder-trace/helix/R8-16.md` §"R8-22 FIX
   PASS") reads its offset from the TRACK (`[data-myk-hold]`, i.e. this
   section), not the row — an in-flow element whose `rect.top + scrollY` is
   scroll-invariant at ANY section height, so growing the section here does
   not touch the invariant that fix depends on. Re-verified live, not just
   argued: see R10-08.md §3.
   ========================================================================== */
.mk-ctacard .mk-ctacard__stage {
  /* NO `position` DECLARATION HERE, AND THAT ABSENCE IS LOAD-BEARING.
     `[data-myk-hold] [data-myk-stage] { position: sticky; top: 0 }`
     (04-ecology.css §G1b) is TWO attribute selectors — specificity (0,2,0) —
     EXACTLY equal to this rule's `.mk-ctacard .mk-ctacard__stage`. Equal
     specificity means SOURCE ORDER decides, and `_tools/compose.py` links
     stylesheets in slug order, so `18-cta-expand.css` always loads AFTER
     `04-ecology.css`. A `position: relative` here — even written as a
     documented "containing block" assertion — would therefore WIN and
     silently turn the sticky pin into a no-op.
     MEASURED, and this is the exact bug this comment exists to prevent from
     coming back: with `position: relative` declared here, live CDP at
     1280x900 mid-hold showed `getComputedStyle(stage).position === "relative"`
     and `stage.getBoundingClientRect().top === -382` (scrolling normally,
     not pinned) instead of `"sticky"` / `0` — the photo covered only the top
     ~520px of the viewport with the section's own `--mk-surface-plain`
     ground showing through below it, i.e. the exact "lost the full screen
     treatment" symptom this fix exists to cure. Removing the declaration and
     leaving `position` entirely unset is what lets the generic rule govern
     it, exactly as sections/02-who-why-b.css's own `.mk-whowhyb__stage` does
     — it declares no `position` for itself either. `sticky` already
     establishes a containing block for the row's `position: absolute` below,
     same as `relative` would have, so nothing is lost by leaving it out. */
  width: 100%;
  aspect-ratio: 64 / 45;      /* base: BYTE-IDENTICAL to the row's old rule    */
}
@media (min-width: 1000px) {
  .mk-page .mk-section.mk-ctacard {
    /* R10-08: 0.85 -> 0.95, +11.8%. See §4a above for the full derivation
       (closes R8-16's own "0.85 vs 0.95" coherence question and answers
       her "persist longer" ask in the same one-line edit). */
    --myk-cta-hold-screens: 0.95;
    height: calc(100svh + var(--myk-cta-hold-screens) * 100svh);
  }
  .mk-ctacard .mk-ctacard__stage {
    aspect-ratio: auto;
    height: 100svh;
  }
}

.mk-ctacard .mk-ctacard__row {
  /* R8-16: absolute-fills the stage instead of sitting in normal flow.
     Reverted back to `position: relative` at <=600px in §RESPONSIVE, where
     the stage has no explicit height of its own and needs an in-flow child
     to size against — exactly mirroring the pre-R8-16 mobile behaviour. */
  position: absolute;
  inset: 0;
  width: 100%;

  display: flex;
  align-items: center;      /* frame B: primaryAxisAlignItems CENTER */
  justify-content: center;  /* frame B: counterAxisAlignItems CENTER */
  /* WAS `120px 120px 12px` — frame B's asymmetric padding, which parked the
     copy 54px below the frame centre by design (§1.1 above). Founder,
     2026-09-02, on both languages: "still not vertically centered. Still
     low. Too low on English too. Go fix them." Symmetric 120 puts the flex
     centre on the frame centre (stack top 334 for a 232px stack at 900). The
     rest-state card is D38's inset(120px) — row y 120..780 at 900 — and the
     stack at rest (row y 454..686, measured) sits inside it. (The independent
     review caught the first draft of this note quoting the pre-D38 268..670
     window.) */
  padding: 120px;
  margin: 0;

  /* NOT `overflow: hidden`. The old 19-cta-b clipped with overflow + a static
     border-radius; here the clip-path is the only clipper, and overflow would
     add a second, non-animating one. */
  overflow: visible;

  /* See §2 and §3. Stated as declarations so a later `all: unset` or a shared
     rule cannot reintroduce them silently. */
  transition: none;
  border-radius: 0;
}

/* The clip is written inline by JS on every frame; `[data-bg-animation]` is
   inert for styling in Salient too (there is no rule keyed to it anywhere in
   the vendored CSS). These are the layout rules the mechanic needs. */
.mk-ctacard .row-bg-wrap,
.mk-ctacard .row-bg-wrap .inner-wrap {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.mk-ctacard .row-bg-wrap .inner-wrap { z-index: 1; }
.mk-ctacard .row-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
}
.mk-ctacard .row-bg-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: absolute;
  top: 0;
  left: 0;
}
.mk-ctacard .row-bg-overlay {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 3;
  backface-visibility: hidden;
}

/* --------------------------------------------------------------------------
   CONTENT COLUMN — expand half only (the contract half has none by design).
   -------------------------------------------------------------------------- */
.mk-ctacard .row_col_wrap_12 {
  position: relative;
  z-index: 10;             /* above .row-bg-overlay's z-index 3 */
  width: 100%;
  margin: 0;
}
.mk-ctacard .wpb_column,
.mk-ctacard .vc_column-inner { width: 100%; padding: 0; margin: 0; }
.mk-ctacard .vc_column-inner { display: block; }
.mk-ctacard .vc_row-fluid > .span_12 { margin-inline: 0; }
.mk-ctacard .vc_row-fluid > .span_12 .wpb_column { padding-inline: 0; }

.mk-ctacard .mk-ctacard__stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;               /* frame B itemSpacing 24 */
  width: 100%;
  flex-grow: 0;
}

/* 1 · overline — Poppins Light 16/24, tracking 3px, uppercase.
   The trailing letter-space of the last glyph is what would push a centred
   uppercase string off-centre, so the same value is added as left padding. */
.mk-ctacard .wpb_text_column { margin: 0; }
.mk-ctacard .wpb_text_column h5.mk-ctacard__overline {
  margin: 0;
  font-family: var(--mk-font-sans);
  font-weight: var(--mk-w-light);
  font-size: var(--mk-size-base);
  line-height: var(--mk-lh-node);
  letter-spacing: var(--mk-track-eyebrow);
  text-transform: uppercase;
  color: var(--mk-ink-inverse);
  text-align: center;
  padding-left: var(--mk-track-eyebrow);
}

/* 2 · heading — Merriweather Light 48/60, tracking 0, 546px measure.
   The two-line break is set by the measure, not by a <br>. */
.mk-ctacard .mk-ctacard__heading { margin: 0; }
.mk-ctacard .mk-ctacard__heading h2 {
  margin: 0 auto;
  font-family: var(--mk-font-serif);
  font-weight: var(--mk-w-light);
  letter-spacing: 0;
  color: var(--mk-ink-inverse);
  text-align: center;
  -webkit-text-stroke: 0;      /* the payload's 1px #000 stroke does not render */
  font-optical-sizing: auto;
  font-variation-settings: "wdth" 100;
}
.mk-ctacard .mk-ctacard__heading[data-custom-font-size="true"] h2 {
  font-size: inherit;
  line-height: inherit;
}

/* 3 · button row — HORIZONTAL, gap 12, centred, both pills hug their label.
   ----------------------------------------------------------------------------
   D41 — FOUNDER TRACE FT-20260817: "Both buttons have NO hover states."

   WHAT USED TO BE HERE: fifty lines re-implementing the pill — its padding,
   its typography, its 1px ring, its solid fill, its blur and a partial hover
   (`--primary` got a bare `opacity: 0.87` with no transition; `--ghost` got no
   `:hover` rule at all). css/myk-btn.css already owned every one of those, and
   its header warns about exactly this: "Anything you need to change about a
   Mykrobial button should be reachable by setting a token on a scope. If it is
   not, add a token here — do not copy this block into a section sheet. That is
   how we ended up with three separate implementations of the same 1px stroke."
   This was the fourth. It is deleted, not patched: adding a `:hover` here would
   have been a fifth divergence rather than a fix, and G4 asks for hover states
   that are consistent across every instance of a button class — which only
   holds if there is one implementation.

   WHAT REPLACES IT: the markup now carries `myk-btn myk-btn--primary` and
   `myk-btn myk-btn--outline myk-btn--ring-outline`, the same pair
   02-who-why-b ships, and this block is reduced to the three places where THIS
   SECTION genuinely differs from the component's defaults. Each is a TOKEN on a
   scope, which is the method the component asks for. Nothing here restates a
   value the component already has: font, size, line-height, tracking, colour,
   radius, height, padding, the primary fill and the outline stroke are all
   already correct at their defaults and are deliberately absent below.
   -------------------------------------------------------------------------- */
.mk-ctacard .mk-ctacard__btns {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

/* DIFFERENCE 1 — the blur radius. Figma declares BACKGROUND_BLUR radius 4 on
   these pills; 2px is the measured visual match against the export and was
   carried from 19-cta-b.css unchanged. The component's default is
   `var(--mk-pill-blur, blur(4px))`, so this is a scope override of one token,
   not a re-declaration of the backdrop-filter pair. */
.mk-ctacard .mk-ctacard__btn { --myk-btn-blur: blur(2px); }

/* DIFFERENCE 2 — the trailing letter-space. Poppins with 1px tracking emits a
   trailing advance after the last glyph, which makes a hugging pill 1px wider
   than Figma's box and pushes a centred label half a pixel left. The component
   has a token for precisely this (`--myk-btn-track-trim`, applied in §2.2 as
   `margin-right: calc(icon-advance - track-trim)`); the old code did the same
   arithmetic by hand on `.text`. */
.mk-ctacard .mk-ctacard__btn { --myk-btn-track-trim: var(--mk-track-nav); }

/* DIFFERENCE 3 — the secondary pill's label is Poppins MEDIUM while the primary
   pill's is Regular. Not a mistake and not a variant: the same asymmetry the
   component records for the nav CTA vs the hero CTA (myk-btn.css §2.3, "Label
   weight is a MODIFIER, not a variant"). The component's default is Regular, so
   only the ghost needs a line. */
.mk-ctacard .mk-ctacard__btn--ghost { --myk-btn-weight: var(--mk-w-medium); }

/* NO PADDING COMPENSATION ON THE GHOST, AND ITS REMOVAL IS THE POINT.
   The old code shrank the ghost's padding by 1px per side because its ring was
   a `border`, which under the global `box-sizing: border-box` eats a pixel of
   content box. `myk-btn--ring-outline` draws the ring with `outline` at a
   -0.5px offset, which takes NO layout space at all (myk-btn.css §4, RING
   RENDERER B, measured on 02-who-why-b). Keeping the compensation would now
   make the secondary pill 2px narrower than the primary one. */

/* The `@supports` fallback is kept because it is about the platform, not the
   component: where `backdrop-filter` is unavailable the outline pill needs an
   opaque-enough ground for a white label to stay legible over photography.
   Retargeted from the old `.link_wrap` to the component's own box. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .mk-ctacard .mk-ctacard__btn--ghost { background-color: rgba(11, 15, 18, 0.28); }
}

/* Focus is NOT declared here. myk-btn.css §2.5 ships `:focus-visible` for both
   variants, and §4's `--ring-outline` re-states it at the specificity that
   variant needs. A second declaration in this file is how the two drift. */

/* --------------------------------------------------------------------------
   RESPONSIVE.
   The scrub now runs at EVERY width. The settings JSON's tablet/phone pairs
   were the vendored no-op gate (Salient's own mobile-disable, which every
   shipped instance carries); the mobile round replaced them with real
   keyframes — see §6 below and 18-cta-expand.html's settings comment for the
   numbers. The padding and type reflow here exactly as before.
   -------------------------------------------------------------------------- */
/* R8-16: the old `@media (min-width: 1280px) { .mk-ctacard__row { min-height:
   900px } }` floor is GONE, superseded by §4's `min-width: 1000px` stage rule
   above, which now also closes the 1000-1279px gap that floor never reached
   (that band got the clip-path scrub — Salient's own device gate — but never
   the min-height floor; it is folded into the same immersive treatment as
   1280+ rather than left half-done). */
@media (max-width: 1279px) {
  .mk-ctacard .mk-ctacard__row { padding-inline: clamp(24px, 9.375vw, 120px); }
  .mk-ctacard .mk-ctacard__heading {
    font-size: clamp(30px, 3.75vw, 48px) !important;
    line-height: 1.25 !important;
  }
}
@media (max-width: 600px) {
  /* §6 owns the height model at <=999px now (stage 100svh, row absolute-fill
     — the pre-§6 normal-flow reversion this block used to carry is retired
     with it). Only the phone gutter and the button wrap remain this
     breakpoint's own: 24px is the gutter the §1279 clamp cannot reach down
     here (clamp floor 24px but 9.375vw is still 38.6px at 412). Block-axis
     padding comes from §6's shared 96/88. */
  .mk-ctacard .mk-ctacard__row { padding-inline: 24px; }
  .mk-ctacard .mk-ctacard__btns { flex-wrap: wrap; row-gap: 12px; }
}


/* ==========================================================================
   §5 · R8-16 — THE CHOREOGRAPHY: overline + heading + both buttons, as ONE
   block, reusing R6-09's SAINT-BARTH formula from sections/02-who-why-b.css
   VERBATIM (same 40% in / 40% hold / 20% out windows, same 120px travel, same
   100ms linear follower, same fail-open fallback). `data-mv-skip` on
   `.mk-ctacard__stack` (18-cta-expand.html) hands this whole subtree to this
   rule alone — without it js/myk-motion.js would ALSO adopt the overline and
   the heading child (both match its selector list) and fight this rule for
   `opacity` on the same elements. MEASURED on the pre-fix build: it did.

   `transform`, not `translate` — safe here for the same reason it is safe on
   02-who-why-b's `.mk-whowhyb__text`: nothing in this file puts a `transform`
   on `.mk-ctacard__stack`, so there is nothing of this file's own to collide
   with (myk-motion.css's `translate`-vs-`transform` collision avoidance is
   for elements ITS OWN `[data-mv]` system might otherwise clobber, and this
   element opts out of that system entirely via `data-mv-skip`).

   THE FAIL-OPEN IS EXACT: `var(--myk-hold-p, 0.5)` with no reader running
   computes opacity 1, transform none — Figma's rest composition, unchanged.
   Gated to the same >=1000px as §4: below it there is no hold travel, `p`
   would sit at 0, and this rule would permanently hide the stack.
   ========================================================================== */
@media (min-width: 1000px) {
  .mk-page .mk-ctacard .mk-ctacard__stack {
    --myk-cta-sb-p: var(--myk-hold-p, 0.5);
    opacity: clamp(0, min(calc(var(--myk-cta-sb-p) / 0.4),
                          calc((1 - var(--myk-cta-sb-p)) / 0.2)), 1);
    transform: translate3d(0, calc(120px - 240px * var(--myk-cta-sb-p)), 0);
    transition: opacity 100ms linear, transform 100ms linear;
  }
}

/* ==========================================================================
   §5a · R10-09 (FT-20260822-jehnean-round10) — EXPERIMENT: PERSIST-THROUGH
   EXIT, a gated variant of §5 above. NOT a committed decision — same pattern
   as R8-10's estate-slider scroll-vs-one-shot experiment: additive, one
   modifier class, fully reversible, both states screenshotted so she can
   compare. Full record: founder-trace/helix/R10-09.md.

   HER WORDS: "on the scroll out ... don't bother to fade out and move up.
   Just have it persist as I leave the area and move on to the next section.
   And I would like to see that and then assess how I like it."

   THE MECHANISM. §5's own two declarations (`opacity`, `transform`) are left
   completely untouched — this variant overrides ONLY the custom property
   they both read, `--myk-cta-sb-p`, by re-declaring it on a MORE SPECIFIC
   selector that adds one modifier class. Custom properties cascade
   independently of which rule's OWN declarations consume them via `var()`,
   so §5's `opacity`/`transform` lines keep applying to `.mk-ctacard__stack`
   unedited, but resolve `--myk-cta-sb-p` from whichever rule wins the
   cascade for THIS more specific selector — here, this one.

   `min(var(--myk-hold-p, 0.5), 0.5)` clamps the INPUT the formula sees at
   0.5, never the output: for real progress p in [0, 0.5] the clamped input
   equals p, so §5's entrance is byte-identical — same 40%-fade-in window,
   same continuous rise toward rest. `transform`'s zero-crossing (rest,
   translate none) already lands at p=0.5 in §5's own unmodified formula
   (120 − 240×0.5 = 0), which is why 0.5 is the exact clamp point, not 0.4
   (the opacity plateau's own left edge — that would freeze the stack 24px
   short of rest, a small but real visible offset). At p=0.5 exactly,
   opacity's `clamp()` already evaluates min(0.5/0.4, 0.5/0.2) = min(1.25,
   2.5) → clamped to 1 — so opacity is already fully settled at the SAME
   point transform reaches rest; clamping both off one input at 0.5 does not
   pick different stopping points for the two properties by accident. For
   any real p > 0.5, the clamped input STAYS at 0.5, so opacity stays 1 and
   transform stays translate3d(0,0,0) — Figma's exact rest composition —
   for the remainder of the hold AND through release, because nothing in
   this rule depends on scroll position past that point. No fade-out window
   is removed by deleting code; it is simply never reached.

   THE TOGGLE. `mk-ctacard--persist-exit` on `<section id="s18-cta-expand">`
   (18-cta-expand.html). Remove that one class to fall back to §5's original
   choreographed release — nothing else in this file changes either
   direction, and §5 itself is untouched by this block, so there is nothing
   to unwind if she prefers the original.

   SPECIFICITY, not `!important`. `.mk-page .mk-ctacard.mk-ctacard--persist-
   exit .mk-ctacard__stack` is (0,4,0) against §5's (0,3,0) — one additional
   class, unconditionally higher regardless of source order, so this block
   can sit either before or after §5 in the file with the same result; it is
   placed immediately after for readability (variant next to what it varies).

   SCOPE: entrance and hold are IDENTICAL between the two states by
   construction (same clamp for p<=0.5) — only p>0.5 differs. The full-bleed
   PHOTO is unaffected either way: it was never fading (§4's clip driver
   holds `inset(0px)` for the whole pinned duration regardless of this
   block), so "persist through" for the photo already existed before R10-09;
   this item is scoped to the text/button stack's own choreography only,
   matching her words ("these things" = the overline/heading/buttons, the
   items §5 already animates as one block).
   ========================================================================== */
@media (min-width: 1000px) {
  .mk-page .mk-ctacard.mk-ctacard--persist-exit .mk-ctacard__stack {
    --myk-cta-sb-p: min(var(--myk-hold-p, 0.5), 0.5);
  }
}

/* Reduced motion: show the final composition, never hide it. Same rationale
   and the same `!important` requirement as 02-who-why-b.css's block — it has
   to beat css/reduced-motion.css's later-linked global transition floor. The
   sticky hold itself is left running (it is a static pin, not an animation;
   same precedent as 02-who-why-b, which does not disable it either). */
@media (prefers-reduced-motion: reduce) {
  .mk-page .mk-ctacard .mk-ctacard__stack {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  /* §6a's contract-outro is motion; under reduced motion the stage shows the
     finished full-bleed composition, never a scrub — same principle as the
     driver standing down (css/reduced-motion.css paints the row at its end
     state, and this keeps the stage from re-clipping it). */
  .mk-page .mk-ctacard .mk-ctacard__stage {
    clip-path: none !important;
    transition: none !important;
  }
}

/* ==========================================================================
   §6 · MOBILE ROUND (FT-20260830, mobile-UX round) — THE FULL-SCREEN
   IMMERSIVE EXPAND AT <=999px. REVISED IN ROUND 2 (FT-20260830-r2): the
   founder reviewed round 1 on a real Pixel 7 — "it doesn't do the full
   screen", it starts late behind dead white space, and she wants the full
   screen ANCHORED and then an OUTRO before the footer. Three defects, three
   fixes, all in this block + §6a below + the keyframes in the HTML.

   FIX 1 — svh WAS THE FULL-SCREEN BUG. Round 1 sized the stage 100svh.
   `svh` is the SMALL viewport height — the viewport with the browser chrome
   VISIBLE — and it never changes when the chrome hides. On a Pixel 7 the
   visual viewport is ~780px with chrome and ~915px once a downward scroll
   hides it, which it always is by the time the visitor has scrolled ~18,600px
   to this section. So the pinned "full screen" stage covered 780 of 915px —
   ~85% of her actual screen, with a dead band of this section's own
   `--mk-surface-plain` ground below it for the whole hold. That is her
   "doesn't do the full screen", reproduced by arithmetic (svh is fixed by
   definition; a fixed-viewport probe cannot show it because there svh = dvh).
     · The STAGE is now `100dvh` — the DYNAMIC viewport height, always equal
       to what is actually on screen, chrome visible or not. `100vh` first as
       the fallback for engines without the unit (vh ≈ lvh on mobile, i.e.
       full coverage, at worst a bottom crop while chrome is shown).
     · The TRACK (section) is now sized in `lvh` — the LARGE viewport height,
       a STATIC unit (no mid-scroll reflow, unlike dvh) equal to the
       chrome-hidden viewport, which is the state the section is actually
       scrolled in. Travel = height − stage = 0.95 * 100lvh — the full R10-08
       hold budget on the real device; the svh track gave only
       0.95 * 780/915 = 0.81 real screens, part of her "starts late /
       doesn't persist" reading. The hold reader (04-ecology.html) computes
       p from live rects, so both unit changes feed it correctly with no JS
       edit. In any fixed-viewport environment svh = lvh = dvh and this is
       byte-identical to round 1's geometry.

   FIX 2 (dead white space above) lives in the HTML keyframes: the round-1
   phone/tablet start clips carried a 26.45% top inset — ~206px of blank
   ground INSIDE the row that entered the viewport ahead of the card, so the
   effect read as starting late behind dead white (measured at 412x780: the
   card's top edge entered only after 163px of post-arrival scroll; section
   17 abuts this section flush, so ALL the leading white was this clip's).
   Round 2 rest-clips the card at THE PAGE GUTTER on all four sides — the
   same resolution D38 shipped for the identical desktop complaint, same
   words ("too much white space above"), same fix shape (268px -> 120px page
   gutter there; 206px -> 24px/9.375% here). Derivation: HTML settings block.

   FIX 3 (anchored full screen, then OUTRO) is §6a below.

   THE REST IS ROUND 1, UNCHANGED: 0.95 is R10-08's founder-tuned persist
   value (the same `--myk-cta-hold-screens` >=1000px uses; a different
   mobile constant would reopen the coherence question R10-08 closed).
   PADDING-BLOCK 96/88 rather than the base 120/12: the desktop asymmetry
   exists only to reproduce frame B's 54px below-centre displacement (§1.1),
   a 1280x900 artboard fact with no mobile counterpart. 96/88 is the phone
   tier's own measured pair, promoted to the whole <=999px band. Nothing is
   ever clipped at any progress, which is what keeps `config: []` safe on
   mobile exactly as §1 argues for desktop.

   >=1000px IS UNTOUCHED: every rule here is inside the max-width gate, and
   §4/§4a/§5/§5a still govern the desktop band byte-identically.
   ========================================================================== */
@media (max-width: 999px) {
  .mk-page .mk-section.mk-ctacard {
    --myk-cta-hold-screens: 0.95;   /* R10-08's value, reused — see §6 header */
    /* lvh, NOT svh — Fix 1 above. vh line first: fallback for engines
       without the large-viewport unit. */
    height: calc(100vh + var(--myk-cta-hold-screens) * 100vh);
    height: calc(100lvh + var(--myk-cta-hold-screens) * 100lvh);
  }
  .mk-ctacard .mk-ctacard__stage {
    aspect-ratio: auto;
    /* dvh, NOT svh — Fix 1 above: the anchored frame must fill whatever is
       actually on screen, chrome shown or hidden. vh fallback first. */
    height: 100vh;
    height: 100dvh;
  }
  .mk-ctacard .mk-ctacard__row { padding-block: 96px; }   /* symmetric, same founder call as desktop */
}

/* ==========================================================================
   §6a · ROUND 2 (FT-20260830-r2) — THE OUTRO. HER WORDS: "add the full
   screen effect to that and anchor that full screen, and then you'll outro
   it before you go to the footer."

   WHAT IT IS: over the LAST 20% of the pinned hold — §5's own out-window
   fraction, reused rather than invented — the full-bleed frame contracts
   back to the page-gutter card (the start keyframe's exact geometry) and
   then releases toward the footer as that card. This is the component's own
   third keyframe: the founder described the original Figma filmstrip as
   "starts as a card, then expands to full screen, then the card gets small
   again and disappears" (file header), and D39 retired only the DUPLICATE
   STATIC ROW that once faked the contract, not the concept. The first 80%
   of the hold is untouched full bleed — the anchored moment she asked for
   (~0.76 screens of scroll at rest before the outro begins).

   THE MECHANISM — on the STAGE, never the row. The row's clip-path is owned
   inline, per frame, by js/mk-expanding-card.js; §2 of this file bans any
   CSS interference with it, and a stylesheet clip on the row during the
   APPROACH (when `--myk-hold-p` still reads 0) would force full bleed and
   kill the expand-in entirely. The stage is a different element the driver
   never touches, so the two clips compose: during approach and the anchored
   hold this rule computes `inset(0 ... round 0)` — geometrically a no-op —
   and only for p > 0.8 does the inset grow. Continuity at both boundaries
   is by construction: at p = 0.8 both the driver (inset 0) and this rule
   (0 * gutter = 0) paint full bleed; past release p clamps at 1 and the
   contracted card simply scrolls away.

   THE MATH: `--myk-cta-outro` = clamp(0, (p − 0.8) / 0.2, 1) — 0 through
   the whole approach + anchored hold, 0..1 across the last 20%, 1 from
   release on. Each inset is that scalar times the same gutter the start
   keyframes use (24px phone, 9.375% tablet — HTML settings block carries
   the derivation), and the radius rides the same scalar to the card's own
   4px, so the outro's end state is the rest card verbatim. The 100ms linear
   follower is §5's own — it smooths the scroll quantisation exactly as the
   SAINT-BARTH choreography does; §2's no-transition ban is about the
   DRIVER's scrubbed row, which this element is not.

   FAIL-OPEN, EXACT: with no reader running `var(--myk-hold-p, 0.5)` gives
   clamp(0, (0.5 − 0.8) / 0.2, 1) = 0 → inset(0) — full bleed, a no-op,
   same fail-open shape as §5. Reduced motion: the block at the end of this
   file disables the clip outright.

   TEXT IS NEVER CUT: the stack spans viewport y ~271..582 at 412x780 and
   ~391..641 at 768x1024 (§6 header), and the fully-contracted window is
   24..756 / 96..928 — the copy sits inside the end-state card exactly as
   Figma's keyframe C draws it (copy byte-identical in all three frames).

   >=1000px IS UNTOUCHED: max-width gate, same as §6.
   ========================================================================== */
@media (max-width: 999px) {
  .mk-page .mk-ctacard .mk-ctacard__stage {
    --myk-cta-outro-gutter: 9.375%;   /* tablet gutter; phone override below */
    --myk-cta-outro:
      clamp(0, calc((var(--myk-hold-p, 0.5) - 0.8) / 0.2), 1);
    clip-path: inset(
      calc(var(--myk-cta-outro) * var(--myk-cta-outro-gutter))
      calc(var(--myk-cta-outro) * var(--myk-cta-outro-gutter))
      calc(var(--myk-cta-outro) * var(--myk-cta-outro-gutter))
      calc(var(--myk-cta-outro) * var(--myk-cta-outro-gutter))
      round calc(var(--myk-cta-outro) * 4px));
    transition: clip-path 100ms linear;
  }
}
@media (max-width: 690px) {
  /* The phone tier's page gutter — the same 24px the start keyframe and the
     row's own padding-inline use. Same device split as the settings JSON. */
  .mk-page .mk-ctacard .mk-ctacard__stage { --myk-cta-outro-gutter: 24px; }
}
