// SR SOFT — Animation utilities: cursor spotlight, aurora bg, // magnetic buttons, animated counter, tech marquee, hero parallax, // method timeline progress. const { useEffect, useRef, useState } = React; // ── Global cursor spotlight (subtle radial glow follows pointer) ── function CursorSpot() { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; let tx = window.innerWidth / 2, ty = window.innerHeight / 2; let cx = tx, cy = ty; let raf; const onMove = (e) => { tx = e.clientX; ty = e.clientY; }; const tick = () => { cx += (tx - cx) * 0.12; cy += (ty - cy) * 0.12; el.style.transform = `translate3d(${cx}px, ${cy}px, 0)`; raf = requestAnimationFrame(tick); }; window.addEventListener("pointermove", onMove, { passive: true }); tick(); return () => { window.removeEventListener("pointermove", onMove); cancelAnimationFrame(raf); }; }, []); return
; } // ── Aurora background ─────────────────────────────────────────── function Aurora() { return ; } // ── Magnetic button — attracts toward cursor ──────────────────── function useMagnetic(ref, strength = 0.35) { useEffect(() => { const el = ref.current; if (!el) return; let raf, tx = 0, ty = 0, cx = 0, cy = 0; const onMove = (e) => { const r = el.getBoundingClientRect(); tx = (e.clientX - (r.left + r.width / 2)) * strength; ty = (e.clientY - (r.top + r.height / 2)) * strength; }; const onLeave = () => { tx = 0; ty = 0; }; const tick = () => { cx += (tx - cx) * 0.18; cy += (ty - cy) * 0.18; el.style.transform = `translate3d(${cx}px, ${cy}px, 0)`; raf = requestAnimationFrame(tick); }; el.addEventListener("pointermove", onMove); el.addEventListener("pointerleave", onLeave); tick(); return () => { el.removeEventListener("pointermove", onMove); el.removeEventListener("pointerleave", onLeave); cancelAnimationFrame(raf); }; }, [ref, strength]); } function MagneticBtn({ href, children, className = "btn", style }) { const ref = useRef(null); useMagnetic(ref, 0.25); return ( {children} ); } // ── Animated counter (counts up when scrolled into view) ──────── function AnimatedCounter({ to, suffix = "", prefix = "", duration = 1600, decimals = 0 }) { const ref = useRef(null); const [v, setV] = useState(0); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { const start = performance.now(); const tick = (t) => { const k = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - k, 3); setV(to * eased); if (k < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); io.unobserve(el); } }); }, { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, [to, duration]); const fmt = decimals > 0 ? v.toFixed(decimals) : Math.round(v).toLocaleString("it-IT"); return {prefix}{fmt}{suffix}; } // ── Tech marquee (infinite horizontal scroll) ─────────────────── function TechMarquee({ items }) { const doubled = [...items, ...items]; return ( ); } // ── Pointer parallax tilt for hero visual ─────────────────────── function usePointerParallax(ref, depth = 12) { useEffect(() => { const el = ref.current; if (!el) return; let raf, tx = 0, ty = 0, cx = 0, cy = 0; const layers = el.querySelectorAll("[data-depth]"); const onMove = (e) => { const r = el.getBoundingClientRect(); tx = ((e.clientX - (r.left + r.width / 2)) / r.width) * 2; ty = ((e.clientY - (r.top + r.height / 2)) / r.height) * 2; }; const onLeave = () => { tx = 0; ty = 0; }; const tick = () => { cx += (tx - cx) * 0.08; cy += (ty - cy) * 0.08; layers.forEach((l) => { const d = parseFloat(l.dataset.depth || "1"); l.style.transform = `translate3d(${cx * depth * d}px, ${cy * depth * d}px, 0) rotateY(${cx * d * 1.2}deg) rotateX(${-cy * d * 1.2}deg)`; }); raf = requestAnimationFrame(tick); }; el.addEventListener("pointermove", onMove); el.addEventListener("pointerleave", onLeave); tick(); return () => { el.removeEventListener("pointermove", onMove); el.removeEventListener("pointerleave", onLeave); cancelAnimationFrame(raf); }; }, [ref, depth]); } // ── Method timeline scroll progress ───────────────────────────── function useMethodProgress() { useEffect(() => { const rail = document.querySelector(".method-rail"); if (!rail) return; const update = () => { const r = rail.getBoundingClientRect(); const vh = window.innerHeight; const start = vh * 0.85; const end = vh * 0.25; const total = r.height; const traveled = start - r.top; const visibleLen = total + (start - end); const p = Math.max(0, Math.min(1, traveled / visibleLen)); rail.style.setProperty("--method-progress", `${p * 100}%`); }; update(); window.addEventListener("scroll", update, { passive: true }); window.addEventListener("resize", update); return () => { window.removeEventListener("scroll", update); window.removeEventListener("resize", update); }; }, []); } // Export Object.assign(window, { CursorSpot, Aurora, MagneticBtn, AnimatedCounter, TechMarquee, usePointerParallax, useMethodProgress });