/* ==========================================================================
   SevIQ Motion Basics — self-contained SVG + CSS animation primitives.

   Decision record: SevIQ/docs/adr/0001-svg-css-motion-basics.md
   No JavaScript, no build step, no runtime deps. Drop-in for static Pages.

   Usage:
     1. <link rel="stylesheet" href="/assets/motion/motion.css">
     2. Wrap the host area in `.motion-scope` (provides tokens + reduced-motion).
     3. Add a primitive's markup (see /assets/motion/*.html demos).

   Theming:
     - Dark is the default (matches the live seviq.org dark site).
     - Add `.motion--light` on (or above) `.motion-scope` for the ivory/ink set.
   ========================================================================== */

/* ---- Token contract -------------------------------------------------------
   These are the only colours a primitive may reference. They mirror the
   field-journal palette in /lane.css so motion stays on-brand. */
.motion-scope {
  /* Palette. Accents, ink and line track the /lane.css tokens; muted is
     kept in step at 0.70; bg/surface/grid are motion-only additions. */
  --motion-bg:        #14110d;
  --motion-surface:   rgba(29, 24, 18, 0.85);
  --motion-ink:       #ece5d8;
  --motion-muted:     rgba(236, 229, 216, 0.70);
  --motion-line:      rgba(236, 229, 216, 0.84);
  --motion-grid:      rgba(236, 229, 216, 0.055);

  --motion-accent-1:  #d9a441;  /* gilt       — primary         (--accent) */
  --motion-accent-2:  #ece5d8;  /* bone       — secondary trace (--text)   */
  --motion-accent-3:  #8ab86f;  /* green      — "live"/success  (--ok)     */
  --motion-warn:      #e0b35c;  /* amber                        (--warn)   */
  --motion-danger:    #c9502e;  /* vermillion                   (--verm)   */

  /* Timing scale — named so every primitive shares a consistent, tunable feel.
     Each animation divides its duration by --motion-speed (0.5 = half speed). */
  --motion-speed: 1;
  --motion-dur-fast:     0.9s;   /* spinner                       */
  --motion-dur-base:     1.8s;   /* shimmer, ping ripple          */
  --motion-dur-slow:     2.6s;   /* current flow, lamp glow       */
  --motion-dur-breathe:  3.4s;   /* globe breathe, word pulse     */
  --motion-dur-ambient:  7s;     /* slow orbit loops              */
  --motion-ease-standard: cubic-bezier(.4, 0, .2, 1);
  --motion-ease-entrance: cubic-bezier(.2, .7, .2, 1);
  --motion-ease-exit:     cubic-bezier(.4, 0, 1, 1);

  color: var(--motion-ink);
}

/* Light theme: the field-journal paper palette (ivory / ink / vermillion). */
.motion-scope.motion--light,
.motion--light .motion-scope {
  --motion-bg:        #f2ede3;
  --motion-surface:   #faf7ee;
  --motion-ink:       #1a1714;
  --motion-muted:     rgba(26, 23, 20, 0.72);  /* AA 4.5:1 on ivory + panel */
  --motion-line:      rgba(26, 23, 20, 0.80);
  --motion-grid:      rgba(26, 23, 20, 0.06);

  --motion-accent-1:  #b02e1b;
  --motion-accent-2:  #1a1714;
  --motion-accent-3:  #4e7d3a;  /* darkened: state cues clear 3:1 on light */
  --motion-warn:      #9a6a12;
  --motion-danger:    #b02e1b;
}

/* ==========================================================================
   PRIMITIVE 1 — Globe loader  (.motion-globe)
   A breathing globe with two counter-rotating orbit rings and a pulsing
   wordmark. Category: loaders. Demo: loader-globe.html
   ========================================================================== */
.motion-globe {
  display: block;
  width: var(--motion-globe-size, 120px);
  height: var(--motion-globe-size, 120px);
  overflow: visible;
}
.motion-globe .mg-core {
  fill: var(--motion-accent-1);
  transform-box: fill-box;
  transform-origin: center;
  animation: motion-breathe calc(var(--motion-dur-breathe) / var(--motion-speed)) ease-in-out infinite;
}
.motion-globe .mg-grid {
  fill: none;
  stroke: var(--motion-accent-2);
  stroke-width: 1.4;
  opacity: 0.85;
}
.motion-globe .mg-orbit {
  fill: none;
  stroke: var(--motion-accent-2);
  stroke-width: 1.2;
  stroke-linecap: round;
  transform-box: fill-box;
  transform-origin: center;
}
.motion-globe .mg-orbit--cw {
  opacity: 0.55;
  animation: motion-orbit calc(var(--motion-dur-ambient) / var(--motion-speed)) linear infinite;
}
.motion-globe .mg-orbit--ccw {
  opacity: 0.40;
  animation: motion-orbit calc(var(--motion-dur-ambient) * 1.6 / var(--motion-speed)) linear infinite reverse;
}
.motion-globe .mg-sat {
  fill: var(--motion-accent-3);
  /* view-box origin so the satellite orbits the globe centre (60,60), not its
     own bounding box. */
  transform-box: view-box;
  transform-origin: 60px 60px;
  animation: motion-orbit calc(var(--motion-dur-ambient) / var(--motion-speed)) linear infinite;
}
.motion-globe .mg-word {
  fill: var(--motion-ink);
  font: 700 11px/1 ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  animation: motion-word-pulse calc(var(--motion-dur-breathe) / var(--motion-speed)) ease-in-out infinite;
}

