/* ─────────────────────────────────────────────────────────────
   Hyprflow starfield — animated space background (asset)
   Originally adapted from Uiverse.io (amir_6539); the dot field
   is now generated at runtime by starfield.js.

   WHY IT WAS REBUILT
   The old version placed ~700 box-shadow dots inside a fixed
   2000x2000 box and translated it up by 2000px on a loop. Two
   faults came out of that:

     1. On a screen wider than 2000px the right-hand side was
        empty — every star bunched to the left.
     2. A seamless loop needs a SECOND copy of the field sitting
        one tile below, so that as the first scrolls off the top
        the second is already in place. That copy (#stars:after)
        carried no box-shadow, so at the end of each cycle the
        field vanished and snapped back — the visible jump.

   Now: starfield.js writes dots sized to the real viewport, and
   each layer holds two identical tiles. Translating the pair by
   exactly one tile height puts tile B where tile A began, so the
   seam is invisible and the drift never restarts.

   Usage: <div class="starfield"><div id="stars"></div>
          <div id="stars2"></div><div id="stars3"></div></div>
          <script src="/assets/starfield.js"></script>
   ───────────────────────────────────────────────────────────── */

.starfield{
  position:fixed; inset:0; z-index:0; overflow:hidden; pointer-events:none;
  /* brand blue-black backdrop */
  background:radial-gradient(ellipse at bottom, #0b1424 0%, #05070d 100%);
}
/* soft blue brand glow layered over the field */
.starfield::before{
  content:""; position:absolute; inset:0;
  background:radial-gradient(90% 70% at 82% 2%, rgba(0,72,235,.16) 0%, rgba(0,45,160,.06) 34%, transparent 62%);
}

/* One layer per depth, each holding two stacked tiles. The layer is what
   moves.

   `contain:strict` matters as much as will-change here: it tells the browser
   nothing inside can affect layout outside, so a resize or a browser zoom does
   not have to reconsider the hundreds of dots within. Without it, every zoom
   step re-lays-out and re-rasters the whole field and the page feels heavy. */
#stars,#stars2,#stars3{
  position:absolute; top:0; left:0; width:100%;
  will-change:transform;
  contain:strict;
  animation:animStar linear infinite;
}

/* A tile is just a positioning context for its dots. */
.stile{ position:absolute; left:0; width:100%; contain:layout style paint; }

/* The dots are painted into their tile, NOT promoted individually. Each one
   getting its own compositor layer is what made zooming feel slow — 250+
   promoted elements have to be re-rastered at every zoom step. They are plain
   painted spans now; only the three parent layers are promoted. */
.star{
  position:absolute; border-radius:50%; background:#cfe0ff;
  /* no box-shadow: a shadow on every dot means a blur pass per dot. The glow
     comes from the layer's own opacity and the dots' size variation instead. */
}

/* Depth. Nearer layers are brighter and faster, so they parallax against each
   other. Durations are long: this is ambient, and the old 60s pass was quick
   enough to read as movement rather than drift.

   The soft glow is a single drop-shadow on the LAYER — one blur pass for the
   whole field instead of one per dot. Cheap enough to survive a zoom. */
#stars  { opacity:.5;  animation-duration:190s;
          filter:drop-shadow(0 0 1.5px rgba(207,224,255,.5)) }
#stars2 { opacity:.36; animation-duration:290s }
#stars3 { opacity:.26; animation-duration:430s }

/* The whole trick: -50% of a two-tile layer is exactly one tile height, so
   tile B lands precisely where tile A started. Nothing shifts at the seam. */
@keyframes animStar{
  from{ transform:translate3d(0,0,0) }
  to  { transform:translate3d(0,-50%,0) }
}

/* Slow offset twinkle on a subset. Opacity only — composites, never lays out.
   starfield.js adds .tw and a random duration/delay. */
.star.tw{ animation:starTwinkle ease-in-out infinite alternate }
@keyframes starTwinkle{
  from{ opacity:.3 }
  to  { opacity:1 }
}

/* Motion here is decoration. If the OS asks for less, the field holds still —
   it still reads as a starfield, it just does not drift. */
@media (prefers-reduced-motion:reduce){
  #stars,#stars2,#stars3{ animation:none }
  .star.tw{ animation:none }
}
