/* ==========================================================================
   Impact Marketing KY — signature effects
   Two set-pieces, both driven by NATIVE CSS scroll timelines:
     1. .scene3d   — the homepage 3D depth dolly
     2. .teardown  — the process-page puzzle assembly (FCR homepage, real screens)

   Why native scroll timelines and not a rAF/GSAP loop:
     · transform/opacity animate on the COMPOSITOR — as smooth as the scroll,
       and it costs the main thread nothing (this is what protects O-024's
       mobile PageSpeed gate).
     · Zero JavaScript, so there is no hidden state a dead script can strand
       (Web Designer L-013 satisfied by construction, not by a failsafe timer).
     · Everything lives inside @supports + prefers-reduced-motion, so an
       unsupporting engine renders the finished, readable layout.
   Support: Chrome/Edge 115+, Safari 18+, Firefox 132+ (~84% mid-2026).
   Trap avoided throughout: `animation-timeline` is always declared AFTER the
   `animation` shorthand — the shorthand resets it to auto (MDN, 2026-07-27).
   ========================================================================== */

/* ==========================================================================
   1. THE HOMEPAGE 3D SCROLL
   Real perspective depth per L-015: each plane sits at a genuine translateZ(-d)
   inside a perspective:P viewport and is pre-scaled by (P+d)/P so it still
   fills the frame. Separation then comes from perspective itself — near things
   sweep, far things barely drift — instead of invented scroll speeds.
   P = 1000px. Planes at d = 1600 / 900 / 380 / 0.
   ========================================================================== */
.scene3d{position:relative;height:340vh}
@media (max-width:760px){
  .scene3d{height:260vh}
  /* 76px = the sticky call bar's footprint (base.css) */
  .scene3d__viewport{height:calc(100svh - 76px)}
}

.scene3d__viewport{
  position:sticky;top:0;height:100svh;
  /* On phones the sticky call bar overlays the bottom of the viewport, so the
     hero must stop short of it or the second CTA sits underneath it. */
  overflow:hidden;
  perspective:1000px;
  perspective-origin:50% 46%;
  background:radial-gradient(120% 90% at 50% 12%,#0d0d18 0%,var(--bg) 46%,var(--bg-deep) 100%);
}
.scene3d__depth{position:absolute;inset:0;transform-style:preserve-3d}
.scene3d__plane{
  position:absolute;inset:-10%;
  display:grid;place-items:center;
  transform-style:preserve-3d;
  will-change:transform;
}
/* Pre-scale = (P + d) / P, then overscale so a tilt/dolly never reveals an edge
   (L-015 trap 1: ~1.06 far, ~1.16 near). */
.scene3d__plane[data-z="far"]  {transform:translateZ(-1600px) scale(2.60)}  /* 2.6 = 2600/1000, ×1.0 */
.scene3d__plane[data-z="mid"]  {transform:translateZ(-900px)  scale(1.90)}
.scene3d__plane[data-z="near"] {transform:translateZ(-380px)  scale(1.38)}
.scene3d__plane[data-z="front"]{transform:translateZ(0)       scale(1.00)}

/* Ambient depth furniture — light only, never objects (L-011: AI/graphics
   make the air, never the thing being sold). */
.scene3d__stars{
  position:absolute;inset:0;
  background-image:
    radial-gradient(1.6px 1.6px at 18% 24%,rgba(0,191,255,.55),transparent 60%),
    radial-gradient(1.4px 1.4px at 74% 16%,rgba(106,90,205,.5),transparent 60%),
    radial-gradient(1.8px 1.8px at 42% 68%,rgba(255,255,255,.32),transparent 60%),
    radial-gradient(1.3px 1.3px at 88% 58%,rgba(0,191,255,.4),transparent 60%),
    radial-gradient(1.5px 1.5px at 8% 76%,rgba(106,90,205,.42),transparent 60%),
    radial-gradient(1.2px 1.2px at 60% 88%,rgba(255,255,255,.26),transparent 60%);
}
.scene3d__grid{
  position:absolute;inset:0;
  background-image:
    linear-gradient(rgba(0,191,255,.09) 1px,transparent 1px),
    linear-gradient(90deg,rgba(106,90,205,.09) 1px,transparent 1px);
  background-size:76px 76px;
  mask-image:radial-gradient(74% 58% at 50% 46%,#000 20%,transparent 78%);
  -webkit-mask-image:radial-gradient(74% 58% at 50% 46%,#000 20%,transparent 78%);
}
.scene3d__halo{
  position:absolute;inset:0;
  background:
    radial-gradient(38vmax 30vmax at 26% 30%,var(--glow-blue),transparent 70%),
    radial-gradient(40vmax 32vmax at 76% 66%,var(--glow-purple),transparent 70%);
  filter:blur(28px);
}

/* ---- Real work, floating at real depth --------------------------------
   These are live screenshots of sites Jeff actually built. They are the
   reason the hero is not abstract: the camera flies through his portfolio.
   Individual objects are NOT pre-scaled (that rule is for full-bleed planes) —
   perspective does the sizing, so apparent size = size × P/(P+d).
   ---------------------------------------------------------------------- */
.scene3d__screens{position:absolute;inset:0;transform-style:preserve-3d;pointer-events:none}
.scene3d__screen{
  position:absolute;
  border-radius:10px;overflow:hidden;
  border:1px solid rgba(255,255,255,.14);
  background:#0b0b12;
  box-shadow:0 30px 90px rgba(0,0,0,.6), 0 0 60px rgba(0,191,255,.07);
}
.scene3d__screen img{display:block;width:100%;height:auto}
/* A faint brand wash keeps them reading as part of the scene, not stickers
   pasted on it (L-011: grade a real asset into the room's light). */
.scene3d__screen::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(150deg,rgba(0,191,255,.10),rgba(106,90,205,.16));
  mix-blend-mode:overlay;
}
.scene3d__screen b{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  font-size:9px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;
  color:rgba(255,255,255,.6);background:linear-gradient(transparent,rgba(0,0,0,.88));
  padding:18px 9px 7px;text-align:center;
}
/* Readable at rest, then dim as they sweep past the camera so nothing ever
   competes with the headline at close range. */
.scene3d__screens{opacity:.78}
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    @keyframes screens-pass{
      0%{opacity:.78}
      60%{opacity:.9}
      100%{opacity:.5}
    }
    .scene3d__screens{
      animation:screens-pass linear both;
      animation-timeline:--dolly;
      animation-range:cover 0% cover 100%;
    }
  }
}

