/* QUITOS — Primitives & layout components */ const { useState, useEffect, useRef } = React; /* Lucide icon helper */ function Icon({ name, size = 20, color, strokeWidth = 1.5, style }) { const ref = useRef(null); useEffect(() => { if (window.lucide && ref.current) window.lucide.createIcons({ nameAttr: 'data-lucide' }); }, [name]); return ( ); } /* Scroll-reveal wrapper — GSAP ScrollTrigger */ function Reveal({ children, delay = 0, as: As = 'div', className = '', ...rest }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el || !window.gsap) return; gsap.set(el, { opacity: 0, y: 28 }); const trigger = ScrollTrigger.create({ trigger: el, start: 'top 88%', onEnter: () => { gsap.to(el, { opacity: 1, y: 0, duration: 0.65, delay: delay / 1000, ease: 'power3.out', clearProps: 'opacity,transform', }); }, once: true, }); return () => trigger.kill(); }, [delay]); return {children}; } /* Stagger — reveals direct children one by one */ function Stagger({ children, staggerDelay = 0.1, as: As = 'div', className = '', ...rest }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el || !window.gsap) return; const items = Array.from(el.children); gsap.set(items, { opacity: 0, y: 32 }); const trigger = ScrollTrigger.create({ trigger: el, start: 'top 85%', onEnter: () => { gsap.to(items, { opacity: 1, y: 0, duration: 0.6, ease: 'power3.out', stagger: staggerDelay, clearProps: 'opacity,transform', }); }, once: true, }); return () => trigger.kill(); }, [staggerDelay]); return {children}; } /* Counter-up metric — GSAP tween on scroll */ function Counter({ value, suffix = '', prefix = '', duration = 1.4, formatter }) { const ref = useRef(null); const objRef = useRef({ n: 0 }); const [display, setDisplay] = useState(prefix + "0" + suffix); useEffect(() => { const el = ref.current; if (!el || !window.gsap) return; const trigger = ScrollTrigger.create({ trigger: el, start: 'top 85%', onEnter: () => { gsap.to(objRef.current, { n: value, duration, ease: 'power2.out', onUpdate: () => { const n = Math.round(objRef.current.n); const formatted = formatter ? formatter(n) : n.toLocaleString('fr-FR'); setDisplay(prefix + formatted + suffix); }, }); }, once: true, }); return () => trigger.kill(); }, [value, duration, prefix, suffix, formatter]); return {display}; } /* Top nav */ function Nav({ dark = true }) { const [scrolled, setScrolled] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 12); window.addEventListener('scroll', onScroll, { passive: true }); return () => window.removeEventListener('scroll', onScroll); }, []); const bg = dark ? (scrolled ? 'rgba(10,10,10,0.88)' : 'transparent') : (scrolled ? 'rgba(248,248,246,0.92)' : 'transparent'); const textColor = dark ? '#fff' : '#0A0A0A'; return ( ); } /* Animated brand logo — scroll-trigger appear + hover glow */ function AnimatedBrandLogo({ name, color }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el || !window.gsap) return; gsap.set(el, { opacity: 0, y: 14 }); const trigger = ScrollTrigger.create({ trigger: el, start: 'top 92%', onEnter: () => gsap.to(el, { opacity: 0.45, y: 0, duration: 0.7, ease: 'power3.out' }), once: true, }); return () => trigger.kill(); }, []); return (
{ e.currentTarget.style.opacity = '1'; e.currentTarget.style.filter = `drop-shadow(0 0 10px ${color}88)`; }} onMouseLeave={e => { e.currentTarget.style.opacity = '0.45'; e.currentTarget.style.filter = 'none'; }} > {name}
); } Object.assign(window, { Icon, Reveal, Stagger, Counter, Nav, FeatureIcon, MagneticBtn, AnimatedBrandLogo });