/* ═══════════════════════════════════════════════════════════════════════
   NN MOTION — el lenguaje premium de aulavirtual, hecho reusable
   ═══════════════════════════════════════════════════════════════════════
   Archivo NUEVO a propósito. css/style.css está cacheado immutable a un año
   con ?v= en 56 HTML y bumpearlo ya rompió producción dos veces: una URL
   nueva no tiene ese problema.

   Qué hay acá: los cuatro gestos que hacen que aulavirtual.html se lea
   premium, sacados de sus ~100 keyframes locales y reducidos a lo que
   realmente se repite.

     nnEntra   la entrada con desenfoque (era avEntraSuave). ES la firma.
     .nn-shine el barrido de luz diagonal (era avDestello/avBarrido)
     .nn-ring  el anillo que se expande (era avAnillo)
     .nn-float la flotación lenta (era avFlota)

   ── LA REGLA QUE NO SE NEGOCIA ────────────────────────────────────────
   Todo falla abierto. El estado por defecto de cada selector de acá es la
   página TERMINADA: sin JS, sin la clase .nn-go, con un error a mitad de
   camino o con la pestaña oculta, se ve todo completo. Ninguna pieza queda
   en opacity:0 esperando que algo la rescate. Esa trampa ya dejó un panel
   en blanco una vez.

   Por eso las animaciones de entrada cuelgan SIEMPRE de un ancestro con
   .nn-go, que lo pone el JS. Nunca al revés.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* La curva premium. No es nueva: aulavirtual la llama --e-out y mi-cuenta
     --ease-suave, y las dos son este mismo cubic-bezier. Acá recibe un
     nombre compartido para dejar de tener la misma curva con dos apellidos. */
  --nn-e-out:   cubic-bezier(.16, 1, .3, 1);
  --nn-e-salida: cubic-bezier(.22, 1, .36, 1);   /* = --ease-entrada de mi-cuenta */

  --nn-dur-entrada: .62s;   /* entrada de una pieza */
  --nn-dur-vista:   .34s;   /* cambio de vista entera: más corto, se repite mucho */
  --nn-paso:        52ms;   /* escalón entre hermanos de una grilla */
  --nn-paso-max:    10;     /* tope de escalones: ver .nn-in */
}

/* ══ 1. LA ENTRADA CON DESENFOQUE ═════════════════════════════════════
   El gesto que separa "apareció" de "entró". El blur es lo que lo hace
   caro de imitar y barato de ejecutar: sale de compositor, no de layout.

   Se compone con translateY corto — 14px, no 40 — porque a esta distancia
   el ojo lee "asentó" y no "voló desde abajo". Lo segundo cansa cuando la
   grilla se repinta veinte veces por sesión, que es justo el caso de
   mi-cuenta y del aula. */
@keyframes nnEntra {
  from { opacity: 0; transform: translateY(14px); filter: blur(7px); }
  to   { opacity: 1; transform: none;             filter: blur(0);   }
}

/* Variante con escala, para tarjetas grandes que ocupan su propio bloque.
   En piezas chicas la escala se lee como un tirón; en una card de ancho
   completo se lee como que el objeto se acomodó. */
@keyframes nnEntraCard {
  from { opacity: 0; transform: translateY(18px) scale(.975); filter: blur(9px); }
  to   { opacity: 1; transform: none;                         filter: blur(0);   }
}

/* El elemento es visible SIEMPRE. La animación entra únicamente si un
   ancestro tiene .nn-go — la puerta que abre el JS cuando el contenido ya
   está en su lugar. Sin JS esto no matchea nunca y la página se ve entera. */
.nn-go .nn-in {
  animation: nnEntra var(--nn-dur-entrada) var(--nn-e-out) both;
  /* El escalón sale de --i, que lo escribe el JS al recorrer los hermanos.
     Con el tope, la card número 30 de una lista larga no espera 1,5s: a
     partir del escalón 10 entran todas juntas. Sin el tope, el final de una
     lista larga se siente roto, no coreografiado. */
  animation-delay: calc(min(var(--i, 0), var(--nn-paso-max)) * var(--nn-paso));
}
.nn-go .nn-in-card {
  animation: nnEntraCard var(--nn-dur-entrada) var(--nn-e-out) both;
  animation-delay: calc(min(var(--i, 0), var(--nn-paso-max)) * var(--nn-paso));
}

