/* ============================================================================
   FONDO DE LÍNEAS — animación del telón que dibuja `layout/fondo_lineas.php`.

   Porte del componente de React "background-paths" (framer-motion + Tailwind)
   a SVG + keyframes, sin librerías y sin JavaScript. El dibujo lo arma el PHP;
   acá está TODO lo que se mueve.

   ⚠️ ESTA HOJA VA SIEMPRE DESPUÉS DE `sartori.css`. Dos motivos, los dos
   importantes:

     a) Varias reglas de acá ajustan elementos que ya define sartori.css
        (`.login-fondo`, `.portal-hero-banda`) con la MISMA especificidad, y en
        un empate gana la que se declaró último. Si esta hoja carga antes, esos
        ajustes no existen y el fondo se desborda o queda arriba del contenido.

     b) El apagón de "menos movimiento" que hay en sartori.css. Ver abajo.

   ── EL APAGÓN GLOBAL DE sartori.css, que es el problema central ────────────

   En `assets/css/sartori.css` (línea ~405) vive este barrido:

       @media (prefers-reduced-motion: reduce) {
         *:not(.hero-escena, .hero-escena *) {
           transition: none !important;
           animation-duration: .01ms !important;
           animation-delay: 0s !important;
           animation-iteration-count: 1 !important;
         }
       }

   Eso NO apaga una animación: la hace correr en diez microsegundos, una sola
   vez, y la deja **clavada en su último frame**. Un fondo así no se ve quieto,
   se ve roto. Y encima Santino tiene los efectos de animación de Windows
   desactivados, así que su navegador manda `prefers-reduced-motion: reduce`
   SIEMPRE: para él ese barrido no es el caso raro, es el caso normal.

   La forma "oficial" de sobrevivirlo en este proyecto es sumarse al `:not()`,
   como hizo la escena del oso del hero. Acá **no se hizo**: `sartori.css` es
   un archivo compartido y editarlo desde una tanda que toca otra cosa es
   pisarle la mano a quien lo esté trabajando. Se eligió **convivir**.

   Y conviene decirlo claro porque es contraintuitivo: **no hace falta estar en
   el `:not()`**. Se midió con la preferencia activada y `sartori.css` entero
   cargado — la línea queda con `animation-duration: 93.228s`,
   `iteration-count: infinite` y `playState: running`, mientras un elemento
   suelto de control, en la misma página, queda en `1e-05s` y una iteración.
   El barrido apaga lo que no se defiende, pero no le gana a un selector más
   específico. La convivencia se apoya en dos cosas:

   1. **Ganarle por especificidad.** `*:not(.hero-escena, .hero-escena *)` vale
      (0,1,0) — el `*` no suma nada y el `:not()` toma la especificidad de su
      argumento más específico, que es una clase. Todas las reglas del bloque
      `reduce` de acá abajo usan DOS clases, (0,2,0), y devuelven a mano las
      tres propiedades que el barrido pisa. Entre dos `!important` del mismo
      origen gana el más específico, sin importar el orden de carga.

   2. **No dejar nada animable en el `style` inline.** El PHP escribe la
      duración y la fase de cada línea como variables (`--fl-dur`, `--fl-fase`)
      y no como `animation-duration`/`animation-delay`. Si las escribiera como
      propiedades reales, el barrido igual les ganaría: un `!important` de la
      hoja de autor le gana a un `style` inline SIN `!important`. Las custom
      properties, en cambio, el barrido ni las mira — y desde acá se las
      convierte en tiempo real con una regla que sí es lo bastante específica.

   Si algún día alguien mueve o borra ese barrido de sartori.css, este archivo
   sigue funcionando igual: el bloque `reduce` de acá es correcto por sí solo.

   ── QUÉ PASA EXACTAMENTE CON "MENOS MOVIMIENTO" ────────────────────────────

   Acá "menos movimiento" AQUIETA, no apaga:
     · El recorrido de las líneas sigue, con el ciclo estirado ~3,4 veces
       (de 20-32 s pasa a 68-109 s). Se mueve, pero hay que mirarlo para verlo.
     · El halo de fondo sigue, también estirado.
     · El único que se apaga es el latido rápido del conjunto (7 s), que es un
       micro-loop y es lo que de verdad molesta.

   ── LOS COLORES SON VARIABLES, NO CONSTANTES ───────────────────────────────

   El mismo fondo sirve al login de COMS (índigo), al de Helsek (azul #1B4B8F),
   al de Myrna (borgoña) y al hero del portal. Nada de color va escrito en las
   reglas: todo sale de `--fondo-linea-*`, y el partial las pisa por `style`
   cuando se le pasan colores. El default es el azul luna de COMS, que es el
   que ya usan los resplandores del hero y del login.
   ========================================================================== */

