/* ============================================================================
   v0.4.49: LOADING SPINNER - one indicator for every IO-bound action.
   Before this part, every async button only dimmed via :disabled while a
   request was in flight (AI generate, diagnostic, auth, fusion, ...). Nothing
   told the user "working". This adds a single, theme-matched spinner with two
   activation paths but one identical look:

     * Interactive Blazor buttons bind .is-loading to their busy flag
       (e.g. class="compose-ai @(_busyAction == "compose-ai" ? "is-loading" : null)").
     * Pure-SSR <form method="post"> submits (auth / billing / diagnostic) get
       .is-loading added to the clicked submit button by js/chronos-loading.js.

   The spinner is painted as a ::after ring so NO markup needs a child element,
   and the button's real label is kept in flow (just visually hidden) so the
   button never changes width when it flips to loading.
   ============================================================================ */

@keyframes chronos-spin { to { transform: rotate(360deg); } }

/* Standalone inline spinner - for overlays / inline "loading..." spots that are
   not buttons. Sizes to the surrounding font (1.1em) and inherits text colour. */
.chronos-spinner {
    display: inline-block;
    width: 1.1em;
    height: 1.1em;
    border: 2px solid currentColor;
    border-top-color: transparent;      /* the gap is what makes the rotation legible */
    border-radius: 50%;
    animation: chronos-spin 0.7s linear infinite;
    vertical-align: -0.15em;
    flex-shrink: 0;
}

/* Button loading state. Hides the label/icon and centres a spinner over the
   button box. The button keeps its background, border and size - only the
   content is swapped for the ring. */
.is-loading {
    position: relative;
    pointer-events: none;               /* can't re-fire the action while it's in flight */
    color: transparent !important;      /* hides text nodes AND inherited-colour icon glyphs */
    opacity: 1 !important;              /* the clicked button stays bright; only its idle siblings keep the dim :disabled look */
}
.is-loading > * { visibility: hidden; } /* hide element children (spans / .material-icons) too */

.is-loading::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1.05em;
    height: 1.05em;
    margin: -0.525em 0 0 -0.525em;      /* centre the ring on the button */
    border: 2px solid var(--chronos-violet);   /* default ring reads on light / ghost buttons */
    border-top-color: transparent;
    border-radius: 50%;
    visibility: visible;                /* re-show the ring over the hidden label */
    animation: chronos-spin 0.7s linear infinite;
}

/* White ring on the violet-filled / gradient buttons (their label is white, but
   color:transparent above killed currentColor, so the ink is set explicitly). */
.btn-primary.is-loading::after,
.compose-ai.is-loading::after,
.compose-token.has-tokens.is-loading::after,
button.primary.is-loading::after,
.inv-btn.is-loading::after,
.pet-action-btn.is-loading::after {
    border-color: #fff;
    border-top-color: transparent;
}

/* Reduced motion: a frozen ring reads as a hung app, so - unlike the decorative
   animations elsewhere that switch to animation:none - a loading indicator must
   keep moving. Slow it to a calm 1.6s instead of stopping it. */
@media (prefers-reduced-motion: reduce) {
    .chronos-spinner,
    .is-loading::after { animation-duration: 1.6s; }
}
