/* ============================================================
   Inversiones Multimodal GJG — Componentes "Kinetic Flow"

   Aquí viven:
   1. Las variables de color de los temas claro y oscuro
   2. Las clases de componente que Stitch usaba sin definir
   3. Los ajustes de contraste del tema oscuro
   ============================================================ */

/* ============================================================
   1. PALETA — terna RGB sin comas, para que Tailwind pueda
   aplicar modificadores de opacidad (bg-surface/90, etc.)
   ============================================================ */

:root {
  --surface: 250 248 255;
  --surface-dim: 210 217 244;
  --surface-bright: 250 248 255;
  --surface-container-lowest: 255 255 255;
  --surface-container-low: 242 243 255;
  --surface-container: 234 237 255;
  --surface-container-high: 226 231 255;
  --surface-container-highest: 218 226 253;
  --on-surface: 19 27 46;
  --on-surface-variant: 67 70 86;
  --inverse-surface: 40 48 68;
  --inverse-on-surface: 238 240 255;
  --outline: 103 106 124;          /* oscurecido: con #737688 las etiquetas
                                      de formulario quedaban en 4.49:1 */
  --outline-variant: 195 197 217;
  --surface-tint: 0 76 237;

  --primary: 0 62 199;
  --on-primary: 255 255 255;
  --primary-container: 0 82 255;
  --on-primary-container: 223 227 255;
  --inverse-primary: 183 196 255;

  /* Secundario: verde oliva vibrante (reemplaza el naranja).
     Tono tomado del globo del logo, subido en saturación para que
     funcione como acento y CTA sobre fondo claro y oscuro. */
  --secondary: 74 99 20;               /* #4A6314 oliva oscuro: texto sobre claro */
  --on-secondary: 255 255 255;
  --secondary-container: 92 125 22;    /* #5C7D16 oliva vibrante: fondo de botón (blanco AA) */
  --on-secondary-container: 255 255 255;

  --tertiary: 0 88 91;
  --on-tertiary: 255 255 255;
  --tertiary-container: 0 114 119;
  --on-tertiary-container: 123 248 255;

  --error: 186 26 26;
  --on-error: 255 255 255;
  --error-container: 255 218 214;
  --on-error-container: 147 0 10;

  --primary-fixed: 221 225 255;
  --primary-fixed-dim: 183 196 255;
  --on-primary-fixed: 0 20 82;
  --on-primary-fixed-variant: 0 56 182;
  --secondary-fixed: 220 240 168;      /* #DCF0A8 oliva claro */
  --secondary-fixed-dim: 196 227 136;  /* #C4E388 oliva claro: resaltes sobre fondo oscuro */
  --on-secondary-fixed: 26 38 0;       /* #1A2600 */
  --on-secondary-fixed-variant: 58 82 0; /* #3A5200 */

  /* Metales del logo: dorado y plateado para títulos sobre franjas oliva/oscuras */
  --oro: 201 162 39;                   /* #C9A227 */
  --oro-claro: 224 194 105;            /* #E0C269 sobre fondos oscuros */
  --plata: 199 204 209;                /* #C7CCD1 */
  --plata-clara: 226 230 234;          /* #E2E6EA */
  --tertiary-fixed: 99 247 255;
  --tertiary-fixed-dim: 0 220 229;
  --on-tertiary-fixed: 0 32 33;
  --on-tertiary-fixed-variant: 0 79 83;

  --background: 250 248 255;
  --on-background: 19 27 46;
  --surface-variant: 218 226 253;
  --whatsapp: 37 211 102;

  /* Altos del degradado azul de marca (.kinetic-gradient).

     Atenuados un 20 % respecto de los originales
     (#003ec7 · #0052ff · #0a63ff): la franja previa al footer
     resultaba demasiado luminosa.

     Se oscurecen los COLORES del degradado y no el elemento con
     `filter: brightness(.8)`, porque el filtro apagaría también el
     texto blanco y los botones que van encima.

     Viven aquí, en un solo sitio, porque la clase se define dos veces
     —la versión plana de la sección 3 y la animada de la sección 14—
     y así no pueden divergir. */
  --kinetic-a: #00329f;
  --kinetic-b: #0042cc;
  --kinetic-c: #084fcc;

  /* Auxiliares propias del tema */
  --tarjeta: 255 255 255;              /* fondo de tarjeta (sustituye bg-white) */
  --sombra-ambiente: 0 4px 20px rgba(15, 23, 42, .05);
  --sombra-foco: 0 12px 32px rgba(15, 23, 42, .1);
  --vidrio: 255 255 255;               /* base de .glass-card */
  --vidrio-alfa: .8;
  --borde-vidrio: 226 232 240;
}

/* ---------- Tema oscuro ---------- */
/* Solo se redefine lo que cambia. Los tonos de marca (primary,
   secondary, tertiary) se aclaran porque se usan como TEXTO sobre
   fondo oscuro; los fondos de botón se corrigen más abajo. */

.dark {
  --surface: 15 18 30;
  --surface-dim: 10 12 20;
  --surface-bright: 26 30 45;
  --surface-container-lowest: 10 12 20;
  --surface-container-low: 22 26 40;
  --surface-container: 27 32 50;
  --surface-container-high: 35 41 62;
  --surface-container-highest: 44 51 76;
  --on-surface: 228 230 240;
  --on-surface-variant: 176 180 199;
  --outline: 172 177 198;        /* etiquetas de formulario: se veian
                                     demasiado apagadas sobre fondo oscuro */
  --outline-variant: 60 65 88;

  /* El footer y los paneles oscuros usan inverse-surface: se mantiene
     oscuro en ambos temas, solo se hunde un poco más aquí. */
  --inverse-surface: 8 10 17;
  --inverse-on-surface: 228 230 240;

  --primary: 147 177 255;
  --secondary: 196 227 136;            /* oliva claro como texto sobre oscuro */
  --tertiary: 0 220 229;

  --background: 15 18 30;
  --on-background: 228 230 240;
  --surface-variant: 44 51 76;

  --tarjeta: 27 32 50;
  --sombra-ambiente: 0 4px 20px rgba(0, 0, 0, .45);
  --sombra-foco: 0 12px 32px rgba(0, 0, 0, .6);
  --vidrio: 27 32 50;
  --vidrio-alfa: .72;
  --borde-vidrio: 70 76 100;
}

/* Cambio de tema sin parpadeo brusco */
html.transicion-tema,
html.transicion-tema body,
html.transicion-tema header,
html.transicion-tema footer,
html.transicion-tema section,
html.transicion-tema div,
html.transicion-tema article {
  transition: background-color .25s ease, border-color .25s ease, color .25s ease;
}

/* ============================================================
   2. BASE
   ============================================================ */

body {
  font-family: 'Geist', system-ui, -apple-system, sans-serif;
  scroll-behavior: smooth;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 96px;   /* compensa el header fijo de 80px */
  color-scheme: light;
}

html.dark { color-scheme: dark; }

.material-symbols-outlined {
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}

/* ============================================================
   3. SUPERFICIES Y ELEVACIÓN
   Estas clases se usaban en las páginas de Stitch pero nunca
   estaban definidas, así que las tarjetas salían sin fondo,
   sin borde y sin sombra.
   ============================================================ */

.ambient-shadow,
.surface-shadow {
  box-shadow: var(--sombra-ambiente);
}

.focus-shadow:hover {
  box-shadow: var(--sombra-foco);
}

.glass-card {
  background: rgb(var(--vidrio) / var(--vidrio-alfa));
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgb(var(--borde-vidrio) / .5);
}

/* Para usar sobre imágenes y heros oscuros: igual en ambos temas */
.glass-card-dark {
  background: rgba(255, 255, 255, .12);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, .25);
}

/* Versión plana. La sección 14 la vuelve a declarar con el degradado
   animado y gana por orden; esta se mantiene por si allí se desactiva. */
.kinetic-gradient {
  background: linear-gradient(135deg, var(--kinetic-a) 0%, var(--kinetic-b) 100%);
}

.glass-tag {
  background: rgba(92, 125, 22, .12);
  color: #3f5a0e;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.dark .glass-tag {
  background: rgba(140, 175, 60, .18);
  color: #c4e388;
}