.fondo-lineas {
  /* ── La paleta. Pisar estas tres alcanza para revestir el fondo entero. ── */
  --fondo-linea-color: #8FA0EE;              /* luna de COMS */
  --fondo-linea-color-espejo: #5E73D8;       /* medio tono más hundido: da profundidad */
  --fondo-linea-resplandor: rgba(64, 89, 201, .34);
  --fondo-linea-intensidad: .55;             /* opacidad del conjunto */

  /* Cuánto se estira el ciclo con "menos movimiento". 1 = igual que siempre.
     Era 3.4; bajó a 2.6 el 2026-09-18 junto con el ciclo (20-32s → 15-24s).
     Santino tiene esa preferencia PRENDIDA en Windows, así que éste es el
     número que define lo que él ve: es el multiplicador de `--fl-dur`, o sea
     que su ciclo real va de 39 a 62,4 s. Tocar sólo `fl_ciclo_*` sin tocar
     esto le cambia la velocidad un 25%, que no se nota. */
  --fondo-linea-lentitud: 2.6;

  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;

  /* Es un telón: no se toca, no se selecciona y NO roba el clic. Sin esto, un
     fondo a pantalla completa se come el clic de los campos del login. */
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;

  /* Acota el repintado al recuadro del fondo: son 72 curvas animando
     `stroke-dashoffset`, que es una propiedad que el navegador no puede pasar
     a la placa de video y repinta por CPU. Sin esto, cada cuadro invalida
     también lo que hay alrededor. */
  contain: layout paint style;
}

.fondo-lineas__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;   /* sin esto el svg es inline y arrastra la línea base */
  opacity: var(--fondo-linea-intensidad);

  /* El micro-loop: un latido lento de todo el conjunto, lo único que se apaga
     con "menos movimiento". Va acá, sobre el <svg>, y no sobre cada línea:
     72 animaciones de opacidad extra costarían mucho más que una sola. */
  animation: fondo-lineas-latir 7s ease-in-out infinite;
}

.fondo-lineas .fondo-lineas__linea {
  fill: none;
  stroke: var(--fondo-linea-color);
  stroke-linecap: round;   /* las puntas del trazo cortadas en seco se notan */

  /* El grosor queda en píxeles de pantalla y no se escala con el SVG.
     Por qué: con `preserveAspectRatio="slice"` el viewBox de 696x316 se agranda
     entre 2,7x (celular) y 4,6x (monitor de 1440p) según la pantalla. Sin esto,
     la MISMA línea se ve de 1,7 px en una y de 7 px en otra: fina y elegante
     en un lado, un rayón grueso en el otro. El PHP compensa multiplicando por
     `fl_grosor` para que el trazo no quede demasiado tímido. */
  vector-effect: non-scaling-stroke;

  /* ── El corazón del porte ───────────────────────────────────────────────
     El original anima `pathLength` y `pathOffset` de framer-motion. El
     equivalente en CSS puro son estas dos propiedades.

     `--fl-trazo` (entre .20 y .48) es la fracción de la curva que está
     pintada y `--fl-hueco` es el resto; los dos los calcula el PHP y
     **suman 1 siempre**. Esa suma es la condición del loop perfecto: al
     desplazarse 1 unidad, el patrón queda idéntico a como empezó, así que el
     `from` y el `to` de los keyframes se ven iguales y el ciclo cierra sin
     tirón, sin necesidad de un keyframe de vuelta.

     El hueco viene servido del PHP y no de un `calc(1 - var(--fl-trazo))`
     porque `stroke-dasharray` es una propiedad de SVG y ahí el `calc()` con
     números sin unidad quedaba sin resolver en el valor computado. Andaba,
     pero es la clase de detalle que después falla en un navegador y no en otro.

     Todo esto funciona porque el `<path>` lleva `pathLength="1"`: sin eso,
     `.31` serían 31 centésimas de píxel y no el 31% de la curva. */
  stroke-dasharray: var(--fl-trazo, .32) var(--fl-hueco, .68);

  animation-name: fondo-lineas-fluir;
  animation-timing-function: linear;   /* lineal: es una corriente, no un rebote */
  animation-iteration-count: infinite;
  animation-duration: var(--fl-dur, 24s);
  animation-delay: var(--fl-fase, 0s); /* negativo: arranca ya empezada */
}

