/* ── signal-lock.css ────────────────────────────────────────────────────────
   First-load boot sequence. js/signal-lock.js builds the markup and gates it
   to once per browser session (sessionStorage) — every navigation after the
   first, this file's rules simply never get a matching element to apply to.

   The T° mark, not a generic instrument-panel motif: bar traces in → bar
   fills → (beat) → dot pops in → a bezel ring traces in around the whole
   mark → the dot ejects outward onto the ring's rim → the dot orbits the
   ring once, slow-fast-slow → the dot snaps back to its original resting
   position on the mark → hold → dismiss.

   Pure timed CSS sequence, not event-chained — every stage's timing is
   fixed and known up front (not tied to real asset/content load state), so
   a declarative animation-delay stack is more robust than an event chain
   with nothing external driving it. Chained via the same calc() building
   blocks throughout, so changing one token's duration keeps every later
   stage in sync.

   Timeline:
     0ms                                  bar outline starts tracing
     --dur-draw-sm (260ms)                bar fill starts whispering in
     +200ms   (460ms settled)
     +120ms gap (580ms)                   dot starts popping in
     +--dur-throw (770ms settled)
     (770ms)                              ring starts tracing
     +--dur-draw (1230ms settled)         dot starts ejecting onto the ring
     +--dur-throw (1420ms settled)        dot starts orbiting
     +--dur-orbit (2220ms settled)         dot starts snapping back
     +--dur-throw (2410ms settled)
     +150ms hold (2560ms)                 overlay starts fading out
     +--dur-draw (3020ms)                 fully gone

   The orbit-rotation move (mark-orbit / --ease-orbit) is a genuine fourth
   move beyond the sitewide three (stroke-draw, whisper-fade, terminal-
   readout wipe) — approved as a one-off scoped to this sequence only, not a
   reusable class. See ANIMATION-VOCAB.md. Everything else here is existing
   vocabulary: the bar's trace+fill is stroke-draw+whisper-fade, the dot's
   pop and eject reuse --ease-throw ("a control reporting its own state" —
   fitting, since this dot is about to become one).
   ───────────────────────────────────────────────────────────────────────── */

#signal-lock {
  position: fixed;
  inset: 0;
  z-index: 99999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ground);
  animation: sl-dismiss var(--dur-draw) ease-out
    calc(var(--dur-draw-sm) + 200ms + 120ms + var(--dur-throw) + var(--dur-draw) + var(--dur-throw) + var(--dur-orbit) + var(--dur-throw) + 150ms) forwards;
}

.sl-lockup {
  position: relative;
  width: 18rem;
  height: 18rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.sl-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }

/* 0 — bezel ring, drawn the same dasharray/dashoffset way the old radar
   circle was (dash length + the --sl-ring-len custom property are set
   inline by JS — they depend on the fixed RING_R constant). Full --dur-draw,
   not -sm: this is the "box perimeter" scale the token pair reserves that
   for, not a small element. */
.sl-ring {
  fill: none;
  stroke: var(--rule-strong);
  stroke-width: 10;
  animation: sl-ring-trace var(--dur-draw) var(--ease-reveal) calc(var(--dur-draw-sm) + 200ms + 120ms + var(--dur-throw)) forwards;
  /* stroke-dashoffset can't be composited — every frame is a real repaint,
     not a free GPU-only transform like the rest of this sequence. Cheap
     on a typical desktop display; the same repaint area gets redrawn at
     2-3x the pixel density on a high-DPI phone, which is the actual
     source of "clean on desktop, laggy on mobile" — not the animation
     logic itself. will-change lets the browser pre-allocate a layer for
     it instead of discovering mid-animation that it needs one. */
  will-change: stroke-dashoffset;
}

/* 1 — bar outline traces in: a stroke-only overlay copy of the bar path,
   same technique motion.js's stroke-draw overlay uses (dasharray/dashoffset
   set to the path's own measured length via JS, since this shape has no
   closed-form perimeter the way a circle does). The stroke is centered on
   the path, so it oversteps the fill's true edge by half its width at every
   corner — left alone that reads as a permanent dark fringe/bump once the
   fill lands underneath it, worst at the mark's tighter corners. It has to
   crossfade out as the fill fades in, not just sit there "forwards". */
