/* ============================================================================
   premium.css — Landscaping Outlook premium design system
   ----------------------------------------------------------------------------
   The shared cinematic skin loaded on every page (after base.css + marketplace.css).
   ONE stylesheet renders correctly on the live `body.theme-published` skin AND
   the sandbox warm skin, because:
     · light STUDIO surfaces use --bg, --bg-2, --surface-soft, --ink(s), --border(s), --accent
       (the published theme already overrides these to white/silver/black + emerald),
     · dark CINEMA bands use --forest / --canopy / --sage, which are theme-independent.
   No forked CSS. The palette is NOT redefined here — we reuse base.css token names.

   THE ONE PALETTE LAW (WCAG AA):
     emerald --accent (#1f9d57) FAILS AA for white text / body-size text (3.49:1).
     → white-text-on-emerald CTAs + any body-size emerald TEXT use --accent-dark (#157a43, ~4.8:1).
     → bright --accent is kept ONLY for fills, dots, strokes, scan-lines, and the
       LARGE headline italic word (which clears AA as "large text").

   CONTRACT (the class names / data-attributes / custom props JS + HTML share):
   --- SMOOTH BY CONSTRUCTION (2026-06-20): there is NO per-scroll-frame JS prop
       write any more. The old spine (--p, --py, --card-x, --cam-scale, --ky, --ko,
       written on a shared rAF every scroll frame) is GONE — the dominant glitch on
       real hardware. Motion is now COMPOSITOR-driven: either the @supports view()
       scroll-timelines (§17) or one-shot reveal-on-enter transitions toggled by a
       class (.in / .scene-in) that premium.js adds ONCE via IntersectionObserver.
       Remaining authored custom props (compositor only, NOT written per frame):
     --seam  proof-walk wipe seam authored start (clip-path inset right edge); the
             view()/scene-in animations drive the open (they don't write --seam).
     --frame build-film frame index (informational; the one-time intro paints it).
     --i     stagger index on .r-child / intent chips (one-time DOM pass).
   --- set-piece roots: [data-build-film] still drives the ONE-TIME hero intro;
       the other scenes (proof / flywheel / proposal fold) no
       longer carry a JS-binding attr — the @supports view() rules attach to each
       SECTION's own scroll-timeline (compositor), and the no-view() fallback uses
       a one-shot .scene-in class. NO position:sticky pins, NO tall tracks; the
       page never freezes and the scroll thread does zero per-frame work.
   --- the live reveal/nav contract (unchanged):
     reveals        [data-reveal] / .r-child (+ .in when shown), --i stagger index
     nav state      body.over-dark, body.scrolled, .skip-link, .nav-sentinel
   --- IMMERSIVE HERO COMPONENT-ASSEMBLY (§IV-1): a FULL-BLEED in-flow hero that
       ASSEMBLES component by component on load (no pin, no canvas, no scroll loop).
       .studio.hero-build[data-build-film][data-build-assembly] > .build-stage >
         .build-camera > .build-plane-back, .build-layers > .bl[data-step] (each a
           REAL masked frame s_n revealed in its own region via clip-path/mask),
           .build-frame (LCP dusk poster), .build-callouts > .callout;
         .build-scrim, .build-overlay>.build-words (h1 + CTAs), .build-pricepill,
         .build-intent>.intent-chip, .build-hud>.build-caption/.build-progress/
         .build-replay, .build-alt(sr). STATIC: layers hidden, dusk poster + words.
   --- sections:  .studio-section .cinema(.canopy) .reading-rest .scene-slate .eyebrow
   --- PROOF WALK (§IV-3): IN-FLOW stacked plates (no pin/scrub).
       .proof[data-proof-walk] > .proof-track > .plate > .plate-stage >
         .plate-before/.plate-after, .plate-seam (--accent hairline, transform-
         driven by --seam 62%→0%), .plate-cap (+ .price pill). + .proof-rail.
       static fallback: .proof-fallback (resolved stacked plates).
   --- journey (§IV-6): .journey (+ self-draw connector ::before) .jstep .num
   --- fork (§IV-7): .fork .fork-card(.build) .fork-tag .fork-chips
   --- FLYWHEEL (§IV-8): IN-FLOW card morph (no pin). .flywheel[data-flywheel] >
       .flywheel-bg + .flywheel-flow > .flywheel-stage > .job-card (morphs via --p:
       short translateX + face cross-fade + bg studio→canopy) + .flywheel-seam;
       static .flywheel-fallback two-up + .seam arrow.
   --- PRO BAND + PROPOSAL FOLD (§IV-9): .pro-band(.canopy) .pro-list
       .proposal-fold[data-proposal-fold]>.pf-render/.pf-paper/.pf-logo.
   --- gallery (§IV-10): .gallery .gallery-tile .gallery-cap (services tour)
   --- pricing/faq/final (§IV-12): .pricing .pricing-col .pricing-rule .pricing-free
       .faq .faq-item(<details>)  .final-cta(.in ignites the italic word)
     ============================================================================ */

/* ---------------------------------------------------------------------------
   0. TOKENS — only ONE net-new token (the hotter hero size). Everything else
   reuses base.css. Plus a few private premium helpers (mono family, cinema ink,
   grain, measure) that don't collide with the palette.
   --------------------------------------------------------------------------- */
:root{
  /* ---- FLUID TYPE SCALE (MASTER-PROMPT §III) -----------------------------
     base.css already ships --fs-hero/--fs-h1/--fs-h2/--fs-lead/--fs-body/
     --fs-eyebrow as fixed-cap clamps. The film wants a HOTTER, no-cliff scale
     keyed to the set-piece headlines. To avoid clashing with base.css we add
     NET-NEW tokens (--fs-display + --fs-h3/--fs-caption/--fs-micro) and only
     RE-TUNE the premium hero token (--fs-hero-xl). We DON'T redefine base's
     --fs-h1/--fs-h2/--fs-lead/--fs-body (kept for every non-film page); the
     film set-pieces reach for the tokens below explicitly. */
  --fs-display:  clamp(2.75rem, 6vw + 1rem, 6.5rem);   /* hero / set-piece head */
  --fs-h1:       clamp(2.25rem, 4vw + 1rem, 4.5rem);   /* premium section head (film) */
  --fs-h2:       clamp(1.75rem, 2.5vw + 1rem, 3rem);   /* sub-section */
  --fs-h3:       clamp(1.35rem, 1.5vw + 1rem, 2rem);   /* card title */
  --fs-lead:     clamp(1.15rem, 1vw + 1rem, 1.5rem);   /* lead / bridge */
  --fs-body:     clamp(1rem, 0.5vw + 0.9rem, 1.125rem);/* body */
  --fs-caption:  clamp(0.8rem, 0.3vw + 0.75rem, 0.9rem);/* mono caption / eyebrow */
  --fs-micro:    0.7rem;                                /* stage labels, pill units */
  /* Refined editorial HERO scale — confident, not billboard. The hero headline
     lives in a NARROW left rail, so it must NOT reach the full-bleed --fs-display
     (up to 104px) the founder repeatedly flagged as "too big". This tops out at
     ~68px desktop and reads in 2–3 lines, not 6 cramped ones. The full-width
     centred set-piece heads (proof walk / final CTA) keep the larger display. */
  --fs-hero-xl: clamp(2.25rem, 3.6vw + 1rem, 4.25rem);

  /* line-heights (MASTER-PROMPT §III) */
  --lh-display: 1.05;
  --lh-head:    1.1;
  --lh-body:    1.55;

  /* ---- SPACING SCALE — extend base.css's --sp-1..--sp-10 with the 8px-grid
     names the brief uses (--sp-12/--sp-16/--sp-20/--sp-24/--sp-32) so set-piece
     rhythm reads directly off the spec. base's --sp-1..--sp-10 are untouched. */
  --sp-12: 48px;  --sp-16: 64px;  --sp-20: 80px;  --sp-24: 96px;  --sp-32: 128px;
  /* section rhythm — dark bands take the larger end */
  --band-pad: clamp(64px, 10vw, 160px);
  --gutter:   clamp(16px, 4vw, 40px);

  /* ---- RADII (brief names; mapped onto base's --radius* so both work) ----- */
  --r-sm: 8px;  --r-md: 14px;  --r-lg: 22px;  --r-pill: 999px;
  /* ---- ELEVATION ---------------------------------------------------------- */
  --shadow-card: 0 1px 2px rgba(12,14,17,.04), 0 8px 24px rgba(12,14,17,.06);

  /* expo-out — the premium "settle" curve used by the flow motion system */
  --ease-expo: cubic-bezier(0.16, 1, 0.3, 1);

  --font-mono: 'JetBrains Mono','SF Mono',ui-monospace,Menlo,monospace;
  --limestone: #F7F4EE;                       /* warm-white type on dark cinema */
  --limestone-2: #C7CFC8;                     /* muted warm-white on dark */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  --measure: 62ch;
  --ease-reveal: cubic-bezier(.22,.61,.36,1);

  /* ---- THE SCROLL-PROGRESS SPINE (MASTER-PROMPT §III/§V) ------------------
     Declared here so the cascade has a value before JS runs and the STATIC floor
     can pin them to their final frame.
       --p   : generic scene progress 0→1 (reveals, dissolves, morphs)
       --frame: build-film frame index (informational; canvas paints from it)
     FIX #21: --seam was removed — after the seam inversion (fix #3) the plate
     animations use explicit clip-path insets, not var(--seam), so the token was
     dead and its 50%-vs-62% authored-start mismatch could only mislead a fix. */
  --p: 0;
  --frame: 0;
}

/* ===========================================================================
   0b. THE ORGANIC-LUXURY SKIN — the BREATHING art direction (IMMERSIVE-BRIEF-V2
   §1), scoped to the HOME FILM ONLY. base.css publishes the palette body-wide and
   every other page depends on body.theme-published's tokens, so we must NEVER edit
   base.css or :root here. Instead we RE-TUNE the same token NAMES on a scope that
   ONLY the homepage matches: it carries the component-assembly hero (.hero-build),
   nothing else does. Retuning the tokens reskins every studio surface for free
   (cards, eyebrows, borders, accents) because premium.css already reads the tokens,
   not literal colours. The dark CINEMA bands keep --forest/--canopy/--sage.

   GATE: `body.theme-published:has(.hero-build)` for :has-capable browsers; the JS
   fallback `html.organic` (premium.js sets it only when .hero-build is present)
   covers the rest. We list BOTH so neither path is a hard dependency. The sandbox
   (non-theme-published) also gets it via the html.organic arm. /app excluded (no
   premium.css, no .hero-build). ALL overrides live HERE; base.css is untouched. */
body.theme-published:has(.hero-build),
html.organic body{
  /* breathing off-white ground (replaces the cold silver #fff/#f7f8f9) */
  --bg: #F9F9F9;
  --bg-2: #F2F1ED;              /* warm paper wash for .paper bands */
  --surface-soft: #ECEFEC;
  --panel: #FFFFFF;            /* cards stay crisp white for contrast */
  /* forest-green-undertone near-black ink (was the cold #0c0e11) */
  --ink: #1C2A22;
  --ink-2: #3A4A40;
  --muted: #5C6A60;            /* slate-green secondary, +warmth */
  --dim: #97A299;             /* warmer light-grey for separators */
  --border: #E4E6E1;          /* warmer hairline */
  --border-strong: #CBD0C8;   /* slate */
  /* forest leaf-green — micro-accent fills/dots/strokes ONLY (large fills clear AA
     as "large text"; body-size + white-on-green CTAs use --accent-dark, AA ~5:1) */
  --accent: #2F8F5B;
  --accent-dark: #1F6B41;
  --accent-soft: #EAF3EC;
  --accent-2: #5C6A60;
  --accent-glow: rgba(47,143,91,0.18);
  /* NEW earthy secondaries (the brief's terracotta + gold + slate micro-accents) */
  --terracotta: #C0613B;      /* eyebrow tick separators, hover underlines, act-rule */
  --terracotta-dark: #9E4E2F; /* AA body-size terracotta text */
  --gold: #B8862F;            /* micro-accent ONLY: live-estimate dot, one hairline */
  --slate: #5F6E64;           /* mono caption/eyebrow ink on light (reads slate, AA 4.9:1 on #F9F9F9) */
}

/* the pulse keyframe is authored in emerald rgba; on the organic skin re-tune its
   ring to the leaf-green token so the dot halo matches (compositor opacity only). */
@keyframes pulse-organic{
  0%{ box-shadow: 0 0 0 0 rgba(47,143,91,0.45); }
  70%{ box-shadow: 0 0 0 9px rgba(47,143,91,0); }
  100%{ box-shadow: 0 0 0 0 rgba(47,143,91,0); }
}
body.theme-published:has(.hero-build) .price-pill .dot,
body.theme-published:has(.hero-build) .build-pricepill .dot,
body.theme-published:has(.hero-build) .plate-cap .price .dot,
body.theme-published:has(.hero-build) .studio-status .dot,
html.organic .price-pill .dot,
html.organic .build-pricepill .dot,
html.organic .plate-cap .price .dot,
html.organic .studio-status .dot{ animation-name: pulse-organic; }

/* EYEBROW reads SLATE (not flat grey) with TERRACOTTA tick `<i>·</i>` separators,
   so the mono captions feel editorial-warm, not cold UI grey. The accent-dark cast
   is kept for the act-eyebrow's "go" hue, but the standalone .eyebrow on studio
   light leans slate per the brief. Scoped to the organic skin. */
body.theme-published:has(.hero-build) .eyebrow,
html.organic .eyebrow{ color: var(--slate); }
body.theme-published:has(.hero-build) .eyebrow i,
html.organic .eyebrow i{ color: var(--terracotta); font-style: normal; }

/* TYPE — editorial-serif cut: enable Fraunces SOFT/WONK axes + optical sizing for
   the high-contrast Ogg/Editorial-New feel, with slightly looser optical tracking.
   One net rule on the existing display-head group; the italic <em> stays leaf-green
   on light (already wired to --accent) / --sage on dark (already wired). */
body.theme-published:has(.hero-build) .h-display,
body.theme-published:has(.hero-build) .studio h1,
body.theme-published:has(.hero-build) .studio-section h2,
body.theme-published:has(.hero-build) .section-head h2,
body.theme-published:has(.hero-build) .cinema h2,
body.theme-published:has(.hero-build) .pro-band h2,
body.theme-published:has(.hero-build) .flywheel h2,
body.theme-published:has(.hero-build) .reading-rest p.big,
body.theme-published:has(.hero-build) .bridge p.big,
html.organic .h-display, html.organic .studio h1, html.organic .studio-section h2,
html.organic .section-head h2, html.organic .cinema h2, html.organic .pro-band h2,
html.organic .flywheel h2, html.organic .reading-rest p.big, html.organic .bridge p.big{
  font-optical-sizing: auto;
  letter-spacing: -0.024em;
  font-variation-settings: 'SOFT' 0, 'WONK' 1, 'opsz' 60;
}

/* SECTION AIR — more whitespace so the whole film breathes (brief §1 "breathing").
   Retunes the rhythm tokens + section-head measure ON THE ORGANIC SCOPE only. */
body.theme-published:has(.hero-build),
html.organic{ --band-pad: clamp(88px, 11vw, 180px); }
body.theme-published:has(.hero-build) .section-head,
html.organic .section-head{ max-width: 720px; margin-bottom: 72px; }
body.theme-published:has(.hero-build) .section-head p,
html.organic .section-head p{ line-height: 1.66; }
body.theme-published:has(.hero-build) .studio-section.paper,
html.organic .studio-section.paper{ background: var(--bg-2); }
/* roomier grids + a hairline terracotta top-rule on service/gallery tiles on hover */
body.theme-published:has(.hero-build) .gallery-grid,
body.theme-published:has(.hero-build) .st-grid,
html.organic .gallery-grid, html.organic .st-grid{ gap: clamp(20px, 2.2vw, 32px); }
body.theme-published:has(.hero-build) .service-tile,
html.organic .service-tile{ position: relative; }
body.theme-published:has(.hero-build) .service-tile::after,
html.organic .service-tile::after{
  content:''; position: absolute; left: 0; right: 0; top: 0; height: 2px; z-index: 3;
  background: var(--terracotta); transform: scaleX(0); transform-origin: left center;
  transition: transform .4s var(--ease-reveal);
}
body.theme-published:has(.hero-build) .service-tile:hover::after,
body.theme-published:has(.hero-build) .service-tile:focus-visible::after,
html.organic .service-tile:hover::after,
html.organic .service-tile:focus-visible::after{ transform: scaleX(1); }

/* FLUID ORGANIC HOVERS (brief §2 "water/shifting-soil feel") — a soft breathing
   edge on image cards (border-radius eases) + the existing translateY lift. Pure
   transform/border-radius/box-shadow (compositor), reduced-motion zeroes it (§18). */
body.theme-published:has(.hero-build) .gallery-tile,
body.theme-published:has(.hero-build) .fork-card,
html.organic .gallery-tile, html.organic .fork-card{
  transition: transform .3s var(--ease), box-shadow .3s var(--ease), border-radius .4s var(--ease-reveal);
}
body.theme-published:has(.hero-build) .gallery-tile:hover,
body.theme-published:has(.hero-build) .fork-card:hover,
html.organic .gallery-tile:hover, html.organic .fork-card:hover{ border-radius: 20px; }

