/* ══════════════════════════════════════════════════════════════
   INGENIERÍA P&M · SISTEMA DE PRODUCCIÓN
   Portado del mockup aprobado (mockup/ingenieria-pym-home-claro.html).
   Los tokens (:root) NO viven aquí — los genera build.mjs a partir de
   design/tokens.json en assets/css/tokens.generated.css, que se enlaza
   ANTES que este archivo. Este archivo solo consume var(--token).
   ══════════════════════════════════════════════════════════════ */

*,*::before,*::after{box-sizing:border-box; -webkit-tap-highlight-color:transparent}
html{
  -webkit-text-size-adjust:100%; scroll-behavior:smooth;
  /* nav es fixed (no reserva espacio propio): sin esto, un salto a
     #clients dejaría el título tapado bajo el header. */
  scroll-padding-top:var(--nav-h);
}
body{
  margin:0; background:var(--canvas); color:var(--ink2);
  font-family:var(--font-body); font-size:17px; line-height:1.7;
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
img{max-width:100%; display:block}
a{color:inherit; text-decoration:none}
button{font:inherit; cursor:pointer; border:0; background:none; color:inherit}

.container{width:min(100% - 2.5rem, var(--content-width)); margin-inline:auto}
@media(max-width:640px){ .container{width:calc(100% - 2rem)} }

/* El encabezado no usa `.container`: se ancla a los bordes reales del
   viewport en vez de quedar dentro de una columna con aire vacío a
   los costados en pantallas anchas. */
.nav-wide{
  width:100%; margin-inline:0; padding-inline:clamp(1.25rem, 4vw, 3.5rem);
  display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:2.5rem;
}
@media(max-width:980px){ .nav-wide{display:flex; justify-content:space-between} }

/* ═══════════ ACABADO "LACA BRILLANTE" — barras oscuras ══════════
   Receta elegida en brand/identidad-visual.html §10 (laboratorio de
   color): brillo .75, metalizado .1, piel de naranja .05, ángulo
   125°, curvatura .55 — aplicada sobre --night (el mismo #0A1118 de
   siempre, no un color nuevo) en .footer y el fondo sólido del .nav.
   Dos pseudo-elementos (curvatura + barniz) más dos capas reales
   (metalizado + textura) — mismas 4 capas que en el laboratorio, ahora
   con números fijos en vez de variables ajustables. */
.footer{ position:relative; overflow:hidden }
.nav{ isolation:isolate }
.footer::before, .nav::before{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(255,255,255,.55) 0%, transparent 55%),
    radial-gradient(130% 100% at 50% 100%, rgba(0,0,0,.55) 0%, transparent 60%);
  mix-blend-mode:overlay;
}
.footer::after, .nav::after{
  content:""; position:absolute; inset:-50%; pointer-events:none;
  background:linear-gradient(125deg, transparent 42%, rgba(255,255,255,.75) 50%, transparent 58%);
  mix-blend-mode:soft-light;
}
/* Metalizado + textura: antes eran dos filtros SVG (feTurbulence),
   recalculados por el navegador en cada página; medido en auditoría móvil
   (ver ADR-023) como causa de caída de fluidez, sobre todo al llegar al
   pie. Reemplazados por 2 PNG diminutos pre-generados (mismo ruido
   aleatorio, mismo mix-blend-mode:overlay, mismo resultado visual) que
   solo se repiten (background-repeat), sin recalcularse nunca. */
.finish-fleck, .finish-texture{ position:absolute; inset:0; pointer-events:none }
.finish-fleck{ background:url('/assets/images/finish-fleck.png') repeat; mix-blend-mode:overlay; opacity:.1 }
.finish-texture{ background:url('/assets/images/finish-texture.png') repeat; mix-blend-mode:overlay; opacity:.05 }
/* El nav es transparente al cargar y se vuelve sólido con el scroll
   (--nav-alpha) — el acabado tiene que aparecer al mismo ritmo, no de
   golpe ni antes de que el fondo oscuro exista. */
.nav::before, .nav::after{ opacity:var(--nav-alpha,0) }
.nav .finish-fleck{ opacity:calc(var(--nav-alpha,0) * .1) }
.nav .finish-texture{ opacity:calc(var(--nav-alpha,0) * .05) }
/* El contenido real (logo, menú, botones) va en capas normales del
   DOM después de las de acabado — pinta encima por orden natural, sin
   necesitar z-index. */

/* ═══════════════ CORTINA DE ENTRADA ════════════════════════════
   Un solo fotograma de "presentación" al cargar — sin esto, con las
   fuentes ya cacheadas, todo el héroe aparece de golpe en vez de
   sentirse como una entrada. --ov-curtain-duration (Editor Pase 2,
   global.curtainDuration) — en 0 equivale a apagarla. Con
   `prefers-reduced-motion` ya cae a .01ms por la regla global de más
   abajo, sin necesitar código aparte. */
.page-curtain{
  position:fixed; inset:0; z-index:500; background:var(--ink1); pointer-events:none;
  animation:curtain-out var(--ov-curtain-duration, 500ms) var(--ease-out) forwards;
}
@keyframes curtain-out{ to{opacity:0} }

/* ═══════════ ENTRADA DEL ENCABEZADO ═══════════════════════════
   Analizado de uxdesigncollective.com/what-we-do: la barra ya está
   fija en su lugar, pero arranca desplazada hacia arriba exactamente
   su propia altura (translateY(-100%), oculta arriba del viewport,
   opacidad SIEMPRE en 1 — no es un fundido, es un deslizamiento puro)
   y baja a translateY(0) con una curva de salida marcada. Coincide en
   el tiempo con .page-curtain para que se sienta como una sola
   apertura, no dos animaciones sueltas. --ov-header-entrance-duration
   (Editor Pase 2, global.headerEntranceDuration) en 0 la apaga.
   Sin fill-mode `both` a propósito: dejarlo aplicado de forma
   permanente un `transform` (aunque sea translateY(0), visualmente un
   no-op) convierte a #nav en el contenedor de posicionamiento de sus
   descendientes `position:fixed` — rompía el `inset` a pantalla
   completa de #menu en móvil, que quedaba encogido al tamaño del
   propio header en vez de cubrir todo el viewport. */
.header-entrance{
  animation:header-drop-in var(--ov-header-entrance-duration, 600ms) var(--ease-out);
}
@keyframes header-drop-in{ from{transform:translateY(-100%)} to{transform:translateY(0)} }

/* ═══════════════ BARRA DE PROGRESO DE SCROLL ══════════════════
   Línea de instrumento, no decoración: comunica cuánto falta de la
   página. El JS actualiza --scroll-progress (0 a 1) en el mismo rAF
   que ya mueve el nav y el parallax — ver main.js. */
.scroll-progress{
  position:fixed; top:0; left:0; z-index:200; height:3px;
  width:calc(var(--scroll-progress, 0) * 100%);
  background:var(--green-cta); pointer-events:none;
}

