/* =====================================================================
   iOS 26 "Liquid Glass" loaders
   ---------------------------------------------------------------------
   One loading surface for the three places the app parks the user:

     #pre-loader        the veil over the content column while a page
                        loads (app/Views/layout/index.php)
     .circle-loader     the modal body while its content is fetched
                        (app/Views/modal/index.php, cloned by app.js) and
                        the .modal-mask app.js lays over a modal body on
                        submit (maskModal())
     .circle-done       the check the mask shows when the save succeeded
                        (closeAjaxModal())
     #app-loader        the floating loader appLoader.show() appends for
                        ajax tabs, list reloads, form posts - by default in
                        the bottom-right corner of the page, or inside the
                        container a caller names (app.js)

   The look, everywhere:

     - a glass tile (squircle, blur, hairline highlight, specular sheen,
       soft shadow) - the same element in all three places
     - the 8-spoke iOS activity indicator: ONE element, an SVG mask over
       the ink colour, rotated in 8 steps like the native one
     - soft ambient lights behind the tile; they are what the glass
       refracts, and they make it read as glass instead of as a grey
       square in both themes
     - on the page the tile is centred in the CONTENT column, not the
       viewport, so it sits right whatever the sidebar state is
     - on `window.load` the tile zooms out and the veil dissolves
       (`.nz-pl26-leaving`, added by assets/js/general_helper.js)

   Dark mode is NIZU's body.color-1E202D theme, not prefers-color-scheme.
   Every colour is a token on <body>, re-set under that class, so the
   modal loader and the page loader can never disagree.
   ===================================================================== */

body {
    /* geometry */
    --nz-pl26-tile: 104px;
    --nz-pl26-radius: 30px;
    --nz-pl26-indicator: 40px;

    /* motion - the iOS spring */
    --nz-pl26-ease: cubic-bezier(0.32, 0.72, 0, 1);
    --nz-pl26-in: 320ms;
    --nz-pl26-out: 420ms;

    /* material (light) */
    --nz-pl26-glass: rgba(255, 255, 255, 0.58);
    --nz-pl26-blur: saturate(180%) blur(26px);
    --nz-pl26-stroke: rgba(255, 255, 255, 0.85);
    --nz-pl26-hairline: rgba(60, 60, 67, 0.1);
    --nz-pl26-sheen: rgba(255, 255, 255, 0.9);
    --nz-pl26-sheen-band: rgba(255, 255, 255, 0.28);
    --nz-pl26-shadow: 0 18px 44px rgba(31, 36, 52, 0.12), 0 2px 6px rgba(31, 36, 52, 0.06);

    /* ambient lights (light) */
    --nz-pl26-glow-a: rgba(0, 122, 255, 0.34);
    --nz-pl26-glow-b: rgba(175, 82, 222, 0.24);
    --nz-pl26-glow-c: rgba(48, 209, 88, 0.16);

    /* the indicator ink and the success check */
    --nz-pl26-ink: rgba(60, 60, 67, 0.66);
    --nz-pl26-ok: #34c759;
}

/* NIZU dark = body.color-1E202D (theme stylesheet); the public pages in
   "auto" mode get the same class from the device preference */
body.color-1E202D {
    --nz-pl26-glass: rgba(52, 56, 70, 0.55);
    --nz-pl26-stroke: rgba(255, 255, 255, 0.16);
    --nz-pl26-hairline: rgba(255, 255, 255, 0.06);
    --nz-pl26-sheen: rgba(255, 255, 255, 0.16);
    --nz-pl26-sheen-band: rgba(255, 255, 255, 0.06);
    --nz-pl26-shadow: 0 22px 50px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);

    --nz-pl26-glow-a: rgba(91, 115, 232, 0.5);
    --nz-pl26-glow-b: rgba(191, 90, 242, 0.34);
    --nz-pl26-glow-c: rgba(48, 209, 88, 0.2);

    --nz-pl26-ink: rgba(235, 235, 245, 0.72);
    --nz-pl26-ok: #30d158;
}

/* ============================================================ the tile === */

