/* =========================================================================
   Base: reset, document defaults, ambient background, scrollbars, focus,
   utility helpers. No component styling lives here.
   ========================================================================= */

*,
*::before,
*::after { box-sizing: border-box; }

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Reserve the scrollbar's width always, so switching between a page tall
     enough to scroll and one that isn't doesn't shift body width and cause
     content to visibly jump/reflow. */
  scrollbar-gutter: stable;
}

body {
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: var(--lh);
  color: var(--text);
  background: var(--bg);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  transition: background-color 260ms ease, color 260ms ease;
}

/* Dark/light switch: the big surface containers snap instantly otherwise --
   only these (not every element) so hover/focus transitions elsewhere keep
   their own faster timing instead of getting dragged out to 260ms too. */
html,
.dash-sidebar,
.dash-topbar,
.dash-main,
.dash-content,
.auth-card,
.modal {
  transition: background-color 260ms ease, border-color 260ms ease, color 260ms ease;
}

/* Ambient backdrop: soft red aura over near-black, plus a faint grid. Fixed
   so it never scrolls; pointer-events:none so it never blocks input. */
.backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
  background:
    radial-gradient(900px 620px at 8% -8%, rgba(229, 57, 69, 0.15), transparent 62%),
    radial-gradient(820px 580px at 96% 18%, rgba(110, 29, 38, 0.12), transparent 64%),
    radial-gradient(700px 520px at 48% 108%, rgba(229, 57, 69, 0.07), transparent 66%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.015), transparent 42%),
    var(--bg);
  transition: background-color var(--t-slow);
}
/* Faint noise dithers the huge low-opacity gradients above so they blend
   smoothly into near-black instead of showing visible 8-bit banding steps
   (the "blotchy" look on large dark-to-red radial gradients). Its own DOM
   node, appended last in .backdrop, so it paints on top with no z-index. */
.backdrop-grain {
  display: none;
}
.backdrop::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
  background-size: 46px 46px;
  mask-image: radial-gradient(circle at 50% 30%, #000 0%, transparent 78%);
  opacity: 0.6;
}

/* Stop transient loaders and entrance effects when the tab is backgrounded.
   This is deliberately global: nothing invisible should keep requesting
   animation frames or retaining an active compositor layer. */
body.bg-paused *,
body.bg-paused *::before,
body.bg-paused *::after { animation-play-state: paused !important; }
.backdrop::after {
  display: none;
}
body.bg-paused .backdrop::after { animation-play-state: paused; }

/* =========================================================================
   Ambient backdrop — "storm" pass, tuned to the Power Legends mark:
   smouldering red nebula over near-black, embers drifting up out of it, and
   an occasional lightning strike echoing the bolt in the logo.

   Cost discipline is the same as the layers it replaced: every element is a
   gradient rasterised once, then driven only through transform/opacity on the
   compositor. No canvas, no per-frame JS, no animated blur/filter. The whole
   field sits behind content at z-index -1 and is pointer-events:none via
   .backdrop, and halts on `body.bg-paused` (set when the tab is hidden).
   ========================================================================= */
.backdrop {
  contain: strict;
  /* Saturated but faint beats desaturated and broad: stacking many wide, low
     alpha washes turns the whole frame muddy brown, which is the opposite of
     the mark (hard black almost everywhere, crimson concentrated). Three
     gradients only, and the bottom of the frame is left to go black. */
  background:
    radial-gradient(980px 700px at 50% 24%, rgba(255, 40, 58, 0.17), transparent 62%),
    radial-gradient(760px 540px at -2% -8%, rgba(255, 40, 58, 0.14), transparent 60%),
    radial-gradient(720px 520px at 102% 8%, rgba(214, 24, 48, 0.14), transparent 62%),
    var(--bg);
}
.backdrop::before { display: none; }

/* Vignette. The mark is near-black at every edge and hottest in the middle;
   this reproduces that falloff, keeps sidebar/topbar chrome readable over the
   brighter nebula, and is what stops the layers below reading as a flat wash. */
.backdrop::after {
  content: "";
  display: block;
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 104% 88% at 50% 32%, transparent 34%, rgba(0, 0, 0, 0.38) 70%, rgba(0, 0, 0, 0.8) 100%);
}

/* ---- layer 1: nebula — three huge soft clouds, each drifting and breathing
   on its own timing so the field never visibly loops in sync. Softness comes
   from the gradient stops, not filter: blur(), so these stay cheap. */
