/*
 * movimiento.css — sistema de movimiento de ioClock (portal, login y checado).
 *
 * Solo trae TOKENS, @keyframes comunes y UTILIDADES opt-in (.mov-*). Por sí mismo no anima
 * nada: una pantalla se mueve solo si usa una clase de aquí o los tokens en su propio CSS.
 * El plan por pantalla, las reglas y lo que NO se anima están en Docs/movimiento-plan.md.
 *
 * Los mismos valores los usa el agente WPF (KeySpline = los cuatro números del cubic-bezier),
 * para que el portal, el checado y el agente se sientan iguales.
 *
 * Reglas que este archivo da por hechas:
 *   - Solo se animan transform y opacity (nunca width/height/top/left/margin).
 *   - Las entradas usan relleno "backwards": al terminar no queda ningún transform puesto
 *     (un transform residual vuelve al elemento contenedor de los position:fixed de adentro
 *     y lo deja en una capa de composición para siempre).
 *   - Ninguna transición pasa de 400 ms. Los BUCLES de carga (brillo del esqueleto, giro)
 *     no son transiciones y no cuentan contra ese tope.
 *   - Con prefers-reduced-motion nada se desplaza ni escala: los tokens de distancia y escala
 *     se vuelven neutros y las duraciones bajan a 120 ms, así que todo queda en fundido corto.
 *     Quien use los tokens en su CSS hereda eso sin escribir su propio bloque.
 *   - Colores: solo tokens del tema (themes.css / tokens.css).
 */

:root {
  /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     DURACIONES — entrada
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  --mov-rapido: 120ms;   /* respuesta al pasar o presionar (feedback, no transición) */
  --mov-base:   180ms;   /* micro-interacciones: chips, pestañas, cifras, colores de estado */
  --mov-medio:  240ms;   /* menús, diálogos, paneles, avisos al entrar */
  --mov-lento:  320ms;   /* entrada de página, paneles grandes, gráficas */

  /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     DURACIONES — salida (~65 % de la entrada: irse cuesta menos que llegar)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  --mov-salida-base:  120ms;
  --mov-salida-medio: 160ms;
  --mov-salida-lento: 200ms;

  /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     CURVAS — nunca linear en una transición de UI
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  --mov-curva-entrar:   cubic-bezier(0.2, 0.8, 0.2, 1);   /* frena al llegar, sin rebote */
  --mov-curva-salir:    cubic-bezier(0.4, 0, 1, 1);       /* acelera al irse */
  --mov-curva-estandar: cubic-bezier(0.2, 0, 0, 1);       /* cambios de estado en su lugar */

  /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     ESCALONADO — 40 ms por elemento; tope 8 escalones (320 ms)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  --mov-escalon: 40ms;

  /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     DISTANCIAS Y ESCALAS — cortas: el movimiento orienta, no decora
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  --mov-distancia:       8px;    /* entrar subiendo: páginas, tarjetas, paneles */
  --mov-distancia-corta: 4px;    /* cifras, avisos, menús que bajan del topbar */
  --mov-escala-entrar:   0.98;   /* diálogos y paneles que aparecen en su lugar */
  --mov-press:           0.97;   /* presionar: botones y controles chicos */
  --mov-press-grande:    0.98;   /* presionar: tarjetas y el botón grande del checado */

  /* Al PASAR el mouse. También son tokens para que el movimiento reducido los apague: un
     transform escrito como literal no lo alcanza el bloque de abajo. */
  --mov-levantar:        -1px;   /* botones y tarjetas suben un punto */
  --mov-corrimiento:     3px;    /* la liga del menú lateral se corre a la derecha */
  --mov-crecer:          1.04;   /* botones redondos chicos (e-small) */
  --mov-crecer-chip:     1.03;   /* chips de filtro (ReportesV2) */

  /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     CARGA — un indicador aparece solo si la espera pasa de este umbral
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  --mov-umbral-carga: 300ms;

  /* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     BUCLES — indicadores de "sigo trabajando"; no son transiciones
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
  --mov-brillo: 1.4s;    /* barrido del esqueleto */
  --mov-giro:   0.8s;    /* vuelta completa de un ícono de recarga */
}

/* ══════════════════════════════════════════════════════════════
   @keyframes comunes
   Solo traen "from": el "to" es el estado natural del elemento
   (opacidad y transform propios), así no pisan lo que ya tenga.
   ══════════════════════════════════════════════════════════════ */
@keyframes mov-fundido {
  from { opacity: 0; }
}

