# 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 (
    "{cierre.quote}"
  • {whatsapp.label}
  • {email.label}
  • {linkedin.label}
  • ); } ``` - [ ] **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 ( <>