/* ============================================================================
   GARPI 2026 | Shared motion system
   Timing, easing and distance tokens plus the semantic animation vocabulary.
   Full narrative in MOTION_SYSTEM.md.

   Rules enforced here:
   - transform and opacity only; never animate layout properties
   - every utility has a reduced-motion equivalent at the foot of this file
   - ambient (looping) motion is opt-in and stops entirely under reduced motion
   ========================================================================== */

:root {
  /* -- Duration ----------------------------------------------------------- */
  --dur-instant: 90ms;
  --dur-fast: 160ms;
  --dur-base: 260ms;
  --dur-slow: 420ms;
  --dur-scene: 680ms;
  --dur-ambient: 5200ms;

  /* Per-direction tempo multiplier. Precision runs hot, Executive runs slow. */
  --tempo: 1;

  /* -- Easing -------------------------------------------------------------
     standard  general purpose, decelerating
     entrance  arrives with authority, no overshoot
     exit      leaves quickly, does not linger
     spring    tactile confirmation – selection only, never layout
     signal    symmetric, mechanical – telemetry and traces
     editorial long deceleration – ink settling onto paper                   */
  --ease-standard: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-entrance: cubic-bezier(0.16, 0.84, 0.28, 1);
  --ease-exit: cubic-bezier(0.5, 0, 0.75, 0);
  --ease-spring: cubic-bezier(0.34, 1.42, 0.5, 1);
  --ease-signal: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-editorial: cubic-bezier(0.22, 1, 0.36, 1);

  /* Scene easing is remapped per direction to give each its personality. */
  --ease-scene: var(--ease-entrance);

  /* -- Distance -----------------------------------------------------------
     Travel is small and purposeful. Nothing flies across the viewport.      */
  --dist-nudge: 4px;
  --dist-step: 12px;
  --dist-scene: 28px;

  /* -- Rhythm ------------------------------------------------------------- */
  --stagger: 45ms;

  /* -- Composed transitions ----------------------------------------------- */
  --t-control: transform calc(var(--dur-fast) * var(--tempo)) var(--ease-standard),
               border-color calc(var(--dur-fast) * var(--tempo)) var(--ease-standard),
               background-color calc(var(--dur-fast) * var(--tempo)) var(--ease-standard),
               color calc(var(--dur-fast) * var(--tempo)) var(--ease-standard),
               box-shadow calc(var(--dur-fast) * var(--tempo)) var(--ease-standard);
}

/* ----------------------------------------------------------------------------
   A note on fill-mode, because it caused a real defect.

   `animation-fill-mode: both` keeps the final keyframe applied for ever. Even
   when that keyframe says `transform: none`, the computed value stays a
   matrix – and an element with a transform creates a stacking context. Every
   animated wrapper therefore trapped its descendants' z-index permanently,
   which put the open region list *behind* the sticky action bar and made the
   control unusable with a mouse.

   Entrance animations do not need `forwards`: their end state is already the
   element's natural state. So they take `backwards` – the `from` keyframe
   applies during the delay, and once the animation is done the element simply
   returns to its own styles, transform and all. Only animations whose end
   state genuinely differs from the natural state (the exit, the selected
   marker) keep `both`.
   -------------------------------------------------------------------------- */

/* ============================================================================
   1 | Page entrance – the shell resolves before the benchmark begins
   ========================================================================== */

@keyframes m-shell-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes m-chrome-in {
  from { opacity: 0; transform: translateY(calc(var(--dist-step) * -1)); }
  to   { opacity: 1; transform: none; }
}

.m-shell {
  animation: m-shell-in calc(var(--dur-slow) * var(--tempo)) var(--ease-standard) backwards;
}

.m-chrome {
  animation: m-chrome-in calc(var(--dur-scene) * var(--tempo)) var(--ease-scene) backwards;
}

/* ============================================================================
   2 | Question entrance and exit – the choreography between screens
   ========================================================================== */

@keyframes m-question-in {
  from { opacity: 0; transform: translateY(var(--dist-scene)); }
  to   { opacity: 1; transform: none; }
}

@keyframes m-question-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(calc(var(--dist-step) * -1)); }
}

/* Backwards navigation reverses the spatial direction so "Back" reads as back. */
@keyframes m-question-in-reverse {
  from { opacity: 0; transform: translateY(calc(var(--dist-scene) * -1)); }
  to   { opacity: 1; transform: none; }
}

@keyframes m-question-out-reverse {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(var(--dist-step)); }
}

[data-motion="enter"] .m-scene {
  animation: m-question-in calc(var(--dur-scene) * var(--tempo)) var(--ease-scene) backwards;
}

[data-motion="exit"] .m-scene {
  animation: m-question-out calc(var(--dur-fast) * var(--tempo)) var(--ease-exit) both;
}

[data-motion="enter"][data-direction="back"] .m-scene {
  animation-name: m-question-in-reverse;
}

[data-motion="exit"][data-direction="back"] .m-scene {
  animation-name: m-question-out-reverse;
}

