/* ═══ THE INTRO CINEMATIC — two isles sunder and lock ═══════════════════════════════════════════════
   Josh 2026-07-31: "I put another background animation in the UI folder … I want to use as the background
   to the short intro cinematic when you start a game … with the lightning bolt effects and sparks", then
   "I like this one, can you replace the current intro stinger with it?"

   THE "CURRENT INTRO STINGER" IS #clash — the screen `runClash()` throws up the moment you press ASCEND or
   BEGIN, before prep. It is not the per-round VS card (`#matchIntro`, `g.state==='intro'`), which is a
   different and later moment; that one is untouched. Both start-a-game paths reach #clash:
   `ascGo.onclick → newGame('ascent') → runClash()` and `diffGo.onclick → newGame('skirmish') → runClash()`,
   plus multiplayer at 04-multiplayer.js:151.

   EVERY RULE HERE IS SCOPED UNDER #cineIntro, which exists only inside #clash. That is deliberate: the
   class names this scene wants — .sky, .plate, .half, .flash, .spark, .wave, .burst, .bolt — are exactly
   the words a game about floating islands and lightning already uses elsewhere, and `.flash` in particular
   is ALREADY DEFINED in skysunder.css (`position:absolute;inset:0;background:#fff`) for the battle hit
   flash. Unscoped, this file would have silently restyled it.

   Timing comes from the drop's timeline.json originally (approach 2.2s); retimed to a tighter 1.5s
   approach so the smash reads sooner (Josh 2026-08-16):
     0.00 – 1.50s  the two isle halves approach from off-screen
     1.50 – 2.50s  impact — five staggered seam bursts, energy spine, four forked bolts, elliptical
                   shockwave, sparks, rock debris, dust, and a 1.0s decaying shake
   The clash screens outlast it on purpose (ascent ~3.1s, tournament ~3.9s) and the joined isle is what holds
   under the wordmark for the remainder — see the note on .joined.

   The masters this was cut from live in ../../../../art-cleanup/floating-isles-battle-sequence-v10/, NOT in
   assets/, because tools/sync-public.js copies assets/ui wholesale and they are 13 MB of PNG. The eight
   webp plates beside this file are 1.29 MB and are the only thing the game loads. */

/* z-index MUST BE NEGATIVE, and this is the one rule in the file that is easy to get wrong.
   #clashRows and #clashTitle are flex items with no `position`, so they are NON-POSITIONED, and in the
   paint order a non-positioned block paints BEFORE any positioned sibling with z-index:0 or auto. At
   z-index:0 this backdrop would therefore cover the tournament bracket and the wordmark — the two things
   the screen exists to show. At -1 it paints after #clash's own background and before every descendant,
   which is exactly the slot wanted. #clash is `position:absolute;z-index:40`, so it forms a stacking
   context and the negative index cannot escape it and slide behind the game. This is the same trick
   #menuScene uses in parallax.css, for the same reason. */
#cineIntro{position:absolute;inset:0;overflow:hidden;z-index:-1;pointer-events:none;
  background:#05060a;    /* the sky plate is `cover`, so on an extreme aspect ratio this is what shows */
  contain:paint}         /* the plates are 300% wide; without this they can widen the layout viewport */

/* The shake carrier is inset so the 14px throw at its peak never reveals an edge. */
#cineIntro .cineShake{position:absolute;inset:-2%}

#cineIntro .lyr{position:absolute;left:0;top:0;width:100%;height:100%;
  background-repeat:no-repeat;background-position:center;background-size:cover;
  will-change:transform,opacity}

#cineIntro .sky{background-image:url(sky.webp)}

/* ── THE DRIFTING CLOUD PLATES ────────────────────────────────────────────────────────────────────
   Same seamless-loop arithmetic as the menu scene: the element is 300% wide, the texture is 33.3333% of
   that (= exactly one viewport), and the animation translates 33.3333% — one whole tile, so the wrap
   lands on identical pixels and cannot be seen. Any other pair of numbers jolts once per cycle. */