/* will-change solo mientras dura la entrada. Dejarlo puesto en una grilla
   de 30 cards mantiene 30 capas de compositor vivas para siempre y le come
   memoria al scroll. */
.nn-go .nn-in, .nn-go .nn-in-card { will-change: opacity, transform, filter; }
.nn-in.nn-listo, .nn-in-card.nn-listo { will-change: auto; }


/* ══ 2. EL BARRIDO DE LUZ ═════════════════════════════════════════════
   Una diagonal clara que cruza la superficie cada tanto. Es lo que hace
   que un rectángulo quieto parezca un material y no un div.

   Va en ::after para no pedirle un hijo extra al HTML, y con
   pointer-events:none porque si no se come los clicks de la card. */
.nn-shine { position: relative; overflow: hidden; }
.nn-shine::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: linear-gradient(105deg,
    transparent 42%,
    rgba(255, 255, 255, .10) 50%,
    transparent 58%);
  transform: translateX(-140%);
  animation: nnBarrido 7s ease-in-out infinite;
}
/* Variante verde, para lo que ya es de la marca (progreso, logros, XP) */
.nn-shine-verde::after {
  background: linear-gradient(105deg,
    transparent 42%,
    rgba(0, 230, 118, .22) 50%,
    transparent 58%);
}
/* El barrido pasa y se va: 62% del ciclo es pausa. Un brillo continuo se
   convierte en ruido a los diez segundos. */
@keyframes nnBarrido {
  0%, 62%   { transform: translateX(-140%); }
  86%, 100% { transform: translateX(140%); }
}

/* Línea que recorre un borde. Para cabeceras de sección y barras de
   progreso: dice "esto está vivo" sin mover nada de sitio. */
.nn-recorre { position: relative; overflow: hidden; }
.nn-recorre::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(0, 230, 118, .55), transparent);
  transform: translateX(-100%);
  animation: nnRecorre 7s ease-in-out infinite;
}
@keyframes nnRecorre {
  0%, 10%   { transform: translateX(-100%); }
  90%, 100% { transform: translateX(100%); }
}


/* ══ 3. EL ANILLO ═════════════════════════════════════════════════════
   Un pulso que se expande desde un punto. Marca dónde está la acción
   principal sin agrandar nada ni pedir atención con color. */
.nn-ring { position: relative; }
.nn-ring::before {
  content: ""; position: absolute; inset: -7px; border-radius: inherit;
  border: 1px solid rgba(0, 230, 118, .5); pointer-events: none;
  animation: nnAnillo 2.6s ease-out infinite;
}
@keyframes nnAnillo {
  0%        { opacity: .9; transform: scale(.85); }
  70%, 100% { opacity: 0;  transform: scale(1.5); }
}


/* ══ 4. LA FLOTACIÓN ══════════════════════════════════════════════════
   9px arriba y abajo, 9 segundos. Lo suficientemente lento como para que
   no se note mirándolo de frente y sí se note por el rabillo del ojo. */
.nn-float { animation: nnFlota 9s ease-in-out infinite; }
@keyframes nnFlota {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -9px; }
}


/* ══ 5. VIEW TRANSITIONS ══════════════════════════════════════════════
   Acá está el verdadero motivo de este archivo.

   mi-cuenta y el aula repintan secciones enteras con innerHTML después de
   cada fetch: 137 y 90 puntos respectivamente. Ese patrón NUNCA pudo tener
   animación de salida, porque para cuando querés animar algo ya lo
   borraste del DOM. Es exactamente el hueco que llena AnimatePresence en
   React — y exactamente lo que la API nativa hace sin runtime.

   El navegador saca una foto del antes, deja que el JS reemplace el DOM, y
   cruza las dos fotos con estos pseudo-elementos. El JS de la página no
   cambia: se envuelve la asignación y listo.

   El scoping va por data-nnvt en <html>, que pone y saca js/nn-motion.js.
   Sin ese atributo, ninguna regla de acá matchea — así una view transition
   que dispare cualquier otra cosa del sitio en el futuro se queda con el
   crossfade por defecto en vez de heredar esta coreografía. */

