/* ============================================================================
   07-building — "3.0 Building" — Figma node 2767:2269 — 1280 x 900
   ----------------------------------------------------------------------------
   Every literal below cites figma-truth/07-building.spec.md. Nothing here is
   remembered or inherited. The section-local custom properties at the top are
   values that exist ONLY in this section, so per CONVENTIONS.md §4 they stay
   here and are deliberately NOT added to tokens.css.

   THE COMPONENT IS LIFTED, NOT BUILT. The markup is Salient's
   `nectar-flickity.nectar-carousel` + `nectar-fancy-box[data-style=hover_desc]`
   out of http://127.0.0.1:5010/salient/resort/index.html. Its engine CSS is
   already vendored (plugins/flickity.q-458c954239.css,
   elements/element-fancy-box.q-b2fac09bdb.css). This file only does the two
   jobs the founder named: retarget the paint, and apply the five numbers Figma
   overrides. Three rules further down are Salient's OWN per-instance CSS,
   copied verbatim out of the resort page's inline <style> — that block is
   generated per page by WordPress and therefore was NOT in the vendored files.
   They are marked LIFTED-INLINE and must not be rewritten.
   ========================================================================== */

/* ==========================================================================
   0. D20 — THE SECTION IS IMMERSIVE (founder trace FT-20260817, G1 + D20)
   "Must snap to full screen; neighbours not visible."

   The section used to be a FIXED 900px: head 292 + gallery (4 + 600 + 4) = 900.
   At the 1280x900 design size that happens to equal one viewport, which is why
   it looked right in every capture this project ever took — every harness runs
   at 900. On the founders' own machines it is not 900, so the band above and
   the band below are both on screen at once. The defect was invisible to the
   instrument and obvious to a human, which is the whole reason it survived.

   The fix keeps Figma exactly and adds a viewport term, so the section
   REDUCES to the authored geometry at the design size and fills the viewport
   everywhere else:

       card height = clamp(420px, calc(100svh - 300px), 900px)
       at 100svh = 900  ->  900 - 300 = 600   == spec §1.1, to the pixel
       300 = head 292 + the gallery frame's own 4 + 4 padding (§1.1)

   svh, not vh: on mobile Safari `vh` is the LARGEST viewport (toolbars
   retracted), so a 100vh section is taller than the visible area on first
   paint and the next section is never fully hidden — the exact symptom D20
   describes. `svh` is the smallest viewport and is the only one that
   guarantees "neighbours not visible".

   The SNAP itself is page-global and is NOT owned here: `scroll-snap-type` on
   the scroll container belongs to the single-owner immersive pass (lane
   fix:immersive, which has already measured that snap displaces the fidelity
   harness by 0px at all 20 targets). This file only declares the snap UNIT,
   which is a section-local decision — and it is declared as an attribute so
   that the recovery sections, whose snap unit is a PANEL and not the section,
   can say so too. See founder-trace/SHARED-REQUESTS.md REQ-BR-1.
   ========================================================================== */

#s07-building {
  min-height: 100svh;
  scroll-snap-align: start;
  scroll-snap-stop: always;      /* G2: a flick lands on a slide, never between */

  /* The clamp below is arithmetic against a 292px head, and the head is only
     guaranteed to be 292 at the authored widths. Making the section a flex
     column means a head that comes out taller or shorter GROWS OR SHRINKS the
     gallery instead of over- or under-running the viewport: min-height is a
     floor, so the section can still never be shorter than one screen, which is
     the property D20 actually asks for. */
  display: flex;
  flex-direction: column;
}
#s07-building .mk-building__head    { flex: 0 0 auto; }
#s07-building .mk-building__gallery { flex: 1 1 auto; min-height: 0; }

#s07-building {
  /* The card module. Byte-identical in the 1280 EN frame and the 1440 ES
     frame, which is the proof it is a fixed module and not a column span
     (spec §5.3). Do not express these as percentages.

     ⚠ THE WIDTH IS STILL FIXED, THE HEIGHT IS NO LONGER. D20 asked for a
     full-screen section, not a full-screen card; the track is already 2070px
     wide over a 1280 ground and already bleeds (§1.4), so the horizontal half
     of G1 was never the defect. Only the vertical was. */
  --mk-07-card-w:        410px;   /* §1.1 card frame */
  --mk-07-card-h:        clamp(420px, calc(100svh - 300px), 900px);
                                  /* §1.1 card frame = 600 at the 900 design
                                     height; see §0 for the 300 and the clamp */

  /* Gradient stop 1 = #000000bf = 191/255. Stop 0 is #00000000. The
     gradientTransform resolves to u = y, i.e. the colour axis is exactly
     vertical, top -> bottom (§3.2). */
  --mk-07-scrim-bottom:  rgba(0, 0, 0, 0.749);

  /* Card body run — 18px in a 32px LINE BOX (= 177.78%, not 180%).
     ⚠ THIS DEPARTS FROM THE SPEC, ON MEASUREMENT. §2.2 derives 160/9 =
     17.7778px by taking the EXACT 32.0px baseline pitch (correct — it
     reproduces here) and then ASSUMING lineHeight is the 180% it read off the
     TITLE run. The pitch is what was measured; the 180% was carried over. Both
     readings satisfy the pitch, so every vertical check passes either way and
     the error is visible only in glyph ADVANCE WIDTH.

     Measured against figma-truth/sections-en/07-building.png by fitting a
     horizontal scale s to the built line strip about the text-box left edge and
     minimising |built_s - truth| over the 8 body lines of cards 2 and 3 (the
     lines whose wrap already matched). Sharp single minimum:
         s=1.010 .06203   s=1.012 .04367   s=1.014 .04715   s=1.016 .06889
         s=1.011 .05111   s=1.013 .04204*  s=1.015 .05688   s=1.018 .09313
     s* = 1.013  =>  17.778 x 1.013 = 18.009px.
     The same fit on the three card TITLES returns s* = 1.000 exactly (residual
     0.0095 vs the body's 0.042), so 24px is right and only the body run was
     wrong. Corroborated by ink extents: every built body line was 1.6-1.8%
     narrower than truth on every card, while titles matched to <=1px.
     Visible consequence at 17.778: card 1 wrapped
     "foundation properly sealed from / ground moisture." where Figma wraps
     "foundation properly sealed / from ground moisture."
     ACCEPTANCE TEST: body baseline pitch must stay exactly 32.0px, which is why
     the line height below is a LENGTH and not the 1.8 ratio (18 x 1.8 = 32.4). */
  --mk-07-body-size:     18px;
  --mk-07-body-lh:       32px;

  /* Title-last-baseline -> body-first-baseline is exactly 57.0px in BOTH
     languages (§2.3). Figma does not expose paragraphSpacing, so the CSS
     margin is solved from Poppins' metrics (upem 1000, asc 1050, desc -350,
     verified by parsing fonts/Poppins-*.ttf):
        title descent + half-leading = 0.35*24  + (43.2-33.6)/2      = 13.200
        body  ascent  + half-leading = 1.05*18   + (32-25.2)/2       = 22.300
        margin = 57 - 13.200 - 22.300                                = 21.500
     (Was 21.578 while the body was mis-set to 17.778px; re-solved with the
     measured 18px. The 0.078px difference is sub-pixel and moves nothing, but
     the derivation has to stay honest about which body size it used.)
     ACCEPTANCE TEST: measure title baseline -> body baseline. It must be 57px.
     The wrapper frame's itemSpacing:24 is NOT this gap — it has a single child
     and is inert (§2.3). Using 24 will be wrong. */
  --mk-07-title-body-gap: 21.5px;

  background-color: var(--mk-surface-stone);   /* #cbd6dc — §3.1, both languages */
}

/* ==========================================================================
   1. HEADER BLOCK — 2767:2270 · 1280 x 292
   96 + 24 (eyebrow) + 24 (gap) + 136 (headline) + 12 = 292 exactly, so the
   parent's SPACE_BETWEEN distributes ZERO free space and its itemSpacing:43 is
   inert (§1.1). There is no 43px gap on this page.
   ========================================================================== */

#s07-building .mk-building__head {
  height: 292px;                              /* §1.1 */
  padding-top: var(--mk-space-96);            /* 96 */
  padding-bottom: var(--mk-space-12);         /* 12 */
  display: flex;
  flex-direction: column;
  justify-content: center;                    /* primaryAxisAlignItems CENTER */
  align-items: flex-start;                    /* counterAxisAlignItems MIN */
  gap: var(--mk-space-24);                    /* itemSpacing 24 */
}

/* R8-04 DELETED THE RULE THAT USED TO LIVE HERE —
   `#s07-building .mk-building__head > .wpb_column { width:100%; padding:0;
   margin:0; }` — which reset the lifted Salient `wpb_column.has-animation`
   wrapper that used to carry the eyebrow. That wrapper is gone with A07 (see
   §1b): the eyebrow is now a direct child of `.mk-building__head`, exactly
   like sections/04-ecology.css's, so there is no column left to reset. */

#s07-building .mk-building__eyebrow {
  /* .mk-eyebrow already carries Poppins Light 16/24, tracking 3px and
     text-transform:uppercase (textCase is not serialised — §2.1 / L-5). */
  color: var(--mk-ink-strong);                /* #000000 — §3.1 */
  /* MEASURED: Salient ships `p{padding-bottom:24px}`, which made this node 48px
     tall against Figma's 24 and pushed the whole header out of its 184px
     content box. Zeroing padding is not cosmetic — it is what makes
     96 + 24 + 24 + 136 + 12 = 292 come out right. */
  margin: 0;
  padding: 0;
}

