/* ============================================================================
   09-recovery-42 — "4.2 Recovery" — Figma 2784:399 — 1280x912
   ----------------------------------------------------------------------------
   Ground truth: figma-truth/09-recovery-42.spec.md  (+ sections-en/*.png)
   Lifted from : salient-truth/resort/index.html  (nectar-fancy-box hover_desc)

   Load AFTER css/site.css. Two blocks:
     A. SALIENT CSS THAT THE SHELL'S VENDOR PASS MISSED — verbatim, do not edit.
        The split-heading half is HOISTED to css/salient-elements.css; what is
        left here is the fancy-box instance CSS, which really is section-local.
     B. MYKROBIAL RETARGETING — Figma values on top of the lifted component.
   ========================================================================= */


/* ============================================================================
   BLOCK A — VERBATIM SALIENT CSS, NOT VENDORED BY THE SHELL
   ----------------------------------------------------------------------------
   The shell's vendor pass took only <link>ed stylesheets. Salient emits the
   rules below into an INLINE <style> in the demo page, because they are
   generated per element-instance from the WP option combination
   (o_opacity_0-9 / hover_o_opacity_0-4 / hover_color_000000 / icon_position_top)
   and from the split-heading element being present at all.

   Provenance: <style> blocks of salient-truth/resort/index.html, extracted
   verbatim (whitespace normalised only). Byte-identical to the live mirror.

   The split-heading half of this block HAS NOW BEEN HOISTED to
   css/salient-elements.css, which is exactly what this comment asked for. The
   rules below are the `nectar-fancy-box[data-style=hover_desc]` instance CSS,
   which is generated from THIS element's option combination and is genuinely
   section-local — do not hoist it too.
   ========================================================================= */

/* The `.nectar-split-heading` block that used to sit here is HOISTED to
   css/salient-elements.css — it was duplicated verbatim in 00-hero.css,
   01-who-why.css and 09-recovery-42.css, and one of the three copies had
   dropped a media wrapper. That file loads before every section sheet, so
   the cascade is unchanged. Do not paste it back. */
/* --- .nectar-fancy-box[data-style="hover_desc"] instance rules ------------- */
/* icon_position_top is what bottom-aligns .heading-wrap. Load-bearing. */
.nectar-fancy-box[data-style="hover_desc"].icon_position_top .inner{align-self:stretch;display:flex;flex-direction:column;}
.nectar-fancy-box[data-style="hover_desc"].icon_position_top .heading-wrap{margin-top:auto;}
/* o_opacity_0-9 keeps the RESTING gradient visible on hover. Without it,
   element-fancy-box.css's `.hovered .box-bg:before{opacity:0}` would fade the
   resting gradient out. The `background` half is superseded in Block B by the
   Figma gradient; it is retained verbatim so the M1 native-motion port has the
   original to A/B against (build/DECISION-framework.md §2). */
.nectar-fancy-box[data-style="hover_desc"].o_opacity_0-9 .box-bg:before{background:linear-gradient(to bottom,rgba(15,15,15,0),rgba(15,15,15,0.9) 100%);}
.nectar-fancy-box[data-style="hover_desc"].hovered.o_opacity_0-9 .box-bg:before{opacity:1;}
/* The hover mechanic itself: a second pure-black gradient cross-fades in to
   0.4 over the resting one. 650ms cubic-bezier(.05,.2,.1,1) comes from
   element-fancy-box.css. Nothing moves, nothing scales. Spec §6. */
.nectar-fancy-box[data-style="hover_desc"][data-color].hover_color_000000 .box-bg:after{background:linear-gradient(to bottom,rgba(0,0,0,0),#000000 100%);}
.nectar-fancy-box[data-style="hover_desc"][data-color].hovered.hover_color_000000 .box-bg:after{opacity:0.4;}


/* ============================================================================
   BLOCK B — MYKROBIAL RETARGETING
   Every literal below carries its spec citation, per CONVENTIONS.md §4.
   ========================================================================= */

/* --- 1. SECTION GROUND — 2784:399 ---------------------------------------- */

.mk-recovery-42 {
  background: var(--mk-surface-mist);           /* #f4f9fb — spec §3 */
}

/* HORIZONTAL row. counterAxisAlignItems CENTER; both children are naturally
   912 tall, so centre and stretch coincide — no explicit height is needed and
   none is set (§1 "Images rail vertical arithmetic": 4+450+4+450+4 = 912; the
   text rail is 120 + 672 + 120 = 912). */
.mk-recovery-42__row {
  display: flex;
  align-items: center;
}

/* --- 2. TEXT RAIL — 2784:400, 563 FIXED at both 1280 and 1440 ------------ */

/* Padding is 120 on ALL FOUR sides in Figma. It is expressed as --mk-page-x
   (120 at >=1050) so it degrades with the shell's own margin ladder instead of
   being a hard literal; at both captured frames it evaluates to exactly 120.
   Spec §4 "Left page margin 120 — Text frame padding-left, fixed at both
   widths". Right page margin is 0: the images rail meets the frame edge. */
.mk-recovery-42__rail {
  flex: 0 0 auto;
  padding: var(--mk-page-x);
}

/* 2784:401 "Text group" — 323x672, primary CENTER, itemSpacing 24.
   672 is FIXED and the block is centred inside it: EN's 552-tall block starts
   60px down, ES's 516-tall block 78px down. Spec §5 is explicit that this must
   be reproduced with vertical centring, not a fixed top offset. */
.mk-recovery-42__copy {
  min-height: 672px;                            /* 912 - 120 - 120 — spec §1 */
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--mk-space-24);                      /* itemSpacing 24 — spec §1 */
}

/* .wpb_column.has-animation ships opacity:0 from Salient's style.css and is
   revealed by init.js. Nothing to add — but the flex context above must not be
   clobbered, so no display override is set here. */

/* SALIENT TRAP — measured, not guessed (CONVENTIONS.md §8.6).
   skin-material.q-2c163bc272.css ships `p{padding-bottom:var(
   --nectar-paragraph-bottom-spacing,1.5em)}` (style.css has the same rule with
   a 28px fallback). Every <p> in this section is a Figma TEXT node with a hug
   height, so that padding is pure error. Measured before the fix:
     eyebrow  24 + 24 = 48   (Figma 24)
     body    360 + 30 = 390  (Figma 360)
     card p  32.4 + 27 = 59.4 (Figma 32.4)
   which pushed the copy block to 601.19 instead of 552 and the card text block
   to 123.8 instead of 97. Killing the padding restores all three exactly.
   Specificity is enough on its own — no !important needed. */
.mk-recovery-42 p {
  padding: 0;
}

/* --- 3. EYEBROW — 2784:402 ----------------------------------------------- */
/* .mk-eyebrow already supplies Poppins Light 300 / 16 / 24 / 3px / uppercase. */
.mk-recovery-42__eyebrow {
  margin: 0;
  color: var(--mk-ink-strong);                  /* #000000 — spec §2a */
}

/* Run 2 of the mixed-run node: "2/#" is Poppins MEDIUM 500 while
   "Recovery plan " is Light 300. Recovered by ink-width + coverage solving
   against the local Poppins binaries (Medium +3.7% vs the calibrated +5.6%
   bias; Regular -16.0%, SemiBold +26.2% both far outside the band) — spec §2b.
   NOTE the ES eyebrow is a single uniform Light run and reads "1/5", NOT
   "2/#". Do not harmonise them in the V2 build without a founder decision. */
.mk-recovery-42__counter {
  font-weight: var(--mk-w-medium);
}

/* --- 4. H2 — 2784:403 ---------------------------------------------------- */
/* Merriweather Light 300 48/60, #000000. The opsz-18 pin comes from
   tokens.css `.mk-page :is(h1,h2,h3,.mk-serif)`. */
.mk-recovery-42__heading h2 {
  margin: 0;
  font-size: var(--mk-size-display);            /* 48 — spec §2a */
  font-weight: var(--mk-w-light);               /* Light 300 — spec §2a */
  line-height: var(--mk-lh-display);            /* 60 (AUTO at 48) — spec §2a */
  color: var(--mk-ink-strong);
}

/* Salient's own split-heading CSS (Block A) forces line-height 1.2 (= 57.6px)
   on the generated <span>s. Figma is 60/48 = 1.25. Measured, per
   CONVENTIONS.md §8.6: without this rule the h2 box computes 115.19px instead
   of the 120px Figma reports for 2784:403; with it, 120px exactly.

   The two `> *` rules in Block A that would also fight this are BOTH gated on
   `:not(.markup-generated)` — init.js Ri()/ji() adds that class the moment it
   splits the words, so they stop matching in the shipped state and the h2 keeps
   `.mk-recovery-42__heading h2`'s 60px. They still apply for the ~1 frame
   before the split, but the wrapper is `opacity:0` throughout that window
   (Block A rule 1), so nothing is ever painted at 1.2. No !important needed. */
.mk-recovery-42__heading[data-animation-type="line-reveal-by-space"] span {
  line-height: var(--mk-lh-display);
}

/* Same 60px, for the pre-split window. This selector deliberately mirrors the
   shape of the two Block A rules it has to outrank — they are (0,4,0) and
   (0,5,0), so an ordinary `.class .class` override silently loses. Measured:
   without it the unsplit h2 box is 115.19px (2 x 57.6) and the copy block
   centres at 62.4 instead of 60. Nothing paints in that window today, but the
   fallback should still be Figma-correct if the reveal is ever bypassed
   (reduced motion, a no-JS path, or the M1 native adapter). */
.mk-recovery-42 .mk-recovery-42__heading[data-animation-type="line-reveal-by-space"]:not(.markup-generated):not([data-text-effect="none"]) > * {
  line-height: var(--mk-lh-display);
}

/* The per-word mask is `overflow:hidden` on a span whose box is the 60px line.
   Merriweather's ascent+descent at 48px is ~60.3px, so the descender of "p" in
   "exposure]" would clip by a fraction of a pixel. Widen the mask box and pull
   the extra back out of flow — the mask still hides the 1.3em pre-roll. */
.mk-recovery-42__heading[data-animation-type="line-reveal-by-space"] > * > span {
  padding-bottom: 4px;
  margin-bottom: -4px;
}

/* --- 5. BODY — 2784:404 -------------------------------------------------- */
.mk-recovery-42__body {
  margin: 0;
  font-size: var(--mk-size-body);               /* 20 — spec §2a */
  font-weight: var(--mk-w-regular);
  line-height: var(--mk-lh-body);               /* 180% -> 36 — spec §2a */
  color: var(--mk-ink-body);                    /* #1a2328 — spec §2a */
}

/* --- 6. IMAGES RAIL — 2784:405 ------------------------------------------- */
/* .mk-rail-images gives width: calc(100% - 563px) — the rail is `fill`, which
   is how 717 at 1280 becomes 877 at 1440 (spec §1 "the two facts").
   .mk-stack-4 gives the 4px gap + 4px padding, but Figma's left/right padding
   is 0, so the inline padding is removed here. Spec §4 "Card stack outer pad:
   4px top and bottom, 0 left/right". */
.mk-recovery-42__images {
  flex: 0 0 auto;
  padding-inline: 0;
}

/* --- 7. THE CARD — 2784:406 / 2784:409 ----------------------------------- */

/* Salient's hover_desc uses `padding: 11%`. Figma uses a FIXED 60px on all
   sides (= 8.37% of the 717 card, 13.3% of its height), which is why the
   overlay text box is 597 = 717 - 120 at 1280 and 757 = 877 - 120 at 1440.
   Spec §6 "Delta to flag before building": use the fixed 60px, not the 11%. */
.mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] {
  height: 450px;                                /* spec §1 — fixed at both widths */
  padding: var(--mk-space-60);                  /* 60 — card inner pad, spec §4 */
  /* Net VISIBLE radius. The rail is rounded on its right corners and each card
     on its left; the rail's radius sits inside the transparent 4px band, so
     only the card's left corners ever paint. Pixel-probed in spec §1:
     (563,4) is #f4f9fb, (1279,4) and (1279,907.5) are square. */
  border-radius: var(--mk-radius-card) 0 0 var(--mk-radius-card);
}

