/* ============================================================
   Background FX — Aurora + perspective neon Grid
   ------------------------------------------------------------
   Two independent, GPU-friendly ambient layers driven by tokens
   set in applyHubSettings():
     • #bg-aurora — soft flowing accent gradients behind the grid,
       shown only when no custom image/video background is set
       (body.aurora-on).
     • #bg-grid   — a perspective neon grid (floor + ceiling) that
       scrolls toward a glowing horizon, behind the widgets
       (body.grid-on).
   Both animate transform/opacity only (compositor — no per-frame
   repaint) and fully stop under prefers-reduced-motion.
   ============================================================ */

/* Make the shell an explicit layer so the FX can sit behind it. */
.shell { position: relative; z-index: 1; }

/* ── Aurora ──────────────────────────────────────────────── */
#bg-aurora {
  position: fixed;
  inset: 0;
  z-index: 0;            /* behind the shell (z-index:1), above the page bg */
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.45s ease;
  contain: strict;
  /* Confine the blobs' screen-blend to this layer instead of letting it blend
     against the whole page. Without isolation, anything compositing elsewhere
     (e.g. the media tile's blurred cover appearing when music starts) forces
     Chromium to recomposite the page-wide blend group — a frame drop seen as a
     full-screen flicker on some GPUs. The look is unchanged (the page behind is
     near-black, where screen-blend is a no-op). */
  isolation: isolate;
}
body.aurora-on #bg-aurora { opacity: var(--aurora-opacity, 0.45); }

/* Static depth scene (no per-frame cost): a faint accent bloom high above the
   horizon plus a corner vignette. Together they give the page a lit "room"
   the tiles sit in, instead of a flat colour field. */
#bg-aurora::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(130% 100% at 50% -32%, rgba(var(--accent-rgb), 0.16), transparent 56%),
    radial-gradient(145% 145% at 50% 50%, transparent 58%, rgba(0, 0, 0, 0.40) 100%);
}

/* Ambient light pools — big, extremely slow, deliberately understated.
   The intensity/speed settings still map onto --aurora-opacity/--aurora-duration. */
.aurora-blob {
  position: absolute;
  border-radius: 50%;
  /* Keep the blur radius modest: the filter re-runs on the GPU every frame of
     the transform animation, so softness comes mainly from the gradients. */
  filter: blur(72px);
  mix-blend-mode: screen;
  will-change: transform;
}
/* Primary accent pool, upper left — the "key light". */
.aurora-blob.b1 {
  top: -30vh; left: -14vw;
  width: 68vw; height: 68vw;
  background: radial-gradient(circle at center, rgba(var(--accent-rgb), 0.52), transparent 68%);
  animation: aurora-drift-1 calc(var(--aurora-duration, 40s) * 2) ease-in-out infinite;
}
/* Counter pool, lower right — dimmer, drifting against the key light. */
.aurora-blob.b2 {
  bottom: -34vh; right: -16vw;
  width: 62vw; height: 62vw;
  background: radial-gradient(circle at center, rgba(var(--accent-rgb), 0.30), transparent 68%);
  animation: aurora-drift-2 calc(var(--aurora-duration, 40s) * 2.6) ease-in-out infinite;
}
/* Soft neutral "air" above the horizon — breathes, never travels. */
.aurora-blob.b3 {
  top: -12vh; left: 26vw;
  width: 50vw; height: 50vw;
  background: radial-gradient(circle at center, rgba(255, 255, 255, 0.10), transparent 64%);
  animation: aurora-breathe calc(var(--aurora-duration, 40s) * 3.2) ease-in-out infinite;
}

@keyframes aurora-drift-1 {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(7vw, 5vh, 0) scale(1.08); }
}
@keyframes aurora-drift-2 {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.04); }
  50%      { transform: translate3d(-6vw, -4vh, 0) scale(0.96); }
}
@keyframes aurora-breathe {
  0%, 100% { transform: scale(0.94); }
  50%      { transform: scale(1.10); }
}

/* On light surfaces, screen-blending an accent on white is nearly invisible —
   multiply tints the white instead; the dark vignette is lifted so the light
   theme keeps its airy feel. */
:root[data-appearance="light"] .aurora-blob {
  mix-blend-mode: multiply;
  filter: blur(84px);
}
:root[data-appearance="light"] .aurora-blob.b3 { display: none; }
:root[data-appearance="light"] #bg-aurora::before {
  background:
    radial-gradient(130% 100% at 50% -32%, rgba(var(--accent-rgb), 0.10), transparent 56%),
    radial-gradient(145% 145% at 50% 50%, transparent 62%, rgba(20, 30, 40, 0.10) 100%);
}

