/*
 * Animated abstract backdrops — loaded ONLY when the visitor/site picked one
 * (see inc/enqueue.php), so a site running the plain backdrop pays nothing.
 *
 * Performance contract, deliberately strict — this paints behind EVERY page:
 *   - only `transform` and `opacity` are animated: both run on the compositor,
 *     so there is no layout and no repaint per frame;
 *   - colour comes from soft radial gradients, never `filter: blur()` — a blur
 *     over a full-screen layer is the one thing that melts weak GPUs;
 *   - at most 3 moving layers, each a huge slow shape (40-90s loops) — cheap to
 *     composite and calm to look at;
 *   - `contain: strict` + `pointer-events: none` keep it out of hit-testing and
 *     out of the rest of the page's layout work;
 *   - animation freezes on `prefers-reduced-motion` and on phones, where the
 *     battery cost is real and the effect is barely visible behind content.
 *
 * Hue follows the accent the visitor already chose (--accent-h), so the backdrop
 * always matches the UI instead of being a second, clashing colour control.
 */

.bg-atmos[data-fx]:not([data-fx="off"]) {
  contain: strict;
  overflow: hidden;
}

/* Shared shape layer. Two pseudo-elements + one child = 3 movers max. */
.bg-atmos[data-fx]:not([data-fx="off"]) .bg-fx,
.bg-atmos[data-fx]:not([data-fx="off"])::before,
.bg-atmos[data-fx]:not([data-fx="off"])::after {
  pointer-events: none;
}
.bg-fx {
  position: absolute;
  inset: -25%;
  pointer-events: none;
  will-change: transform;
  transform: translateZ(0);
}
.bg-fx span {
  position: absolute;
  display: block;
  border-radius: 50%;
  will-change: transform;
}

/* ---------------------------------------------------------------- aurora
   Two wide ribbons of light drifting across the top, like a slow northern sky. */
[data-fx="aurora"] .bg-fx span {
  width: 90vw; height: 60vh;
  filter: none;
  opacity: 0.55;
  mix-blend-mode: screen;
}
[data-fx="aurora"] .bg-fx span:nth-child(1) {
  left: -20vw; top: -18vh;
  background: radial-gradient(ellipse at center,
    oklch(0.62 0.2 var(--accent-h, 285) / 0.55), transparent 65%);
  animation: fxAuroraA 46s ease-in-out infinite alternate;
}
[data-fx="aurora"] .bg-fx span:nth-child(2) {
  right: -25vw; top: -8vh;
  background: radial-gradient(ellipse at center,
    oklch(0.6 0.18 calc(var(--accent-h, 285) + 55) / 0.45), transparent 65%);
  animation: fxAuroraB 62s ease-in-out infinite alternate;
}
[data-fx="aurora"] .bg-fx span:nth-child(3) {
  left: 15vw; top: 30vh;
  background: radial-gradient(ellipse at center,
    oklch(0.55 0.16 calc(var(--accent-h, 285) - 45) / 0.35), transparent 60%);
  animation: fxAuroraC 78s ease-in-out infinite alternate;
}
@keyframes fxAuroraA {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(12vw, 6vh, 0) scale(1.15); }
}
@keyframes fxAuroraB {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-14vw, 10vh, 0) scale(1); }
}
@keyframes fxAuroraC {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(8vw, -12vh, 0) scale(1.2); }
}

/* ------------------------------------------------------------------ orbs
   Three soft spheres floating on long, unequal orbits. */
