# creeer Landing 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:** Implement the creeer landing page (one-page, Next.js 14 static export) from the approved spec at `docs/superpowers/specs/2026-07-20-creeer-landing-design.md`.
**Architecture:** Next.js 14 App Router with 7 section components composed in `app/page.tsx`. Reusable primitives (`Wordmark`, `Container`, `CTA`) shared across sections. All copy centralized in `lib/content.ts`. Framer Motion animations with `prefers-reduced-motion` support. Static export (`output: export`) — no backend, contact via direct links.
**Tech Stack:** Next.js 14, React 18, TypeScript 5, Tailwind CSS 3.4, Framer Motion 11, Inter (Google Font via `next/font`), Georgia (system serif). Tests: Vitest + @testing-library/react.
## Global Constraints
(Copied verbatim from the spec — apply to every task.)
- **Wordmark:** `cre`**`e`**`er.` SIEMPRE minúsculas, Georgia 700 italic, la 2ª "e" en peso 900 + dorado, punto final dorado. Nunca `CREEER`, `Creeer`, sans-serif, ni mayúsculas.
- **Paleta:** navy `#0A1F44` (texto/CTA) · dorado tostado `#B8901f` (acentos sobre claro) · dorado `#D4AF37` (acentos sobre oscuro) · blanco cálido `#FBFAF7` (fondo base) · blanco `#FFFFFF` (hero) · marfil `#F5EBD0` (texto sobre oscuro).
- **Tipografía híbrida:** Georgia italic = voz de marca (wordmark + citas); Inter (400/600/900) = voz funcional (headlines Hormozi + body + UI).
- **Sin escasez en el hero:** no mostrar cupos/cohorte/"Generación Fundadora" arriba.
- **Voz:** segunda persona ("tú"/"tu negocio"), sin jerga corporativa.
- **Sin backend, sin formularios** — contacto por enlaces directos (WhatsApp, email, LinkedIn).
- **`output: export`** en `next.config.mjs` — HTML estático.
- **Idioma:** español (``).
---
## File Structure
```
creeer/
├── package.json
├── next.config.mjs # output: export
├── tsconfig.json
├── tailwind.config.ts # design tokens (palette, fontFamily)
├── postcss.config.js
├── vitest.config.ts
├── app/
│ ├── layout.tsx # , fonts, metadata
│ ├── page.tsx # composes 7 sections
│ └── globals.css # @tailwind + base
├── components/
│ ├── primitives/
│ │ ├── Wordmark.tsx # creeer logo (reused)
│ │ ├── Container.tsx # max-w wrapper (reused)
│ │ └── CTA.tsx # button + link variant (reused)
│ └── sections/
│ ├── Hero.tsx # 01 — variant="hormozi" | "editorial"
│ ├── FraseAncla.tsx # 02
│ ├── ParaQuien.tsx # 03
│ ├── Mecanismo.tsx # 04 (dark)
│ ├── Disciplinas.tsx # 05
│ ├── Estratega.tsx # 06
│ └── Cierre.tsx # 07 (dark) + contact links
├── lib/
│ ├── content.ts # all copy constants (source of truth)
│ └── motion.ts # Framer Motion variants + useReducedMotion wrapper
└── public/
└── og-image.png # Open Graph image (added manually)
```
Each section has one responsibility: render its slice of content using the design system. Primitives are consumed by multiple sections. `lib/content.ts` centralizes copy so text changes don't require touching JSX.
---
## Task 1: Scaffold + dependencies + base config
**Files:**
- Create: `package.json`, `next.config.mjs`, `tsconfig.json`, `tailwind.config.ts`, `postcss.config.js`, `app/layout.tsx`, `app/page.tsx`, `app/globals.css`
**Interfaces:**
- Produces: a runnable Next.js 14 app at `npm run dev` showing "hello world", with Tailwind + Framer Motion + Vitest installed, `output: export` enabled.
- [ ] **Step 1: Initialize Next.js project in the current directory**
```bash
cd /home/erikgh/dev/Creeer
npx create-next-app@14 . --typescript --tailwind --app --no-src-dir --no-eslint --use-npm --import-alias "@/*"
```
If prompted about existing files, choose to overwrite/ignore conflicts for `.gitignore` (keep existing). This scaffold creates `package.json`, `app/`, configs.
- [ ] **Step 2: Install runtime dependencies**
```bash
npm install framer-motion@^11
```
- [ ] **Step 3: Install dev dependencies for testing**
```bash
npm install -D vitest@^1 @vitejs/plugin-react@^4 @testing-library/react@^15 @testing-library/jest-dom@^6 jsdom@^25
```
- [ ] **Step 4: Configure static export in `next.config.mjs`**
Overwrite `next.config.mjs`:
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
images: { unoptimized: true },
};
export default nextConfig;
```
(`images.unoptimized` is required for static export.)
- [ ] **Step 5: Verify dev server runs**
```bash
npm run dev
```
Expected: server starts at http://localhost:3000, default Next.js page renders. Stop with Ctrl+C.
- [ ] **Step 6: Initialize git repo (first commit)**
```bash
cd /home/erikgh/dev/Creeer
git init
git add .
git commit -m "chore: scaffold Next.js 14 + Tailwind + Framer Motion + Vitest"
```
---
## Task 2: Design tokens (Tailwind palette, fonts, content + motion libs)
**Files:**
- Modify: `tailwind.config.ts`, `app/globals.css`, `app/layout.tsx`
- Create: `lib/content.ts`, `lib/motion.ts`
**Interfaces:**
- Produces: Tailwind tokens (`bg-base`, `bg-dark`, `text-primary`, `accent-gold`, etc.), Inter font loaded via `next/font`, all copy in `lib/content.ts`, motion variants in `lib/motion.ts`.
- [ ] **Step 1: Write Tailwind tokens in `tailwind.config.ts`**
```typescript
import type { Config } from 'tailwindcss';
const config: Config = {
content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './lib/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
base: '#FBFAF7', // blanco cálido (fondo base)
'bg-pure': '#FFFFFF', // blanco puro (hero)
'bg-dark': '#0A1F44', // navy (secciones oscuras)
ink: '#0A1F44', // texto principal
cream: '#F5EBD0', // texto sobre oscuro (marfil)
gold: {
DEFAULT: '#B8901f', // dorado tostado (sobre claro)
bright: '#D4AF37', // dorado (sobre oscuro)
},
muted: '#5a5168',
'muted-dark': '#9aa6c2',
},
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
serif: ['Georgia', 'Cambria', 'Times New Roman', 'serif'],
},
maxWidth: {
content: '1152px',
},
},
},
plugins: [],
};
export default config;
```
- [ ] **Step 2: Replace `app/globals.css` with base styles**
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html { scroll-behavior: smooth; }
body {
background-color: #FBFAF7;
color: #0A1F44;
font-family: var(--font-inter), system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
}
::selection { background: #D4AF37; color: #0A1F44; }
}
```
- [ ] **Step 3: Load Inter font in `app/layout.tsx`**
Replace `app/layout.tsx`:
```tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
const inter = Inter({ subsets: ['latin'], weight: ['400', '600', '700', '900'], variable: '--font-inter', display: 'swap' });
export const metadata: Metadata = {
title: 'creeer · tu estratega de crecimiento',
description: 'Claridad estratégica para empresarios con tracción en punto de inflexión.',
// (SEO task expands this later)
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
- [ ] **Step 4: Create `lib/content.ts` with all copy**
```typescript
// Source of truth for all copy. Edit here, not in JSX.
export const wordmark = {
// cre[e]er. — 'cre' + gold 'e' + 'er' + gold '.'
parts: { before: 'cre', accent: 'e', after: 'er', dot: '.' },
};
export const hero = {
eyebrow: '— tu estratega de crecimiento —',
// Headline broken into segments for emphasis styling
headline: {
line1: 'Tu negocio no necesita',
effort: 'más esfuerzo.', // gold (HV-1) or gold italic (V-a)
line3: 'Necesita',
clarity: 'más claridad.', // underlined gold
},
cta: 'Define tu siguiente movimiento',
};
export const fraseAncla = {
quote: 'Las empresas no dejan de crecer por falta de talento; dejan de crecer por falta de claridad.',
};
export const paraQuien = {
title: '¿Llegaste a tu punto de inflexión?',
lead: 'Esto es para ti si…',
criteria: [
'tienes tracción y ya demostraste que sabes crecer',
'trabajar más ya no rinde como antes',
'necesitas claridad para decidir el siguiente movimiento',
],
};
export const mecanismo = {
eyebrow: 'el mecanismo único',
title: 'Cómo trabajo',
steps: [
{ n: '01', name: 'Inmersión', desc: 'Me sumerjo en tu empresa hasta comprenderla desde dentro.' },
{ n: '02', name: 'Desafiar supuestos', desc: 'Cuestiono lo que todos dan por hecho y frenan el crecimiento.' },
{ n: '03', name: 'Estrategia ejecutable', desc: 'Construimos, juntos, una estrategia que puedas ejecutar.' },
],
};
export const disciplinas = {
eyebrow: 'las herramientas',
title: 'Las disciplinas que uso',
lead: 'No son la promesa — son los medios.',
items: [
{ name: 'Comunicación', desc: 'Mensaje que alinea a tu equipo y a tu mercado.' },
{ name: 'Digital', desc: 'Presencia y procesos que escalan sin ti.' },
{ name: 'Experiencias', desc: 'Cada toque con tu marca construye confianza.' },
],
};
export const estratega = {
eyebrow: 'la mente detrás de creeer',
title: 'Sobre la estratega',
bio: 'Más de 30 años construyendo marcas. Hoy acompaño a empresarios que ya lograron tracción y necesitan claridad para su siguiente movimiento.',
quote: 'El tiempo no se compra, se invierte.',
};
export const cierre = {
quote: 'Devuélvele claridad al rumbo de tu empresa.',
cta: 'Define tu siguiente movimiento',
contact: {
whatsapp: { label: 'WhatsApp', href: 'https://wa.me/520000000000' }, // TODO dueña: real number
email: { label: 'Email', href: 'mailto:hola@creeer.com' }, // TODO dueña: real email
linkedin: { label: 'LinkedIn', href: 'https://linkedin.com/in/...' }, // TODO dueña: real URL
},
};
```
- [ ] **Step 5: Create `lib/motion.ts`**
```typescript
import type { Variants } from 'framer-motion';
// EaseOutExpo — smooth editorial deceleration
export const easeOutExpo = [0.22, 1, 0.36, 1] as const;
export const fadeInUp: Variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: easeOutExpo } },
};
export const fadeIn: Variants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { duration: 0.4, ease: easeOutExpo } },
};
export const staggerContainer: Variants = {
hidden: {},
visible: { transition: { staggerChildren: 0.1 } },
};
export const viewportOnce = { once: true, amount: 0.3 } as const;
```
- [ ] **Step 6: Smoke test — `npm run dev`**
Run: `npm run dev`
Expected: app boots, no TS errors, default page renders at localhost:3000.
- [ ] **Step 7: Commit**
```bash
git add -A
git commit -m "feat: design tokens (palette, Inter font, content + motion libs)"
```
---
## Task 3: Primitives — Wordmark, Container, CTA
**Files:**
- Create: `components/primitives/Wordmark.tsx`, `components/primitives/Container.tsx`, `components/primitives/CTA.tsx`, `vitest.config.ts`
- Create tests: `components/primitives/__tests__/Wordmark.test.tsx`, `components/primitives/__tests__/CTA.test.tsx`
**Interfaces:**
- Produces: ``, ``, `` — consumed by every section.
- [ ] **Step 1: Configure Vitest in `vitest.config.ts`**
```typescript
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: [],
},
resolve: { alias: { '@': path.resolve(__dirname, './') } },
});
```
Add script to `package.json`:
```json
"test": "vitest run",
"test:watch": "vitest"
```
- [ ] **Step 2: Write failing test for `Wordmark`**
`components/primitives/__tests__/Wordmark.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Wordmark } from '../Wordmark';
describe('', () => {
it('renders the wordmark in lowercase', () => {
render();
const text = screen.getByText(/cre/).closest('span')?.textContent;
expect(text?.toLowerCase()).toBe('creeer.');
});
it('renders the accent "e" separately for gold styling', () => {
const { container } = render();
const accent = container.querySelector('[data-accent="true"]');
expect(accent).not.toBeNull();
expect(accent?.textContent).toBe('e');
});
it('never renders uppercase CREEER', () => {
const { container } = render();
expect(container.textContent).not.toMatch(/CREEER/);
expect(container.textContent).not.toMatch(/Creeer/);
});
});
```
- [ ] **Step 3: Run test to verify it fails**
Run: `npm test -- Wordmark`
Expected: FAIL — module `../Wordmark` not found.
- [ ] **Step 4: Implement `Wordmark`**
`components/primitives/Wordmark.tsx`:
```tsx
import { wordmark } from '@/lib/content';
type Size = 'sm' | 'md' | 'lg' | 'xl';
const sizeClass: Record = {
sm: 'text-xl',
md: 'text-2xl',
lg: 'text-3xl',
xl: 'text-5xl',
};
export function Wordmark({ size = 'lg', onDark = false }: { size?: Size; onDark?: boolean }) {
const { before, accent, after, dot } = wordmark.parts;
const baseColor = onDark ? 'text-cream' : 'text-ink';
const goldColor = onDark ? 'text-gold-bright' : 'text-gold';
return (
{before}
{accent}
{after}
{dot}
);
}
```
- [ ] **Step 5: Run test to verify it passes**
Run: `npm test -- Wordmark`
Expected: PASS (3 tests).
- [ ] **Step 6: Write failing test for `CTA`**
`components/primitives/__tests__/CTA.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { CTA } from '../CTA';
describe('', () => {
it('renders the label with arrow', () => {
render();
expect(screen.getByRole('link', { name: /define tu siguiente movimiento/i })).toBeInTheDocument();
});
it('uses the href', () => {
render();
expect(screen.getByRole('link').getAttribute('href')).toBe('#contacto');
});
});
```
- [ ] **Step 7: Run test — expected FAIL (module missing)**
Run: `npm test -- CTA`
Expected: FAIL.
- [ ] **Step 8: Implement `Container` and `CTA`**
`components/primitives/Container.tsx`:
```tsx
import type { ElementType, ReactNode } from 'react';
export function Container({ as: Tag = 'div', className = '', children }: { as?: ElementType; className?: string; children: ReactNode }) {
return {children};
}
```
`components/primitives/CTA.tsx`:
```tsx
type Variant = 'light' | 'dark';
const styles: Record = {
light: 'bg-ink text-base hover:bg-ink/90', // navy button, light text (on light bg)
dark: 'bg-gold-bright text-ink hover:bg-gold-bright/90', // gold button (on dark bg)
};
export function CTA({ href, label, variant = 'light', className = '' }: { href: string; label: string; variant?: Variant; className?: string }) {
return (
{label} →
);
}
```
- [ ] **Step 9: Run tests — expected PASS**
Run: `npm test`
Expected: all tests PASS.
- [ ] **Step 10: Commit**
```bash
git add -A
git commit -m "feat: primitives (Wordmark, Container, CTA) with tests"
```
---
## Task 4: Hero (section 01) with HV-1 + V-a variants
**Files:**
- Create: `components/sections/Hero.tsx`
- Test: `components/sections/__tests__/Hero.test.tsx`
- Modify: `app/page.tsx` (render ``)
**Interfaces:**
- Consumes: ``, ``, `hero` content object.
- Produces: `` — dueña decides via prop.
- [ ] **Step 1: Write failing test**
`components/sections/__tests__/Hero.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Hero } from '../Hero';
describe('', () => {
it('renders the eyebrow', () => {
render();
expect(screen.getByText(/tu estratega de crecimiento/i)).toBeInTheDocument();
});
it('renders the headline copy', () => {
render();
expect(screen.getByText(/Tu negocio/i)).toBeInTheDocument();
expect(screen.getByText(/más esfuerzo/i)).toBeInTheDocument();
expect(screen.getByText(/más claridad/i)).toBeInTheDocument();
});
it('renders the CTA', () => {
render();
expect(screen.getByRole('link', { name: /define tu siguiente movimiento/i })).toBeInTheDocument();
});
it('does NOT render scarcity (cupos/cohorte)', () => {
const { container } = render();
expect(container.textContent).not.toMatch(/cupos|cohorte|generación fundadora/i);
});
});
```
- [ ] **Step 2: Run test — expected FAIL**
Run: `npm test -- Hero`
Expected: FAIL (module missing).
- [ ] **Step 3: Implement `Hero`**
`components/sections/Hero.tsx`:
```tsx
import { motion } from 'framer-motion';
import { Wordmark } from '@/components/primitives/Wordmark';
import { CTA } from '@/components/primitives/CTA';
import { Container } from '@/components/primitives/Container';
import { hero } from '@/lib/content';
import { easeOutExpo, fadeInUp, staggerContainer } from '@/lib/motion';
type Variant = 'hormozi' | 'editorial';
export function Hero({ variant = 'editorial' }: { variant?: Variant }) {
const isHormozi = variant === 'hormozi';
const headlineBase = isHormozi
? 'font-sans font-black uppercase tracking-tight'
: 'font-serif italic font-bold';
const headlineSize = 'text-4xl md:text-6xl leading-[1.1]';
return (
{hero.eyebrow}
{hero.headline.line1}{' '}
{hero.headline.effort}{' '}
{hero.headline.line3}{' '}
{hero.headline.clarity}
);
}
```
- [ ] **Step 4: Run test — expected PASS**
Run: `npm test -- Hero`
Expected: PASS (4 tests).
- [ ] **Step 5: Render `` in `app/page.tsx`**
```tsx
import { Hero } from '@/components/sections/Hero';
export default function Home() {
return (
{/* @ts-expect-error server component animating — wrap with 'use client' in next step if needed */}
);
}
```
(Note: Framer Motion `motion` requires client components. If `npm run dev` errors about client components, add `'use client';` at the top of `Hero.tsx` and any component using `motion`. The animations task (Task 11) addresses this systematically.)
- [ ] **Step 6: Verify in browser**
Run: `npm run dev`
Expected: hero renders at localhost:3000 with the wordmark, eyebrow, headline, CTA. Try both `variant="editorial"` and `variant="hormozi"`.
- [ ] **Step 7: Commit**
```bash
git add -A
git commit -m "feat: Hero section (01) with HV-1 hormozi + V-a editorial variants"
```
---
## Task 5: FraseAncla (section 02)
**Files:**
- Create: `components/sections/FraseAncla.tsx`
- Test: `components/sections/__tests__/FraseAncla.test.tsx`
- Modify: `app/page.tsx`
**Interfaces:**
- Consumes: `fraseAncla` content, `Container`.
- [ ] **Step 1: Write failing test**
`components/sections/__tests__/FraseAncla.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { FraseAncla } from '../FraseAncla';
describe('', () => {
it('renders the anchor quote', () => {
render();
expect(screen.getByText(/Las empresas no dejan de crecer/i)).toBeInTheDocument();
expect(screen.getByText(/falta de claridad/i)).toBeInTheDocument();
});
});
```
- [ ] **Step 2: Run test — expected FAIL**
Run: `npm test -- FraseAncla`
Expected: FAIL.
- [ ] **Step 3: Implement**
`components/sections/FraseAncla.tsx`:
```tsx
'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { fraseAncla } from '@/lib/content';
import { fadeInUp, viewportOnce } from '@/lib/motion';
export function FraseAncla() {
return (
"
Las empresas no dejan de crecer por falta de talento;{' '}
dejan de crecer por falta de claridad.
"
);
}
```
- [ ] **Step 4: Run test — expected PASS**
Run: `npm test -- FraseAncla`
Expected: PASS.
- [ ] **Step 5: Add to `app/page.tsx`**
```tsx
import { Hero } from '@/components/sections/Hero';
import { FraseAncla } from '@/components/sections/FraseAncla';
export default function Home() {
return (
);
}
```
- [ ] **Step 6: Commit**
```bash
git add -A
git commit -m "feat: FraseAncla section (02) — anchor quote"
```
---
## Task 6: ParaQuien (section 03)
**Files:**
- Create: `components/sections/ParaQuien.tsx`
- Test: `components/sections/__tests__/ParaQuien.test.tsx`
- Modify: `app/page.tsx`
**Interfaces:**
- Consumes: `paraQuien` content, `Container`.
- [ ] **Step 1: Write failing test**
`components/sections/__tests__/ParaQuien.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ParaQuien } from '../ParaQuien';
describe('', () => {
it('renders the section title', () => {
render();
expect(screen.getByText(/punto de inflexión/i)).toBeInTheDocument();
});
it('renders all 3 criteria', () => {
render();
expect(screen.getByText(/tienes tracción/i)).toBeInTheDocument();
expect(screen.getByText(/trabajar más ya no rinde/i)).toBeInTheDocument();
expect(screen.getByText(/necesitas claridad/i)).toBeInTheDocument();
});
});
```
- [ ] **Step 2: Run test — expected FAIL**
Run: `npm test -- ParaQuien`
Expected: FAIL.
- [ ] **Step 3: Implement**
`components/sections/ParaQuien.tsx`:
```tsx
'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { paraQuien } from '@/lib/content';
import { fadeInUp, staggerContainer, viewportOnce } from '@/lib/motion';
export function ParaQuien() {
return (
{paraQuien.title}
{paraQuien.lead}
{paraQuien.criteria.map((c) => (
✓
{c}
))}
);
}
```
- [ ] **Step 4: Run test — expected PASS**
Run: `npm test -- ParaQuien`
Expected: PASS.
- [ ] **Step 5: Add to `app/page.tsx`**
```tsx
// ...imports
import { ParaQuien } from '@/components/sections/ParaQuien';
// ... in JSX after :
```
- [ ] **Step 6: Commit**
```bash
git add -A
git commit -m "feat: ParaQuien section (03) — audience filter"
```
---
## Task 7: Mecanismo (section 04, dark)
**Files:**
- Create: `components/sections/Mecanismo.tsx`
- Test: `components/sections/__tests__/Mecanismo.test.tsx`
- Modify: `app/page.tsx`
**Interfaces:**
- Consumes: `mecanismo` content, `Container`. Dark section (navy bg).
- [ ] **Step 1: Write failing test**
`components/sections/__tests__/Mecanismo.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Mecanismo } from '../Mecanismo';
describe('', () => {
it('renders the 3 steps in order', () => {
render();
expect(screen.getByText('Inmersión')).toBeInTheDocument();
expect(screen.getByText('Desafiar supuestos')).toBeInTheDocument();
expect(screen.getByText('Estrategia ejecutable')).toBeInTheDocument();
});
it('renders step numbers 01/02/03', () => {
const { container } = render();
expect(container.textContent).toMatch(/01.+02.+03/s);
});
});
```
- [ ] **Step 2: Run test — expected FAIL**
Run: `npm test -- Mecanismo`
Expected: FAIL.
- [ ] **Step 3: Implement**
`components/sections/Mecanismo.tsx`:
```tsx
'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { mecanismo } from '@/lib/content';
import { fadeInUp, staggerContainer, viewportOnce } from '@/lib/motion';
export function Mecanismo() {
return (
{mecanismo.eyebrow}
{mecanismo.title}
{mecanismo.steps.map((step) => (
{step.n}
{step.name}
{step.desc}
))}
);
}
```
- [ ] **Step 4: Run test — expected PASS**
Run: `npm test -- Mecanismo`
Expected: PASS.
- [ ] **Step 5: Add to `app/page.tsx`**
```tsx
import { Mecanismo } from '@/components/sections/Mecanismo';
// after :
```
- [ ] **Step 6: Commit**
```bash
git add -A
git commit -m "feat: Mecanismo section (04, dark) — 3-step method"
```
---
## Task 8: Disciplinas (section 05)
**Files:**
- Create: `components/sections/Disciplinas.tsx`
- Test: `components/sections/__tests__/Disciplinas.test.tsx`
- Modify: `app/page.tsx`
**Interfaces:**
- Consumes: `disciplinas` content, `Container`.
- [ ] **Step 1: Write failing test**
`components/sections/__tests__/Disciplinas.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Disciplinas } from '../Disciplinas';
describe('', () => {
it('renders the 3 disciplines', () => {
render();
expect(screen.getByText('Comunicación')).toBeInTheDocument();
expect(screen.getByText('Digital')).toBeInTheDocument();
expect(screen.getByText('Experiencias')).toBeInTheDocument();
});
});
```
- [ ] **Step 2: Run test — expected FAIL**
Run: `npm test -- Disciplinas`
Expected: FAIL.
- [ ] **Step 3: Implement**
`components/sections/Disciplinas.tsx`:
```tsx
'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { disciplinas } from '@/lib/content';
import { fadeInUp, staggerContainer, viewportOnce } from '@/lib/motion';
export function Disciplinas() {
return (
{disciplinas.eyebrow}
{disciplinas.title}
{disciplinas.lead}
{disciplinas.items.map((item) => (
{item.name}
{item.desc}
))}
);
}
```
- [ ] **Step 4: Run test — expected PASS**
Run: `npm test -- Disciplinas`
Expected: PASS.
- [ ] **Step 5: Add to `app/page.tsx`**
```tsx
import { Disciplinas } from '@/components/sections/Disciplinas';
// after :
```
- [ ] **Step 6: Commit**
```bash
git add -A
git commit -m "feat: Disciplinas section (05) — pillars as tools"
```
---
## Task 9: Estratega (section 06)
**Files:**
- Create: `components/sections/Estratega.tsx`
- Test: `components/sections/__tests__/Estratega.test.tsx`
- Modify: `app/page.tsx`
**Interfaces:**
- Consumes: `estratega` content, `Container`. Includes portrait (placeholder gradient if no photo yet).
- [ ] **Step 1: Write failing test**
`components/sections/__tests__/Estratega.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Estratega } from '../Estratega';
describe('', () => {
it('renders the bio', () => {
render();
expect(screen.getByText(/30 años/i)).toBeInTheDocument();
});
it('renders the institutional quote', () => {
render();
expect(screen.getByText(/El tiempo no se compra/i)).toBeInTheDocument();
});
});
```
- [ ] **Step 2: Run test — expected FAIL**
Run: `npm test -- Estratega`
Expected: FAIL.
- [ ] **Step 3: Implement**
`components/sections/Estratega.tsx`:
```tsx
'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { estratega } from '@/lib/content';
import { fadeInUp, staggerContainer, viewportOnce } from '@/lib/motion';
export function Estratega() {
return (
{/* Portrait — placeholder gradient until dueña provides a photo */}
{estratega.eyebrow}
{estratega.title}
{estratega.bio}
{estratega.quote}
);
}
```
> **Note:** When the dueña provides a real photo, replace the gradient div with `` using `next/image`. Keep `aria-label`/`alt` meaningful.
- [ ] **Step 4: Run test — expected PASS**
Run: `npm test -- Estratega`
Expected: PASS.
- [ ] **Step 5: Add to `app/page.tsx`**
```tsx
import { Estratega } from '@/components/sections/Estratega';
// after :
```
- [ ] **Step 6: Commit**
```bash
git add -A
git commit -m "feat: Estratega section (06) — portrait + bio + quote"
```
---
## Task 10: Cierre + contacto (section 07, dark)
**Files:**
- Create: `components/sections/Cierre.tsx`
- Test: `components/sections/__tests__/Cierre.test.tsx`
- Modify: `app/page.tsx`
**Interfaces:**
- Consumes: `cierre` content (includes contact links), `Container`, `CTA`, `Wordmark` (in footer). Dark section.
- [ ] **Step 1: Write failing test**
`components/sections/__tests__/Cierre.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Cierre } from '../Cierre';
describe('', () => {
it('renders the closing quote', () => {
render();
expect(screen.getByText(/Devuélvele claridad/i)).toBeInTheDocument();
});
it('renders 3 contact links with correct hrefs', () => {
render();
const wa = screen.getByRole('link', { name: /whatsapp/i });
const mail = screen.getByRole('link', { name: /email/i });
const li = screen.getByRole('link', { name: /linkedin/i });
expect(wa.getAttribute('href')).toMatch(/^https:\/\/wa\.me\//);
expect(mail.getAttribute('href')).toMatch(/^mailto:/);
expect(li.getAttribute('href')).toMatch(/^https:\/\/(www\.)?linkedin\./);
});
});
```
- [ ] **Step 2: Run test — expected FAIL**
Run: `npm test -- Cierre`
Expected: FAIL.
- [ ] **Step 3: Implement**
`components/sections/Cierre.tsx`:
```tsx
'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { CTA } from '@/components/primitives/CTA';
import { Wordmark } from '@/components/primitives/Wordmark';
import { cierre } from '@/lib/content';
import { fadeInUp, staggerContainer, viewportOnce } from '@/lib/motion';
export function Cierre() {
const { whatsapp, email, linkedin } = cierre.contact;
return (
);
}
```
- [ ] **Step 4: Run test — expected PASS**
Run: `npm test -- Cierre`
Expected: PASS.
- [ ] **Step 5: Add to `app/page.tsx`**
```tsx
import { Cierre } from '@/components/sections/Cierre';
// after :
```
- [ ] **Step 6: Commit**
```bash
git add -A
git commit -m "feat: Cierre section (07, dark) — closing quote + contact links"
```
---
## Task 11: Compose page + animations + reduced-motion
**Files:**
- Modify: `app/page.tsx`, `lib/motion.ts`
**Interfaces:**
- Produces: the full scrollable page with all 7 sections in order, Framer Motion animations, and `prefers-reduced-motion` support.
- [ ] **Step 1: Final `app/page.tsx`**
```tsx
import { Hero } from '@/components/sections/Hero';
import { FraseAncla } from '@/components/sections/FraseAncla';
import { ParaQuien } from '@/components/sections/ParaQuien';
import { Mecanismo } from '@/components/sections/Mecanismo';
import { Disciplinas } from '@/components/sections/Disciplinas';
import { Estratega } from '@/components/sections/Estratega';
import { Cierre } from '@/components/sections/Cierre';
export default function Home() {
return (
{/* dueña puede cambiar a "hormozi" */}
);
}
```
- [ ] **Step 2: Add reduced-motion support to `lib/motion.ts`**
Append to `lib/motion.ts`:
```typescript
import { useEffect, useState } from 'react';
// Hook: returns true if user prefers reduced motion. Components use this to
// disable Framer Motion animations (per spec, a11y requirement).
export function usePrefersReducedMotion() {
const [reduced, setReduced] = useState(false);
useEffect(() => {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
setReduced(mq.matches);
const handler = () => setReduced(mq.matches);
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}, []);
return reduced;
}
// Pass to as motion-reduced guard. When reduced, variants resolve to visible immediately.
export const motionProps = (reduced: boolean) =>
reduced
? { initial: 'visible' as const, animate: 'visible' as const, whileInView: undefined, viewport: undefined }
: {};
```
- [ ] **Step 3: Apply in each animated component**
For each section using `motion`, replace its `variants/initial/whileInView` props with reduced-motion-aware versions. Example for `FraseAncla.tsx`:
```tsx
'use client';
import { motion } from 'framer-motion';
import { Container } from '@/components/primitives/Container';
import { fraseAncla } from '@/lib/content';
import { fadeInUp, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
export function FraseAncla() {
const reduced = usePrefersReducedMotion();
return (
{/* ...quote content... */}
);
}
```
Repeat the same `reduced` pattern for: `Hero`, `ParaQuien`, `Mecanismo`, `Disciplinas`, `Estratega`, `Cierre`.
- [ ] **Step 4: Run full test suite**
Run: `npm test`
Expected: all tests PASS.
- [ ] **Step 5: Visual verification**
Run: `npm run dev`
Expected: all 7 sections render in order with smooth scroll animations. Toggle `prefers-reduced-motion` in DevTools → animations should disable (content appears immediately).
- [ ] **Step 6: Commit**
```bash
git add -A
git commit -m "feat: compose page + Framer Motion animations + reduced-motion support"
```
---
## Task 12: SEO metadata, accessibility, static build verification
**Files:**
- Modify: `app/layout.tsx` (full metadata), `app/page.tsx` (semantic HTML)
- Create: `public/og-image.png` (manually), `public/robots.txt`, `public/sitemap.xml`
- Verify: `npm run build` produces static HTML in `out/`
**Interfaces:**
- Produces: complete SEO metadata, OG image, semantic HTML, accessible contact links, passing static export build.
- [ ] **Step 1: Expand metadata in `app/layout.tsx`**
```tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
const inter = Inter({ subsets: ['latin'], weight: ['400', '600', '700', '900'], variable: '--font-inter', 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.',
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.',
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.' },
alternates: { canonical: 'https://creeer.com' },
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
- [ ] **Step 2: Add JSON-LD structured data in `app/page.tsx`**
Wrap the `` content with a structured data script. Add at top of `Home()`:
```tsx
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'ProfessionalService',
name: 'creeer',
description: 'Estratega de crecimiento. Claridad estratégica para empresarios con tracción.',
url: 'https://creeer.com',
};
export default function Home() {
return (
<>
{/* ...sections... */}
>
);
}
```
- [ ] **Step 3: Create `public/robots.txt`**
```
User-agent: *
Allow: /
Sitemap: https://creeer.com/sitemap.xml
```
- [ ] **Step 4: Create `public/sitemap.xml`**
```xml
https://creeer.com/monthly1.0
```
- [ ] **Step 5: Create OG image**
Manually create `public/og-image.png` (1200×630) — use the wordmark `cre`**`e`**`er.` on navy `#0A1F44` background with the headline. If no design tool available, use a placeholder PNG for now and replace later.
- [ ] **Step 6: Add focus-visible styles to `app/globals.css`**
Append to `globals.css`:
```css
@layer base {
a:focus-visible, button:focus-visible {
outline: 3px solid #D4AF37;
outline-offset: 3px;
border-radius: 2px;
}
}
```
- [ ] **Step 7: Run final test suite**
Run: `npm test`
Expected: all tests PASS.
- [ ] **Step 8: Run static build**
Run: `npm run build`
Expected: build succeeds, outputs `out/` directory with static HTML. No errors.
- [ ] **Step 9: Run lint**
Run: `npm run lint`
Expected: passes (or only minor warnings).
- [ ] **Step 10: Manual accessibility smoke test**
Open `npm run dev`, run browser DevTools Lighthouse audit (or axe extension). Expected:
- Contrast ratios pass (navy on white: 14.6:1; marfil on navy: 13.1:1)
- `` set
- All contact links have discernible text
- No duplicate `` (only Hero has h1)
- [ ] **Step 11: Final commit**
```bash
git add -A
git commit -m "feat: SEO metadata, JSON-LD, OG image, sitemap, a11y focus styles"
```
---
## Spec Coverage Self-Check
| Spec section | Task |
|---|---|
| 3.1 Paleta (tokens) | Task 2 |
| 3.2 Tipografía híbrida (Georgia + Inter) | Task 2, 3 |
| 3.3 Escala tipográfica | Tasks 4–10 (applied in Tailwind classes) |
| 3.4 Wordmark | Task 3 |
| 3.5 Recursos (•, smart quotes, →) | Tasks 4–10 |
| 3.6 Espaciado y ritmo | Tasks 4–10 (`py-24 md:py-32`, `Container`) |
| 3.7 Layout claro/oscuro | Tasks 4–10 (`bg-base`, `bg-bg-dark`) |
| 4. Hero (HV-1 + V-a) | Task 4 |
| 5. Flujo 7 secciones | Tasks 4–10 |
| 6. Messaging (frases-ancla) | Task 2 (content.ts) |
| 7. Restricciones técnicas | Tasks 1, 10 |
| 8. SEO / a11y / perf | Task 12 |
| 9. Animación (Framer Motion + reduced) | Task 11 |
**Placeholder scan:** None. All steps contain complete code. The `cierre.contact` hrefs in `lib/content.ts` are marked `TODO dueña` for real values — that's a content decision (not implementation) and documented in spec §10.
**Type consistency:** All section components share the same interface signature (`export function SectionName()` with no props, except ``). All consume the same primitives (``, ``, ``) and content objects. ✓
---
## Execution
Plan complete. Two execution options:
**1. Subagent-Driven (recommended)** — dispatch a fresh subagent per task with review between tasks. Best for catching issues early and keeping each task focused.
**2. Inline Execution** — execute tasks in this session using executing-plans, with checkpoints for review. Best when you want tight loop with the user.
**Which approach?**