@charset "UTF-8";
/* ═══════════════════════════════════════════════════════════════════════════
   COPENA CONSTRUCCIONES — 05 · MOVIMIENTO

   REGLA 1 · Todo estado «oculto hasta revelar» cuelga de html.js.
   Si el JavaScript no llega a ejecutarse, .js nunca se añade y NADA queda
   invisible. Sin esta guarda, un error de JS dejaría la página en blanco.

   REGLA 2 · Solo se animan transform, opacity y clip-path. Nunca width,
   height, top ni left: disparan layout en cada fotograma.

   REGLA 3 · El bloque de prefers-reduced-motion va al final del archivo y
   este archivo se carga el último, para que sus anulaciones ganen siempre.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══ APARICIÓN ESCALONADA ═══════════════════════════════════════════════
   TRES FAMILIAS, NO UNA. Antes .ap gobernaba los 71 bloques de la página con
   el mismo desplazamiento vertical: al bajar, todo entraba igual, y un
   movimiento que se repite setenta veces deja de leerse como intención.

   Ahora cada tipo de elemento entra como le corresponde:
     .ap        · texto corrido y titulares → sube. Es la base.
     .ap--cota  · cotas, etiquetas y datos técnicos → entran DESDE EL CANAL
                  lateral, como una acotación que se añade a un plano.
     [data-grupo] · filas de ficha y listas numeradas → se descubren de
                  izquierda a derecha con máscara, sin desplazarse.
   Las tres cuestan lo mismo: transform y opacity.                          */

.js .ap{
  opacity:0;
  transform:translateY(var(--ap-dist));
}
.js .ap.visible{
  opacity:1;transform:none;
  transition:opacity var(--v-lento) var(--sal),transform var(--v-lento) var(--sal);
  transition-delay:calc(var(--i,0) * 80ms);
}

/* El hero entra más rápido que el resto de la página, y es deliberado: es lo
   único que ocurre sin que nadie lo haya pedido con un gesto. Debajo del
   pliegue cada bloque entra cuando el visitante llega a él y puede permitirse
   los 620 ms de --v-lento; aquí el reloj corre contra el primer CTA. */
.js .hero .ap.visible{transition-duration:480ms}

/* Cotas y etiquetas técnicas: entran de lado */
/* La cota entra desde el canal lateral, la misma distancia que .ap sube. */
.js .ap--cota{transform:translateX(calc(var(--ap-dist) * -1))}
.js .ap--cota.visible{transform:none}

/* Grupos con máscara: filas de ficha y listas numeradas.

   TRAMPA QUE COSTÓ ENCONTRAR: el recorte lo pone el CONTENEDOR, nunca la
   propia fila. Un elemento recortado a cero con clip-path tiene área nula, y
   entonces IntersectionObserver lo da por NO visible — así que el observador
   que debía revelarlo no dispara jamás y la fila se queda invisible para
   siempre. Observando el padre, que sí tiene área, el problema desaparece.
   Es el mismo patrón que ya usa .carril.visible .obra. */
.js [data-grupo] > *{clip-path:inset(0 100% 0 0)}
.js [data-grupo].visible > *{
  clip-path:inset(0 0 0 0);
  transition:clip-path var(--v-lento) var(--sal);
  transition-delay:calc(var(--i,0) * 80ms);
}

/* ═══ MÁSCARA POR LÍNEA ══════════════════════════════════════════════════
   Los titulares no aparecen con fundido: suben desde detrás de una máscara
   rectangular, como una pieza que se coloca.                               */

/* El padding y el margen negativo son de la misma medida a propósito: suben el
   BORDE DE RECORTE sin mover ni un píxel el texto. Sin ellos, con interlineado
   de 0,84 la caja es más baja que el glifo completo y overflow:hidden le corta
   la tilde a «QUE FIRMÁS» y «QUE PAGÁS» — que en voseo cambia el verbo. */
.lin{display:block;overflow:hidden;padding-top:.16em;margin-top:-.16em}

/* NUNCA partir una línea calzada. El calce la deja exactamente al ancho del
   contenedor, así que cualquier redondeo de subpíxel la empuja a partirse en
   dos; y como .lin lleva overflow:hidden, la segunda línea desaparece sin
   dejar rastro y el bloque parece encogido sin motivo. Costó encontrarlo. */
[data-calce] .lin,
[data-calce] .lin > i{white-space:nowrap}

