/* Page loader — the first-paint splash screen and the slim progress bar that
   runs during enhanced navigation. Markup lives in Components/App.razor,
   behaviour in wwwroot/js/page-loader.js.

   Global, not scoped, like every other sheet in this folder: every selector is
   qpl- prefixed or a descendant of one, and there is no bare element selector,
   so nothing here can reach markup outside the loader.

   The one structural rule: every piece of state is a class on <html>, never an
   inline style or a class on the loader elements themselves. Enhanced
   navigation diffs the <body> against the server's markup, so any attribute JS
   writes onto an element inside it is reverted the moment the visitor navigates
   — the splash would flash back on every page change. <html> is outside that
   diff, so state parked there survives, and a splash re-inserted by the diff is
   born already hidden with no transition to play. */

/* ---------------------------------------------------------------- palette */

/* The brand colours, redeclared locally rather than read from .qnav-root:
   the loader paints before the header exists in the accessibility-relevant
   sense and is not a descendant of it, so it cannot inherit them. Values are
   the same PANTONEs the header uses — 2592C, 2725C, 129C. */
.qpl-splash,
.qpl-bar {
    --qpl-magenta: #a03cd2;
    --qpl-indigo: #5d5de2;
    --qpl-yellow: #f6cd46;
    --qpl-violet: #7a4ddd;
    --qpl-gradient: linear-gradient(135deg, #a03cd2 0%, #7a4ddd 52%, #5d5de2 100%);
}

/* ----------------------------------------------------------------- splash */

.qpl-splash {
    position: fixed;
    inset: 0;
    z-index: 2147483000;
    display: flex;
    align-items: center;
    justify-content: center;

    /* White base with two low-alpha brand blooms, so the panel reads as part of
       the site rather than as a blank interstitial. */
    background-color: #fff;
    background-image:
        radial-gradient(58rem 40rem at 10% -12%, rgba(160, 60, 210, .11), transparent 62%),
        radial-gradient(52rem 38rem at 102% 112%, rgba(93, 93, 226, .13), transparent 62%);

    opacity: 1;
    visibility: visible;

    /* visibility is delayed by the whole fade so the panel stays hittable-free
       but visible while it fades, then leaves the hit-testing tree for good. */
    transition: opacity .45s ease, visibility 0s linear .45s;

    /* Failsafe. If the script never runs — blocked, cached-stale, thrown — the
       splash would otherwise sit over the site permanently. This retires it
       after 10s and is cancelled the instant qpl-ready lands, because the
       selector stops matching. */
    animation: qpl-failsafe 1ms linear 10s forwards;
}

html.qpl-ready .qpl-splash {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    animation: none;
}

@keyframes qpl-failsafe {
    to {
        opacity: 0;
        visibility: hidden;
    }
}

.qpl-splash-inner {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.35rem;
    padding: 1rem;
    /* Lifts the stack fractionally above centre — optically centred beats
       geometrically centred for a single block on a tall viewport. */
    transform: translateY(-2vh);
}

/* Clips the shine sweep to the logo's own box. */
.qpl-mark {
    position: relative;
    overflow: hidden;
    border-radius: .5rem;
    animation: qpl-rise .7s cubic-bezier(.22, 1, .36, 1) both;
}

.qpl-logo {
    display: block;
    /* Deliberately modest — a splash reads as a brief pause, not a title card,
       and the same wordmark sits at 160px in the header two beats later. The
       vw term only takes over below ~430px, where a fixed 180px would crowd
       the viewport. */
    width: clamp(132px, 38vw, 180px);
    height: auto;
}

/* A single band of light travelling across the wordmark. Skewed so the edge
   follows the diagonal of the brand gradient rather than cutting straight down. */
.qpl-shine {
    position: absolute;
    top: -20%;
    left: 0;
    width: 38%;
    height: 140%;
    background: linear-gradient(100deg,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 255, 255, .62) 50%,
        rgba(255, 255, 255, 0) 100%);
    transform: skewX(-18deg) translateX(-180%);
    animation: qpl-shine 2.6s cubic-bezier(.5, 0, .2, 1) .55s infinite;
}

@keyframes qpl-shine {
    0%   { transform: skewX(-18deg) translateX(-180%); }
    /* The sweep is the fast part; the rest of the cycle is the pause between
       passes, which is what keeps it feeling deliberate instead of frantic. */
    45%  { transform: skewX(-18deg) translateX(360%); }
    100% { transform: skewX(-18deg) translateX(360%); }
}