/* Staggered children – the question resolves top-down, evidence last. */
.m-stagger > * {
  animation: m-question-in calc(var(--dur-slow) * var(--tempo)) var(--ease-scene) backwards;
  animation-delay: calc(var(--stagger) * var(--i, 0));
}

[data-motion="exit"] .m-stagger > * {
  animation: none;
  opacity: 1;
  transform: none;
}

/* ============================================================================
   3 | Choice states – hover, focus, press, selected
   ========================================================================== */

@keyframes m-select-confirm {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.035); }
  100% { transform: scale(1); }
}

@keyframes m-check-draw {
  from { stroke-dashoffset: 24; opacity: 0; }
  30%  { opacity: 1; }
  to   { stroke-dashoffset: 0; opacity: 1; }
}

@keyframes m-marker-fill {
  from { transform: scale(0); }
  to   { transform: scale(1); }
}

.m-choice {
  transition: var(--t-control);
}

.m-choice:active {
  transform: translateY(1px) scale(0.995);
}

.m-choice[data-selected="true"] {
  animation: m-select-confirm calc(var(--dur-base) * var(--tempo)) var(--ease-spring);
}

.m-choice[data-selected="true"] .m-marker-dot {
  animation: m-marker-fill calc(var(--dur-fast) * var(--tempo)) var(--ease-spring) both;
}

.m-check-path {
  stroke-dasharray: 24;
}

.m-choice[data-selected="true"] .m-check-path {
  animation: m-check-draw calc(var(--dur-base) * var(--tempo)) var(--ease-standard) both;
}

/* ============================================================================
   4 | Progress – the benchmark advances, it never "fills up" like a game
   ========================================================================== */

.m-progress-fill {
  transform-origin: left center;
  transition: transform calc(var(--dur-slow) * var(--tempo)) var(--ease-standard);
}