[data-fx="orbs"] .bg-fx span {
  opacity: 0.5;
  mix-blend-mode: screen;
}
[data-fx="orbs"] .bg-fx span:nth-child(1) {
  width: 46vw; height: 46vw; left: -8vw; top: -10vh;
  background: radial-gradient(circle at 35% 35%,
    oklch(0.65 0.21 var(--accent-h, 285) / 0.6), transparent 62%);
  animation: fxOrbA 54s ease-in-out infinite alternate;
}
[data-fx="orbs"] .bg-fx span:nth-child(2) {
  width: 38vw; height: 38vw; right: -6vw; top: 22vh;
  background: radial-gradient(circle at 60% 40%,
    oklch(0.62 0.19 calc(var(--accent-h, 285) + 70) / 0.5), transparent 62%);
  animation: fxOrbB 71s ease-in-out infinite alternate;
}
[data-fx="orbs"] .bg-fx span:nth-child(3) {
  width: 30vw; height: 30vw; left: 34vw; bottom: -12vh;
  background: radial-gradient(circle at 50% 50%,
    oklch(0.58 0.17 calc(var(--accent-h, 285) - 60) / 0.45), transparent 62%);
  animation: fxOrbC 88s ease-in-out infinite alternate;
}
@keyframes fxOrbA {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(16vw, 14vh, 0); }
}
@keyframes fxOrbB {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-18vw, -10vh, 0); }
}
@keyframes fxOrbC {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(10vw, -16vh, 0) scale(1.25); }
}

/* ------------------------------------------------------------------ mesh
   One big multi-stop gradient sheet, rotating very slowly. Reads as a living
   colour wash rather than distinct shapes. */
[data-fx="mesh"] .bg-fx {
  inset: -50%;
  background:
    radial-gradient(circle at 22% 28%, oklch(0.6 0.2 var(--accent-h, 285) / 0.5), transparent 45%),
    radial-gradient(circle at 78% 26%, oklch(0.58 0.18 calc(var(--accent-h, 285) + 60) / 0.42), transparent 45%),
    radial-gradient(circle at 60% 78%, oklch(0.55 0.17 calc(var(--accent-h, 285) - 55) / 0.4), transparent 48%),
    radial-gradient(circle at 26% 74%, oklch(0.5 0.14 calc(var(--accent-h, 285) + 25) / 0.32), transparent 46%);
  animation: fxMesh 120s linear infinite;
  opacity: 0.75;
}
@keyframes fxMesh {
  from { transform: rotate(0deg) scale(1.05); }
  to   { transform: rotate(360deg) scale(1.05); }
}

/* ----------------------------------------------------------------- beams
   A slow conic sweep — like a lighthouse behind frosted glass. */