/* El grupo espejado va en otro tono. Es la única diferencia entre los dos, y
   alcanza para que se lean como dos planos y no como un dibujo simétrico. */
.fondo-lineas .fondo-lineas__grupo--espejo .fondo-lineas__linea {
  stroke: var(--fondo-linea-color-espejo, var(--fondo-linea-color));
}

/* El halo. UNO grande y difuso, nunca puntitos: sobre fondo oscuro, las motas
   de 1 o 2 px se leen como píxeles muertos del monitor. Es el mismo recurso
   que ya usan `.login-fondo` y `.portal-hero-banda` para dar profundidad. */
.fondo-lineas__resplandor {
  position: absolute;
  /* Se sale del recuadro a propósito: así el centro del degradé puede quedar
     afuera y lo que entra es sólo la parte ancha, sin borde visible. */
  inset: -30%;
  background:
    radial-gradient(48% 42% at 24% 30%, var(--fondo-linea-resplandor), transparent 70%),
    radial-gradient(42% 38% at 78% 76%, var(--fondo-linea-resplandor), transparent 72%);
  animation: fondo-lineas-respirar 34s ease-in-out infinite;
  will-change: transform;
}

/* ─────────────────────────────────────────────────────────── Los keyframes */

/* Un solo desplazamiento lineal de 1 unidad de `pathLength`. Negativo para que
   el trazo viaje en el sentido del dibujo (de arriba-izquierda a abajo-derecha)
   y no al revés. Al completar 1 unidad el patrón vuelve a estar igual, así que
   el `from` y el `to` se ven idénticos y el loop no tironea. */
@keyframes fondo-lineas-fluir {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -1; }
}

/* El halo deriva despacio. Es lo que evita que el fondo se lea como una imagen
   fija con unas rayas encima. */
@keyframes fondo-lineas-respirar {
  0%, 100% { transform: translate3d(-2.5%, -1.5%, 0) scale(1.04); opacity: .85; }
  50%      { transform: translate3d( 2.5%,  1.5%, 0) scale(1.14); opacity: 1; }
}

/* El latido rápido del conjunto. El 100% vuelve al valor del 0% o el loop da
   un salto. Es el único que se apaga con "menos movimiento". */
@keyframes fondo-lineas-latir {
  0%, 100% { opacity: calc(var(--fondo-linea-intensidad) * .82); }
  50%      { opacity: var(--fondo-linea-intensidad); }
}

/* ══════════════════════════════════════════════════ "Menos movimiento" ════

   Todas las reglas de acá abajo usan DOS clases (0,2,0) para ganarle al
   barrido `*:not(...)` de sartori.css, que vale (0,1,0). Y devuelven las TRES
   propiedades que ese barrido pisa (`animation-duration`, `animation-delay`,
   `animation-iteration-count`): con que falte una, la animación se rompe
   distinto — sin `iteration-count` corre una sola vez y se congela igual.

   ⚠️ Si se toca este bloque hay que probarlo con la preferencia ACTIVADA y
   contra `sartori.css` ENTERO, no contra esta hoja sola. Sin el barrido de
   sartori.css cargado, todo parece andar. */
@media (prefers-reduced-motion: reduce) {

  /* Las líneas siguen viajando, con el ciclo estirado. `--fl-dur` y `--fl-fase`
     son variables y no propiedades reales, así que llegan intactas hasta acá
     (el barrido pisa `animation-duration`, no `--fl-dur`) y recién en esta
     línea se convierten en tiempo, ya multiplicadas. */
  .fondo-lineas .fondo-lineas__linea {
    animation-duration: calc(var(--fl-dur, 24s) * var(--fondo-linea-lentitud)) !important;
    animation-delay: calc(var(--fl-fase, 0s) * var(--fondo-linea-lentitud)) !important;
    animation-iteration-count: infinite !important;
  }

  .fondo-lineas .fondo-lineas__resplandor {
    animation-duration: calc(34s * var(--fondo-linea-lentitud)) !important;
    animation-delay: 0s !important;
    animation-iteration-count: infinite !important;
  }

  /* Este SÍ se apaga: es el micro-loop. `animation: none` y no una duración
     larga, así la opacidad vuelve al valor de la regla base en vez de quedar
     donde la dejó el último frame. */
  .fondo-lineas .fondo-lineas__svg {
    animation: none !important;
    opacity: var(--fondo-linea-intensidad);
  }
}

/* ══════════════════════════════════════════════════════════════ Pantallas ══ */

