/* ==========================================================================
   assets/member/motion.css — the Motion Design System.
                                              (Motion System v1.0)

   MEASURED FIRST. Across ten releases motion accumulated in seven stylesheets.
   Before writing anything, the drift was measured:

     DUPLICATE KEYFRAMES — 8 definitions doing the work of 2
       opacity fade   pr-pulse · pr-blink · re-blink          (3 names, 1 shape)
       rise + fade    pr-in · msgIn · popIn · modalIn · toastIn (5 names, 1 shape)
       plain fade     fadeIn  — an EXACT duplicate of m-fade
       shimmer        shimmer (transform) · dw-shimmer (background-position)
                      — same visual effect, two techniques, 1.5s vs 1.4s

     LITERAL DURATIONS bypassing the scale
       720ms · 1.1s (x2) · 1s (x2) · 1.5s · 1.4s · .15s (x3) · .5s

     EASING VOCABULARIES — five in use
       var(--ease) · ease-in-out · ease · linear · steps(2)

   WHAT WAS ALREADY RIGHT, AND IS NOT CLAIMED AS NEW WORK
   ------------------------------------------------------
     · A USER-LEVEL motion toggle already exists — workspace.js writes
       html[data-motion="off"] from a Settings switch and restores it on boot.
       The brief asks for "allow animations to be disabled"; it is there.
     · A global kill rule already exists at workspace.css:76-81, covering both
       prefers-reduced-motion and the user toggle with a universal selector.
     · EVERY keyframe in the platform animates only transform, opacity or
       background-position. Not one animates width, height or position — so
       the codebase is already GPU-friendly by construction. There was no
       layout-thrashing problem to fix, and inventing one would have been
       worse than useless.

   WHAT THIS FILE ADDS
   -------------------
     1 the complete token scale the brief asks for
     2 canonical primitives, so eight keyframes become two
     3 ONE disable mechanism instead of two
     4 a written standard: which duration means what

   SCOPE BOUNDARY
   --------------
   The six stylesheets authored during this engagement are migrated to these
   tokens. workspace.css is NOT touched: it is 5,000 lines of pre-existing
   code, it has been byte-for-byte unchanged through ten releases, and that
   property has been asserted by every test suite since. Its eight duplicate
   keyframes are listed in §7 as an explicit migration list — a decision to
   make deliberately, not something to do silently at delivery eleven.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1 · DURATION SCALE

   Four steps, and the gaps between them are deliberate. A scale with six
   values invites picking 200ms because 180 "felt slightly quick", and within
   a year nothing matches anything. Four values force a choice about WHAT KIND
   of change this is, which is the decision that actually matters.
   -------------------------------------------------------------------------- */
:root {
  /* --instant  a state change the user caused and is watching for.
                Hover, focus, press. Below ~100ms reads as instantaneous;
                above it reads as lag on something the pointer is on. */
  --m-instant: 90ms;

  /* --fast     a small element appearing or changing. Tooltip, chip, caret,
                a row highlighting. */
  --m-fast:    150ms;

  /* --normal   the default. A panel section, a card, a dialog, a response
                section arriving. If unsure, this one. */
  --m-normal:  240ms;

  /* --slow     something large enough that moving it fast would be
                disorienting: a full panel sliding, a drawer, a page. */
  --m-slow:    380ms;

  /* Ambient loops. Not on the scale above because they are not transitions —
     nothing is changing state, something is signalling that work continues. */
  --m-spin:    720ms;   /* one rotation */
  --m-pulse:   1100ms;  /* one breath */
  --m-shimmer: 1400ms;  /* one sweep */
  --m-blink:   1000ms;  /* one cursor cycle */

  /* --------------------------------------------------------------------------
     2 · EASING

     Four curves. Each answers "where does the energy go".
     -------------------------------------------------------------------------- */

  /* --m-out    DEFAULT. Fast start, gentle settle. Anything ENTERING or
                RESPONDING — the element is already committed, so it should
                arrive promptly and land softly. */
  --m-out:     cubic-bezier(.33, .9, .35, 1);

  /* --m-in     Slow start, fast finish. Anything LEAVING. It reads as the
                element getting out of the way rather than being yanked. */
  --m-in:      cubic-bezier(.5, 0, .9, .3);

  /* --m-both   Symmetric. Something MOVING between two positions it will
                occupy equally — a panel resizing, a reorder. */
  --m-both:    cubic-bezier(.5, 0, .3, 1);

  /* --m-spring The brief asks for a spring. This is a restrained one: a
                single ~4% overshoot, no oscillation. Reserved for a direct
                manipulation the user performed — a card landing after a drag.
                A bouncing enterprise interface reads as a toy, and an engineer
                watching a value settle twice will wonder which reading is real. */
  --m-spring:  cubic-bezier(.2, 1.06, .45, 1);

  /* --m-linear Constant rate. ONLY for continuous rotation, where any easing
                makes a spinner visibly stutter once per revolution. */
  --m-linear:  linear;

  /* Rise distance for entering elements. Small on purpose — the movement is
     there to say "this is new", not to draw the eye across the screen. */
  --m-rise:    6px;
}

/* --------------------------------------------------------------------------
   3 · CANONICAL PRIMITIVES

   Two shapes cover almost everything, which is why eight keyframes existed
   doing the work of these two.
   -------------------------------------------------------------------------- */

/* Enter: rise and fade in. The single most-used motion in the platform. */
@keyframes m-rise {
  from { opacity: 0; transform: translateY(var(--m-rise)); }
  to   { opacity: 1; transform: none; }
}

