/**
 * LM Contable — Loader de pantalla completa (splash), para transiciones
 * "duras" donde no hay nada más en pantalla que mostrar (arranque de Admin/
 * Portal Cliente, login, recarga de "Sincronizar", cierre de sesión). Ver
 * src/app-loader.js (API y tiempos: sin cambios).
 *
 * Diseño v6 (2026-10-08, elegido por el usuario sobre una referencia de
 * esfera de malla orgánica): al centro una esfera de nodos unidos por hilos
 * curvos ("tu información, conectada") que dibuja src/app-loader-net.js en
 * un <canvas>; detrás, la cinta azul del panel del inicio de sesión
 * barriendo desde la derecha, sobre un fondo liso que sigue el tema (claro
 * o navy) para que el corte duro hacia la app (sin fundido de salida,
 * pedido explícito) no destelle.
 *
 * La cinta es un SVG inline en el markup y la esfera un script local
 * pequeño: nada depende de la red ni de fuentes externas. El CSS solo anima
 * transform/opacity.
 */

.lm-app-loader {
  /* Tokens locales, tema claro por defecto. */
  --lml-bg: #f8fafc;
  --lml-glow: rgba(37, 99, 235, .10);
  --lml-ribbon-opacity: .5;
  --lml-text: #475569;
  --lml-text-strong: #0f172a;
  --lml-check: #2563eb;
  --lml-track: rgba(37, 99, 235, .12);
  --lml-ease-out: cubic-bezier(.23, 1, .32, 1);
  --lml-ease-expo: cubic-bezier(.16, 1, .3, 1);

  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--lml-bg);
  visibility: hidden;
  pointer-events: none;
  -webkit-tap-highlight-color: transparent;
}