/* La máscara va ACOTADA AL HERO, no a .lin en general. El bloque de marca del
   pie usa las mismas .lin para calzarse, y con la regla global se quedaba
   traducido 108 % hacia abajo esperando una clase .abierto que solo recibe el
   hero: es decir, invisible para siempre. */
.js .hero .lin > i{
  display:block;font-style:normal;
  transform:translateY(108%);
}
.js .hero.abierto .lin > i{
  transform:none;
  /* 620 ms y 70 de escalonado: la última línea remata a los 830 ms, que es el
     techo que se fijó para la entrada del hero. Eran 900 y 95, y remataba a
     los 1.185. */
  transition:transform var(--v-lento) var(--sal);
  transition-delay:calc(var(--l,0) * 70ms);
}

/* El bloque del pie entra con la misma máscara, pero disparado al verlo. */
.js .pie__marca .lin > i{
  display:block;font-style:normal;
  transform:translateY(108%);
}
.js .pie__marca.visible .lin > i{
  transform:none;
  transition:transform var(--v-muy-lento) var(--sal);
  transition-delay:calc(var(--l,0) * 110ms);
}

/* ═══ REVELADO DE FOTOGRAFÍA ═════════════════════════════════════════════
   Barrido horizontal, nunca fundido. La foto «se descubre».                */

.js .revela{clip-path:inset(0 100% 0 0)}
.js .revela.visible{
  clip-path:inset(0 0 0 0);
  transition:clip-path 1s var(--sal);
  transition-delay:calc(var(--i,0) * 90ms);
}

/* ═══ ENTRADA DE LA GRÚA ═════════════════════════════════════════════════
   Entra DESPUÉS de la primera línea del titular, no a la vez: si compiten,
   ninguna de las dos se lee. El estado oculto cuelga de html.js, así que sin
   JavaScript la grúa está desde el primer fotograma en su sitio.

   No se anima transform aquí: lo escribe grua.js. Se anima una propiedad
   propia, --gr-entrada, que el transform de la escena suma a lo demás.      */
.js .grua .grua__escena{
  opacity:0;
  transform:translateY(20px) scale(.96);
}
.js .grua.cargada .grua__escena{
  opacity:1;
  transform:none;
  transition:opacity 700ms var(--sal) 300ms,transform 700ms var(--sal) 300ms;
}
.js .grua .grua__luz{opacity:0}
.js .grua.cargada .grua__luz{
  opacity:.9;
  transition:opacity 900ms var(--sal) 520ms;
}

/* ═══ ENTRADA DEL HERO ═══════════════════════════════════════════════════ */

/* 1 · la cruz técnica */
.js .hero__cruz i{transform:scale(0)}
.js .hero.abierto .hero__cruz i:first-child{transform:scaleX(1);transition:transform 420ms var(--sal)}
.js .hero.abierto .hero__cruz i:last-child{transform:scaleY(1);transition:transform 420ms var(--sal) 120ms}

/* 2 · las líneas del plano se dibujan */
.js .hero__plano line{
  stroke-dasharray:var(--l,2000);
  stroke-dashoffset:var(--l,2000);
}
.js .hero.abierto .hero__plano line{
  stroke-dashoffset:0;
  transition:stroke-dashoffset 900ms var(--sal);
  transition-delay:calc(120ms + var(--n,0) * 45ms);
}

/* 3 · el indicador de scroll, el último en llegar */
.js .hero__baja{opacity:0;transform:translateY(8px)}
.js .hero.listo .hero__baja{
  opacity:1;transform:none;
  transition:opacity var(--v-lento) var(--sal),transform var(--v-lento) var(--sal);
}
.hero__baja i::after{animation:gotea 2.1s infinite}
@keyframes gotea{
  0%  {transform:translateY(0);opacity:0}
  22% {opacity:1}
  100%{transform:translateY(33px);opacity:0}
}

/* Latido del punto de disponibilidad.

   Antes el halo se hacía animando box-shadow, que es una propiedad de
   PINTADO: obligaba a repintar el hero cada 2,6 segundos, indefinidamente,
   y contradecía la REGLA 2 de la cabecera de este archivo —solo transform,
   opacity y clip-path—. Ahora el halo es un pseudoelemento que se escala:
   mismo efecto exacto, pero corre en el compositor y no repinta nada.    */
.hero__micro .punto{position:relative}
.hero__micro .punto::after{
  content:"";position:absolute;inset:0;
  border-radius:50%;
  background:rgba(111,163,89,.7);
  transform:scale(1);
  animation:late 2.6s infinite;
}
@keyframes late{
  0%,70%{transform:scale(1);opacity:.7}
  85%   {transform:scale(3.25);opacity:0}
  100%  {transform:scale(1);opacity:0}
}