[data-fx="beams"] .bg-fx {
  inset: -60%;
  background: conic-gradient(from 0deg,
    transparent 0deg,
    oklch(0.6 0.19 var(--accent-h, 285) / 0.28) 25deg,
    transparent 60deg,
    oklch(0.58 0.16 calc(var(--accent-h, 285) + 50) / 0.22) 150deg,
    transparent 200deg,
    oklch(0.55 0.15 calc(var(--accent-h, 285) - 40) / 0.2) 290deg,
    transparent 330deg);
  animation: fxBeams 90s linear infinite;
  opacity: 0.7;
}
@keyframes fxBeams {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* ----------------------------------------------------------------- waves
   Wide diagonal bands gliding sideways — quiet, and the cheapest of the set. */
[data-fx="waves"] .bg-fx {
  inset: -30% -60%;
  background: repeating-linear-gradient(115deg,
    transparent 0 12vh,
    oklch(0.6 0.18 var(--accent-h, 285) / 0.16) 12vh 22vh,
    transparent 22vh 40vh,
    oklch(0.55 0.15 calc(var(--accent-h, 285) + 45) / 0.13) 40vh 50vh,
    transparent 50vh 76vh);
  animation: fxWaves 60s linear infinite;
  opacity: 0.8;
}
@keyframes fxWaves {
  from { transform: translate3d(-12vw, 0, 0); }
  to   { transform: translate3d(12vw, 0, 0); }
}

/* ------------------------------------------------------------- starfield
   Three parallax layers of tiny stars drifting sideways. Dots are painted
   once as box-shadows; only the layer translates. */
[data-fx="starfield"] .bg-fx span {
  width: 3px; height: 3px; border-radius: 50%;
  left: 0; top: 0;
  background: transparent;
}
[data-fx="starfield"] .bg-fx span:nth-child(1) {
  opacity: 0.9;
  box-shadow: 8vw 12vh 0 0 oklch(0.9 0.05 var(--accent-h,285)), 22vw 68vh 0 0 oklch(0.85 0.06 var(--accent-h,285)), 34vw 28vh 0 1px #fff, 47vw 81vh 0 0 oklch(0.9 0.04 var(--accent-h,285)), 58vw 15vh 0 0 #fff, 71vw 55vh 0 1px oklch(0.88 0.05 var(--accent-h,285)), 83vw 33vh 0 0 #fff, 94vw 72vh 0 0 oklch(0.9 0.05 var(--accent-h,285)), 15vw 90vh 0 0 #fff, 64vw 92vh 0 0 oklch(0.85 0.05 var(--accent-h,285)), 40vw 50vh 0 0 #fff, 90vw 8vh 0 0 #fff;
  animation: fxStarA 160s linear infinite;
}
[data-fx="starfield"] .bg-fx span:nth-child(2) {
  opacity: 0.55;
  box-shadow: 5vw 40vh 0 0 #fff, 18vw 22vh 0 0 oklch(0.8 0.05 var(--accent-h,285)), 30vw 75vh 0 0 #fff, 44vw 10vh 0 0 #fff, 52vw 62vh 0 0 oklch(0.82 0.05 var(--accent-h,285)), 67vw 35vh 0 0 #fff, 78vw 88vh 0 0 #fff, 88vw 48vh 0 0 oklch(0.8 0.05 var(--accent-h,285)), 97vw 25vh 0 0 #fff, 26vw 95vh 0 0 #fff, 60vw 5vh 0 0 #fff;
  animation: fxStarA 240s linear infinite;
}
[data-fx="starfield"] .bg-fx span:nth-child(3) {
  opacity: 0.35;
  box-shadow: 3vw 55vh 0 0 #fff, 12vw 8vh 0 0 #fff, 25vw 45vh 0 0 #fff, 38vw 85vh 0 0 #fff, 50vw 30vh 0 0 #fff, 62vw 70vh 0 0 #fff, 74vw 18vh 0 0 #fff, 85vw 60vh 0 0 #fff, 96vw 92vh 0 0 #fff, 20vw 65vh 0 0 #fff, 55vw 95vh 0 0 #fff, 92vw 40vh 0 0 #fff;
  animation: fxStarA 340s linear infinite;
}
@keyframes fxStarA {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-100vw, 0, 0); }
}

/* ------------------------------------------------------------- fireflies
   Three glowing motes wandering on their own looping paths, softly blinking. */
[data-fx="fireflies"] .bg-fx span {
  width: 14px; height: 14px;
  background: radial-gradient(circle, oklch(0.85 0.16 var(--accent-h,285) / 0.9), transparent 70%);
  box-shadow: 30vw 25vh 0 2px oklch(0.8 0.15 var(--accent-h,285) / 0.35), 60vw -12vh 0 1px oklch(0.8 0.14 calc(var(--accent-h,285) + 40) / 0.3);
}
[data-fx="fireflies"] .bg-fx span:nth-child(1) { left: 12vw; top: 62vh; animation: fxFlyA 34s ease-in-out infinite alternate, fxBlink 7s ease-in-out infinite; }
[data-fx="fireflies"] .bg-fx span:nth-child(2) { left: 55vw; top: 30vh; animation: fxFlyB 46s ease-in-out infinite alternate, fxBlink 9s 2s ease-in-out infinite; }
[data-fx="fireflies"] .bg-fx span:nth-child(3) { left: 80vw; top: 74vh; animation: fxFlyC 41s ease-in-out infinite alternate, fxBlink 8s 4s ease-in-out infinite; }
@keyframes fxFlyA { 0% { transform: translate3d(0,0,0); } 33% { transform: translate3d(14vw,-18vh,0); } 66% { transform: translate3d(-6vw,-30vh,0); } 100% { transform: translate3d(10vw,-6vh,0); } }
@keyframes fxFlyB { 0% { transform: translate3d(0,0,0); } 33% { transform: translate3d(-16vw,12vh,0); } 66% { transform: translate3d(8vw,26vh,0); } 100% { transform: translate3d(-10vw,4vh,0); } }
@keyframes fxFlyC { 0% { transform: translate3d(0,0,0); } 50% { transform: translate3d(-20vw,-20vh,0); } 100% { transform: translate3d(-4vw,8vh,0); } }
@keyframes fxBlink { 0%, 100% { opacity: 0.15; } 45% { opacity: 0.95; } 60% { opacity: 0.4; } }

