Files
Creeer-Web/lib/motion.ts
T
erikghandClaude Opus 5 a200346780 chore: retira Carrusel, sin consumidores desde que el recorrido se apila
PuntosInflexion era el único que lo montaba. Se va también la referencia
que le quedaba colgando en el comentario de useAutoStep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:15:39 -06:00

87 lines
3.0 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
import type { Variants } from 'framer-motion';
// EaseOutExpo — smooth editorial deceleration
export const easeOutExpo = [0.22, 1, 0.36, 1] as const;
export const fadeInUp: Variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: easeOutExpo } },
};
export const staggerContainer: Variants = {
hidden: {},
visible: { transition: { staggerChildren: 0.1 } },
};
export const viewportOnce = { once: true, amount: 0.3 } as const;
// Hook: returns true if user prefers reduced motion. Components use this to
// disable Framer Motion animations (per spec, a11y requirement).
export function usePrefersReducedMotion() {
const [reduced, setReduced] = useState(false);
useEffect(() => {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
setReduced(mq.matches);
const handler = () => setReduced(mq.matches);
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}, []);
return reduced;
}
// Shared "draw once when scrolled into view" wiring for the SVG illustrations.
// Returns the ref to attach, whether the element has been seen, and the
// reduced-motion flag the caller needs anyway to pick its render branch.
// Observes nothing under reduced motion — those branches render statically.
export function useEnVista(umbral = 0.4) {
const reduced = usePrefersReducedMotion();
const ref = useRef<SVGSVGElement>(null);
const [enVista, setEnVista] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el || reduced) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setEnVista(true);
observer.unobserve(el);
}
},
{ threshold: umbral },
);
observer.observe(el);
return () => observer.disconnect();
}, [reduced, umbral]);
return { ref, enVista, reduced };
}
// Pass to <motion.*> as motion-reduced guard. When reduced, variants resolve to visible immediately.
export const motionProps = (reduced: boolean) =>
reduced
? { initial: 'visible' as const, animate: 'visible' as const, whileInView: undefined, viewport: undefined }
: {};
// Cycles through `count` steps on its own (Mecanismo, ComoEmpezar): each
// manual change restarts the dwell timer, hovering pauses it, and it never
// advances at all for reduced-motion users.
export function useAutoStep(count: number, intervalMs = 3500) {
const reduced = usePrefersReducedMotion();
const [active, setActive] = useState(0);
const [paused, setPaused] = useState(false);
useEffect(() => {
if (reduced || paused || count <= 1) return;
const id = setTimeout(() => setActive((a) => (a + 1) % count), intervalMs);
return () => clearTimeout(id);
}, [active, reduced, paused, count, intervalMs]);
return {
active,
setActive,
hoverProps: { onMouseEnter: () => setPaused(true), onMouseLeave: () => setPaused(false) },
};
}