/* ════════════════════════════════════════════════════════════════
   ELDARA GAMES — AMBIENCE
   ────────────────────────────────────────────────────────────────
   Everything that makes a page feel alive without being content:
   the global light layers, the ambient hosts (.amb) that port the
   in-game title screen (plate, haze, moonlight, glints, embers),
   the eclipse corona and rays, parallax, pausing, the studio
   intro, view transitions and the reduced-motion matrix.

   Rules of the room
     · Only transform and opacity animate (the logo sweep and the
       one-off intro letter-spacing are the documented exceptions).
     · No ambient layer passes 10% peak opacity, except the hall
       bleed (18%) and the hero corona.
     · Colours come from --t-* tokens, so a game theme re-tints all
       of it; Eldara's exact recipes live in themes/eldara.css.

   JS hooks styled here
     #fx                      particle canvas (fx/particles.js)
     .cursor-light            cursor light (ui/pointer.js injects it into
                              <body>, writes its transform, adds .is-active)
     html.is-bleeding         hall light on; home.js sets --hall-x,
                              --hall-y and --hall-glow on <html>
     html[data-mood]          embers | ash | void | motes | none
     html.is-slow             ticker.slow: grain stops jittering
     [data-amb].is-paused     host is offscreen: animations pause
     .plate                   --plate-x/-y (+ -sm) as 0–1 fractions,
                              --pw/--ph (plate px), --plate-scale
     .fleck / .ember          built and parametrised by fx/atmosphere.js
     [data-parallax]          --plx factor (scroll drift ±12vh × --plx)
     .intro                   built and animated by ui/intro.js (WAAPI)
   ════════════════════════════════════════════════════════════════ */