#s07-building .mk-building__headline {
  font-family: var(--mk-font-serif);
  font-weight: var(--mk-w-light);             /* 300 */
  font-size: 108px;                           /* §2.1 — fixed at 1280 AND 1440 */
  line-height: 136px;                         /* §2.1 — AUTO; the node box is 136 */
  letter-spacing: 0;                          /* not set in the payload */
  color: var(--mk-ink-strong);                /* #000000 */
  margin: 0;
  padding: 0;                                 /* Salient ships p/h padding — see the eyebrow note */
  /* ⚠ SPEC CORRECTION — NO TEXT STROKE. §2.1 reads `strokes: ["#000000"],
     strokeWeight: 1, strokeAlign: CENTER` off node 2767:2272 and concludes
     "this is a real paint on the text", prescribing
     `-webkit-text-stroke: 1px`. The RENDER says otherwise, and the render is
     the authority: L-7 records that this serializer never reports a paint's
     `visible` flag, and strokeWeight:1 is Figma's default on every node —
     §1.1 itself annotates the sibling frames "strokeWeight 1 (no stroke
     paint)".

     Measured by sweeping -webkit-text-stroke-width through CSSOM in the
     capture's own Chrome and scoring the headline band against
     figma-truth/sections-en/07-building.png:
         width   %diff    MAE    ink-mass vs truth
         0px     4.163   1.532   0.9975   <-- minimum on all three
         0.2px   4.714   1.816   1.0300
         0.4px   5.579   2.562   1.0650
         0.6px   6.405   3.409   1.0957
         1px     7.155   5.370   1.1618
     Monotone, no interior minimum, and at 0px the built ink mass matches the
     Figma export to 0.25%.

     Direct visual proof — row 210 crosses the counter of the "B" in
     "Building"; luma across x=140..154:
         truth        91 108 118 118 118 118 118
         stroke 0     91 112 119 123 123 122 121
         stroke 1px    0   0   0   0   0   0   0
     A 1px centre stroke closes a counter that Figma leaves open. Do not
     reinstate it, and correct §2.1 rather than re-deriving this. */
  /* Auto-width node starting at the 120 margin, so textAlignHorizontal:CENTER
     is visually inert (§2.1). Left is the truth. */
  text-align: left;

  /* ⚠ OVERRIDES tokens.css, DELIBERATELY, AND IT IS A PAGE-WIDE BUG.
     tokens.css pins `font-optical-sizing: none; "opsz" 18` on every h1/h2/h3
     and .mk-serif, on the argument that Figma's named instances pin opsz 18.
     That is measurably wrong here. Browser sweep of the opsz axis on
     "Building requirements" at Merriweather Light 108px, wdth 100:
         opsz  18 -> 1146.16 px      opsz  72 -> 1055.96 px
         opsz  24 -> 1136.13 px      opsz  96 -> 1015.89 px
         opsz  36 -> 1116.09 px      opsz 108 ->  995.84 px   <-- Figma is 996
         opsz  54 -> 1086.03 px      opsz 144 ->  935.72 px
     Figma node 2767:2272 is 996 wide. opsz 108 matches it to 0.16px; opsz 18
     is 15.1% too wide and WRAPS inside the 1040 content box. So Figma renders
     Merriweather with opsz tracking the font size — CSS's
     `font-optical-sizing: auto` — not pinned at 18. Scoped here; the tokens.css
     rule needs the same correction for every other serif on the page. */
  font-optical-sizing: auto;
  font-variation-settings: "wdth" 100;
}

/* --------------------------------------------------------------------------
   §1b. R8-04 — THE OVERLINE + TITLE MATCH ECOLOGY'S ENTRANCE, EXACTLY
   ----------------------------------------------------------------------------
   Founder-trace FT-20260821-jehnean-round8, lane `r8:building`.

   This REPLACES A07 (FT-20260819, "title animates RIGHT→LEFT like ecological
   considerations"). A07 was a faithful copy of sections/04-ecology.css §G1 —
   Salient's scroll-animation column, `translateX = -(scrollTop - sectionTop)
   x 0.35` — plus a ghost/scroller apparatus (deleted from the HTML: a hidden
   title-slot copy of the headline in the real band, and a second, absolutely
   positioned ghosted copy of the WHOLE band carrying the real, travelling
   headline) whose only job was giving that out-of-flow headline something
   in-flow to reserve height for.

   §G1 no longer exists. R7-11/R7-12/R7-13 (2026-08-21, lane `r7:ecology`)
   retired it in ecology itself — "TOO MASSIVE" per the founder, and
   independently the source of R7-12's mid-exit jump (a `position:absolute;
   inset:0` title pinned inside a sticky stage cannot ride a scroll-position
   controller written for static columns; Salient's own scroll-cache
   sawtoothed against the pinned box on top of that). Ecology's title today is
   §H1's one-shot, self-owned entrance: IntersectionObserver-triggered, no
   scroll coupling.

   Jehnean, R8-04, verbatim: "for how you animated in the ecological
   consideration section and its overline, how they kinda fade and move left
   a little bit — can you also do that same CSS animation for the section
   where the overline is why this building / toxins avoidance and the title
   is building requirements and match that animation for me." Read live off
   sections/04-ecology.css §H1 rather than re-derived — same four custom
   properties, same values:

       travel    20px      (Omai's "welcome home" measured x, mirrored to
                            settle right-to-left per R7-13's axis instruction)
       duration  1000ms
       easing    cubic-bezier(.215, .61, .355, 1)   (GSAP power2.out)
       stagger   120ms     (the title starts 120ms after the overline —
                            R7-13's number, the same "one beat" R7-04 and the
                            hero use elsewhere on this page)

   THE FAIL-OPEN CONTRACT is the reader script's, not this file's: nothing
   here is hidden except under `.mk-building-anim`, and the inline script in
   sections/07-building.html adds that class only when `html.mv` is present —
   script ran AND `prefers-reduced-motion` is not set. No script, no start
   state, no blank section.

   `data-mv-skip` on the eyebrow (HTML) is what lets this section own the
   gesture without a specificity fight against js/myk-motion.js's page-wide
   LEAD adoption, the same device ecology's own eyebrow uses. The headline
   needs no skip: `.mk-building__headline` matches none of that file's
   adoption selectors (checked directly, not assumed).
   -------------------------------------------------------------------------- */
.mk-building {
  --myk-building-in-travel:  20px;
  --myk-building-in-dur:     1000ms;
  --myk-building-in-ease:    cubic-bezier(.215, .61, .355, 1);   /* GSAP power2.out */
  --myk-building-in-stagger: 120ms;
}

.mk-building-anim .mk-building__eyebrow,
.mk-building-anim .mk-building__headline {
  opacity: 0;
  transform: translate3d(var(--myk-building-in-travel), 0, 0);
  transition:
    opacity   var(--myk-building-in-dur) var(--myk-building-in-ease),
    transform var(--myk-building-in-dur) var(--myk-building-in-ease);
}
/* The headline settles 120ms after the overline — one gesture, offset in
   time, not two — matching sections/04-ecology.css §H1's own pair rule. */
.mk-building-anim .mk-building__headline {
  transition-delay: var(--myk-building-in-stagger), var(--myk-building-in-stagger);
}
.mk-building-anim[data-myk-building-in] .mk-building__eyebrow,
.mk-building-anim[data-myk-building-in] .mk-building__headline {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   2. GALLERY BAND — 2767:2294 · 1280 x 608 · pad 4 · gutter 4
   4 + 600 + 4 = 608, and 292 + 608 = 900 (§1.1).
   The track is 2070px wide inside a 1280 frame and is NOT clipped — the EN
   export draws all five cards over transparency past x=1280 (§1.4). That is
   `overflow: visible` on a drag carousel. Do not "fix" it.
   ========================================================================== */

#s07-building .mk-building__gallery {
  padding: var(--mk-space-4);                 /* 4,4,4,4 — the frame's padding */
}

/* --- LIFTED-INLINE (1/3) -------------------------------------------------
   Salient's own per-instance rule from the resort page's inline <style>.
   WITHOUT IT the vendored `.nectar-carousel .flickity-viewport{margin:90px 0}`
   adds 180px to the band and 900 becomes 1080. Scoped to this section so the
   id beats the `[data-controls=touch_total]` margin rules that also target it.
   ---------------------------------------------------------------------- */
#s07-building .nectar-flickity.nectar-carousel.nectar-carousel:not(.masonry).tb-spacing-0 .flickity-viewport {
  margin-top: 0;
  margin-bottom: 0;
}

/* Figma's fixed cell replaces Salient's percentage columns. The id is required:
   the vendored sheet sets .cell width from data-desktop-columns AND again from
   [data-wrap=no-wrap][data-controls=touch_total] inside media queries, and both
   would otherwise win (CONVENTIONS §8.6 — say which rule forced the override). */
