/* ═══════════════════════════════════════════════════════════
   PIPER, fx-atmosphere.css
   Owns exactly two elements that already exist in index.html:
   .aura (ambient light) and .grain (film texture). Both are
   position: fixed, pointer-events: none, and sit at negative or
   very high z-index so they never intercept a click or force a
   layout pass on the sections scrolling past them.

   Load order: this file is a link tag placed after styles.css,
   so every rule below is a deliberate override, not an accident
   of specificity. Nothing here declares a class that is not
   already in the markup; the "mood" layers are pseudo-elements
   of .aura, and the scanline is a pseudo-element of .grain, so
   zero new DOM nodes are required to ship this file.

   Everything below touches transform and opacity only. No rule
   in this file animates a gradient stop, a background-position,
   width, height, or a filter. That restriction is the whole
   performance argument: a fixed full-viewport layer that only
   ever transforms or fades is one composited layer, painted
   once, moved by the GPU for free on every frame after that,
   including a mid-range phone under load.
   ═══════════════════════════════════════════════════════════ */

/* ── AURA: the constant light ────────────────────────────────
   Base frame stays exactly as informative as the old single
   blob, plus a third, static vignette stop that pulls the top
   and bottom edges toward black. That vignette costs nothing
   extra to paint (it is baked into the same gradient list, so
   it is not a second layer) and it is what makes the two mood
   layers below read as light falling into a room instead of a
   sticker sitting on top of the page. */
.aura {
  background:
    radial-gradient(70vw 55vh at 50% 0%, rgb(194 251 94 / .06), transparent 68%),
    radial-gradient(50vw 40vh at 85% 55%, rgb(224 188 124 / .04), transparent 70%),
    linear-gradient(to bottom, rgb(0 0 0 / .5) 0%, transparent 16%, transparent 84%, rgb(0 0 0 / .55) 100%);
}

/* ── AURA MOOD LAYERS: the page changing temperature ─────────
   Two more radial washes, absolutely positioned inside .aura's
   own fixed box rather than fixed themselves. That choice is
   deliberate: styles.css already gives .aura a scroll-linked
   transform (aura-drift) under the same @supports block this
   file extends below, and a transform on .aura turns it into
   the containing block for any fixed descendant. Making the
   moods absolute instead means they inherit .aura's box exactly
   and drift with it for free, with no extra positioning logic
   and no risk of a fixed child detaching from the viewport once
   the parent starts moving.

   Forest carries the cool, editorial passages: the keeper's
   quote and the conservation figure, the two places on the page
   that are asking to be read rather than traded. Accent carries
   the two commerce passages: the live market rail near the top
   and the swap card near the bottom, the only two places a
   visitor is actually looking at a live number or a live form.
   Both default to a visible, non-zero rest state so a browser
   without scroll-driven animation support, or a visitor with
   reduced motion on, still gets a page with light in it instead
   of a flat, inert overlay. */
.aura::before,
.aura::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.aura::before {
  opacity: .32;
  background: radial-gradient(60vw 46vh at 18% 62%, rgb(31 74 42 / .5), transparent 72%);
}

.aura::after {
  opacity: .3;
  background: radial-gradient(55vw 42vh at 82% 30%, rgb(194 251 94 / .32), transparent 70%);
}

@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Percentage stops are placed by section order, not by a
       measured pixel offset, because the page is static HTML
       with no build step to read layout back from. That is an
       honest approximation, not a bug: an ambient light cue only
       has to land in the right neighbourhood of the right
       section, not hit a specific scrollTop. */
    .aura::before {
      animation: mood-forest linear both;
      animation-timeline: scroll(root block);
    }
    .aura::after {
      animation: mood-lime linear both;
      animation-timeline: scroll(root block);
    }
  }
}

@keyframes mood-forest {
  0%   { opacity: .22; }  /* hero: the cover spread stays clean, no tint fighting the video */
  10%  { opacity: .22; }
  17%  { opacity: .58; }  /* the keeper's quote and the receipt */
  27%  { opacity: .34; }
  56%  { opacity: .3; }
  70%  { opacity: .82; }  /* the wild: the 2,500 figure gets the strongest wash on the page */
  81%  { opacity: .38; }
  100% { opacity: .22; }
}

@keyframes mood-lime {
  0%   { opacity: .3; }
  7%   { opacity: .62; }  /* the live market rail, first live numbers on the page */
  21%  { opacity: .28; }
  52%  { opacity: .24; }
  89%  { opacity: .3; }
  95%  { opacity: .66; }  /* the swap card, the one section that is actually software */
  100% { opacity: .3; }
}

/* ── GRAIN: texture that breathes instead of a static sticker ─
   The tile is oversized by 6% on every edge and the whole layer
   is what moves, not its background-position. Moving a
   background-position repaints the full viewport every step;
   moving a transform on an already-oversized box is picked up
   by the compositor and never touches the paint stage at all.
   The 6% overscan is the margin that keeps the crawl from ever
   exposing a bare edge. */
.grain {
  position: fixed;
  inset: -6%;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: .5;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
  background-repeat: repeat;
  will-change: transform;
}

@media (prefers-reduced-motion: no-preference) {
  .grain { animation: grain-crawl 4.8s steps(8) infinite; }
}

@keyframes grain-crawl {
  0%    { transform: translate3d(0%, 0%, 0); }
  12.5% { transform: translate3d(-2.4%, 1.6%, 0); }
  25%   { transform: translate3d(1.8%, -2.1%, 0); }
  37.5% { transform: translate3d(-1.2%, -1.8%, 0); }
  50%   { transform: translate3d(2.2%, .8%, 0); }
  62.5% { transform: translate3d(-2%, 2%, 0); }
  75%   { transform: translate3d(1.4%, -1.2%, 0); }
  87.5% { transform: translate3d(-.8%, 1.9%, 0); }
  100%  { transform: translate3d(0%, 0%, 0); }
}

/* ── SCANLINE: an occasional depth event, not decoration ──────
   One thin accent line sweeps down roughly every fourteen
   seconds and is transparent for all but a fraction of that
   cycle, so it reads as an environment doing something, not as
   a loading indicator or a scroll cue. Absolutely positioned
   inside .grain's own oversized box for the same containing
   block reason the mood layers sit inside .aura, and gated on
   the same media query as every other loop on the page so a
   reduced-motion visitor gets the grain texture with none of
   the movement. */
.grain::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -2%;
  height: 2px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgb(194 251 94 / .55), transparent);
  opacity: 0;
  will-change: transform, opacity;
}

@media (prefers-reduced-motion: no-preference) {
  .grain::after { animation: scanline-sweep 14s linear infinite; }
}

@keyframes scanline-sweep {
  0%    { transform: translateY(0); opacity: 0; }
  1%    { opacity: .4; }
  7%    { opacity: 0; }
  9%    { transform: translateY(112vh); opacity: 0; }
  100%  { transform: translateY(112vh); opacity: 0; }
}

/* ── REDUCED MOTION ────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  /* Both loops are already gated out above. This just pins the
     mood layers to their calm resting opacity so a reduced
     motion visit is a still frame, not a paused mid-transition
     one, in case a browser supports scroll-timeline without
     also respecting the no-preference gate correctly. */
  .aura::before { opacity: .28; }
  .aura::after  { opacity: .26; }
}