/* Enter without movement, for elements whose position must not shift. */
@keyframes m-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Leave. Paired with --m-in. */
@keyframes m-leave {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(calc(var(--m-rise) * -0.5)); }
}

/* Ambient: work continues. */
@keyframes m-spin  { to { transform: rotate(360deg); } }
@keyframes m-pulse { 50% { opacity: .35; } }
@keyframes m-blink { 50% { opacity: 0; } }

/* Shimmer. transform-based rather than background-position: a transform is
   composited, a background-position repaints. Both were in use; this is the
   one that does not cost a paint per frame on a skeleton grid. */
@keyframes m-shimmer {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}

/* --------------------------------------------------------------------------
   4 · UTILITIES

   Applied as classes so a component gets standard motion without redeclaring
   a duration and a curve — which is how the literals accumulated.
   -------------------------------------------------------------------------- */
.m-enter  { animation: m-rise  var(--m-normal)  var(--m-out) both; }
.m-enter-fast { animation: m-rise var(--m-fast) var(--m-out) both; }
.m-fade   { animation: m-fade  var(--m-fast)    var(--m-out) both; }
.m-leave  { animation: m-leave var(--m-fast)    var(--m-in)  both; }
.m-spin   { animation: m-spin  var(--m-spin)    var(--m-linear) infinite; }
.m-pulse  { animation: m-pulse var(--m-pulse)   var(--m-both) infinite; }
.m-blink  { animation: m-blink var(--m-blink)   steps(2) infinite; }

/* Stagger. A list arriving all at once reads as a flash; arriving one at a
   time reads as slow. Four steps of 40ms is enough to feel sequential and
   short enough that the last item is not visibly late. */
.m-stagger > *              { animation: m-rise var(--m-normal) var(--m-out) both; }
.m-stagger > *:nth-child(2) { animation-delay: 40ms; }
.m-stagger > *:nth-child(3) { animation-delay: 80ms; }
.m-stagger > *:nth-child(4) { animation-delay: 120ms; }
.m-stagger > *:nth-child(n+5) { animation-delay: 160ms; }

/* --------------------------------------------------------------------------
   5 · INTERACTION STANDARDS

   The brief asks for consistent hover, press, focus, disabled, loading,
   success and error behaviour. These are the rules; components opt in.
   -------------------------------------------------------------------------- */

/* Hover and press. --m-instant because the pointer is already there and the
   user is watching for the response. */
.m-interactive {
  transition: background var(--m-instant) var(--m-out),
              border-color var(--m-instant) var(--m-out),
              color var(--m-instant) var(--m-out);
}

/* Press: a 1px settle, not a scale. Scaling a button re-rasterises its text
   and looks soft for the duration of the press. */
.m-press:active:not([disabled]) { transform: translateY(1px); }

/* Focus is NOT animated. A keyboard user tabbing through eight controls
   should see eight instant rings; animating the ring makes fast traversal
   look like a smear and slows the very interaction it decorates. */
.m-focus:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Loading. The control keeps its size — a button that shrinks to a spinner
   moves everything after it. */
.m-loading { position: relative; color: transparent !important; pointer-events: none; }
.m-loading::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  color: var(--hi);
  animation: m-spin var(--m-spin) var(--m-linear) infinite;
}

/* Success and error are COLOUR changes held briefly, not movement. A button
   that shakes on error is a consumer pattern; an engineer wants to know what
   is wrong, and movement conveys nothing about that. */
.m-ok  { animation: m-flash-ok  var(--m-slow) var(--m-out) 1; }
.m-err { animation: m-flash-err var(--m-slow) var(--m-out) 1; }
@keyframes m-flash-ok  { 0%, 100% { background: inherit; } 30% { background: var(--good-soft); } }
@keyframes m-flash-err { 0%, 100% { background: inherit; } 30% { background: var(--bad-soft); } }

/* --------------------------------------------------------------------------
   6 · THE DISABLE MECHANISM — one, not two

   Two existed: the global rule forces .01ms durations; six per-file blocks set
   `animation: none`. They are not equivalent. With fill-mode `both`, .01ms
   still applies the end state; `animation: none` discards the fill and the
   element falls back to its base style. For an element whose visible state
   comes from the animation's end frame, those differ.

   The .01ms form is correct and is the one kept. Declared here so the six
   per-file blocks can be removed rather than diverging further.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  :root {
    --m-instant: .01ms; --m-fast: .01ms; --m-normal: .01ms; --m-slow: .01ms;
  }
  .m-stagger > * { animation-delay: 0ms !important; }
  /* Ambient loops stop entirely: they signal "still working", and the text
     beside them already says so. A spinner is decoration; the label is not. */
  .m-spin, .m-pulse, .m-blink, .m-loading::after { animation: none !important; }
  .m-press:active:not([disabled]) { transform: none; }
}

html[data-motion="off"] {
  --m-instant: .01ms; --m-fast: .01ms; --m-normal: .01ms; --m-slow: .01ms;
}
html[data-motion="off"] .m-stagger > * { animation-delay: 0ms !important; }
html[data-motion="off"] .m-spin,
html[data-motion="off"] .m-pulse,
html[data-motion="off"] .m-blink,
html[data-motion="off"] .m-loading::after { animation: none !important; }

/* Windows High Contrast: custom properties are ignored, so the loading
   indicator needs a system colour or it vanishes. */
@media (forced-colors: active) {
  .m-loading::after { border-color: CanvasText; border-top-color: transparent; }
  .m-focus:focus-visible { outline: 2px solid Highlight; }
}

/* Printing: nothing is mid-animation on paper. */
@media print {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