#s07-building .nectar-flickity.nectar-carousel .flickity-slider .cell {
  width: var(--mk-07-card-w);                 /* 410 — §5.3 */
  margin: 0 var(--mk-space-4) 0 0;            /* 4px gutter => 414px pitch */
}
/* Trailing space after card 5 comes from the gallery frame's own 4px padding,
   not from a sixth gutter: 4 + 5*410 + 4*4 = 2070, the exact track width. */
#s07-building .nectar-flickity.nectar-carousel .flickity-slider .cell:last-child {
  margin-right: 0;
}

/* Salient has no [data-border-radius="4px"] rule (it ships 3/5/10/15/20).
   `[data-border-radius*=px] .inner-wrap{overflow:hidden}` already matches, so
   only the radius itself is needed. */
#s07-building .nectar-flickity.nectar-carousel[data-border-radius="4px"] .flickity-slider .inner-wrap {
  border-radius: var(--mk-radius-card);       /* 4 — §1.2 */
}

/* Guard for the fixed 600px band if Flickity has not sized the viewport yet
   (it is a `salientlazyscript` and upgrades on first scroll/pointer). */
#s07-building .mk-building__gallery .flickity-viewport {
  min-height: var(--mk-07-card-h);
}

/* ==========================================================================
   3. THE CARD — nectar-fancy-box[data-style="hover_desc"], retargeted
   Figma card = photo (FILL, identity) + one vertical gradient + a bottom-
   anchored white caption. That IS this Salient component: its
   `.box-bg:before` is already `linear-gradient(to bottom, rgba(15,15,15,0),
   rgba(15,15,15,.9))`, i.e. the same object with a different colour and a
   different extent. Retarget, do not rebuild.
   ========================================================================== */

#s07-building .nectar-fancy-box[data-style="hover_desc"] {
  min-height: var(--mk-07-card-h);            /* 600 — replaces the demo's 75vh */
  padding: var(--mk-space-60);                /* 60 all round => content box 290x480 (§1.2) */
}

/* Figma's gradient. Salient paints its version at height:85% bottom-anchored;
   Figma runs the full 600 (§3.2). Stop 0 is fully transparent black. */
#s07-building .nectar-fancy-box[data-style="hover_desc"] .box-bg:before {
  height: 100%;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, var(--mk-07-scrim-bottom) 100%);
}

/* Spec §7: "there is no hover rule on carousel cells anywhere in the mirrored
   page ... the card is a static composition; the drag indicator is the
   interaction." Salient's hover_desc still cross-fades :before -> :after when
   its JS adds .hovered, so both states are pinned to the resting one. Figma
   has exactly one gradient and no hover frame. */
#s07-building .nectar-fancy-box[data-style="hover_desc"] .box-bg:after,
#s07-building .nectar-fancy-box[data-style="hover_desc"].hovered .box-bg:after { opacity: 0; }
#s07-building .nectar-fancy-box[data-style="hover_desc"].hovered .box-bg:before { opacity: 1; }
/* …and the generic `.nectar-fancy-box:hover .box-bg{transform:scale(1.13)}`.

   `!important` ADDED UNDER D03 (r3:building-slider, 2026-08-20), and the bang
   is load-bearing rather than lazy: the thing it now also has to beat is an
   INLINE style, and an `!important` author declaration is the only author-sheet
   thing that outranks one. What writes it, verbatim from
   vendor/…/js/build/init.q-a3a00a7bf4.js:

       Ot.prototype.galleryParallax = function () { … this.instance.on("scroll",
         function () { … o.style.transform = "translate3d(" + d*(s/11) + "px,0,0)";
                       o.style.willChange  = "transform"; }) … }

   with `o` = `.parallax-layer .box-bg`, `s` = -1 in LTR and, for a no-wrap
   carousel, `d` = slide.target + instance.x. It is gated on
   `.nectar-carousel[data-wrap="no-wrap"]` AND
   `.nectar-fancy-box[data-n-parallax-bg="true"]` AND NOT groupCells — the
   identical gate the donor passes, so this is not something we did wrong; we
   run exactly the donor's code path.

   THIS IS THE HORIZONTAL DRAG-PARALLAX, AND IT IS DELIBERATELY OFF. It is a
   different effect from the one D21 restored. D21's words were "the
   parallax-ON-SCROLL the donor has": that is `.parallax-layer`, driven by page
   scroll, and it is untouched and live — MEASURED translateY 78.52 -> 130.52 ->
   182.52 across 800px of page scroll (ratio 0.13, `data-parallax-speed=
   "subtle"`), inline layer height 717px on all five cards at winH 900. Nothing
   about the founders' parallax is missing.

   The horizontal one is off because it cannot be made safe at this card size
   without paying the crop the founder is complaining about. Horizontal parallax
   needs the photo to be WIDER than the card by at least the translate; the
   photo can only get wider by scaling up; scaling up crops. The arithmetic, for
   an 820x1200 plate in a 410-wide card whose parallax layer is 717 tall:

       headroom(s) = (820*s - 410) / 2
       s = 0.5975 (today's `cover` fit, zero extra crop)  ->  40.0px
       s = 0.6533 (the donor's 130% overscan)             ->  62.9px, +9.3% zoom
       s = 0.6829                                         ->  72.2px, +14.3% zoom

   and the translate the JS actually asks for, MEASURED on a FULLY VISIBLE card
   at rest by sweeping the viewport:

       1280 -> 36.09   1366 -> 32.18   1440 -> 28.82   1512 -> 25.55
       1680 -> 35.82   1728 -> 31.45   1920 -> 14.00

   which fits inside 40.0 — but that is the REST frame only. Worked through the
   drag (targets [0, 0, 397, 794, 794], maxX 798 at 1280), the worst fully
   visible cell asks for 38.0px against 40.0px of headroom. A two-pixel margin
   on the exact defect being repaired is not a fix, it is the same bug with a
   longer fuse, so it was not shipped.

   TO PUT IT BACK — one founder decision, two lines, and a measured price:
       #s07-building .nectar-fancy-box .box-bg { width: 130%; left: -15%; }
       …and delete the `!important` below.
   That is variant A above: MAE 28.026 vs the shipped 23.974, i.e. it costs
   4.05 MAE / 3.5pp of Figma fidelity on the card band to buy drag-parallax.

   Residual, named so it is not rediscovered as a bug: galleryParallax still
   RUNS and still writes `style.transform` + `will-change:transform` on every
   carousel scroll event. The paint is pinned, the write is not. Removing the
   write means dropping `data-n-parallax-bg="true"`, which would take the
   founders' vertical parallax with it. Leave it. */
#s07-building .nectar-fancy-box .box-bg { transform: none !important; }

/* R9-11 · lane `r9:building`, 2026-08-22 — RESTORE, DO NOT INVENT.
   *** BOTH FOUNDERS FLAGGED THIS AS A PROCESS FINDING, NOT A FEATURE REQUEST:
   "the original component already has that feature... there was supposed to
   be minimal to no changes to the components... just content updates." ***

   RCA — where the verbatim-lift discipline broke, read from source, not
   assumed:
     1. The donor demo (salient-truth/resort/index.html, the ACTIVITIES block
        this section is lifted from) ships `.nectar-fancy-box[data-style=
        hover_desc]` cards. Salient's OWN vendored CSS for that component
        (vendor/…/css/build/elements/element-fancy-box.q-b2fac09bdb.css) reads:
            .nectar-fancy-box[data-style=hover_desc] .box-bg
              { transition: transform 1s ease, opacity .5s ease .25s;
                transform: scale(1) }
            .nectar-fancy-box[data-style=hover_desc][data-bg-animation=
              short_zoom] .box-bg
              { transition: transform .8s cubic-bezier(.24,1,.3,1),
                             opacity .55s ease .25s }
            .nectar-fancy-box[data-style=hover_desc][data-bg-animation=
              short_zoom].hovered .box-bg
              { transform: scale(1.13) }
        i.e. the "original component" natively zooms its photo 13% on hover —
        EXACTLY the mechanic R8-08 (founder-trace/helix/R8-08.md) restored on
        the recovery-plan family by retargeting `data-bg-animation` from
        `none`/`long_zoom` to `short_zoom`. This build's cards shipped
        `data-bg-animation="none"`, which turns the feature off at the
        attribute Salient itself uses to gate it — nothing to do with the CSS
        below.
     2. THIS FILE THEN ALSO KILLED IT A SECOND, INDEPENDENT WAY: the line
        immediately above (`.box-bg { transform: none !important; }`, D03,
        r3:building-slider) blanket-pins `.box-bg`'s `transform` to `none`
        for EVERY state, hover included. That pin is CORRECT for what it was
        written to stop — Salient's JS-driven horizontal drag-parallax
        (`galleryParallax`, documented above this rule), which this section
        has never been able to afford without paying real Figma-fidelity crop
        (measured above: +4.05 MAE to buy it). But `:hover`/`.hovered` scale
        and JS drag-parallax both write to the same CSS property, so one
        `!important` blanket rule silenced BOTH — the wanted kill and the
        unwanted one — and nobody had reason to look twice at a rule that was
        doing its documented job.
   Two independent, well-reasoned decisions each individually correct at the
   time; their INTERSECTION is the defect the founders are naming. Restoring
   it is therefore two lines, not a rebuild: retarget `data-bg-animation` on
   all five cards (07-building.html, `none` -> `short_zoom`, matching R8-08's
   own recovery-plan retarget verbatim) and carve a `:hover`-scoped exception
   into the blanket kill so the drag-parallax stays dead while the donor's own
   hover-zoom returns. `!important` is required on the exception because it
   has to outrank the OTHER `!important` at equal-or-lower specificity — the
   `.hovered` class and the `[data-bg-animation="short_zoom"]` attribute
   together push this selector's specificity above the blanket rule's, so the
   exception wins independent of source order, but the bang still has to be
   there or the tie is decided by !important alone and the blanket rule wins
   every time. Salient's own `Zi()` (init.q-a3a00a7bf4.js, confirmed vendored
   and loaded on this page) already adds/removes `.hovered` on
   mouseenter/touchstart for every `[data-style="hover_desc"]` element
   document-wide — no JS change needed, exactly the same binder R8-08's
   09/11/12-recovery already ride.

   SCOPE DECISION, recorded rather than silently taken: the donor's hover_desc
   ALSO natively cross-fades `.box-bg:before/:after` (a gradient/tint swap) on
   the same `.hovered` class, and this file's own rule a few lines up (§3,
   "Spec §7: the card is a static composition") pins that cross-fade off,
   keeping the resting Figma gradient the caption always sits on. That pin is
   LEFT IN PLACE here — R9-10b/R9-11 ask specifically for the image
   interactivity cue ("a little bit of parallax... to help me understand
   they're interactive," matched explicitly to the recovery-plan treatment,
   which is scale-only, no gradient swap in R8-08 either), not a caption-
   legibility change nobody asked for. If the founders want the cross-fade
   too after reviewing this pass, it is the same two-line shape as this fix —
   see 12-recovery-44.css's own `.box-bg::before/::after` pin for the sibling
   precedent either way. */