/* Borde superior de 4px que categoriza la tarjeta (DESIGN.md) */
.kinetic-border { border-top: 4px solid #0052ff; }

.card-hover { transition: transform .3s ease, box-shadow .3s ease; }

.card-hover:hover {
  transform: translateY(-4px);
  box-shadow: var(--sombra-foco);
}

/* Línea de acento a la izquierda: el "momentum" del sistema */
.momentum-line { position: relative; padding-left: 20px; }

.momentum-line::before {
  content: "";
  position: absolute;
  left: 0;
  top: 4px;
  bottom: 4px;
  width: 4px;
  border-radius: 9999px;
  background: linear-gradient(180deg, #0052ff 0%, #00dce5 100%);
}

/* ============================================================
   4. CORRECCIONES DEL TEMA OSCURO

   Algunos tonos se usan a la vez como fondo de botón (con texto
   blanco) y como color de texto. Al aclararlos para que se lean
   sobre fondo oscuro, los botones perderían contraste; aquí se
   les devuelve el tono vivo original.
   ============================================================ */

.dark .bg-primary { background-color: rgb(0 82 255); }
.dark .bg-secondary { background-color: rgb(92 125 22); }
.dark .bg-tertiary { background-color: rgb(0 114 119); }

.dark .hover\:bg-primary:hover { background-color: rgb(0 82 255); }
.dark .hover\:bg-primary-container:hover { background-color: rgb(0 98 255); }

/* Los contenedores vivos siguen siendo fondo de botón… */
.dark .bg-primary-container { background-color: rgb(0 82 255); }
.dark .bg-secondary-container { background-color: rgb(92 125 22); }
.dark .bg-tertiary-container { background-color: rgb(0 114 119); }

/* …pero como texto necesitan aclararse */
.dark .text-primary-container { color: rgb(147 177 255); }
.dark .text-secondary-container { color: rgb(196 227 136); }
.dark .text-tertiary-container { color: rgb(0 220 229); }

/* Las tarjetas blancas pasan a superficie elevada */
.dark .bg-white { background-color: rgb(var(--tarjeta)); }

/* El footer y los paneles oscuros van sobre `inverse-surface`, que es
   oscuro en los DOS temas. `outline-variant`, en cambio, sí cambia
   con el tema, así que el texto salía de un gris distinto en cada uno
   sin que el fondo hubiera cambiado, y de los dos el de tema oscuro
   era el más apagado. Aquí se fija a mano.

   En tema claro se queda en casi blanco: el fondo del pie ahí es
   #283044, bastante más suave que el negro del tema oscuro, y sobre
   él el blanco puro deslumbra. */
footer .text-outline-variant,
.bg-inverse-surface .text-outline-variant {
  color: rgb(232 235 244);
}

/* En tema oscuro, blanco puro. Aquí el fondo del pie es #080a11, casi
   negro, y aguanta el máximo sin cansar. */
.dark footer .text-outline-variant,
.dark .bg-inverse-surface .text-outline-variant {
  color: #fff;
}

/* Bordes de tarjeta algo más marcados en oscuro, ya que la sombra
   se percibe menos sobre fondo negro */
.dark .ambient-shadow,
.dark .surface-shadow {
  border: 1px solid rgb(var(--outline-variant) / .35);
}

/* ============================================================
   5. HEROES CON IMAGEN DE FONDO

   Antes el velo usaba «from-on-background/85». Ese token se
   invierte con el tema: en oscuro pasaba de azul marino a casi
   blanco, así que el velo ACLARABA la foto en lugar de oscurecerla
   y el titular blanco se perdía. Aquí el velo es siempre oscuro y
   en tema oscuro además se atenúa la propia imagen.
   ============================================================ */

/* ------------------------------------------------------------
   Banda final «¿Tiene una carga que mover?»

   Fotografía de fondo con un velo oscuro encima, igual que la
   pantalla de acceso del panel: el velo es lo que permite que el
   titular y los botones blancos se lean sin competir con la imagen.

   Sustituye a `.kinetic-gradient` en esa sección. Se hace así y no
   añadiendo la foto encima del degradado porque aquella clase lleva
   una animación de `background-position`: al compartir propiedad,
   la foto se desplazaría sola.

   El `background-color` queda de respaldo: si la imagen no cargara,
   la banda sigue siendo azul oscuro y el texto blanco legible.

   Se usa `Fcasifooter-fondo.jpg` (1920 px, 246 KB) y no el original
   `Fcasifooter.jpg`, de 7680 px y 6,5 MB.
   ------------------------------------------------------------ */

.cta-foto {
  background-color: var(--kinetic-a);
  background-image:
    linear-gradient(rgba(2, 13, 43, .74), rgba(2, 13, 43, .84)),
    url('../img/Fcasifooter-fondo.jpg?v=73');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* `fixed` provoca saltos y tirones al desplazar en iOS */
  background-attachment: scroll;
}

/* En móvil la foto panorámica se recorta muchísimo: con más velo el
   recorte pasa a ser textura y no distrae del mensaje. */
@media (max-width: 639px) {
  .cta-foto {
    background-image:
      linear-gradient(rgba(2, 13, 43, .82), rgba(2, 13, 43, .9)),
      url('../img/Fcasifooter-fondo.jpg?v=73');
  }
}

.dark .cta-foto {
  background-image:
    linear-gradient(rgba(1, 8, 26, .82), rgba(1, 8, 26, .9)),
    url('../img/Fcasifooter-fondo.jpg?v=73');
}

/* ------------------------------------------------------------
   Altura del hero

   El header es fijo y mide 112 px (cintillo + barra). El hero
   empieza detrás de él, así que sin este relleno el badge queda
   pegado a la navegación.

   Se usa min-height, NO height: si el contenido no cabe —móviles
   pequeños, o texto más grande por accesibilidad— la sección
   crece. Con altura fija, el `overflow:hidden` recortaba el badge
   y los botones en pantallas de 360 px o menos.
   ------------------------------------------------------------ */

/* 112 px del header + un margen de respeto. El extra importa sobre
   todo cuando la sección crece por falta de altura: ahí el contenido
   arranca justo en el relleno, y sin ese margen quedaría de nuevo
   pegado a la navegación. */
.hero-seccion {
  min-height: max(640px, 92vh);
  padding-top: calc(112px + 2.5rem);
}

/* Nota para el futuro: se probó darle un suelo en `vw` a la sección
   en ventanas ultrapanorámicas, para que la foto se recortara menos.
   Se descartó: la franja crecía por encima de la ventana y el botón
   «Solicitar cotización» caía bajo el pliegue. El encuadre de la
   imagen se resuelve en `.hero-imagen`, no estirando la sección. */

/* La flecha de «bajar» va por encima del bloque de texto del hero.
   El contenido vive en una capa con z-index 10 y la fila de botones
   ocupa todo el ancho disponible aunque se vea corta: sin esto, esa
   zona transparente se queda con el clic y la flecha deja de
   funcionar como enlace. */
.hero-flecha {
  z-index: 20;

  /* Centrado sin `transform`, y esto no es una preferencia de estilo.

     El marcado la centraba con `left-1/2` más `-translate-x-1/2`. Pero
     lleva también `animate-bounce`, y esa animación redefine
     `transform` ENTERO en cada fotograma: al hacerlo borra el
     desplazamiento del 50% y deja el BORDE izquierdo de la flecha en
     el centro, no su centro. Se iba 15 px a la derecha —la mitad de
     sus 30 px de ancho— y no cuadraba con las tarjetas de abajo.

     Con `left: 0; right: 0` y márgenes automáticos, el centrado lo
     resuelve el propio flujo y la animación ya no puede tocarlo. */
  left: 0;
  right: 0;
  margin-inline: auto;
  width: max-content;
}

/* En móvil la flecha estorbaría al contenido cuando la sección
   crece. Es decorativa: ahí se oculta. */
@media (max-width: 639px) {
  .hero-seccion {
    min-height: max(560px, 92vh);
    padding-top: calc(112px + 1.75rem);
  }
  .hero-flecha { display: none; }
}

/* ============================================================
   Banda de cifras (portada)

   Antes era una rejilla con `gap-px` sobre un fondo claro para
   simular líneas divisorias. El truco fallaba: como el contenedor
   también tenía relleno lateral, ese fondo claro asomaba por los
   bordes en forma de franjas sueltas. Ahora son tarjetas con su
   propio fondo y separación real, sin depender de ese efecto.
   ============================================================ */

/* El relleno de la banda y la separación entre tarjetas van aquí y no
   en clases de Tailwind, porque `tailwind.css` es un archivo ya
   compilado: las utilidades que no estaban en uso (py-lg, gap-6,
   list-none…) sencillamente no existen en él y no harían nada. */
.cifras-banda {
  padding-top: 2.5rem;
  padding-bottom: 2.5rem;
}

@media (min-width: 768px) {
  .cifras-banda { padding-top: 3.5rem; padding-bottom: 3.5rem; }
}

.cifras-rejilla {
  list-style: none;
  margin: 0;
  padding: 0;
}

@media (min-width: 768px) {
  .cifras-rejilla { gap: 1.5rem; }
}

.cifra-tarjeta {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: .55rem;
  padding: 1.75rem 1rem;
  border-radius: 14px;
  background: rgba(255, 255, 255, .045);
  border: 1px solid rgba(255, 255, 255, .09);
  transition: transform .3s ease, background-color .3s ease, border-color .3s ease;
}

@media (min-width: 768px) {
  .cifra-tarjeta { padding: 2rem 1.25rem; }
}

.cifra-tarjeta:hover {
  transform: translateY(-4px);
  background: rgba(255, 255, 255, .075);
  border-color: rgba(var(--tertiary-fixed-dim) / .38);
}

/* Icono dentro de un disco tenue, para que no compita con la cifra */
.cifra-icono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: rgba(var(--tertiary-fixed-dim) / .12);
  color: rgb(var(--tertiary-fixed-dim));
  transition: background-color .3s ease;
}

.cifra-icono .material-symbols-outlined { font-size: 24px; }
.cifra-tarjeta:hover .cifra-icono { background: rgba(var(--tertiary-fixed-dim) / .2); }

.cifra-numero {
  color: rgb(var(--tertiary-fixed-dim));
  font-variant-numeric: tabular-nums;
}

/* Subrayado que crece de izquierda a derecha cuando la tarjeta
   entra en pantalla, acompañando al conteo. */
.cifra-linea {
  display: block;
  width: 34px;
  height: 2px;
  border-radius: 2px;
  background: rgba(var(--tertiary-fixed-dim) / .55);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .6s cubic-bezier(.22, 1, .36, 1);
  transition-delay: calc(var(--d, 0ms) + 260ms);
}

.reveal.is-visible .cifra-linea { transform: scaleX(1); }

.cifra-texto {
  color: rgba(255, 255, 255, .72);
  max-width: 18ch;
}

@media (prefers-reduced-motion: reduce) {
  .cifra-tarjeta { transition: none; }
  .cifra-tarjeta:hover { transform: none; }
  .cifra-linea { transform: scaleX(1); transition: none; }
}

/* ============================================================
   ENCUADRE DE LAS FOTOS DE PORTADA

   Las dos fotos grandes son de 1376×768 (relación 1.792) y las
   franjas donde viven son bastante más apaisadas, así que `cover`
   siempre recorta. Lo que decide dónde recortar no es el gusto:
   son las posiciones reales de los sujetos, medidas sobre los PNG.

     screen1  (portada)     avión   y 13.3%–17.6%
     screen2  (interiores)  avión   y 11.7%–31.0%
                            locomotora y  7.8%–49.3%
                            buque   y 33.6%–93.6%

   La base solo lleva la transición; cada portada afina lo suyo,
   porque las tres fotos de servicio (F1/F2/F3) siguen con el
   `bg-cover bg-center` de Tailwind y ahí está bien.
   ------------------------------------------------------------ */
.hero-imagen {
  transition: filter .35s ease;
}

/* ---- Portada: screen1 ----
   Aquí el header va FIJO y flotando por encima de la sección, así
   que no basta con que el avión entre en cuadro: si cae en los
   primeros 112 px lo tapa la navegación. Eso era exactamente lo
   que pasaba y por lo que «se veía poco».

   Se sustituye `cover` por un alto explícito —ancho automático,
   deducido de la proporción— que es el mayor de tres suelos:

     100%    cubre la franja de alto (lo que hacía `cover`)
     56vw    cubre la franja de ancho (100vw / 1.792 = 55.8vw)
     920px   el que importa: con este alto el avión, que está al
             13.3%, cae a 122 px, o sea 10 px por debajo del header.
             Sin él, a 1440×900 la foto encaja clavada y el avión
             queda a 110 px: dos píxeles por detrás de la barra.

   Anclado arriba, así el recorte se lo lleva entero el pie de la
   foto, que es asfalto. */
.hero-seccion .hero-imagen {
  background-size: auto max(100%, 56vw, 920px);
  background-position: 50% 0;
}

/* ---- Interiores: screen2 ----
   Aquí no hay solape: `main` lleva un `padding-top` de 112 px, así
   que la sección ya empieza bajo el header. El problema es otro:
   con `h-[70vh]` la franja quedaba en 3.5:1 y de una foto de 1.79:1
   solo se veía la mitad — el avión cortado, el buque sin casco y
   los camiones fuera.

   Dos cambios. El alto deja de ser una fracción de la ventana y
   pasa a ser «lo que queda bajo el header, pero nunca más de lo
   que la foto necesita» (56vw es su proporción exacta). Y el ancla
   sube al 35%, que reparte el recorte que quede dejando el avión y
   la locomotora dentro.

   En una pantalla de escritorio de verdad esto muestra la foto casi
   entera: 98% a 1440×900 y 90% a 1920×1080, con los cuatro sujetos
   completos. En una ventana de 780 px de alto se ve el 63%: ahí la
   geometría no da para más, porque bajo el header solo quedan
   668 px y la foto a ese ancho pide 1066. */
.hero-panoramica {
  height: auto;
  min-height: max(480px, min(calc(100vh - 112px), 56vw));
}

.hero-panoramica .hero-imagen {
  background-size: cover;
  background-position: 50% 31%;
}

/* ---- Embalaje ----
   Esta foto es 4:3, mucho más vertical que las demás, y en una
   franja apaisada se recorta a lo bestia. Lo que interesa —las
   estibas de cajas— vive en los dos tercios de abajo; el tercio
   superior es la estructura del techo. El ancla al 62% deja las
   cajas en cuadro y sacrifica las cerchas. */
.hero-imagen--embalaje {
  background-position: 50% 62%;
}

/* En móvil manda el alto: la foto entra entera de arriba abajo y el
   recorte pasa a ser lateral, así que se centra y punto. */
@media (max-width: 767px) {
  .hero-seccion .hero-imagen,
  .hero-panoramica .hero-imagen {
    background-size: cover;
    background-position: 50% 50%;
  }
}

.dark .hero-imagen {
  filter: brightness(.65) saturate(.95) contrast(1.03);
}

/* Velo uniforme: garantiza contraste en toda la franja, no solo a un lado */
.hero-velo {
  background: rgba(9, 14, 28, .42);
  transition: background-color .35s ease;
}

.dark .hero-velo { background: rgba(6, 9, 18, .48); }

/* Degradado lateral: refuerza la zona donde vive el texto */
.hero-degradado {
  background: linear-gradient(
    to right,
    rgba(9, 14, 28, .82) 0%,
    rgba(9, 14, 28, .5) 45%,
    rgba(9, 14, 28, 0) 85%
  );
}

.dark .hero-degradado {
  background: linear-gradient(
    to right,
    rgba(6, 9, 18, .82) 0%,
    rgba(6, 9, 18, .55) 45%,
    rgba(6, 9, 18, .06) 85%
  );
}

/* En móvil el texto ocupa todo el ancho: el degradado lateral no basta */
@media (max-width: 767px) {
  .hero-degradado {
    background: linear-gradient(to bottom, rgba(9, 14, 28, .55) 0%, rgba(9, 14, 28, .8) 100%);
  }
  .dark .hero-degradado {
    background: linear-gradient(to bottom, rgba(6, 9, 18, .6) 0%, rgba(6, 9, 18, .8) 100%);
  }
}

/* ============================================================
   6. NÚMEROS DECORATIVOS DE TARJETA (01, 02…)
   Con opacidad muy baja quedaban casi invisibles sobre fondo
   oscuro; aquí ganan cuerpo y color sin competir con el titular.
   ============================================================ */

.num-decorativo {
  opacity: .75;
  transition: opacity .3s ease, color .3s ease;
}

.dark .num-decorativo { opacity: .9; }

/* ============================================================
   7. REVELADO Y CONTADORES
   ============================================================ */

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .7s ease, transform .7s ease;
}

.reveal.is-visible { opacity: 1; transform: none; }

/* Para entradas laterales existe la convención `.entra-derecha` /
   `.entra-izquierda` (sección 25) y, para la galería de fotografías,
   la entrada cruzada de la sección 26. No se añaden aquí variantes
   nuevas: dos sistemas para lo mismo terminan pisándose. */

/* Evita el salto de ancho mientras el número va subiendo */
[data-contador] { font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .animate-pulse, .animate-bounce { animation: none !important; }
  html.transicion-tema, html.transicion-tema * { transition: none !important; }
}

/* ============================================================
   8. ACCESIBILIDAD
   ============================================================ */

:focus-visible {
  outline: 3px solid rgb(var(--primary-container));
  outline-offset: 2px;
  border-radius: 4px;
}

.dark :focus-visible { outline-color: rgb(147 177 255); }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: #003ec7;
  color: #fff;
  padding: 12px 24px;
  border-radius: 0 0 8px 0;
  font-weight: 600;
}

.skip-link:focus { left: 0; }

/* ============================================================
   9. ACORDEÓN FAQ
   ============================================================ */

details > summary { list-style: none; }
details > summary::-webkit-details-marker { display: none; }

/* ---- Apertura y cierre suaves ----

   `<details>` abre de golpe por diseño, y no hay forma fiable de
   evitarlo solo con CSS: se probó `::details-content` con
   `interpolate-size: allow-keywords`, y el primer despliegue en frío
   se queda en cero —el contenido arranca con `content-visibility:
   hidden` y `auto` no llega a resolverse—, así que la respuesta no
   aparecía. Funcionaba a partir de la segunda vez, que es peor que
   no animar.

   El deslizamiento lo hace `activarAcordeones()` en gjg.js, con el
   mismo patrón de alto medido que usa el plegable de la calculadora.
   Aquí solo queda lo que sí es cosa del CSS.

   Sin JavaScript, `<details>` sigue abriendo y cerrando por sí solo:
   se pierde la animación, no la función. */

