fix: a11y (gold-deep token AA, Disciplinas ul/li, Cierre nav) + scaffold cleanup
- Add gold-deep #7A5F15 token (~5.97:1 on white, WCAG AA) for small-text eyebrows - Apply text-gold-deep to 4 eyebrows (Hero, Disciplinas, ComoEmpezar, Estratega) - Disciplinas: div grid → semantic ul/li for screen-reader consistency - Cierre: wrap contact links in <nav aria-label="Contacto"> - Estratega: portrait gradient → Tailwind tokens (from-ink to-gold), drop inline hex - Delete unused Geist fonts (app/fonts/, ~134KB) - Replace default README with creeer README - Tests: +2 structural (Disciplinas ul/li, Cierre nav aria-label)
This commit is contained in:
1 parent
3f0a15eac9
commit
b3bd698e9b
11 files changed
+59
-46
No files matched your search
@@ -1,36 +1,36 @@
|
|||||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
# creeer
|
||||||
|
|
||||||
## Getting Started
|
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.
|
||||||
|
|
||||||
First, run the development server:
|
## Requisitos
|
||||||
|
|
||||||
|
- Node.js 20+
|
||||||
|
- pnpm 11+ (`corepack enable`)
|
||||||
|
|
||||||
|
## Comandos
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
pnpm install # instalar dependencias
|
||||||
# or
|
pnpm dev # servidor de desarrollo (http://localhost:3000)
|
||||||
yarn dev
|
pnpm build # build estático → out/
|
||||||
# or
|
pnpm test # tests (Vitest)
|
||||||
pnpm dev
|
pnpm lint # ESLint
|
||||||
# or
|
|
||||||
bun dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
## Estructura
|
||||||
|
|
||||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
```
|
||||||
|
app/ # App Router (layout, page, globals.css)
|
||||||
|
components/
|
||||||
|
primitives/ # Wordmark, Container, CTA (reutilizables)
|
||||||
|
sections/ # Hero, FraseAncla, ParaQuien, Mecanismo, Disciplinas, Estratega, Cierre, ComoEmpezar
|
||||||
|
layout/ # Header, Footer
|
||||||
|
lib/
|
||||||
|
content.ts # todo el copy (source of truth)
|
||||||
|
motion.ts # variantes Framer Motion + reduced-motion
|
||||||
|
docs/ # spec, plan, directrices de marca
|
||||||
|
```
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
## Decisiones de diseño
|
||||||
|
|
||||||
## Learn More
|
Ver `docs/dueña-directrices.md` (directrices de marca) y `docs/superpowers/specs/2026-07-20-creeer-landing-design.md` (spec completo).
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -22,13 +22,15 @@ export function Cierre() {
|
|||||||
<motion.div variants={fadeInUp} className="mt-10">
|
<motion.div variants={fadeInUp} className="mt-10">
|
||||||
<CTA href={whatsapp.href} label={cierre.cta} variant="dark" />
|
<CTA href={whatsapp.href} label={cierre.cta} variant="dark" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.ul variants={fadeInUp} className="mt-12 flex flex-wrap justify-center gap-6 text-sm">
|
<motion.nav variants={fadeInUp} aria-label="Contacto" className="mt-12">
|
||||||
<li><a href={whatsapp.href} className="hover:text-gold-bright transition-colors">{whatsapp.label}</a></li>
|
<ul className="flex flex-wrap justify-center gap-6 text-sm">
|
||||||
<li aria-hidden="true" className="text-muted-dark">·</li>
|
<li><a href={whatsapp.href} className="hover:text-gold-bright transition-colors">{whatsapp.label}</a></li>
|
||||||
<li><a href={email.href} className="hover:text-gold-bright transition-colors">{email.label}</a></li>
|
<li aria-hidden="true" className="text-muted-dark">·</li>
|
||||||
<li aria-hidden="true" className="text-muted-dark">·</li>
|
<li><a href={email.href} className="hover:text-gold-bright transition-colors">{email.label}</a></li>
|
||||||
<li><a href={linkedin.href} className="hover:text-gold-bright transition-colors">{linkedin.label}</a></li>
|
<li aria-hidden="true" className="text-muted-dark">·</li>
|
||||||
</motion.ul>
|
<li><a href={linkedin.href} className="hover:text-gold-bright transition-colors">{linkedin.label}</a></li>
|
||||||
|
</ul>
|
||||||
|
</motion.nav>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Container>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function ComoEmpezar() {
|
|||||||
<section className="bg-base">
|
<section className="bg-base">
|
||||||
<Container className="py-24 md:py-32">
|
<Container className="py-24 md:py-32">
|
||||||
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="text-xs tracking-[0.3em] uppercase font-semibold text-gold">{comoEmpezar.eyebrow}</motion.p>
|
className="text-xs tracking-[0.3em] uppercase font-semibold text-gold-deep">{comoEmpezar.eyebrow}</motion.p>
|
||||||
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="mt-4 font-serif italic font-bold text-3xl md:text-5xl text-ink">{comoEmpezar.title}</motion.h2>
|
className="mt-4 font-serif italic font-bold text-3xl md:text-5xl text-ink">{comoEmpezar.title}</motion.h2>
|
||||||
<motion.ol variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.ol variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
|||||||
@@ -10,20 +10,20 @@ export function Disciplinas() {
|
|||||||
<section className="bg-base">
|
<section className="bg-base">
|
||||||
<Container className="py-24 md:py-32">
|
<Container className="py-24 md:py-32">
|
||||||
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="text-xs tracking-[0.3em] uppercase font-semibold text-gold">{disciplinas.eyebrow}</motion.p>
|
className="text-xs tracking-[0.3em] uppercase font-semibold text-gold-deep">{disciplinas.eyebrow}</motion.p>
|
||||||
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="mt-4 font-serif italic font-bold text-3xl md:text-5xl text-ink">{disciplinas.title}</motion.h2>
|
className="mt-4 font-serif italic font-bold text-3xl md:text-5xl text-ink">{disciplinas.title}</motion.h2>
|
||||||
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="mt-4 text-muted text-lg">{disciplinas.lead}</motion.p>
|
className="mt-4 text-muted text-lg">{disciplinas.lead}</motion.p>
|
||||||
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.ul variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="mt-12 grid gap-8 md:grid-cols-3">
|
className="mt-12 grid gap-8 md:grid-cols-3 list-none p-0">
|
||||||
{disciplinas.items.map((item) => (
|
{disciplinas.items.map((item) => (
|
||||||
<motion.div key={item.name} variants={fadeInUp} className="border-t-2 border-gold pt-5">
|
<motion.li key={item.name} variants={fadeInUp} className="border-t-2 border-gold pt-5">
|
||||||
<h3 className="font-serif italic font-bold text-2xl text-ink">{item.name}</h3>
|
<h3 className="font-serif italic font-bold text-2xl text-ink">{item.name}</h3>
|
||||||
<p className="mt-3 text-muted leading-relaxed">{item.desc}</p>
|
<p className="mt-3 text-muted leading-relaxed">{item.desc}</p>
|
||||||
</motion.div>
|
</motion.li>
|
||||||
))}
|
))}
|
||||||
</motion.div>
|
</motion.ul>
|
||||||
</Container>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ export function Estratega() {
|
|||||||
className="grid gap-12 md:grid-cols-[auto_1fr] md:items-center">
|
className="grid gap-12 md:grid-cols-[auto_1fr] md:items-center">
|
||||||
{/* Portrait — placeholder gradient until dueña provides a photo */}
|
{/* Portrait — placeholder gradient until dueña provides a photo */}
|
||||||
<motion.div variants={fadeInUp}
|
<motion.div variants={fadeInUp}
|
||||||
className="w-40 h-40 md:w-56 md:h-56 rounded-full flex-shrink-0"
|
className="w-40 h-40 md:w-56 md:h-56 rounded-full flex-shrink-0 bg-gradient-to-br from-ink to-gold"
|
||||||
style={{ background: 'linear-gradient(135deg, #0A1F44 0%, #B8901f 100%)' }}
|
|
||||||
aria-label="Retrato de la estratega"
|
aria-label="Retrato de la estratega"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<motion.p variants={fadeInUp} className="text-xs tracking-[0.3em] uppercase font-semibold text-gold">{estratega.eyebrow}</motion.p>
|
<motion.p variants={fadeInUp} className="text-xs tracking-[0.3em] uppercase font-semibold text-gold-deep">{estratega.eyebrow}</motion.p>
|
||||||
<motion.h2 variants={fadeInUp} className="mt-4 font-serif italic font-bold text-3xl md:text-5xl text-ink">{estratega.title}</motion.h2>
|
<motion.h2 variants={fadeInUp} className="mt-4 font-serif italic font-bold text-3xl md:text-5xl text-ink">{estratega.title}</motion.h2>
|
||||||
<motion.p variants={fadeInUp} className="mt-6 text-lg text-muted leading-relaxed max-w-2xl">{estratega.bio}</motion.p>
|
<motion.p variants={fadeInUp} className="mt-6 text-lg text-muted leading-relaxed max-w-2xl">{estratega.bio}</motion.p>
|
||||||
<motion.blockquote variants={fadeInUp} className="mt-6 font-serif italic text-xl md:text-2xl text-ink border-l-4 border-gold pl-4">
|
<motion.blockquote variants={fadeInUp} className="mt-6 font-serif italic text-xl md:text-2xl text-ink border-l-4 border-gold pl-4">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function Hero({ variant = 'editorial' }: { variant?: Variant }) {
|
|||||||
<Container className="min-h-[88vh] flex flex-col items-center justify-center text-center py-24">
|
<Container className="min-h-[88vh] flex flex-col items-center justify-center text-center py-24">
|
||||||
<motion.div variants={staggerContainer} initial="hidden" animate="visible" {...motionProps(reduced)} className="flex flex-col items-center">
|
<motion.div variants={staggerContainer} initial="hidden" animate="visible" {...motionProps(reduced)} className="flex flex-col items-center">
|
||||||
<motion.div variants={fadeInUp}><Wordmark size="xl" /></motion.div>
|
<motion.div variants={fadeInUp}><Wordmark size="xl" /></motion.div>
|
||||||
<motion.p variants={fadeInUp} className="mt-6 text-xs md:text-sm tracking-[0.3em] uppercase font-semibold text-gold">
|
<motion.p variants={fadeInUp} className="mt-6 text-xs md:text-sm tracking-[0.3em] uppercase font-semibold text-gold-deep">
|
||||||
{hero.eyebrow}
|
{hero.eyebrow}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
<motion.h1 variants={fadeInUp} className={`mt-8 ${headlineBase} ${headlineSize} text-ink`}>
|
<motion.h1 variants={fadeInUp} className={`mt-8 ${headlineBase} ${headlineSize} text-ink`}>
|
||||||
|
|||||||
@@ -16,4 +16,8 @@ describe('<Cierre />', () => {
|
|||||||
expect(mail.getAttribute('href')).toMatch(/^mailto:/);
|
expect(mail.getAttribute('href')).toMatch(/^mailto:/);
|
||||||
expect(li.getAttribute('href')).toMatch(/^https:\/\/(www\.)?linkedin\./);
|
expect(li.getAttribute('href')).toMatch(/^https:\/\/(www\.)?linkedin\./);
|
||||||
});
|
});
|
||||||
|
it('wraps contact links in a nav with aria-label="Contacto"', () => {
|
||||||
|
render(<Cierre />);
|
||||||
|
expect(screen.getByRole('navigation', { name: /contacto/i })).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -9,4 +9,11 @@ describe('<Disciplinas />', () => {
|
|||||||
expect(screen.getByText('Digital')).toBeInTheDocument();
|
expect(screen.getByText('Digital')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Experiencias')).toBeInTheDocument();
|
expect(screen.getByText('Experiencias')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
it('renders disciplines in a semantic list (ul/li) for a11y', () => {
|
||||||
|
const { container } = render(<Disciplinas />);
|
||||||
|
const list = container.querySelector('ul');
|
||||||
|
expect(list).not.toBeNull();
|
||||||
|
const items = list?.querySelectorAll('li');
|
||||||
|
expect(items?.length).toBe(3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
+2
-1
@@ -11,7 +11,8 @@ const config: Config = {
|
|||||||
ink: '#0A1F44', // texto principal
|
ink: '#0A1F44', // texto principal
|
||||||
cream: '#F5EBD0', // texto sobre oscuro (marfil)
|
cream: '#F5EBD0', // texto sobre oscuro (marfil)
|
||||||
gold: {
|
gold: {
|
||||||
DEFAULT: '#B8901f', // dorado tostado (sobre claro)
|
DEFAULT: '#B8901f', // dorado tostado (sobre claro) — usar solo en texto grande/decorativo
|
||||||
|
deep: '#7A5F15', // dorado profundo (AA 4.5:1 sobre claro para texto pequeño)
|
||||||
bright: '#D4AF37', // dorado (sobre oscuro)
|
bright: '#D4AF37', // dorado (sobre oscuro)
|
||||||
},
|
},
|
||||||
muted: '#5a5168',
|
muted: '#5a5168',
|
||||||
|
|||||||
Reference in new issue
Block a user