#s07-building .nectar-fancy-box[data-style="hover_desc"][data-bg-animation="short_zoom"].hovered .box-bg {
  transform: scale(1.13) !important;
  transition: transform 0.8s cubic-bezier(.24, 1, .3, 1), opacity .55s ease .25s;
}

/* Photo: scaleMode FILL, identity transform, all seven filters 0 (§3.3). The
   SVG export's pattern matrices resolve to scale = max(410/w, 600/h) with a
   centring offset on every card, which is `cover` + `center` exactly — so the
   non-identity imageTransform on cards 4 and 5 really is the inert leftover
   the spec calls out in L-11. */
#s07-building .nectar-fancy-box .box-bg {
  background-size: cover;
  background-position: center;

  /* MEASURED — this was the single largest remaining error in the section.
     OVERRIDES: `.nectar-fancy-box .box-bg{position:absolute;top:0;left:0;
     height:100.2%;width:100.2%}` (vendor/…/salient-dynamic-styles…css).
     Salient overscans the photo layer by 0.2% to hide the sub-pixel seam its
     own scale-on-hover transform would expose. That transform is already pinned
     to `none` two rules above, and `.inner-wrap` clips, so the overscan buys
     nothing here — and it is not rounding-level.

     Live probe of the capture's own Chrome, before this rule:
         .cell / .nectar-fancy-box / .parallax-layer   410.00 x 600.00
         .box-bg                                       410.81 x 601.19
     The five photos are 820x1200, i.e. EXACTLY 2x the 410x600 card, so Figma's
     `scaleMode: FILL` (§3.3) is a clean 0.5 downscale with no offset. At
     410.81x601.19 `cover` resolves to 0.500992 instead — and because the layer
     is anchored `top:0;left:0`, not centred, the misregistration is zero at the
     top-left and ramps to 0.81px at the right edge and 1.19px at the bottom.
     That is the soft edge-halo across all three visible photos.
     Same defect, same fix, already measured on two sibling sections:
     sections/11-recovery-43.css:182-190 and sections/12-recovery-44.css:221-241
     (the latter recorded MAE 0.99 at 100% vs 3.35 at 100.2%). */
  width: 100%;
  height: 100%;

  /* ⚠ D03 — THE SINGLE LINE THAT CAUSED THE FOUNDER'S "BIGGEST MISS", AND WHY
     IT IS HERE AND NOT IN THE RULE ABOVE.  (r3:building-slider, 2026-08-20.)

     Jehnean: "They're supposed to have a four pixel spacing between these
     images, and it seems like there's a rather strange gap happening now …
     it's almost like the images are incorrectly getting cropped to the right."
     Both sentences are ONE defect and it is not the gutter — the gutter
     measured 4.018 / 3.977 / 4.018 / 3.977 px, i.e. already correct.

     The vendored sheet ships this as a PAIR, and a pair is what it has to be:

         .nectar-flickity .nectar-fancy-box[data-n-parallax-bg=true] .box-bg
             { width: 130%; left: -15% }
         (vendor/…/elements/element-fancy-box.q-b2fac09bdb.css)

     `left:-15%` slides the photo layer left; `width:130%` is the overscan that
     pays for it, so the layer still reaches both card edges. The two numbers
     only mean anything together — they exist so `galleryParallax()` can
     translate the photo horizontally while the carousel is dragged.

     The rule above beats that selector on specificity ((1,2,0) id+2 classes vs
     (0,4,0)) and re-declares `width` — but it never re-declared `left`. So the
     build ran the HALF of the pair that moves the photo and none of the half
     that pays for it. MEASURED live at 1280x900 before the fix:

         .cell / .nectar-fancy-box          x =    4.00   w = 410.00
         .parallax-layer                    x =    2.98   w = 412.05
         .box-bg                            x =  -58.83   w = 412.05
                                            ^ -61.81 = exactly -15% of 412.05

     i.e. every card carried a 61.81px band at its RIGHT edge with no photo in
     it, and 61.81 + 4 = 65.8px of non-image between adjacent photos — which is
     precisely what "a rather strange gap" and "cropped to the right" describe.
     Cards 3-5 were worse still, because galleryParallax's own translateX
     (0, 0, -36.09, -72.18, -72.18 at 1280) stacks on top of the -15%.

     WHEN THIS WAS INTRODUCED: nobody typed it. D21 (FT-20260817) flipped
     `data-n-parallax-bg` from "false" to "true" on all five cards. That
     attribute is in the vendored selector, so a rule that had never matched
     started matching, alone. The section header records D21's cost as "~16% of
     the width" — that was the vertical parallax's `cover` re-fit and it was
     paid knowingly. THIS cost was never measured and was never intended.

     THE FIX, AND THE THREE ALTERNATIVES THAT LOST, ALL MEASURED AGAINST
     figma-truth/sections-en/07-building.png (card band x4-1242, y292-900, the
     2x truth LANCZOS-halved to 1x; MAE over RGB, and the share of pixels off by
     more than 8 on any channel):

         as shipped (left:-15%, width:100%)   MAE 52.839   90.57%
         A  restore the donor pair 130%/-15%  MAE 28.026   82.06%
         B  130%/-15% + background-size:
            auto 100% (photo scale unchanged) MAE 25.386   79.47%
         E  aspect-ratio:820/1200 overscan    MAE 24.606   79.50%
       > C  left:0 + width:100% + no
            horizontal translate              MAE 23.974   78.55%   <- shipped

     B was rejected on pixels, not on taste: `auto 100%` leaves the box wider
     than the painted photo, so a pale strip appears at the right of cards 2
     and 3 — the same defect, quieter. E was rejected on a CSS fact worth
     writing down: `left:0; right:0; margin-inline:auto` does NOT centre an
     absolutely-positioned box once `width` is definite (aspect-ratio made it
     definite). The box is then over-constrained, `right` is ignored in LTR and
     the used `margin-left` computes to 0px — verified in CSSOM, not assumed.
     E was therefore left-anchored, not centred, and its cards 1 and 2 measured
     WORSE than C (24.87 / 27.29 vs 24.42 / 25.82).

     WHY C ALSO DROPS THE HORIZONTAL DRAG-PARALLAX — see the `transform` rule
     below. Short version: keeping it needs overscan, overscan needs headroom,
     and the headroom is 2px. */
  left: 0;
  right: auto;
}

