/* Sparkle mode — an opt-in celebratory treatment for dialogs, for a short list of
   internal admins. Two independent gates, both required:

     WHO  — body.adp-sparkle, stamped by MainLayout for users on the Sparkle
            allow-list (see Classes/Fun/SparkleOptions.cs).
     WHAT — .adp-sparkle-dialog on the dialog itself. Nothing sparkles by default;
            a dialog opts in by carrying this class.

   To opt a dialog in:
     - BaseEditor-derived editors: CustomCssClass="adp-sparkle-dialog"
       (BaseEditor appends it to the dialog's CssClass — see BaseEditor.razor)
     - plain SfDialog: CssClass="adp-sparkle-dialog"
   Syncfusion puts CssClass on the .e-dialog root, which is why the compound
   selector works — same trick .sf-dialog-auto-height uses in dialog.css.

   Two halves:
     - the border ring, drawn here as a ::before pseudo-element
     - the star field, injected as real elements by wwwroot/js/sparkle.js
   The stars are DOM elements rather than a tiled background because a background
   layer has a single opacity — every star drawn that way pulses in lockstep. One
   element per star is what buys independent size, colour and delay.

   Removing the feature: delete this file, sparkle.js, their two references in
   App.razor, the adpSparkle.start() call in MainLayout, and any
   adp-sparkle-dialog markers.

   No prefers-reduced-motion override here, deliberately. It used to set
   animation:none, which is the right default for decoration nobody asked for — but
   this is gated behind a per-user allow-list, so the only people who ever see it
   are those who explicitly opted in and want it moving. */

/* Registered so the conic gradient's angle can animate — an unregistered custom
   property interpolates as a string and would snap rather than sweep. */
@property --adp-sparkle-angle {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
}

/* ---------------------------------------------------------------- border ring */

/* Keyframes restate a normal drop shadow so the pulse adds to the dialog's usual
   elevation rather than flattening it. */
body.adp-sparkle .e-dialog.adp-sparkle-dialog {
    animation: adp-sparkle-glow 3.2s ease-in-out infinite;
}

/* The two-layer mask (whole box XOR content box) punches out the middle, leaving
   only the band the padding reserves.

   Two animations on purpose: the conic sweep gives directional travel, and the hue
   cycle keeps colour moving even where @property is unsupported and the sweep
   silently degrades to a static gradient. */
body.adp-sparkle .e-dialog.adp-sparkle-dialog::before {
    content: "";
    position: absolute;
    inset: -4px;
    padding: 3px;
    border-radius: inherit;
    pointer-events: none;
    background: conic-gradient(from var(--adp-sparkle-angle),
        #ffd76a, #ff8ecb, #b58cff, #6fd6ff, #7ff0e0, #ffd76a);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    animation:
        adp-sparkle-spin 3.5s linear infinite,
        adp-sparkle-hue 9s linear infinite;
}

/* ----------------------------------------------------------------- star field */

/* Injected by sparkle.js. Extends past the dialog on every side so stars can sit
   outside it; never intercepts pointer events. */
.adp-sparkle-field {
    position: absolute;
    inset: -34px;
    pointer-events: none;
    border-radius: inherit;
}

/* Outer span: position + slow rotation. The negative margin centres the star on
   its anchor point without using transform, which the rotation owns. */
.adp-sparkle-star {
    position: absolute;
    width: var(--size);
    height: var(--size);
    margin: calc(var(--size) / -2);
    animation: adp-star-orbit var(--orbit) linear var(--delay) infinite;
}

/* Inner element: the four-point shape, plus the scale/fade twinkle on its own
   randomised duration and delay. */
.adp-sparkle-star > i {
    display: block;
    width: 100%;
    height: 100%;
    background: var(--color);
    clip-path: polygon(50% 0%, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0% 50%, 39% 39%);
    filter: drop-shadow(0 0 3px var(--color));
    animation: adp-star-twinkle var(--duration) ease-in-out var(--delay) infinite;
}

/* ------------------------------------------------------------------ keyframes */

@keyframes adp-sparkle-spin {
    to {
        --adp-sparkle-angle: 360deg;
    }
}

/* Explicit 0deg start rather than relying on interpolation from the initial
   `filter: none` — spec says that works, but stating it costs nothing. */
@keyframes adp-sparkle-hue {
    from {
        filter: hue-rotate(0deg);
    }

    to {
        filter: hue-rotate(360deg);
    }
}

@keyframes adp-sparkle-glow {
    0%, 100% {
        box-shadow: 0 6px 24px rgba(11, 59, 96, 0.25), 0 0 12px 2px rgba(255, 215, 106, 0.55);
    }

    50% {
        box-shadow: 0 6px 24px rgba(11, 59, 96, 0.25), 0 0 22px 6px rgba(255, 142, 203, 0.65);
    }
}

@keyframes adp-star-orbit {
    to {
        transform: rotate(var(--turn));
    }
}

@keyframes adp-star-twinkle {
    0%, 100% {
        opacity: 0;
        transform: scale(0.25);
    }

    50% {
        opacity: 1;
        transform: scale(1);
    }
}