.backdrop-nebula i {
  position: absolute;
  display: block;
  border-radius: 50%;
  will-change: transform, opacity;
}
.backdrop-nebula i:nth-child(1) {
  width: 52vw; height: 44vw; min-width: 480px; min-height: 400px;
  top: -14%; left: -10%;
  background: radial-gradient(ellipse at 50% 50%, rgba(255, 40, 58, 0.24), rgba(178, 18, 42, 0.09) 42%, transparent 70%);
  animation: backdropSmokeA 48s var(--ease) infinite alternate;
}
.backdrop-nebula i:nth-child(2) {
  width: 46vw; height: 40vw; min-width: 420px; min-height: 360px;
  top: 12%; right: -14%;
  background: radial-gradient(ellipse at 50% 50%, rgba(255, 62, 80, 0.19), rgba(150, 18, 38, 0.08) 44%, transparent 71%);
  animation: backdropSmokeB 62s var(--ease) infinite alternate;
}
.backdrop-nebula i:nth-child(3) {
  width: 40vw; height: 34vw; min-width: 360px; min-height: 300px;
  bottom: -18%; left: 30%;
  background: radial-gradient(ellipse at 50% 50%, rgba(255, 40, 58, 0.14), rgba(122, 14, 30, 0.06) 46%, transparent 72%);
  animation: backdropSmokeC 54s var(--ease) infinite alternate;
}
@keyframes backdropSmokeA {
  from { transform: translate3d(0, 0, 0) scale(1); opacity: 0.72; }
  to   { transform: translate3d(6vw, 4vh, 0) scale(1.16); opacity: 1; }
}
@keyframes backdropSmokeB {
  from { transform: translate3d(0, 0, 0) scale(1.12); opacity: 1; }
  to   { transform: translate3d(-5vw, -6vh, 0) scale(0.94); opacity: 0.6; }
}
@keyframes backdropSmokeC {
  from { transform: translate3d(0, 0, 0) scale(0.96); opacity: 0.58; }
  to   { transform: translate3d(-4vw, -5vh, 0) scale(1.2); opacity: 0.92; }
}

/* ---- layer 2: mesh — a faint grid, kept only as texture. Large near-black
   to-red gradients band visibly on 8-bit displays; this dithers them. Drifts
   slowly so it reads as atmosphere rather than a fixed overlay. */
.backdrop-mesh {
  position: absolute;
  inset: -48px;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.016) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.016) 1px, transparent 1px);
  background-size: 54px 54px;
  mask-image: radial-gradient(ellipse at 52% 34%, #000 0%, rgba(0, 0, 0, 0.7) 46%, transparent 80%);
  opacity: 0.5;
  transform: translate3d(-27px, -27px, 0);
  animation: backdropMeshDrift 52s linear infinite;
  will-change: transform;
}
@keyframes backdropMeshDrift { to { transform: translate3d(27px, 27px, 0); } }

/* ---- layer 3: embers — motes rising out of the nebula and burning out.
   Each gets its own delay/duration/drift so the field never pulses together. */
.backdrop-embers i {
  position: absolute;
  bottom: -24px;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: rgba(255, 122, 132, 0.9);
  box-shadow: 0 0 8px 1px rgba(255, 70, 85, 0.45);
  opacity: 0;
  will-change: transform, opacity;
  animation: backdropEmberRise linear infinite;
}
@keyframes backdropEmberRise {
  0%   { transform: translate3d(0, 0, 0) scale(0.5); opacity: 0; }
  10%  { opacity: var(--ember-peak, 0.55); }
  68%  { opacity: var(--ember-peak, 0.55); }
  100% { transform: translate3d(var(--ember-x, 20px), -104vh, 0) scale(1); opacity: 0; }
}
.backdrop-embers i:nth-child(1)  { left: 7%;  animation-duration: 21s; animation-delay: -2s;  --ember-x: 28px;  --ember-peak: 0.5; }
.backdrop-embers i:nth-child(2)  { left: 15%; animation-duration: 28s; animation-delay: -11s; --ember-x: -20px; --ember-peak: 0.32; width: 2px; height: 2px; }
.backdrop-embers i:nth-child(3)  { left: 24%; animation-duration: 24s; animation-delay: -6s;  --ember-x: 36px;  --ember-peak: 0.44; }
.backdrop-embers i:nth-child(4)  { left: 32%; animation-duration: 32s; animation-delay: -18s; --ember-x: -30px; --ember-peak: 0.28; width: 2px; height: 2px; }
.backdrop-embers i:nth-child(5)  { left: 40%; animation-duration: 19s; animation-delay: -9s;  --ember-x: 18px;  --ember-peak: 0.55; }
.backdrop-embers i:nth-child(6)  { left: 48%; animation-duration: 30s; animation-delay: -22s; --ember-x: -36px; --ember-peak: 0.3; }
.backdrop-embers i:nth-child(7)  { left: 56%; animation-duration: 25s; animation-delay: -4s;  --ember-x: 24px;  --ember-peak: 0.48; width: 4px; height: 4px; }
.backdrop-embers i:nth-child(8)  { left: 64%; animation-duration: 34s; animation-delay: -15s; --ember-x: -22px; --ember-peak: 0.26; width: 2px; height: 2px; }
.backdrop-embers i:nth-child(9)  { left: 71%; animation-duration: 22s; animation-delay: -7s;  --ember-x: 32px;  --ember-peak: 0.5; }
.backdrop-embers i:nth-child(10) { left: 79%; animation-duration: 27s; animation-delay: -19s; --ember-x: -26px; --ember-peak: 0.38; }
.backdrop-embers i:nth-child(11) { left: 86%; animation-duration: 31s; animation-delay: -13s; --ember-x: 20px;  --ember-peak: 0.3; width: 2px; height: 2px; }
.backdrop-embers i:nth-child(12) { left: 93%; animation-duration: 23s; animation-delay: -3s;  --ember-x: -32px; --ember-peak: 0.52; }
.backdrop-embers i:nth-child(13) { left: 45%; animation-duration: 37s; animation-delay: -25s; --ember-x: 42px;  --ember-peak: 0.24; width: 2px; height: 2px; }
.backdrop-embers i:nth-child(14) { left: 19%; animation-duration: 35s; animation-delay: -30s; --ember-x: -40px; --ember-peak: 0.28; }