/* ==========================================================================
   §3b. R8-05 — THE CARDS ANIMATE IN, STAGGERED; THE TEXT FOLLOWS, ONE BEAT LATER
   ----------------------------------------------------------------------------
   Founder-trace FT-20260821-jehnean-round8, lane `r8:building`. Jehnean:
   "the cards ... fade in a little bit, and especially be nice if they fade
   in, like, animated in, one, two, three, four, in order ... and then the
   text kinda fades in and moves up for the text that's on the card."

   TWO LAYERS, ONE TRIGGER. A single IntersectionObserver on `.mk-building__gallery`
   (the reader script in sections/07-building.html, same "0px 0px -10% 0px" /
   0.2 geometry R8-04 matches off ecology) sets one attribute,
   `data-myk-building-cards-in`, when the band scrolls into view. Every card
   and every card's text is driven off that one flag with a different
   `transition-delay`, so the "staggered order" IS the choreography — no
   per-card observer, no re-triggering.

   WHY THE WHOLE GALLERY IS ONE TRIGGER, NOT FIVE. Cards 3-5 sit off-canvas at
   every authored width (the track runs to x=2070 over a 1280 ground,
   `overflow:visible` — §2's own note) until the visitor drags the carousel.
   `.mk-building__gallery`'s own box (the 1280-ish frame, not the track) is
   what actually crosses the viewport on page scroll, so observing it once
   fires the whole five-card sequence together — cards 3-5 finish fading in
   on their own schedule, off-screen, and are already at rest opacity by the
   time a drag reveals them. Observing each `.cell` individually would instead
   leave 3-5 stranded at `opacity:0` until dragged, which is a "pop" the
   moment they arrive, not an entrance.

   THE CARD IS OPACITY ONLY — no travel — same rule the GENERAL DIRECTION
   note gives images sitewide ("images fade in FROM THE BACKGROUND COLOUR
   rather than popping in") and the same one PACING.md §2 measured off Omai
   and sections/04-ecology.css §H2 already applies to the blue-zone photograph
   for the identical reason: a full-bleed photo tile that also translates has
   to expose its own edge to do it, and `.box-bg` already carries a hard
   `transform: none !important` pin a few rules up that a translate on this
   card would have to fight for nothing. `.nectar-fancy-box` (not `.cell` and
   not `.box-bg`) is the target: `.cell` carries Flickity's own inline
   positioning transform and must not be touched, and `.box-bg`/`.parallax-layer`
   carry Salient's live inline parallax writes (§3's D21 note) that an added
   transition here would fight every scroll frame. `.nectar-fancy-box` sits
   between the two and owns neither.

   THE TEXT IS A SEPARATE LAYER, ONE BEAT (120ms) BEHIND ITS OWN CARD — same
   "layers resolve on their own clock" idea sections/04-ecology.css §H2 names
   ("FOUR LAYERS, ONE PER ROLE"), and `.heading-wrap` is already bottom-
   anchored inside 60px of card padding (§4), so a 24px rise has room to play
   without touching the card's own edge. `data-mv-skip` is on `.heading-wrap`
   in the HTML — it and its `h4`/`p` children would otherwise ALSO be adopted
   by js/myk-motion.js's page-wide BODY role (`p` is in that table, unscoped,
   for any paragraph >= 24 characters, which every card's body copy is), which
   would double-drive the same text exactly the way R7-04 diagnosed on
   01-who-why's paragraph — two systems multiplying one element's opacity, the
   slower one winning. One owner, this file.

   THE STAGGER STEP IS 120ms, not a new number. R8-05 asked for "something in
   the ~100-150ms family, consistent with R7-04's who-why stagger, unless the
   card count/rhythm argues otherwise." It does not: 120ms is already this
   page's one beat — R7-13 names it for ecology's overline/title pair, R7-04
   names it for the hero's own step, and R8-04 above reuses it again for this
   very section's overline/title. Five cards on a fresh interval would be a
   SECOND rhythm on a page that has spent two rounds converging on one, so it
   stays untouched by the change below.

   R9-02/R9-10a · lane `r9:building`, 2026-08-22. Her words: "the building
   requirements... can you just, like, slow down and sequentially load in
   everything on the section?" MEASURED LIVE FIRST (fresh headless Chrome,
   served==disk, review/r9/building/entrance-before.json, all five standing
   widths): `--myk-07-card-in-dur` / `--myk-07-text-in-dur` both read 600ms,
   stagger 120ms — confirming the header comment above — and the REAL
   wall-clock settle time from trigger to card-5's-text reaching opacity 1
   was 1306-1319ms across the five widths (viewport-independent, as expected
   for a pure-ms clock). Her complaint is that ORDER already reads as
   sequential (confirmed — first-motion samples show only one card moving at
   a time) but the per-element fade itself is too quick to see happen at all;
   R9-02 in the founder-trace explicitly rules out "just bump the stagger" —
   the ask is the DURATION.

   NEW VALUE: 600ms -> 1000ms on both roles, stagger left at 120ms. Not a
   token bump: chosen because this section ALREADY runs its OWN head band
   (eyebrow+headline, R8-04 above) at 1000ms, so 1000ms is not an arbitrary
   pick, it is this section converging its gallery onto the pacing its own
   title already uses — one section, one clock, rather than a title that
   glides for a full second above a gallery that used to snap in over
   six-tenths of one. It is also still the page's own ceiling (sections/
   04-ecology.css §H1's 1000ms), so the section gets slower without becoming
   the slowest thing on the page. MEASURED AFTER in
   review/r9/building/entrance-after.json — see that file and
   founder-trace/helix/R9-02.md for the re-measured settle time.

   A SECOND, PRE-EXISTING BUG THIS MEASUREMENT CAUGHT, dated back to R8-05
   (not introduced this round, but it is exactly why "measure live, don't
   just re-type the token" is the standing rule): re-verifying the ACTUAL
   applied duration on `.heading-wrap` via `getComputedStyle` (not just
   re-reading the custom property off `.mk-building`, which only proves the
   variable resolved, not that anything downstream consumes it) showed
   `transitionDuration: "0.65s, 0.65s"` — NEITHER the old 600ms NOR the new
   1000ms. Root cause: Salient's own vendor CSS ships
       .nectar-fancy-box[data-style=hover_desc]>.inner>.heading-wrap
         { transition: opacity .65s cubic-bezier(.05,.2,.1,1),
                       transform .65s cubic-bezier(.05,.2,.1,1) }
   (element-fancy-box.q-b2fac09bdb.css — its own native hover-reveal
   mechanic, unrelated to entrance) at specificity (0,4,0) — one class/attr
   higher than this section's own `.mk-building-anim .mk-building__gallery
   .heading-wrap` at (0,3,0). No `!important` on either side, so specificity
   alone decided it, and the vendor rule has ALWAYS been winning: the
   `--myk-07-text-in-dur` variable has been dead code on this element since
   R8-05 shipped it, silently running Salient's hardcoded 650ms/different-
   easing instead the entire time. (The DELAY rules two blocks down were
   NEVER affected — `.mk-building-anim .mk-building__gallery .cell:nth-child(N)
   .heading-wrap` is already five classes/pseudo-classes, (0,5,0), which beat
   the vendor rule by one — so the STAGGER always worked; only the per-
   element DURATION was silently overridden.) Fixed below by compounding the
   id onto the SAME element `.mk-building-anim` is added to
   (`#s07-building.mk-building-anim`, not `#s07-building
   .mk-building-anim` — no descendant space, they are the same node, the
   inline reader script's own `section.classList.add("mk-building-anim")`
   proves it), which raises this rule to (1,3,0) and an id beats any number
   of classes. RE-VERIFIED live after the fix: `getComputedStyle` on
   card 5's `.heading-wrap` now reads `1s, 1s` — see
   review/r9/building/heading-wrap-specificity-fix.json. */

.mk-building {
  --myk-07-card-in-dur:    1000ms;                    /* R9-02: was 600ms */
  --myk-07-card-in-ease:   var(--mk-ease-out-quart);  /* tokens.css's general-
                                                          purpose entrance ease;
                                                          not yet consumed
                                                          elsewhere in sections/,
                                                          defined for exactly
                                                          this */
  --myk-07-text-in-dur:    1000ms;                    /* R9-02: was 600ms */
  --myk-07-text-in-travel: 24px;
  --myk-07-stagger:        120ms;   /* the page's one beat — R7-04 / R7-13 / hero.
                                        UNCHANGED — R9-02 is a duration ask, not
                                        an order ask; the sequence was already
                                        sequential. */
}

.mk-building-anim .mk-building__gallery .nectar-fancy-box {
  opacity: 0;
  transition: opacity var(--myk-07-card-in-dur) var(--myk-07-card-in-ease);
}
.mk-building-anim .mk-building__gallery .heading-wrap {
  opacity: 0;
  transform: translateY(var(--myk-07-text-in-travel));
}
/* #s07-building.mk-building-anim, NOT a descendant selector — R9-02 fix.
   Raises ONLY the transition-timing longhands to (1,3,0) so they beat
   Salient's vendor `.nectar-fancy-box[data-style=hover_desc]>.inner>
   .heading-wrap` at (0,4,0), which was silently winning this property
   since R8-05 and pinning the text layer to a hardcoded 650ms regardless
   of this section's own --myk-07-text-in-dur token.

   TWO TRAPS, BOTH MEASURED, NOT THEORETICAL, WHICH IS WHY THIS IS THREE
   RULES AND NOT ONE:
     1. LONGHANDS, NOT THE `transition:` SHORTHAND. The shorthand implicitly
        sets `transition-delay: 0s` for every property it lists, and an
        id-scoped selector beats ANY number of classes — so a shorthand
        `transition: opacity <dur> <ease>, …` here would make THIS rule's
        implied `transition-delay: 0s` beat the stagger rule below
        (`.cell:nth-child(N) .heading-wrap { transition-delay: … }`, 5
        classes/pseudo-classes, (0,5,0)) purely on the id column, collapsing
        every card's text-stagger to 0ms. FIRST version of this fix did
        exactly that — measured: card 5 read `transitionDelay: "0s, 0s"`.
     2. `opacity`/`transform` STAY OUT OF THIS RULE, on the plain (0,3,0)
        selector above instead. Vendor's rule never sets those two
        properties' VALUES (only their transition timing), so the armed
        `opacity:0` above is uncontested and does not need the id boost —
        but the REVEAL rule below (`.mk-building-anim[data-myk-building-
        cards-in] … .heading-wrap { opacity:1 }`) is only (0,4,0). SECOND
        version of this fix put `opacity:0` on the id-scoped rule alongside
        the transition longhands, which made the ARMED state (1,3,0) beat
        the REVEAL state (0,4,0) permanently — measured: card 5's text
        opacity was still exactly `0` at t=3000ms+ after the trigger fired,
        i.e. it could never reveal at all, a strictly worse bug than the one
        being fixed. Keeping opacity/transform on the original, unboosted
        selector leaves the reveal rule's own (0,4,0) free to win over it
        exactly as it always did. */