/* Placement rule: the screens FRAME the headline, they never cross it.
   Everything is pushed to the outer thirds and the centre column stays clear,
   because the answer above the fold outranks the effect (Jeff's law #2). */
/* Desktop-shaped tiles */
/* Tucked IN so the dark well over the copy partially veils their inner edges —
   they read as half-hidden in the murk at rest, then fly outward on scroll.
   translateX comes FIRST in each chain so the slide happens in world space,
   not in the tile's own rotated space. */
.scene3d__screen[data-s="fcr-desk"]{width:540px;left:2%;top:12%;transform:translateX(0) translateZ(-1180px) rotateY(26deg) rotateX(3deg)}
.scene3d__screen[data-s="fcr-svc"] {width:500px;right:0%;top:55%;transform:translateX(0) translateZ(-1050px) rotateY(-24deg) rotateX(-2deg)}
.scene3d__screen[data-s="cpp-desk"]{width:470px;right:3%;top:5%;transform:translateX(0) translateZ(-1420px) rotateY(-21deg)}
/* Phone-shaped tile, nearer so it sweeps past hardest */
.scene3d__screen[data-s="fcr-phone"]{width:200px;left:17%;top:53%;transform:translateX(0) translateZ(-540px) rotateY(20deg) rotateX(2deg)}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    /* Hold a beat, then accelerate outward — so the viewer registers the work
       sitting in the haze before it clears out of the way of the headline. */
    @keyframes fly-l1{0%,12%{transform:translateX(0) translateZ(-1180px) rotateY(26deg) rotateX(3deg)}
                      100%{transform:translateX(-760px) translateZ(-1180px) rotateY(26deg) rotateX(3deg)}}
    @keyframes fly-r1{0%,12%{transform:translateX(0) translateZ(-1050px) rotateY(-24deg) rotateX(-2deg)}
                      100%{transform:translateX(760px) translateZ(-1050px) rotateY(-24deg) rotateX(-2deg)}}
    @keyframes fly-r2{0%,12%{transform:translateX(0) translateZ(-1420px) rotateY(-21deg)}
                      100%{transform:translateX(820px) translateZ(-1420px) rotateY(-21deg)}}
    @keyframes fly-l2{0%,12%{transform:translateX(0) translateZ(-540px) rotateY(20deg) rotateX(2deg)}
                      100%{transform:translateX(-560px) translateZ(-540px) rotateY(20deg) rotateX(2deg)}}
    .scene3d__screen[data-s="fcr-desk"] {animation:fly-l1 ease-in both;animation-timeline:--dolly;animation-range:cover 0% cover 76%}
    .scene3d__screen[data-s="fcr-svc"]  {animation:fly-r1 ease-in both;animation-timeline:--dolly;animation-range:cover 0% cover 76%}
    .scene3d__screen[data-s="cpp-desk"] {animation:fly-r2 ease-in both;animation-timeline:--dolly;animation-range:cover 0% cover 80%}
    .scene3d__screen[data-s="fcr-phone"]{animation:fly-l2 ease-in both;animation-timeline:--dolly;animation-range:cover 0% cover 70%}
  }
}

@media (max-width:1240px){
  .scene3d__screen[data-s="fcr-desk"]{left:-14%}
  .scene3d__screen[data-s="fcr-svc"]{right:-16%}
  .scene3d__screen[data-s="cpp-desk"]{right:-12%}
  .scene3d__screen[data-s="fcr-phone"]{left:4%}
}
@media (max-width:1000px){
  .scene3d__screen[data-s="fcr-svc"],.scene3d__screen[data-s="cpp-desk"]{display:none}
  .scene3d__screen[data-s="fcr-desk"]{width:400px;left:-24%;top:8%}
  .scene3d__screen[data-s="fcr-phone"]{width:150px;left:-2%;top:62%}
}
/* On a phone the screens would fight the headline for the only column there is.
   The headline wins — Jeff's law #2 (the answer above the fold) outranks the effect. */
