Files
Creeer-Web/components/primitives/SimboloTresLineas.tsx
T
erikghandClaude Opus 5 a9758a92f9 feat(identidad): logos SVG oficiales, wordmark trazado y tokens del manual
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>
2026-07-31 10:44:20 -06:00

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>
);
}