El wordmark deja de depender del glifo tipográfico ≡ y pasa a un trazado SVG propio (SimboloTresLineas), que es lo que permite cumplir la instrucción de la dueña: que se lea como E, no como tres líneas sueltas. Incluye las tres versiones del logotipo (principal, negativa, secundaria) y alinea los tokens de color con el Manual de Identidad Visual v1.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import { useRef, useState, useEffect } from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import { usePrefersReducedMotion } from '@/lib/motion';
|
|
|
|
// The manual's own description of the ≡ symbol, made literal: "las líneas van
|
|
// de muchas/desordenadas (complejidad) a pocas/alineadas (claridad)". Five
|
|
// uneven bars resolve, once in view, into the three clean aligned bars of the
|
|
// official icon (see app/icon.svg) — decorative, one-shot, never loops.
|
|
const FINAL = [
|
|
{ x: 13, y: 13, width: 38, height: 8 },
|
|
{ x: 13, y: 28, width: 38, height: 8 },
|
|
{ x: 13, y: 43, width: 38, height: 8 },
|
|
];
|
|
|
|
const BARS = [
|
|
{ from: { x: 6, y: 9, width: 22, height: 6, opacity: 0.9 }, to: FINAL[0] },
|
|
{ from: { x: 36, y: 15, width: 16, height: 5, opacity: 0.45 }, to: { x: 44, y: 17, width: 8, height: 5, opacity: 0 } },
|
|
{ from: { x: 16, y: 25, width: 30, height: 7, opacity: 0.9 }, to: FINAL[1] },
|
|
{ from: { x: 8, y: 37, width: 14, height: 5, opacity: 0.45 }, to: { x: 12, y: 39, width: 8, height: 5, opacity: 0 } },
|
|
{ from: { x: 20, y: 45, width: 26, height: 6, opacity: 0.9 }, to: FINAL[2] },
|
|
];
|
|
|
|
export function SimboloTresLineas({ className = '' }: { className?: string }) {
|
|
const reduced = usePrefersReducedMotion();
|
|
const ref = useRef<SVGSVGElement>(null);
|
|
const [inView, setInView] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const el = ref.current;
|
|
if (!el || reduced) return;
|
|
const observer = new IntersectionObserver(
|
|
([entry]) => {
|
|
if (entry.isIntersecting) {
|
|
setInView(true);
|
|
observer.unobserve(el);
|
|
}
|
|
},
|
|
{ threshold: 0.4 },
|
|
);
|
|
observer.observe(el);
|
|
return () => observer.disconnect();
|
|
}, [reduced]);
|
|
|
|
return (
|
|
<svg ref={ref} viewBox="0 0 64 64" className={className} aria-hidden="true" focusable="false">
|
|
{BARS.map((bar, i) =>
|
|
reduced ? (
|
|
<rect key={i} {...bar.to} opacity={i === 1 || i === 3 ? 0 : 1} rx={1.5} fill="currentColor" />
|
|
) : (
|
|
<motion.rect
|
|
key={i}
|
|
rx={1.5}
|
|
fill="currentColor"
|
|
initial={bar.from}
|
|
animate={inView ? bar.to : bar.from}
|
|
transition={{ duration: 0.7, delay: i * 0.08, ease: [0.22, 1, 0.36, 1] }}
|
|
/>
|
|
),
|
|
)}
|
|
</svg>
|
|
);
|
|
}
|