/* text-wrap discipline (MASTER-PROMPT §III): display+h1 balance, body pretty */
.studio h1, .cinema h2, .section-head h2, .pro-band h2, .flywheel h2,
.final-cta h2, .reading-rest p.big, .build-head h2, .proof .proof-head h2, .h-display{
  text-wrap: balance;
}
.lede, .reading-rest p, .studio .lede, .cinema p, .section-head p,
.fork-card > p, .pro-band p, .pricing-col p, .faq-item p{
  text-wrap: pretty;
}

/* ---------------------------------------------------------------------------
   1. TYPOGRAPHY — the brand fingerprint, amplified.
   Display = Fraunces (one italic word per head). Mono = JetBrains (tool/scene UI).
   --------------------------------------------------------------------------- */
.h-display, .h-section, .h-hero,
.studio h1, .studio-section h2, .cinema h2, .reading-rest p.big,
.fork-card h3, .pro-band h2, .flywheel h2, .pricing h2, .gallery h2,
.faq-block h2{
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.022em;
  font-feature-settings: 'ss01','liga','kern';
  text-wrap: balance;
}

/* the signature device: one italic Fraunces word per head.
   bright emerald is allowed here because hero/H1/H2 sizes are "large text" (AA-safe).
   Covers .bridge p.big em (was flat ink) alongside the studio/section heads. */
.studio h1 em, .studio-section h2 em, .bridge p.big em,
.fork-card h3 em, .flywheel h2 em, .pricing h2 em, .gallery h2 em,
.faq-block h2 em{
  font-style: italic;
  color: var(--accent);
  position: relative;
}
/* FIX #14: an inline <em> makes ::after{left:0;right:0} collapse to width 0 (the
   flywheel underline was absent). inline-block gives the pseudo a box to span. */
.draw-underline em{ display: inline-block; }
/* the italic word's underline draws on reveal (emerald stroke = a "go" accent) */
[data-reveal] .draw-underline em::after,
.draw-underline em::after{
  content:''; position:absolute; left:0; right:0; bottom:-.06em; height:2px;
  background: var(--accent); transform: scaleX(0); transform-origin: left center;
  transition: transform .6s var(--ease-reveal) .15s;
}
[data-reveal].in .draw-underline em::after,
.draw-underline.in em::after{ transform: scaleX(1); }
/* STATIC floor: when the motion system is OFF (no html.flow — narrow/coarse), any
   draw-underline head that isn't inside a [data-reveal] (e.g. the pro-band h2)
   would otherwise rest at scaleX(0). Show it drawn so the brand fingerprint reads. */
html:not(.flow) .draw-underline em::after{ transform: scaleX(1); }

/* on dark cinema bands the emphasis word turns sage (warm, never cold) —
   bright emerald-as-decoration on dark fails the DESIGN-SYSTEM guardrail, so
   every <em> inside a dark band is overridden. */
.cinema h2 em, .pro-band h2 em, .job-card.is-pro em,
.cinema .proof .proof-head h2 em{ font-style: italic; color: var(--sage); }

/* mono eyebrow / scene slate — "a real machine doing real work" */
.eyebrow{
  display: inline-block;
  background: transparent; border: 0; padding: 0; margin: 0 0 18px;
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow); font-weight: 500;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--accent-dark);            /* AA: body-size emerald text → accent-dark */
}
.scene-slate{
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow); font-weight: 500;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--sage);                   /* on dark cinema */
  display: inline-flex; align-items: center; gap: 10px;
}
.scene-slate b{ color: var(--limestone); font-weight: 500; }

.lede, .reading-rest p, .studio .lede{
  font-size: var(--fs-lead); line-height: 1.6; color: var(--muted);
  max-width: var(--measure);
}

/* ---------------------------------------------------------------------------
   2. BUTTONS — consolidate to a primary/ghost pair. Emerald is the ONE "go".
   White-on-emerald uses --accent-dark (AA). Bright --accent stays for fills only.
   --------------------------------------------------------------------------- */
.btn-primary, a.btn-primary,
.btn.btn-accent{                        /* alias the live markup's existing class */
  display: inline-flex; align-items: center; gap: 9px;
  padding: 15px 26px; border-radius: 999px;
  font-family: var(--font-body); font-size: 15.5px; font-weight: 600;
  letter-spacing: -0.01em; cursor: pointer; text-decoration: none;
  background: var(--accent-dark);       /* AA: white text needs accent-dark */
  color: #fff; border: 1px solid transparent;
  box-shadow: 0 8px 22px -8px var(--accent-glow), var(--shadow-sm);
  transition: background .18s var(--ease), box-shadow .2s var(--ease), transform .16s var(--ease);
}
.btn-primary:hover, .btn.btn-accent:hover{
  background: #11663a; color: #fff;
  box-shadow: 0 14px 30px -10px var(--accent-glow), var(--shadow);
  transform: translateY(-1px);
}
.btn-primary:active, .btn.btn-accent:active{ transform: translateY(0); }

.btn-ghost, a.btn-ghost,
.btn.ghost{
  display: inline-flex; align-items: center; gap: 9px;
  padding: 15px 26px; border-radius: 999px;
  font-family: var(--font-body); font-size: 15.5px; font-weight: 600;
  letter-spacing: -0.01em; text-decoration: none;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--border-strong);
  transition: background .16s var(--ease), border-color .16s var(--ease), transform .16s var(--ease);
}
.btn-ghost:hover, .btn.ghost:hover{
  background: var(--surface-soft); border-color: var(--ink-2);
  color: var(--ink); transform: translateY(-1px);
}
.btn-lg, .btn.btn-lg{ padding: 17px 30px; font-size: 16px; }

/* text-link CTA — underline draws on hover, emerald (accent-dark at link size) */
.link-cta{
  color: var(--accent-dark); font-weight: 600; text-decoration: none;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 0% 2px; background-position: 0 100%; background-repeat: no-repeat;
  transition: background-size .3s var(--ease), color .16s var(--ease);
}
.link-cta:hover{ background-size: 100% 2px; color: var(--accent-dark); }

/* ghost on dark cinema = sage-outlined (the pro fork) */
.cinema .btn-ghost, .pro-band .btn-ghost,
.cinema .btn.ghost,  .pro-band .btn.ghost{
  background: transparent; color: var(--limestone);
  border-color: rgba(110,139,123,0.6);
}
.cinema .btn-ghost:hover, .pro-band .btn-ghost:hover,
.cinema .btn.ghost:hover, .pro-band .btn.ghost:hover{
  background: rgba(110,139,123,0.14); border-color: var(--sage); color: var(--limestone);
}

/* ---------------------------------------------------------------------------
   3. SKIP-LINK + FOCUS RINGS (A3, A7) — global accessibility floor.
   --------------------------------------------------------------------------- */