/* ==========================================================================
   PRIMITIVE 2 — Flow primer  (.motion-flow)
   A battery → wire → lamp loop with current flowing as a dashed offset and the
   lamp glowing in time. Category: primers. Demo: primer-flow.html
   ========================================================================== */
.motion-flow {
  display: block;
  width: 100%;
  height: auto;
}
.motion-flow .mf-wire {
  fill: none;
  stroke: var(--motion-line);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.motion-flow .mf-node { fill: var(--motion-ink); }
.motion-flow .mf-label { fill: var(--motion-muted); font: 13px ui-sans-serif, system-ui, sans-serif; }
.motion-flow .mf-plate { stroke: var(--motion-ink); stroke-width: 3; stroke-linecap: round; }
.motion-flow .mf-pole  { fill: var(--motion-muted); font: 700 12px ui-sans-serif, system-ui, sans-serif; }
.motion-flow .mf-arrow { fill: var(--motion-accent-1); }
.motion-flow .mf-current {
  fill: none;
  stroke: var(--motion-accent-1);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 10 20;
  opacity: 0.9;
  animation: motion-dash-flow calc(var(--motion-dur-slow) / var(--motion-speed)) linear infinite;
}
.motion-flow .mf-lamp-glass {
  fill: var(--motion-accent-2);
  opacity: 0.16;
}
.motion-flow .mf-lamp-ring {
  fill: none;
  stroke: var(--motion-accent-2);
  stroke-width: 2;
}
.motion-flow .mf-lamp-halo {
  fill: var(--motion-warn);
  transform-box: fill-box;
  transform-origin: center;
  animation: motion-lamp-glow calc(var(--motion-dur-slow) / var(--motion-speed)) ease-in-out infinite;
}
.motion-flow .mf-lamp-filament {
  fill: none;
  stroke: var(--motion-warn);
  stroke-width: 2.2;
  stroke-linecap: round;
}

/* ==========================================================================
   PRIMITIVE 3 — Phasor primer  (.motion-phasor)
   Two co-rotating vectors (V reference + I lagging by a fixed phase φ) sweeping
   the unit circle — the thing a static phasor diagram can't show. The rotor
   group spins around the viewBox centre (120,120); V and I sit at a fixed angular
   offset inside it, so they rotate together holding φ. Category: primers.
   viewBox is 0 0 240 240. Reuses the motion-orbit keyframe.
   ========================================================================== */
.motion-phasor {
  display: block;
  width: 100%;
  height: auto;
  max-width: 280px;
  margin: 0 auto;
}
.motion-phasor .mp-frame line {
  stroke: var(--motion-grid);
  stroke-width: 1.5;
}
.motion-phasor .mp-frame circle {
  fill: none;
  stroke: var(--motion-line);
  stroke-width: 1.5;
  opacity: 0.22;
}
.motion-phasor .mp-rotor {
  transform-box: view-box;
  transform-origin: 120px 120px;
  /* reverse => counter-clockwise, the standard +ω phasor convention */
  animation: motion-orbit calc(var(--motion-dur-ambient) / var(--motion-speed)) linear infinite reverse;
}
.motion-phasor .mp-v {
  stroke: var(--motion-accent-1);
  stroke-width: 3;
  stroke-linecap: round;
}
.motion-phasor .mp-i {
  stroke: var(--motion-accent-2);
  stroke-width: 2.5;
  stroke-linecap: round;
}
.motion-phasor .mp-tip--v { fill: var(--motion-accent-1); }
.motion-phasor .mp-tip--i { fill: var(--motion-accent-2); }
.motion-phasor .mp-arc {
  fill: none;
  stroke: var(--motion-muted);
  stroke-width: 2;
}
.motion-phasor .mp-hub { fill: var(--motion-ink); }
.motion-phasor .mp-legend text { fill: var(--motion-muted); font: 600 12px ui-sans-serif, system-ui, sans-serif; }

/* ==========================================================================
   UTILITY PRIMITIVES — the seed of the wider catalog (see the ADR).
   Generic, markup-light helpers the next batch builds on.
   ========================================================================== */
/* Indeterminate ring spinner — loaders. */
.motion-spinner {
  width: var(--motion-spinner-size, 28px);
  height: var(--motion-spinner-size, 28px);
  border-radius: 50%;
  border: 3px solid var(--motion-grid);
  border-top-color: var(--motion-accent-2);
  animation: motion-spin calc(var(--motion-dur-fast) / var(--motion-speed)) linear infinite;
}
/* Skeleton shimmer — loading state for text/blocks. */
.motion-skeleton {
  position: relative;
  overflow: hidden;
  background: var(--motion-grid);
  border-radius: 6px;
}
.motion-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--motion-line) 50%, transparent);
  opacity: 0.18;
  animation: motion-shimmer calc(var(--motion-dur-base) / var(--motion-speed)) ease-in-out infinite;
}
/* Status dot — "live"/idle pulse. Ripple is a scaling pseudo-element (transform
   + opacity only) — NOT animated box-shadow, per the performance convention. */