/* THE GRADIENT — spec §3 "Gradient — identical on all four cards".
   Figma: linear top -> bottom over the FULL card height, #00000000 -> #000000bf.
   Salient ships rgba(15,15,15,.75) over 85% height; the spec is explicit that
   the Figma values win. The alpha is 0.75 exactly: the payload's 0xbf reads as
   0.749, and Figma's own SVG export writes `stop-opacity="0.75"`. */
.mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] .box-bg::before {
  height: 100%;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.75) 100%);
}
/* The hover layer keeps Salient's 85% band — it is pure motion, not a Figma
   value, and matching the resting height would change the mechanic's shape. */

/* --- 8. CARD OVERLAY TEXT — 2784:408 / 2784:411 -------------------------- */
/* Measured identically in all four EN+ES cards (spec §2c):
     heading baseline y 323.0 · body baseline y 379.9 · baseline-to-baseline 57.0
     block bottom 390 = 450 - 60 · left ink edge 62-63 => box left 60
   Total block height 43.2 + gap + 32.4 = 97 => paragraph gap 21.2 (derived two
   ways: from the 97px node height -> 21.4; from the 57.0px baseline gap -> 21.2;
   all candidates in 21..21.4 render within 0.3px — spec §7.4).

   Sizes are pinned by line-box arithmetic, not by the width solver: a 24px
   heading at 180% puts the baseline at 3.6 + 25.2 + 1.2 = 30.0 below the line
   box top, matching the measured 293 -> 323.0 exactly (23.75 would give 29.69).
   Spec §2b. */
.mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] .heading-wrap {
  /* `fill`, NOT a fixed measure. Spec §1: "overlay width = rail width - 120,
     i.e. 597 = 717-120 and 757 = 877-120 ... so the card and its text box are
     both `fill`". Pinning 597 renders correctly at 1280 and WRONG at 1440 —
     caught by measuring the 1440 frame, where the card is 877 wide. */
  width: 100%;
  text-shadow: 0 4px 14px var(--mk-shadow-50);  /* DROP_SHADOW #00000080,
                                                   offset y4, radius 14 — §3 */
}
.mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] .heading-wrap h4 {
  margin: 0;
  font-family: var(--mk-font-sans);
  font-size: var(--mk-size-title);              /* 24 — spec §2b */
  font-weight: var(--mk-w-light);               /* Light 300 — spec §2b */
  line-height: 43.2px;                          /* 24 x 180% — spec §2c */
  letter-spacing: 0;
  color: var(--mk-ink-inverse);
  text-transform: none;
}
.mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] .heading-wrap p {
  margin: 21.2px 0 0;                           /* derived paragraph gap — §2c */
  font-family: var(--mk-font-sans);
  font-size: var(--mk-size-body-sm);            /* 18 — spec §2b */
  font-weight: var(--mk-w-regular);             /* Regular 400 — spec §2b */
  line-height: 32.4px;                          /* 18 x 180% — spec §2c */
  color: var(--mk-ink-inverse);
}


/* ============================================================================
   RESPONSIVE
   ----------------------------------------------------------------------------
   >>> ONLY the 1280 and 1440 rows are FACT. Figma has no frame below 1280 for
   >>> this section; everything under `max-width: 1024px` is spec §5 [INFERENCE]
   >>> and needs founder sign-off. The card corner-radius treatment once the
   >>> rail is no longer flush-right is called out in the spec as genuinely
   >>> undecidable from the source — it is left at 4/0/0/4 here rather than
   >>> guessed at. Raised in NOTES.md.
   ========================================================================= */

/* 1024–1279: unchanged row. The text rail holds 563; the images rail absorbs
   the whole delta and the photos re-crop via cover. This is FACT — it is
   exactly what the 1440 frame does (877 = 1440 - 563). Nothing to declare. */

/* 2026-09-02 — WAS `(max-width: 1024px)`. An iPad Pro 13" in portrait is 1024px wide: >=1000 runs the DESKTOP recovery engine (the founder's rule: landscape/>=1000 = desktop treatment), which positions the panels for the ROW layout, while this block had already broken the row into a column — MEASURED at 1024x1366: step 1's panel rendered as a 65px sliver (_tools/_scratch/eyes/ipadpro-rail). The tablet stack now ends where the engine begins. */
@media (max-width: 999px) {
  /* [INFERENCE] spec §5: the row must break to a column somewhere in the
     900–1024 band. It reaches parity with the text rail at 1126 and is
     unusable well before the 563 floor. */
  .mk-recovery-42__row { flex-direction: column; }
  .mk-recovery-42__rail { flex: 1 1 auto; width: 100%; }
  .mk-recovery-42__copy { min-height: 0; }      /* the 672 box exists only to
                                                   centre against the images */
  .mk-recovery-42__images { width: 100%; }
  .mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] {
    height: 340px;                              /* [INFERENCE] spec §5 tablet */
    padding: 40px;                              /* [INFERENCE] spec §5 tablet */
  }
  .mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] .heading-wrap {
    width: 100%;
  }
}

@media (max-width: 640px) {
  /* [INFERENCE] spec §5 mobile row. The eyebrow deliberately does NOT shrink:
     it is already the smallest type and its 3px tracking is load-bearing. */
  .mk-recovery-42__heading h2 { font-size: 32px; }
  .mk-recovery-42__heading h2,
  .mk-recovery-42__heading[data-animation-type="line-reveal-by-space"] span {
    line-height: 40px;                          /* 32 x 1.25, the h2 ratio */
  }
  .mk-recovery-42__body { font-size: 17px; }
  .mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] {
    height: 260px;
    padding: var(--mk-space-24);
  }
  .mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] .heading-wrap h4 {
    font-size: 20px;
    line-height: 36px;                          /* 20 x 180% */
  }
  .mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"] .heading-wrap p {
    margin-top: 17.7px;                         /* keeps the 57 -> ~47 baseline
                                                   gap the spec derives */
    font-size: var(--mk-size-base);             /* 16 */
    line-height: 28.8px;                        /* 16 x 180% */
  }
}


/* ############################################################################
   ###  MK-RPLAN — the 4.x RECOVERY-PLAN COMPONENT  (D23 · D24 · D25 · D26)  ###
   ############################################################################

   FOUNDER TRACE FT-20260817, section "4.x RECOVERY", donor
   http://127.0.0.1:5010/salient/resort/index.html  #fws_6a36b6c777fae
   ("Single Suites 1/3").  The founders' notes spell the id `fws_6a366c777fae`;
   every fws id in their Figma notes drops the same two characters (D14's
   `6a366c77e458` is really `6a36b6c77e458`), so this is the right node and it
   was verified by content, not by the string.

   WHAT THE DONOR ACTUALLY IS — and it is NOT what this build had.
   `.nectar-scrolling-tabs` is a STICKY LEFT RAIL beside a STACK of full-height
   image panels. `.scrolling-tab-nav` is pinned (Salient pins it with `n-sticky`
   / stickkit.js); `.scrolling-tab-content > .wpb_tab` each hold one
   `.column-image-bg` behind a `divider` of `height: 90vh`. You scroll, the
   images cycle one full screen at a time, and the rail does not move. That is
   the mechanic the founders described, almost word for word:

     D23  "Image must bleed past margin/padding on the right; left content half,
           right image edge-to-edge vertically and half-container horizontally."
     D24  "Content on the LEFT stays static while images cycle; content changes
           only when the section (and its number) increments."
     D25  per-stage image counts 2 / 2 / 3 / 3 / 3.
     D26  "Recovery-plan images look stretched — must be correctly sized to a
           full vertical half-container."

   WHAT THIS BUILD HAD INSTEAD: five sections, each a 563px text rail beside a
   VERTICAL STACK of 450px-tall cards inside one 912 / 1366px band. Every image
   was a letterbox, every card carried its own heading, and nothing was sticky.
   D23/D24/D26 are three readings of that one substitution.

   WHY THE DONOR'S CLASS NAMES ARE NOT USED HERE, given CONVENTIONS §0 says to
   lift: `.nectar-scrolling-tabs` CSS is NOT in `vendor/`. It exists only inside
   the resort page's WordPress-generated inline <style>, and the only vendored
   file that mentions the class is one flickity overflow rule. Wearing the donor
   class names would therefore buy zero behaviour and would falsely advertise a
   lift. What IS vendored and IS used verbatim is the layer structure Salient
   uses for a column photograph — `.column-image-bg-wrap > .inner-wrap >
   .column-image-bg` with `data-bg-pos` — which grid-system.css sizes, and which
   sibling section 08-recovery already runs. `position: sticky` replaces
   `n-sticky`/stickkit for the rail: it is the same behaviour, declaratively,
   and it is what stickkit was polyfilling before browsers had it.

   >>> ASSET GAP, MEASURED, NOT RESOLVED BY THIS LANE. <<<
   Every recovery photograph on disk is 1376 x 768 — a 1.79:1 LANDSCAPE crop
   exported from Figma, where the design box was a 717 x 450 letterbox (1.59:1).
   A full-vertical half-container is 0.71:1 PORTRAIT (640 x 900 at 1280 x 900).
   `cover` therefore keeps ~29% of the source width and scales it UP by 1.17x;
   at 2x DPR the panel wants 1280 x 1800 and the source has 1376 x 768. The
   framing is right and the resolution is not. This is an asset request, not a
   CSS bug — see founder-trace/SHARED-REQUESTS.md REQ-BR-3 and NOTES.md.
   `cover` is still correct: `contain` would letterbox (violating D23's
   edge-to-edge) and `fill` is the literal stretch D26 is complaining about.

   THIS BLOCK IS SHARED BY SECTIONS 09, 10, 11, 12 AND 13. It lives in the
   first of the five because compose.py emits section stylesheets in page order
   and there is no shared sheet this lane owns. Its home is
   `css/myk-recovery-plan.css`; that is REQ-BR-2 in SHARED-REQUESTS.md. Every
   rule below is CLASS-level on purpose so each section's own file can still
   win with its id where the geometry is genuinely section-local.
   ########################################################################## */

/* -- 1. THE SPLIT.  D23: "half-container horizontally". 50/50 of the FULL
      BLEED section, so the right column's right edge IS the viewport's right
      edge — that is the "bleeds past margin/padding" half of D23. The 120px
      page margin survives as the rail's own left padding and nowhere else. */
.mk-rplan__grid {
  display: grid;
  grid-template-columns: 50% 50%;
  align-items: start;
  gap: 0;
}

