/* =====================================================================
   MOTION KIT — FX PACK
   Ambient stage atmosphere, screen-to-screen transitions, and the extra
   textured backdrops. Kept in its OWN file so css/motion-kit.css (hand
   maintained) is never rewritten by a feature drop; motion-kit.html loads
   both through the same ?v= cache-buster.

   Everything themes off the same per-game vars as the rest of the kit
   (--b1 / --b2 accents, --main text, --field / --stage-bg background), so a
   session that changes its palette changes the atmosphere with it.
   ===================================================================== */

/* =====================================================================
   1. ATMOSPHERE — the always-on animated background bed
   ---------------------------------------------------------------------
   Sits above the optional background image (z 0) and under the graphics
   layer (.shake, z 2), so a card or the impact word always reads on top.
   The stage carries a SPACE-SEPARATED token list (data-atmo="mesh haze
   sweep"), and each token switches one layer on — presets are just token
   combinations, so a game can run a bare mesh or a full stadium bed with
   no extra CSS.

   Cost control: every layer animates transform/opacity only, on a handful
   of nodes, with long durations. `contain:strict` keeps the whole bed off
   the rest of the page's layout/paint. On a venue wall this runs for the
   entire match, so nothing here uses box-shadow, per-frame JS, or a canvas.

   No filter:blur on a looping layer. A blurred layer that also moves is
   re-filtered by the GPU on EVERY frame, and both the texture (these beds
   overscan to 160-180% of the stage) and the radius (cqh) grow with the
   screen — fine in a window, a stutter in fullscreen on a 4K / Retina
   display. The softness is baked into the gradient stops instead, so the
   layer rasterises once and the compositor only moves it.
   ===================================================================== */
.atmo{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;contain:strict}
.atmo>i{position:absolute;display:none;will-change:transform,opacity}

/* --- mesh: slow drifting colour clouds in the brand accents. The base
       "premiere-graded" look — a flat stage becomes a lit one. --- */
.stage[data-atmo~="mesh"] .at-mesh{display:block;inset:-30%;
  opacity:calc(.5 * var(--atmo-a,1));
  background:
    radial-gradient(40% 44% at 22% 28%, var(--b1) 0%, color-mix(in srgb, var(--b1) 62%, transparent) 28%,
      color-mix(in srgb, var(--b1) 22%, transparent) 54%, transparent 78%),
    radial-gradient(36% 42% at 78% 64%, var(--b2) 0%, color-mix(in srgb, var(--b2) 62%, transparent) 29%,
      color-mix(in srgb, var(--b2) 22%, transparent) 56%, transparent 80%),
    radial-gradient(48% 46% at 52% 96%, var(--b1) 0%, color-mix(in srgb, var(--b1) 62%, transparent) 31%,
      color-mix(in srgb, var(--b1) 22%, transparent) 59%, transparent 84%);
  animation:atMesh 26s ease-in-out infinite alternate}
@keyframes atMesh{
  0%  {transform:translate3d(-2%,-1%,0) scale(1.04) rotate(0deg)}
  50% {transform:translate3d(2.5%,2%,0)  scale(1.12) rotate(3deg)}
  100%{transform:translate3d(-1%,3%,0)  scale(1.06) rotate(-2deg)}}

/* --- haze: two big soft volumes breathing across each other. Reads as
       arena air/smoke caught in the lighting rig. --- */
.stage[data-atmo~="haze"] .at-haze{display:block;inset:-40%;
  /* Both volumes are BRAND-coloured. The first one used to be #fff, which on a
     screen-blend over a dark stage is a big white halo — invisible behind a
     busy graphic and glaringly obvious the moment one is taken out, which is
     exactly when the bed is all there is to look at. */
  opacity:calc(.26 * var(--atmo-a,1));mix-blend-mode:screen;
  background:
    radial-gradient(32% 36% at 30% 70%, var(--b1) 0%, color-mix(in srgb, var(--b1) 62%, transparent) 27%,
      color-mix(in srgb, var(--b1) 22%, transparent) 52%, transparent 78%),
    radial-gradient(28% 32% at 72% 34%, var(--b2) 0%, color-mix(in srgb, var(--b2) 62%, transparent) 28%,
      color-mix(in srgb, var(--b2) 22%, transparent) 54%, transparent 80%);
  animation:atHaze 34s ease-in-out infinite alternate}