details > summary .material-symbols-outlined {
  transition: transform .34s cubic-bezier(.22, .61, .36, 1);
}

/* Al cerrar, el atributo `open` no se retira hasta que termina el
   deslizamiento; sin esto la flecha se quedaría girada todo el rato
   y volvería de golpe al final. */
details[data-cerrando] > summary .material-symbols-outlined {
  transform: rotate(0deg);
}

@media (prefers-reduced-motion: reduce) {
  details > summary .material-symbols-outlined { transition: none; }
}

/* ---- Entrada alterna de las preguntas frecuentes ----

   Cada pregunta llega del lado contrario a la anterior: la primera
   desde la derecha, la segunda desde la izquierda, y así. Como están
   apiladas en una sola columna, el escalonado lo pone el propio
   desplazamiento del usuario —cada una se revela al entrar en
   pantalla—, sin necesidad de retardos.

   `nth-of-type` y no `nth-child`: cuenta solo los `<details>`, así
   la alternancia no se descoloca si algún día se intercala otro
   elemento entre las preguntas.

   Se anima `translate` y NO `transform`: la flecha del acordeón usa
   `transform` para girar, y el deslizamiento de apertura lo hace
   `activarAcordeones()` sobre el div interno con `height`. Ninguno
   de los dos se toca aquí. */

.faq-entra:nth-of-type(odd)  { translate:  70px 0; }   /* 1.ª, 3.ª, 5.ª… */
.faq-entra:nth-of-type(even) { translate: -70px 0; }   /* 2.ª, 4.ª, 6.ª… */

/* La transición que hereda de `.reveal` solo cubre `opacity` y
   `transform`: sin declarar `translate` el recorrido se aplicaría de
   golpe y solo se vería el fundido. */
.faq-entra {
  transition:
    opacity   .6s cubic-bezier(.22, .61, .36, 1),
    translate .6s cubic-bezier(.22, .61, .36, 1);
}

/* En reposo se anula el desplazamiento vertical de `.reveal` para
   que la entrada sea lateral limpia. Deja de aplicar en cuanto la
   pregunta entra, así no interfiere con nada posterior. */
.faq-entra:not(.is-visible) { transform: none; }

/* Esta va la última: pesa lo mismo que las `nth-of-type` de arriba y
   solo gana por el orden. Más arriba, las preguntas se quedarían
   desplazadas para siempre. */
.faq-entra.is-visible { translate: none; }

@media (max-width: 639px) {
  /* Recorrido más corto: 70 px en una tarjeta a ancho completo de
     móvil se percibe como un salto, no como una entrada. */
  .faq-entra:nth-of-type(odd)  { translate:  40px 0; }
  .faq-entra:nth-of-type(even) { translate: -40px 0; }
  .faq-entra.is-visible { translate: none; }
}

/* En reposo asoman por los dos bordes: la sección recorta para que
   eso no le añada scroll horizontal a la página. */
#faq { overflow-x: clip; }

@media (prefers-reduced-motion: reduce) {
  .faq-entra { translate: none; transition: none; }
}

/* ---- Entrada lateral suelta: `.desliza-izq` / `.desliza-der` ----

   Para bloques que no encajan en las convenciones anteriores: se
   pone junto a `.reveal` en el elemento que deba entrar de lado.
   Hoy la usan el titular y las tres tarjetas de «Cotizar un servicio
   específico» en `contacto.html`.

   La transición declara `translate` —sin ella el recorrido se
   aplicaría de golpe— y conserva `filter`, `box-shadow` y
   `transform` con su duración corta: son las que usan el atenuado de
   las hermanas (`data-enfoque`) y la elevación del hover. Si se
   dejaran fuera, ambos efectos pasarían a saltar. */

.desliza-izq { translate: -60px 0; }
.desliza-der { translate:  60px 0; }

/* Va emparejada con `.reveal` —que estos elementos siempre llevan—
   para pesar DOS clases. Con una sola pesaba igual que
   `[data-enfoque] > *`, declarada más abajo en el archivo, y aquella
   ganaba por orden: la lista se quedaba en `filter, box-shadow,
   transform` y el desplazamiento se aplicaba de golpe. Era el caso
   de las tarjetas de «Cotizar un servicio específico», donde solo se
   veía moverse el titular.

   0,85 s: el recorrido tiene que dar tiempo a leerse. Se conservan
   `filter`, `box-shadow` y `transform` con su duración corta, que
   son las del atenuado de las hermanas y la elevación del hover. */
.reveal.desliza-izq,
.reveal.desliza-der {
  transition:
    opacity    .85s cubic-bezier(.22, .61, .36, 1),
    translate  .85s cubic-bezier(.22, .61, .36, 1),
    filter     .3s ease,
    box-shadow .3s ease,
    transform  .3s ease;
}

/* En reposo se anula el desplazamiento vertical de `.reveal`, para
   que la entrada sea lateral limpia. */
.desliza-izq:not(.is-visible),
.desliza-der:not(.is-visible) { transform: none; }

/* Última a propósito: iguala en peso a las de arriba y solo gana por
   el orden. Más arriba, los bloques se quedarían desplazados. */
.desliza-izq.is-visible,
.desliza-der.is-visible { translate: none; }

/* Escalonado solo donde las hermanas entran POR EL MISMO LADO, que
   es el caso de las tres tarjetas de servicio. En las parejas que se
   abren una a cada lado no se aplica: ahí el gesto es simétrico y un
   retardo en un solo costado lo desequilibraría. */
[data-enfoque] > .desliza-izq:nth-child(2),
[data-enfoque] > .desliza-der:nth-child(2) { transition-delay: 150ms; }
[data-enfoque] > .desliza-izq:nth-child(3),
[data-enfoque] > .desliza-der:nth-child(3) { transition-delay: 300ms; }

@media (max-width: 639px) {
  .desliza-izq { translate: -36px 0; }
  .desliza-der { translate:  36px 0; }
  .desliza-izq.is-visible,
  .desliza-der.is-visible { translate: none; }
}

/* En reposo asoman por los bordes: la sección recorta para que eso
   no le añada scroll horizontal a la página. */
section:has(.desliza-izq),
section:has(.desliza-der) { overflow-x: clip; }

@media (prefers-reduced-motion: reduce) {
  .desliza-izq, .desliza-der { translate: none; transition: none; }
}

/* ---- Caja de la guía «¿Qué es un pie cúbico…?» ----

   Llevaba `bg-white/70`. La corrección de tema oscuro de la sección 4
   (`.dark .bg-white`) NO alcanza a esa variante con opacidad: Tailwind
   la compila como una clase distinta (`.bg-white\/70`). Resultado: en
   tema oscuro se quedaba blanca al 70 % con el texto claro encima
   —contraste 1,05:1, ilegible—. Aquí el fondo es un token del tema, así
   que se invierte solo.

   Si añade otra caja parecida, use esta clase y no `bg-white/…`. */

.guia-calc {
  background: rgb(var(--surface-container-lowest));
  border: 1px solid rgb(var(--outline-variant) / .6);
}

.dark .guia-calc {
  background: rgb(var(--surface-container-high));
  border-color: rgb(var(--outline-variant));
}

/* ============================================================
   10. NAVEGACIÓN
   ============================================================ */

/* ------------------------------------------------------------
   Capas dentro del header

   El header es `fixed` con z-50, así que crea su propio contexto de
   apilado y todo lo de dentro se ordena aquí, sin pelearse con el
   resto de la página.

   Estos dos valores importan: sin ellos ambos elementos quedan en
   `z-index: auto` y el navegador los pinta por ORDEN EN EL DOM. Como
   gjg.js añade la barra de progreso al final del header
   (`header.appendChild`), quedaba la última y se dibujaba por encima
   del desplegable, cortando la primera opción del submenú.
   ------------------------------------------------------------ */

/* ------------------------------------------------------------
   Barra del header: logo · navegación · acciones

   Va de lado a lado, como la del panel de estadísticas. Antes
   llevaba `max-w-7xl mx-auto`, que la encajonaba en 1280 px y
   dejaba aire muerto a los costados en pantallas anchas.

   Es una rejilla de tres columnas `1fr auto 1fr` y no un
   `justify-between`: con este último la navegación queda centrada
   solo por casualidad, porque su posición depende de que el bloque
   del logo y el de los botones midan lo mismo. No lo miden. Con la
   rejilla, la columna del medio está centrada de verdad y sigue
   estándolo aunque cambie el texto del logo o se añada un botón.

   La columna de cada hijo se declara explícitamente porque en móvil
   la navegación lleva `hidden`: al desaparecer del flujo, sin esto
   los botones se subirían a la columna del centro en lugar de
   quedarse pegados a la derecha.
   ------------------------------------------------------------ */

.header-barra {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 5rem;
}

/* La navegación se centra sobre la barra, no entre sus vecinos.

   Con una rejilla `1fr auto 1fr` el centrado es solo aparente: las
   columnas laterales reclaman el mismo ancho, pero el bloque de
   acciones mide 187 px y el del logo 56, así que la columna derecha
   se desborda y empuja la navegación 28 px a la izquierda. Medido.

   Sacándola del flujo y anclándola al 50 % de la barra, queda
   centrada de verdad sea cual sea el ancho de sus vecinos. */
.header-barra > *:nth-child(2) {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);

  /* Este z-index es obligatorio, no cosmético.

     El `transform` de arriba convierte al nav en un contexto de
     apilado propio, y eso ENCIERRA dentro de él el `z-index: 20` del
     desplegable de Servicios: deja de competir con el resto del
     header y pasa a valer solo entre los hijos del nav.

     Resultado sin esta línea: la barra de progreso, que gjg.js añade
     al final del header, se pinta por orden de DOM por encima del
     panel desplegable y lo cruza con una raya. Con el z-index aquí,
     el nav entero —desplegable incluido— queda por delante. */
  z-index: 20;
}

/* ---- Tamaño de la navegación y las acciones ----
   Va en CSS y no en clases de Tailwind porque `tailwind.css` está
   compilado: una utilidad que no estuviera ya en uso no existiría. */

.header-barra nav a {
  font-size: 20px;
}

.header-barra nav .material-symbols-outlined {
  font-size: 24px;
}

.header-barra [data-wa] {
  font-size: 20px;
  padding: 15px 27px;
}

.header-barra [data-wa] svg {
  width: 25px;
  height: 25px;
}

/* El botón de tema llevaba solo el icono suelto: sin nada alrededor
   no se percibía como botón hasta pasarle el cursor por encima. El
   recuadro usa `--outline`, que es el gris más oscuro de la paleta en
   claro y el más claro en oscuro, así que se nota en los dos temas.
   El de la hamburguesa lo comparte, para que ambos se lean igual. */
.header-barra [data-tema],
.header-barra #btn-menu {
  padding: 12px;
  border: 1px solid rgb(var(--outline));
  border-radius: 12px;
}

.header-barra [data-tema]:hover,
.header-barra #btn-menu:hover {
  border-color: rgb(var(--primary));
}

.header-barra [data-tema] .material-symbols-outlined,
.header-barra #btn-menu .material-symbols-outlined {
  font-size: 30px;
}

/* Los enlaces del desplegable acompañan al tamaño del nav, un punto
   por debajo para que la lista no compita con la barra.

   El selector lleva `.header-barra` delante porque el panel vive
   DENTRO del nav: sin eso lo pisaría la regla de arriba, que es más
   específica. */
.header-barra .nav-dropdown-panel a { font-size: 17px; }

/* ---- La franja estrecha: entre que aparece la navegación (768 px) y
   que hay sitio de sobra (1100 px) ----

   Medido a 900 px: los tres bloques sumaban 914 px de necesidad para
   900 disponibles. Con solo 14 px de déficit la rejilla comprime las
   columnas laterales y la navegación deja de estar centrada.

   Se recupera espacio de donde sobra —el relleno lateral y la
   separación entre enlaces— en vez de encoger la tipografía. */

/* ---- Hasta 900 px se mantiene el menú de hamburguesa ----

   Tailwind saca la navegación completa en 768 px (`md:flex`), pero
   ahí no cabe: al ir centrada sobre la barra, el espacio útil es el
   ancho menos DOS veces el bloque más ancho, y a 768 px quedaban
   4 px de holgura contra los botones de la derecha. Medido.

   Antes esto no se notaba porque la navegación flotaba entre sus
   vecinos y se corría a la izquierda con tal de caber. Ahora que
   está centrada de verdad, o cabe o no cabe.

   Cinco enlaces con desplegable apretados a 768 px tampoco eran
   cómodos de usar, así que el corte sube a 900. */

/* El corte está en 1040 px y no en los 768 de Tailwind porque la
   navegación va CENTRADA sobre la barra: el espacio que necesita no
   es el suyo, sino el suyo más DOS veces el bloque lateral más ancho.
   Con los enlaces a 20 px, a 1024 px se solapaba 115 px con el logo.
   Medido. */
@media (max-width: 1039px) {
  .header-barra nav[aria-label="Navegación principal"] { display: none; }
  .header-barra #btn-menu { display: inline-flex; }
}

/* Franja intermedia: cabe la navegación, pero no junto al nombre
   completo de la empresa. Se deja la insignia sola, que sigue
   enlazando al inicio.

   El umbral es 1400 y no 1280 por la misma aritmética del centrado:
   con el nombre, el bloque del logo mide 349 px, así que la barra
   necesita 349×2 + 548 de navegación + 96 de relleno = 1342 px
   mínimos. A 1280 se solapaba 35 px. Medido. */
