chore(docs): retira documentación obsoleta y mueve CLAUDE.md a docs/

Fuera las directrices, los finalistas del hero, el branding en jpeg, los
specs y planes ya ejecutados o sustituidos, y la skill react-doctor.
CLAUDE.md pasa a docs/CLAUDE.md. Todo sigue en la historia de git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
erikghandClaude Opus 5.5 committed 2026-09-30 11:26:44 -06:00
1 parent 97fd0a0224
commit d028fa5804
18 files changed
+2 -7692

No files matched your search

-56
View File
@@ -1,56 +0,0 @@
---
name: react-doctor
description: Use when finishing a feature, fixing a bug, before committing React code, or when the user types `/doctor`, asks to scan, triage, or clean up React diagnostics. Covers lint, accessibility, bundle size, architecture. Includes a regression check and a full local-triage workflow that fetches the canonical playbook.
version: "1.2.0"
---
# React Doctor
Scans React codebases for security, performance, correctness, and architecture issues. Outputs a 0–100 health score.
## After making React code changes:
Run `npx react-doctor@latest --verbose --scope changed` and check the score did not regress.
If the score dropped, fix the regressions before committing.
## For general cleanup or code improvement:
Run `npx react-doctor@latest --verbose` (the default `--scope full`) to scan the full codebase. Fix issues by severity — errors first, then warnings.
## For a focused UI design audit:
Run `npx react-doctor@latest design --verbose`. This selects only design-tagged UI composition, typography, interaction, accessibility, and motion rules, including focused rules that remain opt-in during a general health scan.
## /doctor — full local triage workflow
When the user types `/doctor`, says "run react doctor", or asks for a full triage / cleanup pass (not just a regression check), fetch the canonical local-triage playbook and follow every step in it:
```bash
curl --fail --silent --show-error \
--header 'Cache-Control: no-cache' \
https://www.react.doctor/prompts/react-doctor-agent.md
```
The playbook is the single source of truth — a scan → filter → triage → fix → validate loop that edits the working tree directly (never commits, never opens PRs). Updating the prompt at its source updates every agent on its next fetch — no skill reinstall needed.
Pair it with the matching per-rule prompts at `https://www.react.doctor/prompts/rules/<plugin>/<rule>.md` (fetched on demand inside the playbook) so each fix uses the canonical, reviewer-tested recipe.
## Configuring or explaining rules
When the user wants to understand a rule, disagrees with one, or wants to disable / tune which rules run (not fix code), read [references/explain.md](references/explain.md) and follow it. Start with `npx react-doctor@latest rules explain <rule>`, then apply the narrowest control via `npx react-doctor@latest rules disable|set|category|ignore-tag …`, which edits your `doctor.config.*` (or `package.json#reactDoctor`).
## Command
```bash
npx react-doctor@latest --verbose --scope changed
```
| Flag | Purpose |
| ----------------- | ---------------------------------------------------------------- |
| `.` | Scan current directory |
| `--verbose` | Show affected files and line numbers per rule |
| `--scope changed` | Only report issues introduced vs the base branch (default: full) |
| `--scope lines` | Only report issues on the changed lines |
| `--score` | Output only the numeric score |
| `design` | Run only the focused UI design diagnostics |
@@ -1,73 +0,0 @@
# Explaining and configuring rules
Explain React Doctor rules and edit `doctor.config.*` safely. Use this when a user
wants to understand a rule or change which rules run — not for fixing diagnostics
(that is the main `react-doctor` skill / `/doctor`).
Triggers: "why did this rule fire", "I disagree with this rule", "turn this rule off",
"stop flagging X", "too noisy", "disable design rules".
## Workflow
1. Identify the rule key from the diagnostic (e.g. `react-doctor/no-array-index-as-key`).
2. Explain it before changing anything:
```bash
npx react-doctor@latest rules explain react-doctor/no-array-index-as-key
```
3. Pick the narrowest control that matches the user's intent (see decision guide).
4. Apply it with a `rules` subcommand (edits your `doctor.config.*` or `package.json#reactDoctor` in place, preserving other fields and formatting).
5. Validate the change did what they wanted:
```bash
npx react-doctor@latest --verbose --diff
```
## Commands
```bash
npx react-doctor@latest rules list # every rule + its effective severity
npx react-doctor@latest rules list --configured # only what your config changed
npx react-doctor@latest rules list --category Performance # filter by category
npx react-doctor@latest rules explain <rule> # why it matters + how to configure
npx react-doctor@latest rules disable <rule> # rule never runs
npx react-doctor@latest rules enable <rule> # turn back on at its recommended severity
npx react-doctor@latest rules set <rule> warn # off | warn | error
npx react-doctor@latest rules category "React Native" off # whole category
npx react-doctor@latest rules ignore-tag design # skip a rule family (design, test-noise, …)
npx react-doctor@latest rules unignore-tag design
```
Rule references accept the full key (`react-doctor/no-danger`), the bare id (`no-danger`), or a legacy key (`react/no-danger`).
## Decision guide
Match the control to the intent — prefer the narrowest one:
- **User disagrees with one rule / it's a false positive for them** → `rules disable <rule>` (sets `rules.<key> = "off"`; the rule stops running everywhere). This is the default for "I don't want this rule".
- **Rule is fine but wrong severity** → `rules set <rule> warn` or `rules set <rule> error`.
- **A disabled-by-default rule they want on** → `rules enable <rule>`.
- **A whole area is unwanted** (e.g. all React Native rules) → `rules category "<Category>" off`.
- **A behavioral family is noisy** (`design`, `test-noise`, `migration-hint`) → `rules ignore-tag <tag>`.
- **Keep it locally but hide from PR comment / score / CI gate only** → do NOT disable. Edit `surfaces` in your config (`surfaces.prComment.excludeRules`, `surfaces.score.excludeTags`, `surfaces.ciFailure.excludeCategories`). The rule still shows in local `cli` output.
- **Restore test or story findings to production health** → set `surfaces.score.includeFileContexts` or `surfaces.ciFailure.includeFileContexts` to `["test"]`, `["story"]`, or both. Other surface exclusions still apply.
How the layers combine: `ignore.tags` disables every rule carrying that tag **before** linting, so a tagged rule stays off even if `rules`/`categories` set it to `warn`/`error` (a rule-level override cannot re-enable a tag-ignored rule). For rules that aren't tag-disabled, `rules` overrides `categories` overrides the rule's default. `surfaces` is visibility-only and never changes whether a rule runs.
## Config shape
Config lives in `doctor.config.ts` (or `.js`/`.mjs`/`.cjs`/`.json`/`.jsonc`), or the `reactDoctor` key in `package.json`. The `rules` commands edit whichever exists — TS/JS edits preserve formatting (via magicast) — and create `doctor.config.json` when none does, stamping `$schema`:
```ts
// doctor.config.ts
export default {
rules: { "react-doctor/no-array-index-as-key": "off" },
categories: { "React Native": "warn" },
ignore: { tags: ["design"] },
};
```
## Educating the user
When explaining a rule, lead with the "Why it matters" guidance from `rules explain` and, when they want depth, the per-rule recipe at `https://www.react.doctor/prompts/rules/<plugin>/<rule>.md`. Only after they understand it should you offer to disable it — many "bad" rules are catching real issues.
+2 -2
View File
@@ -28,9 +28,9 @@ npx tsc --noEmit # typecheck (no hay script propio)
Landing one-page estática. Next.js 14 App Router con `output: 'export'` en `next.config.mjs` → **no hay backend, ni API routes, ni `next/image` optimizado, ni middleware**. El contacto son enlaces directos (WhatsApp / mailto / LinkedIn); no hay formulario.
**Una sola ruta.** `app/page.tsx` compone las 8 secciones en orden fijo y monta el JSON-LD (`ProfessionalService`) inline. `app/layout.tsx` carga Open Sans vía `next/font/google` exponiéndola como `--font-open-sans`, y concentra toda la metadata/OG/canonical.
**Una sola ruta.** `app/page.tsx` compone las 10 secciones en orden fijo y monta el JSON-LD (`ProfessionalService`) inline. `app/layout.tsx` carga Open Sans vía `next/font/google` exponiéndola como `--font-open-sans`, y concentra toda la metadata/OG/canonical.
**El copy vive en `lib/content.ts`, no en el JSX.** Es el source of truth: cada sección importa su objeto (`hero`, `fraseAncla`, `paraQuien`, `mecanismo`, …). Las frases que necesitan énfasis parcial están **partidas en segmentos** (`headline.line1` / `headline.effort` / `headline.clarity`) para que el componente pinte en dorado o subraye sin fragmentar strings dentro del JSX. Al cambiar copy, edita `lib/content.ts`; al cambiar énfasis visual, edita el componente. Los enlaces de contacto ahí llevan `TODO dueña` con placeholders — no inventes valores reales.
**El copy vive en `lib/content.ts`, no en el JSX.** Es el source of truth: cada sección importa su objeto (`hero`, `fraseAncla`, `paraQuien`, `proceso`, …). Las frases que necesitan énfasis parcial están **partidas en segmentos** (`headline.line1` / `headline.effort` / `headline.clarity`) para que el componente pinte en dorado o subraye sin fragmentar strings dentro del JSX. Al cambiar copy, edita `lib/content.ts`; al cambiar énfasis visual, edita el componente. Los enlaces de contacto ahí llevan `TODO dueña` con placeholders — no inventes valores reales.
**Animación centralizada en `lib/motion.ts`.** Las secciones son client components (`'use client'`) que usan `fadeInUp` + `staggerContainer` con `viewportOnce`, y **siempre** combinan `usePrefersReducedMotion()` con `motionProps(reduced)` — ese spread neutraliza `whileInView`/`initial` cuando el usuario pide menos movimiento. Es requisito de a11y del spec; replícalo en cualquier sección nueva.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 KiB