#pre-loader .nz-pl26__tile,
.circle-loader,
.circle-done,
body .app-loader {
    position: relative;
    box-sizing: border-box;
    width: var(--nz-pl26-tile);
    height: var(--nz-pl26-tile);
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: var(--nz-pl26-radius);
    /* the iOS continuous corner where the browser has it; the plain radius
       above is what everyone else sees */
    corner-shape: squircle;
    background: var(--nz-pl26-glass);
    -webkit-backdrop-filter: var(--nz-pl26-blur);
    backdrop-filter: var(--nz-pl26-blur);
    box-shadow:
        var(--nz-pl26-shadow),
        inset 0 1px 0 var(--nz-pl26-stroke),
        inset 0 0 0 1px var(--nz-pl26-hairline);
    display: flex;
    align-items: center;
    justify-content: center;
    transform: scale(0.92);
    opacity: 0;
    animation: nz-pl26-tile-in var(--nz-pl26-in) var(--nz-pl26-ease) forwards;
    will-change: transform, opacity;
    /* .circle-loader / .circle-done used to be spinning bordered circles */
    font-size: 0;
    line-height: 0;
    overflow: hidden;
}

/* the specular sheen along the top edge: what makes a flat tint read as glass */
#pre-loader .nz-pl26__tile::before,
.circle-loader::before,
.circle-done::before,
body .app-loader::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    corner-shape: inherit;
    background:
        radial-gradient(120% 70% at 30% -10%, var(--nz-pl26-sheen), transparent 55%),
        linear-gradient(180deg, var(--nz-pl26-sheen-band), rgba(255, 255, 255, 0) 45%);
    pointer-events: none;
}

/* ============================================= the activity indicator === */
/* the native indicator is a still picture of 8 spokes fading clockwise,
   turned an eighth of a circle at a time - so: one element, that picture
   as a mask over the ink colour, rotated in 8 steps */

#pre-loader .nz-pl26__indicator,
.circle-loader::after,
body .app-loader > .loading {
    content: "";
    position: relative;
    display: block;
    width: var(--nz-pl26-indicator);
    height: var(--nz-pl26-indicator);
    background: var(--nz-pl26-ink);
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'><g fill='white'><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='1'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.88' transform='rotate(45 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.76' transform='rotate(90 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.64' transform='rotate(135 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.52' transform='rotate(180 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.4' transform='rotate(225 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.29' transform='rotate(270 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.18' transform='rotate(315 20 20)'/></g></svg>") center / contain no-repeat;
    mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'><g fill='white'><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='1'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.88' transform='rotate(45 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.76' transform='rotate(90 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.64' transform='rotate(135 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.52' transform='rotate(180 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.4' transform='rotate(225 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.29' transform='rotate(270 20 20)'/><rect x='18.25' y='1' width='3.5' height='11' rx='1.75' opacity='.18' transform='rotate(315 20 20)'/></g></svg>") center / contain no-repeat;
    animation: nz-pl26-spin 0.9s steps(8, end) infinite;
}

/* ================================================== the success check === */
/* closeAjaxModal() swaps the loader for <div class="circle-done"><i
   data-feather="check"></i></div> and adds .ok a frame later. Nothing runs
   feather.replace() on it, so the check is drawn here: two strokes of a
   rotated corner, revealed by .ok */

.circle-done i,
.circle-done svg {
    display: none !important;
}

.circle-done::after {
    content: "";
    position: relative;
    display: block;
    width: 34px;
    height: 18px;
    margin-top: -8px;
    border-left: 4px solid var(--nz-pl26-ok);
    border-bottom: 4px solid var(--nz-pl26-ok);
    border-radius: 2px;
    transform: rotate(-45deg) scale(0.4);
    opacity: 0;
    transition:
        transform var(--nz-pl26-out) var(--nz-pl26-ease),
        opacity 200ms var(--nz-pl26-ease);
}

.circle-done.ok::after {
    transform: rotate(-45deg) scale(1);
    opacity: 1;
}

/* ========================================================== the modal === */

/* the placeholder body while the modal content is fetched: app.js clones
   #ajaxModalOriginalContent and renames .original-modal-body to .modal-body,
   so both spellings are covered. The lights live on the container's own
   pseudo-elements, since the tile already spends its two */
.original-modal-body,
.modal-body:has(> .circle-loader),
.modal-mask {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    /* maskModal() writes an inline padding-top that used to centre the old
       80px circle by hand; the flex box above centres whatever height */
    padding-top: 0 !important;
    padding-bottom: 0 !important;
}

.original-modal-body,
.modal-body:has(> .circle-loader) {
    min-height: 240px;
}

