Planifica, monitoriza y fideliza a tus pacientes desde una sola plataforma. Automatiza el seguimiento y enfócate en lo que importa.
1 // NutriFlow — HeroSection.tsx 2 // Patrón: useGSAP + scope + contextSafe + SSR-safe 3 4 import { useRef } from "react" 5 import gsap from "gsap" 6 import { useGSAP } from "@gsap/react" 7 8 // ✅ Registrar el plugin una vez, fuera del componente 9 gsap.registerPlugin(useGSAP) 10 11 export default function HeroSection() { 12 13 // ✅ Ref al contenedor raíz para acotar selectores 14 const containerRef = useRef<HTMLElement>(null) 15 const btnRef = useRef<HTMLButtonElement>(null) 16 17 useGSAP((context, contextSafe) => { 18 19 // ── 1. Entrada del título ───────────────────── 20 gsap.from(".hero-title", { 21 opacity: 0, 22 y: 32, 23 duration: 0.8, 24 ease: "power3.out", 25 delay: 0.2 26 }) 27 28 // ── 2. Cards con stagger ────────────────────── 29 gsap.from(".feature-card", { 30 opacity: 0, 31 y: 24, 32 duration: 0.6, 33 stagger: 0.15, 34 ease: "back.out(1.4)", 35 delay: 0.5 36 }) 37 38 // ── 3. Pulse loop en CTA ────────────────────── 39 gsap.to(btnRef.current, { 40 boxShadow: "0 0 0 10px rgba(99,102,241,0.2)", 41 duration: 1.2, 42 repeat: -1, 43 yoyo: true, 44 ease: "sine.inOut", 45 delay: 1.8 46 }) 47 48 // ── 4. contextSafe para click handler ───────── 49 // ✅ Animaciones creadas DESPUÉS de useGSAP 50 // deben envolverse en contextSafe() 51 const onCtaClick = contextSafe!(() => { 52 gsap.timeline() 53 .to(btnRef.current, { 54 scale: 0.93, 55 duration: 0.1 56 }) 57 .to(btnRef.current, { 58 scale: 1, 59 duration: 0.4, 60 ease: "elastic.out(1.2, 0.5)" 61 }) 62 }) 63 64 btnRef.current?.addEventListener("click", onCtaClick) 65 66 // ✅ Limpieza del event listener en cleanup 67 return () => { 68 btnRef.current?.removeEventListener("click", onCtaClick) 69 } 70 71 }, { 72 // ✅ scope limita selectores al contenedor 73 scope: containerRef 74 // ✅ Cleanup automático al desmontar el componente 75 // (useGSAP revierte tweens y ScrollTriggers) 76 }) 77 78 return ( 79 <section ref={containerRef} className="hero-section"> 80 <h1 className="hero-title"> 81 Nutrición<br /> 82 <span className="gradient">sin fricción</span> 83 </h1> 84 85 <div className="cards"> 86 {features.map((f) => ( 87 <div key={f.id} className="feature-card"> 88 <span>{f.icon}</span> 89 <strong>{f.title}</strong> 90 </div> 91 ))} 92 </div> 93 94 <button ref={btnRef} className="btn-cta"> 95 Empieza gratis — 14 días → 96 </button> 97 </section> 98 ) 99 } 100