@keyframes mov-subir {
  from { opacity: 0; transform: translateY(var(--mov-distancia)); }
}

@keyframes mov-subir-corto {
  from { opacity: 0; transform: translateY(var(--mov-distancia-corta)); }
}

@keyframes mov-escalar {
  from { opacity: 0; transform: scale(var(--mov-escala-entrar)); }
}

/* Menús y paneles que cuelgan del topbar: bajan un poco y crecen desde su ancla */
@keyframes mov-desplegar {
  from {
    opacity: 0;
    transform: translateY(calc(var(--mov-distancia-corta) * -1)) scale(var(--mov-escala-entrar));
  }
}

/* Barrido del esqueleto (bucle) */
@keyframes mov-brillo {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}

/* Giro de un ícono de recarga (bucle) */
@keyframes mov-girar {
  to { transform: rotate(360deg); }
}

/* ══════════════════════════════════════════════════════════════
   Utilidades de ENTRADA
   Corren una sola vez: cuando el elemento entra al DOM (o pasa de
   display:none a visible). En Blazor eso significa que NO se repiten
   en cada re-render; para repetirlas a propósito (dato nuevo, otro
   bloque elegido) se cambia el @key del elemento.
   --mov-retraso permite escalonar a mano (style="--mov-retraso:80ms").
   ══════════════════════════════════════════════════════════════ */

/* Entrada estándar: sube y aparece. Tarjetas, paneles, avisos. */
.mov-entrar {
  animation: mov-subir var(--mov-medio) var(--mov-curva-entrar) var(--mov-retraso, 0ms) backwards;
}

/* Contenido de página completo (.content-area con @key por tipo de página, MainLayout). */
.mov-pagina {
  animation: mov-subir var(--mov-lento) var(--mov-curva-entrar) backwards;
}

/* Solo opacidad: reemplazo de contenido en el mismo lugar (cross-fade), contenedores de grid. */
.mov-fundido {
  animation: mov-fundido var(--mov-base) var(--mov-curva-entrar) var(--mov-retraso, 0ms) backwards;
}

/* Aparece en su lugar creciendo apenas: diálogos, pantallas de resultado. */
.mov-escalar {
  animation: mov-escalar var(--mov-medio) var(--mov-curva-entrar) var(--mov-retraso, 0ms) backwards;
}

/* Menú o panel anclado al topbar (campanita, menú de usuario). Ancla: --mov-origen. */
.mov-desplegar {
  transform-origin: var(--mov-origen, top right);
  animation: mov-desplegar var(--mov-medio) var(--mov-curva-entrar) backwards;
}

/* Una cifra que cambió: se usa con @key="valor" para que solo se mueva cuando cambia.
   inline-block porque transform no aplica a elementos inline; tabular-nums para que el
   ancho no brinque entre dígitos. */
.mov-cifra {
  display: inline-block;
  font-variant-numeric: tabular-nums;
  animation: mov-subir-corto var(--mov-base) var(--mov-curva-entrar) backwards;
}

/* Hijos directos entran escalonados: 40 ms cada uno, máximo 8 escalones (320 ms);
   del décimo en adelante entran todos con el último retraso.
   Nunca en filas de SfGrid ni en listas largas: solo tarjetas, indicadores, menús. */
.mov-escalonado > * {
  animation: mov-subir var(--mov-medio) var(--mov-curva-entrar) backwards;
  animation-delay: calc(var(--mov-i) * var(--mov-escalon));
}
.mov-escalonado > :nth-child(1)    { --mov-i: 0; }
.mov-escalonado > :nth-child(2)    { --mov-i: 1; }
.mov-escalonado > :nth-child(3)    { --mov-i: 2; }
.mov-escalonado > :nth-child(4)    { --mov-i: 3; }
.mov-escalonado > :nth-child(5)    { --mov-i: 4; }
.mov-escalonado > :nth-child(6)    { --mov-i: 5; }
.mov-escalonado > :nth-child(7)    { --mov-i: 6; }
.mov-escalonado > :nth-child(8)    { --mov-i: 7; }
.mov-escalonado > :nth-child(n+9)  { --mov-i: 8; }

/* Indicador de carga diferido: invisible durante --mov-umbral-carga; si la operación
   termina antes, nunca se ve (sin parpadeo). Sirve para overlays y avisos de "Cargando". */
.mov-diferido {
  animation: mov-fundido var(--mov-base) var(--mov-curva-entrar) var(--mov-umbral-carga) backwards;
}

