// hero.jsx — three hero variants
const { useEffect: hUseEffect, useRef: hUseRef } = React;

function HeroParticles({ onGo }) {
  const titleRef = hUseRef(null);
  const eyebrowRef = hUseRef(null);
  const ctaRef = hUseRef(null);

  hUseEffect(() => {
    const t = titleRef.current;
    if (!t || !window.gsap) return;
    // Splitting
    t.querySelectorAll("[data-split]").forEach(el => window.ScrollFX.splitText(el));
    const chars = t.querySelectorAll(".split-char");
    window.gsap.set(chars, { yPercent: 110, opacity: 0 });
    const tl = window.gsap.timeline({ defaults: { ease: "power4.out" } });
    tl.to(chars, { yPercent: 0, opacity: 1, duration: 1.2, stagger: { each: 0.025, from: "random" } }, 0.15)
      .from(eyebrowRef.current, { opacity: 0, y: 16, duration: 0.8 }, 0.05)
      .from(ctaRef.current?.children || [], { opacity: 0, y: 24, duration: 0.9, stagger: 0.08 }, 0.7);
    return () => tl.kill();
  }, []);

  return (
    <section className="hero hero--particles" data-screen-label="hero">
      {window.Particles ? <window.Particles /> : null}
      <div className="hero__inner wrap">
        <div className="hero__eyebrow" ref={eyebrowRef}>
          <span className="dot">●</span>
          <span>SCRIPTS.STUDIO — VOL. 09</span>
          <span style={{marginLeft:"auto"}}>BERLIN ↔ SHENZHEN</span>
          <span>MMXXVI</span>
        </div>
        <h1 className="hero__title" ref={titleRef}>
          <div className="split-line"><span data-split>Motion</span></div>
          <div className="split-line"><span data-split>scripts for</span></div>
          <div className="split-line"><span data-split className="outline">designers who</span></div>
          <div className="split-line"><em data-split>render the unrenderable.</em></div>
        </h1>
        <div className="hero__meta">
          <p className="hero__lead">A small studio drop of production-grade scripts for After Effects, Blender and Unreal — every release shipped with source files, presets and a README we actually wrote.</p>
          <div className="hero__stat">
            <div className="hero__stat-num" data-counter data-to="92">0</div>
            <div className="hero__stat-label">scripts shipped</div>
          </div>
          <div className="hero__stat">
            <div className="hero__stat-num" data-counter data-to="14820">0</div>
            <div className="hero__stat-label">downloads</div>
          </div>
        </div>
        <div className="hero__cta-row" ref={ctaRef}>
          <a className="magnetic" data-magnetic href="#/scripts" data-cursor="button" onClick={(e)=>{e.preventDefault(); onGo("/scripts");}}>
            <span className="btn magnetic-inner"><span>Browse all scripts</span><span className="btn__arrow">→</span></span>
          </a>
          <a className="magnetic" data-magnetic href="#/membership" data-cursor="button" onClick={(e)=>{e.preventDefault(); onGo("/membership");}}>
            <span className="btn btn--ghost magnetic-inner"><span>Membership</span></span>
          </a>
        </div>
      </div>
    </section>
  );
}