.skip-link{position:absolute; left:-9999px; top:0; z-index:999; background:var(--green-cta); color:var(--on-green); padding:.75rem 1.25rem; font-weight:700}
.skip-link:focus{left:0}
:focus-visible{outline:2px solid var(--blue); outline-offset:3px; border-radius:3px}

.eyebrow{
  display:inline-flex; align-items:center; gap:.65rem;
  font-family:var(--font-mono); font-size:.7rem; font-weight:400;
  letter-spacing:.2em; text-transform:uppercase; color:var(--blue);
}
.eyebrow::before{content:""; width:26px; height:2px; background:var(--green-cta)}
/* En el hero se retira la barra decorativa: pegada al h1, ese patrón
   (kicker + barra + titular oversized) es el "eyebrow chip" reconocible
   de cualquier hero genérico de IA. En el resto de secciones se conserva
   como convención consistente de etiqueta de bloque. */
.hero-content .eyebrow::before{display:none}
.hero-content .eyebrow{gap:0}

h1,h2,h3{color:var(--ink1); font-family:var(--font-display); font-weight:var(--heading-weight); letter-spacing:-.01em; text-wrap:balance}

/* ════════════════════════════ NAVBAR ══════════════════════════
   Transparente al cargar (deja ver la foto completa del hero) y se
   vuelve sólido en el oscuro de marca a medida que se hace scroll.
   --nav-alpha (0 a 1) lo controla el JS de scroll; aquí solo se
   interpola. El esquema de color del nav es siempre "oscuro" (texto
   claro) porque nunca deja de convivir con la foto o con su versión
   sólida oscura — no alterna con un nav blanco. */
/* Sin backdrop-filter: esa propiedad crea un containing block para
   cualquier descendiente `position:fixed` (spec CSS Filter Effects) —
   tanto el menú móvil como el mega-menú de Servicios (ambos fixed,
   ambos necesitan posicionarse contra el viewport real) quedarían
   encajonados dentro de los 96px del nav. Se prioriza que ambos
   funcionen sobre el desenfoque, que era solo cosmético. */
.nav{
  position:fixed; top:0; left:0; right:0; z-index:100; height:var(--nav-h);
  display:flex; align-items:center;
  background:rgba(10,17,24, var(--nav-alpha, 0));
  border-bottom:1px solid rgba(255,255,255, calc(var(--nav-alpha, 0) * .1));
  box-shadow:0 8px 24px rgba(0,0,0, calc(var(--nav-alpha, 0) * .28));
}
.brand{display:flex; align-items:center; gap:.85rem; margin-right:auto}
.brand-logo{height:58px; width:auto; flex-shrink:0}

.menu{display:flex; align-items:center; justify-content:center; gap:2.3rem}
.menu>a{font-family:var(--font-display); font-size:1.1rem; font-weight:700; letter-spacing:.01em; color:#EDF2F6; position:relative; padding:.4rem 0; transition:color var(--duration-micro) ease}
.menu>a::after{content:""; position:absolute; left:0; bottom:0; height:2px; width:100%; background:var(--green-cta); transform:scaleX(0); transform-origin:right; transition:transform var(--duration-ui) var(--ease-out)}
.menu>a:hover{color:var(--green-cta)}
.menu>a:hover::after{transform:scaleX(1); transform-origin:left}

/* ── Ítem con submenú — mega-menú de "Servicios" ────────────────── */
/* El estado abierto/cerrado lo decide el JS (mouseenter/leave con
   demora, clic para fijarlo, clic afuera o Escape para cerrar) y se
   expresa en una sola fuente de verdad: el atributo aria-expanded del
   disparador. El panel arranca en `top:100%` — sin espacio muerto
   entre el botón y el panel — para que el cursor nunca cruce una
   franja sin elemento hoverable; el aire visual se logra con
   padding interno, no con un margen externo. */
.menu-item{position:relative}
.menu-trigger{
  display:flex; align-items:center; gap:.4rem; position:relative;
  font-family:var(--font-display); font-size:1.1rem; font-weight:700; letter-spacing:.01em; color:#EDF2F6; padding:.4rem 0;
  transition:color var(--duration-micro) ease;
}
.menu-trigger::after{content:""; position:absolute; left:0; bottom:0; height:2px; width:100%; background:var(--green-cta); transform:scaleX(0); transform-origin:right; transition:transform var(--duration-ui) var(--ease-out)}
.menu-caret{width:14px; height:14px; stroke:currentColor; fill:none; stroke-width:2; transition:transform var(--duration-ui) var(--ease-out)}
.menu-trigger[aria-expanded="true"]{color:var(--green-cta)}
.menu-trigger[aria-expanded="true"]::after{transform:scaleX(1); transform-origin:left}
.menu-trigger[aria-expanded="true"] .menu-caret{transform:rotate(180deg)}

/* Panel de ancho completo (no una tarjeta flotante centrada): cubre
   todo el viewport horizontal, pegado justo debajo de topbar+nav. El
   contenido interior sí se centra con un ancho máximo (.mega-menu-panel)
   para que los textos no se estiren delgados en monitores anchos. */
.mega-menu{
  position:fixed; top:var(--nav-h); left:0; right:0;
  background:var(--surface); border-bottom:1px solid var(--border); box-shadow:var(--shadow-lg);
  opacity:0; visibility:hidden; pointer-events:none; transform:translateY(-8px);
  transition:opacity var(--duration-ui) ease, transform var(--duration-ui) var(--ease-out), visibility var(--duration-ui);
}
.menu-trigger[aria-expanded="true"] ~ .mega-menu{opacity:1; visibility:visible; pointer-events:auto; transform:translateY(0)}
.mega-menu-panel{
  width:min(100% - 3rem, 1320px); margin-inline:auto; padding-block:2.25rem;
  display:grid; grid-template-columns:1fr 320px; gap:3rem; align-items:start;
}
.mega-menu-grid{display:grid; grid-template-columns:1fr 1fr; gap:.3rem .5rem}
.mega-menu-featured{background:var(--canvas); border:1px solid var(--border); border-radius:12px; padding:1.5rem}
.mega-menu-featured-label{font-family:var(--font-mono); font-size:.68rem; font-weight:600; letter-spacing:.12em; text-transform:uppercase; color:var(--green-text); margin:0 0 .6rem}
.mega-menu-featured h4{margin:0 0 .5rem; font-size:1.2rem}
.mega-menu-featured p{font-size:.85rem; color:var(--ink3); line-height:1.55; margin:0 0 1.2rem}
.mega-menu-intro{
  grid-column:1/-1; font-family:var(--font-mono); font-size:.72rem; font-weight:600;
  letter-spacing:.04em; color:var(--ink3);
  margin-bottom:.9rem; padding-bottom:.8rem; border-bottom:1px solid var(--border);
}
.mega-menu-item{
  display:flex; gap:.9rem; align-items:flex-start; padding:.7rem .7rem; border-radius:8px;
  box-shadow:inset 3px 0 0 transparent;
  transition:background var(--duration-micro) ease, box-shadow var(--duration-micro) ease;
}
.mega-menu-item:hover{background:var(--canvas); box-shadow:inset 3px 0 0 var(--green-cta)}
.mega-menu-icon{flex-shrink:0; width:46px; height:46px; border-radius:11px; background:rgba(74,143,232,.12); display:flex; align-items:center; justify-content:center}
.mega-menu-item:nth-of-type(even) .mega-menu-icon{background:rgba(92,214,141,.16)}
.mega-menu-icon svg{width:22px; height:22px; stroke:var(--blue); fill:none; stroke-width:1.75}
.mega-menu-item:nth-of-type(even) .mega-menu-icon svg{stroke:var(--green-text)}
.mega-menu-title{display:block; font-family:var(--font-display); font-size:.94rem; font-weight:700; color:var(--ink1); line-height:1.3}
.mega-menu-desc{display:block; font-size:.78rem; color:var(--ink3); margin-top:.2rem; line-height:1.4}
.mega-menu-view-all{
  grid-column:1/-1; text-align:center; font-size:.85rem; font-weight:600; color:var(--green-text);
  padding:.85rem 0 .15rem; margin-top:.4rem; border-top:1px solid var(--border);
}

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:.55rem;
  font-weight:600; font-size:.92rem; padding:.8rem 1.5rem; border-radius:4px;
  min-height:48px; transition:transform var(--duration-ui) var(--ease-out), box-shadow var(--duration-ui) ease, background var(--duration-ui) ease;
}
.btn svg{width:18px; height:18px; flex-shrink:0}
.btn-cta{background:var(--green-cta); color:var(--on-green); font-weight:700; box-shadow:var(--shadow-sm)}
.btn-cta:hover{background:var(--green-cta-hover); transform:translateY(-2px); box-shadow:var(--shadow-md)}
.btn-cta svg{fill:var(--on-green)}
.btn-blue{background:var(--steel-gray); color:var(--on-green)}
.btn-blue:hover{background:var(--steel-gray-hover); transform:translateY(-2px); box-shadow:var(--shadow-md)}
.btn-blue svg{fill:var(--on-green)}
.btn-outline{border:1.5px solid var(--border-strong); color:var(--blue); background:var(--surface)}
.btn-outline:hover{border-color:var(--blue); transform:translateY(-2px); box-shadow:var(--shadow-sm)}
.btn-nav{padding:.85rem 1.6rem; min-height:50px; font-size:.95rem}

