/* FieldMatrix landing page · hero (E1). SPEC 4.1 "Every field. Every morning."
   Layers inside the sticky frame (the .scene-sticky wrapper is in the markup, so FM.scene never re-parents the
   copy and its first-paint animation never restarts):
     .hero-stage (z stage, aria-hidden)
       .hero-flat      the poster <picture> (+ phone field lines, spotlight vignette); phones: slides up, pushes in
       .hero-foot      2D: the stage's bottom edge fading into night as the frame releases
       .hero-canvas    the WebGL county (desktop/tablet), fades in over the poster
       .hero-calls     4 estimate callouts pinned to square-mile sections (beat 3)
       .hero-mark      2D ring + leader for the demo field (phones, static, no-WebGL)
       .hero-you       the demo-field label card (lime rule)
       .hero-note      "Estimates from public records", placed under the lowest callout
     .sec-static (z overlay)  eyebrow, H1, subline, buttons, centered in the upper third over a soft scrim
   Only transform and opacity animate. No shadows, no filters. */

#hero{position:relative;min-height:100vh;min-height:100svh;background:var(--night);--hero-shift:0px;padding:0}   /* no .sec padding before FM.scene: the copy never jumps at boot */
/* before FM.scene runs (and with no JavaScript) the wrapper is a plain full-height box */
#hero:not(.scene)>.scene-sticky{position:relative;height:auto;min-height:100vh;min-height:100svh;overflow:clip}

/* ---- poster: paints on first frame, sits under the canvas ---- */
#hero .hero-poster{position:absolute;inset:0;display:block;margin:0}
#hero .hero-poster img{position:absolute;inset:0;width:100%;height:100%;max-width:none;object-fit:cover;object-position:50% 50%}

/* ---- copy: centered in the upper third ---- */
#hero .sec-static{position:relative;z-index:var(--z-overlay);align-items:center;text-align:center;gap:0;
  padding-top:15vh;padding-top:15svh;padding-bottom:48px}
/* the scrim runs through the button row: the photo never sits bare behind the words or the ghost button */
#hero .sec-static::before{content:"";position:absolute;z-index:-1;pointer-events:none;
  left:50%;top:calc(15svh - 48px);bottom:-96px;width:min(1240px,100vw);transform:translateX(-50%);
  background:radial-gradient(closest-side,rgba(10,13,11,.8),rgba(10,13,11,.56) 62%,rgba(10,13,11,0))}
#hero [data-k=eyebrow]{color:var(--ink-2);margin-bottom:24px;max-width:none}
#hero h1{margin:0;color:var(--ink)}
#hero h1 .l2{color:var(--ink-2)}
/* measures in em, never ch: ch is the width of "0", which differs between the webfont and its metric-matched
   fallback, so a ch measure re-wraps the subline when the font swaps in (34.6em = the old 52ch in Instrument Sans) */
#hero [data-k=sub]{max-width:34.6em;margin:28px auto 0;color:var(--ink-2)}
#hero [data-k=cta]{margin-top:36px;justify-content:center}
/* the ghost button keeps a solid face on the hero, so it never reads as loose text on the photo */
#hero .btn-ghost{background:rgba(10,13,11,.72);border-color:var(--ink-3)}
#hero .btn-ghost:hover{background:rgba(10,13,11,.86);border-color:var(--ink-2)}
#hero [data-k=you]{margin-top:28px;color:var(--ink-2)}
html.js #hero [data-k=you]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:clip;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ---- load reveal: CSS keyframes from the first paint, no JavaScript gate (the largest paint never waits for
   boot). H1 lines rise through their .line mask; eyebrow, subline and buttons rise 16px. Transform only, never
   opacity: Chrome does not count a paint at opacity 0 as the largest paint, so a fade-up would push LCP past boot
   (QA r2: 6 s in Lighthouse mobile). ---- */
@keyframes hero-rise{from{transform:translate3d(0,112%,0)}to{transform:none}}
@keyframes hero-up{from{transform:translate3d(0,16px,0)}to{transform:none}}
html.js:not(.reduced) #hero h1 .line>span{animation:hero-rise .9s cubic-bezier(.16,1,.3,1) .12s both}
html.js:not(.reduced) #hero h1 .line+.line>span{animation-delay:.2s}
html.js:not(.reduced) #hero [data-k=eyebrow]{animation:hero-up .8s cubic-bezier(.16,1,.3,1) .28s both}
html.js:not(.reduced) #hero [data-k=sub]{animation:hero-up .8s cubic-bezier(.16,1,.3,1) .38s both}
html.js:not(.reduced) #hero [data-k=cta]{animation:hero-up .8s cubic-bezier(.16,1,.3,1) .48s both}

/* ---- stage ---- */
#hero .hero-stage{position:absolute;inset:0;z-index:var(--z-stage);overflow:clip;pointer-events:none;background:var(--night)}
#hero .hero-flat{position:absolute;inset:0;bottom:calc(-1 * var(--hero-shift));transform-origin:50% 50%}
#hero .hero-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;opacity:0}
#hero .hero-calls{position:absolute;inset:0}

