refactor(recorrido): fuera la cabecera; el escenario abre la sección

Con el recorrido en segundo lugar, esa franja clara quedaba como una
lasca entre el azul del hero y el escenario oscuro. Se va: la sección
arranca directamente en la secuencia anclada, y su fondo pasa a azul para
que cualquier costura sea oscura.

Quitarla dejaba la sección sin ningún encabezado, que en una pieza de
5.400px es un agujero en el esquema del documento — para un lector de
pantalla las seis parejas colgarían de nada. El rótulo «¿Te reconoces?»,
que ya estaba en el escenario, pasa a ser el <h2>. Hay una prueba que
exige que exista y que sea h2.

Actualizado también el comentario de orden en page.tsx, que describía una
disposición que ya no es la del archivo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
erikghandClaude Opus 5 committed 2026-08-03 16:20:23 -06:00
1 parent d4bff33e47
commit e0ce0d3b22
3 files changed
+23 -46

No files matched your search

+5 -13
View File
@@ -24,23 +24,15 @@ export default function Home() {
<> <>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<Header /> <Header />
{/* Order matters more since PuntosInflexion became the pinned sequence. {/* El recorrido va en segundo lugar por decisión de la dueña: el lector
It no longer only names the six inflection points — it answers each entra por el gancho y cae directo en la secuencia anclada, que plantea
one in creeer's voice, six times over, across six screens. Put third, seis situaciones y las contesta. FraseAncla y Dolores llegan después,
as it was, the reader got the whole solution before Dolores had given y Mecanismo cierra explicando el método detrás de esas respuestas. */}
them a reason to care and before Mecanismo explained the method, and
the pain landed after the answer.
Dolores now comes first: short, in the client's own words, the ache.
Then the recorrido answers it point by point. Then Mecanismo, which
stops reading as repetition and starts reading as the method behind
the answers the reader just heard — and it keeps creeer's navy ground
straight out of the sequence's last frame. */}
<main> <main>
<Hero variant="editorial" /> {/* dueña puede cambiar a "hormozi" */} <Hero variant="editorial" /> {/* dueña puede cambiar a "hormozi" */}
<PuntosInflexion />
<FraseAncla /> <FraseAncla />
<Dolores /> <Dolores />
<PuntosInflexion />
<Mecanismo /> <Mecanismo />
<Disciplinas /> <Disciplinas />
<ParaQuien /> <ParaQuien />
+11 -31
View File
@@ -7,7 +7,6 @@ import { FondoPuntos } from '@/components/primitives/FondoPuntos';
import { FondoFoto } from '@/components/primitives/FondoFoto'; import { FondoFoto } from '@/components/primitives/FondoFoto';
import { ParDialogo } from '@/components/primitives/ParDialogo'; import { ParDialogo } from '@/components/primitives/ParDialogo';
import { EscenaDialogo } from '@/components/primitives/EscenaDialogo'; import { EscenaDialogo } from '@/components/primitives/EscenaDialogo';
import { IlustracionInflexion } from '@/components/primitives/IlustracionInflexion';
import { puntosInflexion } from '@/lib/content'; import { puntosInflexion } from '@/lib/content';
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion'; import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
@@ -32,23 +31,6 @@ const FOTOS = [
'/images/recorrido/portafolio.jpg', // a skyline of towers, every one of them a front '/images/recorrido/portafolio.jpg', // a skyline of towers, every one of them a front
]; ];
function Cabecera({ reduced }: { reduced: boolean }) {
return (
<div className="flex flex-col md:flex-row md:items-center md:justify-between md:gap-12">
<div>
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{puntosInflexion.eyebrow}</motion.p>
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="mt-4 font-sans font-black text-seccion text-azul-profundo max-w-3xl">{puntosInflexion.title}</motion.h2>
</div>
<motion.div variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="mt-8 md:mt-0 shrink-0">
<IlustracionInflexion className="w-56 md:w-72 h-auto mx-auto" />
</motion.div>
</div>
);
}
// Reduced motion, and the server's first paint, get the whole conversation laid // Reduced motion, and the server's first paint, get the whole conversation laid
// out at once. A pinned section that advances on scroll is exactly the thing // out at once. A pinned section that advances on scroll is exactly the thing
// someone asking for less movement is asking to be spared, and hiding five // someone asking for less movement is asking to be spared, and hiding five
@@ -58,8 +40,7 @@ function Estatica() {
<section className="bg-gris-claro relative overflow-hidden"> <section className="bg-gris-claro relative overflow-hidden">
<FondoPuntos className="-bottom-6 -right-12 w-80 md:w-[32rem] text-azul-profundo/10" distance={32} /> <FondoPuntos className="-bottom-6 -right-12 w-80 md:w-[32rem] text-azul-profundo/10" distance={32} />
<Container className="relative py-16 md:py-24"> <Container className="relative py-16 md:py-24">
<Cabecera reduced /> <h2 className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{puntosInflexion.lead}</h2>
<p className="mt-14 text-xs tracking-[0.3em] uppercase font-black text-dorado">{puntosInflexion.lead}</p>
<ul className="mt-8 list-none p-0 flex flex-col gap-16 md:gap-24"> <ul className="mt-8 list-none p-0 flex flex-col gap-16 md:gap-24">
{items.map((item, i) => ( {items.map((item, i) => (
<ParDialogo key={item.name} n={i + 1} {...item} activo reducido onActivar={() => {}} /> <ParDialogo key={item.name} n={i + 1} {...item} activo reducido onActivar={() => {}} />
@@ -142,15 +123,10 @@ function Escenificado() {
const fotoY = useTransform(progreso, [0, 1], [0, -70]); const fotoY = useTransform(progreso, [0, 1], [0, -70]);
return ( return (
<section className="bg-gris-claro relative"> // Navy, not gris-claro: with no header block left there is no light strip
{/* The pinned stage starts the instant this container ends, so without a // to show, and any seam at the section's edges should be dark like the
bottom the dark ground began three pixels under the last line of the // stage itself.
title and the intro read as a squeezed strip between two dark blocks <section className="bg-azul-profundo relative">
rather than as the section's opening. */}
<Container className="relative pt-16 pb-16 md:pt-24 md:pb-24">
<Cabecera reduced={false} />
</Container>
{/* One viewport of stage, six viewports of scroll to move through it. */} {/* One viewport of stage, six viewports of scroll to move through it. */}
<div ref={contenedor} className="relative" style={{ height: `${TOTAL * 100}vh` }}> <div ref={contenedor} className="relative" style={{ height: `${TOTAL * 100}vh` }}>
<div data-escena={activo} data-tiempo={tiempo} className="sticky top-0 h-screen overflow-hidden bg-azul-profundo"> <div data-escena={activo} data-tiempo={tiempo} className="sticky top-0 h-screen overflow-hidden bg-azul-profundo">
@@ -205,9 +181,13 @@ function Escenificado() {
resolves against Container's padding box, so `left-0` lands on resolves against Container's padding box, so `left-0` lands on
the inside of the border and ignores the px-6 the rest of the the inside of the border and ignores the px-6 the rest of the
page is aligned to. On a phone that clipped the first glyph. */} page is aligned to. On a phone that clipped the first glyph. */}
<p className="absolute top-24 left-6 md:left-12 text-xs tracking-[0.3em] uppercase font-black text-dorado md:top-28"> {/* The section's only heading now. Losing the header block cost
the recorrido its <h2>, and a section this size with no heading
at all is a hole in the document outline — for a screen reader
the six exchanges would hang off nothing. */}
<h2 className="absolute top-24 left-6 md:left-12 text-xs tracking-[0.3em] uppercase font-black text-dorado md:top-28">
{puntosInflexion.lead} {puntosInflexion.lead}
</p> </h2>
{/* The rail. Where you are in the conversation, and how much is {/* The rail. Where you are in the conversation, and how much is
left — a pinned section without it feels like a page that left — a pinned section without it feels like a page that
@@ -4,9 +4,14 @@ import { PuntosInflexion } from '../PuntosInflexion';
import { puntosInflexion } from '@/lib/content'; import { puntosInflexion } from '@/lib/content';
describe('<PuntosInflexion />', () => { describe('<PuntosInflexion />', () => {
it('renders the section title', () => { it('still has a heading after losing its header block', () => {
render(<PuntosInflexion />); render(<PuntosInflexion />);
expect(screen.getByText(/llegan a un punto de inflexión/i)).toBeInTheDocument(); // The eyebrow and the deck's title used to sit in a light strip above the
// pinned stage; with them gone the lead carries the heading level. A
// section this long with no heading at all is a hole in the outline —
// for a screen reader the six exchanges would hang off nothing.
const encabezado = screen.getByRole('heading', { name: /te reconoces/i });
expect(encabezado.tagName).toBe('H2');
}); });
it('labels both halves of every exchange with its inflection point', () => { it('labels both halves of every exchange with its inflection point', () => {