#cineIntro .plate{left:-100%;width:300%;background-repeat:repeat-x;background-size:33.3333% 100%;
  mix-blend-mode:screen;background-position:0 0}
@keyframes cineDriftL{from{transform:translate3d(0,0,0)}         to{transform:translate3d(-33.3333%,0,0)}}
@keyframes cineDriftR{from{transform:translate3d(-33.3333%,0,0)} to{transform:translate3d(0,0,0)}}

/* EVERY CLOUD PLATE FADES OUT AT ITS BOTTOM. Josh: "there is a bit of a strange line in the background
   where the clouds hit the earth" — and the line was mine, not the art's. I checked the source plates for
   a hard horizontal discontinuity first and there is none in any of them. Each plate occupies a vertical
   BAND of the screen, and without a mask the band simply stops: the screen-blended brightness ends on a
   straight edge. cloudWall and cloudNear both ended at 54% down, which is exactly where the isles sit, so
   the two coincident edges read as one line along the horizon. Measured on a composite: worst row-to-row
   luminance jump 0.080 before, 0.039 after, and what remains is the horizon painted into sky.webp rather
   than an edge in the compositing. The menu parallax masks its cloud wall for this same reason; I carried
   the plates across from it and did not carry the masks. */
#cineIntro .plate.fadeDown{
  -webkit-mask-image:linear-gradient(180deg,#000 58%,transparent 100%);
          mask-image:linear-gradient(180deg,#000 58%,transparent 100%)}

#cineIntro .cloudWall{background-image:url(cloud-wall.webp);opacity:.62;top:-4%;height:58%;animation:cineDriftR 90s linear infinite}
#cineIntro .cloudNear{background-image:url(cloud-near.webp);opacity:.70;top:2%; height:52%;animation:cineDriftL 46s linear infinite}
#cineIntro .cloudFar {background-image:url(cloud-far.webp); opacity:.42;top:-8%;height:48%;animation:cineDriftR 64s linear infinite}
#cineIntro .fog{background-image:url(fog.webp);opacity:.55;top:auto;bottom:-6%;height:52%;
  animation:cineDriftL 30s 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%)}

/* ── THE TWO HALVES ───────────────────────────────────────────────────────────────────────────────
   Full-frame plates with alpha along a jagged seam (seam.json / seam-cutter.html) — not 50% crops.
   Both layers are 100% wide and stacked; transparent pixels on the far side of the break let them
   interlock when they meet.

   APPROACH DISTANCE IS IN % OF THE ELEMENT. On the old 50%-wide crops, translate -62% moved the
   seam ~31% of the viewport (a little over half a half). Full-bleed plates are twice as wide, so
   the same -62% would park them almost a full frame off-screen and waste the first half of the
   2.2s as empty sky — use -31% / +31% to keep the old on-screen approach. */
#cineIntro .half{position:absolute;left:0;top:0;height:100%;width:100%;background-size:100% 100%;
  background-repeat:no-repeat;will-change:transform}
#cineIntro .halfL{background-image:url(isle-left.webp);  animation:cineInL 1.5s cubic-bezier(.42,0,.72,1) forwards}
#cineIntro .halfR{background-image:url(isle-right.webp); animation:cineInR 1.5s cubic-bezier(.42,0,.72,1) forwards}
@keyframes cineInL{from{transform:translate3d(-31%,0,0)}to{transform:translate3d(0,0,0)}}
@keyframes cineInR{from{transform:translate3d(31%,0,0)} to{transform:translate3d(0,0,0)}}

/* THE JOINED PLATE REPLACES THE HALVES THE INSTANT THEY MEET, and it is the reason isle-joined.webp ships
   beside the two halves at all. The halves are the animation; the joined plate is the resting state. Two
   halves left touching show their cut, and cutting between them under the white flash at 1.5s hides it.
   It is also what holds on screen for the remainder the clash outlasts the
   cinematic — the scene ends on a finished battlefield rather than on nothing. */
#cineIntro .joined{background-image:url(isle-joined.webp);opacity:0;background-size:100% 100%;
  animation:cineJoinIn 0s linear 1.5s forwards}