.motion-pulse-dot {
  position: relative;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--motion-accent-3);
}
.motion-pulse-dot::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--motion-accent-3);
  transform-origin: center;
  animation: motion-ping calc(var(--motion-dur-base) / var(--motion-speed)) var(--motion-ease-exit) infinite;
}
/* Indeterminate progress bar — a segment sweeping across a track. */
.motion-progress {
  position: relative;
  width: 100%;
  height: 6px;
  border-radius: 3px;
  background: var(--motion-grid);
  overflow: hidden;
}
.motion-progress::before {
  content: "";
  position: absolute;
  inset: 0;
  width: 40%;
  border-radius: inherit;
  background: var(--motion-accent-2);
  animation: motion-progress-slide calc(var(--motion-dur-slow) / var(--motion-speed)) var(--motion-ease-standard) infinite;
}
/* "Thinking" dots. Markup: <span class="motion-dots"><i></i><i></i><i></i></span> */
.motion-dots { display: inline-flex; gap: 6px; }
.motion-dots i {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--motion-accent-1);
  animation: motion-dot-bounce calc(var(--motion-dur-base) / var(--motion-speed)) ease-in-out infinite;
}
.motion-dots i:nth-child(2) { animation-delay: 0.18s; }
.motion-dots i:nth-child(3) { animation-delay: 0.36s; }
/* Success check — a tick that draws itself in (one-shot). */
.motion-check .mk-ring { fill: none; stroke: var(--motion-accent-3); stroke-width: 2.5; opacity: 0.55; }
.motion-check .mk-tick {
  fill: none;
  stroke: var(--motion-accent-3);
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 30;
  stroke-dashoffset: 30;
  animation: motion-draw calc(var(--motion-dur-slow) / var(--motion-speed)) var(--motion-ease-entrance) forwards;
}
/* Error cross — two strokes drawing an X (one-shot). */
.motion-cross .mk-ring { fill: none; stroke: var(--motion-danger); stroke-width: 2.5; opacity: 0.55; }
.motion-cross .mk-stroke {
  fill: none;
  stroke: var(--motion-danger);
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  animation: motion-draw calc(var(--motion-dur-slow) / var(--motion-speed)) var(--motion-ease-entrance) forwards;
}
.motion-cross .mk-stroke:nth-of-type(2) { animation-delay: calc(var(--motion-dur-slow) / var(--motion-speed) / 2); }

/* ---- Keyframes ------------------------------------------------------------ */
@keyframes motion-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}
@keyframes motion-orbit {
  to { transform: rotate(360deg); }
}
@keyframes motion-word-pulse {
  0%, 100% { opacity: 0.72; }
  50%      { opacity: 1; }
}
@keyframes motion-dash-flow {
  to { stroke-dashoffset: -30; }
}
@keyframes motion-lamp-glow {
  0%, 100% { opacity: 0.25; transform: scale(0.94); }
  50%      { opacity: 0.85; transform: scale(1.06); }
}
@keyframes motion-spin {
  to { transform: rotate(360deg); }
}
@keyframes motion-shimmer {
  to { transform: translateX(100%); }
}
@keyframes motion-ping {
  0%        { opacity: 0.55; transform: scale(1); }
  70%, 100% { opacity: 0;    transform: scale(2.6); }
}
@keyframes motion-progress-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}
@keyframes motion-dot-bounce {
  0%, 80%, 100% { transform: translateY(0);    opacity: 0.5; }
  40%           { transform: translateY(-6px); opacity: 1; }
}
@keyframes motion-draw {
  to { stroke-dashoffset: 0; }
}

/* ---- Accessibility: respect reduced-motion --------------------------------
   Motion must STOP (not merely slow). Decorative primitives freeze to a
   composed static state; loaders keep meaning via their text label
   (role="status"), not movement.

   Coverage strategy: anything animated INSIDE a `.motion-scope` is caught
   automatically by the universal element + pseudo-element rules below — no
   per-prefix registration needed. The explicit lines after cover the utility
   primitives when used STANDALONE (outside a scope). */
@media (prefers-reduced-motion: reduce) {
  .motion-scope *,
  .motion-scope *::before,
  .motion-scope *::after,
  .motion-spinner,
  .motion-skeleton::after,
  .motion-pulse-dot,
  .motion-pulse-dot::before,
  .motion-progress::before,
  .motion-dots i,
  .motion-phasor .mp-rotor {
    animation: none !important;
  }
  /* One-shot draw cues must show their COMPLETED state, not their start frame. */
  .motion-check .mk-tick,
  .motion-cross .mk-stroke { stroke-dashoffset: 0 !important; }
}