.skip-link{
  position: fixed; top: 10px; left: 10px; z-index: 200;
  transform: translateY(-150%);
  background: var(--ink); color: #fff;
  padding: 11px 18px; border-radius: 10px;
  font-family: var(--font-body); font-size: 14px; font-weight: 600;
  box-shadow: var(--shadow); transition: transform .18s var(--ease);
}
.skip-link:focus{ transform: translateY(0); color: #fff; outline: none; }

/* FIX (a11y): bright --accent (#1f9d57, ~3.49:1 on white) was a borderline focus
   ring on light surfaces — drop to --accent-dark (#157a43, ~4.8:1) so the ring is
   clearly visible. On dark cinema/pro bands the ring switches to --limestone (a
   warm-white that reads clearly on --forest/--canopy). Outlines are never removed. */
:focus-visible{ outline: 3px solid var(--accent-dark); outline-offset: 2px; border-radius: 3px; }
/* limestone variant on dark cinema bands (any emerald ring can vanish into forest) */
.cinema :focus-visible, .pro-band :focus-visible, .flywheel.scene-in :focus-visible,
.cinema .btn :focus-visible, body.over-dark .nav :focus-visible{
  outline-color: var(--limestone);
}

/* ---------------------------------------------------------------------------
   4. NAV — restyle only (markup + JS contract from base.css unchanged).
   Scroll-state: transparent over dark cinema bands, frosted glass over studio.
   Toggled by the premium.js IO sentinel via body.over-dark / body.scrolled.
   --------------------------------------------------------------------------- */
.nav{
  transition: background .3s var(--ease), border-color .3s var(--ease),
              padding .3s var(--ease),
              -webkit-backdrop-filter .3s var(--ease), backdrop-filter .3s var(--ease);
}
/* at the very top of a light page, let it sit a touch quieter */
body:not(.scrolled):not(.over-dark) .nav{
  background: rgba(255,255,255,0.82);
  border-bottom-color: transparent;
}
/* scrolled over a light studio section — full frosted glass */
body.scrolled:not(.over-dark) .nav{
  background: rgba(255,255,255,0.9);
  -webkit-backdrop-filter: saturate(170%) blur(14px);
  backdrop-filter: saturate(170%) blur(14px);
  border-bottom-color: var(--border);
  box-shadow: 0 1px 0 rgba(12,14,17,0.02), 0 8px 24px -18px rgba(12,14,17,0.25);
}

/* --- GLASSMORPHISM SHRINKING NAV (IMMERSIVE-BRIEF-V2 §3), organic skin only ----
   Restyle ONLY, on the existing base.html .nav / .nav-inner / .brand markup and the
   existing premium.js body.scrolled / body.over-dark contract. NO new JS, NO scroll-
   linked per-frame work: the shrink is a single class-gated transition off
   body.scrolled (which premium.js sets ONCE past 40px on a rAF-throttled single
   pageYOffset read). Compositor-first: padding + a transform on the brand mark only.
   reduced-motion (§18) zeroes the transition durations. Scoped via .organic. */
body.theme-published:has(.hero-build) .nav,
html.organic .nav{ padding-block: 16px; }
/* quiet glass at the top of the light page (forest off-white tint, no border) */
body.theme-published:has(.hero-build):not(.scrolled):not(.over-dark) .nav,
html.organic body:not(.scrolled):not(.over-dark) .nav{
  background: rgba(249,249,249,0.55);
  -webkit-backdrop-filter: saturate(150%) blur(10px);
  backdrop-filter: saturate(150%) blur(10px);
  border-bottom-color: transparent;
}
/* once scrolled past the top — denser frosted glass + a warm hairline + soft shadow */
body.theme-published:has(.hero-build).scrolled:not(.over-dark) .nav,
html.organic body.scrolled:not(.over-dark) .nav{
  background: rgba(249,249,249,0.82);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  backdrop-filter: saturate(180%) blur(18px);
  border-bottom-color: var(--border);
  box-shadow: 0 1px 0 rgba(28,42,34,0.02), 0 10px 30px -20px rgba(28,42,34,0.3);
}
/* SHRINK elegantly on scroll — one class-gated state, compositor padding + brand
   scale. No scroll-linked JS; passes the 4x-CPU throttle by construction. */
body.theme-published:has(.hero-build).scrolled .nav,
html.organic body.scrolled .nav{ padding-block: 10px; }
body.theme-published:has(.hero-build) .brand-mark,
html.organic .brand-mark{ transition: transform .3s var(--ease); }
body.theme-published:has(.hero-build).scrolled .brand-mark,
html.organic body.scrolled .brand-mark{ transform: scale(0.86); }
/* fade the brand tag out on scroll (decorative, never the brand name) */
body.theme-published:has(.hero-build) .brand-tag,
html.organic .brand-tag{ transition: opacity .3s var(--ease); }
body.theme-published:has(.hero-build).scrolled .brand-tag,
html.organic body.scrolled .brand-tag{ opacity: 0; }

/* the "Book a consultation" nav CTA — leaf-green pill (AA white-on-green via
   --accent-dark). aria-label is on the <a> in base.html; the :focus-visible ring is
   the global §3 accent-dark/limestone ring. Over dark bands body.over-dark already
   keeps it accent-dark on limestone. */
.nav-consult{ letter-spacing: -0.01em; }
/* over a dark cinema band — transparent, light type, no border */
body.over-dark .nav{
  background: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-bottom-color: transparent;
}
body.over-dark .nav-links a,
body.over-dark .nav-menu-btn{ color: var(--limestone); }
body.over-dark .nav-links a:hover,
body.over-dark .nav-menu-btn:hover{ color: #fff; }
body.over-dark .brand-name{
  -webkit-text-fill-color: var(--limestone); color: var(--limestone); background: none;
}
body.over-dark .brand-tag{ color: var(--limestone-2); }
body.over-dark .brand-mark{ background: rgba(255,255,255,0.12); }
body.over-dark .nav-cta{ background: var(--accent-dark); color: #fff !important; }
/* the IO sentinel is a zero-height tripwire placed at the top of each dark band */
.nav-sentinel{ position: absolute; left: 0; width: 1px; height: 1px; pointer-events: none; opacity: 0; }

/* AA CONTRAST FLOOR (override base.css nav colours) — white-on-emerald and
   body-size emerald text both FAIL AA at #1f9d57 (3.49:1). Drop to --accent-dark
   (#157a43, ~5.39:1). These selectors out-specify base.css's resting nav rules. */
.nav .nav-cta{ background: var(--accent-dark); }
.nav .nav-cta:hover{ background: #11663a; }
.nav-links a:hover,
.nav-links a.active{ color: var(--accent-dark); }

/* ---------------------------------------------------------------------------
   5. SECTION SHELLS — light STUDIO, dark CINEMA, reading-REST.
   --------------------------------------------------------------------------- */
/* FIX #20: fluid section rhythm (no media-query cliff) aligned to the §III scale. */
.studio-section{ padding: clamp(74px, 9vw, 112px) 0; position: relative; }
.studio-section.paper{ background: var(--bg-2); }   /* 2% warm wash, no rule */

/* FIX #21: the orphaned `.reading-rest` block was removed — the live markup is
   `.bridge` (§7b), which carries the studio reading-rest. */

/* CINEMA — full-bleed dark stage, photography-led, grain + vignette */
.cinema{
  position: relative; isolation: isolate; overflow: clip;
  background: var(--forest); color: var(--limestone);
  padding: 120px 0;
}
.cinema.canopy{ background: var(--canopy); }       /* the pro band stage */
.cinema::before{                                    /* soft vignette */
  content:''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(120% 90% at 50% 18%, transparent 40%, rgba(0,0,0,0.42) 100%);
}
.cinema::after{                                     /* barely-there film grain */
  content:''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image: var(--grain); opacity: 0.04;
  mix-blend-mode: overlay;
}
.cinema h2{ color: var(--limestone); font-size: clamp(28px, 3.4vw, 44px); line-height: 1.08; letter-spacing: -0.022em; margin: 14px 0 16px; }
.cinema p{ color: var(--limestone-2); max-width: var(--measure); }
.cinema .eyebrow{ color: var(--sage); }

/* section heads (shared) */
.section-head{ text-align: center; margin: 0 auto 56px; max-width: 760px; }
.section-head h2{
  font-size: var(--fs-h2); font-weight: 500; letter-spacing: -0.022em;
  margin-bottom: 14px; line-height: 1.06;
}
.section-head p{ color: var(--muted); max-width: 60ch; margin: 0 auto; font-size: 17px; line-height: 1.6; }
.cinema .section-head h2{ color: var(--limestone); }
.cinema .section-head p{ color: var(--limestone-2); }

/* ---------------------------------------------------------------------------
   6. HERO STUDIO — the live-demo centrepiece (dir-5). 2-rail split → stacked.
   --------------------------------------------------------------------------- */
.studio{ padding: 120px 0 40px; }
.studio-grid{
  /* widen the TEXT rail (was 1fr : 1.08fr, and an intrinsic-min overflow on the
     demo side was collapsing the text track to ~344px → the H1 wrapped to 6
     cramped lines, exactly the founder's complaint). minmax(0,…) lets the fr
     distribution actually hold so the words get their ~52% share and the refined
     H1 lands in 2–3 confident lines. */
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(32px,5vw,72px); align-items: center;
}
.studio-words{ min-width: 0; }
.studio-side{ min-width: 0; }
@media (max-width:860px){ .studio-grid{ grid-template-columns: 1fr; gap: 40px; text-align: left; } }

.studio h1{
  font-family: var(--font-display); font-weight: 500;
  font-size: var(--fs-hero-xl); line-height: 1.08; letter-spacing: -0.022em;
  margin: 10px 0 22px; max-width: 22ch; text-wrap: balance;
}
.studio .lede{ color: var(--muted); margin: 0 0 30px; max-width: 52ch; }
.studio-ctas{ display: flex; gap: 12px; flex-wrap: wrap; }
.studio-trust{
  margin-top: 30px; display: flex; gap: 10px 20px; flex-wrap: wrap;
  font-family: var(--font-mono); font-size: 12.5px; color: var(--muted);
}
.studio-trust span{ display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
.studio-trust span + span::before{
  content:''; width: 4px; height: 4px; border-radius: 50%; background: var(--border-strong);
  margin-right: 12px; margin-left: -10px;
}

/* the "powered-on" studio card */
.studio-card{
  position: relative; border-radius: var(--radius-lg); overflow: hidden;
  background: var(--accent-soft);                /* faint emerald wash = alive */
  border: 1px solid var(--border);
  box-shadow: 0 44px 100px -36px rgba(12,14,17,0.4), var(--shadow);
  padding: 14px;
}
.studio-stage{
  position: relative; border-radius: var(--radius); overflow: hidden;
  aspect-ratio: 4 / 3; background: var(--forest);
  user-select: none; touch-action: pan-y;
}
.studio-stage img{
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; pointer-events: none;
}
.studio-before{ position: absolute; inset: 0; clip-path: inset(0 50% 0 0); z-index: 1; }
.studio-after{ z-index: 0; }

/* the wipe — reuse the accessible input[type=range] → clip-path contract */
.studio-wipe{
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  z-index: 6; opacity: 0; cursor: ew-resize;
}
.studio-divider{
  position: absolute; top: 0; bottom: 0; left: 50%; width: 2px; z-index: 4;
  background: rgba(255,255,255,0.92); transform: translateX(-50%);
  box-shadow: 0 0 14px rgba(0,0,0,0.35); pointer-events: none;
}
.studio-divider::after{
  content:''; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
  width: 44px; height: 44px; border-radius: 50%; background: #fff;
  box-shadow: 0 4px 18px rgba(14,26,21,0.34);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%23157a43' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='15 18 9 12 15 6'/%3E%3Cpolyline points='9 6 15 12 9 18' transform='translate(2 0)'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: center;
}

/* draw-a-mark canvas overlay (emerald stroke handled in JS) */
.studio-canvas{
  position: absolute; inset: 0; z-index: 5; cursor: crosshair;
  touch-action: none; opacity: 0; pointer-events: none;
  transition: opacity .2s var(--ease);
}
.studio.is-drawing .studio-canvas,
.studio-card.is-drawing .studio-canvas{ opacity: 1; pointer-events: auto; }

/* tags */
.studio-tag{
  position: absolute; top: 14px; z-index: 3; color: #fff;
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 6px 11px; border-radius: 999px;
  background: rgba(14,26,21,0.55); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.studio-tag.before{ left: 14px; }
.studio-tag.after{ right: 14px; background: var(--accent-dark); }

/* live price pill (reuse @keyframes pulse + .dot from home markup) */
.price-pill{
  position: absolute; top: 14px; left: 50%; transform: translateX(-50%); z-index: 7;
  display: inline-flex; align-items: center; gap: 8px;
  background: rgba(255,255,255,0.95); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border-radius: 999px; padding: 9px 16px;
  font-family: var(--font-mono); font-size: 13px; font-weight: 500;
  color: var(--accent-dark); box-shadow: 0 6px 18px rgba(14,26,21,0.2);
  font-variant-numeric: tabular-nums;
}
.price-pill .dot{
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-glow); animation: pulse 2s infinite;
}
@keyframes pulse{
  0%{ box-shadow: 0 0 0 0 rgba(31,157,87,0.45); }
  70%{ box-shadow: 0 0 0 9px rgba(31,157,87,0); }
  100%{ box-shadow: 0 0 0 0 rgba(31,157,87,0); }
}

/* the rendering scan-line (emerald) — sweeps once on render */
.studio-stage.is-rendering::after{
  content:''; position: absolute; left: 0; right: 0; top: 0; height: 3px; z-index: 8;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  box-shadow: 0 0 16px 2px rgba(31,157,87,0.5);
  animation: scanline 2.4s var(--ease) forwards;
}
@keyframes scanline{
  0%{ top: 0; opacity: 0; }
  10%{ opacity: 1; }
  90%{ opacity: 1; }
  100%{ top: 100%; opacity: 0; }
}

/* tool row under the stage */
.studio-tools{
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  padding: 14px 6px 6px;
}
.tool-chip{
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font-mono); font-size: 12px; font-weight: 500;
  color: var(--ink-2); background: var(--panel);
  border: 1px solid var(--border-strong); border-radius: 999px;
  padding: 8px 13px; cursor: pointer;
  transition: all .16s var(--ease);
}
.tool-chip:hover{ border-color: var(--accent); color: var(--accent-dark); }
.tool-chip[aria-pressed="true"]{
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent-dark);
}
.intent-chips{ display: flex; gap: 8px; flex-wrap: wrap; }
.intent-chips .tool-chip{ font-size: 11.5px; padding: 7px 12px; }
/* decorative chips in the baked hero demo: read as labels, not broken buttons
   (no pointer affordance, no hover lift — they don't do anything). */
.tool-chip--static{ cursor: default; }
.tool-chip--static:hover{ border-color: var(--border-strong); color: var(--ink-2); }

.studio-status{
  margin-left: auto; display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono); font-size: 11.5px; color: var(--muted);
  letter-spacing: 0.02em;
}
.studio-status .dot{
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-glow); animation: pulse 2.4s infinite;
}
.studio-status-label{ color: var(--muted); }
.studio-status[data-state="rendering"] .dot{ background: var(--accent); animation: pulse 1.4s infinite; }
.studio-status[data-state="done"] .dot{ background: var(--success); }

/* View as: Homeowner | Landscaper toggle */
.view-toggle{
  display: inline-flex; padding: 3px; border-radius: 999px;
  background: var(--surface-soft); border: 1px solid var(--border);
}
.view-toggle button{
  font-family: var(--font-mono); font-size: 11px; font-weight: 500;
  letter-spacing: 0.04em; text-transform: uppercase;
  background: transparent; color: var(--muted); border: 0;
  padding: 7px 13px; border-radius: 999px; cursor: pointer;
  transition: all .16s var(--ease);
}
.view-toggle button[aria-selected="true"]{
  background: var(--panel); color: var(--ink); box-shadow: var(--shadow-sm);
}
/* pro view swaps the price pill for a quote-ready chip */
.studio[data-view="landscaper"] .price-pill .pp-home,
.studio[data-view="homeowner"] .price-pill .pp-pro{ display: none; }

.studio-reset{
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: 11.5px; color: var(--muted);
  background: none; border: 0; cursor: pointer; padding: 6px;
}
.studio-reset:hover{ color: var(--accent-dark); }

.studio-footnote{
  margin-top: 12px; font-size: 12.5px; color: var(--muted); text-align: center;
}
/* the honesty footnote link gets a deliberate link affordance (was bare) */
.studio-footnote a{
  color: var(--accent-dark); font-weight: 600; text-decoration: none;
  border-bottom: 1.5px solid transparent; transition: border-color .16s var(--ease);
}
.studio-footnote a:hover{ border-bottom-color: var(--accent-dark); }

.studio-skip{ display: inline-block; margin-top: 8px; }

/* the hero "build progress" bar — a thin emerald fill that tracks the stage */
.build-progress{
  height: 3px; margin-top: 10px;
  background: var(--border); border-radius: 2px; overflow: hidden;
}
.build-progress-bar{
  display: block; height: 100%; width: 0;
  background: var(--accent); transition: width .4s var(--ease);
}

/* ---------------------------------------------------------------------------
   6b. THE IMMERSIVE HERO BUILD-STAGE (§IV-1) — the SIGNATURE, rebuilt as a
   FULL-BLEED, REAL-TIME, depth-3D-feeling hero the page FLOWS through — NO pin,
   NO viewport freeze (founder #1). A full-viewport <canvas> paints baked stages
   s0→s6 frame-accurately; on arrival the yard comes alive (one-time autoplay),
   then the SAME crossfade is re-driven by the stage's own scroll-through the
   viewport (premium.js §5 viewProgress). Depth = 2.5D: a slow camera PUSH-IN
   (--cam-scale on .build-camera) + a back plane drifting slower than the canvas
   (--back-y). The hero words overlay a readable left rail (scrim for AA). The
   live price pill + light→dusk score are kept.  STATIC degrade: the dusk poster
   full-bleed + resolved $ band + words legible (no canvas paint, no push-in).
   --------------------------------------------------------------------------- */
.studio.hero-build{
  position: relative; padding: 0; overflow: clip;
}
.hero-build .build-stage{
  position: relative;
  /* full-bleed, immersive: fills (nearly) the viewport so it draws you in. Tall
     enough to give the push-in + build crossfade travel as it scrolls through,
     but the viewport is NEVER frozen — scroll moves the page AND the build. */
  width: 100%; min-height: clamp(560px, 92svh, 1000px);
  margin: 0; border-radius: 0; overflow: hidden;
  background: var(--forest);
  display: flex; align-items: center;
}
/* the camera plane — holds the imagery (canvas + poster). SMOOTH BY CONSTRUCTION:
   the old JS-driven per-scroll-frame push-in/dolly is GONE (it animated the
   transform of a will-change'd layer over a blurred plane every scroll frame — a
   dominant glitch on real hardware). At rest the camera is a plain, static layer
   with NO will-change and NO per-frame transform. Capable browsers get a gentle
   compositor-only push-in via `animation-timeline: view()` (premium.css §17),
   which costs the main thread nothing; everyone else simply rests crisp. */
.hero-build .build-camera{
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
}
/* 2.5D BACK PLANE — a duplicated, softly-blurred dusk frame that reads as depth.
   STATIC now: no --back-y drift, no will-change. Decorative; behind the canvas. */
.hero-build .build-plane-back{
  position: absolute; inset: -6% -4%; z-index: 0;
  background-size: cover; background-position: center 40%;
  filter: blur(7px) brightness(0.82) saturate(0.92);
  transform: scale(1.12);
}
/* ---------------------------------------------------------------------------
   6b-i. THE COMPONENT-ASSEMBLY LAYER STACK (§IV-1) — the founder's REAL assembly.
   Each .bl is the REAL 4K source frame s_n, stacked over the prior composite and
   scoped (mask-image) to its own change-region, so the viewer WATCHES each new
   component build into ITS place — not a whole-frame crossfade. The reveals are
   transform / opacity / clip-path / mask-position only (compositor-cheap), fired
   by the .is-on class the one-time assembly timeline (premium.js) toggles in
   sequence. No canvas, no scroll loop, no per-frame layout reads.
   --------------------------------------------------------------------------- */
.hero-build .build-layers{
  position: absolute; inset: 0; z-index: 1;     /* above the back-plane, below the poster */
  pointer-events: none;
}
.hero-build .build-layers .bl{
  position: absolute; inset: 0; width: 100%; height: 100%;
}
.hero-build .build-layers .bl picture{ display: block; position: absolute; inset: 0; }
.hero-build .build-layers .bl img{
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; draggable: false;
}
/* the bare-yard base shows instantly — it's where the viewer lands. */
.hero-build .build-layers .bl-base{ opacity: 1; }
/* every component layer ships HIDDEN; the timeline toggles .is-on to build it in.
   Default pre-reveal = invisible; the per-layer rules below set the from-state
   (clip/mask/scale) and the .is-on rule reveals it in place. */
.hero-build .build-layers .bl-turf,
.hero-build .build-layers .bl-beds,
.hero-build .build-layers .bl-plants,
.hero-build .build-layers .bl-bloom,
.hero-build .build-layers .bl-tree,
.hero-build .build-layers .bl-path,
.hero-build .build-layers .bl-dusk,
.hero-build .build-layers .bl-festoon{ opacity: 0; }

/* each masked layer wears its registered change-region mask (white = reveal). The
   WebP mask first via image-set so capable browsers pull the tiny WebP; the PNG is
   the floor. Masking constrains the REAL frame to JUST that element's footprint. */
.hero-build .bl-turf img{
  -webkit-mask-image: image-set(url('../samples/build/mask-turf.webp') type('image/webp'), url('../samples/build/mask-turf.png') type('image/png'));
          mask-image: image-set(url('../samples/build/mask-turf.webp') type('image/webp'), url('../samples/build/mask-turf.png') type('image/png'));
}
.hero-build .bl-beds img{
  -webkit-mask-image: image-set(url('../samples/build/mask-beds.webp') type('image/webp'), url('../samples/build/mask-beds.png') type('image/png'));
          mask-image: image-set(url('../samples/build/mask-beds.webp') type('image/webp'), url('../samples/build/mask-beds.png') type('image/png'));
}
.hero-build .bl-plants img{
  -webkit-mask-image: image-set(url('../samples/build/mask-plants.webp') type('image/webp'), url('../samples/build/mask-plants.png') type('image/png'));
          mask-image: image-set(url('../samples/build/mask-plants.webp') type('image/webp'), url('../samples/build/mask-plants.png') type('image/png'));
}
.hero-build .bl-bloom img{
  -webkit-mask-image: image-set(url('../samples/build/mask-bloom.webp') type('image/webp'), url('../samples/build/mask-bloom.png') type('image/png'));
          mask-image: image-set(url('../samples/build/mask-bloom.webp') type('image/webp'), url('../samples/build/mask-bloom.png') type('image/png'));
}
.hero-build .bl-tree img{
  -webkit-mask-image: image-set(url('../samples/build/mask-tree.webp') type('image/webp'), url('../samples/build/mask-tree.png') type('image/png'));
          mask-image: image-set(url('../samples/build/mask-tree.webp') type('image/webp'), url('../samples/build/mask-tree.png') type('image/png'));
}
.hero-build .bl-path img{
  -webkit-mask-image: image-set(url('../samples/build/mask-path.webp') type('image/webp'), url('../samples/build/mask-path.png') type('image/png'));
          mask-image: image-set(url('../samples/build/mask-path.webp') type('image/webp'), url('../samples/build/mask-path.png') type('image/png'));
}
/* the festoon layer is masked to JUST the overhead string-light band, so its
   'lights on' beat lands only on the lights, over the dusk grade. */
.hero-build .bl-festoon img{
  -webkit-mask-image: image-set(url('../samples/build/mask-festoon.webp') type('image/webp'), url('../samples/build/mask-festoon.png') type('image/png'));
          mask-image: image-set(url('../samples/build/mask-festoon.webp') type('image/webp'), url('../samples/build/mask-festoon.png') type('image/png'));
}
/* all masked layers: cover the frame, no-repeat, centred — the mask is registered
   1:1 to the 1280x720 frame so a `cover` mask-size matches the `cover` image. */
.hero-build .bl-turf img, .hero-build .bl-beds img, .hero-build .bl-plants img,
.hero-build .bl-bloom img, .hero-build .bl-tree img, .hero-build .bl-path img,
.hero-build .bl-festoon img{
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: cover; mask-size: cover;
}

/* ---- THE REVEALS (each component builds INTO its place) -------------------
   Each layer's at-rest is its REVEALED state; the from-state lives on the layer
   itself and the .is-on transition plays it. Driven ONLY by the timeline class.
   GROW/SWEEP/FADE per the layer plan; transform/opacity/clip-path/mask-position. */

/* 2 · TURF — rolls out left→right across the lawn (clip-path sweep from the left). */
.hero-build .bl-turf{ clip-path: inset(0 100% 0 0); transition: opacity .35s var(--ease), clip-path 1.05s var(--ease-expo); }
.hero-build .bl-turf.is-on{ opacity: 1; clip-path: inset(0 0 0 0); }

/* 3 · GARDEN BEDS + SANDSTONE EDGING — dig in along the L-shaped bed (wipe up from
   the ground line, so the beds appear to be cut in from the soil). */
.hero-build .bl-beds{ clip-path: inset(100% 0 0 0); transition: opacity .35s var(--ease), clip-path .95s var(--ease-expo); }
.hero-build .bl-beds.is-on{ opacity: 1; clip-path: inset(0 0 0 0); }

/* 4 · NATIVE PLANTING — pops in (scale + fade, so the tufts appear to sprout in
   place). Slight overshoot via the expo ease reads as a "pop". */
.hero-build .bl-plants{ transform: scale(0.965); transform-origin: 30% 70%; transition: opacity .55s var(--ease), transform .7s var(--ease-expo); }
.hero-build .bl-plants.is-on{ opacity: 1; transform: scale(1); }

/* 5a · ESTABLISHED BLOOM — grows UP (vertical clip-path from the bed line + fade). */
.hero-build .bl-bloom{ clip-path: inset(0 0 28% 0); transition: opacity .6s var(--ease), clip-path .8s var(--ease-expo); }
.hero-build .bl-bloom.is-on{ opacity: 1; clip-path: inset(0 0 0 0); }

/* 5b · FEATURE TREE — RISES: clip from the bottom growing upward + a slight scale,
   anchored at its base so it reads as the tree pushing up out of the corner. */
.hero-build .bl-tree{ clip-path: inset(100% 0 0 0); transform: scale(0.98); transform-origin: 85% 100%; transition: opacity .5s var(--ease), clip-path .9s var(--ease-expo), transform .9s var(--ease-expo); }
.hero-build .bl-tree.is-on{ opacity: 1; clip-path: inset(0 0 0 0); transform: scale(1); }

/* 6 · SANDSTONE PATH + BENCH — laid near→far (clip-path sweep from the bottom up,
   so the stones appear to land from the foreground back toward the house). */
.hero-build .bl-path{ clip-path: inset(0 0 0 100%); transition: opacity .4s var(--ease), clip-path 1s var(--ease-expo); }
.hero-build .bl-path.is-on{ opacity: 1; clip-path: inset(0 0 0 0); }

/* 7a · DUSK — the global relight, a whole-frame fade-in-place (NOT a slider beat:
   it covers the full composite, so the warm dusk grade settles over everything). */
.hero-build .bl-dusk{ transition: opacity .75s var(--ease); }
.hero-build .bl-dusk.is-on{ opacity: 1; }

/* 7b · FESTOON LIGHTING — the lights 'fade ON' over the dusk grade, with a brief
   warm flicker so the string lights read as switching on. Masked to the lights. */
.hero-build .bl-festoon{ transition: opacity .5s var(--ease); }
.hero-build .bl-festoon.is-on{ opacity: 1; animation: festoon-on 0.9s var(--ease) 1; }
@keyframes festoon-on{
  0%{ opacity: 0; } 22%{ opacity: 0.9; } 38%{ opacity: 0.45; }
  55%{ opacity: 1; } 70%{ opacity: 0.78; } 100%{ opacity: 1; }
}

/* the STATIC dusk poster — the LCP / no-JS floor: ABOVE the layer stack (z-index:2).
   At rest it is the resting full-bleed 4K hero. While the assembly plays, JS adds
   .is-assembling to the stage, which fades the poster OUT so the layers build in
   front of the bare-yard base; on landing it fades back over the held composite,
   giving the crisp 4K resting still. On STATIC it simply stays full opacity. */
.hero-build .build-frame{
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; pointer-events: none;
  z-index: 2;
  transition: opacity .55s var(--ease, ease);
}
.hero-build .build-stage.is-assembling .build-frame{ opacity: 0; }

/* the readable scrim behind the words (AA contrast over the photo). A left-rail
   gradient so the right side stays bright/immersive while the words clear AA. */
.hero-build .build-scrim{
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    linear-gradient(90deg, rgba(10,18,14,0.78) 0%, rgba(10,18,14,0.52) 38%, rgba(10,18,14,0.12) 64%, rgba(10,18,14,0.0) 100%),
    linear-gradient(0deg, rgba(10,18,14,0.42) 0%, rgba(10,18,14,0.0) 32%);
}
/* the overlaid hero words — readable left rail over the live stage */
.hero-build .build-overlay{
  position: relative; z-index: 3; width: 100%;
  padding-top: clamp(96px, 12vh, 160px); padding-bottom: clamp(64px, 9vh, 120px);
}
.hero-build .build-words{ max-width: 640px; }
/* FIX #24: --sage (~2–3.4:1) over the lit-house photo failed AA; --limestone-2
   on the dark scrim band clears 4.5:1 against the brightest region behind it. */
.hero-build .build-words .eyebrow{ color: var(--limestone-2); }
.hero-build .build-words h1{
  font-family: var(--font-display); font-weight: 500; color: var(--limestone);
  font-size: var(--fs-hero-xl); line-height: 1.06; letter-spacing: -0.022em;
  margin: 10px 0 20px; max-width: 18ch; text-wrap: balance;
}
.hero-build .build-words h1 em{ color: var(--accent); font-style: italic; }
.hero-build .build-words .lede{ color: var(--limestone-2); margin: 0 0 28px; max-width: 50ch; }
.hero-build .studio-ctas{ display: flex; gap: 12px; flex-wrap: wrap; }
.hero-build .studio-trust{
  margin-top: 26px; display: flex; gap: 10px 20px; flex-wrap: wrap;
  font-family: var(--font-mono); font-size: 12.5px; color: var(--limestone-2);
}
.hero-build .studio-trust span{ display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
.hero-build .studio-trust span + span::before{
  content:''; width: 4px; height: 4px; border-radius: 50%; background: rgba(247,244,238,0.4);
  margin-right: 12px; margin-left: -10px;
}
/* ghost CTA over the dark hero photo reads as the cinema ghost (sage outline) */
.hero-build .btn.ghost{
  background: transparent; color: var(--limestone); border-color: rgba(110,139,123,0.6);
}
.hero-build .btn.ghost:hover{ background: rgba(110,139,123,0.16); border-color: var(--sage); color: var(--limestone); }
/* the honesty footnote rail under the stage */
.build-foot{
  margin: 16px auto 0; padding: 0 var(--gutter);
  font-size: 13px; color: var(--muted); text-align: center;
}
.build-foot em{ font-style: italic; color: var(--ink-2); }
.build-foot a{
  color: var(--accent-dark); font-weight: 600; text-decoration: none;
  border-bottom: 1.5px solid transparent; transition: border-color .16s var(--ease);
}
.build-foot a:hover{ border-bottom-color: var(--accent-dark); }

/* the instrument overlay (caption + progress + chips) floats over the stage base */
.hero-build .build-hud{
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  padding: clamp(16px,3vw,26px); pointer-events: none;
  background: linear-gradient(0deg, rgba(14,26,21,0.78) 0%, rgba(14,26,21,0.0) 100%);
}
.build-caption{
  font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--limestone);
  letter-spacing: 0.02em; display: flex; align-items: center; gap: 10px;
}
.build-caption .stage-no{
  color: var(--sage); text-transform: uppercase; letter-spacing: 0.14em; font-size: var(--fs-micro);
}
/* the HUD progress bar on the dark stage (limestone track, emerald fill).
   COMPOSITOR-ONLY: the fill is full-width and scaled on the X axis via
   transform:scaleX(--p) from a left origin, so the scrub never animates `width`
   (a layout property) every rAF — JS writes only the --p-driven transform. */
.build-hud .build-progress{ background: rgba(247,244,238,0.18); margin-top: 12px; height: 3px; }
.build-hud .build-progress-bar{
  width: 100%;
  transform-origin: left center;
  transform: scaleX(0);
  /* the intro engine writes scaleX(--p) for ~2.2s once on load, then freezes at
     scaleX(1); a short transition keeps that one play smooth. No scroll coupling,
     no permanent will-change layer (the bar is static at rest). */
  transition: transform .25s linear;
}

/* the live price pill that ticks up as the build resolves (mono, emerald dot).
   On the dusk frame it lands into a confident $ band (JS adds .is-resolved). */
.build-pricepill{
  position: absolute; top: clamp(14px,2.4vw,20px); right: clamp(14px,2.4vw,20px); z-index: 4;
  display: inline-flex; align-items: center; gap: 8px;
  background: rgba(255,255,255,0.95); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border-radius: var(--r-pill); padding: 9px 15px;
  font-family: var(--font-mono); font-size: var(--fs-caption); font-weight: 500;
  color: var(--accent-dark); box-shadow: 0 8px 22px rgba(14,26,21,0.28);
  font-variant-numeric: tabular-nums;
  transition: box-shadow .5s var(--ease), transform .5s var(--ease-expo);
}
.build-pricepill .dot{
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-glow); animation: pulse 2s infinite;
}
.build-pricepill.is-resolved{
  transform: scale(1.04);
  box-shadow: 0 0 0 1px var(--accent), 0 12px 30px rgba(14,26,21,0.34);
}