@media (max-width:720px){.scene3d__screens{display:none}}

/* Readability guard: the copy sits over a soft well so it never has to compete
   with a bright screenshot passing behind it. */
/* Screens now sit closer in, so the well behind the copy works harder. */
.scene3d__copy::before{
  content:"";position:absolute;inset:-10% -16%;z-index:-1;
  background:radial-gradient(64% 56% at 50% 50%,rgba(7,7,9,.97) 36%,rgba(7,7,9,.86) 62%,transparent 86%);
}

.scene3d__copy{
  position:relative;z-index:5;
  width:min(1000px,100% - (var(--gutter) * 2));
  margin-inline:auto;text-align:center;
  display:grid;place-content:center;height:100%;
}
.scene3d__copy h1{margin-bottom:1.1rem}
.scene3d__copy .lede{margin-inline:auto;text-align:center}
.scene3d__actions{display:flex;flex-wrap:wrap;gap:.85rem;justify-content:center;margin-top:2rem}

.scene3d__cue{
  position:absolute;left:50%;bottom:clamp(1.2rem,3vh,2.4rem);translate:-50% 0;
  z-index:6;display:flex;flex-direction:column;align-items:center;gap:.5rem;
  color:var(--muted);font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;font-weight:700;
}
.scene3d__cue i{display:block;width:1px;height:34px;background:linear-gradient(var(--blue),transparent)}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .scene3d{view-timeline-name:--dolly;view-timeline-axis:block}

    /* The camera moves forward through the layers. */
    @keyframes dolly{
      from{transform:translateZ(0)}
      to{transform:translateZ(760px)}
    }
    .scene3d__depth{
      animation:dolly linear both;
      animation-timeline:--dolly;
      animation-range:cover 0% cover 100%;
    }

    /* Copy holds, then hands off to the page. */
    @keyframes copy-out{
      0%,52%{opacity:1;transform:translateY(0) scale(1)}
      100%{opacity:0;transform:translateY(-42px) scale(.96)}
    }
    .scene3d__copy{
      animation:copy-out linear both;
      animation-timeline:--dolly;
      animation-range:cover 0% cover 82%;
    }
    @keyframes cue-out{0%,14%{opacity:1}34%,100%{opacity:0}}
    .scene3d__cue{
      animation:cue-out linear both;
      animation-timeline:--dolly;
      animation-range:cover 0% cover 40%;
    }
    /* Far layer brightens as the camera closes on it. */
    @keyframes haze{from{opacity:.55}to{opacity:1}}
    .scene3d__halo{
      animation:haze linear both;
      animation-timeline:--dolly;
      animation-range:cover 0% cover 100%;
    }
  }
}

/* ==========================================================================
   2. THE PROCESS TEARDOWN — the puzzle
   Each step explains one section of a real, live client homepage; that
   section's actual screenshot then slides in and SNAPS to the bottom of the
   step, like a piece locking into place. Eight pieces, one finished website.
   Every image is a real capture of fullconstructionroofing.com — never a
   mockup, never generated (L-011 / L-021: fabrication is the one thing that
   cannot butt-join to real work).
   ========================================================================== */
.teardown{position:relative}

.piece{
  position:relative;
  padding-block:clamp(2.6rem,2rem + 3vw,5rem);
  border-bottom:1px solid var(--line);
}
.piece:last-child{border-bottom:0}

.piece__num{
  font-size:clamp(2.6rem,2rem + 3vw,4.4rem);
  font-weight:900;line-height:.8;letter-spacing:-.04em;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  opacity:.92;margin-bottom:.7rem;
  filter:drop-shadow(0 0 22px rgba(0,191,255,.18));
}
.piece__head{max-width:var(--measure)}
.piece__why{
  margin-top:1rem;padding-left:1.1rem;
  border-left:2px solid var(--line-strong);
  color:var(--text-dim);max-width:var(--measure);
}
.piece__why b{color:var(--white);font-weight:700}

/* The snapping tile — framed like a real browser so it reads as a website,
   not a picture of one. */
