/* Pantalla de carga de entrada: anillo con tres puntos orbitando.
   Adaptación del preloader "Three dot animated responsive preloader" de Pieter
   Biesemans (codepen.io/pieter-biesemans/pen/rQaXgX) a la paleta de Centinel.
   Se conservan sus proporciones en `em` —el tamaño entero se gobierna desde el
   `font-size` del anillo, de ahí que sea "responsive"— y se cambian sus azules
   (#335696 / #97e2dd) por el turquesa de la marca. */

/* OCULTA por defecto: solo se enseña al venir de iniciar sesión, y eso lo decide
   el script del <head> del dashboard poniendo `con-carga` en el <html>. Así, al
   navegar entre pestañas de la consola o al recargar, ni aparece ni parpadea. */
.app-loader {
    display: none;

    /* Tiempos de la entrada, en un solo sitio. Son variables y no números
       sueltos porque hay un caso en el que TODOS valen cero: ver `sin-entrada`
       justo debajo. */
    --entrada-ms: 620ms;
    --entrada-pie-ms: 460ms;
    --entrada-pie-retardo: 220ms;
    --orbita-retardo: 360ms;
}

/* SIN ENTRADA: la pantalla ya estaba puesta antes de llegar aquí.
   Ocurre al iniciar sesión. El login enseña esta misma pantalla, espera a que el
   anillo termine de aparecer y solo entonces navega al dashboard. Si el
   dashboard volviera a reproducir la entrada, el anillo se encogería y volvería
   a crecer justo al cambiar de página: un tropezón en mitad de la transición.
   Poniendo los tiempos a cero, aparece ya asentado y la animación se lee como
   UNA sola, continua entre las dos páginas. */
html.sin-entrada .app-loader {
    --entrada-ms: 0ms;
    --entrada-pie-ms: 0ms;
    --entrada-pie-retardo: 0ms;
    --orbita-retardo: 0ms;
}

/* Tapa la ventana entera y va por encima de todo, incluidos los modales, porque
   mientras carga no debe poder tocarse nada de lo que hay debajo. */
html.con-carga .app-loader {
    position: fixed; inset: 0; z-index: 100000;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 34px;
    background: var(--bg-primary);
    /* Mismo halo de ambiente que el resto del shell, para que no se note el corte
       al desaparecer. */
    background-image:
        radial-gradient(1100px 520px at 18% -12%, rgba(45, 212, 191, .10), transparent 60%),
        radial-gradient(900px 480px at 100% 0%, rgba(20, 184, 166, .07), transparent 55%);
    opacity: 1;
    transition: opacity 260ms ease;
}

/* Estado de salida: se desvanece y deja de capturar clics antes de quitarse.
   Lleva el `html.con-carga` delante a propósito: sin él perdería en especificidad
   contra la regla de arriba (que fija `opacity: 1`) y no habría fundido. */
html.con-carga .app-loader.is-hidden { opacity: 0; pointer-events: none; }

/* ---------------------------------------------------------------------------
   Entrada
   ---------------------------------------------------------------------------
   EL FONDO NO SE ANIMA, y es deliberado: su trabajo es tapar desde el primer
   fotograma. Si se desvaneciera, se vería por debajo el dashboard a medio pintar,
   que es justo lo que esta pantalla existe para esconder.

   Lo que entra con suavidad es su CONTENIDO. Antes el anillo aparecía de golpe,
   ya a tamaño completo y girando: se leía como un salto, no como el comienzo de
   algo. Ahora crece un poco y se funde, y los puntos no empiezan a orbitar hasta
   que el anillo está puesto, así que el movimiento se lee en orden. */