/* phone: the field lines are scanned in left to right (clip wrapper -100% -> 0, inner counter-translate) */
#hero .hero-lines{position:absolute;inset:0;overflow:clip;transform:translate3d(-100%,0,0)}
#hero .hero-lines-in{position:absolute;inset:0;transform:translate3d(100%,0,0)}
#hero .hero-lines-in img{position:absolute;inset:0;width:100%;height:100%;max-width:none;object-fit:cover;opacity:.45}
#hero .hero-lines .scan{left:auto;right:0;opacity:0}
/* 2D spotlight: the ground darkens around the demo field (the gradient is set in JS at the field's point) */
#hero .hero-vig{position:absolute;inset:0;opacity:0}
/* 2D: the stage's bottom edge fades into night as the frame dims and releases (opacity set in hero.js draw2D) */
#hero .hero-foot{position:absolute;left:0;right:0;bottom:0;height:22%;opacity:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(10,13,11,0),var(--night))}

/* ---- cards: night-2 at 94%, 1px hair-2, a 2px lime rule on the left, no shadow ---- */
#hero .hcard{position:absolute;left:0;top:0;margin:0;background:rgba(17,22,18,.94);border:1px solid var(--hair-2);
  border-radius:2px 12px 12px 2px;text-align:left}
#hero .hcard::before{content:"";position:absolute;left:-1px;top:-1px;bottom:-1px;width:2px;background:var(--lime);border-radius:2px 0 0 2px}

/* callouts: a lime 8px pin, a 1px leader, the card above the pin. Below the pin (near the top of the screen) is a
   transform: the card moves down 2 gaps + its height (JS), the leader mirrors about the pin. No top/bottom switch. */
#hero .hero-call{position:absolute;left:0;top:0;width:0;height:0;opacity:0}
#hero .hero-call .pin{position:absolute;left:-4px;top:-4px;width:8px;height:8px;border-radius:50%;background:var(--lime)}
#hero .hero-call .lead{position:absolute;left:0;bottom:4px;width:1px;height:40px;background:rgba(216,255,122,.72);transform-origin:0 44px}
#hero .hero-call.is-below .lead{transform:scaleY(-1)}
#hero .hero-call .hcard{top:auto;bottom:44px;left:-1px;padding:11px 16px 11px 15px}
#hero .hcall-rows{display:grid;grid-template-columns:auto auto;column-gap:16px;row-gap:5px;margin:0;white-space:nowrap}
#hero .hcall-rows dt{font:400 .875rem/1.35 var(--f-sans);color:var(--ink-2)}
#hero .hcall-rows dd{margin:0;font:500 .875rem/1.35 var(--f-mono);color:var(--ink);font-variant-numeric:tabular-nums}

/* the demo-field label */
#hero .hero-you{padding:15px 20px 17px 19px;width:max-content;max-width:min(460px,calc(100vw - 32px));opacity:0}
#hero .hy-name{color:var(--amber)}
#hero .hy-season{white-space:nowrap}
/* 3D: the label's leader (2px amber, flush with the card's bottom edge, scaled to length in JS) and the 6px dot on
   the outline's top edge, above the field's middle */
#hero .hero-ylead{position:absolute;left:0;top:0;width:2px;height:1px;background:var(--amber);transform-origin:0 0;opacity:0}
#hero .hero-ydot{position:absolute;left:0;top:0;width:6px;height:6px;border-radius:50%;background:var(--amber);opacity:0}
#hero .hy-pct{margin-top:10px;color:var(--ink);white-space:nowrap}
#hero .hy-line{margin-top:8px;color:var(--ink-2);max-width:20em}

/* 2D ring + leader for the demo field (amber = your field). The leader grows out of the ring; below the ring it is
   the same element mirrored by a transform (JS), never a top/bottom switch. */
#hero .hero-mark{position:absolute;left:0;top:0;width:0;height:0;opacity:0}
#hero .hero-mark .ring{position:absolute;left:-15px;top:-15px;width:30px;height:30px;border-radius:50%;border:2px solid var(--amber)}
#hero .hero-mark .dot{position:absolute;left:-3px;top:-3px;width:6px;height:6px;border-radius:50%;background:var(--amber)}
#hero .hero-mark .lead{position:absolute;left:-.5px;bottom:15px;width:1px;height:var(--lead,52px);background:var(--amber);transform-origin:50% 100%}

/* the estimates note: right under the lowest callout card (positioned per frame by transform) */
#hero .hero-note{position:absolute;left:0;top:0;margin:0;opacity:0;padding:6px 10px;border-radius:8px;white-space:nowrap;
  background:rgba(10,13,11,.9);border:1px solid var(--hair-2);color:var(--ink-2)}
#hero .hero-caps{z-index:var(--z-caption)}