#s07-building.mk-building-anim .mk-building__gallery .heading-wrap {
  transition-property: opacity, transform;
  transition-duration: var(--myk-07-text-in-dur), var(--myk-07-text-in-dur);
  transition-timing-function: var(--myk-07-card-in-ease), var(--myk-07-card-in-ease);
}

/* The stagger: index N's card starts at (N-1) beats; its own text starts one
   beat after that. Five cells, five pairs — spelled out rather than looped
   because CSS has no loop and `:nth-child` needs the literal index anyway. */
.mk-building-anim .mk-building__gallery .cell:nth-child(1) .nectar-fancy-box { transition-delay: 0ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(1) .heading-wrap    { transition-delay: 120ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(2) .nectar-fancy-box { transition-delay: 120ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(2) .heading-wrap    { transition-delay: 240ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(3) .nectar-fancy-box { transition-delay: 240ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(3) .heading-wrap    { transition-delay: 360ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(4) .nectar-fancy-box { transition-delay: 360ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(4) .heading-wrap    { transition-delay: 480ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(5) .nectar-fancy-box { transition-delay: 480ms; }
.mk-building-anim .mk-building__gallery .cell:nth-child(5) .heading-wrap    { transition-delay: 600ms; }

.mk-building-anim[data-myk-building-cards-in] .mk-building__gallery .nectar-fancy-box {
  opacity: 1;
}
.mk-building-anim[data-myk-building-cards-in] .mk-building__gallery .heading-wrap {
  opacity: 1;
  transform: none;
}

/* prefers-reduced-motion needs no pin here — see §6's note: `.mk-building-anim`
   is added by the reader script only when JavaScript ran AND reduced-motion
   does not match, so under reduced motion `opacity:0` above is never written
   in the first place. */

/* ==========================================================================
   4. CAPTION — one Figma TEXT node, two runs, bottom-anchored
   Last body baseline lands at y=469.5 from the top of the 290x480 content box
   in BOTH languages regardless of line count, i.e. the block is bottom-
   anchored and grows upward (§2.3, L-4). Salient's
   `.vertical-alignment-bottom … .inner-wrap{justify-content:flex-end}` plus
   `[data-style=hover_desc] .inner{align-self:flex-end}` already do this.
   ========================================================================== */

#s07-building .nectar-fancy-box .heading-wrap {
  /* DROP_SHADOW #00000080, offset (0,4), radius 14 (§2.4). One effect on the
     whole text node, so it sits on the wrapper, not on each run. */
  text-shadow: 0 4px 14px var(--mk-shadow-50);
}

#s07-building .nectar-fancy-box .heading-wrap h4 {
  font-family: var(--mk-font-sans);
  font-weight: var(--mk-w-light);             /* 300 — §2.2, stem-ratio 0.078 */
  font-size: var(--mk-size-title);            /* 24 */
  line-height: var(--mk-lh-body);             /* 180% => 43.2px, = the measured
                                                 43.0px ES title pitch */
  letter-spacing: 0;
  text-transform: none;
  color: var(--mk-ink-inverse);               /* #ffffff — §3.1 */
  margin: 0;
  padding: 0;
}

#s07-building .nectar-fancy-box .heading-wrap p {
  font-family: var(--mk-font-sans);
  font-weight: var(--mk-w-regular);           /* 400 — §2.2, stem-ratio 0.0995 */
  font-size: var(--mk-07-body-size);          /* 18 — measured, see the top */
  line-height: var(--mk-07-body-lh);          /* 32px LENGTH, not --mk-lh-body:
                                                 1.8 x 18 = 32.4 would break the
                                                 measured 32.0px pitch */
  letter-spacing: 0;
  color: var(--mk-ink-inverse);
  margin: var(--mk-07-title-body-gap) 0 0;    /* see the 57px solve at the top */
  padding: 0;                                 /* kills Salient's p{padding-bottom:24px};
                                                 without it the bottom-anchored block
                                                 sits 24px high and the last baseline
                                                 misses y=469.5 */
}

/* ==========================================================================
   5. DRAG INDICATOR — D22.  THE SKIN IS NOT DEFINED HERE ANY MORE.
   The only pointer-driven motion in the reference (§7.3). init.js appends ONE
   `<div class="nectar-drag-indicator" data-type="solid">` to <body> and shares
   it across every carousel on the page, so it can never be skinned from a
   section file without skinning every other section too. This block used to
   redefine it, verbatim from the resort demo's inline <style>, under the header
   "⚠ FIGMA SPECIFIES NO COLOUR FOR THIS. #f4ece2 / #000000 are the resort
   demo's own values". Figma does specify a colour — on the branding artboard
   3017:633, which no build lane had opened. Founders, D22: "mouse-hover must be
   the BRAND variant: black background, partial transparency, blur."

   The 22 lines that were here are DELETED, not moved. Their replacement is the
   single body-level token block in sections/04-ecology.css (96px box, #000 @ 50%
   `--mk-pill-scrim-fill`, `--mk-pill-blur` = blur(4px), Material chevrons as CSS
   masks) — which that lane wrote against media-misc.md §3.3 and which explicitly
   asks for THESE lines to be removed, because compose.py emits this file AFTER
   04-ecology.css and at equal specificity source order was handing the page back
   to Salient's opaque 94px disc. Both blocks retire together into
   `css/myk-cursor.css`; that is REQ-ECO-1 / REQ-BR-2 in SHARED-REQUESTS.md.

   WHAT REMAINS THIS SECTION'S JOB is the PAINT, and it is not CSS at all:
   init.js `mouseBind()` writes `background-color` INLINE from this carousel's
   own `data-indicator-bg`, and inline beats every stylesheet. The values now
   live on the element in 07-building.html:
       data-indicator-bg="rgba(0,0,0,0.5)"   data-indicator-icon="#ffffff"
       data-indicator-blur="true"
   Changing the token here without changing the attribute there is a no-op.
   That is the whole of D22 and it is measured, not asserted — before the fix
   the live `.color-circle` computed to `rgb(244, 236, 226)` at opacity 1.
   ========================================================================== */

/* --- LIFTED-INLINE (3/3) — the page-dots parking spot that goes with
   tb-spacing-0. The dots are already visibility:hidden under
   data-controls="touch_total" and are absolutely positioned, so they add no
   height; this only keeps them out of the band if that ever changes. ------- */
#s07-building .nectar-flickity.nectar-carousel:not(.masonry).tb-spacing-0 .flickity-page-dots { bottom: -50px; }

/* ==========================================================================
   6. MOTION SAFETY
   Salient's entrance system ARMS the pre-state in JS (inline opacity:0 +
   translateY(75px)) and then animates it in on a Waypoint. If the runtime arms
   it but the trigger never fires — a hidden tab, a capture harness that does
   not scroll, a paused compositor — the header stays invisible. Honour
   prefers-reduced-motion by pinning the finished state, which is both the
   accessible behaviour and a deterministic mode for the parity capture.
   `!important` is required because the stuck values are INLINE, written by
   Salient's JS (CONVENTIONS.md §8.6 — name the rule that forced it).

   R8-04 DELETED TWO RULES THAT USED TO LIVE HERE, deliberately, not silently:
       #s07-building .wpb_column.has-animation { opacity:1!important; ... }
       #s07-building .nectar-split-heading .inner { opacity:1!important; ... }
   Both pinned Salient's own JS-armed pre-state for elements that no longer
   exist in this section — the `wpb_column.has-animation` eyebrow wrapper and
   the `nectar-split-heading` headline wrapper were both removed with A07 (see
   §1b). Leaving an unmatchable selector with a paragraph of rationale above it
   is the next reader's trap (the precedent for deleting rather than stranding
   is sections/01-who-why.css's R7-04 note). §1b's own entrance needs NO pin
   here: it is driven entirely by the inline reader script in
   sections/07-building.html, which adds `html.mv` only when JavaScript ran
   AND `prefers-reduced-motion` does not match — so under reduced motion
   `.mk-building-anim` is never added, `opacity:0` is never written, and there
   is nothing to unstick. Same fail-open contract sections/04-ecology.css §H
   documents for its own entrance. */

@media (prefers-reduced-motion: reduce) {
  #s07-building .box-bg { opacity: 1 !important; }
}

/* ==========================================================================
   7. RESPONSIVE
   ⚠ EVERYTHING IN THIS BLOCK BELOW 1280 IS INFERRED, NOT READ. Figma contains
   only the 1280 EN frame and the 1440 ES frame, and both are identical: fixed
   120 margin, fixed 108px headline, fixed 410x600 cards, fixed 4px gutter.
   Narrowing the viewport does not resize anything in Figma — it only reduces
   how much of the track is visible (§6). The ladder below follows the Salient
   instance's own breakpoints (desktop 3 / small-desktop 2 / tablet 2 / phone 1)
   and the spec's §6 recommendations, and needs founder sign-off (L-9).
   ========================================================================== */

/* 1000–1279 — 2 cells visible. 2*410 + 4 = 824, so the fixed card still fits;
   keep 410 and keep the peek (§6). Nothing to do.
   Below 1000 the fixed card stops fitting, so it becomes fluid at the
   410:600 (0.68333) ratio the spec asks for. */
@media (max-width: 999px) {
  /* D20 CORRECTION, MEASURED. The rule that used to be here took the cell width
     from the CONTAINER — `calc((100% - 4px) / 2)` — and let `aspect-ratio`
     derive the height from it. That inverts the constraint below 1000: at
     999 x 800 it produced a 497-wide card, a 727-tall card and a 900-tall
     section inside an 800-tall viewport, i.e. exactly the 100px of the next
     section showing that D20 forbids. Measured before this fix: section 900,
     viewport 800.
     Below 1000 the binding constraint is HEIGHT, so the width is derived from
     the height budget instead and the ratio is preserved by construction:
         card height = 100svh - 300  (the same budget as §0)
         card width  = height x 410/600
     At 999 x 800 that is a 500-tall, 341-wide card and a section of exactly
     800. Two cells still fit with the peek the spec asks for (682 of 999). */
  #s07-building .nectar-flickity.nectar-carousel .flickity-slider .cell {
    width: min(calc((100% - var(--mk-space-4)) / 2),
               calc(clamp(320px, calc(100svh - 300px), 900px) * 410 / 600));
  }
  #s07-building .nectar-fancy-box[data-style="hover_desc"] {
    min-height: 0;
    height: clamp(320px, calc(100svh - 300px), 900px);
    aspect-ratio: auto;                             /* height is now explicit;
                                                       leaving the ratio on would
                                                       fight it and re-derive the
                                                       wrong height */
  }
  #s07-building .mk-building__gallery .flickity-viewport { min-height: 0; }
}

/* Below 1280 the 108px headline is the first thing to break: the EN node is
   996px wide, so it collides with the margin at roughly 1236. The clamp is the
   spec's own §6 recommendation and is an INVENTION — Figma does not specify it. */
@media (max-width: 1279px) {
  #s07-building .mk-building__headline {
    font-size: clamp(44px, 8.4vw, 108px);           /* INFERRED — §6 */
    line-height: 1.259;                             /* the 136/108 ratio */
  }
  /* R8-04 — NOTHING ELSE IS NEEDED HERE, and that is the point of retiring
     the ghost. There is only one `.mk-building__headline` now (the
     `.mk-building__headwrap h2` selector this rule used to also target was
     the split-heading wrapper's copy, both deleted with A07), so the clamp
     above is the whole story: it re-wraps exactly when it needs to, and
     `.mk-building__head`'s own `height:auto; min-height` rule right below
     grows the band to match — no second box has to be kept in sync by hand
     any more. The version of this file that declared
     `height: calc(clamp(44px, 8.4vw, 108px) * 1.259)` was correct for one line
     and 108px short at 1024 and at 480, where the headline takes two. */
  /* min-height, not height:auto. D20's card-height arithmetic (§0) is
     `100svh - 292 - 8`; letting the head collapse below 292 here would leave a
     dead band under the gallery on exactly the widths where the headline has
     just been allowed to wrap. A head that needs MORE than 292 still gets it —
     the section is a flex column, so the gallery yields. */
  #s07-building .mk-building__head { height: auto; min-height: 292px; }
}

/* < 690 — the 60px caption inset would otherwise leave ~180px of text on a
   300px card (§6). INFERRED. The `width: 85vw` cell rule that used to open
   this block is DELETED, not shadowed: it had been dead since R7-09's
   restatement (same selector, later in this file) re-took every width <=999
   for the two-up min() — which is exactly the 50/50 phone cram the mobile
   round was called on. The live phone width is the slider-hint block at the
   end of this file; deleting rather than stranding is R8-04's own precedent. */
@media (max-width: 689px) {
  #s07-building .nectar-fancy-box[data-style="hover_desc"] {
    padding: 28px;                                  /* INFERRED — §6 */
  }
}


/* ============================================================================
   R7-09 · REQ-ZEBRA-2 — THE TOP INSET IS ONE BREATH, AND D20'S "300" IS RE-BASED
   ----------------------------------------------------------------------------
   THE RULE (founder-trace/FT-20260821-jehnean-round7 R7-09, derived by lane
   `r7:zebra`; full derivation in sections/01-who-why.css §8):

     ONE BREATH. The empty space between any two neighbouring things on this
     page is one breath, and a breath is always the same fraction of the screen
     you are looking at: one fifth of its SHORTER side, never under 96px and
     never over 208px. A section whose photograph runs to its own edge spends
     no breath there and its neighbour supplies the whole one.

       --mk-breath: clamp(96px, 20vmin, 208px)
         1280x900 -> 180   1440x900 -> 180   1750x980 -> 196
          834x1112 -> 166.8   390x844 -> 96 (floor)

   `04-ecology` is a 2.4-screen pinned stage whose ground runs to its own bottom
   edge, so it spends NOTHING at this boundary and this section carries the
   whole breath. Measured from PIXELS at 1750x980 (review/r7/r7-seams/pxseam.py,
   `spentAbove: 0.0`): 04 gives zero, 07 gives 98.

   MEASURED BEFORE, five fresh headless viewports, served bytes SHA-verified:

       seam 04 -> 07     96 / 96 / **96** / 103.2 / 68.6
       one breath       180 / 180 / **196** / 166.8 / 96

   Half a breath at her band, and — the second half of R7-09 — not
   viewport-proportional at all: 96 is 96 at both 900 and 980.

   ---------------------------------------------------------------------------
   WHY THE TOP INSET IS NOT SIMPLY `padding-top`, AND WHAT THE 292 REALLY IS
   ---------------------------------------------------------------------------
   `.mk-building__head` is a FIXED 292px flex column with `justify-content:
   center`, so the visible top inset is `padding-top + slack/2`, and the slack is
   only zero where the content happens to be exactly 184 tall. Measured:

       width   padT    content   slack/2   inset
       1280     96       184        0        96
       1440     96       184        0        96
       1750     96       184        0        96
       834     62.55    136.2      40.6     103.2      <- padding 62.55, inset 103.2
       390     40       182.8      28.6      68.6      <- padding 40,    inset 68.6

   So `--mk-space-96` is fluid and the head re-centres on top of it, and the
   number the eye sees is neither. Setting `padding-top` alone would have landed
   the tablet inset at 190.7 against a 166.8 target. The fix therefore does two
   things: it pins the padding to the breath AND removes the slack from the top
   by top-anchoring the head below 1280, where the headline wraps and the slack
   exists. At >=1280 the head height equals its content exactly, so `flex-start`
   and `center` are the same box and Figma's CENTER alignment is untouched.

   ---------------------------------------------------------------------------
   THE COST, STATED PLAINLY: THE CARD LOSES WHAT THE HEAD GAINS
   ---------------------------------------------------------------------------
   §0 (D20) holds this section at exactly one screen and derives the card height
   from the leftovers:

       card height = clamp(420px, calc(100svh - 300px), 900px)
       300 = head 292 + the gallery frame's own 4 + 4

   That 300 is not a constant, it is `head + 8` — and the head is no longer 292.
   Re-based, the identity is unchanged:

       --mk-07-head-h = breath + 196        (196 = 24 eyebrow + 24 gap
                                              + 136 headline + 12 padding-bottom)
       card height    = clamp(420px, calc(100svh - var(--mk-07-head-h) - 8px), 900px)

   MEASURED CONSEQUENCE — the card gets shorter by exactly what the head gains:

       width        head 292 -> 376/376/392/362.8/292
       card height  600 -> 516 · 600 -> 516 · 680 -> 580 · 812 -> 741.2 · 544 -> 544
       section      900 / 900 / 980 / 1112 / 844      UNCHANGED, still 100svh

   **The 410 x 600 Figma card is now 410 x 516 at the design size.** That is a
   real departure and it is recorded rather than absorbed: OBS-SEAMS-03 in
   founder-trace/SHARED-REQUESTS.md. It is the same kind of departure D20
   already made — D20 abandoned the fixed 600 when it made the height
   `100svh - 300`, so the card has been viewport-derived since then and 600 was
   only ever true at exactly 900svh. The alternative was to let the section grow
   past one screen, which cuts the bottom off the cards at every scroll position
   that shows the headline, and D20's founder wording is "must snap to full
   screen; neighbours not visible".

   THE OTHER TWO SITES OF THE SAME 300 ARE RE-BASED TOO — §7's `<=999` cell
   width and fancy-box height both spell `calc(100svh - 300px)` and both are
   restated below against `--mk-07-head-h`. Missing either one re-opens the
   exact defect §7's own header describes: a card taller than the height budget
   and the next section showing.

   ONE HEAD NOW, NOT TWO. R8-04 (2026-08-21, lane `r8:building`) retired A07's
   travelling ghost/scroller apparatus, so there is exactly one
   `.mk-building__head` element left — the note used to read "both heads move"
   because a second, absolutely-positioned copy of this band carried the real,
   travelling headline and had to be kept in sync by inheriting every rule
   written on `.mk-building__head`. That second element no longer exists; the
   selector below is unchanged because it never needed to say "both" — it was
   always just `.mk-building__head`, and one class now correctly means one box.

   SPECIFICITY AND ORDER. `#s07-building …` is (1,1,0), the same device §1 and
   §7 use, and this block is last in the file so it wins over both on source
   order at equal weight. The `<=1279` and `<=999` branches are RESTATED here
   rather than edited in place, so §1 and §7 still read as the Figma record and
   the R7-09 delta is one contiguous block.

   THE BREATH IS A `var()` WITH A LITERAL FALLBACK ON PURPOSE. `--mk-breath`
   belongs in css/tokens.css (REQ-ZEBRA-1, not yet landed; tokens.css is
   single-owner). Until it does the fallback IS the value.
   ========================================================================== */

#s07-building {
  --mk-07-head-h: calc(var(--mk-breath, clamp(96px, 20vmin, 208px)) + 196px);
  --mk-07-card-h: clamp(420px, calc(100svh - var(--mk-07-head-h) - 8px), 900px);
}

