/* ==========================================================================
   WORKSPACE LOADER — THE CLUSTER
   --------------------------------------------------------------------------
   Six circles low and right of the veil: three carrying work (a still, a
   turning model, a playing clip) and three that are pure geometry, with a
   scatter of small figurines threaded between them. Each one arrives as boot
   progress reaches it, so the thing is still a progress readout — it just
   stopped looking like one.

   MOTION — why it is not a grid any more
   --------------------------------------------------------------------------
   The old version stepped eight cells around a ring on a shared 1250ms beat.
   Everything moved at once, to the same rhythm, between the same fixed
   stations, and that synchrony is exactly what made it read as a machine.

   Here nothing shares a clock. Every element floats on its own path with its
   own period — 8.3s, 11.2s, 9.4s, 13.1s, 10.6s, 12.4s — chosen so the cluster
   never visibly repeats while anyone is watching. `alternate` + `ease-in-out`
   means each one slows at the ends of its travel and never snaps, the way a
   thing suspended in water moves. The drifts are a few percent of the
   element's own size: enough that the composition is never quite the same
   twice, small enough that you read it as life rather than as animation.

   COST
   --------------------------------------------------------------------------
   Every moving property here is `transform` or `opacity` — composited, no
   layout, no paint. There is no JavaScript in the motion at all: JS reveals
   elements and attaches media, CSS does the rest. That matters because this
   is on screen while the main thread is parsing the workspace bundle.

   MEDIA
   --------------------------------------------------------------------------
   Nothing is on the critical path. Circles render as geometry on the first
   frame; the still, the turntable and the clip attach at idle, at low fetch
   priority, and only once they have decoded. Skipped entirely under Save-Data
   or 2G. See workspace-loader-grid.js.

   The turning model is a 24-frame sprite sheet pre-rendered offline from
   img/panda-web.glb — 91KB, stepped by CSS. No three.js, no WebGL, no GLB
   parse at boot. The clip is a 4s 360px cut made for this and nothing else:
   94KB against the 574KB of the shortest file in vid/.
   ========================================================================== */

/* IN FLOW, not fixed.
   --------------------------------------------------------------------------
   This was position:fixed and anchored to the viewport corner, which meant the
   grid column .workspace-loader__scene occupies collapsed to zero width while
   still claiming its fr share. The statement was being squeezed by a column
   that had nothing in it, and the cluster floated over the layout instead of
   belonging to it. In flow, the two columns finally describe what is actually
   on screen and `align-items: center` on the root aligns them for real. */
.workspace-loader .wsl-orbit {
  position: relative;
  z-index: 4;
  /* fills its column up to a ceiling, and never taller than the band the root
     leaves free — a square that overflows vertically is the one failure mode
     an aspect-ratio box has */
  width: min(100%, clamp(260px, 30vw, 500px), 62vh);
  aspect-ratio: 1;
  margin-inline-start: auto;      /* hug the right edge of the column */
  pointer-events: none;
  /* the whole cluster breathes once, very slowly, under everything else */
  animation: wslDrift 26s ease-in-out infinite alternate;
}
/* one soft pool of light under the whole cluster, so the circles read as lit
   from somewhere rather than pasted onto the veil */
.workspace-loader .wsl-orbit::before {
  content: "";
  position: absolute;
  inset: -18%;
  border-radius: 50%;
  background: radial-gradient(circle at 52% 46%,
    rgba(var(--accent-blue-rgb, 127, 200, 194), 0.055), transparent 62%);
  pointer-events: none;
}

@keyframes wslDrift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-1.4%, -2.2%, 0); }
}

/* ---- an item ------------------------------------------------------------
   Two nested boxes on purpose. The outer owns PLACEMENT and the float; the
   inner owns the REVEAL. If one element carried both, the reveal's transform
   and the float's would overwrite each other every frame. */
.workspace-loader .wsl-orbit__item {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--s);
  aspect-ratio: 1;
  animation: wslFloat var(--dur, 10s) ease-in-out var(--dly, 0s) infinite alternate;
  will-change: transform;
}
@keyframes wslFloat {
  from { transform: translate3d(0, 0, 0) rotate(0deg); }
  to   { transform: translate3d(var(--dx, 3%), var(--dy, -4%), 0) rotate(var(--rot, 0deg)); }
}