/* ── Perspective neon grid ───────────────────────────────── */
#bg-grid {
  position: fixed;
  inset: 0;
  z-index: 0;            /* in the background, behind the shell */
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.45s ease;
  perspective: 340px;
  perspective-origin: 50% 50%;
  contain: strict;
}
body.grid-on #bg-grid { opacity: var(--grid-opacity, 0.4); }

/* Glowing horizon line at the vanishing point (screen middle) */
#bg-grid::before {
  content: '';
  position: absolute;
  left: -10%;
  right: -10%;
  top: 50%;
  height: 2px;
  transform: translateY(-50%);
  background: rgba(var(--grid-rgb, 30, 215, 96), 0.85);
  box-shadow: 0 0 22px 5px rgba(var(--grid-rgb, 30, 215, 96), 0.45);
}

.grid-plane {
  position: absolute;
  left: -50%;
  width: 200%;
  height: 150%;
  background-image:
    linear-gradient(to right, rgba(var(--grid-rgb, 30, 215, 96), 0.5) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(var(--grid-rgb, 30, 215, 96), 0.5) 1px, transparent 1px);
  background-size: 52px 52px;
  backface-visibility: hidden;
  will-change: transform;
}
/* Floor: tilts back from the central horizon down to the viewer */
.grid-plane.floor {
  top: 50%;
  transform-origin: 50% 0%;
  transform: rotateX(74deg);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 22%, #000 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 22%, #000 100%);
  animation: grid-floor var(--grid-duration, 18s) linear infinite;
}
/* Ceiling: mirror of the floor above the horizon */
.grid-plane.ceil {
  bottom: 50%;
  transform-origin: 50% 100%;
  transform: rotateX(-74deg);
  -webkit-mask-image: linear-gradient(to top, transparent 0%, #000 22%, #000 100%);
  mask-image: linear-gradient(to top, transparent 0%, #000 22%, #000 100%);
  animation: grid-ceil var(--grid-duration, 18s) linear infinite;
}
/* Scroll exactly one cell (52px) so the loop is seamless; transform-only
   animation keeps it on the compositor (no repaint). */
@keyframes grid-floor {
  from { transform: rotateX(74deg) translateY(0); }
  to   { transform: rotateX(74deg) translateY(52px); }
}
@keyframes grid-ceil {
  from { transform: rotateX(-74deg) translateY(0); }
  to   { transform: rotateX(-74deg) translateY(-52px); }
}

/* ── Lite mode: respect reduced motion (calm static field) ──── */
@media (prefers-reduced-motion: reduce) {
  .aurora-blob,
  .grid-plane { animation: none !important; }
}

/* ── Firefox / Gecko: freeze the ambient motion (GitHub #99). ──────────────
   The aurora blobs are large (68vw) blurred (blur 72px), screen-blended layers.
   Chromium/WebView2 — the dashboard's supported host — animates their drift on
   the compositor for near-free; Firefox re-rasterizes that full-viewport blur on
   every animation frame, so just opening the dashboard in Firefox maxes the CPU/
   GPU (fans spin up, the tab crawls) the whole time it's open. Freezing the drift
   there lets the blurred layers rasterize ONCE and composite from cache — the same
   calm-static state prefers-reduced-motion ships — instead of redrawing per frame.
   Gated on body.fx-motion-lite (set for Gecko only in js/ambient-idle.js), so the
   Chromium experience is unchanged. */
body.fx-motion-lite .aurora-blob,
body.fx-motion-lite .grid-plane { animation: none !important; }

/* ── Game mode: fade out and freeze the ambient layers while a game /
   GPU-intensive app is presenting, so the dashboard stops competing for the
   compositor. Re-enabled automatically when the game exits. ──────────── */
body.game-mode #bg-aurora,
body.game-mode #bg-grid { opacity: 0; }
body.game-mode .aurora-blob,
body.game-mode .grid-plane { animation-play-state: paused; }

/* ── Frosted full-screen overlay open (update modal, etc.): freeze the ambient.
   A full-viewport backdrop-filter blur re-samples everything behind it every
   frame; over the continuously-animating aurora/grid that makes the blur flicker
   on some GPUs/compositors (a known Chromium backdrop-filter-over-animation issue,
   reported on the Xeneon Edge / weaker GPUs — issue #56). Pausing the animation
   while the overlay is up leaves the blurred backdrop static, so there's nothing
   to re-rasterize — invisible behind the blur, and the flicker is gone. Unlike
   game/perf mode the ambient is NOT hidden (opacity kept), so it still shows,
   softly blurred, through the glass. ──────────────────────────────────── */
body.overlay-frozen .aurora-blob,
body.overlay-frozen .grid-plane { animation-play-state: paused; }

/* ── Hybrid GPU: freeze the ambient for the whole session. On an iGPU+dGPU
   machine the native shell deliberately renders on the chip that drives the
   target display (apps/native/src-tauri/src/gpu.rs) so composited frames are not
   copied cross-adapter — ~1.5 idle CPU cores saved, but far less real-time budget
   on the weaker chip. js/custom-bg.js and js/deck.js already freeze the animated
   background and the Deck decor on this signal; the aurora/grid were left out by
   oversight until v4.6.2, which meant the two layers Xenon draws for decoration
   kept animating while the background the USER chose was the one stopped —
   exactly backwards, and it sent a theme author debugging their own code (#118).
   Unlike the freezes above this one is not contextual: it holds for the session,
   so the ambient is a still image on this hardware. The user can switch it off
   (hubSettings.hybridGpuAnimationPause → js/native-bridge.js drops the class),
   accepting the frame-rate cost. No effect on single-GPU machines, dual-discrete
   rigs, or the browser surface. ─────────────────────────────────────────── */
body.low-power-gpu .aurora-blob,
body.low-power-gpu .grid-plane { animation-play-state: paused; }

/* ── Idle / hidden: pause the ambient once the user stops interacting, or when
   the dashboard tab is hidden. The layers stay visible at their current frame
   (opacity untouched) but the per-frame compositor work stops — the real win on
   hybrid-GPU setups, where the dashboard renders on the discrete GPU while the
   Xeneon Edge is driven by the integrated GPU, so every presented frame is copied
   cross-adapter and keeps the discrete GPU awake for a static picture. Resumes
   instantly on the next pointer/key/touch input or when the tab is shown again
   (js/ambient-idle.js). Purely an optimization — the look is identical while the
   user is present, and nothing is ever hidden. ──────────────────────────── */
body.ambient-idle .aurora-blob,
body.ambient-idle .grid-plane { animation-play-state: paused; }
/* The dashboard's many small persistent decorative loops (blinking clock colon,
   ✦ logo shimmer, live/status dots, the "no media" equaliser + art spin, the
   Vitals heartbeat, Bit's bob, ambient Deck keys) are NOT frozen here in CSS —
   they are paused precisely, via the Web Animations API, in js/ambient-idle.js
   (pause every *infinite* running animation, resume on wake). A CSS allowlist
   was tried and is unmaintainable: these loops live on ::before/::after pseudos
   and behind ancestor-state selectors, so a class list silently misses half of
   them, while a blanket `*` pause would strand a toast or now-playing card mid
   entry-animation at opacity 0. Filtering to infinite iterations gets exactly
   the decorative loops and never a finite reveal. */

/* ── Performance mode: a broader, user-confirmed optimization. Hides the
   ambient background layers (like game mode) and stops decorative @keyframes
   animations across the dashboard to cut compositor work. Fully reversible —
   removing body.perf-mode restores everything.
   IMPORTANT: animations must COMPLETE instantly, never freeze (play-state:
   paused). An element revealed while perf-mode is on re-triggers its entry
   animation; frozen at its first frame (opacity: 0) it stays INVISIBLE even
   though the JS shows it — this hid the Game Companion pill and left a blank
   hole where the layout-editor toolbar belongs. Forcing a single ~instant
   iteration lands every element on its final visible state, while infinite
   decorative loops (the real GPU cost) finish one 10ms pass and stop. ──── */
body.perf-mode #bg-aurora,
body.perf-mode #bg-grid,
body.perf-mode #custom-bg-layer { opacity: 0; }
body.perf-mode *,
body.perf-mode *::before,
body.perf-mode *::after {
  animation-duration: 0.01s !important;
  animation-delay: 0s !important;
  animation-iteration-count: 1 !important;
}

/* ============================================================
   Static background — premium, zero-animation
   ------------------------------------------------------------
   An alternative to the animated aurora for users who want the
   dashboard to look rich but cost essentially nothing to render.
   The whole composition is a stack of static gradients painted
   ONCE: no @keyframes, no canvas, no rAF — after the first paint
   the compositor does nothing, so idle cost ≈ a flat colour.
   Selected via Settings → Sfondo (data-bg-static on <body>); it is
   mutually exclusive with #bg-aurora (settings.js switches one off
   when the other is on) and, like the aurora/grid, hidden when a
   custom image/video background is set. Because nothing animates,
   it is deliberately NOT paused by game / performance / idle mode —
   keeping it visible there is free.
   ============================================================ */
#bg-static {
  position: fixed;
  inset: 0;
  z-index: 0;            /* behind the shell (z-index:1), like the aurora/grid */
  pointer-events: none;
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.45s ease;
  contain: strict;
  isolation: isolate;
}

/* Code-defined animated background: a sandboxed iframe (see js/custom-bg.js)
   painting a full-viewport canvas. Same backdrop layer as the aurora/static —
   behind the shell, never intercepting touch. Fades in when mounted. */
#custom-bg-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  contain: strict;
  isolation: isolate;
  opacity: 0;
  transition: opacity 0.45s ease;
}
body.custom-bg-on #custom-bg-layer { opacity: 1; }
#custom-bg-layer iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
  pointer-events: none;
}
body[data-bg-static] #bg-static { opacity: var(--static-opacity, 1); }

