feat(secciones): puntos de inflexión y dolores, con gráfico en toda la página
Dos secciones nuevas tomadas del deck de la dueña, verbatim y sin reescribir: los 6 puntos de inflexión y los 4 dolores en primera persona. El resto de las secciones incorpora la capa gráfica: fondos de puntos, duotono sobre foto en Mecanismo y Cómo empezar, pantalla partida sí/no en Para quién, y pasos conectados con línea de progreso. Las fotos son CC BY-SA 4.0 y llevan crédito en el footer, como exige la licencia al haberlas modificado. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5b400cb161
commit
69b4069525
25 files changed
+592
-120
No files matched your search
+5
-1
@@ -2,6 +2,8 @@ import { Header } from '@/components/layout/Header';
|
|||||||
import { Footer } from '@/components/layout/Footer';
|
import { Footer } from '@/components/layout/Footer';
|
||||||
import { Hero } from '@/components/sections/Hero';
|
import { Hero } from '@/components/sections/Hero';
|
||||||
import { FraseAncla } from '@/components/sections/FraseAncla';
|
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 { ParaQuien } from '@/components/sections/ParaQuien';
|
||||||
import { Mecanismo } from '@/components/sections/Mecanismo';
|
import { Mecanismo } from '@/components/sections/Mecanismo';
|
||||||
import { Disciplinas } from '@/components/sections/Disciplinas';
|
import { Disciplinas } from '@/components/sections/Disciplinas';
|
||||||
@@ -25,9 +27,11 @@ export default function Home() {
|
|||||||
<main>
|
<main>
|
||||||
<Hero variant="editorial" /> {/* dueña puede cambiar a "hormozi" */}
|
<Hero variant="editorial" /> {/* dueña puede cambiar a "hormozi" */}
|
||||||
<FraseAncla />
|
<FraseAncla />
|
||||||
<ParaQuien />
|
<PuntosInflexion />
|
||||||
|
<Dolores />
|
||||||
<Mecanismo />
|
<Mecanismo />
|
||||||
<Disciplinas />
|
<Disciplinas />
|
||||||
|
<ParaQuien />
|
||||||
<Estratega />
|
<Estratega />
|
||||||
<ComoEmpezar />
|
<ComoEmpezar />
|
||||||
<Cierre />
|
<Cierre />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Wordmark } from '@/components/primitives/Wordmark';
|
import { Wordmark } from '@/components/primitives/Wordmark';
|
||||||
import { Container } from '@/components/primitives/Container';
|
import { Container } from '@/components/primitives/Container';
|
||||||
import { footer } from '@/lib/content';
|
import { footer, creditosFotos } from '@/lib/content';
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
@@ -10,6 +10,17 @@ export function Footer() {
|
|||||||
<p className="text-sm text-gris-medio">{footer.tagline}</p>
|
<p className="text-sm text-gris-medio">{footer.tagline}</p>
|
||||||
<p className="text-xs text-gris-medio">{footer.legal}</p>
|
<p className="text-xs text-gris-medio">{footer.legal}</p>
|
||||||
</Container>
|
</Container>
|
||||||
|
<Container className="pb-6 text-center md:text-left">
|
||||||
|
<p className="text-[10px] leading-relaxed text-gris-medio/60">
|
||||||
|
{creditosFotos.map((c, i) => (
|
||||||
|
<span key={c.href}>
|
||||||
|
{i > 0 && ' · '}
|
||||||
|
<a href={c.href} className="hover:text-gris-medio">{c.desc}</a>, modificada,{' '}
|
||||||
|
<a href={c.licenciaHref} className="hover:text-gris-medio">{c.licencia}</a>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
|
</Container>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -12,4 +12,11 @@ describe('<Footer />', () => {
|
|||||||
render(<Footer />);
|
render(<Footer />);
|
||||||
expect(screen.queryByRole('heading', { level: 1 })).toBeNull();
|
expect(screen.queryByRole('heading', { level: 1 })).toBeNull();
|
||||||
});
|
});
|
||||||
|
it('credits the stock photos per their CC BY-SA 4.0 licence', () => {
|
||||||
|
render(<Footer />);
|
||||||
|
expect(screen.getAllByText(/CC BY-SA 4\.0/)).toHaveLength(2);
|
||||||
|
expect(screen.getByText(/Golden Gate Bridge/)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/Monte Nardi/)).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText(/modificada/).length).toBe(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -3,9 +3,11 @@ import { describe, it, expect } from 'vitest';
|
|||||||
import { Header } from '../Header';
|
import { Header } from '../Header';
|
||||||
|
|
||||||
describe('<Header />', () => {
|
describe('<Header />', () => {
|
||||||
it('renders the wordmark', () => {
|
it('renders the wordmark, linked to the top of the page', () => {
|
||||||
const { container } = render(<Header />);
|
render(<Header />);
|
||||||
expect(container.textContent).toContain('cre≡er');
|
const home = screen.getByRole('link', { name: /creeer/i });
|
||||||
|
expect(home).toHaveAttribute('href', '#top');
|
||||||
|
expect(home.querySelector('[data-accent="true"]')).not.toBeNull();
|
||||||
});
|
});
|
||||||
it('renders a persistent CTA linking to the contact section', () => {
|
it('renders a persistent CTA linking to the contact section', () => {
|
||||||
render(<Header />);
|
render(<Header />);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { motion } from 'framer-motion';
|
|||||||
import { Container } from '@/components/primitives/Container';
|
import { Container } from '@/components/primitives/Container';
|
||||||
import { CTA } from '@/components/primitives/CTA';
|
import { CTA } from '@/components/primitives/CTA';
|
||||||
import { Wordmark } from '@/components/primitives/Wordmark';
|
import { Wordmark } from '@/components/primitives/Wordmark';
|
||||||
|
import { FondoPuntos } from '@/components/primitives/FondoPuntos';
|
||||||
import { cierre } from '@/lib/content';
|
import { cierre } from '@/lib/content';
|
||||||
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
||||||
|
|
||||||
@@ -10,25 +11,24 @@ export function Cierre() {
|
|||||||
const reduced = usePrefersReducedMotion();
|
const reduced = usePrefersReducedMotion();
|
||||||
const { whatsapp, email, linkedin } = cierre.contact;
|
const { whatsapp, email, linkedin } = cierre.contact;
|
||||||
return (
|
return (
|
||||||
<section id="contacto" className="bg-azul-profundo text-white scroll-mt-24">
|
<section id="contacto" className="bg-azul-profundo text-white scroll-mt-24 relative overflow-hidden">
|
||||||
<Container className="py-24 md:py-32 text-center">
|
<FondoPuntos className="top-12 -left-16 w-72 md:w-[30rem] text-dorado/25" distance={36} />
|
||||||
|
<Container className="relative py-24 md:py-32 text-center">
|
||||||
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="flex flex-col items-center">
|
className="flex flex-col items-center">
|
||||||
<motion.div variants={fadeInUp}><Wordmark size="lg" onDark /></motion.div>
|
<motion.div variants={fadeInUp}><Wordmark size="lg" onDark showTagline /></motion.div>
|
||||||
<motion.blockquote variants={fadeInUp}
|
<motion.p variants={fadeInUp}
|
||||||
className="mt-10 font-sans italic text-2xl md:text-4xl font-bold leading-snug max-w-3xl">
|
className="mt-12 font-sans font-black text-3xl md:text-5xl leading-[1.05] tracking-tight max-w-3xl">
|
||||||
<span className="text-dorado">“</span>{cierre.quote}<span className="text-dorado">”</span>
|
{cierre.quote}
|
||||||
</motion.blockquote>
|
</motion.p>
|
||||||
<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.nav variants={fadeInUp} aria-label="Contacto" className="mt-12">
|
<motion.nav variants={fadeInUp} aria-label="Contacto" className="mt-14">
|
||||||
<ul className="flex flex-wrap justify-center gap-6 text-sm">
|
<ul className="flex flex-wrap justify-center gap-3 text-sm">
|
||||||
<li><a href={whatsapp.href} className="hover:text-dorado transition-colors">{whatsapp.label}</a></li>
|
<li><a href={whatsapp.href} className="inline-block px-5 py-2.5 rounded-full border border-white/20 hover:border-dorado hover:text-dorado transition-colors">{whatsapp.label}</a></li>
|
||||||
<li aria-hidden="true" className="text-gris-medio">·</li>
|
<li><a href={email.href} className="inline-block px-5 py-2.5 rounded-full border border-white/20 hover:border-dorado hover:text-dorado transition-colors">{email.label}</a></li>
|
||||||
<li><a href={email.href} className="hover:text-dorado transition-colors">{email.label}</a></li>
|
<li><a href={linkedin.href} className="inline-block px-5 py-2.5 rounded-full border border-white/20 hover:border-dorado hover:text-dorado transition-colors">{linkedin.label}</a></li>
|
||||||
<li aria-hidden="true" className="text-gris-medio">·</li>
|
|
||||||
<li><a href={linkedin.href} className="hover:text-dorado transition-colors">{linkedin.label}</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</motion.nav>
|
</motion.nav>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -1,35 +1,62 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import Image from 'next/image';
|
||||||
import { Container } from '@/components/primitives/Container';
|
import { Container } from '@/components/primitives/Container';
|
||||||
import { CTA } from '@/components/primitives/CTA';
|
import { CTA } from '@/components/primitives/CTA';
|
||||||
|
import { Icono, type IconoNombre } from '@/components/primitives/Icono';
|
||||||
import { comoEmpezar, cierre } from '@/lib/content';
|
import { comoEmpezar, cierre } from '@/lib/content';
|
||||||
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps, useAutoStep } from '@/lib/motion';
|
||||||
|
|
||||||
|
const ICONOS: IconoNombre[] = ['escribeme', 'diagnostico', 'plan'];
|
||||||
|
|
||||||
export function ComoEmpezar() {
|
export function ComoEmpezar() {
|
||||||
const reduced = usePrefersReducedMotion();
|
const reduced = usePrefersReducedMotion();
|
||||||
const waHref = `${cierre.contact.whatsapp.href}?text=${encodeURIComponent(comoEmpezar.whatsappText)}`;
|
const waHref = `${cierre.contact.whatsapp.href}?text=${encodeURIComponent(comoEmpezar.whatsappText)}`;
|
||||||
|
const { active, setActive, hoverProps } = useAutoStep(comoEmpezar.steps.length, 3200);
|
||||||
return (
|
return (
|
||||||
<section className="bg-white">
|
<section className="bg-gris-claro">
|
||||||
<Container className="py-24 md:py-32">
|
<Container className="py-24 md:py-32 grid gap-12 md:grid-cols-[1fr_auto] md:items-center">
|
||||||
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}>
|
||||||
className="text-xs tracking-[0.3em] uppercase font-semibold text-dorado">{comoEmpezar.eyebrow}</motion.p>
|
<motion.p variants={fadeInUp} className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{comoEmpezar.eyebrow}</motion.p>
|
||||||
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.h2 variants={fadeInUp} className="mt-4 font-sans font-black text-4xl md:text-6xl leading-[0.95] tracking-tight text-azul-profundo max-w-xl">{comoEmpezar.title}</motion.h2>
|
||||||
className="mt-4 font-sans font-bold text-3xl md:text-5xl text-azul-profundo">{comoEmpezar.title}</motion.h2>
|
<motion.ol variants={staggerContainer} {...hoverProps} className="mt-12 grid gap-10 sm:grid-cols-3 md:grid-cols-1 md:gap-8">
|
||||||
<motion.ol variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
{comoEmpezar.steps.map((step, i) => (
|
||||||
className="mt-12 grid gap-10 md:grid-cols-3">
|
<motion.li key={step.n} variants={fadeInUp}>
|
||||||
{comoEmpezar.steps.map((step) => (
|
<button
|
||||||
<motion.li key={step.n} variants={fadeInUp}
|
type="button"
|
||||||
className="border-t border-dorado/30 pt-6 transition-all duration-300 ease-out hover:-translate-y-1.5 hover:border-dorado/70 motion-reduce:transform-none motion-reduce:transition-none">
|
onClick={() => setActive(i)}
|
||||||
<div className="font-sans font-black text-5xl md:text-6xl text-dorado leading-none">{step.n}</div>
|
aria-pressed={active === i}
|
||||||
<h3 className="mt-4 font-sans font-bold text-xl text-azul-profundo">{step.name}</h3>
|
className={`w-full text-left border-t pt-6 transition-all duration-300 ease-out hover:-translate-y-1.5 motion-reduce:transform-none motion-reduce:transition-none
|
||||||
<p className="mt-3 text-gris-medio leading-relaxed">{step.desc}</p>
|
md:flex md:items-baseline md:gap-4 md:border-t-0 md:border-l-2 md:pt-0 md:pl-5 ${
|
||||||
</motion.li>
|
active === i ? 'border-dorado' : 'border-dorado/30 md:opacity-40'
|
||||||
))}
|
}`}
|
||||||
</motion.ol>
|
>
|
||||||
|
<div className="font-sans font-black text-5xl md:text-3xl text-dorado leading-none shrink-0">{step.n}</div>
|
||||||
|
<div>
|
||||||
|
<div className="mt-4 md:mt-0 flex items-center gap-3">
|
||||||
|
<Icono nombre={ICONOS[i]} className="w-6 h-6 text-dorado shrink-0" />
|
||||||
|
<h3 className="font-sans font-bold text-xl text-azul-profundo">{step.name}</h3>
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 md:mt-1 text-gris-medio leading-relaxed">{step.desc}</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</motion.li>
|
||||||
|
))}
|
||||||
|
</motion.ol>
|
||||||
|
<motion.div variants={fadeInUp} className="mt-12">
|
||||||
|
<CTA href={waHref} label={comoEmpezar.cta} variant="light" />
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
{/* Foto: amanecer sobre la cumbre del Monte Nardi, Tony 1212, CC BY-SA 4.0,
|
||||||
|
modificada (duotono) — créditos en el footer. "Tu siguiente movimiento
|
||||||
|
empieza…": el amanecer como metáfora del comienzo. */}
|
||||||
<motion.div variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.div variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
className="mt-12">
|
className="hidden md:block relative w-72 lg:w-96 aspect-[4/5] rounded-lg overflow-hidden shadow-2xl shadow-azul-profundo/20 rotate-2 motion-reduce:rotate-0">
|
||||||
<CTA href={waHref} label={comoEmpezar.cta} variant="light" />
|
<div className="absolute inset-0 bg-gradient-to-br from-azul-profundo to-dorado" />
|
||||||
|
<Image src="/images/comoempezar-bg.jpg" alt="" fill sizes="(min-width: 1024px) 384px, 288px"
|
||||||
|
className="object-cover mix-blend-luminosity" />
|
||||||
|
<div className="absolute inset-0 bg-azul-profundo/20" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Container>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,27 +1,36 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Container } from '@/components/primitives/Container';
|
import { Container } from '@/components/primitives/Container';
|
||||||
|
import { TarjetaSpotlight } from '@/components/primitives/TarjetaSpotlight';
|
||||||
|
import { Icono, type IconoNombre } from '@/components/primitives/Icono';
|
||||||
import { disciplinas } from '@/lib/content';
|
import { disciplinas } from '@/lib/content';
|
||||||
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
||||||
|
|
||||||
|
const ICONOS: IconoNombre[] = ['comunicacion', 'digital', 'experiencias'];
|
||||||
|
|
||||||
export function Disciplinas() {
|
export function Disciplinas() {
|
||||||
const reduced = usePrefersReducedMotion();
|
const reduced = usePrefersReducedMotion();
|
||||||
return (
|
return (
|
||||||
<section className="bg-white">
|
<section className="bg-white">
|
||||||
<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-dorado">{disciplinas.eyebrow}</motion.p>
|
className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{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-sans font-bold text-3xl md:text-5xl text-azul-profundo">{disciplinas.title}</motion.h2>
|
className="mt-4 font-sans font-black text-4xl md:text-6xl text-azul-profundo tracking-tight">{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-gris-medio text-lg">{disciplinas.lead}</motion.p>
|
className="mt-4 text-gris-medio text-lg">{disciplinas.lead}</motion.p>
|
||||||
<motion.ul 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 list-none p-0">
|
className="mt-12 grid gap-6 md:grid-cols-3 list-none p-0">
|
||||||
{disciplinas.items.map((item) => (
|
{disciplinas.items.map((item, i) => (
|
||||||
<motion.li key={item.name} variants={fadeInUp}
|
<motion.li key={item.name} variants={fadeInUp}
|
||||||
className="border-t-2 border-dorado pt-5 transition-all duration-300 ease-out hover:-translate-y-1.5 hover:border-dorado motion-reduce:transform-none motion-reduce:transition-none">
|
className="transition-transform duration-300 ease-out hover:-translate-y-1.5 motion-reduce:transform-none motion-reduce:transition-none">
|
||||||
<h3 className="font-sans font-bold text-2xl text-azul-profundo">{item.name}</h3>
|
<TarjetaSpotlight className="h-full rounded-lg bg-azul-profundo text-white p-7">
|
||||||
<p className="mt-3 text-gris-medio leading-relaxed">{item.desc}</p>
|
<div className="grid place-items-center w-14 h-14 rounded-full bg-white/10">
|
||||||
|
<Icono nombre={ICONOS[i]} className="w-7 h-7 text-dorado" />
|
||||||
|
</div>
|
||||||
|
<h3 className="mt-5 font-sans font-black text-2xl">{item.name}</h3>
|
||||||
|
<p className="mt-3 text-gris-medio leading-relaxed">{item.desc}</p>
|
||||||
|
</TarjetaSpotlight>
|
||||||
</motion.li>
|
</motion.li>
|
||||||
))}
|
))}
|
||||||
</motion.ul>
|
</motion.ul>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Container } from '@/components/primitives/Container';
|
||||||
|
import { TarjetaSpotlight } from '@/components/primitives/TarjetaSpotlight';
|
||||||
|
import { dolores } from '@/lib/content';
|
||||||
|
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
||||||
|
|
||||||
|
export function Dolores() {
|
||||||
|
const reduced = usePrefersReducedMotion();
|
||||||
|
return (
|
||||||
|
<section className="bg-white">
|
||||||
|
<Container className="py-24 md:py-32">
|
||||||
|
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{dolores.eyebrow}</motion.p>
|
||||||
|
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="mt-4 font-sans font-black text-4xl md:text-6xl text-azul-profundo leading-[0.95] max-w-3xl tracking-tight">{dolores.title}</motion.h2>
|
||||||
|
<motion.ul variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="mt-14 grid gap-8 md:grid-cols-2 list-none p-0">
|
||||||
|
{dolores.quotes.map((quote) => (
|
||||||
|
<motion.li key={quote} variants={fadeInUp}
|
||||||
|
className="transition-transform duration-300 ease-out hover:-translate-y-1.5 motion-reduce:transform-none motion-reduce:transition-none">
|
||||||
|
<TarjetaSpotlight className="h-full rounded-lg bg-gris-claro shadow-lg shadow-azul-profundo/5 border-l-4 border-dorado p-7">
|
||||||
|
<blockquote className="font-sans italic text-xl md:text-2xl leading-snug text-azul-profundo">
|
||||||
|
<span className="text-dorado not-italic">“</span>
|
||||||
|
{quote}
|
||||||
|
<span className="text-dorado not-italic">”</span>
|
||||||
|
</blockquote>
|
||||||
|
</TarjetaSpotlight>
|
||||||
|
</motion.li>
|
||||||
|
))}
|
||||||
|
</motion.ul>
|
||||||
|
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="mt-16 font-sans font-black text-3xl md:text-5xl leading-[1.05] text-azul-profundo max-w-4xl tracking-tight">
|
||||||
|
{dolores.remate.before}{' '}
|
||||||
|
<span className="text-dorado">{dolores.remate.accent}</span>
|
||||||
|
</motion.p>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Container } from '@/components/primitives/Container';
|
import { Container } from '@/components/primitives/Container';
|
||||||
|
import { PatronPuntos } from '@/components/primitives/PatronPuntos';
|
||||||
|
import { Wordmark } from '@/components/primitives/Wordmark';
|
||||||
|
import { ContadorNumero } from '@/components/primitives/ContadorNumero';
|
||||||
import { estratega } from '@/lib/content';
|
import { estratega } from '@/lib/content';
|
||||||
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
||||||
|
|
||||||
@@ -11,15 +14,25 @@ export function Estratega() {
|
|||||||
<Container className="py-24 md:py-32">
|
<Container className="py-24 md:py-32">
|
||||||
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
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 */}
|
{/* TODO dueña: real portrait. Until then, a branded placeholder built
|
||||||
|
from the manual's dot pattern rather than a stand-in stock 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 bg-gradient-to-br from-azul-profundo to-dorado"
|
className="w-44 h-44 md:w-64 md:h-64 rounded-full flex-shrink-0 overflow-hidden relative bg-azul-profundo grid place-items-center ring-4 ring-dorado ring-offset-4"
|
||||||
aria-label="Retrato de la estratega"
|
aria-label="Retrato de la estratega — pendiente"
|
||||||
role="img"
|
role="img"
|
||||||
/>
|
>
|
||||||
|
<PatronPuntos rows={4} cols={7} className="absolute inset-0 m-auto w-[150%] h-auto text-white/15" />
|
||||||
|
<span className="relative"><Wordmark size="sm" onDark /></span>
|
||||||
|
</motion.div>
|
||||||
<div>
|
<div>
|
||||||
<motion.p variants={fadeInUp} className="text-xs tracking-[0.3em] uppercase font-semibold text-dorado">{estratega.eyebrow}</motion.p>
|
<motion.p variants={fadeInUp} className="text-xs tracking-[0.3em] uppercase font-black text-dorado">
|
||||||
<motion.h2 variants={fadeInUp} className="mt-4 font-sans font-bold text-3xl md:text-5xl text-azul-profundo">{estratega.title}</motion.h2>
|
{estratega.eyebrow.before} <span className="normal-case">{estratega.eyebrow.brand}</span>
|
||||||
|
</motion.p>
|
||||||
|
<motion.h2 variants={fadeInUp} className="mt-4 font-sans font-black text-4xl md:text-6xl text-azul-profundo tracking-tight">{estratega.title}</motion.h2>
|
||||||
|
<motion.div variants={fadeInUp} className="mt-6 flex items-baseline gap-3">
|
||||||
|
<ContadorNumero hasta={30} sufijo="+" className="font-sans font-black text-5xl md:text-6xl text-dorado leading-none" />
|
||||||
|
<p className="text-xs uppercase tracking-[0.2em] text-gris-medio font-semibold max-w-[8rem] leading-tight">años construyendo marcas</p>
|
||||||
|
</motion.div>
|
||||||
<motion.p variants={fadeInUp} className="mt-6 text-lg text-gris-medio leading-relaxed max-w-2xl">{estratega.bio}</motion.p>
|
<motion.p variants={fadeInUp} className="mt-6 text-lg text-gris-medio leading-relaxed max-w-2xl">{estratega.bio}</motion.p>
|
||||||
<motion.blockquote variants={fadeInUp} className="mt-6 font-sans italic text-xl md:text-2xl text-azul-profundo border-l-4 border-dorado pl-4">
|
<motion.blockquote variants={fadeInUp} className="mt-6 font-sans italic text-xl md:text-2xl text-azul-profundo border-l-4 border-dorado pl-4">
|
||||||
{estratega.quote}
|
{estratega.quote}
|
||||||
|
|||||||
@@ -2,27 +2,35 @@
|
|||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Container } from '@/components/primitives/Container';
|
import { Container } from '@/components/primitives/Container';
|
||||||
|
import { SimboloTresLineas } from '@/components/primitives/SimboloTresLineas';
|
||||||
|
import { TextoDesenfoque } from '@/components/primitives/TextoDesenfoque';
|
||||||
import { fraseAncla } from '@/lib/content';
|
import { fraseAncla } from '@/lib/content';
|
||||||
import { fadeInUp, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
import { fadeInUp, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
||||||
|
|
||||||
export function FraseAncla() {
|
export function FraseAncla() {
|
||||||
const reduced = usePrefersReducedMotion();
|
const reduced = usePrefersReducedMotion();
|
||||||
return (
|
return (
|
||||||
<section className="bg-white">
|
<section className="bg-azul-profundo relative overflow-hidden">
|
||||||
<Container className="py-24 md:py-32 text-center max-w-4xl">
|
<Container className="relative py-28 md:py-40 text-center max-w-5xl">
|
||||||
<motion.blockquote
|
<motion.div variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="flex justify-center">
|
||||||
|
<SimboloTresLineas className="pointer-events-none select-none w-14 text-dorado" />
|
||||||
|
</motion.div>
|
||||||
|
<motion.p
|
||||||
variants={fadeInUp}
|
variants={fadeInUp}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
whileInView="visible"
|
whileInView="visible"
|
||||||
viewport={viewportOnce}
|
viewport={viewportOnce}
|
||||||
{...motionProps(reduced)}
|
{...motionProps(reduced)}
|
||||||
className="font-sans italic text-2xl md:text-4xl font-bold leading-snug text-azul-profundo"
|
className="mt-10 font-sans font-black text-4xl md:text-6xl leading-[0.95] tracking-tight text-white"
|
||||||
>
|
>
|
||||||
<span className="text-dorado">“</span>
|
<TextoDesenfoque
|
||||||
{fraseAncla.quote.before}{' '}
|
segments={[
|
||||||
<span className="text-dorado">{fraseAncla.quote.accent}</span>
|
{ text: fraseAncla.quote.before },
|
||||||
<span className="text-dorado">”</span>
|
{ text: fraseAncla.quote.accent, className: 'text-dorado' },
|
||||||
</motion.blockquote>
|
]}
|
||||||
|
/>
|
||||||
|
</motion.p>
|
||||||
</Container>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import { motion } from 'framer-motion';
|
|||||||
import { Wordmark } from '@/components/primitives/Wordmark';
|
import { Wordmark } from '@/components/primitives/Wordmark';
|
||||||
import { CTA } from '@/components/primitives/CTA';
|
import { CTA } from '@/components/primitives/CTA';
|
||||||
import { Container } from '@/components/primitives/Container';
|
import { Container } from '@/components/primitives/Container';
|
||||||
import { hero } from '@/lib/content';
|
import { FondoPuntos } from '@/components/primitives/FondoPuntos';
|
||||||
|
import { Carrusel } from '@/components/primitives/Carrusel';
|
||||||
|
import { TextoDesenfoque } from '@/components/primitives/TextoDesenfoque';
|
||||||
|
import { TarjetaSpotlight } from '@/components/primitives/TarjetaSpotlight';
|
||||||
|
import { hero, dolores } from '@/lib/content';
|
||||||
import { fadeInUp, staggerContainer, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
import { fadeInUp, staggerContainer, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
||||||
|
|
||||||
type Variant = 'hormozi' | 'editorial';
|
type Variant = 'hormozi' | 'editorial';
|
||||||
@@ -15,28 +19,55 @@ export function Hero({ variant = 'editorial' }: { variant?: Variant }) {
|
|||||||
const headlineBase = isHormozi
|
const headlineBase = isHormozi
|
||||||
? 'font-sans font-black uppercase tracking-tight'
|
? 'font-sans font-black uppercase tracking-tight'
|
||||||
: 'font-sans font-bold';
|
: 'font-sans font-bold';
|
||||||
const headlineSize = 'text-4xl md:text-6xl leading-[1.1]';
|
const headlineSize = 'text-5xl md:text-7xl leading-[0.98] tracking-tight';
|
||||||
|
const clarityBorder = isHormozi ? 'border-b-[5px] border-dorado pb-0.5' : 'border-b-[3px] border-dorado pb-0.5';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="bg-white">
|
<section className="bg-white relative overflow-hidden">
|
||||||
<Container className="min-h-[88vh] flex flex-col items-center justify-center text-center py-24">
|
<FondoPuntos className="top-24 -right-16 w-72 md:w-[28rem] text-azul-profundo/10" interactivo />
|
||||||
<motion.div variants={staggerContainer} initial="hidden" animate="visible" {...motionProps(reduced)} className="flex flex-col items-center">
|
<FondoPuntos className="bottom-16 -left-20 w-64 md:w-96 text-dorado/20" distance={28} rows={2} interactivo />
|
||||||
<motion.div variants={fadeInUp}><Wordmark size="xl" /></motion.div>
|
<Container className="relative min-h-[88vh] flex items-center py-24">
|
||||||
<motion.p variants={fadeInUp} className="mt-6 text-xs md:text-sm tracking-[0.3em] uppercase font-semibold text-dorado">
|
<div className="grid gap-14 lg:grid-cols-[1.1fr_0.9fr] lg:items-center w-full">
|
||||||
{hero.eyebrow}
|
<motion.div variants={staggerContainer} initial="hidden" animate="visible" {...motionProps(reduced)}
|
||||||
</motion.p>
|
className="min-w-0 flex flex-col items-center text-center lg:items-start lg:text-left">
|
||||||
<motion.h1 variants={fadeInUp} className={`mt-8 ${headlineBase} ${headlineSize} text-azul-profundo`}>
|
<motion.div variants={fadeInUp}><Wordmark size="xl" showTagline /></motion.div>
|
||||||
{hero.headline.line1}{' '}
|
<motion.p variants={fadeInUp} className="mt-6 text-xs md:text-sm tracking-[0.3em] uppercase font-black text-dorado">
|
||||||
<span className="text-dorado">{hero.headline.effort}</span>{' '}
|
{hero.eyebrow}
|
||||||
{hero.headline.line3}{' '}
|
</motion.p>
|
||||||
<span style={{ borderBottom: isHormozi ? '5px solid #C4942E' : '3px solid #C4942E', paddingBottom: 2 }}>
|
<motion.h1 variants={fadeInUp} className={`mt-8 ${headlineBase} ${headlineSize} text-azul-profundo`}>
|
||||||
{hero.headline.clarity}
|
<TextoDesenfoque
|
||||||
</span>
|
as="span"
|
||||||
</motion.h1>
|
segments={[
|
||||||
<motion.div variants={fadeInUp} className="mt-10">
|
{ text: hero.headline.line1 },
|
||||||
<CTA href="#contacto" label={hero.cta} variant="light" />
|
{ text: hero.headline.effort, className: 'text-dorado' },
|
||||||
|
{ text: hero.headline.line3 },
|
||||||
|
{ text: hero.headline.clarity, className: clarityBorder },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</motion.h1>
|
||||||
|
<motion.div variants={fadeInUp} className="mt-10 flex flex-col md:flex-row items-center gap-4">
|
||||||
|
<CTA href="#contacto" label={hero.cta} variant="light" className="md:whitespace-nowrap" />
|
||||||
|
<CTA href="#mecanismo" label={hero.ctaSecundario} variant="ghost" className="md:whitespace-nowrap" />
|
||||||
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
{/* Rota las frases textuales de `dolores` — quien llega al hero se ve
|
||||||
|
reflejado antes de haber bajado a leer la sección completa. */}
|
||||||
|
<motion.div variants={fadeInUp} initial="hidden" animate="visible" {...motionProps(reduced)}
|
||||||
|
className="hidden lg:block min-w-0">
|
||||||
|
<Carrusel
|
||||||
|
ariaLabel="Lo que sienten nuestros clientes"
|
||||||
|
autoplayMs={4500}
|
||||||
|
slides={dolores.quotes.map((quote) => (
|
||||||
|
<TarjetaSpotlight key={quote} className="rounded-2xl border border-azul-profundo/10 bg-white shadow-xl shadow-azul-profundo/5 p-8 lg:p-10 min-h-[15rem] flex flex-col justify-center">
|
||||||
|
<p className="text-xs uppercase tracking-[0.3em] font-semibold text-dorado">lo que escuchamos</p>
|
||||||
|
<blockquote className="mt-4 font-sans italic text-xl lg:text-2xl leading-snug text-azul-profundo">
|
||||||
|
<span className="text-dorado not-italic">“</span>{quote}<span className="text-dorado not-italic">”</span>
|
||||||
|
</blockquote>
|
||||||
|
</TarjetaSpotlight>
|
||||||
|
))}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,26 +2,78 @@
|
|||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Container } from '@/components/primitives/Container';
|
import { Container } from '@/components/primitives/Container';
|
||||||
|
import { FondoFoto } from '@/components/primitives/FondoFoto';
|
||||||
|
import { Icono, type IconoNombre } from '@/components/primitives/Icono';
|
||||||
import { mecanismo } from '@/lib/content';
|
import { mecanismo } from '@/lib/content';
|
||||||
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps, useAutoStep } from '@/lib/motion';
|
||||||
|
|
||||||
|
const ICONOS: IconoNombre[] = ['inmersion', 'desafiar', 'estrategia'];
|
||||||
|
|
||||||
export function Mecanismo() {
|
export function Mecanismo() {
|
||||||
const reduced = usePrefersReducedMotion();
|
const reduced = usePrefersReducedMotion();
|
||||||
|
const { active, setActive, hoverProps } = useAutoStep(mecanismo.steps.length, 3200);
|
||||||
return (
|
return (
|
||||||
<section className="bg-azul-profundo text-white">
|
<section id="mecanismo" className="bg-azul-profundo text-white scroll-mt-24 relative overflow-hidden">
|
||||||
<Container className="py-24 md:py-32">
|
{/* Foto: Golden Gate Bridge, Dietmar Rabich, CC BY-SA 4.0, modificada (duotono) — créditos en el footer */}
|
||||||
|
<FondoFoto src="/images/mecanismo-bg.jpg" className="inset-0 opacity-25" wash="bg-azul-profundo/80" />
|
||||||
|
<Container className="relative 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-dorado">{mecanismo.eyebrow}</motion.p>
|
className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{mecanismo.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-sans font-bold text-3xl md:text-5xl">{mecanismo.title}</motion.h2>
|
className="mt-4 font-sans font-black text-4xl md:text-6xl tracking-tight">{mecanismo.title}</motion.h2>
|
||||||
<motion.ol variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
|
||||||
|
{/* Conector de pasos: avanza solo cada ~3s (se pausa con hover o si el
|
||||||
|
usuario elige un paso), y adelanta la línea de progreso — el mismo
|
||||||
|
lenguaje de "proceso conectado" de en-ti.com. */}
|
||||||
|
<div {...hoverProps} className="mt-14 hidden md:flex items-center" role="tablist" aria-label="Pasos del mecanismo">
|
||||||
|
{mecanismo.steps.map((step, i) => (
|
||||||
|
<div key={step.n} className="flex items-center flex-1 last:flex-none">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={active === i}
|
||||||
|
aria-label={`Paso ${step.n}: ${step.name}`}
|
||||||
|
onClick={() => setActive(i)}
|
||||||
|
onMouseEnter={() => setActive(i)}
|
||||||
|
className={`shrink-0 grid place-items-center w-14 h-14 rounded-full border-2 font-sans font-black text-lg transition-colors duration-300 ${
|
||||||
|
active === i ? 'bg-dorado border-dorado text-azul-profundo' : 'border-white/25 text-white/60 hover:border-dorado/60 hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{step.n}
|
||||||
|
</button>
|
||||||
|
{i < mecanismo.steps.length - 1 && (
|
||||||
|
<div className="flex-1 h-px mx-2 bg-white/15 relative overflow-hidden">
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-y-0 left-0 w-full origin-left bg-dorado motion-reduce:transition-none"
|
||||||
|
initial={false}
|
||||||
|
animate={{ scaleX: active > i ? 1 : 0 }}
|
||||||
|
transition={{ duration: 0.4, ease: 'easeOut' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.ol variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)} {...hoverProps}
|
||||||
className="mt-12 grid gap-10 md:grid-cols-3">
|
className="mt-12 grid gap-10 md:grid-cols-3">
|
||||||
{mecanismo.steps.map((step) => (
|
{mecanismo.steps.map((step, i) => (
|
||||||
<motion.li key={step.n} variants={fadeInUp}
|
<motion.li key={step.n} variants={fadeInUp}>
|
||||||
className="border-t border-dorado/30 pt-6 transition-all duration-300 ease-out hover:-translate-y-1.5 hover:border-dorado/70 motion-reduce:transform-none motion-reduce:transition-none">
|
<button
|
||||||
<div className="font-sans font-black text-5xl md:text-6xl text-dorado leading-none">{step.n}</div>
|
type="button"
|
||||||
<h3 className="mt-4 font-sans font-bold text-xl">{step.name}</h3>
|
onClick={() => setActive(i)}
|
||||||
<p className="mt-3 text-gris-medio leading-relaxed">{step.desc}</p>
|
aria-pressed={active === i}
|
||||||
|
className={`w-full text-left border-t pt-6 transition-all duration-300 ease-out hover:-translate-y-1.5 motion-reduce:transform-none motion-reduce:transition-none ${
|
||||||
|
active === i ? 'border-dorado md:-translate-y-1.5' : 'border-dorado/30 md:opacity-40'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="font-sans font-black text-5xl md:text-6xl text-dorado leading-none">{step.n}</div>
|
||||||
|
<Icono nombre={ICONOS[i]} className="w-10 h-10 md:w-12 md:h-12 text-white/70" />
|
||||||
|
</div>
|
||||||
|
<h3 className="mt-4 font-sans font-bold text-xl">{step.name}</h3>
|
||||||
|
<p className="mt-3 text-gris-medio leading-relaxed">{step.desc}</p>
|
||||||
|
</button>
|
||||||
</motion.li>
|
</motion.li>
|
||||||
))}
|
))}
|
||||||
</motion.ol>
|
</motion.ol>
|
||||||
|
|||||||
@@ -8,23 +8,41 @@ import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, moti
|
|||||||
export function ParaQuien() {
|
export function ParaQuien() {
|
||||||
const reduced = usePrefersReducedMotion();
|
const reduced = usePrefersReducedMotion();
|
||||||
return (
|
return (
|
||||||
<section className="bg-white">
|
<section className="bg-white relative overflow-hidden">
|
||||||
<Container className="py-24 md:py-32">
|
<Container className="relative pt-24 md:pt-32 pb-12 md:pb-16">
|
||||||
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)} className="max-w-3xl">
|
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
<motion.h2 variants={fadeInUp} className="font-sans font-bold text-3xl md:text-5xl text-azul-profundo leading-tight">
|
className="font-sans font-black text-4xl md:text-6xl text-azul-profundo leading-[0.95] max-w-4xl tracking-tight">{paraQuien.title}</motion.h2>
|
||||||
{paraQuien.title}
|
</Container>
|
||||||
</motion.h2>
|
{/* Pantalla partida: luz contra oscuridad, para dramatizar la bifurcación
|
||||||
<motion.p variants={fadeInUp} className="mt-6 text-lg text-gris-medio">{paraQuien.lead}</motion.p>
|
"sí es para ti / no es para ti" — sale del ancho del Container a
|
||||||
<motion.ul variants={fadeInUp} className="mt-8 space-y-3">
|
propósito, a todo lo ancho de la ventana. */}
|
||||||
|
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="grid md:grid-cols-2">
|
||||||
|
<motion.div variants={fadeInUp} className="bg-white px-6 md:px-16 py-16 md:py-20 border-t-4 border-dorado">
|
||||||
|
<p className="text-xs tracking-[0.3em] uppercase font-black text-dorado">Sí</p>
|
||||||
|
<p className="mt-3 text-2xl font-sans font-bold text-azul-profundo max-w-sm">{paraQuien.lead}</p>
|
||||||
|
<ul className="mt-8 space-y-4">
|
||||||
{paraQuien.criteria.map((c) => (
|
{paraQuien.criteria.map((c) => (
|
||||||
<li key={c} className="flex items-start gap-3">
|
<li key={c} className="flex items-start gap-3">
|
||||||
<span className="text-dorado font-bold mt-1" aria-hidden="true">✓</span>
|
<span aria-hidden="true" className="grid place-items-center w-7 h-7 rounded-full bg-dorado text-azul-profundo font-black text-sm shrink-0">✓</span>
|
||||||
<span className="text-azul-profundo text-lg">{c}</span>
|
<span className="text-azul-profundo text-lg leading-snug">{c}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</motion.ul>
|
</ul>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</Container>
|
<motion.div variants={fadeInUp} className="bg-azul-profundo text-white px-6 md:px-16 py-16 md:py-20 border-t-4 border-white/10">
|
||||||
|
<p className="text-xs tracking-[0.3em] uppercase font-black text-white/50">No</p>
|
||||||
|
<p className="mt-3 text-2xl font-sans font-bold text-white/70 max-w-sm">{paraQuien.leadNo}</p>
|
||||||
|
<ul className="mt-8 space-y-4">
|
||||||
|
{paraQuien.criteriaNo.map((c) => (
|
||||||
|
<li key={c} className="flex items-start gap-3">
|
||||||
|
<span aria-hidden="true" className="grid place-items-center w-7 h-7 rounded-full border border-white/30 text-white/50 font-black text-sm shrink-0">✕</span>
|
||||||
|
<span className="text-white/60 text-lg leading-snug">{c}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Container } from '@/components/primitives/Container';
|
||||||
|
import { FondoPuntos } from '@/components/primitives/FondoPuntos';
|
||||||
|
import { Carrusel } from '@/components/primitives/Carrusel';
|
||||||
|
import { TarjetaSpotlight } from '@/components/primitives/TarjetaSpotlight';
|
||||||
|
import { IlustracionInflexion } from '@/components/primitives/IlustracionInflexion';
|
||||||
|
import { puntosInflexion } from '@/lib/content';
|
||||||
|
import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, motionProps } from '@/lib/motion';
|
||||||
|
|
||||||
|
function Tarjeta({ n, name, desc }: { n: number; name: string; desc: string }) {
|
||||||
|
return (
|
||||||
|
<TarjetaSpotlight className="relative h-full rounded-md border-t-2 border-dorado pt-5 pl-1 pr-1 pb-1 overflow-visible">
|
||||||
|
<span aria-hidden="true" className="pointer-events-none absolute -top-2 right-0 font-sans font-black text-5xl text-azul-profundo/[0.06] leading-none select-none">
|
||||||
|
{String(n).padStart(2, '0')}
|
||||||
|
</span>
|
||||||
|
<h3 className="relative font-sans font-bold text-xl text-azul-profundo">{name}</h3>
|
||||||
|
<p className="relative mt-2 text-gris-medio leading-relaxed">{desc}</p>
|
||||||
|
</TarjetaSpotlight>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PuntosInflexion() {
|
||||||
|
const reduced = usePrefersReducedMotion();
|
||||||
|
return (
|
||||||
|
<section className="bg-gris-claro relative overflow-hidden">
|
||||||
|
<FondoPuntos className="-bottom-6 -right-12 w-80 md:w-[32rem] text-azul-profundo/10" distance={32} />
|
||||||
|
<Container className="relative py-24 md:py-32">
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between md:gap-12">
|
||||||
|
<div>
|
||||||
|
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{puntosInflexion.eyebrow}</motion.p>
|
||||||
|
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="mt-4 font-sans font-black text-4xl md:text-6xl text-azul-profundo leading-[0.95] tracking-tight max-w-3xl">{puntosInflexion.title}</motion.h2>
|
||||||
|
</div>
|
||||||
|
<motion.div variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="mt-8 md:mt-0 shrink-0">
|
||||||
|
<IlustracionInflexion className="w-56 md:w-72 h-auto mx-auto" />
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Carrusel táctil en móvil (contador "01 / 06", como en-ti.com); grid fijo desde md. */}
|
||||||
|
<div className="mt-12 md:hidden">
|
||||||
|
<Carrusel
|
||||||
|
ariaLabel="Los seis puntos de inflexión"
|
||||||
|
mostrarContador
|
||||||
|
slides={puntosInflexion.items.map((item, i) => <Tarjeta key={item.name} n={i + 1} {...item} />)}
|
||||||
|
slideClassName="pr-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<motion.ul variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||||
|
className="mt-12 hidden md:grid md:grid-cols-3 md:gap-8 list-none p-0">
|
||||||
|
{puntosInflexion.items.map((item, i) => (
|
||||||
|
<motion.li key={item.name} variants={fadeInUp}
|
||||||
|
className="transition-transform duration-300 ease-out hover:-translate-y-1.5 motion-reduce:transform-none motion-reduce:transition-none">
|
||||||
|
<Tarjeta n={i + 1} {...item} />
|
||||||
|
</motion.li>
|
||||||
|
))}
|
||||||
|
</motion.ul>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen, act } from '@testing-library/react';
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import { ComoEmpezar } from '../ComoEmpezar';
|
import { ComoEmpezar } from '../ComoEmpezar';
|
||||||
|
|
||||||
describe('<ComoEmpezar />', () => {
|
describe('<ComoEmpezar />', () => {
|
||||||
@@ -15,4 +15,26 @@ describe('<ComoEmpezar />', () => {
|
|||||||
expect(href).toMatch(/^https:\/\/wa\.me\//);
|
expect(href).toMatch(/^https:\/\/wa\.me\//);
|
||||||
expect(href).toContain('text=');
|
expect(href).toContain('text=');
|
||||||
});
|
});
|
||||||
|
it('renders the sunrise photo panel as decorative, not real content', () => {
|
||||||
|
const { container } = render(<ComoEmpezar />);
|
||||||
|
const img = container.querySelector('img');
|
||||||
|
// next/image rewrites `src` through its loader in this test environment;
|
||||||
|
// the static export (images.unoptimized: true) serves the plain path.
|
||||||
|
expect(img?.getAttribute('src')).toContain('comoempezar-bg.jpg');
|
||||||
|
expect(img).toHaveAttribute('alt', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('auto-advancing step highlight', () => {
|
||||||
|
beforeEach(() => { vi.useFakeTimers(); });
|
||||||
|
afterEach(() => { vi.useRealTimers(); });
|
||||||
|
|
||||||
|
it('highlights the first step by default, then advances on its own', () => {
|
||||||
|
render(<ComoEmpezar />);
|
||||||
|
const first = screen.getByText('Escríbeme').closest('button');
|
||||||
|
const second = screen.getByText('Sesión de diagnóstico').closest('button');
|
||||||
|
expect(first).toHaveAttribute('aria-pressed', 'true');
|
||||||
|
act(() => { vi.advanceTimersByTime(3300); });
|
||||||
|
expect(second).toHaveAttribute('aria-pressed', 'true');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { Dolores } from '../Dolores';
|
||||||
|
|
||||||
|
describe('<Dolores />', () => {
|
||||||
|
it('renders the four pains verbatim, in the client\'s first person', () => {
|
||||||
|
render(<Dolores />);
|
||||||
|
expect(screen.getByText(/Trabajo más y gano casi lo mismo que hace dos años\./)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/no sé en cuál enfocar mi tiempo y mi dinero\./)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/no puede crecer si yo no estoy en cada decisión\./)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/ya no me sirve para el siguiente nivel\./)).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByRole('listitem')).toHaveLength(4);
|
||||||
|
});
|
||||||
|
it('closes with the resolution, not with more effort', () => {
|
||||||
|
const { container } = render(<Dolores />);
|
||||||
|
expect(container.textContent).toContain('Esto no se resuelve con más esfuerzo.');
|
||||||
|
expect(container.textContent).toContain('Se resuelve con una estrategia clara.');
|
||||||
|
});
|
||||||
|
it('does NOT introduce scarcity', () => {
|
||||||
|
const { container } = render(<Dolores />);
|
||||||
|
expect(container.textContent).not.toMatch(/cupos|cohorte|generación fundadora/i);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,4 +11,12 @@ describe('<Estratega />', () => {
|
|||||||
render(<Estratega />);
|
render(<Estratega />);
|
||||||
expect(screen.getByText(/El tiempo no se compra/i)).toBeInTheDocument();
|
expect(screen.getByText(/El tiempo no se compra/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
it('never lets CSS uppercase turn the brand name into CREEER', () => {
|
||||||
|
const { container } = render(<Estratega />);
|
||||||
|
const brand = screen.getByText('creeer');
|
||||||
|
expect(brand.className).toContain('normal-case');
|
||||||
|
// the eyebrow around it may be uppercase, the brand name may not
|
||||||
|
expect(brand.closest('p')?.className).toContain('uppercase');
|
||||||
|
expect(container.textContent).not.toMatch(/CREEER|Creeer/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { FraseAncla } from '../FraseAncla';
|
import { FraseAncla } from '../FraseAncla';
|
||||||
|
|
||||||
describe('<FraseAncla />', () => {
|
describe('<FraseAncla />', () => {
|
||||||
it('renders the anchor quote', () => {
|
it('renders the anchor quote', () => {
|
||||||
render(<FraseAncla />);
|
const { container } = render(<FraseAncla />);
|
||||||
expect(screen.getByText(/Las empresas no dejan de crecer/i)).toBeInTheDocument();
|
expect(container.textContent).toMatch(/Las empresas no dejan de crecer/i);
|
||||||
expect(screen.getByText(/falta de claridad/i)).toBeInTheDocument();
|
expect(container.textContent).toMatch(/falta de claridad/i);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -8,17 +8,26 @@ describe('<Hero />', () => {
|
|||||||
expect(screen.getByText(/tu estratega de crecimiento/i)).toBeInTheDocument();
|
expect(screen.getByText(/tu estratega de crecimiento/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it('renders the headline copy', () => {
|
it('renders the headline copy', () => {
|
||||||
render(<Hero variant="hormozi" />);
|
const { container } = render(<Hero variant="hormozi" />);
|
||||||
expect(screen.getByText(/Tu negocio/i)).toBeInTheDocument();
|
expect(container.textContent).toMatch(/Tu negocio/i);
|
||||||
expect(screen.getByText(/más esfuerzo/i)).toBeInTheDocument();
|
expect(container.textContent).toMatch(/más esfuerzo/i);
|
||||||
expect(screen.getByText(/más claridad/i)).toBeInTheDocument();
|
expect(container.textContent).toMatch(/más claridad/i);
|
||||||
});
|
});
|
||||||
it('renders the CTA', () => {
|
it('renders the CTA', () => {
|
||||||
render(<Hero variant="editorial" />);
|
render(<Hero variant="editorial" />);
|
||||||
expect(screen.getByRole('link', { name: /define tu siguiente movimiento/i })).toBeInTheDocument();
|
expect(screen.getByRole('link', { name: /define tu siguiente movimiento/i })).toHaveAttribute('href', '#contacto');
|
||||||
|
});
|
||||||
|
it('offers a secondary route into the process for readers not ready to convert', () => {
|
||||||
|
render(<Hero variant="editorial" />);
|
||||||
|
expect(screen.getByRole('link', { name: /ver cómo trabajo/i })).toHaveAttribute('href', '#mecanismo');
|
||||||
});
|
});
|
||||||
it('does NOT render scarcity (cupos/cohorte)', () => {
|
it('does NOT render scarcity (cupos/cohorte)', () => {
|
||||||
const { container } = render(<Hero variant="editorial" />);
|
const { container } = render(<Hero variant="editorial" />);
|
||||||
expect(container.textContent).not.toMatch(/cupos|cohorte|generación fundadora/i);
|
expect(container.textContent).not.toMatch(/cupos|cohorte|generación fundadora/i);
|
||||||
});
|
});
|
||||||
|
it('mirrors the client\'s own words back in a rotating carousel', () => {
|
||||||
|
render(<Hero variant="editorial" />);
|
||||||
|
expect(screen.getByRole('group', { name: /lo que sienten nuestros clientes/i })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/Trabajo más y gano casi lo mismo/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen, fireEvent, act } from '@testing-library/react';
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
import { Mecanismo } from '../Mecanismo';
|
import { Mecanismo } from '../Mecanismo';
|
||||||
|
|
||||||
describe('<Mecanismo />', () => {
|
describe('<Mecanismo />', () => {
|
||||||
@@ -13,4 +13,31 @@ describe('<Mecanismo />', () => {
|
|||||||
const { container } = render(<Mecanismo />);
|
const { container } = render(<Mecanismo />);
|
||||||
expect(container.textContent).toMatch(/01.+02.+03/);
|
expect(container.textContent).toMatch(/01.+02.+03/);
|
||||||
});
|
});
|
||||||
|
it('keeps the background photo subtle enough not to fight the text', () => {
|
||||||
|
const { container } = render(<Mecanismo />);
|
||||||
|
const bg = container.querySelector('img');
|
||||||
|
expect(bg).toHaveAttribute('src', '/images/mecanismo-bg.jpg');
|
||||||
|
expect(bg?.closest('[class*="opacity-"]')).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('auto-advancing step indicator', () => {
|
||||||
|
beforeEach(() => { vi.useFakeTimers(); });
|
||||||
|
afterEach(() => { vi.useRealTimers(); });
|
||||||
|
|
||||||
|
it('highlights the first step by default, then advances on its own', () => {
|
||||||
|
render(<Mecanismo />);
|
||||||
|
const tabs = screen.getAllByRole('tab');
|
||||||
|
expect(tabs[0]).toHaveAttribute('aria-selected', 'true');
|
||||||
|
act(() => { vi.advanceTimersByTime(3300); });
|
||||||
|
expect(tabs[1]).toHaveAttribute('aria-selected', 'true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stops advancing while the pointer is over the steps', () => {
|
||||||
|
render(<Mecanismo />);
|
||||||
|
const tabs = screen.getAllByRole('tab');
|
||||||
|
fireEvent.mouseEnter(screen.getByRole('tablist'));
|
||||||
|
act(() => { vi.advanceTimersByTime(6000); });
|
||||||
|
expect(tabs[0]).toHaveAttribute('aria-selected', 'true');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -7,10 +7,23 @@ describe('<ParaQuien />', () => {
|
|||||||
render(<ParaQuien />);
|
render(<ParaQuien />);
|
||||||
expect(screen.getByText(/punto de inflexión/i)).toBeInTheDocument();
|
expect(screen.getByText(/punto de inflexión/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it('renders all 3 criteria', () => {
|
it('renders the qualifying criteria', () => {
|
||||||
render(<ParaQuien />);
|
render(<ParaQuien />);
|
||||||
expect(screen.getByText(/tienes tracción/i)).toBeInTheDocument();
|
expect(screen.getByText(/tienes tracción/i)).toBeInTheDocument();
|
||||||
expect(screen.getByText(/trabajar más ya no rinde/i)).toBeInTheDocument();
|
expect(screen.getByText(/trabajar más ya no rinde/i)).toBeInTheDocument();
|
||||||
expect(screen.getByText(/necesitas claridad/i)).toBeInTheDocument();
|
expect(screen.getByText(/necesitas claridad/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/sin perder tu esencia/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('renders the disqualifying criteria from the dueña deck', () => {
|
||||||
|
render(<ParaQuien />);
|
||||||
|
expect(screen.getByText(/Esto no es para ti si/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/fase inicial o sin tracción/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/solución rápida de venta/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/un curso, no una transformación/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/crisis de supervivencia/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('keeps the disqualifiers impersonal, so they read as a profile and not an accusation', () => {
|
||||||
|
render(<ParaQuien />);
|
||||||
|
expect(screen.getByText(/fase inicial o sin tracción/i).textContent).not.toMatch(/\btú\b|\btu\b/i);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { PuntosInflexion } from '../PuntosInflexion';
|
||||||
|
|
||||||
|
describe('<PuntosInflexion />', () => {
|
||||||
|
it('renders the section title', () => {
|
||||||
|
render(<PuntosInflexion />);
|
||||||
|
expect(screen.getByText(/llegan a un punto de inflexión/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('renders the six inflection points from the dueña deck', () => {
|
||||||
|
render(<PuntosInflexion />);
|
||||||
|
// Each point renders twice in the markup — once in the mobile carousel,
|
||||||
|
// once in the desktop grid — with CSS `hidden`/`md:` toggling which one
|
||||||
|
// is actually visible at a given breakpoint.
|
||||||
|
for (const name of ['Crecer', 'Consolidarse', 'Recuperar rentabilidad', 'Simplificar', 'Expandirse', 'Replantear el portafolio']) {
|
||||||
|
expect(screen.getAllByText(name).length).toBeGreaterThanOrEqual(1);
|
||||||
|
}
|
||||||
|
expect(screen.getAllByRole('listitem')).toHaveLength(6);
|
||||||
|
});
|
||||||
|
it('does NOT introduce scarcity', () => {
|
||||||
|
const { container } = render(<PuntosInflexion />);
|
||||||
|
expect(container.textContent).not.toMatch(/cupos|cohorte|generación fundadora/i);
|
||||||
|
});
|
||||||
|
});
|
||||||
+60
-1
@@ -5,6 +5,12 @@ export const wordmark = {
|
|||||||
parts: { before: 'cre', accent: '≡', after: 'er' },
|
parts: { before: 'cre', accent: '≡', after: 'er' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const brandPillars = {
|
||||||
|
pillar1: "Claridad",
|
||||||
|
pillar2: "Dirección",
|
||||||
|
pillar3: "Crecimiento",
|
||||||
|
};
|
||||||
|
|
||||||
export const hero = {
|
export const hero = {
|
||||||
eyebrow: '— tu estratega de crecimiento —',
|
eyebrow: '— tu estratega de crecimiento —',
|
||||||
// Headline broken into segments for emphasis styling
|
// Headline broken into segments for emphasis styling
|
||||||
@@ -15,6 +21,7 @@ export const hero = {
|
|||||||
clarity: 'más claridad.', // underlined gold
|
clarity: 'más claridad.', // underlined gold
|
||||||
},
|
},
|
||||||
cta: 'Define tu siguiente movimiento',
|
cta: 'Define tu siguiente movimiento',
|
||||||
|
ctaSecundario: 'Ver cómo trabajo',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fraseAncla = {
|
export const fraseAncla = {
|
||||||
@@ -25,6 +32,39 @@ export const fraseAncla = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The six inflection points, verbatim from the dueña's deck
|
||||||
|
// "Rumbo Estratégico · Propuesta Socio" (slide 2).
|
||||||
|
export const puntosInflexion = {
|
||||||
|
eyebrow: 'el contexto',
|
||||||
|
title: 'Todos los negocios consolidados llegan a un punto de inflexión',
|
||||||
|
items: [
|
||||||
|
{ name: 'Crecer', desc: 'Dar el siguiente salto sin saber por dónde.' },
|
||||||
|
{ name: 'Consolidarse', desc: 'Ordenar lo construido antes de escalar.' },
|
||||||
|
{ name: 'Recuperar rentabilidad', desc: 'El esfuerzo ya no se traduce en resultado.' },
|
||||||
|
{ name: 'Simplificar', desc: 'Demasiada complejidad operativa.' },
|
||||||
|
{ name: 'Expandirse', desc: 'Nuevos mercados, mismo mapa de decisión.' },
|
||||||
|
{ name: 'Replantear el portafolio', desc: 'Qué seguir, qué dejar ir.' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Slide 3 of the same deck. The four quotes are the client's own words —
|
||||||
|
// keep them verbatim, in first person.
|
||||||
|
export const dolores = {
|
||||||
|
eyebrow: 'lo que realmente sientes',
|
||||||
|
title: 'Los dolores detrás del punto de inflexión',
|
||||||
|
quotes: [
|
||||||
|
'Trabajo más y gano casi lo mismo que hace dos años.',
|
||||||
|
'Tengo varios proyectos y no sé en cuál enfocar mi tiempo y mi dinero.',
|
||||||
|
'Mi empresa no puede crecer si yo no estoy en cada decisión.',
|
||||||
|
'Lo que me trajo hasta aquí ya no me sirve para el siguiente nivel.',
|
||||||
|
],
|
||||||
|
// Split so the resolution renders in gold.
|
||||||
|
remate: {
|
||||||
|
before: 'Esto no se resuelve con más esfuerzo.',
|
||||||
|
accent: 'Se resuelve con una estrategia clara.',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const paraQuien = {
|
export const paraQuien = {
|
||||||
title: '¿Llegaste a tu punto de inflexión?',
|
title: '¿Llegaste a tu punto de inflexión?',
|
||||||
lead: 'Esto es para ti si…',
|
lead: 'Esto es para ti si…',
|
||||||
@@ -32,6 +72,16 @@ export const paraQuien = {
|
|||||||
'tienes tracción y ya demostraste que sabes crecer',
|
'tienes tracción y ya demostraste que sabes crecer',
|
||||||
'trabajar más ya no rinde como antes',
|
'trabajar más ya no rinde como antes',
|
||||||
'necesitas claridad para decidir el siguiente movimiento',
|
'necesitas claridad para decidir el siguiente movimiento',
|
||||||
|
'quieres profesionalizar sin perder tu esencia',
|
||||||
|
],
|
||||||
|
// Deck slide 9, "NO ES PARA". Kept impersonal on purpose: this disqualifies
|
||||||
|
// a profile, it does not accuse the reader.
|
||||||
|
leadNo: 'Esto no es para ti si…',
|
||||||
|
criteriaNo: [
|
||||||
|
'el negocio está en fase inicial o sin tracción',
|
||||||
|
'buscas una solución rápida de venta',
|
||||||
|
'buscas un curso, no una transformación',
|
||||||
|
'la empresa está en crisis de supervivencia, no en transición',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,7 +107,9 @@ export const disciplinas = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const estratega = {
|
export const estratega = {
|
||||||
eyebrow: 'la mente detrás de creeer',
|
// Split so the brand name keeps its lowercase inside an uppercase eyebrow —
|
||||||
|
// CSS text-transform would otherwise render it as CREEER.
|
||||||
|
eyebrow: { before: 'la mente detrás de', brand: 'creeer' },
|
||||||
title: 'Sobre la estratega',
|
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.',
|
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.',
|
quote: 'El tiempo no se compra, se invierte.',
|
||||||
@@ -94,3 +146,10 @@ export const footer = {
|
|||||||
eslogan: 'Estrategia que transforma.',
|
eslogan: 'Estrategia que transforma.',
|
||||||
legal: '© 2026 creeer',
|
legal: '© 2026 creeer',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// CC BY-SA 4.0 requires attribution + a link to the licence + noting the
|
||||||
|
// image was modified (both got a navy/gold duotone treatment).
|
||||||
|
export const creditosFotos = [
|
||||||
|
{ desc: 'Foto: Golden Gate Bridge — Dietmar Rabich', href: 'https://commons.wikimedia.org/wiki/File:San_Francisco_(CA,_USA),_Golden_Gate_Bridge_--_2022_--_3023_(bw).jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
|
||||||
|
{ desc: 'Foto: amanecer, Monte Nardi — Tony 1212', href: 'https://commons.wikimedia.org/wiki/File:Sunrise_over_the_summit_of_Mount_Nardi_from_the_south.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
|
||||||
|
];
|
||||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
Reference in new issue
Block a user