/* ---- reduced motion / ?static=1: poster, copy and the label at its poster point ---- */
#hero.is-static .hero-canvas,#hero.is-static .hero-calls,#hero.is-static .hero-note{display:none}
#hero.is-static .hero-mark,#hero.is-static .hero-you{opacity:1}
/* The photo's box is set here, from the first paint (html.reduced is set before it), and never moves after it
   (QA final: it used to drop and shrink once hero.js had laid it out, CLS 0.14 at 390x844). The wrapper is a grid:
   row 1 holds the copy, row 2 the photo, pulled up by the demo field's height in it plus 16px, so the field sits just
   under the buttons (the ring then clears them by 16px). Row 1 is at least as tall as the lift that ends the label
   card 20px above the fold (at most 7% of the screen), so where the field is already well below the buttons (wide
   screens) the photo sits lifted by that much instead. hero.js only measures the box and puts the ring and the card
   on it. Before init the box is the <picture>; after, .hero-flat holds the picture in the same grid slot.
     --fy    the field's y in the photo's box (cover crop). Desktop poster 1600x900, data-you y 82.3; phones
             terrain-m.jpg 900x1600, facts.hero.phoneLabel y 38
     --lift  100% - (15 ring + 24 leader + 20 margin) - card height - field y, clamped to [-7%, 0] */
html.reduced #hero{--u:1vh;--hy-h:142px;
  --fy:max(calc(82.3 * var(--u)),calc(50 * var(--u) + 18.17vw));
  --lift:clamp(calc(-7 * var(--u)),calc(100 * var(--u) - 59px - var(--hy-h) - var(--fy)),0px)}
@supports (height:1svh){html.reduced #hero{--u:1svh}}
html.reduced #hero>.scene-sticky{display:grid;grid-template-columns:minmax(0,1fr);align-content:start;
  grid-template-rows:minmax(calc(var(--lift) + 16px + var(--fy)),auto) auto}
html.reduced #hero>.scene-sticky>.sec-static{grid-area:1/1;align-self:start;width:100%}
html.reduced #hero>.scene-sticky>.hero-poster,
html.reduced #hero>.scene-sticky>.hero-flat{grid-area:2/1;position:relative;inset:auto;z-index:var(--z-stage);
  height:calc(100 * var(--u));margin:calc(-16px - var(--fy)) 0 0}
/* the photo's bottom edge fades into night, so a lifted or lowered photo never shows a straight cut */
html.reduced #hero>.scene-sticky>.hero-poster::after,
#hero.is-static .hero-flat::after{content:"";position:absolute;left:0;right:0;bottom:0;height:18%;pointer-events:none;
  background:linear-gradient(180deg,rgba(10,13,11,0),var(--night))}
/* portrait screens: the desktop poster's centred crop would put the demo field past the right content edge */
@media (min-width:520px) and (max-aspect-ratio:6/5){
  html.reduced #hero .hero-poster img{object-position:75% 50%}
}
#hero .hero-mark.is-top,#hero .hero-you.is-top{z-index:calc(var(--z-overlay) + 1);pointer-events:none}

/* ---- capture mode (posters): ground only ---- */
html.capture #hero .sec-static,html.capture #hero .hero-calls,html.capture #hero .hero-you,
html.capture #hero .hero-note,html.capture #hero .hero-mark,html.capture #hero .hero-caps,
html.capture #hero .hero-ylead,html.capture #hero .hero-ydot{display:none}

@media (max-width:759px){
  #hero .sec-static{padding-top:14vh;padding-top:14svh}
  #hero [data-k=eyebrow]{margin-bottom:18px;max-width:26em}
  #hero [data-k=sub]{margin-top:20px}
  #hero [data-k=cta]{margin-top:28px}
  #hero .hero-you{padding:13px 16px 15px 15px}
  #hero .hy-line{font-size:1rem}
  html.reduced #hero{--hy-h:128px}
}
@media (max-width:519px){
  /* phones: the photo is taller than the screen by the distance it slides up as the copy leaves (hero.js
     PHONE_SHIFT), so it fills from under the nav for the rest of the scene. Same box before and after boot. */
  html.js:not(.reduced) #hero{--hero-shift:16vh;--hero-shift:16svh}
  #hero>.scene-sticky>.hero-poster{bottom:calc(-1 * var(--hero-shift))}
  #hero .sec-static{padding-top:12vh;padding-top:12svh}
  /* the photo's bottom edge fades into night (it meets "What it is" there, dimmed, as the frame releases) */
  html.js:not(.reduced) #hero .hero-flat::after{content:"";position:absolute;left:0;right:0;bottom:0;height:14%;pointer-events:none;
    background:linear-gradient(180deg,rgba(10,13,11,0),var(--night))}
  /* the label's first line never wraps to a dangling separator: the season gets its own line */
  #hero .hy-sep{display:none}
  #hero .hy-season{display:block}
  /* reduced: the field's y in the portrait crop (terrain-m.jpg, cover; phoneLabel y 38) */
  html.reduced #hero{--fy:min(calc(38 * var(--u)),calc(50 * var(--u) - 21.3333vw))}
}
