/* ==========================================================================
   UBM — motion.css
   Keyframes + animation utilities referenced by assets/js/motion.js and
   assets/js/illustrations.js.

   Load order: fonts.css → tokens.css → base.css → motion.css → sections.css → rtl.css

   Rules for this file:
   · No colour literals — everything is currentColor or a token. The one
     exception is a mask stencil, where only the alpha channel is read.
   · Every animation is opt-in by class or data-attribute.
   · The whole file collapses under prefers-reduced-motion at the bottom.

   On durations: tokens.css defines INTERACTION durations (--d-fast … --d-reveal,
   all sub-second) and those are used for every transition. Ambient loop periods
   — a 68-second orbital drift, a blinking link LED — are a different quantity
   and have no token. They live here as named, overridable custom properties so
   a page can retune one without touching this file, and every one of them is
   switched off under prefers-reduced-motion.
   ========================================================================== */

.marquee, .marquee__track, .hero__cue-line, .draw--auto,
.is-live, .anim-pulse, .anim-spin-slow, .anim-blink, .anim-flow, .skeleton {
  --loop-marquee: 40s;    /* fallback only; motion.js computes the real value */
  --loop-orbit:   68s;
  --loop-orbit-alt: 94s;
  --loop-pulse:   3.6s;
  --loop-ping:    2.4s;
  --loop-blink:   2.1s;
  --loop-flow:    3.2s;
  --loop-shimmer: 1.5s;
  --loop-draw:    1.4s;
  --loop-cue:     2.8s;
}

/* --------------------------------------------------------------------------
   1. KEYFRAMES
   -------------------------------------------------------------------------- */

/* Seamless horizontal marquee. motion.js clones the track so one full set is
   exactly 50% of the track width; rtl.css flips --marquee-to. */
@keyframes ubm-marquee {
  from { transform: translate3d(var(--marquee-from, 0%), 0, 0); }
  to   { transform: translate3d(var(--marquee-to, -50%), 0, 0); }
}

/* Soft breathing halo — orbit core, live status dots, chat presence. */
@keyframes ubm-pulse {
  0%, 100% { opacity: .28; transform: scale(1); }
  50%      { opacity: .72; transform: scale(1.14); }
}

/* Expanding ring — "the system is live" ping on a node or status dot. */
@keyframes ubm-ping {
  0%   { opacity: .55; transform: scale(.6); }
  70%  { opacity: 0;   transform: scale(2.3); }
  100% { opacity: 0;   transform: scale(2.3); }
}

/* Self-drawing stroke. Pair with stroke-dasharray:var(--len) on the path. */
@keyframes ubm-draw {
  from { stroke-dashoffset: var(--len, 1000); }
  to   { stroke-dashoffset: 0; }
}

/* A short dash travelling along a path — data moving through fibre.
   --flow-len must equal one full dash+gap period (--flow-dash) or the loop
   visibly jumps at the wrap. Default period: 26 + 240 = 266. */
@keyframes ubm-flow {
  from { stroke-dashoffset: var(--flow-len, 266); }
  to   { stroke-dashoffset: 0; }
}

/* Skeleton / loading shimmer. */
@keyframes ubm-shimmer {
  from { background-position-x: -140%; }
  to   { background-position-x: 240%; }
}

/* Link LED. Deliberately irregular so a rack of ports does not look synced. */
@keyframes ubm-blink {
  0%, 44%   { opacity: 1; }
  50%, 58%  { opacity: .12; }
  64%, 100% { opacity: 1; }
}

/* Slow orbital rotation for decorative SVG rings. */
@keyframes ubm-orbit-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Scroll cue — the hero's descending hairline. */
@keyframes ubm-scroll-cue {
  0%        { transform: scaleY(0); transform-origin: top; }
  45%       { transform: scaleY(1); transform-origin: top; }
  55%       { transform: scaleY(1); transform-origin: bottom; }
  100%      { transform: scaleY(0); transform-origin: bottom; }
}

/* Toast entry/exit. */
@keyframes ubm-toast-in {
  from { opacity: 0; transform: translate3d(0, 14px, 0) scale(.98); }
  to   { opacity: 1; transform: none; }
}
@keyframes ubm-toast-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translate3d(0, 8px, 0) scale(.98); }
}

/* --------------------------------------------------------------------------
   2. MARQUEE
   <div class="marquee" data-marquee data-marquee-speed="42">
     <div class="marquee__track">…</div>
   </div>
   -------------------------------------------------------------------------- */
