/* ============================================================
   STELLARIA DESIGN SYSTEM · MOTION · v1.0.0
   ------------------------------------------------------------
   Canonical motion module. Keyframes, animation utilities,
   transition utilities, hover affordances, and a feature-
   detected scroll-driven block. One easing, three durations.
   Every kinetic effect is gated by `prefers-reduced-motion`.

   What's in here
     1.  Keyframes              — ds-* canonical motions
     2.  Animation utilities    — .anim-* wrappers
     3.  Transition utilities   — .transition-* micro-interactions
     4.  Hover lift             — .hover-lift (tint shift, 1px translate)
     5.  Scroll-driven block    — @supports (animation-timeline: ...)
     6.  Status dot patterns    — .dot--live consumes .anim-pulse
     7.  Reduced-motion override

   Import order
     <link rel="stylesheet" href="design-system/css/tokens.css">
     <link rel="stylesheet" href="design-system/css/base.css">
     <link rel="stylesheet" href="design-system/css/components.css">
     <link rel="stylesheet" href="design-system/css/motion.css">  /* this file */
     <link rel="stylesheet" href="design-system/css/utilities.css">

   Principles
     · Functional, not decorative. If removal isn't missed, don't ship.
     · No bounce, no overshoot, no spring physics.
     · Animate transform and opacity only.
     · Settle fast and land cleanly.
     · One easing (--ease-tactical) for everything that isn't linear.
   ============================================================ */


/* ============================================================
   1 · KEYFRAMES
   All names prefixed `ds-` to avoid collision with project-
   level animations. Each keyframe is a single, named, named
   function — composition (delay, stagger, fill-mode) lives on
   the utility class below.
   ============================================================ */

/* Reveal · simple opacity rise. The most common entrance. */
@keyframes ds-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Reveal · opacity + 8px upward translate. Default reveal for
   content that enters from below (cards, copy blocks, toasts
   anchored top). */
