Files
Creeer-Web/components/sections/Dolores.tsx
T
erikghandClaude Opus 5 d4bff33e47 feat(dolores): cada frase se resuelve al llegar, palabra a palabra
Prometí en la vista previa que entrarían «de una en una al bajar» y lo
que monté las traía a las cuatro a la vez: un stagger de contenedor se
dispara con la sección al 30% en pantalla, y la sección mide 1.200px, así
que el lector veía aparecer todas las frases mucho antes de llegar a
ellas.

Ahora cada cita usa TextoDesenfoque, que lleva su propio observador: cada
una espera su turno y entonces se resuelve desde un desenfoque, palabra a
palabra. Es el dolor entrando en foco, y es el mismo recurso que ya usan
el hero y la frase ancla — vocabulario del proyecto, no un invento nuevo.

Verificado midiendo la opacidad de la palabra animada de cada cita: al
asomar la sección, las dos primeras están resueltas y las dos últimas a
cero; a media sección, las cuatro. Antes de corregir el selector medía el
span envoltorio y daba unos en todas, que no significaba nada.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 16:07:00 -06:00

72 lines
4.0 KiB
TypeScript

'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { IlustracionNudo } from '@/components/primitives/IlustracionNudo';
import { TextoDesenfoque } from '@/components/primitives/TextoDesenfoque';
import { dolores } from '@/lib/content';
import { fadeInUp, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
// The four quotes are the client's own words, from slide 3 of the dueña's deck.
//
// They were four tidy cards, which was the opposite of what they describe; then
// they were a marquee, which was worse — a loop belongs to a sale banner, and
// the brand's locked tone is "seguridad + cercanía", "simple, directo, limpio".
// It also sliced every sentence at both edges, so none was ever fully framed.
//
// Now they arrive one at a time as the reader comes down and they stay. What
// carries the weight is the pile, not the travel: each one lands further in or
// further out than the last, the way overheard sentences do, and by the fourth
// the reader is looking at everything at once. Then the resolution, flush left
// and gold, with nothing moving.
// Deliberately not a staircase. A regular indent reads as a list; an irregular
// one reads as four separate voices that happen to be in the same room.
const SANGRIAS = ['md:ml-0', 'md:ml-[14%]', 'md:ml-[5%]', 'md:ml-[21%]'];
export function Dolores() {
const reduced = usePrefersReducedMotion();
return (
<section className="bg-white relative overflow-hidden">
{/* Smaller and a touch stronger than it was: sprawling at 8% across the
whole title it read as a smudge over the headline rather than as a
knot. Stays centred. */}
<IlustracionNudo className="pointer-events-none select-none absolute top-0 left-1/2 -translate-x-1/2 w-56 md:w-80 text-azul-profundo/[0.11]" />
<Container className="relative py-16 md:py-24">
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{dolores.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">{dolores.title}</motion.h2>
{/* No stagger on the list. A container stagger fires all four the moment
the section is 30% on screen, and the section is 1.200px tall — the
reader would watch every sentence appear long before scrolling to
it, which is not what "se apilan al bajar" means. TextoDesenfoque
carries its own observer, so each quote waits for its own arrival
and then resolves out of blur, word by word: the pain coming into
focus. Same device as the hero and the anchor phrase. */}
<ul className="mt-14 flex flex-col gap-9 md:gap-12 list-none p-0">
{dolores.quotes.map((quote, i) => (
<li key={quote} className={`max-w-3xl ${SANGRIAS[i]}`}>
<blockquote className="font-sans italic font-bold leading-snug text-azul-profundo text-2xl md:text-4xl lg:text-[2.6rem]">
<span className="not-italic text-dorado">“</span>
<TextoDesenfoque segments={[{ text: quote }]} delayMs={38} />
<span className="not-italic text-dorado">”</span>
</blockquote>
</li>
))}
</ul>
{/* Flush left and still, against four sentences that are not. */}
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="mt-16 md:mt-20 font-sans font-black text-ancla text-azul-profundo max-w-4xl">
{dolores.remate.before}{' '}
<span className="text-dorado">{dolores.remate.accent}</span>
</motion.p>
</Container>
</section>
);
}