﻿/* ════════════════════════════════════════════════════════════════════════════
   THE PORTAL SPINNER
   The one loading indicator. This file is what the comment in Licenses.razor has
   been asking for since 2026-08-25.

   It replaced eight species turning at seven speeds in six sizes: Bootstrap's
   spinner-grow (a pulsing disc, not a ring) on ten pages, three identical copies
   of this ring under three names (msab-spinner, billing-spinner, pay-spinner),
   two more gradients on Downloads/Licenses and Support, and five hand-rolled
   border-top rings at 0.6s, 0.7s and 0.95rem-ish sizes. Two spinners visible at
   once beat against each other, which is how anyone noticed.

   WHY IT LIVES HERE AND NOT IN A PAGE. A style block in a .razor file is GLOBAL:
   the same class name defined on several pages resolves to whichever page loaded
   last, silently. That is why every page had its own prefix. One file, loaded
   once from App.razor, is the fix - do not copy any of this back into a page.

   CONSTRUCTION. A conic gradient masked down to a thin band, rotating inside a
   STATIC track. The track is the element's own border so it does not turn with
   the sweep; the sweep is an ::after inset by the border width so the two sit
   concentric. The stroke fades from transparent to solid gold, so the leading
   edge reads as motion rather than as a hard segment. The animation runs in the
   browser, so it keeps turning while the circuit thread is blocked in a CRM or
   Azure call - which is exactly when it is on screen.

   USAGE
     <span class="pspin pspin-lg"></span>          page and tab loads
     <span class="pspin"></span>                   panel-level work
     <span class="pspin pspin-sm"></span>          buttons, rows, inline
     <span class="pspin pspin-sm pspin-ink"></span>    on a gold or light button
     <span class="pspin pspin-sm pspin-light"></span>  on a saturated button
   Every modifier needs .pspin as well. Sizes and tones are separate axes.
   ════════════════════════════════════════════════════════════════════════════ */

:root {
    /* One tempo. Every rotating thing in the portal uses it, including the Admin
       toolbar's refresh glyph, so nothing beats against anything else. */
    --pspin-speed: 0.9s;
    --pspin-gold: #fec52d;
}

.pspin {
    /* --pspin-w drives the box, --pspin-b the ring. The ::after below derives its
       inset and its mask from --pspin-b, so a size modifier only sets these two. */
    --pspin-w: 20px;
    --pspin-b: 2px;

    display: inline-block;
    position: relative;
    flex-shrink: 0;
    width: var(--pspin-w);
    height: var(--pspin-w);
    border-radius: 50%;
    border: var(--pspin-b) solid rgba(254, 197, 45, 0.16);
}

    .pspin::after {
        content: "";
        position: absolute;
        inset: calc(-1 * var(--pspin-b));
        border-radius: 50%;
        background: conic-gradient(from 0deg,
            rgba(254, 197, 45, 0)    0deg,
            rgba(254, 197, 45, 0.10) 90deg,
            rgba(254, 197, 45, 0.45) 230deg,
            rgba(254, 197, 45, 0.95) 330deg,
            #fec52d                  360deg);
        -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--pspin-b)), #000 calc(100% - var(--pspin-b)));
                mask: radial-gradient(farthest-side, transparent calc(100% - var(--pspin-b)), #000 calc(100% - var(--pspin-b)));
        animation: pspin-rotate var(--pspin-speed) linear infinite;
    }

/* ── sizes ───────────────────────────────────────────────────────────────── */

/* Page and tab loads. The only size that carries the glow: at 48px it lifts the
   ring off the panel, and below it the blur is wider than the stroke and just
   muddies. drop-shadow reads roughly twice as strong as box-shadow - keep it low. */
.pspin.pspin-lg {
    --pspin-w: 3rem;
    --pspin-b: 3px;
}

    .pspin.pspin-lg::after {
        filter: drop-shadow(0 0 4px rgba(254, 197, 45, 0.35));
    }

/* Buttons, table rows, inline beside text. 2px, because a 3px ring at 14px reads
   as a solid disc. */
.pspin.pspin-sm {
    --pspin-w: 14px;
    --pspin-b: 2px;
}

/* ── tones ───────────────────────────────────────────────────────────────── */
/* A gold ring on a gold button is invisible, and a dark ring on a dark panel is
   too. The tone is picked from what sits BEHIND the spinner, never from the page.
   Explicit gradients rather than a mixed colour: an unsupported colour function
   invalidates the whole background and the ring simply disappears. */

.pspin.pspin-ink {
    border-color: rgba(0, 0, 0, 0.18);
}

    .pspin.pspin-ink::after {
        background: conic-gradient(from 0deg,
            rgba(30, 26, 16, 0)    0deg,
            rgba(30, 26, 16, 0.12) 90deg,
            rgba(30, 26, 16, 0.50) 230deg,
            rgba(30, 26, 16, 0.95) 330deg,
            #1e1a10                360deg);
        filter: none;
    }

.pspin.pspin-light {
    border-color: rgba(255, 255, 255, 0.22);
}

    .pspin.pspin-light::after {
        background: conic-gradient(from 0deg,
            rgba(255, 255, 255, 0)    0deg,
            rgba(255, 255, 255, 0.12) 90deg,
            rgba(255, 255, 255, 0.50) 230deg,
            rgba(255, 255, 255, 0.95) 330deg,
            #ffffff                   360deg);
        filter: none;
    }

/* ── the page-load arrangement ───────────────────────────────────────────── */
/* Centred, and tall enough that the panel does not collapse and jump when the
   content arrives. Each page keeps its OWN words - "Loading files", "Fetching
   invoices" - because they say more than a generic LOADING; only the treatment
   is shared. */

.pspin-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    min-height: 220px;
    padding: 2rem 1rem;
    text-align: center;
}

.pspin-label {
    margin: 0;
    font-family: 'Borda', 'Roboto', sans-serif;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.40);
    animation: pspin-breathe 2.2s ease-in-out infinite;
}

/* ── a rotating icon ─────────────────────────────────────────────────────── */
/* Kept as a glyph, not converted to a ring: on the Admin toolbar the icon IS the
   control, it means "this button is working" rather than "the page is loading",
   and a ring beside a 16px icon in a 32px button is clutter. It shares the tempo
   so it does not visibly disagree with a ring elsewhere on the same screen. */

.pspin-glyph {
    display: inline-block;
    animation: pspin-rotate var(--pspin-speed) linear infinite;
}

/* ── motion ──────────────────────────────────────────────────────────────── */

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

@keyframes pspin-breathe {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 0.95; }
}

/* Slowed right down rather than stopped: a still spinner reads as a hung page,
   which is the opposite of what it is there to say. */
@media (prefers-reduced-motion: reduce) {
    .pspin::after,
    .pspin-glyph {
        animation-duration: 3s;
    }

    .pspin-label {
        animation: none;
        opacity: 0.7;
    }
}
