feat: rediseño «El documento» en curso, contacto real y enlaces externos

Primitivas y contenido del rediseño (escala tipográfica, Revelar,
MarcaSeccion, Seccion, Antetitulo, SimboloMarca, Resplandor, ClaridadHero,
sección Proceso de ocho etapas, lib/content.ts reestructurado en seis
secciones con alias para las viejas). Mecanismo y FormaTrabajar se retiran.

Encima, lo de esta sesión:
- recorrido: creeer vuelve a responder cada situación en vez de repetirla
  (el alias apuntaba respuesta a situacion); respuestas en plural. Créditos
  de las seis fotos de México.
- contacto: WhatsApp, email (con mailto:) y LinkedIn reales; iconos de las
  redes en el cierre y el footer (IconoRed, trazos de Simple Icons, CC0).
- enlaces: lo externo abre en otra pestaña con noopener y aviso para
  lectores de pantalla (lib/enlaces.ts); mailto y anclas se quedan.
- Proceso: las ocho tarjetas no aparecían nunca en móvil (amount 0.3 sobre
  una lista de ~3000px); ahora cada una se revela sola. El ≡ de la 08 ya no
  tapa un entregable, título equilibrado, etiqueta a 12px, sin elevar al
  hover lo que no es clicable.
- Disciplinas: la frase «No son la promesa — son los medios» gana peso y
  se agrupa con título e ilustración; sin cortes huérfanos en móvil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