/* Fine static grain, painted once. Large accent gradients band visibly on the
   Xeneon Edge LCD; a low-alpha noise overlay dithers those steps away. It never
   re-rasterizes (no animation), so it stays effectively free. */
#bg-static::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.045;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
:root[data-appearance="light"] #bg-static::after { opacity: 0.03; }

/* ── Nebulosa: the aurora, frozen — soft accent pools + depth vignette ── */
body[data-bg-static="nebulosa"] #bg-static {
  background:
    radial-gradient(120% 95% at 16% 10%, rgba(var(--accent-rgb), 0.42), transparent 55%),
    radial-gradient(110% 100% at 88% 90%, rgba(var(--accent-rgb), 0.22), transparent 58%),
    radial-gradient(80% 72% at 62% 28%, rgba(255, 255, 255, 0.06), transparent 60%),
    radial-gradient(150% 130% at 50% 46%, transparent 52%, rgba(0, 0, 0, 0.55) 100%),
    linear-gradient(180deg, #0b0e13, #070a0e);
}
:root[data-appearance="light"] body[data-bg-static="nebulosa"] #bg-static {
  background:
    radial-gradient(120% 95% at 16% 10%, rgba(var(--accent-rgb), 0.30), transparent 56%),
    radial-gradient(110% 100% at 88% 90%, rgba(var(--accent-rgb), 0.16), transparent 60%),
    radial-gradient(150% 130% at 50% 46%, transparent 60%, rgba(20, 30, 45, 0.10) 100%),
    linear-gradient(180deg, #f4f7fb, #e7ecf3);
}

