Files
erikghandClaude Opus 5 5b400cb161 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>
2026-07-31 10:44:28 -06:00

47 lines
1.4 KiB
TypeScript

// "Patrón de puntos" from the Manual de Identidad Visual (recursos visuales).
// Geometry measured off the manual: the dots shrink and tighten from left to
// right, resolving complexity into focus — the same idea as the ≡ symbol.
// Colour comes from `currentColor`, so callers set it with a text-* class.
const R0 = 5.5; // radius of the leftmost dot
const R_DECAY = 0.847; // per column
const S0 = 29; // gap between the first two columns
const S_DECAY = 0.97; // per column
const ROW_GAP = 24.5;
export function PatronPuntos({
rows = 3,
cols = 8,
className = '',
}: {
rows?: number;
cols?: number;
className?: string;
}) {
const columns: { cx: number; r: number }[] = [];
let cx = R0;
for (let i = 0; i < cols; i++) {
const r = R0 * R_DECAY ** i;
columns.push({ cx, r });
cx += S0 * S_DECAY ** i;
}
const last = columns[columns.length - 1];
const width = last.cx + last.r;
const height = (rows - 1) * ROW_GAP + 2 * R0;
return (
<svg
viewBox={`0 0 ${width.toFixed(1)} ${height.toFixed(1)}`}
className={className}
aria-hidden="true"
focusable="false"
>
{Array.from({ length: rows }, (_, row) =>
columns.map(({ cx: x, r }, col) => (
<circle key={`${row}-${col}`} cx={x.toFixed(2)} cy={(R0 + row * ROW_GAP).toFixed(2)} r={r.toFixed(2)} fill="currentColor" />
)),
)}
</svg>
);
}