/* =====================================================================
   Core / loader.css
   ---------------------------------------------------------------------
   The one loading state on this site. Every page uses it — the home
   page, the interior pages, and the full-screen labs pieces — so
   arriving anywhere reads the same way.

   THE IDEA — the waterline, again. The mark sits dim in the middle of
   the dark and a single band of warm light rises through it from below,
   so the letters are only fully lit while the light is passing over
   them. It is the same event the home page's sea is built around: light
   crossing a surface, and the surface reading back. Nothing spins.

   THE PROGRESS RULE IS GONE, and with it the last thing on this plane
   that was not the mark. It was honest — it tracked real work through
   VALLoader.track() — but it was a second object competing with the
   letters, and it answered a question nobody was asking: on a static
   site with no build step, the wait is a second, and a visitor watching
   a bar fill for a second is a visitor being shown a bar.

   What took its job is the thing that was behind the plane all along.
   core/glass.js now holds the sheet SEALED — a thick lens, closed over
   the page — until this plane starts to clear, and surfaces it as the
   plane goes. So the background of the loading state is the page
   transition itself, mid-gesture, and the plane over it is a scrim
   rather than a wall: see the alpha in #vl-loader below. Nothing here
   has to know that; it only has to be see-through.

   Self-contained on purpose. This file names its own --vl-* tokens
   instead of borrowing site.css's or index.html's, because the whole
   point is that the loader is one thing everywhere rather than a copy
   per page — it must not shift when the surrounding stylesheet does.
   The values are the shared palette: --vl-void is site.css's --void-0,
   --vl-warm is the home shader's light source.
===================================================================== */

:root{
  --vl-void:   #04060a;
  --vl-warm:   #fff3e6;
  --vl-signal: #e2e6ea;
  --vl-dim:    rgba(226,238,246,.16);

  --vl-logo-h:    clamp(15px, 3.4vmin, 27px);
  --vl-mark-ratio: 1447 / 278;   /* assets/val-logo.svg's own bounding box, W:H */

  --vl-ease:    cubic-bezier(.15,.72,.2,1);
  --vl-sweep:   2.4s;   /* one pass of the light */
  --vl-dur-out: .58s;   /* plane fade on dismiss */

  /* HOW MUCH OF THE SHEET COMES THROUGH, on the pages that have one.
     0 would be a window, 1 a wall. Lower to see more of the seal
     opening behind the mark; raise it if the mark stops reading. */
  --vl-scrim:   .55;
}

/* The plane itself is only a frame for the mark — it paints nothing. The
   ground is .vl-plane below, so the two can be at different opacities:
   the ground steps back to let the sheet through, the mark does not. */
#vl-loader{
  position:fixed;inset:0;z-index:200;
  display:grid;place-items:center;
  opacity:1;
  transition:opacity var(--vl-dur-out) var(--vl-ease);
}

/* THE GROUND. The same radial the interior pages' veil uses — the sea's
   colour at distance, so the loader, the transition and the water are
   one continuous ground.

   Opaque by default, and only steps back to --vl-scrim once
   `body.has-glass` says there is a live sheet behind it. That
   distinction is doing real work rather than being cautious:

     · on a page with the sheet, what is behind this is the seal in the
       middle of opening (core/glass.js holds it shut until the plane
       starts to clear), which is the whole point of the loading state;
     · on a page without one — the labs pieces, or any machine where
       WebGL failed — what is behind it is the page, and Undercurrent in
       particular brings this plane BACK mid-session to cover a camera
       and a model loading. There it has to be a wall.

   One gradient at two opacities rather than two gradients, so the change
   when the sheet arrives is a cross-fade and not a pop. */