/* ═══ SISTEMA OBRA BLINDADA ══════════════════════════════════════════════ */

/* En escritorio las etapas se apilan y solo una está activa */
.etapa{transition:opacity var(--v-lento) var(--sal),transform var(--v-lento) var(--sal)}

/* ═══ PROYECTOS ══════════════════════════════════════════════════════════
   Las fichas llegan escalonadas al entrar la sección. Es un disparo por
   IntersectionObserver, no una animación ligada al scroll: nadie queda
   atrapado y la rueda del ratón nunca se secuestra.                        */

.js .carril .obra{opacity:0;transform:translateX(36px)}
.js .carril.visible .obra{
  opacity:1;transform:none;
  transition:opacity var(--v-lento) var(--sal),transform var(--v-lento) var(--sal);
  transition-delay:calc(var(--i,0) * 90ms);
}

/* ═══ SOCIOS ═════════════════════════════════════════════════════════════
   La franja crece con flex-grow. El contenido interior lleva ancho fijo
   (ver responsive.css), así que el texto no se re-maqueta al animar: la
   caja se abre y el contenido simplemente se descubre.                     */

.socio{transition:flex-grow var(--v-lento) var(--sal)}

/* Entrada: el retrato se descubre con una máscara VERTICAL, de abajo a
   arriba. Es el mismo recurso que .revela, girado, para que la fila de
   cuatro caras verticales entre en la dirección de su propio formato.
   El escalonado sale del --i que ya lleva cada ficha: 80 ms de diferencia.
   La ficha entera (número, nombre, cargo) entra con .ap, así que la foto no
   puede tardar más que el texto: 0,8 s de tope.                            */
.js .socio .socio__retrato picture{clip-path:inset(100% 0 0 0)}
.js .socio.visible .socio__retrato picture{
  clip-path:inset(0 0 0 0);
  transition:clip-path .8s var(--sal);
  transition-delay:calc(var(--i,0) * 80ms + 120ms);
}

/* ═══ DOCUMENTOS ═════════════════════════════════════════════════════════ */

.lamina{transition:transform var(--v-lento) var(--sal),opacity var(--v-medio) var(--sal)}

/* La guarda .js faltaba: sin JavaScript el sello se quedaba invisible para
   siempre, que es justo lo que prohíbe la REGLA 1 de la cabecera. */
.js .sello{opacity:0;transform:rotate(-9deg) scale(1.5)}
.sello.visible{
  opacity:1;transform:rotate(-9deg) scale(1);
  transition:transform 380ms cubic-bezier(.2,1.45,.4,1),opacity 220ms;
}

/* ═══ FAMILIA C · LÍNEAS ESTRUCTURALES ═══════════════════════════════
   El filete que ABRE un bloque se traza de izquierda a derecha. La página se
   lee como un plano, y en un plano las líneas se trazan.

   SON CINCO. La página ya tenía líneas animadas —el hilo maestro, los tramos de
   sección, el subrayado de cada dolor, el acento del CTA, el filete de la
   alianza—; veinte más convertirían el recurso en ruido. assets/js/movimiento.js
   anima --filete de 0 a 1 con ScrollTrigger.batch.

   ⚠️ SE DIBUJA CON UN PSEUDOELEMENTO, NO CON EL BORDE, porque un border-top no
   se puede escalar y animar su ancho sería animar layout. Bajo html.gsap el
   borde se vuelve transparente —sigue ocupando su sitio, así que no hay salto—
   y la línea la pinta ::before por encima. Sin GSAP no se toca nada: el borde
   original se ve como siempre.

   Cada filete declara el color y el grosor del borde que sustituye. Si alguien
   cambia un borde en secciones.css tiene que cambiarlo aquí también; por eso
   están los cinco juntos y no repartidos.                                    */

/* Con @property el navegador interpola el número en el compositor en vez de
   tratarlo como una cadena. Donde no esté soportado manda el var(--filete,0). */
@property --filete{syntax:"<number>";inherits:false;initial-value:0}