#s07-building .mk-building__head {
  height: var(--mk-07-head-h);
  padding-top: var(--mk-breath, clamp(96px, 20vmin, 208px));
}

/* Below 1280 the headline wraps, so the head has slack and `center` would put
   half of it above the eyebrow and re-inflate the inset. Top-anchor instead:
   the surplus falls between the headline and the gallery, which is an internal
   gap and not a seam. `height: auto` + `min-height` is §7's own shape — a head
   that needs MORE than the budget still gets it and the gallery yields. */
@media (max-width: 1279px) {
  #s07-building .mk-building__head {
    height: auto;
    min-height: var(--mk-07-head-h);
    justify-content: flex-start;
  }
}

/* §7's two other spellings of `100svh - 300`, re-based on the same head. */
@media (max-width: 999px) {
  #s07-building .nectar-flickity.nectar-carousel .flickity-slider .cell {
    width: min(calc((100% - var(--mk-space-4)) / 2),
               calc(clamp(320px, calc(100svh - var(--mk-07-head-h) - 8px), 900px)
                    * 410 / 600));
  }
  #s07-building .nectar-fancy-box[data-style="hover_desc"] {
    height: clamp(320px, calc(100svh - var(--mk-07-head-h) - 8px), 900px);
  }
}

/* ==========================================================================
   MOBILE ROUND · lane `building` — THE SLIDER-HINT WIDTH AT THE PHONE TIER
   Founder feedback (mobile recon, 412x915): the gallery crams two cards
   side-by-side 50/50. Wanted instead: the Salient slider-hint — card 1 at
   about three-fourths of the width, card 2 cropped off the right edge as the
   visible cue that the row drags.

   WHY 50/50 WAS SHOWING AT PHONE AT ALL: §7's `<=689` cell rule (85vw, now
   deleted — see §7's own note) had been dead since R7-09 landed: the R7-09
   restatement above re-spells the `<=999` two-up min() with an IDENTICAL
   selector LATER in the file, so source order handed every phone width back
   to the two-up branch. Measured live before this fix at 412x915: cells
   200.00px at x = 4 / 208 — the exact 50/50 in the founder's screenshot.
   This block sits after that restatement for the same source-order reason.

   THE WIDTH IS 78vw AT <=690 — the band system's own phone tier, not §7's
   689. With the gallery frame's 4px pad + the 4px gutter, card 2's visible
   strip at 412 is 412 - 8 - 321.4 = 82.6px = 20.0vw, and card 1 is 78vw —
   the asked ~three-fourths card + ~15-20vw hint. MEASURED after: cell
   321.36px at x = 4, card 2 at x = 329.34, docWidth 412 = innerWidth (no
   page overflow).

   WHY NOT THE WHOLE <=999 RANGE, THE MAP OF WHO OWNS WHAT WIDTH:
     <=690    THIS RULE. css/_band-mobile.css §3(b) deliberately does not
              own 07's width and defers here (its B3 note). B3's preferred
              spelling `min(410px, 78vw)` and this rule disagree only at
              526-690, where min() would re-inflate the second card's
              reveal to ~34vw at 640 — the founder ask is the hint, so the
              proportional 78vw ships and B3 stays open for one owner.
     691-999  css/_band-xs.css §4 authors `min(410px, 78vw)` on this exact
              selector and loads after every sections/*.css, so it wins the
              band on source order no matter what is written here (it also
              filed BR-XS-5 to retire the R7-09 min() above at source).
              That band was never the 50/50 defect: 410px cards with the
              third card peeking. If _band-xs.css ever retires without
              executing BR-XS-5, the R7-09 min() above re-exposes the
              two-up cram at 691-999 — that hazard is BR-XS-5's to close.

   HEIGHT IS DELIBERATELY NOT TOUCHED: the R7-09 re-based clamp above still
   owns it, so the section stays exactly one screen (D20). The card's aspect
   at phone departs from 410:600 either way — 200x615 = 0.33 before this
   fix, 321x615 = 0.52 after, nearer the design's 0.68.

   DRAG NEEDS NOTHING FROM HERE, verified live rather than assumed (fresh
   headless Chrome, mobile metrics + touch emulation, a real
   Input.dispatchTouchEvent swipe): the instance is Flickity
   (`flickity-enabled is-draggable`, options.draggable true, contain true —
   contain is what rests card 1 flush-left despite cellAlign:center), and
   the swipe moved selectedIndex 0 -> 2. There is no overflow-x/scroll-snap
   surface to configure: the viewport is Salient's `overflow:visible` box
   and the page is already guarded by html-level `overflow-x:hidden`.
   ========================================================================== */