/* ---- layer 4: lightning — the logo's bolt, struck rarely and briefly.
   The wrapper carries a soft halo and does the flashing; the clipped child is
   the bolt silhouette itself (clip-path would crop a box-shadow, hence the
   split). Only opacity animates. Strikes are deliberately low-contrast and
   ~20s apart: this is atmosphere behind a settings UI, not a strobe. */
.backdrop-bolt {
  --bolt-peak: 0.2;
  position: absolute;
  width: clamp(84px, 11vw, 168px);
  aspect-ratio: 0.5;
  background: radial-gradient(ellipse at 50% 46%, rgba(255, 70, 85, 0.5), rgba(229, 57, 69, 0.16) 42%, transparent 72%);
  opacity: 0;
  will-change: opacity;
}
.backdrop-bolt i {
  position: absolute;
  inset: 0;
  display: block;
  clip-path: polygon(58% 0%, 22% 52%, 45% 52%, 32% 100%, 80% 40%, 52% 40%, 74% 0%);
  background: linear-gradient(172deg, rgba(255, 236, 238, 0.85), rgba(255, 80, 95, 0.8) 40%, rgba(229, 57, 69, 0.12));
}
/* Both sit hard against an edge so a strike reads as a glimpse of something
   larger off-screen, not as a logo watermark pasted on the page. */
.backdrop-bolt-a {
  top: -9%; right: 7%;
  transform: rotate(9deg);
  animation: backdropBolt 23s linear infinite;
}
.backdrop-bolt-b {
  --bolt-peak: 0.13;
  top: 21%; left: -3%;
  width: clamp(62px, 8vw, 118px);
  transform: rotate(-14deg);
  animation: backdropBolt 37s linear infinite;
  animation-delay: -12s;
}
/* Double-strike then a long dark gap — how real lightning reads. Peak comes
   from --bolt-peak so each bolt can be dimmed without a second keyframe set. */
@keyframes backdropBolt {
  0%, 3.4%   { opacity: 0; }
  3.9%       { opacity: var(--bolt-peak); }
  4.9%       { opacity: calc(var(--bolt-peak) * 0.1); }
  5.6%       { opacity: calc(var(--bolt-peak) * 0.72); }
  6.6%       { opacity: calc(var(--bolt-peak) * 0.07); }
  7.2%       { opacity: calc(var(--bolt-peak) * 0.42); }
  9%, 100%   { opacity: 0; }
}

/* Sky flash paired with bolt A: the room lighting up, not the bolt itself.
   Same 23s period and phase, so the two read as one event. */
.backdrop-flash {
  position: absolute;
  inset: 0;
  /* Centred on bolt A's position so the two read as one event. */
  background: radial-gradient(1100px 820px at 88% 0%, rgba(255, 70, 85, 0.13), rgba(229, 57, 69, 0.045) 40%, transparent 64%);
  opacity: 0;
  will-change: opacity;
  animation: backdropFlash 23s linear infinite;
}
@keyframes backdropFlash {
  0%, 3.3%  { opacity: 0; }
  3.9%      { opacity: 1; }
  5%        { opacity: 0.1; }
  5.6%      { opacity: 0.75; }
  7%        { opacity: 0.06; }
  9%, 100%  { opacity: 0; }
}