html.gsap [data-filete]{position:relative;border-top-color:transparent;--filete:0}
html.gsap [data-filete]::before{
  content:"";position:absolute;left:0;right:0;
  top:calc(var(--filete-alto,2px) * -1);
  height:var(--filete-alto,2px);
  background:var(--filete-color,currentColor);
  transform:scaleX(var(--filete,0));
  transform-origin:left center;
}
html.gsap .conceptos,
html.gsap .servicios__lista   {--filete-color:var(--oro-linea);--filete-alto:2px}
html.gsap .transformacion__cifras{--filete-color:var(--oro);--filete-alto:2px}
html.gsap .ficha              {--filete-color:var(--borde-osc);--filete-alto:1px}
html.gsap .cierre__datos      {--filete-color:rgba(250,248,243,.2);--filete-alto:1px}

/* ═══ CABECERA QUE SE RETIRA ════════════════════════════════════
   Solo escritorio y solo con GSAP cargado, que es lo mismo: movimiento.js no se
   carga por debajo de 1000px. En un teléfono la cabecera lleva el botón de menú
   y una cabecera que va y viene bajo el pulgar es un blanco móvil.

   Se anima transform y nada más. Los tres candados —menú abierto, foco dentro
   de la cabecera y los primeros píxeles de scroll— están en movimiento.js.    */
html.gsap .cabecera{transition:background var(--v-medio) var(--dentro),
                               border-color var(--v-medio) var(--dentro),
                               backdrop-filter var(--v-medio),
                               transform var(--m-ui) var(--sal)}
html.gsap .cabecera--retirada{transform:translateY(-100%)}

/* ═══ FAMILIA B · MÁSCARA DE IMAGEN ════════════════════════════════
   [data-revela] no necesita estado inicial en CSS, y es a propósito: el recorte
   lo escribe GSAP con un fromTo, así que si GSAP no carga NO hay ninguna regla
   que deje la imagen recortada a cero. Es la REGLA 1 de la cabecera de este
   archivo aplicada a la letra: el estado oculto no se declara, se aplica.

   Son dos, y son las dos únicas imágenes grandes que no tenían revelado propio:
   la comparativa de la sección 01 y el logotipo de la alianza. Los retratos
   entran con máscara vertical y las fichas de obra escalonadas desde antes.

   Los comparadores se descartaron a propósito: un clip-path en su contenedor
   recorta el anillo de foco de su <input type=range>, que es exactamente el
   fallo de WCAG 2.4.7 que este proyecto ya corrigió una vez (C3).            */

/* ═══ CIFRAS ═════════════════════════════════════════════════════════════════════
   Colgaba de [data-cuenta], el atributo del contador. El contador se retiró
   (escenas.js §3) y las cifras siguen necesitando ancho tabular: sin él, un 12
   y un 5 uno debajo del otro no alinean sus troqueles.                       */
.dato__n,.garantia__cifra .n{font-variant-numeric:tabular-nums}

/* ═══════════════════════════════════════════════════════════════════════════
   MOVIMIENTO REDUCIDO
   Todo queda en su estado final y legible. Ni una transición, ni un
   keyframe, ni scroll suavizado. La página funciona igual: cambia lo que
   se mueve, no lo que se puede leer.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══ CORREDOR DE CONFIANZA ══════════════════════════════════════════════
   -50 % es exactamente el ancho de UNA pista, y hay dos idénticas: al llegar
   al final del ciclo el fotograma es idéntico al primero, así que el bucle no
   tiene costura. Curva lineal e infinita, sin rebote.

   Se detiene en tres casos, y ninguno depende de un bucle en JavaScript:
     · con el cursor o el foco encima  → :hover, :focus-within
     · con la pestaña oculta           → html.oculto, desde interfaz.js
     · con movimiento reducido         → el bloque de abajo
   La duración se declara en el elemento para poder bajarla en móvil.        */

/* Velocidad de la cinta. Vive aquí, junto a la animación, y no en
   responsive.css: las media queries no aportan especificidad, y como este
   archivo se carga el último su valor ganaba siempre. El de escritorio
   estaba declarado allí y no se aplicaba nunca. */
.corredor{--corre:64s}
@media (min-width:900px){ .corredor{--corre:78s} }

