feat(grafico): SecuenciaClaridad — la metáfora del manual, dibujada
This commit is contained in:
1 parent
1b1b39c67f
commit
6783e32324
2 files changed
+167
No files matched your search
@@ -0,0 +1,134 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useEnVista } from '@/lib/motion';
|
||||
import { VIEWBOX_SIMBOLO, LINEAS_SIMBOLO } from '@/lib/simbolo';
|
||||
|
||||
// The manual's core metaphor, finally drawn: Complejidad → Enfoque → Claridad.
|
||||
// Eighteen crossing lines converge in three groups; in the "completa" variant
|
||||
// each group collapses onto one of the three bars of the ≡, so the mark is
|
||||
// literally produced by the resolution of the tangle rather than placed next
|
||||
// to it. The "hero" variant stops at convergence and leaves the tension open —
|
||||
// FraseAncla pays it off further down the page.
|
||||
|
||||
type Variant = 'completa' | 'hero';
|
||||
|
||||
// Scattered start positions. Hand-placed rather than random so the tangle is
|
||||
// identical on every render and between server and client.
|
||||
const DISPERSAS = [
|
||||
{ x1: 2, y1: 8, x2: 40, y2: 3, barra: 0 },
|
||||
{ x1: 20, y1: 2, x2: 61, y2: 24, barra: 0 },
|
||||
{ x1: 6, y1: 22, x2: 52, y2: 6, barra: 0 },
|
||||
{ x1: 34, y1: 5, x2: 12, y2: 27, barra: 0 },
|
||||
{ x1: 48, y1: 2, x2: 58, y2: 30, barra: 0 },
|
||||
{ x1: 3, y1: 15, x2: 46, y2: 19, barra: 0 },
|
||||
{ x1: 5, y1: 30, x2: 60, y2: 26, barra: 1 },
|
||||
{ x1: 14, y1: 40, x2: 55, y2: 20, barra: 1 },
|
||||
{ x1: 2, y1: 26, x2: 38, y2: 44, barra: 1 },
|
||||
{ x1: 44, y1: 22, x2: 18, y2: 41, barra: 1 },
|
||||
{ x1: 30, y1: 18, x2: 62, y2: 40, barra: 1 },
|
||||
{ x1: 8, y1: 36, x2: 50, y2: 33, barra: 1 },
|
||||
{ x1: 4, y1: 44, x2: 47, y2: 58, barra: 2 },
|
||||
{ x1: 24, y1: 62, x2: 60, y2: 42, barra: 2 },
|
||||
{ x1: 10, y1: 56, x2: 56, y2: 50, barra: 2 },
|
||||
{ x1: 40, y1: 61, x2: 14, y2: 46, barra: 2 },
|
||||
{ x1: 2, y1: 50, x2: 44, y2: 62, barra: 2 },
|
||||
{ x1: 33, y1: 44, x2: 62, y2: 57, barra: 2 },
|
||||
];
|
||||
|
||||
// One line per group survives to become a bar of the ≡; the rest fade as they
|
||||
// converge — many/disordered becomes few/aligned, per the manual.
|
||||
const SOBREVIVE = [0, 6, 12];
|
||||
|
||||
const EASE = [0.22, 1, 0.36, 1] as const;
|
||||
|
||||
export function SecuenciaClaridad({
|
||||
variant = 'completa',
|
||||
className = '',
|
||||
}: {
|
||||
variant?: Variant;
|
||||
className?: string;
|
||||
}) {
|
||||
const { ref, enVista, reduced } = useEnVista(0.35);
|
||||
|
||||
const completa = variant === 'completa';
|
||||
|
||||
// Where each line ends up. In "completa" the survivors land exactly on the
|
||||
// symbol's own coordinates; in "hero" they merely tighten toward their
|
||||
// group's axis without ever aligning.
|
||||
const destino = (i: number) => {
|
||||
const { barra } = DISPERSAS[i];
|
||||
const linea = LINEAS_SIMBOLO[barra];
|
||||
const sobrevive = SOBREVIVE.includes(i);
|
||||
if (!completa) {
|
||||
const eje = linea.y1;
|
||||
const d = DISPERSAS[i];
|
||||
return {
|
||||
x1: d.x1 + (26 - d.x1) * 0.35,
|
||||
y1: d.y1 + (eje - d.y1) * 0.55,
|
||||
x2: d.x2 + (38 - d.x2) * 0.35,
|
||||
y2: d.y2 + (eje - d.y2) * 0.55,
|
||||
opacity: sobrevive ? 0.95 : 0.3,
|
||||
};
|
||||
}
|
||||
return {
|
||||
x1: linea.x1,
|
||||
y1: linea.y1,
|
||||
x2: linea.x2,
|
||||
y2: linea.y2,
|
||||
opacity: sobrevive ? 1 : 0,
|
||||
};
|
||||
};
|
||||
|
||||
const grosor = (i: number) => (completa && SOBREVIVE.includes(i) ? LINEAS_SIMBOLO[0].grosor : 1.5);
|
||||
|
||||
// Reduced motion renders plain <line> elements: motion.line would apply its
|
||||
// own dash scaffolding and leave the drawing invisible (see the same trap in
|
||||
// IlustracionInflexion).
|
||||
if (reduced) {
|
||||
return (
|
||||
<svg ref={ref} viewBox={VIEWBOX_SIMBOLO} className={className} aria-hidden="true" focusable="false">
|
||||
{DISPERSAS.map((_, i) => {
|
||||
const d = destino(i);
|
||||
const visible = completa ? SOBREVIVE.includes(i) : true;
|
||||
return (
|
||||
<line
|
||||
key={i}
|
||||
{...(completa && SOBREVIVE.includes(i) ? { 'data-resolucion': true } : {})}
|
||||
x1={d.x1}
|
||||
y1={d.y1}
|
||||
x2={d.x2}
|
||||
y2={d.y2}
|
||||
stroke="currentColor"
|
||||
strokeWidth={grosor(i)}
|
||||
strokeLinecap="round"
|
||||
opacity={visible ? d.opacity : 0}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<svg ref={ref} viewBox={VIEWBOX_SIMBOLO} className={className} aria-hidden="true" focusable="false">
|
||||
{DISPERSAS.map(({ barra: _barra, ...inicio }, i) => {
|
||||
const d = destino(i);
|
||||
// `barra` is grouping metadata, not an SVG attribute — destructured out
|
||||
// so it never reaches the DOM.
|
||||
const reposo = { ...inicio, opacity: 0.55, strokeWidth: 1.5 };
|
||||
return (
|
||||
<motion.line
|
||||
key={i}
|
||||
{...(completa && SOBREVIVE.includes(i) ? { 'data-resolucion': true } : {})}
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
initial={reposo}
|
||||
animate={enVista ? { ...d, strokeWidth: grosor(i) } : reposo}
|
||||
transition={{ duration: 1.2, delay: 0.25 + (i % 6) * 0.05, ease: EASE }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user