feat(diseño): los diez arreglos de la revisión visual

Revisada la página con Chromium a 1440x900 y 390x844 en nueve alturas de
scroll, y verificado cada cambio contra capturas nuevas.

- El ≡ deja de leerse como icono de menú. lib/simbolo.ts se anunciaba como
  single source of truth pero no coincidía con Wordmark.tsx, que traza la
  marca con las proporciones medidas del Manual: barra de largo 5.919× su
  grosor, hueco de 2×, extremos de píldora. Ahora se derivan de ahí. De
  paso, las líneas se meten medio grosor por cada extremo: el remate
  redondo alargaba el trazo un grosor entero sobre la barra que debía
  reproducir.
- Banda: 12px en escritorio y 7px en móvil vía CSS (14px era el 3,6% de
  una pantalla de 390px y partía los párrafos), opacidad al 18%, y muelle
  de 90/30 a 210/40 — sobreamortiguado tardaba ~2s en asentar y la punta
  se arrastraba a media pantalla. El punto suelto que aparecía centrado
  arriba al cargar era el remate redondo pintándose en el origen con
  longitud cero: ahora la opacidad sigue al avance.
- Ritmo vertical un 25-30% más apretado en las diez secciones. La página
  pasa de 8.061 a 7.457px sin tocar contenido.
- Números 01-06: de marca de agua de 48px al 6% (que chocaba con títulos
  y reglas) a índice dorado sentado sobre la regla.
- Hero: los dos CTA al mismo ancho en móvil, el patrón de puntos fuera
  del logotipo, y la secuencia visible en todos los tamaños — oculta bajo
  lg dejaba el hero del móvil sin ninguna pieza gráfica. La convergencia
  de la variante "hero" se cierra más para que se lea como líneas
  buscando su eje y no como un garabato tachado.
- Estratega: fuera la retícula de puntos del retrato, que parecía el
  recuadro de imagen rota del navegador; la etiqueta del 30+ cabía en
  menos de lo que mide "construyendo" y salía en tres líneas.
- El nudo de Dolores, más pequeño y con algo más de contraste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
erikghandClaude Opus 5 committed 2026-07-31 16:34:07 -06:00
1 parent 174fcb855a
commit fa86605b39
15 files changed
+135 -52

No files matched your search

+21 -9
View File
@@ -3,7 +3,7 @@
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';
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.
@@ -23,8 +23,12 @@ 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;
// 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
@@ -48,7 +52,13 @@ 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 });
// 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) => {
@@ -65,6 +75,7 @@ export function HiloConductor({ className = '' }: { className?: string }) {
const trazo = reduced ? (
<path
d={TRAZO}
className={GROSORES}
fill="none"
stroke="currentColor"
strokeWidth={GROSOR}
@@ -74,12 +85,13 @@ export function HiloConductor({ className = '' }: { className?: string }) {
) : (
<motion.path
d={TRAZO}
className={GROSORES}
fill="none"
stroke="currentColor"
strokeWidth={GROSOR}
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
style={{ pathLength: avance }}
style={{ pathLength: avance, opacity: aparicion }}
/>
);
@@ -93,7 +105,7 @@ export function HiloConductor({ className = '' }: { className?: string }) {
auto` resolves from the width (1152 → 13824px) and silently wins
over `bottom`, adding ~6000px of blank scroll below the footer. */}
<svg
className="absolute top-0 left-1/2 h-[calc(100%-5rem)] w-full -translate-x-1/2 text-dorado/25"
className="absolute top-0 left-1/2 h-[calc(100%-5rem)] w-full -translate-x-1/2 text-dorado/[0.18]"
viewBox={`0 0 100 ${ALTO}`}
preserveAspectRatio="none"
aria-hidden="true"
@@ -105,19 +117,19 @@ export function HiloConductor({ className = '' }: { className?: string }) {
the wordmark's ≡, never a lookalike. Uniformly scaled in its own
svg, because the band's stretch would flatten the bars. */}
<svg
className="absolute bottom-4 left-1/2 w-24 -translate-x-1/2 text-dorado"
className="absolute bottom-4 left-1/2 w-20 -translate-x-1/2 text-dorado"
viewBox={VIEWBOX_SIMBOLO}
aria-hidden="true"
focusable="false"
>
{BARRAS_SIMBOLO.map((barra, i) =>
reduced ? (
<rect key={i} {...barra} rx={1.5} fill="currentColor" />
<rect key={i} {...barra} rx={RADIO_BARRA} fill="currentColor" />
) : (
<motion.rect
key={i}
{...barra}
rx={1.5}
rx={RADIO_BARRA}
fill="currentColor"
initial={{ opacity: 0 }}
animate={{ opacity: resuelto ? 1 : 0 }}