/* ── Estado "cargando" de los botones ──────────────────────────────────────────
   Por que el boton CRECE al ponerse a cargar:

   El patron de toda la app es pintar un <span class="spinner-border spinner-border-sm">
   DENTRO del boton, delante del texto. Esa ruleta es un elemento mas del flujo de
   contenido, y su tamano NO lo pone el boton: lo ponen las variables del tema
   (--spinner-width / --spinner-height, derivadas de --icon-font-size en all.min.css).
   Cuando el circulo sale mas alto que la linea de texto del boton, empuja el alto —y
   con el margen, el ancho—, asi que el boton entero se deforma justo en el momento en
   que el usuario ya no puede hacer nada con el. El tamano de los spinner-border fuera
   de botones lo ata utils/iconSpin.css (1em, no 1rem).

   Aqui se corrige de raiz, y de una vez para todos los botones:

     1. La ruleta en flujo desaparece  -> el boton conserva EXACTAMENTE su geometria.
     2. El contenido se atenua          -> misma lectura que un boton inhabilitado.
     3. Un ::after cubre el boton entero con un barrido de carga. Al ser absoluto no
        participa del layout, y su gradiente se recorta solo a la caja del boton (no
        hace falta overflow:hidden, que se comeria el anillo de foco).

   El enganche es :has(), asi que cualquier boton que YA pintaba un spinner-border
   hereda el comportamiento nuevo sin tocar su markup. .btn.is-loading queda como
   enganche explicito para botones que quieran el estado sin pintar ruleta ninguna.

   El :not(.d-none) NO es decorativo: hay 5 botones (DocumentUploadModal,
   DocumentViewerModal x3, SubcontractorWorkers) que llevan el spinner SIEMPRE en el
   markup y lo encienden desde JS quitandole .d-none. Sin ese :not(), esos botones
   saldrian con el barrido de carga puesto de forma permanente.

   Ojo con la cascada: el tema (all.min.css) NO va en un <link> del _Host, lo inyecta
   sfBlazorSB.loadResources con appendChild -> entra DESPUES que esta hoja y gana los
   empates de especificidad. Por eso las reglas de aqui van con selector compuesto
   (.btn .spinner-border = 0,2,0) y no con .spinner-border a secas. */

.btn:has(.spinner-border:not(.d-none)),
.btn.is-loading {
	position: relative;
	opacity: var(--btn-disabled-opacity, .65);
}

/* La ruleta en flujo es justo lo que se queria quitar. El boton ya va `disabled`
   mientras esta en vuelo, asi que el estado sigue siendo evidente sin ella. */
.btn .spinner-border,
.btn .spinner-grow {
	display: none;
}

.btn:has(.spinner-border:not(.d-none))::after,
.btn.is-loading::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
	pointer-events: none;

	/* currentColor: el barrido sale blanco sobre .btn-primary y oscuro sobre
	   .btn-light sin una regla por variante. */
	background-image: linear-gradient(90deg,
			transparent 0%,
			currentColor 45%,
			currentColor 55%,
			transparent 100%);
	background-repeat: no-repeat;
	background-size: 200% 100%;
	opacity: .18;
	animation: atlas-btn-loading-sweep 1.2s linear infinite;
}

/* 200% 0 deja la banda fuera por la derecha; -100% 0, fuera por la izquierda. */
@keyframes atlas-btn-loading-sweep {
	from { background-position: 200% 0; }
	to   { background-position: -100% 0; }
}

/* Sin movimiento: velo fijo sobre el boton, que sigue diciendo "ocupado". */
@media (prefers-reduced-motion: reduce) {
	.btn:has(.spinner-border:not(.d-none))::after,
	.btn.is-loading::after {
		animation: none;
		background-image: none;
		background-color: currentColor;
		opacity: .12;
	}
}