@keyframes cineJoinIn{to{opacity:1}}

/* ── IMPACT: all of it at 1.50s ───────────────────────────────────────────────────────────────────
   The seam is the vertical centre line. Everything below is drawn there and thrown outward. */
#cineIntro .impact{position:absolute;inset:0;pointer-events:none;opacity:0;animation:cineShowAt 0s linear 1.5s forwards}
@keyframes cineShowAt{to{opacity:1}}

/* the full-height energy spine down the join — the lightning Josh asked for, at the moment it is earned */
#cineIntro .spine{position:absolute;left:50%;top:0;width:5px;height:100%;margin-left:-2.5px;
  background:linear-gradient(180deg,transparent,#cfe8ff 12%,#fff 50%,#cfe8ff 88%,transparent);
  filter:blur(.4px) drop-shadow(0 0 16px #7cc4ff) drop-shadow(0 0 44px #4aa3ff);
  transform-origin:50% 50%;animation:cineSpine 1s cubic-bezier(.2,.7,.3,1) 1.5s forwards;opacity:0}
@keyframes cineSpine{
  0%{opacity:0;transform:scaleX(.2) scaleY(.1)}
  8%{opacity:1;transform:scaleX(3.4) scaleY(1)}
  26%{opacity:.9;transform:scaleX(1) scaleY(1)}
  100%{opacity:0;transform:scaleX(.3) scaleY(1)}}

/* four forked bolts either side of the spine, staggered so they do not fire as one */
#cineIntro .bolt{position:absolute;left:50%;top:0;height:100%;width:2px;opacity:0;
  background:linear-gradient(180deg,transparent,#eaf6ff,transparent);
  filter:drop-shadow(0 0 10px #8fd0ff);transform-origin:50% 0}
#cineIntro .b1{animation:cineBolt .55s ease-out 1.52s forwards;transform:rotate(6deg)  translateX(-40px)}
#cineIntro .b2{animation:cineBolt .55s ease-out 1.58s forwards;transform:rotate(-8deg) translateX(34px)}
#cineIntro .b3{animation:cineBolt .45s ease-out 1.64s forwards;transform:rotate(3deg)  translateX(-84px)}
#cineIntro .b4{animation:cineBolt .45s ease-out 1.70s forwards;transform:rotate(-4deg) translateX(78px)}
@keyframes cineBolt{0%{opacity:0;transform:scaleY(0)}18%{opacity:1}100%{opacity:0;transform:scaleY(1)}}

/* the elliptical shockwave — squashed on Y because it is spreading across a flat isle, not into the sky */
#cineIntro .wave{position:absolute;left:50%;top:50%;width:120px;height:120px;margin:-60px 0 0 -60px;
  border:3px solid rgba(190,225,255,.9);border-radius:50%;opacity:0;
  box-shadow:0 0 40px rgba(120,190,255,.7),inset 0 0 30px rgba(120,190,255,.5);
  animation:cineWave 1s cubic-bezier(.15,.75,.3,1) 1.5s forwards}
@keyframes cineWave{0%{opacity:0;transform:scale(.1) scaleY(.5)}
                    10%{opacity:1}
                    100%{opacity:0;transform:scale(16) scaleY(.62);border-width:1px}}

/* five staggered seam bursts up the join; k3 is the big one at the middle */
#cineIntro .burst{position:absolute;left:50%;width:220px;height:220px;margin-left:-110px;border-radius:50%;
  background:radial-gradient(circle,#fff 0%,#bfe3ff 18%,rgba(120,180,255,.5) 42%,transparent 70%);
  opacity:0;transform:scale(.2)}
@keyframes cineBurst{0%{opacity:0;transform:scale(.15)}22%{opacity:1;transform:scale(1)}
                     100%{opacity:0;transform:scale(1.9)}}
#cineIntro .k1{top:8%;  animation:cineBurst .60s ease-out 1.50s forwards}
#cineIntro .k2{top:30%; animation:cineBurst .60s ease-out 1.56s forwards}
#cineIntro .k3{top:50%; animation:cineBurst .75s ease-out 1.52s forwards;width:340px;height:340px;margin-left:-170px}
#cineIntro .k4{top:68%; animation:cineBurst .60s ease-out 1.61s forwards}
#cineIntro .k5{top:86%; animation:cineBurst .60s ease-out 1.67s forwards}

/* the white flash that sells the hit. NOTE THE SCOPE: skysunder.css already owns a bare `.flash`
   (the battle hit flash) and this must never reach it. */
#cineIntro .cineFlash{position:absolute;inset:0;background:#eaf4ff;opacity:0;mix-blend-mode:screen;
  animation:cineFlash .5s ease-out 1.5s forwards}
@keyframes cineFlash{0%{opacity:0}6%{opacity:.85}100%{opacity:0}}

/* Sparks, rock debris and dust are SPAWNED BY JS, not written out: 72 elements as 72 rules would be
   unreadable, and their whole job is that each carries its own random vector and delay — written as CSS
   they would need 72 hand-picked numbers and would still read as a comb. See cineSpawn() in 10-render.js. */
#cineIntro .spark{position:absolute;width:3px;height:3px;border-radius:50%;background:#ffe9b8;
  box-shadow:0 0 8px 2px rgba(255,200,110,.85);opacity:0}