.piece__frame{
  margin-top:clamp(1.4rem,1rem + 1.4vw,2.4rem);
  border-radius:12px;overflow:hidden;
  border:1px solid var(--line-strong);
  background:#0b0b12;
  box-shadow:0 26px 70px rgba(0,0,0,.55);
  transform-origin:50% 100%;
}
.piece__chrome{
  display:flex;align-items:center;gap:.4rem;
  padding:.6rem .85rem;background:#15151f;border-bottom:1px solid var(--line);
}
.piece__chrome i{width:9px;height:9px;border-radius:50%;background:#2f2f3d;flex:none}
.piece__chrome span{
  margin-left:.5rem;font-size:.68rem;color:var(--muted);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.piece__frame img{width:100%;display:block}

/* Progress rail — the puzzle filling in */
.teardown__rail{
  position:sticky;top:calc(68px + .8rem);z-index:5;
  display:flex;gap:5px;padding:.55rem .8rem;margin-bottom:1.5rem;
  background:rgba(7,7,9,.8);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  border:1px solid var(--line);border-radius:999px;width:fit-content;
}
.teardown__rail b{
  display:block;width:22px;height:4px;border-radius:2px;background:var(--line-strong);
  transition:background-color .3s var(--ease);
}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    /* The snap: comes in low and slightly tipped, overshoots a hair, locks.
       Range ends at `cover 46%` so the piece is SEATED while the reader is
       still on the step — the lock is the payoff, not something they scroll past. */
    @keyframes snap-in{
      0%{opacity:0;transform:translateY(66px) rotateX(9deg) scale(.955)}
      72%{opacity:1;transform:translateY(-7px) rotateX(0deg) scale(1.006)}
      100%{opacity:1;transform:translateY(0) rotateX(0deg) scale(1)}
    }
    .piece__frame{
      animation:snap-in linear both;
      animation-timeline:view();
      animation-range:entry 14% cover 46%;
    }
    @keyframes lock{to{background-color:var(--blue)}}
    .teardown__rail b{
      animation:lock linear both;
      animation-timeline:--teardown-progress;
    }
  }
}

/* ==========================================================================
   Work / gallery
   ========================================================================== */
.work-card{
  display:grid;gap:0;overflow:hidden;padding:0;
  grid-template-columns:1fr;
}
@media (min-width:860px){
  /* align-items:center, NOT stretch — stretching made the shot column match the
     text height, so object-fit:cover cropped the hero's sides away. */
  .work-card{grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr);align-items:center}
}
.work-card__shot{
  position:relative;overflow:hidden;background:#0b0b12;
  aspect-ratio:900/620;      /* the capture's own ratio, so nothing is cropped */
  min-height:0;
}
.work-card__shot img{width:100%;height:100%;object-fit:cover;object-position:top center;transition:transform .6s var(--ease)}
.work-card:hover .work-card__shot img{transform:scale(1.03)}
@media (prefers-reduced-motion:reduce){.work-card__shot img{transition:none}.work-card:hover .work-card__shot img{transform:none}}
.work-card__body{padding:clamp(1.5rem,1.2rem + 1.2vw,2.4rem);display:flex;flex-direction:column;gap:.9rem;justify-content:center}
.work-card__tag{
  display:inline-flex;align-items:center;gap:.45rem;align-self:flex-start;
  font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;font-weight:700;
  padding:.4rem .75rem;border-radius:999px;
  border:1px solid var(--line-strong);color:var(--text-dim);background:var(--card);
}
.work-card__tag.is-client{color:var(--blue);border-color:rgba(0,191,255,.35)}
.work-card__meta{display:flex;flex-wrap:wrap;gap:.5rem 1.4rem;font-size:var(--step--1);color:var(--muted);margin-top:.2rem}
.work-card__meta b{color:var(--white);font-weight:700}

/* ==========================================================================
   Blog
   ========================================================================== */
.post-card{display:flex;flex-direction:column;gap:.7rem;text-decoration:none;height:100%}
.post-card__kicker{font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;font-weight:700;color:var(--blue)}
.post-card h3{font-size:var(--step-1);transition:color .2s var(--ease)}
.post-card:hover h3{color:var(--blue)}
.post-card p{color:var(--text-dim);font-size:var(--step--1);line-height:1.6}
.post-card__more{margin-top:auto;padding-top:.6rem;font-weight:700;color:var(--white);font-size:.9rem}

.prose{max-width:72ch}
.prose > * + *{margin-top:1.15em}
.prose h2{font-size:var(--step-2);margin-top:2.2em}
.prose h3{font-size:var(--step-1);margin-top:1.8em}
.prose p,.prose li{color:var(--text-dim);line-height:1.75}
.prose strong{color:var(--white)}
.prose ul,.prose ol{padding-left:1.3em;display:grid;gap:.5em}
.prose ul{list-style:disc}
.prose ol{list-style:decimal}
.prose a{color:var(--blue);text-decoration:underline;text-underline-offset:3px}
.prose blockquote{
  border-left:3px solid var(--blue);padding:.3em 0 .3em 1.2em;
  color:var(--white);font-size:var(--step-1);line-height:1.5;font-weight:500;
}
.callout{
  background:var(--card);border:1px solid var(--line);border-left:3px solid var(--blue);
  border-radius:0 var(--radius) var(--radius) 0;padding:1.2rem 1.4rem;
}
.callout p{color:var(--text-dim);margin:0}
.callout b{color:var(--white)}

