/* ==========================================================================
   STILLMIND MENTAL HEALTH · ATMOSPHERE
   The background feel that runs beneath every page: warm paper, two soft
   colour fields that drift slowly with scroll, a fixed film grain, and a
   faint vignette.

   Performance contract:
   - Everything here is fixed and pointer-events: none, so nothing repaints
     while content scrolls.
   - The blurred fields are composited once and then only translated.
   - Grain is a single inline SVG turbulence, not an image request.
   ========================================================================== */

.atmosphere {
  position: fixed;
  inset: 0;
  z-index: var(--z-base);
  pointer-events: none;
  overflow: hidden;
  /* Isolate paint and layout, so the drift never repaints the document.
     Deliberately not `contain: strict`, which adds size containment and
     is unnecessary here because inset:0 already fixes the box. */
  contain: layout paint style;
}

/* --------------------------------------------------------------------
   Colour fields
   Two large, very soft blooms. They sit far enough apart that the page
   never looks like a gradient mesh, which is the tell we are avoiding.
   -------------------------------------------------------------------- */

.atmosphere__field {
  position: absolute;
  border-radius: 50%;
  filter: blur(var(--atm-field-blur));
  will-change: transform;
}

.atmosphere__field--one {
  top: -22vh;
  right: -14vw;
  width: 62vw;
  height: 62vw;
  min-width: 440px;
  min-height: 440px;
  background: var(--atm-field-1);
}

.atmosphere__field--two {
  bottom: -26vh;
  left: -18vw;
  width: 70vw;
  height: 70vw;
  min-width: 480px;
  min-height: 480px;
  background: var(--atm-field-2);
}

/* --------------------------------------------------------------------
   Drift
   A scroll-driven animation, so it needs no JavaScript, no frame loop
   and no scroll events. This used to run from a scroll listener, which
   does not fire reliably: a page can be scrolled by a container rather
   than by the document, and some environments move the scroll position
   without dispatching an event at all.

   Where animation-timeline is unsupported the fields simply sit still,
   which is a perfectly good background.
   -------------------------------------------------------------------- */

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 768px) {
    .atmosphere__field--one {
      animation: field-drift-up linear both;
      animation-timeline: scroll(root block);
    }

    .atmosphere__field--two {
      animation: field-drift-down linear both;
      animation-timeline: scroll(root block);
    }
  }
}

@keyframes field-drift-up {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -60px, 0); }
}

@keyframes field-drift-down {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, 44px, 0); }
}

/* --------------------------------------------------------------------
   Vignette
   Pulls the eye inward. Kept extremely faint so it reads as paper rather
   than as a photo filter.
   -------------------------------------------------------------------- */

.atmosphere__vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    120% 90% at 50% 40%,
    transparent 40%,
    var(--atm-vignette) 100%
  );
}

/* --------------------------------------------------------------------
   Film grain
   Fixed, so it never scrolls with content and never forces a repaint.
   Generated inline, so it costs no network request.
   -------------------------------------------------------------------- */

.atmosphere__grain {
  position: absolute;
  inset: -50%;
  width: 200%;
  height: 200%;
  opacity: var(--atm-grain-opacity);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* The grain shimmers very slightly, which stops it reading as a static
   texture overlay. Four steps only, so it is cheap. */
@media (prefers-reduced-motion: no-preference) {
  .atmosphere__grain { animation: grain-shift 6s steps(4) infinite; }
}

@keyframes grain-shift {
  0%   { transform: translate3d(0, 0, 0); }
  25%  { transform: translate3d(-2%, 1%, 0); }
  50%  { transform: translate3d(1%, -2%, 0); }
  75%  { transform: translate3d(-1%, -1%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* --------------------------------------------------------------------
   Dark sections
   When a full-bleed forest section passes over the atmosphere, the fields
   invert to warm light rather than disappearing, so depth is preserved.
   -------------------------------------------------------------------- */

.section-dark {
  position: relative;
  background: var(--c-forest);
  isolation: isolate;
}

.section-dark::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(90% 70% at 18% 0%, rgba(71, 110, 100, 0.32), transparent 62%),
    radial-gradient(70% 60% at 92% 100%, rgba(39, 100, 70, 0.28), transparent 60%);
}

.section-ink { background: var(--c-ink); }

/* --------------------------------------------------------------------
   Mobile
   The fields are reduced and the grain animation stops. On a phone the
   atmosphere is a texture, not a performance cost.
   -------------------------------------------------------------------- */

@media (max-width: 767px) {
  :root { --atm-field-blur: 80px; }
  .atmosphere__grain { animation: none; }
}

/* Very low-end or battery-saving contexts drop the blur entirely. */
@media (update: slow) {
  .atmosphere__field { display: none; }
}