@keyframes m-tick-arrive {
  from { transform: scale(0.4); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}

.m-progress-tick[data-state="done"] .m-tick-mark {
  animation: m-tick-arrive calc(var(--dur-base) * var(--tempo)) var(--ease-spring) both;
}

/* ============================================================================
   5 | Validation – calm, local, specific. No shake, no flash.
   ========================================================================== */

@keyframes m-notice-in {
  from { opacity: 0; transform: translateY(calc(var(--dist-nudge) * -1)); }
  to   { opacity: 1; transform: none; }
}

@keyframes m-rule-attention {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

.m-notice {
  animation: m-notice-in calc(var(--dur-base) * var(--tempo)) var(--ease-standard) backwards;
}

.m-notice::before {
  transform-origin: left center;
  animation: m-rule-attention calc(var(--dur-base) * var(--tempo)) var(--ease-standard) backwards;
}

/* ============================================================================
   6 | Semantic vocabulary – each concept expresses the meaning of its screen
   ========================================================================== */

/* 6.1 Asset performance – scattered signals stabilise and move into rhythm. */
@keyframes m-signal-stabilise {
  0%   { transform: translateY(var(--drift, 8px)); opacity: 0.25; }
  60%  { opacity: 1; }
  100% { transform: none; opacity: 1; }
}

/* 6.2 Reliability – fragments align into one continuous operating system. */
@keyframes m-fragment-align {
  from { transform: translate(var(--dx, 10px), var(--dy, -6px)) rotate(var(--dr, 4deg)); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* 6.3 Availability – uptime bands rise and illuminate from the baseline. */
@keyframes m-band-rise {
  from { transform: scaleY(0.04); opacity: 0.35; }
  to   { transform: scaleY(1); opacity: 1; }
}

/* 6.4 Governance – separate nodes connect into a controlled structure. */
@keyframes m-link-connect {
  from { stroke-dashoffset: var(--len, 120); }
  to   { stroke-dashoffset: 0; }
}

@keyframes m-node-settle {
  from { transform: scale(0.2); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}

/* 6.5 Maturity – isolated activity progresses into an integrated system. */
@keyframes m-rung-integrate {
  from { transform: translateX(calc(var(--dist-step) * -1)) scaleX(0.4); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* 6.6 Risk – an unstable trace resolves into a clearer pattern. */
@keyframes m-trace-resolve {
  from { stroke-dashoffset: var(--len, 400); opacity: 0.4; }
  to   { stroke-dashoffset: 0; opacity: 1; }
}

/* 6.7 Resilience – absorb a disturbance, return to equilibrium. */
@keyframes m-absorb-return {
  0%   { transform: translateY(0); }
  22%  { transform: translateY(calc(var(--dist-step) * 0.75)); }
  58%  { transform: translateY(calc(var(--dist-nudge) * -1)); }
  100% { transform: translateY(0); }
}

/* 6.8 Lifecycle value – value flows through connected asset stages. */
@keyframes m-value-flow {
  from { stroke-dashoffset: 0; }
  to   { stroke-dashoffset: -32; }
}

/* 6.9 Strategic outlook – pathways converge on the selected priority. */
@keyframes m-path-converge {
  from { stroke-dashoffset: var(--len, 200); opacity: 0; }
  60%  { opacity: 0.55; }
  to   { stroke-dashoffset: 0; opacity: 1; }
}

/* 6.10 Completion – evidence assembles into a refined benchmark profile. */
@keyframes m-assemble {
  from { transform: translate(var(--dx, 0), var(--dy, 14px)) scale(0.82); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

@keyframes m-seal-draw {
  from { stroke-dashoffset: var(--len, 300); }
  to   { stroke-dashoffset: 0; }
}

/* -- Applied classes ------------------------------------------------------ */
.m-stabilise  { animation: m-signal-stabilise calc(var(--dur-scene) * var(--tempo)) var(--ease-scene) backwards; }
.m-align      { animation: m-fragment-align calc(var(--dur-scene) * var(--tempo)) var(--ease-scene) backwards; }
.m-rise       { animation: m-band-rise calc(var(--dur-scene) * var(--tempo)) var(--ease-scene) backwards; transform-origin: bottom center; }
.m-connect    { animation: m-link-connect calc(var(--dur-scene) * var(--tempo)) var(--ease-signal) backwards; }
.m-node       { animation: m-node-settle calc(var(--dur-slow) * var(--tempo)) var(--ease-spring) backwards; }
.m-integrate  { animation: m-rung-integrate calc(var(--dur-slow) * var(--tempo)) var(--ease-scene) backwards; transform-origin: left center; }
.m-resolve    { animation: m-trace-resolve calc(var(--dur-scene) * var(--tempo)) var(--ease-signal) backwards; }
.m-converge   { animation: m-path-converge calc(var(--dur-scene) * var(--tempo)) var(--ease-signal) backwards; }
.m-assembleR  { animation: m-assemble calc(var(--dur-scene) * var(--tempo)) var(--ease-spring) backwards; }
.m-seal       { animation: m-seal-draw calc(var(--dur-scene) * var(--tempo)) var(--ease-editorial) backwards; }

/* Ordered delay for any semantic group. */
.m-seq > * { animation-delay: calc(var(--stagger) * var(--i, 0)); }

/* -- Ambient (looping) motion – opt-in, always subordinate ---------------- */
.m-ambient-flow { animation: m-value-flow var(--dur-ambient) linear infinite; }
.m-ambient-absorb { animation: m-absorb-return var(--dur-ambient) var(--ease-standard) infinite; }

/* ============================================================================
   7 | Completion sequence
   ========================================================================== */

@keyframes m-complete-lift {
  from { opacity: 0; transform: translateY(var(--dist-scene)) scale(0.97); }
  to   { opacity: 1; transform: none; }
}

.m-complete {
  animation: m-complete-lift calc(var(--dur-scene) * var(--tempo)) var(--ease-spring) backwards;
  animation-delay: calc(var(--stagger) * var(--i, 0));
}

/* ============================================================================
   8 | Reduced-motion equivalents
   Meaning is preserved through opacity and static end-state only. Nothing
   travels, nothing loops, nothing delays the respondent.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  :root {
    --dist-nudge: 0px;
    --dist-step: 0px;
    --dist-scene: 0px;
    --stagger: 0ms;
    --dur-scene: 140ms;
    --dur-slow: 140ms;
    --dur-base: 120ms;
    --dur-fast: 100ms;
    --tempo: 1;
  }

  /* Cross-fade replaces every spatial transition. */
  @keyframes m-question-in { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-question-out { from { opacity: 1; } to { opacity: 0; } }
  @keyframes m-question-in-reverse { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-question-out-reverse { from { opacity: 1; } to { opacity: 0; } }
  @keyframes m-chrome-in { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-complete-lift { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-notice-in { from { opacity: 0; } to { opacity: 1; } }

  /* Semantic concepts resolve instantly to their meaningful end state. */
  @keyframes m-signal-stabilise { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-fragment-align { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-band-rise { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-link-connect { from { stroke-dashoffset: 0; opacity: 0; } to { stroke-dashoffset: 0; opacity: 1; } }
  @keyframes m-node-settle { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-rung-integrate { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-trace-resolve { from { stroke-dashoffset: 0; opacity: 0; } to { stroke-dashoffset: 0; opacity: 1; } }
  @keyframes m-path-converge { from { stroke-dashoffset: 0; opacity: 0; } to { stroke-dashoffset: 0; opacity: 1; } }
  @keyframes m-assemble { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-seal-draw { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 0; } }
  @keyframes m-select-confirm { from { transform: none; } to { transform: none; } }
  @keyframes m-marker-fill { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-check-draw { from { stroke-dashoffset: 0; opacity: 0; } to { stroke-dashoffset: 0; opacity: 1; } }
  @keyframes m-tick-arrive { from { opacity: 0; } to { opacity: 1; } }
  @keyframes m-rule-attention { from { transform: none; } to { transform: none; } }

  /* Ambient motion is removed outright. */
  .m-ambient-flow,
  .m-ambient-absorb {
    animation: none !important;
  }

  .m-choice:active { transform: none; }

  .m-progress-fill { transition-duration: 120ms; }

  html { scroll-behavior: auto !important; }
}