@media (prefers-reduced-motion: no-preference) {

  /* ── Cambio de vista (el router de mi-cuenta, el índice del aula) ──
     Lo viejo se va hacia arriba, lo nuevo entra desde abajo: el gesto dice
     "avanzaste", no "se recargó". Corto —340ms— porque esto se dispara en
     cada click del nav y una transición lenta que se repite se transforma
     en un peaje. */
  html[data-nnvt="vista"]::view-transition-old(root) {
    animation: nnVistaSale var(--nn-dur-vista) var(--nn-e-salida) both;
  }
  html[data-nnvt="vista"]::view-transition-new(root) {
    animation: nnVistaEntra var(--nn-dur-vista) var(--nn-e-out) both;
  }
  @keyframes nnVistaSale  { to   { opacity: 0; transform: translateY(-10px); filter: blur(5px); } }
  @keyframes nnVistaEntra { from { opacity: 0; transform: translateY(14px);  filter: blur(7px); } }

  /* ── Repintado de un bloque de datos (llegó un fetch) ──────────────
     OJO con lo que se anima acá: el objetivo es nn-bloque, NO root.

     js/nn-motion.js le pone view-transition-name:nn-bloque al contenedor
     que se está repintando. Eso lo saca de la captura de root y lo
     convierte en su propia capa. Animar root en este caso desvanecería
     la página ENTERA por un cambio de dos líneas en una card — que es
     justo el error que hace que las view transitions tengan fama de
     lentas. El resto de la página queda en el crossfade por defecto,
     que entre dos fotos idénticas es invisible y gratis.

     Sin desplazamiento: el contenido se actualizó donde ya estaba, y
     moverlo mentiría sobre lo que pasó. Solo el desenfoque, que es lo
     que comunica "esto se rehízo".

     Las dos capas corren encimadas, así que la salida es más rápida que
     la entrada: si duraran igual, el solapamiento se ve como un fantasma
     doble. */
  html[data-nnvt="datos"]::view-transition-old(nn-bloque) {
    animation: nnDatosSale .16s ease-in both;
  }
  html[data-nnvt="datos"]::view-transition-new(nn-bloque) {
    animation: nnDatosEntra .3s var(--nn-e-out) both;
  }
  @keyframes nnDatosSale  { to   { opacity: 0; filter: blur(4px); } }
  @keyframes nnDatosEntra { from { opacity: 0; filter: blur(6px); } }

  /* El bloque suele cambiar de alto al repintarse (llegaron 3 filas donde
     había 1). El grupo interpola esa caja: sin esto el contenido nuevo
     aparece con el alto viejo y salta al final. */
  html[data-nnvt="datos"]::view-transition-group(nn-bloque) {
    animation-duration: .3s;
    animation-timing-function: var(--nn-e-out);
  }

  /* ── Abrir una clase del aula ──────────────────────────────────────
     Entrar a una clase es ir hacia adentro, no hacia adelante: por eso
     escala en vez de desplazar. Es el mismo gesto que usa iOS al abrir
     una app desde su ícono. */
  html[data-nnvt="clase"]::view-transition-old(nn-bloque) {
    animation: nnClaseSale .2s ease-in both;
  }
  html[data-nnvt="clase"]::view-transition-new(nn-bloque) {
    animation: nnClaseEntra .42s var(--nn-e-out) both;
  }
  /* La clase nueva casi nunca mide lo mismo que el esqueleto que reemplaza.
     Sin interpolar el grupo, el contenido entra con el alto viejo y pega un
     salto al final. */
  html[data-nnvt="clase"]::view-transition-group(nn-bloque) {
    animation-duration: .42s;
    animation-timing-function: var(--nn-e-out);
  }
  @keyframes nnClaseSale  { to   { opacity: 0; transform: scale(1.015); filter: blur(6px); } }
  @keyframes nnClaseEntra { from { opacity: 0; transform: scale(.985);  filter: blur(8px); } }
}

/* Nada de ::view-transition-group(*) global acá: sin el scope de data-nnvt
   le cambiaría la duración a cualquier view transition que el sitio dispare
   en el futuro, incluidas las que no vengan de este archivo. */


/* ══ 6. EL INTERRUPTOR ════════════════════════════════════════════════
   Una sola regla apaga TODO lo de este archivo. No hay versión "suave" de
   una coreografía de entrada: o corre o no corre.

   Ojo con la última regla: sin ella, una view transition con reduced
   motion sigue haciendo el crossfade por defecto del navegador. */
@media (prefers-reduced-motion: reduce) {
  .nn-go .nn-in,
  .nn-go .nn-in-card { animation: none; opacity: 1; transform: none; filter: none; }
  .nn-shine::after,
  .nn-recorre::after,
  .nn-ring::before   { animation: none; display: none; }
  .nn-float          { animation: none; }

  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
