feat(recorrido): la conversación se responde al bajar, y el lector puede gobernarla

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 <noreply@anthropic.com>
This commit is contained in:
erikghandClaude Opus 5 committed 2026-08-03 11:33:56 -06:00
1 parent a200346780
commit 90a35714f2
3 files changed
+194 -33

No files matched your search

+23 -5
View File
@@ -1,5 +1,6 @@
'use client';
import { useCallback, useState } from 'react';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { FondoPuntos } from '@/components/primitives/FondoPuntos';
@@ -10,6 +11,15 @@ import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, moti
export function PuntosInflexion() {
const reduced = usePrefersReducedMotion();
// How far down the conversation the reader has got. It only ever moves
// forward: an answer that faded back out on the way past would leave the page
// emptier the further you read. Scroll drives it, and so do hover and
// keyboard focus, so the reader can also run the conversation themselves
// instead of only being carried through it.
const [alcanzado, setAlcanzado] = useState(0);
const activar = useCallback((n: number) => setAlcanzado((previo) => Math.max(previo, n)), []);
return (
<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} />
@@ -32,13 +42,21 @@ export function PuntosInflexion() {
{/* The conversation. One <li> per exchange, read top to bottom — no
carousel at any breakpoint, because "te bajas" is the whole shape
the dueña asked for. The stagger reaches ParDialogo's two halves
through React context, so each pair arrives in two beats: the
situation, then the answer. */}
the dueña asked for. Each answer arrives when the reader reaches its
pair, so the section answers as you go down instead of sitting there
already answered. The stagger reaches ParDialogo's two halves
through React context. */}
<motion.ul variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="mt-8 list-none p-0 flex flex-col gap-12 md:gap-16">
className="mt-8 list-none p-0 flex flex-col gap-16 md:gap-24">
{puntosInflexion.items.map((item, i) => (
<ParDialogo key={item.name} n={i + 1} {...item} />
<ParDialogo
key={item.name}
n={i + 1}
{...item}
activo={i + 1 <= alcanzado}
reducido={reduced}
onActivar={activar}
/>
))}
</motion.ul>
</Container>