From 89e54a6e84e08c37a73786846151d22bfb6a63a6 Mon Sep 17 00:00:00 2001
From: erikgh
Date: Wed, 30 Sep 2026 11:27:20 -0600
Subject: [PATCH] =?UTF-8?q?feat:=20redise=C3=B1o=20=C2=ABEl=20documento?=
=?UTF-8?q?=C2=BB=20en=20curso,=20contacto=20real=20y=20enlaces=20externos?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Primitivas y contenido del rediseño (escala tipográfica, Revelar,
MarcaSeccion, Seccion, Antetitulo, SimboloMarca, Resplandor, ClaridadHero,
sección Proceso de ocho etapas, lib/content.ts reestructurado en seis
secciones con alias para las viejas). Mecanismo y FormaTrabajar se retiran.
Encima, lo de esta sesión:
- recorrido: creeer vuelve a responder cada situación en vez de repetirla
(el alias apuntaba respuesta a situacion); respuestas en plural. Créditos
de las seis fotos de México.
- contacto: WhatsApp, email (con mailto:) y LinkedIn reales; iconos de las
redes en el cierre y el footer (IconoRed, trazos de Simple Icons, CC0).
- enlaces: lo externo abre en otra pestaña con noopener y aviso para
lectores de pantalla (lib/enlaces.ts); mailto y anclas se quedan.
- Proceso: las ocho tarjetas no aparecían nunca en móvil (amount 0.3 sobre
una lista de ~3000px); ahora cada una se revela sola. El ≡ de la 08 ya no
tapa un entregable, título equilibrado, etiqueta a 12px, sin elevar al
hover lo que no es clicable.
- Disciplinas: la frase «No son la promesa — son los medios» gana peso y
se agrupa con título e ilustración; sin cortes huérfanos en móvil.
Co-Authored-By: Claude Opus 5.5
---
AGENTS.md | 5 +-
README.md | 4 +-
app/globals.css | 38 +-
app/layout.tsx | 16 +-
app/page.tsx | 17 +-
components/layout/Footer.tsx | 21 +-
components/layout/__tests__/Footer.test.tsx | 26 +
components/primitives/Antetitulo.tsx | 41 +
components/primitives/CTA.tsx | 8 +-
components/primitives/ClaridadHero.tsx | 57 +
components/primitives/Icono.tsx | 264 +-
components/primitives/IconoRed.tsx | 41 +
components/primitives/MarcaSeccion.tsx | 60 +
components/primitives/ParDialogo.tsx | 4 +-
components/primitives/Resplandor.tsx | 25 +
components/primitives/Revelar.tsx | 59 +
components/primitives/Seccion.tsx | 67 +
components/primitives/SecuenciaClaridad.tsx | 88 +-
components/primitives/SimboloMarca.tsx | 29 +
.../primitives/__tests__/Antetitulo.test.tsx | 60 +
components/primitives/__tests__/CTA.test.tsx | 12 +-
.../__tests__/ClaridadHero.test.tsx | 23 +
.../__tests__/EscenaClaridad.test.tsx | 54 +
.../primitives/__tests__/Icono.test.tsx | 16 +-
.../primitives/__tests__/IconoRed.test.tsx | 26 +
.../__tests__/MarcaSeccion.test.tsx | 55 +
.../primitives/__tests__/ParDialogo.test.tsx | 6 +-
.../primitives/__tests__/Revelar.test.tsx | 52 +
.../primitives/__tests__/Seccion.test.tsx | 43 +
.../__tests__/SimboloMarca.test.tsx | 35 +
components/sections/Cierre.tsx | 21 +-
components/sections/ComoEmpezar.tsx | 7 +-
components/sections/Disciplinas.tsx | 63 +-
components/sections/Dolores.tsx | 8 +-
components/sections/Estratega.tsx | 47 +-
components/sections/FormaTrabajar.tsx | 77 -
components/sections/FraseAncla.tsx | 19 +-
components/sections/Hero.tsx | 64 +-
components/sections/Mecanismo.tsx | 113 -
components/sections/ParaQuien.tsx | 7 +-
components/sections/Proceso.tsx | 218 ++
components/sections/PuntosInflexion.tsx | 21 +-
components/sections/__tests__/Cierre.test.tsx | 13 +
.../sections/__tests__/ComoEmpezar.test.tsx | 6 +-
.../sections/__tests__/Estratega.test.tsx | 11 +-
.../sections/__tests__/FraseAncla.test.tsx | 5 +
components/sections/__tests__/Hero.test.tsx | 47 +-
.../sections/__tests__/Mecanismo.test.tsx | 43 -
.../sections/__tests__/ParaQuien.test.tsx | 4 +-
.../sections/__tests__/Proceso.test.tsx | 119 +
.../__tests__/PuntosInflexion.test.tsx | 9 +
.../2026-08-19-creeer-rediseno-documento.md | 2273 +++++++++++++++++
.../plans/2026-08-21-animacion-tres-lineas.md | 320 +++
...-08-19-creeer-rediseno-documento-design.md | 247 ++
lib/__tests__/content.test.ts | 101 +
lib/__tests__/contraste.test.ts | 41 +
lib/__tests__/enlaces.test.ts | 15 +
lib/__tests__/escala.test.ts | 31 +
lib/content.ts | 370 +--
lib/enlaces.ts | 19 +
lib/motion.ts | 2 +-
package.json | 5 +-
pnpm-lock.yaml | 203 ++
public/images/mecanismo-bg.jpg | Bin 174448 -> 0 bytes
public/images/proceso/01.jpg | Bin 0 -> 133356 bytes
public/images/proceso/02.jpg | Bin 0 -> 76057 bytes
public/images/proceso/03.jpg | Bin 0 -> 176517 bytes
public/images/proceso/04.jpg | Bin 0 -> 262526 bytes
public/images/proceso/05.jpg | Bin 0 -> 78495 bytes
public/images/proceso/06.jpg | Bin 0 -> 125717 bytes
public/images/proceso/07.jpg | Bin 0 -> 129029 bytes
public/images/proceso/08.jpg | Bin 0 -> 96347 bytes
tailwind.config.ts | 52 +-
73 files changed, 5261 insertions(+), 592 deletions(-)
create mode 100644 components/primitives/Antetitulo.tsx
create mode 100644 components/primitives/ClaridadHero.tsx
create mode 100644 components/primitives/IconoRed.tsx
create mode 100644 components/primitives/MarcaSeccion.tsx
create mode 100644 components/primitives/Resplandor.tsx
create mode 100644 components/primitives/Revelar.tsx
create mode 100644 components/primitives/Seccion.tsx
create mode 100644 components/primitives/SimboloMarca.tsx
create mode 100644 components/primitives/__tests__/Antetitulo.test.tsx
create mode 100644 components/primitives/__tests__/ClaridadHero.test.tsx
create mode 100644 components/primitives/__tests__/EscenaClaridad.test.tsx
create mode 100644 components/primitives/__tests__/IconoRed.test.tsx
create mode 100644 components/primitives/__tests__/MarcaSeccion.test.tsx
create mode 100644 components/primitives/__tests__/Revelar.test.tsx
create mode 100644 components/primitives/__tests__/Seccion.test.tsx
create mode 100644 components/primitives/__tests__/SimboloMarca.test.tsx
delete mode 100644 components/sections/FormaTrabajar.tsx
delete mode 100644 components/sections/Mecanismo.tsx
create mode 100644 components/sections/Proceso.tsx
delete mode 100644 components/sections/__tests__/Mecanismo.test.tsx
create mode 100644 components/sections/__tests__/Proceso.test.tsx
create mode 100644 docs/superpowers/plans/2026-08-19-creeer-rediseno-documento.md
create mode 100644 docs/superpowers/plans/2026-08-21-animacion-tres-lineas.md
create mode 100644 docs/superpowers/specs/2026-08-19-creeer-rediseno-documento-design.md
create mode 100644 lib/__tests__/content.test.ts
create mode 100644 lib/__tests__/contraste.test.ts
create mode 100644 lib/__tests__/enlaces.test.ts
create mode 100644 lib/__tests__/escala.test.ts
create mode 100644 lib/enlaces.ts
delete mode 100755 public/images/mecanismo-bg.jpg
create mode 100644 public/images/proceso/01.jpg
create mode 100644 public/images/proceso/02.jpg
create mode 100644 public/images/proceso/03.jpg
create mode 100644 public/images/proceso/04.jpg
create mode 100644 public/images/proceso/05.jpg
create mode 100644 public/images/proceso/06.jpg
create mode 100644 public/images/proceso/07.jpg
create mode 100644 public/images/proceso/08.jpg
diff --git a/AGENTS.md b/AGENTS.md
index 153a4d7..cf364f3 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,6 +1,6 @@
# creeer — Landing page
-One-page landing para **creeer**, marca personal de una estratega de crecimiento. Stack: Next.js 14 (App Router) + TypeScript + Tailwind CSS + Framer Motion, exportada como sitio estático. Español.
+One-page landing para **creeer**, firma de claridad estratégica. Stack: Next.js 14 (App Router) + TypeScript + Tailwind CSS + Framer Motion, exportada como sitio estático. Español.
## Estado del proyecto
@@ -17,7 +17,8 @@ One-page landing para **creeer**, marca personal de una estratega de crecimiento
- **Paleta:** Azul Profundo `#0A1D3D` + Dorado Estratégico `#C4942E` + Blanco `#FFFFFF` + neutros (Azul Claro `#C8EEF5`, Gris `#F2F2F2`, Gris Medio `#AAAAAA`). **Sin amarillo eléctrico** tipo Hormozi.
- **Tipografía:** **Open Sans** (vía `next/font/google`). Es lo más cercano a Aptos en open-source (mismo diseñador: Steve Matteson). **Aptos descartada** en web: la EULA de Microsoft prohíbe servirla públicamente. Decisión de la dueña, 28/jul/2026.
- **Tagline de marca:** "CLARIDAD · DIRECCIÓN · CRECIMIENTO". Eslogan: "Estrategia que transforma."
-- **Voz:** segunda persona ("tú"/"tu negocio"), sin jerga corporativa.
+- **Voz:** segunda persona ("tú"/"tu negocio"), sin jerga corporativa. **creeer habla en primera persona del plural** ("nos sumergimos", "cuestionamos") — decisión de Erik del 6/ago/2026, **visto bueno de la dueña el 19/ago/2026**; antes era singular. Única excepción: las citas de `dolores`, donde el "yo" es el cliente. Hay un test que lo blinda en `lib/__tests__/content.test.ts`.
+- **Cifra de trayectoria: 25+** años construyendo marcas (confirmado por la dueña, 19/ago/2026). No 30+.
- **Sin escasez en el hero:** no mostrar cupos, cohorte ni "Generación Fundadora" arriba (decisión de la dueña).
## Stack y constraints
diff --git a/README.md b/README.md
index 72fd62f..82e9ff5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# creeer
-Landing page one-page para **creeer**, marca personal de una estratega de crecimiento. Next.js 14 (App Router) + TypeScript + Tailwind CSS + Framer Motion, exportada como sitio estático.
+Landing page one-page para **creeer**, firma de claridad estratégica. Next.js 14 (App Router) + TypeScript + Tailwind CSS + Framer Motion, exportada como sitio estático.
## Requisitos
@@ -23,7 +23,7 @@ pnpm lint # ESLint
app/ # App Router (layout, page, globals.css)
components/
primitives/ # Wordmark, Container, CTA (reutilizables)
- sections/ # Hero, FraseAncla, ParaQuien, Mecanismo, Disciplinas, Estratega, Cierre, ComoEmpezar
+ sections/ # Hero, PuntosInflexion, FraseAncla, Dolores, Proceso, Disciplinas, ParaQuien, Estratega, ComoEmpezar, Cierre
layout/ # Header, Footer
lib/
content.ts # todo el copy (source of truth)
diff --git a/app/globals.css b/app/globals.css
index 18c262f..050b757 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -18,9 +18,43 @@
}
@layer base {
- a:focus-visible, button:focus-visible {
- outline: 3px solid #C4942E;
+ /* The ring needs to contrast against the background it appears on, not
+ against the brand. The global gold gave 2.76:1 on white, below the 3:1
+ WCAG 2.2 requires for a focus indicator. Deep blue on white gives 16.7:1;
+ and on the two navy sections, where the navy would disappear, gold takes
+ over, which gives 6.07:1 there. */
+ a:focus-visible,
+ button:focus-visible {
+ outline: 3px solid #0a1d3d;
outline-offset: 3px;
border-radius: 2px;
}
+
+ .bg-azul-profundo a:focus-visible,
+ .bg-azul-profundo button:focus-visible {
+ outline-color: #c4942e;
+ }
+}
+
+/* The page's only motion gesture. The hidden state depends on `.js`, which an
+ inline script in sets before first paint: without JavaScript the
+ class never lands, nothing is hidden, and the page reads in full. Anyone
+ who asked for less motion is also excluded here — the media query leaves
+ them out entirely rather than neutralising it afterwards. */
+@media (prefers-reduced-motion: no-preference) {
+ .js [data-revelar] {
+ opacity: 0;
+ transform: translateY(12px);
+ }
+ .js [data-revelar][data-visible] {
+ animation: revelar 400ms cubic-bezier(0.22, 1, 0.36, 1) both;
+ animation-delay: calc(var(--i, 0) * 60ms);
+ }
+}
+
+@keyframes revelar {
+ to {
+ opacity: 1;
+ transform: none;
+ }
}
diff --git a/app/layout.tsx b/app/layout.tsx
index b386bbd..f793c5d 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -5,23 +5,29 @@ import './globals.css';
const openSans = Open_Sans({ subsets: ['latin'], weight: ['400', '600', '700', '800'], style: ['normal', 'italic'], variable: '--font-open-sans', display: 'swap' });
export const metadata: Metadata = {
- title: 'creeer · tu estratega de crecimiento',
- description: 'Claridad estratégica para empresarios con tracción que llegaron a su punto de inflexión. Inmersión profunda, estrategia ejecutable.',
+ title: 'creeer · claridad estratégica para tu negocio',
+ description: 'Tu negocio no necesita más esfuerzo — necesita más claridad. Estrategia ejecutable para empresarios con tracción en su punto de inflexión.',
metadataBase: new URL('https://creeer.com'), // TODO dueña: real domain
openGraph: {
- title: 'creeer · tu estratega de crecimiento',
- description: 'Claridad estratégica para empresarios con tracción en punto de inflexión.',
+ title: 'creeer · claridad estratégica para tu negocio',
+ description: 'Tu negocio no necesita más esfuerzo — necesita más claridad. Estrategia ejecutable para empresarios con tracción.',
type: 'website',
locale: 'es',
images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'creeer' }],
},
- twitter: { card: 'summary_large_image', title: 'creeer', description: 'Claridad estratégica para empresarios.' },
+ twitter: { card: 'summary_large_image', title: 'creeer', description: 'Tu negocio no necesita más esfuerzo — necesita más claridad.' },
alternates: { canonical: 'https://creeer.com' },
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
+
+ {/* Blocking and before first paint on purpose: it decides whether the
+ CSS can hide anything at all. If this doesn't run, there is no
+ hidden state to reveal. */}
+
+
{children}
);
diff --git a/app/page.tsx b/app/page.tsx
index 4de2064..39cadf2 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -5,8 +5,7 @@ import { FraseAncla } from '@/components/sections/FraseAncla';
import { PuntosInflexion } from '@/components/sections/PuntosInflexion';
import { Dolores } from '@/components/sections/Dolores';
import { ParaQuien } from '@/components/sections/ParaQuien';
-import { Mecanismo } from '@/components/sections/Mecanismo';
-import { FormaTrabajar } from '@/components/sections/FormaTrabajar';
+import { Proceso } from '@/components/sections/Proceso';
import { Disciplinas } from '@/components/sections/Disciplinas';
import { Estratega } from '@/components/sections/Estratega';
import { ComoEmpezar } from '@/components/sections/ComoEmpezar';
@@ -16,7 +15,7 @@ const jsonLd = {
'@context': 'https://schema.org',
'@type': 'ProfessionalService',
name: 'creeer',
- description: 'Estratega de crecimiento. Claridad estratégica para empresarios con tracción.',
+ description: 'Claridad estratégica para empresarios con tracción en su punto de inflexión.',
url: 'https://creeer.com',
};
@@ -27,16 +26,20 @@ export default function Home() {
{/* El recorrido va en segundo lugar por decisión de la dueña: el lector
entra por el gancho y cae directo en la secuencia anclada, que plantea
- seis situaciones y las contesta. FraseAncla y Dolores llegan después,
- y Mecanismo cierra explicando el método detrás de esas respuestas. */}
+ seis situaciones y las contesta. FraseAncla y Dolores llegan después.
+ Disciplinas precede a Proceso a propósito: su lead —«no son la
+ promesa, son los medios»— tiene que aterrizar ANTES de que el lector
+ se encuentre ocho etapas llamadas «Comunicación y branding» y
+ «Marketing y ventas», o Proceso se lee como catálogo de agencia, que
+ es justo lo que el posicionamiento de la dueña descarta. Después de
+ Proceso llegaba tarde, como nota al pie. */}
{/* dueña puede cambiar a "hormozi" */}
-
-
+
diff --git a/components/layout/Footer.tsx b/components/layout/Footer.tsx
index d826fea..08103fb 100644
--- a/components/layout/Footer.tsx
+++ b/components/layout/Footer.tsx
@@ -1,6 +1,8 @@
import { Wordmark } from '@/components/primitives/Wordmark';
import { Container } from '@/components/primitives/Container';
-import { footer, creditosFotos } from '@/lib/content';
+import { IconoRed, NOMBRES_RED } from '@/components/primitives/IconoRed';
+import { footer, creditosFotos, empezar } from '@/lib/content';
+import { esExterno, propsEnlace, AVISO_PESTANA } from '@/lib/enlaces';
export function Footer() {
return (
@@ -8,6 +10,19 @@ export function Footer() {
diff --git a/components/layout/__tests__/Footer.test.tsx b/components/layout/__tests__/Footer.test.tsx
index aaba8b7..059417f 100644
--- a/components/layout/__tests__/Footer.test.tsx
+++ b/components/layout/__tests__/Footer.test.tsx
@@ -29,3 +29,29 @@ describe('', () => {
expect(screen.getAllByText(/modificada/)).toHaveLength(creditosFotos.length);
});
});
+
+describe(' — redes', () => {
+ it('links every contact channel by name, since the icons alone say nothing to a screen reader', () => {
+ render();
+ const lista = screen.getByRole('list', { name: /redes y contacto/i });
+ expect(lista.querySelectorAll('a')).toHaveLength(3);
+ expect(screen.getByRole('link', { name: /^WhatsApp/ })).toHaveAttribute('href', expect.stringMatching(/^https:\/\/wa\.me\//));
+ expect(screen.getByRole('link', { name: 'Email' })).toHaveAttribute('href', expect.stringMatching(/^mailto:/));
+ expect(screen.getByRole('link', { name: /^LinkedIn/ })).toHaveAttribute('href', expect.stringMatching(/linkedin\./));
+ });
+
+ it('opens every outside link in a new tab, so the reader keeps the page', () => {
+ render();
+ for (const a of screen.getAllByRole('link')) {
+ const href = a.getAttribute('href') ?? '';
+ if (/^https?:/.test(href)) {
+ expect(a).toHaveAttribute('target', '_blank');
+ expect(a).toHaveAttribute('rel', 'noopener noreferrer');
+ expect(a).toHaveAccessibleName(/se abre en otra pestaña/);
+ } else {
+ // mailto: in a new tab only leaves an empty tab behind.
+ expect(a).not.toHaveAttribute('target');
+ }
+ }
+ });
+});
diff --git a/components/primitives/Antetitulo.tsx b/components/primitives/Antetitulo.tsx
new file mode 100644
index 0000000..e9b44d6
--- /dev/null
+++ b/components/primitives/Antetitulo.tsx
@@ -0,0 +1,41 @@
+import type { ElementType, ReactNode } from 'react';
+
+// The short gold line in letter-spaced caps that announces each section.
+//
+// It used to be hand-written in eight places, and the colour travelled with it:
+// `text-dorado` reads at 6,07:1 on azul-profundo and at 2,76:1 on white, so
+// half of those copies were illegible and every fix had to be made eight times.
+// The rule lives here now, and the default is the safe one — forgetting the
+// prop cannot produce an unreadable antetítulo.
+//
+// `as` takes `motion.p` where the section wants it to animate in, and `h2`
+// where the antetítulo is the only heading the section has.
+
+type Fondo = 'claro' | 'oscuro';
+
+const tono: Record = {
+ claro: 'text-dorado-texto',
+ oscuro: 'text-dorado',
+};
+
+export function Antetitulo({
+ children,
+ sobre = 'claro',
+ as: Tag = 'p',
+ className = '',
+ ...rest
+}: {
+ children: ReactNode;
+ sobre?: Fondo;
+ as?: ElementType;
+ className?: string;
+ // Whatever the caller's `as` needs — in practice Framer's variants/viewport
+ // props when a section wants its antetítulo to animate in.
+ [prop: string]: unknown;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/components/primitives/CTA.tsx b/components/primitives/CTA.tsx
index a09ecda..64b3693 100644
--- a/components/primitives/CTA.tsx
+++ b/components/primitives/CTA.tsx
@@ -1,9 +1,13 @@
+import { esExterno, propsEnlace, AVISO_PESTANA } from '@/lib/enlaces';
+
type Variant = 'light' | 'dark' | 'ghost';
// Every variant carries border-2 so filled and outlined CTAs line up when
// they sit side by side.
const styles: Record = {
light: 'border-2 border-transparent bg-azul-profundo text-white hover:bg-azul-profundo/90',
- dark: 'border-2 border-transparent bg-dorado text-white hover:bg-dorado/90',
+ // Azul profundo sobre dorado, no blanco: blanco sobre #C4942E es 2,76:1 y
+ // esta es la variante del botón que cierra la página.
+ dark: 'border-2 border-transparent bg-dorado text-azul-profundo hover:bg-dorado/90',
ghost: 'border-2 border-azul-profundo text-azul-profundo hover:bg-azul-profundo hover:text-white',
};
@@ -11,10 +15,12 @@ export function CTA({ href, label, variant = 'light', className = '' }: { href:
return (
{label}{' '}
→
+ {esExterno(href) && ({AVISO_PESTANA})}
);
}
diff --git a/components/primitives/ClaridadHero.tsx b/components/primitives/ClaridadHero.tsx
new file mode 100644
index 0000000..31462ad
--- /dev/null
+++ b/components/primitives/ClaridadHero.tsx
@@ -0,0 +1,57 @@
+'use client';
+
+import dynamic from 'next/dynamic';
+import { useEffect, useRef, useState } from 'react';
+
+// The building's public door, and the only file a section should import.
+//
+// three.js is around 185 kB gzipped and the hero is the LCP — the page opens
+// from a WhatsApp link on mobile data, so the headline, the CTAs and the Dream
+// Outcome have to be painted and clickable long before any of this arrives.
+// `dynamic` holds the scene back until after hydration, and `ssr: false` is not
+// optional: three.js needs a real canvas, which the static export does not have
+// at build time.
+//
+// The `dynamic` call lives here, behind 'use client', because Next 14 refuses
+// `ssr: false` inside a Server Component — and `Hero` is one, deliberately: it
+// is almost all text and has no reason to ship as JavaScript.
+const EscenaClaridad = dynamic(
+ () => import('@/components/primitives/EscenaClaridad').then((m) => m.EscenaClaridad),
+ { ssr: false, loading: () => null },
+);
+
+export function ClaridadHero({ className = '' }: { className?: string }) {
+ const contenedor = useRef(null);
+ const [cerca, setCerca] = useState(false);
+
+ // On a phone the drawing sits under the two CTAs, a screen and a half down,
+ // and plenty of readers tap through without ever reaching it. Deferring to
+ // hydration is not enough: the chunk waits until the box is nearly in view,
+ // the same deal the six photos of the recorrido got. On a laptop the box is
+ // beside the headline, so this resolves on the first observation and costs
+ // nothing.
+ useEffect(() => {
+ const el = contenedor.current;
+ if (!el) return;
+ if (typeof IntersectionObserver === 'undefined') {
+ setCerca(true);
+ return;
+ }
+ const obs = new IntersectionObserver(
+ ([entrada]) => {
+ if (!entrada.isIntersecting) return;
+ setCerca(true);
+ obs.disconnect();
+ },
+ { rootMargin: '300px' },
+ );
+ obs.observe(el);
+ return () => obs.disconnect();
+ }, []);
+
+ return (
+
+ {cerca ? : null}
+
+ );
+}
diff --git a/components/primitives/Icono.tsx b/components/primitives/Icono.tsx
index 379a2b6..8ad070f 100644
--- a/components/primitives/Icono.tsx
+++ b/components/primitives/Icono.tsx
@@ -1,6 +1,6 @@
import type { ReactNode } from 'react';
-// Set of simple line icons — one per concept used across Mecanismo,
+// Set of simple line icons — one per concept used across Proceso,
// ComoEmpezar and Disciplinas. Same stroke weight and geometric language as
// the manual's own iconography guidance ("líneas simples, geométricas,
// consistentes en grosor"), hand-drawn to match each step's specific idea
@@ -16,10 +16,39 @@ export type IconoNombre =
| 'comunicacion'
| 'digital'
| 'experiencias'
- | 'reloj';
+ | 'consumidor'
+ | 'ventas'
+ | 'compania'
+ // Entregables — one mark per deliverable, the way the lámina gives every
+ // line its own.
+ | 'matriz'
+ | 'prioridades'
+ | 'rayo'
+ | 'mapa'
+ | 'restriccion'
+ | 'brote'
+ | 'segmentos'
+ | 'ficha'
+ | 'recorrido'
+ | 'hueco'
+ | 'diana'
+ | 'arquitectura'
+ | 'libro'
+ | 'megafono'
+ | 'calendario'
+ | 'ondas'
+ | 'documento'
+ | 'medidor'
+ | 'tablero'
+ | 'comite'
+ | 'hitos'
+ | 'cohete'
+ | 'bombilla'
+ | 'anio'
+ | 'escudo';
const PATHS: Record = {
- // A line dives through two layers down to a core point — "me sumerjo
+ // A line dives through two layers down to a core point — "nos sumergimos
// hasta comprenderla desde dentro".
inmersion: (
<>
@@ -30,7 +59,7 @@ const PATHS: Record = {
>
),
- // A circle (the assumption) cracked by a bolt — "cuestiono lo que todos
+ // A circle (the assumption) cracked by a bolt — "cuestionamos lo que todos
// dan por hecho".
desafiar: (
<>
@@ -46,7 +75,7 @@ const PATHS: Record = {
>
),
- // A speech bubble mid-thought — "cuéntame dónde está tu negocio hoy".
+ // A speech bubble mid-thought — "cuéntanos dónde está tu negocio hoy".
escribeme: (
<>
@@ -102,15 +131,232 @@ const PATHS: Record = {
>
),
- // Simple clock — "cada proceso tiene su ritmo".
- reloj: (
+ // A person with the insight sitting inside the head, not beside it — the
+ // point of Consumer Knowledge is what you find in there.
+ consumidor: (
<>
-
-
+
+
+
>
),
+ // Three rising bars under an arrow — sell in, sell out, rotación.
+ ventas: (
+ <>
+
+
+
+
+
+
+ >
+ ),
+
+ // Two towers — the company seen from outside. Stage 01 needs its own mark
+ // because "Diagnóstico ejecutivo", one of its own deliverables, already has
+ // the magnifier, and the two would sit repeated in the same card.
+ compania: (
+ <>
+
+
+
+
+
+
+ >
+ ),
+
+ // ── Entregables ────────────────────────────────────────────────────────
+ // Deliberately plainer than the concept icons above: these render at 18px
+ // inside the deliverables list, where anything busier turns to mush.
+
+ // Four quadrants — the SWOT grid itself.
+ matriz: (
+ <>
+
+
+ >
+ ),
+ // Three rules, longest first — a ranked list.
+ prioridades: (
+ <>
+
+
+
+ >
+ ),
+ // A bolt — the win you can take this quarter.
+ rayo: ,
+ // Folded map.
+ mapa: (
+ <>
+
+
+ >
+ ),
+ // A funnel — where the business narrows.
+ restriccion: ,
+ // A sprout.
+ brote: (
+ <>
+
+
+
+ >
+ ),
+ // A disc cut into parts.
+ segmentos: (
+ <>
+
+
+ >
+ ),
+ // An ID card — the persona, written down.
+ ficha: (
+ <>
+
+
+
+
+ >
+ ),
+ // Waypoints along a dotted route.
+ recorrido: (
+ <>
+
+
+
+ >
+ ),
+ // The gap between two solid runs — the need nobody covers.
+ hueco: (
+ <>
+
+
+ >
+ ),
+ // Crosshair on the target.
+ diana: (
+ <>
+
+
+
+ >
+ ),
+ // A parent and two children.
+ arquitectura: (
+ <>
+
+
+
+
+ >
+ ),
+ // An open book.
+ libro: (
+ <>
+
+
+
+ >
+ ),
+ // A megaphone.
+ megafono: (
+ <>
+
+
+ >
+ ),
+ // A month blocked out.
+ calendario: (
+ <>
+
+
+
+ >
+ ),
+ // Broadcast.
+ ondas: (
+ <>
+
+
+
+
+ >
+ ),
+ // A written page.
+ documento: (
+ <>
+
+
+
+ >
+ ),
+ // A gauge — the number the CEO watches.
+ medidor: (
+ <>
+
+
+
+ >
+ ),
+ // Bars inside a frame.
+ tablero: (
+ <>
+
+
+ >
+ ),
+ // Three round a table.
+ comite: (
+ <>
+
+
+
+
+
+ >
+ ),
+ // Milestones on the line, the last one reached.
+ hitos: (
+ <>
+
+
+
+
+ >
+ ),
+ // Launch.
+ cohete: (
+ <>
+
+
+
+ >
+ ),
+ // A lit idea.
+ bombilla: (
+ <>
+
+
+ >
+ ),
+ // A planner full of months.
+ anio: (
+ <>
+
+
+
+ >
+ ),
+ // A shield — what keeps the growth from drifting.
+ escudo: ,
};
+// Every drawn icon, straight from the registry above — so a test iterating
+// these can never fall behind the set that actually exists.
+export const NOMBRES_ICONO = Object.keys(PATHS) as IconoNombre[];
+
export function Icono({ nombre, className = '' }: { nombre: IconoNombre; className?: string }) {
return (