/* global React, LIcons */ // Hero: vídeo de apresentação num card que "deita" conforme a rolagem. // Porte do ContainerScroll (Aceternity) para o React UMD da LP — sem framer-motion: // o progresso da rolagem é lido na mão e escrito direto no style, dentro de rAF. const { useState: useHvState, useEffect: useHvEffect, useRef: useHvRef } = React; const HV_MOBILE_Q = '(max-width: 768px)'; // Curso de rolagem em que o card sai deitado e chega reto. const HV_CURSO = { desk: 460, mob: 300 }; // mesma curva do original: rotateX →0, escala e o título subindo. function hvFrame(p, mobile) { const from = mobile ? 0.9 : 1.04; const to = mobile ? 1 : 1; const tomba = mobile ? 13 : 17; return { rotate: tomba - tomba * p, scale: from + (to - from) * p, lift: (mobile ? -60 : -90) * p, }; } function HeroVideo({ title }) { const wrapRef = useHvRef(null); const cardRef = useHvRef(null); const headRef = useHvRef(null); const videoRef = useHvRef(null); const [muted, setMuted] = useHvState(true); const [playing, setPlaying] = useHvState(true); // ---- rolagem → transform ------------------------------------------------ useHvEffect(() => { const wrap = wrapRef.current; if (!wrap) return; const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; let raf = 0; const apply = () => { raf = 0; const card = cardRef.current; const head = headRef.current; if (!card) return; const mobile = window.matchMedia(HV_MOBILE_Q).matches; let p = 1; if (!reduced) { // O original mede um bloco de 80rem só para ter pista de rolagem; aqui a // hero fica no topo da página e um bloco desses deixaria um vão vazio. // Então o curso é a própria rolagem a partir do topo: em repouso o card // está deitado e o título no lugar; ao rolar, ele levanta e o título sobe. const curso = mobile ? HV_CURSO.mob : HV_CURSO.desk; const y = window.pageYOffset || document.documentElement.scrollTop || 0; p = Math.min(1, Math.max(0, y / curso)); } const f = hvFrame(p, mobile); card.style.transform = 'rotateX(' + f.rotate.toFixed(2) + 'deg) scale(' + f.scale.toFixed(3) + ')'; if (head) head.style.transform = 'translateY(' + f.lift.toFixed(1) + 'px)'; }; const onScroll = () => { if (!raf) raf = requestAnimationFrame(apply); }; apply(); window.addEventListener('scroll', onScroll, { passive: true }); window.addEventListener('resize', onScroll); return () => { if (raf) cancelAnimationFrame(raf); window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); }; }, []); // ---- só toca enquanto estiver na tela ----------------------------------- useHvEffect(() => { const v = videoRef.current; if (!v || typeof IntersectionObserver === 'undefined') return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { const q = v.play(); if (q && q.catch) q.catch(() => {}); } else v.pause(); }); }, { threshold: 0.2 }); io.observe(v); return () => io.disconnect(); }, []); const toggleSom = () => { const v = videoRef.current; if (!v) return; v.muted = !v.muted; setMuted(v.muted); if (!v.muted) { const q = v.play(); if (q && q.catch) q.catch(() => {}); } }; const togglePlay = () => { const v = videoRef.current; if (!v) return; if (v.paused) { const q = v.play(); if (q && q.catch) q.catch(() => {}); setPlaying(true); } else { v.pause(); setPlaying(false); } }; return (
{title}
); } Object.assign(window, { HeroVideo });