@keyframes ds-fade-up {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Reveal · mirror of fade-up. Use when a panel descends from
   above (dropdown menu, collapsing detail). */
@keyframes ds-fade-down {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Reveal · opacity + slight scale-up. Reserved for modal panels
   and confirmation surfaces. Scale delta is 0.02 — anything
   larger reads as a bounce. */
@keyframes ds-scale-in {
  from { opacity: 0; transform: scale(0.98); }
  to   { opacity: 1; transform: scale(1); }
}

/* Reveal · slide in from the right edge. Toasts, drawers anchored
   to a right rail. Translate is small so it reads as a settle. */
@keyframes ds-slide-in-right {
  from { opacity: 0; transform: translateX(8px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* Reveal · mirror of slide-in-right. Drawers anchored left,
   left-anchored notifications. */
@keyframes ds-slide-in-left {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* Indicator · opacity oscillation for live status dots. Pattern
   inherited from StellariaWebpageClaude/css/animations.css
   (`ticker-pulse`); simplified here to opacity-only so it
   composes with any sized dot without scaling artifacts. */
@keyframes ds-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.4; }
}

/* Indicator · skeleton shimmer. Slides a highlight gradient
   across a tinted surface to telegraph a loading state.
   Applied to `background-position` on a 200% wide gradient. */
@keyframes ds-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

/* Indicator · indeterminate spinner. Full rotation, linear curve.
   Reserved for indeterminate progress where curvature would lie. */
@keyframes ds-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Indicator · subtle opacity flicker for tactical stamps. The
   delta is tight on purpose — a stamp must remain readable
   throughout the cycle. */
@keyframes ds-stamp-flicker {
  0%, 100% { opacity: 1; }
  47%      { opacity: 0.86; }
  52%      { opacity: 1; }
  78%      { opacity: 0.94; }
}

/* Indicator · vertical scan bar. A 1px horizontal line traverses
   a container top → bottom. For scanning / processing surfaces.
   The element itself must be `position: absolute` inside a
   `position: relative` parent. */
@keyframes ds-scan-bar {
  from { transform: translateY(0); }
  to   { transform: translateY(100%); }
}

/* Indicator · indeterminate progress fill. A 30%-wide bar
   travels left → right inside the track. The wrapper must
   `overflow: hidden` and the bar must be 30% wide. */
@keyframes ds-progress-indeterminate {
  from { transform: translateX(-100%); }
  to   { transform: translateX(400%); }
}


/* ============================================================
   2 · ANIMATION UTILITY CLASSES
   Wrap each keyframe with the canonical duration + easing.
   Per-instance delay via `--delay` custom property.

   All non-infinite animations use:
     - duration: --dur-mid
     - easing:   --ease-tactical
     - fill:     both (so the from-state holds before delay
                       and the to-state holds after)
   ============================================================ */

.anim-fade-in {
  animation: ds-fade-in var(--dur-mid) var(--ease-tactical) var(--delay, 0s) both;
}

.anim-fade-up {
  animation: ds-fade-up var(--dur-mid) var(--ease-tactical) var(--delay, 0s) both;
}

.anim-fade-down {
  animation: ds-fade-down var(--dur-mid) var(--ease-tactical) var(--delay, 0s) both;
}

.anim-scale-in {
  animation: ds-scale-in var(--dur-mid) var(--ease-tactical) var(--delay, 0s) both;
}

.anim-slide-in-right {
  animation: ds-slide-in-right var(--dur-mid) var(--ease-tactical) var(--delay, 0s) both;
}

.anim-slide-in-left {
  animation: ds-slide-in-left var(--dur-mid) var(--ease-tactical) var(--delay, 0s) both;
}

/* Infinite indicators. No fill-mode (loops back to from). */
.anim-pulse {
  animation: ds-pulse 2s var(--ease-tactical) var(--delay, 0s) infinite;
}

.anim-shimmer {
  /* Consumers must paint a gradient onto background-image and
     set background-size: 200% 100%. The shimmer travels the
     existing gradient; the utility owns only the keyframe. */
  animation: ds-shimmer 1.6s var(--ease-linear) var(--delay, 0s) infinite;
  background-size: 200% 100%;
}

.anim-spin {
  animation: ds-spin 0.9s var(--ease-linear) var(--delay, 0s) infinite;
}

.anim-stamp-flicker {
  animation: ds-stamp-flicker 4s var(--ease-tactical) var(--delay, 0s) infinite;
}

.anim-scan-bar {
  animation: ds-scan-bar var(--dur-slow) var(--ease-linear) var(--delay, 0s) infinite;
}

.anim-progress-indeterminate {
  animation: ds-progress-indeterminate 1.4s var(--ease-tactical) var(--delay, 0s) infinite;
}


/* ============================================================
   3 · TRANSITION UTILITIES
   For instant micro-interactions on user input. Pair with
   :hover, :focus-visible, :active, or a class toggled by JS.

   Use the most specific class possible. .transition-all is a
   fallback for prototyping only — production should declare
   the property under change.
   ============================================================ */

.transition-color {
  transition:
    color           var(--dur-fast) var(--ease-tactical),
    background-color var(--dur-fast) var(--ease-tactical),
    border-color    var(--dur-fast) var(--ease-tactical),
    fill            var(--dur-fast) var(--ease-tactical),
    stroke          var(--dur-fast) var(--ease-tactical);
}

.transition-transform {
  transition: transform var(--dur-mid) var(--ease-tactical);
}

.transition-opacity {
  transition: opacity var(--dur-fast) var(--ease-tactical);
}

/* Fallback for prototyping only. Prefer the specific classes
   above — `transition: all` forces the browser to test every
   property on every change and can mask layout-thrashing
   transitions on width/height/top/left. */
.transition-all {
  transition: all var(--dur-fast) var(--ease-tactical);
}


/* ============================================================
   4 · HOVER LIFT
   The institutional hover affordance: not a shadow lift, not a
   scale, not a glow. A 1px upward translate paired with a tint
   step on the elevation ladder. Reads as "this is interactive"
   without performing.
   ============================================================ */

.hover-lift {
  transition:
    transform        var(--dur-fast) var(--ease-tactical),
    background-color var(--dur-fast) var(--ease-tactical);
}

.hover-lift:hover {
  transform: translateY(-1px);
  background: var(--c-surface-2);
}


/* ============================================================
   5 · SCROLL-DRIVEN ANIMATION
   Modern scroll-linked reveals via CSS-only animation-timeline.
   Reserved for the hero or one marquee per page — never on
   card grids. See SPECIFICATIONS.md §5.3 rule 5.

   These need both `animation-timeline: view()` and
   `animation-range: entry 0% entry 100%`. The block is feature-
   detected so older browsers see static content (no JS or GSAP
   fallback is provided in pure-CSS form — projects that need
   one ship it in their own JS).

   Why view() rather than scroll(): view() scopes the timeline
   to the element's own intersection with the scrollport, so
   each instance animates relative to its own position rather
   than the document. That makes the utilities composable on
   any element without coordinating ranges globally.
   ============================================================ */

@supports (animation-timeline: view()) {
  .scroll-fade-in {
    animation: ds-fade-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }

  .scroll-rise {
    animation: ds-fade-up linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
  }
}


/* ============================================================
   6 · STATUS DOT PATTERNS
   The dot styling itself (size, color, position) lives in
   components.css. Motion owns only the kinetic layer. The
   pulse utility is applied to the dot element directly:

     <span class="dot dot--live anim-pulse"></span>

   The class below is the canonical wiring so a project can
   apply `.dot--live` and inherit the pulse without remembering
   the utility.
   ============================================================ */

.dot--live {
  animation: ds-pulse 2s var(--ease-tactical) infinite;
}


/* ============================================================
   7 · REDUCED MOTION
   The contract: every kinetic effect freezes or fades to
   static. The blanket override below covers every duration
   token; the explicit `animation: none` list covers infinite
   indicators that would otherwise loop a single frame.

   Test path:
     · macOS  System Settings → Accessibility → Display → Reduce motion
     · Win 11 Settings → Accessibility → Visual effects → Animation effects
     · Linux  Per-DE; GNOME Settings → Accessibility → Reduce animation
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .anim-pulse,
  .anim-shimmer,
  .anim-spin,
  .anim-stamp-flicker,
  .anim-scan-bar,
  .anim-progress-indeterminate,
  .scroll-fade-in,
  .scroll-rise,
  .dot--live {
    animation: none !important;
  }

  .hover-lift:hover {
    transform: none;
  }
}