/* Celular: la mitad de las líneas. No es sólo por rendimiento (72 curvas
   repintando por CPU en un teléfono de gama media se nota), es sobre todo
   visual: con `slice`, en una pantalla vertical el viewBox se amplía casi 3x y
   las líneas quedan tan juntas que el fondo se empasta. Con una de cada dos se
   ve el mismo dibujo, más aireado.

   `:nth-child(even)` recorre los hijos de CADA <g> por separado, así que saca
   una sí y una no en los dos grupos. */
@media (max-width: 700px) {
  .fondo-lineas .fondo-lineas__linea:nth-child(even) { display: none; }
  .fondo-lineas .fondo-lineas__svg { opacity: calc(var(--fondo-linea-intensidad) * 1.15); }
}

/* Alto contraste de Windows: el sistema fuerza los colores y las curvas salen
   como rayones blancos sobre el formulario. Ahí el fondo sobra. */
@media (forced-colors: active) {
  .fondo-lineas { display: none; }
}

/* Nunca en papel: gasta tinta y no aporta nada. */
@media print {
  .fondo-lineas { display: none; }
}

/* ════════════════════════════════════════════ Dónde se monta: los anfitriones

   Estas reglas NO están en sartori.css a propósito: viven acá para que agregar
   o sacar el fondo sea agregar o sacar un <link> y una línea de PHP, sin tocar
   la hoja compartida. Todas repiten la especificidad de sartori.css o la
   suben, y esta hoja carga después — por eso ganan. */

/* ── Login (COMS / IES / Myrna / panel de Helsek) ───────────────────────────

   Acá hay una restricción de la que sale todo lo demás: el telón NO puede ir
   adentro de `.login-fondo`, porque ese div lo abre `acl/login_acl.php`, que
   es una vista compartida que esta tanda no toca. El fondo se cuelga entonces
   del `<body>`, como hermano del login.

   El problema de colgarlo del body es que `.login-fondo` trae un degradé
   OPACO (el cielo nocturno de la marca): cualquier cosa detrás de él no se ve.
   Así que el telón no va detrás del login sino **entre el cielo y la tarjeta**,
   y las tres capas se ordenan con z-index explícitos:

       .login-fondo  →  sin z-index         (su cielo, al fondo)
       .fondo-lineas--fijo  →  z-index: 1   (las líneas, en el medio)
       .login-caja   →  z-index: 2          (la tarjeta, adelante)

   La clave es que `.login-fondo` quede con `z-index: auto`: así NO crea un
   contexto de apilamiento y la tarjeta de adentro puede competir de igual a
   igual con el telón de afuera. Si alguien le pone un `z-index`, un `filter` o
   un `transform`, la tarjeta queda encerrada y el fondo la tapa. */
.login-fondo {
  position: relative;   /* z-index: auto a propósito. Ver arriba. */
}
.login-fondo .login-caja {
  position: relative;
  z-index: 2;
}

/* `fixed` y no `absolute`: el telón se ancla al viewport, así que no importa
   cuánto crezca el formulario (un error de login agrega una alerta y lo
   estira) ni hace falta que nadie lo recorte. De paso no puede empujar la
   página a lo ancho, que es de donde sale el scroll horizontal en el celular. */
.fondo-lineas--fijo {
  position: fixed;
  z-index: 1;
}

/* ── Telón de PÁGINA del portal público ────────────────────────────────────
   Pedido de Santino (2026-09-18): "quiero que todo sea azul y que esté la
   animación en toda la página". Antes el telón vivía adentro del hero, o sea
   en la primera pantalla y nada más; de ahí para abajo la página cortaba a
   blanco.

   LA DIFERENCIA CON `--fijo`, que es la del login: allá el telón se cuela
   ENTRE un cielo opaco (`.login-fondo`) y la tarjeta, y por eso va en
   `z-index: 1`. Acá el telón ES el cielo: se trae el degradé de la marca
   puesto en su propio `background` y se va al fondo de todo.

   ⚠️ `z-index: -1` y no 0, y no es lo mismo. `.portal-pie` (un `<footer>`) y
   `.portal-banda-corta` (un `<div>`) NO tienen `position`, así que pintan en
   los pasos 4 y 7 del orden de pintado — antes que cualquier posicionado con
   z-index 0 o auto. Con 0, el telón les pasaría por arriba del texto. Con -1
   pinta en el paso 3: detrás del contenido y delante del canvas.

   ⚠️ Eso depende de que el color de piso viva en el <body> y se propague al
   canvas del navegador. `html` no declara `background` en ninguna de las tres
   ramas. Si alguien se lo pone, el piso queda DELANTE del telón y las líneas
   desaparecen enteras sin que falle nada.

   ⚠️ Va como hermano directo del <body>. Cualquier ancestro con `transform`,
   `filter`, `perspective`, `backdrop-filter`, `will-change` o
   `contain: paint` se convierte en bloque contenedor de un `fixed` y el telón
   deja de estar fijo. `.portal-topbar` tiene `backdrop-filter`: metido ahí
   adentro el síntoma aparece lejos de la causa.

   ⚠️ La altura va en `lvh` y no en `inset: 0`. Con `slice` la escala del SVG
   es max(ancho/696, alto/316): si la caja cambia de alto al scrollear, el
   dibujo entero hace zoom. `lvh` es la altura con la barra de URL colapsada y
   no se mueve nunca; `svh` dejaría una franja sin líneas abajo. El `vh` de
   arriba es el respaldo para los navegadores sin unidades nuevas. */