/* -- 1b. THE MISSING LIFTED-INLINE RULE THAT MAKES `position: sticky` WORK.
      --- LIFTED-INLINE: verbatim from the resort page's inline <style>
          (salient-truth/resort/assets/inline/inline-style-3.css). ---

          html body, html body.compensate-for-scrollbar { overflow: visible; }

      Salient's style.css ships `body { max-width:100%; overflow:hidden; … }`
      and WordPress then emits the override above into the page's own inline
      <style>. Our vendor pass took only <link>ed stylesheets, so the base rule
      came across and the override did not — the same gap that
      sections/07-building.css already documents for three other rules under the
      heading "generated per page by WordPress and therefore was NOT in the
      vendored files".

      It is not cosmetic. `body { overflow: hidden }` makes <body> a scroll
      container whose scroll offset is always 0 (html is what actually scrolls,
      `overflow: hidden scroll`). Every `position: sticky` inside <body> then
      resolves against that non-scrolling scrollport and never sticks. MEASURED
      on this page before the rule was restored: the rail's viewport top tracked
      scroll 1:1 at -0 / -850 / -1750 / -2650, i.e. zero stickiness, with no
      overflow, transform, filter or contain on any ancestor of the rail.
      MEASURED on the live donor at the same moment: `body` computes to
      `overflow: visible` and `.scrolling-tab-nav` computes to
      `position: sticky` — the donor uses plain CSS sticky and it works there
      for exactly this reason.

      SCOPE: this is a <body> rule and cannot be section-scoped, so it does not
      belong in a section file. Its home is css/salient-elements.css, which is
      already where this build hoists LIFTED-INLINE rules that turn out to be
      page-wide (the .nectar-split-heading block, see BLOCK A above). Filed as
      REQ-BR-4 in founder-trace/SHARED-REQUESTS.md. Delete from here when it
      lands there. `html body` is specificity (0,0,2) and beats Salient's
      `body` (0,0,1) on its own; no !important. */
html body,
html body.compensate-for-scrollbar { overflow: visible; }

/* -- 2. THE RAIL.  D24: "content on the LEFT stays static while images cycle."
      One screen tall and pinned, so it is on screen for the whole stack and
      leaves with it. `top: 0` and not a header offset: the nav is transparent
      and overlays, and the copy is vertically centred inside the screen, so a
      header inset would push it off-centre by half the header height. */
.mk-rplan__rail {
  position: sticky;
  top: 0;
  height: 100svh;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  padding: var(--mk-page-x);          /* 120 — unchanged from the Figma rail */
  padding-right: var(--mk-space-60);  /* the old rail was 563 wide with 120 both
                                         sides; at 50% it is 640, so the inner
                                         gutter to the photo relaxes to 60 and
                                         the measure grows 323 -> 460. The text
                                         SIZES are untouched; only the measure. */
}
.mk-rplan__rail > * { max-width: 100%; }

/* -- 3. THE STAGE.  No padding, no gaps, no radius: D23 says edge-to-edge, and
      every 4px inset and 4px gutter in the old build was a Figma value for a
      band that no longer exists. */
.mk-rplan__stage {
  display: block;
  padding: 0;
  gap: 0;
  border-radius: 0;
  min-width: 0;
}

/* -- 4. THE PANEL.  One image, one screen. `scroll-snap-align` makes the PANEL
      the snap unit rather than the section (G2: "a flick — whole-slide, never a
      half-visible in-between"); `scroll-snap-type` on the scroll container is
      page-global and is NOT set here — see SHARED-REQUESTS.md REQ-BR-1. */
.mk-rplan__panel {
  position: relative;
  height: 100svh;
  width: 100%;
  margin: 0;
  padding: var(--mk-space-60);        /* caption inset 60 — the one Figma value
                                         that carries over unchanged */
  box-sizing: border-box;
  border-radius: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;          /* caption bottom-anchored, as authored */
  align-items: flex-start;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/* Panel children stretch. `align-items: flex-start` on a flex COLUMN sets the
   cross axis, i.e. WIDTH — it was shrinking 13's `.vc_column-inner` to its text
   measure, 399px inside a 640px panel, which took the photograph with it
   (measured: `.column-image-bg` 399 x 900 at left 640, leaving a 241px band of
   section ground down the right edge — the exact thing D23 says must not
   happen). The caption is left-aligned by its own text-align, not by the
   panel's cross-axis alignment. */
.mk-rplan__panel > * { align-self: stretch; width: 100%; }

/* ENTRANCE ANIMATIONS ARE OFF INSIDE A PANEL, and this is a consequence of G2,
   not a preference. Salient arms `.wpb_column.has-animation` at `opacity: 0`
   in CSS and reveals it on a Waypoint at 88% of the viewport. When the panels
   were 450px letterboxes, three of them crossed that line inside one screen of
   ordinary scrolling. Now each panel is a full screen AND the interaction the
   founders specified is a FLICK — "reaching a slider snaps you to it; the
   scroll wheel advances slides" — so the normal way to arrive at panel 3 is a
   jump that never crosses panel 3's trigger. Caught exactly that way: 13's
   caption column measured `opacity: 0` on a snap-landing and its text was
   simply absent from the frame. The `has-animation` classes are removed from
   the panel markup so Salient never arms them; this rule is the safety net for
   anything that still carries one. */
.mk-rplan__panel .wpb_column.has-animation,
.mk-rplan__panel.has-animation {
  opacity: 1 !important;        /* Salient writes the pre-state INLINE from JS
                                   (CONVENTIONS §8.6 — name the rule that forced
                                   the !important: style.css
                                   `.wpb_column.has-animation{opacity:0}` plus
                                   init.js's inline armour). */
  transform: none !important;
}

/* -- 5. THE PHOTOGRAPH.  D26. `cover` + `center`, and it is stated on every
      one of the three markup shapes the five sections use, because they were
      built from three different Salient components (fancy-box `.box-bg`,
      column bg `.column-image-bg`, post-grid `<img>`). */
.mk-rplan__panel > .mk-rplan__img,
.mk-rplan__panel .box-bg,
.mk-rplan__panel .column-image-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 0;
  transform: none;
}
.mk-rplan__panel .nectar-post-grid-item-bg img,
.mk-rplan__panel .nectar-post-grid-item-bg img:not([srcset]) {
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  object-fit: cover;                  /* NOT `fill` — see §6 below */
  object-position: center;
}

/* -- 6. THE SCRIM.  DELIBERATELY NOT REDECLARED.
      Every one of the five sections already paints Figma's own gradient
      (#00000000 -> #000000bf, full height, bottom-anchored) on whichever
      pseudo-element its donor component provides — `.box-bg::before` on 09 / 11
      / 12, `.inner::before` on 10's post-grid, `.column-bg-overlay` on 13. Those
      rules are Figma-verified per section and they still match, because the
      panel keeps its original element. Adding a second gradient here would
      double the density and read as a muddy photo, which is a new defect
      invented while fixing an old one. All this block does is make sure the
      existing scrim covers the panel now that the panel is a screen tall. */
.mk-rplan__panel .box-bg::before,
.mk-rplan__panel .box-bg::after,
.mk-rplan__panel > .inner::before,
.mk-rplan__panel .column-bg-overlay { height: 100%; }

/* Keep the caption above the photo and the scrim. `.box-bg` is z-index 1 and
   its gradient pseudo-elements are z-index 10 in element-fancy-box.css, so the
   caption needs to clear 10, not 1. */
.mk-rplan__panel > .inner,
.mk-rplan__panel > .content,
.mk-rplan__panel > .mk-rplan__caption { position: relative; z-index: 11; }

/* -- 7. THE CAPTION. Type sizes are NOT restated — each section already has its
      own Figma-verified caption rules and those stay authoritative. Only the
      box changes: it used to be `fill` of a 717-wide card, it is now a measure
      inside a 640-wide panel. */
.mk-rplan__panel .mk-rplan__caption,
.mk-rplan__panel .heading-wrap,
.mk-rplan__panel .content,
.mk-rplan__panel .item-main {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 100%;
  padding: 0;
  margin: 0;
}

/* -- 8. RESPONSIVE. Below 1000 the 50/50 split stops being readable: a 500px
      column of 20px body at a 460 measure is fine, a 375px one is not. The
      component stacks — the rail becomes a SOLID top strip above the panels
      (FT-20260830 mobile pass: "text on top, images below"). `sticky`, not
      static: with JS the engaged rail is promoted to fixed at the same spot,
      so the sticky/fixed handoff at the band's edges is a visual no-op —
      and withOUT JS the strip still holds over its own images, which is the
      closest no-JS reading of the founder's design. The solid ground is why
      a rail may sit over panels at all; z-index 12 clears the caption z:11
      (a panel at opacity 1 is not a stacking context, so captions compete
      globally). INFERRED: Figma authors 1280 and 1440 only. */
@media (max-width: 999px) {
  .mk-rplan__grid { grid-template-columns: 100%; }
  .mk-rplan__rail {
    position: sticky;
    top: 0;
    z-index: 12;
    height: auto;
    padding: var(--mk-space-60) var(--mk-space-24);
    background: var(--mk-surface-mist);
    justify-content: flex-start;
  }
  .mk-rplan__panel { height: 72svh; padding: var(--mk-space-24); }
}

/* Motion safety: a sticky rail is not motion, but a snap container is, and a
   user who has asked for reduced motion should not have the page taking the
   scroll away from them. */
@media (prefers-reduced-motion: reduce) {
  .mk-rplan__panel { scroll-snap-align: none; scroll-snap-stop: normal; }
}

/* ==========================================================================
   09 · MK-RPLAN BINDING — D23 / D24 / D26
   The class-level component above cannot reach past this file's own id rules,
   so the section-local geometry that the retired 912px band needed is undone
   here, at the same id specificity, at the end of the file.
   RETIRED, and why each one has to go:
     .mk-recovery-42__row      display:flex + align-items:center — a flex row
                               cannot make the rail sticky against a taller
                               sibling; the grid can.
     .mk-recovery-42__rail     flex:0 0 auto at mk-measure-563 — D23 says HALF.
     .mk-recovery-42__copy     min-height 672 (= 912 - 240) — that arithmetic
                               belongs to a band height that no longer exists.
     .nectar-fancy-box         height 450 — the letterbox D26 is about.
     .mk-stack-4               4px pad + 4px gutter — the "edge-to-edge"
                               violation named in D23. (Class removed in markup.)
   ========================================================================== */
#s09-recovery-42 { min-height: 100svh; }
#s09-recovery-42 .mk-recovery-42__row   { display: grid; align-items: start; }
#s09-recovery-42 .mk-recovery-42__rail  { flex: none; padding: var(--mk-page-x); padding-right: var(--mk-space-60); }
#s09-recovery-42 .mk-recovery-42__copy  { min-height: 0; width: 100%; }
#s09-recovery-42 .mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"],
#s09-recovery-42 .nectar-fancy-box[data-style="hover_desc"].mk-rplan__panel {
  height: 100svh;
  padding: var(--mk-space-60);
  border-radius: 0;
}
/* The caption measure was `717 - 120` = 597 "fill" of the old card. In a 640
   panel with a 60 inset the fill is 520; leave it fill, do not re-pin 597. */
#s09-recovery-42 .nectar-fancy-box[data-style="hover_desc"] .heading-wrap { width: 100%; }

/* ############################################################################
   ###  R3 · MK-RPLAN, ROUND-3 PASS   (D05 · D06 · D07 · A10 · the donor)    ###
   ############################################################################
   FOUNDER TRACE FT-20260819, lane r3:recovery-plan.  Appended rather than
   edited in place so the D23/D24/D26 block above stays readable as the
   round-2 record — every rule here that walks one of those back says so.

   THIS BLOCK IS SHARED BY 09, 10, 11, 12 AND 13, exactly like the component
   above it, and for the same reason (compose.py emits section stylesheets in
   page order; there is no shared sheet this lane owns).  Its home is still
   css/myk-recovery-plan.css — SHARED-REQUESTS.md REQ-BR-2.

   ONE THING TO KNOW BEFORE READING: css/_band-xl.css is linked AFTER every
   sections/*.css (index.html lines 141-145 vs 169), so a class-level rule in
   this file cannot beat it.  Where this block has to win against band-xl it
   uses the section ids, which it does at (1,1,0) vs band-xl's (0,1,0).
   ######################################################################### */