@keyframes qpl-rise {
    from { opacity: 0; transform: translateY(10px) scale(.985); }
    to   { opacity: 1; transform: none; }
}

/* Indeterminate track. A fixed-width fill shuttling across a tinted rail —
   honest about not knowing the progress, unlike a bar that creeps to 90% and
   stalls. */
.qpl-track {
    position: relative;
    width: 124px;
    max-width: 46vw;
    height: 3px;
    border-radius: 999px;
    background: rgba(93, 93, 226, .15);
    overflow: hidden;
    animation: qpl-rise .7s cubic-bezier(.22, 1, .36, 1) .12s both;
}

.qpl-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: 44%;
    border-radius: 999px;
    background: var(--qpl-gradient);
    box-shadow: 0 0 12px rgba(122, 77, 221, .55);

    /* Close to linear on purpose. A pronounced ease-in-out parks the fill at
       the ends of its travel, which are exactly where it is clipped out of the
       rail — half the cycle then shows an empty track, which reads as stalled
       rather than loading. */
    animation: qpl-shuttle 1.3s cubic-bezier(.4, .15, .6, .85) infinite;
}

/* Two fills on one cycle, the second started half a cycle in. A single fill is
   fully outside the rail at the moment it wraps, so the track blinks empty once
   every 1.3s; with the pair, one is always entering as the other leaves. The
   negative delay is what starts it mid-cycle rather than after one. */
.qpl-fill:nth-child(2) {
    animation-delay: -.65s;
}

/* The bounds are the edges of visibility and no further: at -105% the fill has
   just cleared the left of the rail, at 232% it is just leaving the right. */
@keyframes qpl-shuttle {
    0%   { transform: translateX(-105%); }
    100% { transform: translateX(232%); }
}

/* ------------------------------------------------------- navigation bar */

/* Enhanced navigation swaps the page without a document load, so the browser
   shows no spinner of its own. This is the replacement: a hairline that creeps
   while the response is in flight and snaps shut when it lands. */
.qpl-bar {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 2147483001;
    width: 0;
    height: 3px;
    border-radius: 0 999px 999px 0;
    background: linear-gradient(90deg, #a03cd2 0%, #7a4ddd 42%, #5d5de2 78%, #f6cd46 100%);
    box-shadow: 0 0 10px rgba(122, 77, 221, .7), 0 0 4px rgba(160, 60, 210, .55);
    opacity: 0;
    pointer-events: none;
}

/* Decelerating hard, so the first third of the bar is spent in the first few
   hundred milliseconds — the part of the wait the visitor actually watches —
   and it never quite reaches the end on its own. */
html.qpl-nav .qpl-bar {
    opacity: 1;
    animation: qpl-creep 14s cubic-bezier(.03, .86, .2, 1) forwards;
}

@keyframes qpl-creep {
    from { width: 0; }
    to   { width: 92%; }
}

/* Must stay after the rule above: same specificity, source order decides.

   Dropping the animation and setting a width in the same style change is what
   runs the bar forward from wherever the creep had reached — a transition's
   start value is the computed style *including* the animation that was in
   effect, so this reads as 41% → 100% rather than a reset to zero. */
html.qpl-nav-done .qpl-bar {
    animation: none;
    width: 100%;
    opacity: 0;
    transition: width .22s ease-out, opacity .32s ease .16s;
}

/* ------------------------------------------------------------ utilities */

/* Announces the wait to screen readers without putting a redundant "Loading"
   caption under a logo that already carries the tagline. */
.qpl-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* --------------------------------------------------------- reduced motion */

/* The decoration goes; the two elements that *carry information* — the shuttle
   and the creep — stay, slowed down. A loading indicator that does not move is
   indistinguishable from a frozen page, which is the opposite of accessible. */
@media (prefers-reduced-motion: reduce) {

    .qpl-shine {
        display: none;
    }

    .qpl-mark,
    .qpl-track {
        animation: none;
    }

    .qpl-fill {
        animation-duration: 2.4s;
        animation-timing-function: ease-in-out;
    }

    html.qpl-nav .qpl-bar {
        animation-duration: 20s;
        animation-timing-function: linear;
    }
}
