/* ══════════════════════════════════════════════════════════════
   Keln — shared atmosphere layer.

   Companion to keln-atmos.js. Three stacked fixed layers behind
   every non-landing page:

     .atmos__grad    two drifting radial gradients (JS writes the
                     centre positions as custom properties)
     .atmos__field   the canvas star field
     .atmos__grain   static texture

   z-index sits at -3/-2/-1 so the whole stack is behind page
   content but still inside the body's stacking context. Every
   layer is pointer-events:none at both the host and the child
   level, so the atmosphere can never intercept a click even if a
   future layer forgets to opt out.

   THIS FILE REPLACES:
     · #bgfx and #grid inline rules in dashboard.html
     · #bgfx::before / #bgfx::after in dashboard-portal-aesthetic.css
     · body{background-image:…} inline in auth.html

   Delete those when you adopt this file, or they will paint on top
   of it and the whole point is lost.
   ══════════════════════════════════════════════════════════════ */

.atmos {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;

  /* Written by keln-atmos.js from theme tokens. The fallbacks
     matter: if the script fails to run, the page still gets a
     reasonable sky rather than a flat void. Fail-safe discipline —
     a gating layer defaults to "show something". */
  --atmos-grad-a: .6;
  --atmos-grain-a: .1;
  --atmos-c1: rgba(91, 140, 255, .18);
  --atmos-c2: rgba(34, 211, 166, .13);

  /* Gradient centres, animated by JS at ~8fps. */
  --ax: 50%;
  --ay: 12%;
  --bx: 82%;
  --by: 30%;
}

.atmos > * {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* ── 1. Gradient ─────────────────────────────────────────────
   Two soft radials on a near-black base. The centres travel on
   slow Lissajous curves driven from JS, which is why the stops
   reference custom properties instead of fixed percentages. */
.atmos__grad {
  z-index: -3;
  opacity: var(--atmos-grad-a);
  background:
    radial-gradient(1100px 760px at var(--ax) var(--ay),
                    var(--atmos-c1), transparent 58%),
    radial-gradient(940px 820px at var(--bx) var(--by),
                    var(--atmos-c2), transparent 60%);

  /* Promotes the layer so the 8fps property writes composite
     instead of repainting the whole document. */
  will-change: background-position;
}

/* ── 2. Star field ───────────────────────────────────────────
   The canvas element itself. Sized in JS from the pixel budget;
   CSS only positions it. */
.atmos__field {
  z-index: -2;
  display: block;
  width: 100%;
  height: 100%;
}

/* ── 3. Grain ────────────────────────────────────────────────
   Static on purpose. Grain that animates reads as video noise and
   costs a full-viewport repaint per frame; grain that sits still
   reads as paper and costs nothing after the first paint.

   `screen` keeps it additive on a dark ground — `overlay` would
   crush the blacks and lose the star field underneath it. */
.atmos__grain {
  z-index: -1;
  opacity: var(--atmos-grain-a);
  mix-blend-mode: screen;
  background-image:
    radial-gradient(rgba(255, 255, 255, .5) .55px, transparent .75px),
    radial-gradient(rgba(255, 255, 255, .32) .45px, transparent .7px);
  background-size: 3px 3px, 4px 4px;
  background-position: 0 0, 1px 2px;
}

/* ── Intensity ───────────────────────────────────────────────
   The numeric work happens in JS (star counts, zoom speed). These
   rules only carry what CSS is better at: the vignette that keeps
   text legible over a busy field.

   `calm` — the dashboard. Heavier vignette, because the dashboard
   has dense UI over it and the sky must never fight the content.
   `full` — pricing. Lighter, because the page is mostly type and
   air and the sky is doing the selling. */
.atmos[data-intensity="calm"]::after,
.atmos[data-intensity="full"]::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.atmos[data-intensity="calm"]::after {
  background: radial-gradient(ellipse 120% 90% at 50% 40%,
              transparent 42%, rgba(4, 7, 12, .55) 100%);
}

.atmos[data-intensity="full"]::after {
  background: radial-gradient(ellipse 130% 100% at 50% 38%,
              transparent 55%, rgba(4, 7, 12, .38) 100%);
}

/* ── Reduced motion ──────────────────────────────────────────
   keln-atmos.js already stops its RAF loop and paints one static
   frame. This is the CSS half: drop the layers back so a still
   image does not sit at full strength forever, and drop the
   will-change hint since nothing changes any more. */
@media (prefers-reduced-motion: reduce) {
  .atmos__grad {
    opacity: calc(var(--atmos-grad-a) * .55);
    will-change: auto;
  }
  .atmos__grain {
    opacity: calc(var(--atmos-grain-a) * .6);
  }
}

/* ── Small screens ───────────────────────────────────────────
   A phone is already fighting for battery and fill rate, and the
   field is far less legible at this size. Pull it back rather
   than removing it — the page should still feel like Keln. */
@media (max-width: 620px) {
  .atmos__grad { opacity: calc(var(--atmos-grad-a) * .8); }
  .atmos__grain { opacity: calc(var(--atmos-grain-a) * .7); }
}