.workspace-loader .wsl-orbit__skin {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
  opacity: 0;
  transform: scale(0.86);
  filter: blur(6px);
  transition:
    opacity 1.1s cubic-bezier(0.22, 1, 0.36, 1),
    transform 1.25s cubic-bezier(0.22, 1, 0.36, 1),
    filter 0.9s ease;
}
/* arrived: progress has reached this one */
.workspace-loader .wsl-orbit__item.is-on .wsl-orbit__skin {
  opacity: 1;
  transform: scale(1);
  filter: blur(0);
}

/* No borders and no plates anywhere. What separates a circle from the veil is
   light coming off it, which is what a lit object actually does. */
.workspace-loader .wsl-orbit__item.is-on .wsl-orbit__skin::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 24px 60px -28px rgba(0, 0, 0, 0.85);
  pointer-events: none;
}

/* ---- media --------------------------------------------------------------- */
.workspace-loader .wsl-orbit__skin > img,
.workspace-loader .wsl-orbit__skin > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  opacity: 0;
  transition: opacity 0.9s ease;
}
.workspace-loader .wsl-orbit__item.has-media .wsl-orbit__skin > img,
.workspace-loader .wsl-orbit__item.has-media .wsl-orbit__skin > video {
  opacity: 0.94;
  /* the only saturated surface on the veil; pulled toward the palette so it
     reads as part of the cluster rather than a hole cut into it */
  filter: saturate(0.82) brightness(0.92) contrast(1.04);
}

/* a soft vignette so a bright thumbnail never out-punches the meter */
.workspace-loader .wsl-orbit--media .wsl-orbit__skin {
  background: radial-gradient(circle at 50% 42%,
    rgba(255, 255, 255, 0.035), rgba(7, 9, 10, 0.30) 78%);
}

/* ---- the turning model ---------------------------------------------------
   A 24-frame turntable, stepped. The sheet is 25 columns wide — 24 unique
   plus a repeat of the first — because at `background-size: 2500%` the value
   `background-position-x: 100%` resolves to exactly -(25-1) box widths, so
   steps(24) lands on frame boundaries with no drift. With 24 columns the
   arithmetic is off by 1/24 of a frame per step and the model visibly
   slides sideways as it turns. */
.workspace-loader .wsl-orbit__turntable {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  background-size: 2500% 100%;
  background-position: 0% center;
  opacity: 0;
  transition: opacity 0.9s ease;
}
.workspace-loader .wsl-orbit__item.has-media .wsl-orbit__turntable {
  opacity: 1;
  animation: wslTurn 4.2s steps(24) infinite;
}
@keyframes wslTurn {
  from { background-position-x: 0%; }
  to   { background-position-x: 100%; }
}
/* the model is cut out, so it needs a floor to stand on rather than a plate */
.workspace-loader .wsl-orbit--model .wsl-orbit__skin {
  overflow: visible;
  background: radial-gradient(ellipse 54% 26% at 50% 88%,
    rgba(var(--accent-blue-rgb, 127, 200, 194), 0.16), transparent 70%);
}

/* ==========================================================================
   THE GEOMETRY — three circles that carry no picture
   ========================================================================== */