.marquee {
  position: relative;
  overflow: hidden;
  /* Fade the rail into the surface at both ends. This is a mask stencil —
     only the alpha channel is read, so the colour keyword is opacity, not a
     design colour. rtl.css does not need to flip it: it is symmetrical. */
  mask-image: linear-gradient(to right, transparent, black 8%, black 92%, transparent);
}
.marquee__track {
  display: flex;
  align-items: center;
  gap: var(--marquee-gap, var(--sp-8));
  inline-size: max-content;
  will-change: transform;
}
[data-marquee-ready="true"] .marquee__track {
  animation: ubm-marquee var(--marquee-duration, var(--loop-marquee)) var(--e-linear) infinite;
}
.marquee:hover .marquee__track,
.marquee:focus-within .marquee__track,
[data-marquee-paused="true"] .marquee__track { animation-play-state: paused; }

/* --------------------------------------------------------------------------
   3. ANIMATION UTILITIES
   -------------------------------------------------------------------------- */

/* Live indicator: a dot with an expanding ring behind it. */
.is-live { position: relative; }
.is-live::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  border: 1px solid currentColor;
  animation: ubm-ping var(--loop-ping) var(--e-out) infinite;
  pointer-events: none;
}

.anim-pulse { animation: ubm-pulse var(--loop-pulse) var(--e-in-out) infinite; }
.anim-spin-slow { animation: ubm-orbit-spin var(--loop-orbit) var(--e-linear) infinite; transform-origin: 50% 50%; }
.anim-spin-slow--rev { animation-direction: reverse; animation-duration: var(--loop-orbit-alt); }

.anim-blink { animation: ubm-blink var(--loop-blink) steps(1, end) infinite; }
/* Coprime-ish periods so a rack of ports never blinks in unison */
.anim-blink:nth-of-type(2n) { animation-duration: calc(var(--loop-blink) * 0.69); animation-delay: calc(var(--loop-blink) * -0.19); }
.anim-blink:nth-of-type(3n) { animation-duration: calc(var(--loop-blink) * 1.38); animation-delay: calc(var(--loop-blink) * -0.52); }
.anim-blink:nth-of-type(5n) { animation-duration: calc(var(--loop-blink) * 0.50); animation-delay: calc(var(--loop-blink) * -0.36); }

.anim-flow {
  stroke-dasharray: var(--flow-dash, 26 240);
  animation: ubm-flow var(--flow-duration, var(--loop-flow)) var(--e-linear) infinite;
}

.skeleton {
  background-image: linear-gradient(
    100deg,
    var(--surface-sunken) 8%,
    color-mix(in srgb, var(--surface-sunken) 55%, var(--line)) 22%,
    var(--surface-sunken) 40%
  );
  background-size: 220% 100%;
  animation: ubm-shimmer var(--loop-shimmer) var(--e-linear) infinite;
  border-radius: var(--r-sm);
  color: transparent;
  user-select: none;
}

/* --------------------------------------------------------------------------
   4. SCROLL-DRAWN SVG — shared plumbing for illustrations.js
   Paths get style="--len:<pathLength>" and stroke-dashoffset driven by JS.
   -------------------------------------------------------------------------- */
.draw {
  stroke-dasharray: var(--len, 1000);
  stroke-dashoffset: var(--len, 1000);
}
.draw--done { stroke-dashoffset: 0; }
/* CSS-driven variant for cases with no scroll link (cards, hover states). */
.draw--auto {
  animation: ubm-draw var(--draw-duration, var(--loop-draw)) var(--e-out) forwards;
  animation-delay: var(--draw-delay, 0ms);
}

/* --------------------------------------------------------------------------
   5. HOVER MICRO-MOTION shared by cards and nav
   -------------------------------------------------------------------------- */
.hover-rise { transition: transform var(--d-base) var(--e-out), box-shadow var(--d-base) var(--e-out); }
.hover-rise:hover { transform: translate3d(0, -3px, 0); }

[data-magnetic] { transition: transform var(--d-slow) var(--e-out); }
[data-magnetic]:active { transition-duration: var(--d-fast); }

[data-tilt] { transition: transform var(--d-slow) var(--e-out); transform-style: preserve-3d; }

/* --------------------------------------------------------------------------
   6. REDUCED MOTION — hard stop.
   tokens.css already collapses the durations; this removes the loops.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .marquee { mask-image: none; overflow-x: auto; }
  .marquee__track { animation: none !important; inline-size: auto; flex-wrap: wrap; }

  .is-live::after,
  .anim-pulse, .anim-spin-slow, .anim-blink, .anim-flow,
  .skeleton, .draw--auto {
    animation: none !important;
  }
  .anim-flow { stroke-dasharray: none; }
  .draw { stroke-dashoffset: 0 !important; }

  [data-magnetic], [data-tilt] { transition: none !important; transform: none !important; }
}