@keyframes atHaze{
  0%  {transform:translate3d(-4%,2%,0) scale(1)}
  100%{transform:translate3d(5%,-3%,0) scale(1.18)}}

/* --- sweep: one broad light bar crossing the stage. The single most
       "broadcast package" cue — a moving highlight on the backdrop. --- */
.stage[data-atmo~="sweep"] .at-sweep{display:block;top:-30%;bottom:-30%;left:-60%;width:52%;
  opacity:calc(.3 * var(--atmo-a,1));mix-blend-mode:screen;transform:rotate(14deg);
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.28) 42%,var(--b2) 52%,transparent);
  animation:atSweep 9s cubic-bezier(.5,0,.5,1) infinite}
@keyframes atSweep{
  0%  {transform:translate3d(0,0,0) rotate(14deg);opacity:0}
  18% {opacity:calc(.3 * var(--atmo-a,1))}
  70% {opacity:calc(.3 * var(--atmo-a,1))}
  100%{transform:translate3d(340%,0,0) rotate(14deg);opacity:0}}

/* --- lines: diagonal brand stripes scrolling behind everything. The
       element is oversized and translated (never background-position), so
       the scroll stays on the compositor. --- */
.stage[data-atmo~="lines"] .at-lines{display:block;top:-40%;bottom:-40%;left:-60%;width:220%;
  opacity:calc(.16 * var(--atmo-a,1));transform:rotate(-18deg);
  background:repeating-linear-gradient(90deg,
    var(--b2) 0 .5cqw, transparent .5cqw 3cqw,
    var(--b1) 3cqw 3.4cqw, transparent 3.4cqw 8cqw);
  animation:atLines 18s linear infinite}
@keyframes atLines{
  0%  {transform:translate3d(0,0,0) rotate(-18deg)}
  100%{transform:translate3d(-8cqw,0,0) rotate(-18deg)}}

/* --- pitch: mown turf / court bands with a slow parallax creep. --- */
.stage[data-atmo~="pitch"] .at-pitch{display:block;inset:-20%;
  opacity:calc(.2 * var(--atmo-a,1));
  background:repeating-linear-gradient(102deg,
    rgba(255,255,255,.055) 0 7cqw, transparent 7cqw 14cqw);
  animation:atPitch 30s ease-in-out infinite alternate}
@keyframes atPitch{0%{transform:translate3d(0,0,0)}100%{transform:translate3d(3cqw,1cqh,0)}}

/* --- dust: floating motes. Three tiled speck layers panning at different
       speeds — parallax without a particle system. --- */
