diff --git a/app/page.tsx b/app/page.tsx
index 2d20e92..ec603cc 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,6 +1,5 @@
import { Header } from '@/components/layout/Header';
import { Footer } from '@/components/layout/Footer';
-import { HiloConductor } from '@/components/primitives/HiloConductor';
import { Hero } from '@/components/sections/Hero';
import { FraseAncla } from '@/components/sections/FraseAncla';
import { PuntosInflexion } from '@/components/sections/PuntosInflexion';
@@ -25,8 +24,7 @@ export default function Home() {
<>
-
-
+ {/* dueña puede cambiar a "hormozi" */}
diff --git a/components/primitives/HiloConductor.tsx b/components/primitives/HiloConductor.tsx
deleted file mode 100644
index 5d6df1b..0000000
--- a/components/primitives/HiloConductor.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-'use client';
-
-import { useState } from 'react';
-import { motion, useMotionValueEvent, useScroll, useSpring, useTransform } from 'framer-motion';
-import { usePrefersReducedMotion } from '@/lib/motion';
-import { BARRAS_SIMBOLO, RADIO_BARRA, 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. The attribute
-// is the desktop weight and the SSR fallback; GROSORES overrides it in CSS,
-// because 14px is 3.6% of a 390px screen and cut straight through the body
-// copy there. CSS stroke-width wins over the presentation attribute.
-const GROSOR = 12;
-const GROSORES = '[stroke-width:7px] md:[stroke-width:12px]';
-
-// 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 halfway down the document. Tied to raw scrollYProgress the
-// leading tip sat in mid-viewport for most of the page, reading as an amputated
-// stub — the "unfinished" complaint. Finishing early lets the tip outrun the
-// reader within the hero and leaves a complete spine for everything after.
-const FIN = 0.5;
-
-export function HiloConductor({ className = '' }: { className?: string }) {
- const reduced = usePrefersReducedMotion();
- const { scrollYProgress } = useScroll();
- const recorrido = useTransform(scrollYProgress, [0, FIN], [0, 1], { clamp: true });
- // Stiff enough to keep up with the reader. At 90/30 the spring is heavily
- // overdamped and takes some two seconds to settle, so the leading edge
- // visibly trailed behind the scroll and read as an amputated stub.
- const avance = useSpring(recorrido, { stiffness: 210, damping: 40, restDelta: 0.001 });
- // At rest the round cap still paints its own dot at the path's origin, which
- // landed as a stray blob dead centre at the top of the page on first load.
- const aparicion = useTransform(avance, [0, 0.015], [0, 1]);
-
- 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. */}
-
- {/* Explicit height, never a top+bottom pair: an
-
- );
-}
diff --git a/components/primitives/__tests__/HiloConductor.test.tsx b/components/primitives/__tests__/HiloConductor.test.tsx
deleted file mode 100644
index af8e971..0000000
--- a/components/primitives/__tests__/HiloConductor.test.tsx
+++ /dev/null
@@ -1,168 +0,0 @@
-import { render } from '@testing-library/react';
-import { describe, it, expect, afterEach } from 'vitest';
-import { HiloConductor } from '../HiloConductor';
-import { BARRAS_SIMBOLO, VIEWBOX_SIMBOLO } from '@/lib/simbolo';
-
-const preferirMenosMovimiento = () => {
- window.matchMedia = (query: string) =>
- ({
- matches: true,
- media: query,
- onchange: null,
- addEventListener: () => {},
- removeEventListener: () => {},
- addListener: () => {},
- removeListener: () => {},
- dispatchEvent: () => false,
- }) as unknown as MediaQueryList;
-};
-
-const simboloFinal = (container: HTMLElement) =>
- Array.from(container.querySelectorAll('svg')).find(
- (svg) => svg.getAttribute('viewBox') === VIEWBOX_SIMBOLO,
- );
-
-describe('', () => {
- const matchMediaOriginal = window.matchMedia;
- afterEach(() => {
- window.matchMedia = matchMediaOriginal;
- });
-
- it('is decorative: hidden from assistive tech', () => {
- const { container } = render();
- const svg = container.querySelector('svg');
- expect(svg).toHaveAttribute('aria-hidden', 'true');
- expect(svg).toHaveAttribute('focusable', 'false');
- });
-
- it('draws a single continuous thread', () => {
- const { container } = render();
- expect(container.querySelectorAll('path')).toHaveLength(1);
- });
-
- it('travels with the document instead of sticking to the viewport', () => {
- const { container } = render();
- // `fixed` would read as an ornament pinned to the window; the thread
- // belongs to the page and must scroll with it.
- expect(container.firstElementChild?.className).toContain('absolute');
- expect(container.firstElementChild?.className).not.toContain('fixed');
- });
-
- it('stays out of the way of pointer input', () => {
- const { container } = render();
- expect(container.firstElementChild?.className).toContain('pointer-events-none');
- });
-
- it('runs down the centre of the column, not in a side gutter', () => {
- const { container } = render();
- const cls = container.querySelector('svg')?.getAttribute('class') ?? '';
- // `left-1/2 -translate-x-1/2` anchors the thread to the column's centre;
- // the old `right-full` banished it to the left gutter, which is why the
- // thread read as off to the side instead of as the page's spine.
- expect(cls).toContain('left-1/2');
- expect(cls).toContain('-translate-x-1/2');
- expect(cls).not.toContain('right-full');
- });
-
- it('declares a stacking level that beats section roots but stays under the header', () => {
- const { container } = render();
- const wrapperClass = container.firstElementChild?.className ?? '';
- // Section roots never set z-index (auto), so any explicit positive value
- // paints above them regardless of whether the section itself happens to
- // be `position: relative` or `static` — that inconsistency was the bug.
- // The sticky header is z-50 and must still win.
- expect(wrapperClass).toMatch(/\bz-\d+\b/);
- expect(wrapperClass).not.toContain('z-50');
- });
-
- it('renders on every breakpoint, not only on very wide screens', () => {
- const { container } = render();
- const wrapperClass = container.firstElementChild?.className ?? '';
- // The thread is the brand's through-line; it belongs in the middle on a
- // phone too. Legibility is handled by layering it behind the text, not by
- // hiding it below a breakpoint.
- expect(wrapperClass).not.toContain('hidden');
- expect(wrapperClass).not.toContain('2xl:block');
- });
-
- it('keeps its declared weight on screen despite the non-uniform viewBox scale', () => {
- const { container } = render();
- // The band is stretched with preserveAspectRatio="none": y over the whole
- // document, x over a couple hundred pixels. That squeezes the stroke of a
- // near-vertical line by the x factor alone, so a declared 1.5 rendered as
- // well under 1 real pixel — a ghost hairline. non-scaling-stroke pins the
- // width to the value we actually chose.
- expect(container.querySelector('path')).toHaveAttribute('vector-effect', 'non-scaling-stroke');
- });
-
- it('sizes the band with an explicit height, never a top+bottom pair', () => {
- const { container } = render();
- const cls = container.querySelector('svg')?.getAttribute('class') ?? '';
- // An carrying a viewBox is a replaced element with an intrinsic
- // ratio, so `height: auto` resolves from the width and silently beats
- // `bottom` — which stretched this one to 13824px and hung ~6000px of blank
- // scroll below the footer. jsdom has no layout, so the class is the guard.
- expect(cls).toMatch(/\bh-\[/);
- expect(cls).not.toMatch(/\bbottom-/);
- });
-
- it('reads as a band, not as a hairline', () => {
- const { container } = render();
- const path = container.querySelector('path');
- // Twice regressed into a thread nobody could see. The band is the page's
- // spine and has to carry weight; the text stays legible because it sits a
- // stacking level above, not because the band is faint. The attribute is the
- // desktop weight, the classes narrow it on phones where 12px is 3% of the
- // screen — CSS stroke-width beats the presentation attribute.
- expect(Number(path?.getAttribute('stroke-width'))).toBeGreaterThanOrEqual(10);
- expect(path?.getAttribute('class') ?? '').toMatch(/md:\[stroke-width:/);
- });
-
- it('sweeps wide enough across the column to read as a route', () => {
- const { container } = render();
- const d = container.querySelector('path')?.getAttribute('d') ?? '';
- // Coordinates come in x/y pairs, so the even ones are the horizontal
- // excursion, in a 0–100 viewBox stretched across the content column.
- // Too little travel and the swell collapses into a straight rule.
- const numeros = (d.match(/-?\d+(?:\.\d+)?/g) ?? []).map(Number);
- const xs = numeros.filter((_, i) => i % 2 === 0);
- expect(Math.max(...xs) - Math.min(...xs)).toBeGreaterThanOrEqual(30);
- expect((d.match(/C/g) ?? []).length).toBeGreaterThanOrEqual(3);
- });
-
- it('resolves into the ≡ of the wordmark instead of stopping in mid-air', () => {
- const { container } = render();
- const simbolo = simboloFinal(container);
- // Per the design: the thread "resuelve en el ≡ del wordmark en el cierre".
- // It reuses the brand geometry rather than drawing a lookalike.
- expect(simbolo).toBeDefined();
- expect(simbolo?.querySelectorAll('rect')).toHaveLength(BARRAS_SIMBOLO.length);
- });
-
- it('shows the resolved ≡ from the start under reduced motion', () => {
- preferirMenosMovimiento();
-
- const { container } = render();
- const barras = Array.from(simboloFinal(container)?.querySelectorAll('rect') ?? []);
- // These readers never get the scroll draw that reveals the mark, so the
- // ending has to be there already — otherwise the thread ends in nothing.
- expect(barras).toHaveLength(BARRAS_SIMBOLO.length);
- barras.forEach((barra) => {
- expect(barra.getAttribute('style') ?? '').not.toContain('opacity: 0');
- });
- });
-
- it('renders a genuine plain path under reduced motion, not a neutralised motion.path', () => {
- preferirMenosMovimiento();
-
- const { container } = render();
- const path = container.querySelector('path');
- // motion.path stamps stroke-dasharray/stroke-dashoffset onto the DOM even
- // without initial/animate — the dash-scaffolding trap that leaves the
- // thread invisible for reduced-motion users. A real plain never
- // gets them, so their absence proves the branches weren't collapsed back
- // into one.
- expect(path).not.toHaveAttribute('stroke-dasharray');
- expect(path).not.toHaveAttribute('stroke-dashoffset');
- });
-});
diff --git a/lib/simbolo.ts b/lib/simbolo.ts
index 5ec0d56..0345bae 100644
--- a/lib/simbolo.ts
+++ b/lib/simbolo.ts
@@ -1,7 +1,9 @@
// Single source of truth for the ≡ glyph geometry. Everything that draws the
-// mark reads from here — HiloConductor and SecuenciaClaridad resolve into it,
-// the former with , the latter with — so the symbol they produce
-// is provably the same one and not a lookalike.
+// mark reads from here — today that is SecuenciaClaridad, which resolves its
+// tangle into it — so the symbol it produces is provably the same one and not a
+// lookalike. BARRAS_SIMBOLO stays the canonical form even with no one drawing
+// the bars directly: the strokes below are derived from it, and the test that
+// holds the two representations to each other is what keeps them honest.
//
// The proportions are the Manual de Identidad Visual's, already measured and
// traced in Wordmark.tsx: bar length = 5.919 × thickness, gap = 2 × thickness,
@@ -17,7 +19,6 @@ const LARGO_POR_GROSOR = 5.919;
const HUECO_POR_GROSOR = 2;
export const GROSOR_BARRA = 8;
-export const RADIO_BARRA = GROSOR_BARRA / 2;
const LARGO_BARRA = GROSOR_BARRA * LARGO_POR_GROSOR;
const HUECO = GROSOR_BARRA * HUECO_POR_GROSOR;