/* One switch stops every ambient layer (tab hidden / perf mode). */
body.bg-paused .backdrop-nebula i,
body.bg-paused .backdrop-embers i,
body.bg-paused .backdrop-mesh,
body.bg-paused .backdrop-bolt,
body.bg-paused .backdrop-flash { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  .backdrop-mesh,
  .backdrop-nebula i { animation: none !important; }
  .backdrop-nebula i { opacity: 0.8; }
  .backdrop-embers,
  .backdrop-bolt,
  .backdrop-flash { display: none; }
}

/* Small screens: drop the ember field and the second bolt — less paint on the
   devices least able to afford it. Nebula alone still carries the look. */
@media (max-width: 720px) {
  .backdrop-embers,
  .backdrop-bolt-b { display: none; }
  .backdrop-mesh { opacity: 0.36; }
  .backdrop-nebula i:nth-child(3) { display: none; }
}

/* ---- links + typography defaults */
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { font-weight: 650; line-height: 1.2; letter-spacing: -0.01em; }
h1 { font-size: var(--fs-3xl); }
h2 { font-size: var(--fs-2xl); }
h3 { font-size: var(--fs-lg); }
code, .mono { font-family: var(--font-mono); font-size: 0.86em; }
small { font-size: var(--fs-xs); }
button { font-family: inherit; }

/* ---- form controls: strip native chrome so styling is consistent */
input, textarea, select, button { font: inherit; color: inherit; }
textarea { resize: vertical; }
input[type="search"]::-webkit-search-cancel-button { appearance: none; }
input::placeholder, textarea::placeholder { color: var(--text-muted); }

/* ---- focus: single accessible ring everywhere, keyboard-only */
:focus { outline: none; }
:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
  border-radius: var(--r-xs);
}

/* ---- scrollbars */
* { scrollbar-width: thin; scrollbar-color: rgba(229, 57, 69, 0.35) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.09);
  border-radius: var(--r-pill);
  border: 2px solid transparent;
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(229, 57, 69, 0.5); background-clip: padding-box; }

/* ---- text helpers */
.muted { color: var(--text-2); }
.dim { color: var(--text-muted); }
.mono { font-family: var(--font-mono); }
.nowrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.text-red { color: var(--red-bright); }
.text-success { color: var(--success); }
.text-warning { color: var(--warning); }
.text-error { color: var(--error); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---- visibility */
.hidden { display: none !important; }
[hidden] { display: none !important; }

/* ---- entrance helpers reused across views */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes popIn {
  0% { opacity: 0; transform: scale(0.94); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes shimmer {
  0% { background-position: -420px 0; }
  100% { background-position: 420px 0; }
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- light-theme surface fixups (backdrop + scrollbars read inverted) ---- */
:root[data-theme="light"] .backdrop {
  background:
    radial-gradient(1200px 720px at 12% -8%, rgba(229, 57, 69, 0.10), transparent 60%),
    radial-gradient(1000px 700px at 92% 4%, rgba(229, 57, 69, 0.07), transparent 62%),
    var(--bg);
}
:root[data-theme="light"] .backdrop::before {
  opacity: 0.5;
  background-image:
    linear-gradient(rgba(20, 10, 25, 0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(20, 10, 25, 0.035) 1px, transparent 1px);
}
/* The vignette darkens toward black, which is wrong over a light surface --
   invert it to a soft warm edge instead. */
:root[data-theme="light"] .backdrop::after {
  background: radial-gradient(ellipse 118% 96% at 50% 40%, transparent 42%, rgba(120, 40, 55, 0.05) 76%, rgba(120, 40, 55, 0.09) 100%);
}
:root[data-theme="light"] .backdrop-mesh {
  opacity: 0.46;
  background-image:
    linear-gradient(rgba(20, 10, 25, 0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(20, 10, 25, 0.035) 1px, transparent 1px);
}
/* Set on the container, not the children: the children's opacity is driven by
   a running keyframe, and animations outrank normal declarations. */
:root[data-theme="light"] .backdrop-nebula { opacity: 0.4; }
/* Embers and lightning only read against near-black; on the light theme they
   look like dirt on the screen, so the storm layers sit this one out. */
:root[data-theme="light"] .backdrop-embers,
:root[data-theme="light"] .backdrop-bolt,
:root[data-theme="light"] .backdrop-flash { display: none; }
:root[data-theme="light"] * { scrollbar-color: rgba(20, 10, 25, 0.22) transparent; }
:root[data-theme="light"] *::-webkit-scrollbar-thumb { background: rgba(20, 10, 25, 0.16); background-clip: padding-box; }
:root[data-theme="light"] *::-webkit-scrollbar-thumb:hover { background: rgba(229, 57, 69, 0.5); background-clip: padding-box; }
