/* ============================================================
   CONNECTOR — design system
   Negro / blanco / azul eléctrico. Clash Display + Archivo.
   Tipografía fluida con clamp(). Mobile-first.
   ============================================================ */
/* ---------- tipografía de marca (display / títulos) — Clash Display ---------- */
@font-face { font-family:'Clash Display'; font-weight:400; font-style:normal; font-display:swap;
  src:url('../fonts/clash-400.woff2') format('woff2'); }
@font-face { font-family:'Clash Display'; font-weight:500; font-style:normal; font-display:swap;
  src:url('../fonts/clash-500.woff2') format('woff2'); }
@font-face { font-family:'Clash Display'; font-weight:600; font-style:normal; font-display:swap;
  src:url('../fonts/clash-600.woff2') format('woff2'); }
@font-face { font-family:'Clash Display'; font-weight:700; font-style:normal; font-display:swap;
  src:url('../fonts/clash-700.woff2') format('woff2'); }
/* Archivo AUTO-HOSPEDADA (antes venía de fonts.googleapis.com y bloqueaba el render
   1.7 s: hoja externa + DNS + TLS a dos dominios). Es fuente VARIABLE, un solo
   archivo cubre 400-700. unicode-range: el navegador sólo baja el subset que necesita. */
@font-face { font-family:'Archivo'; font-weight:400 700; font-style:normal; font-display:swap;
  src:url('../fonts/archivo-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD; }
@font-face { font-family:'Archivo'; font-weight:400 700; font-style:normal; font-display:swap;
  src:url('../fonts/archivo-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF; }
:root {
  /* Negro ENTINTADO, no #000 puro. Era el único valor del sistema sin entintar:
     --surface (#0b0c12) y --line (#262a38) ya van hacia el azul-noche, así que sobre
     un fondo puro las tarjetas casi no se despegaban. Con el tinte, la misma
     superficie se lee elevada sin tocar ni un componente.
     ⚠️ NO cambiar los #000 de las reglas `mask`/`-webkit-mask`: ahí el negro no es
     color, es "opaco" en el canal de máscara. Ni los `color:#000` sobre botón blanco,
     que son texto y quieren el contraste máximo. */
  --bg: #05060a;
  --fg: #fff;
  --mut: #8f8f97;              /* texto secundario (unifica #9a9a9a / #777) */
  --txt-soft: #dcdcdc;         /* párrafo suave (unifica #e6e6e6 / #cfcfcf / #d5d5d5 / #eee) */
  /* ===== SUPERFICIES (fondos y bordes de cards/paneles) ===== */
  --surface: #0b0c12;          /* fondo de card — azul-noche, como la consola del mapa */
  --surface-2: #0f1018;        /* card en hover / elevado */
  --line: #1e1e1e;             /* borde base */
  --line-2: #33384f;           /* borde en hover — azulado, familia del mapa */
  /* ===== AZUL DE MARCA — familia del mapa (unifica #2323ff/#4a5cff/#8b97ff…) ===== */
  --acc: #4a5cff;              /* azul primario */
  --acc-lt: #8b97ff;           /* azul claro — labels, acentos, texto sobre azul */
  --acc-dk: #3346ff;           /* azul hover de botones */
  --acc-tint: rgba(74,92,255,.12);   /* fondo tenue azul */
  --acc-glow: rgba(74,92,255,.40);   /* resplandor del acento */
  --cyan: #3da9ff;             /* legacy — focus/links */
  /* ===== RADIOS ===== */
  --r-sm: 8px;                 /* badges, chips pequeños */
  --r-md: 12px;                /* media, formularios */
  --r-lg: 18px;                /* cards */
  --r-xl: 22px;                /* paneles grandes (consola de sedes) */
  --r-pill: 999px;
  --font-d: 'Clash Display', 'Archivo', sans-serif;
  --font-b: 'Archivo', -apple-system, sans-serif;
  --pad: clamp(20px, 4vw, 64px);
  /* ===== ESCALA DE ESPACIO VERTICAL =====
     No existía: --pad era el único token y era horizontal, así que cada sección se
     inventó su propio clamp. Convivían SIETE ritmos (0/43/65/72/86px de relleno) y
     los huecos entre secciones iban de 0px a 172px sin patrón.
     Razón ~1.55 entre pasos, como la escala tipográfica: pasos que se distinguen. */
  --sp-xs: clamp(12px, 1.4vw, 18px);
  --sp-sm: clamp(20px, 2.4vw, 30px);
  --sp-md: clamp(32px, 3.8vw, 48px);
  --sp-lg: clamp(52px, 6.2vw, 78px);
  --sp-xl: clamp(80px, 9.5vw, 124px);
  --sp-2xl: clamp(100px, 14.5vw, 196px);
  /* Ritmo ENTRE secciones: tres tiers con intención, no un valor único.
     El espacio ES jerarquía — el respiro grande anuncia el momento grande. */
  /* Los tres tiers subieron UN PASO de la escala (jul 2026): el ritmo se sentía
     apretado. Antes md/lg/xl → 48/78/124px a 1440. Ahora lg/xl/2xl → 78/124/196px.
     Se mueven los TIERS, no la escala: --sp-* sigue sirviendo para espaciados
     internos (.bigsay p, .empty-note, .pbody) que no debían crecer. */
  --sec-tight: var(--sp-lg);   /* continuación de lo anterior (los logos bajo Clientes) */
  --sec-base:  var(--sp-xl);   /* ritmo normal */
  --sec-open:  var(--sp-2xl);  /* antes de un momento grande (video, anclados, CTA) */

  /* MOVIMIENTO: antes 12 duraciones y 25 transiciones sin curva declarada (caían
     en el `ease` del navegador, que arranca lento). Ahora 4 tiempos y UNA curva. */
  --m-fast: .16s;
  --m-base: .30s;
  --m-mid:  .45s;
  --m-slow: .60s;
  --e-out:   cubic-bezier(.22,1,.36,1);   /* LA curva del sitio: salida fuerte */
  --e-inout: cubic-bezier(.65,0,.35,1);   /* ida y vuelta: nav y menú */
  /* ===== RITMO DE SCROLL DE LO ANCLADO =====
     Cuánto scroll ocupa CADA elemento. Antes: 560vh la rueda, 250vh testimonios y
     los premios medidos por JS — tres números sin relación, y el mismo gesto avanzaba
     a 46 / 74 / 50 vh por elemento. Ahora sale del NÚMERO de elementos (--n lo pone
     PHP), así que se adapta solo al agregar o quitar contenido desde el admin. */
  --scroll-item: 46svh;        /* elementos que COMPARTEN pantalla: rueda, testimonios */
  --scroll-item-m: 26svh;      /* móvil: sólo RESPALDO del riel (el JS mide el recorrido real) */
  /* escala tipográfica de CUERPO (Archivo) */
  --t-xs: clamp(11px, .8vw, 13px);
  --t-sm: clamp(13px, 1vw, 15px);
  --t-md: clamp(15px, 1.2vw, 18px);
  --t-lg: clamp(20px, 2.4vw, 34px);
  /* ===== SISTEMA TIPOGRÁFICO ===== */
  /* Títulos (Clash Display, 700). Jerarquía: h1 > h2 > medio > tarjeta > mini */
  --d-1: clamp(40px, 7vw, 100px);     /* H1 · título de página / CTA / contadores */
  --d-2: clamp(28px, 4vw, 56px);      /* H2 · título de sección */
  --d-3: clamp(21px, 2.2vw, 32px);    /* display medio · footer, texto de logos, vacantes */
  --d-4: clamp(18px, 1.7vw, 25px);    /* H3 · título de tarjeta */
  --d-5: clamp(14px, 1.3vw, 18px);    /* mini display · nav, año de showcase */
  --d-mega: clamp(46px, 9.5vw, 128px); /* marquees decorativos */
  /* Párrafos (Archivo). Lead = párrafo prominente; body = estándar */
  --p-lead: clamp(19px, 1.7vw, 26px); /* intros y párrafos destacados (filosofía, etc.) */
  --p-body: var(--t-md);              /* párrafo estándar 15-18 */
}
* { margin:0; padding:0; box-sizing:border-box; }
html { scroll-behavior:auto; }
html.lenis, html.lenis body { height:auto; }
.lenis.lenis-smooth { scroll-behavior:auto !important; }
body {
  background:var(--bg); color:var(--fg);
  font:400 var(--t-md)/1.55 var(--font-b);
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
a { color:inherit; text-decoration:none; }
img { max-width:100%; display:block; }
.label { font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.14em; text-transform:uppercase; color:var(--mut); }

/* ---------- cursor contextual (solo desktop con mouse) ---------- */
#cursor { display:none; }
@media (hover:hover) and (pointer:fine) {
  /* GSAP es el ÚNICO dueño de transform (posición y escala). Transicionar transform
     aquí lo suavizaba una segunda vez sobre cada frame de GSAP: por eso se sentía
     trabado. Aquí sólo van propiedades que GSAP no toca. */
  #cursor { position:fixed; top:0; left:0; z-index:200; pointer-events:none;
    display:grid; place-items:center; opacity:0;
    min-width:14px; height:14px; padding:0; border-radius:999px;
    border:1.5px solid rgba(255,255,255,.6); background:transparent;
    transform-origin:center; will-change:transform, opacity;
    transition:opacity var(--m-base) var(--e-out),background var(--m-base) var(--e-out),border-color var(--m-base) var(--e-out); }
  /* el tamaño cambia UNA vez al entrar (no se transiciona: min-width/height/padding
     son propiedades de layout y animarlas cuesta un reflow por frame).
     OJO con estos números: antes GSAP cacheaba el scale(.4) del CSS y lo reescribía
     inline cada frame, así que .ring/.pill se dibujaban al 40% de lo declarado
     (64px se veían 25.6px; padding 42/60 se veía 17/24). Ahora el scale llega a 1 de
     verdad, así que estos valores son los REALES en pantalla. */
  #cursor.ring { opacity:1; min-width:30px; height:30px; }
  /* v279: la pastilla del cursor pasa a ROJO con letra blanca (antes blanca con letra
     negra). Va `--red-deep` y no `--red`: sobre rojo pleno el blanco se queda en 4.38:1
     y no llega al mínimo; con --red-deep da 4.61:1. Misma razón que la pastilla del
     número en las tarjetas. */
  #cursor.pill { opacity:1; min-width:auto; height:auto; padding:14px 24px; background:var(--red-deep); border-color:var(--red-deep); }
  #cursorLabel { display:none; color:#fff; font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.06em;
    text-transform:uppercase; white-space:nowrap; }
  #cursor.pill #cursorLabel { display:block; }
  /* dentro de zonas con cursor propio ocultamos el nativo */
  [data-cursor], [data-cursor] * { cursor:none; }
}

/* ---------- nav ---------- */
/* nav SIN mix-blend-mode: la mezcla lo hacía verse "detrás" de imágenes/objetos.
   Texto blanco sólido + el degradado de .nav::before detrás garantiza legibilidad. */
.nav { position:fixed; inset:0 0 auto 0; z-index:100; display:flex; justify-content:space-between; align-items:center;
  /* 18px y no 28: el nav medía 92px y se comía el 10% de la pantalla en un portátil.
     Con este relleno queda en 72px en TODOS los anchos (36px de contenido en
     escritorio + 36 de relleno; 44 del burger + 28 en móvil), que es el rango
     recomendado. El burger conserva sus 44px mínimos: los pone el bloque guardia
     del final del archivo, no este relleno. */
  padding:clamp(14px,1.4vw,18px) var(--pad); transition:transform var(--m-base) var(--e-inout); }
.nav.hide { transform:translateY(-110%); }
.nav-logo { font:700 var(--d-5)/1 var(--font-d); letter-spacing:.1em; }
.nav-logo sup { font-size:.5em; }
.nav-links { display:flex; gap:clamp(18px,2.6vw,38px); align-items:center; font:500 var(--t-sm)/1 var(--font-b); }
.nav-links a { position:relative; opacity:.85; transition:opacity var(--m-base) var(--e-out); }
.nav-links a:hover { opacity:1; }
.nav-links a:not(.nav-cta)::after { content:''; position:absolute; left:0; bottom:-6px; height:1px; width:100%; background:var(--fg);
  transform:scaleX(0); transform-origin:left; transition:transform var(--m-base) var(--e-out); }
.nav-links a:not(.nav-cta):hover::after { transform:scaleX(1); }
.nav-cta { border:1px solid rgba(255,255,255,.5); padding:10px 20px; border-radius:999px; transition:background var(--m-base) var(--e-out),color var(--m-base) var(--e-out),transform var(--m-fast) var(--e-out); }
.nav-cta:hover { background:var(--fg); color:#000; }
.nav-cta:active { transform:scale(.96); }

.nav-burger { display:none; background:none; border:0; width:40px; height:34px; cursor:pointer; position:relative; z-index:120; transition:transform var(--m-fast) var(--e-out); }
.nav-burger:active { transform:scale(.9); }
.nav-burger span { display:block; height:2px; background:var(--fg); margin:7px 4px;
  transition:transform var(--m-base) var(--e-out); }
@media (max-width:760px) {
  .nav-links { display:none; }
  .nav-burger { display:block; }
  body.menu-open .nav-burger span:nth-child(1){ transform:translateY(4.5px) rotate(45deg); }
  body.menu-open .nav-burger span:nth-child(2){ transform:translateY(-4.5px) rotate(-45deg); }
}
.mobile-menu { position:fixed; inset:0; background:var(--bg); z-index:110; display:flex; flex-direction:column; justify-content:center;
  padding:var(--pad); gap:6px; clip-path:inset(0 0 100% 0); transition:clip-path var(--m-slow) var(--e-inout); }
body.menu-open .mobile-menu { clip-path:inset(0 0 0% 0); }
.mobile-menu a { font:700 var(--d-1)/1.2 var(--font-d); text-transform:uppercase; letter-spacing:.02em;
 }
.mobile-menu a:hover { opacity:1; transform:translateX(10px); }
.mobile-menu a:active { opacity:.7; transform:translateX(6px) scale(.98); transition-duration:.12s; }

/* ---------- hero ---------- */
.hero { position:relative; height:100svh; display:flex; flex-direction:column; justify-content:flex-end; overflow:hidden; }
.hero-canvas-wrap { position:absolute; inset:0; }
.hero-canvas-wrap canvas { width:100%; height:100%; display:block; }
.hero-static { position:absolute; inset:0; display:none; align-items:center; justify-content:center; padding:0 var(--pad); }
.hero-static svg { width:min(84%, 1400px); height:auto; }
.hero.no-fx .hero-canvas-wrap { display:none; }
.hero.no-fx .hero-static { display:flex; }
.hero-scroll { position:absolute; z-index:5; left:50%; bottom:clamp(22px,3.5vh,44px); transform:translateX(-50%);
  font:500 var(--t-xs)/1 var(--font-b); letter-spacing:.14em; text-transform:uppercase; color:var(--mut); }
.hero-scroll::before { content:'↓'; display:inline-block; margin-right:8px; animation:bob 1.6s ease-in-out infinite; }
@keyframes bob { 50% { transform:translateY(4px); } }

/* ── Entrada del hero en CSS, no en GSAP (v178) ───────────────────────────────
   Lo hacía `gsap.from(['.hero-tag-center','.hero-scroll'], {autoAlpha:0,...})`,
   que pone el elemento en opacity:0 EN CUANTO CORRE EL JS. Como main.js va detrás
   de cuatro archivos de GSAP diferidos, la frase del hero —que es el elemento más
   grande de la primera pantalla— no era candidata a LCP hasta ~3s. Medido con
   Lighthouse: el desglose del LCP daba TTFB 438ms + **render delay 3179ms**, sin
   ninguna fase de carga de recurso, la firma de "texto que existe pero no se pinta".
   En CSS arranca con el primer pintado, sin depender de ningún script.
   ⚠️ Se anima `translate`, NO `transform`: .hero-scroll lleva translateX(-50%) en
   escritorio y `none` en móvil, y `translate` es una propiedad independiente que
   se compone con transform sin pisarlo. */
@keyframes heroSube { from { opacity:0; translate:0 26px; } to { opacity:1; translate:0 0; } }
.hero-tag-center, .hero-scroll { animation:heroSube 1.05s cubic-bezier(.215,.61,.355,1) backwards; }
.hero-tag-center { animation-delay:.2s; }
.hero-scroll     { animation-delay:.27s; }
@media (max-width:760px) {
  .hero-scroll { left:auto; right:var(--pad); transform:none; }
}

/* ---------- secciones base ---------- */
/* ⚠️ El ritmo vertical va en MARGIN, no en padding. Con padding, el hueco entre dos
   secciones es la SUMA de los dos rellenos y salen valores que no están en la escala
   (medido: 8 huecos distintos, de 48 a 202px). Entre hermanos los márgenes COLAPSAN al
   mayor de los dos, así que cada hueco es exactamente UN token. El padding se queda
   sólo en el eje horizontal. */
section[data-section] { padding-inline:var(--pad); margin-block:var(--sec-base); }

/* ─── FOLD: la sección ocupa al menos una pantalla y centra su contenido en ella.
   El problema medido no era el hueco entre secciones (esos ya salían de 3 tokens),
   sino que varias secciones no llegaban a llenar una pantalla —video 0.82, stats
   0.66, clientes 0.44— así que al hacer scroll nunca aterrizabas en una sección
   completa: siempre a media idea, con la siguiente asomando. Dándoles el mínimo de
   una pantalla, el aire pasa a vivir DENTRO de la sección en vez de acumularse
   entre secciones, y cada gesto de scroll cae en un bloque.
   No se aplica a las bandas de transición (.strips) ni a lo ya anclado, que de por
   sí ocupan una o más pantallas. */
.is-fold { min-height:100svh; display:flex; flex-direction:column; justify-content:center; }
/* ⚠️ El CTA CON imagen ya declara su propio flex en FILA a ≥900px (texto a la derecha,
   foto anclada abajo-izquierda). `.cta.has-media` (0,2,0) le gana a `.is-fold` (0,1,0)
   en `display` y `align-items`, pero NO en `flex-direction`, que la utilidad sí pone:
   sin esta guarda, en cuanto Christian suba la imagen del CTA el layout se voltearía
   a columna. Hoy no hay imagen, así que sería un bug latente e invisible. */
@media (min-width:900px) { .cta.has-media.is-fold { flex-direction:row; align-items:center; } }
/* ⚠️ El flex NO rompe el colapso de márgenes con los HERMANOS (eso sólo afecta a
   padre-hijo), así que el sistema de ritmo sigue intacto. */
@media (max-width:760px) { .is-fold { min-height:0; display:block; } }
.sec-head { display:flex; justify-content:space-between; align-items:flex-end; margin-bottom:clamp(30px,5vh,64px); gap:20px; }
.sec-title { font:700 var(--d-2)/1.02 var(--font-d); text-transform:uppercase; letter-spacing:.01em; }
.link-arrow { font:600 var(--t-sm)/1 var(--font-b); color:var(--mut); white-space:nowrap; transition:color var(--m-base) var(--e-out); }
.link-arrow:hover { color:var(--fg); }
.link-arrow span { display:inline-block; transition:transform var(--m-base) var(--e-out); }
.link-arrow:hover span { transform:translateX(5px); }

/* Las filas de proyecto (.proj-list / .proj-row / .proj-bg con su cortina) se fueron
   en v257: los proyectos dejaron de existir al pasar a 6 categorías y proyecto.php se
   eliminó con ellos. Estaban aquí, entre .link-arrow y el strip de texto grande. */

/* /bolsa sin vacantes: el formulario sube. Va como `section.sin-vacantes` y NO como
   `.sin-vacantes` a secas — la regla general es `section[data-section]` (0,1,1) y una
   clase sola (0,1,0) pierde. Es la misma trampa de section.svcs y section.awards. */
section.sin-vacantes { margin-top:var(--sec-tight); }

/* ---------- strip de texto grande ---------- */
.bigsay { max-width:1100px; }
/* párrafo destacado (lead): intros, filosofía, etc. — un solo tamaño */
.lead { font:400 var(--p-lead)/1.5 var(--font-b); color:#d8d8d8; letter-spacing:0; max-width:40ch; }
.lead .line { display:block; overflow:hidden; }

/* ---------- CTA / footer ---------- */
/* padding `lg` y no `open`: el margen del hermano ya separa la sección, así que
   un padding de 196 encima daba 392px hasta el texto del CTA. */
.cta { text-align:left; padding:var(--sp-lg) var(--pad); }
/* ⚠️ El CTA NO llena una pantalla (v209). Lleva `.is-fold`, que le pone
   `min-height:100svh`, pero su contenido mide sólo 374px a 1440×900: el fold repartía
   los 526px sobrantes arriba y abajo y dejaba 415px de aire antes del texto y 359px
   después. Con el mínimo fuera, el hueco lo definen el margen del hermano (124px, que
   colapsa) y su propio relleno (--sp-lg, 78px): 202px por lado, simétricos.
   El fold se le puso en v173 para darle presencia; con el contenido real que tiene hoy
   sólo añadía vacío. En móvil no cambia nada: ahí `.is-fold` ya estaba desactivado.
   Basta una clase más que `.is-fold` (0,2,0 contra 0,1,0), sin media query. */
.cta.is-fold { min-height:0; }
/* ⚠️ Mismo caso en el CREW de /nosotros (v249): su contenido mide ~570px y el fold lo
   estiraba a los 900 de la pantalla, repartiendo 164px de vacío arriba y otros 163
   abajo. Con el mínimo fuera, el aire lo dan los márgenes de los hermanos (124px, que
   colapsan) y queda al ritmo del resto de la página. Misma receta que el CTA: una clase
   más que `.is-fold` (0,2,0 contra 0,1,0), sin media query, y en móvil no cambia nada
   porque ahí `.is-fold` ya venía desactivado. */
.crew.is-fold { min-height:0; }
.cta-title { font:700 var(--d-1)/0.95 var(--font-d); text-transform:uppercase; margin:14px 0 18px; }
.cta-sub { color:var(--mut); max-width:46ch; margin:0 0 34px; }
/* Los dos botones del CTA final. `align-items:center` y no `baseline`: el principal es
   una pastilla con relleno y el secundario texto suelto, así que por línea base el
   segundo se pegaría al borde inferior del primero. */
.cta-actions { display:flex; align-items:center; gap:clamp(18px,2.4vw,34px); flex-wrap:wrap;
  margin-top:clamp(20px,2.6vw,34px); }
/* Mismo lenguaje que el enlace secundario del 404: texto con subrayado fino, no un
   segundo botón sólido que compita con «Iniciar misión». */
.cta-alt { position:relative; font:600 var(--t-sm)/1 var(--font-b); color:var(--mut);
  border-bottom:1px solid var(--line-2); padding-bottom:4px;
  transition:color var(--m-base) var(--e-out), border-color var(--m-base) var(--e-out); }
.cta-alt:hover { color:var(--fg); border-color:var(--acc); }
/* Como texto suelto mide 18px de alto: por debajo de los 44 mínimos para el dedo. El área
   crece con un pseudo-elemento, que no ocupa espacio ni mueve el subrayado — el mismo
   recurso que usan el logo y el casco del pie. */
.cta-alt::before { content:''; position:absolute; inset:50% auto auto 50%;
  width:calc(100% + 16px); height:44px; transform:translate(-50%,-50%); }

.btn-big { display:inline-block; font:600 var(--t-sm)/1 var(--font-b); border:1px solid rgba(255,255,255,.4);
  padding:18px 34px; border-radius:999px; transition:background var(--m-base) var(--e-out),color var(--m-base) var(--e-out),scale var(--m-fast) var(--e-out); }
.btn-big:hover { }
.btn-big:active { scale:.97; }   /* `scale`, no `transform`: este botón lleva data-fade y GSAP le escribe transform */
.btn-big span { display:inline-block; transition:transform var(--m-base) var(--e-out); }
.btn-big:hover span { transform:translateX(5px); }
.footer { border-top:1px solid var(--line); padding:clamp(40px,7vh,70px) var(--pad) 26px;
  position:relative; }
/* CIERRE de la página: el mismo resplandor azul del hero, volteado, pero anclado al
   FINAL REAL del documento — el footer es el último elemento, así que su `bottom:0`
   es el fin de la página. Sube hasta entrar en el CTA, sin corte posible porque nada
   lo recorta aquí. Simetría: hero::after abre desde el techo, esto cierra desde el piso. */
.footer::before { content:''; position:absolute; z-index:0; pointer-events:none;
  left:0; right:0; bottom:0; height:clamp(520px,76svh,900px);
  background:radial-gradient(ellipse 95% 80% at 50% 102%, rgba(74,92,255,.22), rgba(74,92,255,.06) 48%, transparent 74%); }
/* el contenido del footer se posiciona para pintarse POR ENCIMA del glow (mismo
   patrón que `.cta > *`): sin esto, un z-index:0 posicionado le pasaría por delante */
.footer > * { position:relative; z-index:1; }
.footer-top { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:18px; margin-bottom:34px; }
.footer-logo { font:700 var(--d-3)/1 var(--font-d); letter-spacing:.08em; }
.footer-logo sup { font-size:.5em; }
.footer-nav { display:flex; gap:clamp(14px,2.4vw,30px); flex-wrap:wrap; font-size:var(--t-sm); color:var(--mut); }
.footer-nav a:hover { color:var(--fg); }

/* ---- el casco: acceso del crew, al final del menú del pie (v252) ----
   Vive SÓLO aquí: es para el equipo, no para clientes, y arriba quedaba a la vista.
   Historia corta: casco → cohete (v230, porque el casco parecía un limón) → casco otra
   vez, redibujado sobre el original que pasó Christian. Ver `casco_svg()` en logo.php.
   26px, medido: es LÍNEA, no silueta, y por debajo de 26 los trazos del casquete y de
   la visera se funden. Ver `casco_svg()` para los números.
   ⚠️ `.footer-nav a.foot-casco`, NO `.foot-casco` a secas: la regla del pie es
   `.footer-nav a` (0,1,1) y una sola clase (0,1,0) PIERDE. Misma trampa de
   `section[data-section]` que ya mordió varias veces en este archivo. */
.footer-nav a.foot-casco { position:relative; display:inline-flex;
  align-items:center; justify-content:center; }
.footer-nav a.foot-casco svg { width:26px; height:26px; display:block; flex:none;
  opacity:.75;
  transition:opacity var(--m-base) var(--e-out), transform var(--m-mid) var(--e-out); }

/* ---- se enciende ----
   El cohete despegaba y estiraba la llama; un casco no despega. Al pasar el ratón sube
   la opacidad y crece un pelo, como si se activara el visor. Nada de destellos cruzando
   la visera: eso exigía un clipPath, que fue justo la fuente de bugs del casco original.
   `:active` además de `:hover`: en táctil no hay hover y al tocar no pasaría nada. */
.footer-nav a.foot-casco:hover  svg,
.footer-nav a.foot-casco:active svg { opacity:1; transform:scale(1.12); }

/* Sin texto el enlace mide 26px: sigue por debajo de los 44 mínimos para el dedo. El área
   de toque sube a 44px con un pseudo-elemento, que no ocupa espacio ni mueve el menú. */
.footer-nav a.foot-casco::before { content:''; position:absolute; inset:50% auto auto 50%;
  width:44px; height:44px; transform:translate(-50%,-50%); }

.footer-mid { display:flex; justify-content:space-between; flex-wrap:wrap; gap:14px; color:var(--mut);
  font-size:var(--t-sm); padding:22px 0; border-top:1px solid var(--line); }
.footer-mid a:hover { color:var(--fg); }
.footer-social { display:flex; gap:16px; }
/* --mut y no #555: a 12px el gris #555 daba 2.72:1 sobre el fondo real, cuando el
   mínimo para texto pequeño es 4.5. Con --mut sube a 6.3:1 y sigue leyéndose como
   secundario. */
.footer-bottom { color:var(--mut); font-size:var(--t-xs); padding-top:18px; }

/* ---------- anim helpers ---------- */
[data-fade] { opacity:0; transform:translateY(28px); }
.reveal-lines .line { display:block; overflow:hidden; }
.reveal-lines .line > div { transform:translateY(110%); }
@media (prefers-reduced-motion: reduce) {
  [data-fade] { }
  .reveal-lines .line > div { }
  .hero-scroll::before { animation:none; }
  .form-ok { animation:none; }
  /* sin movimiento: se apaga el cursor propio y vuelve el nativo. (Antes se forzaba
     transform:none, que le quitaba a GSAP la posición y lo dejaba clavado en la esquina.) */
  #cursor { }
  [data-cursor], [data-cursor] * { }
}

/* ---------- páginas internas: hero corto ---------- */
.page-hero { padding:calc(clamp(70px,14vh,160px) + 40px) var(--pad) clamp(30px,6vh,70px); }
.page-hero h1 { font:700 var(--d-1)/0.95 var(--font-d); text-transform:uppercase; }
.page-hero .label { margin-bottom:14px; }
.page-hero .intro { max-width:56ch; color:var(--txt-soft); margin-top:22px; font-size:var(--p-lead); line-height:1.35; }

/* ============================================================
   v2 — secciones estilo referencia
   ============================================================ */
/* hero: tagline centrada bajo el logo */
.hero-tag-center { position:absolute; z-index:5; left:50%; bottom:20vh; transform:translateX(-50%);
  text-align:center; max-width:36ch; color:var(--txt-soft); font-size:var(--t-md); font-weight:500; }
@media (max-width:760px) {
  .hero-tag-center { bottom:26vh; width:88%; }
}

/* tiras gigantes marquee */
/* ⚠️ El PADDING no es decorativo: es el aire que necesita la INCLINACIÓN.
   Las tiras rotan ±4° según la velocidad de scroll, y `overflow:hidden` recorta
   contra la caja del contenedor — a 4°, un punto en el borde de la pantalla sube
   o baja ~50px (720px de medio ancho × sin 4°), así que sin relleno el texto se
   corta en las esquinas. En el pase de proporciones (v140) este padding se
   convirtió en margin y el aire desapareció: la regresión que se veía como
   "encimado". Ahora van los dos: padding para la rotación, margin para el ritmo. */
/* ⚠️ El relleno va en `vw`, NO en `vh`: lo que contiene es la excursión de la
   rotación (±4°), que vale (ancho/2)·sen4° — depende del ANCHO, no del alto. Con
   `6vh` en un móvil de 390×844 daba 50px para contener 14: el aire acababa siendo
   1.35× el contenido de la banda, el peor ratio de la página en móvil. */
/* ⚠️ Sólo queda la tira PEQUEÑA (`.strips.small`, los nombres de clientes bajo el muro
   de logos). Las tiras GRANDES de servicios se quitaron de la home en v210, y con ellas
   la inclinación por velocidad de scroll: por eso ya no hay nada que "contener" aquí.
   El `padding-block` de abajo lo pisa entero `.strips.small { padding:… }`; se deja como
   base por si algún día vuelve una tira a tamaño completo. `tight`, no `open`: es una
   banda de transición y con 196px por lado el aire superaba al contenido. */
.strips { overflow:hidden; padding-block:clamp(18px,3.8vw,96px);
  margin-block:var(--sec-tight); }
.strip { white-space:nowrap; font:700 var(--d-mega)/1.06 var(--font-d);
  text-transform:uppercase; color:#fff; will-change:transform; }
.strip .dot { display:inline-block; transform:translateY(-.18em); font-size:.35em; margin:0 .5em; }
/* stats con contadores */
/* ============================================================
   LOGROS: una cifra principal, y de ella SALEN las demás (v200)
   La primera fila del admin es el titular ("10 años"): enorme y en rojo. Las
   otras tres cuelgan de ella por tres trazos curvos que dibuja el JS midiendo
   posiciones reales, así la relación se VE — no son cuatro datos en fila, es
   uno del que se derivan tres.
   Sustituye a la secuencia anclada de una cifra por scroll (v190-193): enseñaba
   una a la vez, así que nunca se veían juntas y no había jerarquía posible
   —las cuatro pesaban igual—. De paso devuelve 1.84 pantallas a la home.
   La sección es un FOLD normal (.is-fold), ya no una pista anclada.
   ============================================================ */
/* El centrado vivía en `.st-stage`, que desapareció con el anclaje. Va aquí, y
   `.bigsay` pierde su max-width para que el titular ocupe el ancho como antes.
   NO se centra con `align-items` en el flex del fold: eso encogería `.st-web` a su
   contenido y la rejilla de métricas dejaría de repartirse el ancho. */
section.bigstats { text-align:center; }
.bigstats .bigsay { max-width:none; }
.bigstats .bigsay p { color:var(--mut); max-width:44ch; margin-inline:auto; }

.st-web { position:relative; width:100%; margin-top:clamp(26px,4svh,56px); }
.st-hero { text-align:center; }
/* ⚠️ En propiedades LARGAS, no con el atajo `font`. `font:700 var(--font-d)` es
   INVÁLIDO —el atajo exige tamaño Y familia— así que el navegador tiraba la
   declaración entera y las cifras salían en Archivo 400, la fuente de cuerpo, en
   vez de Clash Display 700. No avisa por consola y el tamaño sí se aplicaba (va en
   otra regla), así que parecía sólo "un grosor más delgado". Misma familia que la
   trampa del var() inválido: una declaración mal formada no degrada, desaparece. */
.st-n { font-family:var(--font-d); font-weight:700; line-height:.9;
  letter-spacing:-.02em; color:#fff; }
.st-hero .st-n { font-size:clamp(76px,12vw,168px); color:var(--red); }
/* «Años» pesa como las CIFRAS de abajo (+145, +35K, +200), no como sus etiquetas: es la
   unidad del 10 y forma pareja con él, así que comparte tipografía, grosor y tamaño con
   `.st-item .st-n`. La frase que va debajo (.st-l2) sí queda como las etiquetas. */
.st-hero .st-l { font:700 clamp(32px,4.4vw,64px)/.9 var(--font-d); color:#fff;
  letter-spacing:-.02em;
  margin-top:clamp(8px,1.1vw,16px); max-width:26ch; margin-inline:auto; }

/* El hueco de arriba ES donde viven los trazos: si se aprieta, la derivación
   deja de leerse y vuelven a parecer dos bloques sueltos. */
.st-grid { display:grid; grid-template-columns:repeat(var(--n,3),minmax(0,1fr));
  gap:clamp(14px,2.2vw,40px); margin-top:clamp(84px,15svh,150px); text-align:center; }
.st-item .st-n { font-size:clamp(32px,4.4vw,64px); }
.st-item .st-l { font:500 var(--t-sm)/1.35 var(--font-b); color:var(--mut);
  margin-top:clamp(6px,.8vw,12px); }

/* ⚠️ El trazo y su degradado se pintan como ATRIBUTOS desde el JS, nunca desde
   aquí: un `stroke:url(#id)` escrito en main.css se resuelve contra la HOJA y no
   contra el documento — la misma trampa que costó los 404 de `--card-img`. */
.st-links { position:absolute; inset:0; width:100%; height:100%; pointer-events:none; }
/* segunda línea de una cifra: «Años» manda arriba y la frase queda debajo, más chica
   y apagada, para que el dato y su explicación no compitan */
/* la frase bajo «Años» se ve igual que las etiquetas de las otras cifras */
.st-l2 { display:block; color:var(--mut); text-transform:none;
  font:500 var(--t-sm)/1.35 var(--font-b); letter-spacing:0;
  margin-top:clamp(10px,1.4vw,18px); }

@media (max-width:760px) {
  /* En una sola columna los tres trazos se cruzarían entre sí. Se cambian por una
     marca roja sobre cada cifra: dice lo mismo (cuelga de lo de arriba) sin geometría. */
  .st-links { display:none; }
  .st-grid { grid-template-columns:1fr; gap:clamp(24px,4svh,40px);
    margin-top:clamp(38px,6svh,58px); }
  .st-item { position:relative; padding-top:20px; }
  .st-item::before { content:''; position:absolute; top:0; left:50%; margin-left:-.5px;
    width:1px; height:13px; background:var(--red); }
}

/* testimonios: palabra gigante sticky + tarjetas parallax encima */
/* ⚠️ svh, NO vh, en TODO lo anclado (sticky) y en sus pistas.
   En el celular `100vh` es el viewport GRANDE (con la barra de URL ya escondida):
   mientras la barra se ve, el escenario anclado es más alto que lo visible y la
   sección pinneada sale cortada; y cuando la barra se colapsa, TODO cambia de alto
   a media animación y ScrollTrigger recalcula → salto. `svh` es el viewport chico
   (barra visible), así que el alto no cambia durante el scroll. Se deja la línea en
   vh arriba como respaldo para navegadores sin svh. */
/* margin, NO padding: .testi2 ES la pista (su alto define el recorrido del anclaje),
   así que un padding le sumaría alto y el pin terminaría antes de tiempo. */
/* ⚠️ Paso PROPIO, no `--scroll-item` (46svh). Ese token lo comparte la rueda de
   servicios, donde las cards se turnan en pantalla y necesitan ese recorrido; aquí
   las tarjetas se ven de dos en dos sobre la palabra gigante y 46svh dejaba ~59svh
   de scroll entre una y otra: se sentían lejísimos. Con 26svh el paso baja a ~40svh
   (-32%) sin que lleguen a encimarse, porque las posiciones siguen repartiéndose
   del 8% al 86% del recorrido (las calcula index.php según cuántos haya). */
/* v287 · la separación entre tarjetas la reparte el JS midiendo su ALTO real, y de ahí
   sale también el alto de la pista. El `--testi-item` de antes repartía los `top` con paso
   uniforme, que NO es lo mismo: las citas miden distinto, así que cada tarjeta se comía su
   propio hueco. Medido a 1440x900 con 4 tarjetas: altos 349/375/389/401 y huecos reales
   82/56/42 — y la última acababa 168px FUERA de la pista.
   `--testi-item` se queda como respaldo: es lo que gobierna el alto si el JS no llega a
   correr (sin JS, o si falla), y así la sección nunca se queda sin recorrido. */
.testi2 { --testi-item: 26svh; --testi-gap: clamp(64px, 8.5svh, 104px);
  --testi-pad: clamp(90px, 14svh, 170px);
  position:relative; height:calc(100svh + var(--n,3) * var(--testi-item));
  margin-block:var(--sec-base); }
.testi2-pin { position:sticky; top:0; height:100vh; height:100svh; display:flex; align-items:center; overflow:hidden; }
/* una línea por palabra: cabe más grande y se lee, en vez de salirse de cuadro */
/* El mínimo es 48px y no 56: por debajo de 320px de ancho la letra deja de encoger
   (manda el piso del clamp) y la palabra se salía del cuadro. Con "Mission Reports" (7+7
   letras) entraba raspando; con "Nuestros clientes" (8+8) se recortaba 19px por lado en
   una pantalla de 280. Encima de 320 no cambia NADA: ahí manda el 15vw. */
.testi2-word { font:700 clamp(48px,15vw,260px)/.88 var(--font-d); text-transform:uppercase;
  white-space:nowrap; will-change:transform; text-align:center; }
.testi2-word .ln { display:block; }
/* ---- foto de quien habla, a la izquierda (v239) ----
   La tarjeta con foto se ensancha de 410 a 520px A PROPÓSITO: con 410 el texto pasaba
   de 346px de medida a 206 y la cita crecía en alto muchísimo, que es justo lo que se
   quería evitar. Con 520 la medida del texto queda en ~316 y la tarjeta apenas cambia.
   Sólo se ensancha cuando HAY foto; sin ella todo sigue igual que antes. */
.testi2-card.has-foto { width:min(520px,88vw);
  display:grid; grid-template-columns:clamp(96px,10vw,128px) minmax(0,1fr);
  column-gap:clamp(14px,1.6vw,22px); align-items:stretch; }
/* ⚠️ La foto ocupa las DOS filas y se estira al alto de la tarjeta, en vez de llevar su
   propio 2:3. Con aspect-ratio fijo medía 192px de alto contra ~140 de la cita, así que
   mandaba ella y la tarjeta pasaba de 277 a 365px — justo el crecimiento que se quería
   evitar. Como franja, el alto lo sigue marcando el texto y la foto lo rellena.
   La foto de origen sí conviene subirla en 2:3: da material vertical de sobra para que
   `cover` recorte por los lados sin quedarse corta. */
/* ⚠️ `span 2`, NO `1 / -1`: sin `grid-template-rows` la rejilla es implícita y el `-1`
     apunta a la última línea de la EXPLÍCITA —que no existe—, así que la foto se quedaba
     en una sola fila y medía 147px en vez de llenar la tarjeta. */
.testi2-card.has-foto .testi-foto { grid-row:1 / span 2; }
.testi2-card.has-foto .who { grid-column:2; }

.testi-foto { display:block; position:relative; width:100%;
  border-radius:var(--r-md); overflow:hidden; background:var(--surface-2);
  border:1px solid var(--line); }
/* la imagen va absoluta para que el enlace pueda estirarse al alto de la rejilla sin
   que su contenido le imponga una altura propia */
.testi-foto img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block;
  transition:transform var(--m-slow) var(--e-out), filter var(--m-base) var(--e-out); }
a.testi-foto:hover img { transform:scale(1.05); }

/* el botón de play dice que hay video detrás, sin necesidad de leer nada */
.testi-play { position:absolute; inset:50% auto auto 50%; transform:translate(-50%,-50%);
  width:34px; height:34px; border-radius:50%; background:rgba(10,11,16,.62);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border:1px solid rgba(255,255,255,.34);
  transition:background var(--m-base) var(--e-out), transform var(--m-base) var(--e-out); }
.testi-play::after { content:''; position:absolute; inset:50% auto auto 50%;
  transform:translate(-42%,-50%); border-style:solid; border-width:6px 0 6px 10px;
  border-color:transparent transparent transparent #fff; }
a.testi-foto:hover .testi-play { background:var(--acc); border-color:var(--acc);
  transform:translate(-50%,-50%) scale(1.08); }

@media (max-width:560px) {
  /* en pantalla angosta la foto se achica para no comerse la medida del texto */
  .testi2-card.has-foto { grid-template-columns:76px minmax(0,1fr); column-gap:12px; }
  .testi-play { width:28px; height:28px; }
}

.testi2-card { position:absolute; width:min(410px,84vw); z-index:3;
  /* mismo tono azul-noche que --surface, semi-translúcido para que se asome la palabra */
 border-radius:var(--r-lg); padding:clamp(22px,2.4vw,32px); will-change:transform; }
.testi2-card p { font-weight:600; font-size:var(--t-md); line-height:1.5; }
.testi2-card .who { display:flex; justify-content:space-between; align-items:center; gap:12px;
  margin-top:clamp(40px,6vh,80px); padding-top:16px; border-top:1px solid var(--line); }
/* dos renglones alineados a la izquierda; el logo queda solo a la derecha porque
   `.who` reparte a los extremos. `min-width:0` para que un nombre largo se ajuste
   en vez de empujar al logo fuera de la tarjeta. */
.testi2-card .who-txt { display:flex; flex-direction:column; gap:5px;
  align-items:flex-start; text-align:left; min-width:0; }
.testi2-card .who b { font:700 var(--t-sm)/1.15 var(--font-d); letter-spacing:.06em; text-transform:uppercase; }
.testi2-card .who span { color:var(--mut); font-size:var(--t-xs); line-height:1.25; }
@media (max-width:760px) {
  /* el height de .testi2 se fija más abajo (340svh): una regla aquí quedaría muerta */
  .testi2-card { left:8vw !important; right:auto !important; }
}

/* v3.1 — palabra de testimonios centrada, reveal por letras */
.testi2-pin { justify-content:center; }
/* ⚠️ SIN `will-change:opacity`. Es una letra: con la palabra partida en caracteres
   eran ~17 pistas de composición simultáneas para un fundido de 0.12s cada una.
   Prometer capa por letra cuesta más memoria de GPU en un teléfono de la que
   ahorra en el fundido. */
.testi2-word .ch { display:inline-block; }

/* ============================================================
   v5 — contenido real: clientes, sedes, timeline, equipo,
   formularios, bolsa, detalle de proyecto
   ============================================================ */
/* clientes (marquee de nombres) */
.clients .sub { color:var(--mut); max-width:52ch; margin-top:16px; }
.strips.small { padding:clamp(20px,4vh,50px) 0; }
.strips.small .strip { font-size:clamp(34px,6vw,92px); color:#2e2e2e;
  -webkit-text-stroke:1px #4a4a4a; }

/* sedes */
/* el ritmo vertical ya lo da section[data-section] (base). Declararlo aquí como
   `.sedes` sería redundante Y perdería por especificidad: trampa puesta para nada. */
.sedes { text-align:left; }
.sedes .sec-title { margin-top:14px; }
.sedes .mxmap-wrap { }
/* escenario compacto: sedes (izq) · mapa (centro) · card (der).
   align-items:start = botones y card fijos arriba; solo el mapa se mueve por dentro (zoom) */
.sedes-stage { display:grid;
 margin-top:clamp(24px,4vh,44px); }
.sedes .cities { display:flex; flex-direction:column; padding-top:clamp(10px,3vh,40px); }
.sedes .city { font:600 clamp(13px,1.1vw,15px)/1 var(--font-b); letter-spacing:.12em; text-transform:uppercase;
  color:#8f8f8f;
 cursor:pointer; }
.sedes .city:hover { color:var(--fg); border-color:var(--line-2); }
.sedes .city.lit { color:#fff; border-color:var(--acc); transform:none; }

/* card de la sede activa (columna derecha; anclada arriba y con alto estable
   para que al cambiar de sede NO se muevan los botones ni la card) */
.sede-panel { overflow:hidden;
  display:flex; flex-direction:column; opacity:0; transform:translateY(12px);
  transition:opacity var(--m-mid) var(--e-out),transform var(--m-mid) var(--e-out); }
.sede-panel.show { opacity:1; transform:translateY(0); }
.sede-panel .sp-photo { width:100%; aspect-ratio:16/10; background:var(--surface); flex:0 0 auto; }
.sede-panel .sp-photo img { width:100%; height:100%; object-fit:cover; display:block; }
.sede-panel .sp-main { padding:clamp(18px,1.5vw,24px); display:flex; flex-direction:column; gap:13px; }
.sede-panel .sp-head { display:flex; align-items:baseline; gap:9px; }
.sede-panel .sp-city { font:700 var(--d-4)/1 var(--font-d); text-transform:uppercase; color:#fff; }
.sede-panel .sp-code { font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.14em; color:var(--acc-lt); }
.sede-panel .sp-addrs { display:flex; flex-direction:column; gap:12px; }
.sede-panel .sp-addr span, .sede-panel .sp-tel span { display:block; font:600 var(--t-xs)/1 var(--font-b);
  letter-spacing:.12em; text-transform:uppercase; color:var(--mut); margin-bottom:5px; }
.sede-panel .sp-addr b { font:500 var(--t-sm)/1.4 var(--font-b); color:var(--txt-soft); display:block; }
.sede-panel .sp-tel b { font:500 var(--t-sm)/1 var(--font-b); }
.sede-panel .sp-tel a, .sede-panel .sp-addr a { color:var(--txt-soft); }
.sede-panel .sp-contact { display:flex; flex-direction:column; gap:14px; margin-top:2px;
  padding-top:14px; border-top:1px solid var(--line); }
.sede-panel .sp-maps { display:inline-flex; align-items:center; gap:7px; align-self:flex-start;
  font:600 var(--t-sm)/1 var(--font-b); color:#fff; background:var(--acc); border-radius:var(--r-pill);
  padding:11px 18px; transition:background var(--m-base) var(--e-out),transform var(--m-fast) var(--e-out); }
.sede-panel .sp-maps:hover { background:var(--acc-dk); }
.sede-panel .sp-maps:active { transform:scale(.97); }
/* (el layout móvil de sedes se define en el bloque de la consola v46, más abajo) */

/* timeline trayectoria */
.tl { border-left:1px solid var(--line); margin-left:clamp(6px,2vw,20px); padding-left:clamp(20px,4vw,54px); }
.tl-item { position:relative; padding:clamp(24px,4.5vh,50px) 0; border-bottom:1px solid var(--line); }
.tl-item::before { content:''; position:absolute; left:calc(-1 * clamp(20px,4vw,54px) - 5px); top:clamp(38px,6vh,66px);
  width:9px; height:9px; border-radius:50%; background:var(--fg); }
.tl-item .year { font:700 var(--d-2)/1 var(--font-d); transition:color var(--m-mid) var(--e-out); }
.tl-item:hover .year { }
.tl-item h3 { font:600 var(--t-lg)/1.2 var(--font-b); letter-spacing:-.01em; margin-top:8px; }
.tl-item p { color:var(--mut); font-size:var(--t-sm); margin-top:6px; max-width:60ch; }

/* equipo */
.team-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:clamp(14px,2vw,26px); }
.team-card { background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden;
  transition:border-color var(--m-base) var(--e-out),box-shadow var(--m-base) var(--e-out),transform var(--m-base) var(--e-out); }
.team-card:hover { border-color:var(--line-2); transform:translateY(-5px);
 }
.team-card .ph { aspect-ratio:4/4.6; background:linear-gradient(160deg,#15151d,#050505);
  display:flex; align-items:center; justify-content:center;
  font:700 var(--d-1)/1 var(--font-d); color:#26262e; }
.team-card .inf { padding:16px 18px 20px; }
.team-card .inf b { font:700 var(--t-md)/1.2 var(--font-d); text-transform:uppercase; letter-spacing:.04em; display:block; }
/* ---- el cargo, en dos renglones (v237) ----
   Venía como una sola línea con el título en inglés, un guion largo y el puesto en
   español compitiendo por el mismo peso. Ahora manda el español —en azul claro, que en
   este sitio es el color de las etiquetas (el rojo está reservado para datos como los
   años)— y el inglés queda debajo, más chico y apagado. */
.team-card .inf span { display:block; text-transform:uppercase; }
/* tamaño propio y no --t-xs: con el mismo token quedaba en 11.5px contra los 11 del
   inglés y la jerarquía la sostenía sólo el color. Así hay diferencia real. */
.team-card .inf .rol-es { color:var(--acc-lt); font:600 clamp(12px,.94vw,14px)/1.35 var(--font-b);
  letter-spacing:.07em; margin-top:8px; }
.team-card .inf .rol-en { color:var(--mut); font:400 11px/1.35 var(--font-b);
  letter-spacing:.06em; margin-top:5px; }

/* formularios */
.formgrid { display:grid; grid-template-columns:1.3fr .7fr; gap:clamp(30px,5vw,80px); align-items:start; }
@media (max-width:860px) { .formgrid { grid-template-columns:1fr; } }
.form label { display:block; font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.1em; text-transform:uppercase;
  color:var(--mut); margin:26px 0 10px; }
.form input[type=text], .form input[type=email], .form select, .form textarea {
  width:100%; background:transparent; border:0; border-bottom:1px solid var(--line-2); color:var(--fg);
  font:500 var(--t-lg)/1.3 var(--font-b); padding:6px 0 14px; border-radius:0; transition:border-color var(--m-base) var(--e-out); }
/* select = dropdown propio: más chico que los inputs de texto y con flecha
   (appearance:none quita la nativa, así que la reponemos con un chevron SVG) */
.form select { appearance:none; -webkit-appearance:none; cursor:pointer;
  font:500 var(--t-md)/1.4 var(--font-b); padding:6px 34px 14px 0;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%238f8f97' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat:no-repeat; background-position:right 2px center; }
.form select option { background:var(--surface); color:var(--fg); font-size:15px; }
.form textarea { min-height:120px; resize:vertical; }
.form input:focus, .form select:focus, .form textarea:focus { outline:none; }
.form input[type=file] { color:var(--mut); font-size:var(--t-sm); padding:8px 0; }
.form .send { margin-top:38px; }
.form-ok { background:#0d2016; border:1px solid #19c37d; color:#7ee2b1; padding:18px 22px; border-radius:var(--r-md);
  margin-bottom:30px; font-size:var(--t-md); animation:formOkIn .45s var(--e-out); }
@keyframes formOkIn { from { opacity:0; transform:translateY(-10px); } }
.cinfo .blk { margin-bottom:clamp(24px,4vh,40px); }
.cinfo .blk p { color:var(--txt-soft); font-size:var(--t-md); margin-top:6px; }
.cinfo .blk a:hover { color:var(--acc-lt); }

/* bolsa de trabajo */
.job-card { border:1px solid var(--line); border-radius:var(--r-lg); padding:clamp(22px,3.4vh,38px);
  margin-bottom:16px; background:var(--surface); display:grid; grid-template-columns:1fr auto; gap:16px; align-items:center;
  transition:border-color var(--m-base) var(--e-out),background var(--m-base) var(--e-out),box-shadow var(--m-base) var(--e-out); }
.job-card:hover { border-color:var(--line-2); background:var(--surface-2);
 }
.job-card h3 { font:700 var(--d-3)/1.1 var(--font-d); text-transform:uppercase; }
.job-card .meta { display:flex; flex-wrap:wrap; gap:10px; margin-top:12px; }
.job-card .meta span { font:500 var(--t-xs)/1 var(--font-b); letter-spacing:.06em; text-transform:uppercase;
  border:1px solid var(--line-2); color:var(--txt-soft); padding:7px 12px; border-radius:var(--r-pill); }
.job-card .desc { color:var(--mut); font-size:var(--t-sm); margin-top:14px; max-width:70ch; }
@media (max-width:700px) { .job-card { grid-template-columns:1fr; } }

/* detalle de proyecto — imagen centrada (flex) para que el sobre-escalado del
   parallax se reparta arriba y abajo y NO se destape una franja negra arriba */
.pdetail-hero { position:relative; height:72svh; overflow:hidden; border-radius:0 0 var(--r-xl) var(--r-xl);
  display:flex; align-items:center; }
.pdetail-hero > img:not(.brand-badge) { flex:none; }
.pdetail-hero img { width:100%; object-fit:cover; }
.pdetail-hero::after { content:''; position:absolute; inset:auto 0 0 0; height:45%;
  background:linear-gradient(transparent, rgba(0,0,0,.65)); }
.pdetail-hero h1 { position:absolute; z-index:2; left:var(--pad); bottom:clamp(18px,4.5vh,44px);
  font:700 var(--d-1)/1 var(--font-d); text-transform:uppercase; }
.pmeta { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:20px;
  padding:clamp(26px,5vh,50px) var(--pad); border-bottom:1px solid var(--line); }
.pmeta div b { display:block; font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.1em;
  text-transform:uppercase; color:var(--mut); margin-bottom:8px; }
.pbody { padding:clamp(40px,8vh,90px) var(--pad); max-width:900px; }
.pbody p { color:#d9d9d9; font-size:var(--p-lead); line-height:1.5; margin-bottom:1em; }
/* v254 — el video de la categoría. Sustituye a la lista de proyectos que había en esta
   página: ahora cada categoría se cuenta con su texto, este video y la galería de abajo.
   16/9 fijo para que el hueco esté reservado ANTES de que cargue: sin la proporción, el
   iframe entra a 150px de alto y el resto de la página da un salto al llegar el video. */
.cat-video { position:relative; border-radius:var(--r-lg); overflow:hidden;
  aspect-ratio:16/9; background:#06070c; border:1px solid var(--line); }
.cat-video video,
.cat-video iframe { position:absolute; inset:0; width:100%; height:100%; display:block; border:0;
  object-fit:cover; }

.pgallery { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(420px,100%),1fr)); gap:14px;
  padding:0 var(--pad) clamp(40px,8vh,90px); }
.pgallery figure { border-radius:14px; overflow:hidden; aspect-ratio:16/10; }
.pgallery img { width:100%; height:100%; object-fit:cover; transition:scale var(--m-slow) var(--e-out); }
.pgallery figure:hover img { scale:1.04; }

/* selección + focus + scrollbar (pulido global) */
/* ::selection se define abajo con el rojo de marca (--red); no duplicar aquí */
:focus-visible { outline:2px solid var(--cyan); outline-offset:3px; border-radius:var(--r-sm); }
::-webkit-scrollbar { width:10px; }
::-webkit-scrollbar-thumb { background:#242424; border-radius:99px; }
::-webkit-scrollbar-track { background:var(--bg); }

/* ============================================================
   v6 — blur de orillas en el hero de detalle
   ============================================================ */
/* blur/viñeteo en las orillas de la imagen (CSS, funciona con cualquier foto) */
.pdetail-hero::before {
  content:''; position:absolute; inset:0; pointer-events:none; z-index:1;
}

/* ============================================================
   v7 — fixes QA móvil
   ============================================================ */
@media (max-width:760px) {                 /* sin sticky stack: se encimaban ilegibles */
  .sec-title { max-width:none !important; }   /* títulos con max-width inline se cortaban */
  .pmeta { grid-template-columns:repeat(2,1fr); }
  .tl-item .year { font-size:clamp(30px,9vw,44px); }
  .testi2 { height:calc(100svh + var(--n,3) * 60svh);   /* respaldo si el JS no corre */
    --testi-gap:clamp(56px,7svh,88px); --testi-pad:clamp(70px,10svh,120px); }
}
@media (max-width:760px) {
  .sec-head { flex-direction:column; align-items:flex-start; }
  .sec-head .sub, .sec-head .label { text-align:left !important; max-width:100% !important; }
}
/* fix: con el menú abierto, el nav (burger/X) debe quedar ENCIMA del overlay para poder cerrar */
body.menu-open .nav { z-index:130; transform:none !important; }

/* ============================================================
   v9 — logo vectorial + showcase con más profundidad
   ============================================================ */
.st0 { fill:currentColor; }
.nav-logo { color:#fff; }
.nav-logo svg { height:clamp(14px,1.3vw,18px); width:auto; display:block; }
.footer-logo { color:#fff; display:inline-flex; align-items:center; gap:clamp(12px,1.4vw,20px); }
.footer-logo svg { height:clamp(20px,2.6vw,32px); width:auto; display:block; }
.footer-icon { height:clamp(28px,3.4vw,44px); width:auto; display:block; }

/* desenfoque de orillas más presente (empieza antes y con más blur) */
.pdetail-hero::before {
}

/* imagen más alta = recorrido de parallax más profundo (capa de fondo) */
.pdetail-hero img { height:134%; }

/* ============================================================
   v10 — blur dramático, parallax mouse en servicios, nav shade
   ============================================================ */
/* degradado negro tras el nav para legibilidad sobre fotos — como ::before del
   propio nav: se oculta/muestra CON el nav (sin elemento separado que se desfase). */
.nav::before { content:''; position:absolute; left:0; right:0; top:0; z-index:-1;
  height:clamp(110px,18vh,180px); pointer-events:none;
  background:linear-gradient(to bottom, rgba(0,0,0,.82) 0%, rgba(0,0,0,.4) 52%, rgba(0,0,0,0) 100%); }
/* clase vieja del shade separado (ya no se usa; por si algún HTML cacheado la trae) */

/* hero de categoría/proyecto: blur suave, centro nítido amplio (la foto se lee
   igual que un proyecto del home; el hero es más alto y no debe taparse tanto) */
.pdetail-hero::before {
  backdrop-filter:blur(14px) saturate(1.05); -webkit-backdrop-filter:blur(14px) saturate(1.05);
  -webkit-mask:radial-gradient(125% 112% at 50% 44%, #0000 58%, #000 92%);
          mask:radial-gradient(125% 112% at 50% 44%, #0000 58%, #000 92%);
}
/* en la página de un PROYECTO la foto va NÍTIDA (sin blur de orillas), para lucir
   el trabajo; el degradado inferior (::after) se queda para leer el título */
.pdetail-hero.pd-sharp::before { backdrop-filter:none; -webkit-backdrop-filter:none; }

/* fotos de servicios: margen interno para el parallax de mouse */

/* ============================================================
   v12 — auditoría: nav activo
   ============================================================ */
.nav-links a.active { opacity:1; }
.nav-links a.active:not(.nav-cta)::after { transform:scaleX(1); }

/* ============================================================
   v15 — muro de logos de clientes (marquee automático en filas)
   ============================================================ */
/* los logos son CONTINUACIÓN de Clientes: arriba va tight, no un corte nuevo */
/* abre y cierra en `tight`: arriba porque continúa a Clientes, abajo porque lo que
   sigue es la banda de tiras, que es un PUENTE — el hueco lo manda el mayor de los
   dos márgenes, así que dejar éste en `base` anulaba el `tight` de .strips. */
/* ---- clientes destacados (v234) ----
   Cinco marcas con su propio texto, encima del muro de logos. Es una LISTA de renglones
   —logo a la izquierda, texto a la derecha— y no una rejilla de tarjetas: con cinco
   elementos una rejilla deja una fila coja de dos, y además cada texto son dos o tres
   líneas que en columna angosta quedan apretadas. El renglón con separador es además el
   mismo componente que ya usa el listado de proyectos, así que se siente del sitio. */
.cli-dest { margin-top:clamp(30px,4.5vw,58px); border-top:1px solid var(--line); }
.cli-d { display:grid; grid-template-columns:clamp(150px,17vw,230px) minmax(0,1fr);
  align-items:center; gap:clamp(18px,3.4vw,52px);
  padding:clamp(22px,3vw,34px) 0; border-bottom:1px solid var(--line); }
.cli-d-marca { display:flex; align-items:center; justify-content:center; min-height:46px; }
/* El tope no es igual para todos: cada img trae su propio `--fh` que sale de index.php,
   de MEDIR cuánta tinta pone el archivo. Emparejar las cajas no emparejaba los pesos:
   OXXO cubre 56% de su recuadro (marco sólido) y Vinoteca 27% (letra fina), así que a
   caja igual OXXO pintaba 2.9 veces más. Escalando por tinta la diferencia baja a 1.26x.
   Manda SÓLO el alto y el ancho sale por la proporción: con dos topes activos, cada uno
   con su clamp, ganaba uno u otro según el ancho de pantalla y los tamaños bailaban.
   `max-width` queda de red por si en una pantalla intermedia un wordmark se pasa.
   ⚠️ Los PNG originales traían el logo centrado en un lienzo con 50-85% de margen
   transparente. Se recortaron al contenido (v244, v249); si subes uno nuevo, recórtalo
   igual o la medición lo tomará como logo diminuto dentro de una caja enorme. */
.cli-d-marca img { max-width:100%; max-height:calc(var(--fh,1) * clamp(56px,6.4vw,88px));
  width:auto; height:auto;
  object-fit:contain; opacity:.9;
  /* los logos vienen en colores distintos; en blanco y negro la fila se lee como una
     sola familia, igual que en el muro */
  filter:grayscale(1) brightness(1.35); }
.cli-d:hover .cli-d-marca img { filter:none; opacity:1; }
.cli-d-txt { font:700 var(--d-4)/1 var(--font-d); text-transform:uppercase;
  letter-spacing:.01em; color:#fff; }
.cli-d-desc { color:var(--txt-soft); font:400 var(--p-body)/1.6 var(--font-b); margin:0; }
/* El tiempo de relación, sacado de la frase en index.php para que se lea como DATO.
   Rojo y negrita: el mismo idioma que el año del carrusel y los números de las cifras
   —en este sitio el rojo marca dato y posición, nunca acción—.
   `nowrap` para que «10 años» no se parta nunca entre dos renglones: es una sola
   unidad, y partida deja de leerse como cifra. */
.cli-d-tiempo { color:var(--red); font-weight:700; font-size:1.09em;
  letter-spacing:.005em; white-space:nowrap; }
@media (max-width:760px) {
  .cli-d { grid-template-columns:1fr; gap:12px; align-items:start; }
  .cli-d-marca { min-height:0; justify-content:flex-start; }
  .cli-d-marca img { max-width:min(100%,250px); max-height:calc(var(--fh,1) * 58px); }
}

.logo-wall { overflow:hidden;
  display:grid; }
.logo-row { overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 9%,#000 91%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 9%,#000 91%,transparent); }
.logo-track { display:flex; width:max-content; align-items:center; will-change:transform;
  animation:logoScroll calc(var(--n,12) * 2.8s) linear infinite; }
.logo-row.rev .logo-track { animation-direction:reverse; }
.logo-row:hover .logo-track { animation-play-state:paused; }
@keyframes logoScroll { to { transform:translateX(-50%); } }
/* margen (no gap) para que el loop de -50% cierre exacto */
/* min-width reserva espacio → la pista mantiene su ancho aunque una imagen tarde
   en cargar (evita el salto del loop = "trabado") */
.logo-cell { flex:0 0 auto; display:flex; align-items:center; justify-content:center;
  min-width:clamp(70px,9vw,120px); }
.logo-cell img { width:auto; object-fit:contain;
  filter:grayscale(1) brightness(1.08); }
.logo-cell:hover img { filter:none; opacity:1; }
/* clientes sin logo aún: nombre en texto delineado (mismo look de la tira) */
.logo-cell.txt { font:700 var(--d-3)/1 var(--font-d); text-transform:uppercase;
  white-space:nowrap; color:#2e2e2e; -webkit-text-stroke:1px #4a4a4a; letter-spacing:.02em;
  transition:color var(--m-base) var(--e-out); }
.logo-cell.txt:hover { color:var(--mut); }
@media (prefers-reduced-motion: reduce) { .logo-track { animation:none; } }

/* ============================================================
   v16 — cards de servicios (home + proyectos) y página servicio
   ============================================================ */
/* Rejilla de /proyectos: 1 columna en móvil, 2 en tableta, 3 en escritorio.
   Antes era `auto-fill` con mínimo de 300px, que a 1440 daba CUATRO columnas: con seis
   categorías dejaba 4 arriba y 2 sueltas abajo. Con número fijo, seis entran en dos
   filas parejas de tres. Los cortes se eligieron por el ancho que le queda a la tarjeta,
   no por tamaños de dispositivo: a 1024 con 3 columnas la tarjeta mide 281px, que es lo
   mínimo con lo que su titular sigue cabiendo en dos renglones. */
.svc-cards { display:grid; grid-template-columns:1fr; }
@media (min-width:600px)  { .svc-cards { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (min-width:1024px) { .svc-cards { grid-template-columns:repeat(3,minmax(0,1fr)); } }
.svc-card { flex-direction:column; justify-content:space-between;
 }
.svc-card:hover { border-color:var(--line-2); background:var(--surface-2);
 }
.svc-card:active { transform:scale(.98); transition:transform var(--m-fast) var(--e-out); }
.svc-card .top { margin-bottom:18px; }
/* Rojo con letra BLANCA. Va sobre --red-deep, no --red: con el rojo pleno el
   blanco se queda en 4.38:1 y no llega al mínimo. Con --red-deep, 4.61:1. */
.svc-card .num { background:var(--red-deep); color:#fff; font:600 var(--t-xs)/1 var(--font-b); border-radius:var(--r-sm); padding:8px 10px; }
.svc-card .cnt { font:500 var(--t-xs)/1 var(--font-b); letter-spacing:.08em; text-transform:uppercase; }
.svc-card h3 { font:700 var(--d-4)/1.05 var(--font-d); text-transform:uppercase; margin-bottom:10px; }
.svc-card p { color:var(--mut); font-size:var(--t-sm); max-width:36ch; }

/* enlaces "otros servicios" al pie de servicio.php */
.svc-links { display:flex; flex-wrap:wrap; gap:10px; }
.svc-link { border:1px solid var(--line); border-radius:999px; padding:12px 20px;
  font:500 var(--t-sm)/1 var(--font-b); color:var(--txt-soft); transition:border-color var(--m-base) var(--e-out),color var(--m-base) var(--e-out),background var(--m-base) var(--e-out),transform var(--m-fast) var(--e-out); }
.svc-link:hover { border-color:var(--line-2); color:var(--fg); background:var(--surface-2); }
.svc-link:active { transform:scale(.96); }
.svc-link span { display:inline-block; transition:transform var(--m-base) var(--e-out); }
.svc-link:hover span { transform:translateX(4px); }

/* ---------- puente entre las marcas destacadas y el muro de logos ----------
   Un respiro entre dos bloques densos. Va centrado y con aire generoso arriba y abajo:
   su trabajo es separar, así que el espacio ES el componente. La frase en tipografía
   de titulares pero en caja normal, NO en versalitas como el resto de encabezados:
   son tres oraciones seguidas y en mayúsculas se leen a tropezones. */
.cli-puente { text-align:center; margin-inline:auto;
  max-width:min(92vw, 60ch);
  padding-block:clamp(54px,8.5vh,104px) clamp(38px,6vh,74px); }
.cli-puente-fr { margin:0 0 clamp(13px,1.5vw,20px);
  font:700 clamp(22px,2.7vw,40px)/1.18 var(--font-d);
  color:var(--fg); }
/* cada oración, su propio renglón (las arma index.php). `balance` va DENTRO de cada
   una: en un teléfono una oración larga todavía se parte, y ahí sí conviene repartir. */
.cli-puente-fr span { display:block; text-wrap:balance; }
.cli-puente-sub { margin:0 auto; max-width:54ch;
  font-size:var(--p-body); line-height:1.6; color:var(--mut); text-wrap:pretty; }
/* la cifra, en el mismo rojo con el que se glorifican los años de arriba */
.cli-puente-num { color:var(--red); font-weight:700; white-space:nowrap; }

/* ============================================================
   v20 — logos de clientes más grandes y compactos (4 filas)
   ============================================================ */
.logo-wall { margin:var(--sec-tight) 0 var(--sec-tight); gap:clamp(30px,5vh,58px); }
.logo-cell { margin-right:clamp(36px,5vw,80px); }
.logo-cell img { height:clamp(44px,5.6vw,80px); max-width:300px; }
.logo-track { animation-duration:calc(var(--n,10) * 3.4s); }
.logo-cell.txt { font-size:clamp(32px,4vw,62px); }

/* ============================================================
   v21 — cards de servicio con foto de portada arriba
   (número y contador de proyectos sobre la imagen)
   ============================================================ */
.svc-card { }
.svc-card .media {
 }
.svc-card .media img {
 }
.svc-card:hover .media img { }
.svc-card .media::after {
 }
.svc-card .top { }
/* velo al 72%, no al 55%: esta pastilla va SOBRE la foto, y con una portada clara
   (la de Indio Chamoyada, p.ej.) el fondo efectivo subía a 119 y el texto se
   quedaba en 3.26:1. Al 72% aguanta incluso sobre blanco puro: 5.9:1.
   ⚠️ No se puede confiar en el `backdrop-filter` para esto: Safari lo ignora
   encima de imágenes en elementos transformados, así que el contraste tiene que
   salir del color de fondo, no del desenfoque. */
.svc-card .cnt { background:rgba(8,8,8,.72); -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  border:1px solid rgba(255,255,255,.18); color:#fff; padding:8px 13px; border-radius:var(--r-pill); }
.svc-card .body {
 }
.svc-card .body h3 { margin-bottom:0; }
/* ============================================================
   v23 — mapa de México por estados (reemplaza al globo):
   hover en chip de ciudad → su estado se ilumina + zoom
   ============================================================ */
/* mapa dentro del grid (columna derecha); fade muy difuminado en las 4 orillas */
.sedes { position:relative; }
.mxmap-wrap { width:100%; }
/* mapa completo, SIN máscara: antes se degradaban las orillas para disimular el corte
   del zoom, y sin zoom ese degradado sólo se comía Sonora y Yucatán —justo dos sedes. */
.mxmap { width:100%; height:auto; display:block; }
/* estados con más presencia: relleno azul-noche + líneas visoras azules más marcadas */
.mx-states path { fill:#171a2e; stroke:#5b67b8; stroke-width:1.2; vector-effect:non-scaling-stroke;
  transition:fill var(--m-mid) var(--e-out),stroke var(--m-mid) var(--e-out),opacity var(--m-mid) var(--e-out); }
/* tres niveles de lectura: la sede (.on), lo que atiende (.reg) y el resto, atenuado */
.mx-states path.on  { fill:rgba(51,70,255,.72); stroke:#dfe3ff; stroke-width:1.9; }
.mx-states path.reg { fill:rgba(51,70,255,.18); stroke:#6f7ce0; }
.mxmap.focus .mx-states path:not(.on):not(.reg) { opacity:.34; }
.mx-sede { cursor:pointer; }
.mx-sede .dotc { fill:#4a5cff; stroke:#fff; stroke-width:1.2; transition:fill var(--m-base) var(--e-out),stroke var(--m-base) var(--e-out);
  filter:drop-shadow(0 0 5px rgba(74,92,255,.9)); }
.mx-sede .ring { fill:none; stroke:#7f8cff; stroke-width:1.4; opacity:0;
  transform-box:fill-box; transform-origin:center; }
.mx-sede .lbl { fill:#fff; font:600 15px/1 Archivo, sans-serif; letter-spacing:.04em;
  paint-order:stroke; stroke:rgba(0,0,0,.85); stroke-width:4px; }
.mx-sede.on .dotc { fill:#fff; stroke:#4a5cff; }
.mx-sede.on .ring { animation:sedePulse 1.5s ease-out infinite; }
.mxmap.focus .mx-sede:not(.on) { opacity:.42; }
@keyframes sedePulse { 0% { transform:scale(.4); opacity:.95; } 100% { transform:scale(2.8); opacity:0; } }

/* ============================================================
   v24 — hero "infinito": tinte espacial de fondo + capa de
   estrellas con profundidad (blend screen: solo ilumina lo oscuro)
   ============================================================ */
/* .hero SIN fondo propio: era #000 opaco y tapaba el cielo global (canvas fijo
   detrás de todo). El negro lo pone el body. */
/* .hero-stars ya no existe: el campo de estrellas se mudó a `.sky`, fijo al
   viewport y para TODO el sitio (ver bloque v106 al final). */

/* SIMETRÍA: el mismo resplandor azul del CTA final, VOLTEADO 180°, al inicio.
   El del cierre entra desde el piso (`ellipse … at 50% 102%`); éste entra desde
   el techo (`at 50% -2%`). Mismas medidas y mismas paradas de color: la página
   abre y cierra con la misma luz. Como .hero lleva overflow:hidden, se recorta
   en el borde superior igual que el del CTA en el inferior — ahí no hay sección
   que lo delate porque es el principio de la página. */
/* ⚠️ z-index 0: AL FONDO, detrás del astronauta. Estaba en 1, el MISMO que
   .hero-astro, y como el ::after es el último del DOM se pintaba ENCIMA — el
   resplandor le pasaba por delante en vez de quedar de atmósfera detrás.
   Queda por encima del cielo global (.sky, fixed en z-index −1), que es lo que
   se busca: el azul tiñe las estrellas, no al revés.
   Orden del hero: glow z0 → astronauta z1 → logo z2 → textos z5. */
.hero::after { content:''; position:absolute; left:0; right:0; top:0; height:100%;
  pointer-events:none; z-index:0;
  background:radial-gradient(ellipse 95% 80% at 50% -2%, rgba(74,92,255,.22), rgba(74,92,255,.06) 48%, transparent 74%); }

/* ============================================================
   v25 — astronauta film-like en el hero + acentos ROJOS de marca
   ============================================================ */
:root { --red:#ed1b2f;
  /* mismo rojo con 3% menos de luz: es el que se usa cuando encima va TEXTO
     BLANCO. Con --red el blanco da 4.38:1 y no llega al mínimo de 4.5; con éste,
     4.61:1. A simple vista son el mismo color. */
  --red-deep:#e61a2e; }
::selection { background:var(--red); color:#fff; }

/* astronauta real (NASA, dominio público) flotando en el infinito.
   blend screen: el negro de la foto desaparece solo sobre el fondo */
.hero-astro { position:absolute;
 pointer-events:none; opacity:0;
  animation:astroIn 2s ease .9s forwards; will-change:transform; }
@keyframes astroIn { to { opacity:1; } }
/* .inner: solo el tilt 3D de GSAP (parallax de puntero). El float visible vive en la img (v29). */
.hero-astro .inner { }
.hero-astro img { display:block;
 }
@media (max-width:900px) { .hero-astro { top:46%; width:clamp(140px,20vh,220px); } }
@media (max-width:560px) { .hero-astro { } }
@media (prefers-reduced-motion: reduce) { .hero-astro .inner { animation:none; } }

/* "Making it possible" como pill roja (el sello de la marca) */
/* con el pill de fondo, el peso alto se ve tosco: bajamos a 500 y un poco de tamaño;
   el pill ya resalta lo suficiente */
/* ⚠️ `--red-deep`, no `--red`: aquí va TEXTO BLANCO encima, y sobre --red el blanco
   se queda en 4.38:1 — por debajo del mínimo de 4.5. Con --red-deep sube a 4.61:1 y
   a simple vista es el mismo color. Es justo el caso para el que existe el token, y
   se había quedado sin aplicar en este pill (es el lockup de la marca). */
.tag-pill { display:table; margin:14px auto 0; background:var(--red-deep); color:#fff;
  font:500 1.08em/1.15 var(--font-d); letter-spacing:.04em;
  padding:.4em 1em .34em; border-radius:.95em; }

/* detalles rojos por el sitio (contraste, no protagonismo) */
.strip .dot { color:var(--red); -webkit-text-stroke:0; }
.hero-scroll::before { color:var(--red); }

/* ============================================================
   v26 — astronauta oficial de Christian (PNG transparente):
   fuera el blend/mask de la foto NASA, halo ambiental sutil
   ============================================================ */
.hero-astro img { mix-blend-mode:normal;
  -webkit-mask-image:none; mask-image:none;
 }

/* ============================================================
   v27 — hero con PROFUNDIDAD REAL (sandwich de capas):
   estrellas (fondo) → astronauta → logo líquido (frente).
   El canvas WebGL pasa a blend screen: su negro se vuelve
   transparente y las letras/glow OCLUYEN y PINTAN al astronauta.
   ============================================================ */
.hero-canvas-wrap { z-index:2; }
.hero-canvas-wrap canvas { }
.hero-static { z-index:2; }
/* centrado horizontal con left/right/margin (no transform: GSAP usa x/y para el parallax) */
.hero-astro { z-index:1; top:26%; left:0; right:0; margin-inline:auto;
  width:clamp(230px,36vh,430px); perspective:900px; }
.hero-astro .inner { animation:none; transform-style:preserve-3d; will-change:transform; }
/* ⚠️ ESTE es el `filter` que MANDA sobre el astronauta: hay otros dos antes
   (bloques v25 y v107) que la cascada pisa por completo. Si algún día se ve raro
   el color, es aquí donde hay que mirar, no en los de arriba.
   Llevaba `brightness(.76) contrast(1.06) saturate(.9)`: lo pintaba al 76% de
   brillo y 10% desaturado — la foto se veía notoriamente más apagada que el
   archivo original. Ahora va SIN corrección de color.
   El `drop-shadow` se queda: no toca los píxeles de la imagen, sólo añade un halo
   azul muy tenue por fuera de su silueta para que no se sienta pegoteada. */
.hero-astro img { width:100%; height:auto;   /* FIX: la altura fija del atributo lo deformaba */
  filter:drop-shadow(0 0 34px rgba(90,110,255,.10)); }
@media (prefers-reduced-motion: reduce) { .hero-astro img { animation:none; } }

/* ============================================================
   v107 — SIN mix-blend-mode (antes: screen en el wrap, v28).
   El blend existía porque el shader del glow escribía alfa 1.0 fija: el canvas
   salía OPACO (negro donde no hay brillo) y `screen` servía para borrar ese negro.
   Pero un modo de fusión OBLIGA a aislar el grupo, y el hero acababa componiéndose
   contra negro: tapaba el cielo y el grano globales → se veía una división clara
   entre el hero y la sección siguiente.
   Arreglado en la raíz (hero-logo.js, GLOW_FS emite alfa = intensidad), así que el
   canvas ya es transparente de verdad y no necesita blend.
   ⚠ NO volver a poner mix-blend-mode aquí: reintroduce el aislamiento.
   ============================================================ */
.hero-canvas-wrap { mix-blend-mode:normal; }
.hero-canvas-wrap canvas { mix-blend-mode:normal; }

/* ============================================================
   v29 — astronauta: deriva orgánica (ya no metrónomo simétrico)
   + llegada con profundidad. El parallax de puntero se movió a
   GSAP en hero-logo.js (interrumpible, con inercia real).
   El efecto de logo líquido con el puntero NO se tocó.
   ============================================================ */
/* arranca y cierra en neutral (= estado final de astroImgIn): el enganche
   entrada → float es continuo, sin el salto lateral que se veía al aparecer */
@keyframes astroFloat {
  0%   { transform:translateY(0)     translateX(0)    rotate(0deg)    scale(1); }
  22%  { transform:translateY(-10px) translateX(-3px) rotate(-2.6deg) scale(1.006); }
  48%  { transform:translateY(4px)   translateX(4px)  rotate(1.8deg)  scale(.997); }
  74%  { transform:translateY(10px)  translateX(-2px) rotate(2.3deg)  scale(1.003); }
  100% { transform:translateY(0)     translateX(0)    rotate(0deg)    scale(1); }
}
@keyframes astroImgIn { from { transform:translateY(26px) scale(.92); } to { transform:translateY(0) scale(1); } }
.hero-astro img {
  animation:astroImgIn 1.4s cubic-bezier(.16,1,.3,1) .9s both, astroFloat 17s ease-in-out infinite 2.3s;
}

/* ============================================================
   v35 — audit de animaciones (skill improve-animations):
   (a) hovers con transform NO se disparan en pantallas táctiles
       (evita el "hover pegado" al tocar una card y volver)
   (b) links del menú móvil entran escalonados (stagger)
   ============================================================ */
@media (hover: none) {
  .svc-card:hover { }
  .svc-card:hover .media img { }
  .sedes .city:hover { transform:none; }
  .mobile-menu a:hover { transform:none; }
}

/* menú móvil: cada link llega con un pequeño desfase al abrir */
.mobile-menu a { opacity:0; transform:translateY(14px);
  transition:opacity var(--m-fast) var(--e-out),transform var(--m-mid) var(--e-out); }
body.menu-open .mobile-menu a { opacity:.9; transform:translateY(0); }
body.menu-open .mobile-menu a:nth-child(1) { transition-delay:.12s; }
body.menu-open .mobile-menu a:nth-child(2) { transition-delay:.17s; }
body.menu-open .mobile-menu a:nth-child(3) { transition-delay:.22s; }
body.menu-open .mobile-menu a:nth-child(4) { transition-delay:.27s; }
body.menu-open .mobile-menu a:nth-child(5) { transition-delay:.32s; }
/* el 6º es Contacto, el último desde que el casco salió del menú móvil (v229) */
body.menu-open .mobile-menu a:nth-child(6) { transition-delay:.37s; }
@media (prefers-reduced-motion: reduce) {
  .mobile-menu a { transition:opacity var(--m-fast) var(--e-out); transform:none; }
  body.menu-open .mobile-menu a { transform:none; }
}

/* ============================================================
   v37 — transición entre páginas SUTIL: sin panel que tape.
   La navegación es instantánea (nativa) y la página nueva
   entra con un fundido suave y corto del contenido.
   ============================================================ */
#main { animation:pageIn .32s ease-out both; }
@keyframes pageIn { from { opacity:0; } to { opacity:1; } }
@media (prefers-reduced-motion: reduce) { #main { animation:none; } }

/* ============================================================
   v44 — auditoría integral: limpieza de CSS muerto (hero viejo,
   testimonios marquee v1, globo v17-19), h1 accesible en home,
   preload de fuente display y caché de fuentes
   ============================================================ */
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }

/* ============================================================
   v45 — mapa nivel 2: red de conexiones entre sedes (marca:
   Connector conecta), zoom con GSAP, glow del estado activo,
   halo de profundidad, progreso del auto-ciclo en el botón y
   card con contenido escalonado
   ============================================================ */
/* arcos tipo ruta de vuelo; en reposo, constelación tenue entre todas */
.mx-links path { fill:none; stroke:#7a8aff; stroke-width:1.15; opacity:.85;
  vector-effect:non-scaling-stroke; pointer-events:none;
  transition:stroke-dashoffset .9s cubic-bezier(.4,0,.2,1); }
.mx-links path.idle { stroke:#4b5480; opacity:.42; stroke-width:1; }
.mxmap.focus .mx-links path { opacity:.55; }

/* estado activo con resplandor; la cobertura no lo lleva, para que no compitan */
.mx-states path.on { filter:drop-shadow(0 0 9px rgba(74,92,255,.55)); }
.mx-sede { cursor:pointer; }   /* desde v234 el marcador se elige con clic */
/* zona de clic invisible: `transparent` sí recibe el puntero, `none` no */
.mx-sede .mx-hit { fill:transparent; }

/* halo de profundidad detrás del mapa */
.mxmap-wrap { position:relative; }
.mxmap-wrap::before { content:''; position:absolute; inset:6%;
  background:radial-gradient(ellipse at 50% 46%, rgba(74,92,255,.09), transparent 62%);
  pointer-events:none; }
.mxmap { position:relative; }   /* el svg pinta sobre el halo */

/* El recorrido automático se retiró en v234: la sede se elige con CLIC y se queda
   hasta que se elija otra, para que el mapa no se mueva mientras alguien lee una
   dirección. Con él se va la barrita que anunciaba la rotación. */
.sedes .city { position:relative; overflow:hidden; cursor:pointer; }

/* card: foto y contenido entran escalonados al cambiar de sede */
.sede-panel .sp-photo img { opacity:0; animation:spFade .55s ease .05s forwards; }
.sede-panel .sp-main > * { opacity:0; transform:translateY(9px);
  animation:spUp .5s var(--e-out) forwards; }
.sede-panel .sp-main > *:nth-child(2) { animation-delay:.07s; }
.sede-panel .sp-main > *:nth-child(3) { animation-delay:.14s; }
@keyframes spFade { to { opacity:1; } }
@keyframes spUp { to { opacity:1; transform:none; } }

@media (prefers-reduced-motion: reduce) {
  .sede-panel .sp-photo img, .sede-panel .sp-main > * { opacity:1; transform:none; animation:none; }
  .mx-links path { transition:none; }
}

/* ============================================================
   v46 — sedes como CONSOLA unificada: botones | mapa | card son
   tres zonas de un mismo panel con divisores internos (antes
   flotaban desconectados). Botones = lista real de sedes con
   código + ciudad e indicador lateral.
   ============================================================ */
.sedes-stage { grid-template-columns:clamp(180px,20vw,250px) 1fr minmax(300px,360px);
  gap:0; align-items:stretch; border:1px solid var(--line); border-radius:22px;
  overflow:hidden; background:linear-gradient(180deg,#0b0c12,#07080c); }

/* zona izquierda: lista de sedes */
.sedes .cities { gap:0; padding:clamp(14px,1.6vh,22px) 0; border-right:1px solid var(--line); align-self:stretch; }
.sedes .city { display:flex; flex-direction:column; align-items:flex-start; gap:5px; text-align:left;
  background:none; border:0; border-left:2px solid transparent; border-radius:0;
  padding:15px clamp(16px,1.7vw,24px);
  transition:border-color var(--m-base) var(--e-out),background var(--m-base) var(--e-out); }
.sedes .city + .city { border-top:1px solid rgba(255,255,255,.05); }
.sedes .city .c-code { font:700 var(--d-5)/1 var(--font-d); letter-spacing:.06em; color:#9a9a9a; transition:color var(--m-base) var(--e-out); }
/* #63636b daba 3.4:1 a 10.5px. Es el texto más pequeño de la página, así que es
   justo donde MENOS se puede escatimar contraste. */
.sedes .city .c-name { font:500 var(--t-xs)/1 var(--font-b); letter-spacing:.13em; text-transform:uppercase; color:var(--mut); transition:color var(--m-base) var(--e-out); }
.sedes .city:hover { background:rgba(255,255,255,.03); border-left-color:#3a3d55; }
.sedes .city:hover .c-code { color:#fff; }
/* `lit` = la que se está viendo (basta pasar el ratón). `is-fijada` = la elegida con
   clic, la que queda cuando se retira el ratón: lleva un punto para distinguirse de una
   que sólo se está mirando de paso. */
.sedes .city.lit { background:linear-gradient(90deg,rgba(74,92,255,.14),transparent 72%); border-left-color:var(--acc); }
.sedes .city.is-fijada .c-code::after { content:'●'; margin-left:7px; font-size:.6em;
  vertical-align:middle; color:var(--acc); }
.sedes .city.lit .c-code { color:#fff; }
.sedes .city.lit .c-name { color:var(--acc-lt); }
/* progreso del auto-ciclo: línea al pie de la fila completa */

/* zona central: mapa con aire */
.sedes .mxmap-wrap { max-width:none; justify-self:stretch; align-self:center; padding:clamp(14px,2vw,30px); }

/* zona derecha: card integrada al panel (sin marco propio) */
.sede-panel { align-self:stretch; border:0; border-left:1px solid var(--line); border-radius:0;
  background:rgba(255,255,255,.015); min-height:0; }

/* ---------- consola de sedes en MÓVIL: una sola columna apilada,
     orden título → mapa → botones → info, sin gaps internos ---------- */
@media (max-width:960px) {
  .sedes-stage { grid-template-columns:1fr; gap:0; border-radius:18px; align-items:stretch; }
  .sedes .mxmap-wrap { order:1; max-width:none; justify-self:stretch; padding:clamp(10px,3vw,18px); }
  /* botones: fila de sedes con divisores verticales, código arriba y ciudad abajo */
  .sedes .cities { order:2; flex-direction:row; padding:0;
    border-top:1px solid var(--line); border-bottom:1px solid var(--line); border-right:0; }
  .sedes .city { flex:1 1 0; min-width:0; align-items:center; text-align:center; gap:3px;
    padding:11px 6px; border:0; border-left:1px solid rgba(255,255,255,.06); }
  .sedes .city:first-child { border-left:0; }
  .sedes .city + .city { border-top:0; }
  .sedes .city .c-name { font-size:var(--t-xs); letter-spacing:.06em; white-space:nowrap; overflow:hidden;
    text-overflow:ellipsis; max-width:100%; }
  .sedes .city.lit { border-left:1px solid rgba(255,255,255,.06);
    background:linear-gradient(180deg,rgba(74,92,255,.16),transparent); box-shadow:inset 0 -2px 0 var(--acc); }
  .sedes .city:first-child.lit { border-left:0; }
  /* info: ancho completo, altura libre (sin el min-height de escritorio) */
  .sede-panel { order:3; border:0; border-top:1px solid var(--line); border-radius:0;
    max-width:none; margin:0; min-height:0 !important; }
}
/* en teléfonos angostos, la ciudad completa no cabe: solo el código */
@media (max-width:560px) {
  .sedes .city .c-name { display:none; }
}

/* ============================================================
   v47 — ícono de marca (esquina) sobre showcases del home y
   heroes de detalle de proyecto/categoría
   ============================================================ */
/* especificidad ≥ que .pdetail-hero img (que estira la foto a 100%) para que el
   ícono no herede ese ancho */
.pdetail-hero .brand-badge {
  position:absolute; z-index:4; right:clamp(16px,2.4vw,30px);
  width:clamp(30px,3.2vw,44px); height:auto; opacity:.92; pointer-events:none;
  filter:drop-shadow(0 1px 4px rgba(0,0,0,.55)); object-fit:contain; }
.pdetail-hero .brand-badge { top:clamp(84px,10vh,110px); }   /* debajo del nav */

/* ============================================================
   v48 — página 404 "Houston": astronauta del hero a la deriva
   ============================================================ */
/* composición CENTRADA: astronauta a la deriva DETRÁS, contenido al centro */
.err404 { position:relative; min-height:90svh; display:flex; flex-direction:column;
  align-items:center; justify-content:center; text-align:center;
  padding:clamp(90px,16vh,160px) var(--pad) clamp(60px,10vh,120px); overflow:hidden; }
/* imagen opcional (nave, etc.) arriba del texto — se sube en el admin */
.err404-img, .thanks-img { display:block; width:auto; max-width:min(440px,78%); max-height:40vh;
  margin:0 auto clamp(22px,4vh,44px); object-fit:contain;
  filter:drop-shadow(0 0 50px rgba(90,110,255,.14)); animation:astroFloat 17s ease-in-out infinite; }
.err404-body { position:relative; z-index:2; width:min(880px,100%); }
.err404 h1 { font:700 clamp(44px,8vw,110px)/1 var(--font-d); text-transform:uppercase; letter-spacing:.01em; }
.err404 .intro { max-width:46ch; margin:20px auto 0; color:var(--txt-soft); font-size:var(--p-lead); line-height:1.4; }
.err404-actions { display:flex; align-items:center; justify-content:center; gap:26px; flex-wrap:wrap; margin-top:34px; }
.err404-alt { font:600 var(--t-sm)/1 var(--font-b); color:var(--mut); border-bottom:1px solid var(--line-2);
  padding-bottom:4px; transition:color var(--m-base) var(--e-out),border-color var(--m-base) var(--e-out); }
.err404-alt:hover { color:var(--fg); border-color:var(--acc); }
@media (max-width:760px) {
  .err404-img, .thanks-img { max-width:70%; max-height:30vh; }
}
@media (prefers-reduced-motion: reduce) { .err404-astro { animation:none; } }

/* ============================================================
   v49 — página legal (aviso de privacidad)
   ============================================================ */
.legal { max-width:68ch; }
.legal h2 { font:700 var(--d-4)/1.15 var(--font-d); text-transform:uppercase; letter-spacing:.01em;
  margin:clamp(28px,4.5vh,44px) 0 12px; }
.legal p { color:var(--txt-soft); font-size:var(--p-body); line-height:1.65; }
.legal a { color:var(--acc-lt); border-bottom:1px solid var(--line-2); }
.legal a:hover { color:var(--fg); border-color:var(--acc); }
/* nota legal bajo el botón de enviar */
.form-legal { margin-top:16px; font-size:var(--t-xs); color:var(--mut); }
.form-legal a { color:var(--txt-soft); text-decoration:underline; }

/* ============================================================
   v50 — thank you page del formulario
   ============================================================ */
/* centrada: título con aire (clamp menor que --d-1 para que "¡Transmisión" nunca
   se recorte con el mask del reveal) */
.thanks { min-height:82svh; display:flex; align-items:center; justify-content:center; text-align:center;
  padding:clamp(90px,16vh,160px) var(--pad) clamp(60px,10vh,120px); }
.thanks-body { width:min(880px,100%); margin:0 auto; }
.thanks h1 { font:700 clamp(36px,6vw,84px)/1.04 var(--font-d); text-transform:uppercase; letter-spacing:.01em; }
.thanks .intro { max-width:46ch; margin-inline:auto; color:var(--txt-soft); margin-top:20px; font-size:var(--p-lead); line-height:1.45; }
.thanks-actions { display:flex; align-items:center; justify-content:center; gap:26px; flex-wrap:wrap; margin-top:34px; }

/* ============================================================
   v51 — CONTRASTE: luz y profundidad (refs de Christian).
   Identidad intacta (negro + azul + rojo); se agregan fuentes
   de luz, botones sólidos y bordes más presentes.
   ============================================================ */
/* bordes más visibles en TODO el sitio (azulado sutil, ya no se pierden en el negro) */
:root { --line:#262a38; }

/* CTA principal: sólido blanco (como las refs), hover al azul de marca */
.btn-big { background:var(--fg); color:#000; border-color:var(--fg); }
.btn-big:hover { background:var(--acc); border-color:var(--acc); color:#fff; }

/* El hero va en NEGRO PURO: se quitó el degradado azul (decisión de Christian,
   jul 2026). Era un elipse pegado al fondo y, como .hero lleva overflow:hidden,
   se recortaba en su punto más brillante y dejaba una raya al pasar a la sección
   siguiente. Se probó subirlo (envolvía al astronauta) y en lineal de arriba a
   abajo; al final se eliminó. La luz del hero ahora la dan sólo las estrellas y
   el propio astronauta. El glow azul del CTA final (.cta::before) SÍ se conserva. */

/* CTA final: resplandor lateral sutil para que el cierre no muera en negro */
.cta { position:relative; overflow:hidden; }
.cta::before { position:absolute; left:-12%; top:-40%; width:72%; height:190%;
  pointer-events:none; background:radial-gradient(ellipse at 30% 50%, rgba(74,92,255,.13), transparent 62%); }
.cta > * { position:relative; }

/* ============================================================
   v52 — refinamiento de contraste (skill impeccable):
   · CTA: luz desde ABAJO hacia arriba (pedido de Christian) —
     anclada al piso de la sección, se desvanece antes de las
     orillas: nunca se ve cortada. Mismo motivo que el hero
     (marco de luz: abre y cierra la página).
   · Stats: la rejilla `.stat` se rehízo como secuencia `.st-*` en
     v191 y sus reglas se retiraron en la auditoría de lanzamiento.
   · Cards: canto de luz superior (bevel) = profundidad física.
   ============================================================ */
/* ⚠️ El glow del cierre YA NO vive aquí: `.cta` lleva `overflow:hidden` (lo necesita
   por `.cta-media`) y la elipse va centrada en `50% 102%`, o sea que su punto MÁS
   BRILLANTE caía fuera del recorte y dejaba una raya horizontal dura, con 333px de
   negro debajo hasta el final de la página. Es el mismo fallo que tuvo el hero.
   Ahora se ancla al PIE del documento: ver `.footer::before`. */
.cta::before { content:none; }

/* canto de luz superior: las superficies "reciben luz" y se despegan del negro */
.svc-card, .team-card, .job-card, .sede-panel, .testi2-card, .sedes-stage {
  box-shadow:inset 0 1px 0 rgba(255,255,255,.055); }
.svc-card:hover, .team-card:hover {
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08), 0 0 0 1px var(--acc-tint), 0 22px 48px -26px var(--acc-glow); }
.job-card:hover {
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08), 0 0 0 1px var(--acc-tint), 0 20px 44px -26px var(--acc-glow); }

/* ============================================================
   v53 — más contraste con los colores del sistema (impeccable):
   · Testimonios: glass REAL (pedido de Christian) — la palabra
     gigante se difumina tras la tarjeta. Propósito, no default.
   · Punteo de marca: nombre del cliente con ● rojo (mismo signo
     del motto/strips), años de trayectoria en azul, eyebrows de
     sección en azul claro, focus de formularios en azul.
   ============================================================ */
/* tarjeta de testimonio = vidrio esmerilado sobre la palabra.
   Safari y Chrome NO muestrean igual un backdrop-filter dentro de un elemento
   transformado (estas cards llevan parallax de GSAP): con el fondo muy transparente
   la diferencia se notaba muchísimo. El fondo ahora es casi opaco —la card se lee
   idéntica en ambos— y el blur queda como matiz, no como estructura. */
/* ⚠️ v279 — SIN backdrop-filter, y el fondo sube de .90/.95 a .94/.97 para compensar.
   Estas tarjetas llevan parallax de GSAP (`y` por fotograma), así que el navegador
   tenía que RE-DESENFOCAR el fondo en cada frame mientras se movían: de los efectos
   del sitio era el más caro por fotograma en móvil. Y casi no se veía — con el fondo
   al 90-95% el desenfoque sólo actuaba sobre el 5-10% restante. Subiendo dos puntos la
   opacidad se ve igual (la palabra gigante de atrás se sigue insinuando, sin bordes
   duros) y desaparece el coste. */
.testi2-card {
  background:linear-gradient(160deg, rgba(15,17,28,.94), rgba(9,10,17,.97));
  border:1px solid var(--line-2); }
/* sin soporte de backdrop-filter (o si el navegador lo desactiva): fondo sólido */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) {
  .testi2-card { }
}
.testi2-card p { color:#f0f0f2; }
/* punteo rojo de marca tras el nombre (como "Making it possible ●") */
.testi2-card .who b::after { content:' ●'; color:var(--red); font-size:.72em; }
/* logo del cliente, en la esquina opuesta al nombre. Pequeño a propósito: acompaña,
   no compite con la cita. `flex:0 0 auto` para que no lo encoja el nombre largo. */
.testi-logo { flex:0 0 auto; height:clamp(18px,1.6vw,24px); width:auto; max-width:120px;
  object-fit:contain; object-position:right center; opacity:.85; }

/* años de la trayectoria: azul del sistema (dim → brillante al hover) */
/* rojo: el año ES el dato de la trayectoria (misma regla que el año del premio y
   los contadores de logros). Y de paso arregla un incumplimiento: #3d4568 daba
   2.17:1, por debajo del 3:1 mínimo para texto grande. En rojo, 4.63:1. */
.tl-item .year { color:var(--red); }
.tl-item:hover .year { color:#fff; }

/* eyebrows de sección en azul claro (ritmo de color, sin repintar el sitio) */
.page-hero .label, .sedes > .label, .cta .label { color:var(--acc-lt); }

/* focus de formularios: azul de marca (antes blanco) */
.form input:focus, .form select:focus, .form textarea:focus { border-bottom-color:var(--acc); }

/* ============================================================
   v54 — foto real por miembro del crew (fallback a iniciales)
   ============================================================ */
.team-card .ph { position:relative; overflow:hidden; }
.team-card .ph img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  filter:grayscale(.15) contrast(1.02); transition:filter var(--m-mid) var(--e-out),transform var(--m-mid) var(--e-out); }
.team-card:hover .ph img { filter:none; transform:scale(1.04); }

/* ============================================================
   v55 — Nosotros: banda del crew a sangre + Filosofía como
   manifiesto (skill impeccable · layout). Ritmo: texto → foto
   → manifiesto. Espacio con intención, no vacío accidental.
   ============================================================ */
/* foto grupal del crew, full-bleed, fundida al negro arriba y abajo */
/* márgenes del SISTEMA, no clamps propios: daban 45px arriba y 63px abajo, dos
   huecos que no existían en ninguna otra parte del sitio. Es una banda a sangre
   —como .strips—, así que va en `tight`. */
/* ── Apertura de Nosotros: foto a la izquierda, texto a la derecha ───────────
   La franja a sangre anterior (.crew-band) metía una foto 3:2 en un hueco de
   2.86:1 y le cortaba el 48% del alto. Aquí la foto va CUADRADA, que es el
   recorte más ceñido que no toca a nadie: el grupo ocupa del 22% al 82% del
   ancho y un 1:1 conserva del 17% al 83%. */
.about-hero.has-shot { display:grid; align-items:center;
  grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr); gap:clamp(28px,4.5vw,72px);
  /* menos relleno arriba que el page-hero de una columna: aquí la foto ya da altura
     y con el original (166px) la sección se pasaba de pantalla */
  padding-top:clamp(104px,13vh,140px); }
/* ⚠️ El titular NO puede usar --d-1 aquí: ese token se calcula contra el viewport y
   en una columna de 675px sale a 100px = CINCO líneas. Escala propia, calibrada
   para que quepa en tres. */
/* ⚠️ La ENTRADILLA baja con el titular, o la proporción se rompe.
   El h1 de esta apertura usa una escala reducida (la de abajo) porque va en columna, pero
   `.page-hero .intro` seguía en `--p-lead`, que está calibrado para los heroes de UNA
   columna donde el h1 mide 100px. Medido a 1440: en /proyectos la razón titular/párrafo
   es 4.08 y aquí se quedaba en 2.47 — el párrafo pesaba 65% de más contra su titular y
   por eso se leía sobredimensionado.
   Con `--p-body` sube a 3.5, y de paso es el MISMO tamaño que la descripción de las
   divisiones más abajo, así que la página deja de tener dos tamaños de párrafo. Como
   efecto lateral la medida cae de ~46 a ~65 caracteres por línea, que es la buena. */
.about-hero.has-shot .intro { font-size:var(--p-body); line-height:1.6; max-width:60ch; }
.about-hero.has-shot h1 { font-size:clamp(34px,4.2vw,64px);
  /* el titular cierra el bloque, va DESPUÉS del párrafo (v248): necesita aire arriba,
     que antes se lo daba el `margin-top` del propio párrafo cuando iba debajo */
  margin-top:clamp(24px,3.4vh,44px); }
.about-shot { margin:0; position:relative; aspect-ratio:1/1; overflow:hidden;
  border-radius:var(--r-lg); border:1px solid var(--line); background:var(--surface); }
.about-shot img { width:100%; height:100%; object-fit:cover; object-position:center; display:block; }
/* el mismo canto de luz que el resto de superficies, para que no flote suelta */
.about-shot::after { content:''; position:absolute; inset:0; pointer-events:none;
  border-radius:inherit; box-shadow:inset 0 1px 0 rgba(255,255,255,.06); }
@media (max-width:860px) {
  /* apilado: primero el texto, la foto debajo — en móvil el titular debe abrir */
  .about-hero.has-shot { grid-template-columns:1fr; gap:clamp(24px,5vw,36px); }
  /* cuadrada también en móvil: el archivo YA está recortado a 1:1, así que un 4:3
     volvería a recortar y se comería el 12% de abajo — la primera fila y el banner. */
  .about-shot { order:2; }
  .about-copy { order:1; }
}

/* ============================================================
   Filosofía (v159): REUTILIZA el riel anclado de Premios (.aw-*).
   No es una sección distinta: es el mismo componente con otra variante, así que
   comparte CSS, JS, comportamiento móvil y fallback de reducir-movimiento. Aquí
   sólo cambia lo que de verdad difiere: no hay columna lateral (ni etiquetas ni
   descripción), y el texto es prosa, no un título en mayúsculas.
   ============================================================ */
/* La variante `.filo` del riel (2 columnas, prosa cruzando la foto, panel de portada
   sólo-imagen) se retiró en v220 junto con la sección. Capabilities heredó el riel pero
   en su variante COMPLETA de 3 zonas, que es la misma del blog y no necesita reglas
   propias. `section.caps` toma el relevo del `padding-block:0` unas líneas más abajo. */

/* ── BLOG: listado de entradas ─────────────────────────────────────────────────
   Tarjetas con foto arriba y texto abajo. NO se reutiliza .svc-card porque aquella
   lleva su propio tratamiento de número y contador sobre la imagen. */
/* ---- las secciones del blog: riel de filtros ----
   Son <a> con su propia URL (/blog?c=premios), no botones: se comparten, Google las
   ve y funcionan sin JS. El JS sólo evita la recarga al cambiar de pestaña.
   La activa va en blanco sólido y el hover en azul, que aquí manda la regla del
   sistema: azul = interacción. El rojo queda para el dato (la categoría en la ficha). */
.blog-filtros { display:flex; flex-wrap:wrap; gap:10px;
  margin-bottom:clamp(26px,3.4vw,44px); }
.bf { display:inline-flex; align-items:center; gap:9px; padding:11px 18px;
  border:1px solid var(--line); border-radius:999px; background:var(--surface);
  font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.1em; text-transform:uppercase;
  color:var(--txt-soft); white-space:nowrap;
  transition:color var(--m-base) var(--e-out), border-color var(--m-base) var(--e-out),
             background var(--m-base) var(--e-out); }
/* ⚠️ SIN `font-size:.9em`: la pastilla ya va en --t-xs (11px), que es el piso de la
   escala, así que el .9em la dejaba en 9.9px — por debajo del mínimo del sitio. El
   contador se distingue por COLOR, no por tamaño. */
.bf i { font-style:normal; color:var(--mut);
  transition:color var(--m-base) var(--e-out); }
.bf:hover { color:#fff; border-color:var(--line-2); background:var(--surface-2); }
.bf.is-on { background:#fff; border-color:#fff; color:var(--bg); }
.bf.is-on i { color:rgba(5,6,10,.45); }
/* sin entradas todavía: se anuncia la sección pero no se vende como llena */
.bf[data-vacia] { opacity:.5; }
.bf.is-on[data-vacia] { opacity:1; }
@media (max-width:700px) {
  /* con 5 pestañas y nombres largos, envolver comía cuatro renglones: se desliza */
  .blog-filtros { flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none;
    -webkit-overflow-scrolling:touch; padding-bottom:2px; }
  .blog-filtros::-webkit-scrollbar { display:none; }
  .bf { flex:0 0 auto; }
}

.blog-grid { display:grid; gap:clamp(18px,2.4vw,32px);
  grid-template-columns:repeat(auto-fill,minmax(min(320px,100%),1fr)); }
.blog-card { display:flex; flex-direction:column; border:1px solid var(--line);
  border-radius:var(--r-lg); overflow:hidden; background:var(--surface);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.055);
  /* `opacity` entra en la lista para que al cambiar de sección la ficha aparezca
     con un fundido: el JS sólo fija el estado final y de animarlo se encarga esto. */
  transition:border-color var(--m-base) var(--e-out), background var(--m-base) var(--e-out),
             opacity var(--m-mid) var(--e-out), transform var(--m-mid) var(--e-out); }
.blog-card:hover { border-color:var(--line-2); background:var(--surface-2); transform:translateY(-6px); }
.blog-card:active { transform:scale(.99); }
/* 16:10, la misma proporción que pide el admin, así la foto nunca se deforma */
.blog-shot { aspect-ratio:16/10; overflow:hidden; background:#0b0c12; }
.blog-shot img { width:100%; height:100%; object-fit:cover; display:block;
  transition:transform var(--m-slow) var(--e-out); }
.blog-card:hover .blog-shot img { transform:scale(1.04); }
.blog-body { display:flex; flex-direction:column; gap:clamp(8px,1vw,12px);
  padding:clamp(20px,2.4vw,30px); flex:1; }
.blog-meta { display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  font:500 var(--t-xs)/1 var(--font-b); color:var(--mut);
  text-transform:uppercase; letter-spacing:.1em; }
.blog-kick { color:var(--red); }
/* en la ficha la sección es un enlace: regresa a su pestaña del blog */
a.blog-kick:hover { color:var(--acc-lt); }
/* las etiquetas sueltas quedan por debajo de la sección, que ahora es el dato fuerte */
.blog-kick-soft { color:var(--mut); }
/* el medio de una nota de prensa: dato, no acción — por eso no es enlace ni va en azul */
.post-medio { color:var(--mut); text-transform:none; letter-spacing:0; font-size:var(--t-sm); }
.post-medio b { color:var(--txt-soft); font-weight:600; }
/* v285 · el crédito del medio ahora ENLAZA al artículo original. El <a> global va con
   `color:inherit; text-decoration:none`, así que sin esto el enlace se vería idéntico a
   texto plano y nadie sabría que se puede pulsar — que es justo lo contrario de lo que
   pide el acuerdo con el medio. Mismo recurso que `.legal a`: azul claro + filete.
   ⚠️ El :hover va dentro de la guarda (hover:hover): suelto, en un móvil se queda PEGADO
   al primer toque, que es la trampa ya documentada en este archivo con .svc-card. */
.post-medio a { color:var(--acc-lt); border-bottom:1px solid var(--line-2); padding-bottom:2px;
  transition:color var(--m-base) var(--e-out), border-color var(--m-base) var(--e-out); }
.post-medio a b { color:inherit; }
@media (hover:hover) and (pointer:fine) {
  .post-medio a:hover { color:var(--fg); border-color:var(--acc); }
}
/* subtítulos dentro del cuerpo de una nota (bloques que empiezan con «## ») */
.post-h { font:700 var(--d-4)/1.15 var(--font-d); text-transform:uppercase; color:#fff;
  letter-spacing:.01em; margin-top:clamp(28px,3.4vw,46px); margin-bottom:clamp(10px,1.2vw,16px); }
.post-body > .post-h:first-child { margin-top:0; }
.blog-title { font:700 var(--d-4)/1.15 var(--font-d); text-transform:uppercase;
  letter-spacing:.01em; color:#fff; }
.blog-exc { color:var(--txt-soft); font:400 var(--p-body)/1.55 var(--font-b); flex:1; }
/* El enlace a la izquierda y el sello del medio en la esquina OPUESTA. `margin-top:auto`
   pasó del enlace a la fila entera: si se queda en el enlace, el sello no baja con él y
   las tarjetas de distinto alto dejan de alinear su pie. */
.blog-pie { display:flex; align-items:center; justify-content:space-between;
  gap:14px; margin-top:auto; padding-top:clamp(10px,1.2vw,16px); }
/* v286 · el sello del medio pasa a ser un PILL SOBRE la foto, esquina superior izquierda.
   ⚠️ Los 5 logos son BLANCO PURO sobre transparente (medido: luminancia 255, saturación 0),
   así que el pill TIENE que ser oscuro — uno claro los volvería invisibles. Y por eso el
   logo va a opacidad 1: sobre fondo sólido ya no hace falta atenuarlo, que era lo que hacía
   antes para no competir con el texto de la tarjeta.
   ⚠️ Nada de backdrop-filter: va encima de una foto y dentro de una tarjeta que se
   transforma en el hover, justo donde Safari lo muestrea mal (ya documentado con el vidrio
   de testimonios y los controles del demoreel). Fondo sólido y a lo seguro.
   ⚠️ La separación del canto NO es estética: `.blog-card` recorta con radio de 18px, así que
   un pill pegado a la esquina se comería el redondeo. */
.blog-shot { position:relative; }
.blog-medio { position:absolute; top:clamp(12px,1.3vw,16px); left:clamp(12px,1.3vw,16px);
  z-index:2; display:inline-flex; align-items:center; padding:8px 14px;
  background:var(--bg); border-radius:var(--r-pill);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06), 0 10px 24px -14px rgba(0,0,0,.95); }
.blog-medio img { max-width:92px; max-height:20px; width:auto; height:auto;
  object-fit:contain; opacity:1; }
/* sin logo, el nombre en texto pequeño: el mismo dato, sin hueco raro */
.blog-medio-txt { font:600 11px/1 var(--font-b); letter-spacing:.1em;
  text-transform:uppercase; color:var(--txt-soft); }

/* enlace dentro del panel del carrusel de la portada */
.aw-link { display:inline-block; margin-top:clamp(14px,1.8vw,24px);
  font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.1em; text-transform:uppercase;
  color:#fff; border-bottom:1px solid var(--line-2); padding-bottom:6px;
  transition:border-color var(--m-base) var(--e-out), color var(--m-base) var(--e-out); }
.aw-link:hover { color:var(--acc-lt); border-color:var(--acc); }  /* azul = interacción. En rojo chocaba con el año del panel, que ES rojo,
     y rompía la regla del sistema (rojo = posición y dato, no acción). */
.aw-link span { display:inline-block; margin-left:6px;
  transition:transform var(--m-base) var(--e-out); }
.aw-link:hover span { transform:translateX(4px); }

/* nota individual */
.post-meta { display:flex; gap:12px; flex-wrap:wrap; align-items:center;
  font:500 var(--t-xs)/1 var(--font-b); color:var(--mut);
  text-transform:uppercase; letter-spacing:.1em; margin-bottom:clamp(16px,2vw,26px); }
.post-lead { font:400 var(--p-lead)/1.5 var(--font-b); color:#fff; max-width:46ch; }
.post-body { margin-top:clamp(22px,3vw,40px); max-width:68ch; }
.post-body p { color:var(--txt-soft); font:400 var(--p-body)/1.7 var(--font-b);
  margin-bottom:clamp(14px,1.8vw,24px); }

/* ============================================================
   v56 — imagen en el banner del CTA "¿Cuál es tu próxima misión?".
   Desktop: anclada abajo-izquierda (el corte de la foto queda en
   el borde inferior → no se ve "mocho"); texto a la derecha.
   Móvil: al final, ancho completo, pegada abajo.
   ============================================================ */
.cta.has-media { position:relative; overflow:hidden; }
.cta-content { position:relative; z-index:2; }
.cta-media { position:absolute; z-index:1; left:0; bottom:0; pointer-events:none;
  width:min(56%, 820px); height:auto;
  filter:drop-shadow(0 0 60px rgba(74,92,255,.12)); }
@media (min-width:900px){
  .cta.has-media { display:flex; align-items:center; min-height:clamp(440px,58vh,600px); }
  .cta.has-media .cta-content { width:min(560px, 46%); margin-left:auto; }
}
@media (max-width:899px){
  .cta.has-media { padding-bottom:0; }
  .cta-media { position:relative; left:auto; bottom:auto; width:100%; display:block;
    margin-top:clamp(28px,6vh,56px); }
}

/* ============================================================
   v57 — svc-cards estilo Apple: imagen full-bleed + scrim para el
   texto + HALO de color (copia borrosa de la foto que sangra al
   negro). El color del sitio sale de las fotos, no de pintar.
   ============================================================ */
.svc-cards { gap:clamp(22px,2.6vw,36px); }         /* aire para que respiren los halos */

/* TODAS las cards pasan a formato retrato con texto sobrepuesto */
.svc-card { aspect-ratio:4/5; position:relative; display:block; padding:0; min-height:0;
  border:0; background:none; overflow:visible; isolation:isolate; border-radius:var(--r-lg);
  box-shadow:none; transition:transform var(--m-mid) var(--e-out); }
.svc-card:hover { transform:translateY(-6px); box-shadow:none; }

/* la foto (o superficie si no hay), nítida, recortada y redondeada */
.svc-card .media { position:absolute; inset:0; aspect-ratio:auto; border-radius:var(--r-lg);
  overflow:hidden; background:linear-gradient(160deg,#14151d,#08090e); }
.svc-card .media img { width:100%; height:100%; object-fit:cover; transition:transform var(--m-slow) var(--e-out); }
.svc-card:hover .media img { transform:scale(1.05); }

/* ---- SÓLO en /proyectos: las tarjetas en blanco y negro, a color al pasar el mouse ----
   El selector arranca en `.svc-cards`, el contenedor en plural, que únicamente existe en
   proyectos.php: la portada monta las mismas tarjetas dentro de `.svcs-orbit`, así que no
   se entera. Tampoco entra a las páginas de servicio ni a las fichas de proyecto.

   Va sobre la FOTO y sobre la pastilla del número, no sobre la tarjeta entera: un
   `filter` en el padre la convierte en «backdrop root» y el scrim de la tarjeta
   (`.media::after`, que va con backdrop-filter) dejaría de leer lo que tiene detrás.

   Encerrado en (hover:hover): en un teléfono no hay ratón que devuelva el color, y sin
   esto la página entera de proyectos se quedaría en blanco y negro para siempre.
   Las transiciones se repiten completas a propósito: al ser un selector más específico,
   declarar sólo `transition:filter` habría borrado la de `transform` y la foto dejaría
   de acercarse en el hover. */
@media (hover:hover) and (pointer:fine) {
  .svc-cards .svc-card .media img {
    filter:grayscale(1);
    transition:transform var(--m-slow) var(--e-out), filter var(--m-mid) var(--e-out); }
  .svc-cards .svc-card:hover .media img { filter:grayscale(0); }
}
/* ---- v284: el número y el título, en ROJO sobre la foto en gris ----
   Con la foto en blanco y negro, la tarjeta se quedaba sin un solo punto de color y el
   título blanco sobre gris no tenía dónde agarrarse. El rojo es el único acento que
   entra aquí: en el sistema significa «dato», y el número de misión lo es.
   ⚠️ El número ANTES iba con `filter:grayscale(1)`, que le quitaba el rojo justamente
   para acompañar a la foto. Esa regla se fue: es lo que pedía Christian.
   ⚠️ Va con `.svc-cards` (contenedor en PLURAL), que sólo existe en proyectos.php —
   verificado. La rueda del home usa `.svcs-orbit .svc-card` y no se entera.
   ⚠️ Y va FUERA del `@media (hover:hover)`: dentro, en un teléfono —donde no hay
   hover— el número habría seguido en gris y el título en blanco, o sea el cambio no
   se vería en móvil. El color no depende de que haya ratón. */
.svc-cards .svc-card .num { filter:none; }
/* ⚠️ El título va en `--acc-lt`, NO en `--acc`. Medido sobre los tres niveles de velo de
   la tarjeta: el azul de marca da 4.02 / 2.88 / 1.80:1 —falla en cuanto la foto asoma—
   y el claro da 7.51 / 5.37 / 3.36:1, que pasa en los tres. Es además el azul que el
   sistema ya usa para texto sobre oscuro (`.aw-link:hover`, `.legal a`).
   Se probó primero en ROJO y Christian lo descartó: se veía raro. El azul encaja mejor
   con la regla del sistema —azul = interacción, rojo = dato— y el título ES un enlace.
   El NÚMERO se queda rojo: ése sí es un dato. */
.svc-cards .svc-card .body h3 {
  color:var(--acc-lt);
  transition:color var(--m-base) var(--e-out); }
/* ⚠️ El hover va dentro de `hover:hover`, no suelto. En un teléfono el `:hover` se queda
   PEGADO al tocar la tarjeta —ya pasó en este archivo con `.svc-card:hover`, está
   documentado— y el título se habría quedado blanco para siempre tras el primer toque. */
@media (hover:hover) and (pointer:fine) {
  .svc-cards .svc-card:hover .body h3 { color:#fff; }
}
/* BLUR real de abajo hacia arriba (frosted, tipo Apple): backdrop-filter enmascarado
   con degradado + tinte oscuro para leer el texto. Fuerte abajo, nulo arriba.
   ⚠️ Sólo de 861px para arriba: ver el bloque móvil, donde se cambia por un degradado
   plano. En un teléfono son 6 tarjetas desenfocando su fondo a la vez y el efecto no
   se distingue del degradado; en la rueda ya se había tomado la misma decisión. */
.svc-card .media::after { content:''; position:absolute; inset:0; pointer-events:none;
  -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
  background:linear-gradient(to top, rgba(0,0,0,.72) 2%, rgba(0,0,0,.32) 30%, transparent 56%);
  -webkit-mask:linear-gradient(to top, #000 0%, #000 20%, transparent 54%);
          mask:linear-gradient(to top, #000 0%, #000 20%, transparent 54%); }

/* El HALO de color (copia borrosa de la foto sangrando fuera de la card) se QUITÓ:
   pesaba demasiado y ensuciaba el negro. La tarjeta se sostiene con su borde y su
   canto de luz. Con él se va también la custom property `--card-img` del markup. */

/* número + contador arriba, sobre la foto */
.svc-card .top { position:absolute; z-index:2; inset:16px 16px auto; display:flex;
  justify-content:space-between; align-items:center; gap:12px; margin:0; }

/* texto SOBRE la foto, anclado abajo */
.svc-card .body { position:absolute; z-index:2; inset:auto 0 0 0; display:flex; flex-direction:column;
  flex:none; gap:7px; padding:clamp(18px,1.9vw,26px); }
.svc-card .body h3 { color:#fff; margin:0; }
.svc-card .body p { color:rgba(255,255,255,.74); max-width:32ch; margin:0;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }

/* ============================================================
   v58 — Demoreel (reemplaza los showcases destacados) + banners
   de premios/reconocimientos. Home.
   ============================================================ */
/* Primer momento grande después del hero: respiro amplio arriba.
   ⚠️ `section.reel`, NO `.reel`: la regla general `section[data-section]` es (0,1,1) y
   una sola clase (0,1,0) PIERDE — el margen se quedaba en `base`. Tercera vez que esta
   misma trampa muerde en este archivo (ver section.svcs y section.awards). */
section.reel { margin-top:var(--sec-base); }
.reel-frame { position:relative; width:100%; aspect-ratio:16/9; }
/* Nota de consistencia: Safari NO muestrea los cuadros de un <video> con
   backdrop-filter (toma lo que hay detrás del video), así que estos controles se
   veían planos en Safari y esmerilados en Chrome. Los fondos de .reel-launch y
   .reel-ctrl van lo bastante opacos como para leerse igual en ambos; el blur es
   sólo matiz. Si tocas esos valores, mantenlos ≥.6. */
/* el cuadro nítido, recortado con esquinas redondeadas */
/* sin box-shadow azul: llevaba un aro de --acc-tint y un halo de --acc-glow que
   sangraba bajo el cuadro. Queda el borde neutro. Las mismas dos sombras se usan en
   los hover de tarjetas (svc-card, project-card…) y ESAS se quedan: aquí sólo se
   quita la del video. */
.reel-stage { position:absolute; inset:0; z-index:1; overflow:hidden; border-radius:var(--r-xl);
  background:#06070c; border:1px solid var(--line); }
.reel-video, .reel-embed { position:absolute; inset:0; width:100%; height:100%; border:0; display:block; }
.reel-video { object-fit:cover; }
/* botón central para iniciar — ícono de play (SVG, sin emoji) */
.reel-launch { position:absolute; z-index:3; inset:0; margin:auto;
  width:clamp(62px,7vw,86px); height:clamp(62px,7vw,86px); display:grid; place-items:center;
  border-radius:50%; color:#fff; background:rgba(10,11,18,.62);
  -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
  border:1px solid rgba(255,255,255,.32); cursor:pointer;
  transition:background var(--m-base) var(--e-out),border-color var(--m-base) var(--e-out),transform var(--m-base) var(--e-out),opacity var(--m-mid) var(--e-out); }
.reel-launch svg { width:38%; height:38%; fill:currentColor; margin-left:6%; display:block; }
.reel-launch:hover { background:rgba(74,92,255,.55); border-color:var(--acc); transform:scale(1.08); }
.reel-frame.is-playing .reel-launch { opacity:0; pointer-events:none; transform:scale(.9); }
/* ---- el botón central en modo "activar sonido" (v206) ----
   Con el autoarranque el video llega ya reproduciéndose EN MUDO, así que el play
   central sobra y desaparecía al instante: no quedaba ninguna pista de que se le
   puede poner audio. En ese estado el mismo botón se convierte en una pastilla con
   etiqueta y SÍ se queda visible aunque el video esté corriendo.
   ⚠️ Va DESPUÉS de la regla de arriba y con una clase más (0,3,0 contra 0,2,0):
   si no, el `opacity:0` de `.is-playing` lo apaga igual. */
.reel-frame.is-playing .reel-launch.is-sound {
  opacity:1; pointer-events:auto; transform:none;
  width:max-content; height:max-content; padding:14px 22px 14px 18px;
  border-radius:var(--r-pill); grid-auto-flow:column; gap:10px;
  font:600 var(--t-sm)/1 var(--font-b); letter-spacing:.02em; white-space:nowrap; }
.reel-launch.is-sound svg { width:20px; height:20px; margin-left:0; flex:0 0 auto; }
/* a los 5s se hace discreto, pero sigue ahí y sigue siendo clicable: tiene que verse
   para que se sepa que existe, sin tapar el video el resto del tiempo */
.reel-frame.is-playing .reel-launch.is-sound.is-soft { opacity:.62; transform:scale(.92); }
.reel-frame.is-playing .reel-launch.is-sound:hover,
.reel-frame.is-playing .reel-launch.is-sound:focus-visible { opacity:1; transform:none; }
/* barra de controles — aparece al pasar el mouse o en pausa */
.reel-bar { position:absolute; z-index:3; left:0; right:0; bottom:0; display:flex; align-items:center;
  gap:clamp(10px,1.4vw,16px); padding:clamp(12px,1.6vw,20px);
  background:linear-gradient(to top, rgba(4,5,10,.74), rgba(4,5,10,0));
  opacity:0; transform:translateY(8px); transition:opacity var(--m-base) var(--e-out),transform var(--m-base) var(--e-out); pointer-events:none; }
.reel-frame.is-started:hover .reel-bar,
.reel-frame.is-started:not(.is-playing) .reel-bar { opacity:1; transform:none; pointer-events:auto; }
.reel-ctrl { flex:none; font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.08em; text-transform:uppercase;
  color:#fff; background:rgba(10,11,18,.66); -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  border:1px solid rgba(255,255,255,.22); border-radius:var(--r-pill); padding:9px 15px; cursor:pointer;
  transition:background var(--m-base) var(--e-out),border-color var(--m-base) var(--e-out); white-space:nowrap; min-width:104px; text-align:center; }
.reel-ctrl:hover { background:rgba(74,92,255,.5); border-color:var(--acc); }
.reel-ctrl:focus-visible, .reel-launch:focus-visible { outline:2px solid var(--acc-lt); outline-offset:3px; }
.reel-stage:focus-visible { outline:2px solid var(--acc-lt); outline-offset:3px; }
/* tiempo — cifras tabulares para que no bailen */
.reel-time { flex:none; font:500 var(--t-xs)/1 var(--font-b); letter-spacing:.04em;
  color:rgba(255,255,255,.74); font-variant-numeric:tabular-nums; }
/* barra de progreso — engorda al apuntarla, con manija y buffer */
.reel-scrub { flex:1; height:26px; display:flex; align-items:center; cursor:pointer; touch-action:none; }
.reel-prog { flex:1; height:3px; border-radius:3px; background:rgba(255,255,255,.22); position:relative;
  transition:height var(--m-fast) var(--e-out); }
.reel-scrub:hover .reel-prog, .reel-scrub.is-drag .reel-prog { height:6px; }
.reel-buf { position:absolute; left:0; top:0; bottom:0; width:var(--b,0%);   /* cargado */
  background:rgba(255,255,255,.3); border-radius:3px; }
.reel-prog::after { content:''; position:absolute; left:0; top:0; bottom:0; width:var(--p,0%);
  background:var(--acc); border-radius:3px; box-shadow:0 0 10px var(--acc-glow); }
.reel-prog::before { content:''; position:absolute; top:50%; left:var(--p,0%); z-index:2;
  width:13px; height:13px; margin:-6.5px 0 0 -6.5px; border-radius:50%; background:#fff;
  opacity:0; transform:scale(.5); transition:opacity var(--m-fast) var(--e-out),transform var(--m-fast) var(--e-out); }
.reel-scrub:hover .reel-prog::before, .reel-scrub.is-drag .reel-prog::before { opacity:1; transform:scale(1); }
@media (hover:none) { .reel-frame.is-started .reel-bar { opacity:1; transform:none; pointer-events:auto; } }
@media (max-width:620px){ .reel-time { display:none; } .reel-ctrl { min-width:0; padding:9px 13px; } }

/* premios: carrusel de DOS por vista (foto arriba, texto abajo) con controles prev/next */
/* padding 0: el encabezado ahora vive DENTRO del escenario anclado (ver .aw-stage
   .sec-head). Cuando estaba fuera de la pista quedaba casi una pantalla de negro
   entre "Reconnocimientos" y el primer panel, porque el panel va centrado en un
   escenario de 100svh y el título quedaba arriba, en flujo normal. */
/* ⚠️ `section.awards`, NO `.awards`: la regla general es `section[data-section]`
   con especificidad (0,1,1) y una sola clase (0,1,0) PIERDE — quedaban 108px de
   relleno muerto antes de que empezara el anclaje. Misma trampa que section.svcs. */
section.awards { padding-block:0; margin-block:var(--sec-base); }
/* Clientes y su muro de logos son UNA unidad: sin corte entre los dos. Estaba como
   estilo inline en el markup (padding-bottom:0 / margin-bottom:0), fuera del sistema. */
section.clients { margin-bottom:0; }
section.clients .sec-head { margin-bottom:0; }
.bigsay p { margin-top:var(--sp-xs); }
/* utilidades del sistema, para no volver a escribir espaciado en atributos style */
section[data-section].pt-0 { padding-top:0; }
.empty-note { color:var(--mut); padding-block:var(--sp-md); }
.pbody { padding-top:var(--sp-md); }

/* ============================================================
   v129 — PREMIOS: paneles editoriales en scroll horizontal anclado.
   Reemplaza al carrusel de "dos por vista" con flechas (su CSS y su JS se
   ELIMINARON: .award-row/.award/.award-nav/.aw-btn ya no existen).
   El encabezado de sección queda FUERA de la pista, así entra con el scroll
   normal y el anclaje empieza después.
   ============================================================ */
.aw-track { position:relative; width:100vw; margin-inline:calc(50% - 50vw); }
/* columna: el riel y la barra de avance forman UN grupo que se centra junto.
   Antes la barra iba `position:absolute` clavada al fondo del escenario mientras el
   panel se centraba, así que quedaban ~154px de negro entre los dos. */
.aw-stage { position:sticky; top:0; height:100vh; height:100svh; overflow:hidden;
  display:flex; flex-direction:column; justify-content:center;
  /* el encabezado va en position:absolute (fuera del flujo), así que el riel se
     centraba ignorándolo y lo tapaba en cuanto la foto crecía. Este padding le
     reserva su sitio. */
  padding-top:clamp(118px,17svh,186px); }
/* encabezado anclado ARRIBA del escenario: se ve junto a los paneles, no a una
   pantalla de distancia. Mismo criterio que el título del riel móvil de servicios. */
/* La posición la comparten Premios y Filosofía; la ALINEACIÓN no. Por eso el
   centrado se declara por sección y no aquí: Reconocimientos va a la izquierda y
   Filosofía sigue centrada. */
.aw-stage .sec-head { position:absolute; left:var(--pad); right:var(--pad);
  top:clamp(76px,11svh,118px); margin:0; z-index:4; pointer-events:none;
  flex-direction:column; gap:clamp(10px,1.4vw,16px); }
.awards .aw-stage .sec-head { align-items:flex-start; text-align:left; }
.caps   .aw-stage .sec-head { align-items:center;     text-align:center; }   /* heredado de .filo (v220) */
/* position:relative para que sea el offsetParent de los paneles: el JS mide el
   recorrido con offsetLeft (no con rect, que cambiaría con el translate) */
.aw-rail { position:relative; display:flex; align-items:center;
  gap:clamp(24px,3vw,56px); padding-inline:var(--pad); will-change:transform; }

/* Panel de 86vw: deja asomar el siguiente por la derecha, como la referencia.
   ⚠️ ESTE COMPONENTE LO COMPARTEN DOS CARRUSELES: el de notas de la portada y el de
   Flight crew en /nosotros. En v253 lo pasé a dos columnas para las notas sin caer en
   que Nosotros sigue montando TRES hijos (.aw-head, .aw-media, .aw-side) — el tercero
   se iba a una segunda fila. De ahí que la maqueta nueva viva en `.is-nota` y ésta,
   la de tres columnas con el título cruzando la foto, siga siendo la de por defecto.

   Las tres áreas se SOLAPAN a propósito: el título ocupa las columnas 1-2, así que su
   texto se cruza con la foto (que vive en la 2). */
.aw-panel { flex:0 0 86vw; position:relative; display:grid;
  /* la columna del centro pesa más: la foto es el héroe del panel. Con .9/1.08/.92
     salía de 433px y en la referencia ocupa ~40% del panel, no 35%. */
  grid-template-columns:minmax(0,.82fr) minmax(0,1.35fr) minmax(0,.83fr);
  gap:clamp(18px,2.6vw,44px); align-items:center; }
/* ⚠️ EL BLEND VA AQUÍ, NO EN .aw-title. `mix-blend-mode` se mezcla con el backdrop
   del grupo aislado más cercano, y .aw-head YA crea uno propio (es un grid item con
   z-index): puesto en el título, éste se mezclaba contra el fondo TRANSPARENTE de su
   contenedor y la foto —que es un HERMANO, fuera de ese grupo— nunca entraba. */
.aw-head  { grid-area:1/1/2/3; z-index:2; pointer-events:none;
  mix-blend-mode:difference; }
.aw-media { grid-area:1/2/2/3; z-index:1; }
.aw-side  { grid-area:1/3/2/4; z-index:2; }

/* ---- la variante de las NOTAS (v253): texto a la izquierda, foto a la derecha ----
   Aquí no hay solape, así que no hay blend: el título es blanco normal sobre el fondo.
   `.aw-media` vuelve a `grid-area:auto` o seguiría clavada en la columna 2 de la
   rejilla vieja, que en ésta ni existe. */
.aw-panel.is-nota { grid-template-columns:minmax(0,1fr) minmax(0,1.06fr);
  gap:clamp(24px,4vw,68px); }
.aw-panel.is-nota:not(.has-media) { grid-template-columns:minmax(0,1fr); }
.aw-panel.is-nota .aw-media { grid-area:auto; }
.aw-info { display:flex; flex-direction:column; align-items:flex-start; }
.aw-panel.is-nota .aw-title { max-width:17ch; margin-top:clamp(10px,1.2vw,16px); }

/* el sello del medio abre la columna: lo primero es de dónde salió la nota */
/* v286 · mismo pill que en las tarjetas de /blog. Al salir de la columna de texto, el año
   pasa a ser el primer elemento y el titular recupera ese alto. */
.aw-medio { position:absolute; top:clamp(14px,1.4vw,20px); left:clamp(14px,1.4vw,20px);
  z-index:2; display:inline-flex; align-items:center;
  padding:clamp(9px,.9vw,12px) clamp(14px,1.4vw,19px);
  background:var(--bg); border-radius:var(--r-pill);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06), 0 10px 24px -14px rgba(0,0,0,.95); }
.aw-medio img { max-width:clamp(100px,9.5vw,138px); max-height:clamp(20px,2vw,26px);
  width:auto; height:auto; object-fit:contain; opacity:1; }
/* respaldo sin logo: el nombre en el mismo tono y caja que el año, para que la
   columna no cambie de altura según haya imagen o no */
.aw-medio-txt { font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.14em;
  text-transform:uppercase; color:var(--fg); }

/* el año, donde antes iba "Reconocimiento 01": numerar los premios no aportaba
   nada y el año sí es dato. Va en color de acento, como estaba en .aw-year. */
.aw-num { display:block; font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.14em;
  text-transform:uppercase; color:var(--red); }
/* ⚠️ LA FIRMA DEL EFECTO en el carrusel de Nosotros: el título cruza la foto y donde
   se solapa cambia de color solo. `difference` contra el fondo casi negro deja el texto
   BLANCO, y contra la foto lo invierte. En `.is-nota` no aplica: ahí no hay solape. */
/* El icono de la división, sobre el título (v260). Alto fijo y ancho automático: los ocho
   PNG vienen recortados a su contenido y con proporciones distintas (de 202x200 a 398x266),
   así que topar el ANCHO los dejaría de tamaños dispares. Con el alto fijo se ven de la
   misma familia, que es como están pensados.
   No lleva `filter`: los archivos ya se subieron en BLANCO. Venían en trazo negro, que
   sobre este fondo no se vería. */
.aw-icon { display:block; height:clamp(34px,3.4vw,52px); width:auto;
  margin-bottom:clamp(10px,1.2vw,16px); }

.aw-title { margin-top:clamp(12px,1.4vw,20px); max-width:15ch; color:#fff;
  font:700 var(--d-2)/.94 var(--font-d); text-transform:uppercase; letter-spacing:.01em; }

/* 7/5, medido sobre la referencia (695x495 en su captura), no 4/3 */
.aw-media { position:relative; border-radius:var(--r-lg); overflow:hidden; aspect-ratio:7/5; background:#06070c; }
.aw-media img { width:100%; height:100%; object-fit:cover; display:block; }

.aw-tags { display:flex; flex-wrap:wrap; gap:clamp(8px,.9vw,12px); list-style:none; }
.aw-tags li { font:500 var(--t-xs)/1 var(--font-b); padding:13px 17px;
  border:1px solid var(--line); border-radius:var(--r-md); color:var(--txt-soft);
  background:rgba(255,255,255,.02); }
.aw-desc { margin-top:clamp(18px,2.4vw,34px); max-width:34ch;
  font:400 var(--p-body)/1.55 var(--font-b); color:var(--txt-soft); }

/* avance + contador: mismo lenguaje que el scrubber del demoreel y el riel móvil.
   En FLUJO (no absolute) justo debajo del riel: ver la nota de .aw-stage. */
.aw-prog { display:flex; align-items:center; gap:clamp(12px,1.6vw,22px);
  margin-top:clamp(14px,2.6vh,40px); padding-inline:var(--pad); z-index:3; }
/* la barra ocupa TODO el ancho del contenido (era min(320px,34vw), un muñón suelto
   en la esquina): así mide lo mismo que los paneles y el contador cierra a la derecha */
.aw-bar { flex:1; height:2px; border-radius:2px;
  background:var(--line); overflow:hidden; }
.aw-bar i { display:block; height:100%; background:var(--red);
  transform:scaleX(0); transform-origin:0 50%; }
.aw-count { font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.1em; color:var(--mut);
  font-variant-numeric:tabular-nums; }

@media (max-width:860px) {
  /* apilado: no hay solape, así que el blend se apaga (y con él el aislamiento) */
  .aw-panel,
  .aw-panel.is-nota,
  .aw-panel.is-nota:not(.has-media) { flex:0 0 82vw; grid-template-columns:1fr;
    gap:clamp(12px,3vw,20px); align-content:center; }
  /* apilado no hay solape → el blend se apaga (y con él el aislamiento, que en un
     teléfono no sale gratis) */
  .aw-head, .aw-media, .aw-side { grid-area:auto; }
  .aw-head { mix-blend-mode:normal; }
  .aw-medio img { max-height:22px; }
  .aw-title { max-width:none; font-size:var(--d-3); }
  /* El panel se centra en el hueco que queda ENTRE el encabezado y la barra de
     avance. Con sólo padding-top, el panel más alto (el de descripción larga)
     alcanzaba la barra por 3px. */
  .aw-stage { padding:clamp(130px,19svh,165px) 0 clamp(20px,3svh,32px); }
  .aw-media { aspect-ratio:16/10; }
  .aw-tags li { padding:10px 13px; }
  .aw-desc { margin-top:clamp(12px,3vw,20px); max-width:none; }
}

/* ============================================================
   v100 — consistencia Safari/Chrome: si el navegador no soporta
   backdrop-filter (o lo desactiva por ahorro de energía), los
   elementos de vidrio caen a fondo sólido en vez de quedar lavados.
   ============================================================ */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))) {
  .reel-launch, .reel-ctrl { background:rgba(10,11,18,.88); }
  .svc-card .cnt           { background:rgba(8,8,8,.88); }
  .testi2-card             { background:linear-gradient(160deg,#0f111c,#090a11); }
}

/* ============================================================
   v104 — el astronauta TAMBIÉN en móvil, con parallax de scroll.
   Antes se ocultaba en <=560px. En táctil el logo WebGL no corre
   (hero-logo.js sale con !fine y deja .no-fx + el SVG estático), pero las
   estrellas SÍ: quedan tres capas reales para dar profundidad.
   Va aquí abajo a propósito: el display:none vivía en un @media de la
   parte de arriba y una regla temprana no lo habría ganado.
   ============================================================ */
@media (max-width:560px) {
  .hero-astro { display:block; top:24%; width:min(60vw,250px); }
  /* el logo estático se sube un poco para que el astronauta respire detrás */
  .hero.no-fx .hero-static { align-items:flex-start; padding-top:38vh; }
}
@media (max-width:900px) and (min-width:561px) {
  .hero-astro { top:24%; width:min(46vw,300px); }
}

/* ============================================================
   v105 — el marquee de logos no arranca hasta tener sus imágenes.
   Los logos se cargan diferidos (data-src, ver main.js) para no competir con el
   CSS y las fuentes del arranque. Pero la animación empezaba igual, así que en el
   celular el track ya iba desplazado cuando llegaban las imágenes y se veía a
   medio poblar. `is-ready` la pone en marcha cuando la fila está completa.
   Va al final: el bloque v15 de los logos redeclara animation más abajo.
   ============================================================ */
.logo-track { animation-play-state:paused; }
.logo-row.is-ready .logo-track { animation-play-state:running; }
.logo-row.is-ready:hover .logo-track { animation-play-state:paused; }  /* se mantiene el pausar al pasar el mouse */
/* mientras no hay imagen, la celda ya reserva su sitio (min-width) y el logo entra
   con un fundido corto en vez de aparecer de golpe. OJO: la transición repite
   `filter` a propósito — declarar sólo opacity reemplaza la lista entera y se
   perdería el fundido de grises→color del hover. */
.logo-cell img { opacity:0; transition:opacity var(--m-mid) var(--e-out),filter var(--m-base) var(--e-out); }
.logo-row.is-ready .logo-cell img { opacity:.8; }
.logo-row.is-ready .logo-cell:hover img { opacity:1; }

/* ============================================================
   v106 — FONDO GLOBAL: una sola página, no secciones sueltas.
   Dos capas fijas al viewport, detrás de todo el contenido:
     z-index -2  .grain  textura de grano (repetida)
     z-index -1  .sky    campo de estrellas (canvas, main.js)
   Van en NEGATIVO para quedar sobre el fondo del body pero debajo del
   contenido: un position:fixed con z-index:0 se pintaría ENCIMA del texto
   (los positivos y el 0 se pintan sobre el contenido no posicionado).
   ⚠ Ninguna sección debe llevar fondo opaco propio o tapará estas capas:
   por eso se quitó `.hero { background:#000 }`.
   La textura es NUESTRA (generada, 160×160, 8.9 KB); la de la referencia
   pesaba 330 KB y es un asset con licencia de plantilla de pago.
   ============================================================ */
.sky { position:fixed; inset:0; width:100%; height:100%; z-index:-1;
  pointer-events:none; }
body::before { content:''; position:fixed; inset:0; z-index:-2; pointer-events:none;
  background:url('../img/grain.webp') repeat 0 0 / 160px 160px;
  opacity:.045;                       /* sutil: sobre negro puro el grano salta mucho */
  }
/* en pantallas densas el tile se ve el doble de fino si no se compensa */
@media (min-resolution:1.5dppx) { body::before { background-size:120px 120px; opacity:.055; } }
@media (prefers-reduced-motion: reduce) { .sky { opacity:.85; } }

/* ============================================================
   v109 — SERVICIOS DEL HOME EN RUEDA (sólo index; /proyectos sigue en rejilla).
   Corregido contra la referencia real (capturas de Christian):
     · el TÍTULO va CENTRADO y DETRÁS; las cards le pasan por encima y él
       se desenfoca cuando llegan (antes estaba arriba y tapando)
     · primero se ve el título SOLO; las cards entran después
     · cards GRANDES que llenan el ancho (antes: círculo enorme con huecos)
     · giran CON la rueda (antes iban derechas con un vaivén de ±7°)
     · avanzan del 01 al último, de DERECHA A IZQUIERDA
   ============================================================ */
/* A SANGRE: section[data-section] mete `--pad` (~115px en 1440) y el escenario
   quedaba angosto. La rueda debe ocupar el 100% del ancho de la ventana. */
/* `section.svcs` y no `.svcs`: la regla general es `section[data-section]` (0,1,1) y
   una sola clase (0,1,0) PIERDE, así que el padding seguía angostando el escenario.
   Con elemento+clase igualamos especificidad y, al ir después, ganamos. */
section.svcs { position:relative; width:100vw; margin-inline:calc(50% - 50vw); margin-block:var(--sec-base);
  padding-inline:0; padding-block:0; }
.svcs-track { position:relative; height:calc(100svh + var(--n,10) * var(--scroll-item)); }
.svcs-stage { position:sticky; top:0; height:100vh; height:100svh; overflow:hidden; }
/* título centrado y DETRÁS de las cards */
/* La ALTURA del título es su propia variable: sube o baja SOLO el encabezado.
   El planeta va anclado a `bottom:0` y la órbita la posiciona el JS con
   `--orbit-top`, así que ninguno de los dos se entera de este valor — que es justo
   lo que se quería: acercar el título a la sección anterior sin mover ni el mundo
   ni las tarjetas. Con 50% quedaba centrado en el escenario y la sección se sentía
   despegada de lo de arriba. */
.svcs-stage { --svcs-head-y: 38%; }
.svcs-head { position:absolute; z-index:1; left:0; right:0; top:var(--svcs-head-y);
  transform:translateY(-50%); display:flex; flex-direction:column; align-items:center;
  gap:12px; text-align:center; padding:0 var(--pad); pointer-events:none;
  will-change:filter, opacity; }
.svcs-head .sec-title { text-align:center; }
.svcs-head .label { max-width:34ch; }
/* barra de avance del riel: SÓLO móvil (en escritorio el arco ya dice dónde vas) */
.svcs-rail { display:none; }
/* ============================================================
   PLANETA en el CENTRO DE LA ÓRBITA (v141).
   Sin él las cards flotaban sueltas en negro; con él se leen como lo que son:
   en órbita. El punto central vive BAJO el viewport, así que sólo se ve el
   casquete superior — por eso la foto puede venir recortada por abajo.

   ⚠️ Va anclado al PISO del escenario (`bottom:0`), NO al centro de la órbita.
   Primero se ancló al centro (el translate que ponía el centro de la esfera —que
   cae en 49.97%/110.09% de la imagen— sobre el punto de la órbita). Efecto real:
   el borde inferior de la imagen quedaba 196px POR DEBAJO del escenario, así que
   el `overflow:hidden` cortaba el planeta a PLENA OPACIDAD y se veía una raya
   horizontal dura en cuanto el anclaje empezaba a soltarse.
   La foto trae un degradado a transparente en sus ~200 filas finales; anclándola
   al piso, ese degradado TERMINA justo donde recorta el escenario, así que no hay
   corte posible, esté donde esté el scroll.
   El ancho sale del RADIO de la órbita, no de vw: si el radio cambia con el
   viewport y el planeta no, dejan de sentirse ligados.
   ============================================================ */
.svcs-planet { position:absolute; left:50%; bottom:0;
  /* El ancho lo gobierna el ALTO, no el radio: el título vive fijo al 50% del
     escenario, así que si la cima del planeta sube por encima del ~58% lo tapa y
     el texto blanco se vuelve ilegible sobre el limbo iluminado. Con 78svh la cima
     cae al 64% y quedan ~75px de aire bajo el título. El tope en vw evita que en
     una ventana angosta y alta el planeta se salga de lado. */
  width:min(78svh, 96vw); height:auto;
  transform:translateX(-50%);
  z-index:0; pointer-events:none; user-select:none;
  opacity:0; transition:opacity .9s var(--e-out); }
.svcs-planet.is-ready { opacity:1; }

/* punto central de la órbita (queda fuera de cuadro, abajo).
   ⚠️ El `top` se toma de --orbit-top, que main.js escribe en el style del elemento.
   NO se escribe `style.top` directo: el inline le gana a CUALQUIER media query, así
   que al pasar de escritorio a móvil el riel se quedaba con el top de la rueda
   (1465px) y las cards salían disparadas fuera de cuadro. Con la variable, la regla
   móvil (`top:auto`) gana sola y no hay nada que limpiar. */
.svcs-orbit { position:absolute; left:50%; top:var(--orbit-top,165%); width:0; height:0; z-index:2; }
.svcs-orbit .svc-card { position:absolute; left:0; top:0;
  /* Ancho atado al ANCHO **y AL ALTO** de la ventana (ver el cálculo del radio en
     main.js). El término de alto es el que faltaba: con sólo `20vw` una pantalla
     ancha y baja (1920×900) daba una card de 384px que, sumada a la caída del arco
     y al hueco del nav, no cabía y salía cortada por abajo. El divisor 2.6 es la
     suma de esas tres cosas con holgura. Era clamp(250px,24vw,380px). */
  width:clamp(180px, min(20vw, calc((100vh - 90px) / 2.6)), 330px);
  aspect-ratio:4/5; margin:0;
  transform-origin:50% 50%; will-change:transform,opacity;
  /* ⚠ SIN transición de transform NI de opacity: GSAP escribe las dos por frame y la
     regla general de .svc-card las transiciona .45s → las cards se quedaban clavadas
     en el centro y el fundido de entrada/salida iba con retraso. */
  transition:none; }
/* ⚠ NADA de backdrop-filter dentro de la rueda: la card va ROTADA y el navegador
   muestrea mal el backdrop, difuminando la foto ENTERA (se veían todas borrosas).
   Mismo problema que el glass de testimonios. Aquí basta un degradado plano:
   se ve igual de legible y es predecible con cualquier rotación. */
.svcs-orbit .svc-card .media::after {
  -webkit-backdrop-filter:none; backdrop-filter:none;
  -webkit-mask:none; mask:none;
  background:linear-gradient(to top, rgba(0,0,0,.86) 4%, rgba(0,0,0,.5) 32%, transparent 64%); }

/* ---- v265: la tarjeta del orbe pasa a APAISADA, texto izquierda / foto derecha ----
   Con 6 divisiones en vez de 8 las cards se veían chicas en el hueco. En vez de sólo
   agrandarlas, cambian de formato: el texto sale de encima de la foto y se le da su
   propia columna, que es lo que hace que se lean de verdad.

   ⚠️ VA EN `min-width:861px`. En el móvil esta sección NO es un orbe sino un riel
   horizontal, y ahí una card partida en dos columnas deja el texto en ~130px. El riel
   conserva el formato vertical con el texto sobrepuesto.

   ⚠️ Y va DESPUÉS de la regla base: las @media no suman especificidad, así que un
   bloque idéntico colocado antes habría quedado muerto (la trampa de siempre en este
   archivo).

   El ancho y la proporción NO son a ojo: el radio del orbe lo deriva main.js de la
   caja de la card, y ensancharla sube el `piso` (que no se encimen) hasta poder
   rebasar el `tope` (que no las corte el borde de abajo). Simulado con la fórmula real
   del JS sobre 10 viewports (de 1280x620 a 2560x1440): con 16:10 a 27vw el margen
   libre bajo la card nunca baja de 0 y en 1440x900 sobran 98px, contra 85 del 4:5.
   Si algún día se cambia la proporción, hay que rehacer ese barrido. */
@media (min-width:861px) {
  /* ⚠️ LA CAJA NO SE TOCA. El radio del orbe lo deriva main.js de `cw`/`ch`, y el
     margen de holgura (tope − piso, o sea "que no se encimen" contra "que no las corte
     el borde de abajo") va de 111 a 411px con esta caja. Barrido de 19 viewports en
     v278 buscando agrandarla: bajar el divisor de alto de 1.62 a 1.52 daba 533px en
     vez de 500 (+6.6%) pero dejaba el margen en 4–16px y CORTABA en 1024x600. Seis por
     ciento no vale esa fragilidad. Lo que cambió en v278 es SÓLO el interior.
     El ancho vive en `--cw` para que tipografía y espaciados salgan de la misma cuenta
     y no haya dos fórmulas que se desincronicen. */
  .svcs-orbit { --cw:clamp(300px, min(38vw, calc((100vh - 90px) / 1.62)), 680px); }
  .svcs-orbit .svc-card {
    /* 1.9:1 y no 16:10. Con 6 divisiones la tarjeta se veía chica, y la proporción es
       la palanca REAL: el limitante del orbe es el `apex`, que crece con la DIAGONAL de
       la tarjeta, así que una más apaisada cabe más ancha. Barrido con la fórmula del
       JS sobre 17 viewports (1024x768 → 3440x1440): 16:10 topaba en 388px a 1440x900 y
       1.9:1 llega a 500px, un 30% más, con 0 cortes. */
    width:var(--cw);
    aspect-ratio:1.9;
    /* ⚠️ La tarjeta se declara CONTENEDOR y su interior se mide en `cqw` (% del ancho
       REAL de la tarjeta), no con la fórmula de `--cw`. Con «reducir movimiento» la
       rueda se vuelve una rejilla y el ancho real deja de coincidir con la fórmula:
       atado a `--cw`, el titular se calculaba para 500px dentro de una tarjeta de 470
       y «TRANSFORMACIÓN» se salía. Con cqw la cuenta es correcta en los dos casos. */
    container-type:inline-size;
    /* v278: FOTO a la izquierda, TEXTO a la derecha (antes al revés). */
    display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);
    background:var(--surface); border:1px solid var(--line);
    border-radius:calc(var(--cw) * .045); overflow:hidden; }

  /* la foto deja de ser fondo y pasa a ser la mitad IZQUIERDA */
  .svcs-orbit .svc-card .media { position:relative; inset:auto;
    grid-column:1; grid-row:1; height:100%; border-radius:0; }
  /* sin texto encima, el velo sobre la foto ya no pinta nada */
  .svcs-orbit .svc-card .media::after { display:none; }
  /* ⚠️ La foto se DISUELVE hacia el texto en vez de cortarse en seco. Con el borde duro
     la tarjeta se leía como dos cajas pegadas; con la máscara es una sola superficie.
     Se usa `mask`, NO un degradado encima: un overlay tendría que pintar el color exacto
     del panel y dejaría de coincidir en cuanto cambie --surface. La máscara quita alfa,
     así que lo que asoma es el fondo real de la tarjeta, sea el que sea.
     Y a diferencia de `backdrop-filter` —que aquí está prohibido porque la tarjeta va
     ROTADA y el navegador muestrea mal el fondo— la máscara es alfa del propio elemento
     y la rotación no le afecta.
     ⚠️ v278: el degradado va `to left` porque la foto cambió de lado — se desvanece por
     su borde DERECHO, que es el que ahora toca al texto. */
  .svcs-orbit .svc-card .media {
    -webkit-mask-image:linear-gradient(to left, transparent 0, #000 30%);
            mask-image:linear-gradient(to left, transparent 0, #000 30%); }

  /* el texto sale del apilado y se centra en su columna, a la DERECHA */
  .svcs-orbit .svc-card .body { position:static; grid-column:2; grid-row:1;
    justify-content:center; align-items:flex-start;
    gap:1.8cqw; padding:3.6cqw; }
  .svcs-orbit .svc-card .body p { -webkit-line-clamp:3; max-width:none;
    font-size:2.8cqw; color:rgba(255,255,255,.72); }
  /* ⚠️ EL TAMAÑO DEL TITULAR LO FIJA LA PALABRA MÁS LARGA, no la media. De las seis
     divisiones la peor es «TRANSFORMACIÓN»: mide 10.1px de ancho por cada 1px de
     cuerpo. El ancho útil de la columna es (0.535 − 0.072)·cw = 0.463·cw una vez
     descontado el padding —que también es proporcional, y por eso la cuenta se sostiene
     en CUALQUIER tamaño de tarjeta—, así que el máximo seguro es 4.58cqw. Se usa
     4.4cqw, un 4% por debajo. Con un valor fijo en px la palabra se salía por encima
     de la foto en las tarjetas chicas (389px a 1024x768).
     ⚠️ `break-word`, NUNCA `anywhere`: con `anywhere` el navegador parte una palabra
     aunque QUEPA si con eso llena mejor el renglón — salía «TRANSFORMACIÓ / N». */
  .svcs-orbit .svc-card .body h3 { font-size:4.4cqw; line-height:1.04;
    overflow-wrap:break-word; }

  /* el número se ancla a la esquina de la FOTO (que ahora sí está posicionada), o
     quedaría en mitad de la tarjeta */
  /* ⚠️ `right:auto` OBLIGATORIO: la regla base usa `inset:16px 16px auto`, o sea left Y
     right a la vez, y con las dos puestas el bloque se ESTIRA — el número acababa
     estirado por toda la foto. Sólo con `left` se ancla a la esquina.
     v278: se ancla a la IZQUIERDA porque la foto cambió de lado, y ese es su borde
     sólido (el derecho es el que se desvanece). */
  .svcs-orbit .svc-card .top { position:absolute; z-index:3; right:auto; width:auto;
    top:clamp(10px,1vw,14px); left:clamp(10px,1vw,14px); margin:0; }
}


/* MÓVIL: riel horizontal anclado (scroll vertical → desplazamiento lateral) */
@media (max-width:860px) {
  /* v279 — el scrim frosted pasa a degradado PLANO en móvil. Un `backdrop-filter` por
     tarjeta obliga al navegador a muestrear y desenfocar lo que hay detrás de cada una;
     con la rejilla de /proyectos son 6 a la vez. Visualmente es indistinguible sobre
     una foto (la rueda ya lo había comprobado) y se ahorra el paso de composición. */
  .svc-card .media::after {
    -webkit-backdrop-filter:none; backdrop-filter:none;
    -webkit-mask:none; mask:none;
    background:linear-gradient(to top, rgba(0,0,0,.82) 2%, rgba(0,0,0,.42) 30%, transparent 58%); }
  /* respaldo por si el JS no corre: el alto real lo fija main.js con el recorrido
     medido, para que el gear sea 1.0 igual que en premios */
  .svcs-track { height:calc(100svh + var(--n,10) * var(--scroll-item-m)); }
  /* El título va ARRIBA, no de telón detrás de las cards. En 390px un título
     centrado por detrás no se lee: se cruzaba con las fotos y no se entendía si
     era encabezado o parte de la card. Aquí encabeza el riel y se queda quieto. */
  .svcs-head { top:clamp(88px,13svh,132px); transform:none;
    align-items:flex-start; text-align:left; }
  .svcs-head .sec-title, .svcs-head .label { text-align:left; }
  /* en móvil la rueda es un riel horizontal: no hay centro de órbita que ocupar */
  .svcs-planet { display:none; }
  /* ---- v277: riel CENTRADO, una tarjeta protagonista ----
     Antes el riel arrancaba pegado a la izquierda y las tarjetas eran verticales con
     el texto encima de la foto: se veían chicas, el título competía con la imagen y
     no quedaba claro cuál era la "actual". Ahora la tarjeta viva va al CENTRO, las
     vecinas asoman como cantos a los lados y el texto SALE de la foto: se sienta a la
     izquierda, encima del canto vecino, que es lo que le da el aire de la referencia.

     La geometría es una sola cuenta y por eso vive en `--cardw`:
       padding = (100vw − cardw)/2   → la primera y la última quedan centradas
       canto visible = padding − gap = (50 − 34) − 6 = 10vw
     El JS mide el recorrido con `paddingLeft`, así que este padding simétrico le sirve
     tal cual: no hay número mágico duplicado entre CSS y JS. */
  .svcs-orbit { --cardw:min(68vw,320px); --out:10vw;
    position:absolute; left:0; top:auto;
    bottom:clamp(140px,23svh,230px); width:auto; height:auto; z-index:2;
    display:flex; align-items:flex-end; gap:6vw;
    padding-left:calc((100vw - var(--cardw)) / 2);
    /* ⚠️ SIN transform aquí: GSAP escribe la x del riel y adoptaría este translate
       como valor base. (Ya nos pasó con la vieja imagen flotante de proyectos, que
       salía 170px corrida porque el -50% del CSS se perdía al escribir x en píxeles;
       aquel efecto se retiró en v226, la lección no.) El anclaje vertical se hace
       con `bottom`, que GSAP no toca. */
    transform:none; }
  .svcs-orbit .svc-card { position:relative; left:auto; top:auto; flex:0 0 auto;
    width:var(--cardw); aspect-ratio:5/6;
    /* pivote al CENTRO: ya no se inclinan, sólo se encogen hacia dentro */
    transform-origin:50% 50%; }
  /* radio más generoso, que es medio carácter de la referencia */
  .svcs-orbit .svc-card .media { border-radius:clamp(22px,6.5vw,32px); }
  /* El scrim se queda, pero PLANO y más alto: el título es grande y se apoya en la
     mitad baja de la foto. Sin él, sobre una foto clara (la multitud del festival) el
     texto se perdía; se probó quitarlo y ese fue justo el fallo. Sin backdrop-filter:
     dentro del riel se muestrea mal y difuminaba la foto entera. */
  .svcs-orbit .svc-card .media::after { display:block;
    -webkit-backdrop-filter:none; backdrop-filter:none;
    -webkit-mask:none; mask:none;
    background:linear-gradient(to top, rgba(0,0,0,.92) 0%, rgba(0,0,0,.72) 26%,
                                       rgba(0,0,0,.28) 52%, transparent 76%); }
  /* ⚠️ TODO lo que sigue cuelga de `.js-riel`, la clase que main.js pone en la etapa
     cuando de verdad está manejando el riel. Sin ella —JS caído, o "reducir
     movimiento", que hace `return` ANTES de esta sección— nadie añadiría `is-act` y el
     texto se quedaría invisible para siempre. Así, sin JS, la tarjeta cae sola al
     formato de antes: texto sobre la foto y sin puntos. */
  /* el texto SALE de la tarjeta y se recuesta a la izquierda, pisando el canto vecino.
     `inset` de la regla base pone los cuatro lados: hay que soltar top/right a mano. */
  .svcs-stage.js-riel .svcs-orbit .svc-card .body { inset:auto;
    left:calc(-1 * var(--out)); bottom:clamp(12px,3.5vw,22px);
    width:calc(var(--cardw) + var(--out) - 4vw);
    padding:0; gap:5px; z-index:4;
    /* sombra ancha y suave: el texto cae sobre foto Y sobre fondo, y tiene que
       leerse en los dos sin que se note un recuadro */
    text-shadow:0 2px 18px rgba(0,0,0,.92), 0 1px 4px rgba(0,0,0,.8); }
  .svcs-stage.js-riel .svcs-orbit .svc-card .body h3 { font-size:clamp(25px,7vw,34px); line-height:1.02;
    overflow-wrap:break-word; }
  .svcs-stage.js-riel .svcs-orbit .svc-card .body p { color:rgba(255,255,255,.8); font-size:var(--t-sm);
    max-width:none; -webkit-line-clamp:2; }
  /* el relevo del texto lo hace el CSS; el JS sólo pone la clase (ver main.js).
     `visibility` además de `opacity`: si no, el bloque invisible seguía tapando el
     toque sobre la tarjeta vecina. */
  .svcs-stage.js-riel .svcs-orbit .svc-card .body { opacity:0; visibility:hidden;
    transition:opacity .26s var(--e-out), visibility 0s linear .26s; }
  .svcs-stage.js-riel .svcs-orbit .svc-card.is-act .body { opacity:1; visibility:visible;
    transition:opacity .26s var(--e-out), visibility 0s; }
  /* avance: puntos, no barra. Con 6 misiones la barra no decía en cuál ibas. */
  .svcs-stage.js-riel .svcs-rail { display:flex; justify-content:center; align-items:center;
    gap:9px; position:absolute; z-index:3;
    left:var(--pad); right:var(--pad); bottom:clamp(104px,17svh,175px);
    height:auto; background:none; border-radius:0; overflow:visible; }
  .svcs-stage.js-riel .svcs-rail i { display:block; width:7px; height:7px; flex:0 0 auto;
    border-radius:50%; background:#fff; opacity:.24;
    transition:opacity .26s var(--e-out), width .26s var(--e-out); }
  /* el punto vivo se ALARGA además de encenderse: con 6 puntos iguales, sólo el
     cambio de opacidad se perdía de un vistazo */
  .svcs-stage.js-riel .svcs-rail i.is-act { opacity:1; width:20px; border-radius:4px; }
}
@media (prefers-reduced-motion: reduce) {
  .svcs-track { height:auto; }
  .svcs-stage { position:static; height:auto; overflow:visible; padding:clamp(70px,10vh,110px) 0; }
  .svcs-head { position:static; transform:none; margin-bottom:clamp(30px,5vh,60px); filter:none !important; opacity:1 !important; }
  .svcs-orbit {
    /* ⚠️ width/height a auto NO es adorno: la regla base hace de esto un PUNTO
       (`width:0;height:0`) porque en la rueda es el centro de la órbita y las
       tarjetas van absolutas alrededor. Sin devolverle medida, la rejilla salía de
       0px de ancho y las tarjetas de 2px de alto — o sea que quien navega con
       «reducir movimiento» veía la sección de servicios COLAPSADA. Detectado al
       reusar este bloque para la vista previa del panel (sep 2026). */
    width:auto; height:auto; top:auto; left:auto;
    position:static; display:grid; gap:clamp(12px,1.6vw,20px);
    grid-template-columns:repeat(auto-fill,minmax(min(280px,100%),1fr)); transform:none; padding:0; }
  .svcs-orbit .svc-card { position:relative; width:auto; transform:none !important; }
  .svcs-planet { display:none; }   /* sin órbita no hay centro; y es decorativo */
  /* premios: sin anclaje ni recorrido horizontal, los paneles se apilan.
     El alto inline de .aw-track lo pone el JS, que aquí no llega a correr
     (`if (reduced) return` va antes), pero el !important lo cubre igual. */
  .aw-track { height:auto !important; width:auto; margin-inline:0; }
  .aw-stage { position:static; height:auto; overflow:visible; }
  .aw-rail { flex-direction:column; align-items:stretch; transform:none !important;
    padding-inline:0; gap:clamp(40px,7vh,90px); }
  .aw-panel,
  .aw-panel.is-nota,
  .aw-panel.is-nota:not(.has-media) { flex:0 0 auto; width:100%; grid-template-columns:1fr; }
  .aw-head, .aw-media, .aw-side { grid-area:auto; }
  .aw-title { mix-blend-mode:normal; max-width:none; }
  .aw-prog { display:none; }
}

/* ============================================================
   INTRANET (v204) — dos accesos para el crew
   Sólo dos tarjetas y cada una manda fuera (Estrellas es una plataforma que ya
   existe; Beneficios, los acuerdos para canjear). Reusa el patrón de card del
   sistema —superficie, borde, canto de luz y el mismo hover— en vez de inventar
   uno nuevo: son dos accesos, no una sección de marca.
   ============================================================ */
/* Se acomoda solo al número de tarjetas Y al ancho: `auto-fit` mete tantas columnas
   de 260px como quepan. Medido con el relleno y el hueco reales: 4 en línea desde
   ~1240px, 3 entre ~900 y ~1240, 2 entre ~780 y ~900, y 1 abajo de eso. Con `repeat(2,…)`
   fijo, las 4 quedaban en 2×2 y desperdiciaban el ancho de un escritorio.
   ⚠️ El mínimo es 260px y no más: por debajo, el título en Clash a `--d-4` se parte en
   tres renglones y la tarjeta se vuelve una columna de texto. */
.intra-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:clamp(18px,2.4vw,34px); }

.intra-card { display:flex; flex-direction:column; overflow:hidden;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.055);
  transition:border-color var(--m-mid) var(--e-out), background var(--m-mid) var(--e-out),
             box-shadow var(--m-mid) var(--e-out), transform var(--m-mid) var(--e-out); }
.intra-card:hover { background:var(--surface-2); border-color:var(--line-2); transform:translateY(-4px);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08), 0 0 0 1px var(--acc-tint),
             0 22px 48px -26px var(--acc-glow); }

/* la caja de foto se reserva SIEMPRE, con o sin imagen: si sólo una de las dos
   tarjetas tiene foto, sin esto quedan de alturas distintas y se ve roto */
.intra-shot { aspect-ratio:16/10; background:#0d0e16; overflow:hidden;
  border-bottom:1px solid var(--line); }
.intra-shot img { width:100%; height:100%; object-fit:cover;
  transition:transform var(--m-slow) var(--e-out); }
.intra-card:hover .intra-shot img { transform:scale(1.04); }

/* el relleno se topa en 24px: con 4 columnas la tarjeta baja a ~300px y los 32px de
   antes se comían el 21% de su ancho por los dos lados */
.intra-body { padding:clamp(18px,1.7vw,24px); display:flex; flex-direction:column;
  gap:clamp(8px,.9vw,12px); flex:1; }
.intra-title { font:700 clamp(17px,1.35vw,21px)/1.18 var(--font-d); text-transform:uppercase;
  letter-spacing:.01em; }   /* propio, no --d-4: ese se calcula contra el VIEWPORT y en una
                               columna de 300px salía a 25px y se partía en tres renglones */
.intra-desc { font:400 var(--p-body)/1.6 var(--font-b); color:var(--txt-soft); max-width:46ch; }
.intra-body .link-arrow { margin-top:auto; padding-top:6px; }
.intra-card:hover .link-arrow { color:var(--fg); }
.intra-card:hover .link-arrow span { transform:translateX(5px); }

/* sin link configurado no es enlace: se ve, pero avisa en vez de dar un clic muerto */
.intra-card.is-off { opacity:.62; }
.intra-pend { margin-top:auto; padding-top:6px; font:600 var(--t-xs)/1 var(--font-b);
  letter-spacing:.1em; text-transform:uppercase; color:var(--mut); }

/* ---- "Próximamente" ----
   No es lo mismo que una tarjeta sin configurar: aquí el destino AÚN NO EXISTE y se
   anuncia a propósito, así que no se apaga como `.is-off` — se ve entera y con una
   pastilla que lo dice. Tampoco es enlace (se pinta como <div>), así que no hay clic
   muerto ni cursor de "Entrar". */
.intra-card.is-soon { cursor:default; }
.intra-card.is-soon:hover { background:var(--surface); border-color:var(--line);
  transform:none; box-shadow:inset 0 1px 0 rgba(255,255,255,.055); }
.intra-card.is-soon .intra-title,
.intra-card.is-soon .intra-desc { opacity:.72; }
.intra-card.is-soon .intra-shot { opacity:.5; }
.intra-soon { margin-top:auto; align-self:flex-start; padding:.5em .9em;
  border-radius:var(--r-pill); background:var(--acc-tint); color:var(--acc-lt);
  font:600 var(--t-xs)/1 var(--font-b); letter-spacing:.1em; text-transform:uppercase; }

/* ⚠️ Se quitó el `grid-template-columns:1fr` que había aquí: forzaba UNA columna desde
   760px y anulaba el auto-fit justo en el rango de tablet, donde caben 2 de ~340px sin
   problema. El auto-fit ya baja a 1 columna solo por debajo de ~500px, que es donde de
   verdad hace falta. Una media query que repite lo que el layout ya resuelve sólo sirve
   para pelearse con él. */

/* ============================================================
   v103 — prefers-reduced-motion: BLOQUE FINAL (va al final a propósito)
   Las media queries NO suman especificidad: un bloque de reduced-motion
   colocado a media hoja queda anulado por cualquier regla global posterior.
   Le pasaba a .form-ok (L214 vs L398) y al astronauta (L730 vs L738), que
   seguían animándose con el sistema en "reducir movimiento".
   Se ACORTAN las animaciones en vez de matarlas: varias usan `forwards`
   para dejar el elemento visible (.hero-astro, .sede-panel, .form-ok) y
   con animation:none se quedarían en opacity 0, invisibles.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration:.01ms !important; animation-delay:0s !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important; transition-delay:0s !important;
    scroll-behavior:auto !important;
  }
  /* GSAP no corre con reduced-motion (main.js hace return): los estados
     iniciales que pone el CSS deben quedar visibles a mano */
  [data-fade] { opacity:1 !important; transform:none !important; }
  .reveal-lines .line > div { transform:none !important; }
  #cursor { display:none !important; }
  [data-cursor], [data-cursor] * { cursor:auto !important; }
}

/* ============================================================
   v103 — guarda TÁCTIL, también al final y por el mismo motivo.
   Existe un @media (hover:none) en la parte de arriba del archivo, pero el
   bloque v57 de las svc-cards redeclara después .svc-card:hover{transform}
   y lo anulaba: al tocar una card en el celular se quedaba "levantada"
   (hover pegado) hasta tocar en otro lado.
   ============================================================ */
@media (hover: none) {
  .svc-card:hover { transform:none; }
  .svc-card:hover .media img { transform:none; }
  .svc-card.has-img:hover::before { opacity:.58; transform:none; }
  .reel-launch:hover { transform:none; }
}

/* ============================================================
   v124 — ERGONOMÍA TÁCTIL. Va AL FINAL por lo mismo que los dos bloques de
   arriba: las @media no suman especificidad y aquí se pisan reglas globales
   (.nav-burger, .city, los links del footer) declaradas mucho antes.

   Auditado en 390x844: 12 controles quedaban por debajo del mínimo de 44px que
   piden Apple y Google, entre ellos los DOS más importantes del móvil —
   el botón de menú (40x34) y el logo, que es el "volver al inicio" (112x14).
   Los que son sólo texto se agrandan con padding; los que llevan logo se
   agrandan con un ::after invisible, para NO mover el diseño.
   ============================================================ */
@media (max-width:860px) {
  .nav-burger { min-width:44px; min-height:44px; }
  /* área de toque del logo sin desplazar el logo */
  .nav-logo, .footer-logo { position:relative; }
  .nav-logo::after, .footer-logo::after { content:''; position:absolute;
    left:-8px; right:-8px; top:50%; height:44px; transform:translateY(-50%); }
  /* chips de sede: 36px de alto y el nombre de la ciudad en 9px, ilegible */
  .sedes .city { min-height:48px; justify-content:center; }
  .sedes .city .c-name { font-size:11px; letter-spacing:.08em; }
  /* el tamaño ya lo pone --t-xs (11-13px): esta guarda quedó redundante */
  .sede-panel .sp-maps { min-height:44px; align-items:center; }
  /* el teléfono del panel de sedes medía 14px de alto (no está en el footer,
     como parecía por el texto: vive dentro de .sp-tel) */
  .sede-panel a { display:inline-block; padding-block:16px; }
  /* links de texto del footer y datos de contacto */
  .footer-nav a, .footer-mid a, .footer-bottom a { display:inline-block;
    padding-block:15px; }
  .footer-nav { gap:2px 18px; }
  /* los nombres de red son palabras cortas: sin padding lateral quedan en 38px
     de ancho y se fallan al tocar */
  .footer-social a { padding-inline:6px; }
  /* la sección de testimonios era 340svh para 3 tarjetas: se acorta para que el
     tramo anclado no se coma media página en el celular (ver el reparto de
     posiciones por cantidad en index.php) */
  /* el alto de .testi2 se deriva de --n más arriba; aquí sólo el ritmo con los vecinos */
  .testi2 { margin-block:var(--sec-base); }

  /* ---- v272: seis enlaces más que seguían por debajo de 44px ----
     Auditoría de móvil a 375px sobre las 9 rutas. Los que llevan borde o fondo propio
     crecen con `min-height`, que no mueve nada porque ya iban centrados; los que son
     texto suelto usan un ::before invisible, para no separar los renglones vecinos.
     ⚠️ `a.link-arrow` y `a.blog-kick` con el TAG delante: las dos clases se usan también
     como <span> dentro de la tarjeta del blog, donde el enlace es la tarjeta entera y
     agrandarlos ahí no aportaría nada. */
  .reel-bar .reel-ctrl { min-height:44px; }
  .bf { min-height:44px; }
  .svc-link { min-height:44px; align-items:center; }
  .aw-link, a.link-arrow, a.blog-kick { position:relative; }
  .aw-link::before, a.link-arrow::before, a.blog-kick::before {
    content:''; position:absolute; left:-6px; right:-6px; top:50%;
    height:44px; transform:translateY(-50%); }

  /* ---- v272: el formulario de contacto ----
     ⚠️ 16px MÍNIMO en los campos. El <select> de Estado iba en 15 y Safari de iPhone
     hace ZOOM automático al enfocar cualquier campo por debajo de 16: la página salta
     de escala y el usuario tiene que volver a alejarla a mano. No es estética, es el
     comportamiento del navegador. Los demás campos ya iban en 20. */
  .form select, .form input, .form textarea { font-size:max(16px, 1em); }
  .form select { min-height:44px; }
  /* El de archivo se queda en --t-sm a propósito: no abre teclado, así que NO dispara el
     zoom de iOS y el 16px no le aplica. Lo que sí le faltaba era altura de toque. */
  .form input[type=file] { min-height:44px; }
  /* enlaces de texto suelto: aviso de privacidad y los datos de contacto */
  .form-legal a, .cinfo a { display:inline-block; padding-block:14px; }
  .cinfo .blk p { margin:0; }
}

/* ============================================================
   v150 — CAPABILITIES: recorrido anclado (página Nosotros)
   Reemplaza a las cards largas que había antes. Tres columnas que suben A LA VEZ:
   nombre · foto con su número · descripción + áreas.

   La clave del layout: cada columna es una VENTANA (.caps-col / .caps-nums) de
   alto --caps-h, que el JS mide según su contenido, y sus slots miden lo mismo.
   El paso de cada tira es su propia ventana, no un valor común: con un paso
   único (el más alto de las tres) las columnas bajas se quedaban en blanco a
   media transición. La sincronía la da el índice fraccionario, no los píxeles.
   ============================================================ */
.caps-track { position:relative; height:calc(100svh + var(--n,5) * var(--scroll-item)); }
.caps-stage { position:sticky; top:0; height:100vh; height:100svh; overflow:hidden;
  display:flex; flex-direction:column; justify-content:center;
  gap:clamp(28px,5vh,64px); }
/* centrada: .sec-head global reparte a los extremos (space-between) y aquí, con el
   recorrido simétrico debajo, el título a la izquierda y la bajada a la derecha
   descuadraba la escena. */
.caps-stage .sec-head { margin:0; flex-direction:column; align-items:center;
  text-align:center; gap:clamp(10px,1.4vw,16px); }
.caps-stage .sec-head .label { max-width:38ch; }
.caps-grid { width:100%; display:grid; align-items:center;
  /* la foto es el centro de la escena: se lleva más ancho que los textos.
     Con 1.15fr quedaba en 389px dentro de un stage de 1168 y sobraba aire. */
  grid-template-columns:minmax(0,1fr) minmax(0,1.6fr) minmax(0,1fr);
  gap:clamp(18px,3.2vw,48px); }

/* --- las tiras: cada slot mide lo mismo y el JS traslada la tira entera --- */
.caps-col { height:var(--caps-h,200px); overflow:hidden; }   /* ventana; el alto lo mide el JS */
.caps-strip { will-change:transform; }
.caps-slot { height:var(--caps-h,200px); display:flex; flex-direction:column;
  justify-content:center; }   /* mismo alto que su ventana: sin huecos entre pasos */

/* ⚠️ Tamaño propio, NO var(--d-2): a 56px la palabra más larga del recorrido
   («estratégica») medía 429px dentro de una columna de 340 y se cortaba —eran 10
   palabras de 11 pasándose—. 2.95vw/44px es el tope al que la más ancha cabe; medido
   rasterizando los anchos reales, no a ojo. */
.caps-title { font:700 var(--d-2)/1.02 var(--font-d); font-size:clamp(22px,2.95vw,44px);
  text-transform:uppercase;
  letter-spacing:.01em; color:#fff; }
.caps-descs { text-align:right; }
.caps-desc p { color:var(--txt-soft); font:400 var(--p-body)/1.55 var(--font-b);
  margin-left:auto; max-width:34ch; }
.caps-areas { list-style:none; display:flex; flex-wrap:wrap; justify-content:flex-end;
  gap:clamp(7px,.8vw,10px); margin-top:clamp(16px,2vw,26px); }
.caps-areas li { font:500 var(--t-xs)/1 var(--font-b); padding:9px 14px;
  border:1px solid var(--line); border-radius:var(--r-md); color:var(--mut);
  background:rgba(255,255,255,.02); }

/* --- la foto: rectangular SIEMPRE (object-fit:cover), nunca se deforma --- */
.caps-media { position:relative; width:100%; aspect-ratio:14/9; }

/* ---- Trayectoria: el recorrido vertical, con foto (v222) ----
   Hereda el componente de Capabilities COMPLETO —incluida la caja de foto del centro
   con su cross-fade de hito a hito—; lo único distinto es lo que va encima: el AÑO en
   lugar del número de orden. Por eso no toca la rejilla ni las ventanas: el centro
   sigue pesando 1.6fr porque vuelve a ser una foto.
   El año son cuatro cifras donde el número eran dos, así que baja de tamaño para no
   comerse el ancho de la foto. Especificidad (0,2,0): gana al `font:` de .caps-num
   sin importar el orden. */
.caps-tl .caps-num { font-size:clamp(40px,5.4vw,88px); letter-spacing:-.02em; }
.caps-tl .caps-desc p { max-width:38ch; }
/* 14:9 = la proporción que pide el admin (1400×900), así `cover` casi no recorta */
.caps-shot { position:absolute; inset:0; margin:0; border-radius:var(--r-lg);
  overflow:hidden; background:var(--surface); border:1px solid var(--line); opacity:0; }
.caps-shot img { width:100%; height:100%; object-fit:cover; display:block; }
/* v281 — un hito puede ser video (el 10º aniversario). Mismo encuadre que la foto:
   `cover` recorta los lados —la caja es 14:9 y el video 16:9—, y se comprobó contra el
   remate del logo, que queda entre el 15% y el 82% del ancho: no lo toca. */
.caps-shot video { width:100%; height:100%; object-fit:cover; display:block;
  background:#000; pointer-events:none; }
/* ⚠️ `visibility`, NO `display:none` ni `opacity`. Con `display` cambiaría el paso del
   riel; con `opacity` se pelearía con main.js, que la escribe por fotograma. */
.caps-num.sin-anio { visibility:hidden; }

/* números: una sola tira que se desplaza igual que las demás. El que queda sobre
   la foto va opaco; los vecinos asoman en gris (el JS pone la opacidad). */
.caps-nums { position:absolute; inset:0; overflow:hidden; z-index:2;
  pointer-events:none; }
.caps-nums .caps-slot { align-items:center; }
.caps-num { font:700 clamp(56px,7vw,104px)/1 var(--font-d); color:#fff;
  text-shadow:0 4px 30px rgba(0,0,0,.45); opacity:.14; }

@media (max-width:860px) {
  /* apilado: nombre arriba, foto, y descripción debajo. Sigue anclado. */
  .caps-grid { grid-template-columns:1fr; gap:clamp(14px,4vw,24px); justify-items:center; }
  .caps-descs { }
  .caps-desc p { margin-inline:auto; }
  .caps-areas { justify-content:center; }
  .caps-titles, .caps-descs { text-align:center; }
  .caps-title { text-align:center; }
}
@media (prefers-reduced-motion: reduce) {
  /* la entrada del hero se acorta, no se anula: con `animation:none` el elemento
     se quedaría en el estado `from`, o sea invisible. */
  .hero-tag-center, .hero-scroll { animation-duration:.01ms; }
  /* sin anclaje: las divisiones se apilan una tras otra, todas visibles */
  .caps-track { height:auto !important; }
  .caps-stage { position:static; height:auto; overflow:visible; display:block; }
  .caps-grid { grid-template-columns:1fr; gap:clamp(14px,3vw,26px); }
  .caps-col, .caps-media { height:auto; aspect-ratio:auto; overflow:visible; }
  .caps-slot { height:auto !important; padding-block:clamp(18px,3vh,34px); }
  .caps-strip { transform:none !important; }
  .caps-media { display:none; }        /* sin recorrido, las fotos apiladas no aportan */
  /* …pero en Trayectoria el AÑO vive dentro de esa caja y sí es dato del hito:
     se queda la caja, se van las fotos, y los años bajan como lista estática. */
  .caps-tl .caps-media { display:block; }
  .caps-tl .caps-shot  { display:none; }
  .caps-tl .caps-nums  { position:static; }
  .caps-tl .caps-num   { opacity:1; color:var(--red); text-shadow:none; }
  /* ⚠️ El año vuelve. Se esconde sólo porque el VIDEO trae su propio remate; aquí las
     fotos y el video están en `display:none`, así que sin esto el hito de 2026 saldría
     en blanco: ni video ni año. La regla que oculta el año y la que oculta el video
     tienen que ir siempre de la mano. */
  .caps-tl .caps-num.sin-anio { visibility:visible; }
  .caps-descs { text-align:left; }
  .caps-areas { justify-content:flex-start; }
  /* el casco del pie no crece: el cambio de opacidad basta como señal de hover */
  .footer-nav a.foot-casco:hover svg,
  .footer-nav a.foot-casco:active svg { transform:none; }
}

/* ============================================================
   v200 — logros con "reducir movimiento": BLOQUE FINAL a propósito
   El JS de scroll sale antes del bloque de logros, así que los trazos NO se
   dibujan (el <svg> se queda vacío). La marca roja del móvil los sustituye,
   para que la derivación se siga leyendo sin geometría ni animación.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .st-links { display:none; }
  .st-grid { margin-top:clamp(38px,6svh,58px); }
  .st-item { position:relative; padding-top:20px; }
  .st-item::before { content:''; position:absolute; top:0; left:50%; margin-left:-.5px;
    width:1px; height:13px; background:var(--red); }
}
