/* ══════════════════════════════════════════════════════════════════════════════════════════════════
   MAIN MENU PARALLAX — the layered floating-isles scene behind the title screen.

   Josh 2026-07-31: "the mp4 approach was a failed attempt I did earlier, and I want it cut out and gone.
   I made this new paralax to replace it."

   Built from art-cleanup/floating-isles-main-menu-export-v1/scene-spec.json (moved out of assets/ on
   2026-07-31 — assets/ui is copied to the deploy wholesale and those are 8.1 MB of PNG masters) — every opacity, loop time and
   vertical band below is that spec's number, not a taste call. The art is at assets/ui/parallax/ (7 files,
   0.7 MB, converted from 8.1 MB of PNG).

   ── WHY THIS IS ITS OWN STYLESHEET ────────────────────────────────────────────────────────────────
   It could have gone into skysunder.css. It did not, for two reasons: another session is working in that
   file and a 100 KB shared stylesheet is the worst place to land a change concurrently; and this scene is
   one self-contained feature, so keeping it in one file makes it removable in one step if it ever needs to
   go the way the timelapse did. Linked last, so it wins the cascade without !important anywhere.

   ── SIX MOVING LAYERS, NOT TWELVE ─────────────────────────────────────────────────────────────────
   The export's README says to draw two copies of each plate side by side and translate one texture width;
   the bundled React reference does exactly that, at twelve animated elements. But all four plates were
   measured and they TILE — left and right edge columns differ by 0.00–0.32, against a 11–54 baseline for
   two random columns of the same image. So background-repeat:repeat-x on ONE element is identical output
   with half the layers.

   ── THE LOOP ARITHMETIC HAS TO BE EXACT ───────────────────────────────────────────────────────────
   Each plate is 300% wide at left:-100%, with the texture tiled at 33.3333% of the element. One tile is
   therefore exactly one viewport width, and translating the element 33.3333% of ITSELF advances exactly
   one tile: the wrap is invisible. The spare viewport-width either side is what absorbs the pointer
   parallax without exposing an edge.
   (First draft used 208% wide with background-size:auto, translating -50%. That is not a whole number of
   tiles and would have jolted once per cycle — caught by doing the multiplication, not by looking.)
   ══════════════════════════════════════════════════════════════════════════════════════════════════ */

/* The old still backdrop (menu-backdrop.webp) is gone from skysunder.css; the scene supplies its own sky.
   The flat colour is only what shows for the frame before menu-sky.webp decodes. */
#titleScreen{background-image:none;background-color:#0b1020}

/* TWO IDS, deliberately — the same trap the timelapse hit and left a comment about.
   assets/ui-theme-baseline.css raises every direct child of the title screen with `#titleScreen > *
   {z-index:2}`, and that out-specifies a bare `#menuScene`. A single id would have put this entire scene
   ON TOP of the menu. Two ids beat it and put the sky back underneath, where a background belongs.

   z-index:-1 rather than 0, for the same reason the video needed it: skysunder.css paints the purple tint
   as ::before/::after at z-index 0 and 1, and a pseudo-element shares its z-index with element children
   while painting first, so an equally-ranked child wins and the tint vanishes behind the art. Negative
   puts the scene below both washes, and it cannot escape the screen because #titleScreen is already a
   stacking context (`isolation:isolate`, set in ui-theme-baseline.css). */
#titleScreen #menuScene{position:absolute;inset:0;overflow:hidden;z-index:-1;
  /* Turns off the <head> guard that keeps the scene from painting before this sheet arrives — see the
     comment beside it in skysunder.html. Two ids, so it beats the single-id rule there. */
  visibility:visible;
  /* Isolate paint so button hover outside this subtree does not dirty mix-blend plates. */
  contain:paint;isolation:isolate;
  --px:0; --py:0}                       /* pointer parallax, 0,0 until the mouse moves */

#menuScene .lyr{position:absolute;left:0;pointer-events:none;will-change:transform;
  /* Keep plates on their own compositor layer so sibling button :active
     paint does not force a full-scene image re-upload (Electron flash). */
  transform:translateZ(0);backface-visibility:hidden}

/* the static base */
#menuScene .sky{inset:0;width:100%;height:100%;
  background:url(menu-sky.webp) center/cover no-repeat;
  transform:translate3d(calc(var(--px)*-6px),calc(var(--py)*-4px),0) scale(1.03)}

/* the drifting plates — black-matte art, so screen blending is what makes them cloud rather than box.
   Screen blend stays; stable translateZ layer (via .lyr) keeps Electron from re-rasterizing on button press. */
#menuScene .plate{left:-100%;width:300%;background-repeat:repeat-x;background-size:33.3333% 100%;
  mix-blend-mode:screen}