.mk-rplan {
  /* ---- D06.  "I'd expect to see the first one and also kind of see the
     second one... I'd drop the height on the images on the right a little so
     even when you're seeing the first one you're seeing hints of the second,
     much like the component on the Salient resort one. This might also solve
     some of the cropped image issue."

     WHY 80svh AND NOT THE DONOR'S 90vh.  The donor's stack is
     `<div style="height: 90vh" class="divider">` per panel with
     `margin-bottom: 20px` from
     `.navigation_func_active_link_only[data-tab-spacing="20px"]
      .scrolling-tab-content > div`, so at a 900px viewport it shows 810 + 20 +
     a 70px sliver.  Figma's gutter is 4, not 20 (D07), so copying 90vh here
     would leave an 86px sliver — a hint, but it would leave the SECOND half of
     her sentence unpaid.  D05/D06 are one complaint: the panels are portrait
     and the photographs are landscape.

     MEASURED, at 1512x900, panel box 756x<h>, source 1376x768.  The cover
     scale is h/768 and the retained width is 756/(1376 x scale):
       height 1080 (rhythm)  1.406x UP,  keeps 39.1% of the source width
       height  900 (round 2) 1.172x UP,        46.9%  <- what she is looking at
       height  810 (donor)   1.055x up,        52.1%
       height  720 (this)    0.9375x DOWN,     58.6%  — no upscale at all
     720 is the largest panel that does not enlarge a single pixel of the
     source at 1x, and it hands back a quarter more of every photograph.  At a
     900 viewport that leaves 900 - 720 - 4 = 176px of the next image showing,
     which is the hint she asked for and then some.  At 1728x1030 the panel is
     864x824: scale 1.073, 58.5% kept, 202px of hint.
     Expressed as svh, not px, so it stays breakpoint-driven (B07). */
  --mk-rplan-panel-h: 80svh;

  /* ---- D07.  "Each image needs a 4px rounded corner and a 4px gap between
     them, per Figma."  Both numbers are Figma's own and both were zeroed by
     round 2 (block above, §3: "No padding, no gaps, no radius").
       figma-truth/09-recovery-42.spec.md §1.4:
         "Net visible radius per card: `border-radius: 4px 0 0 4px`.
          Do not round the right side."
       …spec §Tokens: "Card stack gutter | 4 px | `Images` itemSpacing"
       …spec §1.5 arithmetic: 4 + 450 + 4 + 450 + 4 = 912 exactly
     The right pair of corners stays square because the card runs off the
     right edge of the screen — rounding an edge you cannot see puts a 4px
     notch of section ground in the corner of the viewport. */
  --mk-rplan-gap: 4px;
  --mk-rplan-radius: 4px;

  /* Donor timings, so the CSS and js/myk-recovery-plan.js cannot drift.
     Read out of the resort page, not chosen:
       out 0.3s / in 0.6s + 0.1s delay / counter 0.7s, all
       cubic-bezier(.25,1,.5,1), all ±18px. */
  --mk-rplan-ease:      cubic-bezier(.25, 1, .5, 1);
  --mk-rplan-out-dur:   .3s;
  --mk-rplan-in-dur:    .6s;
  --mk-rplan-in-delay:  .32s;   /* 2026-09-02 founder: the old title must be fully out (.3s) before the new one starts */
  --mk-rplan-num-dur:   .7s;
  --mk-rplan-shift:     18px;

  /* A10 pacing. "Take a little more time to get into that section." R3-G's
     target feel is "SLOWER, more open, more relaxed than ours." */
  --mk-rplan-arrive-dur: 1.1s;
}

/* ==========================================================================
   R3-1 · THE PANEL BOX — D06 + D07
   ========================================================================== */
.mk-rplan__panel {
  height: var(--mk-rplan-panel-h);
  margin: 0 0 var(--mk-rplan-gap);
  border-radius: var(--mk-rplan-radius) 0 0 var(--mk-rplan-radius);

  /* REQ-SCROLL-01 + FT-20260819 C01/C03. Round 2 put `scroll-snap-align:
     start; scroll-snap-stop: always` on all thirteen panels and left the
     container's `scroll-snap-type` to another owner. MEASURED now:
     html and body both compute `scroll-snap-type: none`, so the thirteen
     declarations are inert — but they are thirteen armed charges waiting for
     any lane that sets a page-global snap, and the founder's verdict on the
     last one was "it grabs my screen and makes me go there… super janky…
     too rigid." Disarmed here rather than left to luck. Not one of the five
     Salient donors uses scroll-snap anywhere. */
  scroll-snap-align: none;
  scroll-snap-stop: normal;
}
/* `overflow: hidden` on the panel already clips to the radius; this is for the
   two shapes that paint their photo on a layer with its own radius reset
   (post-grid `.inner`, fancy-box `.box-bg`). */
.mk-rplan__panel > .mk-rplan__img,
.mk-rplan__panel .box-bg,
.mk-rplan__panel .column-image-bg,
.mk-rplan__panel .nectar-post-grid-item-bg,
.mk-rplan__panel > .inner { border-radius: inherit; }

/* Figma's rail padding is 4 top / 0 sides / 0 bottom, and every panel carries
   its own 4px bottom margin, so the stack reads
   4 + h + 4 + h + 4 = 2h + 12 — the spec's own arithmetic, with the last
   panel's margin standing in for the container's bottom padding.
   The `padding-block` declaration that actually ships is in block R3-5, where
   it is wrapped in the `--mk-rplan-stage-pad` hook B04 needs; this line is the
   Figma value it is built from. */

/* ==========================================================================
   R3-2 · D04 — THE IMAGE RAIL MUST REACH THE RIGHT EDGE OF THE BROWSER
   "Recovery 'Step 1 of 5 / recover in a safe environment' is not flush to the
    right edge of the browser; stray right margin/padding. Correct at reduced
    width, wrong at hers."

   ROOT CAUSE, MEASURED HERE INDEPENDENTLY: `css/_band-xl.css` §1 puts
   `padding-inline: var(--mk-xl-inset)` on all five recovery rows above 1440,
   where `--mk-xl-inset: calc(50% - min(100vw,1440px)/2)`.  At 1512 that is
   36px, and it is 36px of section ground down the right-hand side of every
   recovery photograph; at 1280 it is 0, which is why she saw it at her width
   and not at a reduced one.  `padding-inline` is BOTH sides: the LEFT half is
   correct (it keeps the rail's ink on the page gutter above 1440, which is
   what that file is for) and the RIGHT half is the defect, because this
   component's right column is full-bleed by design — FT-20260817 D23, twice.

   `r3:vertical-rhythm` found the same thing and wrote the better-shaped fix in
   css/rhythm.css §4 (keep `padding-left`, zero only `padding-right`).  THAT
   FIX IS CURRENTLY DEAD, and the reason is worth recording because it is
   invisible from the source: css/rhythm.css line 408 opens a prose comment and
   line 417 inside it quotes a declaration WITH ITS OWN TRAILING COMMENT, i.e.
   an opener and a closer nested inside the outer one.

   CSS COMMENTS DO NOT NEST.  The inner closer ends the outer comment, the
   prose after it is parsed as declarations, and the parser never recovers:
   MEASURED in the live CSSOM, css/rhythm.css contributes 3 rules and its
   entire §4 (the D04 fix) — and its §2 recovery block with it — is absent.
   Reported as REQ-BR-9 in founder-trace/SHARED-REQUESTS.md; it is their file
   and their one-character fix, not this lane's to make.

   THIS LANE WROTE THE SAME BUG INTO THIS VERY COMMENT while describing it, and
   lost the block below to it for one build.  That is why the verification for
   this file is a live CSSOM RULE COUNT and not a reading — a stylesheet that
   silently drops its own tail looks identical to one that was never edited.
   The check is `document.styleSheets[i].cssRules.length`; the scanner is
   scratchpad/nestscan.py.

   SO THIS BLOCK IS BOTH THE FIX AND THE BELT.  It is written to be IDEMPOTENT
   with rhythm.css §4 once that block revives: `padding-inline: 0` at id
   specificity (1,1,0) out-ranks both _band-xl.css and rhythm.css (both
   (0,1,0)), and the gutter is re-applied on the RAIL, so ink-left computes to
   the same 120 / 156 / 240 / 360 at 1280 / 1512 / 1680 / 1920 under either
   regime and the photo reaches the glass under both.
   The pinned rail in block R3-A does NOT depend on this: it measures its own
   box off the stage, so it stays correct whichever padding wins.
   ========================================================================== */
@media (min-width: 1441px) {
  #s09-recovery-42 .mk-recovery-42__row,
  #s10-recovery-41,
  #s11-recovery-43 .mk-recovery-43__split,
  #s12-recovery-44,
  #s13-recovery-45 > .wpb_row > .row_col_wrap_12 { padding-inline: 0; }
}
/* `max(0px, …)` makes this a no-op at and below 1440, so the five rail rules
   that consume it need no breakpoint of their own. */
.mk-rplan { --mk-rplan-gutter: calc(var(--mk-page-x) + max(0px, (100vw - 1440px) / 2)); }
/* The rail declaration itself CANNOT live here for the other four. Each of
   them re-states `padding: var(--mk-page-x)` (the shorthand, which resets
   padding-left) at the same id specificity in its own file, and those files
   are linked after this one — so a rail rule written here would be silently
   overwritten by four of the five. One line per file, at the end of each,
   is the only placement that wins. 09's is at the end of this file. */

/* ==========================================================================
   R3-3 · D05 — THE PHOTOGRAPHS
   "Recovery images: lower quality, poorly placed in their containers;
    'remediation' is lost entirely at her resolution."
   THREE SEPARATE DEFECTS WERE READING AS ONE:
     (a) UPSCALE.  Every source on disk is 1376x768. `cover` into a 720x900
         panel scales it UP 1.172x. R3-1's 80svh panel makes that 0.9375x — a
         downscale — so nothing is enlarged at 1x any more. That is the
         "lower quality" half, and it is fixed by geometry, not by a filter.
     (b) PLACEMENT.  `background-position: center` on a 1.79:1 landscape source
         inside a portrait box throws away the outer 44% of every frame from
         BOTH sides. These photographs are not centre-weighted: the remediation
         technician sits at 0.72-0.87 of the frame, the doctor at 0.72-0.92,
         the IV pole at 0.78-0.88. Centre-cropping deletes the subject and
         keeps the empty wall — literally "'remediation' is lost entirely."
         Each override below was chosen by rendering the actual 720x720 crop of
         the actual asset and looking at it, not by guessing; the simulation is
         at scratchpad/crop-sim2.png. The value is CSS `background-position-x`,
         i.e. the fraction of the overflow scrolled past, so a larger number
         moves the window RIGHT.
     (c) RESOLUTION.  10-recovery-41's two assets are 55KB and 37KB JPEGs at
         1376x768 — the most heavily compressed images on the page, and the
         only two recovery photos with no .webp/.png sibling. (a) and (b) are
         fixed here; (c) is an asset request, REQ-BR-3 / REQ-BR-7.
   Vertical stays centred everywhere: all thirteen subjects are on the
   horizon line of their frame.
   ========================================================================== */
#s09-recovery-42 .mk-recovery-42__card--1 .box-bg { background-position: 58% center; }
#s09-recovery-42 .mk-recovery-42__card--2 .box-bg { background-position: 86% center; }