.stage[data-atmo~="dust"] .at-dust{display:block;inset:-25%;
  opacity:calc(.5 * var(--atmo-a,1));
  background:
    radial-gradient(circle at 12% 24%, #fff 0 .09cqh, transparent .1cqh),
    radial-gradient(circle at 48% 62%, #fff 0 .13cqh, transparent .14cqh),
    radial-gradient(circle at 82% 18%, var(--b2) 0 .11cqh, transparent .12cqh),
    radial-gradient(circle at 66% 88%, #fff 0 .08cqh, transparent .09cqh);
  background-size:23cqw 27cqh, 31cqw 34cqh, 19cqw 23cqh, 27cqw 31cqh;
  animation:atDust 40s linear infinite}
@keyframes atDust{
  0%  {transform:translate3d(0,0,0)}
  100%{transform:translate3d(-4cqw,-7cqh,0)}}

/* --- worn video: the three artefacts that read as old tape, moving. A noise
       bed stepped frame by frame (never tweened — grain that slides looks like
       dirt on the lens), scanlines drifting slowly upward, and a soft tracking
       band rolling down the frame. Together they age the whole picture without
       touching a single graphic. --- */
.stage[data-atmo~="tape"] .at-tape{display:block;inset:-8%;
  opacity:calc(.5 * var(--atmo-a,1));mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='t'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.7' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23t)' opacity='.85'/%3E%3C/svg%3E");
  background-size:30cqh 30cqh;
  animation:atTape .42s steps(4,end) infinite}
@keyframes atTape{
  0%  {transform:translate3d(0,0,0)}
  25% {transform:translate3d(-3%,2%,0)}
  50% {transform:translate3d(2%,-3%,0)}
  75% {transform:translate3d(-2%,-1%,0)}
  100%{transform:translate3d(0,0,0)}}
.stage[data-atmo~="tape"] .at-scan{display:block;inset:-10%;
  opacity:calc(.35 * var(--atmo-a,1));mix-blend-mode:overlay;
  background:repeating-linear-gradient(0deg,rgba(0,0,0,.55) 0 .18cqh,transparent .18cqh .5cqh);
  animation:atScan 7s linear infinite}
@keyframes atScan{0%{transform:translate3d(0,0,0)}100%{transform:translate3d(0,-2cqh,0)}}
/* The tracking band: one soft, brighter stripe crossing the frame every few
   seconds. It is the artefact that says TAPE rather than just "noisy". */
.stage[data-atmo~="tape"] .at-roll{display:block;left:-10%;right:-10%;height:14cqh;
  opacity:calc(.28 * var(--atmo-a,1));mix-blend-mode:screen;
  background:linear-gradient(180deg,transparent,rgba(255,255,255,.5) 45%,var(--b2) 55%,transparent);
  animation:atRoll 6.5s linear infinite}
@keyframes atRoll{0%{transform:translate3d(0,-120cqh,0)}100%{transform:translate3d(0,120cqh,0)}}

/* --- vignette: the edge falloff every broadcast backdrop has. Static, so
       it costs one paint and nothing after. --- */
.stage[data-atmo~="vig"] .at-vig{display:block;inset:0;
  background:radial-gradient(72% 66% at 50% 46%, transparent 42%, rgba(0,0,0,.62) 100%)}

/* The reactive hit (.at-hit) lived here: a screen-blended bloom centred at
   50% 46% — dead behind the impact word — blurred 6cqh and fired on every cue.
   That was THE WHITE HALO. It is removed, not dialled back: it was never asked
   for, and a bloom that big cannot be tinted into something that is not a glow
   around the type. The element, its JS (atmoHit) and the "Bed reacts to each
   cue" setting are gone with it. */

/* --- film grain: an independent toggle (data-grain), over the graphics but
       under the pixel grid, so the WHOLE picture — cards included — sits in
       the same emulsion. The tile is an inline feTurbulence, stepped rather
       than tweened so it flickers like real grain instead of sliding. --- */
.grain{position:absolute;inset:-6%;z-index:19;pointer-events:none;display:none;
  opacity:calc(.16 * var(--grain-a,1));mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.9'/%3E%3C/svg%3E");
  background-size:34cqh 34cqh;
  animation:grainShift .5s steps(5,end) infinite}
.stage[data-grain="1"] .grain{display:block}
@keyframes grainShift{
  0%  {transform:translate3d(0,0,0)}
  20% {transform:translate3d(-2%,1.5%,0)}
  40% {transform:translate3d(1.5%,-2%,0)}
  60% {transform:translate3d(-1%,-1.5%,0)}
  80% {transform:translate3d(2%,1%,0)}
  100%{transform:translate3d(0,0,0)}}

/* Reduced motion: keep the look (colour bed, vignette, grain texture), drop
   every loop. A static graded backdrop still beats a flat one. */
@media (prefers-reduced-motion:reduce){
  .atmo>i,.grain{animation:none!important}
  .stage[data-atmo~="sweep"] .at-sweep{opacity:0}
}

/* =====================================================================
   2. TRANSITIONS — the cover between two screens of a sequence
   ---------------------------------------------------------------------
   One full-stage layer above every graphic (cards z 3-22, logo bug 22,
   pixel grid 20) and below the operator HUD. It is invisible and inert
   until runTransition() shows it, animates its children, swaps the screen
   underneath at the cover point, then takes it back out.
   ===================================================================== */
.trans{position:absolute;inset:0;z-index:30;pointer-events:none;opacity:0;overflow:hidden;contain:strict}
/* Every cover child starts INVISIBLE as well as display:none: each transition
   animates its own opacity (fill:'both', so the first frame is already right).
   Without this a transform-only cover would paint one opaque frame before its
   animation took hold — a full-screen flash on the wall. */
.trans>i,.trans>img{position:absolute;display:none;opacity:0;will-change:transform,opacity,clip-path}

/* Veil — the brand slab most transitions are built from. */
.trans .tr-veil{inset:-10%;
  background:linear-gradient(118deg,var(--b1) 0%,var(--b2) 58%,var(--b1) 100%)}
/* A second, darker veil for depth: the two ride at different speeds. */
.trans .tr-veil2{inset:-10%;
  background:linear-gradient(118deg,var(--plate,#0c1017),color-mix(in srgb,var(--b1) 40%,#000))}
/* Glitch reuses the dark veil as a signal DROPOUT — that has to be black, not
   a themed plate: a branded "loss of picture" reads as a graphic, not a fault. */
.trans[data-kind="glitch"] .tr-veil2{background:#000}
.trans .tr-flash{inset:0;background:#fff;mix-blend-mode:screen;opacity:0}
/* Bars — five slabs that close the picture like a shutter. */
.trans .tr-bar{top:-12%;height:124%;width:calc(100% / 5 + .6%);
  background:linear-gradient(180deg,var(--b1),var(--b2))}
/* Glitch slabs — hard offset colour bands during the cut. */
.trans .tr-slab{left:-10%;width:120%;mix-blend-mode:screen;opacity:0}
/* The sponsor/opener logo riding a bumper wipe. */
.trans .tr-logo{top:50%;left:50%;height:26cqh;max-width:56cqw;object-fit:contain;
  transform:translate(-50%,-50%);opacity:0;
  filter:drop-shadow(0 1cqh 3cqh rgba(0,0,0,.45))}
/* Only the kind being played paints — every other child stays display:none,
   so an idle transition layer costs nothing between cues. */
.trans[data-kind="fade"]   .tr-veil,
.trans[data-kind="wipe"]   .tr-veil,
.trans[data-kind="wipe"]   .tr-veil2,
.trans[data-kind="slide"]  .tr-veil,
.trans[data-kind="slide"]  .tr-veil2,
.trans[data-kind="iris"]   .tr-veil,
.trans[data-kind="logo"]   .tr-veil,
.trans[data-kind="logo"]   .tr-logo,
.trans[data-kind="bars"]   .tr-bar,
.trans[data-kind="glitch"] .tr-slab,
.trans[data-kind="glitch"] .tr-veil2,
.trans[data-kind="glitch"] .tr-flash,
.trans[data-kind="flash"]  .tr-flash,
.trans[data-kind="zoom"]   .tr-flash{display:block}

/* =====================================================================
   3. EXTRA BACKDROPS — textured layers for the impact burst
   ---------------------------------------------------------------------
   Same contract as the built-in ones: a .bd inside #impact, filled by a
   spawn function and animated on the shared scrub track.
   ===================================================================== */
/* Smoke — soft volumes billowing out from the middle. */
.bd-smoke{inset:0;z-index:7;opacity:1;overflow:hidden;mix-blend-mode:screen}
.bd-smoke i{position:absolute;top:50%;left:50%;width:44cqh;height:44cqh;border-radius:50%;opacity:0;
  filter:blur(5cqh);
  background:radial-gradient(closest-side,rgba(255,255,255,.55),transparent 70%)}
/* Light leaks — wide colour washes raking across the frame. */
.bd-leaks{inset:0;z-index:7;opacity:1;overflow:hidden;mix-blend-mode:screen}
.bd-leaks i{position:absolute;top:-30%;height:160%;width:46cqw;opacity:0;filter:blur(4cqh);
  background:linear-gradient(90deg,transparent,var(--b2) 45%,var(--b1) 62%,transparent)}
/* Halftone — a print dot grid punching out from centre. */
.bd-halftone{inset:-20%;z-index:6;opacity:0;
  background-image:radial-gradient(circle, var(--b2) 0 22%, transparent 24%);
  background-size:2.6cqh 2.6cqh}
/* Strobe — hard full-frame brand hits, the loudest layer in the pack. */
.bd-strobe{inset:0;z-index:12;opacity:0;background:var(--b2);mix-blend-mode:screen}

@media (prefers-reduced-motion:reduce){
  .bd-smoke,.bd-leaks,.bd-halftone,.bd-strobe{display:none}
}


/* =====================================================================
   4. WALL SHAPE — designing once for a board that is not 16:9
   ---------------------------------------------------------------------
   Every size in this kit is a container unit, and 65 of them are cqh: the
   package scales off the stage's HEIGHT. That is right on a 16:9 jumbotron
   and wrong everywhere else — on a ribbon board (3840x480) the type shrinks
   to unreadable, on a near-square wall the impact word overflows.

   With a wall shape set, the stage becomes the largest box of the DESIGN
   ratio that fits the physical canvas, centred, with the remainder painted
   as bars. Every container unit then resolves against a box of known
   proportions, so one design lands correctly on any board.

   The maths is a container query on .app (`min(100cqw, 100cqh * ratio)`),
   not viewport units: the venue page is also embedded in the back office
   preview iframe, where vh is the browser window, not the frame.
   ===================================================================== */
body[data-wall] .app{container-type:size;background:var(--wall-bar,#000)}
body[data-wall] .stage{
  position:absolute;inset:0;margin:auto;
  width:min(100cqw, calc(100cqh * var(--wall-ar,1.7778)));
  height:min(100cqh, calc(100cqw / var(--wall-ar,1.7778)))}
/* The bars are part of the picture on a wall, so they are painted, not left
   transparent: --wall-bar is black by default and can be keyed to the venue's
   own unlit colour when the board is not truly black. */


/* =====================================================================
   5. STANDBY — the picture between the moments
   ---------------------------------------------------------------------
   CUT sends the wall here instead of to a black stage: the atmosphere bed
   keeps running, the sponsor logo sits centre with an optional line under
   it. Themed from the same vars as everything else.
   ===================================================================== */
.gfx.standby{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:3cqh;z-index:5;pointer-events:none}
.gfx.standby .sb-logo{max-width:46cqw;max-height:30cqh;object-fit:contain;display:none;
  filter:drop-shadow(0 1cqh 3cqh rgba(0,0,0,.5))}
.gfx.standby[data-logo] .sb-logo{display:block}
.gfx.standby .sb-line{font-family:var(--font-display);font-weight:900;text-transform:uppercase;
  color:var(--main,#fff);font-size:6cqh;letter-spacing:.14em;text-align:center;max-width:86cqw;
  text-shadow:0 .4cqh 1.6cqh rgba(0,0,0,.55)}
.gfx.standby .sb-line:empty{display:none}


/* Echo stack — the word repeated BEHIND itself, each copy further back and
   fainter. Rendered as rows in the existing slices host, which already sits
   under the live word. */
/* Absolutely positioned inside the (flex) slices host, so each copy is centred
   on the stage rather than stacking in flow — the trail has to sit exactly
   behind the live word, not beside it. Every keyframe carries the -50%
   centring translate with it. */
.imp-slices .ec-row{position:absolute;left:50%;top:50%;white-space:pre;
  font-family:var(--font-display);font-weight:900;text-transform:uppercase;
  color:var(--main,#fff);line-height:.82;opacity:0;will-change:transform,opacity}


/* =====================================================================
   6. WHITE, TAMED
   ---------------------------------------------------------------------
   Several layers in the base stylesheet are pure white at high alpha and
   screen/overlay blended. Behind a busy graphic they read as light; alone
   on a dark stage — as the sting settles, or the moment a graphic leaves —
   they read as a big white halo, which is what they were reported as.

   None of these are named "white" in the UI (Light sweep, Beat flash's
   default vignette, the opener flare, the atmosphere sweep), so they are
   brand-tinted and dialled back here rather than in the hand-maintained
   file. The one option that IS called White flash keeps its colour, just
   less of it.
   ===================================================================== */
/* Beat pulse — fired once as the word lands (and once per syllable in Chant).
   The default "vignette" style paints --b2 out to 108%, so a palette whose
   secondary is white produced a full-screen white bloom on every call. */
.chant-pulse{background:radial-gradient(circle at 50% 50%,
  transparent 44%, color-mix(in srgb, var(--b2) 62%, transparent) 112%)}
.chant-pulse[data-style="flash"]{background:radial-gradient(circle at 50% 50%,
  rgba(255,255,255,.62) 0%, rgba(255,255,255,.34) 52%, rgba(255,255,255,.08) 100%)}
/* Light sweep — a light bar across the picture, not a white-out. */
.imp-sweep{background:linear-gradient(100deg,transparent 40%,
  color-mix(in srgb, var(--b2) 55%, rgba(255,255,255,.5)) 50%, transparent 60%)}
/* Opener lens flare — was a 70cqw disc of 90% white. */
.op-flare{background:radial-gradient(closest-side,
  rgba(255,255,255,.55), color-mix(in srgb, var(--b2) 40%, transparent) 34%, transparent 64%)}
/* Light beams backdrop */
.bd-beams i{background:linear-gradient(90deg,transparent,
  color-mix(in srgb, var(--b2) 60%, rgba(255,255,255,.35)), transparent)}
/* Atmosphere sweep + tape tracking band: brand light, not headlight. */
/* Extra stops stand in for the blur these layers used to carry (see section 1). */
.stage[data-atmo~="sweep"] .at-sweep{
  background:linear-gradient(90deg,transparent 4%,rgba(255,255,255,.06) 28%,rgba(255,255,255,.16) 42%,
    var(--b2) 52%,color-mix(in srgb, var(--b2) 40%, transparent) 68%,transparent 94%)}
.stage[data-atmo~="tape"] .at-roll{
  background:linear-gradient(180deg,transparent 4%,rgba(255,255,255,.1) 28%,rgba(255,255,255,.28) 45%,
    var(--b2) 55%,color-mix(in srgb, var(--b2) 35%, transparent) 74%,transparent 96%)}


/* =====================================================================
   7. NEVER FULLY STILL — the background half
   ---------------------------------------------------------------------
   The per-cue drift (idleLoop in the JS) keeps the graphics alive. This
   keeps the PICTURE alive when no graphic is up at all — standby, a gap
   between plays, the whole pre-game hour. Slow enough that nobody watches
   it, fast enough that the wall is never a frozen frame.
   Driven by body.mk-idle, which follows the "Never fully still" extra.
   ===================================================================== */
body.mk-idle .stage-bg{animation:idleBg 42s ease-in-out infinite alternate}
@keyframes idleBg{
  0%  {transform:scale(1.02) translate3d(0,0,0)}
  100%{transform:scale(1.07) translate3d(-1.2%,-1%,0)}}
/* With no background image the stage itself is the picture: drift its gradient.
   Not by animating background-position — that repaints the full stage on every
   frame, which is what made fullscreen stutter on a large display. A slightly
   oversized copy of the stage background moves on transform instead: one
   layer, rasterised once, moved by the compositor. */
body.mk-idle .stage:not([data-bg])::before{content:'';position:absolute;inset:-6%;z-index:0;
  pointer-events:none;background:inherit;will-change:transform;
  animation:idleGrad 54s ease-in-out infinite alternate}
@keyframes idleGrad{
  0%  {transform:translate3d(.64%,.64%,0)}
  100%{transform:translate3d(-.64%,-.64%,0)}}
@media (prefers-reduced-motion:reduce){
  body.mk-idle .stage-bg,body.mk-idle .stage:not([data-bg])::before{animation:none}
}


/* =====================================================================
   8. COST CONTROL
   ---------------------------------------------------------------------
   Two cheap wins for the back office, which keeps this page open in an
   iframe for a whole editing session:
   · paused — the tab is in the background, so nothing is being watched;
   · lite   — the preview frame, where a 7cqh blur on a full-bleed layer
     costs the same compositing as it does on the wall and reads as almost
     nothing at 400px wide.
   Neither affects a venue screen (never hidden, never lite).
   ===================================================================== */
body.mk-paused .atmo>i,
body.mk-paused .grain,
body.mk-paused .stage-bg,
body.mk-paused .stage::before{animation-play-state:paused!important}
body[data-lite] .atmo>i{filter:none!important}
body[data-lite] .grain{animation-duration:1.2s}


/* =====================================================================
   9. COLOUR GLITCH — a broken signal as a background
   ---------------------------------------------------------------------
   Two forms of the same idea:
   · .at-glitch  — an ATMOSPHERE layer: colour bands that keep tearing for
     as long as the look is on, cheap enough to run all match (one element,
     stepped transforms, no per-frame JS);
   · .bd-datamosh — a BACKDROP: a hard burst of displaced colour blocks
     fired with a cue, built from spawned elements like the confetti.
   Both lean on magenta/cyan rather than the brand accents: a data tear
   that happens to be on-brand reads as decoration, not as a fault, and
   the fault is the effect.
   ===================================================================== */
.stage[data-atmo~="glitch"] .at-glitch{display:block;inset:-6%;
  opacity:calc(.5 * var(--atmo-a,1));mix-blend-mode:screen;
  background:
    repeating-linear-gradient(0deg,
      rgba(255,0,140,.55) 0 .6cqh, transparent .6cqh 3.2cqh),
    repeating-linear-gradient(0deg,
      rgba(0,225,255,.45) 1.6cqh 2.2cqh, transparent 2.2cqh 5.4cqh),
    repeating-linear-gradient(0deg,
      rgba(255,220,0,.3) 3cqh 3.4cqh, transparent 3.4cqh 9cqh);
  animation:atGlitchShift 2.6s steps(1,end) infinite, atGlitchHue 9s linear infinite}
@keyframes atGlitchShift{
  0%  {transform:translate3d(0,0,0) scaleY(1)}
  12% {transform:translate3d(-2.5%,1.2cqh,0) scaleY(1.02)}
  24% {transform:translate3d(1.8%,-.8cqh,0) scaleY(.99)}
  38% {transform:translate3d(0,2.4cqh,0) scaleY(1)}
  52% {transform:translate3d(-1.2%,-2cqh,0) scaleY(1.01)}
  70% {transform:translate3d(2.2%,.6cqh,0) scaleY(1)}
  100%{transform:translate3d(0,-1.4cqh,0) scaleY(1)}}
@keyframes atGlitchHue{0%{filter:hue-rotate(0)}100%{filter:hue-rotate(360deg)}}

.bd-datamosh{inset:0;z-index:9;opacity:1;overflow:hidden;mix-blend-mode:screen}
.bd-datamosh i{position:absolute;left:-8%;width:116%;opacity:0}

/* GAME OVER type: the word sits on its own interference field — a noise wash
   plus the horizontal tear lines that make a locked-up frame read as broken
   signal rather than as a design. Painted only while that engine runs. */
.imp-word[data-fx="gameover"]{position:relative}
.imp-word[data-fx="gameover"]::before{content:'';position:absolute;inset:-18% -8%;
  pointer-events:none;mix-blend-mode:screen;opacity:.5;
  background:repeating-linear-gradient(0deg,rgba(255,255,255,.18) 0 1px,transparent 1px 3px);
  animation:goScan .18s steps(2,end) infinite}
@keyframes goScan{0%{transform:translateY(0)}100%{transform:translateY(3px)}}


/* =====================================================================
   10. MOTION BACKGROUND — a looping video behind the package
   ---------------------------------------------------------------------
   Sits above the still background image and below the atmosphere bed, so
   the graded layers and the vignette still apply on top of it. Opacity is
   a variable because a full-strength motion loop behind a scorebug is
   usually too much: 40-70% is where footage supports the graphics instead
   of fighting them.
   ===================================================================== */
.stage-video{position:absolute;inset:0;z-index:0;width:100%;height:100%;
  object-fit:cover;display:none;pointer-events:none;opacity:var(--bgvid-a,1)}
.stage[data-bgvideo] .stage-video{display:block}
/* With a video running, the stage's own gradient would show through the
   letterbox of a differently-shaped clip — paint it black instead. */
.stage[data-bgvideo]{background-color:#000}


/* A motion background is chosen to be SEEN. The two full-frame layers that sit
   over it — the impact flood (softened in the JS) and the atmosphere vignette —
   are pulled back here so the clip reads through them instead of behind them.
   Everything keyed to the accents is left alone: it is the graphics' colour,
   not a wash over the picture. */
.stage[data-bgvideo][data-atmo~="vig"] .at-vig{opacity:.55}
.stage[data-bgvideo] .at-haze{opacity:calc(.16 * var(--atmo-a,1))}
