Files
Creeer-Web/components/primitives/HiloConductor.tsx
T
erikgh 0239d4ac15 fix(grafico): HiloConductor a la banda lateral, con stacking explícito
El hilo pintaba encima del texto en las secciones sin position:relative
(Dolores, Disciplinas, Estratega, ComoEmpezar) y detrás del texto en las
que sí la tienen, por el orden de pintado del navegador. Se mueve a la
banda vacía a la izquierda de la columna max-w-content (oculto bajo 2xl,
donde no hay margen seguro) y gana un z-index explícito por debajo del
header.
2026-07-31 11:25:34 -06:00

79 lines
3.3 KiB
TypeScript

'use client';
import { motion, useScroll, useSpring } from 'framer-motion';
import { usePrefersReducedMotion } from '@/lib/motion';
// One continuous gold line running the height of the page, drawing itself as
// the reader scrolls. It is the spine of the layout: it stitches the sections
// together so the landing reads as a route rather than a stack of blocks.
//
// Positioned `absolute`, never `fixed` — the thread belongs to the document
// and scrolls with it. A fixed layer would read as an ornament stuck to the
// window.
//
// Uses the page-level useScroll() with no target ref on purpose: no element
// measuring, no "container has a static position" warnings, and one scroll
// listener for the whole page.
//
// It lives in the empty margin band to the left of the centred
// `max-w-content` (1152px) column, never over it — some sections are
// `position: relative` for unrelated reasons and some are not, so an
// absolutely-positioned overlay with no explicit stacking level paints
// inconsistently: above the static sections, behind the relative ones. `z-30`
// gives it its own stacking context that beats every section root regardless
// of that accident, while staying under the sticky header's `z-50`.
//
// Hidden below `2xl` (1536px): that's the first breakpoint that guarantees a
// safe margin outside the 1152px column — (1536 - 1152) / 2 = 192px per side,
// comfortable room for the thread's own offset plus breathing space to the
// viewport edge. Narrower viewports have no safe band, so the thread simply
// doesn't render there rather than creeping over the text.
const TRAZO =
'M50 0 C 20 140, 80 260, 50 400 C 20 540, 80 660, 50 800 C 20 940, 80 1060, 50 1200';
export function HiloConductor({ className = '' }: { className?: string }) {
const reduced = usePrefersReducedMotion();
const { scrollYProgress } = useScroll();
const avance = useSpring(scrollYProgress, { stiffness: 90, damping: 30, restDelta: 0.001 });
// Percentage-based band geometry against the containing block, never
// `100vw` — `100vw` includes the scrollbar and would overshoot the real
// document width by ~15px on desktop. `inset-0` and the inner `mx-auto
// max-w-content` box both resolve against `<main>`'s own width instead.
const wrapper = `pointer-events-none absolute inset-0 z-30 hidden 2xl:block ${className}`;
// Plain <path> for reduced motion: motion.path applies dash scaffolding even
// without initial/animate and would leave the thread invisible.
const trazo = reduced ? (
<path d={TRAZO} fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" />
) : (
<motion.path
d={TRAZO}
fill="none"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
style={{ pathLength: avance }}
/>
);
return (
<div className={wrapper}>
{/* Mirrors Container's own centring (`mx-auto max-w-content`) so the
band below is measured from the real column edge. */}
<div className="relative mx-auto h-full w-full max-w-content">
<svg
className="absolute inset-y-0 right-full mr-10 h-full w-14 text-dorado/25"
viewBox="0 0 100 1200"
preserveAspectRatio="none"
aria-hidden="true"
focusable="false"
>
{trazo}
</svg>
</div>
</div>
);
}