/* ------------------------------------------------------------------ rain
   Fine diagonal streaks sliding down — window glass on a slow night. */
[data-fx="rain"] .bg-fx {
  inset: -60% -30%;
  background: repeating-linear-gradient(103deg,
    transparent 0 5vh,
    oklch(0.7 0.1 var(--accent-h,285) / 0.11) 5vh calc(5vh + 2px),
    transparent calc(5vh + 2px) 11vh,
    oklch(0.72 0.09 calc(var(--accent-h,285) + 30) / 0.08) 11vh calc(11vh + 1px),
    transparent calc(11vh + 1px) 17vh);
  animation: fxRain 11s linear infinite;
  opacity: 0.9;
}
@keyframes fxRain {
  from { transform: translate3d(0, -17vh, 0); }
  to   { transform: translate3d(-4vw, 17vh, 0); }
}

/* ------------------------------------------------------------------ snow
   Three layers of flakes falling at different speeds with a light sway. */
[data-fx="snow"] .bg-fx span {
  width: 5px; height: 5px; border-radius: 50%;
  left: 0; top: -10vh;
  background: transparent;
}
[data-fx="snow"] .bg-fx span:nth-child(1) {
  opacity: 0.8;
  box-shadow: 6vw 2vh 0 1px #fff, 19vw 30vh 0 0 #fff, 31vw 12vh 0 1px oklch(0.95 0.02 var(--accent-h,285)), 45vw 55vh 0 0 #fff, 57vw 20vh 0 1px #fff, 70vw 70vh 0 0 #fff, 82vw 8vh 0 1px #fff, 93vw 45vh 0 0 oklch(0.95 0.02 var(--accent-h,285)), 25vw 82vh 0 0 #fff, 65vw 92vh 0 1px #fff;
  animation: fxSnowA 26s linear infinite;
}
[data-fx="snow"] .bg-fx span:nth-child(2) {
  opacity: 0.5;
  box-shadow: 12vw 18vh 0 0 #fff, 27vw 62vh 0 0 #fff, 40vw 35vh 0 0 #fff, 52vw 78vh 0 0 #fff, 66vw 48vh 0 0 #fff, 78vw 25vh 0 0 #fff, 90vw 65vh 0 0 #fff, 98vw 10vh 0 0 #fff, 8vw 88vh 0 0 #fff;
  animation: fxSnowB 40s linear infinite;
}
[data-fx="snow"] .bg-fx span:nth-child(3) {
  opacity: 0.3;
  box-shadow: 4vw 42vh 0 0 #fff, 16vw 70vh 0 0 #fff, 34vw 88vh 0 0 #fff, 48vw 15vh 0 0 #fff, 60vw 58vh 0 0 #fff, 74vw 82vh 0 0 #fff, 86vw 38vh 0 0 #fff, 95vw 90vh 0 0 #fff;
  animation: fxSnowA 56s linear infinite reverse;
}
@keyframes fxSnowA {
  0%   { transform: translate3d(0, -20vh, 0); }
  50%  { transform: translate3d(3vw, 40vh, 0); }
  100% { transform: translate3d(-2vw, 100vh, 0); }
}
@keyframes fxSnowB {
  0%   { transform: translate3d(0, -20vh, 0); }
  50%  { transform: translate3d(-4vw, 40vh, 0); }
  100% { transform: translate3d(2vw, 100vh, 0); }
}

/* ------------------------------------------------------------------ grid
   Synthwave floor: a perspective lattice scrolling toward the viewer. */