@media (min-width: 1040px) and (max-width: 1399px) {
  .header-barra > a > span { display: none; }
}

/* Y en la parte baja de esa franja se aprieta además el aire lateral
   y la separación entre enlaces. */
@media (min-width: 1040px) and (max-width: 1279px) {
  .header-barra {
    padding-left: 24px;
    padding-right: 24px;
  }
  .header-barra nav { gap: .85rem; }
}

/* Barra de progreso de lectura, pegada al borde inferior del header.

   El z-index la manda al fondo del header a propósito: gjg.js la
   añade al final del DOM, así que sin él se pintaría por encima del
   desplegable de Servicios y lo cruzaría con una raya. */
.progreso-lectura {
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 2px;
  width: 0;
  background: linear-gradient(90deg, #0052ff 0%, #00dce5 100%);
  transition: width .1s linear;
  pointer-events: none;
  z-index: 1;
  z-index: 1;                 /* siempre por debajo de los submenús */
}

/* Subrayado que crece desde el centro al pasar el cursor.
   Los enlaces activos ya llevan su propio borde, así que se excluyen. */
.nav-enlace {
  position: relative;
  padding-bottom: 4px;
}

.nav-enlace::after {
  content: "";
  position: absolute;
  left: 50%;
  right: 50%;
  bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: rgb(var(--primary));
  transition: left .25s ease, right .25s ease;
}

.nav-enlace:hover::after,
.nav-enlace:focus-visible::after {
  left: 0;
  right: 0;
}

/* Las imágenes aparecen con una transición suave en vez de dar un salto */
.aparece {
  opacity: 0;
  transition: opacity .5s ease;
}

.aparece.cargada { opacity: 1; }

/* Los iconos de las tarjetas acompañan el hover del contenedor */
.card-hover .material-symbols-outlined,
.group:hover .icono-servicio {
  transition: transform .3s ease;
}

.card-hover:hover .material-symbols-outlined { transform: scale(1.08); }

/* El z-index va en el contenedor, no en el panel: así el desplegable
   entero forma una sola capa y se apila por encima de la barra de
   progreso (z-index 1) y de cualquier cosa que se añada al header
   más adelante. */
.nav-dropdown {
  position: relative;
  z-index: 20;
}

.nav-dropdown-panel {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  min-width: 260px;
  opacity: 0;
  visibility: hidden;
  transition: opacity .2s ease, transform .2s ease, visibility .2s;
  z-index: 20;
}

.nav-dropdown:hover .nav-dropdown-panel,
.nav-dropdown:focus-within .nav-dropdown-panel {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

#menu-movil { display: none; }
#menu-movil.abierto { display: block; }

/* ============================================================
   11. BOTÓN FLOTANTE DE WHATSAPP
   ============================================================ */

.wa-flotante {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 40;
  width: 56px;
  height: 56px;
  border-radius: 9999px;
  background: #25d366;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 24px rgba(37, 211, 102, .4);
  transition: transform .2s ease;
}

/* Entra deslizándose desde la derecha al cargar la página, con un
   respiro para que no compita con la entrada del hero.

   `backwards` y NO `forwards`: con `forwards` el último fotograma se
   queda fijado y, como fija `transform`, machacaría el `scale(1.08)`
   del hover y el botón dejaría de responder al ratón para siempre.
   Con `backwards` la animación solo manda antes y durante; al
   terminar, el elemento vuelve a sus estilos normales. No hay salto
   porque el fotograma final ya es su posición de reposo. */
@keyframes waEntra {
  from { opacity: 0; transform: translateX(80px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .wa-flotante {
    animation: waEntra .55s cubic-bezier(.22, .61, .36, 1) .45s backwards;
  }
}

.wa-flotante:hover { transform: scale(1.08); }

/* Botón «volver arriba»: aparece al bajar, encima del de WhatsApp */
.subir-arriba {
  position: fixed;
  right: 20px;
  bottom: 88px;              /* justo sobre el botón de WhatsApp */
  z-index: 40;
  width: 44px;
  height: 44px;
  border-radius: 9999px;
  background: rgb(var(--inverse-surface));
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--sombra-foco);
  opacity: 0;
  visibility: hidden;
  /* Entra desde la derecha, no desde abajo: mismo gesto que el botón
     de WhatsApp que tiene justo debajo, para que los dos se lean
     como una misma columna que aparece por el borde. */
  transform: translateX(80px);
  transition: opacity .3s ease, transform .3s cubic-bezier(.22, .61, .36, 1), visibility .3s;
}

.subir-arriba.visible {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.subir-arriba:hover { background: rgb(var(--primary-container)); }

/* Con menos movimiento, los dos flotantes aparecen sin deslizarse. */
@media (prefers-reduced-motion: reduce) {
  .subir-arriba { transform: none; transition: opacity .2s ease, visibility .2s; }
  .wa-flotante  { animation: none; }
}

/* ============================================================
   12. TIPOGRAFÍA RESPONSIVE
   ============================================================ */

@media (max-width: 767px) {
  .text-headline-xl {
    font-size: 32px !important;
    line-height: 40px !important;
    letter-spacing: -0.01em !important;
  }
  .text-headline-lg {
    font-size: 26px !important;
    line-height: 34px !important;
  }
}

/* Los titulares en mayúscula necesitan algo de aire entre letras */
h1.uppercase, h2.uppercase { letter-spacing: 0.01em; }

/* ============================================================
   14. REFINAMIENTOS VISUALES Y MOVIMIENTO (nivel producción)

   Todo el movimiento respeta prefers-reduced-motion y solo anima
   propiedades baratas (transform / opacity). Los colores y el
   contraste no cambian: es pulido, no un rediseño.
   ============================================================ */

/* ---- Selección de texto con color de marca ---- */
::selection { background: rgba(0, 82, 255, .18); }
.dark ::selection { background: rgba(0, 220, 229, .22); }

/* ---- Barra de desplazamiento fina y de marca (escritorio) ---- */
@media (pointer: fine) {
  * { scrollbar-width: thin; scrollbar-color: rgb(var(--outline-variant)) transparent; }
  *::-webkit-scrollbar { width: 11px; height: 11px; }
  *::-webkit-scrollbar-track { background: transparent; }
  *::-webkit-scrollbar-thumb {
    background: rgb(var(--outline-variant));
    border-radius: 9999px;
    border: 3px solid transparent;
    background-clip: content-box;
  }
  *::-webkit-scrollbar-thumb:hover {
    background: rgb(var(--primary-container));
    background-clip: content-box;
  }
}

/* ---- Hero: deriva lenta, sin acercamiento apreciable ----
   Antes era un «Ken Burns» de scale(1.06) a scale(1.14): ese
   acercamiento se comía hasta un 14% de la foto y dejaba el avión
   —que vive arriba a la derecha— fuera de cuadro.

   Ahora la escala es fija en 1.04 y NO crece: ese 4% existe solo
   para tener margen por donde desplazar la capa. El movimiento es
   puro translate, que la GPU compone sin repintar. Animar
   `background-position` en su lugar sería más limpio en teoría,
   pero obliga a repintar una imagen a pantalla completa en cada
   fotograma durante 26 s seguidos. */
/* Sin `transform`. Cualquier escala, por pequeña que sea, se aplica
   desde el centro y sube el contenido: con 1.04 el avión perdía
   14 px justo por donde le hacían falta. El encuadre lo fija
   `background-size` más arriba, y ahí no hay margen que regalar. */

/* ---- Hero: entrada escalonada del contenido al cargar ----

   Entra por la izquierda, no desde abajo. Es el mismo gesto que usan
   las secciones de Servicios y Rutas más adelante, así que la página
   empieza como va a continuar.

   60 px, igual que allí. El desplazamiento sobrante no asoma por el
   borde de la ventana porque la sección del hero ya lleva
   `overflow-hidden` en su marcado. */
@keyframes heroIn {
  from { opacity: 0; transform: translateX(-60px); }
  to   { opacity: 1; transform: none; }
}

.hero-item { opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  .hero-item { animation: heroIn .8s cubic-bezier(.2, .7, .2, 1) both; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-item { opacity: 1 !important; }
}

/* ---- Gradiente "kinetic" en movimiento lento (CTA / cotización) ---- */
.kinetic-gradient {
  background: linear-gradient(120deg,
              var(--kinetic-a) 0%, var(--kinetic-b) 42%,
              var(--kinetic-c) 58%, var(--kinetic-a) 100%);
  background-size: 220% 220%;
}
@media (prefers-reduced-motion: no-preference) {
  .kinetic-gradient { animation: kineticShift 16s ease infinite; }
}
@keyframes kineticShift {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

/* ---- Botones CTA: brillo que barre al pasar el cursor ---- */
.btn-sheen { position: relative; overflow: hidden; }
.btn-sheen > * { position: relative; z-index: 1; }
.btn-sheen::after {
  content: "";
  position: absolute;
  top: 0;
  left: -130%;
  width: 55%;
  height: 100%;
  background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, .28) 50%, transparent 100%);
  transform: skewX(-18deg);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .btn-sheen:hover::after { animation: sheen .85s ease; }
}
@keyframes sheen { to { left: 135%; } }

/* ---- Escalonado de rejillas: la variable --d la asigna el JS ---- */
.reveal { transition-delay: var(--d, 0ms); }

/* ---- Profundidad: acentos radiales muy sutiles por sección ---- */
.glow-seccion { position: relative; isolation: isolate; }
.glow-seccion::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(58% 45% at 88% 0%, rgba(0, 82, 255, .05), transparent 70%),
    radial-gradient(48% 38% at 2% 100%, rgba(0, 220, 229, .045), transparent 70%);
}
.dark .glow-seccion::before {
  background:
    radial-gradient(58% 45% at 88% 0%, rgba(40, 120, 255, .10), transparent 70%),
    radial-gradient(48% 38% at 2% 100%, rgba(0, 220, 229, .07), transparent 70%);
}

/* ---- Header: más nítido y opaco al desplazarse ---- */
header { transition: background-color .3s ease, box-shadow .3s ease; }
header.desplazado {
  background: rgb(var(--surface) / .95);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}

/* ---- Campos de formulario: foco suave y ligera elevación ---- */
input, select, textarea {
  transition: border-color .2s ease, box-shadow .2s ease, background-color .25s ease;
}
input:focus, select:focus, textarea:focus {
  box-shadow: 0 0 0 3px rgb(var(--primary-container) / .18);
}

/* ---- Iconos de tarjeta: acompañan el hover del contenedor ---- */
.card-hover .material-symbols-outlined { transition: transform .3s ease, color .3s ease; }
.card-hover:hover .material-symbols-outlined { transform: translateY(-2px) scale(1.06); }

/* ---- Enlaces de servicio: la flecha avanza con el hover ---- */
.group:hover .material-symbols-outlined { transition: transform .3s ease; }

/* ============================================================
   15. IDENTIDAD: LOGO, METALES Y PATRÓN TECNOLÓGICO
   ============================================================ */

/* El logo (Logo2) tiene fondo blanco: se recorta en círculo para
   ocultar las esquinas y verse limpio sobre cualquier fondo. */
.logo-gjg {
  border-radius: 9999px;
  background: #ffffff;
}

/* Títulos metálicos sobre franjas oliva u oscuras */
.txt-oro { color: rgb(var(--oro)); }
.dark .txt-oro { color: rgb(var(--oro-claro)); }
.txt-plata { color: rgb(var(--plata)); }

.metal-oro {
  background: linear-gradient(180deg, #f4dd8a 0%, #c9a227 45%, #8a6d13 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.metal-plata {
  background: linear-gradient(180deg, #ffffff 0%, #c7ccd1 45%, #8a9096 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Franja de acento verde oliva reutilizable (para secciones y CTA) */
.banda-oliva {
  background: linear-gradient(120deg, #3f5a0e 0%, #5c7d16 48%, #6f9412 100%);
}

/* ---- Patrón de red tecnológica (puntos + líneas de conexión) ----
   Marca de agua muy sutil que da sensación de conectividad global.
   Se coloca detrás del contenido con un pseudo-elemento. */
.patron-red { position: relative; isolation: isolate; }
.patron-red::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url('../img/patron-red.svg');
  background-size: 620px 620px;
  opacity: .5;
}
.dark .patron-red::before { opacity: .35; filter: brightness(1.4); }

/* Sobre fondos oscuros (hero, footer) el patrón va en claro */
.patron-red-claro::before {
  background-image: url('../img/patron-red-claro.svg');
  opacity: .35;
}

/* ============================================================
   16. CINTILLO PROMOCIONAL (HERO TICKER)
   ============================================================ */

.cintillo {
  background: rgb(var(--inverse-surface));
  border-bottom: 1px solid rgb(255 255 255 / .08);
  overflow: hidden;
}

.cintillo__pista {
  display: inline-flex;
  align-items: center;
  gap: 3rem;
  white-space: nowrap;
  padding: 6px 0;
  will-change: transform;
}

.cintillo__item {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  color: rgb(var(--secondary-fixed-dim));
}
.cintillo__item .material-symbols-outlined { font-size: 16px; color: rgb(var(--oro-claro)); }

@media (prefers-reduced-motion: no-preference) {
  .cintillo__pista { animation: cintilloScroll 38s linear infinite; }
  .cintillo:hover .cintillo__pista { animation-play-state: paused; }
}
@keyframes cintilloScroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Sin animación: se muestra centrado y puede envolver */
@media (prefers-reduced-motion: reduce) {
  .cintillo__pista { animation: none; white-space: normal; justify-content: center; text-align: center; }
}

/* ============================================================
   17. TARJETAS DE SERVICIO (marítimo · aéreo · terrestre)

   La interacción vive aquí y no en utilidades de Tailwind porque
   `tailwind.css` es un archivo ya compilado: una clase que no
   estuviera en uso al compilar sencillamente no existe en él.

   El acento de cada tarjeta se declara UNA vez en --acento y de
   ahí lo toman el título y su subrayado. Como apunta a las
   variables del tema, el modo oscuro se resuelve solo: en claro
   son los tonos profundos de marca sobre blanco, y en oscuro las
   variantes aclaradas sobre la superficie elevada.
   ============================================================ */

.tarjeta-servicio               { --acento: var(--primary); }
.tarjeta-servicio--maritimo     { --acento: var(--primary); }    /* azul  */
.tarjeta-servicio--aereo        { --acento: var(--secondary); }  /* oliva */
.tarjeta-servicio--terrestre    { --acento: var(--tertiary); }   /* teal  */

/* ---- Imagen: zoom lento y contenido por el overflow del marco ----
   La transición incluye también `opacity` porque estas imágenes
   llevan la clase .aparece (el fundido de carga que aplica gjg.js):
   al declarar `transition` aquí se pisaría la suya y la aparición
   sería un salto seco. */
.tarjeta-servicio__imagen {
  transform: scale(1);
  transition: transform .45s cubic-bezier(.22, .61, .36, 1), opacity .5s ease;
  will-change: transform;
}

.tarjeta-servicio:hover .tarjeta-servicio__imagen,
.tarjeta-servicio:focus-visible .tarjeta-servicio__imagen {
  transform: scale(1.06);
}

/* ---- Título: cambia al acento y estrena subrayado ----
   `align-self` es lo que ajusta la caja al ancho del texto: el
   título es un elemento flex de la columna de la tarjeta, así que
   un `display:inline-block` se convertiría en bloque y el
   subrayado abarcaría toda la columna. */
.tarjeta-servicio__titulo {
  position: relative;
  align-self: flex-start;
  color: rgb(var(--on-surface));
  transition: color .3s ease;
}

/* Crece de izquierda a derecha, igual que el subrayado del nav */
.tarjeta-servicio__titulo::after {
  content: "";
  position: absolute;
  left: 0;
  right: 100%;
  bottom: -3px;               /* separación del texto, tipo text-underline-offset */
  height: 2px;
  border-radius: 2px;
  background: rgb(var(--acento));
  transition: right .4s cubic-bezier(.22, .61, .36, 1);
}

.tarjeta-servicio:hover .tarjeta-servicio__titulo,
.tarjeta-servicio:focus-visible .tarjeta-servicio__titulo {
  color: rgb(var(--acento));
}

.tarjeta-servicio:hover .tarjeta-servicio__titulo::after,
.tarjeta-servicio:focus-visible .tarjeta-servicio__titulo::after {
  right: 0;
}

/* Sin movimiento: el zoom se retira por completo, pero el color y el
   subrayado se mantienen —son la señal de que la tarjeta está
   activa, no adorno—; solo dejan de animarse. */
@media (prefers-reduced-motion: reduce) {
  .tarjeta-servicio__imagen,
  .tarjeta-servicio__titulo,
  .tarjeta-servicio__titulo::after { transition: none; }
  .tarjeta-servicio:hover .tarjeta-servicio__imagen,
  .tarjeta-servicio:focus-visible .tarjeta-servicio__imagen { transform: none; }
}

/* ============================================================
   18. CARRUSEL «¿POR QUÉ TRABAJAR CON GJG?»

   La pista es un contenedor con scroll horizontal y scroll-snap:
   el arrastre táctil, la rueda y el teclado funcionan solos, sin
   JavaScript. El JS únicamente añade el autoavance, las flechas y
   los puntos. Si el JS fallara, la sección seguiría siendo una
   lista desplazable perfectamente usable.

   Cuántas tarjetas se ven a la vez lo decide solo el CSS; el JS lo
   deduce midiendo. Para cambiarlo, toque únicamente los
   `flex-basis` de abajo.
   ============================================================ */

.pq-carrusel {
  --pq-hueco: 24px;              /* mismo aire que gap-gutter */
  position: relative;
}

.pq-carrusel__pista {
  display: flex;
  gap: var(--pq-hueco);
  list-style: none;
  margin: 0;
  /* El relleno vertical deja sitio a la elevación de .card-hover y a
     la sombra: sin él, `overflow-x` recorta ambas por arriba y abajo
     (al activar el desbordamiento en un eje, el otro deja de ser
     visible y también recorta). */
  padding: 6px 0 22px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;         /* la navegación son los puntos */
  -ms-overflow-style: none;
}

/* Gana a la barra de marca global de la sección 14 por especificidad */
.pq-carrusel__pista::-webkit-scrollbar { display: none; }

.pq-tarjeta {
  flex: 0 0 100%;                /* móvil: una tarjeta por vista */
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
}

@media (min-width: 640px) {
  .pq-tarjeta { flex-basis: calc((100% - var(--pq-hueco)) / 2); }
}
@media (min-width: 1024px) {
  .pq-tarjeta { flex-basis: calc((100% - 2 * var(--pq-hueco)) / 3); }
}
@media (min-width: 1280px) {
  .pq-tarjeta { flex-basis: calc((100% - 3 * var(--pq-hueco)) / 4); }
}

/* ---- Controles: flechas y puntos, centrados bajo la pista ---- */

.pq-carrusel__controles {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.pq-flecha {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 9999px;
  /* surface-container-lowest es blanco en claro y casi negro en
     oscuro, así que el círculo se despega del fondo en ambos temas */
  background: rgb(var(--surface-container-lowest));
  color: rgb(var(--primary));
  border: 1px solid rgb(var(--outline-variant) / .7);
  box-shadow: var(--sombra-ambiente);
  cursor: pointer;
  transition: background-color .25s ease, border-color .25s ease,
              color .25s ease, transform .2s ease;
}

/* primary-container (#0052FF) no se redefine en tema oscuro: es el
   mismo azul vivo en ambos, y el blanco encima cumple AA. Usar
   `primary` aquí rompería el contraste en oscuro, porque ahí se
   aclara para servir de texto. */
.pq-flecha:hover {
  background: rgb(var(--primary-container));
  border-color: rgb(var(--primary-container));
  color: #fff;
  transform: scale(1.06);
}

.pq-flecha:active { transform: scale(.95); }
.pq-flecha .material-symbols-outlined { font-size: 22px; }

.pq-carrusel__puntos {
  display: flex;
  align-items: center;
  gap: 2px;
}

/* El botón mide 24 px aunque el punto sea de 8: es el área táctil.
   `.svc-punto` (rejilla de servicios en móvil, sección 19) comparte
   exactamente el mismo aspecto, así que comparte también las reglas. */
.pq-punto,
.svc-punto {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  padding: 0 4px;
  background: none;
  border: 0;
  cursor: pointer;
}

.pq-punto::after,
.svc-punto::after {
  content: "";
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 9999px;
  /* on-surface se invierte con el tema: el punto apagado siempre
     contrasta con el fondo, sea claro u oscuro.

     El alfa .5 no es decorativo: es el mínimo que alcanza el 3:1 que
     pide la WCAG 1.4.11 para elementos de interfaz en AMBOS temas
     (3.24 en claro, 4.40 en oscuro). Con .25 se quedaba en 1.70 y los
     puntos inactivos casi no se veían sobre el fondo claro. */
  background: rgb(var(--on-surface) / .5);
  transition: width .35s cubic-bezier(.22, .61, .36, 1), background-color .3s ease;
}

.pq-punto:hover::after,
.svc-punto:hover::after { background: rgb(var(--on-surface) / .75); }

/* El punto activo se estira en píldora */
.pq-punto[aria-current="true"]::after,
.svc-punto[aria-current="true"]::after {
  width: 26px;
  background: rgb(var(--primary));
}

@media (prefers-reduced-motion: reduce) {
  .pq-carrusel__pista, .svc-rejilla { scroll-behavior: auto; }
  .pq-flecha, .pq-punto::after, .svc-punto::after { transition: none; }
  .pq-flecha:hover, .pq-flecha:active { transform: none; }
}

/* ============================================================
   19. REJILLA DE SERVICIOS: cuadrícula en escritorio,
       carrusel deslizable en móvil

   Es el MISMO marcado en los dos casos: cambia solo el `display`.
   En móvil es una pista con scroll y scroll-snap (el dedo desliza
   sin JavaScript); a partir de 768 px pasa a cuadrícula de tres y
   el scroll deja de existir. Los puntos se ocultan ahí, porque ya
   no hay nada que navegar.
   ============================================================ */

.svc-rejilla {
  display: flex;
  gap: 24px;                  /* mismo aire que gap-gutter */
  list-style: none;
  margin: 0;
  /* Sitio para la sombra y la elevación: al activar el desbordamiento
     en un eje, el otro también recorta */
  padding: 6px 0 18px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.svc-rejilla::-webkit-scrollbar { display: none; }

.svc-rejilla > * {
  flex: 0 0 100%;             /* móvil: una tarjeta por vista */
  scroll-snap-align: start;
}

.svc-puntos {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
}

@media (min-width: 768px) {
  .svc-rejilla {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    overflow: visible;         /* que la sombra no se recorte */
    padding: 0;
  }
  /* En cuadrícula las propiedades de flex no pintan nada, pero se
     anulan para que no queden como ruido heredado */
  .svc-rejilla > * { flex: initial; }
  .svc-puntos { display: none; }
}

/* ---- Variante que pasa a cuadrícula más tarde ----

   Por defecto el corte está en 768 px. Los tres bloques de negocio
   llevan párrafo, lista y enlace, y ahí tres columnas quedarían de
   unos 230 px: estrechísimas. Esta variante mantiene la pista
   deslizable hasta 1024 px, que es donde el diseño original ya ponía
   su `lg:grid-cols-3`. Se marca en el CONTENEDOR, no en la pista,
   porque también hay que devolver los puntos a la vista. */

@media (min-width: 768px) and (max-width: 1023.98px) {
  .svc-ancho .svc-rejilla {
    display: flex;
    overflow-x: auto;
    padding: 6px 0 18px;
  }
  .svc-ancho .svc-rejilla > * { flex: 0 0 100%; }
  .svc-ancho .svc-puntos { display: flex; }
}

/* ---- Enfoque: al señalar una tarjeta, las otras se atenúan ----

   Se limita a dispositivos con cursor real: en una pantalla táctil
   el «hover» se queda pegado tras el toque y dejaría dos tarjetas
   apagadas hasta que el usuario tocara en otro sitio. */

/* `[data-enfoque]` extiende este mismo efecto a cualquier rejilla sin
   duplicar reglas: comparte nivel de atenuado, curva y duración, así
   que el comportamiento es idéntico en todo el sitio. Hoy lo usan las
   dos rejillas de `contacto.html` (canales de contacto y «Cotizar un
   servicio específico»). Basta con añadir el atributo al contenedor
   cuyos HIJOS DIRECTOS son las tarjetas. */
.svc-rejilla,
[data-enfoque] { --svc-atenuado: .78; }

.dark .svc-rejilla,
.dark [data-enfoque] { --svc-atenuado: .62; }

@media (hover: hover) and (min-width: 768px) {
  /* Se apunta a los hijos directos, sin nombrar clase: así vale igual
     para las tarjetas de servicio (que son <a>) y para los bloques de
     negocio (que son <article>).

     `:has()` y no `.svc-rejilla:hover` a secas: con eso último, dejar
     el cursor en el hueco ENTRE dos tarjetas —o en el relleno de la
     pista— atenuaría las tres a la vez, que parece un fallo. Así solo
     se apagan las otras cuando de verdad hay una tarjeta señalada.
     Donde `:has()` no exista, sencillamente no hay atenuado. */
  .svc-rejilla:has(> :hover) > *:not(:hover),
  [data-enfoque]:has(> :hover) > *:not(:hover) {
    filter: brightness(var(--svc-atenuado)) saturate(.9);
  }
}

/* La transición se declara aquí y no con `transition-all` de Tailwind
   porque aquella dura 150 ms: el atenuado necesita los 300 ms para no
   parecer un parpadeo.

   Van los tres juntos a propósito: `box-shadow` es lo que anima
   `.focus-shadow:hover` y `transform` lo que eleva `.card-hover`. Esta
   regla está más abajo en el archivo y pisaría sus transiciones si se
   dejara alguna fuera. */
.svc-rejilla > *,
[data-enfoque] > * {
  transition: filter .3s ease, box-shadow .3s ease, transform .3s ease;
}

@media (prefers-reduced-motion: reduce) {
  .svc-rejilla > *,
  [data-enfoque] > * { transition: none; }
}

/* ---- Resalte del título de la tarjeta señalada ----

   Compañero del atenuado: mientras las demás se apagan, la señalada
   tiñe su título con el acento de la tarjeta y estrena un subrayado
   que crece de izquierda a derecha. Es el mismo lenguaje visual que
   `.tarjeta-servicio`, pero para tarjetas sin foto.

   Lo usan los cuatro criterios de «Cómo trabajamos» en `nosotros.html`.
   Para aplicarlo en otra tarjeta: la clase `.tarjeta-acento` (más el
   modificador de color si no es azul) en la tarjeta, y
   `.tarjeta-acento__titulo` en su encabezado. */

.tarjeta-acento          { --acento: var(--primary); }    /* azul  */
.tarjeta-acento--oliva   { --acento: var(--secondary); }  /* oliva */
.tarjeta-acento--teal    { --acento: var(--tertiary); }   /* teal  */

.tarjeta-acento__titulo {
  position: relative;
  /* Sin esto el título sería un bloque a todo el ancho y el subrayado
     cruzaría la tarjeta entera en lugar de ceñirse al texto. */
  width: fit-content;
  color: rgb(var(--on-surface));
  transition: color .3s ease;
}

.tarjeta-acento__titulo::after {
  content: "";
  position: absolute;
  left: 0;
  right: 100%;                /* se anima `right`, no `transform`: así
                                 funciona igual en un elemento de bloque */
  bottom: -3px;
  height: 2px;
  border-radius: 2px;
  background: rgb(var(--acento));
  transition: right .4s cubic-bezier(.22, .61, .36, 1);
}

/* Solo con cursor real: en táctil el «hover» se queda pegado tras el
   toque y el título quedaría resaltado hasta tocar en otro sitio. */
@media (hover: hover) {
  .tarjeta-acento:hover .tarjeta-acento__titulo { color: rgb(var(--acento)); }
  .tarjeta-acento:hover .tarjeta-acento__titulo::after { right: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .tarjeta-acento__titulo,
  .tarjeta-acento__titulo::after { transition: none; }
}

/* ---- Botón de envío mientras se abre WhatsApp ---- */

.btn-cargando {
  opacity: .85;
  cursor: progress;
}

.btn-girador {
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  margin-right: .55em;
  vertical-align: -.15em;
  border: 2px solid rgba(255, 255, 255, .35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: giro .7s linear infinite;
}

@keyframes giro { to { transform: rotate(360deg); } }

/* Sin movimiento el girador se queda quieto: sigue indicando espera
   por su forma, sin girar. */
@media (prefers-reduced-motion: reduce) {
  .btn-girador { animation: none; }
}

/* ============================================================
   19b. GALERÍA DE OPERACIONES (prueba social)

   Fotografías y video de despachos reales. El video ocupa dos
   columnas donde hay sitio, porque es la pieza que más convierte.

   Las imágenes van todas a la misma proporción con `object-fit`
   para que la rejilla no se descuadre: el material viene de
   teléfonos, en horizontal y en vertical.
   ============================================================ */

/* ------------------------------------------------------------
   El material llega en TRES proporciones distintas: el video de
   contenedor es 16:9, los dos grabados con teléfono son 9:16 y las
   fotos son 4:3. Mezclarlos en una sola rejilla se ve desordenado
   por mucho que se ajusten los huecos.

   La solución es agrupar por forma: cada banda es uniforme por
   dentro, y entre bandas el cambio se lee como intencional.

       1. Video horizontal, destacado y centrado
       2. Fila de videos verticales, tamaño teléfono
       3. Rejilla de fotografías

   Si añade material nuevo, colóquelo en la banda que le corresponda
   por proporción, no al final de la última.
   ------------------------------------------------------------ */

.ops-banda + .ops-banda { margin-top: 48px; }

.ops-banda__titulo {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin-bottom: 20px;
  color: rgb(var(--on-surface-variant));
}

/* Línea que prolonga el título hasta el borde: separa las bandas sin
   meter otro bloque de color */
.ops-banda__titulo::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: rgb(var(--outline-variant) / .7);
}

.ops-banda figure { margin: 0; }

.ops-banda figcaption { padding: 12px 16px 16px; }

.ops-marco {
  display: block;
  width: 100%;
  object-fit: cover;
  background: rgb(var(--surface-container-high));
}

/* ---- Botón de reproducción centrado ----

   En reposo el video no muestra la barra de controles, solo este
   botón sobre el fotograma. Al pulsarlo arranca y aparecen los
   controles nativos (volumen, pantalla completa y línea de tiempo).

   El envoltorio y el botón los crea `gjg.js`: en el HTML los videos
   conservan el atributo `controls`, de modo que sin JavaScript
   siguen siendo reproducibles con la barra de siempre. */

.video-caja {
  position: relative;
  display: block;
  line-height: 0;               /* evita el hueco bajo el video */
}

/* El velo es muy leve: no está para oscurecer el fotograma, sino
   para que el triángulo blanco no se pierda sobre un vídeo claro.
   El grueso del contraste lo pone la sombra del propio triángulo. */
.video-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: rgba(6, 12, 28, .16);
  cursor: pointer;
  transition: background-color .25s ease, opacity .25s ease;
}

.video-play:hover { background: rgba(6, 12, 28, .3); }

.video-play:focus-visible {
  outline: 3px solid rgb(var(--tertiary-fixed-dim));
  outline-offset: -3px;
}

/* Triángulo suelto, sin disco alrededor —el patrón de Instagram y
   TikTok—. El disco blanco con el triángulo azul se leía como un
   botón de interfaz pegado encima; así el icono flota sobre el
   fotograma y no compite con él.

   Las esquinas van redondeadas con `stroke-linejoin: round` sobre el
   mismo relleno: es más limpio que dibujar las curvas a mano en el
   `path`, y el grosor del trazo controla cuánto se redondean. */
.video-play__disco {
  display: block;
  width: 76px;
  height: 76px;
  transition: transform .25s ease, opacity .25s ease;
  opacity: .94;
}

.video-play:hover .video-play__disco {
  transform: scale(1.09);
  opacity: 1;
}

.video-play:active .video-play__disco { transform: scale(.97); }

.video-play__disco svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: #fff;
  stroke: #fff;
  stroke-width: 9;
  stroke-linejoin: round;
  /* Doble sombra: una cerrada que despega el icono del fotograma y
     otra abierta que lo sostiene sobre fondos claros. Sin ellas, un
     triángulo blanco sobre una pared blanca desaparece. */
  filter:
    drop-shadow(0 1px 2px rgba(0, 0, 0, .45))
    drop-shadow(0 4px 18px rgba(0, 0, 0, .5));
}

/* Al arrancar, el botón se retira */
.video-play[hidden] { display: none; }

/* El disco gris del navegador, fuera.
   Es el que aparecía sobre el video en pausa una vez encendida la
   barra nativa, y se comía el botón de marca: dos iconos de play
   distintos para lo mismo, uno de ellos feo. Se quita solo el disco
   central; la barra de reproducción sigue intacta, que es lo que
   permite avanzar, pausar y subir el volumen.

   `-webkit-` cubre Chrome, Edge, Opera y Safari. Firefox no expone
   su shadow DOM, así que allí el disco nativo sigue saliendo: es el
   único navegador donde no se puede tocar, y tampoco rompe nada. */
.ops-marco::-webkit-media-controls-overlay-play-button,
.ops-marco::-webkit-media-controls-start-playback-button {
  display: none !important;
  -webkit-appearance: none;
}

@media (max-width: 639px) {
  .video-play__disco { width: 62px; height: 62px; }
}

@media (prefers-reduced-motion: reduce) {
  .video-play, .video-play__disco { transition: none; }
  .video-play:hover .video-play__disco { transform: none; }
}

/* ---- 1. Video horizontal ----
   Se limita el ancho: a pantalla completa un 16:9 se come toda la
   vista y empuja el resto de la sección fuera de pantalla. */
/* El selector lleva `.ops-banda` delante a propósito: el reset
   `.ops-banda figure { margin: 0 }` de arriba tiene más especificidad
   que `.ops-destacado` a secas y se comía el centrado. */
.ops-banda .ops-destacado {
  max-width: 760px;
  margin-inline: auto;
}

.ops-destacado .ops-marco { aspect-ratio: 16 / 9; }

/* ---- 2. Videos verticales ----
   `auto-fit` con un máximo de 280 px: quedan del tamaño de un
   teléfono, que es como se grabaron. Sin ese tope, en escritorio
   medirían 400 px de ancho por 710 de alto. */
.ops-verticales {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 280px));
  justify-content: center;
  gap: 24px;
}

.ops-verticales .ops-marco { aspect-ratio: 9 / 16; }

/* ---- Entrada de los tres videos verticales ----

   Cada uno llega de un lado distinto: el de la izquierda desde la
   izquierda, el de la derecha desde la derecha y el del centro
   subiendo desde abajo. Los tres convergen a la vez en su sitio.

   Solo desde 1024 px, que es donde los tres caben en una fila. Por
   debajo se apilan y «izquierda» y «derecha» dejarían de significar
   nada: el gesto contaría algo distinto de lo que se ve.

   Se anima `translate`, la propiedad suelta, y no `transform`: esa
   la usan los botones de reproducción para su escalado, y ocuparla
   aquí anularía aquel efecto. Misma duración y curva que la galería
   de fotografías, para que el apartado tenga un solo lenguaje. */

.ops-verticales--anim.reveal {
  opacity: 1;
  transform: none;
  transition: none;
}

@media (min-width: 1024px) {
  .ops-verticales--anim > * {
    opacity: 0;
    transition:
      opacity   .78s cubic-bezier(.22, .61, .36, 1),
      translate .78s cubic-bezier(.22, .61, .36, 1);
  }

  .ops-verticales--anim > *:nth-child(1) { translate: -70px 0; }
  .ops-verticales--anim > *:nth-child(2) { translate: 0 60px; }   /* sube */
  .ops-verticales--anim > *:nth-child(3) { translate:  70px 0; }

  /* Con la clase de estado pegada al disparador, este selector iguala
     en peso a los `nth-child` de arriba y gana por ir después. Si
     fuera más débil, los videos se quedarían desplazados para
     siempre. */
  .ops-verticales--anim.is-visible > * {
    opacity: 1;
    translate: none;
  }

  /* En reposo asoman 70 px por los dos bordes: la sección recorta
     para que eso no le añada scroll horizontal a la página. */
  #referencias { overflow-x: clip; }
}

@media (prefers-reduced-motion: reduce) {
  .ops-verticales--anim > * {
    opacity: 1;
    translate: none;
    transition: none;
  }
}

/* ---- 3. Fotografías ---- */
.ops-fotos {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}

@media (min-width: 640px) {
  .ops-fotos { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .ops-fotos { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.ops-fotos .ops-marco { aspect-ratio: 4 / 3; }

/* ============================================================
   20. PLEGABLES (revelado progresivo)

   Componente genérico: un disparador y un panel que se despliega.
   Lo usa la calculadora de volumen dentro de los formularios de
   cotización, para que quien solo quiere una consulta rápida no se
   encuentre de entrada con el módulo de medidas y sus condiciones.

   El deslizamiento se hace con `max-height` calculado en JS a
   partir del alto real del contenido: `height: auto` no se puede
   animar. Al terminar de abrir, el JS pone `max-height: none` para
   que el panel siga creciendo si el usuario agrega bultos.

   Cerrado, el panel lleva el atributo `hidden`: así su contenido
   sale del recorrido del tabulador y los lectores de pantalla no lo
   anuncian. Sin eso se podría escribir a ciegas en campos que no
   se ven.
   ============================================================ */

.plegable__disparador {
  display: flex;
  align-items: center;
  gap: .65rem;
  width: 100%;
  padding: .9rem 1rem;
  border: 1px solid rgb(var(--outline-variant) / .6);
  border-radius: 10px;
  background: rgb(var(--surface-container-low));
  color: rgb(var(--on-surface));
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color .2s ease, background-color .2s ease, box-shadow .2s ease;
}

.plegable__disparador:hover {
  border-color: rgb(var(--primary) / .55);
  background: rgb(var(--surface-container-lowest));
}

.plegable__disparador:focus-visible {
  outline: none;
  border-color: rgb(var(--primary));
  box-shadow: 0 0 0 3px rgb(var(--primary) / .25);
}

.plegable__icono {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  border-radius: 8px;
  background: rgb(var(--primary) / .1);
  color: rgb(var(--primary));
}

.plegable__icono .material-symbols-outlined { font-size: 20px; }

/* `min-width: 0` sin suelo dejaba que el bloque de texto se encogiera
   hasta 0 px en pantallas estrechas: el título se partía letra a letra
   y el disparador crecía hasta 300 px de alto. El suelo obliga a que
   sean la marca y la ayuda las que se retiren, no el título. */
.plegable__texto { flex: 1 1 auto; min-width: 7rem; }

.plegable__titulo {
  display: block;
  font-weight: 600;
  line-height: 1.3;
}

.plegable__ayuda {
  display: block;
  margin-top: 2px;
  color: rgb(var(--on-surface-variant));
}

.plegable__marca {
  flex: 0 0 auto;
  padding: .2rem .6rem;
  border-radius: 999px;
  background: rgb(var(--outline-variant) / .35);
  color: rgb(var(--on-surface-variant));
  white-space: nowrap;
}

.plegable__flecha {
  flex: 0 0 auto;
  color: rgb(var(--outline));
  transition: transform .3s ease;
}

.plegable__disparador[aria-expanded="true"] { border-color: rgb(var(--primary) / .45); }
.plegable__disparador[aria-expanded="true"] .plegable__flecha { transform: rotate(180deg); }

.plegable__panel {
  overflow: hidden;
  max-height: 0;
  /* El margen superior también se anima: si fuera fijo, al cerrar
     quedaría un hueco flotando bajo el disparador. */
  margin-top: 0;
  transition: max-height .38s cubic-bezier(.22, 1, .36, 1),
              margin-top .38s cubic-bezier(.22, 1, .36, 1);
}

.plegable__panel[hidden] { display: none; }
.plegable__panel--abierto { margin-top: .75rem; }

/* En «Opcional» la marca se ve mejor con el acento de marca */
.plegable__marca--opcional {
  background: rgb(var(--primary) / .1);
  color: rgb(var(--primary));
}

/* En móvil el disparador se queda con lo esencial: icono, pregunta y
   flecha. La marca «Opcional» y la línea de ayuda se retiran porque
   con menos de 300 px útiles obligaban al título a partirse. La
   pregunta ya deja claro que es opcional. */
@media (max-width: 639px) {
  .plegable__disparador { padding: .8rem .85rem; gap: .55rem; }
  .plegable__icono { width: 30px; height: 30px; }
  .plegable__icono .material-symbols-outlined { font-size: 18px; }
  .plegable__marca { display: none; }
  .plegable__ayuda { display: none; }
}

/* La tarjeta del formulario reservaba 48 px de relleno a cada lado en
   todos los tamaños: en un teléfono de 320 px dejaba 182 px útiles y
   cualquier composición horizontal se rompía. */
@media (max-width: 639px) {
  .cotiza-panel { padding: 1.25rem; }
}

@media (prefers-reduced-motion: reduce) {
  .plegable__panel { transition: none; }
  .plegable__flecha { transition: none; }
}

/* Al imprimir no hay nada que plegar: se muestra todo. */
@media print {
  .plegable__panel { max-height: none !important; margin-top: .75rem !important; }
  .plegable__panel[hidden] { display: block !important; }
  .plegable__disparador { display: none !important; }
}

/* ============================================================
   21. IMPRESIÓN
   ============================================================ */

@media print {
  header, footer, .wa-flotante, .subir-arriba, .skip-link, .cintillo { display: none !important; }
  .hero-imagen { animation: none !important; }
  .patron-red::before, .patron-red-claro::before { display: none !important; }

  /* Los carruseles se despliegan: en papel no hay con qué
     desplazarlos, así que las tarjetas se apilan y los controles
     y los puntos sobran. */
  .pq-carrusel__controles, .svc-puntos { display: none !important; }
  .pq-carrusel__pista, .svc-rejilla {
    display: block !important;
    overflow: visible !important;
    padding: 0 !important;
  }
  .pq-tarjeta, .svc-rejilla > * { break-inside: avoid; margin-bottom: 12px; }

  /* Nada de tarjetas apagadas en papel */
  .svc-rejilla > .tarjeta-servicio { filter: none !important; }
}

/* ============================================================
   22. PIE DE PÁGINA Y CABECERA — TAMAÑO Y CLARIDAD

   Dos problemas distintos con el mismo síntoma: texto apagado.

   1. El pie se pintaba con `text-outline-variant`, un token que SÍ
      cambia con el tema mientras que su fondo (`inverse-surface`)
      NO: es oscuro en claro y en oscuro. El resultado era un gris
      azulado a medio camino, ni blanco ni gris de verdad, y encima
      distinto según el tema. Aquí el color se fija de una vez, sin
      depender del tema, porque el fondo tampoco depende de él.

   2. Los cuerpos iban a 12 px. Es el tamaño de una nota al pie, no
      el de la navegación secundaria de un sitio.

   Todo va por selector de elemento `footer`, así que alcanza a las
   diez páginas que lo llevan sin tocar el HTML de ninguna.
   ------------------------------------------------------------ */

/* El color no se toca aquí: se fija arriba, en la sección 4, junto al
   resto de correcciones de `inverse-surface`. Duplicarlo en dos
   sitios es la forma segura de que dentro de tres meses uno de los
   dos se quede atrás.

   El hover ya no puede distinguirse solo por color —de casi blanco a
   blanco no se nota—, así que lo marca el subrayado. */
footer a.text-outline-variant:hover,
footer ul.text-outline-variant a:hover {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---- Tamaños ----
   Escalón de ~25 %, el mismo que se aplicó a la cabecera. */
footer .text-label-sm { font-size: 15px; }
footer h3.text-label-md { font-size: 16px; }
footer p.text-body-md { font-size: 17px; line-height: 1.6; }
footer .text-headline-md { font-size: 28px; }

/* Los iconos acompañan al texto: a 18 px junto a una línea de 15
   quedaban pequeños y desalineados. */
footer .material-symbols-outlined { font-size: 20px; }

/* Los tres discos de contacto y el logotipo, en proporción. */
footer .w-10.h-10 { width: 44px; height: 44px; }
footer .w-10.h-10 svg { width: 22px; height: 22px; }
footer .logo-gjg { width: 56px; height: 56px; }

/* Con la línea más grande, los enlaces necesitan algo más de aire.
   La clase repetida no es un descuido: `space-y-3` de Tailwind pesa
   tres clases por sus dos `:not([hidden])`, y hay que igualarla para
   poder ganarle sin recurrir a `!important`. */
footer .space-y-3.space-y-3.space-y-3 > li + li { margin-top: 14px; }

/* ---- Cabecera en tema oscuro ----
   `on-surface-variant` es un gris con un punto de azul pensado para
   texto secundario sobre fondo claro. Sobre el fondo oscuro se lee
   apagado justo donde hace falta lo contrario: la navegación.

   Se sube solo dentro de la cabecera. El token se usa en todo el
   sitio y subirlo en bloque aplanaría la jerarquía de las tarjetas,
   donde ese gris sí está haciendo su trabajo. */
.dark header .text-on-surface-variant {
  color: #fff;
}

/* El selector de arriba también alcanzaba al `hover:text-primary` de
   Tailwind y le quitaba el azul al pasar por encima. Se devuelve. */
.dark header a.text-on-surface-variant:hover {
  color: rgb(var(--primary));
}

/* ============================================================
   23. MARCA DE LA CABECERA

   Misma tipografía que la barra del panel de estadísticas: Oswald
   en versalitas, condensada y con el interletrado abierto. Es lo que
   le da el aire de cabecera de panel, y de paso el nombre completo
   —«Inversiones Multimodal GJG»— cabe donde antes solo entraba
   «Inversiones GJG», porque Oswald es bastante más estrecha que
   Geist a igualdad de tamaño.

   Los colores NO se copian del panel: allí la barra es oscura en los
   dos temas y el texto va en blanco; aquí la cabecera sigue al tema,
   así que se conservan `text-primary` y `text-on-surface-variant`,
   que ya están resueltos para ambos.
   ------------------------------------------------------------ */

/* 22 px y no los 25 del panel. Ahí la barra es solo marca y usuario;
   aquí compite con la navegación centrada, y el nombre largo se
   comía el hueco: a 1440 px se solapaba 14 px con el menú. A 22 el
   texto mide 298 px en vez de 339 y entra con aire de sobra hasta
   el punto de corte. Medido, no estimado. */
.marca-titulo {
  font-family: 'Oswald', 'Geist', system-ui, sans-serif;
  font-weight: 600;
  font-size: 27.5px;
  letter-spacing: .045em;
  text-transform: uppercase;
  line-height: 1.15;
  white-space: nowrap;
}

/* ---- Entrada de la marca de la cabecera ----
   Llega deslizándose desde la izquierda al cargar, en el mismo gesto
   que el contenido del hero.

   32 px y no más: la barra tiene 48 px de relleno lateral en
   escritorio y 24 en móvil, así que con 32 la marca nunca asoma por
   fuera del borde y no hace falta recortar la cabecera —que no puede
   recortarse, porque de ella cuelga el desplegable de Servicios—.

   `backwards` como en los flotantes: si fuera `forwards`, el último
   fotograma quedaría fijado y bloquearía cualquier `transform`
   posterior. */
@keyframes marcaEntra {
  from { opacity: 0; transform: translateX(-32px); }
  to   { opacity: 1; transform: none; }
}

/* ---- Entrada de los botones de la derecha ----
   WhatsApp y el cambio de tema llegan desde su propio lado, un pelo
   después que la marca: la cabecera se compone de fuera hacia dentro. */
@keyframes accionesEntran {
  from { opacity: 0; transform: translateX(32px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .header-barra > a:first-child {
    animation: marcaEntra .6s cubic-bezier(.22, .61, .36, 1) .1s backwards;
  }
  .header-barra > div:last-child {
    animation: accionesEntran .6s cubic-bezier(.22, .61, .36, 1) .2s backwards;
  }
  /* La navegación no viene de los lados: baja. Está centrada entre
     los dos bloques que ya entran por izquierda y derecha, y si
     también se desplazara en horizontal las tres cosas se cruzarían
     en el mismo eje. Bajando, cierra la cabecera desde arriba. */
  .header-barra > nav {
    animation: navBaja .55s cubic-bezier(.22, .61, .36, 1) .3s backwards;
  }
}

@keyframes navBaja {
  from { opacity: 0; transform: translate(-50%, calc(-50% - 22px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}

.marca-sub {
  font-family: 'Oswald', 'Geist', system-ui, sans-serif;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: .16em;
  line-height: 1.3;
  white-space: nowrap;
}

/* Tope duro al bloque de marca.
   Con Oswald cargada el título mide 299 px y nunca llega a este
   límite. Está para el rato en que NO está cargada: `display=swap`
   pinta primero con Geist, que no es condensada y saca el nombre
   unos 60 px más ancho. Sin tope, ese primer fotograma se metía
   dentro del menú. Con él, se recorta con puntos suspensivos y al
   llegar la fuente vuelve a caber entero. */
.header-barra > a > span {
  max-width: 380px;
}

.marca-titulo,
/* ---- Entrada de la marca de la cabecera ----
   Llega deslizándose desde la izquierda al cargar, en el mismo gesto
   que el contenido del hero.

   32 px y no más: la barra tiene 48 px de relleno lateral en
   escritorio y 24 en móvil, así que con 32 la marca nunca asoma por
   fuera del borde y no hace falta recortar la cabecera —que no puede
   recortarse, porque de ella cuelga el desplegable de Servicios—.

   `backwards` como en los flotantes: si fuera `forwards`, el último
   fotograma quedaría fijado y bloquearía cualquier `transform`
   posterior. */
@keyframes marcaEntra {
  from { opacity: 0; transform: translateX(-32px); }
  to   { opacity: 1; transform: none; }
}

/* ---- Entrada de los botones de la derecha ----
   WhatsApp y el cambio de tema llegan desde su propio lado, un pelo
   después que la marca: la cabecera se compone de fuera hacia dentro. */
@keyframes accionesEntran {
  from { opacity: 0; transform: translateX(32px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .header-barra > a:first-child {
    animation: marcaEntra .6s cubic-bezier(.22, .61, .36, 1) .1s backwards;
  }
  .header-barra > div:last-child {
    animation: accionesEntran .6s cubic-bezier(.22, .61, .36, 1) .2s backwards;
  }
  /* La navegación no viene de los lados: baja. Está centrada entre
     los dos bloques que ya entran por izquierda y derecha, y si
     también se desplazara en horizontal las tres cosas se cruzarían
     en el mismo eje. Bajando, cierra la cabecera desde arriba. */
  .header-barra > nav {
    animation: navBaja .55s cubic-bezier(.22, .61, .36, 1) .3s backwards;
  }
}

@keyframes navBaja {
  from { opacity: 0; transform: translate(-50%, calc(-50% - 22px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}

.marca-sub {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A 27.5 px el bloque de marca mide 441 px, y el hueco que deja el
   menú centrado solo llega a esa cifra a partir de ~1560. Por debajo
   se vuelve al tamaño anterior en vez de esconder el nombre: perder
   14 px de letra es mejor trato que perder el nombre de la empresa
   en cualquier portátil de 1440.

   Por debajo de 1400 sigue mandando la regla de más arriba, que deja
   solo la insignia porque ahí ya no cabe ni a 22. */
@media (max-width: 1559px) {
  .marca-titulo { font-size: 22px; }
  /* ---- Entrada de la marca de la cabecera ----
   Llega deslizándose desde la izquierda al cargar, en el mismo gesto
   que el contenido del hero.

   32 px y no más: la barra tiene 48 px de relleno lateral en
   escritorio y 24 en móvil, así que con 32 la marca nunca asoma por
   fuera del borde y no hace falta recortar la cabecera —que no puede
   recortarse, porque de ella cuelga el desplegable de Servicios—.

   `backwards` como en los flotantes: si fuera `forwards`, el último
   fotograma quedaría fijado y bloquearía cualquier `transform`
   posterior. */
@keyframes marcaEntra {
  from { opacity: 0; transform: translateX(-32px); }
  to   { opacity: 1; transform: none; }
}

/* ---- Entrada de los botones de la derecha ----
   WhatsApp y el cambio de tema llegan desde su propio lado, un pelo
   después que la marca: la cabecera se compone de fuera hacia dentro. */
@keyframes accionesEntran {
  from { opacity: 0; transform: translateX(32px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .header-barra > a:first-child {
    animation: marcaEntra .6s cubic-bezier(.22, .61, .36, 1) .1s backwards;
  }
  .header-barra > div:last-child {
    animation: accionesEntran .6s cubic-bezier(.22, .61, .36, 1) .2s backwards;
  }
  /* La navegación no viene de los lados: baja. Está centrada entre
     los dos bloques que ya entran por izquierda y derecha, y si
     también se desplazara en horizontal las tres cosas se cruzarían
     en el mismo eje. Bajando, cierra la cabecera desde arriba. */
  .header-barra > nav {
    animation: navBaja .55s cubic-bezier(.22, .61, .36, 1) .3s backwards;
  }
}

@keyframes navBaja {
  from { opacity: 0; transform: translate(-50%, calc(-50% - 22px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}

.marca-sub { font-size: 13px; }
  .header-barra > a > span { max-width: 300px; }
}

/* ============================================================
   24. ENTRADA LATERAL DE LAS TARJETAS

   Las tarjetas entran deslizándose de lado, una detrás de otra,
   cuando su sección aparece en pantalla. Dos direcciones:

     .entra-derecha    llegan desde la derecha  (Rutas y cobertura)
     .entra-izquierda  llegan desde la izquierda (Servicios)

   Alternarlas hace que la página se lea como un vaivén al bajar,
   en vez de repetir el mismo gesto en cada bloque.

   El disparador es el `.is-visible` que el observador ya pone en el
   contenedor `.reveal`: no hace falta JavaScript nuevo ni un segundo
   observador. Las hijas solo reaccionan al estado del padre.

   Solo desde 768 px. Por debajo, `.svc-rejilla` deja de ser una
   cuadrícula y pasa a ser una pista con scroll horizontal y
   scroll-snap; empujar las tarjetas de lado dentro de un contenedor
   que ya desborda en ese eje ensancharía la pista y pelearía con el
   desplazamiento del dedo.
   ------------------------------------------------------------ */

@media (min-width: 768px) {
  /* Mientras esperan su turno, los bloques están 60 px a la derecha
     —o a la izquierda— de donde acabarán. Eso asoma por el borde de
     la ventana y le añade scroll horizontal a TODA la página: medido,
     2 px a 1280, y más cuanto más estrecha sea la ventana.

     Se recorta en la sección que los contiene. `clip` y no `hidden`
     a propósito: `hidden` convertiría la sección en un contenedor de
     desplazamiento y eso rompe `position: sticky` en sus hijos.
     `clip` recorta sin más. */
  section:has(.entra-derecha),
  section:has(.entra-izquierda) { overflow-x: clip; }

  /* Quién se mueve depende de lo que haya dentro.

     Si el elemento CONTIENE una rejilla de tarjetas, él se queda
     quieto y el movimiento lo hacen las tarjetas: si se desplazaran
     los dos, el recorrido se sumaría y saldrían de mucho más lejos.

     Si NO la contiene —el encabezado con el título, el texto y el
     botón— entonces se desplaza él mismo. Así el bloque entero entra
     por el mismo lado y el apartado se lee como un solo gesto. */
  .reveal.entra-derecha:has(.svc-rejilla),
  .reveal.entra-izquierda:has(.svc-rejilla) { transform: none; }

  .reveal.entra-derecha:not(:has(.svc-rejilla))   { translate: 60px 0; transform: none; }
  .reveal.entra-izquierda:not(:has(.svc-rejilla)) { translate: -60px 0; transform: none; }

  /* El encabezado se desplaza con `translate`, pero la transición
     que hereda de `.reveal` solo cubre `opacity` y `transform`: sin
     esta declaración el recorrido lateral se aplicaba de golpe y
     solo se veía el fundido. Misma curva y duración que las
     tarjetas, para que el bloque entre como un solo gesto. */
  .reveal.entra-derecha:not(:has(.svc-rejilla)),
  .reveal.entra-izquierda:not(:has(.svc-rejilla)) {
    transition:
      opacity   .6s cubic-bezier(.22, .61, .36, 1),
      translate .6s cubic-bezier(.22, .61, .36, 1);
  }

  /* El `:not(:has(...))` de arriba pesa tres clases y le ganaría al
     `.reveal.is-visible` que devuelve el elemento a su sitio. Se
     iguala aquí, o se quedaría desplazado para siempre. */
  .reveal.entra-derecha.is-visible:not(:has(.svc-rejilla)),
  .reveal.entra-izquierda.is-visible:not(:has(.svc-rejilla)) { translate: none; }

  .reveal.entra-derecha   .svc-rejilla > * { translate: 60px 0; }
  .reveal.entra-izquierda .svc-rejilla > * { translate: -60px 0; }

  .reveal.entra-derecha   .svc-rejilla > *,
  .reveal.entra-izquierda .svc-rejilla > * {
    opacity: 0;
    /* Curva de salida rápida: arranca con brío y frena al llegar,
       que es lo que hace que se perciba como algo que ENTRA y no
       como algo que se arrastra.

       Se anima `translate`, la propiedad suelta, y NO `transform`.
       Las tarjetas usan `transform` para el zoom del hover, y si la
       entrada ocupara esa propiedad la anularía: el `hover` dejaría
       de levantarlas. Separadas, cada una va por su lado y hasta
       pueden coincidir en el tiempo sin pisarse.

       Las tres últimas NO son decoración: `.svc-rejilla > *` ya
       declaraba una transición para `filter`, `box-shadow` y
       `transform` —el atenuado de las hermanas y la elevación del
       hover—. Esta regla pesa más y, si solo listara las dos suyas,
       BORRARÍA aquellas: el hover pasaría a saltar de golpe. */
    transition:
      opacity    .6s cubic-bezier(.22, .61, .36, 1),
      translate  .6s cubic-bezier(.22, .61, .36, 1),
      filter     .3s ease,
      box-shadow .3s ease,
      transform  .3s ease;
  }

  .reveal.entra-derecha.is-visible   .svc-rejilla > *,
  .reveal.entra-izquierda.is-visible .svc-rejilla > * {
    opacity: 1;
    translate: none;
  }

  /* Escalonado. 110 ms basta para leer las tres como una secuencia;
     por encima de ~150 la última se hace esperar.

     En la variante que entra por la izquierda el orden se invierte:
     la primera en moverse es la que está más cerca del borde por el
     que entran, o el escalonado se percibe al revés del movimiento. */
  .reveal.entra-derecha.is-visible .svc-rejilla > *:nth-child(2) { transition-delay: 110ms; }
  .reveal.entra-derecha.is-visible .svc-rejilla > *:nth-child(3) { transition-delay: 220ms; }

  .reveal.entra-izquierda.is-visible .svc-rejilla > *:nth-child(2) { transition-delay: 110ms; }
  .reveal.entra-izquierda.is-visible .svc-rejilla > *:nth-child(3) { transition-delay: 220ms; }
}

/* Quien pide menos movimiento ve las tarjetas ya colocadas. */
@media (prefers-reduced-motion: reduce) {
  .reveal.entra-derecha   .svc-rejilla > *,
  .reveal.entra-izquierda .svc-rejilla > * {
    opacity: 1;
    translate: none;
    transition: none;
  }
}

/* ============================================================
   25. ENTRADA EN ABANICO DE LA BANDA DE CIFRAS

   Las cuatro tarjetas se abren desde el centro: las de la mitad
   izquierda llegan desde la izquierda y las de la derecha desde la
   derecha. El movimiento acompaña a la posición de cada una en vez
   de arrastrarlas a todas hacia el mismo lado.

   Qué tarjeta es «de la izquierda» depende del ancho, porque la
   rejilla cambia de forma:

     · 2 columnas (por defecto)  ->  impares izquierda, pares derecha
     · 4 columnas (desde 1024)   ->  1 y 2 izquierda, 3 y 4 derecha

   Por eso se resuelve con `nth-child` en dos tramos y no con clases
   fijas en el HTML: una clase escrita a mano acertaría en un ancho y
   fallaría en el otro.

   40 px y no 60: son tarjetas pequeñas y en un abanico el recorrido
   se percibe más largo que en una fila que entra entera.
   ------------------------------------------------------------ */

/* El desplazamiento se hace con `translate`, pero la transición que
   heredaban estas tarjetas solo cubría `opacity` y `transform`: el
   recorrido lateral se aplicaba de golpe, sin animar, y solo se veía
   el fundido. Aquí se declara la lista completa.

   Se conservan `transform`, `background-color` y `border-color` con
   su duración corta: son las que usa el `hover` para elevar la
   tarjeta y teñir el borde. Si se dejaran fuera, la elevación al
   pasar el cursor se volvería instantánea. */
.cifras-rejilla > .reveal {
  transition:
    opacity          .78s cubic-bezier(.22, .61, .36, 1),
    translate        .78s cubic-bezier(.22, .61, .36, 1),
    transform        .3s ease,
    background-color .3s ease,
    border-color     .3s ease;
}

/* En reposo se anula el desplazamiento vertical que trae `.reveal`:
   así la entrada es lateral limpia y no diagonal. El `:not()` deja
   de aplicar en cuanto la tarjeta entra, de modo que no interfiere
   con el `transform` del hover. */
.cifras-rejilla > .reveal:not(.is-visible) { transform: none; }

/* 70 px, como en el resto del apartado: con los 40 anteriores el
   gesto apenas se percibía. */
.cifras-rejilla > .reveal:nth-child(odd)  { translate: -70px 0; }
.cifras-rejilla > .reveal:nth-child(even) { translate: 70px 0; }

@media (min-width: 1024px) {
  .cifras-rejilla > .reveal:nth-child(-n+2) { translate: -70px 0; }
  .cifras-rejilla > .reveal:nth-child(n+3)  { translate: 70px 0; }
}

/* Esta regla va la ÚLTIMA a propósito. Pesa lo mismo que las de
   arriba —tres clases— así que lo único que la hace ganar es el
   orden. Si se mueve más arriba, las tarjetas se quedan desplazadas
   para siempre. */
.cifras-rejilla > .reveal.is-visible { translate: none; }

/* La banda recorta: en reposo las tarjetas asoman 40 px por los dos
   bordes y eso le añadiría scroll horizontal a la página entera.
   `clip` en vez de `hidden` para no convertirla en un contenedor de
   desplazamiento. */
.cifras-banda { overflow-x: clip; }

@media (prefers-reduced-motion: reduce) {
  .cifras-rejilla > .reveal { translate: none; }
}

/* ============================================================
   26. ENTRADA CRUZADA DE LA GALERÍA DE FOTOGRAFÍAS

   Las tres de arriba llegan desde la derecha y las tres de abajo
   desde la izquierda: el bloque se cierra como unas tijeras en vez
   de desfilar todo hacia el mismo lado.

   Solo desde 1024 px, que es donde `.ops-fotos` tiene de verdad tres
   columnas. Por debajo son dos —o una en móvil—, y entonces «las tres
   de arriba» ya no son las tres primeras: el gesto contaría una cosa
   distinta de la que se ve. Ahí entran solo con el fundido del
   contenedor, sin desplazamiento.

   Se anima `translate` y no `transform`, igual que en las secciones
   anteriores, para no ocupar la propiedad que usan los hover.
   ------------------------------------------------------------ */

/* El disparador es la BANDA DE FOTOGRAFÍAS, no el contenedor de toda
   la galería. Aquel empieza en los vídeos: bastaba con que asomara el
   primer vídeo para que la animación se lanzara, y al llegar a las
   fotos ya había terminado.

   `.ops-banda--fotos` lleva también la clase `reveal` en el HTML,
   pero solo como enganche para el observador: aquí abajo se le
   anulan sus propios efectos para que la sección no se mueva ni se
   funda. Lo único que hace es recibir `.is-visible` en el momento
   justo. */

.ops-banda--fotos.reveal {
  opacity: 1;
  transform: none;
  transition: none;
}

@media (min-width: 1024px) {
  /* 0,78 s = los 0,6 originales un 30 % más lentos, para que el
     recorrido se aprecie. Los retardos de más abajo se alargan en la
     misma proporción: si solo se estirara la duración, las seis
     entrarían más juntas y se perdería el escalonado. */
  .ops-banda--fotos .ops-fotos > * {
    opacity: 0;
    transition:
      opacity   .78s cubic-bezier(.22, .61, .36, 1),
      translate .78s cubic-bezier(.22, .61, .36, 1);
  }

  .ops-banda--fotos .ops-fotos > *:nth-child(-n+3) { translate:  70px 0; }
  .ops-banda--fotos .ops-fotos > *:nth-child(n+4)  { translate: -70px 0; }

  /* Lleva la clase de estado pegada al disparador a propósito: las
     reglas `nth-child` de arriba pesan dos clases más un
     pseudo-selector, y esta, con `.is-visible` unida, las iguala.
     Separadas perdería y las fotos se quedarían desplazadas para
     siempre. Igualadas, gana por ir después. */
  .ops-banda--fotos.is-visible .ops-fotos > * { opacity: 1; translate: none; }

  /* Cada fila escalona de izquierda a derecha, y la de abajo arranca
     cuando la de arriba va por la mitad: se leen como dos gestos
     encadenados, no como seis piezas sueltas. */
  .ops-banda--fotos.is-visible .ops-fotos > *:nth-child(2) { transition-delay: 115ms; }
  .ops-banda--fotos.is-visible .ops-fotos > *:nth-child(3) { transition-delay: 235ms; }
  .ops-banda--fotos.is-visible .ops-fotos > *:nth-child(4) { transition-delay: 195ms; }
  .ops-banda--fotos.is-visible .ops-fotos > *:nth-child(5) { transition-delay: 310ms; }
  .ops-banda--fotos.is-visible .ops-fotos > *:nth-child(6) { transition-delay: 430ms; }

  /* En reposo asoman 70 px por los dos bordes: la sección recorta
     para que eso no le añada scroll horizontal a la página. */
  #referencias { overflow-x: clip; }
}

@media (prefers-reduced-motion: reduce) {
  .ops-banda--fotos .ops-fotos > * {
    opacity: 1;
    translate: none;
    transition: none;
  }
}