@layer ambience{

  /* ── global layers ─────────────────────────────────────────────
     Pages add no markup for these: the vignette and grain are body
     pseudo-elements, the hall light is html::before, and the canvas
     and cursor light are injected by particles.js and pointer.js.  */

  /* Vignette */
  body::before{
    content:""; position:fixed; inset:0; z-index:var(--z-fx);
    pointer-events:none;
    background:radial-gradient(120% 100% at 50% 40%,transparent 55%,rgb(0 0 0 / var(--t-vignette)) 100%);
  }

  /* Grain: a 160px fractal-noise tile on an oversized layer, jittered in
     six steps at 8fps on fine pointers; static everywhere else. */
  body::after{
    content:""; position:fixed; inset:-160px; z-index:var(--z-grain);
    pointer-events:none;
    background: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='.9' numOctaves='2' 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") 0 0 / 160px 160px;
    opacity:var(--t-grain);
    mix-blend-mode:overlay;
  }
  html.fine:not(.is-slow) body::after{ animation:grain-jitter .75s steps(1,end) infinite; }
  @keyframes grain-jitter{
    0%    { transform:translate3d(0,0,0); }
    16.6% { transform:translate3d(-62px,38px,0); }
    33.3% { transform:translate3d(47px,-71px,0); }
    50%   { transform:translate3d(-23px,-49px,0); }
    66.6% { transform:translate3d(78px,27px,0); }
    83.3% { transform:translate3d(-91px,-12px,0); }
  }

  /* Hall light (home): a hovered game card's glow bleeds into the hall. */
  html::before{
    content:""; position:fixed; inset:0; z-index:var(--z-fx);
    pointer-events:none;
    background:radial-gradient(60vmax at var(--hall-x,50%) var(--hall-y,50%),var(--hall-glow,transparent),transparent 70%);
    opacity:0;
    transition:opacity var(--d-6) var(--ease-soft);
  }
  html.is-bleeding::before{ opacity:.18; transition-duration:var(--d-5); }

  /* Particle canvas */
  #fx{
    position:fixed; inset:0; z-index:var(--z-fx);
    width:100%; height:100%;
    pointer-events:none;
  }

  /* Cursor light: under content, fine pointers only, gone while media
     plays. pointer.js centres it with its own transform and adds
     .is-active once the pointer is in the window. */
  .cursor-light{
    position:fixed; left:0; top:0; z-index:var(--z-fx);
    width:640px; height:640px;
    border-radius:50%;
    pointer-events:none;
    background:radial-gradient(closest-side,var(--t-cursor),transparent);
    mix-blend-mode:var(--t-cursor-blend);
    opacity:0;
    transition:opacity var(--d-4) var(--ease-soft);
  }
  .cursor-light.is-active{ opacity:1; }
  html:not(.fine) .cursor-light{ display:none; }
  html.is-media .cursor-light{ opacity:0; }

  /* Realm sky (Eldara page): three fixed gradient skies; the one that
     matches html[data-mood] shows, crossfading over 1.4s. Embers is the
     default, so the sky is right before JS and on "none" sections. */
  .realm-sky{
    position:fixed; inset:0; z-index:var(--z-bg);
    pointer-events:none;
    background:var(--t-bg);
  }
  .realm-sky > i{
    position:absolute; inset:0;
    opacity:0;
    transition:opacity var(--d-6) var(--ease-io);
  }
  .realm-sky > [data-sky="embers"]{ background:var(--sky-embers); opacity:1; }
  .realm-sky > [data-sky="ash"]{ background:var(--sky-ash); }
  .realm-sky > [data-sky="void"]{ background:var(--sky-void); }
  html:is([data-mood="ash"],[data-mood="void"]) .realm-sky > [data-sky="embers"]{ opacity:0; }
  html[data-mood="ash"] .realm-sky > [data-sky="ash"],
  html[data-mood="void"] .realm-sky > [data-sky="void"]{ opacity:1; }


  /* ── ambient hosts ─────────────────────────────────────────────
     .amb[data-amb] contains its layers and is paused offscreen.    */
  .amb{ position:relative; overflow:clip; isolation:isolate; }

  /* Plate: a painted backdrop, object-fit cover at (--plate-x, --plate-y).
     It is an absolutely positioned, size-contained fill layer: give it
     an explicit size if you take it out of absolute positioning.
     .plate__field is the image's cover-cropped rectangle, computed here
     from container units; atmosphere.js may also write it inline.
     Children pin to plate pixels: style="--x:836;--y:140".          */
  .plate{
    --pw:1672; --ph:941;
    --_px:var(--plate-x,.5); --_py:var(--plate-y,.5);
    position:absolute; inset:0;
    margin:0;
    overflow:hidden;
    container-type:size;
  }
  @media (width <= 600px){
    .plate{ --_px:var(--plate-x-sm,var(--plate-x,.5)); --_py:var(--plate-y-sm,var(--plate-y,.5)); }
  }
  .plate > picture{ display:contents; }
  .plate__img{
    position:absolute; inset:0;
    width:100%; height:100%; max-width:none;
    object-fit:cover;
    object-position:calc(var(--_px) * 100%) calc(var(--_py) * 100%);
  }
  .plate__field{
    --_fw:max(100cqw,100cqh * var(--pw) / var(--ph));
    --_fh:max(100cqh,100cqw * var(--ph) / var(--pw));
    position:absolute;
    left:calc((100cqw - var(--_fw)) * var(--_px));
    top:calc((100cqh - var(--_fh)) * var(--_py));
    width:var(--_fw); height:var(--_fh);
    pointer-events:none;
  }
  .plate__field > [data-glints]{ position:absolute; inset:0; }
  .plate__pin{
    position:absolute;
    left:calc(var(--x) / var(--pw) * 100%);
    top:calc(var(--y) / var(--ph) * 100%);
    translate:-50% -50%;
  }

  /* Corona: the eclipse bloom, sized in plate pixels (--r) */
  .corona{
    position:absolute;
    left:calc(var(--x) / var(--pw) * 100%);
    top:calc(var(--y) / var(--ph) * 100%);
    width:calc(var(--r) * 2 / var(--pw) * 100%);
    aspect-ratio:1;
    translate:-50% -50%;
    border-radius:50%;
    background:var(--t-corona);
    mix-blend-mode:screen;
    pointer-events:none;
    animation:corona 7s var(--ease-io) infinite alternate;
  }
  @keyframes corona{ from{ opacity:.35; transform:scale(.98); } to{ opacity:.75; transform:scale(1.04); } }

  /* Glints: flecks that twinkle over the gold painted into the plate.
     atmosphere.js places each one (left/top in % of the field) and sets
     --glow-size, --glow-duration (4.7 + i%7 × .83)s, --glow-delay (−i × 1.79 − 1.1)s. */
  .fleck{
    position:absolute;
    width:var(--glow-size); height:var(--glow-size);
    transform:translate(-50%,-50%) scale(var(--plate-scale,1));
    background:var(--t-fleck);
    opacity:0;
    animation:fleck-glow var(--glow-duration,6s) ease-in-out var(--glow-delay,0s) infinite;
  }
  @keyframes fleck-glow{ 0%,100%{ opacity:.08; } 19%{ opacity:.22; } 43%{ opacity:.82; } 58%{ opacity:.5; } 79%{ opacity:.12; } }

  /* Rays: conic light from an origin (--ox, --oy), swaying ±4°. The mask
     opens a small hole at the origin (--rays-hole) so the shafts start at
     the eclipse's rim instead of streaking across the dark disc. */
  .rays{
    position:absolute; left:var(--ox,50%); top:var(--oy,15%);
    width:220vmax; height:220vmax;
    translate:-50% -50%;
    pointer-events:none;
    background:repeating-conic-gradient(from 0deg,transparent 0 7deg,var(--t-ray) 8deg 9.5deg,transparent 11deg 19deg);
    -webkit-mask:radial-gradient(circle,transparent var(--rays-hole,3%),#000 calc(var(--rays-hole,3%) + 5%),transparent 42%);
    mask:radial-gradient(circle,transparent var(--rays-hole,3%),#000 calc(var(--rays-hole,3%) + 5%),transparent 42%);
    mix-blend-mode:screen;
    opacity:var(--rays-o,.4);
    animation:rays-sway 32s var(--ease-io) infinite alternate;
  }
  @keyframes rays-sway{ from{ transform:rotate(-4deg); } to{ transform:rotate(4deg); } }
  html:not(.fine) .rays{ mix-blend-mode:normal; opacity:calc(var(--rays-o,.4) * .6); }

  /* Haze: the title screen's two mist layers, with its exact drifts.
     Hosts may scale it with --haze-opacity. */
  .haze{
    position:absolute; inset:0;
    overflow:hidden;
    pointer-events:none;
    opacity:var(--haze-opacity,1);
  }
  .haze__a,.haze__b{ position:absolute; background:center / 100% 100% no-repeat; }
  .haze[data-haze="cool"] .haze__a{ background-image:var(--haze-cool-a); }
  .haze[data-haze="cool"] .haze__b{ background-image:var(--haze-cool-b); }
  .haze[data-haze="warm"] .haze__a{ background-image:var(--haze-warm-a); }
  .haze[data-haze="warm"] .haze__b{ background-image:var(--haze-warm-b); }
  .haze__a{ inset:-12%; animation:haze-a 16s ease-in-out -5s infinite alternate; }
  .haze__b{ inset:-18% -9% -5% -19%; animation:haze-b 23s ease-in-out -14s infinite alternate; }
  @keyframes haze-a{ 0%{ transform:translate3d(-11%,-3%,0); opacity:.62; } 50%{ opacity:.88; } 100%{ transform:translate3d(11%,4%,0); opacity:.72; } }
  @keyframes haze-b{ from{ transform:translate3d(10%,-5%,0) scale(1.03); opacity:.48; } to{ transform:translate3d(-10%,6%,0) scale(1.08); opacity:.76; } }

  /* Moonlight: the blue pulse around the title screen's crest (title CTA) */
  .moonlight{
    position:absolute; inset:-6%;
    pointer-events:none;
    background:var(--t-moon);
    animation:moonlight 11s ease-in-out -4s infinite alternate;
  }
  @keyframes moonlight{ from{ opacity:.25; transform:translate3d(1%,0,0); } to{ opacity:.85; transform:translate3d(-2%,2%,0); } }

  /* Embers: a 1:1 port of the title screen's tumbling sparks.
     atmosphere.js sets --x/--y (%), --size, --duration, --delay,
     --drift and --opacity on each .ember. */
  .embers{ position:absolute; inset:0; overflow:hidden; pointer-events:none; }
  .ember{
    position:absolute; left:var(--x); top:var(--y);
    width:var(--size); height:var(--size);
    border-radius:1px;
    background:var(--t-ember);
    box-shadow:0 0 8px var(--t-ember-glow);
    opacity:0;
    animation:ember var(--duration) linear var(--delay) infinite;
  }
  .ember::after{
    content:""; position:absolute; inset:-5px;
    border-radius:50%;
    background:radial-gradient(circle,var(--t-ember-halo),transparent 68%);
    opacity:0;
    animation:ember-glimmer 5.7s ease-in-out var(--delay) infinite;
  }
  @keyframes ember{
    0%  { opacity:0; transform:translate3d(0,18vh,0) rotate(25deg); }
    14% { opacity:var(--opacity); }
    55% { opacity:calc(var(--opacity) * .7); }
    82% { opacity:var(--opacity); }
    100%{ opacity:0; transform:translate3d(var(--drift),-32vh,0) rotate(155deg); }
  }
  @keyframes ember-glimmer{ 0%,100%{ opacity:.12; } 36%{ opacity:.92; } 71%{ opacity:.25; } }


  /* ── parallax ──────────────────────────────────────────────────
     Art layers only, never text or global layers. Drives the
     individual translate property, so it composes with the layer's
     own transform animation (breathe, drift); where view() timelines
     are missing, atmosphere.js writes the same translate inline.
     Put data-parallax on an element whose own CSS sets no `animation`
     and no pointer depth ([data-depth] also writes translate): wrap it. */
  @supports (animation-timeline:view()){
    [data-parallax]{
      animation:plx linear both;
      animation-timeline:view();
      animation-range:cover;
    }
  }
  @keyframes plx{
    from{ translate:0 calc(var(--plx,.2) * -12vh); }
    to  { translate:0 calc(var(--plx,.2) * 12vh); }
  }


  /* ── pausing ───────────────────────────────────────────────────
     An offscreen host pauses its subtree; a hidden tab pauses
     everything; an open dialog pauses the ambience behind its
     blurred backdrop so the blur never has to re-render.          */
  [data-amb].is-paused,
  [data-amb].is-paused *,
  [data-amb].is-paused *::before,
  [data-amb].is-paused *::after,
  html.is-hidden *,
  html.is-hidden *::before,
  html.is-hidden *::after,
  html:is(.is-media,:has(dialog[open])) [data-amb] *,
  html:is(.is-media,:has(dialog[open])) [data-amb] *::after,
  html:is(.is-media,:has(dialog[open])) body::after{ animation-play-state:paused !important; }


  /* ── view transitions ──────────────────────────────────────────
     Cross-document: the root crossfades, the header holds still, and
     game art + title morph between the catalog and the game page
     (transitions.js assigns game-art / game-title).               */
  ::view-transition-old(root){ animation:vt-out .28s var(--ease-io) both; }
  ::view-transition-new(root){ animation:vt-in .42s var(--ease-out) both; }
  ::view-transition-group(game-art),
  ::view-transition-group(game-title){ animation-duration:.7s; animation-timing-function:var(--ease-out); }
  .site-header{ view-transition-name:site-header; }
  @keyframes vt-out{ to{ opacity:0; transform:scale(.985); } }
  @keyframes vt-in{ from{ opacity:0; } }


  /* ── reduced motion ────────────────────────────────────────────
     The whole matrix (spec §7.9) in one place, for core components
     too. No intro, canvas, cursor light, drift, parallax, rays,
     haze, moonlight, glints, sweep, shimmer or pulses; CSS embers
     hold still at .22; reveals are instant (core gates them); the
     header never hides; view transitions are off (end of file).   */
  @media (prefers-reduced-motion:reduce){
    #fx,
    .cursor-light,
    .rays,
    .haze,
    .moonlight,
    .fleck,
    .ember::after,
    .wordmark::after,
    [data-theme] .btn--primary::after{ display:none !important; }

    .ember{ animation:none !important; opacity:.22 !important; transform:rotate(45deg) !important; }
    .corona{ animation:none !important; opacity:.55 !important; }
    .plate,
    .plate__img,
    [data-parallax]{ animation:none !important; }

    body::after,
    .chip__dot::after,
    .scroll-cue::after,
    .site-footer__arch .sigil__beam,
    .newsletter__status::before{ animation:none !important; }

    .site-header{ translate:none !important; }
    .site-nav,
    .site-nav__list a,
    .site-nav__list::after,
    .site-header__actions{ transition:none !important; animation:none !important; }
  }
}

/* Cross-document view transitions (Chromium); off under reduced motion. */
@view-transition{ navigation:auto; }
@media (prefers-reduced-motion:reduce){ @view-transition{ navigation:none; } }
