'use client'; import { useState } from 'react'; import { motion, useMotionValueEvent, useScroll, useSpring, useTransform } from 'framer-motion'; import { usePrefersReducedMotion } from '@/lib/motion'; import { BARRAS_SIMBOLO, VIEWBOX_SIMBOLO } from '@/lib/simbolo'; // The gold spine of the page: one continuous line down the vertical centre, // drawing itself as the reader scrolls and resolving into the ≡ at the close. // It is what turns ten sections into a single route — *claridad · dirección · // crecimiento* made visible, per the Manual de Identidad Visual. // // Centred, never in a side gutter: the brand's through-line belongs in the // middle, on every breakpoint. It sits ABOVE the section backgrounds (z-10) // but BELOW the text, which `Container` lifts to z-20 — so the thread reads // as a faint watermark spine behind the reading, never crossing it. // // Page-level useScroll() with no target ref on purpose: no element measuring, // no "container has a static position" warnings, one listener for the page. const TRAMOS = 4; const ALTO = 1200; const PASO = ALTO / TRAMOS; // A band, not a thread. Held in real pixels by vector-effect below, so it never // again collapses to a hairline when the viewBox scale changes. const GROSOR = 14; // Four long swells rather than one page-long curve or ten short ones. Stretched // over the whole document with preserveAspectRatio="none", a single swell reads // as a straight rule and ten read as a fidget; four put roughly one screen's // worth of travel — some 180px across the column — into every viewport. const TRAZO = Array.from({ length: TRAMOS }, (_, i) => { const y = i * PASO; const x = i % 2 === 0 ? 72 : 28; // The final bend straightens as it lands so the thread enters the ≡ vertically. const salida = i === TRAMOS - 1 ? 50 : x; return `C ${x} ${y + PASO * 0.25}, ${salida} ${y + PASO * 0.75}, 50 ${y + PASO}`; }).reduce((d, tramo) => `${d} ${tramo}`, 'M50 0'); // The draw finishes at 85% of the document instead of at the very bottom: // scrollYProgress only reaches 1 past the footer, so tying the thread to it // left the tip trailing in mid-screen and the ending never seen. const FIN = 0.85; export function HiloConductor({ className = '' }: { className?: string }) { const reduced = usePrefersReducedMotion(); const { scrollYProgress } = useScroll(); const recorrido = useTransform(scrollYProgress, [0, FIN], [0, 1], { clamp: true }); const avance = useSpring(recorrido, { stiffness: 90, damping: 30, restDelta: 0.001 }); const [dibujado, setDibujado] = useState(false); useMotionValueEvent(avance, 'change', (v) => { if (v > 0.98) setDibujado(true); }); const resuelto = reduced || dibujado; const wrapper = `pointer-events-none absolute inset-0 z-10 ${className}`; // Plain for reduced motion: motion.path applies dash scaffolding even // without initial/animate and would leave the thread invisible. // vector-effect keeps the stroke at its declared width — the non-uniform // viewBox scale squeezes a vertical line's stroke by the x factor alone. const trazo = reduced ? ( ) : ( ); return (
{/* Mirrors Container's own centring (`mx-auto max-w-content`) so the thread is measured from the real column edge and stays centred in it. */}
{/* The ending. The line arrives and becomes the mark — same geometry as the wordmark's ≡, never a lookalike. Uniformly scaled in its own svg, because the band's stretch would flatten the bars. */}
); }