-300
View File
@@ -1,300 +0,0 @@
# Directrices de la dueña · creeer
> **Source of truth para decisiones de marca, copy y diseño.**
> Esta es la voz y el criterio de la dueña de creeer. Toda decisión del proyecto debe pasar por este filtro. Si una propuesta contradice algo de aquí, **preguntar antes de avanzar**.
---
## Quién es la dueña
La dueña es **la mente detrás de creeer** — estratega de crecimiento con +30 años construyendo marcas. Su negocio real es **claridad estratégica para empresarios con tracción que llegaron a un punto de inflexión** (no vende marketing, vende la transformación).
El nombre **creeer** es un juego con "creer" (en ti, en mí, en el proyecto) + "career".
---
## Mensajes verbatim de la dueña (contexto y voz)
Estos son los mensajes que la dueña envió por WhatsApp durante el brainstorming, transcritos literalmente. **Preservar su voz y sus palabras.**
### Sobre la sensación que quiere proyectar
> **"Sabes lo que quiero que proyectemos es seguridad pero cercanía."**
*(3:03 p.m., 20/7/2026)* — Esta es la directriz maestra de tono. **No frío/distante, no agresivo puro. Autoridad accesible.**
### Sobre el wordmark en minúsculas
> **"Por eso está en minúsculas."**
*(3:04 p.m., 20/7/2026)* — La decisión de escribir `creeer` en minúsculas es **consciente y estratégica**: transmite cercanía, humildad, accesibilidad (frente al frío de los mayúsculas institucionales). **NUNCA usar `CREEER` ni `Creeer` en el wordmark** — siempre `creeer`.
### Sobre la paleta y el estilo
> **"Creo que el blanco con azul."**
> **"Simple directo."**
> **"Limpio."**
*(3:12 p.m., 20/7/2026)* — Tres directrices visuales:
1. **Blanco con azul** → fondo claro + navy (no dark mode como dirección principal)
2. **Simple directo** → sin decoración, sin elementos accesorios, copy directo
3. **Limpio** → mucho aire, blanco, tipografía hace el trabajo
### Referencia a Alex Hormozi
La dueña mencionó a **Alex Hormozi** como referencia estética/comunicacional. Su ADN:
- Bold sans-serif gigante, a menudo uppercase
- Alto contraste (negro/blanco/un acento eléctrico — en creeer, dorado)
- Copy directo, corto, cuantificado (números, métricas, claims)
- Palabras clave en caja (background detrás de la palabra)
- Voz agresiva, results-driven, "sin paja"
> ⚠️ **Tensión a equilibrar:** Hormozi es agresivo/directo, pero la dueña también pidió **cercanía + minúsculas + blanco+azul + limpio**. La síntesis es **"Hormozi en voz, creeer en alma"**: el punch visual de Hormozi (bold sans, palabras en caja) con el dorado tostado, el wordmark serif en minúsculas y la paleta cálida de creeer.
### Sobre la tipografía y el símbolo del wordmark (28/jul/2026)
Contexto: se le pasaron 3 opciones Open Sans / Carlito / Fira Sans. Verbatim:
> **Erik (11:28):** "Open Sans / Carlito / Fira Sans — Son muy similares, pero tal vez haya detallitos que creen mejores."
> **Dueña (12:56):** "Toma la más común."
> **Dueña (12:57):** "Sólo cuida que la E que está en 3 líneas se lea como E. Por favor."
> **Dueña (12:57):** "Gracias."
Dos directivas:
1. **Tipografía → Open Sans** ("la más común" de las tres).
2. **Símbolo del wordmark:** el `≡` (tres líneas) debe **leerse como E** — no como líneas abstractas ni ícono de menú. El wordmark tiene que leerse `creeer`. Implementación: glifo en SVG que forma una **E** con tres barras horizontales + espina vertical izquierda, en dorado.
> ⚠️ **Contradicción pendiente con el manual.** El Manual de Identidad Visual v1.0 dibuja el `≡` como **tres barras sueltas, sin espina vertical**. El punto 2 de arriba (la espina) es una interpretación del mensaje de la dueña, no algo que el manual muestre. El logotipo trazado en `public/logo-creeer-principal.svg` sigue al manual. **Preguntar a la dueña cuál manda.**
### Sobre la nueva imagen y el formato de la página (29/jul/2026)
Contexto: mandó el Manual de Identidad Visual (`docs/branding.jpeg`) y una referencia de sitio. Verbatim:
> **Dueña (10:13):** *[imagen del Manual de Identidad Visual v1.0]*
> **Dueña (10:13):** "esta es la nueva imagen, lo que ofrecemos es claridad."
> **Dueña (10:13):** "claridad estratégica"
> **Dueña (10:14):** "me gusta el formato de esta página,"
> **Dueña (10:14):** "ya que haremos preguntas y vamos ir dando solución a sus principales dolores"
> **Dueña (10:14):** https://en-ti.com/
> **Dueña (10:15):** "me falta definir la información, pero les voy a dejar un par de proyectos que hice, justamente dando esta claridad estratégica a 2 compañías."
Tres directivas:
1. **El manual v1.0 es la imagen definitiva** y la oferta se nombra **"claridad estratégica"** (no "estrategia", no "marketing").
2. **Adoptar la estructura de `https://en-ti.com/`** — confirmado con la dueña (29/jul/2026): le gustó **la estructura** de esa página. Es referencia de *estructura*, no de estética: la paleta, el logotipo y el tono los sigue fijando el manual + las decisiones bloqueadas.
3. **Pendiente de la dueña:** 2 casos de proyectos reales donde dio claridad estratégica. La información aún no está definida por ella; no inventar casos ni métricas.
**Estructura de en-ti.com, verificada en el sitio (29/jul/2026):** hero declarativo con 2 CTAs → barra de métricas → soluciones en 6 tarjetas → fichas ampliadas → proceso de 5 pasos → "a quién servimos" (8 industrias) → logos de clientes → productos propios → muro de prensa → CTA repetido (*"Cotiza tu proyecto"*).
> **Ojo al releer esto:** la página **no contiene ni una sola pregunta** — todos sus encabezados son enunciados. El patrón real de sus tarjetas es **nombrar el dolor y resolverlo en dos líneas**, p. ej. *"Recuperación de equipo crítico — Cuando un servidor o equipo especializado falla, diagnosticamos, restauramos y devolvemos la operación, sin reemplazar toda la inversión."* Lo de "haremos preguntas" es intención de la dueña para el copy, no algo que la referencia demuestre.
### Sobre el avance con gráfico y movimiento (30/jul/2026)
> **Dueña (Jazmín):** "súper, sólo le falta algo de gráfico y movimiento, denme chance de entregar algo y si pudieras en la tarde nos conectamos un rato para comentarles."
Dos lecturas:
1. **Aprueba el rumbo actual** ("súper") y pide más **gráfico y movimiento** — refuerza seguir sumando elementos visuales/animados (en la línea de lo ya trabajado: fondos de puntos, blur-in del headline, spotlight en tarjetas, carrusel), siempre dentro de la paleta y el tono ya fijados.
2. **Pide tiempo** ("denme chance de entregar algo") antes de seguir — probablemente va a compartir algo (¿los 2 casos pendientes? ¿más contenido del deck?) — y propone **conectar en la tarde** para comentar con el equipo ("comentarles", plural). No agendar nada unilateralmente; esperar a que ella confirme la llamada.
---
## Deck "Rumbo Estratégico · Propuesta Socio" (29/jul/2026)
> **Fuente:** `creeer_Rumbo_Estrategico_Propuesta_Socio.pptx`, 14 diapositivas, autoría de la dueña (Jazmín Rodríguez).
> ⚠️ **Es un deck para un socio estratégico, no material público.** Las diapositivas 11–13 (roadmap con fechas internas, qué se le pide al socio, próximos pasos) **no van a la landing**. El propio deck menciona que habrá un "deck público" aparte.
> ⚠️ **La voz del deck es "nosotros" ("no vendemos marketing", "trabajamos con…") porque le habla a un socio.** La landing va en segunda persona ("tú") y primera del singular, según las decisiones bloqueadas. **No copiar el copy verbatim sin cambiar la persona.**
### Los 6 puntos de inflexión (diapositiva 2) — material para tarjetas
| Punto | Descripción verbatim |
|---|---|
| Crecer | Dar el siguiente salto sin saber por dónde |
| Consolidarse | Ordenar lo construido antes de escalar |
| Recuperar rentabilidad | El esfuerzo ya no se traduce en resultado |
| Simplificar | Demasiada complejidad operativa |
| Expandirse | Nuevos mercados, mismo mapa de decisión |
| Replantear el portafolio | Qué seguir, qué dejar ir |
### Los dolores, en primera persona (diapositiva 3) — verbatim
> "Trabajo más y gano casi lo mismo que hace dos años."
> "Tengo varios proyectos y no sé en cuál enfocar mi tiempo y mi dinero."
> "Mi empresa no puede crecer si yo no estoy en cada decisión."
> "Lo que me trajo hasta aquí ya no me sirve para el siguiente nivel."
Remate de la diapositiva: **"Esto no se resuelve con más esfuerzo. Se resuelve con una estrategia clara."**
Estos son los "principales dolores" que la dueña anunció por WhatsApp. **Ya están escritos en su voz — usarlos, no reescribirlos.**
### Confirmaciones que aporta el deck
- **El insight (diap. 4) ratifica el lead de `Disciplinas`:** *"No vendemos marketing. Vendemos claridad estratégica… El marketing es una de las disciplinas que usamos para transformar empresas — no el producto que vendemos."* → `Disciplinas` se queda como está, separado de cualquier bloque de soluciones.
- **`Mecanismo` coincide** con el proceso del deck (diap. 7): Inmersión → Desafío → Construcción.
- **`ParaQuien` se queda corto:** el deck (diap. 9) tiene **4 "SÍ ES PARA" + 4 "NO ES PARA"**; la landing sólo tiene 3 criterios afirmativos.
- **Encuadre clave (diap. 5):** *"El cliente no está en crisis: está en transición."*
- **Frase-ancla alternativa (diap. 14):** *"Cuando una empresa pierde claridad, deja de crecer por estrategia y empieza a crecer por inercia."* Convive con la de `lib/content.ts`; **decidir cuál manda.**
### Alertas
- **Diapositiva 10 = escasez.** "Generación Fundadora", 10 empresas, "precio y condiciones de fundador, no repetibles". Está **explícitamente prohibida en el hero** por decisión de la dueña, y hay un test negativo en `Hero.test.tsx` que falla con `cupos|cohorte|generación fundadora`. **Preguntar si puede vivir más abajo en la página o si queda fuera del todo.**
- **Los 2 casos siguen sin llegar.** El deck no los contiene.
- **Calendario (diap. 11):** lanzamiento última semana de agosto 2026; hoy la fase es "Validación" (pilotos con 5–10 empresarios).
---
## Principios derivados (cómo aplicar los mensajes)
| Directriz de la dueña | Implicación práctica |
|---|---|
| "Seguridad + cercanía" | Serif para autoridad + voz en "tú" + fondos cálidos para cercanía |
| "minúsculas" | Wordmark SIEMPRE `creeer`. Eyebrows y CTAs pueden ser uppercase (system-ui) pero el wordmark jamás |
| "blanco con azul" | Fondo principal blanco/blanco cálido, navy como tinta y CTA |
| "Simple directo" | Sin elementos decorativos accesorios. Copy corto, una idea por línea |
| "Limpio" | Mucho aire (whitespace), máximo 2 familias tipográficas, paleta reducida |
| Referencia Hormozi | Permitido bold sans uppercase + palabras en caja + métricas, **siempre que** no rompan cercanía |
---
## Posicionamiento estratégico (de su documento de nicho)
Esto NO es copy de landing — es la brújula estratégica que la dueña ya validó. Toda la messaging debe ser coherente con esto.
### Lo que creeer NO es
- ❌ Un diplomado de marketing
- ❌ Una consultoría genérica
- ❌ Clases/asesoría por hora
- ❌ Venta de disciplinas (Comunicación/Digital/Experiencias) como promesa
### Lo que creeer SÍ es
- ✅ **Claridad estratégica** para empresarios con tracción en punto de inflexión
- ✅ Transformación, no horas
- ✅ Inmersión profunda → desafiar supuestos → estrategia ejecutable
### Cliente ideal
Empresarios que **ya lograron tracción**, pero llegaron al punto donde **trabajar más ya no basta** — necesitan mejores decisiones estratégicas. NO cualquier PYME. NO empresas en crisis. **Empresas en transición.**
### Frases-ancla aprobadas (de su documento)
- *"Las empresas no dejan de crecer por falta de talento; dejan de crecer por falta de claridad."*
- *"Cuando el esfuerzo ya no alcanza, la estrategia se vuelve indispensable."*
- *"El tiempo no se compra, se invierte."* (institucional)
- *"Devuélvele claridad al rumbo de tu empresa."* (promesa)
### Tagline de marca
*"creeer en ti, creeer en mí, creeer en el proyecto."*
### Pilares (disciplinas, NO la promesa)
**Comunicación · Digital · Experiencias** (+ Integración y acción). Son las herramientas, no el producto. **Viven en sección "cómo", no en el hero.**
---
## Sistema de diseño oficial — Manual de Identidad Visual v1.0 (mayo 2026)
> **Fuente:** `docs/branding.jpeg` — Manual de Identidad Visual aprobado por la dueña (28/jul/2026). **Reemplaza** todas las decisiones previas de paleta, tipografía y logotipo del brainstorming y del pptx.
### Filosofía de marca
> *"Transformamos la complejidad en claridad estratégica para construir ecosistemas empresariales que crecen con dirección y propósito."*
Proceso visual: **Complejidad** (múltiples variables) → **Enfoque** (priorizamos y alineamos lo esencial) → **Claridad** (una estrategia, una dirección, resultados extraordinarios).
### Símbolo principal
- **Tres líneas horizontales** (≡) que representan el proceso de transformar lo complejo en lo esencial
- Las líneas van de muchas/desordenadas (complejidad) a pocas/alineadas (claridad)
- Significado: "una estrategia clara, con dirección y propósito"
### Logotipo
- Wordmark: **`cre≡er`** — la "e" central se reemplaza por el **símbolo de tres líneas** (≡) en dorado
- SIEMPRE en minúsculas
- Tagline debajo: `CLARIDAD · DIRECCIÓN · CRECIMIENTO`
- Eslogan: *"Estrategia que transforma."*
**Versiones del logotipo:**
| Versión | Descripción |
|---------|-------------|
| Principal | `cre≡er` + tagline "CLARIDAD · DIRECCIÓN · CRECIMIENTO" |
| Secundaria | `cre≡er` sin tagline |
| Ícono | Solo el símbolo ≡ (tres líneas) |
| Negativa | Fondo navy, logotipo en blanco + dorado |
| Negativo (ícono) | Símbolo ≡ en dorado sobre navy |
| Escala de grises | Todo en grises |
**Tamaño mínimo:** impreso 20 mm, digital 120 px.
### Paleta de colores
**Colores principales:**
| Nombre | Hex | Pantone | RGB | Uso |
|--------|-----|---------|-----|-----|
| **Azul Profundo** | `#0A1D3D` | 2965 C | 10, 29, 61 | Texto principal, fondos oscuros, CTAs |
| **Dorado Estratégico** | `#C4942E` | 872 C | 196, 154, 46 | Acentos, títulos, símbolo ≡, énfasis |
**Colores neutros:**
| Nombre | Hex | RGB | Uso |
|--------|-----|-----|-----|
| Azul Claro | `#C8EEF5` | 222, 226, 245 | Fondos alternativos, secciones suaves |
| Gris Claro | `#F2F2F2` | 242, 242, 242 | Fondos neutros, separadores |
| Gris Medio | `#AAAAAA` | 166, 166, 166 | Texto secundario, placeholders |
| Blanco | `#FFFFFF` | 255, 255, 255 | Fondo principal |
**Regla:** NO amarillo eléctrico tipo Hormozi (rompe cercanía).
### Tipografía
**Manual de marca (impreso/presentación):** Aptos (principal) + Aptos Narrow (secundaria).
**Implementación web (landing):** **Open Sans** — vía `next/font/google`, pesos 400/600/700/800 + itálicas.
> ⚠️ **Por qué no Aptos en web:** Aptos es proprietaria de Microsoft; su EULA prohíbe distribuirla o servirla en un sitio público ("No Distribution Rights" + "publish for others to copy"). La dueña, informada del riesgo, inicialmente autorizó usarla; pero al faltar los pesos regular/semibold en la conversión a `.woff2`, decidió pasar a la alternativa open-source más cercana. **Open Sans** comparte diseñador con Aptos (Steve Matteson) y es OFL (libre para web). Decisión: 28/jul/2026.
### Iconografía
- Estilo: **líneas simples, geométricas, consistentes en grosor**
- Iconos definidos: Análisis, Enfoque, Estrategia, Crecimiento, Alineación, Resultados
- Colores: Azul Profundo + Dorado Estratégico
### Recursos visuales
- **Patrón de puntos** (grid de puntos dorados)
- **Líneas y divisores** (horizontales, en dorado o navy)
- **Recurso gráfico** (las tres líneas del símbolo en diversas orientaciones y escalas)
### Patrones de alineación
- `center` → hero / títulos de impacto
- `right` → momentos asimétricos
- `left` → contenido, citas, cierre
- Mucho aire/whitespace alrededor de los textos
---
## Restricciones fijadas (decisiones bloqueadas)
Estas decisiones **no se reabren sin consultar a la dueña.**
### Copy
- Voz en **segunda persona ("tú")** — nunca "el empresario", siempre "tu negocio"
- Sin jerga corporativa
- "Simple, directo, limpio" aplica al copy también
### Frases-ancla aprobadas
- *"Las empresas no dejan de crecer por falta de talento; dejan de crecer por falta de claridad."*
- *"Cuando el esfuerzo ya no alcanza, la estrategia se vuelve indispensable."*
- *"El tiempo no se compra, se invierte."* (institucional)
- *"Devuélvele claridad al rumbo de tu empresa."* (promesa)
- *"Estrategia que transforma."* (eslogan oficial)
### Taglines
- **Eslogan oficial:** *"Estrategia que transforma."*
- **Tagline de marca:** *"CLARIDAD · DIRECCIÓN · CRECIMIENTO"*
- **Tagline emocional:** *"creeer en ti, creeer en mí, creeer en el proyecto."*
### Estructura
- One-page (single page)
- Sin backend, contacto por enlaces directos (WhatsApp, email, LinkedIn)
- Sin escasez en el hero (la dueña pidió quitar cupos/cohorte del hero)
### Pendiente de decisión de la dueña
- **Hero final:** HV-1 (bold sans sin cajas) vs HV-2 (palabras en caja) — ver `docs/hero-finalists.md`
- ~~**Tipografía web:** Aptos vs Inter~~ → **RESUELTO (28/jul/2026): Open Sans**. Ver sección "Tipografía" arriba.
---
## Cómo usar este archivo
Cuando trabajes en creeer (copy, diseño, código, decisiones de producto):
1. **Lee esto primero** — es la voz de la dueña.
2. **Pasa cada decisión por el filtro** de "seguridad + cercanía" + "simple, directo, limpio".
3. **Si una propuesta contradice algo de aquí**, detente y pregunta antes de avanzar.
4. **Si la dueña envía un mensaje nuevo**, agrégalo a la sección "Mensajes verbatim" con fecha y contexto.
5. **Las decisiones bloqueadas** (paleta, wordmark, voz) no se reabren sin su aprobación explícita.
-118
View File
@@ -1,118 +0,0 @@
# Hero finalists · creeer
Dos candidatos finales para el hero de la landing. **Mismo copy, misma paleta, mismo wordmark, mismo layout centrado.** La diferencia es el **tono del headline**:
- **HV-1** → punch Hormozi (headline en Inter 900 uppercase)
- **V-a** → pureza editorial (headline en Georgia italic, todo serif)
La dueña puede elegir uno. Ambos respetan: wordmark `cre`**`e`**`er.` en Georgia italic, voz en "tú", paleta del pptx, "simple, directo, limpio", sin escasez en el hero.
---
## Base común
**Wordmark:** `cre`**`e`**`er.` en **Georgia 700 italic**, minúscula, con la **"e" central** (2ª de las 3) en peso 900 + dorado `#B8901f`, y punto final dorado. *(El wordmark es la firma de marca — siempre serif italic, sin importar el hero elegido.)*
**Eyebrow:** `— tu estratega de crecimiento —` (small caps, dorado tostado)
**Headline copy:**
> Tu negocio no necesita más esfuerzo.
> Necesita más claridad.
**CTA:** `Define tu siguiente movimiento →` (navy fill, white text, chunky)
**Paleta:**
- Blanco `#FFFFFF` (fondo)
- Navy `#0A1F44` (texto + CTA)
- Dorado tostado `#B8901f` (acentos)
**Sin escasez:** NO se muestran cupos, cohorte ni "Generación Fundadora" en el hero (decisión de la dueña). Esos elementos, si aparecen, viven en sección posterior de la página.
**Voz:** segunda persona ("tú"/"tu negocio"), directa, sin jerga corporativa.
---
## HV-1 · Hormozi ligero (punch)
**Tratamiento:**
- Headline en **Inter 900 uppercase** con tracking negativo (`letter-spacing: -1.5px`)
- "más **esfuerzo**" en color dorado tostado `#B8901f`
- "más **claridad**" con **subrayado dorado** grueso (5px solid)
- Wordmark sigue en Georgia italic (contraste serif/sans intencional)
**Sensación:** punto medio exacto entre la limpieza del pptx y el punch Hormozi. El contraste tipográfico (wordmark serif + headline sans) da energía sin perder clase.
**Trade-off:**
- ✅ Punch visual, más conversión
- ✅ Headline memorable, retención media
- ✅ Mantiene el wordmark serif (marca intacta)
- ⚠️ Más agresivo que V-a; algo menos de cercanía
**Mejor si la dueña prioriza:** impacto, conversión, diferenciación.
---
## V-a · Editorial minimal (cercanía)
**Tratamiento:**
- **TODO en Georgia serif italic** — wordmark + eyebrow + headline + CTA
- "más **esfuerzo**" en color dorado tostado, italic
- "más **claridad**" subrayada en dorado
- Sin métricas, sin subhead, sin elementos accesorios. Solo 4 bloques: wordmark → eyebrow → headline → CTA
**Sensación:** calma editorial pura. Máximo aire. "Simple, directo, limpio" llevado al extremo. La cercanía que pidió la dueña en su forma más pura.
**Trade-off:**
- ✅ Máxima cercanía y limpieza
- ✅ Coherencia tipográfica total (todo serif)
- ✅ Elegancia atemporal
- ⚠️ Menos punch de conversión que HV-1
- ⚠️ Podría sentirse "demasiado tranquilo" para vender
**Mejor si la dueña prioriza:** "seguridad + cercanía", "simple, directo, limpio", prestigio editorial.
---
## Spectrum visual
| Hero | Cercanía | Punch Hormozi | Estilo |
|---|---|---|---|
| **V-a** (todo serif italic) | **Alta** | Bajo | Editorial puro — calma, elegancia |
| **HV-1** (serif + sans Hormozi) | Media | **Medio-alto** | Hybrid — punch + marca |
---
## Cómo decidir (pregunta para la dueña)
> ¿Prefieres que el hero **respire y se sienta cálido** (V-a), o que **tenga punch e impacte** (HV-1)?
Ambos funcionan. La decisión es de tono, no de calidad. El wordmark, la paleta y el copy son idénticos — solo cambia la energía del headline.
---
## Mockups en vivo
Servidor visual companion (activo durante la sesión de brainstorming):
```
http://localhost:55739/?key=7cb508aff417782d47d2fdf9c54062393af1d0d1e6e2d19090228dbf67df333b
```
Archivos fuente de los mockups:
- `.superpowers/brainstorm/2447-1784580629/content/three-georgia-heroes.html` (#2 V-a)
- `.superpowers/brainstorm/2447-1784580629/content/heroes-wordmark-restored.html` (HV-1 con wordmark Georgia)
- `.superpowers/brainstorm/2447-1784580629/content/va-large-locked.html` (V-a en grande)
---
## Nota técnica (para implementación)
Independientemente de cuál se elija, la implementación es la misma base Next.js + Tailwind + Framer Motion:
- Componente `<Hero>` con el wordmark, eyebrow, headline y CTA
- Animación de entrada escalonada (Framer Motion): wordmark → eyebrow → headline → CTA
- Fuentes: **Georgia** (system serif, no requiere import) + **Inter** (Google Fonts, solo si se usa HV-1)
**Diferencia entre HV-1 y V-a en código:**
- V-a: headline usa `font-family: Georgia, serif; font-style: italic`
- HV-1: headline usa `font-family: 'Inter', sans-serif; font-weight: 900; text-transform: uppercase`
Cambiar entre ambos es **trivial** (un `className` condicional). Se puede dejar como prop del componente `<Hero variant="editorial" | "hormozi">` para que la dueña decida en QA.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,462 +0,0 @@
# creeer Landing Additions — Header + Cómo empezar + Footer — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a minimal sticky Header, a new "Cómo empezar" on-ramp section (section 07), and a slim Footer to the existing 7-section `creeer` landing page.
**Architecture:** Three additive pieces reusing the existing primitives (`Wordmark`, `CTA`, `Container`), tokens, the `motion` + `motionProps(reduced)` pattern, and the copy-in-`lib/content.ts` convention. Header and Footer are static server components wrapping `<main>`; `ComoEmpezar` is a client section following the exact pattern of `Mecanismo.tsx`/`Disciplinas.tsx`. Cierre becomes section 08. No backend, no forms — the site stays a static export.
**Tech Stack:** Next.js 14 (App Router, `output: export`), React 18, TypeScript, Tailwind CSS 3.4, Framer Motion 11. Tests: Vitest + @testing-library/react.
## Global Constraints
(Copied from the spec `docs/superpowers/specs/2026-07-21-creeer-landing-additions-design.md` — apply to every task.)
- **Wordmark** always via the `Wordmark` primitive — never re-implemented, never uppercased.
- **Paleta (tokens):** `ink #0A1F44` · `gold #B8901f` (accents on LIGHT) · `gold-bright #D4AF37` (accents on DARK) · `base #FBFAF7` (light bg, class `bg-base`) · dark bg class `bg-bg-dark` · `cream` (text on dark) · `muted`/`muted-dark`.
- **Tipografía:** Georgia italic (`font-serif italic`) = wordmark + section titles; Inter = body/UI.
- **Sin backend, sin formularios** — contacto solo por enlaces directos (WhatsApp/mailto/LinkedIn).
- **Un único `<h1>`** en toda la página — vive en el Hero. New pieces use `<h2>`/`<h3>` or no heading.
- **Todo el copy** vive en `lib/content.ts` (nuevas claves `header`, `comoEmpezar`, `footer`).
- **Voz** en segunda persona, español.
- **Reduced motion:** every Framer Motion element gets `{...motionProps(reduced)}` after its `initial`/`whileInView`/`viewport` props (`reduced = usePrefersReducedMotion()`). Header/Footer are static (no motion).
- **`tsconfig.json` has no `target`** → avoid ES2018+ regex flags (`s`/dotAll) or `tsc --noEmit` fails.
## File Structure
```
components/
├── layout/
│ ├── Header.tsx # Task 1 — sticky bar (server component)
│ ├── Footer.tsx # Task 3 — slim footer strip (server component)
│ └── __tests__/
│ ├── Header.test.tsx # Task 1
│ └── Footer.test.tsx # Task 3
└── sections/
├── ComoEmpezar.tsx # Task 2 — section 07 (client, motion pattern)
└── __tests__/
└── ComoEmpezar.test.tsx # Task 2
lib/content.ts # Tasks 1/2/3 append `header` / `comoEmpezar` / `footer`
app/page.tsx # Tasks 1/2/3 wire the pieces; Task 4 anchor offset
components/sections/Cierre.tsx # Task 4 — add scroll-mt to the #contacto anchor
```
Each component task also wires itself into `app/page.tsx` (matching the repo's established incremental-compose pattern), so the page stays working after every task.
---
### Task 1: Header (minimal sticky)
**Files:**
- Create: `components/layout/Header.tsx`
- Create: `components/layout/__tests__/Header.test.tsx`
- Modify: `lib/content.ts` (append `header`)
- Modify: `app/page.tsx` (place `<Header />` before `<main>`)
**Interfaces:**
- Consumes: `Wordmark` (`size`, `onDark`), `CTA` (`href`, `label`, `variant`), `Container`, and `header.cta` from content.
- Produces: `Header()` at `components/layout/Header.tsx` — no props. Consumed by `app/page.tsx`.
- [ ] **Step 1: Append the `header` copy to `lib/content.ts`**
Add at the end of `lib/content.ts`:
```ts
export const header = {
cta: 'Hablemos', // short nav CTA → #contacto (the full CTA lives in Hero/Cierre)
};
```
- [ ] **Step 2: Write the failing test `components/layout/__tests__/Header.test.tsx`**
```tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Header } from '../Header';
describe('<Header />', () => {
it('renders the wordmark', () => {
const { container } = render(<Header />);
expect(container.textContent).toContain('creeer');
});
it('renders a persistent CTA linking to the contact section', () => {
render(<Header />);
const cta = screen.getByRole('link', { name: /Hablemos/i });
expect(cta).toHaveAttribute('href', '#contacto');
});
});
```
- [ ] **Step 3: Run the test to verify it fails**
Run: `npm test -- Header`
Expected: FAIL with "Cannot find module '../Header'".
- [ ] **Step 4: Write `components/layout/Header.tsx`**
```tsx
import { Wordmark } from '@/components/primitives/Wordmark';
import { CTA } from '@/components/primitives/CTA';
import { Container } from '@/components/primitives/Container';
import { header } from '@/lib/content';
export function Header() {
return (
<header className="sticky top-0 z-50 border-b border-ink/10 bg-base/80 backdrop-blur">
<Container className="flex items-center justify-between py-3">
<a href="#top" aria-label="creeer — inicio">
<Wordmark size="md" />
</a>
<nav aria-label="Principal">
<CTA href="#contacto" label={header.cta} variant="light" />
</nav>
</Container>
</header>
);
}
```
Notes: server component (no `'use client'`, no motion — `position: sticky` is pure CSS). `#top` is the special top-of-document fragment (no element with that id is required). The `CTA` primitive appends the `→`, so `header.cta` holds just the word.
- [ ] **Step 5: Run the test to verify it passes**
Run: `npm test -- Header`
Expected: PASS, 2 tests.
- [ ] **Step 6: Wire `<Header />` into `app/page.tsx`**
Add the import after the existing section imports (top of the file):
```tsx
import { Header } from '@/components/layout/Header';
```
Then insert `<Header />` between the JSON-LD `<script>` and `<main>`:
```tsx
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<Header />
<main>
```
- [ ] **Step 7: Typecheck and run the full suite**
Run: `npx tsc --noEmit && npm test`
Expected: tsc clean; all tests pass.
- [ ] **Step 8: Commit**
```bash
git add components/layout/Header.tsx components/layout/__tests__/Header.test.tsx lib/content.ts app/page.tsx
git commit -m "feat: sticky Header with persistent contact CTA"
```
---
### Task 2: "Cómo empezar" section (07)
**Files:**
- Create: `components/sections/ComoEmpezar.tsx`
- Create: `components/sections/__tests__/ComoEmpezar.test.tsx`
- Modify: `lib/content.ts` (append `comoEmpezar`)
- Modify: `app/page.tsx` (place `<ComoEmpezar />` before `<Cierre />`)
**Interfaces:**
- Consumes: `Container`, `CTA`, `comoEmpezar` + `cierre.contact.whatsapp.href` from content, and `fadeInUp`/`staggerContainer`/`viewportOnce`/`usePrefersReducedMotion`/`motionProps` from `lib/motion`.
- Produces: `ComoEmpezar()` at `components/sections/ComoEmpezar.tsx` — no props. Consumed by `app/page.tsx`.
- [ ] **Step 1: Append the `comoEmpezar` copy to `lib/content.ts`**
Add at the end of `lib/content.ts`:
```ts
export const comoEmpezar = {
eyebrow: 'cómo empezar',
title: 'Tu siguiente movimiento empieza con una conversación.',
steps: [
{ n: '01', name: 'Escríbeme', desc: 'Cuéntame dónde está tu negocio hoy.' },
{ n: '02', name: 'Sesión de diagnóstico', desc: 'Una conversación para ubicar tu punto de inflexión.' },
{ n: '03', name: 'Estrategia ejecutable', desc: 'Sales con claridad y tu siguiente movimiento definido.' },
],
cta: 'Agenda tu sesión de diagnóstico',
whatsappText: 'Hola, quiero agendar una sesión de diagnóstico',
};
```
- [ ] **Step 2: Write the failing test `components/sections/__tests__/ComoEmpezar.test.tsx`**
```tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ComoEmpezar } from '../ComoEmpezar';
describe('<ComoEmpezar />', () => {
it('renders the on-ramp steps', () => {
render(<ComoEmpezar />);
expect(screen.getByText('Escríbeme')).toBeInTheDocument();
expect(screen.getByText('Sesión de diagnóstico')).toBeInTheDocument();
});
it('links the CTA to WhatsApp with a prefilled message', () => {
render(<ComoEmpezar />);
const cta = screen.getByRole('link', { name: /Agenda tu sesión de diagnóstico/i });
const href = cta.getAttribute('href') ?? '';
expect(href).toMatch(/^https:\/\/wa\.me\//);
expect(href).toContain('text=');
});
});
```
- [ ] **Step 3: Run the test to verify it fails**
Run: `npm test -- ComoEmpezar`
Expected: FAIL with "Cannot find module '../ComoEmpezar'".
- [ ] **Step 4: Write `components/sections/ComoEmpezar.tsx`**
```tsx
'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { CTA } from '@/components/primitives/CTA';
import { comoEmpezar, cierre } from '@/lib/content';
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
export function ComoEmpezar() {
const reduced = usePrefersReducedMotion();
const waHref = `${cierre.contact.whatsapp.href}?text=${encodeURIComponent(comoEmpezar.whatsappText)}`;
return (
<section className="bg-base">
<Container className="py-24 md:py-32">
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="text-xs tracking-[0.3em] uppercase font-semibold text-gold">{comoEmpezar.eyebrow}</motion.p>
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="mt-4 font-serif italic font-bold text-3xl md:text-5xl text-ink">{comoEmpezar.title}</motion.h2>
<motion.ol variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="mt-12 grid gap-10 md:grid-cols-3">
{comoEmpezar.steps.map((step) => (
<motion.li key={step.n} variants={fadeInUp} className="border-t border-gold/30 pt-6">
<div className="font-serif italic font-black text-5xl md:text-6xl text-gold leading-none">{step.n}</div>
<h3 className="mt-4 font-sans font-bold text-xl text-ink">{step.name}</h3>
<p className="mt-3 text-muted leading-relaxed">{step.desc}</p>
</motion.li>
))}
</motion.ol>
<motion.div variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
className="mt-12">
<CTA href={waHref} label={comoEmpezar.cta} variant="light" />
</motion.div>
</Container>
</section>
);
}
```
This mirrors `Mecanismo.tsx` exactly, but in the light-section palette (`bg-base`, accents in `gold` not `gold-bright`, `text-muted` for descriptions, `text-ink` for headings).
- [ ] **Step 5: Run the test to verify it passes**
Run: `npm test -- ComoEmpezar`
Expected: PASS, 2 tests.
- [ ] **Step 6: Wire `<ComoEmpezar />` into `app/page.tsx`**
Add the import with the other section imports:
```tsx
import { ComoEmpezar } from '@/components/sections/ComoEmpezar';
```
Insert `<ComoEmpezar />` between `<Estratega />` and `<Cierre />`:
```tsx
<Estratega />
<ComoEmpezar />
<Cierre />
```
- [ ] **Step 7: Typecheck and run the full suite**
Run: `npx tsc --noEmit && npm test`
Expected: tsc clean; all tests pass.
- [ ] **Step 8: Commit**
```bash
git add components/sections/ComoEmpezar.tsx components/sections/__tests__/ComoEmpezar.test.tsx lib/content.ts app/page.tsx
git commit -m "feat: Cómo empezar section (07) — 3-step on-ramp to WhatsApp"
```
---
### Task 3: Footer (slim strip)
**Files:**
- Create: `components/layout/Footer.tsx`
- Create: `components/layout/__tests__/Footer.test.tsx`
- Modify: `lib/content.ts` (append `footer`)
- Modify: `app/page.tsx` (place `<Footer />` after `</main>`)
**Interfaces:**
- Consumes: `Wordmark` (`size`, `onDark`), `Container`, and `footer.tagline`/`footer.legal` from content.
- Produces: `Footer()` at `components/layout/Footer.tsx` — no props. Consumed by `app/page.tsx`.
- [ ] **Step 1: Append the `footer` copy to `lib/content.ts`**
Add at the end of `lib/content.ts`:
```ts
export const footer = {
tagline: 'tu estratega de crecimiento',
legal: '© 2026 creeer',
};
```
- [ ] **Step 2: Write the failing test `components/layout/__tests__/Footer.test.tsx`**
```tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Footer } from '../Footer';
describe('<Footer />', () => {
it('renders the tagline and the legal line', () => {
render(<Footer />);
expect(screen.getByText('tu estratega de crecimiento')).toBeInTheDocument();
expect(screen.getByText(/© 2026 creeer/)).toBeInTheDocument();
});
it('does not introduce a second h1', () => {
render(<Footer />);
expect(screen.queryByRole('heading', { level: 1 })).toBeNull();
});
});
```
- [ ] **Step 3: Run the test to verify it fails**
Run: `npm test -- Footer`
Expected: FAIL with "Cannot find module '../Footer'".
- [ ] **Step 4: Write `components/layout/Footer.tsx`**
```tsx
import { Wordmark } from '@/components/primitives/Wordmark';
import { Container } from '@/components/primitives/Container';
import { footer } from '@/lib/content';
export function Footer() {
return (
<footer className="bg-bg-dark text-cream border-t border-cream/10">
<Container className="flex flex-col items-center gap-3 py-10 text-center md:flex-row md:justify-between md:text-left">
<Wordmark size="sm" onDark />
<p className="text-sm text-muted-dark">{footer.tagline}</p>
<p className="text-xs text-muted-dark">{footer.legal}</p>
</Container>
</footer>
);
}
```
Slim dark strip (`bg-bg-dark text-cream`) with a subtle top divider. It deliberately does NOT repeat the contact links — `Cierre` already owns them, and a second dark block of contacts would be redundant.
- [ ] **Step 5: Run the test to verify it passes**
Run: `npm test -- Footer`
Expected: PASS, 2 tests.
- [ ] **Step 6: Wire `<Footer />` into `app/page.tsx`**
Add the import near the `Header` import:
```tsx
import { Footer } from '@/components/layout/Footer';
```
Insert `<Footer />` after the closing `</main>` and before the fragment close:
```tsx
</main>
<Footer />
</>
```
- [ ] **Step 7: Typecheck and run the full suite**
Run: `npx tsc --noEmit && npm test`
Expected: tsc clean; all tests pass.
- [ ] **Step 8: Commit**
```bash
git add components/layout/Footer.tsx components/layout/__tests__/Footer.test.tsx lib/content.ts app/page.tsx
git commit -m "feat: slim Footer strip with wordmark, tagline and legal line"
```
---
### Task 4: Sticky-header anchor offset + static build verification
**Files:**
- Modify: `components/sections/Cierre.tsx` (add `scroll-mt` to the `#contacto` anchor target)
**Interfaces:**
- Consumes: the composed page from Tasks 1–3.
- Produces: a verified static export; the `#contacto` anchor target that clears the sticky header.
- [ ] **Step 1: Locate the `#contacto` anchor in `components/sections/Cierre.tsx`**
Run: `grep -n 'id="contacto"' components/sections/Cierre.tsx`
Expected: one match — the element carrying `id="contacto"` (the closing section's outer element).
- [ ] **Step 2: Add `scroll-mt-24` to that element's className**
On the element that has `id="contacto"`, add `scroll-mt-24` to its `className` so the sticky header (~60px) never overlaps the contact anchor when the Header/Hero CTAs jump to it. Example — if the element reads:
```tsx
<section id="contacto" className="bg-bg-dark text-cream">
```
change it to:
```tsx
<section id="contacto" className="bg-bg-dark text-cream scroll-mt-24">
```
(Only add the one utility class; do not change anything else in `Cierre.tsx`.)
- [ ] **Step 3: Run the full suite and typecheck**
Run: `npx tsc --noEmit && npm test`
Expected: tsc clean; all tests pass (Cierre's existing tests still green — the class addition changes nothing they assert).
- [ ] **Step 4: Run lint**
Run: `npx next lint`
Expected: "No ESLint warnings or errors".
- [ ] **Step 5: Run the static build and verify the output**
Run: `npm run build`
Expected: build succeeds. Then verify the composed page:
(The exported HTML is minified onto few lines, so count occurrences with `grep -o … | wc -l`, not `grep -c` which counts lines.)
Run: `grep -o '<section' out/index.html | wc -l` → expect `8` (Hero, FraseAncla, ParaQuien, Mecanismo, Disciplinas, Estratega, ComoEmpezar, Cierre — one `<section>` each).
Run: `grep -o '<h1' out/index.html | wc -l` → expect `1` (single h1, still the Hero).
Run: `grep -o '<header' out/index.html | wc -l` → expect `1`.
Run: `grep -o '<footer' out/index.html | wc -l` → expect `1`.
Run: `grep -o 'wa.me' out/index.html | wc -l` → expect at least `2` (Cierre's WhatsApp link + ComoEmpezar's prefilled link).
- [ ] **Step 6: Commit**
```bash
git add components/sections/Cierre.tsx
git commit -m "fix: scroll-margin on #contacto so the sticky header never overlaps it"
```
---
## After this plan
- The header/footer/on-ramp are complete. Remaining follow-ups (own branches): testimonials/social proof, results/case studies, FAQ, real OG image + branded favicon, real portrait, and the dueña's real contact details (the `wa.me/520000000000` placeholder in `lib/content.ts` still applies to both Cierre and the new ComoEmpezar CTA).
- Merge `feat/header-comoempezar-footer` into `main` once reviewed.
@@ -1,531 +0,0 @@
# Coherencia de las ilustraciones + hilo conductor al centro — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Que cada ilustración exprese el mensaje de su sección y la narrativa de marca (Complejidad → Enfoque → Claridad), y que el hilo dorado sea **visible bajando por el centro**, tras el texto.
**Architecture:** El hilo (`HiloConductor`) se mueve del canal izquierdo (donde vive hoy, `right-full`, `hidden 2xl:block`) al **centro** y se muestra en todos los breakpoints; para que no cruce/dañe la lectura, el contenido se levanta por encima de él subiendo `Container` a `relative z-20` (fondos de sección `z-auto` < hilo `z-10` < texto `z-20`). `IlustracionInflexion` se rehace como una curva que se dobla en el punto (sin fork, para dejar de duplicar a `ParaQuien`). Nudo/Bifurcación/Convergencia unifican sus grosores a la escala `1.5 / 2 / 3`. El nudo se reciente en `Dolores` para que el hilo central lo atraviese.
**Tech Stack:** Next.js 14 App Router (`output: 'export'`), TypeScript, Tailwind, Framer Motion, Vitest + jsdom + Testing Library.
## Global Constraints
- **Gestor de paquetes: `pnpm`.** Nunca `npm`.
- **Idioma:** copy, docs y mensajes de commit en **español**; comentarios de código en **inglés**.
- **No se toca el copy** de `lib/content.ts`. Ninguna tarea edita ese archivo.
- **No se reordenan las secciones** de `app/page.tsx`.
- **Colores por token**, nunca hex suelto: `text-dorado`, `text-azul-profundo`, `text-gris-medio`.
- **El wordmark `cre≡er` va siempre en minúsculas** y sólo se renderiza vía `<Wordmark />`.
- **No se agrega escasez** al hero. El test negativo `Hero.test.tsx` (`cupos|cohorte|generación fundadora`) debe seguir pasando.
- **Toda animación** pasa por `usePrefersReducedMotion()` / `useEnVista()`. La rama reducida SIEMPRE renderiza elementos SVG planos (`<path>`, `<line>`), nunca `motion.*` neutralizados (el bug del dash-scaffolding ya documentado).
- **Ilustraciones decorativas:** `aria-hidden="true"` y `focusable="false"` en el `<svg>`.
- **Escala tipográfica de trazo (nueva, fija):** base `2`, énfasis `3`, detalle fino `1.5`. Excepción única y documentada: `SecuenciaClaridad` resuelve en las barras reales del `≡` (grosor de `LINEAS_SIMBOLO`).
- **Verificación por tarea:** `pnpm test` en verde antes de cada commit. Al final del plan, además: `pnpm lint`, `npx tsc --noEmit` y `pnpm build`.
- **Rama de trabajo:** `feat/hilo-conductor-visual` (ya activa).
---
## Estructura de archivos
**Modificar:**
| Archivo | Responsabilidad del cambio |
|---|---|
| `components/primitives/HiloConductor.tsx` | Mover el hilo al centro, visible en todos los breakpoints, `z-10`, trazo más tenue/recto |
| `components/primitives/Container.tsx` | Añadir `relative z-20` para que el texto quede sobre el hilo |
| `components/primitives/IlustracionInflexion.tsx` | Rehacer: curva que se dobla en el punto (azul → dorada), sin fork, lienzo `220×120` |
| `components/primitives/IlustracionNudo.tsx` | Unificar grosores de las hebras a la escala |
| `components/primitives/IlustracionBifurcacion.tsx` | Grosor del tronco `2.5 → 2` |
| `components/primitives/IlustracionConvergencia.tsx` | Grosor de la estrategia `3.5 → 3` |
| `components/sections/Dolores.tsx` | Recentrar el nudo para que el hilo central lo atraviese |
**Crear:**
| Archivo | Responsabilidad |
|---|---|
| `components/primitives/__tests__/Container.test.tsx` | Fija el mecanismo de apilado (el contenido queda sobre el hilo) |
**Tests a actualizar:** `HiloConductor.test.tsx`, `IlustracionInflexion.test.tsx`, `IlustracionNudo.test.tsx`, `IlustracionBifurcacion.test.tsx`, `IlustracionConvergencia.test.tsx`, `Dolores.test.tsx`.
---
### Task 1: El hilo al centro, visible, tras el texto
Hoy el hilo vive en el canal izquierdo (`right-full`) y sólo aparece desde `2xl`. La dueña pidió que esté **en medio**. Se mueve al centro, se muestra en todos los breakpoints, y se pone **tras el texto** subiendo `Container` a `relative z-20` (fondos `z-auto` < hilo `z-10` < texto `z-20`): el hilo se lee como una columna tenue tras la lectura, nunca cruzándola.
**Files:**
- Modify: `components/primitives/HiloConductor.tsx`
- Modify: `components/primitives/Container.tsx`
- Modify: `components/primitives/__tests__/HiloConductor.test.tsx`
- Create: `components/primitives/__tests__/Container.test.tsx`
**Interfaces:**
- Consumes: `usePrefersReducedMotion`; `useScroll`, `useSpring`, `motion` de `framer-motion`.
- Produces: `HiloConductor` centrado y visible (mismo nombre de export, mismos consumidores: `app/page.tsx`). `Container` con `relative z-20` (mismo nombre de export).
- [ ] **Step 1: Actualizar los tests del hilo (deben fallar) y crear el test de Container**
En `components/primitives/__tests__/HiloConductor.test.tsx`, **sustituir** el test `sits in the margin band beside the column, never over it` por:
```tsx
it('runs down the centre of the column, not in a side gutter', () => {
const { container } = render(<HiloConductor />);
const cls = container.querySelector('svg')?.getAttribute('class') ?? '';
// `left-1/2 -translate-x-1/2` anchors the thread to the column's centre;
// the old `right-full` banished it to the left gutter, which is why the
// thread read as off to the side instead of as the page's spine.
expect(cls).toContain('left-1/2');
expect(cls).toContain('-translate-x-1/2');
expect(cls).not.toContain('right-full');
});
```
Y **sustituir** el test `is hidden below the breakpoint that guarantees a safe margin` por:
```tsx
it('renders on every breakpoint, not only on very wide screens', () => {
const { container } = render(<HiloConductor />);
const wrapperClass = container.firstElementChild?.className ?? '';
// The thread is the brand's through-line; it belongs in the middle on a
// phone too. Legibility is handled by layering it behind the text, not by
// hiding it below a breakpoint.
expect(wrapperClass).not.toContain('hidden');
expect(wrapperClass).not.toContain('2xl:block');
});
```
Los demás tests del archivo (decorativo, un solo path, `absolute` no `fixed`, `pointer-events-none`, nivel de apilado `z-\d+`, rama reducida con `<path>` plano) **se quedan sin cambios**.
Crear `components/primitives/__tests__/Container.test.tsx`:
```tsx
import { render } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Container } from '../Container';
describe('<Container />', () => {
it('lifts its content above the conductor thread', () => {
const { container } = render(<Container>contenido</Container>);
const cls = container.firstElementChild?.className ?? '';
// The thread sits at z-10; the section backgrounds are z-auto. Lifting the
// content column to z-20 is what puts the text *above* a centred thread,
// so the spine reads behind the reading instead of across it.
expect(cls).toContain('relative');
expect(cls).toMatch(/\bz-20\b/);
});
});
```
- [ ] **Step 2: Correr — deben fallar**
Run: `pnpm test --run components/primitives/__tests__/HiloConductor.test.tsx components/primitives/__tests__/Container.test.tsx`
Expected: FAIL — el svg del hilo aún tiene `right-full`/`hidden`, y Container no tiene `z-20`.
- [ ] **Step 3: Reescribir `HiloConductor.tsx`**
Contenido completo del archivo:
```tsx
'use client';
import { motion, useScroll, useSpring } from 'framer-motion';
import { usePrefersReducedMotion } from '@/lib/motion';
// The gold spine of the page: one continuous line down the vertical centre,
// drawing itself as the reader scrolls. It is what turns ten sections into a
// single route — *claridad · dirección · crecimiento* made visible, per the
// Manual de Identidad Visual.
//
// Centred, never in a side gutter: the brand's through-line belongs in the
// middle, on every breakpoint. It sits ABOVE the section backgrounds (z-10)
// but BELOW the text, which `Container` lifts to z-20 — so the thread reads
// as a faint watermark spine behind the reading, never crossing it.
//
// Page-level useScroll() with no target ref on purpose: no element measuring,
// no "container has a static position" warnings, one listener for the page.
const TRAZO =
'M50 0 C 42 200, 58 400, 50 600 C 42 800, 58 1000, 50 1200';
export function HiloConductor({ className = '' }: { className?: string }) {
const reduced = usePrefersReducedMotion();
const { scrollYProgress } = useScroll();
const avance = useSpring(scrollYProgress, { stiffness: 90, damping: 30, restDelta: 0.001 });
const wrapper = `pointer-events-none absolute inset-0 z-10 ${className}`;
// Plain <path> for reduced motion: motion.path applies dash scaffolding even
// without initial/animate and would leave the thread invisible.
const trazo = reduced ? (
<path d={TRAZO} fill="none" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" />
) : (
<motion.path
d={TRAZO}
fill="none"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
style={{ pathLength: avance }}
/>
);
return (
<div className={wrapper}>
{/* Mirrors Container's own centring (`mx-auto max-w-content`) so the
thread is measured from the real column edge and stays centred in it. */}
<div className="relative mx-auto h-full w-full max-w-content">
<svg
className="absolute inset-y-0 left-1/2 h-full w-14 -translate-x-1/2 text-dorado/25"
viewBox="0 0 100 1200"
preserveAspectRatio="none"
aria-hidden="true"
focusable="false"
>
{trazo}
</svg>
</div>
</div>
);
}
```
- [ ] **Step 4: Añadir `relative z-20` a `Container.tsx`**
Sustituir la línea de retorno por:
```tsx
return <Tag className={`relative z-20 mx-auto w-full max-w-content px-6 md:px-12 ${className}`}>{children}</Tag>;
```
- [ ] **Step 5: Correr — debe pasar**
Run: `pnpm test --run components/primitives/__tests__/HiloConductor.test.tsx components/primitives/__tests__/Container.test.tsx`
Expected: PASS (8 tests del hilo + 1 de Container).
- [ ] **Step 6: Correr la suite completa y commitear**
Run: `pnpm test`
Expected: PASS (los cambios de apilado no rompen tests de sección, que verifican copy/contrato, no clases de Container).
```bash
git add components/primitives/HiloConductor.tsx components/primitives/Container.tsx components/primitives/__tests__/HiloConductor.test.tsx components/primitives/__tests__/Container.test.tsx
git commit -m "feat(hilo): el conductor baja visible por el centro, tras el texto"
```
---
### Task 2: `IlustracionInflexion` — la curva que se dobla, sin fork
Hoy dibuja tres caminos divergentes (crecimiento / deriva / estrategia): el **mismo fork** que `IlustracionBifurcacion`. Se rehace como **una sola línea que se dobla** en el punto de inflexión — azul (el presente) antes del punto, dorada (la claridad) después — para que encarene el concepto de "punto de inflexión" y deje de duplicar a `ParaQuien`. Lienzo `220×120` (compartido con Bifurcación y Convergencia).
**Files:**
- Modify: `components/primitives/IlustracionInflexion.tsx`
- Modify: `components/primitives/__tests__/IlustracionInflexion.test.tsx`
**Interfaces:**
- Consumes: `useEnVista` de `lib/motion`.
- Produces: `IlustracionInflexion({ className?: string })` (mismo nombre; la consume `PuntosInflexion`).
- [ ] **Step 1: Reescribir el test (debe fallar)**
Contenido completo de `components/primitives/__tests__/IlustracionInflexion.test.tsx`:
```tsx
import { render } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { IlustracionInflexion } from '../IlustracionInflexion';
describe('<IlustracionInflexion />', () => {
it('is decorative: hidden from assistive tech', () => {
const { container } = render(<IlustracionInflexion />);
const svg = container.querySelector('svg');
expect(svg).toHaveAttribute('aria-hidden', 'true');
expect(svg).toHaveAttribute('focusable', 'false');
});
it('shares the unified canvas with the other scene illustrations', () => {
const { container } = render(<IlustracionInflexion />);
expect(container.querySelector('svg')).toHaveAttribute('viewBox', '0 0 220 120');
});
it('is a single bend at the inflection point, not a fork', () => {
const { container } = render(<IlustracionInflexion />);
// The previous version drew three diverging paths (growth / drift /
// strategy) — the same fork as ParaQuien. The rework is one continuous
// line that bends at the point: two segments, pre- and post-inflection.
expect(container.querySelectorAll('path')).toHaveLength(2);
});
it('marks the inflection point itself', () => {
const { container } = render(<IlustracionInflexion />);
expect(container.querySelectorAll('circle').length).toBeGreaterThanOrEqual(1);
});
it('bends from the present (azul) into clarity (dorado)', () => {
const { container } = render(<IlustracionInflexion />);
const clases = Array.from(container.querySelectorAll('path')).map((p) => p.getAttribute('class') ?? '');
expect(clases.some((c) => c.includes('text-azul-profundo'))).toBe(true);
expect(clases.some((c) => c.includes('text-dorado'))).toBe(true);
});
});
```
- [ ] **Step 2: Correr — debe fallar**
Run: `pnpm test --run components/primitives/__tests__/IlustracionInflexion.test.tsx`
Expected: FAIL — hoy hay 3 paths (no 2) y el viewBox es `0 0 320 180`.
- [ ] **Step 3: Reescribir `IlustracionInflexion.tsx`**
Contenido completo del archivo:
```tsx
'use client';
import { motion } from 'framer-motion';
import { useEnVista } from '@/lib/motion';
// The section's own subject, drawn: the inflection point itself. One line
// rises gently (the present, azul), bends at the marked point, and from there
// climbs steeply in dorado — clarity accelerating the climb. No fork, no
// "deriva": that bifurcation belongs to ParaQuien, not here.
const ANTES = 'M10 110 C 55 104, 85 88, 110 72';
const DESPUES = 'M110 72 C 145 60, 178 24, 210 12';
export function IlustracionInflexion({ className = '' }: { className?: string }) {
const { ref, enVista, reduced } = useEnVista();
const punto = (
<>
<circle cx={110} cy={72} r={6} fill="currentColor" className="text-dorado" />
<circle cx={110} cy={72} r={10} fill="none" stroke="currentColor" className="text-dorado" strokeWidth={1} strokeOpacity={0.4} />
</>
);
// Plain <path> under reduced motion — motion.path applies its own dash
// scaffolding and would leave the drawing invisible.
if (reduced) {
return (
<svg ref={ref} viewBox="0 0 220 120" className={className} aria-hidden="true" focusable="false">
<path d={ANTES} fill="none" stroke="currentColor" className="text-azul-profundo" strokeWidth={2} strokeLinecap="round" />
<path d={DESPUES} fill="none" stroke="currentColor" className="text-dorado" strokeWidth={3} strokeLinecap="round" />
{punto}
</svg>
);
}
const dibuja = (delay: number) => ({
initial: { pathLength: 0 },
animate: enVista ? { pathLength: 1 } : { pathLength: 0 },
transition: { duration: 1.1, delay, ease: [0.22, 1, 0.36, 1] as const },
});
return (
<svg ref={ref} viewBox="0 0 220 120" className={className} aria-hidden="true" focusable="false">
<motion.path d={ANTES} fill="none" stroke="currentColor" className="text-azul-profundo" strokeWidth={2} strokeLinecap="round" {...dibuja(0)} />
<motion.path d={DESPUES} fill="none" stroke="currentColor" className="text-dorado" strokeWidth={3} strokeLinecap="round" {...dibuja(0.5)} />
{punto}
</svg>
);
}
```
- [ ] **Step 4: Correr — debe pasar**
Run: `pnpm test --run components/primitives/__tests__/IlustracionInflexion.test.tsx`
Expected: PASS (5 tests).
- [ ] **Step 5: Correr la suite completa y commitear**
Run: `pnpm test`
Expected: PASS.
```bash
git add components/primitives/IlustracionInflexion.tsx components/primitives/__tests__/IlustracionInflexion.test.tsx
git commit -m "feat(inflexion): la curva que se dobla en el punto, sin fork"
```
---
### Task 3: Unificar grosores a la escala `1.5 / 2 / 3`
`IlustracionNudo` (`2.5`), `IlustracionBifurcacion` (tronco `2.5`) e `IlustracionConvergencia` (estrategia `3.5`) quedan fuera de la escala. Un test de contrato exige que todo trazo pertenezca a `{1.5, 2, 3}` y impulsa los cambios.
**Files:**
- Modify: `components/primitives/IlustracionNudo.tsx`
- Modify: `components/primitives/IlustracionBifurcacion.tsx`
- Modify: `components/primitives/IlustracionConvergencia.tsx`
- Modify: `components/primitives/__tests__/IlustracionNudo.test.tsx`
- Modify: `components/primitives/__tests__/IlustracionBifurcacion.test.tsx`
- Modify: `components/primitives/__tests__/IlustracionConvergencia.test.tsx`
**Interfaces:**
- Sin cambios de interfaz pública. Sólo grosores.
- [ ] **Step 1: Añadir el test de escala (debe fallar)**
Añadir a `components/primitives/__tests__/IlustracionNudo.test.tsx`, dentro del `describe`:
```tsx
it('keeps every stroke within the shared line-art scale', () => {
const { container } = render(<IlustracionNudo />);
const anchos = Array.from(container.querySelectorAll('path')).map((p) => p.getAttribute('stroke-width'));
expect(anchos.length).toBeGreaterThan(0);
anchos.forEach((a) => expect(['1.5', '2', '3']).toContain(a));
});
```
Añadir a `components/primitives/__tests__/IlustracionBifurcacion.test.tsx`, dentro del `describe`:
```tsx
it('keeps every stroke within the shared line-art scale', () => {
const { container } = render(<IlustracionBifurcacion />);
const anchos = Array.from(container.querySelectorAll('path')).map((p) => p.getAttribute('stroke-width'));
expect(anchos.length).toBeGreaterThan(0);
anchos.forEach((a) => expect(['1.5', '2', '3']).toContain(a));
});
```
Añadir a `components/primitives/__tests__/IlustracionConvergencia.test.tsx`, dentro del `describe`:
```tsx
it('keeps every stroke within the shared line-art scale', () => {
const { container } = render(<IlustracionConvergencia />);
const anchos = Array.from(container.querySelectorAll('path')).map((p) => p.getAttribute('stroke-width'));
expect(anchos.length).toBeGreaterThan(0);
anchos.forEach((a) => expect(['1.5', '2', '3']).toContain(a));
});
```
- [ ] **Step 2: Correr — deben fallar**
Run: `pnpm test --run components/primitives/__tests__/IlustracionNudo.test.tsx components/primitives/__tests__/IlustracionBifurcacion.test.tsx components/primitives/__tests__/IlustracionConvergencia.test.tsx`
Expected: FAIL — `2.5` (Nudo, Bifurcación) y `3.5` (Convergencia) no están en `{1.5, 2, 3}`.
- [ ] **Step 3: Unificar los grosores**
En `components/primitives/IlustracionNudo.tsx`, sustituir el array `HEBRAS` por grosores de la escala (la primera hebra pasa de `2.5` a `3` —énfasis— para conservar la jerarquía visual del nudo):
```ts
const HEBRAS = [
{ d: 'M30 90 C 60 20, 130 20, 140 70 C 148 112, 78 118, 70 78 C 63 44, 120 38, 150 66', ancho: 3 },
{ d: 'M20 60 C 70 110, 120 110, 160 60', ancho: 2 },
{ d: 'M40 110 C 80 40, 110 130, 155 95', ancho: 2 },
{ d: 'M25 40 C 90 70, 100 100, 165 40', ancho: 1.5 },
];
```
En `components/primitives/IlustracionBifurcacion.tsx`, cambiar el `strokeWidth` del tronco de `2.5` a `2` **en las dos ramas** (reduced y animada):
```tsx
<path d={TRONCO} fill="none" stroke="currentColor" className="text-azul-profundo" strokeWidth={2} strokeLinecap="round" />
```
```tsx
<motion.path d={TRONCO} fill="none" stroke="currentColor" className="text-azul-profundo" strokeWidth={2} strokeLinecap="round" {...dibuja(0)} />
```
En `components/primitives/IlustracionConvergencia.tsx`, cambiar el `strokeWidth` de la estrategia de `3.5` a `3` **en las dos ramas**:
```tsx
<path d={ESTRATEGIA} fill="none" stroke="currentColor" className="text-dorado" strokeWidth={3} strokeLinecap="round" />
```
```tsx
<motion.path d={ESTRATEGIA} fill="none" stroke="currentColor" className="text-dorado" strokeWidth={3} strokeLinecap="round" {...dibuja(0.7)} />
```
- [ ] **Step 4: Correr — deben pasar**
Run: `pnpm test --run components/primitives/__tests__/IlustracionNudo.test.tsx components/primitives/__tests__/IlustracionBifurcacion.test.tsx components/primitives/__tests__/IlustracionConvergencia.test.tsx`
Expected: PASS.
- [ ] **Step 5: Correr la suite completa y commitear**
Run: `pnpm test`
Expected: PASS.
```bash
git add components/primitives/IlustracionNudo.tsx components/primitives/IlustracionBifurcacion.tsx components/primitives/IlustracionConvergencia.tsx components/primitives/__tests__/IlustracionNudo.test.tsx components/primitives/__tests__/IlustracionBifurcacion.test.tsx components/primitives/__tests__/IlustracionConvergencia.test.tsx
git commit -m "refactor(ilustraciones): grosores unificados a la escala 1.5/2/3"
```
---
### Task 4: El nudo, al centro para que el hilo lo atraviese (`Dolores`)
El nudo hoy vive arriba a la derecha (`-top-6 right-4`); el hilo central no lo toca. Se recentrea en `Dolores` para que el hilo (Task 1) lo atraviese —el dolor, cosido por el hilo que es lo que lo va a desatar—, conectando el nudo con el remate *"se resuelve con una estrategia clara"*.
**Files:**
- Modify: `components/sections/Dolores.tsx`
- Modify: `components/sections/__tests__/Dolores.test.tsx`
**Interfaces:**
- Consumes: `IlustracionNudo` (Task 3 ya dejó sus grosores listos).
- Sin cambios de interfaz pública.
- [ ] **Step 1: Añadir el test que falla**
Añadir a `components/sections/__tests__/Dolores.test.tsx`, dentro del `describe`:
```tsx
it('centres the knot so the conductor thread runs through it', () => {
const { container } = render(<Dolores />);
// The knot is the centred decorative svg of the section; the conductor
// thread (Task 1) runs down the centre, so only a centred knot gets
// traversed by it — stitching the pain to the resolution below.
const centrados = Array.from(container.querySelectorAll('svg')).filter((s) => {
const cls = s.getAttribute('class') ?? '';
return cls.includes('left-1/2') && cls.includes('-translate-x-1/2');
});
expect(centrados.length).toBeGreaterThanOrEqual(1);
});
```
- [ ] **Step 2: Correr — debe fallar**
Run: `pnpm test --run components/sections/__tests__/Dolores.test.tsx`
Expected: FAIL — el nudo está en `right-4`, no centrado.
- [ ] **Step 3: Recentrar el nudo en `Dolores`**
En `components/sections/Dolores.tsx`, sustituir la línea del `<IlustracionNudo ...>` por:
```tsx
<IlustracionNudo className="pointer-events-none select-none absolute top-0 left-1/2 -translate-x-1/2 w-64 md:w-[28rem] text-azul-profundo/[0.08]" />
```
- [ ] **Step 4: Correr — debe pasar**
Run: `pnpm test --run components/sections/__tests__/Dolores.test.tsx`
Expected: PASS.
- [ ] **Step 5: Correr la suite completa y commitear**
Run: `pnpm test`
Expected: PASS.
```bash
git add components/sections/Dolores.tsx components/sections/__tests__/Dolores.test.tsx
git commit -m "feat(dolores): el nudo al centro para que el hilo lo atraviese"
```
---
## Verificación final
- [ ] `pnpm test` — todos los tests en verde
- [ ] `pnpm lint` — sin warnings
- [ ] `npx tsc --noEmit` — sin errores
- [ ] `pnpm build` — el export estático compila
- [ ] `pnpm dev` y revisar a mano:
- (a) el hilo dorado baja **visible por el centro** en móvil y desktop, tras el texto (no lo cruza);
- (b) `IlustracionInflexion` es una curva que se dobla, **distinta** del fork de `ParaQuien`;
- (c) el nudo de `Dolores` está al centro y el hilo lo atraviesa;
- (d) los trazos de Nudo/Bifurcación/Convergencia se ven parejos (escala `1.5/2/3`).
- [ ] Con `prefers-reduced-motion: reduce`: ninguna ilustración invisible, el hilo se ve estático, la página se lee completa.
## Notas para quien ejecute
- **El apilado es la decisión clave del Task 1.** Si en `pnpm dev` el hilo, estando centrado, resta legibilidad a algún título (no debería: el texto queda a `z-20`, sobre el hilo a `z-10`), **no** vuelvas a esconderlo en el canal: baja su opacidad (`dorada/20`) o reduce `w-14` antes de tocar el posicionamiento. "Hilo visible al centro" es un pedido explícito de la dueña.
- **El lienzo del nudo se mantiene en `0 0 190 140`.** Es un ornamento de fondo que se renderiza a otra escala; forzarlo a `220×120` (como dice el spec §4) arriesga el dibujo sin aportar coherencia. La unificación de grosores (Task 3) ya cumple la intención del spec.
- **La "deriva gris punteada" de `Inflexion` (spec §4/§7) desaparece por diseño.** El rework "sin fork" (Task 2) no tiene ruta de deriva —encarnar el punto de inflexión como una curva que se dobla, no como una bifurcación—, así que el bug del punteado no tiene dónde aplicar y se resuelve por eliminación. La única ruta punteada que queda en la página (la del "no" en `ParaQuien`) ya se revela por opacidad desde el plan anterior. Por eso la verificación final no lista "deriva punteada de Inflexion".
- **No reabrir** las decisiones bloqueadas del `AGENTS.md` (paleta, wordmark, voz, "simple, directo, limpio").
File diff suppressed because it is too large. Load diff
@@ -1,467 +0,0 @@
# El recorrido de diálogo — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Convertir `PuntosInflexion` de una rejilla de seis tarjetas mudas en una conversación: cada punto de inflexión enuncia la situación del lector y creeer contesta qué hace al respecto.
**Architecture:** Nace un primitivo `ParDialogo` que encapsula un intercambio (situación + respuesta + su separación visual) y no sabe nada de la sección que lo monta. `lib/content.ts` cambia la forma de `puntosInflexion.items` de `{ name, desc }` a `{ name, situacion, respuesta }`. `PuntosInflexion` conserva su cabecera y sustituye el cuerpo por una `<ul>` de seis `ParDialogo`. El carrusel móvil desaparece y con él el primitivo `Carrusel`, que se queda sin consumidores.
**Tech Stack:** Next.js 14 App Router (`output: 'export'`), React 18, TypeScript, Tailwind, Framer Motion 11, Vitest + jsdom + Testing Library.
**Spec:** `docs/superpowers/specs/2026-08-03-recorrido-dialogo-design.md`
## Global Constraints
- **Gestor de paquetes: `pnpm`** (`pnpm@11.7.0` vía corepack). No usar npm.
- **Rama nueva por cada cambio.** Este trabajo va en `feat/recorrido-dialogo`. Nunca trabajar directo en `main`.
- **No hacer commits sin aprobación explícita del usuario.** Los pasos de commit de este plan están escritos, pero **hay que pedir el visto bueno antes de ejecutarlos**.
- **El copy vive en `lib/content.ts`, no en el JSX.** Las comillas angulares que envuelven la situación son puntuación del componente, no copy.
- **El wordmark nunca se escribe a mano en el JSX.** Solo `<Wordmark />` dibuja `cre≡er`.
- **Proyecto en español** — copy, docs y commits. **Los comentarios de código, en inglés.**
- **Toda sección animada** combina `usePrefersReducedMotion()` con `motionProps(reduced)`. Es requisito de accesibilidad del spec.
- **Tokens de color de `tailwind.config.ts`** (`azul-profundo`, `dorado`, `gris-claro`, `gris-medio`), nunca hex sueltos.
- **Sin escasez:** ningún texto puede contener `cupos`, `cohorte` ni `generación fundadora`.
- **Ningún conector puede depender de `pathLength`** sobre un SVG con `preserveAspectRatio="none"`. Chromium recorre el guion en espacio de pantalla y normaliza la unidad desde el espacio de usuario, así que el trazo llega partido. Usar bordes o formas rellenas.
## File Structure
| Archivo | Responsabilidad |
|---------|-----------------|
| `components/primitives/ParDialogo.tsx` | **Nuevo.** Un intercambio: la situación del lector y la respuesta de creeer, con las dos voces distinguibles sin depender del color |
| `components/primitives/__tests__/ParDialogo.test.tsx` | **Nuevo.** Contrato del primitivo |
| `lib/content.ts` | `puntosInflexion.items` pasa a `{ name, situacion, respuesta }`; se añade `lead` |
| `components/sections/PuntosInflexion.tsx` | Cabecera intacta; el cuerpo pasa de rejilla + carrusel a `<ul>` de `ParDialogo` |
| `components/sections/__tests__/PuntosInflexion.test.tsx` | Se actualiza: ya no hay tarjetas ni duplicado por carrusel |
| `components/primitives/Carrusel.tsx` | **Se retira.** Sin consumidores tras el cambio |
| `components/primitives/__tests__/Carrusel.test.tsx` | **Se retira** con él |
---
### Task 0: Abrir la rama
**Files:** ninguno.
- [ ] **Step 1: Crear la rama de trabajo**
El repositorio arrastra la retirada del hilo conductor sin commitear en `feat/hilo-conductor-visual`. Antes de empezar, confirmar con el usuario qué hacer con esos cambios (commitearlos allí o arrastrarlos). Después:
```bash
git checkout -b feat/recorrido-dialogo
git status --short
```
Expected: la rama nueva existe y `git status` muestra solo lo que el usuario haya decidido arrastrar.
---
### Task 1: `ParDialogo`, el intercambio
**Files:**
- Create: `components/primitives/ParDialogo.tsx`
- Test: `components/primitives/__tests__/ParDialogo.test.tsx`
**Interfaces:**
- Consumes: `Wordmark` de `@/components/primitives/Wordmark` (props `size?: 'sm'|'md'|'lg'|'xl'`, `onDark?: boolean`, `showTagline?: boolean`); `fadeInUp` de `@/lib/motion`.
- Produces: `ParDialogo({ n, name, situacion, respuesta }: { n: number; name: string; situacion: string; respuesta: string })`. Renderiza un `<li>`, así que **el que lo monta debe darle una `<ul>` o `<ol>` como padre**. La Task 2 depende de esa firma exacta.
- [ ] **Step 1: Escribir el test que falla**
Crear `components/primitives/__tests__/ParDialogo.test.tsx`:
```tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ParDialogo } from '../ParDialogo';
const par = {
n: 1,
name: 'Crecer',
situacion: 'El siguiente salto no tiene mapa.',
respuesta: 'Me sumerjo hasta entender tu empresa desde dentro.',
};
describe('<ParDialogo />', () => {
it('renders both voices of the exchange', () => {
render(<ul><ParDialogo {...par} /></ul>);
expect(screen.getByText(/El siguiente salto no tiene mapa/)).toBeInTheDocument();
expect(screen.getByText(/Me sumerjo hasta entender tu empresa desde dentro/)).toBeInTheDocument();
});
it('attributes the answer to creeer, not to the reader', () => {
render(<ul><ParDialogo {...par} /></ul>);
// Without a speaker label the answer reads as more description of the
// problem. The wordmark is that label — and <Wordmark /> is the only
// thing allowed to draw cre≡er, never hand-typed in the JSX.
expect(screen.getByLabelText('creeer')).toBeInTheDocument();
});
it('labels the situation with its inflection point', () => {
render(<ul><ParDialogo {...par} /></ul>);
expect(screen.getByText(/01 · Crecer/)).toBeInTheDocument();
});
it('tells the two voices apart by more than colour', () => {
const { container } = render(<ul><ParDialogo {...par} /></ul>);
// A reader who cannot separate gold from navy still has the speaker
// label, the column, and the rule hanging off each block. Colour alone
// would fail the spec's accessibility criterion.
const bloques = container.querySelectorAll('li > div');
expect(bloques).toHaveLength(2);
bloques.forEach((b) => expect(b.className).toMatch(/border-l-2/));
});
it('is a list item, so the section can mount six of them as a list', () => {
render(<ul><ParDialogo {...par} /></ul>);
expect(screen.getAllByRole('listitem')).toHaveLength(1);
});
});
```
- [ ] **Step 2: Correr el test y verificar que falla**
Run: `pnpm test components/primitives/__tests__/ParDialogo.test.tsx`
Expected: FAIL — `Failed to resolve import "../ParDialogo"`.
- [ ] **Step 3: Escribir el componente**
Crear `components/primitives/ParDialogo.tsx`:
```tsx
'use client';
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.
//
// 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 situación → respuesta, not left → right. Flipping sides per pair would
// cost 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.
//
// No motion.li here on purpose: Framer propagates variants through React
// context, so the two motion.div children still inherit the stagger from the
// <ul> the section wraps them in, even across this plain <li>.
export function ParDialogo({
n,
name,
situacion,
respuesta,
}: {
n: number;
name: string;
situacion: string;
respuesta: string;
}) {
return (
<li className="grid gap-5 md:grid-cols-2 md:gap-12">
<motion.div variants={fadeInUp} className="border-l-2 border-azul-profundo/25 pl-5">
<p className="text-xs font-black uppercase tracking-[0.2em] text-dorado">
{String(n).padStart(2, '0')} · {name}
</p>
<p className="mt-3 font-sans font-bold text-xl md:text-2xl text-azul-profundo leading-snug">
«{situacion}»
</p>
</motion.div>
<motion.div variants={fadeInUp} className="border-l-2 border-dorado pl-5 md:mt-10">
<Wordmark size="sm" />
<p className="mt-3 text-gris-medio leading-relaxed">{respuesta}</p>
</motion.div>
</li>
);
}
```
- [ ] **Step 4: Correr el test y verificar que pasa**
Run: `pnpm test components/primitives/__tests__/ParDialogo.test.tsx`
Expected: PASS (5 tests).
- [ ] **Step 5: Typecheck**
Run: `npx tsc --noEmit`
Expected: sin salida.
- [ ] **Step 6: Commit** *(pedir aprobación primero — ver Global Constraints)*
```bash
git add components/primitives/ParDialogo.tsx components/primitives/__tests__/ParDialogo.test.tsx
git commit -m "feat(recorrido): ParDialogo — la situación y la respuesta como un intercambio"
```
---
### Task 2: El copy y la sección
**Files:**
- Modify: `lib/content.ts:35-48` (el bloque `puntosInflexion`)
- Modify: `components/sections/PuntosInflexion.tsx`
- Test: `components/sections/__tests__/PuntosInflexion.test.tsx`
**Interfaces:**
- Consumes: `ParDialogo({ n, name, situacion, respuesta })` de la Task 1.
- Produces: `puntosInflexion` con forma `{ eyebrow: string; title: string; lead: string; items: { name: string; situacion: string; respuesta: string }[] }`. **`desc` desaparece**: ningún otro archivo lo lee (verificado — `PuntosInflexion.tsx` era su único consumidor).
- [ ] **Step 1: Escribir los tests que fallan**
Reemplazar el contenido de `components/sections/__tests__/PuntosInflexion.test.tsx`:
```tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { PuntosInflexion } from '../PuntosInflexion';
import { puntosInflexion } from '@/lib/content';
describe('<PuntosInflexion />', () => {
it('renders the section title', () => {
render(<PuntosInflexion />);
expect(screen.getByText(/llegan a un punto de inflexión/i)).toBeInTheDocument();
});
it('renders the six inflection points from the dueña deck', () => {
render(<PuntosInflexion />);
// Each point now renders once, not twice: the mobile carousel is gone and
// the conversation stacks vertically at every breakpoint.
for (const name of ['Crecer', 'Consolidarse', 'Recuperar rentabilidad', 'Simplificar', 'Expandirse', 'Replantear el portafolio']) {
expect(screen.getByText(new RegExp(name))).toBeInTheDocument();
}
expect(screen.getAllByRole('listitem')).toHaveLength(6);
});
it('answers every situation instead of only naming it', () => {
render(<PuntosInflexion />);
// This is the whole point of the section, and the one thing that would
// silently regress back into the old mute grid: six situations, six
// answers, each spoken by creeer.
expect(screen.getAllByLabelText('creeer')).toHaveLength(6);
for (const { respuesta } of puntosInflexion.items) {
expect(screen.getByText(respuesta)).toBeInTheDocument();
}
});
it('states each situation in the second person, as the dueña reads them', () => {
render(<PuntosInflexion />);
// Her own phrasing in the 31/jul call: "estás atrapado en tu compañía",
// "creciste muy rápido pero quieres expandirte". The deck's infinitives
// ("Dar el siguiente salto") would put the reader outside their own story.
for (const { situacion } of puntosInflexion.items) {
expect(situacion).toMatch(/\b(tu|tus|te|ya|creciste|construiste|sabes|puedes|quieres|dejó|volvió|abiertos|mismo)\b/i);
}
});
it('does NOT introduce scarcity', () => {
const { container } = render(<PuntosInflexion />);
expect(container.textContent).not.toMatch(/cupos|cohorte|generación fundadora/i);
});
});
```
- [ ] **Step 2: Correr los tests y verificar que fallan**
Run: `pnpm test components/sections/__tests__/PuntosInflexion.test.tsx`
Expected: FAIL — `getAllByLabelText('creeer')` no encuentra nada y `puntosInflexion.items[0].respuesta` es `undefined`.
- [ ] **Step 3: Reescribir el copy en `lib/content.ts`**
Sustituir el bloque `puntosInflexion` entero (líneas 35–48) por:
```ts
// The six inflection points, from the dueña's deck "Rumbo Estratégico ·
// Propuesta Socio" (slide 2). The deck states each one in the infinitive
// ("Dar el siguiente salto sin saber por dónde"); here it is stated in the
// second person, the way she reads them aloud herself — "estás atrapado en tu
// compañía", "creciste muy rápido pero quieres expandirte" (call of 31/jul,
// 6:01–7:15). `respuesta` is new copy and still needs her sign-off; 01 and 05
// are the two exchanges she improvised on that call, written out.
//
// `situacion` describes the state of the business. It must not drift into how
// the reader feels — that is `dolores`, four sections down, and the two would
// collapse into each other.
export const puntosInflexion = {
eyebrow: 'el contexto',
title: 'Todos los negocios consolidados llegan a un punto de inflexión',
lead: '¿Te reconoces?',
items: [
{
name: 'Crecer',
situacion: 'Ya demostraste que sabes crecer. El siguiente salto no tiene mapa.',
respuesta: 'Me sumerjo hasta entender tu empresa desde dentro. Sales sabiendo por dónde, no intentándolo por todos lados a la vez.',
},
{
name: 'Consolidarse',
situacion: 'Construiste rápido. Escalar así multiplicaría el desorden.',
respuesta: 'Ordenamos lo que ya existe antes de ponerle más peso encima. Escalas sobre algo que aguanta.',
},
{
name: 'Recuperar rentabilidad',
situacion: 'El esfuerzo dejó de traducirse en resultado.',
respuesta: 'Cuestiono lo que todos dan por hecho en tu operación. El margen no siempre está en vender más.',
},
{
name: 'Simplificar',
situacion: 'La operación se volvió más compleja que el negocio.',
respuesta: 'Separo lo que solo tú puedes decidir de lo que la empresa debería resolver sin ti.',
},
{
name: 'Expandirse',
situacion: 'Mercados nuevos, el mismo mapa de decisión.',
respuesta: 'Reviso tu proceso y ubico la oportunidad real antes de mover una ficha. Expandirte deja de ser una apuesta.',
},
{
name: 'Replantear el portafolio',
situacion: 'Varios frentes abiertos y ninguno con su lugar claro.',
respuesta: 'Construimos, juntos, el criterio para decidir qué sigue y qué se deja ir. Por escrito, no en tu cabeza.',
},
],
};
```
- [ ] **Step 4: Reescribir `PuntosInflexion.tsx`**
Sustituir el archivo entero por:
```tsx
'use client';
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();
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. The stagger reaches ParDialogo's two halves
through React context, so each pair arrives in two beats: the
situation, then the answer. */}
<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">
{puntosInflexion.items.map((item, i) => (
<ParDialogo key={item.name} n={i + 1} {...item} />
))}
</motion.ul>
</Container>
</section>
);
}
```
- [ ] **Step 5: Correr los tests y verificar que pasan**
Run: `pnpm test components/sections/__tests__/PuntosInflexion.test.tsx`
Expected: PASS (5 tests).
- [ ] **Step 6: Correr la suite entera y el typecheck**
Run: `pnpm test && npx tsc --noEmit`
Expected: todo verde. `Carrusel.test.tsx` sigue pasando por su cuenta — el primitivo aún existe, solo dejó de usarse.
- [ ] **Step 7: Commit** *(pedir aprobación primero)*
```bash
git add lib/content.ts components/sections/PuntosInflexion.tsx components/sections/__tests__/PuntosInflexion.test.tsx
git commit -m "feat(recorrido): los seis puntos dejan de ser tarjetas y pasan a contestar"
```
---
### Task 3: Retirar el carrusel
**Files:**
- Delete: `components/primitives/Carrusel.tsx`
- Delete: `components/primitives/__tests__/Carrusel.test.tsx`
**Interfaces:**
- Consumes: nada.
- Produces: nada. Es una retirada.
- [ ] **Step 1: Verificar que de verdad quedó sin consumidores**
Run: `grep -rn "Carrusel" --include="*.tsx" --include="*.ts" components app lib`
Expected: solo `components/primitives/Carrusel.tsx` y su propio test. Si aparece cualquier otro archivo, **parar**: el primitivo sigue en uso y no se retira.
- [ ] **Step 2: Retirarlo**
```bash
git rm components/primitives/Carrusel.tsx components/primitives/__tests__/Carrusel.test.tsx
```
- [ ] **Step 3: Verificar que nada se rompió**
Run: `pnpm test && npx tsc --noEmit && pnpm lint`
Expected: todo verde, con la suite más corta por los tests del carrusel que se fueron.
- [ ] **Step 4: Commit** *(pedir aprobación primero)*
```bash
git add -A components/primitives
git commit -m "chore: retira Carrusel, sin consumidores desde que el recorrido se apila"
```
---
### Task 4: Verificación en navegador
**Files:** ninguno. Es la comprobación de los criterios de aceptación del spec que jsdom no puede dar, porque **jsdom no hace layout**. Dos de los peores fallos de este proyecto —una banda que alargaba el documento 5.800px y otra que llegaba partida en cuatro— pasaron toda la suite en verde.
- [ ] **Step 1: Build de producción**
Run: `pnpm build`
Expected: export estático sin errores.
- [ ] **Step 2: Levantar el servidor**
Run: `pnpm dev`
Expected: sirve en `http://localhost:3000`. **No correr `pnpm build` a la vez que `pnpm dev`**: comparten `.next` y el servidor acaba sirviendo una página corrupta (se midió un documento de 60.569px por esto).
- [ ] **Step 3: Revisar a 1440×900 y a 390×844**
Comprobar contra los criterios del spec:
- los seis pares se leen de arriba abajo como conversación;
- cada respuesta se distingue como voz de creeer sin depender solo del color;
- en 390px no hay desbordes horizontales ni carrusel;
- la sección no deja huecos verticales desproporcionados frente a sus vecinas (`Dolores` viene justo después).
- [ ] **Step 4: Revisar con `prefers-reduced-motion: reduce`**
Cargar la página con la preferencia activada y confirmar que **ningún par queda invisible** y que todas las reglas laterales se dibujan. Es el fallo clásico de este proyecto: las ramas de movimiento reducido que se quedan en `opacity: 0`.
- [ ] **Step 5: Mirar `Dolores` y `PuntosInflexion` juntas**
El spec lo pide explícitamente. Las situaciones describen el estado del negocio; los dolores, cómo se siente. Si al leerlas seguidas suenan a lo mismo, informarlo — **no reescribir `dolores` por cuenta propia**, es texto verbatim de la dueña.
- [ ] **Step 6: Informar al usuario**
Resumir qué se verificó y qué queda pendiente del visto bueno de la dueña: las seis respuestas son redacción nueva y las situaciones reescriben líneas marcadas como verbatim de su deck.
@@ -1,291 +0,0 @@
# Spec de diseño · creeer landing page
**Fecha:** 2026-07-20
**Estado:** Borrador (pendiente de revisión de la dueña para la decisión final del hero)
**Skill usado:** `brainstorming`
> **Lectura obligatoria antes de implementar:** `docs/dueña-directrices.md` (directrices verbatim de la dueña + posicionamiento estratégico). Toda decisión debe pasar por su filtro: **"seguridad + cercanía"** y **"simple, directo, limpio"**.
---
## 1. Visión del proyecto
Landing page **one-page** para **creeer**, marca personal de una estratega de crecimiento. El objetivo es **credibilidad y prestigio** (no venta directa ni generación de leads por formulario). El contacto es por enlaces directos.
**Promesa central:** claridad estratégica para empresarios con tracción que llegaron a un punto de inflexión.
**Tono maestro:** seguridad + cercanía. Autoridad accesible, no distante.
---
## 2. Brief consolidado
| Eje | Decisión |
|---|---|
| Tipo de sitio | Landing one-page (single page) |
| Objetivo #1 | Credibilidad / prestigio |
| Audiencia | Empresarios con tracción en punto de inflexión |
| Idioma | Español |
| Stack | Next.js 14 (App Router) + TypeScript + Tailwind CSS + Framer Motion |
| Modo | `output: export` (HTML estático, sin backend) |
| Contacto | Enlaces directos (WhatsApp, email, LinkedIn) — sin formulario |
| Deploy | Vercel o Netlify |
---
## 3. Sistema de diseño
> **Actualizado 28/jul/2026** — Basado en el Manual de Identidad Visual v1.0 (mayo 2026). Ver `docs/branding.jpeg` y `docs/dueña-directrices.md`.
### 3.1 Paleta
Tokens de color (Tailwind):
| Token | Hex | Uso |
|---|---|---|
| `bg-white` | `#FFFFFF` | Fondo base y hero |
| `bg-azul-profundo` | `#0A1D3D` | Secciones oscuras (04 y 07) |
| `text-azul-profundo` | `#0A1D3D` | Texto principal |
| `text-white` | `#FFFFFF` | Texto sobre fondo oscuro |
| `text-dorado` / `bg-dorado` | `#C4942E` | Dorado Estratégico — acentos, títulos, símbolo ≡ |
| `bg-azul-claro` | `#C8EEF5` | Fondos alternativos, secciones suaves |
| `bg-gris-claro` | `#F2F2F2` | Fondos neutros, separadores |
| `text-gris-medio` | `#AAAAAA` | Texto secundario / placeholders |
**NO usar amarillo eléctrico tipo Hormozi** — rompe la cercanía.
### 3.2 Tipografía
| Uso | Familia | Pesos | Estilo |
|---|---|---|---|
| Headlines, body, CTA, UI | **Open Sans** | 400, 600, 700, 800 + itálicas | normal |
> **Nota web:** Aptos (manual de marca) es proprietaria de Microsoft y su EULA prohíbe servirla en web. La landing usa **Open Sans** (OFL, `next/font/google`) — lo más cercano a Aptos en open-source (mismo diseñador: Steve Matteson). Decisión 28/jul/2026.
### 3.3 Escala tipográfica
| Rol | Tamaño (móvil / desktop) | Peso |
|---|---|---|
| Hero headline | 32px / 48px | Bold o Black |
| Sección título | 28px / 36px | Bold |
| Cita ancla | 24px / 32px | Bold italic |
| Subtítulo / body large | 18px / 24px | Regular |
| Body | 16px / 18px | Regular |
| Eyebrow / label | 11px / 12px | Medium uppercase (tracking +3px) |
| Caption / métricas label | 10px / 11px | Medium uppercase |
### 3.4 Wordmark / Logotipo
**`cre≡er`** — siempre en minúsculas, Open Sans:
- `cre` en Azul Profundo `#0A1D3D` (sobre claro) o blanco `#FFFFFF` (sobre oscuro)
- **`≡`** (símbolo de tres líneas, reemplaza la "e" central) en **Dorado Estratégico `#C4942E`**
- `er` en el color del "cre"
**Tagline debajo:** `CLARIDAD · DIRECCIÓN · CRECIMIENTO`
**Eslogan:** *"Estrategia que transforma."*
**Tamaño:** base 32–48px según contexto. Es la firma de marca, siempre en minúsculas.
### 3.5 Recursos visuales recurrentes
- Separador de disciplinas: **" • "** (Comunicación • Digital • Experiencias)
- Listas numeradas: **"1." "2." "3."** (sin bullets)
- Smart quotes **"" ""** para citas institucionales
- Flecha **"→"** en CTAs
- Mucho aire/whitespace
- **Patrón de puntos** (grid de puntos dorados) — recurso del branding
- **Líneas y divisores** en dorado o azul profundo
- **Iconografía:** líneas simples, geométricas, consistentes en grosor
### 3.6 Espaciado y ritmo
- Container max-width: **1152px** (max-w-5xl / 7xl en Tailwind)
- Padding horizontal: 24px (móvil) / 48px (desktop)
- Espaciado vertical entre secciones: **96px** desktop / 64px móvil
- Border-radius: **0** (cuadrado, editorial) o **4px** sutil en cards
### 3.7 Layout de secciones
- Secciones **claras**: fondo blanco `#FFFFFF`. Alternar con `#F2F2F2` (gris claro) o `#C8EEF5` (azul claro) para ritmo visual.
- Secciones **oscuras** (04, 07): fondo Azul Profundo `#0A1D3D` con texto blanco y acentos Dorado `#C4942E`
- Esto da **ritmo visual** al scroll y enmarca las partes estratégicas (mecanismo + cierre)
---
## 4. Hero (finalistas pendientes de decisión de la dueña)
Dos finalistas guardados en `docs/hero-finalists.md`. La dueña elige uno. Ambos comparten:
- Logotipo `cre≡er` (Open Sans)
- Eyebrow: `— tu estratega de crecimiento —`
- Headline copy: *"Tu negocio no necesita más esfuerzo. Necesita más claridad."*
- CTA: `Define tu siguiente movimiento →` (Azul Profundo, white text, chunky)
- Fondo blanco `#FFFFFF`
- Sin escasez (sin cupos/cohorte)
### 4.1 HV-1 · Hormozi ligero
- Headline en **Open Sans ExtraBold (800) uppercase**
- "más esfuerzo" en dorado, "más claridad" subrayado en dorado
- + punch / conversión, − cercanía
### 4.2 V-a · Editorial minimal
- Headline en **Open Sans Bold**
- Sin subhead, sin métricas. Solo wordmark → eyebrow → headline → CTA
- + cercanía / limpieza, − punch
**Implementación:** el componente `<Hero>` recibe una prop `variant="hormozi" | "editorial"`. La diferencia es 1 className en el headline. La dueña puede decidir en QA.
---
## 5. Flujo de secciones (one-page, 7 secciones)
### 01 · Hero
La promesa. HV-1 o V-a. CTA primario.
### 02 · Frase ancla
Cita a gran escala, Georgia italic centrada:
> *"Las empresas no dejan de crecer por falta de talento; dejan de crecer por falta de claridad."*
Motivo recurrente de marca. Fondo claro.
### 03 · Para quién (filtro)
Cualifica al lector. Headline: *"¿Llegaste a tu punto de inflexión?"* + 3 chips/tags:
- ✓ tienes tracción
- ✓ más esfuerzo no rinde
- ✓ necesitas claridad
Lenguaje: "Esto es para ti si..." Fondo claro.
### 04 · Cómo trabajo (mecanismo) — SECCIÓN OSCURA
El mecanismo único (lo inimitable). Tres pasos en secuencia:
**Inmersión → Desafiar supuestos → Estrategia ejecutable**
Fondo navy `#0A1F44`, texto marfil, números dorado `#D4AF37`. **Sin iconos** — los números 01/02/03 (Georgia italic, grandes) hacen todo el peso visual.
### 05 · Las disciplinas
Grid de 3 columnas:
- **Comunicación**
- **Digital**
- **Experiencias**
Framed como **herramientas**, no como la promesa. Borde superior dorado en cada card. Fondo claro.
### 06 · Sobre la estratega
La mente detrás de creeer. Retrato (foto de la dueña) + bio corta en primera persona. Credibilidad humana. Refuerza "cercanía".
> **Pendiente:** ¿la dueña tiene foto profesional disponible? Si no, se usa placeholder con gradiente navy/dorado y se deja la sección lista para meter la foto después.
### 07 · Cierre + contacto — SECCIÓN OSCURA
Frase de cierre (Georgia italic sobre navy):
> *"Devuélvele claridad al rumbo de tu empresa."*
CTA final: `Define tu siguiente movimiento →` (botón dorado). Debajo, enlaces directos:
- WhatsApp
- Email
- LinkedIn
Fondo navy, acentos dorado.
### Recurrencia del CTA
`Define tu siguiente movimiento →` aparece en **01** y **07**. Ancla el principio y el final.
---
## 6. Messaging y copy
### 6.1 Principio rector
Aplicar el skill `transformation-positioning`: **vender el hoyo, no el taladro**. La claridad es el producto; estrategia, marketing, comunicación son los medios.
### 6.2 Frases-ancla aprobadas (de la dueña)
- *"Las empresas no dejan de crecer por falta de talento; dejan de crecer por falta de claridad."* (sección 02)
- *"Cuando el esfuerzo ya no alcanza, la estrategia se vuelve indispensable."* (uso opcional)
- *"El tiempo no se compra, se invierte."* (sección 06 o footer)
- *"Devuélvele claridad al rumbo de tu empresa."* (sección 07)
### 6.3 Voz
- Segunda persona ("tú" / "tu negocio") — nunca "el empresario"
- Directa, sin jerga corporativa
- "Simple, directo, limpio" aplica al copy también
---
## 7. Restricciones técnicas
- **Sin backend** — contacto por enlaces directos (WhatsApp `https://wa.me/...`, email `mailto:`, LinkedIn)
- **Estático** — `output: export` en `next.config.js`
- **Sin escasez en el hero** — decisión de la dueña (no mostrar cupos/cohorte/Generación Fundadora en el hero)
- **Wordmark siempre minúsculas** — `creeer` jamás `CREEER` ni `Creeer`
---
## 8. SEO, accesibilidad y rendimiento
### 8.1 SEO
- Title tag: `creeer · tu estratega de crecimiento`
- Meta description: la frase de posicionamiento (150–160 chars)
- Open Graph image + Twitter card
- Lang `es` en `<html>`
- Estructura semántica: `<main>`, `<section>`, `<h1>` único en hero, `<h2>` por sección
- JSON-LD `Person` o `ProfessionalService` para la estratega
### 8.2 Accesibilidad (WCAG 2.2 AA)
- Contraste navy `#0A1F44` sobre blanco: 14.6:1 ✅
- Contraste dorado tostado `#B8901f` sobre blanco: 4.0:1 — usar solo para texto grande o decorativo, no body
- Contraste marfil `#F5EBD0` sobre navy: 13.1:1 ✅
- `alt` en el retrato de la dueña (sección 06)
- `aria-label` en iconos de contacto (WhatsApp, email, LinkedIn)
- Focus visible en todos los links y botones
- `prefers-reduced-motion`: desactivar animaciones Framer Motion
### 8.3 Rendimiento (Core Web Vitals)
- Fuentes: Inter desde Google Fonts con `display: swap` + `preload`. Georgia es system font.
- LCP: el headline del hero (texto, no imagen) — rápido
- Animaciones Framer Motion: usar `LazyMotion` + `domAnimation` para reducir bundle
- Imagen del retrato: `<Image>` de Next con `priority` solo si está above the fold (sección 06 está below the fold, sin priority)
- Sin JavaScript crítico para el contenido (el hero es texto, se ve sin JS)
---
## 9. Animación (Framer Motion)
Entrada escalonada del hero al montar:
1. Wordmark (fade + slight Y)
2. Eyebrow (fade, delay 0.1s)
3. Headline (fade + Y, delay 0.2s)
4. CTA (fade + Y, delay 0.3s)
Duración: 0.4–0.6s cada uno. Easing: `[0.22, 1, 0.36, 1]` (easeOutExpo).
En scroll (secciones 02–07): `whileInView` con `viewport={{ once: true }}` — fade + Y de 20px, duración 0.5s.
**Accesibilidad:** todo envuelto en check de `useReducedMotion()` — si el usuario prefiere reducir movimiento, las animaciones se desactivan y el contenido aparece estático.
---
## 10. Pendientes / decisiones abiertas
| Pendiente | Quién decide | Impacto |
|---|---|---|
| Hero final: HV-1 vs V-a | Dueña | 1 className en `<Hero>` |
| Foto de la dueña para sección 06 | Dueña | Si no hay, placeholder gradiente |
| Casos/testimonios (sección nueva entre 05 y 06) | Dueña | Solo si tiene casos documentados |
| Datos de contacto reales (WhatsApp, email, LinkedIn) | Dueña | Links del footer/07 |
| Dominio | Dueña | Deploy |
| Copy definitivo de cada sección | Redacción posterior | Contenido |
---
## 11. Entregables de diseño
- `docs/dueña-directrices.md` — directrices verbatim + posicionamiento
- `docs/hero-finalists.md` — HV-1 y V-a con specs completas
- `docs/superpowers/specs/2026-07-20-creeer-landing-design.md` — este spec
- Mockups en `.superpowers/brainstorm/2447-1784580629/content/` (sesión visual companion)
---
## 12. Próximos pasos
1. **Dueña revisa** este spec + `hero-finalists.md` y elige HV-1 o V-a
2. Una vez aprobado, invocar el skill `writing-plans` para crear el **plan de implementación** detallado (sprint por sprint, tareas técnicas, orden de build)
3. El plan se ejecutará con Next.js scaffold + Tailwind + Framer Motion, un componente por sección
@@ -1,161 +0,0 @@
# creeer Landing — Additions: Header + Cómo empezar + Footer
**Date:** 2026-07-21
**Branch:** `feat/header-comoempezar-footer`
**Builds on:** `docs/superpowers/specs/2026-07-20-creeer-landing-design.md` (original 7-section landing, already implemented).
## Goal
Complete the page's structural chrome and its conversion on-ramp by adding three pieces to the existing one-page landing:
1. A minimal sticky **Header** (wordmark + persistent CTA).
2. A new **"Cómo empezar"** section (3 client-facing steps ending in a direct WhatsApp link).
3. A slim **Footer**.
No new architecture: these reuse the existing primitives, tokens, motion pattern, and content-in-`lib/content.ts` convention. Still a statically-exported Next.js 14 site — no backend, no forms, contact via direct links only.
## Global constraints (inherited — apply to every piece here)
- **Wordmark** always lowercase `creeer.`, rendered via the shared `Wordmark` primitive (never re-implemented, never uppercased).
- **Paleta (tokens):** `ink #0A1F44` · `gold #B8901f` (accents on LIGHT) · `gold-bright #D4AF37` (accents on DARK) · `base #FBFAF7` (light bg) · `cream #F5EBD0` (text on dark) · `muted`/`muted-dark`.
- **Tipografía híbrida:** Georgia italic = voz de marca (wordmark + citas + títulos de sección); Inter = body/UI.
- **Sin backend, sin formularios** — contacto solo por enlaces directos (WhatsApp/mailto/LinkedIn).
- **Un único `<h1>`** en toda la página — vive en el Hero. New pieces use `<h2>`/`<h3>` or no heading; Header/Footer carry no heading.
- **Todo el copy** vive en `lib/content.ts` (nuevas claves `comoEmpezar` y `footer`).
- **Voz** en segunda persona, sin jerga corporativa. **Idioma** español.
- **Todas las animaciones Framer Motion** se desactivan con `prefers-reduced-motion` via `usePrefersReducedMotion()` + `motionProps(reduced)` (same pattern the 7 sections already use).
- **`output: export`** — el build estático debe seguir produciendo `out/`.
## File structure (additions)
```
components/
├── layout/
│ ├── Header.tsx # NEW — sticky bar (server component, pure CSS sticky)
│ ├── Footer.tsx # NEW — slim footer strip
│ └── __tests__/
│ ├── Header.test.tsx # NEW
│ └── Footer.test.tsx # NEW
└── sections/
├── ComoEmpezar.tsx # NEW — section 07 (client component, motion pattern)
└── __tests__/
└── ComoEmpezar.test.tsx # NEW
lib/content.ts # ADD `comoEmpezar` and `footer` keys
app/page.tsx # compose Header + ComoEmpezar + Footer
```
## Component 1 — Header (minimal sticky)
**Interface:** `Header()` at `components/layout/Header.tsx`. No props. Server component (no state, no JS — `position: sticky` is pure CSS).
**Layout & style:**
- `<header>` with `sticky top-0 z-50`, light background (`bg-base/80` or `bg-pure/80`) + `backdrop-blur` + a subtle bottom border (`border-b border-ink/10`). The light bar stays legible while scrolling over both light and dark sections.
- A `Container`-width inner row, `flex items-center justify-between`, vertical padding ~`py-3`/`py-4`.
- **Left:** `<Wordmark size="base" />` wrapped in an `<a href="#top">` (or the page top) — the wordmark ink color is the default (dark on the light bar).
- **Right:** the `CTA` primitive, `variant="light"`, with a **short** label from a new `header.cta` content key (`"Hablemos"`, `→` appended by the primitive), `href="#contacto"`. Rationale: the full-length "Define tu siguiente movimiento" would overflow next to the wordmark on ~375px screens; a compact nav CTA fits mobile and desktop, and the full CTA still appears in the Hero and Cierre. Layout is identical on mobile (no menu, no hamburger).
- Semantic `<header>`; wrap the CTA link region in `<nav aria-label="Principal">`. No animation (nothing to gate on reduced motion).
**A11y:** the sticky header must not obscure anchor targets — ensure `#contacto` still scrolls into view (the closing section has ample padding; acceptable without extra scroll-margin, but `scroll-mt` on anchor targets may be added if visual testing shows clipping).
## Component 2 — "Cómo empezar" section (07)
**Interface:** `ComoEmpezar()` at `components/sections/ComoEmpezar.tsx`. Client component using the established `motion` + `usePrefersReducedMotion()` + `motionProps(reduced)` + `Container` pattern (mirror `Mecanismo.tsx`/`Disciplinas.tsx`).
**Placement:** inserted between `<Estratega />` (06) and `<Cierre />`. Cierre becomes section 08. This positions the on-ramp right before the contact close.
**Purpose & differentiation:** this is the *client's on-ramp* ("cómo empezar contigo"), distinct from `Mecanismo` which is *her method* ("cómo trabajo"). Framing is the engagement path that ends in the WhatsApp CTA.
**Layout & style:** light section (`bg-base`), `py-24 md:py-32`. Eyebrow + `<h2>` title (Georgia italic), then 3 numbered steps in a `md:grid-cols-3` grid using the Mecanismo visual language (large Georgia-italic numbers `01/02/03` in `gold`, step name `<h3>`, short description in `muted`). A final CTA below the steps.
**Steps (starting copy in `content.ts`, editable by the dueña):**
1. **Escríbeme** — cuéntame dónde está tu negocio hoy.
2. **Sesión de diagnóstico** — una conversación para ubicar tu punto de inflexión.
3. **Estrategia ejecutable** — sales con claridad y tu siguiente movimiento definido.
**CTA (direct WhatsApp with prefilled message):** an `<a>` (or the `CTA` primitive) whose `href` is the WhatsApp click-to-chat URL with a prefilled text, e.g. `https://wa.me/<number>?text=Hola%2C%20quiero%20agendar%20una%20sesi%C3%B3n%20de%20diagn%C3%B3stico`. The base number reuses `cierre.contact.whatsapp` (currently the `520000000000` placeholder — a TODO pending the dueña's real number). The full prefilled href and the un-encoded message live in `content.ts` under `comoEmpezar` so copy/number changes touch only content.
**Light-section token discipline:** accents in `gold` (never `gold-bright`), `bg-base`, `text-ink`/`text-muted` — matching the other light sections.
## Component 3 — Footer (slim strip)
**Interface:** `Footer()` at `components/layout/Footer.tsx`. No props. Static (no motion).
**Rationale:** `Cierre` already renders the closing quote, primary CTA, and the three contact links. The footer therefore does **not** repeat the contact list — that would be redundant and produce a heavy "double dark block" right after the dark Cierre. Instead it is a thin closing strip.
**Layout & style:** `<footer>` with `bg-dark`, a subtle top divider (`border-t border-cream/10`), modest vertical padding (`py-8`/`py-10`), `Container`-width. Contents, centered or space-between:
- `<Wordmark size="base" onDark />` (small, gold-bright middle-e / period per the primitive).
- A short tagline: `"tu estratega de crecimiento"` (`text-muted-dark`/`cream`, small).
- Legal line: `© 2026 creeer` (year from a constant in `content.ts`, or computed — see decision below).
**Decision — year:** use a static `© 2026 creeer` string stored in `content.ts` `footer.legal` (static export → no need for a live `new Date()`; keeps the build deterministic and the copy in one place). The dueña/maintainer updates the year with the rest of the copy.
**Out of scope:** no privacy-policy page / aviso de privacidad, no social-icon set beyond what Cierre already links. These can be added later as their own change.
## Content additions (`lib/content.ts`)
```ts
export const header = {
cta: 'Hablemos', // short nav CTA → #contacto (full CTA lives in Hero/Cierre)
};
export const comoEmpezar = {
eyebrow: 'cómo empezar',
title: 'Tu siguiente movimiento empieza con una conversación.', // Georgia-italic h2
steps: [
{ n: '01', name: 'Escríbeme', desc: 'cuéntame dónde está tu negocio hoy.' },
{ n: '02', name: 'Sesión de diagnóstico', desc: 'una conversación para ubicar tu punto de inflexión.' },
{ n: '03', name: 'Estrategia ejecutable', desc: 'sales con claridad y tu siguiente movimiento definido.' },
],
cta: 'Agenda tu sesión de diagnóstico',
// WhatsApp prefilled: base number reused from `cierre.contact.whatsapp`
whatsappText: 'Hola, quiero agendar una sesión de diagnóstico',
};
export const footer = {
tagline: 'tu estratega de crecimiento',
legal: '© 2026 creeer',
};
```
The `ComoEmpezar` component builds the WhatsApp href from the `cierre.contact.whatsapp` number + `encodeURIComponent(comoEmpezar.whatsappText)` so number and message stay single-sourced. All copy above (title, steps, CTA, tagline) is a sensible default that the dueña can edit in `content.ts`.
## Page composition (`app/page.tsx`)
```
<Header />
<main>
<Hero variant="editorial" />
<FraseAncla />
<ParaQuien />
<Mecanismo />
<Disciplinas />
<Estratega />
<ComoEmpezar /> {/* NEW — 07 */}
<Cierre /> {/* now 08 */}
</main>
<Footer />
```
The JSON-LD `<script>` already present in the page is preserved. Single-`<h1>` invariant holds (only Hero).
## Testing (thin, matching the existing per-section pattern)
- **Header:** renders the wordmark text and a CTA link with `href="#contacto"`.
- **ComoEmpezar:** renders the three step names and a WhatsApp link whose `href` starts with `https://wa.me/` and includes the encoded prefilled `text`.
- **Footer:** renders the wordmark and the legal/year line; does not introduce a second `<h1>`.
`matchMedia` is already stubbed in `vitest.setup.ts`, so `ComoEmpezar`'s `usePrefersReducedMotion()` works in tests.
## Constraints satisfied
- No backend, no forms; contact via direct links (Header CTA → `#contacto`; ComoEmpezar → `wa.me`).
- Existing palette/typography/tokens reused; light section uses `gold`, dark footer uses `gold-bright`/`cream`.
- Single `<h1>` preserved (Hero only).
- Reduced-motion honored uniformly (ComoEmpezar follows the motion pattern; Header/Footer are static).
- Static export unaffected (all client interactivity is CSS/anchor links).
## Out of scope (future changes, each its own branch)
- Testimonials / social proof, results/case studies, FAQ.
- Privacy-policy page, cookie/analytics integration.
- Real OG image, branded favicon, real portrait photo, real contact details (dueña-pending).
@@ -1,101 +0,0 @@
# Coherencia de las ilustraciones + hilo conductor al centro
**Fecha:** 31/jul/2026
**Estado:** aprobado en brainstorming, pendiente de plan de implementación
**Origen:** petición de la dueña — *"que las imágenes tengan coherencia con el topic de la página, lo que es el marketing, todos los mensajes que vienen en todos los textos de la página y el branding… además de que el hilo sea en medio."*
**Continúa:** `docs/superpowers/specs/2026-07-31-hilo-conductor-sistema-visual-design.md` (cuyas primitivas ya están implementadas en la rama `feat/hilo-conductor-visual`).
---
## 1. Problema
Las ilustraciones de línea existen, pero **no cuentan una sola historia con la página**. Tres fallas concretas:
1. **Dos secciones dibujan lo mismo.** `IlustracionInflexion` (PuntosInflexión) y `IlustracionBifurcacion` (ParaQuien) son casi el mismo diagrama: una línea que crece, llega a un punto marcado, y se parte en un camino dorado (el bueno) y uno gris (el malo). Pero sus secciones dicen cosas distintas — PuntosInflexión enumera *seis contextos* de inflexión; ParaQuien plantea un *sí/no*-. Que ambas se ilustren igual diluye el mensaje de cada una.
2. **El hilo dorado no se ve.** `HiloConductor` está montado detrás de las secciones, que tienen fondos opacos (`bg-white`, `bg-azul-profundo`…), así que lo tapan por completo. La marca gira toda alrededor de **Complejidad → Enfoque → Claridad** y el `≡`, pero en la página no hay columna visual que una una sección con la siguiente. De ahí el pedido *"que el hilo sea en medio"*.
3. **El nudo de `Dolores` no apunta a la claridad.** El copy va del dolor a *"se resuelve con una estrategia clara"*, pero la ilustración es un nudo estático, desconectado del arco "hacia la claridad" y del hilo.
Y dos fallas de oficio que refuerzan la sensación de incoherencia:
4. **Lenguaje visual suelto.** Cada ilustración usa su propio viewbox (`64×64`, `100×100`, `190×140`, `220×120`, `320×180`) y sus propios grosores (`1.5`, `2`, `2.5`, `3`, `3.5`, `8`). No se leen como un set.
5. **Bug del punteado.** La "deriva" gris de `IlustracionInflexion` lleva `strokeDasharray="1 9"` pero se anima con `pathLength`, y framer-motion pisa ese dasharray con el suyo — por lo que hoy se renderiza sólida, no punteada. (Mismo bug ya corregido en `IlustracionBifurcacion`.)
## 2. Concepto rector: el hilo como columna vertebral
El hilo dorado se vuelve **visible** y baja por el **centro vertical** de toda la página. Cada ilustración se ancla al centro sobre él y es una **estación** del recorrido: el hilo "entra" y "sale" de cada dibujo. Las ilustraciones dejan de ser adornos sueltos y pasan a ser los momentos donde el hilo cobra forma.
Cada estación **encarna el mensaje de su sección** y, en conjunto, refuerzan la narrativa de marca (Complejidad → Enfoque → Claridad; claridad estratégica; el punto de inflexión). No es decoración: es *claridad · dirección · crecimiento* literalmente dibujado, como pide el Manual de Identidad Visual v1.0.
## 3. Decisiones por estación
Orden de la página (no se reordena). Se lista el estado nuevo de cada ilustración:
| Sección | Mensaje que debe expresar | Ilustración | Estado |
|---|---|---|---|
| Hero | "necesitas más claridad, no más esfuerzo" | `SecuenciaClaridad variant="hero"` | **Sin cambios** — 18 líneas cruzadas, *sin* resolver. |
| FraseAncla | "dejan de crecer por falta de claridad" | `SecuenciaClaridad variant="completa"` | **Sin cambios** — las líneas *sí* resuelven en el `≡`. Es el corazón de marca; queda perfecto. |
| PuntosInflexión | "llegas a un punto de inflexión" | `IlustracionInflexion` | **REHACER** — pasa a ser *una curva que se dobla* en el punto de inflexión marcado, **sin fork**. Cuenta el concepto de "punto de inflexión", deja de duplicar a ParaQuien. |
| Dolores | dolor → "se resuelve con una estrategia clara" | `IlustracionNudo` | **AJUSTAR** — el nudo se queda, pero el hilo dorado lo atraviesa (es lo que lo va a desatar). Conecta el dolor con la resolución. |
| Mecanismo | "cómo trabajo" (inmersión → desafiar → estrategia) | — (sin nueva ilustración) | El hilo pasa por la sección; su "dibujo" ya son los conectores de pasos. No sumar ruido. |
| Disciplinas | "son los medios, no la promesa" | `IlustracionConvergencia` | **Sin cambios** — 3 hebras → 1 estrategia dorada. |
| ParaQuien | "¿sí es para ti?" | `IlustracionBifurcacion` | **Sin cambios** — fork dorado/gris en el punto de decisión (ahora único). |
| Estratega | "sobre la estratega" | `MarcoRetrato` | **Sin cambios** — arco dorado, marco neutral. |
| ComoEmpezar / Cierre | — | — | El hilo los atraviesa; no llevan ilustración de línea. |
**Principio de la reutilización:** no se crean ilustraciones nuevas. Se rehace `IlustracionInflexion` y se ajusta el montaje de `IlustracionNudo`; el resto se queda. Esto mantiene el scope pequeño y centrado en *coherencia*, no en suma de piezas.
## 4. Lenguaje visual unificado
Para que las siete piezas se lean como un mismo set reflejando al `≡`:
- **Color por regla fija (no por gusto):**
- `text-azul-profundo` = la base, el presente, el tronco.
- `text-gris-medio` + trazo punteado = deriva, sin dirección, el camino que no.
- `text-dorado` = la resolución, la claridad, la estrategia — **y es el color del hilo conductor**.
- **Escala de grosores fija:** base `2`, énfasis `3`, detalle fino `1.5`. Desaparecen los `2.5`/`3.5` (y el `8` suelto de las hebras del nudo). **Excepción intencional y documentada:** `SecuenciaClaridad` resuelve en las barras reales del `≡` (grosor de `LINEAS_SIMBOLO`), porque ahí *está* dibujando el símbolo de marca, no una ilustración.
- **ViewBox:** las escenas horizontales convergen a un mismo lienzo `0 0 220 120` (`IlustracionInflexion`, `IlustracionNudo` ya reentran; `Bifurcacion`/`Convergencia` ya lo usan). `SecuenciaClaridad` mantiene `0 0 64 64` (lienzo del `≡`) y `MarcoRetrato` mantiene `0 0 100 100` (marco circular) — son cajas conceptuales distintas y se explicita por qué.
- **Revelado:** todas siguen usando `useEnVista` + `pathLength` (dibujo al entrar al viewport). **Bug del punteado arreglado:** las rutas punteadas se revelan por *opacidad* (no por `pathLength`), igual que la corrección ya aplicada en `IlustracionBifurcacion`. Esto aplica a la deriva de la nueva `IlustracionInflexion` y, si aplica, al nudo.
## 5. El hilo conductor, visible al centro
Hoy invisible; debe verse bajando por el medio:
- **Posicionamiento:** sigue `position: absolute` dentro de `<main className="relative">` (no `fixed`), centrado horizontalmente (`left-1/2 -translate-x-1/2`). Ya está así; se conserva.
- **Capa:** renderizado **por encima de los fondos de sección** pero **por debajo del texto**. Así ya no lo tapan los `bg-*` opacos, pero tampoco cruza/compite con la lectura.
- **Tenuidad:** dorado a baja opacidad (`text-dorado/20`–`/30`) y `pointer-events-none`. Se lee como una marca de agua que cose, no como una línea que cruza el texto.
- **Dibujo al hacer scroll:** conserva el `pathLength` ligado al `useScroll` de página.
- **Anclaje de estaciones:** las ilustraciones con estación se alinean al centro de modo que el hilo entre y salga de cada dibujo. Cuando la ilustración viva hoy a un costado del título (`PuntosInflexión`, `Disciplinas`), se decide en el plan si se**re-centra** o si el **hilo se desvía suavemente para tocarla** — ambas son válidas; la segunda respeta más el layout actual.
**Riesgo de layout (a resolver en el plan):** que el hilo, al pasar por el centro, reste legibilidad a títulos anchos. Mitigaciones candidatas: (a) capa tenue tras el texto, como se describe; (b) el hilo sólo "asoma" en los huecos entre secciones y dentro de cada ilustración, y se oculta bajo el texto. El plan debe prototipar y elegir.
## 6. Alcance (qué cambia y qué no)
**Cambia:**
- `HiloConductor.tsx`: pasa a una capa visible al centro (z-index/orden de capas + opacidad).
- `IlustracionInflexion.tsx`: se rehace como curva de inflexión sin fork, en lienzo `220×120`, con grosores de la escala y la deriva revelándose por opacidad.
- `IlustracionNudo.tsx`: grosores unificados a la escala; montaje en `Dolores` para que el hilo lo atraviese.
- Ajustes menores de grosores en `IlustracionBifurcacion` y `IlustracionConvergencia` para entrar en la escala (`2`/`3`).
- Tests co-locados: se actualizan los contratos que cambien (p. ej. número de trazos de la nueva `Inflexion`) y se añade un test que afirme la regla de color/grosor si aporta valor.
**No se toca (restricciones heredadas del plan anterior y de `AGENTS.md`):**
- **El copy de `lib/content.ts`** — ninguna decisión de este spec edita texto.
- **El orden de las secciones** de `app/page.tsx`.
- **Las primitivas que no cambian** (`SecuenciaClaridad`, `MarcoRetrato`, `HiloConductor` en su mecánica de scroll).
- **Sin escasez** en el hero (sigue pasando el test negativo `cupos|cohorte|generación fundadora`).
- **Colores por token**, nunca hex suelto. Wordmark `cre≡er` siempre minúsculo y vía `<Wordmark/>`.
## 7. Verificación de aceptación
- Cada ilustración expresa el mensaje de su sección ( revisión manual contra la tabla del §3).
- `IlustracionInflexion` y `IlustracionBifurcacion` ya **no** dibujan lo mismo.
- El hilo dorado es **visible** bajando por el centro en `pnpm dev`, sin restar legibilidad a los títulos.
- La deriva gris de `Inflexion` se ve **punteada** en runtime.
- Las siete piezas comparten grosores (`2`/`3`/`1.5`) y la regla de color azul/gris/dorado.
- `pnpm test`, `pnpm lint`, `npx tsc --noEmit` y `pnpm build` en verde.
- Con `prefers-reduced-motion: reduce`: ninguna ilustración queda invisible, el hilo se ve estático, la página se lee completa.
## 8. Notas para quien implemente
- **No reabrir** las decisiones bloqueadas del `AGENTS.md` (paleta, wordmark, voz, "simple, directo, limpio").
- **Si el hilo al centro pelea con la legibilidad** y ninguna mitigación del §5 funciona, **parar y consultar**: "hilo visible al centro" es un pedido explícito de la dueña; no se abandona sin su visto bueno, pero tampoco se entrega a costa de la lectura.
- La contradicción abierta sobre si el `≡` lleva espina vertical (`docs/dueña-directrices.md`) **no se resuelve aquí**: las ilustraciones usan el símbolo tal como lo sirve `SimboloTresLineas`/`lib/simbolo.ts` hoy.
@@ -1,185 +0,0 @@
# El hilo conductor — sistema visual, ilustración y movimiento
**Fecha:** 31/jul/2026
**Estado:** aprobado en brainstorming, pendiente de plan de implementación
**Origen:** mensaje de la dueña del 30/jul/2026 — *"súper, sólo le falta algo de gráfico y movimiento"*
---
## 1. Problema
La landing ya tiene contenido correcto y una primera capa de gráfico y movimiento (fondos de puntos, blur-in de títulos, spotlight en tarjetas, carrusel, ilustración de inflexión). Aun así, tres cosas la mantienen por debajo del nivel que la marca merece:
1. **No hay hilo visual.** Es una pila de diez secciones con cortes duros entre fondos. Nada conecta una sección con la siguiente.
2. **Casi no hay ilustración.** Existe una sola pieza dibujada (`IlustracionInflexion`); el resto es iconografía de línea y patrones de puntos. La metáfora gráfica central del Manual de Identidad Visual — **Complejidad → Enfoque → Claridad** — no está dibujada en ninguna parte de la página.
3. **El movimiento compite consigo mismo.** Tres elementos se animan solos al mismo tiempo (carrusel del hero, `useAutoStep` en `Mecanismo` y en `ComoEmpezar`), y dos de ellos usan la misma interacción. El resultado se lee como ruido, no como movimiento.
## 2. Concepto rector: el hilo
**Una línea dorada continua atraviesa la landing verticalmente**, dibujándose conforme el usuario hace scroll. Entra por el hero, cose sección con sección, se transforma en la ilustración de cada bloque y **resuelve en el `≡` del wordmark en el cierre**.
No es decoración. Es *claridad · dirección · crecimiento* dibujado: el hilo **es** la dirección. Resuelve simultáneamente los tres problemas de arriba — aporta ilustración, aporta movimiento con sentido, y elimina los cortes duros.
**Lenguaje gráfico (decidido):** line-art geométrico. Trazo fino sin relleno (1.5–3px), derivado del `≡` y del patrón de puntos del manual. Dorado `#C4942E` sobre azul profundo, azul profundo sobre claro. Sin degradados, sin formas rellenas, sin sombras suaves — ese vocabulario se lee como plantilla y contradice "simple, directo, limpio".
## 3. Componentes nuevos
Todos son primitivas en `components/primitives/`, con test co-locado en `__tests__/`.
### 3.1 `HiloConductor`
SVG de altura completa del documento, con `pathLength` ligado a `useScroll` de Framer Motion. Se dibuja de arriba hacia abajo conforme baja el usuario.
- **Posicionamiento:** `position: absolute` sobre el flujo de la página, **no** `fixed` — el hilo pertenece al documento y se desplaza con él; una capa fija se leería como un adorno pegado a la ventana.
- **Interfaz:** sin props obligatorias. Se monta una sola vez, en `app/page.tsx`, entre `<Header/>` y `<main>`.
- **Depende de:** `framer-motion` (`useScroll`, `useTransform`), `usePrefersReducedMotion`.
- **Reduced motion:** renderiza el trazo completo, estático, sin `motion.path`.
- **Riesgo conocido:** `motion.path` aplica su propio andamiaje de `dasharray` incluso sin `initial`/`animate`, lo que dejó líneas invisibles para reduced-motion en `IlustracionInflexion`. La rama reducida debe renderizar `<path>` planos, no `motion.path` neutralizados. Es el mismo bug ya documentado en ese archivo.
### 3.2 `SecuenciaClaridad` — la pieza central
La metáfora del manual, por fin dibujada. Unas 20 líneas cruzadas y desordenadas que convergen progresivamente y se resuelven en el `≡` dorado.
- **Interfaz:** `{ variant?: 'completa' | 'hero'; className?: string }` (por defecto `'completa'`).
- **Dónde, y por qué en dos lugares:** la narrativa se parte en dos actos deliberadamente.
- **Hero** → `variant="hero"`: sólo las fases de dispersión y convergencia, **sin resolver** en el `≡`. Plantea la complejidad y deja la tensión abierta. Ocupa el lugar que hoy tiene el `Carrusel`.
- **`FraseAncla`** → `variant="completa"`: las tres fases, resolviendo en el `≡` dorado justo cuando el copy dice *"dejan de crecer por falta de claridad"*. Es el pago de la tensión abierta en el hero.
- **Animación:** fases encadenadas por `staggerChildren` al entrar en viewport — dispersión → convergencia → (sólo en `completa`) resolución en `≡`.
- **Nota de marca:** el `≡` que produce debe leerse como **E**, per instrucción de la dueña del 28/jul. Reutiliza el trazado de `SimboloTresLineas`, no dibuja uno nuevo.
### 3.3 `IlustracionNudo`
Líneas enredadas formando un nudo que no se deshace. `Dolores` es hoy la única sección sin ninguna pieza gráfica, y es la de mayor carga emocional.
- **Dónde:** `Dolores`, como elemento lateral o de fondo tenue.
- **Interfaz:** `{ className?: string }`.
### 3.4 `IlustracionBifurcacion`
Dos caminos divergiendo desde un punto único: uno dorado y continuo, otro gris y punteado.
- **Dónde:** `ParaQuien`, reforzando el split screen sí/no que ya existe.
- **Interfaz:** `{ className?: string }`.
### 3.5 `IlustracionConvergencia`
Tres trazos que confluyen en uno solo — comunicación, digital y experiencias sirviendo a una sola estrategia.
- **Dónde:** `Disciplinas`. Refuerza el lead ya escrito: *"No son la promesa — son los medios."*
- **Interfaz:** `{ className?: string }`.
### 3.6 `MarcoRetrato`
Marco line-art para el retrato de la estratega.
- **Dónde:** `Estratega`, sustituyendo el placeholder de círculo con patrón de puntos.
- **Requisito de diseño:** debe verse intencional **hoy**, con el placeholder dentro, y aceptar la foto real de la dueña cuando llegue **sin rehacer nada**. El hueco de contenido no debe verse como un hueco.
- **Interfaz:** `{ children: ReactNode; className?: string }`.
### 3.7 `ProgresoLectura`
Barra dorada de 2px en el borde inferior del header, ligada a `useScroll`.
- **Dónde:** `components/layout/Header.tsx`.
- **Reduced motion:** se oculta por completo (es puro feedback de movimiento; estática no comunica nada).
## 4. Cambios en secciones existentes
### 4.1 Ritmo de fondos
Hoy `Disciplinas → ParaQuien → Estratega` son tres secciones blancas seguidas.
| Sección | Fondo hoy | Fondo nuevo |
|---|---|---|
| Hero | blanco | blanco |
| FraseAncla | azul profundo | azul profundo |
| PuntosInflexion | gris claro | gris claro |
| Dolores | blanco | blanco |
| Mecanismo | azul + foto | azul + foto |
| Disciplinas | blanco | blanco |
| ParaQuien | split blanco/azul | split blanco/azul |
| **Estratega** | **blanco** | **gris claro** |
| **ComoEmpezar** | **gris claro** | **blanco** |
| Cierre | azul profundo | azul profundo |
Dos cambios, cero fondos iguales consecutivos.
### 4.2 Rebalanceo del movimiento
**Se quita:**
- **El `Carrusel` de dolores en el hero.** Rota verbatim las cuatro frases de `dolores.quotes`, que vuelven a aparecer completas tres secciones más abajo. Duplica contenido, adelanta el golpe emocional y le compite atención al headline. **Lo reemplaza `SecuenciaClaridad variant="hero"`** (§3.2) como pieza derecha del hero.
- **El `useAutoStep` de `ComoEmpezar`.** Tener `Mecanismo` y `ComoEmpezar` avanzando solos es la misma interacción dos veces en la misma página. `Mecanismo` la conserva; `ComoEmpezar` pasa a estado estático con realce por hover/focus.
- **El `md:opacity-40` sobre pasos no activos** (`Mecanismo` y `ComoEmpezar`). Hoy dos de cada tres pasos quedan ilegibles hasta que el usuario interactúa. Es un problema de contraste, no de estética. Se sustituye por un realce del paso activo (borde dorado, elevación) dejando los tres legibles.
- **Uno de los dos `FondoPuntos interactivo` del hero.** Dos capas siguiendo el cursor a la vez es ruido; queda una.
**Se conserva** el `Carrusel` en `PuntosInflexion` (móvil): ahí sí resuelve un problema real de espacio y replica el patrón de `en-ti.com`.
**Se suma:**
- `HiloConductor` con scroll (§3.1).
- Line-drawing en las cinco ilustraciones nuevas.
- Parallax suave (±20px) en las dos fotos duotono de `Mecanismo` y `ComoEmpezar`.
- `ProgresoLectura` en el header, y compactado del wordmark al bajar.
### 4.3 Jerarquía tipográfica
Las ocho secciones usan hoy el mismo tratamiento exacto: `font-black text-4xl md:text-6xl tracking-tight`. Nada distingue una declaración de marca de un encabezado de sección.
Se introducen dos niveles:
- **Momento-ancla** — `FraseAncla`, el remate de `Dolores`, `Cierre`. Más grande, más aire arriba y abajo, `leading` más cerrado.
- **Sección normal** — el resto. Un escalón por debajo, con más peso en el contenido que en el título.
La escala vive en `tailwind.config.ts` como tokens, no como cadenas de clases repetidas en cada sección.
## 5. Accesibilidad
- Toda animación nueva pasa por `usePrefersReducedMotion()` + `motionProps(reduced)`, y las ilustraciones renderizan una rama estática con `<path>` planos (no `motion.path` neutralizados) cuando `reduced` es `true`.
- Las ilustraciones son decorativas: `aria-hidden="true"` y `focusable="false"`.
- Eliminar `md:opacity-40` **mejora** el contraste actual; ningún cambio lo empeora.
- Los pasos de `Mecanismo` siguen siendo `role="tab"` navegables por teclado; `ComoEmpezar` deja de necesitar botones al perder el autoplay, y sus pasos vuelven a ser contenido estático.
- El `focus-visible` dorado global de `app/globals.css` se mantiene; ningún componente nuevo redefine focus.
## 6. Rendimiento
- Todas las ilustraciones son SVG inline. Cero peticiones de red, cero imágenes nuevas.
- Se anima únicamente `pathLength`, `opacity` y `transform` — propiedades compuestas, sin layout thrashing.
- `HiloConductor` usa un solo `useScroll` a nivel de página, no uno por sección.
- El hero pierde el `Carrusel` y una capa interactiva de puntos: **menos** JS ejecutándose en el LCP que hoy.
## 7. Fuera de alcance
- **No se toca el copy** de `lib/content.ts`.
- **No se inventan casos ni métricas.** Los 2 casos de la dueña siguen pendientes.
- **No se agrega escasez al hero.** El test negativo de `Hero.test.tsx` (`cupos|cohorte|generación fundadora`) sigue vivo.
- **No se reordenan las secciones.** El orden actual cuenta bien la historia y la dueña lo aprobó el 30/jul.
- **No se resuelve** la contradicción pendiente entre el manual y la espina del `≡` (documentada en `docs/dueña-directrices.md`). Eso lo decide la dueña.
## 8. Huecos de contenido pendientes de la dueña
Ambos se diseñan para verse intencionales hoy y aceptar el material real sin rehacer nada:
| Hueco | Estado | Tratamiento |
|---|---|---|
| Retrato de la estratega | placeholder | `MarcoRetrato` (§3.6) |
| 2 casos de claridad estratégica | no entregados | no se maqueta sección fantasma; se añadirá cuando llegue el material |
## 9. Pruebas
Cada primitiva nueva lleva test co-locado en `__tests__/`, siguiendo el patrón vigente — se verifica **contrato y marca, no estilos**:
- Renderiza y es `aria-hidden`.
- Rama de reduced motion produce trazos visibles (regresión directa del bug de `motion.path` documentado en `IlustracionInflexion`).
- `SecuenciaClaridad` resuelve en el símbolo de `SimboloTresLineas`, no en un trazado propio.
Los tests existentes de secciones deben seguir pasando sin cambios de copy. Los de `Hero` y `ComoEmpezar` requieren ajuste **estructural** por la salida del carrusel y del autoplay — no de copy.
## 10. Criterios de éxito
1. Un usuario que hace scroll percibe la página como **un recorrido continuo**, no como diez bloques apilados.
2. La metáfora Complejidad → Enfoque → Claridad del manual es **visible**, no sólo textual.
3. Hay **más movimiento percibido** y **menos elementos compitiendo** que hoy.
4. Con `prefers-reduced-motion: reduce` la página se ve completa y legible, sin trazos invisibles.
5. `pnpm test`, `pnpm lint` y `npx tsc --noEmit` pasan limpios.
@@ -1,110 +0,0 @@
# El recorrido: la página que te contesta
**Fecha:** 2026-08-03
**Origen:** llamada de Zoom con la dueña del 31/jul/2026 (`Grabación 2026-07-31 171150.mp4`, minutos 5:30–7:40).
**Rama:** `feat/recorrido-dialogo`.
---
## 1. De dónde sale esto
En esa llamada Jazmín comparte pantalla, recorre `en-ti.com` como referencia y describe una idea para la página de creeer. Transcripción del tramo:
> **[06:01]** Pero también estaba pensando que podríamos hacer esto. O sea, dices: empiezas aquí y dices "estás atrapado en tu compañía y no estás creciendo", y ya te bajas, ¿no? Y entonces te bajas y contestas: "esto es para ti". Aquí desafiamos o cuestionamos absolutamente todo lo que has hecho y lo replanteamos, te generamos… y luego otra vez te regresas, ¿no?
>
> **[06:39]** O la siguiente: vas otra vez de cómo está, y aquí preguntas otra cosa. "No estás creciendo, creciste muy rápido pero quieres expandirte y sientes que no…" Quieres expandirte pero llegaste a un punto de inflexión — y ya contesta creeer y dice "hacemos una inmersión, revisamos tu proceso y vemos las áreas de oportunidad, o cuáles son los siguientes pasos para esa expansión".
>
> **[07:15]** ¿Sí me explico? Y luego te regresas, y luego otro. O sea, como que te va contestando y te va como llevando de uno a otro.
**Advertencia sobre la fuente.** Es transcripción automática (Whisper-small sobre el audio de Zoom), no taquigrafía. Tres pasajes quedan en duda: *"y sientes que no sientes"* está mal transcrito y falta el final de la frase; *"hacemos una inversión"* se lee aquí como **inmersión**, que es el paso 01 de `mecanismo` y encaja con el resto de la frase; y hay una repetición por solape de ventanas alrededor de 07:14. El resto se da por fiable. **Pendiente:** que la dueña o Erik confirmen la fidelidad antes de que esto entre en `docs/dueña-directrices.md`.
## 2. El problema
La página tiene hoy las piezas de lo que ella describe, pero sueltas y mudas:
- `PuntosInflexion` enumera seis situaciones en una rejilla de tarjetas. Ninguna dice qué hace creeer al respecto.
- `Mecanismo` dice qué hace creeer —inmersión, desafiar supuestos, estrategia ejecutable— pero desligado de la situación de quien lee.
- `ParaQuien` dice "esto es para ti si…" como lista de requisitos, no como respuesta.
El lector se reconoce en una tarjeta y ahí se acaba. Nada le contesta. Eso es exactamente lo que ella pide: *"te va contestando y te va como llevando de uno a otro"*.
## 3. Concepto: el diálogo
`PuntosInflexion` deja de ser una rejilla y pasa a ser una **secuencia de seis pares**. Cada par es un intercambio:
- **la situación**, en segunda persona, con la voz del lector;
- **la respuesta de creeer**, en primera persona, con la voz de la estratega.
Alternan al bajar, unidos por un conector, de modo que la sección se lee como una conversación y no como un catálogo. Todo el texto queda en el DOM: nada se esconde tras un clic, ni por SEO ni por accesibilidad.
La segunda persona no es invención: es como los enuncia ella misma en la llamada (*"estás atrapado…", "creciste muy rápido pero quieres expandirte…"*).
## 4. El contenido
Los seis puntos de inflexión se conservan —son de la diapositiva 2 de su deck— y cada uno gana su par.
| # | Punto | Situación (tú) | Respuesta (cre≡er) |
|---|-------|----------------|--------------------|
| 01 | Crecer | Ya demostraste que sabes crecer. El siguiente salto no tiene mapa. | Me sumerjo hasta entender tu empresa desde dentro. Sales sabiendo por dónde, no intentándolo por todos lados a la vez. |
| 02 | Consolidarse | Construiste rápido. Escalar así multiplicaría el desorden. | Ordenamos lo que ya existe antes de ponerle más peso encima. Escalas sobre algo que aguanta. |
| 03 | Recuperar rentabilidad | El esfuerzo dejó de traducirse en resultado. | Cuestiono lo que todos dan por hecho en tu operación. El margen no siempre está en vender más. |
| 04 | Simplificar | La operación se volvió más compleja que el negocio. | Separo lo que solo tú puedes decidir de lo que la empresa debería resolver sin ti. |
| 05 | Expandirse | Mercados nuevos, el mismo mapa de decisión. | Reviso tu proceso y ubico la oportunidad real antes de mover una ficha. Expandirte deja de ser una apuesta. |
| 06 | Replantear el portafolio | Varios frentes abiertos y ninguno con su lugar claro. | Construimos, juntos, el criterio para decidir qué sigue y qué se deja ir. Por escrito, no en tu cabeza. |
Los pares **01** y **05** son los dos ejemplos que ella da en el audio, redactados.
### Frontera con `Dolores`
`Dolores` son cuatro citas en primera persona sobre lo que el lector **siente** ("Trabajo más y gano casi lo mismo que hace dos años"). Las seis situaciones de aquí describen el estado del **negocio**, no el ánimo. Esa frontera es deliberada y hay que sostenerla al redactar: si una situación empieza a sonar a queja, invade `Dolores`.
Las dos secciones van seguidas en la página. Conviene mirarlas juntas una vez implementado esto.
### Riesgo de repetición
Las tres respuestas posibles salen todas del mismo mecanismo. Con seis pares hay riesgo real de que las seis digan lo mismo con distintas palabras. Mitigación: cada respuesta abre por el movimiento concreto de esa situación y cierra por lo que cambia para el lector, no por la actividad. Si aun así sobran, **recortar a cuatro es decisión de la dueña** — los seis son suyos.
## 5. Arquitectura
| Archivo | Cambio |
|---------|--------|
| `lib/content.ts` | `puntosInflexion.items` cambia de `{ name, desc }` a `{ name, situacion, respuesta }` |
| `components/primitives/ParDialogo.tsx` | **Nuevo.** Un par: situación, respuesta y el conector entre ambas |
| `components/primitives/__tests__/ParDialogo.test.tsx` | **Nuevo** |
| `components/sections/PuntosInflexion.tsx` | De rejilla + carrusel a secuencia de `ParDialogo` |
| `components/sections/__tests__/PuntosInflexion.test.tsx` | Actualizar: ya no hay tarjetas ni carrusel |
| `components/primitives/Carrusel.tsx` + su test | **Retirar.** Queda sin consumidores (ver abajo) |
**`ParDialogo`** recibe `n`, `name`, `situacion` y `respuesta`, y no sabe nada de la sección que lo monta. Es la única pieza que conoce la forma del intercambio.
Los lados **no alternan par a par**: la situación va siempre al mismo lado y la respuesta siempre al contrario, como en una conversación, donde cada interlocutor mantiene su margen. Lo que alterna al bajar es situación → respuesta → situación → respuesta. Voltear los lados en cada par rompería la lectura de quién habla.
**`PuntosInflexion`** conserva su cabecera actual —eyebrow, título e `IlustracionInflexion`— y sustituye el cuerpo.
**El carrusel móvil desaparece**: el diálogo se apila en vertical y no necesita deslizamiento — además, *"te bajas"* es literalmente lo que pide la dueña. Esto deja a `Carrusel` **sin ningún consumidor**: hoy `PuntosInflexion` es el único. Se retira junto con su test, como se hizo con `SimboloTresLineas`; el historial de git lo conserva si vuelve a hacer falta. Dejarlo montado sin uso repetiría un huérfano que ya se limpió una vez en este proyecto.
**Movimiento.** Cada par entra con `fadeInUp` dentro de un `staggerContainer`, con `viewportOnce`, y **siempre** combinando `usePrefersReducedMotion()` con `motionProps(reduced)`, como el resto de las secciones.
**Los conectores se dibujan con bordes o con formas rellenas, nunca con `pathLength` sobre un SVG escalado de forma no uniforme.** Esa combinación se rompe en Chromium —el guion se recorre en espacio de pantalla pero se normaliza desde el espacio de usuario, y el trazo llega partido en trozos— y es lo que acaba de retirarse con el hilo conductor.
**Tests.** Verifican copy y contrato de marca, no estilos, como el resto del proyecto: que aparezcan las seis situaciones y las seis respuestas; que cada respuesta esté atribuida a creeer y cada situación al lector; que con `prefers-reduced-motion` no quede nada invisible.
## 6. Decisiones que necesitan el visto bueno de la dueña
1. **Se reescriben líneas marcadas como verbatim de su deck.** Los `desc` actuales ("Dar el siguiente salto sin saber por dónde") pasan a segunda persona y dejan de ser literales de la diapositiva 2. Se hace porque es como ella misma los enuncia en la llamada, pero es su texto.
2. **Ella dice "nosotros", la página dice "yo".** En el audio: *"desafiamos", "cuestionamos", "hacemos una inmersión"*. La web está en primera persona del singular porque es una estratega sola, y así está `mecanismo`. Aquí se mantiene el singular salvo donde el trabajo es conjunto ("Construimos, juntos"). Pasar todo a plural sería un cambio global de voz, no de esta sección.
3. **Las seis respuestas son redacción nueva**, no salidas de ningún material suyo. Hay que validarlas una por una.
## 7. Fuera de alcance
La otra mitad de ese tramo de audio: el claim del hero (*"a la mejor, si ponemos 'oye, damos claridad'"*), la **diana con un punto**, y jugar con los puntitos de la marca. Ahí ella está pensando en voz alta y no cierra nada. Merece su propio spec y su propia decisión.
## 8. Criterios de aceptación
- [ ] Los seis pares se leen de arriba abajo como una conversación: situación, respuesta, situación, respuesta.
- [ ] Todo el texto está en el DOM sin interacción: nada tras un clic, un hover o un deslizamiento.
- [ ] Cada respuesta se distingue visualmente como voz de creeer, y cada situación como voz del lector, sin depender solo del color.
- [ ] En móvil (390px) la secuencia se lee sin desbordes horizontales y sin carrusel.
- [ ] Con `prefers-reduced-motion: reduce` no queda ningún par invisible ni ningún conector sin dibujar.
- [ ] `pnpm test`, `npx tsc --noEmit`, `pnpm lint` y `pnpm build` limpios.
- [ ] Ningún conector depende de `pathLength` sobre un SVG con `preserveAspectRatio="none"`.
@@ -1,247 +0,0 @@
# Rediseño completo · «El documento»
**Fecha:** 7/ago/2026
**Rama:** a crear desde `fix/contraste-y-hero-visible`
**Estado:** aprobado por el usuario (dirección y alcance), pendiente de la dueña
---
## 1. Encargo
Rediseñar la landing por completo. El usuario fijó qué sobrevive:
| Se conserva | Queda libre |
|---|---|
| La paleta entera (tokens de `tailwind.config.ts`) | Estructura y orden de secciones |
| El wordmark `cre≡er` (SVG trazado del manual) | Copy |
| Open Sans | Layout, retícula, escala tipográfica |
| | Sistema gráfico, ilustraciones, fotografía |
| | Movimiento |
| | Arquitectura de componentes |
**Conflicto declarado y aceptado:** `docs/dueña-directrices.md` registra que la dueña pidió el 30/jul "algo de gráfico y movimiento". Esta dirección reduce ambos. Y su doc dice de los dolores y los puntos de inflexión: "ya están escritos en su voz — usarlos, no reescribirlos"; este spec los conserva verbatim (§10), pero reorganiza todo lo demás. Ambas cosas necesitan el visto bueno de la dueña antes de publicar.
## 2. Concepto rector
creeer vende un documento. Su propio deck lo dice: cada etapa "deja algo por escrito", y la etapa 08 *es* un One Year Plan.
**La página se comporta como el entregable en vez de describirlo.** Un documento estratégico escrito sobre el negocio del lector: secciones numeradas, columna de margen, reglas finas, medida de texto disciplinada, cero decoración ambiental. La página demuestra claridad en lugar de prometerla.
De ahí sale todo lo demás. Cuando una decisión no esté en este spec, la pregunta es: *¿esto lo haría un documento estratégico bien compuesto?*
## 3. Estructura: 6 secciones
| # | Sección | Fondo | Origen |
|---|---|---|---|
| 01 | Apertura | blanco | `Hero` |
| 02 | Dónde estás | blanco | `PuntosInflexion` + `Dolores` |
| 03 | Qué cambia | **navy** | `FraseAncla` + lead de `Disciplinas` |
| 04 | El trabajo | blanco | `Proceso` |
| 05 | Para quién · Quién | blanco | `ParaQuien` + `Estratega` |
| 06 | Empezar | **navy** | `ComoEmpezar` + `Cierre` |
### 01 · Apertura
Wordmark (`size="xl"`, con tagline), antetítulo, titular en `display`, una línea de quién, dos CTAs. Sin ilustración, sin campos de puntos. Altura natural — se acabó el `min-h-[80vh]`, que en móvil pelea con la barra del navegador.
Su columna de margen lleva el número `01` y su `MarcaSeccion` en `paso={0}`: las líneas más dispersas de toda la página. El sistema queda establecido desde la primera pantalla.
### 02 · Dónde estás
Los seis puntos de inflexión como tabla de dos columnas: nombre (700) | situación (`cuerpo`), separados por filete `gris-claro`. Se lee entera en segundos.
Debajo, un aparte enmarcado con los cuatro dolores en primera persona, a `sumario` en cursiva, uno por línea con filete entre ellos. Cierra el remate de la dueña ("Esto no se resuelve con más esfuerzo…") a `titulo`, con la resolución en dorado.
**Desaparece el escenario fijado de 600vh**, con su `IntersectionObserver` manual, sus seis fotografías, sus velos, su raíl y su barra de progreso.
### 03 · Qué cambia — navy
El centro emocional y el primer navy. La frase ancla va a `titulo` con la segunda cláusula en dorado — no a `display`, que se gasta una sola vez en toda la página y es del titular de 01 (§4). Debajo, el insight que hoy vive en `Disciplinas`: "No vendemos marketing. Vendemos claridad estratégica". Las tres disciplinas quedan como una línea de texto, no como tres tarjetas con icono.
### 04 · El trabajo — blanco
Las ocho etapas como índice numerado, no como ocho tarjetas con foto. Cada etapa: número tabular, nombre a `sumario`/700, y los entregables como chips `azul-claro` con texto navy. Las tres fases aprobadas (Inmersión → Desafiar supuestos → Estrategia ejecutable) como tira encima del índice.
La etapa 08 se distingue por ser la última y por llevar su nombre a `titulo` en vez de a `sumario`, no por cambiar de color de fondo ni de tarjeta.
**Desaparecen las ocho fotografías** y los 27 iconos de entregable (a 18px con trazo efectivo de 1,2px no se distinguen entre sí).
### 05 · Para quién · Quién — blanco
Dos columnas separadas por filete vertical: "Sí es para ti" / "No es para ti", cuatro criterios cada una. Sin losa navy — el navy está reservado a 03 y 06.
Debajo, quién está detrás: hueco de retrato (`aspect-[4/5]`), el 30+ como cifra estática, bio y la cita institucional. **La cifra ya no cuenta hacia arriba.**
### 06 · Empezar — navy
Los tres pasos como lista numerada, el CTA principal a WhatsApp, y los tres enlaces de contacto. Cierra con el wordmark y el legal. Absorbe el footer.
## 4. Sistema tipográfico
Open Sans, cinco roles. Reemplazan a `ancla` y `seccion`, que se eliminan.
```ts
fontSize: {
display: ['clamp(3.5rem, 9vw, 7rem)', { lineHeight: '0.94', letterSpacing: '-0.03em' }],
titulo: ['clamp(2.25rem, 4.5vw, 3.5rem)', { lineHeight: '1', letterSpacing: '-0.02em' }],
sumario: ['clamp(1.375rem, 2vw, 1.75rem)', { lineHeight: '1.35', letterSpacing: '-0.01em' }],
cuerpo: ['1.0625rem', { lineHeight: '1.7' }],
rotulo: ['0.75rem', { lineHeight: '1.2', letterSpacing: '0.3em' }],
}
```
**Reglas duras:**
- `display` aparece **exactamente una vez** en toda la página: el titular de 01. Hoy hay cinco elementos empatados en 4,5rem, y por eso ninguno es el más grande.
- Medida máxima de texto corrido: **68 caracteres** (`max-w-[68ch]`).
- Pesos: 400 cuerpo · 600 énfasis y rótulos · 700 títulos · 800 display. **Prohibido `font-black`**: Open Sans llega a 800 y `layout.tsx` carga hasta 800, así que los 34 usos actuales caen a 800 de todos modos. Se elimina el peso 900 del vocabulario.
- Números de sección y de etapa con `font-variant-numeric: tabular-nums`.
- `text-balance` en titulares, `text-pretty` en párrafos.
## 5. Retícula
```
│ 02 │ DÓNDE ESTÁS │
│ ≡ │ │
│ │ Seis situaciones. Reconoce la tuya. │
│ │ │
│ │ Crecer El siguiente salto │
│ │ no tiene mapa. │
```
- Contenedor: `max-w-content` sube de 1152px a **1280px**, con `px-6 md:px-12`.
- `Seccion` monta `grid md:grid-cols-[7rem_minmax(0,1fr)] gap-x-10`. Columna izquierda: número tabular + `MarcaSeccion`. Columna derecha: todo el contenido.
- En móvil colapsa a una columna; el número y la marca van en línea sobre el antetítulo.
- La tensión entre reglas anchas y texto angosto (68ch dentro de 1280px) es deliberada: es lo que hace que la composición se lea editorial.
**Ritmo vertical**, tres niveles — hoy ocho secciones comparten `py-16 md:py-24` y la página va como un metrónomo:
| Nivel | Padding | Dónde |
|---|---|---|
| `compacta` | `py-20 md:py-28` | 04, 05 |
| `normal` | `py-28 md:py-40` | 02 |
| `momento` | `py-36 md:py-56` | 01, 03, 06 |
01 va en `momento` y sin `min-height`: la apertura respira por padding, no por alto de viewport.
## 6. Color
Los tokens no cambian. Cambia la disciplina de uso.
- **Un solo blanco.** Se eliminan `hondo-blanco` y `hondo-claro`: sus gradientes se solapan (`hondo-claro` empieza en `#FFFFFF`) y hacían indistinguibles cuatro secciones seguidas. Fondo claro = `#FFFFFF` plano.
- **`gris-claro`** deja de ser fondo de sección: solo filetes y rellenos de ficha.
- **Navy exactamente dos veces**, en 03 y 06. Se conserva `hondo-navy` para darles relieve.
- **Dorado, tres trabajos y ninguno más:** el `≡` del wordmark, `MarcaSeccion`, y una palabra de acento por declaración. Se elimina el dorado como relleno de icono y como campo ambiental. Se eliminan `velo-dorado`, `velo-frio` y `dorado-metal`.
- **`azul-claro #C8EEF5`** — hoy declarado y sin usar — pasa a ser el fondo de los chips de entregables en 04, con texto navy: **13,5:1**.
- Se conserva `dorado-texto #8A6520` para texto pequeño dorado sobre claro.
**Contraste mínimo exigido:** 4,5:1 texto normal, 3:1 texto grande y gráficos con significado. Esto cierra los pendientes que quedaron abiertos en la revisión: el acento del titular, el 30+ de Estratega, la flecha entre fases y los iconos dorados sobre blanco — todos desaparecen o se recolorean.
## 7. `MarcaSeccion`: el ≡ como sistema
La única idea gráfica de la página, y la respuesta a "le falta gráfico".
Cada sección lleva su marca en la columna de margen, y **la marca progresa al bajar**: muchas líneas desordenadas al principio, tres barras alineadas al final. La filosofía del manual (Complejidad → Enfoque → Claridad) deja de estar ilustrada en un rincón y pasa a ser el mobiliario del documento.
**Implementación:** reutiliza la geometría ya probada de `lib/simbolo.ts` (`VIEWBOX_SIMBOLO`, `LINEAS_SIMBOLO`, `BARRAS_SIMBOLO`) y las posiciones dispersas de `SecuenciaClaridad`. `MarcaSeccion({ paso })` con `paso` de 0 a 5 interpola **estáticamente** cada línea entre su posición dispersa y su barra destino con factor `paso/5`; las líneas no supervivientes bajan de opacidad con el mismo factor. En `paso === 5` quedan las tres barras del `≡` exactas.
Sin animación, sin estado, sin JS: es un SVG calculado en render. Server component.
**Sustituye a todo el aparato gráfico actual.** Se eliminan diecisiete primitivas: `FondoPuntos`, `PatronPuntos`, `Resplandor`, `IlustracionNudo`, `IlustracionBifurcacion`, `IlustracionInflexion`, `IlustracionConvergencia`, `SecuenciaClaridad`, `SimboloMarca`, `TarjetaSpotlight`, `MarcoRetrato`, `EscenaDialogo`, `ParDialogo`, `FondoFoto`, `TextoDesenfoque`, `ContadorNumero`, `ProgresoLectura`, y el sistema de iconos `Icono` con sus 41 glifos.
## 8. Movimiento
**Un solo gesto: `revelar`.** 12px de subida + fade, 400ms, `cubic-bezier(0.22, 1, 0.36, 1)`, una vez, al entrar en viewport, escalonado 60ms entre hermanos.
Se eliminan: parallax, scroll fijado, contadores, spotlight, hover-lift, blur palabra a palabra, barra de progreso de lectura, compactación del header, dibujado de ilustraciones y puntos reactivos al cursor.
**Montado al revés de como está hoy — este es el punto crítico.** El export actual manda 112 elementos en `opacity: 0`; si el JS no llega, la página no se lee.
1. `app/layout.tsx` incluye un script inline bloqueante en `<head>`: `document.documentElement.classList.add('js')`.
2. `globals.css`:
```css
@media (prefers-reduced-motion: no-preference) {
.js [data-revelar] { opacity: 0; transform: translateY(12px); }
.js [data-revelar][data-visible] { animation: revelar 400ms cubic-bezier(.22,1,.36,1) both;
animation-delay: calc(var(--i, 0) * 60ms); }
}
@keyframes revelar { to { opacity: 1; transform: none; } }
```
3. `<Revelar indice>` (client) pinta un envoltorio con `data-revelar` y `style={{ '--i': indice }}`. Un único `IntersectionObserver` a nivel de módulo, compartido por todas las instancias, marca `data-visible` al entrar y deja de observar. `indice` lo pasa la sección: es la posición del bloque entre sus hermanos, y sirve solo para escalonar el retardo. Cuando se omite, vale 0.
**Consecuencias:** sin JS, todo visible. Con `prefers-reduced-motion: reduce`, el estado oculto nunca llega a aplicarse — no hace falta el hook `usePrefersReducedMotion` ni el spread `motionProps` en ninguna sección.
## 9. Arquitectura
- **Fuera `framer-motion`** (53,6 kB del bundle compartido) y **fuera `embla-carousel`** + `embla-carousel-autoplay`, que ya no usa nadie.
- **Las secciones vuelven a ser server components.** Hoy las diez son `'use client'`. El único componente cliente será `Revelar`.
- **Siete primitivas**, frente a las ~20 actuales:
| Primitiva | Papel |
|---|---|
| `Container` | ancho y padding horizontal |
| `Seccion` | la retícula de margen + el ritmo vertical + fondo |
| `MarcaSeccion` | la progresión del ≡ |
| `Antetitulo` | el rótulo dorado (ya existe) |
| `CTA` | los botones (ya existe) |
| `Wordmark` | el logotipo (ya existe, intacto) |
| `Revelar` | el único gesto de movimiento |
- `lib/content.ts` sigue siendo el source of truth del copy.
- `lib/motion.ts` se elimina.
## 10. Copy
**Verbatim, sin tocar** (son palabras de la dueña, de su deck):
- Los seis puntos de inflexión (nombre + situación).
- Los cuatro dolores en primera persona.
- El remate: "Esto no se resuelve con más esfuerzo. Se resuelve con una estrategia clara."
- Las frases-ancla aprobadas.
- Los nombres de las ocho etapas y sus entregables.
- El insight: "No vendemos marketing. Vendemos claridad estratégica."
**Se escribe nuevo** solo el tejido conectivo que pide la estructura nueva: antetítulos y títulos de las seis secciones, y los sumarios de una línea bajo cada uno. Se recortan las respuestas largas del recorrido, que existían para llenar seis pantallas fijadas.
**Se mantiene la prohibición de escasez** en 01 (`cupos|cohorte|generación fundadora`), decisión explícita de la dueña.
Los placeholders `TODO dueña` (WhatsApp, email, LinkedIn, dominio, retrato) siguen siendo placeholders. No se inventan valores.
## 11. Accesibilidad
- Skip link al `<main>` — hoy no hay.
- Un solo `<h1>` (01); cada sección con su `<h2>`.
- `100svh`/`100dvh` en vez de `100vh` donde haga falta.
- Foco visible con contraste ≥ 3:1 **contra el fondo sobre el que aparece**: anillo navy sobre claro, dorado sobre navy. El dorado global de hoy da 2,76:1 sobre blanco.
- Nada de contenido oculto tras estado de scroll.
## 12. Tests
Se mantiene la convención del repo: **copy y contrato de marca, no estilos**, salvo cuando el estilo *es* el contrato (contraste, contenido oculto).
Se conservan intactos: `simbolo.test.ts`, `Wordmark.test.tsx`, `Antetitulo.test.tsx`, `CTA.test.tsx`, `Container.test.tsx`, `contraste.test.ts` y `content.test.ts`.
Se reescriben, no se conservan, los tests de sección: las seis secciones nuevas no se corresponden una a una con las diez viejas. Cada una estrena su test de copy, y el test negativo de escasez viaja de `Hero.test.tsx` al de la sección 01.
Se añaden:
1. **Guarda de contenido oculto**, generalizada a las seis secciones: ninguna renderiza elementos con `style.opacity === '0'` ni `style.filter` con blur. Es el fallo que ya nos mordió una vez.
2. **`MarcaSeccion`**: `paso={5}` produce exactamente las tres barras de `LINEAS_SIMBOLO`; `paso={0}` produce las nueve líneas dispersas.
3. **Guarda de escala**: la página monta exactamente un elemento con `text-display`.
Se eliminan los tests de los componentes eliminados.
## 13. Criterios de aceptación
1. `pnpm test`, `npx tsc --noEmit` y `pnpm lint` limpios.
2. `pnpm build` correcto, y `out/index.html` con **cero** ocurrencias de `opacity:0` y de `filter:blur`.
3. `framer-motion` y `embla-carousel` no aparecen en `package.json` ni en el bundle.
4. Exactamente **dos** secciones navy y **un** elemento a escala `display`.
5. Todo par texto/fondo cumple 4,5:1 (normal) o 3:1 (grande y gráficos).
6. Con JavaScript desactivado, la página se lee completa.
## 14. Fuera de alcance
- El retrato real de la estratega y los datos de contacto: siguen pendientes de la dueña.
- Los dos casos de proyectos reales que la dueña prometió el 29/jul.
- Cualquier decisión sobre si la escasez ("Generación Fundadora") puede vivir fuera del hero.
## 15. Riesgos
1. **Va contra el último encargo de la dueña.** Pidió "algo de gráfico y movimiento"; esto reduce ambos. El argumento a favor es que el ≡ progresivo es más gráfico que diez adornos ambientales, y que un solo gesto de movimiento se nota más que doce repartidos. Es su marca: necesita aprobarlo.
2. **Se retira trabajo real.** El recorrido fijado, cinco ilustraciones SVG, catorce fotografías acreditadas y el sistema de iconos son semanas de trabajo. Queda todo en git, pero sale de la página.
3. **Con una sola familia tipográfica, la distinción depende entera de la composición.** Si el resultado se siente genérico, la salida natural sería una segunda familia para display — y eso choca con la decisión de tipografía que el usuario marcó como intocable.