diff --git a/components/sections/Dolores.tsx b/components/sections/Dolores.tsx
index c57007e..a092f6a 100644
--- a/components/sections/Dolores.tsx
+++ b/components/sections/Dolores.tsx
@@ -3,38 +3,107 @@
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { IlustracionNudo } from '@/components/primitives/IlustracionNudo';
-import { TarjetaSpotlight } from '@/components/primitives/TarjetaSpotlight';
import { dolores } from '@/lib/content';
-import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
+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 used to sit in four tidy cards — the opposite of what they describe. Here
+// they drift past instead: the noise you carry around, still going, while the
+// resolution underneath does not move at all. The contrast between the two is
+// the argument.
+//
+// They stay legible on purpose. Fading them to a whisper would have made a
+// better texture and thrown away the client's own voice; the murmur is carried
+// by the motion, not by hiding the words.
+
+const VUELTA_SEGUNDOS = 58;
+
+function Rotulo({
+ frases,
+ haciaIzquierda,
+ mudo = false,
+}: {
+ frases: string[];
+ haciaIzquierda: boolean;
+ mudo?: boolean;
+}) {
+ const tanda = (oculta: boolean) => (
+
+ {frases.map((frase) => (
+
+ “
+ {frase}
+ ”
+
+ ))}
+
+ );
+
+ return (
+
+ {/* Two identical runs inside one track: translating the track by exactly
+ half its own width lands the second run where the first began, so the
+ loop has no seam. The duplicate is hidden from assistive tech — it is
+ the same four sentences twice. */}
+
+ {tanda(false)}
+ {tanda(true)}
+
+
+ );
+}
export function Dolores() {
const reduced = usePrefersReducedMotion();
+ // Rotated so the two rows never show the same sentence side by side.
+ const segunda = [...dolores.quotes.slice(2), ...dolores.quotes.slice(0, 2)];
+
return (
{/* 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 so the conductor thread runs through it. */}
+ knot. Stays centred. */}
-
+
+
{dolores.eyebrow}
{dolores.title}
-
- {dolores.quotes.map((quote) => (
-
-
-
- “
- {quote}
- ”
-
-
-
- ))}
-
+
+
+ {/* Full-bleed on purpose: the noise does not respect the column. */}
+ {reduced ? (
+
+
+ {dolores.quotes.map((quote) => (
+ -
+ “
+ {quote}
+ ”
+
+ ))}
+
+
+ ) : (
+
+
+ {/* The second row is the same four sentences again, rotated so no
+ pair lines up. It is a visual echo, so it is silent: without
+ `mudo` a screen reader would read all four pains, then read them
+ all over again. */}
+
+
+ )}
+
+
{dolores.remate.before}{' '}
diff --git a/components/sections/__tests__/Dolores.test.tsx b/components/sections/__tests__/Dolores.test.tsx
index 55cdaa5..54023b3 100644
--- a/components/sections/__tests__/Dolores.test.tsx
+++ b/components/sections/__tests__/Dolores.test.tsx
@@ -1,15 +1,30 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Dolores } from '../Dolores';
+import { dolores } from '@/lib/content';
describe('', () => {
it('renders the four pains verbatim, in the client\'s first person', () => {
render();
- expect(screen.getByText(/Trabajo más y gano casi lo mismo que hace dos años\./)).toBeInTheDocument();
- expect(screen.getByText(/no sé en cuál enfocar mi tiempo y mi dinero\./)).toBeInTheDocument();
- expect(screen.getByText(/no puede crecer si yo no estoy en cada decisión\./)).toBeInTheDocument();
- expect(screen.getByText(/ya no me sirve para el siguiente nivel\./)).toBeInTheDocument();
- expect(screen.getAllByRole('listitem')).toHaveLength(4);
+ for (const dolor of dolores.quotes) {
+ expect(screen.getAllByText(new RegExp(dolor.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))).length).toBeGreaterThan(0);
+ }
+ });
+
+ it('says each pain only once out loud, however many times it is painted', () => {
+ const { container } = render();
+ // The drifting rows repeat the four sentences four times over: twice per
+ // row so the loop has no seam, and twice again because there are two rows.
+ // Only one of those runs may reach assistive tech — otherwise a screen
+ // reader recites the whole list of pains, and then recites it again.
+ for (const dolor of dolores.quotes) {
+ const apariciones = Array.from(container.querySelectorAll('span')).filter(
+ (el) => el.textContent?.includes(dolor),
+ );
+ const audibles = apariciones.filter((el) => !el.closest('[aria-hidden="true"]'));
+ expect(apariciones.length).toBeGreaterThan(1);
+ expect(audibles).toHaveLength(1);
+ }
});
it('closes with the resolution, not with more effort', () => {
const { container } = render();