.vl-plane{
  position:absolute;inset:0;
  background:radial-gradient(130% 100% at 50% 60%,#0b111a 0%,#05080c 46%,var(--vl-void) 100%);
  transition:opacity var(--vl-dur-out) var(--vl-ease);
}
body.has-glass .vl-plane{opacity:var(--vl-scrim)}
#vl-loader.is-out{opacity:0}
#vl-loader.is-done{display:none}

.vl-core{
  position:relative;   /* over .vl-plane */
  display:flex;flex-direction:column;align-items:center;
  gap:calc(var(--vl-logo-h) * 1.6);
  transition:opacity var(--vl-dur-out) var(--vl-ease),
             transform var(--vl-dur-out) var(--vl-ease);
}
#vl-loader.is-out .vl-core{opacity:0;transform:translateY(-6px)}

/* ---------- the mark ---------------------------------------------------
   Two stacked copies of the same mask. The base copy is a flat dim fill;
   the lit copy paints a warm band through the identical shape, and moving
   that band's background-position is the whole animation.

   The mask is assets/val-logo.svg, the VAL wordmark — the same file the
   header mark (core/site.css) reads, painted a plain white with nothing
   conditional in it so it is unambiguous under either the alpha or the
   luminance interpretation a mask-image is allowed to pick. */
.vl-mark{position:relative;display:block;
  height:var(--vl-logo-h);width:calc(var(--vl-logo-h) * var(--vl-mark-ratio))}
.vl-mark > span{position:absolute;inset:0}

.vl-mark .mk{
  display:block;width:100%;height:100%;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
  -webkit-mask-image:url(/assets/val-logo.svg);mask-image:url(/assets/val-logo.svg);
}

.vl-mark .vl-base .mk{background:var(--vl-dim)}

/* The band is the middle of a gradient 3.2x taller than the glyph box, so
   at either end of its travel it sits entirely outside it and the mark
   reads as unlit. Scrolling background-position 0% -> 100% walks it up
   through it. */
.vl-mark .vl-lit .mk{
  background-image:linear-gradient(to top,
    rgba(255,243,230,0) 32%,
    var(--vl-warm) 48%, var(--vl-warm) 52%,
    rgba(255,243,230,0) 68%);
  background-size:100% 320%;
  background-position:0 0%;
  animation:vl-rise var(--vl-sweep) var(--vl-ease) infinite;
  filter:drop-shadow(0 0 7px rgba(255,243,230,.3));
}

@keyframes vl-rise{
  0%   {background-position:0 0%}
  70%  {background-position:0 100%}
  100% {background-position:0 100%}
}

/* Arrival. The light stops travelling and the whole mark holds lit for
   one beat before the plane clears, so the page is revealed by the same
   light that was reading the letters rather than by a fade that happens
   to end. This is a separate state from .is-out for exactly that reason:
   if the two ran together the lit frame would never be seen. */
#vl-loader.is-lit .vl-mark .vl-lit .mk{
  animation:none;background-image:none;background-color:var(--vl-warm);
  filter:drop-shadow(0 0 10px rgba(255,243,230,.34));
  transition:background-color var(--vl-dur-out) var(--vl-ease);
}

/* ---------- status line ------------------------------------------------
   Empty and hidden unless a page has something the visitor actually
   needs to know — the labs pieces use it to say which of the camera and
   the tracking model they are still waiting on.                         */
/* Taken out of the flow entirely while empty, not just made invisible:
   a hidden-but-present line still takes its height and the flex gap
   above it, which pushed the mark off the centre of the screen on every
   page that has nothing to say. And the whole point of this plane now
   is that it is the mark, centred, and nothing else. */
.vl-status:empty{display:none}
.vl-status{
  margin:0;min-height:1em;
  font:500 10px/1 'Syne', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  letter-spacing:.26em;text-transform:uppercase;
  color:rgba(226,238,246,.42);text-align:center;
  opacity:0;transition:opacity var(--vl-dur-out) var(--vl-ease);
}
.vl-status:not(:empty){opacity:1}

/* ---------- reduced motion ---------------------------------------------
   The light stops travelling and simply holds across the letters. The
   sheet behind the plane does not seal at all under this setting (see
   core/glass.js), so what is left is a mark on a dark ground, which is
   the honest minimum this state can be.                                 */
@media (prefers-reduced-motion: reduce){
  .vl-mark .vl-lit .mk{
    animation:none;background-image:none;
    background-color:var(--vl-signal);opacity:.62;filter:none;
  }
  #vl-loader,.vl-core{transition-duration:.2s}
  #vl-loader.is-out .vl-core{transform:none}
}