#cineIntro .rock{position:absolute;width:9px;height:7px;border-radius:2px;background:#6d6357;opacity:0;
  box-shadow:0 1px 3px rgba(0,0,0,.6)}
#cineIntro .dust{position:absolute;border-radius:50%;
  background:radial-gradient(circle,rgba(190,180,165,.5),transparent 70%);opacity:0}

/* THE SHAKE MOVES THE SCENE, NOT THE SCREEN. In the standalone preview the shake carried everything
   because everything WAS the cinematic. Here the wordmark and the tournament bracket sit on top of it, and
   they are the thing the player is reading — shaking type is unpleasant, and #clashRows' slam animations
   are transforms of their own that would then be composed with a moving parent. Shaking only the backdrop
   still lands the hit and leaves the text legible. */
@keyframes cineShake{
  0%{transform:translate3d(0,0,0)}
  6%{transform:translate3d(-14px,9px,0)}   12%{transform:translate3d(12px,-11px,0)}
  20%{transform:translate3d(-10px,-6px,0)} 28%{transform:translate3d(8px,7px,0)}
  38%{transform:translate3d(-6px,4px,0)}   50%{transform:translate3d(5px,-4px,0)}
  64%{transform:translate3d(-3px,2px,0)}   80%{transform:translate3d(2px,-1px,0)}
  100%{transform:translate3d(0,0,0)}}
#cineIntro.shaking .cineShake{animation:cineShake 1s cubic-bezier(.25,.6,.3,1) 1.5s both}

/* A darkening wash so gold Cinzel on a bright sky stays readable. It is a gradient rather than a flat
   veil because the type sits in the middle band and the corners are better left bright. */
#cineIntro .cineVeil{position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(ellipse 78% 62% at 50% 50%,rgba(6,8,20,.62),rgba(6,8,20,.30) 68%,rgba(6,8,20,.55))}

/* ── REDUCED MOTION ───────────────────────────────────────────────────────────────────────────────
   An intro that cannot be avoided is the worst possible place to ignore this setting: the drifting plates
   run for the whole screen, and the impact is a white flash plus a 1s shake. The scene is not removed —
   the player still gets the battlefield, held on its finished frame — but nothing moves and nothing
   flashes. #ascFx already made the same choice, which is the precedent being followed. */
@media(prefers-reduced-motion:reduce){
  #cineIntro .plate,#cineIntro .fog{animation:none}
  #cineIntro .halfL,#cineIntro .halfR{display:none}   /* the joined plate below is the finished frame */
  #cineIntro .joined{opacity:1;animation:none}
  #cineIntro .impact{display:none}
  #cineIntro .cineFlash{display:none}
  #cineIntro.shaking .cineShake{animation:none}
}
