feat(diseño): los diez arreglos de la revisión visual
Revisada la página con Chromium a 1440x900 y 390x844 en nueve alturas de scroll, y verificado cada cambio contra capturas nuevas. - El ≡ deja de leerse como icono de menú. lib/simbolo.ts se anunciaba como single source of truth pero no coincidía con Wordmark.tsx, que traza la marca con las proporciones medidas del Manual: barra de largo 5.919× su grosor, hueco de 2×, extremos de píldora. Ahora se derivan de ahí. De paso, las líneas se meten medio grosor por cada extremo: el remate redondo alargaba el trazo un grosor entero sobre la barra que debía reproducir. - Banda: 12px en escritorio y 7px en móvil vía CSS (14px era el 3,6% de una pantalla de 390px y partía los párrafos), opacidad al 18%, y muelle de 90/30 a 210/40 — sobreamortiguado tardaba ~2s en asentar y la punta se arrastraba a media pantalla. El punto suelto que aparecía centrado arriba al cargar era el remate redondo pintándose en el origen con longitud cero: ahora la opacidad sigue al avance. - Ritmo vertical un 25-30% más apretado en las diez secciones. La página pasa de 8.061 a 7.457px sin tocar contenido. - Números 01-06: de marca de agua de 48px al 6% (que chocaba con títulos y reglas) a índice dorado sentado sobre la regla. - Hero: los dos CTA al mismo ancho en móvil, el patrón de puntos fuera del logotipo, y la secuencia visible en todos los tamaños — oculta bajo lg dejaba el hero del móvil sin ninguna pieza gráfica. La convergencia de la variante "hero" se cierra más para que se lea como líneas buscando su eje y no como un garabato tachado. - Estratega: fuera la retícula de puntos del retrato, que parecía el recuadro de imagen rota del navegador; la etiqueta del 30+ cabía en menos de lo que mide "construyendo" y salía en tres líneas. - El nudo de Dolores, más pequeño y con algo más de contraste. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
174fcb855a
commit
fa86605b39
15 files changed
+135
-52
No files matched your search
@@ -3,7 +3,7 @@
|
||||
import { useState } from 'react';
|
||||
import { motion, useMotionValueEvent, useScroll, useSpring, useTransform } from 'framer-motion';
|
||||
import { usePrefersReducedMotion } from '@/lib/motion';
|
||||
import { BARRAS_SIMBOLO, VIEWBOX_SIMBOLO } from '@/lib/simbolo';
|
||||
import { BARRAS_SIMBOLO, RADIO_BARRA, VIEWBOX_SIMBOLO } from '@/lib/simbolo';
|
||||
|
||||
// The gold spine of the page: one continuous line down the vertical centre,
|
||||
// drawing itself as the reader scrolls and resolving into the ≡ at the close.
|
||||
@@ -23,8 +23,12 @@ const ALTO = 1200;
|
||||
const PASO = ALTO / TRAMOS;
|
||||
|
||||
// A band, not a thread. Held in real pixels by vector-effect below, so it never
|
||||
// again collapses to a hairline when the viewBox scale changes.
|
||||
const GROSOR = 14;
|
||||
// again collapses to a hairline when the viewBox scale changes. The attribute
|
||||
// is the desktop weight and the SSR fallback; GROSORES overrides it in CSS,
|
||||
// because 14px is 3.6% of a 390px screen and cut straight through the body
|
||||
// copy there. CSS stroke-width wins over the presentation attribute.
|
||||
const GROSOR = 12;
|
||||
const GROSORES = '[stroke-width:7px] md:[stroke-width:12px]';
|
||||
|
||||
// Four long swells rather than one page-long curve or ten short ones. Stretched
|
||||
// over the whole document with preserveAspectRatio="none", a single swell reads
|
||||
@@ -48,7 +52,13 @@ export function HiloConductor({ className = '' }: { className?: string }) {
|
||||
const reduced = usePrefersReducedMotion();
|
||||
const { scrollYProgress } = useScroll();
|
||||
const recorrido = useTransform(scrollYProgress, [0, FIN], [0, 1], { clamp: true });
|
||||
const avance = useSpring(recorrido, { stiffness: 90, damping: 30, restDelta: 0.001 });
|
||||
// Stiff enough to keep up with the reader. At 90/30 the spring is heavily
|
||||
// overdamped and takes some two seconds to settle, so the leading edge
|
||||
// visibly trailed behind the scroll and read as an amputated stub.
|
||||
const avance = useSpring(recorrido, { stiffness: 210, damping: 40, restDelta: 0.001 });
|
||||
// At rest the round cap still paints its own dot at the path's origin, which
|
||||
// landed as a stray blob dead centre at the top of the page on first load.
|
||||
const aparicion = useTransform(avance, [0, 0.015], [0, 1]);
|
||||
|
||||
const [dibujado, setDibujado] = useState(false);
|
||||
useMotionValueEvent(avance, 'change', (v) => {
|
||||
@@ -65,6 +75,7 @@ export function HiloConductor({ className = '' }: { className?: string }) {
|
||||
const trazo = reduced ? (
|
||||
<path
|
||||
d={TRAZO}
|
||||
className={GROSORES}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={GROSOR}
|
||||
@@ -74,12 +85,13 @@ export function HiloConductor({ className = '' }: { className?: string }) {
|
||||
) : (
|
||||
<motion.path
|
||||
d={TRAZO}
|
||||
className={GROSORES}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={GROSOR}
|
||||
strokeLinecap="round"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
style={{ pathLength: avance }}
|
||||
style={{ pathLength: avance, opacity: aparicion }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -93,7 +105,7 @@ export function HiloConductor({ className = '' }: { className?: string }) {
|
||||
auto` resolves from the width (1152 → 13824px) and silently wins
|
||||
over `bottom`, adding ~6000px of blank scroll below the footer. */}
|
||||
<svg
|
||||
className="absolute top-0 left-1/2 h-[calc(100%-5rem)] w-full -translate-x-1/2 text-dorado/25"
|
||||
className="absolute top-0 left-1/2 h-[calc(100%-5rem)] w-full -translate-x-1/2 text-dorado/[0.18]"
|
||||
viewBox={`0 0 100 ${ALTO}`}
|
||||
preserveAspectRatio="none"
|
||||
aria-hidden="true"
|
||||
@@ -105,19 +117,19 @@ export function HiloConductor({ className = '' }: { className?: string }) {
|
||||
the wordmark's ≡, never a lookalike. Uniformly scaled in its own
|
||||
svg, because the band's stretch would flatten the bars. */}
|
||||
<svg
|
||||
className="absolute bottom-4 left-1/2 w-24 -translate-x-1/2 text-dorado"
|
||||
className="absolute bottom-4 left-1/2 w-20 -translate-x-1/2 text-dorado"
|
||||
viewBox={VIEWBOX_SIMBOLO}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
>
|
||||
{BARRAS_SIMBOLO.map((barra, i) =>
|
||||
reduced ? (
|
||||
<rect key={i} {...barra} rx={1.5} fill="currentColor" />
|
||||
<rect key={i} {...barra} rx={RADIO_BARRA} fill="currentColor" />
|
||||
) : (
|
||||
<motion.rect
|
||||
key={i}
|
||||
{...barra}
|
||||
rx={1.5}
|
||||
rx={RADIO_BARRA}
|
||||
fill="currentColor"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: resuelto ? 1 : 0 }}
|
||||
|
||||
@@ -63,12 +63,15 @@ export function SecuenciaClaridad({
|
||||
if (!completa) {
|
||||
const eje = linea.y1;
|
||||
const d = DISPERSAS[i];
|
||||
// Pulled a good way further in than it used to be: at a third of the way
|
||||
// the tangle still read as a scratched-out scribble instead of as lines
|
||||
// finding their axis. It must arrive close to aligned and stop short.
|
||||
return {
|
||||
x1: d.x1 + (26 - d.x1) * 0.35,
|
||||
y1: d.y1 + (eje - d.y1) * 0.55,
|
||||
x2: d.x2 + (38 - d.x2) * 0.35,
|
||||
y2: d.y2 + (eje - d.y2) * 0.55,
|
||||
opacity: sobrevive ? 0.95 : 0.3,
|
||||
x1: d.x1 + (26 - d.x1) * 0.5,
|
||||
y1: d.y1 + (eje - d.y1) * 0.7,
|
||||
x2: d.x2 + (38 - d.x2) * 0.5,
|
||||
y2: d.y2 + (eje - d.y2) * 0.7,
|
||||
opacity: sobrevive ? 0.95 : 0.28,
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -108,11 +108,14 @@ describe('<HiloConductor />', () => {
|
||||
|
||||
it('reads as a band, not as a hairline', () => {
|
||||
const { container } = render(<HiloConductor />);
|
||||
const grosor = Number(container.querySelector('path')?.getAttribute('stroke-width'));
|
||||
const path = container.querySelector('path');
|
||||
// Twice regressed into a thread nobody could see. The band is the page's
|
||||
// spine and has to carry weight; the text stays legible because it sits a
|
||||
// stacking level above, not because the band is faint.
|
||||
expect(grosor).toBeGreaterThanOrEqual(10);
|
||||
// stacking level above, not because the band is faint. The attribute is the
|
||||
// desktop weight, the classes narrow it on phones where 12px is 3% of the
|
||||
// screen — CSS stroke-width beats the presentation attribute.
|
||||
expect(Number(path?.getAttribute('stroke-width'))).toBeGreaterThanOrEqual(10);
|
||||
expect(path?.getAttribute('class') ?? '').toMatch(/md:\[stroke-width:/);
|
||||
});
|
||||
|
||||
it('sweeps wide enough across the column to read as a route', () => {
|
||||
|
||||
@@ -13,7 +13,30 @@ describe('geometría del ≡', () => {
|
||||
expect(izquierdas.size).toBe(1);
|
||||
});
|
||||
|
||||
it('derives stroke lines through each bar centre', () => {
|
||||
expect(LINEAS_SIMBOLO.map((l) => l.y1)).toEqual([17, 32, 47]);
|
||||
it('holds the Manual proportions that make the mark read as an E', () => {
|
||||
const [b1, b2, b3] = BARRAS_SIMBOLO;
|
||||
// Measured off the Manual and traced in Wordmark.tsx. Stubbier bars or
|
||||
// tighter gaps than these and the ≡ reads as a hamburger menu icon.
|
||||
expect(b1.width / b1.height).toBeCloseTo(5.919, 3);
|
||||
expect((b2.y - (b1.y + b1.height)) / b1.height).toBeCloseTo(2, 6);
|
||||
expect(b3.y - b2.y).toBeCloseTo(b2.y - b1.y, 6);
|
||||
});
|
||||
|
||||
it('centres the mark in its own viewBox', () => {
|
||||
const [b1, , b3] = BARRAS_SIMBOLO;
|
||||
expect(b1.y).toBeCloseTo(64 - (b3.y + b3.height), 6);
|
||||
expect(b1.x).toBeCloseTo(64 - (b1.x + b1.width), 6);
|
||||
});
|
||||
|
||||
it('reproduces each bar exactly when drawn as a round-capped stroke', () => {
|
||||
BARRAS_SIMBOLO.forEach((barra, i) => {
|
||||
const linea = LINEAS_SIMBOLO[i];
|
||||
// A round cap reaches half the stroke width past its endpoint, so an
|
||||
// un-inset line would draw a pill a full thickness longer than the bar.
|
||||
expect(linea.x1 - linea.grosor / 2).toBeCloseTo(barra.x, 6);
|
||||
expect(linea.x2 + linea.grosor / 2).toBeCloseTo(barra.x + barra.width, 6);
|
||||
expect(linea.y1).toBeCloseTo(barra.y + barra.height / 2, 6);
|
||||
expect(linea.y2).toBeCloseTo(linea.y1, 6);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@ export function Cierre() {
|
||||
return (
|
||||
<section id="contacto" className="bg-azul-profundo text-white scroll-mt-24 relative overflow-hidden">
|
||||
<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">
|
||||
<Container className="relative py-16 md:py-24 text-center">
|
||||
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||
className="flex flex-col items-center">
|
||||
<motion.div variants={fadeInUp}><Wordmark size="lg" onDark showTagline /></motion.div>
|
||||
|
||||
@@ -19,7 +19,7 @@ export function ComoEmpezar() {
|
||||
const desplazamiento = useTransform(scrollYProgress, [0, 1], [20, -20]);
|
||||
return (
|
||||
<section className="bg-white">
|
||||
<Container className="py-24 md:py-32 grid gap-12 md:grid-cols-[1fr_auto] md:items-center">
|
||||
<Container className="py-16 md:py-24 grid gap-12 md:grid-cols-[1fr_auto] md:items-center">
|
||||
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}>
|
||||
<motion.p variants={fadeInUp} className="text-xs tracking-[0.3em] uppercase font-black text-dorado">{comoEmpezar.eyebrow}</motion.p>
|
||||
<motion.h2 variants={fadeInUp} className="mt-4 font-sans font-black text-seccion text-azul-profundo max-w-xl">{comoEmpezar.title}</motion.h2>
|
||||
|
||||
@@ -13,7 +13,7 @@ export function Disciplinas() {
|
||||
const reduced = usePrefersReducedMotion();
|
||||
return (
|
||||
<section className="bg-white">
|
||||
<Container className="py-24 md:py-32">
|
||||
<Container className="py-16 md:py-24">
|
||||
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||
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)}
|
||||
|
||||
@@ -11,8 +11,11 @@ export function Dolores() {
|
||||
const reduced = usePrefersReducedMotion();
|
||||
return (
|
||||
<section className="bg-white relative overflow-hidden">
|
||||
<IlustracionNudo className="pointer-events-none select-none absolute top-0 left-1/2 -translate-x-1/2 w-64 md:w-[28rem] text-azul-profundo/[0.08]" />
|
||||
<Container className="relative py-24 md:py-32">
|
||||
{/* Smaller and a touch stronger than it was: sprawling at 8% across the
|
||||
whole title it read as a smudge over the headline rather than as a
|
||||
knot. Stays centred so the conductor thread runs through it. */}
|
||||
<IlustracionNudo className="pointer-events-none select-none absolute top-0 left-1/2 -translate-x-1/2 w-56 md:w-80 text-azul-profundo/[0.11]" />
|
||||
<Container className="relative py-16 md:py-24">
|
||||
<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)}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
import { motion } from 'framer-motion';
|
||||
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 { MarcoRetrato } from '@/components/primitives/MarcoRetrato';
|
||||
@@ -12,16 +11,20 @@ export function Estratega() {
|
||||
const reduced = usePrefersReducedMotion();
|
||||
return (
|
||||
<section className="bg-gris-claro">
|
||||
<Container className="py-24 md:py-32">
|
||||
<Container className="py-16 md:py-24">
|
||||
<motion.div variants={staggerContainer} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||
className="grid gap-12 md:grid-cols-[auto_1fr] md:items-center">
|
||||
{/* TODO dueña: real portrait. The frame is built to take it as-is —
|
||||
drop an <Image fill /> in place of the placeholder below. */}
|
||||
<motion.div variants={fadeInUp}>
|
||||
<MarcoRetrato>
|
||||
{/* Reads as a brand plate, not as a broken image. The dot grid
|
||||
that used to sit behind the wordmark looked like the
|
||||
checkerboard a browser shows for a picture that failed to
|
||||
load; the ≡ alone, meanwhile, reads as a hamburger menu once
|
||||
it stands by itself in a circle. The wordmark is neither. */}
|
||||
<div className="w-full h-full bg-azul-profundo grid place-items-center" role="img" aria-label="Retrato de la estratega — pendiente">
|
||||
<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>
|
||||
<Wordmark size="md" onDark />
|
||||
</div>
|
||||
</MarcoRetrato>
|
||||
</motion.div>
|
||||
@@ -32,7 +35,9 @@ export function Estratega() {
|
||||
<motion.h2 variants={fadeInUp} className="mt-4 font-sans font-black text-seccion text-azul-profundo">{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>
|
||||
{/* 8rem was narrower than the word "construyendo" itself, which
|
||||
broke the label into three ragged lines beside the number. */}
|
||||
<p className="text-xs uppercase tracking-[0.14em] text-gris-medio font-semibold max-w-[12rem] leading-snug">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.blockquote variants={fadeInUp} className="mt-6 font-sans italic text-xl md:text-2xl text-azul-profundo border-l-4 border-dorado pl-4">
|
||||
|
||||
@@ -11,7 +11,7 @@ export function FraseAncla() {
|
||||
const reduced = usePrefersReducedMotion();
|
||||
return (
|
||||
<section className="bg-azul-profundo relative overflow-hidden">
|
||||
<Container className="relative py-28 md:py-40 text-center max-w-5xl">
|
||||
<Container className="relative py-20 md:py-28 text-center max-w-5xl">
|
||||
<motion.div variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||
className="flex justify-center">
|
||||
{/* Payoff for the tension the hero leaves open: here the lines do
|
||||
|
||||
@@ -23,10 +23,13 @@ export function Hero({ variant = 'editorial' }: { variant?: Variant }) {
|
||||
|
||||
return (
|
||||
<section className="bg-white relative overflow-hidden">
|
||||
<FondoPuntos className="top-24 -right-16 w-72 md:w-[28rem] text-azul-profundo/10" interactivo />
|
||||
{/* Kept clear of the wordmark on narrow screens: at 390px the md field
|
||||
reached back across the logotype and the tagline, so the pattern read
|
||||
as collision rather than texture. */}
|
||||
<FondoPuntos className="top-80 -right-20 w-56 md:top-24 md:-right-16 md:w-[28rem] text-azul-profundo/10" interactivo />
|
||||
<FondoPuntos className="bottom-16 -left-20 w-64 md:w-96 text-dorado/20" distance={28} rows={2} />
|
||||
<Container className="relative min-h-[88vh] flex items-center py-24">
|
||||
<div className="grid gap-14 lg:grid-cols-[1.1fr_0.9fr] lg:items-center w-full">
|
||||
<Container className="relative min-h-[80vh] lg:min-h-[88vh] flex items-center py-16 md:py-24">
|
||||
<div className="grid gap-8 lg:gap-14 lg:grid-cols-[1.1fr_0.9fr] lg:items-center w-full">
|
||||
<motion.div variants={staggerContainer} initial="hidden" animate="visible" {...motionProps(reduced)}
|
||||
className="min-w-0 flex flex-col items-center text-center lg:items-start lg:text-left">
|
||||
<motion.div variants={fadeInUp}><Wordmark size="xl" showTagline /></motion.div>
|
||||
@@ -44,16 +47,21 @@ export function Hero({ variant = 'editorial' }: { variant?: Variant }) {
|
||||
]}
|
||||
/>
|
||||
</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" />
|
||||
{/* Full width while stacked: at their natural widths the two CTAs
|
||||
came out ragged and unequal, which read as an accident rather
|
||||
than as hierarchy. */}
|
||||
<motion.div variants={fadeInUp} className="mt-10 w-full flex flex-col md:flex-row items-stretch md:items-center md:w-auto gap-4">
|
||||
<CTA href="#contacto" label={hero.cta} variant="light" className="text-center md:whitespace-nowrap" />
|
||||
<CTA href="#mecanismo" label={hero.ctaSecundario} variant="ghost" className="text-center md:whitespace-nowrap" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
{/* Complexity → focus, left unresolved: the hero poses the tension
|
||||
and FraseAncla pays it off by resolving into the ≡. */}
|
||||
and FraseAncla pays it off by resolving into the ≡. Shown on every
|
||||
breakpoint — hidden below lg it left the phone hero with no piece
|
||||
of the brand's own graphic language at all. */}
|
||||
<motion.div variants={fadeInUp} initial="hidden" animate="visible" {...motionProps(reduced)}
|
||||
className="hidden lg:block min-w-0">
|
||||
<SecuenciaClaridad variant="hero" className="w-full max-w-md mx-auto text-azul-profundo" />
|
||||
className="min-w-0">
|
||||
<SecuenciaClaridad variant="hero" className="w-32 lg:w-full max-w-md mx-auto text-azul-profundo" />
|
||||
</motion.div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function Mecanismo() {
|
||||
>
|
||||
<FondoFoto src="/images/mecanismo-bg.jpg" className="inset-0 opacity-25" wash="bg-azul-profundo/80" />
|
||||
</motion.div>
|
||||
<Container className="relative py-24 md:py-32">
|
||||
<Container className="relative py-16 md:py-24">
|
||||
<motion.p variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||
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)}
|
||||
|
||||
@@ -10,7 +10,7 @@ export function ParaQuien() {
|
||||
const reduced = usePrefersReducedMotion();
|
||||
return (
|
||||
<section className="bg-white relative overflow-hidden">
|
||||
<Container className="relative pt-24 md:pt-32 pb-12 md:pb-16 md:flex md:items-center md:justify-between md:gap-12">
|
||||
<Container className="relative pt-16 md:pt-24 pb-8 md:pb-12 md:flex md:items-center md:justify-between md:gap-12">
|
||||
<motion.h2 variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||
className="font-sans font-black text-seccion text-azul-profundo max-w-3xl">{paraQuien.title}</motion.h2>
|
||||
<motion.div variants={fadeInUp} initial="hidden" whileInView="visible" viewport={viewportOnce} {...motionProps(reduced)}
|
||||
|
||||
@@ -12,7 +12,10 @@ import { fadeInUp, staggerContainer, viewportOnce, usePrefersReducedMotion, moti
|
||||
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">
|
||||
{/* An index, not a watermark. At 48px and 6% opacity these collided with
|
||||
the titles and with the gold rule while carrying no contrast of their
|
||||
own; sat on the rule they encode the sequence and stay out of the way. */}
|
||||
<span aria-hidden="true" className="pointer-events-none absolute -top-1.5 right-0 bg-gris-claro pl-2 font-sans font-black text-xs tracking-[0.2em] text-dorado leading-none select-none">
|
||||
{String(n).padStart(2, '0')}
|
||||
</span>
|
||||
<h3 className="relative font-sans font-bold text-xl text-azul-profundo">{name}</h3>
|
||||
@@ -26,7 +29,7 @@ export function PuntosInflexion() {
|
||||
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">
|
||||
<Container className="relative py-16 md:py-24">
|
||||
<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)}
|
||||
|
||||
+32
-9
@@ -2,21 +2,44 @@
|
||||
// mark reads from here — HiloConductor and SecuenciaClaridad resolve into it,
|
||||
// the former with <rect>, the latter with <line> — so the symbol they produce
|
||||
// is provably the same one and not a lookalike.
|
||||
//
|
||||
// The proportions are the Manual de Identidad Visual's, already measured and
|
||||
// traced in Wordmark.tsx: bar length = 5.919 × thickness, gap = 2 × thickness,
|
||||
// ends fully rounded. Three bars and two gaps make the mark exactly seven
|
||||
// thicknesses tall. These are not cosmetic: with stubby bars and tight gaps the
|
||||
// mark stops reading as an E — the owner's 28/jul instruction — and starts
|
||||
// reading as a hamburger menu icon.
|
||||
|
||||
export const VIEWBOX_SIMBOLO = '0 0 64 64';
|
||||
|
||||
export const BARRAS_SIMBOLO = [
|
||||
{ x: 13, y: 13, width: 38, height: 8 },
|
||||
{ x: 13, y: 28, width: 38, height: 8 },
|
||||
{ x: 13, y: 43, width: 38, height: 8 },
|
||||
] as const;
|
||||
const LADO = 64;
|
||||
const LARGO_POR_GROSOR = 5.919;
|
||||
const HUECO_POR_GROSOR = 2;
|
||||
|
||||
// Same three bars as strokes: a horizontal line through each bar's centre,
|
||||
// with the bar's height as stroke width.
|
||||
export const GROSOR_BARRA = 8;
|
||||
export const RADIO_BARRA = GROSOR_BARRA / 2;
|
||||
|
||||
const LARGO_BARRA = GROSOR_BARRA * LARGO_POR_GROSOR;
|
||||
const HUECO = GROSOR_BARRA * HUECO_POR_GROSOR;
|
||||
const PASO = GROSOR_BARRA + HUECO;
|
||||
|
||||
const IZQUIERDA = (LADO - LARGO_BARRA) / 2;
|
||||
const ARRIBA = (LADO - (3 * GROSOR_BARRA + 2 * HUECO)) / 2;
|
||||
|
||||
export const BARRAS_SIMBOLO = [0, 1, 2].map((i) => ({
|
||||
x: IZQUIERDA,
|
||||
y: ARRIBA + i * PASO,
|
||||
width: LARGO_BARRA,
|
||||
height: GROSOR_BARRA,
|
||||
}));
|
||||
|
||||
// The same three bars as strokes. A round cap reaches half the stroke width
|
||||
// past its endpoint, so the ends are inset by exactly that much — otherwise the
|
||||
// drawn line overshoots by a full thickness the rect it has to reproduce.
|
||||
export const LINEAS_SIMBOLO = BARRAS_SIMBOLO.map((b) => ({
|
||||
x1: b.x,
|
||||
x1: b.x + b.height / 2,
|
||||
y1: b.y + b.height / 2,
|
||||
x2: b.x + b.width,
|
||||
x2: b.x + b.width - b.height / 2,
|
||||
y2: b.y + b.height / 2,
|
||||
grosor: b.height,
|
||||
}));
|
||||
Reference in new issue
Block a user