.nav-actions{display:flex; align-items:center; gap:1.1rem}

.hamburger{display:none; width:46px; height:46px; align-items:center; justify-content:center}
.hamburger span{display:block; width:24px; height:2px; background:#fff; position:relative; transition:background var(--duration-micro) ease}
.hamburger span::before,.hamburger span::after{content:""; position:absolute; left:0; width:24px; height:2px; background:#fff; transition:transform var(--duration-ui) var(--ease-out)}
.hamburger span::before{top:-7px}
.hamburger span::after{top:7px}
.menu-open .hamburger span{background:transparent}
.menu-open .hamburger span::before{transform:translateY(7px) rotate(45deg)}
.menu-open .hamburger span::after{transform:translateY(-7px) rotate(-45deg)}

@media(max-width:980px){
  .menu{
    position:fixed; inset:var(--nav-h) 0 0 0; flex-direction:column;
    justify-content:center; gap:1.5rem; background:rgba(255,255,255,.98);
    backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px);
    opacity:0; visibility:hidden; transform:translateY(-10px); overflow-y:auto; padding-block:2rem;
    transition:opacity var(--duration-ui) var(--ease-out), transform var(--duration-ui) var(--ease-out), visibility var(--duration-ui);
  }
  .menu-open .menu{opacity:1; visibility:visible; transform:none}
  .menu>a, .menu-trigger{font-family:var(--font-display); font-size:1.8rem; font-weight:600; color:var(--ink1); justify-content:center}
  .menu-item{width:100%; display:flex; flex-direction:column; align-items:center}
  .menu-caret{display:none}
  /* En móvil, "Servicios" se muestra ya expandido apenas se abre el
     menú -- no hace falta un segundo toque sobre el disparador (pedido
     de Enrique). Por eso opacity/visibility/pointer-events SÍ se
     repiten aquí, pero condicionados a `.menu-open` (el menú entero
     realmente abierto, clase en <body> -- ver main.js) en vez de
     dejarlos incondicionales como antes: repetirlos sin ninguna
     condición dejaba el panel siempre clickeable en móvil aunque el
     menú estuviera cerrado (bug real, detectado con Playwright — ver
     ADR-018, Décima vuelta). `.menu-open` sí es un estado seguro: solo
     existe mientras el usuario tiene el menú completo abierto. */
  .mega-menu{
    position:static; transform:none;
    box-shadow:none; border:none; background:none;
  }
  .menu-open .mega-menu{opacity:1; visibility:visible; pointer-events:auto}
  .mega-menu-panel{width:100%; padding:0; grid-template-columns:1fr; gap:0}
  .mega-menu-grid{grid-template-columns:1fr; width:100%; max-width:340px; margin:.75rem auto 0; gap:0}
  .mega-menu-featured{display:none}
  .mega-menu-item{justify-content:center; text-align:center; padding:.5rem}
  .mega-menu-title{font-size:1rem}
  .mega-menu-view-all{display:none}
  .hamburger{display:flex}
}

/* ═══════════════════════════ HERO ═════════════════════════════ */
/* Carrusel fotográfico a pantalla completa con velo y texto en blanco
   encima. El overlay se calculó por contraste (no a ojo): incluso
   contra la foto más clara del set, el texto blanco y el verde de
   acento superan 4.5:1. */
.hero{
  position:relative; display:flex; align-items:flex-end; overflow:hidden; background:var(--ink1);
  /* --ov-hero-min-vh (Editor Pase 2, site/design/overrides.json → hero.minHeightVh):
     100vh de respaldo para navegadores sin soporte de dvh. */
  min-height:100vh;
  min-height:calc(var(--ov-hero-min-vh, 100) * 1dvh);
}
.hero-scroll-cue{
  position:absolute; z-index:3; left:50%; bottom:1.75rem; transform:translateX(-50%);
  width:28px; height:28px; border:1.5px solid rgba(255,255,255,.45); border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  animation:hero-float 2.2s ease-in-out infinite;
  opacity:1; transition:opacity 300ms ease;
}
/* Desaparece apenas el visitante empieza a scrollear — ver main.js.
   Una vez que ya entendió que puede bajar, seguir rebotando ahí solo
   compite por atención con el resto del héroe. */
.hero-scroll-cue.is-hidden{opacity:0; pointer-events:none}
.hero-scroll-cue svg{width:14px; height:14px; stroke:#fff; fill:none; stroke-width:2}
@keyframes hero-float{0%,100%{transform:translateX(-50%) translateY(0)} 50%{transform:translateX(-50%) translateY(6px)}}
.hero-carousel{position:absolute; inset:0; z-index:0; will-change:transform}
.hero-slide{
  position:absolute; inset:0; background-size:cover; background-position:center 42%;
  opacity:0; transition:opacity 1400ms ease; filter:contrast(1.05) saturate(.9);
}
/* Ken Burns — zoom lentísimo mientras la foto está en pantalla (6s,
   sincronizado con el intervalo del carrusel en main.js). Tan sutil
   que no se nota conscientemente, pero evita que la escena se sienta
   congelada. --ov-ken-burns-scale la controla el Editor (pestaña
   Sección → Héroe). `forwards` sostiene el zoom final hasta que la
   clase se retira al cambiar de foto. */
.hero-slide.is-active{opacity:1; animation:hero-ken-burns 6500ms ease-out forwards}
@keyframes hero-ken-burns{ from{transform:scale(1)} to{transform:scale(var(--ov-ken-burns-scale, 1.08))} }
.hero-overlay{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  /* --ov-hero-overlay (Editor Pase 2, hero.overlayOpacity): intensidad del velo. */
  opacity:var(--ov-hero-overlay, 1);
  background:
    /* Banda superior fija: garantiza que el nav transparente sea
       legible desde el primer fotograma, sin importar qué tan clara
       sea la foto activa del carrusel. */
    linear-gradient(to bottom, rgba(10,17,24,.62) 0%, rgba(10,17,24,0) 20%),
    linear-gradient(100deg, rgba(10,17,24,.84) 0%, rgba(10,17,24,.64) 30%, rgba(10,17,24,.32) 58%, rgba(10,17,24,.1) 82%),
    linear-gradient(to top, rgba(10,17,24,.6) 0%, rgba(10,17,24,0) 42%);
}
.hero-content{
  position:relative; z-index:2;
  /* topbar + nav son fixed y no reservan espacio: este padding calculado
     (no un rem fijo) es lo único que evita que el kicker quede debajo
     del header, en cualquier breakpoint donde cambie --nav-h. */
  padding-top:calc(var(--nav-h) + 3rem);
  padding-bottom:5.5rem;
  /* Dos zonas independientes en vez de una sola columna angosta: el
     titular usa el ancho real de la pantalla (aquí es donde vive el
     peso visual) y el bloque de apoyo (párrafo + CTAs + nota) queda en
     una columna angosta aparte, no debajo del título. Ambas se anclan
     abajo — heredado de .hero{align-items:flex-end} — como en la
     referencia de composición aprobada. */
  display:grid; grid-template-columns:1.7fr 1fr; column-gap:4rem; align-items:end;
}
.hero-content .eyebrow{color:var(--green-cta); margin-bottom:1.35rem}
.hero-content .eyebrow::before{background:var(--green-cta)}
.hero h1{
  color:#fff; font-size:clamp(2.8rem,6.2vw,5.6rem); line-height:1.01; margin:0;
  letter-spacing:-.02em;
}
.hero h1 em{font-style:normal; color:var(--green-cta)}
.hero-support{padding-bottom:.4rem}
.hero-subtitle{font-size:clamp(1.02rem,1.25vw,1.16rem); color:#E4EBF0; margin:0 0 2.1rem; max-width:34ch}
.hero-ctas{display:flex; flex-wrap:wrap; gap:.85rem; margin-bottom:1.5rem}
.hero-note{display:flex; align-items:center; gap:.5rem; font-size:.82rem; color:#C7D2D9}
.hero-note svg{width:16px; height:16px; stroke:var(--green-cta); fill:none; stroke-width:2; flex-shrink:0}

/* Indicadores con progreso real — no solo cambian de color, muestran
   la cuenta regresiva hasta la siguiente foto (como un pequeño
   instrumento, no una decoración). El anillo de fondo (track) siempre
   visible; el trazo verde (fill) recorre el círculo en los mismos 6s
   del intervalo del carrusel — la duración de la transición DEBE
   coincidir con el intervalo de main.js. */
.hero-dots{position:absolute; z-index:3; right:1.5rem; bottom:1.75rem; display:flex; gap:.6rem}
.hero-dot{
  width:18px; height:18px; padding:0; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  transition:transform var(--duration-ui) ease;
}
.hero-dot.is-active{transform:scale(1.15)}
.hero-dot-ring{width:100%; height:100%; transform:rotate(-90deg)}
.hero-dot-track{fill:none; stroke:rgba(255,255,255,.35); stroke-width:2; transition:stroke var(--duration-ui) ease}
.hero-dot:hover .hero-dot-track{stroke:rgba(255,255,255,.6)}
.hero-dot-fill{
  fill:none; stroke:var(--green-cta); stroke-width:2; stroke-linecap:round;
  stroke-dasharray:50.27; stroke-dashoffset:50.27; transition:none;
}
.hero-dot.is-active .hero-dot-fill{stroke-dashoffset:0; transition:stroke-dashoffset 6000ms linear}

@media(max-width:980px){
  .hero-content{
    padding-top:calc(var(--nav-h) + 1.5rem); padding-bottom:3.5rem;
    grid-template-columns:1fr; row-gap:1.8rem;
  }
  .hero-subtitle{max-width:54ch}
  .hero-overlay{background:linear-gradient(rgba(10,17,24,.52) 0%, rgba(10,17,24,.5) 55%, rgba(10,17,24,.86) 100%)}
}
@media(max-width:520px){
  .hero-dots{right:1rem; bottom:1.25rem}
  .hero-scroll-cue{display:none}
}

/* ══════════════════ CLIENTES — la prueba, en 2.º lugar ════════ */
/* Marca de centro tipo plano técnico, en cada borde entre secciones —
   mismo lenguaje que ya usan .eyebrow::before y .metric::before (una
   rayita corta de acento), aplicado ahora a las transiciones entre
   bloques grandes en vez de dejarlas como una simple línea gris. */
.rule-mark{position:relative}
.rule-mark::before{
  content:""; position:absolute; top:-1px; left:50%; transform:translateX(-50%);
  width:2px; height:9px; background:var(--ink1); pointer-events:none;
}

/* Secciones alargadas a propósito (padding-block 6.5rem→11rem, y más
   aire entre sus bloques internos) — sin ese recorrido de scroll, el
   revelado scrubbed (ver más abajo) no tiene espacio donde suceder. */
/* Antes en #FAFBFB, recalibrado a mano al fondo del <video> (ver ADR-015):
   ya no hace falta — logo_animado_loop.webp (ADR-015, "el archivo WebP")
   trae transparencia real (canal alfa, verificado con ffprobe:
   pix_fmt=argb), así que no hay ningún fondo de imagen que calzar. Vuelve
   a --surface, igual que el resto de fondos claros del sitio. */
.clients{background:var(--surface); border-block:1px solid var(--border); padding:11rem 0}
/* Imagen (con transparencia real, se funde con el fondo de arriba sin
   necesitar marco ni calibrar ningún color) a la izquierda, texto a la
   derecha — en vez del bloque centrado que tenía antes. Mitad y mitad:
   la imagen es el argumento visual tanto como el texto, no una viñeta
   decorativa pequeña. */
.clients-header{
  display:grid; grid-template-columns:1fr 1fr; align-items:center;
  gap:3.5rem; margin-bottom:6.5rem;
}
.clients-header-media{position:relative}
.clients-header-media img{width:100%; height:auto; display:block}
.clients-header h2{font-size:clamp(2rem,3.2vw,2.75rem); margin:.85rem 0 .75rem}
.clients-header p{color:var(--ink3); font-size:1.05rem; margin:0; max-width:58ch}
@media(max-width:860px){
  .clients-header{grid-template-columns:1fr; text-align:center; gap:1.75rem}
  .clients-header-media{max-width:280px; margin-inline:auto}
  .clients-header p{margin-inline:auto}
}
/* Un solo carrusel de logos — reemplaza la fila estática de tarjetas +
   la franja de texto que había antes (dos piezas separadas, "dos
   líneas"). Todos los clientes aparecen con su logo real; el que
   todavía no tiene archivo queda en clients.marquee (home.json) como
   lista de referencia, sin renderizar, hasta que llegue el suyo. */
.logo-marquee{position:relative; overflow:hidden; margin-bottom:6.5rem;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 9%,#000 91%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 9%,#000 91%,transparent)}
.logo-marquee-row{display:flex; align-items:center; gap:3.5rem; width:max-content; animation:marquee-scroll 34s linear infinite}
.logo-marquee:hover .logo-marquee-row{animation-play-state:paused}
@keyframes marquee-scroll{ from{transform:translateX(0)} to{transform:translateX(-50%)} }
/* Misma altura fija + object-fit:contain que ya usábamos: cada logo
   llega con su propia relación de aspecto (de un cuadrado a un
   rectángulo 2.3:1) — sin esto se verían a tamaños muy distintos. */
.logo-marquee-item{height:56px; width:auto; max-width:none; object-fit:contain; flex-shrink:0}

/* ═══════════════════════════ MÉTRICAS ═════════════════════════ */
.metrics{background:var(--canvas); border-top:1px solid var(--border); padding-block:10rem}
.metrics-grid{display:grid; grid-template-columns:repeat(5,1fr); gap:2rem}
.metric{padding-top:1.4rem; border-top:2px solid var(--green-cta); position:relative}
.metric::before{content:""; position:absolute; top:-2px; left:0; width:20px; height:2px; background:var(--ink1)}
.metric-number{
  font-family:var(--font-display); font-weight:var(--heading-weight); line-height:1;
  font-size:clamp(2.6rem,4.6vw,4rem); color:var(--blue);
  display:flex; align-items:baseline; gap:.06em; font-variant-numeric:tabular-nums;
}
.metric-suffix{font-size:max(.42em, 11px); color:var(--green-text); font-weight:700}
.metric-label{font-size:.92rem; color:var(--ink2); margin-top:.6rem; line-height:1.55; max-width:20ch}
@media(max-width:900px){ .metrics-grid{grid-template-columns:repeat(2,1fr); gap:2rem 1.5rem} }
@media(max-width:460px){ .metrics-grid{grid-template-columns:1fr} }

/* ═════════════ POR QUÉ ELEGIRNOS — Contenido adaptado del sitio
   actual (ingenieriapym.com), ver ADR de contenido de Inicio. Mismo
   lenguaje visual que .metrics (borde superior + marca), pero con
   texto en vez de una cifra — un ícono ya existente en el sistema
   (no se diseñó ninguno nuevo) marca cada pilar. ══════════════════ */
.differentiators{background:var(--surface); border-top:1px solid var(--border); padding-block:9rem}
.differentiators-title{font-size:clamp(1.9rem,3vw,2.5rem); margin:.85rem 0 3rem; max-width:32ch}
.differentiators-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:2.5rem}
.differentiator{padding-top:1.4rem; border-top:2px solid var(--green-cta); position:relative}
.differentiator::before{content:""; position:absolute; top:-2px; left:0; width:20px; height:2px; background:var(--ink1)}
.differentiator-icon{display:flex; align-items:center; justify-content:center; width:46px; height:46px; border-radius:11px; background:rgba(74,143,232,.12); margin-bottom:1.1rem}
.differentiator-icon svg{width:22px; height:22px; stroke:var(--blue); fill:none; stroke-width:1.75}
.differentiator-title{font-size:1.15rem; margin:0 0 .5rem}
.differentiator-desc{font-size:.92rem; color:var(--ink3); line-height:1.6; margin:0; max-width:32ch}
@media(max-width:860px){ .differentiators-grid{grid-template-columns:1fr; gap:2.5rem} }

/* ═════════════ RESUMEN DE SERVICIOS — reusa el mismo listado de 6
   líneas del mega-menú de navegación (site.json → megaMenu.items,
   ver build.mjs) en vez de duplicar contenido; cada tarjeta enlaza a
   su propia página de servicio. ═══════════════════════════════════ */
.services-summary{background:var(--canvas); border-top:1px solid var(--border); padding-block:9rem}
.services-summary-title{font-size:clamp(1.9rem,3vw,2.5rem); margin:.85rem 0 3rem; max-width:36ch}
.services-summary-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:.6rem}
/* Forma de tarjeta y animación de color inspiradas en el bloque "Creator
   Tools" de emperiavr.com (estudiado en vivo: DOM + CSS reales, no a ojo)
   -- se adapta la forma/curvatura/ajuste sin margen y el mecanismo de
   color (icono mono que se tiñe al interactuar), no su tema oscuro: nuestra
   tarjeta sigue sobre var(--surface), como el resto del sitio. El lavado
   diagonal de la referencia (degradado de su color de marca a transparente,
   detrás de la tarjeta, opacity 0->1 en .5s solo en :hover) se replica con
   nuestro verde industrial (--verde-cuerpo, kit metálico de la identidad
   visual, ADR-017) en vez de su dorado. */
.service-card{
  position:relative; overflow:hidden; display:flex; flex-direction:column; justify-content:space-between;
  aspect-ratio:1/1; padding:2rem;
  background:#436E4C; border:1px solid var(--border); border-radius:24px;
}
.service-card::before{
  /* En reposo la tarjeta es 100% verde industrial (el color de fondo de
     .service-card) -- este velo blanco, con una ventana diagonal recortada
     en la esquina del ícono, aparece al pasar el mouse (opacity 0->1,
     mismo mecanismo y tiempo que el lavado de emperiavr.com) y "invade"
     la tarjeta desde abajo-derecha sin llegar a cubrir esa cuña -- deja la
     esquina siempre en verde, donde vive el ícono. */
  content:""; position:absolute; inset:0; z-index:0;
  background:linear-gradient(135deg, transparent 0%, transparent 18%, var(--surface) 40%);
  opacity:0;
}
.service-card-icon{position:relative; z-index:1; display:block; width:32px; height:32px}
.service-card-icon svg{width:100%; height:100%; stroke:currentColor; fill:none; stroke-width:1.5; color:#fff}
.service-card-text{position:relative; z-index:1; display:flex; flex-direction:column; gap:.5rem}
.service-card-title{font-size:1.05rem; font-weight:700; color:#fff}
.service-card-desc{font-size:.86rem; color:rgba(255,255,255,.85); line-height:1.5}
/* Tarjetas con foto real del servicio (site.json → megaMenu.items[].photo,
   ver ADR-018): en vez de revelar blanco liso, revelan la foto -- la
   misma cuña verde de la esquina sigue tapando el ícono (capa 1), un velo
   oscuro fijo garantiza que el texto se lea sin importar qué tan clara
   sea cada foto que llegue después (capa 2), la foto cubre el resto
   (capa 3). El texto se queda blanco siempre aquí -- más simple que
   afinar el contraste foto por foto. */
.service-card--photo::before{
  background:
    linear-gradient(135deg, #436E4C 0%, #436E4C 18%, transparent 40%),
    linear-gradient(rgba(10,17,24,.15), rgba(10,17,24,.55)),
    var(--service-photo) var(--service-photo-position, center)/cover no-repeat;
}
/* Hover real (mouse) -- ADR-018 Duodécima vuelta: estas reglas (y sus
   transiciones) solo existen en dispositivos con hover:hover +
   pointer:fine. Antes vivían sueltas y se "contrarrestaban" con una
   regla aparte para móvil (@media hover:none) -- en teoría mutuamente
   excluyentes, pero algunos navegadores táctiles aplican :hover de
   forma transitoria mientras se arrastra el dedo durante el scroll, lo
   que disparaba estas reglas por error y se veía como un parpadeo
   verde/foto de medio segundo (la transición). Con el :hover encerrado
   aquí, un dispositivo táctil no tiene ninguna regla que ese pseudo-
   estado pueda disparar, pase lo que pase con el navegador. */
@media(hover:hover) and (pointer:fine){
  .service-card::before{transition:opacity .5s ease-in-out}
  .service-card-title, .service-card-desc{transition:color .5s ease-in-out}
  .service-card:hover::before{opacity:1}
  .service-card:hover .service-card-title{color:var(--ink1)}
  .service-card:hover .service-card-desc{color:var(--ink3)}
  .service-card--photo:hover .service-card-title,
  .service-card--photo:hover .service-card-desc{color:#fff}
}
.services-summary-note{margin-top:2.5rem; font-size:.9rem; color:var(--ink3)}
@media(max-width:900px){ .services-summary-grid{grid-template-columns:repeat(2,1fr)} }
@media(max-width:600px){ .services-summary-grid{grid-template-columns:1fr} }
/* En dispositivos sin forma real de pasar el puntero (táctiles), el
   :hover no tiene sentido -- en vez de dejarlo en reposo (icono+título
   nada más) o depender de un :hover simulado e inconsistente, se
   muestra directamente el estado "activo": blanco liso o la foto real
   del servicio, según corresponda -- tiene más sentido de venta que
   esconder la foto detrás de un gesto que el dispositivo no tiene.
   Solo afecta hover:none -- escritorio no se toca. */
@media(hover:none){
  /* Sin transition aquí a propósito (defensa extra, ADR-018 Duodécima
     vuelta): si algo llegara a recalcular estos valores, el cambio es
     instantáneo e imperceptible, no una animación vistosa de .5s. */
  .service-card::before{opacity:1; transition:none}
  .service-card:not(.service-card--photo) .service-card-title{color:var(--ink1); transition:none}
  .service-card:not(.service-card--photo) .service-card-desc{color:var(--ink3); transition:none}
}

/* ═════════════ POLÍTICA DE CALIDAD — sello de confianza final,
   justo antes del pie. Mismo lenguaje visual que "Por qué elegirnos"
   (eyebrow + título + grid), pero con el check ya usado en el hero en
   vez de un ícono por ítem, porque cada política es una oración
   completa, no un título corto. ══════════════════════════════════ */
.quality-policy{background:var(--canvas); border-top:1px solid var(--border); padding-block:7rem}
.quality-policy-title{font-size:clamp(1.9rem,3vw,2.5rem); margin:.85rem 0 3rem; max-width:32ch}
.quality-policy-grid{display:grid; grid-template-columns:repeat(2,1fr); gap:1.75rem 2.5rem}
.quality-policy-item{display:flex; align-items:flex-start; gap:.9rem}
.quality-policy-icon{flex:none; display:flex; align-items:center; justify-content:center; width:28px; height:28px; margin-top:.15rem}
.quality-policy-icon svg{width:100%; height:100%; stroke:var(--green-cta); fill:none; stroke-width:2}
.quality-policy-text{font-size:.96rem; color:var(--ink3); line-height:1.6; margin:0}
@media(max-width:860px){ .quality-policy-grid{grid-template-columns:1fr; gap:1.5rem} }

/* ═════════ FICHA DE SERVICIO — plantilla compartida por las 6
   páginas individuales (ADR-020). Reusa el lenguaje visual ya
   existente (velo de .hero-overlay simplificado a una sola capa, el
   acento de borde verde de .differentiator, los botones del héroe de
   Inicio) — nada de componentes nuevos desde cero. ══════════════ */
.service-hero{
  position:relative; overflow:hidden; display:flex; align-items:flex-end;
  min-height:56vh; background:var(--ink1);
}
.service-hero-media{position:absolute; inset:0; background-size:cover; background-position:center; filter:contrast(1.05) saturate(.9)}
.service-hero-overlay{
  position:absolute; inset:0;
  background:
    linear-gradient(to bottom, rgba(10,17,24,.62) 0%, rgba(10,17,24,.15) 35%, rgba(10,17,24,.75) 100%);
}
.service-hero-content{position:relative; z-index:1; padding-top:calc(var(--nav-h) + 3rem); padding-bottom:3.5rem}
.service-hero-content .eyebrow{color:var(--green-cta); margin-bottom:1rem}
.service-hero-content .eyebrow::before{background:var(--green-cta)}
.service-hero h1{color:#fff; font-size:clamp(2.2rem,4.6vw,3.6rem); line-height:1.05; margin:0; letter-spacing:-.02em}
.service-breadcrumb{display:flex; flex-wrap:wrap; gap:.4rem; font-family:var(--font-mono); font-size:.75rem; letter-spacing:.03em; color:#B6C7D6; margin-bottom:1.4rem}
.service-breadcrumb a{color:#B6C7D6}
.service-breadcrumb a:hover{color:#fff}

.service-intro{padding-block:5rem}
.service-intro-text{font-size:1.25rem; color:var(--ink2); line-height:1.6; max-width:62ch}

.service-scope{background:var(--surface); border-top:1px solid var(--border); padding-block:6rem}
.service-scope-title{font-size:clamp(1.9rem,3vw,2.5rem); margin:.85rem 0 3rem; max-width:32ch}
.service-scope-grid{display:grid; grid-template-columns:repeat(2,1fr); gap:2.5rem}
.service-scope-item{padding-top:1.4rem; border-top:2px solid var(--green-cta)}
.service-scope-icon{display:flex; align-items:center; justify-content:center; width:46px; height:46px; border-radius:11px; background:rgba(74,143,232,.12); margin-bottom:1.1rem}
.service-scope-icon svg{width:22px; height:22px; stroke:var(--blue); fill:none; stroke-width:1.75}
.service-scope-item-title{font-size:1.25rem; margin:0 0 .5rem}
.service-scope-item-desc{font-size:.95rem; color:var(--ink3); margin:0 0 1.25rem; line-height:1.6}
.service-scope-list{margin:0 0 1.5rem; padding-left:1.2rem; display:flex; flex-direction:column; gap:.55rem}
.service-scope-list li{font-size:.92rem; color:var(--ink2); line-height:1.5}
.service-scope-note{font-size:.86rem; color:var(--ink3); line-height:1.6; margin:0; padding:1rem 1.2rem; background:var(--canvas); border-radius:10px; border:1px solid var(--border)}
@media(max-width:860px){ .service-scope-grid{grid-template-columns:1fr; gap:2.5rem} }
/* Cuando el servicio solo tiene un bloque real de alcance (ver ADR-024,
   ej. Filtración de aire) -- centrado en una columna angosta en vez de
   dejar la mitad de la grilla de 2 columnas vacía. */
.service-scope-grid--single{grid-template-columns:1fr; max-width:640px; margin-inline:auto}

.service-gallery{padding-block:6rem}
.service-gallery-title{font-size:clamp(1.9rem,3vw,2.5rem); margin:.85rem 0 3rem; max-width:32ch}
.service-gallery-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:1.5rem}
.service-gallery-item{margin:0}
.service-gallery-item img{display:block; width:100%; aspect-ratio:4/3; object-fit:cover; border-radius:16px; border:1px solid var(--border)}
.service-gallery-item figcaption{margin-top:.85rem; font-size:.86rem; color:var(--ink3); line-height:1.5}
@media(max-width:900px){ .service-gallery-grid{grid-template-columns:1fr; gap:2rem} }

/* Línea de tiempo de "Nosotros" -- único bloque visual nuevo de esa
   página, el resto reutiliza las clases de las fichas de servicio. */
.about-timeline{display:flex; flex-direction:column; gap:2rem; margin-top:3rem}
.about-timeline-item{display:flex; gap:1.5rem; align-items:flex-start}
.about-timeline-year{flex:0 0 auto; min-width:70px; font-family:var(--font-mono); font-size:1.1rem; font-weight:700; color:var(--green-text); padding-top:.2rem}
.about-timeline-text{font-size:1rem; color:var(--ink2); line-height:1.6; margin:0; padding:0 0 0 1.5rem; border-left:2px solid var(--border)}
@media(max-width:640px){ .about-timeline-year{min-width:56px; font-size:1rem} }

/* Contacto/Cotización (ver ADR-026) -- .panel-cotizacion porta el diseño
   "panel de control industrial" ya aprobado en brand/identidad-visual.html
   §06, adaptado a los tokens reales del sitio (no al sistema --kit-* del
   mockup, que solo existe ahí) y sin el filtro SVG que ADR-023 ya
   reemplazó por PNG estático en el resto del sitio. */
.contact-layout{display:grid; grid-template-columns:1fr 320px; gap:3rem; align-items:start}
@media(max-width:900px){ .contact-layout{grid-template-columns:1fr} }

.panel-cotizacion{
  position:relative; overflow:hidden; padding:2.75rem 2.5rem 2.5rem; border-radius:24px;
  background:var(--night); box-shadow:0 20px 48px rgba(10,17,24,.28), inset 0 1px 0 rgba(255,255,255,.06);
}
.panel-cotizacion-grid{position:relative; z-index:1; display:grid; grid-template-columns:1fr 1fr; gap:1.6rem 1.25rem}
.panel-cotizacion-grid .span-2{grid-column:1 / -1}
@media(max-width:560px){ .panel-cotizacion-grid{grid-template-columns:1fr} }

.campo-industrial label{
  display:inline-flex; align-items:center; gap:.5rem; font-family:var(--font-mono); font-size:.7rem;
  letter-spacing:.06em; text-transform:uppercase; font-weight:600; color:#fff; margin-bottom:.6rem;
}
/* La "luz" es un bisel en miniatura (radial-gradient) -- gris en reposo,
   verde con resplandor al enfocar (:focus-within, sin JS), rojo en error. */
.campo-industrial .luz{
  width:9px; height:9px; border-radius:50%; flex-shrink:0; transition:background .2s, box-shadow .2s;
  background:radial-gradient(circle at 35% 30%, #868D86, #5A6058 70%);
  box-shadow:inset 0 0 1.5px rgba(0,0,0,.6);
}
.campo-industrial:focus-within .luz{
  background:radial-gradient(circle at 35% 30%, var(--green-neon), var(--green-cta) 75%);
  box-shadow:0 0 6px 2px var(--green-neon), inset 0 0 1.5px rgba(0,0,0,.3);
}
.campo-industrial.error .luz{
  background:radial-gradient(circle at 35% 30%, #FF9C9C, #E25353 75%);
  box-shadow:0 0 6px 2px #E25353, inset 0 0 1.5px rgba(0,0,0,.3);
}
.campo-industrial input, .campo-industrial select, .campo-industrial textarea{
  width:100%; font-family:var(--font-body); font-size:1rem; padding:.9rem 1.1rem; border:none; border-radius:10px;
  background:var(--surface); color:var(--ink1); box-shadow:inset 0 2px 8px rgba(0,0,0,.28); transition:box-shadow .2s;
}
.campo-industrial input:focus, .campo-industrial select:focus, .campo-industrial textarea:focus{
  outline:none; box-shadow:inset 0 2px 6px rgba(0,0,0,.2), 0 0 0 4px rgba(74,232,122,.3);
}
.campo-industrial.error input, .campo-industrial.error select, .campo-industrial.error textarea{
  box-shadow:inset 0 2px 8px rgba(0,0,0,.28), 0 0 0 3px rgba(194,59,59,.35);
}
.campo-industrial-select{position:relative}
.campo-industrial-select select{appearance:none; -webkit-appearance:none; cursor:pointer}
.campo-industrial-select::after{
  content:""; position:absolute; right:1.1rem; top:50%; transform:translateY(-65%); pointer-events:none;
  width:0; height:0; border-left:5px solid transparent; border-right:5px solid transparent; border-top:6px solid var(--ink3);
}
.honeypot-field{position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden}
.panel-cotizacion-consent{position:relative; z-index:1; display:flex; gap:.6rem; align-items:flex-start; font-size:.82rem; color:#B6C7D6; line-height:1.5; margin-top:1.5rem}
.panel-cotizacion-consent input{margin-top:.2rem; flex-shrink:0}
.panel-cotizacion .btn-cta{position:relative; z-index:1; width:100%; padding:1.1rem 2.2rem; font-size:1.05rem; margin-top:1.25rem; border:none; cursor:pointer}
.panel-cotizacion-success, .panel-cotizacion-fail{
  padding:2.75rem 2.5rem; border-radius:24px; background:var(--night); text-align:center;
  box-shadow:0 20px 48px rgba(10,17,24,.28);
}
.panel-cotizacion-success h3, .panel-cotizacion-fail h3{color:#fff; margin:0 0 .6rem}
.panel-cotizacion-success p, .panel-cotizacion-fail p{color:#B6C7D6; margin:0}

.contact-info{display:flex; flex-direction:column; gap:2rem}
.contact-info-block .eyebrow{margin-bottom:.75rem}
.contact-info-line{font-size:.95rem; color:var(--ink2); line-height:1.7; margin:0}
.contact-info-line a{color:var(--ink2)}
.contact-info-line a:hover{color:var(--green-text)}

.service-cta{background:var(--night); padding-block:6rem; text-align:center}
.service-cta-content{max-width:56ch; margin:0 auto; display:flex; flex-direction:column; align-items:center; gap:1rem}
.service-cta h2{color:#fff; font-size:clamp(1.7rem,2.8vw,2.3rem); margin:0}
.service-cta p{color:#B6C7D6; font-size:1.05rem; margin:0}
.service-cta-actions{display:flex; gap:1rem; flex-wrap:wrap; justify-content:center; margin-top:.75rem}

/* ════════════════════════ REVELADOS ═══════════════════════════
   Misma curva de salida en opacidad y transform. `.reveal-zoom` da a
   las tarjetas un poco de escala además del ascenso, para que no todo
   el sitio se mueva exactamente igual. */
/* ══════ Revelado — dos modos (Editor Pase 2, revealGroups.<grupo>) ═
   "time": se dispara una vez al entrar a la vista y transiciona por
   tiempo (--ov-duration) — solo para el héroe, que es una entrada de
   página, no algo que el usuario reveló scrolleando.
   "scroll": el progreso (0–1) es una función directa de la posición
   de scroll, recalculada en cada frame por el mismo rAF que ya mueve
   el paralaje del héroe (ver main.js) — sin transition propia: darle
   una transición aquí haría que el movimiento "persiga" al scroll en
   vez de seguirlo 1:1, que es justo lo que hace que se sienta atado
   de verdad al gesto de scrollear. */
.reveal{ opacity:0; transform:translateY(var(--ov-distance, 22px)) }

.reveal[data-reveal-mode="time"]{
  transition:opacity var(--ov-duration, var(--duration-reveal)) var(--ease-out),
             transform var(--ov-duration, var(--duration-reveal)) var(--ease-out);
}
.reveal[data-reveal-mode="time"].is-visible{opacity:1; transform:none}

.reveal[data-reveal-mode="scroll"]{
  opacity:var(--reveal-progress, 0);
  transform:translateY(calc((1 - var(--reveal-progress, 0)) * var(--ov-distance, 40px)));
}

.reveal-zoom{transform:translateY(var(--ov-distance, 16px)) scale(.96)}
.reveal-zoom[data-reveal-mode="time"].is-visible{transform:none}
.reveal-zoom[data-reveal-mode="scroll"]{
  transform:
    translateY(calc((1 - var(--reveal-progress, 0)) * var(--ov-distance, 40px)))
    scale(calc(1 - (1 - var(--reveal-progress, 0)) * .04));
}

/* ═══════════════════════════ PIE ══════════════════════════════ */
.footer{background:var(--night); color:#B6C7D6; padding-block:9rem 2.5rem}
.footer-grid{display:grid; grid-template-columns:1.4fr 1fr 1fr; gap:3.5rem; margin-bottom:3.5rem}
.footer h2{color:#fff; font-size:1.15rem; margin:0 0 1.25rem; font-family:var(--font-display); font-weight:var(--heading-weight); letter-spacing:.04em; text-transform:uppercase}
.footer p,.footer li{font-size:.95rem; line-height:1.9; margin:0}
.footer ul{list-style:none; padding:0}
.footer a{transition:color var(--duration-micro) ease}
.footer a:hover{color:var(--green-neon)}
.footer-brand{font-family:var(--font-display); font-weight:var(--heading-weight); font-size:1.9rem; color:#fff; letter-spacing:.02em}
.footer-brand em{font-style:normal; color:var(--green-neon)}
.footer-base{border-top:1px solid rgba(135,247,254,.14); padding-top:2rem; display:flex; flex-wrap:wrap; gap:1rem; justify-content:space-between; align-items:center}
.footer-base p{font-size:.82rem; color:#7D93A6}
.footer-credit{font-family:var(--font-mono); font-size:.68rem; letter-spacing:.14em; text-transform:uppercase; color:#7D93A6; text-align:right}
@media(max-width:800px){ .footer-grid{grid-template-columns:1fr; gap:2.5rem} }

/* ═══════════════════════ FLOTANTE WHATSAPP ════════════════════ */
.whatsapp-fab{
  position:fixed; right:1.25rem; bottom:1.25rem; z-index:95;
  display:flex; align-items:center; gap:.6rem;
  background:var(--whatsapp); color:var(--on-green); font-weight:700; font-size:.9rem;
  padding:.8rem 1.25rem; border-radius:100px; box-shadow:var(--shadow-lg);
  opacity:0; visibility:hidden; transform:translateY(12px);
  transition:opacity var(--duration-ui) ease, transform var(--duration-ui) var(--ease-out), visibility var(--duration-ui);
}
.whatsapp-fab.is-visible{opacity:1; visibility:visible; transform:none}
.whatsapp-fab:hover{transform:translateY(-2px)}
.whatsapp-fab svg{width:22px; height:22px; fill:var(--on-green)}
@media(max-width:520px){ .whatsapp-fab span{display:none} .whatsapp-fab{padding:.9rem; border-radius:50%} }

/* ══════════════════ MOVIMIENTO REDUCIDO ═══════════════════════ */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important; animation-iteration-count:1 !important; transition-duration:.01ms !important; scroll-behavior:auto !important}
  .reveal{opacity:1; transform:none}
  .logo-marquee-row{animation:none; flex-wrap:wrap; width:auto; justify-content:center}
  .logo-marquee{-webkit-mask-image:none; mask-image:none}
}