/* draw-on-photo intent chips that sit on frame 0 (teach the affordance) and fade
   as the build begins. JS toggles .is-faded once --p clears the first stage. */
.build-intent{
  position: absolute; top: clamp(14px,2.4vw,20px); left: clamp(14px,2.4vw,20px); z-index: 4;
  display: flex; gap: 8px; flex-wrap: wrap; max-width: 60%;
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
.build-intent.is-faded{ opacity: 0; transform: translateY(-6px); pointer-events: none; }
/* on the immersive hero the left rail is the WORDS, so float the teaching chips
   on the lower RIGHT (clear of the words + the bottom-left HUD caption). */
.hero-build .build-intent{
  top: auto; left: auto;
  right: clamp(14px,2.4vw,20px); bottom: clamp(74px,12vh,120px); max-width: 44%;
  justify-content: flex-end;
}
.intent-chip{
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: var(--fs-micro); font-weight: 500;
  letter-spacing: 0.06em; color: var(--limestone);
  background: rgba(14,26,21,0.5); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  border: 1px solid rgba(247,244,238,0.22); border-radius: var(--r-pill);
  padding: 7px 12px;
}
.intent-chip::before{
  content:''; width: 7px; height: 7px; border-radius: 2px; background: var(--accent);
}
/* the screen-reader alternative to the decorative canvas (the 7-stage story) */
.build-alt{
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0; padding: 0; margin: -1px;
}

/* ---------------------------------------------------------------------------
   6c. COMPONENT CALLOUT PINS (§1) — pure DOM/CSS markers that pin to the yard and
   label each component as its stage lands. NOT painted to canvas → zero per-scroll
   cost. syncHud(p) (premium.js §4) toggles .is-on on any callout whose data-from <=
   stageI during the ~2.2s intro, then freezes; the dusk-frame set (data-from 5/6)
   rests visible on the poster + STATIC, so the still hero is richer too.
   --------------------------------------------------------------------------- */
.build-callouts{
  position: absolute; inset: 0; z-index: 3;     /* above the layer stack + poster, below the scrim */
  pointer-events: none;
}
.callout{
  position: absolute; left: var(--cx, 50%); top: var(--cy, 50%);
  /* the dot is the anchor; the label sits just to its right on a hairline leader */
  display: flex; align-items: center; gap: 8px;
  transform: translate(0, -50%) translateY(6px);
  opacity: 0; transition: opacity .5s var(--ease, ease), transform .5s var(--ease-expo);
}
.callout.is-on{ opacity: 1; transform: translate(0, -50%); }
/* STATIC floor: with the motion system OFF (narrow/coarse/no-JS), the intro never
   runs, so pin every callout visible on the dusk poster — the most-polished still
   is genuinely more detailed than today (the §1 promise). */
html:not(.flow) .callout{ opacity: 1; transform: translate(0, -50%); }
.callout .co-dot{
  width: 9px; height: 9px; border-radius: 50%; background: var(--accent); flex: none;
  box-shadow: 0 0 0 3px rgba(31,157,87,0.22), 0 0 10px 1px var(--accent-glow);
}
.callout .co-leader{ width: 16px; height: 1px; background: rgba(247,244,238,0.5); flex: none; }
.callout .co-label{
  font-family: var(--font-mono); font-size: var(--fs-micro); font-weight: 500;
  letter-spacing: 0.06em; color: var(--limestone); white-space: nowrap;
  background: rgba(14,26,21,0.6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  border: 1px solid rgba(247,244,238,0.18); border-radius: var(--r-pill);
  padding: 5px 10px;
}
/* a callout near the right edge flips its label to the LEFT of the dot so it
   never runs off the stage. */
.callout[style*="--cx:8"]{ flex-direction: row-reverse; }

/* the price pill + BILL-OF-MATERIALS micro-strip share a top-right block so the
   BOM pills resolve directly under the price pill (§3). */
.build-priceblock{
  position: absolute; top: clamp(14px,2.4vw,20px); right: clamp(14px,2.4vw,20px); z-index: 4;
  display: flex; flex-direction: column; align-items: flex-end; gap: 8px; max-width: 60%;
}
/* the price pill inside the block is no longer self-positioned (the block places it) */
.build-priceblock .build-pricepill{ position: static; top: auto; right: auto; }
.build-bom{
  display: flex; gap: 7px; flex-wrap: wrap; justify-content: flex-end;
  opacity: 0; transform: translateY(-4px);
  transition: opacity .5s var(--ease, ease), transform .5s var(--ease-expo);
}
.build-bom.is-on{ opacity: 1; transform: none; }
.bom-pill{
  font-family: var(--font-mono); font-size: var(--fs-micro); font-weight: 500;
  letter-spacing: 0.04em; color: var(--limestone);
  background: rgba(14,26,21,0.55); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  border: 1px solid rgba(247,244,238,0.2); border-radius: var(--r-pill);
  padding: 5px 11px; white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* the baked-demo disclosure, now a small mono caption inside the HUD (folded out of
   the in-hero .build-foot that crowded the fold). */
.build-note{
  display: block; margin-top: 8px;
  font-family: var(--font-mono); font-size: var(--fs-micro); letter-spacing: 0.06em;
  color: var(--limestone-2); opacity: 0.85;
}
/* the REPLAY control (§IV-1) — re-runs the one-time assembly. A small mono pill in
   the HUD; pointer-events on (the HUD itself is pointer-events:none). It fades in
   only after the assembly has landed (premium.js adds .is-ready) so it doesn't
   distract during the first watch, and is hidden under STATIC (no motion to replay). */
.build-replay{
  position: absolute; right: clamp(16px,3vw,26px); bottom: clamp(16px,3vw,26px);
  display: inline-flex; align-items: center; gap: 7px; pointer-events: auto;
  font-family: var(--font-mono); font-size: var(--fs-micro); font-weight: 500;
  letter-spacing: 0.06em; color: var(--limestone); cursor: pointer;
  background: rgba(14,26,21,0.55); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  border: 1px solid rgba(247,244,238,0.22); border-radius: var(--r-pill);
  padding: 7px 13px;
  opacity: 0; transform: translateY(4px); pointer-events: none;
  transition: opacity .4s var(--ease), transform .4s var(--ease), background .16s var(--ease), border-color .16s var(--ease);
}
.build-replay svg{ color: var(--accent); }
.build-replay:hover{ background: rgba(31,157,87,0.22); border-color: var(--accent); }
.build-replay:focus-visible{ outline: 2px solid var(--accent); outline-offset: 2px; }
/* shown once the assembly has finished its first run (premium.js .is-ready). */
.hero-build .build-stage.is-ready .build-replay{ opacity: 1; transform: none; pointer-events: auto; }

/* ---------------------------------------------------------------------------
   7. THE PROOF WALK (§IV-3) — the designated CENTREPIECE. First dark --forest
   band. REBUILT as an IN-FLOW stack of DIFFERENT-yard before/after plates — NO
   pin, NO horizontal scrub, NO viewport freeze (founder #1). Each plate is a
   split (left=real before, right=render) whose clip-path seam (--seam) opens
   from ITS OWN scroll-through the viewport (premium.js §6 viewProgress), so every
   plate reveals fully (reaches 0%) and symmetrically as it passes, continuously.
   The seam hairline rides --seam via TRANSFORM (not left/calc) so it never
   re-lays-out per frame (kills the old 0.0172 CLS).  STATIC degrade: a clean
   vertical stack of fully-resolved plates with $ bands (.proof-fallback).
   --------------------------------------------------------------------------- */
.proof{ padding: 96px 0 var(--band-pad); position: relative; }
.proof .proof-head{ padding: 0 var(--gutter) 0; text-align: center; margin-bottom: clamp(40px,6vh,72px); }
.proof .proof-head h2{
  font-family: var(--font-display); font-weight: 500; color: var(--limestone);
  font-size: var(--fs-h1); line-height: var(--lh-head); letter-spacing: -0.022em;
  margin: 0 auto; max-width: 18ch;
}
/* the in-flow stack: large, near-edge-to-edge plates that each pass through the
   viewport on their own. No transform on the track — it just lays them out. */
.proof-track{
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(48px,9vh,120px);
  padding: 0 var(--gutter);
}
/* mounted museum plate — large + in flow */
.plate{
  position: relative; width: min(94vw, 1040px);
  border: 1px solid rgba(247,244,238,0.16); border-radius: var(--r-lg);
  overflow: hidden; background: #16231d;
  box-shadow: 0 40px 90px -44px rgba(0,0,0,0.6);
}
/* FIX #5: the plate sources are 16:9 (3200×1800); a 16/10 box cover-cropped ~10%
   off every render. Match the source aspect so nothing is discarded + upscaled. */
.plate-stage{ position: relative; aspect-ratio: 16/9; overflow: hidden; }
/* The 4K plates are wrapped in <picture> (webp-first responsive srcset). Keep the
   wrapper LAYOUT-TRANSPARENT so every existing rule (absolute fill, clip-path seam,
   z-index) still lands on the inner <img> exactly as before. */
.plate-stage picture, .hero-build .build-camera picture{ display: contents; }
.plate-stage img{ position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* FIX #3 (proof seam inverted) — the .plate-after (the RENDER) must be what gets
   revealed, not hidden. AUTHORED START = the real BEFORE photo fully covers the
   render (clip-path inset(0) = nothing clipped). As the plate passes through the
   viewport the FLOW animation (§17) clips the before AWAY to the right
   (inset(0 100% 0 0)), wiping it open so the render is exposed left→right. The
   seam hairline rides the wipe edge from the LEFT edge to the right. clip-path +
   transform are compositor-friendly (no layout). */
.plate-stage .plate-before{ clip-path: inset(0 0 0 0); z-index: 1; }
.plate-stage .plate-after{ z-index: 0; }
/* the seam line itself — an --accent hairline + faint glow at the wipe edge.
   COMPOSITOR-ONLY: the seam wrapper is FULL-WIDTH and slid by translateX() (a %
   relative to its own = the parent width); it rests at the LEFT edge and rides
   the wipe across to translateX(100%) in lock-step with the clip — transform
   only, never left/calc (the old CLS). */
.plate-seam{
  position: absolute; top: 0; bottom: 0; left: 0; z-index: 2; width: 100%;
  transform: translateX(0%);
  will-change: transform; pointer-events: none;
}
.plate-seam::before{
  content:''; position: absolute; top: 0; bottom: 0; left: 0; width: 2px;
  background: var(--accent);
  box-shadow: 0 0 14px 1px var(--accent-glow), 0 0 3px var(--accent);
}
.plate-tag{
  position: absolute; top: 12px; z-index: 3; color: var(--limestone);
  font-family: var(--font-mono); font-size: var(--fs-micro); font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 6px 11px; border-radius: var(--r-pill);
  background: rgba(14,26,21,0.55); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.plate-tag.before{ left: 12px; }
.plate-tag.after{ right: 12px; background: var(--accent-dark); }
.plate-cap{
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 16px 20px; font-family: var(--font-mono); font-size: var(--fs-caption);
  color: var(--limestone-2);
}
.plate-cap b{ color: var(--limestone); font-weight: 500; }
/* the per-plate $ pill (mono, pulsing emerald dot) */
.plate-cap .price{
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--limestone); font-variant-numeric: tabular-nums;
  background: rgba(247,244,238,0.08); border: 1px solid rgba(247,244,238,0.16);
  border-radius: var(--r-pill); padding: 5px 11px;
}
.plate-cap .price .dot{
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-glow); animation: pulse 2s infinite;
}
/* the caption rail — a mono line that drifts (parallax --py) as the band passes.
   Now an in-flow line centred under the plate stack. */
.proof-rail{
  margin: clamp(44px,7vh,80px) auto 0; text-align: center;
  font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--limestone-2);
  max-width: 56ch;
}
.proof-rail em{ color: var(--sage); font-style: italic; }
/* the rail's parallax drift is now COMPOSITOR-ONLY via animation-timeline:view()
   (premium.css §17) — no JS --py write per scroll frame. Nothing here. */

/* reduced-motion / mobile fallback: vertical stack */
.proof-fallback{ display: none; }
/* the static stack plates FILL their column (the 78vw track width was for the
   horizontal scrub; in the vertical gallery it left an odd narrow strip). */
.proof-fallback .plate{ width: 100%; max-width: 720px; margin: 0 auto; }
.proof-fallback .plate-stage .plate-before{ clip-path: none; opacity: 1; }
/* in the static stack each plate shows the resolved AFTER full-bleed + its seam
   collapsed (the gallery read the brief asks for). JS/markup show the after img. */

/* ---------------------------------------------------------------------------
   7b. THE BRIDGE (§IV-2) — a studio reading-rest that NAMES + AIMS the desire.
   One large kinetic-wiped line + one calm emerald CTA. The kinetic wipe is the
   shared flowLoop word-mask; this just gives the rest its breathing room.
   STATIC degrade: headline + CTA simply shown.
   --------------------------------------------------------------------------- */
.bridge{ padding: var(--band-pad) var(--gutter); text-align: center; }
.bridge p.big{
  font-family: var(--font-display); font-weight: 500;
  font-size: var(--fs-h2); line-height: 1.18; letter-spacing: -0.02em;
  color: var(--ink); max-width: 24ch; margin: 0 auto 28px;
}
.bridge .btn-primary{ margin: 0 auto; }

/* ---------------------------------------------------------------------------
   8. HOW IT WORKS — the .journey upgrade with a self-drawing emerald connector.
   --------------------------------------------------------------------------- */
.journey{
  display: grid; grid-template-columns: repeat(5,1fr); gap: 16px; position: relative;
}
/* FIX #17: a near-solid emerald spine for the RESOLVED state (the old
   transparent→accent→transparent gradient left segments 1→2 and 4→5 reading
   half-drawn even when "drawn"); the ends are softly feathered and tuck behind
   the white step discs, so it reads as one continuous connector. */
.journey::before{
  content:''; position: absolute; top: 28px; left: 10%; right: 10%; height: 2px;
  background: linear-gradient(90deg, transparent 0, var(--accent) 6%, var(--accent) 94%, transparent 100%);
  opacity: 0.92; z-index: 0;
  transform-origin: left center; transform: scaleX(0);
  transition: transform 1.1s var(--ease-reveal) .15s;
}
[data-reveal].in .journey::before,
.journey.in::before{ transform: scaleX(1); }
.jstep{ position: relative; z-index: 1; text-align: center; padding: 0 6px; }
.jstep .num{
  width: 56px; height: 56px; margin: 0 auto 20px; border-radius: 50%;
  background: var(--panel); border: 2px solid var(--accent);
  color: var(--accent-dark);                    /* AA: emerald-on-white text */
  display: grid; place-items: center;
  font-family: var(--font-mono); font-weight: 500; font-size: 18px;
  box-shadow: 0 8px 20px -6px var(--accent-glow);
}
/* FIX #20: fixed 16.5/13.5px → a fluid clamp (no media cliff). The step title sits
   in a 5-up grid so it stays card-title sized, not the full --fs-h3. */
.jstep h3{ font-size: clamp(15.5px, 0.6vw + 13.5px, 18px); font-weight: 600; margin-bottom: 8px; letter-spacing: -0.01em; line-height: 1.25; }
.jstep p{ color: var(--muted); font-size: var(--fs-body); line-height: 1.55; }
.journey-aside{
  grid-column: 1 / -1; margin-top: 22px; text-align: center;
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--accent-dark);
}
@media (max-width:860px){
  /* Worst mobile defect (audit): the side-by-side [number | text] grid crushed
     each step's body copy into a ~6-char column. Restack to a FULL-WIDTH single
     column — number ABOVE the text — so the copy flows the whole content width.
     Uses flex (not grid) so a stray min-content track can never re-collapse it. */
  .journey{ display: block; }
  .journey::before{ display: none; }
  .jstep{
    display: flex; flex-direction: row; align-items: flex-start;
    gap: 14px; text-align: left; width: 100%;
    padding: 18px 0; border-bottom: 1px solid var(--border);
  }
  .jstep .num{ margin: 0; flex: 0 0 auto; width: 48px; height: 48px; font-size: 16px; }
  .jstep .jstep-body{ flex: 1 1 auto; min-width: 0; }
  .jstep h3{ margin-bottom: 6px; }
  .jstep p{ font-size: 14.5px; }
  .journey-aside{ margin-top: 18px; }
}

/* ---------------------------------------------------------------------------
   9. THE FORK — cards slide in from outer margins to meet a centre rule.
   --------------------------------------------------------------------------- */
.fork{ display: grid; grid-template-columns: 1fr 1fr; gap: 24px; position: relative; }
/* FIX #20: align the fork single-column breakpoint to the 860 guard. */
@media (max-width:860px){ .fork{ grid-template-columns: 1fr; } }
.fork-card{
  border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 38px;
  background: var(--panel);
  box-shadow: var(--shadow-sm), 0 18px 44px -28px rgba(12,14,17,0.16);
  display: flex; flex-direction: column;
  transition: transform .22s var(--ease), box-shadow .22s var(--ease);
}
.fork-card:hover{ transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.fork-card.build{
  border-color: rgba(31,157,87,0.42);
  box-shadow: 0 20px 48px -26px var(--accent-glow);
}
.fork-tag{
  font-family: var(--font-mono); font-size: 11px; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent-dark);
  margin-bottom: 12px;
}
.fork-card h3{ font-size: 23px; letter-spacing: -0.018em; margin-bottom: 10px; }
.fork-card > p{ color: var(--muted); font-size: 15px; line-height: 1.62; margin-bottom: 22px; }
.fork-card > p em{ color: var(--ink-2); font-style: italic; }
.fork-chips{ display: flex; flex-wrap: wrap; gap: 9px; margin-bottom: 24px; }
.fork-chips a{
  font-family: var(--font-body); font-size: 13px; font-weight: 500; color: var(--ink-2);
  background: var(--surface-soft); border: 1px solid var(--border); border-radius: 999px;
  padding: 9px 15px; transition: all .15s var(--ease);
}
.fork-chips a:hover{
  color: var(--accent-dark); border-color: var(--accent); background: var(--accent-soft);
  transform: translateY(-1px);
}
/* FIX #25: the role-forked primary CTA per card (chips are secondary). */
.fork-cta{ margin-bottom: 18px; }
.fork-cta .btn{ width: 100%; justify-content: center; }
.fork-meta{ margin-top: auto; padding-top: 6px; font-size: 13px; color: var(--muted); }
.fork-meta b{ color: var(--ink); font-weight: 600; }
/* reveal: slide from outer margins toward the centre rule */
[data-reveal] .fork-card.build,
.fork-card.build.r-child{ }
.fork.in .fork-card{ transform: none; }

/* ---------------------------------------------------------------------------
   9b. EXPLORE CATEGORIES — the .cats pill row (was completely unstyled, rendered
   as a run-on wall of links). Tasteful rounded pills, ≥44px tap targets.
   --------------------------------------------------------------------------- */
.cats{ display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.cat{
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 10px 16px; border-radius: 999px;
  border: 1px solid var(--border); background: #fff; color: var(--ink-2);
  font-family: var(--font-body); font-size: 13.5px; font-weight: 500;
  transition: all .16s var(--ease);
}
.cat:hover{
  border-color: var(--accent); color: var(--accent-dark);
  background: var(--accent-soft); transform: translateY(-1px);
  box-shadow: 0 8px 18px -10px var(--accent-glow);
}

/* ---------------------------------------------------------------------------
   10. THE FLYWHEEL HAND-OFF (§IV-8) — the SECOND SIGNATURE, the thesis. ONE
   job-card pinned centre enters as "Homeowner sees this — your project", and as
   scroll pushes it across a central seam it translateX()'s + re-labels +
   re-composes into "Landscaper receives this — scoped brief", cross-faded at the
   midpoint so it reads as ONE object transforming (geometry CONSERVED), never two
   cards swapping. The background shifts studio-light → --canopy behind it.
   Native carrier: sticky pin + the shared-rAF --p. STATIC degrade: the two cards
   side by side + a connecting arrow + caption (.flywheel-fallback).
   --------------------------------------------------------------------------- */
.flywheel{
  position: relative; text-align: center; overflow: clip;
  padding: var(--band-pad) 0;
  /* the background score: studio-light at --p 0 → --canopy at --p 1. A single
     element crossfade so the band literally darkens as the artifact crosses. */
  background: var(--bg);
  transition: background .5s var(--ease);
}
/* the darkening backdrop layer (so type/heads can sit above it). Starts hidden
   (light band); the compositor view() block (§17) fades it to --canopy as the
   section passes (or the .scene-in fallback does it once on enter). No JS --p. */
.flywheel-bg{
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: var(--canopy);
  opacity: 0;
}
/* IN-FLOW carrier (no pin, no freeze): just a positioning context above the bg */
.flywheel-flow{
  position: relative; z-index: 1;
  display: grid; place-items: center;
  padding: clamp(40px,7vh,90px) var(--gutter);
}
/* the in-flow stage holds the morphing card + its central seam */
.flywheel-stage{
  position: relative; width: 100%; display: grid; place-items: center;
  padding: clamp(20px,4vh,48px) 0;
}
.flywheel .section-head{ margin-bottom: 40px; position: relative; z-index: 1; }
/* the head flips its colour as the band darkens. Default = ink (light band); the
   compositor view() block (§17) / .scene-in fallback (§17b) flips it to limestone
   as the band reaches --canopy. (STATIC keeps it ink on the light fallback.) */
html.flow .flywheel-flow .section-head h2{ color: var(--ink); }
html.flow .flywheel-flow .section-head p{ color: var(--muted); }
/* FIX #12: the eyebrow ("The flywheel") is --accent-dark (2.30:1 on --canopy) once
   the band darkens — flip it to --limestone-2 in lock-step with the head so it
   never reads below AA mid-transition. STATIC keeps it on the light fallback. */

.job-card{
  position: relative; z-index: 1; max-width: 460px; margin: 0 auto; text-align: left;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden;
  box-shadow: var(--shadow-lg);
  transition: background .5s var(--ease), border-color .5s var(--ease), color .5s var(--ease);
}
/* CONSERVED GEOMETRY: the card translateX's a SHORT distance across the central
   seam as the section passes — transform-only, compositor-driven (the view() block
   §17 / the .scene-in fallback §17b own it). No JS --card-x write, no permanent
   will-change layer. The card never resizes. */
.job-card .job-photo{ aspect-ratio: 16/10; background: var(--forest); position: relative; overflow: hidden; }
.job-card .job-photo img{ width: 100%; height: 100%; object-fit: cover; }
.job-card .job-body{ position: relative; padding: 20px 22px; }
.job-card .job-label{
  font-family: var(--font-mono); font-size: var(--fs-micro); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent-dark); margin-bottom: 10px;
}
.job-card .job-fields{ display: grid; gap: 8px; }
.job-card .job-fields div{
  display: flex; justify-content: space-between; gap: 12px;
  font-size: 13.5px; color: var(--ink-2);
  padding-bottom: 8px; border-bottom: 1px solid var(--border);
}
.job-card .job-fields b{ color: var(--ink); font-weight: 600; }

/* the re-composition: the homeowner body + the pro body are STACKED in the same
   card; as the section passes, .home fades out and .pro fades in at the midpoint so
   ONE object transforms (the compositor view() block §17 / .scene-in fallback §17b
   own the crossfade). Authored start: .home visible, .pro hidden. */
.job-card .job-face{ transition: opacity .4s var(--ease); }
.job-card .job-face.pro{ position: absolute; inset: 0; padding: 20px 22px; opacity: 0; }

/* pro inbox re-skin. The card body darkens (via .is-pro on the STATIC two-up, or
   the compositor fw-reskin / .scene-in transition under flow). AA: the pro-face
   field colours are scoped to .job-face.pro DIRECTLY so they read correctly on the
   dark card whenever the pro face is shown — no dependency on a JS .is-pro toggle
   (which the smooth-by-construction rebuild no longer sets). */
/* FIX #15: on the dark card, drop-shadow (--shadow-lg) reads wrong/invisible — use
   the §III dark-band elevation: a 1px sage@12% hairline + inner glow. */
.job-card.is-pro{
  background: #13211b; border-color: rgba(110,139,123,0.16); color: var(--limestone);
  box-shadow: 0 0 0 1px rgba(110,139,123,0.12), inset 0 1px 0 rgba(247,244,238,0.04);
}
/* AA: sage @ micro on #13211b is below 4.5:1 — lift the pro inbox label/fields. */
.job-card.is-pro .job-label,
.job-card .job-face.pro .job-label{ color: var(--limestone-2); }
.job-card.is-pro .job-fields div,
.job-card .job-face.pro .job-fields div{ color: var(--limestone-2); border-bottom-color: rgba(247,244,238,0.12); }
.job-card.is-pro .job-fields b,
.job-card .job-face.pro .job-fields b{ color: var(--limestone); }
/* the central seam hairline the card crosses (emerald, faint glow) */
.flywheel-seam{
  position: absolute; top: 12%; bottom: 12%; left: 50%; width: 2px; z-index: 0;
  transform: translateX(-1px);
  background: linear-gradient(180deg, transparent, var(--accent), transparent);
  box-shadow: 0 0 14px 1px var(--accent-glow); opacity: 0.7; pointer-events: none;
}

/* reduced-motion / mobile: a labelled two-up. On NARROW it stacks (card / arrow /
   card); on WIDE reduced-motion it becomes the brief's "two cards side by side +
   connecting arrow" — a 3-col grid (homeowner card | seam arrow | pro card). */
.flywheel-fallback{ display: none; gap: 24px; align-items: center; }
.flywheel-fallback .job-card{ width: 100%; max-width: 460px; margin: 0 auto; }
.flywheel-fallback .seam{
  display: flex; align-items: center; justify-content: center; gap: 10px;
  font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--muted);
  text-align: center; margin: 8px 0;
}
.flywheel-fallback .seam::before,
.flywheel-fallback .seam::after{ content:''; height: 1px; flex: 1; max-width: 60px; background: var(--border-strong); }
/* WIDE: two cards side by side with the arrow between them (the §IV-8 degrade). */
@media (min-width: 880px){
  .flywheel-fallback{
    grid-template-columns: 1fr auto 1fr;
    align-items: center; gap: 20px; max-width: 1040px;
  }
  .flywheel-fallback .job-card{ margin: 0; }
  .flywheel-fallback .seam{ flex-direction: column; margin: 0; white-space: nowrap; }
  /* turn the divider hairlines into a horizontal connector arrow on wide */
  .flywheel-fallback .seam::before,
  .flywheel-fallback .seam::after{ max-width: 36px; }
}