.original-modal-body::before,
.original-modal-body::after,
.modal-body:has(> .circle-loader)::before,
.modal-body:has(> .circle-loader)::after,
.modal-mask::before,
.modal-mask::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 260px;
    height: 260px;
    margin: -130px 0 0 -130px;
    border-radius: 50%;
    filter: blur(40px);
    pointer-events: none;
    opacity: 0;
    animation: nz-pl26-glow-in 900ms var(--nz-pl26-ease) forwards;
}

.original-modal-body::before,
.modal-body:has(> .circle-loader)::before,
.modal-mask::before {
    background: radial-gradient(circle at 50% 50%, var(--nz-pl26-glow-a), transparent 68%);
    transform: translate(-70px, -50px);
    animation: nz-pl26-glow-in 900ms var(--nz-pl26-ease) forwards,
        nz-pl26-drift-a 9s ease-in-out 900ms infinite alternate;
}

.original-modal-body::after,
.modal-body:has(> .circle-loader)::after,
.modal-mask::after {
    background: radial-gradient(circle at 50% 50%, var(--nz-pl26-glow-b), transparent 68%);
    transform: translate(80px, 50px);
    animation: nz-pl26-glow-in 900ms var(--nz-pl26-ease) 120ms forwards,
        nz-pl26-drift-b 11s ease-in-out 900ms infinite alternate;
}

/* the tile paints above the lights */
.modal-body > .circle-loader,
.original-modal-body > .circle-loader,
.modal-mask > .circle-loader,
.modal-mask > .circle-done {
    z-index: 1;
}

/* ====================================================== the appLoader === */
/* appLoader.show() (app.js) appends
     <div id="app-loader" class="app-loader" style="z-index:…;top:…;right:…">
       <div class="loading"></div></div>
   to <body>, or to the container a caller names, and positions it with
   that inline style: nothing here touches top/right/bottom/left. The old
   box was 90px with the ring 45px left of its `right` edge; the tile is the
   `--nz-pl26-tile` square, and when the caller centred it on a container
   (`right: half the width`) it is shifted by half its own width so the tile
   itself sits on the centre. `body .app-loader` outranks the theme sheet's
   `.app-loader { background: transparent }`, which is printed after this
   file.
   The tile is NOT `position: relative` here - the inline style needs the
   absolute positioning app.all.css gives .app-loader. */

body .app-loader {
    position: absolute;
    margin: 0;
    padding: 0;
    /* the base rule pins it 45px from the bottom, 5px from the right; keep
       it clear of the iOS 26 mobile tab bar and off the very edge */
    bottom: 45px;
    right: 16px;
}

/* the old rule mirrored bottom:45px/right:5px; the caller-supplied inline
   `right:` (a container centre) is honoured by shifting half a tile */
body .app-loader[style*="right:"] {
    transform: translateX(50%) scale(0.92);
    animation-name: nz-pl26-tile-in-shifted;
}

/* the ajax tabs pass `bottom:auto` and no top, so the tile lands flush
   against the tab bar at the top of the pane: give it some air */
body .app-loader[style*="bottom:auto"] {
    margin-top: 28px;
}

body .app-loader > .loading {
    /* the base rule draws a 70px spinning bordered ring: reset all of it */
    width: var(--nz-pl26-indicator);
    height: var(--nz-pl26-indicator);
    border: 0;
    border-radius: 0;
    background: var(--nz-pl26-ink);
}

@keyframes nz-pl26-tile-in-shifted {
    to {
        transform: translateX(50%) scale(1);
        opacity: 1;
    }
}

/* ====================================================== the page veil === */

#pre-loader {
    /* where the content column starts: mirrors .page-container's margins
       (assets/scss/style.scss + _media.scss) state by state below */
    --nz-pl26-left: 250px;
    --nz-pl26-top: 65px;

    position: fixed;
    inset: 0;
    width: auto;
    height: auto;
    /* above every piece of content (the dashboard's sticky bar is 20, a
       frozen appTable cell 41), below the chrome: the mobile tab bar is 99,
       the topbar 999, the sidebar 1005. The old value, 10, let the dashboard
       bar and its cards float over the veil while they loaded */
    z-index: 98;
    overflow: hidden;
    /* the canvas colour itself stays with the theme sheets (#f9f9f9 light,
       #1C1F26 dark) and the head pre-paint - not repeated here */
    opacity: 1;
    transition: opacity var(--nz-pl26-out) var(--nz-pl26-ease);
}

@media (min-width: 991px) {
    body.sidebar-toggled #pre-loader {
        --nz-pl26-left: 70px;
    }
}

