/* =============================================================================
 * Lumen loader ,  a rotating buffer ring and a walking cat.
 * ===========================================================================
 * Two indicators, side by side: the ring is the app's long-standing "busy" arc
 * (the same shape as `.rpSpin` in the reader), and beside it a cat walks. The
 * ring was here first and stays ,  a cat alone is charming but a lone moving
 * sprite is a weaker "something is happening" signal than a spinner people have
 * read a thousand times. Both show together, so neither carries it alone.
 *
 * The cat's rig is Rachel Nabors': the sprite is twelve frames stacked
 * vertically, the box clips to exactly one frame, and `steps(12)` slides the
 * image up by the full height so the twelve frames play in turn.
 *
 *   sprite_catwalk.png is 2280px tall = 12 frames x 190px
 *   -> the box is 190px tall and the animation travels -2280px.
 *
 * CREDIT: Rachel Nabors' CSS Animations course. See
 * assets/vendor/nabors-cat/ATTRIBUTION.md.
 * =========================================================================== */

.lumen-loader{
  position:fixed;
  left:0;
  right:0;
  bottom:0;
  z-index:9998;
  display:flex;
  align-items:flex-end;
  gap:10px;
  padding:0 14px 8px;
  pointer-events:none;
}
.lumen-loader[hidden]{display:none}

/* ---- the clip window -------------------------------------------------------
   Exactly one frame tall, and the FULL WIDTH of the atlas.

   The width was the bug: this box was 190px wide because 190 is the frame
   height, but each frame is 400px across and the cat fills ~370px of it. The
   window was showing the left half of every frame, which is why the cat looked
   sliced in half with a hard vertical edge. Frame height and frame width are
   not the same number in a vertically-stacked sheet. */
.lumen-loader-cat{
  --frame-w:400px;             /* the atlas is this wide */
  --frame-h:190px;             /* one frame of the vertically-stacked atlas */
  --cat-scale:.34;             /* shrink to a corner, not a billboard */
  position:relative;
  width:calc(var(--frame-w) * var(--cat-scale));
  height:calc(var(--frame-h) * var(--cat-scale));
  overflow:hidden;
  flex:none;
  image-rendering:pixelated;
}

/* The image itself: full size, inside a box scaled down around it. Scaling the
   box rather than the image is what keeps the steps maths intact ,  the travel
   stays a whole number of frames at any size. */
.lumen-loader-cat::before{
  content:'';
  position:absolute;
  left:0;
  top:0;
  width:var(--frame-w);
  height:2280px;
  background-image:url('/assets/vendor/nabors-cat/sprite_catwalk.png');
  background-repeat:no-repeat;
  background-position:0 0;
  transform:scale(var(--cat-scale));
  transform-origin:top left;
  animation:lumen-walkCat 1.45s steps(12) infinite;
  /* A flat silhouette, in one ink, for the whole app: black on a light theme,
     white on a dark one.

     `--loader-invert` is emitted by scripts/build-themes.mjs from each theme's
     own `scheme` field ,  0 for light, 1 for dark ,  so this stays correct for a
     theme that does not exist yet and never has to name one, and it cannot drift
     out of step with the scheme because there is only one source.

     `brightness(0)` collapses every coloured pixel in the sprite to black while
     leaving alpha alone, so the cat's outline survives and its interior
     detailing does not ,  a single-ink mark that reads at 136px instead of a
     small picture nobody can make out. `invert(1)` then flips that black to
     white where the background is dark. */
  filter:brightness(0) invert(var(--loader-invert,0));
  -webkit-filter:brightness(0) invert(var(--loader-invert,0));
}
/* There used to be a `[data-skin]` override here that lightened the art on the
   two darkest aesthetics. It is gone because it was the same recolour problem
   one level down: `--loader-invert` is already derived from `scheme`, so any
   rule naming a skin is a second opinion that can disagree with the first. A
   silhouette in one ink needs no per-theme adjustment to read. */

/* 100% of the cycle is -2280px: 12 frames x 190px. `steps(12)` ,  and the step-end
   default, so a frame is held rather than being the instant it is replaced. */
@keyframes lumen-walkCat{
  100%{transform:scale(var(--cat-scale)) translateY(-2280px)}
}

/* Reduced motion: no flipbook. The first frame sits in the box, so there is
   still an indicator and nothing moves. The script also skips the animation. */
@media (prefers-reduced-motion:reduce){
  .lumen-loader-cat::before{
    animation:none;
    transform:scale(var(--cat-scale));
  }
  /* Still an indicator, just a still one. */
  .lumen-loader-buffer{animation:none}
}

/* ---- the rotating buffer ring ------------------------------------------------
   Kept from before the walking cat. It is the same arc the reader uses for
   `.rpSpin`, lifted to a size that reads at the bottom of the screen and tinted
   from the theme so it sits with the cat instead of on top of it. Both run at
   once: the ring carries "busy", the cat carries personality. */
.lumen-loader-buffer{
  width:16px;
  height:16px;
  flex:none;
  /* Bottom-aligned, not centred: the container is as tall as the cat's frame, so
     centring left the ring floating 24px above the ground the cat walks on. On
     the same baseline the two read as one strip instead of two stacked widgets. */
  align-self:flex-end;
  margin-bottom:3px;
  border:2px solid color-mix(in srgb,var(--accent) 28%,transparent);
  border-top-color:var(--accent);
  border-radius:50%;
  animation:lumen-bufferSpin .8s linear infinite;
}
@keyframes lumen-bufferSpin{
  to{transform:rotate(360deg)}
}
/* The restart hook play() toggles. One selector reaching the ring and the
   pseudo-element together, which inline styles on the parent cannot do. */
.lumen-loader.is-restarting .lumen-loader-buffer,
.lumen-loader.is-restarting .lumen-loader-cat::before{
  animation:none;
}

/* ---- why there is no label ----------------------------------------------------
   The strip used to carry a status line, then a rotating pool of a few hundred
   jokes. Both were removed on purpose. A label that changes every few seconds is
   something to read, and something to read is something that pulls the eye off
   the work it stands in for; and the jokes were being told at moments when the
   user was not in the mood for one. The ring says "busy" in the app's own
   language and the cat says it is a cat. A screen reader is told "Working" by
   the container's aria-label, which is the one place a sentence is actually
   needed. */

/* Very small screens: the cat alone is already wide, so it comes down a notch. */
@media (max-width:520px){
  .lumen-loader-cat{--cat-scale:.26}
}