feat(primitivas): capa gráfica y de movimiento reutilizable
Diez primitivas que dan a la landing el "gráfico y movimiento" que pidió la dueña, todas con guarda de prefers-reduced-motion: - FondoPuntos / PatronPuntos — el patrón de puntos del manual - TarjetaSpotlight, TextoDesenfoque, ContadorNumero — realce y reveal - Carrusel — deslizable, con contador, autoplay pausable - Icono, SimboloTresLineas, IlustracionInflexion — línea, sin relleno - FondoFoto — duotono azul/dorado sobre foto useAutoStep en lib/motion.ts comparte la forma del autoplay del carrusel para indicadores de paso que no son carrusel. IlustracionInflexion documenta un detalle que muerde: motion.path aplica su propio andamiaje de dasharray aunque no reciba initial/animate, así que la rama de reduced motion renderiza <path> planos en vez de intentar neutralizar los animados. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9758a92f9
commit
5b400cb161
19 files changed
+881
No files matched your search
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { motion, useMotionValue, useScroll, useSpring, useTransform } from 'framer-motion';
|
||||
import { PatronPuntos } from './PatronPuntos';
|
||||
import { usePrefersReducedMotion } from '@/lib/motion';
|
||||
|
||||
// Decorative background: the manual's dot pattern, drifting slightly against
|
||||
// the scroll. Purely presentational — hidden from assistive tech, and it holds
|
||||
// still for anyone who asked for less movement.
|
||||
export function FondoPuntos({
|
||||
className = '',
|
||||
distance = 40,
|
||||
rows,
|
||||
cols,
|
||||
interactivo = false,
|
||||
}: {
|
||||
className?: string;
|
||||
distance?: number;
|
||||
rows?: number;
|
||||
cols?: number;
|
||||
// Además de derivar con el scroll, sigue el cursor levemente — reservado
|
||||
// para los patrones más prominentes (el hero), no para cada instancia.
|
||||
interactivo?: boolean;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const reduced = usePrefersReducedMotion();
|
||||
const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'] });
|
||||
const scrollY = useTransform(scrollYProgress, [0, 1], [distance, -distance]);
|
||||
|
||||
const mouseXRaw = useMotionValue(0);
|
||||
const mouseYRaw = useMotionValue(0);
|
||||
const mouseX = useSpring(mouseXRaw, { stiffness: 40, damping: 15 });
|
||||
const mouseY = useSpring(mouseYRaw, { stiffness: 40, damping: 15 });
|
||||
|
||||
useEffect(() => {
|
||||
if (!interactivo || reduced) return;
|
||||
const handler = (e: MouseEvent) => {
|
||||
mouseXRaw.set((e.clientX / window.innerWidth - 0.5) * 16);
|
||||
mouseYRaw.set((e.clientY / window.innerHeight - 0.5) * 16);
|
||||
};
|
||||
window.addEventListener('mousemove', handler);
|
||||
return () => window.removeEventListener('mousemove', handler);
|
||||
}, [interactivo, reduced, mouseXRaw, mouseYRaw]);
|
||||
|
||||
const y = useTransform([scrollY, mouseY], ([a, b]) => (a as number) + (b as number));
|
||||
|
||||
return (
|
||||
<div ref={ref} aria-hidden="true" className={`pointer-events-none absolute select-none ${className}`}>
|
||||
<motion.div style={reduced ? undefined : { y, x: interactivo ? mouseX : undefined }}>
|
||||
<PatronPuntos rows={rows} cols={cols} className="w-full h-auto" />
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user