[data-fx="grid"] .bg-fx {
  inset: 42% -60% -80%;
  background:
    repeating-linear-gradient(90deg,
      oklch(0.65 0.19 var(--accent-h,285) / 0.35) 0 2px, transparent 2px 7vw),
    repeating-linear-gradient(0deg,
      oklch(0.65 0.19 var(--accent-h,285) / 0.3) 0 2px, transparent 2px 6vh),
    linear-gradient(oklch(0.4 0.14 calc(var(--accent-h,285) + 40) / 0.25), transparent 60%);
  transform: perspective(48vh) rotateX(62deg);
  transform-origin: 50% 0;
  animation: fxGrid 4.5s linear infinite;
  opacity: 0.55;
}
@keyframes fxGrid {
  from { transform: perspective(48vh) rotateX(62deg) translate3d(0, 0, 0); }
  to   { transform: perspective(48vh) rotateX(62deg) translate3d(0, 6vh, 0); }
}

/* ----------------------------------------------------------------- pulse
   Concentric rings breathing out from the centre, one after another. */
[data-fx="pulse"] .bg-fx span {
  width: 60vmin; height: 60vmin;
  left: calc(50% - 30vmin); top: calc(50% - 30vmin);
  background: radial-gradient(circle,
    transparent 52%,
    oklch(0.65 0.18 var(--accent-h,285) / 0.35) 56%,
    transparent 62%);
  opacity: 0;
}
[data-fx="pulse"] .bg-fx span:nth-child(1) { animation: fxPulse 9s ease-out infinite; }
[data-fx="pulse"] .bg-fx span:nth-child(2) { animation: fxPulse 9s 3s ease-out infinite; }
[data-fx="pulse"] .bg-fx span:nth-child(3) { animation: fxPulse 9s 6s ease-out infinite; }
@keyframes fxPulse {
  0%   { transform: scale(0.25); opacity: 0; }
  12%  { opacity: 0.7; }
  100% { transform: scale(2.6); opacity: 0; }
}

/* ------------------------------------------------------------------ lava
   Big warm blobs slowly rising and sinking from the bottom — a lava lamp
   at one frame per forever. */
[data-fx="lava"] .bg-fx span { mix-blend-mode: screen; }
[data-fx="lava"] .bg-fx span:nth-child(1) {
  width: 44vw; height: 52vh; left: 4vw; bottom: -30vh;
  background: radial-gradient(ellipse at 50% 60%, oklch(0.6 0.21 var(--accent-h,285) / 0.55), transparent 62%);
  animation: fxLavaA 47s ease-in-out infinite alternate;
}
[data-fx="lava"] .bg-fx span:nth-child(2) {
  width: 36vw; height: 46vh; right: 8vw; bottom: -26vh;
  background: radial-gradient(ellipse at 50% 60%, oklch(0.58 0.19 calc(var(--accent-h,285) + 35) / 0.5), transparent 62%);
  animation: fxLavaB 63s ease-in-out infinite alternate;
}
[data-fx="lava"] .bg-fx span:nth-child(3) {
  width: 26vw; height: 34vh; left: 42vw; bottom: -20vh;
  background: radial-gradient(ellipse at 50% 60%, oklch(0.55 0.17 calc(var(--accent-h,285) - 25) / 0.45), transparent 60%);
  animation: fxLavaA 82s ease-in-out infinite alternate-reverse;
}
@keyframes fxLavaA {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(6vw, -42vh, 0) scale(1.18); }
}
@keyframes fxLavaB {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-8vw, -34vh, 0) scale(0.95); }
}

/* ----------------------------------------------------------------- smoke
   Almost-monochrome veils curling through the page. Barely there — texture,
   not a light show. */
