/* ==========================================================================
   BOSS — loading states
     .loader          full-screen boot splash (every page load)
     .navbar-progress thin top bar on every navigation
     .spinner         inline async spinner
     .skeleton        shimmer placeholder
   ========================================================================== */

/* ============================================================== splash === */

.loader {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: grid;
    place-items: center;
    background: var(--bg-deep);
    /* Fades itself out even if JS never runs, so the page can't stay covered.
       Sits deliberately after app.js's own 2s cap, so this only ever acts when
       the script is absent or broken — never in a race with it. */
    animation: loader-failsafe .5s var(--ease) 2.6s forwards;
}

.loader::before {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(60% 50% at 50% 42%, rgba(var(--a1-rgb), .22) 0%, transparent 70%),
        radial-gradient(45% 40% at 70% 70%, rgba(var(--a3-rgb), .14) 0%, transparent 70%);
    animation: loader-glow 3s var(--ease) infinite alternate;
}

@keyframes loader-glow {
    from { opacity: .55; }
    to   { opacity: 1; }
}

/* A slow conic wash drifting behind the glow, for atmosphere rather than
   focus — kept faint and blended so it reads as depth, not a spotlight. */
.loader::after {
    content: "";
    position: absolute;
    inset: 0;
    background: conic-gradient(from 0deg at 50% 45%,
        transparent 0deg, rgba(var(--a1-rgb), .09) 90deg, transparent 180deg,
        rgba(var(--a3-rgb), .07) 270deg, transparent 360deg);
    mix-blend-mode: screen;
    animation: loader-drift 9s linear infinite;
}

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

/* Removed by app.js once the window has loaded. pointer-events goes first so
   the page beneath is clickable the instant the fade starts, rather than half
   a second later when the node is finally removed. */
.loader.is-done {
    animation: none;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .45s var(--ease), visibility .45s;
}

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

.loader__inner {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 22px;
}

/* ------------------------------------------------------------ the mark -- */

.loader__mark {
    position: relative;
    display: grid;
    place-items: center;
    width: 120px;
    height: 120px;
}

.loader__logo {
    width: 54px;
    height: 54px;
    overflow: visible;
}

/* --------------------------------------------------------------- aura --- */

.loader__aura {
    position: absolute;
    inset: -22px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(var(--a1-rgb), .38), transparent 70%);
    filter: blur(20px);
    animation: loader-aura-pulse 2.2s var(--ease) infinite;
}

@keyframes loader-aura-pulse {
    0%, 100% { opacity: .45; transform: scale(.9); }
    50%      { opacity: 1;   transform: scale(1.1); }
}

/* -------------------------------------------------------------- sparks -- */
/* Each is a 120x120 box rotating about its own center; the visible dot
   lives at the top edge via ::before, so rotating the box carries the dot
   around the mark. Three different radii, speeds and directions keep them
   from ever reading as one synchronized ring. */

.loader__spark {
    position: absolute;
    inset: 0;
    width: 120px;
    height: 120px;
    animation: loader-orbit 2.8s linear infinite;
}

.loader__spark::before {
    content: "";
    position: absolute;
    top: 2px;
    left: 50%;
    width: 4px;
    height: 4px;
    margin-left: -2px;
    border-radius: 50%;
    background: var(--a1);
    box-shadow: 0 0 8px 2px rgba(var(--a1-rgb), .8);
    animation: loader-twinkle 1.4s var(--ease) infinite;
}

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

@keyframes loader-twinkle {
    0%, 100% { opacity: .35; }
    50%      { opacity: 1; }
}

.loader__spark--1 { animation-duration: 2.6s; }

.loader__spark--2 {
    animation-duration: 3.6s;
    animation-direction: reverse;
}

.loader__spark--2::before {
    top: auto;
    bottom: 10px;
    left: 12px;
    background: var(--a3);
    box-shadow: 0 0 8px 2px rgba(var(--a3-rgb), .8);
    animation-delay: .5s;
}

.loader__spark--3 { animation-duration: 4.4s; }

.loader__spark--3::before {
    top: 50%;
    left: 4px;
    margin-left: 0;
    margin-top: -2px;
    width: 3px;
    height: 3px;
    animation-delay: 1s;
}