function HeroType({ onGo }) {
  const titleRef = hUseRef(null);
  hUseEffect(() => {
    if (!window.gsap) return;
    const lines = titleRef.current?.querySelectorAll(".stack-row") || [];
    window.gsap.from(lines, { opacity: 0, xPercent: -6, duration: 1.1, ease: "power4.out", stagger: 0.08 });
  }, []);
  return (
    <section className="hero hero--type" data-screen-label="hero-type" style={{justifyContent:"space-between"}}>
      <div className="hero__eyebrow" style={{padding: "120px 0 0 32px"}}>
        <span className="dot">●</span><span>SCRIPTS.STUDIO — VOL. 09</span>
        <span style={{marginLeft:"auto"}}>FOR DESIGNERS</span>
      </div>
      <div className="wrap" ref={titleRef} style={{margin:"auto 0", width:"100%"}}>
        <div className="stack">
          <div className="stack-row">
            <span className="display" style={{fontSize:"clamp(72px, 17vw, 280px)"}}>Render</span>
            <span className="meta">A01 / Kinetic Type</span>
          </div>
          <div className="stack-row">
            <span className="display outline" style={{fontSize:"clamp(72px, 17vw, 280px)", WebkitTextStroke:"1.5px #f5f5f5", color:"transparent"}}>the unreal</span>
            <span className="meta">A02 / Volumetric Sim</span>
          </div>
          <div className="stack-row">
            <span className="display" style={{fontSize:"clamp(72px, 17vw, 280px)"}}><em style={{fontStyle:"normal", color:"var(--accent)"}}>scripts</em></span>
            <span className="meta">A03 / Procedural Worlds</span>
          </div>
          <div className="stack-row">
            <span className="display" style={{fontSize:"clamp(72px, 17vw, 280px)"}}>made for</span>
            <span className="meta">A04 / Branding Engines</span>
          </div>
          <div className="stack-row">
            <span className="display outline" style={{fontSize:"clamp(72px, 17vw, 280px)", WebkitTextStroke:"1.5px #f5f5f5", color:"transparent"}}>designers.</span>
            <span className="meta">↘ EST. 2021</span>
          </div>
        </div>
      </div>
      <div className="wrap" style={{display:"flex", justifyContent:"space-between", alignItems:"flex-end", paddingBottom:"40px", gap:"32px"}}>
        <p className="hero__lead" style={{maxWidth:"380px"}}>A small studio drop of production-grade scripts for After Effects, Blender and Unreal — every release shipped with source files.</p>
        <div className="hero__cta-row" style={{margin:0}}>
          <a className="magnetic" data-magnetic href="#/scripts" data-cursor="button" onClick={(e)=>{e.preventDefault(); onGo("/scripts");}}>
            <span className="btn magnetic-inner"><span>Browse all scripts</span><span className="btn__arrow">→</span></span>
          </a>
          <a className="magnetic" data-magnetic href="#/membership" data-cursor="button" onClick={(e)=>{e.preventDefault(); onGo("/membership");}}>
            <span className="btn btn--ghost magnetic-inner"><span>Membership</span></span>
          </a>
        </div>
      </div>
    </section>
  );
}

function HeroGrid({ onGo }) {
  const titleRef = hUseRef(null);
  hUseEffect(() => {
    if (!window.gsap) return;
    const t = titleRef.current;
    t.querySelectorAll("[data-split]").forEach(el => window.ScrollFX.splitText(el));
    const chars = t.querySelectorAll(".split-char");
    window.gsap.set(chars, { yPercent: 110, opacity: 0 });
    window.gsap.to(chars, { yPercent: 0, opacity: 1, duration: 1.2, ease: "power4.out", stagger: { each: 0.02, from: "random" } });
    // Tile animations
    const tiles = document.querySelectorAll(".hero--grid .tile");
    window.gsap.from(tiles, { opacity: 0, scale: 1.05, duration: 1.2, ease: "power3.out", stagger: 0.03 });
  }, []);

  const palettes = window.AppData?.PALETTES || [];
  const tiles = Array.from({ length: 12 }).map((_, i) => palettes[i % palettes.length]);

  return (
    <section className="hero hero--grid" data-screen-label="hero-grid" style={{minHeight: "100vh"}}>
      <div className="grid-bg">
        {tiles.map((p, i) => (
          <div className="tile" key={i}>
            <div className="ani" style={{
              background: `radial-gradient(circle at ${20+ (i*9)%70}% ${30+(i*11)%50}%, ${p[0]} 0%, ${p[1]} 35%, transparent 70%), radial-gradient(circle at ${70-(i*7)%50}% ${60-(i*5)%40}%, ${p[2]} 0%, transparent 60%)`,
              filter: "blur(30px)",
              animation: `tileFloat ${12 + (i % 6)}s ease-in-out infinite alternate`,
              animationDelay: `${i * 0.4}s`
            }}></div>
          </div>
        ))}
      </div>
      <style>{`@keyframes tileFloat { 0% { transform: scale(1.1) translate(0,0); } 100% { transform: scale(1.25) translate(-4%, 4%); } }`}</style>
      <div className="hero__inner wrap" style={{textAlign:"center", marginTop:"auto", marginBottom:"auto"}}>
        <div className="hero__eyebrow" style={{justifyContent:"center"}}><span className="dot">●</span><span>SCRIPTS.STUDIO</span><span>VOL. 09 / MMXXVI</span></div>
        <h1 className="hero__title" ref={titleRef} style={{textAlign:"center", fontSize:"clamp(64px, 15vw, 240px)"}}>
          <div className="split-line"><span data-split>scripts for</span></div>
          <div className="split-line"><em data-split>designers.</em></div>
        </h1>
        <p className="hero__lead" style={{margin:"40px auto 32px", textAlign:"center", maxWidth:"520px"}}>
          A small studio drop of production-grade scripts for After Effects, Blender and Unreal. Every release shipped with source files, presets and a README we actually wrote.
        </p>
        <div className="hero__cta-row" style={{justifyContent:"center"}}>
          <a className="magnetic" data-magnetic href="#/scripts" data-cursor="button" onClick={(e)=>{e.preventDefault(); onGo("/scripts");}}>
            <span className="btn magnetic-inner"><span>Browse all scripts</span><span className="btn__arrow">→</span></span>
          </a>
          <a className="magnetic" data-magnetic href="#/membership" data-cursor="button" onClick={(e)=>{e.preventDefault(); onGo("/membership");}}>
            <span className="btn btn--ghost magnetic-inner"><span>Membership</span></span>
          </a>
        </div>
      </div>
    </section>
  );
}