/* ==========================================================================
   3. THE PULL-BACK — from the website, out to a phone in somebody's hand
   Method is L-022, not L-021: the generated plate supplies the ENVIRONMENT
   (hand, phone body, workshop bokeh, brand rim light) and the real website is
   composited into the measured screen quad as its own layer. Nothing about
   Jeff's site is ever fabricated — the model never sees it.

   Screen quad measured from the plate (2026-07-27):
     left 43.0959%  top 11.4583%  width 14.8983%  height 60.5469%
     → screen centre = 50.545%, 41.732%   (the pivot everything hangs on)

   The stage is translated so the screen centre sits at the viewport centre,
   and transform-origin is that SAME point — so the screen stays pinned dead
   centre through the whole zoom instead of drifting.
   ========================================================================== */
.zoom{position:relative;height:300vh}
.zoom__viewport{
  position:sticky;top:0;height:100svh;overflow:hidden;
  /* The hero's viewport gradient is anchored at its TOP, so its BOTTOM edge is
     --bg-deep. Butting another top-anchored gradient under it puts dark against
     light and draws a line. This one opens at --bg-deep and eases in. */
  background:
    linear-gradient(180deg,var(--bg-deep) 0%,rgba(5,5,5,0) 34%),
    radial-gradient(120% 90% at 50% 34%,#0d0d18 0%,var(--bg) 52%,var(--bg-deep) 100%);
}
/* A grid line from the depth planes lands exactly on the top edge and draws a
   4px cyan rule across the join. Masking the planes can't remove it — a mask on
   a preserve-3d element forces flattening and gets dropped. So the join is
   capped explicitly, in the hero's own bottom colour. */
.zoom__viewport::before{
  content:"";position:absolute;top:0;left:0;right:0;height:44px;z-index:5;
  background:linear-gradient(180deg,var(--bg) 0%,var(--bg) 22%,rgba(7,7,9,0) 100%);
  pointer-events:none;
}

/* The opening frame: a full-bleed capture of the desktop hero, so the section
   begins as an unbroken continuation of the site. No phone, no hand, no plate.
   It shrinks and dissolves as the phone composite rises underneath it — the
   crossfade happens mid-motion, which is what hides the desktop->mobile swap. */
.zoom__intro{
  position:absolute;inset:0;z-index:2;
  background:radial-gradient(120% 90% at 50% 34%,#0d0d18 0%,var(--bg) 52%,var(--bg-deep) 100%);
  will-change:opacity;
}

.zoom__stage{
  position:absolute;left:50%;top:50%;
  width:max(104vw,186vh);            /* plate aspect 1.7917; a little over so rounding never letterboxes */
  translate:-50.545% -41.732%;
  transform-origin:50.545% 41.732%;
  transform:scale(1.62);
  opacity:0;                         /* hidden until the intro dissolves into it */
  will-change:transform,opacity,filter;
}
.zoom__plate{display:block;width:100%;height:auto}
.zoom__screen{
  position:absolute;
  left:43.0959%;top:11.4583%;width:14.8983%;height:60.5469%;
  overflow:hidden;
  border-radius:1.4%/0.66%;
}
.zoom__screen img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block}
.zoom__screen{background:#05050a}   /* the phone shows a dark screen before it lights up */
/* Grade the real screenshot into the plate's light or it reads as a sticker
   pasted on rather than a lit screen (L-011). */
.zoom__screen::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(160deg,rgba(120,190,255,.14),rgba(106,90,205,.10) 60%,rgba(0,0,0,.16));
  mix-blend-mode:soft-light;
}

.zoom__copy{
  position:absolute;left:50%;bottom:clamp(2rem,7vh,5rem);translate:-50% 0;
  width:min(760px,100% - (var(--gutter) * 2));
  text-align:center;z-index:3;opacity:0;
}
.zoom__copy::before{
  content:"";position:absolute;inset:-45% -30%;z-index:-1;
  background:radial-gradient(60% 60% at 50% 55%,rgba(5,5,10,.94) 30%,rgba(5,5,10,.72) 60%,transparent 85%);
}
.zoom__copy h2{font-size:var(--step-3);text-wrap:balance}
.zoom__copy p{color:var(--text-dim);margin-top:.9rem;font-size:var(--step-0)}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .zoom{view-timeline-name:--pullback;view-timeline-axis:block}
    /* Three beats, as asked: website only -> phone appears -> full scene + copy. */
    /* Intro holds as plain website, then shrinks away. */
    /* The handover is smoothed three ways at once:
         1. both layers DEFOCUS through the crossover, so the desktop->mobile
            layout swap happens while nothing is sharp enough to compare
         2. the intro collapses toward the phone screen's exact centre
         3. eased curves, so nothing changes speed abruptly
       A straight linear opacity swap between two different layouts is what
       read as rough. */
    /* No transform at all — it is the BACKDROP. Scaling it made a hard-edged
       rectangle of grid float over the workshop photo. It simply dissolves. */
    @keyframes intro-out{
      0%,26%  {opacity:1}
      52%     {opacity:0}
      100%    {opacity:0}
    }
    .zoom__intro{
      animation:intro-out cubic-bezier(.42,0,.32,1) both;
      animation-timeline:--pullback;
      animation-range:cover 0% cover 100%;
    }
    /* The phone composite rises underneath it and keeps pulling back. */
    @keyframes pull-back{
      0%,28%{transform:scale(1.62);opacity:0; filter:blur(7px)}
      44%   {transform:scale(1.42);opacity:.8;filter:blur(3px)}
      58%   {transform:scale(1.26);opacity:1; filter:blur(0px)}
      100%  {transform:scale(1);   opacity:1; filter:blur(0px)}
    }
    .zoom__stage{
      animation:pull-back cubic-bezier(.42,0,.32,1) both;
      animation-timeline:--pullback;
      animation-range:cover 0% cover 88%;
    }
    /* The site switches on inside the phone AFTER the intro has cleared, so the
       same headline is never legible in two places at once. */
    @keyframes screen-on{
      0%,56%{opacity:0}
      70%,100%{opacity:1}
    }
    .zoom__screen img{
      animation:screen-on linear both;
      animation-timeline:--pullback;
      animation-range:cover 0% cover 100%;
    }

    @keyframes line-in{
      0%,72%{opacity:0;transform:translateY(22px)}
      92%,100%{opacity:1;transform:none}
    }
    .zoom__copy{
      animation:line-in linear both;
      animation-timeline:--pullback;
      animation-range:cover 0% cover 92%;
    }
  }
}
/* No scroll-timeline support, or reduced motion: show the finished frame with
   the line already in place. Nothing is ever stranded invisible. */
