Files
Creeer-Web/components/sections/PuntosInflexion.tsx
T
erikghandClaude Opus 5 90a35714f2 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>
2026-08-03 11:33:56 -06:00

66 lines
3.5 KiB
TypeScript

'use client';
import { useCallback, useState } from 'react';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { FondoPuntos } from '@/components/primitives/FondoPuntos';
import { ParDialogo } from '@/components/primitives/ParDialogo';
import { IlustracionInflexion } from '@/components/primitives/IlustracionInflexion';
import { puntosInflexion } from '@/lib/content';
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
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} />
<Container className="relative py-16 md:py-24">
<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>
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="mt-14 text-xs tracking-[0.3em] uppercase font-black text-dorado">{puntosInflexion.lead}</motion.p>
{/* 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. 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-16 md:gap-24">
{puntosInflexion.items.map((item, i) => (
<ParDialogo
key={item.name}
n={i + 1}
{...item}
activo={i + 1 <= alcanzado}
reducido={reduced}
onActivar={activar}
/>
))}
</motion.ul>
</Container>
</section>
);
}