[data-fx="smoke"] .bg-fx span {
  opacity: 0.5;
}
[data-fx="smoke"] .bg-fx span:nth-child(1) {
  width: 85vw; height: 70vh; left: -20vw; top: 5vh;
  background: radial-gradient(ellipse at 40% 50%, oklch(0.6 0.05 var(--accent-h,285) / 0.16), transparent 65%);
  animation: fxSmokeA 90s ease-in-out infinite alternate;
}
[data-fx="smoke"] .bg-fx span:nth-child(2) {
  width: 70vw; height: 60vh; right: -25vw; top: 30vh;
  background: radial-gradient(ellipse at 60% 45%, oklch(0.55 0.06 var(--accent-h,285) / 0.13), transparent 65%);
  animation: fxSmokeB 120s ease-in-out infinite alternate;
}
[data-fx="smoke"] .bg-fx span:nth-child(3) {
  width: 60vw; height: 50vh; left: 20vw; bottom: -15vh;
  background: radial-gradient(ellipse, oklch(0.5 0.04 var(--accent-h,285) / 0.12), transparent 62%);
  animation: fxSmokeA 150s ease-in-out infinite alternate-reverse;
}
@keyframes fxSmokeA {
  from { transform: translate3d(0, 0, 0) rotate(0deg) scale(1); }
  to   { transform: translate3d(14vw, -6vh, 0) rotate(9deg) scale(1.2); }
}
@keyframes fxSmokeB {
  from { transform: translate3d(0, 0, 0) rotate(0deg) scale(1.15); }
  to   { transform: translate3d(-12vw, 8vh, 0) rotate(-7deg) scale(1); }
}

/* ------------------------------------------------------------- spotlight
   A stage beam sweeping lazily from above, with a soft ambient pool. */
[data-fx="spotlight"] .bg-fx {
  inset: -20% -20%;
  background: conic-gradient(from 175deg at 50% -8%,
    transparent 0deg,
    oklch(0.75 0.12 var(--accent-h,285) / 0.22) 8deg,
    oklch(0.8 0.1 var(--accent-h,285) / 0.3) 12deg,
    oklch(0.75 0.12 var(--accent-h,285) / 0.22) 16deg,
    transparent 24deg);
  transform-origin: 50% -8%;
  animation: fxSpot 24s ease-in-out infinite alternate;
  opacity: 0.9;
}
@keyframes fxSpot {
  from { transform: rotate(-14deg); }
  to   { transform: rotate(14deg); }
}

/* ------------------------------------------------------------------ flow
   One oversized three-colour sheet panning diagonally — pure colour weather. */
[data-fx="flow"] .bg-fx {
  inset: -80% -60%;
  background: linear-gradient(118deg,
    oklch(0.55 0.18 var(--accent-h,285) / 0.4),
    oklch(0.5 0.15 calc(var(--accent-h,285) + 70) / 0.3) 35%,
    oklch(0.45 0.12 calc(var(--accent-h,285) - 55) / 0.32) 65%,
    oklch(0.55 0.18 var(--accent-h,285) / 0.4));
  animation: fxFlow 48s ease-in-out infinite alternate;
  opacity: 0.55;
}
@keyframes fxFlow {
  from { transform: translate3d(-8vw, -6vh, 0) scale(1); }
  to   { transform: translate3d(8vw, 6vh, 0) scale(1.12); }
}

/* ----------------------------------------------------------------- bokeh
   Large out-of-focus lights breathing in and out, like a night street seen
   through a fast lens. */