@supports not (animation-timeline:view()){
  .zoom__stage{transform:scale(1);opacity:1}
  .zoom__intro{display:none}
  .zoom__copy{opacity:1}
}
@media (prefers-reduced-motion:reduce){
  .zoom{height:auto}
  .zoom__intro{display:none}
  .zoom__stage{opacity:1}
  .zoom__viewport{position:relative;height:auto;padding-block:var(--section-y)}
  .zoom__stage{position:relative;left:auto;top:auto;translate:none;transform:none;width:100%}
  .zoom__copy{position:relative;left:auto;bottom:auto;translate:none;opacity:1;margin:2rem auto 0}
}
@media (max-width:760px){
  .zoom{height:240vh}
  .zoom__viewport{height:calc(100svh - 76px)}
  .zoom__stage{transform:scale(1.5);opacity:0}
  @supports (animation-timeline:view()){
    @media (prefers-reduced-motion:no-preference){
      @keyframes pull-back{0%,28%{transform:scale(1.5);opacity:0}44%{opacity:.8}58%{transform:scale(1.22);opacity:1}100%{transform:scale(1);opacity:1}}
    }
  }
}

/* ==========================================================================
   4. INNER-PAGE FURNITURE
   Everything here uses Jeff's OWN existing brand assets — the six service
   icons and the Kentucky landscape set from the current site. Nothing
   generated, nothing stock, nothing new to pay for.
   ========================================================================== */

/* ---- Page hero with the service's own icon ---- */
.phero{position:relative;overflow:hidden}
.phero__grid{display:grid;gap:clamp(1.6rem,1rem + 3vw,3.5rem);align-items:center}
@media (min-width:860px){.phero__grid{grid-template-columns:1.25fr .75fr}}
.phero__icon{position:relative;justify-self:center;width:min(280px,72vw);aspect-ratio:1}
.phero__icon img{width:100%;height:100%;object-fit:contain;position:relative;z-index:1}
.phero__icon::before{
  content:"";position:absolute;inset:-18%;border-radius:50%;
  background:radial-gradient(circle,rgba(0,191,255,.22),rgba(106,90,205,.16) 45%,transparent 70%);
  filter:blur(26px);
}
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    @keyframes icon-drift{from{transform:translateY(14px) scale(.97)}to{transform:translateY(-14px) scale(1)}}
    .phero__icon img{animation:icon-drift linear both;animation-timeline:view();animation-range:entry 0% exit 100%}
  }
}

/* ---- Kentucky landscape band — a breath of real place between sections ---- */
.landband{position:relative;overflow:hidden;isolation:isolate}
.landband img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-2;
}
.landband::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,var(--bg) 0%,rgba(7,7,9,.42) 30%,rgba(7,7,9,.52) 70%,var(--bg-deep) 100%);
}
.landband .wrap{padding-block:clamp(3.5rem,2.5rem + 5vw,7rem)}
.landband--slim .wrap{padding-block:clamp(3rem,2rem + 4vw,5.5rem);text-align:center}
.landband--slim h2{font-size:var(--step-2);max-width:22ch;margin-inline:auto}
.landband--slim p{color:#e7e7f0;margin-top:.9rem;max-width:46ch;margin-inline:auto;text-shadow:0 1px 16px rgba(0,0,0,.9)}
.landband h2,.landband .eyebrow{text-shadow:0 2px 24px rgba(0,0,0,.85)}
.landband ul a{text-shadow:0 1px 12px rgba(0,0,0,.9)}
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    /* Slow drift — the band should feel like weather, not a slideshow. */
    @keyframes band-drift{from{transform:scale(1.14) translateY(-2%)}to{transform:scale(1.14) translateY(2%)}}
    .landband img{animation:band-drift linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
  }
}