function HeroVideo({ onGo, videoUrl }) {
  const sectionRef = hUseRef(null);
  const videoRef = hUseRef(null);
  const dotsRef = hUseRef(null);
  const [page, setPage] = React.useState(0);
  const pageRef = hUseRef(0);
  const userTriggeredRef = hUseRef(false);
  const [loaded, setLoaded] = React.useState(false);
  const [activeBg, setActiveBg] = React.useState(() => window.AppData?.getActiveBgVideo?.() || null);

  // Resolve video source list: admin/settings active background (multi-res) > explicit Tweaks override > 4K fallback
  const srcList = (activeBg?.urls && activeBg.urls.length > 0)
    ? activeBg.urls
    : (videoUrl ? [videoUrl] : [
        "https://videos.pexels.com/video-files/7670836/7670836-uhd_3840_2160_25fps.mp4",
        "https://videos.pexels.com/video-files/7670836/7670836-uhd_2560_1440_30fps.mp4",
        "https://videos.pexels.com/video-files/7670836/7670836-hd_1920_1080_30fps.mp4"
      ]);
  const [srcIdx, setSrcIdx] = React.useState(0);
  React.useEffect(() => { setSrcIdx(0); }, [activeBg?.id, videoUrl]);
  const src = srcList[srcIdx] || srcList[srcList.length - 1];

  hUseEffect(() => {
    const upd = () => setActiveBg(window.AppData.getActiveBgVideo());
    window.addEventListener("pa:settings", upd);
    return () => window.removeEventListener("pa:settings", upd);
  }, []);

  const PAGES = [
    {
      eyebrow: "SCRIPTS.STUDIO — VOL. 09 / MMXXVI",
      title: ["render", "in motion."],
      lead: "A small studio drop of production-grade scripts for After Effects, Blender and Unreal — built by designers, for designers.",
      cta: { primary: { label: "Browse all scripts", to: "/scripts" }, ghost: { label: "Membership", to: "/membership" } }
    },
    {
      eyebrow: "↘ 01 · NINETY-TWO SCRIPTS, ZERO FILLER",
      title: ["92 scripts.", "1 brief."],
      lead: "Every release goes through a two-week production cycle before it ships. If it breaks the brief, it doesn't go out.",
      stats: [
        { k: "92+", v: "scripts shipped" },
        { k: "14.8K", v: "creators using daily" },
        { k: "4.9★", v: "average rating" }
      ]
    },
    {
      eyebrow: "↘ 02 · WHAT YOU GET",
      title: ["source files.", "every time."],
      lead: "JSX. .blend. .uasset. No watermarks, no gates. Lifetime patches on your major version, commercial licence to $1M ARR.",
      bullets: ["Unencrypted source", "Lifetime patches", "Commercial licence", "README we actually wrote"]
    },
    {
      eyebrow: "↘ 03 · BEGIN",
      title: ["start", "shipping."],
      lead: "Pay once, or join the membership — month, quarter, half-year, year. Whatever your rhythm is.",
      cta: { primary: { label: "Browse the index", to: "/scripts" }, ghost: { label: "Pricing", to: "/membership" } }
    }
  ];

  const N = PAGES.length;

  // Free scroll + threshold snap. No wheel intercept — Lenis lets the user drag
  // the page freely with high damping for a resistant feel. On scroll-idle:
  //   • If user moved < 50% of viewport from anchor panel → bounce back (spring)
  //   • If user moved ≥ 50% of viewport → glide forward to the next panel
  hUseEffect(() => {
    if (!window.ScrollFX?.lenis) return;
    const section = sectionRef.current;
    if (!section) return;
    const lenis = window.ScrollFX.lenis;

    let stopTimer = null;
    let suppressUntil = 0;
    let snappedY = 0;
    let history = [];

    const panelTops = () => Array.from(section.querySelectorAll(".hero__video-panel"))
      .map(p => p.offsetTop + section.offsetTop);

    const heroInView = () => {
      const sr = section.getBoundingClientRect();
      return sr.top < window.innerHeight && sr.bottom > 0;
    };

    const nearestPanelIdx = (y, tops) => {
      let best = 0, bestD = Infinity;
      tops.forEach((t, i) => { const d = Math.abs(t - y); if (d < bestD) { bestD = d; best = i; } });
      return best;
    };

    const snap = () => {
      if (!heroInView()) return;
      const tops = panelTops();
      const y = window.scrollY;
      if (y > tops[tops.length - 1] + window.innerHeight * 0.5) return;
      if (y < tops[0] - window.innerHeight * 0.5) return;

      let idx = tops.indexOf(snappedY);
      if (idx < 0) idx = nearestPanelIdx(snappedY, tops);

      const dy = y - tops[idx];
      // Halfway threshold — pass the midpoint to advance
      const threshold = window.innerHeight * 0.5;
      let targetIdx;
      if (Math.abs(dy) < threshold) {
        targetIdx = idx; // didn't reach halfway → bounce back
      } else {
        targetIdx = Math.max(0, Math.min(tops.length - 1, idx + (dy > 0 ? 1 : -1)));
      }
      const target = tops[targetIdx];
      snappedY = target;
      if (Math.abs(target - y) < 2) return;
      const isBounceBack = targetIdx === idx;
      suppressUntil = performance.now() + 1500;
      if (isBounceBack) {
        // Elastic-out: dramatic over-shoot + multiple settling oscillations.
        // Visibly springs past the panel, then bounces back into place.
        const elasticOut = (t) => {
          if (t === 0 || t === 1) return t;
          const p = 0.4;
          return Math.pow(2, -10 * t) * Math.sin((t - p / 4) * (2 * Math.PI) / p) + 1;
        };
        lenis.scrollTo(target, { duration: 1.2, easing: elasticOut });
      } else {
        // Forward glide — smooth cubic-out with enough duration that the video
        // can play the keyframe segment at a comfortable rate.
        lenis.scrollTo(target, { duration: 1.0, easing: t => 1 - Math.pow(1 - t, 3) });
      }
    };

    const onScroll = () => {
      const now = performance.now();
      history.push({ t: now, y: window.scrollY });
      if (history.length > 8) history.shift();
      clearTimeout(stopTimer);
      if (now < suppressUntil) return;
      stopTimer = setTimeout(() => {
        const h = history;
        if (h.length >= 2) {
          const last = h[h.length - 1];
          const prev = h[h.length - 2];
          if (Math.abs(last.y - prev.y) > 1) return;
        }
        snap();
      }, 180);
    };

    lenis.on("scroll", onScroll);
    return () => {
      lenis.off("scroll", onScroll);
      clearTimeout(stopTimer);
    };
  }, [N]);

  // Keyboard navigation only — wheel/touch go through the free-scroll + snap path
  hUseEffect(() => {
    if (!window.ScrollFX?.lenis) return;
    const section = sectionRef.current;
    if (!section) return;
    const lenis = window.ScrollFX.lenis;

    let lockUntil = 0;
    const panelTops = () => Array.from(section.querySelectorAll(".hero__video-panel"))
      .map(p => p.offsetTop + section.offsetTop);
    const currentIndex = () => {
      const tops = panelTops();
      const y = window.scrollY + window.innerHeight * 0.4;
      let idx = 0;
      tops.forEach((t, i) => { if (y >= t) idx = i; });
      return idx;
    };
    const advance = (dir) => {
      const now = performance.now();
      if (now < lockUntil) return false;
      const tops = panelTops();
      const cur = currentIndex();
      const next = Math.max(0, Math.min(tops.length - 1, cur + dir));
      if (next === cur) return false;
      lockUntil = now + 1100;
      lenis.scrollTo(tops[next], { duration: 1.0, easing: t => 1 - Math.pow(1 - t, 3) });
      return true;
    };
    const heroInView = () => {
      const sr = section.getBoundingClientRect();
      return sr.top <= window.innerHeight * 0.4 && sr.bottom >= window.innerHeight * 0.6;
    };
    const onKey = (e) => {
      if (!heroInView()) return;
      if (!["ArrowDown","ArrowUp","PageDown","PageUp"," "].includes(e.key)) return;
      const dir = (e.key === "ArrowUp" || e.key === "PageUp") ? -1 : 1;
      const cur = currentIndex();
      const tops = panelTops();
      if (dir > 0 && cur === tops.length - 1) return;
      if (dir < 0 && cur === 0) return;
      e.preventDefault();
      advance(dir);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [N]);

  hUseEffect(() => {
    pageRef.current = page;
  }, [page]);

  // Detect which section is closest to viewport center — that's the active page.
  hUseEffect(() => {
    const section = sectionRef.current;
    if (!section) return;
    const panels = section.querySelectorAll(".hero__video-panel");
    let raf = null, last = -1;
    const tick = () => {
      raf = null;
      const mid = window.innerHeight / 2;
      let bestIdx = 0, bestDist = Infinity;
      panels.forEach((p, i) => {
        const r = p.getBoundingClientRect();
        const center = r.top + r.height / 2;
        const d = Math.abs(center - mid);
        if (d < bestDist) { bestDist = d; bestIdx = i; }
      });
      if (bestIdx !== last) {
        last = bestIdx;
        userTriggeredRef.current = true;
        setPage(bestIdx);
      }
    };
    const schedule = () => { if (raf == null) raf = requestAnimationFrame(tick); };
    schedule();
    window.addEventListener("scroll", schedule, { passive: true });
    window.addEventListener("resize", schedule);
    let unsubLenis = null;
    if (window.ScrollFX?.lenis) {
      const h = () => schedule();
      window.ScrollFX.lenis.on("scroll", h);
      unsubLenis = () => window.ScrollFX.lenis.off("scroll", h);
    }
    return () => {
      window.removeEventListener("scroll", schedule);
      window.removeEventListener("resize", schedule);
      if (unsubLenis) unsubLenis();
      if (raf) cancelAnimationFrame(raf);
    };
  }, [N]);

  // Video: play the segment that corresponds to current page
  hUseEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    const onMeta = () => setLoaded(true);
    const onError = () => {
      // Auto-fall back to next URL in srcList (lower resolution)
      if (srcIdx < srcList.length - 1) setSrcIdx(srcIdx + 1);
    };
    v.addEventListener("loadedmetadata", onMeta);
    v.addEventListener("error", onError);
    v.load();
    return () => {
      v.removeEventListener("loadedmetadata", onMeta);
      v.removeEventListener("error", onError);
    };
  }, [src]);

  // Scrub the video's currentTime in lockstep with scroll position across the hero.
  // Each panel = a keyframe in the video. Scrolling between panels plays the
  // segment between two keyframes; stopping freezes on the current frame.
  // Scroll-bound video scrub.
  //   Forward scroll → native playback at variable rate (smooth, HW-accelerated).
  //   Backward scroll → short seeks (browsers cache recent frames).
  //   Scroll stops → pause and snap to the exact scroll-mapped frame.
  hUseEffect(() => {
    const section = sectionRef.current;
    const v = videoRef.current;
    if (!section || !v) return;
    v.play().then(() => v.pause()).catch(() => {});

    let raf = null, loopActive = false, stopTimer = null;

    const computeTarget = () => {
      const dur = v.duration;
      if (!dur || !isFinite(dur)) return null;
      const r = section.getBoundingClientRect();
      const total = section.offsetHeight - window.innerHeight;
      const p = Math.max(0, Math.min(1, -r.top / total));
      return p * (dur - 0.05);
    };

    const tick = () => {
      const target = computeTarget();
      if (target == null) return;
      const delta = target - v.currentTime;
      if (delta > 0.04) {
        if (v.paused) v.play().catch(() => {});
        // Tuned for 0.95s cubic-out scroll → ~5.5x video rate covers a 5s segment with room
        v.playbackRate = Math.min(6, Math.max(0.5, delta * 8));
      } else if (delta < -0.04) {
        if (!v.paused) v.pause();
        try { v.currentTime = target; } catch (e) {}
      } else if (!v.paused) {
        v.pause();
        try { v.currentTime = target; } catch (e) {}
      }
    };

    const startLoop = () => {
      if (loopActive) return;
      loopActive = true;
      const step = () => {
        if (!loopActive) return;
        tick();
        raf = requestAnimationFrame(step);
      };
      raf = requestAnimationFrame(step);
    };
    const stopLoop = () => {
      loopActive = false;
      if (raf) { cancelAnimationFrame(raf); raf = null; }
      // Final snap: ensure paused, ensure currentTime matches exactly
      const target = computeTarget();
      if (target != null) {
        if (!v.paused) v.pause();
        if (Math.abs(v.currentTime - target) > 0.02) {
          try { v.currentTime = target; } catch (e) {}
        }
      }
    };

    const onScroll = () => {
      startLoop();
      clearTimeout(stopTimer);
      stopTimer = setTimeout(stopLoop, 160);
    };
    onScroll();

    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    let unsubLenis = null;
    if (window.ScrollFX?.lenis) {
      window.ScrollFX.lenis.on("scroll", onScroll);
      unsubLenis = () => window.ScrollFX.lenis.off("scroll", onScroll);
    }
    const onMeta = () => { setLoaded(true); onScroll(); };
    v.addEventListener("loadedmetadata", onMeta);

    return () => {
      v.removeEventListener("loadedmetadata", onMeta);
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (unsubLenis) unsubLenis();
      clearTimeout(stopTimer);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [src, N]);

  // Set initial hidden state on ALL panels' anim elements + animate current panel
  hUseEffect(() => {
    if (!window.gsap || !sectionRef.current) return;
    const allLines = sectionRef.current.querySelectorAll("[data-anim-line]");
    const allOthers = sectionRef.current.querySelectorAll("[data-anim-fade]");
    window.gsap.set(allLines, { yPercent: 110, opacity: 0 });
    window.gsap.set(allOthers, { opacity: 0, y: 24 });
  }, []);

  // Animate the currently-centered panel whenever it changes
  hUseEffect(() => {
    if (!window.gsap) return;
    const panel = sectionRef.current?.querySelector(`[data-panel="${page}"]`);
    if (!panel) return;
    const lines = panel.querySelectorAll("[data-anim-line]");
    const others = panel.querySelectorAll("[data-anim-fade]");
    window.gsap.to(lines, { yPercent: 0, opacity: 1, duration: 1.05, ease: "power4.out", stagger: 0.07, overwrite: true });
    window.gsap.to(others, { opacity: 1, y: 0, duration: 0.9, ease: "power3.out", stagger: 0.08, delay: 0.3, overwrite: true });
  }, [page]);

  const cur = PAGES[page];
  const progress = (page + 1) / N;

  return (
    <section className="hero hero--video" ref={sectionRef} data-screen-label="hero-video">
      {/* Fixed full-screen background video */}
      <div className="hero__video-bg">
        <video
          ref={videoRef}
          className="hero__video"
          src={src}
          key={src}
          muted
          playsInline
          preload="auto"
        />
        <div className="hero__video-overlay"></div>
        <div className="hero__video-bg-fallback" style={activeBg?.poster ? {
          background: `radial-gradient(60% 50% at 30% 30%, ${activeBg.poster[0]} 0%, transparent 60%),`+
                      `radial-gradient(60% 50% at 70% 70%, ${activeBg.poster[1]} 0%, transparent 60%),`+
                      `radial-gradient(40% 40% at 50% 50%, ${activeBg.poster[2]} 0%, transparent 55%),`+
                      `var(--bg)`,
          filter: "blur(60px)"
        } : null}></div>
      </div>

      {/* Fixed chrome — strip, eyebrow, counter, dots, hint */}
      <div className="hero__video-chrome">
        <div className="hero__video-strip">
          {PAGES.map((_, i) => (
            <div key={i} className={`hero__video-strip-cell ${i <= page ? "is-on" : ""} ${i === page ? "is-cur" : ""}`}>
              <span className="bar"></span>
            </div>
          ))}
        </div>
        <div className="hero__video-counter">
          <span className="mono">{String(page + 1).padStart(2, "0")}</span>
          <span style={{color:"var(--fg-3)"}}>/</span>
          <span className="mono" style={{color:"var(--fg-3)"}}>{String(N).padStart(2, "0")}</span>
        </div>
        <div className="hero__video-eyebrow">
          <span className="dot">●</span><span>{cur.eyebrow}</span>
        </div>
        <div className="hero__video-dots" ref={dotsRef}>
          {PAGES.map((_, i) => (
            <button key={i} className={`hero__video-dot ${i === page ? "is-active" : ""}`}
              onClick={() => {
                userTriggeredRef.current = true;
                const panel = sectionRef.current?.querySelectorAll(".hero__video-panel")[i];
                if (panel && window.ScrollFX?.lenis) window.ScrollFX.lenis.scrollTo(panel, { duration: 1.4 });
                else if (panel) panel.scrollIntoView({ behavior: "smooth" });
              }}
              data-cursor="button" aria-label={`Go to page ${i+1}`}>
              <span></span>
            </button>
          ))}
        </div>
        <div className="hero__video-hint">
          {page < N - 1 ? (
            <React.Fragment>
              <span className="mono">SCROLL</span>
              <span className="line"></span>
              <span style={{color:"var(--fg-3)"}}>Next chapter · 0{page + 2}</span>
            </React.Fragment>
          ) : (
            <React.Fragment>
              <span className="mono">↑</span>
              <span className="line"></span>
              <span style={{color:"var(--fg-3)"}}>Scroll back to begin</span>
            </React.Fragment>
          )}
        </div>
      </div>

      {/* Scrollable content panels — real vertical scroll, video stays fixed behind */}
      {PAGES.map((p, idx) => (
        <article key={idx} className="hero__video-panel" data-panel={idx}>
          <div className="hero__video-panel-inner wrap">
            <h1 className="hero__video-title">
              {p.title.map((line, li) => (
                <span key={li} className="split-line">
                  <span data-anim-line className={li === 1 ? "accent" : ""}>{line}</span>
                </span>
              ))}
            </h1>
            <div className="hero__video-meta">
              <p className="hero__video-lead" data-anim-fade>{p.lead}</p>
              {p.stats && (
                <div className="hero__video-stats" data-anim-fade>
                  {p.stats.map((s, si) => (
                    <div key={si} className="hero__video-stat">
                      <div className="k">{s.k}</div>
                      <div className="v">{s.v}</div>
                    </div>
                  ))}
                </div>
              )}
              {p.bullets && (
                <ul className="hero__video-bullets" data-anim-fade>
                  {p.bullets.map((b, bi) => (
                    <li key={bi}><span className="num mono">{String(bi + 1).padStart(2, "0")}</span>{b}</li>
                  ))}
                </ul>
              )}
              {p.cta && (
                <div className="hero__video-ctas" data-anim-fade>
                  <a className="magnetic" data-magnetic href={"#" + p.cta.primary.to} data-cursor="button" onClick={(e)=>{e.preventDefault(); onGo(p.cta.primary.to);}}>
                    <span className="btn magnetic-inner"><span>{p.cta.primary.label}</span><span className="btn__arrow">→</span></span>
                  </a>
                  {p.cta.ghost && (
                    <a className="magnetic" data-magnetic href={"#" + p.cta.ghost.to} data-cursor="button" onClick={(e)=>{e.preventDefault(); onGo(p.cta.ghost.to);}}>
                      <span className="btn btn--ghost magnetic-inner"><span>{p.cta.ghost.label}</span></span>
                    </a>
                  )}
                </div>
              )}
            </div>
          </div>
        </article>
      ))}

      <style>{`
        .hero--video { position: relative; width: 100%; padding: 0; display: block; }
        /* Fixed full-screen background */
        .hero--video .hero__video-bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
        .hero--video .hero__video { width: 100%; height: 100%; object-fit: cover; display: block; }
        .hero--video .hero__video-bg-fallback { position: absolute; inset: 0; z-index: -1; background:
          radial-gradient(60% 50% at 30% 30%, #ff3366 0%, transparent 60%),
          radial-gradient(60% 50% at 70% 70%, #ff7a3d 0%, transparent 60%),
          radial-gradient(40% 40% at 50% 50%, #ffd93d 0%, transparent 55%),
          var(--bg);
          filter: blur(60px);
          animation: fbDrift 22s ease-in-out infinite alternate;
        }
        @keyframes fbDrift { 0% { transform: scale(1.1) translate(0,0);} 100% { transform: scale(1.25) translate(-4%,3%);} }
        .hero--video .hero__video-overlay { position: absolute; inset: 0;
          background: linear-gradient(180deg, rgba(10,10,10,0.55) 0%, rgba(10,10,10,0.35) 30%, rgba(10,10,10,0.45) 60%, rgba(10,10,10,0.85) 100%),
            radial-gradient(40% 40% at 50% 60%, transparent 0%, rgba(10,10,10,0.45) 100%);
        }
        /* Fixed chrome layer above video, below content */
        .hero--video .hero__video-chrome { position: fixed; inset: 0; z-index: 2; pointer-events: none; }
        .hero--video .hero__video-chrome > * { pointer-events: auto; }
        .hero--video .hero__video-strip { position: absolute; top: 78px; left: 32px; right: 32px; display: grid; grid-template-columns: repeat(${N}, 1fr); gap: 12px; }
        .hero--video .hero__video-strip-cell { height: 2px; background: rgba(245,245,245,0.15); position: relative; overflow: hidden; }
        .hero--video .hero__video-strip-cell .bar { position: absolute; inset: 0; background: var(--fg); transform: scaleX(0); transform-origin: left center; transition: transform .6s cubic-bezier(.7,.05,.2,1); }
        .hero--video .hero__video-strip-cell.is-on .bar { transform: scaleX(1); }
        .hero--video .hero__video-strip-cell.is-cur .bar { background: var(--accent); }
        .hero--video .hero__video-counter { position: absolute; top: 100px; right: 32px; font-size: 12px; letter-spacing: 0.15em; display: flex; gap: 8px; align-items: center; }
        .hero--video .hero__video-eyebrow { position: absolute; top: 100px; left: 32px; font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--fg-2); display: flex; gap: 12px; align-items: center; }
        .hero--video .hero__video-eyebrow .dot { color: var(--accent); }
        .hero--video .hero__video-dots { position: absolute; right: 32px; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; gap: 14px; }
        .hero--video .hero__video-dot { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; }
        .hero--video .hero__video-dot span { width: 6px; height: 6px; border-radius: 999px; background: rgba(245,245,245,0.25); }
        .hero--video .hero__video-dot:hover span { background: var(--fg); }
        .hero--video .hero__video-dot.is-active span { background: var(--accent); width: 24px; border-radius: 3px; }
        .hero--video .hero__video-hint { position: absolute; left: 50%; bottom: 36px; transform: translateX(-50%);
          display: flex; align-items: center; gap: 14px; font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--fg-2); }
        .hero--video .hero__video-hint .line { width: 38px; height: 1px; background: linear-gradient(90deg, var(--fg) 0%, transparent 100%); animation: hintLine 1.8s ease-in-out infinite; transform-origin: left center; }
        @keyframes hintLine { 0%, 100% { transform: scaleX(0.3); opacity: 0.4; } 50% { transform: scaleX(1); opacity: 1; } }

        /* Scrollable panels — each fills viewport, sit above fixed video */
        .hero--video .hero__video-panel { position: relative; z-index: 1; height: 100vh; min-height: 100vh; display: flex; align-items: flex-end; padding: 0 0 120px; }
        .hero--video .hero__video-panel-inner { display: flex; flex-direction: column; gap: 28px; padding: 0 32px; }
        .hero--video .hero__video-title { font-family: "Clash Display", sans-serif; font-weight: 500; font-size: clamp(56px, 11vw, 180px); line-height: 0.9; letter-spacing: -0.04em; display: flex; flex-direction: column; margin: 0; }
        .hero--video .hero__video-title .split-line { overflow: hidden; display: block; }
        .hero--video .hero__video-title [data-anim-line] { display: inline-block; will-change: transform, opacity; }
        .hero--video .hero__video-title [data-anim-line].accent { color: var(--accent); }

        .hero--video .hero__video-meta { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr); gap: 60px; align-items: end; max-width: 1280px; }
        .hero--video .hero__video-lead { color: var(--fg); font-size: 15px; line-height: 1.55; max-width: 420px; opacity: 0.9; margin: 0; }
        .hero--video .hero__video-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
        .hero--video .hero__video-stat .k { font-family: "Clash Display", sans-serif; font-size: clamp(36px, 4.2vw, 64px); font-weight: 500; line-height: 1; letter-spacing: -0.03em; }
        .hero--video .hero__video-stat .v { font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--fg-2); margin-top: 6px; }
        .hero--video .hero__video-bullets { list-style: none; display: grid; grid-template-columns: 1fr 1fr; gap: 14px 32px; padding: 0; margin: 0; }
        .hero--video .hero__video-bullets li { font-size: 14px; color: var(--fg); display: flex; gap: 14px; align-items: baseline; line-height: 1.4; }
        .hero--video .hero__video-bullets .num { font-size: 11px; color: var(--accent); letter-spacing: 0.1em; }
        .hero--video .hero__video-ctas { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; }

        @media (max-width: 900px) {
          .hero--video .hero__video-meta { grid-template-columns: 1fr; gap: 32px; }
          .hero--video .hero__video-stats { grid-template-columns: 1fr 1fr; }
          .hero--video .hero__video-bullets { grid-template-columns: 1fr; }
          .hero--video .hero__video-dots { right: 16px; }
          .hero--video .hero__video-eyebrow span:not(.dot) { display: none; }
        }
      `}</style>
    </section>
  );
}

window.Hero = { HeroParticles, HeroType, HeroGrid, HeroVideo };