:root[data-theme="dark"] .lm-app-loader {
  --lml-bg: linear-gradient(160deg, #0b1120 0%, #111827 55%, #0f1b3a 100%);
  --lml-glow: rgba(37, 99, 235, .30);
  --lml-ribbon-opacity: 1;
  --lml-text: #94a3b8;
  --lml-text-strong: #f1f5f9;
  --lml-check: #60a5fa;
  --lml-track: rgba(147, 197, 253, .14);
}

.lm-app-loader.is-visible {
  visibility: visible;
  pointer-events: auto;
}

/* ---- Escenario: resplandor + cinta ------------------------- */

.lm-app-loader-stage {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Resplandor azul arriba a la derecha (fuente de luz de la cinta). */
.lm-app-loader-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(60% 55% at 86% 14%, var(--lml-glow), transparent 70%),
    radial-gradient(45% 40% at 4% 100%, color-mix(in srgb, var(--lml-glow) 55%, transparent), transparent 70%);
}

/* Sobrescaneo a la izquierda: la entrada desliza la cinta hasta 6% y el
   borde del SVG nunca debe quedar a la vista. */
.lm-app-loader-ribbon {
  position: absolute;
  top: 0;
  left: -8%;
  z-index: 1;
  width: 108%;
  height: 100%;
  opacity: var(--lml-ribbon-opacity);
}

/* Celular: la cinta queda como un gran chevrón que abraza la esfera; se
   atenúa un poco para no competir con el estado. */
@media (max-width: 640px) {
  :root[data-theme="dark"] .lm-app-loader { --lml-ribbon-opacity: .8; }
  .lm-app-loader { --lml-ribbon-opacity: .4; }
}

/* ---- Composición central -------------------------------------------- */

.lm-app-loader-wrap {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Compensa óptica: el bloque se ve centrado un poco por encima del medio. */
  margin-top: -3vh;
}

/* ---- Red: "tu información, conectada" --------------------------------- */
/* El ícono del loader (2026-10-08, sobre una referencia de esfera de malla
   orgánica): una esfera de nodos unidos por hilos curvos que gira y respira
   mientras carga. Cada nodo es un dato del negocio y cada hilo una
   relación: lo que hace el portal. En "Listo" la esfera se calma, queda
   redonda y sus hilos se iluminan. Se dibuja en <canvas> con un script
   inline (sin red); los colores salen de estos tokens. */

.lm-app-loader {
  --lml-net-line: 30, 58, 138;      /* RGB de los hilos */
  --lml-net-node: 15, 23, 42;       /* RGB de los nodos */
  --lml-net-hub: 37, 99, 235;       /* RGB de los nodos principales */
  --lml-net-blend: source-over;
  --lml-net-alpha: .55;
}
:root[data-theme="dark"] .lm-app-loader {
  --lml-net-line: 147, 197, 253;
  --lml-net-node: 226, 232, 240;
  --lml-net-hub: 96, 165, 250;
  --lml-net-blend: lighter;
  --lml-net-alpha: .5;
}

.lm-app-loader-net {
  display: block;
  width: 124px;
  height: 124px;
  margin-bottom: 16px;
}

/* ---- Estado + barra ------------------------------------------------- */

.lm-app-loader-status {
  position: relative;
  height: 20px;
  min-width: 220px;
  margin-top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lm-app-loader-msg {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--lml-text);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity .3s var(--lml-ease-out), transform .3s var(--lml-ease-out);
  white-space: nowrap;
}

.lm-app-loader-msg.is-active { opacity: 1; transform: translateY(0); }

.lm-app-loader-msg--done { color: var(--lml-text-strong); font-weight: 600; }

.lm-app-loader-check {
  display: inline-flex;
  width: 15px;
  height: 15px;
  color: var(--lml-check);
  opacity: 0;
  transform: scale(.5);
}
.lm-app-loader-check svg { display: block; width: 100%; height: 100%; }

.lm-app-loader-msg--done.is-active .lm-app-loader-check { animation: lm-loader-check-pop .38s cubic-bezier(.34, 1.56, .64, 1) .05s forwards; }
@keyframes lm-loader-check-pop { to { opacity: 1; transform: scale(1); } }

/* is-settled: el check ya hizo su pop en la página anterior (playLogoutTransition()
   lo deja asentado antes de navegar, ver app-loader.js) -- a diferencia de
   una transition, una animation SÍ se reproduce en el primerísimo cálculo de
   estilo de un documento nuevo aunque la clase ya venga puesta desde el
   <script> inline síncrono. Sin esto, index.html volvía a reproducir el pop
   del check al llegar -- un segundo "Listo" encima del que ya se había visto
   (reportado). Solo lo usa el <script> inline de index.html para la
   continuación post-logout. */
.lm-app-loader.is-settled .lm-app-loader-msg--done.is-active .lm-app-loader-check { animation: none; opacity: 1; transform: none; }

.lm-app-loader-progress-track {
  width: 160px;
  height: 2px;
  margin-top: 14px;
  position: relative;
  overflow: hidden;
  border-radius: 999px;
  background: var(--lml-track);
}

/* La barra ocupa todo el ancho y se recorta con scaleX (solo transform):
   indeterminada mientras is-playing (la duración real varía), y al llamar
   hideAppLoader() is-finishing la completa al 100% antes de que el overlay
   desaparezca. is-finishing va DESPUÉS de is-playing a propósito:
   hideAppLoader() agrega is-finishing sin quitar is-playing. */
.lm-app-loader-progress-fill {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, rgba(96, 165, 250, 0) 0%, #60a5fa 45%, #2563eb 100%);
  transform-origin: left center;
  transform: translateX(-40%) scaleX(.4);
  opacity: 0;
}

.lm-app-loader.is-playing .lm-app-loader-progress-fill { animation: lm-loader-progress 1.7s cubic-bezier(.65, 0, .35, 1) infinite; }
.lm-app-loader.is-finishing .lm-app-loader-progress-fill {
  animation: none;
  transform: translateX(0) scaleX(1);
  opacity: 1;
  transition: transform .35s var(--lml-ease-out), opacity .2s ease;
}

@keyframes lm-loader-progress {
  0%   { transform: translateX(-40%) scaleX(.4); opacity: 0; }
  14%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { transform: translateX(100%) scaleX(.4); opacity: 0; }
}

/* ---- Entrada (solo con is-entering) ---------------------------------- */

/* Gateada por is-entering, NO por is-playing -- separadas a propósito: la
   recarga de "Sincronizar" (syncPortal() en admin.js) muestra el loader en
   la página actual, recarga a los ~60ms, y la página nueva lo vuelve a
   mostrar de forma síncrona -- si la entrada se gatea con is-playing, ese
   segundo "aparecer" la reinicia desde opacidad 0 y se ve como un salto/
   parpadeo (reportado). showAppLoader({instant:true}) y los <script> inline
   de continuidad omiten is-entering: el overlay aparece ya asentado. Todo
   el estado base de arriba ES el estado final; aquí solo se anima hacia él.
   Sin fundido de salida (pedido explícito): hideAppLoader() quita
   is-visible de golpe tras la pausa de "Listo". */
.lm-app-loader.is-entering .lm-app-loader-ribbon { animation: lm-loader-ribbon-in 1.1s var(--lml-ease-expo) both; }
.lm-app-loader.is-entering .lm-app-loader-net { animation: lm-loader-fade .7s var(--lml-ease-expo) both; }
.lm-app-loader.is-entering .lm-app-loader-status,
.lm-app-loader.is-entering .lm-app-loader-progress-track { animation: lm-loader-rise .55s var(--lml-ease-out) .14s both; }

@keyframes lm-loader-fade { from { opacity: 0; } }
@keyframes lm-loader-rise { from { opacity: 0; transform: translateY(6px); } }
@keyframes lm-loader-ribbon-in { from { opacity: 0; transform: translateX(6%); } }

/* ---- Movimiento reducido: sin desplazamientos, solo fundidos ---------- */

@media (prefers-reduced-motion: reduce) {
  .lm-app-loader.is-entering .lm-app-loader-ribbon,
  .lm-app-loader.is-entering .lm-app-loader-net,
  .lm-app-loader.is-entering .lm-app-loader-status,
  .lm-app-loader.is-entering .lm-app-loader-progress-track { animation: lm-loader-fade .3s ease-out both; }
  .lm-app-loader-msg { transform: none; transition: opacity .2s ease; }
  .lm-app-loader-check,
  .lm-app-loader-msg--done.is-active .lm-app-loader-check { animation: none; opacity: 1; transform: none; }
  .lm-app-loader.is-playing:not(.is-finishing) .lm-app-loader-progress-fill { animation: none; transform: scaleX(.6); opacity: 1; }
  .lm-app-loader.is-finishing .lm-app-loader-progress-fill { transition: opacity .2s ease; }
}

/* Entrada suave del contenido real al reemplazar un skeleton/estado previo
   -- decorativa únicamente, nunca gatea cuándo el contenido real queda
   visible/interactivo (eso sigue pasando exactamente igual que hoy). Se
   quita y se vuelve a agregar en JS (con un reflow forzado en medio) para
   que reinicie limpio en cada navegación, en vez de solo la primera vez. */
@keyframes lm-content-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.lm-content-enter { animation: lm-content-enter .15s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .lm-content-enter { animation-duration: .01s; }
}