@media (max-width: 990px) {
    #pre-loader {
        --nz-pl26-left: 0px;
    }
}

@media (max-width: 500px) {
    #pre-loader {
        --nz-pl26-top: 10px;
    }
}

body.compact-view-active #pre-loader,
.public-page-container #pre-loader {
    --nz-pl26-left: 0px;
}

#pre-loader .nz-pl26__stage {
    position: absolute;
    inset-block: var(--nz-pl26-top) 0;
    inset-inline: var(--nz-pl26-left) 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* the ambient lights of the page veil */
#pre-loader .nz-pl26__glow {
    position: absolute;
    width: 320px;
    height: 320px;
    border-radius: 50%;
    filter: blur(48px);
    opacity: 0;
    will-change: transform, opacity;
    animation: nz-pl26-glow-in 900ms var(--nz-pl26-ease) forwards;
}

#pre-loader .nz-pl26__glow--a {
    background: radial-gradient(circle at 50% 50%, var(--nz-pl26-glow-a), transparent 68%);
    transform: translate(-90px, -70px);
    animation: nz-pl26-glow-in 900ms var(--nz-pl26-ease) forwards,
        nz-pl26-drift-a 9s ease-in-out 900ms infinite alternate;
}

#pre-loader .nz-pl26__glow--b {
    background: radial-gradient(circle at 50% 50%, var(--nz-pl26-glow-b), transparent 68%);
    transform: translate(100px, 60px);
    animation: nz-pl26-glow-in 900ms var(--nz-pl26-ease) 120ms forwards,
        nz-pl26-drift-b 11s ease-in-out 900ms infinite alternate;
}

#pre-loader .nz-pl26__glow--c {
    width: 220px;
    height: 220px;
    background: radial-gradient(circle at 50% 50%, var(--nz-pl26-glow-c), transparent 68%);
    transform: translate(20px, 120px);
    animation: nz-pl26-glow-in 900ms var(--nz-pl26-ease) 240ms forwards,
        nz-pl26-drift-c 13s ease-in-out 900ms infinite alternate;
}

/* the exit */
#pre-loader.nz-pl26-leaving {
    opacity: 0;
    /* the page underneath is live from the first frame of the dissolve */
    pointer-events: none;
}

#pre-loader.nz-pl26-leaving .nz-pl26__tile {
    animation: none;
    transform: scale(1.14);
    opacity: 0;
    transition:
        transform var(--nz-pl26-out) var(--nz-pl26-ease),
        opacity calc(var(--nz-pl26-out) * 0.75) var(--nz-pl26-ease);
}

#pre-loader.nz-pl26-leaving .nz-pl26__glow {
    animation: none;
    opacity: 0;
    transition: opacity var(--nz-pl26-out) var(--nz-pl26-ease);
}

/* ========================================================= keyframes === */

@keyframes nz-pl26-tile-in {
    to {
        transform: scale(1);
        opacity: 1;
    }
}

@keyframes nz-pl26-glow-in {
    to {
        opacity: 1;
    }
}

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

@keyframes nz-pl26-drift-a {
    from { transform: translate(-90px, -70px); }
    to { transform: translate(-40px, -110px); }
}

@keyframes nz-pl26-drift-b {
    from { transform: translate(100px, 60px); }
    to { transform: translate(60px, 110px); }
}

@keyframes nz-pl26-drift-c {
    from { transform: translate(20px, 120px); }
    to { transform: translate(-60px, 80px); }
}

/* ===================================================== accessibility === */

@media (prefers-reduced-motion: reduce) {
    #pre-loader .nz-pl26__glow,
    .original-modal-body::before,
    .original-modal-body::after,
    .modal-body:has(> .circle-loader)::before,
    .modal-body:has(> .circle-loader)::after,
    .modal-mask::before,
    .modal-mask::after {
        animation: nz-pl26-glow-in 1ms linear forwards;
    }

    #pre-loader .nz-pl26__tile,
    .circle-loader,
    .circle-done,
    body .app-loader {
        animation: nz-pl26-tile-in 1ms linear forwards;
    }

    body .app-loader[style*="right:"] {
        animation-name: nz-pl26-tile-in-shifted;
    }

    #pre-loader.nz-pl26-leaving .nz-pl26__tile {
        transform: none;
        transition: opacity 200ms linear;
    }

    #pre-loader.nz-pl26-leaving {
        transition-duration: 200ms;
    }
}

@media print {
    #pre-loader {
        display: none !important;
    }
}