/* ==========================================================================
   R3-4 · A10 — "Images fade in as their section arrives. Take a little more
   time to get into that section."
   OPACITY ONLY.  R3-G reads the pacing reference as "text scrolls up + fades
   IN, then fades OUT, while BACKGROUNDS STAY IN PLACE", so the photograph must
   not travel. A translate here would also collide with the sticky rail at a
   seam and put a 12px jitter into the one moment this component has to be
   calm. Armed by js/myk-recovery-plan.js — with the script absent the panels
   are simply opaque, which is the pre-R3 page.
   ========================================================================== */
.mk-rplan-js .mk-rplan__panel.mk-rplan-arrive {
  opacity: 0;
  transition: opacity var(--mk-rplan-arrive-dur) var(--mk-rplan-ease);
}
.mk-rplan-js .mk-rplan__panel.mk-rplan-arrive.is-arrived { opacity: 1; }

/* ==========================================================================
   R3-A · THE DONOR ANIMATION — Salient `nectar-scrolling-tabs`
   "the text on the left side actually animated quite differently, where it
    kind of does that up-and-fade-in animation on the text as it changes the
    number. Your animation looks quite different where it more just moves into
    the next section. Can you grab that from the theme and match it correctly?"
   DONOR: http://127.0.0.1:5010/salient/resort/index.html — the
   "Single Suites / Private Cabins / Ocean Villas   1 2 3 /3" row she named by
   its `class row-bg viewport-desktop` wrapper. Local byte-identical copy at
   salient-truth/resort/index.html; the four keyframe blocks below are COPIED
   OUT of that page's inline <style> (char 64985+) with only the names
   prefixed, because the donor's CSS is WordPress page-inline output and is
   therefore not in vendor/ — wearing its class names would advertise a lift
   that could not work. The mechanism is lifted; the selector is ours.
   WHY THE OLD ONE LOOKED LIKE "moves into the next section" is a LAYOUT
   fact and it is written up in js/myk-recovery-plan.js, not here.
   ========================================================================== */
@keyframes mkRplanTabOutUp   { 0% { opacity: 1; transform: translateY(0); }
                             100% { opacity: 0; transform: translateY(calc(-1 * var(--mk-rplan-shift))); } }
@keyframes mkRplanTabInUp    { 0% { opacity: 0; transform: translateY(var(--mk-rplan-shift)); }
                             100% { opacity: 1; transform: translateY(0); } }
@keyframes mkRplanTabOutDown { 0% { opacity: 1; transform: translateY(0); }
                             100% { opacity: 0; transform: translateY(var(--mk-rplan-shift)); } }
@keyframes mkRplanTabInDown  { 0% { opacity: 0; transform: translateY(calc(-1 * var(--mk-rplan-shift))); }
                             100% { opacity: 1; transform: translateY(0); } }

/* THE PIN.  Donor: `.scrolling-tab-nav { position: sticky; top: var(
   --nectar-sticky-tabs-vert-y) }` over the WHOLE tab stack. We have five
   sections, so sticky tops out at one section and the rail then scrolls away —
   the defect. `fixed` for the duration of the band is the same pinned rail.

   `left` AND `width` ARE MEASURED, NOT ASSUMED, and that is the whole reason
   this works after css/rhythm.css §D04. The obvious declaration is
   `left: 0; width: 50%`, because the grid is `50% 50%` — and it is WRONG the
   moment anything insets the row. rhythm.css keeps `padding-left:
   var(--mk-xl-inset)` above 1441, so at 1512 the grid's content box starts at
   x=36 and each column is 738, not 756. A rail that pinned to `left:0;
   width:50%` would jump its ink 36px left and grow 18px wide at the exact
   moment the founder is looking at the text — a defect INVENTED by the fix for
   another one. js/myk-recovery-plan.js measures the in-flow stage (the stage is
   never pinned, so it is always honest) and writes the two values here:
       --mk-rplan-pin-x = stage.left - stage.width      (the column's left)
       --mk-rplan-pin-w = stage.width                   (the column's width)
   recomputed on resize. The fallbacks are the naive values, so a JS failure
   degrades to something plausible rather than to nothing.
   z-index stays low: the site header must still pass over it. */
.mk-rplan-js .mk-rplan[data-rplan-rail] > .mk-rplan__grid > .mk-rplan__rail,
.mk-rplan-js .mk-rplan[data-rplan-rail] .mk-rplan__grid > .mk-rplan__rail,
.mk-rplan-js .mk-rplan[data-rplan-rail] > .mk-rplan__rail,
.mk-rplan-js .mk-rplan[data-rplan-rail] > .wpb_row > .mk-rplan__grid > .mk-rplan__rail {
  position: fixed;
  top: 0;
  left: var(--mk-rplan-pin-x, 0px);
  width: var(--mk-rplan-pin-w, 50%);
  height: 100svh;
  z-index: 5;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="hidden"] .mk-rplan__rail,