/* ══════════════════════════════════════════════════════════════
   Utilidad de ENTRADA y SALIDA para elementos que se quedan en el DOM
   (overlay del menú en móvil, paneles que solo se ocultan). La salida
   sale gratis: es un cambio de clase en el mismo render, sin esperar
   al servidor. Lo que Blazor quita con @if no tiene salida animada.
   ══════════════════════════════════════════════════════════════ */
.mov-conmutar {
  transition: opacity var(--mov-medio) var(--mov-curva-entrar),
              transform var(--mov-medio) var(--mov-curva-entrar),
              visibility 0s;
}
.mov-conmutar.is-oculto {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(calc(var(--mov-distancia-corta) * -1));
  transition: opacity var(--mov-salida-medio) var(--mov-curva-salir),
              transform var(--mov-salida-medio) var(--mov-curva-salir),
              visibility 0s var(--mov-salida-medio);
}

/* ══════════════════════════════════════════════════════════════
   PRESIONAR — respuesta inmediata en el cliente, sin viaje al servidor.
   Escala sin mover el diseño (transform no empuja a los vecinos).
   La lista de transiciones incluye color y fondo para no borrar los de
   un botón que ya los tenía. El estado presionado debe llevar también
   un cambio de color o fondo: con movimiento reducido la escala es 1.
   ══════════════════════════════════════════════════════════════ */
.mov-press,
.mov-press-grande {
  transition: transform var(--mov-rapido) var(--mov-curva-estandar),
              background-color var(--mov-rapido) var(--mov-curva-estandar),
              border-color var(--mov-rapido) var(--mov-curva-estandar),
              color var(--mov-rapido) var(--mov-curva-estandar),
              box-shadow var(--mov-base) var(--mov-curva-estandar),
              opacity var(--mov-base) var(--mov-curva-estandar);
  touch-action: manipulation;              /* sin la espera del doble toque en móvil */
  -webkit-tap-highlight-color: transparent; /* el feedback lo da la escala, no el gris del navegador */
}
.mov-press:active:not(:disabled):not([aria-disabled="true"]) {
  transform: scale(var(--mov-press));
}
.mov-press-grande:active:not(:disabled):not([aria-disabled="true"]) {
  transform: scale(var(--mov-press-grande));
}

/* ══════════════════════════════════════════════════════════════
   ESQUELETO — reserva el lugar del contenido mientras carga (sin CLS).
   El tamaño lo pone quien lo usa (alto/ancho del contenido real).
   El contenedor lleva aria-busy="true" y el esqueleto aria-hidden="true".
   ══════════════════════════════════════════════════════════════ */
.mov-esqueleto {
  position: relative;
  overflow: hidden;
  min-height: 12px;
  border-radius: var(--radius-sm);
  background: var(--line);
}
.mov-esqueleto::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg,
    transparent 0,
    color-mix(in srgb, var(--card) 70%, transparent) 50%,
    transparent 100%);
  animation: mov-brillo var(--mov-brillo) var(--mov-curva-estandar) infinite;
}

/* Ícono de recarga girando mientras se consulta (bucle: giro constante, por eso linear). */
.mov-girando {
  display: inline-block;
  animation: mov-girar var(--mov-giro) linear infinite;
}

/* ══════════════════════════════════════════════════════════════
   MOVIMIENTO REDUCIDO — nada se desplaza ni escala; a lo más un
   fundido de 120 ms. Los tokens neutros alcanzan a todo el CSS que
   los use; las utilidades de bucle se apagan aquí.
   ══════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  :root {
    --mov-base:  120ms;
    --mov-medio: 120ms;
    --mov-lento: 120ms;

    --mov-salida-base:  80ms;
    --mov-salida-medio: 80ms;
    --mov-salida-lento: 80ms;

    --mov-escalon: 0ms;

    --mov-distancia:       0px;
    --mov-distancia-corta: 0px;
    --mov-escala-entrar:   1;
    --mov-press:           1;
    --mov-press-grande:    1;

    --mov-levantar:        0px;
    --mov-corrimiento:     0px;
    --mov-crecer:          1;
    --mov-crecer-chip:     1;
  }

  /* El esqueleto se queda quieto (sigue reservando el lugar) */
  .mov-esqueleto::after { animation: none; display: none; }

  /* El ícono no gira: el estado lo dicen el texto y aria-busy */
  .mov-girando { animation: none; }
}

/* Impresión (reportes): nada a medio animar en el papel */
@media print {
  [class^="mov-"], [class*=" mov-"],
  [class^="mov-"]::after, [class*=" mov-"]::after {
    animation: none !important;
    transition: none !important;
  }
}