@keyframes cargaEntradaAnillo {
    from { opacity: 0; transform: scale(.86); }
    to   { opacity: 1; transform: scale(1); }
}
@keyframes cargaEntradaPie {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* El pie entra DESPUÉS del anillo: primero el objeto, luego su rótulo. */
.app-loader__pie {
    animation: cargaEntradaPie var(--entrada-pie-ms) cubic-bezier(.22, .61, .36, 1) var(--entrada-pie-retardo) both;
}

/* ---------------------------------------------------------------------------
   El anillo y sus tres puntos
   --------------------------------------------------------------------------- */

/* TODO lo de dentro se mide en `em`, así que este `font-size` es el único mando
   de tamaño: el anillo son 80em. Con el clamp queda ~190px en escritorio y ~120px
   en un móvil, sin necesidad de media queries. */
.app-loader__ring {
    position: relative;
    font-size: clamp(1.5px, 0.28vmin, 2.4px);
    width: 80em; height: 80em;
    box-sizing: border-box;
    border: 2em solid var(--accent-primary-hover, #5eead4);
    border-radius: 50%;
    /* Dos animaciones a la vez y sin estorbarse: el halo trabaja sobre `filter`
       y la entrada sobre `opacity`/`transform`. La entrada lleva `both` para que
       el anillo NO se pinte antes de empezar (si no, se vería un fotograma a
       tamaño completo justo lo que se quería evitar). */
    animation:
        cargaHaloExterior 4s ease infinite alternate,
        cargaEntradaAnillo var(--entrada-ms) cubic-bezier(.22, .61, .36, 1) both;
}

/* Los dos aros interiores, cada vez más tenues: dan la sensación de profundidad
   sin añadir un solo nodo al DOM. */
.app-loader__ring::before,
.app-loader__ring::after {
    content: ""; position: absolute; display: block; box-sizing: border-box;
    top: 50%; left: 50%; transform: translate(-50%, -50%);
    border-radius: 50%;
}
.app-loader__ring::before {
    width: 76em; height: 76em;
    border: 1.5em solid rgba(13, 148, 136, .75);
    animation: cargaHaloInterior 4s ease infinite alternate;
}
.app-loader__ring::after {
    width: 73em; height: 73em;
    border: 1.5em solid rgba(13, 148, 136, .25);
    filter: drop-shadow(0 0 7em #0d9488);
}

/* Cada `.dot` NO es el punto: es su eje de giro. El punto de verdad es su
   `::after`, colgado del borde superior; así, al rotar el eje, el punto describe
   la órbita. */
.app-loader__dot {
    position: absolute; box-sizing: border-box;
    top: 50%; width: 10em; height: 10em;
    overflow: visible;
    transform: rotate(0deg);
}
.app-loader__dot::after {
    content: ""; position: absolute; display: block; box-sizing: border-box;
    top: -2em; left: calc(50% - 2em);
    width: 4em; height: 4em;
    background: var(--accent-primary-hover, #5eead4);
    border-radius: 50%;
    filter: drop-shadow(0 0 .5em #5eead4) drop-shadow(0 0 .25em #0d9488);
}

/* Los tres arrancan repartidos y cada uno avanza al puesto del siguiente. El
   tercero, además de volver al primer puesto, gira 360º: es lo que hace que el
   ciclo se lea como continuo y no como un salto. */
/* El retardo hace que la órbita arranque con el anillo ya puesto. Durante esos
   360 ms los puntos se quedan quietos en su `left`, que es exactamente el 0% de
   su animación: no hay salto al empezar a moverse. */
.app-loader__dot:nth-child(1) { left: calc(33% - 5em); animation: cargaPunto1 2s ease var(--orbita-retardo) infinite; }
.app-loader__dot:nth-child(2) { left: calc(50% - 5em); animation: cargaPunto2 2s ease var(--orbita-retardo) infinite; }
.app-loader__dot:nth-child(3) { left: calc(67% - 5em); animation: cargaPunto3 2s ease var(--orbita-retardo) infinite; }

@keyframes cargaPunto1 {
    0%   { left: calc(33% - 5em); }
    100% { left: calc(50% - 5em); }
}
@keyframes cargaPunto2 {
    0%   { left: calc(50% - 5em); }
    100% { left: calc(67% - 5em); }
}
@keyframes cargaPunto3 {
    0%   { left: calc(67% - 5em); transform: rotate(0deg); }
    100% { left: calc(33% - 5em); transform: rotate(360deg); }
}

@keyframes cargaHaloExterior {
    0%   { filter: drop-shadow(0 0 1em #0d9488); }
    100% { filter: drop-shadow(0 0 3em #0d9488); }
}
@keyframes cargaHaloInterior {
    0%   { filter: drop-shadow(0 0 3em #0d9488); }
    100% { filter: drop-shadow(0 0 5em #0d9488); }
}

/* ---------------------------------------------------------------------------
   Marca y mensaje, bajo el anillo
   --------------------------------------------------------------------------- */
.app-loader__pie {
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    text-align: center;
}
.app-loader__title {
    display: flex; align-items: baseline; gap: 7px;
    font-size: 1.06rem; letter-spacing: .02em;
}
.app-loader__title strong { color: var(--text-primary); font-weight: 700; }
.app-loader__title span { color: var(--accent-primary); font-weight: 600; }

.app-loader__msg { margin: 0; font-size: .82rem; color: var(--text-secondary); }

/* Respeta a quien pidió menos movimiento: se queda quieta, pero sigue tapando.
   La entrada NO se elimina del todo: se cambia por un fundido de opacidad, sin
   escala ni desplazamiento. Un fundido no es movimiento, y aparecer de golpe es
   precisamente lo que se estaba corrigiendo. */
@keyframes cargaEntradaSuave {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .app-loader__dot { animation: none; }
    .app-loader__ring { animation: cargaEntradaSuave 400ms ease both; }
    .app-loader__pie  { animation: cargaEntradaSuave 400ms ease 120ms both; }
}