@media (prefers-reduced-motion:no-preference){
  html{scroll-behavior:smooth}

  .corredor__marco{animation:corre var(--corre) linear infinite}
  .corredor:hover .corredor__marco,
  html.oculto .corredor__marco{animation-play-state:paused}

  /* ⚠️ AQUÍ HABÍA UN «.corredor:focus-within» Y SE RETIRÓ.
     Cuando se escribió no había NI UN elemento enfocable dentro de la cinta,
     así que la regla no llegaba a aplicarse nunca —ese era justamente el
     fallo de WCAG 2.2.2—. Al añadir el control de pausa sí lo hay, y
     entonces la regla se volvió activa y dañina: mientras el control tuviera
     el foco, la cinta quedaba pausada por :focus-within, de modo que al
     desmarcar el checkbox para reanudar no pasaba nada hasta salir de él.
     Medido: running → paused → paused. El control parecía roto.
     El estado lo decide ahora el checkbox, y solo el checkbox. */

  /* Control de pausa · WCAG 2.2.2, nivel A.
     :has() sobre la sección, no un combinador de hermanos: así da igual dónde
     acabe el checkbox dentro del marcado. Este proyecto ya perdió una vez el
     indicador de foco del comparador por confiar en el orden de los hermanos.
     Sin JavaScript por medio: la animación es CSS y su freno también. */
  .corredor:has(.corredor__interruptor:checked) .corredor__marco{
    animation-play-state:paused;
  }
}

@keyframes corre{
  from{transform:translate3d(0,0,0)}
  to  {transform:translate3d(-50%,0,0)}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}

  *,*::before,*::after{
    animation-duration:.001ms!important;
    animation-iteration-count:1!important;
    transition-duration:.001ms!important;
    transition-delay:0s!important;
    scroll-behavior:auto!important;
  }

  /* Estados finales explícitos: no basta con quitar la transición, hay que
     asegurarse de que nada quede en su estado inicial invisible.           */
  .js .ap,
  .js .ap--cota,
  .js .hero.abierto .lin > i,
  .js .hero .lin > i,
  .js .pie__marca .lin > i,
  .js .hero__baja,
  .js .carril .obra{opacity:1!important;transform:none!important}
  .obra__foto img{transform:none!important}

  .js .revela,
  .js [data-grupo] > *,
  .js .socio .socio__retrato picture,
  .servicio__foto img{clip-path:none!important}

  /* La grúa se queda quieta y visible, con su luz y su sombra estáticas. */
  .js .grua .grua__escena{opacity:1!important;transform:none!important}
  .js .grua .grua__luz{opacity:.9!important}

  .js .hero__cruz i{transform:none!important}
  .js .hero__cruz i:first-child{transform:scaleX(1)!important}
  .js .hero__cruz i:last-child{transform:scaleY(1)!important}

  .js .hero__plano line{stroke-dasharray:none!important;stroke-dashoffset:0!important}

  .hilo__viva,.tramo,.riel__nodo::after{transform:scaleY(1)!important}
  .trazo path{stroke-dasharray:none!important;stroke-dashoffset:0!important}
  .cierre__acento::before{transform:scaleY(1)!important}
  .cierre__acento::after{transform:scaleX(1)!important}
  .alianza__filete{transform:scaleX(1)!important}
  .dolor b::after{transform:scaleX(1)!important}
  .progreso i{transform:scaleX(1)!important}

  .sello{opacity:1!important;transform:rotate(-9deg)!important}
  .lamina{transform:none!important;opacity:1!important;position:static!important}

  .socio{flex:1 1 auto!important}

  /* ── SISTEMA OBRA BLINDADA ───────────────────────────────────
     AQUÍ HABÍA DIECISÉIS LÍNEAS DE !important, y no hacen falta ninguna.

     Estaban porque en escritorio las cuatro etapas se apilaban en absolute con
     opacity:0 y solo la .activa se veía; con movimiento reducido nadie ponía
     .activa y quedaban 300vh de columna vacía. Se midió: 0 de 4 a 1400×940.

     El 18/08/2026 la sección dejó de apilarse: las cuatro etapas están en flujo
     y visibles siempre, con o sin movimiento, con o sin GSAP, con o sin
     JavaScript. El fallo no se tapa mejor: ya no se puede producir.

     Lo único que sigue haciendo falta es apagar la transición del filete, y de
     eso ya se encarga el * de arriba de este bloque.                        */

  .hero__micro .punto::after,.hero__baja i::after{animation:none!important;opacity:0!important}

  /* La cinta se queda quieta y legible. No se oculta: las frases son
     contenido, no decoración, y tienen que poder leerse igual. */
  .corredor__marco{animation:none!important;transform:none!important}
  .corredor{overflow-x:auto}

  /* El carrusel sigue siendo táctil y operable, pero sin desplazamiento
     suavizado, que es justo lo que molesta con movimiento reducido.        */
  .carril{scroll-behavior:auto!important}
}