.mk-rplan-js .mk-rplan[data-rplan-rail="hidden"] > .mk-rplan__rail {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* THE SWAP.  Donor, verbatim timings:
     li.prev-active:not(.active) { animation: out 0.3s …ease forwards }
     li.active                   { animation: in  0.6s …ease forwards 0.1s;
                                   opacity: 0 }
   `opacity: 0` on the incoming lane is the donor's own pre-state and it is why
   the swap never flashes: the element is transparent from the moment the class
   lands until its 0.1s delay expires. */
.mk-rplan-js .mk-rplan[data-rplan-rail="out-up"]   .mk-rplan__lane {
  animation: mkRplanTabOutUp var(--mk-rplan-out-dur) var(--mk-rplan-ease) forwards;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="out-down"] .mk-rplan__lane {
  animation: mkRplanTabOutDown var(--mk-rplan-out-dur) var(--mk-rplan-ease) forwards;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="in-up"]    .mk-rplan__lane {
  opacity: 0;
  animation: mkRplanTabInUp var(--mk-rplan-in-dur) var(--mk-rplan-ease) var(--mk-rplan-in-delay) forwards;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="in-down"]  .mk-rplan__lane {
  opacity: 0;
  animation: mkRplanTabInDown var(--mk-rplan-in-dur) var(--mk-rplan-ease) var(--mk-rplan-in-delay) forwards;
}

/* THE EYEBROW LANE fades but does not travel. The donor keeps its counter
   OUTSIDE the animated <li> for exactly this reason — a number that is rolling
   0.7s while its own box slides 18px is unreadable. Same fade window as the
   copy so the two arrive together. */
.mk-rplan-js .mk-rplan[data-rplan-rail] .mk-rplan__lane[data-rplan-lane="eyebrow"] {
  animation: none;
  transition: opacity var(--mk-rplan-out-dur) var(--mk-rplan-ease);
}
.mk-rplan-js .mk-rplan[data-rplan-rail^="out"] .mk-rplan__lane[data-rplan-lane="eyebrow"] { opacity: 0; }
.mk-rplan-js .mk-rplan[data-rplan-rail^="in"]  .mk-rplan__lane[data-rplan-lane="eyebrow"],
.mk-rplan-js .mk-rplan[data-rplan-rail="active"] .mk-rplan__lane[data-rplan-lane="eyebrow"] { opacity: 1; }

/* THE COUNTER ROLL. Donor markup and CSS, reproduced exactly:
     .current  { overflow:hidden; position:relative; padding-right:6px }
     .current .inner { display:block; transition: transform .7s …ease }
     .current span:not(:first-child) { position:absolute; top:0; left:0 }
   with an inline `top: N00%` on each digit, which beats that `top: 0` and is
   what stacks them. js/myk-recovery-plan.js builds the digits and drives
   `translateY(-N00%)`, the same one line `updateActiveTitle()` writes. */
.mk-rplan__num {
  position: relative;
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-right: 1px;                 /* donor pads 6px; our glyphs are tabular
                                         and the eyebrow is letter-spaced 3px,
                                         so 6 would open a visible hole */
}
.mk-rplan__num-inner {
  display: block;
  transition: transform var(--mk-rplan-num-dur) var(--mk-rplan-ease);
}
.mk-rplan__num-inner > span { display: block; }
.mk-rplan__num-inner > span:not(:first-child) { position: absolute; left: 0; }
.mk-rplan__num-sep { white-space: pre; }

/* MOTION SAFETY. The pin is layout, not motion, so it stays — a user who asked
   for less motion still wants the rail to hold rather than slide. Everything
   that moves is off. */
@media (prefers-reduced-motion: reduce) {
  .mk-rplan-js .mk-rplan[data-rplan-rail] .mk-rplan__lane { animation: none; opacity: 1; }
  .mk-rplan__num-inner { transition: none; }
  .mk-rplan-js .mk-rplan__panel.mk-rplan-arrive { opacity: 1; transition: none; }
}

/* ==========================================================================
   R4-M · THE MOBILE STACK (<=999px) — round 9 (FT-20260901): the donor's
   OWN CSS, ported. Rounds 4-8 re-implemented the cited component
   (/salient/portfolio-layered/, `nectar-post-grid.layout-stacked`
   `blurred_scale`) with invented machinery — a position:fixed rail, a
   per-frame `--pinned` class, stage margin-top reservations, per-count
   min-heights — and every round broke in a new way. The founder's round-9
   direction: "you should literally apply the CSS from the salient link I
   gave you for the cards." Done. Donor rules, read verbatim out of that
   page's own inline <style> and adapted only in names and numbers:

       .layout-stacked .nectar-post-grid-item { position: sticky;
                                                top: calc(40px); margin: 0; }
       .nectar-post-grid.layout-stacked[data-stack-animation-effect=
         "blurred_scale"] { transform-origin: top; }
       .layout-stacked .nectar-post-grid-item:not(:last-child)
         { will-change: transform, filter; }   (JS sets this inline)
       grid-row-gap between items; border-radius 25px
         (--post-grid-border-radius: 25px on the donor container)

   Every card sticks at ONE shared top (below this section's own text),
   the next card slides up OVER it at full opacity (DOM paint order — no
   entrance animation of any kind, the donor has none), and the covered
   card is scrubbed scale 1->.8 / blur 0->5px by js/myk-recovery-plan.js's
   direct port of the donor's raf(). Opacity is never touched.

   THE TEXT is ordinary sticky section content again (base mobile block
   §8 above already states `position: sticky; top: 0` with the mist
   ground) — no fixed pin, no reservation arithmetic, no double-counted
   flow. Its entrance is the site-standard staggered system; its exit is
   a short scroll-scrubbed fade at section end (see the JS).

   Geometry per the founder's Pixel-7 mock: ~24px text-to-card gap
   (--mk-rplan-card-top = measured rail height + 10, + the 14px strip-bot
   = 24 visual), card an inset rounded box whose bottom sits ~24px off the
   viewport bottom, --mk-rplan-card-gap of ground between cards (the
   donor's grid-row-gap, scaled up to buy reading hold — "it stays there
   for a moment, gives me an opportunity to read it"), and a longer
   --mk-rplan-tail after a section's last card so this section's text has
   fully faded before the next section's text enters ("you're not fading
   out one before you replace it... you end up with double text").
   Strip padding vars are consumed by each file's own id-ranked mates.
   ========================================================================== */
@media (max-width: 999px) {
  .mk-rplan {
    --mk-rplan-strip-top: 72px;   /* header band measured at 412: top 24 +
                                     h 40 = 64; 72 clears it by 8. Was 88 —
                                     SEEN (eyes.py, Pixel 7): the header
                                     hides on scroll-down, so the extra 16
                                     read as dead space over the overline */
    --mk-rplan-strip-bot: 14px;
    --mk-rplan-card-gap: 40svh;   /* donor grid-row-gap, scaled for hold; 45 read
                                     slow on device (founder, 2026-09-01) */
    --mk-rplan-tail: 25svh;       /* last card's ground before section end.
                                     Was 70svh — SEEN: text and image gone,
                                     then ~600px of empty mist before the
                                     next step's text ("scrolling for a long
                                     time... there's nothing"). 25svh is the
                                     minimum that still lets the last card
                                     hold through the joint 180px fade
                                     (capacity = gap + tail) and keeps the
                                     next section's text below the fold
                                     until that fade has finished */
  }
  /* Card height rides THE EXISTING VAR, not a new one: all five files'
     own id-ranked desktop height rules (09:R3-5, 10/11/12/13 mates)
     consume `--mk-rplan-panel-h`, and an id rule outranks any class rule
     written here — overriding the var's VALUE at mobile is how every
     prior round won this fight, and stays the one mechanism all five
     sections share. viewport - card-top - 24px bottom breathing room,
     per the mock. */
  .mk-rplan-js .mk-rplan {
    --mk-rplan-panel-h: calc(100svh - var(--mk-rplan-card-top, 55svh) - 24px);
  }
  /* lane gap inside the text block (R5 item 2: desktop's 24 is dead
     height in a mobile strip). 12's lanes sit straight ON the rail, so
     its copy root IS the rail — second selector. */
  .mk-rplan-js .mk-rplan .mk-rplan__rail .mk-rplan__copy,
  .mk-rplan-js .mk-rplan .mk-rplan__rail.mk-rplan__copy { gap: 16px; }
  /* THE STAGE IS A GRID WITH ROW-GAP SLOTS — the donor's own arrangement,
     and NOT interchangeable with margins: MEASURED LIVE, a sticky card's
     displacement allowance is only the room BELOW its own flow box inside
     its containment, and a margin-bottom slot is part of the card's own
     margin box — card 2's 45svh margin ate its entire allowance and it
     released the instant it locked (top tracked scroll 1:1 from the lock
     point, both margin and an equivalent padding-bottom variant). Grid
     row-gaps are container ground, not card box — with the slots here,
     card 1 held for exactly its slot and released on schedule. The
     ::after tail row is the one addition the donor doesn't need: the
     donor's last item never has to hold (its raf() skips the last item
     and its page just moves on), but OUR last card must stay seated
     through the text's exit fade — a sticky element with nothing below
     it inside its containment cannot hold AT ALL, so the tail row puts
     real, empty ground under it (capacity = gap + tail). */
  /* THE PILE CONTAINER'S GRID IS SET INLINE BY THE ENGINE, NOT HERE.
     Three shared-rule attempts (margin-bottom slots; row-gap on
     `.mk-rplan__stage`; row-gap on `:has(> .mk-rplan__panel)`) each lost
     in a DIFFERENT one of the five markups to a different id-ranked rule
     (10's own `#s10 ... .mk-recovery-41__grid { gap: 0 }`, 11's stage
     display, 13's inner-row display) — the five sections' donor markups
     are too dissimilar for one cascade-level declaration to win in all of
     them, and per-file counter-mates are the whack-a-mole that burned
     rounds 4-8. js/myk-recovery-plan.js's measure() therefore writes
     `display:grid; grid-template-columns:100%; row-gap:
     var(--mk-rplan-card-gap)` INLINE on each pile's measured direct
     parent (inline: outranked by nothing here), and appends one
     `.mk-rplan__tail` spacer row per pile — the ground under the last
     card that gives it sticky capacity to stay seated through the text's
     exit fade (a sticky element with nothing below it inside its
     containment cannot hold at all — measured). Only the spacer's height
     lives in CSS: */
  .mk-rplan-js .mk-rplan .mk-rplan__tail { height: var(--mk-rplan-tail); }
  /* THE TEXT BLOCK MUST BE ALLOWED TO EXIST (round 10, FT-20260901 — the
     first round verified with eyes.py rather than computed numbers).
     SEEN on Pixel 7: sections 09 (1/5) and 13 (5/5) rendered a blank void
     where overline/title/paragraph belong; 10/11/12 rendered fine. Their
     eyebrows measured opacity 1 with `.mv-in` applied — so the lanes were
     entering correctly INSIDE a container that never became visible: 09
     and 13 are the two sections whose copy is wrapped in a Salient
     `.wpb_column.has-animation` (`opacity: 0` pre-state, written INLINE by
     init.js, released only by Salient's own waypoint — which does not fire
     for a column living inside a sticky mobile rail). 10/11/12 wrap their
     copy in plain divs, hence they work. This is the founder's round-1
     "step-1 text must ALWAYS load"; round 9 deleted the JS force-arm that
     had been papering over it and re-exposed it. The right shape of fix is
     CSS, not JS timing: neutralise the COLUMN-level pre-state on mobile
     rails outright, and let the lanes' own myk-motion entrances (overline,
     then title, then paragraph — the site-wide cadence) be the thing you
     see. !important because Salient writes the pre-state inline. Mobile
     only; desktop's own engine arms this column itself. */
  .mk-rplan-js .mk-rplan .mk-rplan__rail .wpb_column.has-animation,
  .mk-rplan-js .mk-rplan .mk-rplan__rail.has-animation {
    opacity: 1 !important;
    transform: none !important;
  }
  /* Belt for the heading's Salient-side pre-state on mobile rails. The JS
     (bootMobile) flips every rail split-heading to `data-text-effect="none"`
     so myk-motion adopts the child <h2> and drives its entrance; this makes
     sure nothing Salient still stamps on the WRAPPER or its inner spans
     (`:not(.markup-generated) { opacity: 0 }`, `span .inner { translateY
     (1.3em) }` in css/salient-elements.css) can hide a heading whose
     visibility now belongs to the [data-mv] lane inside it. Same shape as
     R5-ARM's desktop belt below, minus the desktop-only attribute gate. */
  .mk-rplan-js .mk-rplan .mk-rplan__rail .nectar-split-heading { opacity: 1; }
  .mk-rplan-js .mk-rplan .mk-rplan__rail .nectar-split-heading span .inner {
    transform: none;
    opacity: 1;
  }
  /* THE CARD — donor's geometry, brand's radius. width:auto releases the
     base rule's 100% so the page-x side margins make the mock's inset box;
     block margins are ZERO (slots are the grid's row-gap, see above).
     RETRACTED (FT-20260901, same day): the donor's own 25px radius was
     used here on the theory that the founder's mock was drawing the
     donor's box. Her explicit correction — "you're supposed to use what's
     in the Figma... match my design" — overrides that: this is
     `--mk-rplan-radius` (4px), the SAME token desktop already uses for
     these five cards, ALL FOUR corners rounded (not desktop's left-pair —
     desktop's card bleeds to the viewport's right edge and mobile's does
     not, so nothing here is edge-to-edge). transform-origin top = donor,
     so a covered card still shrinks in place under the incoming one
     instead of drifting. */
  .mk-rplan-js .mk-rplan .mk-rplan__panel {
    position: sticky;
    top: var(--mk-rplan-card-top, 55svh);
    width: auto;
    margin: 0 var(--mk-page-x);
    /* !important, documented: the five files' own desktop radius rules are
       id-ranked ((1,5,0)-class, e.g. `.mk-page #s09-recovery-42
       .mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"]
       .mk-rplan__panel { border-radius: var(--mk-rplan-radius) 0 0 ... }`)
       and there is no var-level seam for a 4-corner override. */
    border-radius: var(--mk-rplan-radius) !important;
    transform-origin: 50% 0;
  }
}

/* ==========================================================================
   R3-5 · THE PANEL HEIGHT vs css/rhythm.css §B04 — A LIVE DISAGREEMENT
          BETWEEN TWO ITEMS OF THE SAME FOUNDER TRACE. READ THIS BEFORE
          TOUCHING EITHER SIDE.
   ==========================================================================
   FT-20260819 contains both of these, in the founder's own words:

     B04  "Recovery-plan sections: markedly taller. Currently the sections
           above and below stay in view."
     D06  "Recovery images are TOO TALL. Reduce height so a hint of the SECOND
           image is visible… I'd drop the height on the images on the right a
           little so even when you're seeing the first one you're seeing hints
           of the second, much like the component on the Salient resort one.
           This might also solve some of the cropped image issue."

   `r3:vertical-rhythm` paid B04 by raising the PANEL to
   `--mk-band-12 = max(1080px, 120svh)` (css/rhythm.css §2, two blocks, the
   second id-scoped).  That is a 1.2-screen image, which makes D06 strictly
   worse than the 100svh she was already objecting to: at 1512x900 it leaves
   MINUS 180px of the second image showing.  The two cannot both be satisfied
   on the same box.

   THEY ARE NOT ACTUALLY IN CONFLICT — THEY ARE ABOUT DIFFERENT BOXES.
   B04's evidence is about the SECTION ("the sections above and below stay in
   view").  D06's is about the IMAGE.  Section height = stage padding + n
   panels, so B04 has a second lever that does not touch the photograph, and it
   is the donor's own: the resort row this component is lifted from carries
   `padding-top: calc(100vw * 0.06); padding-bottom: calc(100vw * 0.12)` on the
   ROW, with 90vh panels inside it.  Ground above and below the stack, not a
   taller photograph.

   SO: the panel is D06's, and this lane takes it, because D06 is a direct
   instruction about the images to the lane that owns them.  `--mk-rplan-
   stage-pad` below is the hook B04 needs — one declaration, in rhythm.css,
   restores any section height wanted without touching a photograph:

       .mk-page .mk-rplan { --mk-rplan-stage-pad: calc(100vw * 0.06); }

   MEASURED at 1512x900, per section, so the trade is explicit:
       panel 120svh (rhythm)  09/10 = 2172   11/12/13 = 3256   image 1.5x UPSCALE
       panel 100svh (round 2) 09/10 = 1812   11/12/13 = 2716   image 1.17x upscale
       panel  80svh (D06)     09/10 = 1452   11/12/13 = 2176   image 0.94x DOWN
   Filed as REQ-BR-8 in founder-trace/SHARED-REQUESTS.md, addressed to
   `r3:vertical-rhythm`, with this arithmetic.

   WHY THESE SELECTORS CARRY AN EXTRA CLASS.  css/rhythm.css is linked at
   index.html:179, AFTER every sections/*.css (141-145), and its five id-scoped
   selectors are deliberate copies of the five below prefixed with `.mk-page`,
   i.e. (1,4,0) against the (1,3,0) they were copied from.  Equal-or-lower
   specificity in this file loses on source order.  Each selector below is
   therefore the SAME selector plus the panel's own modifier class — (1,5,0),
   one step, no `!important`, and the loser is struck through in devtools where
   the next reader can see which rule won and why.  When REQ-BR-8 is folded
   back, delete `height` from rhythm.css's two blocks and these drop to
   (1,3,0) with no behaviour change.
   ========================================================================== */
.mk-rplan { --mk-rplan-stage-pad: 0px; }
.mk-rplan__stage {
  padding-block: calc(var(--mk-rplan-stage-pad) + var(--mk-rplan-gap))
                 var(--mk-rplan-stage-pad);
}

.mk-page #s09-recovery-42 .mk-recovery-42 .nectar-fancy-box[data-style="hover_desc"].mk-rplan__panel,
.mk-page #s09-recovery-42 .nectar-fancy-box[data-style="hover_desc"].mk-rplan__panel.mk-recovery-42__card {
  height: var(--mk-rplan-panel-h);
  border-radius: var(--mk-rplan-radius) 0 0 var(--mk-rplan-radius);
}