/* ── Prisma: mesh gradient — accent-led with analogous violet/cyan depth ── */
body[data-bg-static="prisma"] #bg-static {
  background:
    radial-gradient(90% 82% at 8% 18%, rgba(var(--accent-rgb), 0.40), transparent 52%),
    radial-gradient(82% 80% at 86% 12%, rgba(132, 92, 255, 0.30), transparent 55%),
    radial-gradient(95% 92% at 78% 92%, rgba(var(--accent-rgb), 0.26), transparent 55%),
    radial-gradient(80% 80% at 22% 96%, rgba(40, 185, 235, 0.24), transparent 55%),
    linear-gradient(135deg, #0a0d13, #0c0910);
}
:root[data-appearance="light"] body[data-bg-static="prisma"] #bg-static {
  background:
    radial-gradient(90% 82% at 8% 18%, rgba(var(--accent-rgb), 0.26), transparent 54%),
    radial-gradient(82% 80% at 86% 12%, rgba(132, 92, 255, 0.20), transparent 56%),
    radial-gradient(95% 92% at 78% 92%, rgba(var(--accent-rgb), 0.18), transparent 56%),
    radial-gradient(80% 80% at 22% 96%, rgba(40, 185, 235, 0.16), transparent 56%),
    linear-gradient(135deg, #f5f7fb, #eef0f7);
}

/* ── Halo: cinematic single accent glow into deep near-black ── */
body[data-bg-static="halo"] #bg-static {
  background:
    radial-gradient(70% 78% at 74% 24%, rgba(var(--accent-rgb), 0.52), rgba(var(--accent-rgb), 0.10) 32%, transparent 60%),
    radial-gradient(160% 130% at 50% 52%, transparent 38%, rgba(0, 0, 0, 0.74) 100%),
    linear-gradient(180deg, #090b0f, #050608);
}
:root[data-appearance="light"] body[data-bg-static="halo"] #bg-static {
  background:
    radial-gradient(70% 78% at 74% 24%, rgba(var(--accent-rgb), 0.34), rgba(var(--accent-rgb), 0.08) 34%, transparent 62%),
    radial-gradient(160% 130% at 50% 52%, transparent 52%, rgba(20, 28, 44, 0.12) 100%),
    linear-gradient(180deg, #eef2f7, #e0e6ee);
}
