/* global React, Icon, gsap */
const { useState, useEffect, useRef } = React;
const NAV = [
{ label: 'SERVICIOS', href: '#servicios' },
{ label: 'RITUALES', href: '#solucion' },
{ label: 'TESTIMONIOS', href: '#testimonios' },
{ label: 'CONTACTO', href: '#consulta' },
];
/* Video presence presets — reshape how alive the background feels */
const MOTION = {
intimo: { amp: 6, scale: 1.03 },
equilibrado: { amp: 14, scale: 1.06 },
envolvente: { amp: 26, scale: 1.13 },
};
/* Editorial voice — reshapes the hero message */
const COPY = {
sereno: {
l1: 'Lo que se fue, también puede trabajarse.',
l2: 'Retorno del ser amado, suerte, limpias y protección.',
subA: 'Trabajo espiritual guiado, con seriedad y sin promesas vacías.',
subB: ' Cada caso se explica antes de empezar.',
cta: 'Quiero mi consulta',
},
cercano: {
l1: 'No tienes que cargar esto en soledad.',
l2: 'Retorno del ser amado, suerte, limpias y protección.',
subA: 'Estamos aquí para escucharte y acompañarte con calma.',
subB: ' Sin juicios, a tu ritmo.',
cta: 'Hablemos hoy',
},
directo: {
l1: 'Recupera lo que perdiste.',
l2: 'Amor, suerte, limpias y protección.',
subA: 'Resultados con método y seriedad, no promesas vacías.',
subB: ' Primera consulta gratis.',
cta: 'Empezar ahora',
},
};
/* ============================================================
NAVBAR — fixed, glass pill center, collapses <768px
============================================================ */
const Navbar = () => {
const [open, setOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
document.body.style.overflow = open ? 'hidden' : '';
return () => { document.body.style.overflow = ''; };
}, [open]);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 24);
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
return () => window.removeEventListener('scroll', onScroll);
}, []);
return (
QUIERO MI CONSULTA
{open && (
)}
);
};
/* ============================================================
VIDEO BACKGROUND — ping-pong + GSAP mouse parallax
============================================================ */
const VideoBackground = ({ motion = 'equilibrado' }) => {
const cfg = MOTION[motion] || MOTION.equilibrado;
const wrapRef = useRef(null);
const videoRef = useRef(null);
// Ping-pong playback
useEffect(() => {
const v = videoRef.current;
if (!v) return;
let raf, last = 0, dir = 1;
const RATE = 1; // reverse scrub speed multiplier
const onLoaded = () => { v.playbackRate = 1; v.play().catch(() => {}); };
const onEnded = () => {
// switch to reverse scrub from the end
dir = -1;
v.pause();
last = performance.now();
raf = requestAnimationFrame(scrub);
};
const scrub = (now) => {
const dt = (now - last) / 1000;
last = now;
if (dir === -1) {
v.currentTime = Math.max(0, v.currentTime - dt * RATE);
if (v.currentTime <= 0.02) {
dir = 1;
v.currentTime = 0;
v.play().catch(() => {});
return; // native forward takes over; ended fires again
}
raf = requestAnimationFrame(scrub);
}
};
v.addEventListener('loadeddata', onLoaded);
v.addEventListener('ended', onEnded);
if (v.readyState >= 2) onLoaded();
return () => {
cancelAnimationFrame(raf);
v.removeEventListener('loadeddata', onLoaded);
v.removeEventListener('ended', onEnded);
};
}, []);
// GSAP mouse parallax with lerp
useEffect(() => {
const el = wrapRef.current;
if (!el || typeof gsap === 'undefined') return;
let targetX = 0, targetY = 0, curX = 0, curY = 0, raf;
const cx = window.innerWidth / 2, cy = window.innerHeight / 2;
const onMove = (e) => {
targetX = ((e.clientX - cx) / cx) * cfg.amp;
targetY = ((e.clientY - cy) / cy) * cfg.amp;
};
const tick = () => {
curX += (targetX - curX) * 0.06;
curY += (targetY - curY) * 0.06;
gsap.set(el, { x: curX, y: curY });
raf = requestAnimationFrame(tick);
};
window.addEventListener('mousemove', onMove);
raf = requestAnimationFrame(tick);
return () => { window.removeEventListener('mousemove', onMove); cancelAnimationFrame(raf); };
}, [cfg.amp]);
return (
);
};
/* ============================================================
HERO
============================================================ */
const Hero = ({ tone = 'sereno', motion = 'equilibrado' }) => {
const c = COPY[tone] || COPY.sereno;
const [shown, setShown] = useState(false);
useEffect(() => { const t = setTimeout(() => setShown(true), 80); return () => clearTimeout(t); }, []);
const base = 'transition-all duration-1000 ease-[cubic-bezier(.2,.8,.2,1)]';
const on = shown ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6';
return (
{/* Headline */}
{c.l1}
{c.l2}
{/* Bottom block */}
);
};
Object.assign(window, { Navbar, Hero, VideoBackground });