/* ---- Service cards carrying their icon ---- */
.svc-card{display:flex;flex-direction:column;gap:.7rem;text-decoration:none;height:100%;position:relative;overflow:hidden}
.svc-card__ico{width:56px;height:56px;flex:none;margin-bottom:.2rem}
.svc-card__ico img{width:100%;height:100%;object-fit:contain}
.svc-card h3{font-size:var(--step-1);transition:color .2s var(--ease)}
.svc-card:hover h3{color:var(--blue)}
.svc-card p{color:var(--text-dim);font-size:var(--step--1);line-height:1.6}
.svc-card__more{margin-top:auto;padding-top:.6rem;font-weight:700;font-size:.9rem}
.svc-card::before{
  content:"";position:absolute;right:-30%;top:-40%;width:70%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle,rgba(0,191,255,.10),transparent 65%);
  opacity:0;transition:opacity .3s var(--ease);pointer-events:none;
}
.svc-card:hover::before{opacity:1}

/* ---- Blog post header strip ---- */
.post-hero{position:relative;overflow:hidden}
.post-hero__aurora{
  position:absolute;inset:0;z-index:-1;opacity:.4;
  -webkit-mask-image:linear-gradient(180deg,#000,transparent 82%);
  mask-image:linear-gradient(180deg,#000,transparent 82%);
}
.post-hero__aurora img{width:100%;height:100%;object-fit:cover}

/* ==========================================================================
   5. THE ASSEMBLY — the whole homepage builds itself, and nothing leaves
   Every piece that lands STAYS. The column keeps all eight, and scales down as
   it grows so the growing page always fits the frame. By the last beat you are
   looking at the complete site as one object, tilted in 3D — a render of the
   finished thing, assembled in front of you.

   Scale per beat = frameHeight / (bandsVisible * bandHeight). With 8 bands of
   430 in a ~620 frame that runs 1 -> 0.18. The column is anchored top-centre,
   so the page builds downward the way a page is actually read.
   ========================================================================== */
.asm{position:relative;height:900vh}
/* ONE column at every width. The two-column desktop split was the whole
   problem: it left the frame at half width, so the assembly felt small on the
   big screen and great on a phone. Now the frame is sized off the VIEWPORT
   HEIGHT and the note sits under it — the phone layout, scaled up. */
.asm__sticky{
  position:sticky;top:0;height:100svh;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:clamp(.9rem,1.6vh,1.7rem);
}

.asm__frame{
  position:relative;
  aspect-ratio:1200/940;
  height:min(62svh,720px);      /* driven by viewport height, so it fills the screen */
  width:auto;max-width:min(96vw,1120px);
  border:1px solid var(--line-strong);border-radius:10px;
  background:#0b0b12;box-shadow:0 30px 80px rgba(0,0,0,.6);
}
.asm__chrome{
  position:absolute;top:0;left:0;right:0;z-index:4;
  display:flex;align-items:center;gap:.4rem;
  position:absolute;top:0;left:0;right:0;z-index:4;
  height:4%;min-height:18px;padding:0 .75rem;background:#15151f;
  border:1px solid var(--line-strong);border-bottom:0;border-radius:9px 9px 0 0;
}
.asm__chrome i{width:8px;height:8px;border-radius:50%;background:#2f2f3d;flex:none}
.asm__chrome span{margin-left:.4rem;font-size:.62rem;color:var(--muted);font-family:ui-monospace,Menlo,monospace}
.asm__view{
  position:absolute;inset:4% 0 0;
  overflow:hidden;                       /* clip the stack to the window */
  perspective:1600px;perspective-origin:50% 40%;
  border-radius:0 0 9px 9px;
}
.asm__stack{
  position:absolute;top:0;left:0;right:0;
  transform-origin:50% 0%;
  transform-style:preserve-3d;
  will-change:transform;
}
.asm__band{position:relative;display:block;width:100%;transform-style:preserve-3d;will-change:transform,opacity}
.asm__band img{display:block;width:100%;height:auto;border-left:1px solid var(--line);border-right:1px solid var(--line)}
/* the seam each piece clicks into */
.asm__band + .asm__band{box-shadow:0 -1px 0 rgba(0,191,255,.45)}

.asm__notes{position:relative;width:min(880px,94vw);min-height:clamp(170px,22vh,240px)}
.asm__note{position:absolute;inset:0;opacity:0;display:flex;flex-direction:column;justify-content:flex-start;text-align:center;align-items:center}
.asm__note b.n{
  font-size:clamp(1.5rem,1.1rem + 1.3vw,2.2rem);font-weight:900;line-height:.85;letter-spacing:-.04em;
  background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;
  -webkit-text-fill-color:transparent;margin-bottom:.5rem;
}
.asm__note h3{font-size:var(--step-2);margin-bottom:.6rem;text-wrap:balance}
.asm__note p{color:var(--text-dim);max-width:64ch;margin-inline:auto}
.asm__note p b{color:var(--white)}
.asm__pips{display:flex;gap:5px;margin-top:.9rem;justify-content:center}
.asm__pips i{width:20px;height:3px;border-radius:2px;background:var(--line-strong);display:block}

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .asm{view-timeline-name:--asm;view-timeline-axis:block}

    /* Holds, then steps — the hold is what makes it read as a click rather
       than a drift. The tilt eases in over the back half so the finished page
       settles as a 3D object. */
    /* Full size the whole way through — the site stays LEGIBLE and nothing is
       cropped. The stack steps up by each section's real height (offsets are
       computed from the actual image heights, not a uniform guess), so each new
       piece clicks in directly below the last. Only at the very end does it
       pull back to reveal the finished page as one 3D object. */
    @keyframes asm-grow{
      0%,8%    {transform:translateY(0)       scale(1) rotateX(0deg) rotateY(0deg)}
      11%,19%  {transform:translateY(-16.98%) scale(1) rotateX(0deg) rotateY(0deg)}
      22%,30%  {transform:translateY(-31.99%) scale(1) rotateX(0deg) rotateY(0deg)}
      33%,41%  {transform:translateY(-48.56%) scale(1) rotateX(0deg) rotateY(0deg)}
      44%,52%  {transform:translateY(-61.17%) scale(1) rotateX(0deg) rotateY(0deg)}
      55%,63%  {transform:translateY(-73.72%) scale(1) rotateX(0deg) rotateY(0deg)}
      66%,74%  {transform:translateY(-83.93%) scale(1) rotateX(0deg) rotateY(0deg)}
      77%,86%  {transform:translateY(-83.93%) scale(1) rotateX(0deg) rotateY(0deg)}
      100%     {transform:translateY(1.6%)    scale(.132) rotateX(8deg) rotateY(-6deg)}
    }
    .asm__stack{
      animation:asm-grow cubic-bezier(.5,0,.18,1) both;
      animation-timeline:--asm;
      animation-range:contain 0% contain 100%;
    }

    /* Each piece flies up from below and forward in Z, then clicks flat into
       the slot directly under the one before it. Slight overshoot on landing. */
    @keyframes band-fly{
      0%  {opacity:0;transform:translate3d(0,90px,320px) rotateX(-16deg)}
      70% {opacity:1}
      100%{opacity:1;transform:translate3d(0,0,0) rotateX(0deg)}
    }
    .asm__band{animation:band-fly cubic-bezier(.25,1.35,.45,1) both;animation-timeline:--asm}
    .asm__band:nth-child(2){animation-range:contain 0%  contain 6%}
    .asm__band:nth-child(3){animation-range:contain 9%  contain 15%}
    .asm__band:nth-child(3){animation-range:contain 22% contain 28%}
    .asm__band:nth-child(4){animation-range:contain 33% contain 39%}
    .asm__band:nth-child(5){animation-range:contain 44% contain 50%}
    .asm__band:nth-child(6){animation-range:contain 55% contain 61%}
    .asm__band:nth-child(7){animation-range:contain 66% contain 72%}
    .asm__band:nth-child(8){animation-range:contain 77% contain 83%}

    @keyframes note-cycle{
      0%{opacity:0;transform:translateY(14px)}
      18%,78%{opacity:1;transform:none}
      100%{opacity:0;transform:translateY(-14px)}
    }
    .asm__note{animation:note-cycle ease-in-out both;animation-timeline:--asm}
    .asm__note:nth-child(1){animation-range:contain 0%  contain 12%}
    .asm__note:nth-child(2){animation-range:contain 11% contain 22%}
    .asm__note:nth-child(3){animation-range:contain 22% contain 33%}
    .asm__note:nth-child(4){animation-range:contain 33% contain 44%}
    .asm__note:nth-child(5){animation-range:contain 44% contain 55%}
    .asm__note:nth-child(6){animation-range:contain 55% contain 66%}
    .asm__note:nth-child(7){animation-range:contain 66% contain 77%}
    .asm__note:nth-child(8){animation-range:contain 77% contain 100%}
  }
}

/* No support / reduced motion: the readable stacked version. */
@supports not (animation-timeline:view()){
  .asm{height:auto}
  .asm__sticky{position:static;height:auto;display:block}
  .asm__frame{aspect-ratio:auto;perspective:none}
  .asm__view{position:static;inset:auto;padding-top:30px}
  .asm__stack{position:static;transform:none}
  .asm__notes{min-height:0;display:grid;gap:2rem;margin-top:2rem}
  .asm__note{position:static;opacity:1}
}
@media (prefers-reduced-motion:reduce){
  .asm{height:auto}
  .asm__sticky{position:static;height:auto;display:block}
  .asm__frame{aspect-ratio:auto;perspective:none}
  .asm__view{position:static;inset:auto;padding-top:30px}
  .asm__stack{position:static;transform:none!important}
  .asm__band{opacity:1!important;transform:none!important}
  .asm__notes{min-height:0;display:grid;gap:2rem;margin-top:2rem}
  .asm__note{position:static;opacity:1}
}
@media (max-width:999px){
  .asm{height:760vh}
  .asm__frame{height:min(54svh,520px)}
  .asm__notes{min-height:clamp(165px,26vh,215px)}
}