/* Each piece animates about its own bounding box rather than the SVG canvas,
   so a bowl grows out of the spine it attaches to instead of the corner. */
.loader__crown,
.loader__spine,
.loader__bowl {
    transform-box: fill-box;
}

/* The crown arrives first, dropping the last few units onto the letter. */
.loader__crown {
    fill: var(--logo-gold);
    transform-origin: bottom center;
    animation: loader-crown .32s var(--ease-spring) both;
}

@keyframes loader-crown {
    from { opacity: 0; transform: translateY(-6px) scale(.7); }
    to   { opacity: 1; transform: none; }
}

/* The spine wipes down from under the crown, like a pen stroke. */
.loader__spine {
    fill: var(--logo-ink);
    transform-origin: top center;
    animation: loader-wipe .3s var(--ease-out) .1s both;
}

@keyframes loader-wipe {
    from { transform: scaleY(0); }
    to   { transform: scaleY(1); }
}

/* Bowls land in reading order, each hinging off the spine at its left edge. */
.loader__bowl {
    transform-origin: left center;
    animation: loader-land .34s var(--ease-spring) both;
}

.loader__bowl--upper {
    fill: var(--logo-ink);
    animation-delay: .2s;
}

/* The lower bowl carries the gold, matching the knockout lockup in
   assets/img/logo-mark-white.svg — the mark without its tile. */
.loader__bowl--lower {
    fill: var(--logo-gold);
    animation-delay: .29s;
}

@keyframes loader-land {
    from { opacity: 0; transform: scaleX(.1); }
    to   { opacity: 1; transform: scaleX(1); }
}

/* ------------------------------------------------------------ the ring -- */

.loader__ring {
    position: absolute;
    inset: 0;
    width: 120px;
    height: 120px;
    transform: rotate(-90deg);
}

.loader__ring-track {
    fill: none;
    stroke: rgba(var(--tint), .07);
    stroke-width: 3;
}

.loader__ring-arc {
    fill: none;
    stroke: var(--a1);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 339;
    /* Arc length and rotation both animate, giving a chasing-tail spinner. */
    animation: loader-arc 1.5s var(--ease) infinite,
               loader-spin 2.2s linear infinite;
    transform-origin: 60px 60px;
}

@keyframes loader-arc {
    0%   { stroke-dashoffset: 322; }
    50%  { stroke-dashoffset: 105; }
    100% { stroke-dashoffset: 322; transform: rotate(360deg); }
}

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

/* Inner ring counter-rotates against the outer one, thinner and dimmer so
   it reads as a second layer rather than competing with the main arc. */
.loader__ring-track2 {
    fill: none;
    stroke: rgba(var(--tint), .05);
    stroke-width: 2;
}

.loader__ring-arc2 {
    fill: none;
    stroke: var(--a3);
    stroke-width: 2;
    stroke-linecap: round;
    opacity: .6;
    stroke-dasharray: 270;
    stroke-dashoffset: 190;
    animation: loader-spin-reverse 3.2s linear infinite;
    transform-origin: 60px 60px;
}

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

/* ----------------------------------------------------------- wordmark --- */

.loader__word {
    display: flex;
    align-items: baseline;
    font-family: "Poppins", "Inter", system-ui, sans-serif;
    font-size: 25px;
    line-height: 1;
    color: var(--logo-ink);
    letter-spacing: .01em;
    animation: loader-rise .34s var(--ease-out) .42s both;
}

/* Matches the lockup: the initial carries the weight. */
.loader__b {
    font-weight: 700;
    color: var(--logo-gold);
}

.loader__oss {
    font-weight: 300;
    letter-spacing: .06em;
    /* A soft highlight sweeps across the light half of the wordmark once it
       has landed — a glint rather than a rewrite of its color. */
    background: linear-gradient(100deg,
        var(--logo-ink) 0%, var(--logo-ink) 42%,
        var(--a1) 50%,
        var(--logo-ink) 58%, var(--logo-ink) 100%);
    background-size: 280% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: loader-shimmer 2.6s linear .9s infinite;
}

@keyframes loader-shimmer {
    from { background-position: 220% 0; }
    to   { background-position: -60% 0; }
}