[data-fx="bokeh"] .bg-fx span { mix-blend-mode: screen; }
[data-fx="bokeh"] .bg-fx span:nth-child(1) {
  width: 24vw; height: 24vw; left: 8vw; top: 12vh;
  background: radial-gradient(circle, oklch(0.7 0.17 var(--accent-h,285) / 0.4) 55%, transparent 70%);
  animation: fxBokehA 17s ease-in-out infinite alternate, fxDriftA 60s ease-in-out infinite alternate;
}
[data-fx="bokeh"] .bg-fx span:nth-child(2) {
  width: 16vw; height: 16vw; right: 14vw; top: 30vh;
  background: radial-gradient(circle, oklch(0.72 0.15 calc(var(--accent-h,285) + 55) / 0.38) 55%, transparent 70%);
  animation: fxBokehB 23s ease-in-out infinite alternate, fxDriftB 75s ease-in-out infinite alternate;
}
[data-fx="bokeh"] .bg-fx span:nth-child(3) {
  width: 20vw; height: 20vw; left: 45vw; bottom: 8vh;
  background: radial-gradient(circle, oklch(0.68 0.14 calc(var(--accent-h,285) - 45) / 0.35) 55%, transparent 70%);
  animation: fxBokehA 19s 5s ease-in-out infinite alternate, fxDriftA 90s ease-in-out infinite alternate-reverse;
}
@keyframes fxBokehA { from { opacity: 0.15; } to { opacity: 0.75; } }
@keyframes fxBokehB { from { opacity: 0.6; } to { opacity: 0.1; } }
@keyframes fxDriftA { from { transform: translate3d(0,0,0) scale(1); } to { transform: translate3d(9vw, 5vh, 0) scale(1.15); } }
@keyframes fxDriftB { from { transform: translate3d(0,0,0) scale(1.1); } to { transform: translate3d(-7vw, -6vh, 0) scale(0.95); } }

/* ------------------------------------------------------------------ halo
   One breathing ring of light behind the content centre. Calm, ceremonial. */
[data-fx="halo"] .bg-fx span:nth-child(1) {
  width: 70vmin; height: 70vmin; left: calc(50% - 35vmin); top: calc(50% - 35vmin);
  background: radial-gradient(circle,
    transparent 40%,
    oklch(0.65 0.17 var(--accent-h,285) / 0.3) 52%,
    transparent 66%);
  animation: fxHaloA 11s ease-in-out infinite alternate;
}
[data-fx="halo"] .bg-fx span:nth-child(2) {
  width: 96vmin; height: 96vmin; left: calc(50% - 48vmin); top: calc(50% - 48vmin);
  background: radial-gradient(circle,
    transparent 44%,
    oklch(0.6 0.14 calc(var(--accent-h,285) + 45) / 0.18) 55%,
    transparent 68%);
  animation: fxHaloB 11s ease-in-out infinite alternate;
}
[data-fx="halo"] .bg-fx span:nth-child(3) {
  width: 40vmin; height: 40vmin; left: calc(50% - 20vmin); top: calc(50% - 20vmin);
  background: radial-gradient(circle, oklch(0.7 0.16 var(--accent-h,285) / 0.2), transparent 65%);
  animation: fxHaloA 17s ease-in-out infinite alternate-reverse;
}
@keyframes fxHaloA { from { transform: scale(0.92); opacity: 0.5; } to { transform: scale(1.1); opacity: 0.95; } }
@keyframes fxHaloB { from { transform: scale(1.06); opacity: 0.8; } to { transform: scale(0.94); opacity: 0.35; } }

/* --------------------------------------------------------------- ribbons
   Tall silk bands swaying sideways — theatre curtains in a draught. */
[data-fx="ribbons"] .bg-fx {
  inset: -30% -40%;
  background: repeating-linear-gradient(94deg,
    transparent 0 9vw,
    oklch(0.6 0.16 var(--accent-h,285) / 0.14) 9vw 15vw,
    transparent 15vw 26vw,
    oklch(0.55 0.14 calc(var(--accent-h,285) + 50) / 0.11) 26vw 31vw,
    transparent 31vw 44vw);
  transform: skewX(-6deg);
  animation: fxRibbons 38s ease-in-out infinite alternate;
  opacity: 0.85;
}
@keyframes fxRibbons {
  from { transform: skewX(-6deg) translate3d(-6vw, 0, 0); }
  to   { transform: skewX(-4deg) translate3d(6vw, 0, 0); }
}

/* ---------------------------------------------------------------- sparks
   Warm embers rising from the bottom edge and fading out on the way up. */