/* 1. concentric hairlines, each ring turning against the next */
.workspace-loader .wsl-orbit--rings .wsl-orbit__skin { overflow: visible; }
.workspace-loader .wsl-orbit__ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid rgba(var(--accent-blue-rgb, 127, 200, 194), 0.44);
  /* a ring that fades out around its circumference reads as lit; a complete
     one reads as drawn */
  -webkit-mask-image: linear-gradient(200deg, #000 12%, rgba(0, 0, 0, 0.15) 62%, transparent 88%);
  mask-image: linear-gradient(200deg, #000 12%, rgba(0, 0, 0, 0.15) 62%, transparent 88%);
  animation: wslSpin 34s linear infinite;
}
.workspace-loader .wsl-orbit__ring:nth-child(2) {
  inset: 17%;
  border-color: rgba(var(--accent-blue-rgb, 127, 200, 194), 0.60);
  animation-duration: 23s;
  animation-direction: reverse;
}
.workspace-loader .wsl-orbit__ring:nth-child(3) {
  inset: 36%;
  border-color: rgba(255, 255, 255, 0.24);
  animation-duration: 47s;
}
@keyframes wslSpin { to { transform: rotate(360deg); } }

/* 2. a dashed ring — conic stripes cut to an annulus, so it stays one node */
.workspace-loader .wsl-orbit--dashes .wsl-orbit__skin { overflow: visible; }
.workspace-loader .wsl-orbit__dashes {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: repeating-conic-gradient(
    from 0deg,
    rgba(var(--accent-blue-rgb, 127, 200, 194), 0.52) 0deg 2.4deg,
    transparent 2.4deg 9deg);
  -webkit-mask: radial-gradient(closest-side, transparent 0 79%, #000 80%);
  mask: radial-gradient(closest-side, transparent 0 79%, #000 80%);
  animation: wslSpin 58s linear infinite;
}

/* 3. a soft orb carrying the workspace's own floor grid, so one circle is a
      window onto the ground rather than an object sitting on it */
.workspace-loader .wsl-orbit--orb .wsl-orbit__skin {
  background:
    linear-gradient(rgba(216, 168, 143, 0.30) 1px, transparent 1px) 0 0 / 22% 22%,
    linear-gradient(90deg, rgba(216, 168, 143, 0.26) 1px, transparent 1px) 0 0 / 22% 22%,
    radial-gradient(circle at 42% 34%,
      rgba(var(--accent-blue-rgb, 127, 200, 194), 0.20),
      rgba(var(--accent-blue-rgb, 127, 200, 194), 0.05) 46%,
      transparent 74%);
  /* closest-side inscribes the circle in the box, so the corners fall
     outside the last stop and clear completely */
  -webkit-mask-image: radial-gradient(circle closest-side, #000 0 58%, rgba(0, 0, 0, 0.45) 72%, transparent 82%);
  mask-image: radial-gradient(circle closest-side, #000 0 58%, rgba(0, 0, 0, 0.45) 72%, transparent 82%);
}
.workspace-loader .wsl-orbit--orb.is-on .wsl-orbit__skin {
  animation: wslBreathe 9.6s ease-in-out 1.3s infinite;
}
@keyframes wslBreathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.045); }
}

/* ---- the figurines ------------------------------------------------------
   Small, threaded between the six so the cluster has a middle ground and does
   not read as six things on a flat plane. Dimmer, and they carry no text. */
.workspace-loader .wsl-orbit--chip .wsl-orbit__skin {
  background: rgba(255, 255, 255, 0.02);
}
.workspace-loader .wsl-orbit--chip.is-on .wsl-orbit__skin { opacity: 0.7; }
.workspace-loader .wsl-orbit--chip .wsl-orbit__skin > img {
  filter: grayscale(0.4) brightness(0.8);
}

/* ---- exit ---------------------------------------------------------------- */
.workspace-loader.is-exiting .wsl-orbit {
  animation: wslOut 0.34s cubic-bezier(0.4, 0, 1, 1) forwards;
}
@keyframes wslOut {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(0.975); }
}

/* Stacked (see workspace-loader.css @940): the cluster is the only thing in
   its row, so the auto start-margin that pinned it to the outside edge of a
   two-column layout has to let go. */
@media (max-width: 940px) {
  .workspace-loader .wsl-orbit { margin-inline: auto; }
}

/* ---- room ----------------------------------------------------------------
   The cluster is the optional layer. On a short or narrow viewport the meter
   wins and this goes, rather than the two fighting over the same corner. */
@media (max-height: 640px), (max-width: 560px) {
  .workspace-loader .wsl-orbit { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .workspace-loader .wsl-orbit,
  .workspace-loader .wsl-orbit__item,
  .workspace-loader .wsl-orbit__ring,
  .workspace-loader .wsl-orbit__dashes,
  .workspace-loader .wsl-orbit--orb.is-on .wsl-orbit__skin,
  .workspace-loader .wsl-orbit__item.has-media .wsl-orbit__turntable {
    animation: none;
  }
  .workspace-loader .wsl-orbit__skin {
    transition: opacity 0.25s ease;
    transform: none;
    filter: none;
  }
}


/* WARM PALETTE (2026-08-18): belt-and-braces — the orbit reads the accent
   vars with teal fallbacks; pin them warm here too in case this sheet is
   ever used outside the .workspace-loader scope. */
.workspace-loader, .wsl-orbit {
  --accent-blue: #F54F1B;
  --accent-blue-rgb: 245, 79, 27;
}
