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>
47 lines
1.4 KiB
TypeScript
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>
|
|
);
|
|
}
|