.fondo-lineas--portal {
  position: fixed;
  z-index: -1;
  top: 0;
  left: 0;
  right: 0;
  bottom: auto;
  height: 100vh;
  height: 100lvh;

  /* Los dos resplandores se mudaron acá desde el <div> del halo. Dibujan lo
     mismo y ahorran una capa compuesta del tamaño del viewport con
     `will-change: transform` animándose a 34 s. */
  background:
    radial-gradient(1100px 620px at 82% -6%, var(--portal-brillo-a, rgba(143, 160, 238, .26)), transparent 66%),
    radial-gradient(900px 560px at -8% 94%, var(--portal-brillo-b, rgba(64, 89, 201, .26)), transparent 70%),
    var(--portal-cielo, linear-gradient(168deg, #1A2140, #222B4E 58%, #1A2140));
}

/* Le hace lugar al emblema, igual que `--hero` hacía en la primera pantalla.
   `.hero-escena` (el oso de COMS) y `.hero-diente` (Myrna y Erica) viven en el
   MISMO rincón de arriba a la derecha y las dos se esconden por debajo de
   1100 px: una sola regla sirve a las tres marcas.

   Diferencia con la máscara del hero: ésta es del viewport y por lo tanto se
   aplica durante todo el scroll, no sólo arriba. Por eso el piso no baja a
   `transparent` sino a .30 — así el rincón se lee como luz de ambiente y no
   como un agujero en el dibujo tres pantallas más abajo. */
@media (min-width: 1101px) {
  .fondo-lineas--portal {
    -webkit-mask-image: radial-gradient(120% 140% at 12% 76%, #000 38%, rgba(0, 0, 0, .55) 72%, rgba(0, 0, 0, .30) 100%);
            mask-image: radial-gradient(120% 140% at 12% 76%, #000 38%, rgba(0, 0, 0, .55) 72%, rgba(0, 0, 0, .30) 100%);
  }
}

/* ── Hero del portal público ────────────────────────────────────────────────
   `.portal-hero-banda` ya es `position: relative` con `overflow: hidden`; se
   repite por si alguien mueve esas reglas. Lo que sí hace falta es levantar el
   contenido, porque ahí adentro vive la escena del oso y la luna. */
.portal-hero-banda {
  position: relative;
  overflow: hidden;
}
.portal-hero-banda > .portal-ancho {
  position: relative;
  z-index: 1;
}

/* Le hace lugar a la escena del oso.

   La escena (`.hero-escena`) está arriba a la derecha del hero y tiene un
   `drop-shadow` de 70 px; con las líneas pasándole por atrás a intensidad
   plena, el oso pierde silueta y las dos cosas se pelean. Esta máscara apaga
   el fondo justo en ese rincón, de forma gradual: el fondo queda fuerte abajo
   a la izquierda (detrás del título, donde suma) y se desvanece hacia
   arriba a la derecha (donde estorba).

   Sólo arriba de 1100 px porque de ahí para abajo sartori.css esconde la
   escena — sin oso, no hay a quién hacerle lugar y el fondo va entero. */
@media (min-width: 1101px) {
  .fondo-lineas--hero {
    -webkit-mask-image: radial-gradient(115% 135% at 14% 78%, #000 34%, rgba(0,0,0,.28) 70%, transparent 100%);
    mask-image: radial-gradient(115% 135% at 14% 78%, #000 34%, rgba(0,0,0,.28) 70%, transparent 100%);
  }
}
