"use client"; import dynamic from "next/dynamic"; import { useCallback, useEffect, useRef, useState } from "react"; import type { HeroFieldSceneProps } from "./HeroFieldScene"; const Scene = dynamic(() => import("./HeroFieldScene"), { ssr: false, }); function supportsWebGL2() { if (typeof window.WebGL2RenderingContext === "undefined") return false; try { const context = document.createElement("canvas").getContext("webgl2"); context?.getExtension("WEBGL_lose_context")?.loseContext(); return context !== null; } catch { return false; } } /** * Fundo do hero: liga a rolagem do hero à variável CSS --hero-p (0 no topo, 1 no fim do mergulho) * e monta a cena WebGL só quando a página fica ociosa, com WebGL2 e sem preferência por movimento reduzido. */ export default function HeroField() { const rootRef = useRef(null); const progressRef = useRef(0); const [mount, setMount] = useState(false); const [visible, setVisible] = useState(false); const [ready, setReady] = useState(false); const [failed, setFailed] = useState(false); // fim do mergulho: a cena já está no escuro e o hero se dissolve; parar o desenho poupa a GPU para o notebook const [done, setDone] = useState(false); useEffect(() => { const section = rootRef.current?.closest(".hero"); if (!section) return; let frame = 0; const update = () => { frame = 0; const rect = section.getBoundingClientRect(); const span = Math.max(rect.height - window.innerHeight, window.innerHeight * 0.8); const p = Math.min(1, Math.max(0, -rect.top / span)); progressRef.current = p; section.style.setProperty("--hero-p", p.toFixed(4)); setDone(p >= 0.999); }; const queue = () => { if (!frame) frame = requestAnimationFrame(update); }; update(); window.addEventListener("scroll", queue, { passive: true }); window.addEventListener("resize", queue); let intersecting = false; const sync = () => setVisible(intersecting && !document.hidden); const observer = new IntersectionObserver(([entry]) => { intersecting = entry.isIntersecting; sync(); }); observer.observe(section); document.addEventListener("visibilitychange", sync); return () => { cancelAnimationFrame(frame); window.removeEventListener("scroll", queue); window.removeEventListener("resize", queue); observer.disconnect(); document.removeEventListener("visibilitychange", sync); }; }, []); useEffect(() => { if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; if (!supportsWebGL2()) return; // Título, botões e hidratação vêm primeiro; o Three.js entra quando o navegador fica ocioso. if (typeof window.requestIdleCallback === "function") { const id = window.requestIdleCallback(() => setMount(true), { timeout: 2500 }); return () => window.cancelIdleCallback(id); } const id = setTimeout(() => setMount(true), 1200); return () => clearTimeout(id); }, []); const handleReady = useCallback(() => setReady(true), []); const handleError = useCallback(() => setFailed(true), []); return ( ); }