/* D04 — the page gutter the row's `padding-inline` used to supply, moved onto
   the rail so the media column can bleed to the glass. Rationale, the
   measurement and the rhythm.css interaction: 09-recovery-42.css block R3-2.
   MUST be the last padding declaration for this rail in this file. */
#s09-recovery-42 .mk-recovery-42__rail { padding-left: var(--mk-rplan-gutter); }

/* THE PINNED RAIL, AT ID SPECIFICITY.  The component states the pin in
   09-recovery-42.css block R3-A at (0,3,0), and four of the five sections pin
   their rail width with an id rule of their own (12 uses 100%, 13 uses auto)
   that out-ranks it.  MEASURED with only the component rule: 12 and 13
   computed 1512px wide instead of 756 when fixed — a rail spread across the
   whole viewport, on top of its own photographs.  A width of 100% of a grid
   AREA and of the VIEWPORT are the same number until the element leaves flow,
   which is exactly why this only appears once something is pinned.  Restated
   here, per file, where it wins.
   grid-column is belt: a fixed rail is out of grid flow, so without an
   explicit track the stage would auto-place into column 1. */
#s09-recovery-42[data-rplan-rail] .mk-recovery-42__rail {
  position: fixed;
  top: 0;
  left: var(--mk-rplan-pin-x, 0px);
  width: var(--mk-rplan-pin-w, 50%);
  height: 100svh;
}
#s09-recovery-42 .mk-rplan__rail  { grid-column: 1; }
#s09-recovery-42 .mk-rplan__stage { grid-column: 2; }

/* ==========================================================================
   09 · MOBILE STRIP MATES (<=999px) — for block R4-M above. The desktop id
   pin keeps position/left/width (JS feeds it 0 / 100vw below 1000); what
   must change RANK-FOR-RANK against this file's own desktop id rules is the
   strip height, the grid release — `grid-column: 2` above would exile the
   stage into a 0px implicit column once the template is 100% — and the
   strip padding. Same shape at the end of 10/11/12/13.
   ========================================================================== */
@media (max-width: 999px) {
  #s09-recovery-42 .mk-recovery-42__row { grid-template-columns: 100%; }
  #s09-recovery-42 .mk-rplan__rail,
  #s09-recovery-42 .mk-rplan__stage { grid-column: auto; }
  #s09-recovery-42 .mk-recovery-42__rail {
    padding: var(--mk-rplan-strip-top) var(--mk-page-x)
             var(--mk-rplan-strip-bot, var(--mk-space-24));  /* R5 item 2 */
  }
  /* Caption: NOT restated. Round 6 retires the top-band squeeze (R5-STACK,
     deleted) that this id-rank mate used to restore against — panels no
     longer shrink per stacked index, so each card's own Figma-verified
     bottom-anchored caption (block B §8 above) simply applies unmodified. */
}

/* ############################################################################
   ###  R5 · MK-RPLAN, ROUND-2 MOBILE PASS (FT-20260830 founder review)     ###
   ############################################################################
   Lane rplan2. Four founder items on the mobile recovery band (architecture
   kept: text pinned on top, images below):
     1. step-1 text must ALWAYS load        -> JS force-arm + block R5-ARM
     2. text strip up, images taller        -> R4-M vars above (88/14/16)
     3. within-step additive stack           -> R4-M uniform sticky top
        (item 3's original donor, tether nectar-sticky-media-sections
        effect-overlapping, and its R5-STACK peeking-title-band caption
        treatment, are RETIRED as of round 6: the founder's own mock of the
        wanted result shows a later card covering an earlier one in FULL,
        not leaving a title sliver visible — see R4-M above and
        js/myk-recovery-plan.js's bootMobile() header.)
     4. between-step back-out + fade/blur
        (donor: portfolio-layered
        layout-stacked blurred_scale,
        measured scale[1,.8] blur[0,5px]
        transform-origin top)               -> R5-OUTRO
   Shared by 09/10/11/12/13 exactly like every block above; per-file id-rank
   mates live at the end of each section file. Covenants unchanged: zero
   wheel/scroll listeners, zero scroll-snap — sticky offsets and CSS
   transform/opacity/filter written every frame by a passive scrollY read
   (see the JS file's SCROLL COVENANT note). Desktop >=1000px: every rule
   below except R5-ARM is inside max-width:999px; R5-ARM only forces states
   desktop choreography reaches on its own, so >=1000 stays pixel-identical.
   ######################################################################### */

/* ==========================================================================
   R5-ARM · AN ACTIVE RAIL CAN NEVER HAVE A HIDDEN LANE (item 1, CSS belt).
   js/myk-recovery-plan.js armLanes() adds each reveal system's own release
   class the moment a rail goes active/in-*. This belt covers the one race
   the classes cannot: Salient's init.js SPLITTING a heading after arming
   (the split rebuilds the inner spans without `.animated`, re-hiding the
   text until a choreography that is desktop-gated re-reveals it). While a
   rail is engaged, its split-heading wrappers and inners are held at their
   REVEALED state outright. (0,5,0)+ vs salient-elements.css's (0,3,0) —
   wins on specificity, and the lane-swap ANIMATIONS still win over these
   static declarations while they run, so the donor swap is untouched.
   `.mk-rplan-armed` is the JS-armed twin of the panel-side has-animation
   armour at line ~518: Salient writes the column pre-state INLINE from
   init.js, so matching its strength needs !important.
   ========================================================================== */
.mk-rplan-js .mk-rplan[data-rplan-rail="active"] .mk-rplan__rail .nectar-split-heading,
.mk-rplan-js .mk-rplan[data-rplan-rail^="in"] .mk-rplan__rail .nectar-split-heading {
  opacity: 1;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="active"] .mk-rplan__rail .nectar-split-heading span .inner,
.mk-rplan-js .mk-rplan[data-rplan-rail^="in"] .mk-rplan__rail .nectar-split-heading span .inner {
  transform: none;
  opacity: 1;
}
.mk-rplan .mk-rplan__rail .mk-rplan-armed {
  opacity: 1 !important;
  transform: none !important;
}
/* The JS-OFF belt (probed: with script execution disabled, the strip showed
   eyebrow + body but NO heading — the split-heading pre-state `opacity: 0`
   has no non-JS release anywhere in the vendored set). With no JS at all,
   `mk-rplan-js` never lands, so this fires and the heading simply paints.
   Mobile-gated: the desktop no-JS story predates this round and is not this
   item's to change. Cost if ONLY myk-recovery-plan.js were missing while
   Salient still runs: the unsplit heading paints for the pre-split frame —
   a flash in a failure mode, against text that never appears at all. */
@media (max-width: 999px) {
  html:not(.mk-rplan-js) .mk-rplan__rail .nectar-split-heading[data-animation-type="line-reveal-by-space"]:not(.markup-generated) {
    opacity: 1;
  }
}

/* ==========================================================================
   R5-STACK — RETIRED (FT-20260831, mobile round 6).
   Squeezed each card's caption into a top-anchored --mk-rplan-title-h band
   so a covered card's title stayed visible above the next one, per the
   tether donor's peeking-sliver pile. Retired along with R4-M's stepped
   offset: the founder's own mock of the wanted result shows a later card
   covering an earlier one in FULL — no sliver, no top-band squeeze — so
   each card's caption reverts to its authored, Figma-verified bottom
   anchor (block B §8 above), unmodified, same as desktop.
   ========================================================================== */

/* ==========================================================================
   R5-OUTRO / R6 (all sub-blocks) — RETIRED (FT-20260831, mobile round 4).
   Three rounds of discrete state (`data-rplan-pos="before"`, `.mk-rplan-
   piled`, the R6-GATE/-ENTER/-PIN/-RESERVE belt) each closed one race and
   opened another — round 3's own verifier found a fast flick could leave a
   panel PERMANENTLY invisible (the release selector and the outro selector
   both landing in the same committed frame, so neither ever matched). All
   of it is replaced by js/myk-recovery-plan.js's bootMobile(): one
   requestAnimationFrame loop reading `window.scrollY` (passive — see that
   file's own SCROLL COVENANT note for why this does not violate REQ-
   SCROLL-01), computing every panel's (and, as of round 6, the rail's own)
   arrive/recede progress as a pure continuous function of scroll position
   and writing opacity/transform/filter/visibility inline every frame.
   There is no attribute left for a race to land between. See the JS file's
   mobile-engine header comment for the full, current account of the
   layout this drives — R4-M above is round 6's version, not round 4's. */

/* ==========================================================================
   THE DECK (mobile, round 12 — FT-20260901). The engine folds the five steps
   into one deck inside this section at boot (see js/myk-recovery-plan.js
   bootMobile): a sticky text stage over one thirteen-card pile. The stage is
   the founder's persisted text block — "RECOVERY PLAN" + one rolling counter
   + one slot of stacked layers {title, copy}, one visible at a time — and the
   swap is the desktop's own state machine and keyframes (mkRplanTab*), on
   the layer instead of the section. Cards keep every rule they already had
   (.mk-rplan__panel sticky at --mk-rplan-card-top, --mk-rplan-panel-h).
   ========================================================================== */
@media (max-width: 999px) {
  .mk-rplan-js .mk-rplan.mk-deck-host { padding-bottom: 0; }
  /* Space between the last card and the next section as the deck leaves
     (the founder: "persist space between the safe long-term housing card
     and 'So much more than a safe stay'"). */
  .mk-rplan-js .mk-deck { position: relative; padding-bottom: max(48px, var(--mk-space-60, 48px)); }  /* --mk-space-60 computes 24px at 412 */
  /* The stage's sticky range is the absolute wrap, whose bottom the engine
     pulls up by (cardTop + cardH - stageH + padding) so text and last card
     release on the same scrollY. */
  .mk-rplan-js .mk-deck__stage-wrap { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 12; pointer-events: none; }
  .mk-rplan-js .mk-deck__stage-wrap .mk-deck__stage { pointer-events: auto; }
  /* Donor ghosts generate no box, but their clearfix pseudo-elements did —
     each became a zero-height grid item eating a full row-gap. */
  .mk-rplan-js .mk-deck__ghost::before,
  .mk-rplan-js .mk-deck__ghost::after { display: none !important; content: none !important; }
  .mk-rplan-js .mk-deck__stage {
    position: sticky;
    top: 0;
    z-index: 12;
    background: var(--mk-surface-mist);
    padding: var(--mk-rplan-strip-top) var(--mk-page-x) var(--mk-rplan-strip-bot, 14px);
    display: flex;
    flex-direction: column;
    gap: 16px;
  }
  .mk-rplan-js .mk-deck__eyebrow { margin: 0; }
  .mk-rplan-js .mk-deck__slots { position: relative; }
  .mk-rplan-js .mk-deck__layer {
    position: absolute;
    top: 0; left: 0; width: 100%;
    display: flex; flex-direction: column; gap: 16px;
  }
  /* The cloned titles sit outside their sections' heading wrappers, so the
     rails' mobile h2 rule (.mk-recovery-42__heading h2 { font-size: 32px })
     no longer reaches them and the site default (39px) did — MEASURED in
     deck2: 39px/47px vs the rail's 32px/40px, two-line "[Minimize exposure]".
     One size for all five titles, the rail's. */
  .mk-rplan-js .mk-deck__title { margin: 0; font-size: 32px; line-height: 40px; }
  .mk-rplan-js .mk-deck__copy p {
    margin: 0;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 8;         /* one copy height for every step (decision 3) */
    overflow: hidden;
  }
  /* Layer states — the desktop swap, verbatim timings (0.3s out, 0.6s in
     after 0.1s). The incoming copy trails its title by 140ms so the order
     inside a swap is top-down, as the founder asked. */
  .mk-rplan-js .mk-deck__layer[data-deck="hidden"] { opacity: 0; visibility: hidden; pointer-events: none; }
  .mk-rplan-js .mk-deck__layer[data-deck="out-up"]   .mk-rplan__lane { animation: mkRplanTabOutUp   var(--mk-rplan-out-dur) var(--mk-rplan-ease) forwards; }
  .mk-rplan-js .mk-deck__layer[data-deck="out-down"] .mk-rplan__lane { animation: mkRplanTabOutDown var(--mk-rplan-out-dur) var(--mk-rplan-ease) forwards; }
  .mk-rplan-js .mk-deck__layer[data-deck="in-up"]    .mk-rplan__lane { opacity: 0; animation: mkRplanTabInUp   var(--mk-rplan-in-dur) var(--mk-rplan-ease) var(--mk-rplan-in-delay) forwards; }
  .mk-rplan-js .mk-deck__layer[data-deck="in-down"]  .mk-rplan__lane { opacity: 0; animation: mkRplanTabInDown var(--mk-rplan-in-dur) var(--mk-rplan-ease) var(--mk-rplan-in-delay) forwards; }
  .mk-rplan-js .mk-deck__layer[data-deck^="in"] .mk-deck__copy { animation-delay: calc(var(--mk-rplan-in-delay) + 140ms); }
  /* The pile: cards inset on the page grid, one size, brand radius. Inline
     geometry from the engine outranks the five donor markups' vendor rules. */
  .mk-rplan-js .mk-deck__pile > .mk-rplan__panel {
    width: auto !important;
    margin: 0 var(--mk-page-x) !important;
    padding-bottom: 0 !important;
    height: var(--mk-rplan-panel-h) !important;
    min-height: 0 !important;
    border-radius: var(--mk-rplan-radius) !important;
  }
  /* tail height is set inline by the engine (TAIL_FRAC of a slot) */
  @media (prefers-reduced-motion: reduce) {
    .mk-rplan-js .mk-deck__layer .mk-rplan__lane { animation: none !important; opacity: 1; }
    .mk-rplan-js .mk-deck__layer[data-deck="hidden"] .mk-rplan__lane { opacity: 0; }
    .mk-rplan-js .mk-deck__layer[data-deck^="out"] { opacity: 0; visibility: hidden; }
  }
}