@keyframes skyDriftL{from{transform:translate3d(0,0,0)}         to{transform:translate3d(-33.3333%,0,0)}}
@keyframes skyDriftR{from{transform:translate3d(-33.3333%,0,0)} to{transform:translate3d(0,0,0)}}

#menuScene .cloudWall {background-image:url(cloud-wall.webp);opacity:.72;top:-4%; height:60%;
  animation:skyDriftR 320s linear infinite;
  -webkit-mask-image:radial-gradient(ellipse 130% 118% at 50% -18%,#000 62%,transparent 100%);
          mask-image:radial-gradient(ellipse 130% 118% at 50% -18%,#000 62%,transparent 100%)}
#menuScene .cloudGiant{background-image:url(cloud-near.webp);opacity:.40;top:-17%;height:58%;
  animation:skyDriftL 220s linear infinite}
#menuScene .cloudAlt  {background-image:url(cloud-far.webp); opacity:.28;top:-10%;height:51%;
  animation:skyDriftR 276s linear infinite}
#menuScene .cloudFill {background-image:url(cloud-near.webp);opacity:.34;top:8%;  height:36%;
  animation:skyDriftL 164s linear infinite}
#menuScene .cloudMid  {background-image:url(cloud-far.webp); opacity:.87;top:-1%; height:39%;
  animation:skyDriftL 118s linear infinite}
#menuScene .cloudNear {background-image:url(cloud-near.webp);opacity:.78;top:1%;  height:52%;
  animation:skyDriftR  76s linear infinite}

/* the two isles. x/y/width are percentages of the scene, straight from the spec.
   translate3d keeps bob on a stable compositor layer (same Electron flash rationale as .lyr). */
#menuScene .isle{position:absolute;height:auto;pointer-events:none;will-change:transform;
  transform:translateZ(0);backface-visibility:hidden}
@keyframes isleBobFar {0%,100%{transform:translate3d(0,0,0)}50%{transform:translate3d(0,-10px,0)}}
@keyframes isleBobNear{0%,100%{transform:translate3d(0,0,0)}50%{transform:translate3d(0,-13px,0)}}
#menuScene .isleFar {left:62.5%;top:39%;  width:12.2%;animation:isleBobFar  12.6s ease-in-out infinite}
#menuScene .isleNear{left:16.5%;top:31.5%;width:25.5%;animation:isleBobNear  9.2s ease-in-out infinite}
/* depth: the near isle answers the pointer harder than the far one */
#menuScene .isleFarWrap {position:absolute;inset:0;
  transform:translate3d(calc(var(--px)*-14px),calc(var(--py)*-9px),0);backface-visibility:hidden}
#menuScene .isleNearWrap{position:absolute;inset:0;
  transform:translate3d(calc(var(--px)*-26px),calc(var(--py)*-16px),0);backface-visibility:hidden}

/* fog, over the isles. The spec asks for a "curved elliptical top fade" but ships no mask asset, so these
   are recreated as radial-gradient masks — without them each fog band ends on a hard horizontal line. */
#menuScene .fogBack {background-image:url(fog.webp);opacity:.42;bottom:0;  height:56%;
  animation:skyDriftL 54s linear infinite;
  -webkit-mask-image:radial-gradient(ellipse 140% 112% at 50% 118%,#000 48%,transparent 100%);
          mask-image:radial-gradient(ellipse 140% 112% at 50% 118%,#000 48%,transparent 100%)}
#menuScene .fogFront{background-image:url(fog.webp);opacity:.68;bottom:-6%;height:48%;
  animation:skyDriftR 37s linear infinite;
  -webkit-mask-image:radial-gradient(ellipse 120% 106% at 46% 122%,#000 42%,transparent 100%);
          mask-image:radial-gradient(ellipse 120% 106% at 46% 122%,#000 42%,transparent 100%)}

/* keeps the menu text legible over a busy sky */
#menuScene .vig{position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(ellipse 78% 68% at 50% 46%,transparent 40%,rgba(4,6,14,.31) 100%)}

/* ── NOT DECODING WHILE HIDDEN, AND REDUCED MOTION ────────────────────────────────────────────────
   Both inherited from the timelapse this replaces, whose comment made the case well: the menu sits behind
   every other screen for most of a session, and animation running under a battle is pure waste. Six
   composited full-width layers is more waste than one video decode, not less.
   `.sceneOff` is set by the controller when the title screen is hidden. display:none rather than opacity,
   because an animating element at opacity:0 still costs. */
#titleScreen #menuScene.sceneOff{display:none}
@media (prefers-reduced-motion: reduce){
  #menuScene *{animation:none !important}
  #menuScene{--px:0 !important;--py:0 !important}
}