.loader__tag {
    /* Pulls back against .loader__inner's 22px gap: the expansion belongs to
       the wordmark above it, not as a third separate block. */
    margin-top: -12px;
    font-size: 9.5px;
    font-weight: 500;
    letter-spacing: .17em;
    text-transform: uppercase;
    color: var(--text-faint);
    text-align: center;
    animation: loader-rise .34s var(--ease-out) .5s both;
}

@keyframes loader-rise {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* --------------------------------------------------------- shimmer bar -- */

.loader__bar {
    position: relative;
    width: 148px;
    height: 3px;
    border-radius: 99px;
    overflow: hidden;
    background: rgba(var(--tint), .09);
    animation: loader-rise .3s var(--ease-out) .56s both;
}

.loader__bar span {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: var(--grad-brand);
    box-shadow: 0 0 10px 1px rgba(var(--a1-rgb), .5);
    /* Indeterminate: the fill sweeps rather than reporting real progress. */
    animation: loader-sweep 1.4s var(--ease) infinite;
}

@keyframes loader-sweep {
    0%   { transform: translateX(-100%) scaleX(.55); }
    60%  { transform: translateX(60%) scaleX(1); }
    100% { transform: translateX(100%) scaleX(.4); }
}

/* ==================================================== nav progress bar === */

.navbar-progress {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 210;
    height: 3px;
    opacity: 0;
    pointer-events: none;
    transition: opacity .25s var(--ease);
}

.navbar-progress.is-active {
    opacity: 1;
}

.navbar-progress span {
    display: block;
    width: 0;
    height: 100%;
    border-radius: 0 3px 3px 0;
    background: var(--grad-brand);
    box-shadow: 0 0 12px 1px rgba(var(--a1-rgb), .75);
    transition: width .25s var(--ease-out);
}

/* ============================================================= spinner === */

.spinner {
    display: inline-block;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2.5px solid rgba(var(--tint), .14);
    border-top-color: var(--a1);
    animation: spinner-turn .7s linear infinite;
}

.spinner--sm {
    width: 16px;
    height: 16px;
    border-width: 2px;
}

.spinner--lg {
    width: 34px;
    height: 34px;
    border-width: 3px;
}

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

/* Centred block used while a drawer or panel fetches. */
.loading-block {
    display: grid;
    place-items: center;
    gap: 14px;
    padding: 48px 24px;
    text-align: center;
}

.loading-block__text {
    font-size: 13.5px;
    color: var(--text-faint);
}

/* ============================================================ skeleton === */

.skeleton {
    position: relative;
    overflow: hidden;
    border-radius: var(--r-sm);
    background: rgba(var(--tint), .07);
}

.skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent, rgba(var(--tint), .13), transparent);
    animation: skeleton-sweep 1.3s var(--ease) infinite;
}

@keyframes skeleton-sweep {
    to { transform: translateX(100%); }
}

.skeleton--text {
    height: 11px;
    margin-bottom: 8px;
}

.skeleton--title {
    height: 15px;
    width: 45%;
    margin-bottom: 12px;
}

.skeleton--row {
    height: 52px;
    margin-bottom: 8px;
    border-radius: var(--r-md);
}

/* ==================================================== reduced motion ==== */

@media (prefers-reduced-motion: reduce) {

    /* The mark is shown fully assembled rather than building itself, and the
       `both` fill on those animations is what would otherwise hold the pieces
       at their opening frame — hence the explicit opacity and transform. */
    .loader__logo,
    .loader__crown,
    .loader__spine,
    .loader__bowl,
    .loader__word,
    .loader__tag,
    .loader__bar,
    .loader__bar span,
    .loader__ring-arc,
    .loader__ring-arc2,
    .loader::before,
    .loader::after,
    .loader__aura,
    .loader__spark,
    .loader__spark::before {
        animation: none !important;
        opacity: 1;
        transform: none;
    }

    /* The shimmer relies on its animation to ever reveal the lighter half of
       the gradient — frozen, it would otherwise just be a static edge inside
       the letters, so it falls back to a plain solid fill instead. */
    .loader__oss {
        background: none;
        -webkit-background-clip: initial;
        background-clip: initial;
        color: var(--logo-ink);
    }

    .loader__spark::before {
        opacity: .6;
    }

    .skeleton::after {
        animation: none;
    }

    .spinner {
        animation-duration: 2s;
    }
}