.sl-bar-trace {
  fill: none;
  stroke: var(--fg);
  stroke-width: 8;
  stroke-linejoin: round;
  animation:
    sl-bar-trace var(--dur-draw-sm) var(--ease-reveal) forwards,
    sl-bar-trace-fade var(--dur-whisper) ease-out var(--dur-draw-sm) forwards;
  will-change: stroke-dashoffset; /* see .sl-ring's comment above */
}

/* 1b — bar fill whispers in underneath the outline once it's finished
   tracing. Whisper-fade, not --ease-reveal: a settle, not a construction. */
.sl-bar-fill {
  fill: var(--fg);
  opacity: 0;
  animation: sl-whisper var(--dur-whisper) ease-out var(--dur-draw-sm) forwards;
}

/* 2 — dot pops in at its native position, ejects onto the ring once it's
   drawn, then — after the orbit below completes — snaps back to that same
   native position, restoring the mark to its real resting appearance before
   dismiss. All three reuse --ease-throw: "a control reporting its own
   state" — apt for a dot popping in, flying out to a rail, and snapping
   home again. */
.sl-dot {
  fill: var(--fg);
  scale: 0;
  opacity: 0;
  animation:
    sl-dot-pop var(--dur-throw) var(--ease-throw) calc(var(--dur-draw-sm) + 200ms + 120ms) forwards,
    sl-dot-eject var(--dur-throw) var(--ease-throw) calc(var(--dur-draw-sm) + 200ms + 120ms + var(--dur-throw) + var(--dur-draw)) forwards,
    sl-dot-snap-back var(--dur-throw) var(--ease-throw) calc(var(--dur-draw-sm) + 200ms + 120ms + var(--dur-throw) + var(--dur-draw) + var(--dur-throw) + var(--dur-orbit)) forwards;
}

/* 3 — the dot's parent group orbits the ring once the dot has ejected onto
   it. Rotating the group (not the dot itself) sweeps the already-ejected
   dot through a full circle of radius RING_R around the mark's center,
   since the dot sits at that radius after its own translate above. */
.sl-orbit {
  animation: sl-mark-orbit var(--dur-orbit) var(--ease-orbit)
    calc(var(--dur-draw-sm) + 200ms + 120ms + var(--dur-throw) + var(--dur-draw) + var(--dur-throw)) forwards;
}

@keyframes sl-ring-trace { from { stroke-dashoffset: var(--sl-ring-len); } to { stroke-dashoffset: 0; } }
@keyframes sl-bar-trace      { from { stroke-dashoffset: var(--sl-bar-len); } to { stroke-dashoffset: 0; } }
@keyframes sl-bar-trace-fade { to { opacity: 0; } }
@keyframes sl-whisper        { to { opacity: 1; } }
@keyframes sl-dot-pop    { to { scale: 1; opacity: 1; } }
@keyframes sl-dot-eject     { to { translate: 108.0px -113.2px; } }
@keyframes sl-mark-orbit    { to { rotate: 360deg; } }
@keyframes sl-dot-snap-back { from { translate: 108.0px -113.2px; } to { translate: 0 0; } }
@keyframes sl-dismiss    { to { opacity: 0; visibility: hidden; } }

/* Static fallback: the mark's real resting appearance — bar filled, dot on
   its own native position (already snapped "back," there's nothing to eject
   or orbit), ring fully drawn around it. js/signal-lock.js gives the
   overlay a fixed short dismiss timer in this case (not the full animated
   sequence's timer — there's nothing to wait out) rather than skipping the
   moment entirely; a blank gap is the wrong failure mode here, not just
   fast motion. */
@media (prefers-reduced-motion: reduce) {
  #signal-lock { animation: none; }
  .sl-ring { animation: none; stroke-dashoffset: 0; }
  .sl-bar-trace { animation: none; stroke-dashoffset: 0; opacity: 0; }
  .sl-bar-fill { animation: none; opacity: 1; }
  .sl-dot { animation: none; scale: 1; opacity: 1; translate: 0 0; }
  .sl-orbit { animation: none; }
}

/* Mobile screens are near-universally high-DPI (2-3x), which is what makes
   the ring/bar-trace repaint (see .sl-ring's comment) actually costly there
   and not on a typical desktop display — same animation, more pixels to
   redraw per frame. Trade a little antialiasing smoothness on the stroked
   paths for a cheaper rasterization pass; invisible at this animation
   speed, but doesn't touch desktop at all. */
@media (max-width: 39.99em) {
  .sl-svg { shape-rendering: optimizeSpeed; }
}