/* ---------------------------------------------------------------------------
   11. THE PRO BAND — dark --canopy, lead with jobs-won/time-saved (not price).
   --------------------------------------------------------------------------- */
.pro-band{
  position: relative; isolation: isolate; overflow: clip;
  background: var(--canopy); color: var(--limestone);
  border-radius: 28px; padding: 64px 56px;
  display: grid; grid-template-columns: 1.1fr 1fr; gap: 52px; align-items: center;
  box-shadow: 0 34px 74px -32px rgba(12,14,17,0.45);
}
.pro-band::after{
  content:''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image: var(--grain); opacity: 0.035; mix-blend-mode: overlay;
}
@media (max-width:820px){ .pro-band{ grid-template-columns: 1fr; padding: 44px 28px; gap: 32px; } }
/* AA: sage eyebrow on --canopy is only 3.33:1 — lift to the lighter foliage tint */
.pro-band .eyebrow{ color: #C7CFC8; }
.pro-band h2{ font-size: clamp(28px, 3.4vw, 44px); line-height: 1.08; letter-spacing: -0.022em; margin: 14px 0 16px; color: var(--limestone); }
.pro-band p{ color: var(--limestone-2); font-size: 16px; line-height: 1.65; max-width: 48ch; margin-bottom: 28px; }
.pro-list{ list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.pro-list li{ display: flex; gap: 13px; align-items: flex-start; font-size: 15px; color: var(--limestone-2); }
.pro-list li svg{ width: 22px; height: 22px; flex: none; color: var(--sage); margin-top: 1px; }
.pro-list li b{ color: var(--limestone); font-weight: 600; }
/* (the old .proposal-mock placeholder was dead — the live markup is .proposal-fold,
   styled in §11b below — so its orphan selector was removed.) */
.pro-stats{
  display: flex; gap: 26px; flex-wrap: wrap; margin-top: 26px;
  font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--limestone-2);
}
.pro-stats b{ color: var(--limestone); font-weight: 500; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
   11b. THE BRANDED-PROPOSAL FOLD (§IV-9) — a clip-path/transform "fold": a
   client-yard render folds into a polished proposal stamped with the
   landscaper's OWN logo. Beats on --p: (1) the render sits; (2) the proposal
   paper folds up over it (clip-path inset from the top); (3) the logo RESOLVES
   LAST ("win the job before you leave the driveway"). --sage accents,
   --limestone type.  STATIC degrade: the finished branded proposal shown.
   --------------------------------------------------------------------------- */
/* FIX #5: was a 3/4 PORTRAIT box over a 16:9 LANDSCAPE render — cover discarded
   most of the width then upscaled. 4/3 keeps far more of the source (and the
   paper proposal reads fine in landscape). */
.proposal-fold{
  position: relative; border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid rgba(247,244,238,0.16); background: #16231d;
  box-shadow: 0 30px 60px -28px rgba(0,0,0,0.6);
  aspect-ratio: 4 / 3;
  /* FIX (logo clip): the absolutely-positioned .pf-paper packs h3 + 3 lines + the
     logo lockup; at narrow widths the rigid 4/3 box is shorter than that content,
     so the bottom .pf-logo (margin-top:auto) was clipped by overflow:hidden. Give
     the box a content floor so it can never crop the lockup. */
  min-height: 340px;
}
/* below the pro-band's two-column break the render+paper share the full width and
   the box gets tall enough that 4/3 would crop the logo — let it grow to content.
   The paper goes in-flow (position:relative) so it gives the box its height (the
   absolute .pf-render stays a backdrop behind it); the logo can never be clipped. */
@media (max-width: 820px){
  .proposal-fold{ aspect-ratio: auto; min-height: 0; }
  .proposal-fold .pf-paper{ position: relative; }
}
.pf-render{ position: absolute; inset: 0; z-index: 1; }
.pf-render img{ width: 100%; height: 100%; object-fit: cover; display: block; }
/* the proposal "paper" folds up over the render (clip-path inset from top → 0) */
/* FIX #9: the RESOLVED proposal is the DEFAULT frame (clip-path inset(0)) so it is
   complete at rest for STATIC + no-JS + any path where the small overflow-hidden
   aspect box never resolved a view() range (the bug that left the fold blank). The
   FLOW paths (§17 view() / §17b .scene-in) override to a closed start and replay
   the unfold as a one-shot on top. */
.pf-paper{
  position: absolute; inset: 0; z-index: 2;
  background: var(--limestone); color: #1a2b22;
  padding: clamp(20px,3vw,30px);
  display: flex; flex-direction: column; gap: 12px;
  -webkit-clip-path: inset(0 0 0 0);
  clip-path: inset(0 0 0 0);
}
/* the paper unfolds (clip-path inset top 100% → 0) as the fold passes — owned by
   the compositor view() block (§17) / the .scene-in fallback (§17b). No JS --p. */
.pf-paper h3{
  font-family: var(--font-display); font-weight: 500; font-style: italic;
  font-size: var(--fs-h3); color: #16231d; margin: 0; letter-spacing: -0.01em;
}
.pf-paper .pf-line{
  display: flex; justify-content: space-between; gap: 12px;
  font-family: var(--font-mono); font-size: var(--fs-caption); color: #3a4a42;
  border-bottom: 1px solid rgba(26,43,34,0.14); padding-bottom: 8px;
}
.pf-paper .pf-line b{ color: #16231d; font-weight: 500; }
/* the logo lockup — RESOLVES LAST. FIX #9: the resolved (stamped) state is the
   DEFAULT so STATIC/no-JS show it; FLOW paths override to a hidden start and
   stamp it on enter. */
.pf-logo{
  margin-top: auto; display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  opacity: 1; transform: none;
  transition: opacity .5s var(--ease), transform .5s var(--ease-expo);
}
.pf-logo .pf-mark{
  width: 34px; height: 34px; border-radius: 9px;
  background: linear-gradient(135deg, var(--canopy), var(--forest));
  display: grid; place-items: center; color: var(--sage); flex: none;
}
/* min-width:0 lets a long business name wrap instead of pushing the lockup wider
   than the paper (which the box would then clip horizontally). */
.pf-logo .pf-name{ font-family: var(--font-display); font-weight: 600; color: #16231d; font-size: 15px; min-width: 0; overflow-wrap: anywhere; }
.pf-logo .pf-name small{ display: block; font: 500 10px/1.2 var(--font-mono); color: #5a6a62; letter-spacing: 0.08em; }

/* ---------------------------------------------------------------------------
   12. PROOF & TRUST GALLERY — mounted-plate grid, micro before→after on hover.
   --------------------------------------------------------------------------- */
.gallery .section-head h2{ color: var(--ink); }
.gallery-grid{ display: grid; grid-template-columns: repeat(2,1fr); gap: 18px; margin-top: 8px; }
@media (min-width:1000px){ .gallery-grid{ grid-template-columns: repeat(4,1fr); } }
/* FIX #5: tiles were aspect 4/5 (portrait) over 16:9 (landscape) sources — cover
   discarded ~half the source width then upscaled the remaining strip (a major
   softness compounder). 3/2 keeps almost all of a 16:9 frame (crops ~16% of
   width only) so the render stays crisp. */
.gallery-tile{
  position: relative; border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid var(--border); aspect-ratio: 3/2; background: var(--surface-soft);
  box-shadow: var(--shadow-sm);
  transition: transform .25s var(--ease), box-shadow .25s var(--ease);
}
.gallery-tile:hover, .gallery-tile:focus-within{
  transform: translateY(-4px); box-shadow: var(--shadow-lg);
}
.gallery-tile img{ width: 100%; height: 100%; object-fit: cover; transition: transform .6s ease, opacity .2s ease; }
/* FIX #21: the `.tile-after` micro before→after hover rules had no matching element
   in markup (the tiles are single renders) — removed. */
.gallery-cap{
  position: absolute; left: 12px; bottom: 12px; z-index: 2; color: #fff;
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
  padding: 6px 12px; border-radius: 999px;
  background: rgba(14,26,21,0.6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.gallery-cap .price{ color: #fff; opacity: 0.85; }

/* ---------------------------------------------------------------------------
   12b. EVERYTHING WE COVER — the 12-service immersive tour (§5). Reuses the
   .gallery-grid mechanics + the staggered .r-child IntersectionObserver reveal
   (compositor-cheap, NOT pinned, NOT scrubbed). Each tile is a real render
   thumbnail + a Fraunces service title + one plain line, the WHOLE tile linking
   to that service's cost-guide page. The image fills the tile; the label/line sit
   on a readable gradient footer over it, so the tour reads as breadth at a glance.
   --------------------------------------------------------------------------- */
.services-tour .st-grid{ margin-top: 8px; }
@media (min-width:1000px){ .services-tour .st-grid{ grid-template-columns: repeat(4,1fr); } }
/* 1-up on narrow phones (the STATIC tier) so each label + line stays readable */
@media (max-width:560px){ .services-tour .st-grid{ grid-template-columns: 1fr; } }
/* the whole tile is the link; reuse .gallery-tile's frame, crop + lift-on-hover */
.service-tile{ display: block; text-decoration: none; color: inherit; }
.service-tile img{ transition: transform .5s var(--ease); }
.service-tile:hover img, .service-tile:focus-visible img{ transform: scale(1.04); }
/* the label + one line, on a bottom scrim so they clear AA over any render */
.service-tile .st-body{
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 16px 16px 14px;
  background: linear-gradient(0deg, rgba(12,18,15,0.9) 0%, rgba(12,18,15,0.62) 52%, rgba(12,18,15,0) 100%);
}
.service-tile .st-body h3{
  font-family: var(--font-display); font-weight: 500; letter-spacing: -0.018em;
  font-size: clamp(1.05rem, 0.6vw + 0.95rem, 1.35rem); line-height: 1.15;
  color: #fff; margin: 0 0 4px;
}
.service-tile .st-body p{
  font-size: 13px; line-height: 1.45; color: rgba(247,244,238,0.9);
  margin: 0; max-width: 40ch; text-wrap: pretty;
}
/* the quiet hub line + the SEO hub links under the grid (nothing SEO is lost) */
.services-tour .st-foot{
  margin: 34px auto 0; text-align: center;
  font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--muted);
}
.services-tour .st-links{
  display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 18px;
}

/* ---------------------------------------------------------------------------
   12c. TWO-SIDED ACT LABELS (§ two-sided) — a mono section-eyebrow signposts each
   act ("For homeowners" over How-it-works; "For gardeners & landscapers" on the
   pro band) so a visitor always knows which protagonist a run is speaking to. The
   pro-side gets a one-line framing under its eyebrow.
   --------------------------------------------------------------------------- */
.act-eyebrow{ position: relative; }
/* the homeowner act-eyebrow sits on light studio (inherits .eyebrow accent-dark);
   the pro band's already flips to the lighter foliage tint via .pro-band .eyebrow. */
.pro-framing{
  font-family: var(--font-mono); font-size: var(--fs-caption); font-weight: 500;
  letter-spacing: 0.04em; color: var(--limestone); margin: 0 0 6px;
}

/* ---------------------------------------------------------------------------
   12d. THE THREE-ACT PROCESS FILM — connective tissue + beats, all carried by
   ALREADY-SHIPPING primitives (the corner locator is static DOM; the roster +
   quote-slips + chat bubbles ride .in / .r-child / .scene-in and the draw-x
   keyframe). ZERO new mechanics, ZERO per-frame scroll work.
   --------------------------------------------------------------------------- */

/* --- §0 THE PERSISTENT CORNER LOCATOR -------------------------------------
   A documentary lower-third pinned under the nav. position:fixed (NOT scroll-
   jacked, NOT per-frame); its colour token flips light/dark with the band via the
   SAME body.over-dark class the nav already toggles. It fades on the final CTA
   (premium.js adds body.locator-done on the .final-cta sentinel IO). */
.corner-locator{
  position: fixed; top: 60px; left: var(--gutter); z-index: 90;
  display: inline-flex; align-items: center; gap: 9px;
  padding: 6px 12px; border-radius: var(--r-pill);
  font-family: var(--font-mono); font-size: 11px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--limestone);
  /* a frosted chip so it reads as a documentary REC lower-third on ANY band (the
     hero is a dark photo at the very top, where the nav itself is not over-dark) */
  background: rgba(14,26,21,0.5);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  border: 1px solid rgba(247,244,238,0.16);
  pointer-events: none;
  transition: opacity .4s var(--ease), background .3s var(--ease),
              border-color .3s var(--ease), color .3s var(--ease);
}
.corner-locator .dot{
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 0 var(--accent-glow); animation: pulse 2.4s infinite;
}
.corner-locator .cl-text i{ color: var(--sage); font-style: normal; margin: 0 2px; }
/* scrolled onto a LIGHT studio band (not over-dark): flip to a light frosted chip
   with ink text so it stays AA-legible on white. */
body.scrolled:not(.over-dark) .corner-locator{
  background: rgba(255,255,255,0.82); border-color: var(--border);
  color: var(--ink-2);
}
body.scrolled:not(.over-dark) .corner-locator .cl-text i{ color: var(--dim); }
/* its job is done at the final CTA — fade it out (one class write, no loop) */
body.locator-done .corner-locator{ opacity: 0; }
/* mobile: collapse to the dot + the address, then hide entirely on tiny screens */
@media (max-width: 860px){ .corner-locator{ top: 58px; font-size: 10px; } }
@media (max-width: 560px){ .corner-locator{ display: none; } }

/* --- §7 EXTENSION: PICK YOUR PROS + QUOTES BACK ---------------------------
   a roster of 3 local-pro cards (NO lead/contact counts, ever) + 3 quote-back
   slips that fan in. Built from .r-child stagger; the --i delay cascades them. */
.roster-beat{ margin-top: clamp(40px,6vw,72px); }
.roster-head{
  font-family: var(--font-display); font-weight: 500; letter-spacing: -0.018em;
  font-size: var(--fs-h3); color: var(--ink); text-align: center;
  margin: 0 auto clamp(24px,3vw,34px); max-width: 24ch;
}
.roster-head em{ font-style: italic; color: var(--accent); position: relative; display: inline-block; }
.roster-cards{
  display: grid; grid-template-columns: repeat(3,1fr); gap: 16px; margin-bottom: 18px;
}
.roster-card{
  display: flex; align-items: flex-start; gap: 12px;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--panel); padding: 16px 18px; box-shadow: var(--shadow-sm);
}
.roster-card .rc-mark{
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  background: var(--accent-soft); color: var(--accent-dark);
  display: grid; place-items: center;
}
.roster-card .rc-body{ display: flex; flex-direction: column; min-width: 0; }
.roster-card .rc-body b{ font-size: 14.5px; font-weight: 600; color: var(--ink); letter-spacing: -0.01em; }
.roster-card .rc-body span{ font-size: 12.5px; color: var(--ink-2); margin-top: 2px; }
.roster-card .rc-body i{ font-style: italic; font-size: 12px; color: var(--muted); margin-top: 6px; }
.quote-slips{ display: grid; grid-template-columns: repeat(3,1fr); gap: 16px; }
.quote-slip{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  border: 1px solid rgba(31,157,87,0.4); border-radius: var(--r-pill);
  background: var(--accent-soft); padding: 11px 16px;
  font-family: var(--font-mono); font-size: 12.5px; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.quote-slip .qs-dot{
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none;
  box-shadow: 0 0 0 0 var(--accent-glow); animation: pulse 2.2s infinite;
}
.quote-slip b{ color: var(--accent-dark); font-weight: 600; }
.quote-slip span{ color: var(--ink); font-weight: 500; }
.quote-slip i{ color: var(--muted); font-style: normal; margin-left: auto; }
@media (max-width: 860px){
  .roster-cards, .quote-slips{ grid-template-columns: 1fr; }
  .quote-slip i{ margin-left: 0; }
}

/* --- §10 THE FLYWHEEL NOTIFICATION LINE -----------------------------------
   a mono chip that resolves above the morphing card so the hand-off reads as a
   brief LANDING in an inbox. Rests resolved on STATIC; under flow it eases in on
   the same .scene-in the flywheel already carries. The pulsing .dot is shipping. */
.flywheel-notify{
  position: relative; z-index: 1; margin: 0 auto clamp(18px,2.5vw,26px);
  display: inline-flex; align-items: center; gap: 9px;
  border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  background: var(--panel); padding: 9px 16px; box-shadow: var(--shadow-sm);
  font-family: var(--font-mono); font-size: var(--fs-caption); font-weight: 500;
  letter-spacing: 0.04em; color: var(--ink-2);
}
.flywheel-notify .dot{
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: none;
  box-shadow: 0 0 0 0 var(--accent-glow); animation: pulse 2s infinite;
}
.flywheel-notify .fn-text i{ color: var(--dim); font-style: normal; margin: 0 3px; }
/* under flow it eases in with the morph (a touch after the head); on the dark band
   it flips to a sage-outlined dark chip in lock-step with the card re-skin. */
html.flow:not(.is-static) .flywheel-notify{ opacity: 0; transform: translateY(6px); transition: opacity .5s var(--ease-reveal) .25s, transform .5s var(--ease-expo) .25s, background .6s var(--ease) .42s, border-color .6s var(--ease) .42s, color .6s var(--ease) .42s; }
html.flow:not(.is-static) .flywheel.scene-in .flywheel-notify{
  opacity: 1; transform: none;
  background: #13211b; border-color: rgba(110,139,123,0.28); color: var(--limestone-2);
}
html.flow:not(.is-static) .flywheel.scene-in .flywheel-notify .fn-text i{ color: var(--sage); }

/* --- §13 THE QUICK-JOB THREAD (Act III) -----------------------------------
   a light studio band as a same-day chat thread. Each bubble is a staggered
   .r-child resolving on enter; NET-NEW CSS is only the column wrapper + left/right
   alignment + the reused compact card. Zero per-frame cost. */
.quick-thread .section-head{ margin-bottom: clamp(34px,5vw,52px); }
.qt-thread{
  display: flex; flex-direction: column; gap: 18px;
  max-width: 680px; margin: 0 auto;
}
.qt-bubble{ display: flex; flex-direction: column; max-width: 86%; }
.qt-bubble.out{ align-self: flex-end; align-items: flex-end; text-align: left; }
.qt-bubble.in{ align-self: flex-start; align-items: flex-start; }
/* the homeowner (outgoing) card: a soft emerald-tinted bubble with a thumb + ask */
.qt-card{
  display: flex; gap: 14px; align-items: center;
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--panel); padding: 14px 16px; box-shadow: var(--shadow-sm);
  max-width: 100%;
}
.qt-bubble.out .qt-card{ background: var(--accent-soft); border-color: rgba(31,157,87,0.32); }
.qt-thumb{
  width: 92px; height: 70px; flex: none; border-radius: var(--r-sm); overflow: hidden;
  background: var(--surface-soft);
}
.qt-thumb img{ width: 100%; height: 100%; object-fit: cover; display: block; }
.qt-body{ min-width: 0; }
.qt-body p{ margin: 0 0 8px; font-size: 15px; line-height: 1.4; color: var(--ink); font-weight: 500; }
.qt-body p:last-child{ margin-bottom: 0; }
/* the mono quick-job tag reuses the corner-locator voice */
.qj-tag{
  display: inline-block; font-family: var(--font-mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted);
}
.qj-tag i{ color: var(--dim); font-style: normal; margin: 0 2px; }
/* the scoped-brief landing line + the compact reused job-card */
.qt-meta{
  margin: 0 0 10px; font-family: var(--font-mono); font-size: var(--fs-caption);
  letter-spacing: 0.02em; color: var(--accent-dark);
}
.job-card.qt-brief{ max-width: 380px; margin: 0; box-shadow: var(--shadow-sm); }
.job-card.qt-brief .job-body{ padding: 16px 18px; }
.job-card.qt-brief .job-fields div{ font-size: 13px; }
/* the gardener quote chip (reuses .price-pill, made in-flow here) + caption */
.price-pill.qt-quote{
  position: static; transform: none; left: auto; top: auto;
  font-size: 13px; padding: 10px 16px;
}
.price-pill.qt-quote i{ color: var(--dim); font-style: normal; margin: 0 3px; }
.qt-cap{
  display: block; margin-top: 8px; font-family: var(--font-mono); font-size: 11.5px;
  letter-spacing: 0.04em; color: var(--muted);
}
/* the mini before/after lawn strip (the .plate at small scale, seam pre-resolved) */
.qt-plate{
  width: 320px; max-width: 100%; border-radius: var(--r-md); overflow: hidden;
  border: 1px solid var(--border); box-shadow: var(--shadow-sm);
}
.qt-plate .plate-stage{ aspect-ratio: 16/9; }
/* the strip resolves to the AFTER (mowed lawn): clip the before away. On STATIC it
   rests resolved; under flow it eases open once via .scene-in (no scrub). */
.qt-plate .plate-stage .plate-before{ clip-path: inset(0 100% 0 0); }
.qt-plate .plate-seam{ display: none; }
html.flow:not(.is-static) .qt-plate .plate-stage .plate-before{ clip-path: inset(0 0 0 0); }
html.flow:not(.is-static) .qt-plate.scene-in .plate-stage .plate-before{
  clip-path: inset(0 100% 0 0); transition: clip-path 1s var(--ease-reveal);
}
.qt-booked{
  display: inline-flex; align-items: center; gap: 8px; margin: 12px 0 0;
  font-size: 15px; font-weight: 600; color: var(--ink);
}
.qt-booked svg{ color: var(--accent-dark); }
.qt-foot{
  margin: clamp(28px,4vw,40px) auto 0; text-align: center;
  font-family: var(--font-mono); font-size: var(--fs-caption); color: var(--muted);
}
@media (max-width: 560px){
  .qt-bubble{ max-width: 100%; }
  .qt-card{ flex-direction: column; align-items: flex-start; }
  .qt-thumb{ width: 100%; height: 120px; }
  .qt-plate{ width: 100%; }
}

/* --- §11 the on-site caption on the proposal render ----------------------- */
.pf-render{ position: absolute; inset: 0; }
.pf-onsite{
  position: absolute; left: 12px; bottom: 12px; z-index: 2;
  font-family: var(--font-mono); font-size: var(--fs-micro); font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--limestone);
  background: rgba(14,26,21,0.6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  border: 1px solid rgba(247,244,238,0.18); border-radius: var(--r-pill);
  padding: 5px 11px;
}
/* once the proposal paper unfolds it covers the render; the caption is part of the
   render layer beneath, so it only reads during the unfold + on the resolved still
   where a sliver shows. Keep it on the render layer (z below the paper). */

/* --- §15 services-tour slate + §19 final-cta slate ------------------------ */
.services-tour .st-slate,
.final-cta .final-slate{
  font-family: var(--font-mono); font-size: 11px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase; margin: 0 0 14px;
}
.services-tour .st-slate{ color: var(--muted); }
.final-cta .final-slate{ color: var(--sage); }

/* ---------------------------------------------------------------------------
   13. PRICING POSTURE — two honest columns split by a centre rule that draws.
   --------------------------------------------------------------------------- */
.pricing{ text-align: center; }
.pricing-free{
  font-family: var(--font-display); font-style: italic; font-weight: 500;
  font-size: clamp(34px,4.4vw,58px); color: var(--accent); line-height: 1;
  letter-spacing: -0.02em; display: inline-block; margin-bottom: 8px;
}
.pricing-cols{
  display: grid; grid-template-columns: 1fr 1px 1fr; gap: 40px;
  max-width: 920px; margin: 48px auto 0; text-align: left; align-items: start;
}
@media (max-width:760px){ .pricing-cols{ grid-template-columns: 1fr; gap: 28px; } .pricing-rule{ display: none; } }
.pricing-rule{
  background: var(--border); align-self: stretch; width: 1px;
  transform: scaleY(0); transform-origin: top center;
  transition: transform .7s var(--ease-reveal) .15s;
}
.pricing.in .pricing-rule, [data-reveal].in .pricing-rule{ transform: scaleY(1); }
.pricing-col h3{
  font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent-dark);
  margin-bottom: 14px;
}
.pricing-col p{ color: var(--ink-2); font-size: 15px; line-height: 1.66; max-width: 38ch; }

/* ---------------------------------------------------------------------------
   14. FAQ — keep the <details> accordion (schema-bearing markup unchanged).
   --------------------------------------------------------------------------- */
.faq-block{ max-width: 800px; margin: 0 auto; }
.faq-item{
  padding: 24px 26px; margin-bottom: 12px; cursor: pointer; border-radius: var(--radius);
  background: var(--panel); border: 1px solid var(--border);
  transition: box-shadow .2s var(--ease), border-color .2s var(--ease);
}
.faq-item[open]{ box-shadow: var(--shadow); border-color: var(--border-strong); }
.faq-item summary{
  list-style: none; font-weight: 600; font-size: 16.5px;
  display: flex; justify-content: space-between; align-items: center; gap: 14px;
}
.faq-item summary::-webkit-details-marker{ display: none; }
.faq-item summary::after{
  content:'+'; font-size: 26px; color: var(--accent-dark); font-weight: 300; flex: none;
  transition: transform .2s var(--ease);
}
.faq-item[open] summary::after{ transform: rotate(45deg); }
.faq-item p{ color: var(--muted); font-size: 14.5px; line-height: 1.7; margin-top: 14px; }
.faq-item p a{ color: var(--accent-dark); font-weight: 600; }

/* ---------------------------------------------------------------------------
   15. FINAL CTA — dark forest book-end, italic word ignites emerald on enter.
   --------------------------------------------------------------------------- */
.final-cta{ text-align: center; padding: 120px 24px; }
.final-cta h2{
  font-family: var(--font-display); font-weight: 500; color: var(--limestone);
  font-size: clamp(30px, 3.8vw, 50px); letter-spacing: -0.024em; margin-bottom: 16px; line-height: 1.06;
}
.final-cta h2 em{ font-style: italic; color: var(--sage); transition: color .5s var(--ease); }
.final-cta.in h2 em{ color: var(--accent); }
.final-cta p{ color: var(--limestone-2); margin: 0 auto 30px; max-width: 56ch; font-size: 16.5px; }
.final-cta .glow{ position: relative; display: inline-flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.final-cta .glow::before{
  content:''; position: absolute; inset: -40px -20px; z-index: -1; pointer-events: none;
  background: radial-gradient(60% 80% at 50% 50%, rgba(31,157,87,0.22), transparent 70%);
  opacity: 0; transition: opacity .6s var(--ease);
}
.final-cta.in .glow::before{ opacity: 1; }

/* ---------------------------------------------------------------------------
   16. FOOTER — restyle only (markup from base.css unchanged).
   --------------------------------------------------------------------------- */
footer{ margin-top: 120px; background: var(--bg-2); border-top: 1px solid var(--border); }
.foot-col h4{ font-family: var(--font-mono); letter-spacing: 0.1em; }
.foot-about p{ line-height: 1.7; }

/* ---------------------------------------------------------------------------
   17. TIER-1 REVEALS — base IO fallback (matches home.html's existing classes),
   then the native CSS scroll-driven upgrade gated by @supports.
   --------------------------------------------------------------------------- */
[data-reveal]{
  opacity: 0; transform: translateY(28px);
  transition: opacity .8s var(--ease-reveal), transform .8s var(--ease-reveal);
}
[data-reveal].in{ opacity: 1; transform: none; }
.r-child{
  opacity: 0; transform: translateY(20px) scale(.99);
  transition: opacity .65s var(--ease-reveal), transform .65s var(--ease-reveal);
  transition-delay: calc(var(--i,0) * 70ms);
}
.r-child.in{ opacity: 1; transform: none; }

/* fork cards arrive from the outer margins toward the centre rule */
.fork .fork-card.r-child:first-child{ transform: translateY(20px) translateX(-26px); }
.fork .fork-card.r-child:last-child{ transform: translateY(20px) translateX(26px); }
.fork .fork-card.r-child.in{ transform: none; }

/* Native CSS scroll-driven reveals — ZERO JS on capable browsers.
   Gated by @supports so non-supporting browsers fall back to the IO above. */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    /* ----------------------------------------------------------------------
       FIX #1 (THE master-guard leak) — the ENTIRE view() scroll-timeline block
       is now gated on `html.flow:not(.is-static)`. Previously these rules ran on
       EVERY modern phone (view() supported, motion not reduced, width<860 → the
       JS guard sets STATIC) and OVERRODE premium.js's snapped `.in`/final frames —
       the single biggest source of the "glitching on real hardware" the founder
       hit (scroll-coupled motion fighting the STATIC cut: fork cards resting part-
       faded, muddy x-vs-y ghost, inverted proof plates, half-drawn flywheel, hero
       push-in on a frozen poster). With the FLOW gate, STATIC mobile keeps the
       authored final frames and the scroll thread does zero work. Keyframes are
       global (can't be scoped) and harmless when no rule references them.
       ---------------------------------------------------------------------- */
    html.flow:not(.is-static) [data-reveal]{
      opacity: 1; transform: none;                 /* hand control to the timeline */
      animation: reveal-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 42%;
    }
    html.flow:not(.is-static) .r-child{
      animation: reveal-rise linear both;
      animation-timeline: view();
      animation-range: entry 4% entry 46%;
    }
    @keyframes reveal-rise{
      from{ opacity: 0; transform: translateY(28px); }
      to{ opacity: 1; transform: none; }
    }
    /* gentle transform-only parallax on cinema plates / the proposal fold */
    html.flow:not(.is-static) .proposal-fold,
    html.flow:not(.is-static) .plate{
      animation: parallax-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 60%;
    }
    @keyframes parallax-rise{
      from{ transform: translateY(34px); }
      to{ transform: translateY(0); }
    }
    /* connector / underline / centre rules draw on their own view-timeline */
    html.flow:not(.is-static) .journey::before{ animation: draw-x linear both; animation-timeline: view(); animation-range: entry 10% cover 30%; }
    @keyframes draw-x{ from{ transform: scaleX(0); } to{ transform: scaleX(1); } }
    html.flow:not(.is-static) .pricing-rule{ animation: draw-y linear both; animation-timeline: view(); animation-range: entry 10% cover 35%; }
    @keyframes draw-y{ from{ transform: scaleY(0); } to{ transform: scaleY(1); } }
    html.flow:not(.is-static) .draw-underline em::after{ animation: draw-x linear both; animation-timeline: view(); animation-range: entry 20% cover 30%; }

    /* gallery tiles drift on their own view-timeline (transform-only, capped) */
    html.flow:not(.is-static) .gallery-tile{ animation: parallax-rise linear both; animation-timeline: view(); animation-range: entry 0% cover 70%; }


    /* the proof-walk caption rail drifts on a scroll() timeline (parallax flavour). */
    html.flow:not(.is-static) .proof-rail{
      animation: rail-drift linear both; animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    @keyframes rail-drift{ from{ transform: translateY(16px); } to{ transform: translateY(-16px); } }

    /* PROOF WALK (zero-JS fallback) — each IN-FLOW plate opens its OWN seam from
       its own scroll-through (premium.js drives this on the flow path; this is the
       capable-browser/no-JS floor so the reveal still plays).
       FIX #3 (inverted seam): the centrepiece must reveal the RENDER, not hide it.
       FLOW start = BEFORE photo fully visible; end = before clipped to NOTHING so
       the after (render) is fully exposed. The hairline rides the wipe edge
       left→right in lock-step. Both on the compositor, as the plate passes. */
    html.flow:not(.is-static) .proof-track .plate .plate-before{
      animation: plate-open linear both; animation-timeline: view();
      animation-range: entry 8% cover 55%;
    }
    html.flow:not(.is-static) .proof-track .plate .plate-seam{
      animation: plate-seam-slide linear both; animation-timeline: view();
      animation-range: entry 8% cover 55%;
    }
    @keyframes plate-open{
      from{ clip-path: inset(0 0 0 0); } to{ clip-path: inset(0 100% 0 0); }
    }
    @keyframes plate-seam-slide{
      from{ transform: translateX(0%); } to{ transform: translateX(100%); }
    }

    /* FLYWHEEL: intentionally NOT scroll-scrubbed here. The card-morph used to run
       on the section's --fw view-timeline (translateX(-9%→9%) + a mid-pass face
       crossfade), which slid the whole card sideways every scroll frame and produced
       a muddy co-paint window at the midpoint — the founder's "glitching on real
       hardware." It is now a ONE-SHOT reveal: premium.js adds .scene-in once on
       enter and §17b plays a single clean transition (home face cross-cuts to pro,
       band darkens once, NO per-frame slide). That reveal lives OUTSIDE this
       @supports gate so it runs on view()-capable browsers too. See §17b. */

    /* PROPOSAL FOLD (compositor) — the proposal paper is the resolved DEFAULT
       (fix #9); the fold + stamp play as a one-shot reveal on top, but the small
       overflow-hidden aspect box means the view() range may never resolve, so the
       fold runs from a CLOSED override only under flow and the paper's resting
       base is already inset(0). The logo stamps LAST. */
    html.flow:not(.is-static) .proposal-fold .pf-paper{
      animation: pf-unfold linear both; animation-timeline: view();
      animation-range: entry 20% cover 60%;
    }
    html.flow:not(.is-static) .proposal-fold .pf-logo{
      animation: pf-stamp linear both; animation-timeline: view();
      animation-range: cover 55% cover 75%;
    }
    @keyframes pf-unfold{
      from{ clip-path: inset(100% 0 0 0); } to{ clip-path: inset(0 0 0 0); }
    }
    @keyframes pf-stamp{ from{ opacity: 0; transform: translateY(8px); } to{ opacity: 1; transform: none; } }

    /* IMMERSIVE HERO push-in (compositor) — the dusk poster + canvas get a gentle
       camera push-in on the hero's own view-timeline so it reads alive as it
       scrolls, with ZERO main-thread cost (no JS --cam-scale per frame). */
    html.flow:not(.is-static) .hero-build .build-camera{
      animation: hero-pushin linear both; animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    @keyframes hero-pushin{ from{ transform: scale(1.06); } to{ transform: scale(1.0); } }
  }
}

/* ===========================================================================
   17b. THE FLOW MOTION SYSTEM — SMOOTH BY CONSTRUCTION (2026-06-20 rebuild).
   premium.js sets <html class="flow"> ONLY when the guard passes (fine pointer,
   no reduced-motion, width >= 860). The old system had JS write per-element
   custom props (--p/--py/--kp) on a shared rAF EVERY scroll frame — the dominant
   glitch on real hardware. That is GONE. Motion is now either:
     (a) compositor-driven scroll timelines (the @supports view() block, §17),
         which the OLD code disabled with `animation:none !important` to "hand the
         wheel to JS" — that override is DELETED, so view() now runs natively, OR
     (b) one-shot reveal-on-enter: premium.js adds `.in` (reveals) / `.scene-in`
         (set-pieces) ONCE on enter via a shared IntersectionObserver, and the CSS
         below plays a single transition to the authored final frame.
   NO JS reads layout or writes transforms on any scroll/rAF tick. There is no
   smooth-scroll lerp (it fought real input), no parallax JS loop, no per-word
   kinetic-type loop (the heads render clean; their italic <em> underline draws on
   its own view-timeline). `.flow` here only flips a couple of crisp/flow niceties.
   =========================================================================== */

/* The fork cards arrive from the outer margins on reveal — handled by the base
   `.fork .fork-card.r-child` transforms (§17) which resolve on `.in`. Nothing to
   add here; the one-shot IO sets `.in` on enter (or view() does on capable UAs). */

/* --- SET-PIECE FALLBACK (no native view() scroll-timelines) ---------------
   On browsers WITHOUT `animation-timeline: view()`, premium.js adds `.scene-in`
   to each set-piece ONCE when it enters the viewport, and these rules play a
   SINGLE transition to the resolved final frame (reveal-on-enter, NOT a scrub).
   Gated by @supports `not (...)` so capable browsers ignore them entirely and use
   the compositor timelines (§17). All transform/opacity/clip-path — compositor. */
@supports not (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    html.flow:not(.is-static) [data-reveal],
    html.flow:not(.is-static) .r-child{
      opacity: 0; transform: translateY(28px);
      transition: opacity .8s var(--ease-reveal), transform .8s var(--ease-reveal);
    }
    html.flow:not(.is-static) [data-reveal].in,
    html.flow:not(.is-static) .r-child.in{ opacity: 1; transform: none; }

    /* proof plates: FIX #3 — the seam opens once on enter by clipping the BEFORE
       photo AWAY (inset(0)→inset(0 100% 0 0)) so the RENDER is revealed, not
       hidden. The hairline rides the wipe edge left→right in lock-step. */
    html.flow:not(.is-static) .proof.scene-in .plate .plate-before{
      clip-path: inset(0 100% 0 0);
      transition: clip-path 1.1s var(--ease-reveal);
    }
    html.flow:not(.is-static) .proof.scene-in .plate .plate-seam{
      transform: translateX(100%);
      transition: transform 1.1s var(--ease-reveal);
    }

    /* flywheel: the one-shot .scene-in reveal is defined UNIVERSALLY in §17c below
       (outside this @supports gate) so it runs on view()-capable browsers too —
       premium.js always carries the flywheel via .scene-in, never a scroll scrub. */

    /* proposal fold: the paper is the resolved default (fix #9); under flow the
       fold replays as a one-shot reveal-on-enter from a closed start. */
    html.flow:not(.is-static) .proposal-fold .pf-paper{ clip-path: inset(100% 0 0 0); }
    html.flow:not(.is-static) .proposal-fold.scene-in .pf-paper{
      clip-path: inset(0 0 0 0); transition: clip-path 1s var(--ease-reveal);
    }
    html.flow:not(.is-static) .proposal-fold .pf-logo{ opacity: 0; transform: translateY(8px); }
    html.flow:not(.is-static) .proposal-fold.scene-in .pf-logo{
      opacity: 1; transform: none;
      transition: opacity .5s var(--ease-reveal) .8s, transform .5s var(--ease-expo) .8s;
    }
  }
}

/* ===========================================================================
   17c. THE FLYWHEEL — ONE-SHOT REVEAL (universal, all flow browsers).
   ---------------------------------------------------------------------------
   The founder's "glitching on real hardware" lived in the old view()-scrubbed
   card-morph: the card slid translateX(-9%→9%) every scroll frame and the two
   faces co-painted in a muddy mid-pass crossfade. DELETED. The flywheel is now a
   clean ONE-SHOT reveal that resolves "one artifact, two sides" with a SINGLE set
   of CSS transitions when the section enters — premium.js (§5 scenes) adds
   `.scene-in` ONCE via IntersectionObserver, on EVERY flow browser (it does not
   stand down for view() here). There is NO scroll scrub, NO layout read, NO
   translateX slide. The home face hard-cuts to the pro face (delays keep them from
   co-painting), the card re-skins light→dark inbox once, and the band darkens once.
   STATIC keeps the labelled two-up fallback (§18b / §19). This block lives OUTSIDE
   the @supports gates so it owns the flywheel uniformly.

   Authored START (the home / light frame) is the base flywheel CSS (§10): .pro
   face opacity 0, .flywheel-bg opacity 0, card on --panel, head ink. We only need
   the RESOLVED `.scene-in` end-state + its one-shot transitions here.
   =========================================================================== */
html.flow:not(.is-static) .flywheel .job-card{
  /* under flow, prime the transitions so the reveal eases (no instant snap) */
  transition: background .6s var(--ease) .42s, border-color .6s var(--ease) .42s,
              color .6s var(--ease) .42s, box-shadow .6s var(--ease) .42s,
              transform .9s var(--ease-reveal);
}
html.flow:not(.is-static) .flywheel.scene-in .job-card{
  /* the card re-skins to the dark pro inbox once (no slide — geometry conserved) */
  background: #13211b; border-color: rgba(110,139,123,0.16); color: var(--limestone);
  box-shadow: 0 0 0 1px rgba(110,139,123,0.12), inset 0 1px 0 rgba(247,244,238,0.04);
}
/* faces hard-cut: home fades fully out, THEN pro fades in — they never co-paint as
   a garbled overlap (the old midpoint glitch). visibility hard-hides the home text
   so it can't ghost under the pro face. */
html.flow:not(.is-static) .flywheel.scene-in .job-card .job-face.home{
  opacity: 0; visibility: hidden;
  transition: opacity .42s var(--ease-reveal) .35s, visibility 0s linear .77s;
}
html.flow:not(.is-static) .flywheel.scene-in .job-card .job-face.pro{
  opacity: 1; transition: opacity .5s var(--ease-reveal) .55s;
}
/* the band darkens once; the heads flip ink → limestone in lock-step. */
html.flow:not(.is-static) .flywheel.scene-in .flywheel-bg{
  opacity: 1; transition: opacity .9s var(--ease-reveal);
}
html.flow:not(.is-static) .flywheel.scene-in .section-head h2{
  color: var(--limestone); transition: color .8s var(--ease-reveal);
}
html.flow:not(.is-static) .flywheel.scene-in .section-head p{
  color: var(--limestone-2); transition: color .8s var(--ease-reveal);
}
html.flow:not(.is-static) .flywheel.scene-in .section-head .eyebrow{
  color: var(--limestone-2); transition: color .8s var(--ease-reveal);
}

/* ===========================================================================
   18. THE ONE REDUCED-MOTION BLOCK — disables transforms/animations/auto-play,
   shows every final state, keeps the slider operable. (Exactly one such block.)
   =========================================================================== */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  html{ scroll-behavior: auto; }

  /* reveals snap to final */
  [data-reveal], [data-reveal].in, .r-child, .r-child.in,
  .fork .fork-card.r-child:first-child, .fork .fork-card.r-child:last-child{
    opacity: 1 !important; transform: none !important;
  }
  /* connectors / rules / underline sit drawn */
  .journey::before{ transform: scaleX(1) !important; }
  .pricing-rule{ transform: scaleY(1) !important; }
  .draw-underline em::after{ transform: scaleX(1) !important; }
  /* the flywheel notification + Act III mini-strip rest resolved */
  .flywheel-notify{ opacity: 1 !important; transform: none !important; }
  .qt-plate .plate-stage .plate-before{ clip-path: inset(0 100% 0 0) !important; } /* show after */
  /* kill auto-play: pulse, scan-line, glow */
  .price-pill .dot, .studio-status .dot,
  .build-pricepill .dot, .plate-cap .price .dot{ animation: none !important; }
  .studio-stage.is-rendering::after{ display: none !important; }
  .final-cta .glow::before{ opacity: 1 !important; }
  .final-cta h2 em{ color: var(--accent) !important; }
  /* the italic underline word still reads */
  .gallery-tile img{ transition: none !important; }

  /* --- IMMERSIVE HERO BUILD: collapse to the dusk poster + resolved price band ---
     no canvas paint loop, no autoplay, no camera push-in, no parallax. The dusk
     poster fills the stage, the price pill sits resolved, the teaching intent
     chips fade, the words stay legible over the scrim. */
  .hero-build .build-camera{ transform: none !important; }
  .hero-build .build-plane-back{ transform: scale(1.12) !important; }
  .build-hud .build-progress-bar{ transform: scaleX(1) !important; }
  .build-pricepill{ transform: none !important; }
  .build-intent{ opacity: 0 !important; }
  /* callouts + BOM rest resolved on the dusk poster (the richer authored still) */
  .callout{ opacity: 1 !important; transform: translate(0, -50%) !important; }
  .build-bom{ opacity: 1 !important; transform: none !important; }
  /* on STATIC the assembly never runs — the dusk poster carries the finished frame
     with every component in place; hide the build-layer stack entirely (no half-
     built frames peeking) and keep the poster fully visible. No replay control. */
  .hero-build .build-layers{ display: none !important; }
  .hero-build .build-frame{ opacity: 1 !important; }
  .build-replay{ display: none !important; }

  /* --- PROOF WALK: vertical stack of resolved AFTER plates (a gallery) ------- */
  .proof-track{ display: none !important; }
  .proof-fallback{ display: grid !important; gap: 20px; }
  .proof-fallback .plate-stage .plate-before{ clip-path: inset(0 100% 0 0) !important; } /* show after */
  .proof-fallback .plate-seam{ display: none !important; }

  /* --- FLYWHEEL: the labelled two-up + arrow (geometry shown side by side) --- */
  .flywheel-bg{ opacity: 0 !important; }      /* stay light; the two-up reads it */
  .flywheel-flow .job-card{ display: none !important; }
  .flywheel-stage{ display: none !important; }
  .flywheel-seam{ display: none !important; }
  .flywheel-fallback{ display: grid !important; }

  /* --- PROPOSAL FOLD: the finished branded proposal, stamped --------------- */
  .pf-paper{ clip-path: inset(0 0 0 0) !important; }
  .pf-logo{ opacity: 1 !important; transform: none !important; }
}

/* ===========================================================================
   19. MOBILE / COARSE-POINTER COLLAPSE — the proof walk + flywheel ALSO need
   their static stacks on real phones (the horizontal track would overflow
   ~250vw and clip). Mirrors the reduced-motion collapse for width/pointer.
   The JS guard (width<860 || coarse) shows the same static states, so the two
   stay in lock-step.
   =========================================================================== */
@media (max-width: 860px), (pointer: coarse){
  /* every set-piece shows a complete resolved final frame — the most-polished
     STATIC editorial cut (§VIII). All flow motion is .flow-gated so it's already
     off here; we reveal the authored fallbacks + neutralise the depth transforms.
     The immersive hero collapses to the dusk poster + words; no canvas, no push-in. */
  .hero-build .build-camera{ transform: none; }
  .hero-build .build-plane-back{ transform: scale(1.12); }
  .build-hud .build-progress-bar{ transform: scaleX(1); }
  .build-intent{ opacity: 0; }
  /* coarse/narrow = STATIC: the assembly never runs — hide the build-layer stack
     and keep the dusk poster fully visible. No replay control. */
  .hero-build .build-layers{ display: none; }
  .hero-build .build-frame{ opacity: 1; }
  .build-replay{ display: none; }

  .proof-track{ display: none; }
  .proof-fallback{ display: grid; gap: 20px; }
  .proof-fallback .plate-stage .plate-before{ clip-path: inset(0 100% 0 0); } /* after */
  .proof-fallback .plate-seam{ display: none; }

  .flywheel-bg{ opacity: 0; }
  .flywheel-flow .job-card{ display: none; }
  .flywheel-stage{ display: none; }
  .flywheel-seam{ display: none; }
  .flywheel-fallback{ display: grid; }

  /* the live proposal fold is carried by its resolved default; hard-pin it open */
  .pf-paper{ clip-path: inset(0 0 0 0) !important; }
  .pf-logo{ opacity: 1 !important; transform: none !important; }

  /* the proof-walk + flywheel heads stay legible on their light/dark fallback */
  .flywheel-fallback{ gap: 18px; }
}

/* portrait-friendly crops on phones */
@media (max-width: 640px){
  .pro-band{ border-radius: 20px; }
}

/* the hero must sit high on the phone fold — don't burn it on top padding */
@media (max-width: 860px){
  .studio:not(.hero-build){ padding: 84px 0 32px; }
  /* the immersive hero on phones: a strong but not over-tall full-bleed stage,
     words first, the dusk poster behind. Still flows; no canvas, no push-in. */
  .hero-build .build-stage{ min-height: clamp(520px, 84svh, 760px); }
  .hero-build .build-overlay{ padding-top: clamp(84px,12vh,120px); }
  /* FIX #6: "See your yard redesigned." overflowed at 360–414px (clamp floor 2rem
     + text-wrap:balance refused to break the long word). Lower the floor + allow
     wrapping/hyphenation so it always fits the narrow rail. */
  .hero-build .build-words h1{
    font-size: clamp(1.75rem, 6.4vw, 2.75rem); max-width: 100%;
    text-wrap: balance; overflow-wrap: break-word; word-break: normal; hyphens: auto;
  }
  .hero-build .build-intent{ display: none; }   /* keep the phone hero uncluttered */
  /* the 6 pinned callout labels would crowd a ~360px hero — drop them on phones;
     the dusk poster + BOM strip + words carry the richer still. The BOM stays. */
  .build-callouts{ display: none; }
  .hero-build .build-scrim{
    background:
      linear-gradient(0deg, rgba(10,18,14,0.86) 0%, rgba(10,18,14,0.5) 46%, rgba(10,18,14,0.18) 100%);
  }
}

/* drop the inline pseudo-separator dots once the trust rows wrap narrow */
@media (max-width: 560px){
  .studio-trust span + span::before{ display: none; }

  /* FIX #7: the price pill clipped to "$18…" at the right gutter on narrow phones
     (no <=560px rule). Shrink padding/font + drop the "Live estimate · " label to
     just the band so the full range clears the gutter. The pulsing dot is kept. */
  .build-priceblock{ top: 12px; right: 12px; max-width: calc(100vw - 24px); }
  .build-pricepill{
    padding: 7px 11px; font-size: 11px; gap: 6px;
    max-width: calc(100vw - 24px);
  }
  .build-pricepill .pp-label{ display: none; }
  .build-pricepill .pp-val{ white-space: nowrap; }
  /* the BOM micro-strip would wrap to several rows on a narrow phone — drop it; the
     resolved price band carries the costed-build signal on small screens. */
  .build-bom{ display: none; }
}

/* ===========================================================================
   20. HERO TEXT FADE / SCALE ON LOAD (IMMERSIVE-BRIEF-V2 §2) — the founder's named
   hero beat. The .build-words children (eyebrow / h1 / lede / CTAs / trust) fade
   opacity 0→1 + scale 0.985→1 + translateY 12→0, staggered ~80ms, via a CSS
   @keyframes that runs ONCE on LOAD (animation, not scroll-linked) — pure
   transform/opacity on the compositor, and it terminates. Gated to html.flow so
   STATIC / reduced-motion show the words immediately. The component-ASSEMBLY hero
   background is untouched; this only adds the text entrance.
   =========================================================================== */
html.flow .hero-build .build-words > *{
  animation: hero-words-in .7s var(--ease-expo) both;
}
html.flow .hero-build .build-words > .eyebrow{ animation-delay: .05s; }
html.flow .hero-build .build-words > h1{ animation-delay: .13s; }
html.flow .hero-build .build-words > .lede{ animation-delay: .21s; }
html.flow .hero-build .build-words > .studio-ctas{ animation-delay: .29s; }
html.flow .hero-build .build-words > .studio-trust{ animation-delay: .37s; }
@keyframes hero-words-in{
  from{ opacity: 0; transform: translateY(12px) scale(0.985); }
  to{ opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------------------
   .ba-grid — a simple 2-up before/after pair grid, used by the AI-design SEO
   sub-page (templates/ai_garden_design.html). Kept lean; the homepage's old
   interactive drag-portfolio (.ba-demo slider) was removed.
   --------------------------------------------------------------------------- */
.ba-grid{
  display: grid; grid-template-columns: 1fr; gap: clamp(20px,2.4vw,32px);
  max-width: 1180px; margin: 0 auto;
}
@media (min-width: 880px){ .ba-grid{ grid-template-columns: 1fr 1fr; } }

/* ===========================================================================
   25. STATIC / REDUCED-MOTION FLOOR for the immersive hero text entrance.
   =========================================================================== */
@media (prefers-reduced-motion: reduce){
  .hero-build .build-words > *{ animation: none !important; opacity: 1 !important; transform: none !important; }
}
@media (max-width: 860px), (pointer: coarse){
  /* the AI-design sub-page pairs go single-column on phones */
  .ba-grid{ grid-template-columns: 1fr; }
}