/* 2026-09-02 — DECK STAGE, SUBPIXEL SHIMMER. Founder (Pixel 7): the 2/5 title
   "[Testing]" looks "jittery ... moving by a pixel or two" while scrolling.
   MEASURED (73 samples at 7.3px scroll steps): the stage stays at top 0 and
   the title's box at top 112 for every sample — nothing moves in layout. Its
   LEFT is 24.72px (`--mk-page-x` = 6vw at 412), i.e. text rasterised at a
   fractional x on a sticky box the compositor re-places every frame; at
   DPR 2.625 that snaps differently frame to frame, which reads as a 1px
   shimmer. Two changes: the inline padding is rounded to a whole CSS pixel,
   and the stage is promoted to its own compositing layer so its text is
   rasterised once and moved, not re-rasterised per scroll frame. */
@media (max-width: 999px) {
  .mk-rplan-js .mk-deck__stage {
    padding-inline: round(var(--mk-page-x), 1px);
    transform: translateZ(0);
    backface-visibility: hidden;
  }
}

/* ==========================================================================
   2026-09-02 — THE 3/5 AND 4/5 BLIP (desktop). Founder: the paragraph
   "animates in, then disappears, then reappears". MEASURED per frame (rAF
   probe through the 11 swap): the copy `p` carries js/myk-motion.js's
   `[data-mv]` TRANSITION (opacity .7s, delay .1s). When the rail goes
   hidden -> in-up, the rail rule's `opacity: 0` starts that transition
   (1 -> 0 over 800ms) UNDERNEATH the keyframe animation; the keyframe wins
   while it runs, but the moment `active` removes it (t=1324ms) the still-
   running transition shows through at 0.09 and then climbs back to 1. The
   title (a split heading, no data-mv) never blipped; neither did 5/5 (copy
   inside a split heading). So: no transition on a lane while the rail is
   swapping. The eyebrow lane keeps its own fade (it has no keyframes). The
   deck (mobile) is given the same guard for the same reason. */
.mk-rplan-js .mk-rplan[data-rplan-rail^="in-"]  .mk-rplan__lane:not([data-rplan-lane="eyebrow"]),
.mk-rplan-js .mk-rplan[data-rplan-rail^="out-"] .mk-rplan__lane:not([data-rplan-lane="eyebrow"]),
.mk-rplan-js .mk-rplan[data-rplan-rail="hidden"] .mk-rplan__lane:not([data-rplan-lane="eyebrow"]),
.mk-rplan-js .mk-deck__layer[data-deck^="in"]  .mk-rplan__lane,
.mk-rplan-js .mk-deck__layer[data-deck^="out"] .mk-rplan__lane,
.mk-rplan-js .mk-deck__layer[data-deck="hidden"] .mk-rplan__lane {
  transition: none !important;
}

/* 2026-09-02 — iPAD PRO PORTRAIT (1024px), step 1's panel was a 65px sliver.
   MEASURED at 1024x1366: `.mk-rplan__stage` computed width 0 inside its 512px
   grid cell, because the band sheet's `.mk-rail-images { width: calc(100% -
   563px) }` (css/_band-md.css, written for the 563px text rail beside a
   full-width images rail) resolves NEGATIVE inside a 50% grid column. From
   1000px up the desktop engine lays the row out as a two-column grid, so the
   stage is simply its whole cell. Id-scoped to outrank the band sheets. */
@media (min-width: 1000px) {
  #s09-recovery-42 .mk-rplan__grid > .mk-rplan__stage { width: 100%; }
}

/* ==========================================================================
   2026-09-02 — TITLES MUST NEVER COEXIST (founder: "Stabilize and Rebuild end
   up on the screen at the same time … make sure your text animates out
   before your next animates in").
   MEASURED per frame (rAF probe, EN 1440, every boundary, both directions):
     · every step had a 2-frame crossfade tail — the donor's .1s in-delay
       started the new title while the old one was still fading. Fixed by
       `--mk-rplan-in-delay: .32s` (+ IN_DELAY 320 in the engine): out .3s
       completes before in begins.
     · 4 <-> 5 was the one she saw: 9 frames both at opacity 1 going down,
       66 frames (1.1s) going up. 13's title lane is a `.nectar-split-heading
       [data-text-effect="none"]` WRAPPER, and 00-hero.css/03-unique.css pin
       that wrapper `opacity: 1 !important` so it paints before Salient splits
       it — an !important declaration outranks any keyframe, so the swap
       could move the wrapper but never fade it. 1 <-> 2 had the same shape
       (09's title is a split-heading wrapper too; 14 frames both at 1).
   So on a split-heading lane the swap runs on the HEADING INSIDE the
   wrapper, and the wrapper itself is held still and opaque. The same guard
   that keeps myk-motion's transition off a swapping lane covers the child.
   ========================================================================== */
.mk-rplan-js .mk-rplan[data-rplan-rail] .mk-rplan__lane.nectar-split-heading {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}
.mk-rplan-js .mk-rplan[data-rplan-rail^="in-"]  .mk-rplan__lane > :is(h1, h2, h3, h4),
.mk-rplan-js .mk-rplan[data-rplan-rail^="out-"] .mk-rplan__lane > :is(h1, h2, h3, h4),
.mk-rplan-js .mk-rplan[data-rplan-rail="hidden"] .mk-rplan__lane > :is(h1, h2, h3, h4) {
  transition: none !important;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="out-up"]   .mk-rplan__lane.nectar-split-heading > :is(h1, h2, h3, h4) {
  animation: mkRplanTabOutUp var(--mk-rplan-out-dur) var(--mk-rplan-ease) forwards;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="out-down"] .mk-rplan__lane.nectar-split-heading > :is(h1, h2, h3, h4) {
  animation: mkRplanTabOutDown var(--mk-rplan-out-dur) var(--mk-rplan-ease) forwards;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="in-up"]    .mk-rplan__lane.nectar-split-heading > :is(h1, h2, h3, h4) {
  opacity: 0;
  animation: mkRplanTabInUp var(--mk-rplan-in-dur) var(--mk-rplan-ease) var(--mk-rplan-in-delay) forwards;
}
.mk-rplan-js .mk-rplan[data-rplan-rail="in-down"]  .mk-rplan__lane.nectar-split-heading > :is(h1, h2, h3, h4) {
  opacity: 0;
  animation: mkRplanTabInDown var(--mk-rplan-in-dur) var(--mk-rplan-ease) var(--mk-rplan-in-delay) forwards;
}


/* ==========================================================================
   2026-09-02 — THE OVERLINE STAYS PUT (founder, desktop). The rail centred each
   step's copy block on its own, so the eyebrow's y depended on that step's
   title and paragraph height. The engine now measures the tallest step's
   block and publishes the top it would take when centred as
   `--mk-rplan-copy-top`; every rail starts its block on that line, so
   "Recovery plan N/5" never moves and the title and paragraph swap beneath
   it. Without the engine (no --mk-rplan-copy-top) the rail keeps centring.
   ========================================================================== */
/* Done INLINE by the engine (measureCopyTop): the five donors pad and centre
   their rails in five different places with id-ranked rules, so a shared
   stylesheet rule could not reach them all. `--mk-rplan-copy-top` is still
   published on each section for anything that wants the shared line. */


/* ==========================================================================
   2026-09-02 — ONE CAPTION ENTRANCE FOR ALL THIRTEEN CARDS (desktop).
   The engine tags each card's first heading `.mk-rplan__cap-title` and its
   other caption text `.mk-rplan__cap-line`. Both fade up 24px once the card
   has arrived (the same `is-arrived` the card's own 1.1s fade keys off), the
   line 140ms behind the title — the shape 13's captions already had through
   myk-motion, now shared by 09/10/11/12's donor captions. `translate`, not
   `transform`: the fancy-box donors move their headings with `transform` on
   hover and must keep doing so. Guarded to the desktop engine; the phone deck
   slides its cards in whole. Reduced motion keeps the captions static.
   ========================================================================== */
@media (min-width: 1000px) and (prefers-reduced-motion: no-preference) {
  .mk-rplan-js .mk-rplan__panel.mk-rplan-arrive .mk-rplan__cap-title,
  .mk-rplan-js .mk-rplan__panel.mk-rplan-arrive .mk-rplan__cap-line {
    /* `!important` on the clock: the fancy-box donors give `.hover-content`
       its own 0.18s transition with an id-ranked selector, which let a card's
       line land 200ms BEFORE its title (measured: line 0.5 at +200ms, title
       at +420ms). One clock for every caption, title first. */
    transition:
      opacity   .7s var(--mk-rplan-ease) .25s,
      translate .7s var(--mk-rplan-ease) .25s !important;
  }
  .mk-rplan-js .mk-rplan__panel.mk-rplan-arrive .mk-rplan__cap-line {
    transition-delay: .39s !important;
  }
  .mk-rplan-js .mk-rplan__panel.mk-rplan-arrive:not(.is-arrived) .mk-rplan__cap-title,
  .mk-rplan-js .mk-rplan__panel.mk-rplan-arrive:not(.is-arrived) .mk-rplan__cap-line {
    opacity: 0 !important;      /* the donors pin some captions opaque at rest */
    translate: 0 24px;
  }
}
