/* =====================================================================
   SurveyAll — ambience.
   Decorative backdrop motion. See app/ambience.js for why this is CSS
   keyframes and not the spring engine.

   Everything below animates ONLY transform and opacity, which the
   browser can run on the compositor without touching the main thread.
   That is a hard rule, not a preference: the main thread belongs to the
   charts, and a background that steals frames from a bar chart is worse
   than no background at all. Nothing here may animate background-position,
   filter, or any property that forces layout or paint.
   ===================================================================== */

/* Ping-pong between two extremes rather than looping start→end. A hard
   loop has a cut in it, and a cut is the thing that reads as cheap; an
   alternating ease has no seam to find at any amplitude or duration. */
@keyframes amb-drift {
  from {
    transform: translate3d(calc(var(--amb-x, 0px) * -1), calc(var(--amb-y, 0px) * -1), 0)
               rotate(calc(var(--amb-r, 0deg) * -1))
               scale(var(--amb-s0, 1));
  }
  to {
    transform: translate3d(var(--amb-x, 0px), var(--amb-y, 0px), 0)
               rotate(var(--amb-r, 0deg))
               scale(var(--amb-s1, 1));
  }
}

@keyframes amb-breathe {
  from { opacity: var(--amb-o0, .7); }
  to   { opacity: var(--amb-o1, 1); }
}

/* The backdrop fades in on mount so switching ambience on — or arriving
   on a slide — doesn't pop a second image into the room. */
@keyframes amb-arrive {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* --------------------------------------------------------------- base */

/* Applied to the existing backdrop element itself (lattice drift, Ken
   Burns). THIS one must cover: the moving thing is the backdrop, so any
   gap on the trailing side is a bare strip of --ground.

   The overhang is in pixels, not percent, because the drift it has to
   outrun is in pixels — a lattice travels a fraction of its own cell,
   which is a fixed px size at every screen size. An earlier -3% was
   ample on a projector and too small on an editor thumbnail, which is
   exactly backwards. 24px clears the largest travel `lively` asks for
   with room to spare, at any size. */
.stage-backdrop.is-drifting,
.sp-backdrop.is-drifting {
  inset: -24px;
  animation: amb-drift var(--amb-drift-dur, 73s) ease-in-out infinite alternate;
}

/* -------------------------------------------------------------- stack */

.amb-stack {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  animation: amb-arrive 1.6s ease-out both;
}

.amb-layer {
  position: absolute;
  /* Oversized, but NOT to guarantee coverage the way the base is — this
     layer is a bloom on top of a backdrop that already covers the frame,
     and its gradient has faded to fully transparent long before its own
     edge. A corner the rotation swings it away from simply gets less
     light, which is what a bloom does everywhere else too. The overhang
     is here so the ellipse stays generous relative to the frame and its
     falloff never tightens up against the edge as the layer breathes.

     Keep it modest anyway: three composited layers at 1.36× the stage is
     the effect's real cost — GPU texture memory, which is why nothing
     but the projector and the one editor canvas ever mounts it. */
  inset: -18%;
  background-repeat: no-repeat;
  background-position: center;
  animation:
    amb-drift var(--amb-drift-dur, 71s) ease-in-out infinite alternate,
    amb-breathe var(--amb-breathe-dur, 43s) ease-in-out infinite alternate;
}

/* Stagger the start so three layers don't set off together on first
   paint and spend the first minute in lockstep before their co-prime
   periods pull them apart. Negative delays start each one already
   part-way through its cycle. */
.amb-layer:nth-child(2) { animation-delay: -19s, -7s; }
.amb-layer:nth-child(3) { animation-delay: -41s, -23s; }

/* ------------------------------------------------------- reduced motion */

/* app/ambience.js already refuses to mount any of this when the OS asks
   for reduced motion. This is the second lock: the preference can change
   while a deck is being projected, and a media query re-evaluates live
   where a JS check made at mount time does not. */
@media (prefers-reduced-motion: reduce) {
  .stage-backdrop.is-drifting,
  .sp-backdrop.is-drifting,
  .amb-stack,
  .amb-layer {
    animation: none !important;
  }
  .stage-backdrop.is-drifting,
  .sp-backdrop.is-drifting { inset: 0; }
}
