From 90a35714f23b23f2a6c3d06b81c8dcc63dd93b4b Mon Sep 17 00:00:00 2001 From: erikgh Date: Mon, 3 Aug 2026 11:33:56 -0600 Subject: [PATCH] =?UTF-8?q?feat(recorrido):=20la=20conversaci=C3=B3n=20se?= =?UTF-8?q?=20responde=20al=20bajar,=20y=20el=20lector=20puede=20gobernarl?= =?UTF-8?q?a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cada respuesta llega cuando su par alcanza el centro de la pantalla: la regla dorada se dibuja y el bloque entra. El estado solo avanza, así que nada se apaga por detrás del lector. El scroll no es el único mando — hover y foco de teclado llevan al mismo sitio. Corrige un fallo que las pruebas no podían ver: Framer resuelve la variante escribiendo `opacity: 1` en el style inline, que gana a la clase opacity-0 de Tailwind. El revelado no ocurría; solo se animaba la regla, que es un transform que Framer no toca. La entrada y el revelado viven ahora en elementos distintos, con una prueba que lo fija. Co-Authored-By: Claude Opus 5 --- components/primitives/ParDialogo.tsx | 112 +++++++++++++++--- .../primitives/__tests__/ParDialogo.test.tsx | 87 ++++++++++++-- components/sections/PuntosInflexion.tsx | 28 ++++- 3 files changed, 194 insertions(+), 33 deletions(-) diff --git a/components/primitives/ParDialogo.tsx b/components/primitives/ParDialogo.tsx index a3b2d21..21d068a 100644 --- a/components/primitives/ParDialogo.tsx +++ b/components/primitives/ParDialogo.tsx @@ -1,11 +1,14 @@ 'use client'; +import { useEffect, useRef } from 'react'; import { motion } from 'framer-motion'; import { Wordmark } from '@/components/primitives/Wordmark'; import { fadeInUp } from '@/lib/motion'; // One exchange of the page's conversation: the reader's situation, then what -// creeer does about it. +// creeer does about it. The answer arrives as you scroll into the pair — its +// gold rule draws down and the block comes up to full contrast — so the section +// reads as being answered rather than as a list that happens to have answers. // // The sides never swap from one pair to the next. Each voice keeps its own // margin, the way a conversation reads on a page; what alternates going down is @@ -13,39 +16,114 @@ import { fadeInUp } from '@/lib/motion'; // the reader the one cue that tells them who is speaking. // // Three cues carry that distinction, never colour alone: the speaker label, the -// column, and the rule hanging off each block. The answer also drops half a -// step below the situation on wide screens, so the eye reads it as a reply -// rather than as a second column of the same thought. +// column, and the rule hanging off each block. +// +// The answer text is in the DOM at all times, at every breakpoint — opacity, +// never `display: none` or `hidden`, so screen readers still announce it and +// the static export still ships it to search engines. +// +// `activo` latches: the section turns it on when the reader reaches the pair +// and never turns it off again. An answer that faded back out once you scrolled +// past would leave the page emptier the further you read, and would re-create +// the failure this project has already hit twice — content stranded at opacity +// zero with every test green, because jsdom has no layout and no scrolling. // // No motion.li here on purpose: Framer propagates variants through React // context, so the two motion.div children still inherit the stagger from the -//