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:
erikghandClaude Opus 5 committed 2026-07-31 10:44:28 -06:00
1 parent a9758a92f9
commit 5b400cb161
19 files changed
+881

No files matched your search

@@ -0,0 +1,21 @@
import { render, fireEvent } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { FondoPuntos } from '../FondoPuntos';
describe('<FondoPuntos />', () => {
it('is decorative: hidden from assistive tech', () => {
const { container } = render(<FondoPuntos />);
expect(container.firstElementChild).toHaveAttribute('aria-hidden', 'true');
});
it('renders the dot pattern', () => {
const { container } = render(<FondoPuntos />);
expect(container.querySelectorAll('circle').length).toBeGreaterThan(0);
});
it('does not throw when the cursor moves, with or without interactivo', () => {
const { container } = render(<FondoPuntos interactivo />);
expect(() => fireEvent.mouseMove(window, { clientX: 100, clientY: 100 })).not.toThrow();
expect(container.querySelectorAll('circle').length).toBeGreaterThan(0);
});
});