@media (max-width: 690px) {
  #s07-building .nectar-flickity.nectar-carousel .flickity-slider .cell {
    width: 78vw;
  }
}

/* ==========================================================================
   R14-03 — HORIZONTAL DRAG-RELEASE SPEED — "a little bit fast... slow down
   the horizontal scroll." (founder-trace/FT-20260823-jehnean-round14.md)

   THE MECHANISM, READ LIVE BEFORE TOUCHING ANYTHING (not assumed): this
   gallery's release/snap motion is NOT driven by Flickity's own physics
   options alone. `new Flickity(...).options` confirmed live —
   `selectedAttraction: 0.025, friction: 0.28` — both Flickity's library
   STOCK DEFAULTS, never customized by this build either way. The dial that
   actually governs the visible speed is a SEPARATE, already-vendored CSS
   rule this section had never touched or even mentioned before this item
   (grepped: zero prior hits for "transition-enabled" anywhere in this repo's
   own sections/*.css):

     vendor/.../plugins/flickity.q-458c954239.css —
     .nectar-flickity.nectar-carousel[data-format=default][data-wrap=no-wrap]
       .transition-enabled .flickity-slider { transition: transform .3s
       cubic-bezier(.2,.6,.3,1) }

   `.transition-enabled` gets added ~200ms after Salient's own carousel-init
   (init.q-a3a00a7bf4.js) and this element matches every condition
   (data-format="default", data-wrap="no-wrap", both classes on one node —
   confirmed live via `el.className` on the initialized instance). Flickity's
   physics loop (`animate()`) still writes `.flickity-slider`'s transform via
   inline style EVERY requestAnimationFrame, whether the trigger was a drag
   release or a dot/arrow click — so this CSS transition doesn't replace that
   loop, it re-triggers underneath it on every one of those ~60fps writes,
   compounding into a longer, softer visible tail than the bare 300ms reads.
   MEASURED, not assumed (review/r14-slider-speed/verify-report-baseline.json,
   method: review/r14-slider-speed/drag_speed_verify.py — a REAL trusted CDP
   drag via Input.dispatchMouseEvent, not the interactive preview pane, which
   was confirmed during this item to throttle requestAnimationFrame to ~1Hz
   even while reporting itself visible, making in-pane wall-clock timing
   worthless): actual release-to-settle at the declared 300ms was several
   times that in every real drag sampled.

   THE FIX is duration-only, on the SAME property, SAME easing curve Salient
   already ships — doubled, matching this codebase's own established "slow,
   premium" range elsewhere in this file and in 14-amenities.css (0.55s-1s;
   see e.g. the `--mk-am-reveal-dur` families and the `transform 1s ease`
   bg-layer rest transition). NOT touched: `selectedAttraction`/`friction`
   (still Flickity's own defaults — this file does not own the vendored JS),
   the drag-indicator cursor CSS (D22, below — separate mechanism, separate
   element, untouched), or the active-drag scale/press feedback.

   GUARDED to `:not(.is-dragging)` on purpose: `.is-dragging` is added to
   THIS SAME element (confirmed live) the instant Flickity's own drag
   threshold is crossed and removed synchronously on release/`_dragEnd`.
   Scoping the slower transition to exclude it means the WHILE-DRAGGING
   1:1 pointer-tracking feel — proven comfortable today, not reported as a
   problem, out of this item's scope — is completely unchanged; only the
   post-release snap this item was asked to slow down is touched. `#s07-
   building` on the front of the selector is there for specificity headroom
   over the vendored rule (3 attribute/class conditions), not because ID
   selectors are this file's normal style — cascade order already wins here
   too (sections load after the vendored stylesheet, compose.py's own
   ordering guarantee) but a future stylesheet reorder should not be able to
   silently re-lose this fix. */
#s07-building .nectar-flickity.nectar-carousel.transition-enabled:not(.is-dragging) .flickity-slider {
  transition: transform 0.6s cubic-bezier(.2, .6, .3, 1);
}