erikghandClaude Opus 5.5 committed 2026-09-30 11:27:20 -06:00
1 parent 3242a8bf71
commit 89e54a6e84
73 files changed
+5261 -592

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
import { describe, it, expect } from 'vitest';
import * as content from '../content';
import * as contenido from '../content';
import {
apertura, dondeEstas, queCambia, elTrabajo, paraQuienQuien, empezar,
puntosInflexion, dolores, fraseAncla,
} from '../content';
// The landing speaks in the first person plural (Erik's decision, 6/ago/2026,
// recorded in docs/superpowers/specs/2026-08-06-proceso-ocho-etapas-design.md).
// Before that it was singular, and the change touched copy in six different
// objects — easy to reintroduce by accident when editing one of them.
//
// The reader stays "tú": that half is the dueña's locked decision and is not
// what this file guards.
// Unambiguous first-person-singular markers: conjugated verbs and imperatives
// with the enclitic "-me". Deliberately excludes anything that doubles as a
// noun — "trabajo" is one ("el trabajo completo"), which is why the whole
// word list is verbs and never bare stems.
const SINGULAR = /\b(me sumerjo|sumerjo|cuestiono|separo|reviso|ubico|acompaño|escríbeme|cuéntame|conmigo)\b/i;
describe('voz de la marca en lib/content.ts', () => {
it('speaks in the plural everywhere creeer is the one speaking', () => {
// `dolores` is the one exception and it is not an oversight: those four
// quotes are in the first person singular because the CLIENT is talking,
// not creeer ("Trabajo más y gano casi lo mismo que hace dos años").
// They are verbatim from slide 3 of the dueña's deck and must not move.
const { dolores, ...resto } = content;
for (const [nombre, valor] of Object.entries(resto)) {
expect(JSON.stringify(valor), `«${nombre}» quedó en singular`).not.toMatch(SINGULAR);
}
// The eyebrow is where the old singular voice survived the plural
// migration — locked now that the dueña confirmed it (19/ago/2026).
expect(apertura.eyebrow).not.toMatch(/\btu estratega\b/i);
// Guard the guard: if the quotes are ever rewritten, this fails and forces
// a decision instead of silently weakening the check above.
expect(JSON.stringify(dolores)).toMatch(/Trabajo más y gano casi lo mismo/);
});
it('keeps the reader in the second person', () => {
expect(JSON.stringify(content.paraQuien)).toMatch(/\btienes\b/i);
expect(JSON.stringify(content.proceso)).toMatch(/\btu\b/i);
});
});
describe('la estructura de seis secciones', () => {
it('numera el documento del 01 al 06 sin saltos', () => {
expect(
[apertura, dondeEstas, queCambia, elTrabajo, paraQuienQuien, empezar].map((s) => s.numero),
).toEqual(['01', '02', '03', '04', '05', '06']);
});
it('conserva verbatim las palabras de la dueña', () => {
// Her doc is explicit: "they are already written in her voice — use them,
// don't rewrite them." These tests fail if someone rewrites them.
expect(dondeEstas.dolores).toContain('Trabajo más y gano casi lo mismo que hace dos años.');
expect(dondeEstas.remate.accent).toBe('Se resuelve con una estrategia clara.');
expect(queCambia.ancla.accent).toBe('dejan de crecer por falta de claridad.');
expect(dondeEstas.puntos.map((p) => p.name)).toEqual([
'Crecer', 'Consolidarse', 'Recuperar rentabilidad',
'Simplificar', 'Expandirse', 'Replantear el portafolio',
]);
});
it('los alias viejos siguen leyendo de la misma fuente, no de una copia', () => {
// While the two structures coexist, a silent divergence between them
// would mean different copy on the same page.
expect(puntosInflexion.items[0].situacion).toBe(dondeEstas.puntos[0].situacion);
expect(dolores.quotes).toEqual(dondeEstas.dolores);
expect(fraseAncla.quote).toBe(queCambia.ancla);
});
it('mantiene en pie todo lo que las secciones viejas importan', () => {
// The ten old sections stay mounted until Task 13, and a single missing
// name here breaks the whole build. This is the real list of names the
// code imports today from `@/lib/content`.
const modulo = contenido as Record<string, unknown>;
[
'brandPillars', 'cierre', 'comoEmpezar', 'creditosFotos', 'disciplinas',
'dolores', 'estratega', 'footer', 'fraseAncla', 'header', 'hero',
'paraQuien', 'proceso', 'puntosInflexion',
].forEach((nombre) => expect(modulo[nombre], nombre).toBeDefined());
});
it('no promete escasez en la apertura', () => {
expect(JSON.stringify(apertura)).not.toMatch(/cupos|cohorte|generación fundadora/i);
});
it('arma los tres contactos como enlaces que abren algo', () => {
// An email without `mailto:` is a relative URL: the browser navigates to
// /nombre@dominio and lands on a 404 instead of opening the mail client.
const { whatsapp, email, linkedin } = empezar.contacto;
expect(whatsapp.href).toMatch(/^https:\/\/wa\.me\/\d{10,15}$/);
expect(email.href).toMatch(/^mailto:[^\s@]+@[^\s@]+\.[^\s@]+$/);
expect(linkedin.href).toMatch(/^https:\/\/(www\.)?linkedin\.com\/in\/[\w-]+\/?$/);
});
});
+41
View File
@@ -0,0 +1,41 @@
import { readFileSync } from 'node:fs';
import { describe, it, expect } from 'vitest';
// Guardas de contraste, leídas del código fuente porque son una regla de
// sistema, no el comportamiento de un componente suelto: lo que hay que impedir
// es que el token vuelva a aparecer donde no se lee.
//
// `gris-medio` (#AAAAAA) da 7,2:1 sobre azul profundo — ahí es el token
// correcto, y en Disciplinas y el footer se queda. Sobre blanco da 2,3:1 y
// sobre gris-claro 2,1:1: en fondo claro es texto que no se lee.
//
// `dorado` (#C4942E) da 6,07:1 sobre azul profundo y 2,76:1 / 2,46:1 sobre los
// dos fondos claros. Sigue siendo el color de rellenos, reglas, iconos y el ≡;
// lo que no puede ser en claro es texto pequeño, y para eso está `dorado-texto`.
const SOBRE_CLARO = [
'components/sections/Hero.tsx',
'components/sections/Dolores.tsx',
'components/sections/Proceso.tsx',
'components/sections/ParaQuien.tsx',
'components/sections/Estratega.tsx',
'components/sections/ComoEmpezar.tsx',
// La rama de movimiento reducido del recorrido va sobre gris-claro, y es
// justo la que sirve a quien más necesita que el texto se lea.
'components/primitives/ParDialogo.tsx',
];
describe('tokens de texto sobre fondo claro', () => {
it.each(SOBRE_CLARO)('%s no pinta texto en gris-medio', (archivo) => {
expect(readFileSync(archivo, 'utf8')).not.toMatch(/text-gris-medio/);
});
it('ParDialogo etiqueta al hablante en el dorado legible', () => {
// La regla vertical sigue siendo `bg-dorado` — el dorado del manual como
// relleno no tiene problema. Lo que no puede quedarse en #C4942E es la
// etiqueta, que es texto de 12px sobre gris-claro.
const fuente = readFileSync('components/primitives/ParDialogo.tsx', 'utf8');
expect(fuente).toMatch(/text-dorado-texto/);
expect(fuente).not.toMatch(/text-dorado(?!-)/);
});
});
+15
View File
@@ -0,0 +1,15 @@
import { describe, it, expect } from 'vitest';
import { esExterno, propsEnlace } from '../enlaces';
describe('propsEnlace', () => {
it('opens outside links in a new tab without handing over window.opener', () => {
expect(propsEnlace('https://wa.me/525558185802')).toEqual({ target: '_blank', rel: 'noopener noreferrer' });
expect(propsEnlace('https://www.linkedin.com/in/x')).toEqual({ target: '_blank', rel: 'noopener noreferrer' });
});
it('leaves in-page anchors and mailto: in the same tab', () => {
expect(propsEnlace('#contacto')).toEqual({});
expect(propsEnlace('mailto:hola@creeer.com')).toEqual({});
expect(esExterno('mailto:hola@creeer.com')).toBe(false);
});
});
+31
View File
@@ -0,0 +1,31 @@
import { describe, it, expect } from 'vitest';
import config from '../../tailwind.config';
// The scale is a contract, not a preference: the problem this redesign fixes is
// that five elements were tied for largest on the page, and because of that,
// none of them read as largest. Five roles: only one reaches above 4rem.
const escala = () => (config.theme?.extend?.fontSize ?? {}) as Record<string, [string, object]>;
describe('escala tipográfica', () => {
it('define los cinco roles del documento', () => {
expect(Object.keys(escala())).toEqual(
expect.arrayContaining(['display', 'titulo', 'sumario', 'cuerpo', 'rotulo']),
);
});
it('display es el único que llega a 7rem', () => {
const conSieteRem = Object.entries(escala()).filter(([, v]) => v[0].includes('7rem'));
expect(conSieteRem.map(([k]) => k)).toEqual(['display']);
});
it('el cuerpo se compone holgado y el display apretado', () => {
const lh = (rol: string) => (escala()[rol][1] as { lineHeight: string }).lineHeight;
expect(Number(lh('display'))).toBeLessThan(1);
expect(Number(lh('cuerpo'))).toBeGreaterThan(1.6);
});
it('reserva una medida de 68 caracteres para el texto corrido', () => {
const anchos = (config.theme?.extend?.maxWidth ?? {}) as Record<string, string>;
expect(anchos.medida).toBe('68ch');
});
});
+216 -154
View File
@@ -11,160 +11,199 @@ export const brandPillars = {
pillar3: "Crecimiento",
};
export const hero = {
eyebrow: '— tu estratega de crecimiento —',
// Headline broken into segments for emphasis styling
// ── 01 · Apertura ──────────────────────────────────────────────────────────
export const apertura = {
numero: '01',
eyebrow: 'claridad estratégica para tu negocio',
headline: {
line1: 'Tu negocio no necesita',
effort: 'más esfuerzo.', // gold (HV-1) or gold italic (V-a)
acento: 'más esfuerzo.',
line3: 'Necesita',
clarity: 'más claridad.', // underlined gold
cierre: 'más claridad.',
},
cta: 'Define tu siguiente movimiento',
ctaSecundario: 'Ver cómo trabajo',
quien: 'Claridad estratégica para empresarios con tracción que llegaron a su punto de inflexión.',
// Hormozi: Dream Outcome — the hero names the problem; this line names the
// result the reader walks away with. Concrete, time-bound, personal.
dreamOutcome: 'Un plan a 12 meses para que tu negocio crezca sin depender de ti en cada decisión.',
cta: 'Agenda una sesión de claridad — 30 min, sin costo',
ctaSecundario: 'Ver cómo trabajamos',
};
export const fraseAncla = {
// Quote split into parts so the second clause can render in gold.
quote: {
before: 'Las empresas no dejan de crecer por falta de talento;',
accent: 'dejan de crecer por falta de claridad.', // gold
},
};
// ── 02 · Dónde estás ───────────────────────────────────────────────────────
// Los seis puntos y los cuatro dolores son de las diapositivas 2 y 3 del deck
// de la dueña. Verbatim. `respuesta` desaparece: existía para llenar seis
// pantallas fijadas y el recorrido ya no existe.
const PUNTOS = [
{ name: 'Crecer', situacion: 'Ya demostraste que sabes crecer. El siguiente salto no tiene mapa.' },
{ name: 'Consolidarse', situacion: 'Construiste rápido. Escalar así multiplicaría el desorden.' },
{ name: 'Recuperar rentabilidad', situacion: 'Tu esfuerzo dejó de traducirse en resultado.' },
{ name: 'Simplificar', situacion: 'Tu operación se volvió más compleja que tu negocio.' },
{ name: 'Expandirse', situacion: 'Vas a mercados nuevos con el mismo mapa de decisión.' },
{ name: 'Replantear el portafolio', situacion: 'Tienes varios frentes abiertos y ninguno con su lugar claro.' },
];
// The six inflection points, from the dueña's deck "Rumbo Estratégico ·
// Propuesta Socio" (slide 2). The deck states each one in the infinitive
// ("Dar el siguiente salto sin saber por dónde"); here it is stated in the
// second person, the way she reads them aloud herself — "estás atrapado en tu
// compañía", "creciste muy rápido pero quieres expandirte" (call of 31/jul,
// 6:01–7:15). `respuesta` is new copy and still needs her sign-off; 01 and 05
// are the two exchanges she improvised on that call, written out.
//
// `situacion` describes the state of the business. It must not drift into how
// the reader feels — that is `dolores`, one section down, in the client's own
// first-person words, and the two would collapse into each other.
export const puntosInflexion = {
eyebrow: 'el contexto',
title: 'Todos los negocios consolidados llegan a un punto de inflexión',
lead: '¿Te reconoces?',
items: [
{
name: 'Crecer',
situacion: 'Ya demostraste que sabes crecer. El siguiente salto no tiene mapa.',
respuesta: 'Me sumerjo hasta entender tu empresa desde dentro. Sales sabiendo por dónde, no intentándolo por todos lados a la vez.',
},
{
name: 'Consolidarse',
situacion: 'Construiste rápido. Escalar así multiplicaría el desorden.',
respuesta: 'Ordenamos lo que ya existe antes de ponerle más peso encima. Escalas sobre algo que aguanta.',
},
{
name: 'Recuperar rentabilidad',
situacion: 'Tu esfuerzo dejó de traducirse en resultado.',
respuesta: 'Cuestiono lo que todos dan por hecho en tu operación. El margen no siempre está en vender más.',
},
{
name: 'Simplificar',
situacion: 'Tu operación se volvió más compleja que tu negocio.',
respuesta: 'Separo lo que solo tú puedes decidir de lo que la empresa debería resolver sin ti.',
},
{
name: 'Expandirse',
situacion: 'Vas a mercados nuevos con el mismo mapa de decisión.',
respuesta: 'Reviso tu proceso y ubico la oportunidad real antes de mover una ficha. Expandirte deja de ser una apuesta.',
},
{
name: 'Replantear el portafolio',
situacion: 'Tienes varios frentes abiertos y ninguno con su lugar claro.',
respuesta: 'Construimos, juntos, el criterio para decidir qué sigue y qué se deja ir. Por escrito, no en tu cabeza.',
},
],
};
// 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: [
export const dondeEstas = {
numero: '02',
eyebrow: 'dónde estás',
title: 'Tu negocio llegó a un punto de inflexión',
sumario: 'Seis situaciones. Reconoce la tuya.',
puntos: PUNTOS,
etiquetaDolores: 'Y por dentro suena así',
dolores: [
'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 = {
// ── 03 · Qué cambia ────────────────────────────────────────────────────────
export const queCambia = {
numero: '03',
eyebrow: 'qué cambia',
ancla: {
before: 'Las empresas no dejan de crecer por falta de talento;',
accent: 'dejan de crecer por falta de claridad.',
},
// El "después": los dolores nombran el problema; esto pinta el resultado.
// Paga directo el tercer dolor — "Mi empresa no puede crecer si yo no estoy
// en cada decisión".
despues: 'Sabes exactamente dónde invertir, tu equipo ejecuta sin que estés encima, y cada decisión tiene un plan detrás.',
insight: 'No vendemos marketing. Vendemos claridad estratégica.',
// Las tres disciplinas, degradadas de tres tarjetas con icono a una línea de
// texto: el propio deck dice que son los medios, no la promesa.
medios: 'Comunicación, digital y experiencias son las herramientas que usamos. No son lo que vendemos.',
};
// ── 04 · El trabajo ────────────────────────────────────────────────────────
export const elTrabajo = {
numero: '04',
eyebrow: 'el trabajo',
// Hormozi: Name your mechanism. A named process is a system; an unnamed
// process is a list. Systems sell. Lists don't.
metodo: 'Ruta de Claridad',
title: 'La Ruta de Claridad: de tu operación de hoy a tu plan a un año',
sumario: 'Ocho etapas. Cada una deja algo por escrito.',
fases: ['Inmersión', 'Desafiar supuestos', 'Estrategia ejecutable'],
etiquetaEntregables: 'Entregables',
etapas: [
{ n: '01', name: 'Análisis de compañía y FODA', entregables: ['FODA', 'Diagnóstico ejecutivo', 'Prioridades', 'Quick wins'] },
{ n: '02', name: 'Análisis interno', entregables: ['Mapa de capacidades', 'Restricciones del negocio', 'Oportunidades de crecimiento'] },
{ n: '03', name: 'Conocimiento del consumidor', entregables: ['Segmentación', 'Buyer persona', 'Customer journey', 'Necesidades no cubiertas'] },
{ n: '04', name: 'Estrategia de marca', entregables: ['Posicionamiento', 'Arquitectura de marcas', 'Manual estratégico'] },
{ n: '05', name: 'Comunicación y branding', entregables: ['Plataforma de comunicación', 'Campaña anual', 'Estrategia de medios', 'Plan de contenido'] },
{ n: '06', name: 'Marketing y ventas', entregables: ['Dashboard CEO', 'Dashboard comercial', 'Comité de crecimiento'] },
{ n: '07', name: 'Innovación comercial', entregables: ['Roadmap de innovación', 'Go to market', 'Prioridades de innovación'] },
{ n: '08', name: 'One Year Plan', entregables: ['Roadmap a 12 meses', 'Iniciativas prioritarias', 'Gobernanza del crecimiento'], remate: true },
],
};
// ── 05 · Para quién · Quién ────────────────────────────────────────────────
export const paraQuienQuien = {
numero: '05',
eyebrow: 'para quién',
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',
'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',
],
si: {
lead: 'Esto es para ti si…',
criterios: [
'tienes tracción y ya demostraste que sabes crecer',
'trabajar más ya no rinde como antes',
'necesitas claridad para decidir el siguiente movimiento',
'quieres profesionalizar sin perder tu esencia',
],
},
no: {
lead: 'Esto no es para ti si…',
criterios: [
'el negocio está en fase inicial o sin tracción',
'buscas una solución rápida, no un cambio de fondo',
'buscas un curso, no una transformación',
'la empresa está en crisis de supervivencia, no en transición',
],
},
quien: {
eyebrow: 'quiénes estamos detrás',
title: 'Quiénes somos',
cifra: '25+',
cifraPie: 'años construyendo marcas',
bio: '25 años de estrategia detrás de marcas que pasaron de locales a líderes de categoría. Hemos acompañado a más de 40 empresas en retail, servicios y manufactura a encontrar su siguiente movimiento.', // TODO dueña: confirmar cifra y lista de industrias
quote: 'Tu negocio ya demostró que funciona. Ahora necesita un plan que lo lleve más lejos.',
},
};
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.' },
// ── 06 · Empezar ───────────────────────────────────────────────────────────
export const empezar = {
numero: '06',
eyebrow: 'empezar',
title: 'Tu siguiente movimiento empieza con una conversación.',
pasos: [
{ n: '01', name: 'Escríbenos', desc: 'Cuéntanos dónde está tu negocio hoy.' },
{ n: '02', name: 'Sesión de claridad', desc: 'Una conversación para ubicar tu punto de inflexión.' },
{ n: '03', name: 'Plan en mano', desc: 'Sales con claridad y tu siguiente movimiento definido.' },
],
cta: 'Agenda tu sesión de claridad',
whatsappText: 'Hola, quiero agendar una sesión de claridad',
quote: 'Devuélvele claridad al rumbo de tu empresa.',
contacto: {
whatsapp: { label: 'WhatsApp', href: 'https://wa.me/525558185802' },
email: { label: 'Email', href: 'mailto:jazmin.rodgon@gmail.com' },
linkedin: { label: 'LinkedIn', href: 'https://www.linkedin.com/in/jazminrodriguez-jazinta' },
},
legal: '© 2026 creeer',
};
// Practical details of the engagement — what the reader can expect when
// working with creeer. Sits between Mecanismo (the high-level HOW) and
// Disciplinas (the tools). All copy derived from approved sources:
// mecanismo steps, puntosInflexion respuestas, comoEmpezar.
// ⚠️ «Duración» left deliberately open — the dueña hasn't specified a
// timeline. The card invites a conversation instead of committing a number.
export const formaTrabajar = {
eyebrow: 'qué esperar',
title: 'Así es trabajar conmigo',
items: [
{
name: 'Metodología',
desc: 'Inmersión en tu empresa, desafío de lo que todos dan por hecho y construcción conjunta de tu estrategia.',
},
{
name: 'Quién participa',
desc: 'Tú y yo. Sin intermediarios. Trabajo directo con quien toma las decisiones.',
},
{
name: 'De dónde partimos',
desc: 'Tu operación real: números, procesos, equipo. Me sumerjo hasta comprenderla desde dentro.',
},
{
name: 'Qué te llevas',
desc: 'Un diagnóstico claro, tus prioridades ordenadas y una estrategia ejecutable. Por escrito, no en tu cabeza.',
},
{
name: 'Duración',
desc: 'Cada proceso tiene su ritmo. Lo definimos juntos en la sesión de diagnóstico.',
},
],
// ── Alias de compatibilidad ────────────────────────────────────────────────
// Los consumen las diez secciones viejas mientras conviven con las seis
// nuevas. Derivan de los objetos de arriba, nunca duplican sus cadenas, y se
// van enteros en la limpieza final. No añadir usos nuevos.
export const hero = {
eyebrow: `— ${apertura.eyebrow} —`,
headline: {
line1: apertura.headline.line1,
effort: apertura.headline.acento,
line3: apertura.headline.line3,
clarity: apertura.headline.cierre,
},
cta: apertura.cta,
ctaSecundario: apertura.ctaSecundario,
};
// La voz de creeer en el recorrido que sigue montado: cada situación del lector
// recibe su respuesta. El rediseño las retira junto con el recorrido (ver
// `dondeEstas`), pero mientras la sección vieja siga en la página, pregunta y
// respuesta no pueden decir lo mismo. Son las del 31/jul pasadas al plural;
// 01 y 05 las improvisó la dueña en esa llamada. Mismo orden que PUNTOS.
const RESPUESTAS = [
'Nos sumergimos hasta entender tu empresa desde dentro. Sales sabiendo por dónde, no intentándolo por todos lados a la vez.',
'Ordenamos lo que ya existe antes de ponerle más peso encima. Escalas sobre algo que aguanta.',
'Cuestionamos lo que todos dan por hecho en tu operación. El margen no siempre está en vender más.',
'Separamos lo que solo tú puedes decidir de lo que la empresa debería resolver sin ti.',
'Revisamos tu proceso y ubicamos la oportunidad real antes de mover una ficha. Expandirte deja de ser una apuesta.',
'Construimos, juntos, el criterio para decidir qué sigue y qué se deja ir. Por escrito, no en tu cabeza.',
];
export const puntosInflexion = {
eyebrow: 'el contexto',
title: dondeEstas.title,
lead: '¿Te reconoces?',
items: PUNTOS.map((p, i) => ({ ...p, respuesta: RESPUESTAS[i] })),
};
export const dolores = {
eyebrow: 'lo que realmente sientes',
title: 'Los dolores detrás del punto de inflexión',
quotes: dondeEstas.dolores,
remate: dondeEstas.remate,
};
export const disciplinas = {
eyebrow: 'las herramientas',
title: 'Las disciplinas que uso',
title: 'Las disciplinas que usamos',
lead: 'No son la promesa — son los medios.',
items: [
{ name: 'Comunicación', desc: 'Mensaje que alinea a tu equipo y a tu mercado.' },
@@ -173,37 +212,48 @@ export const disciplinas = {
],
};
export const proceso = {
eyebrow: 'el trabajo completo',
title: elTrabajo.title,
lead: elTrabajo.sumario,
fases: elTrabajo.fases,
etiquetaEntregables: elTrabajo.etiquetaEntregables,
etapas: elTrabajo.etapas.map(({ n, name, entregables, remate }) =>
remate ? { n, name, entregables, destacada: true } : { n, name, entregables },
),
};
export const paraQuien = {
title: paraQuienQuien.title,
lead: paraQuienQuien.si.lead,
criteria: paraQuienQuien.si.criterios,
leadNo: paraQuienQuien.no.lead,
criteriaNo: paraQuienQuien.no.criterios,
};
export const estratega = {
// 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',
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.',
eyebrow: { before: 'quiénes estamos detrás de', brand: 'creeer' },
title: paraQuienQuien.quien.title,
bio: paraQuienQuien.quien.bio,
quote: paraQuienQuien.quien.quote,
};
export const comoEmpezar = {
eyebrow: 'cómo empezar',
title: 'Tu siguiente movimiento empieza con una conversación.',
steps: [
{ n: '01', name: 'Escríbeme', desc: 'Cuéntame dónde está tu negocio hoy.' },
{ n: '02', name: 'Sesión de diagnóstico', desc: 'Una conversación para ubicar tu punto de inflexión.' },
{ n: '03', name: 'Plan en mano', desc: 'Sales con claridad y tu siguiente movimiento definido.' },
],
cta: 'Agenda tu sesión de diagnóstico',
whatsappText: 'Hola, quiero agendar una sesión de diagnóstico',
title: empezar.title,
steps: empezar.pasos,
cta: empezar.cta,
whatsappText: empezar.whatsappText,
};
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
},
quote: empezar.quote,
cta: apertura.cta,
contact: empezar.contacto,
};
export const fraseAncla = { quote: queCambia.ancla, despues: queCambia.despues };
export const header = {
cta: 'Hablemos', // short nav CTA → #contacto (the full CTA lives in Hero/Cierre)
};
@@ -219,12 +269,24 @@ export const footer = {
// image was modified — all of them get the navy/gold duotone treatment, and the
// six in `recorrido` are additionally cropped and resized.
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/' },
{ desc: 'Foto: distrito financiero de Riad — B.alotaby', href: 'https://commons.wikimedia.org/wiki/File:Riyadh_Skyline_showing_the_King_Abdullah_Financial_District_(KAFD)_and_the_famous_Kingdom_Tower_.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
{ desc: 'Foto: sala de reuniones — Breather', href: 'https://commons.wikimedia.org/wiki/File:Minimalist_meeting_room_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
{ desc: 'Foto: cosechadora en el trigo — Michael Gäbler', href: 'https://commons.wikimedia.org/wiki/File:The_combine_Claas_Lexion_584_in_the_wheat_harvest.jpg', licencia: 'CC BY-SA 3.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/3.0/' },
{ desc: 'Foto: pantalla de la Bolsa de Fráncfort — Ank Kumar', href: 'https://commons.wikimedia.org/wiki/File:Frankfurt_Stock_Exchange_(Ank_Kumar)_04.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
{ desc: 'Foto: panel de salidas, aeropuerto de Ginebra — Tiia Monto', href: 'https://commons.wikimedia.org/wiki/File:Departure_board_at_Geneva_Airport.jpg', licencia: 'CC BY-SA 3.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/3.0/' },
{ desc: 'Foto: distrito financiero de Boston — Ingfbruno', href: 'https://commons.wikimedia.org/wiki/File:USA-Financial_District.JPG', licencia: 'CC BY-SA 3.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/3.0/' },
{ desc: 'Foto: Torre Mitikah en construcción, Ciudad de México — ProtoplasmaKid', href: 'https://commons.wikimedia.org/wiki/File:Torre_Mitikah_en_construcci%C3%B3n.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
{ desc: 'Foto: oficina — Importaciones a México', href: 'https://commons.wikimedia.org/wiki/File:Importaciones_a_M%C3%A9xico_oficina.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
{ desc: 'Foto: piñas de agave, Tequila, Jalisco — Gzzz', href: 'https://commons.wikimedia.org/wiki/File:Hearts_of_agave_plants_Tequila_Mexico.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
{ desc: 'Foto: Distribuidor Vial San Antonio, Ciudad de México — ProtoplasmaKid', href: 'https://commons.wikimedia.org/wiki/File:Distribuidor_Vial_San_Antonio_2025_32.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
{ desc: 'Foto: puertas de embarque, aeropuerto de Cancún — Vmzp85', href: 'https://commons.wikimedia.org/wiki/File:MMUN006.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
{ desc: 'Foto: skyline de Monterrey — Daniel Escobedo', href: 'https://commons.wikimedia.org/wiki/File:Skyline_de_Monterrey.jpg', licencia: 'CC BY-SA 4.0', licenciaHref: 'https://creativecommons.org/licenses/by-sa/4.0/' },
// Las ocho de `Proceso`, todas CC0 — ninguna obliga a atribuir, pero se
// acreditan igual por coherencia con el resto de la lista. `archivo` ata cada
// crédito a su imagen: hay un test en Proceso que falla si aparece una foto
// en la sección sin entrada aquí.
{ archivo: '/images/proceso/01.jpg', desc: 'Foto: sala de juntas — Benjamin Child', href: 'https://commons.wikimedia.org/wiki/File:Benjamin_Child_2015_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
{ archivo: '/images/proceso/02.jpg', desc: 'Foto: taller — Adi Goldstein', href: 'https://commons.wikimedia.org/wiki/File:The_Workshop_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
{ archivo: '/images/proceso/03.jpg', desc: 'Foto: escaleras concurridas — José Martín Ramírez C', href: 'https://commons.wikimedia.org/wiki/File:Busy_stairs_in_large_building_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
{ archivo: '/images/proceso/04.jpg', desc: 'Foto: tipografía sobre escritorio — rawpixel.com', href: 'https://commons.wikimedia.org/wiki/File:Typography,_coffee_and_roses_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
{ archivo: '/images/proceso/05.jpg', desc: 'Foto: estudio fotográfico — Alexander Dummer', href: 'https://commons.wikimedia.org/wiki/File:Photographic_studio_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
{ archivo: '/images/proceso/06.jpg', desc: 'Foto: tienda de discos — Samuel Dixon', href: 'https://commons.wikimedia.org/wiki/File:Vintage_record_store_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
{ archivo: '/images/proceso/07.jpg', desc: 'Foto: dispositivos sobre la mesa — Alejandro Escamilla', href: 'https://commons.wikimedia.org/wiki/File:Iphone,_macbook_and_smartphone_on_a_table_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
{ archivo: '/images/proceso/08.jpg', desc: 'Foto: planificador mensual — Eric Rothermel', href: 'https://commons.wikimedia.org/wiki/File:Monthly_schedule_(Unsplash).jpg', licencia: 'CC0', licenciaHref: 'https://creativecommons.org/publicdomain/zero/1.0/' },
];
+19
View File
@@ -0,0 +1,19 @@
// Links that leave the site open in a new tab, so the reader never loses the
// page: WhatsApp, LinkedIn, the photo credits. Everything else stays put —
// in-page anchors (#contacto) obviously, and mailto: too, which hands off to
// the mail client and would only leave an empty tab behind.
//
// `rel="noopener noreferrer"`: without noopener the opened page gets a handle
// on this one through window.opener.
export function esExterno(href: string): boolean {
return /^https?:\/\//i.test(href);
}
export function propsEnlace(href: string) {
return esExterno(href) ? ({ target: '_blank', rel: 'noopener noreferrer' } as const) : {};
}
// Screen-reader users get told before the context switch happens (WCAG G201);
// sighted readers see the new tab open.
export const AVISO_PESTANA = 'se abre en otra pestaña';
+1 -1
View File
@@ -64,7 +64,7 @@ export const motionProps = (reduced: boolean) =>
? { initial: 'visible' as const, animate: 'visible' as const, whileInView: undefined, viewport: undefined }
: {};
// Cycles through `count` steps on its own (Mecanismo, ComoEmpezar): each
// Cycles through `count` steps on its own (ComoEmpezar): each
// manual change restarts the dwell timer, hovering pauses it, and it never
// advances at all for reduced-motion users.
export function useAutoStep(count: number, intervalMs = 3500) {