[data-fx="sparks"] .bg-fx span {
  width: 6px; height: 6px; border-radius: 50%;
  left: 0; bottom: -6vh; top: auto;
  background: transparent;
}
[data-fx="sparks"] .bg-fx span:nth-child(1) {
  box-shadow: 10vw 0 0 0 oklch(0.75 0.17 var(--accent-h,285)), 32vw -6vh 0 1px oklch(0.7 0.18 calc(var(--accent-h,285) + 20)), 56vw -2vh 0 0 oklch(0.75 0.16 var(--accent-h,285)), 78vw -8vh 0 0 oklch(0.72 0.17 var(--accent-h,285)), 92vw -4vh 0 1px oklch(0.7 0.15 calc(var(--accent-h,285) - 15));
  animation: fxSparkA 14s linear infinite;
}
[data-fx="sparks"] .bg-fx span:nth-child(2) {
  box-shadow: 20vw -3vh 0 0 oklch(0.72 0.16 var(--accent-h,285)), 44vw -9vh 0 0 oklch(0.68 0.17 calc(var(--accent-h,285) + 25)), 66vw -1vh 0 1px oklch(0.74 0.16 var(--accent-h,285)), 86vw -7vh 0 0 oklch(0.7 0.15 var(--accent-h,285));
  animation: fxSparkA 19s 4s linear infinite;
}
[data-fx="sparks"] .bg-fx span:nth-child(3) {
  box-shadow: 6vw -8vh 0 0 oklch(0.7 0.15 var(--accent-h,285)), 27vw -2vh 0 0 oklch(0.72 0.16 calc(var(--accent-h,285) + 15)), 50vw -6vh 0 0 oklch(0.7 0.16 var(--accent-h,285)), 72vw -4vh 0 0 oklch(0.68 0.14 var(--accent-h,285)), 96vw -9vh 0 0 oklch(0.7 0.16 var(--accent-h,285));
  animation: fxSparkA 24s 8s linear infinite;
}
@keyframes fxSparkA {
  0%   { transform: translate3d(0, 0, 0); opacity: 0; }
  8%   { opacity: 0.9; }
  70%  { opacity: 0.5; }
  100% { transform: translate3d(3vw, -110vh, 0); opacity: 0; }
}

/* ---------------------------------------------------------------- vortex
   Two spiral arms turning against each other — a slow galactic churn. */
[data-fx="vortex"] .bg-fx {
  inset: -55%;
  background: conic-gradient(from 0deg,
    transparent 0deg,
    oklch(0.6 0.18 var(--accent-h,285) / 0.3) 40deg,
    transparent 100deg,
    oklch(0.55 0.16 calc(var(--accent-h,285) + 60) / 0.26) 160deg,
    transparent 220deg,
    oklch(0.5 0.15 calc(var(--accent-h,285) - 40) / 0.22) 280deg,
    transparent 340deg);
  animation: fxVortexA 110s linear infinite;
  opacity: 0.65;
}
[data-fx="vortex"] .bg-fx span:nth-child(1) {
  inset: 12%;
  width: auto; height: auto; border-radius: 0;
  background: conic-gradient(from 180deg,
    transparent 0deg,
    oklch(0.62 0.16 calc(var(--accent-h,285) + 30) / 0.24) 60deg,
    transparent 140deg,
    oklch(0.58 0.14 var(--accent-h,285) / 0.2) 230deg,
    transparent 320deg);
  animation: fxVortexB 150s linear infinite;
}
@keyframes fxVortexA { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes fxVortexB { from { transform: rotate(360deg); } to { transform: rotate(0deg); } }

/* Light scheme: the same shapes, dialled down — on white they would shout. */
html[data-theme="light"] [data-fx] .bg-fx { opacity: 0.42; }
html[data-theme="light"] [data-fx] .bg-fx span { mix-blend-mode: multiply; opacity: 0.3; }

/* Phones: keep the colour, drop the motion. A full-screen animation is the last
   thing a battery needs, and behind a scrolling feed nobody sees it move. */
@media (max-width: 640px) {
  [data-fx] .bg-fx,
  [data-fx] .bg-fx span { animation: none !important; }
}

/* Honour the OS setting — freeze, never remove (the colour is part of the design). */
@media (prefers-reduced-motion: reduce) {
  [data-fx] .bg-fx,
  [data-fx] .bg-fx span { animation: none !important; }
}
