/* =========================================================
   DNUV Design System — Animations (R296 / W10 / U14)
   ---------------------------------------------------------
   Global animation contract:

   1. Canonical @keyframes — every popover / modal / toast /
      shimmer / pulse keyframe lives here. Component files
      reference these by name (no per-file @keyframes drift).

   2. Global prefers-reduced-motion rule — disables every
      transition + animation across the kit. Opacity-only fades
      (info, not motion) and the cam-status flip (state change,
      not decoration) are exempted explicitly below.

   3. Micro-interaction polish — button press, page route
      fade, notification badge nudge. Token-driven (no magic
      numbers).

   Load order: this file MUST come AFTER colors_and_type.css
   (so --ease-* / --dur-* tokens resolve) and AFTER the legacy
   inline <style> block in index.html (so its rules win).
   ========================================================= */

/* ---------- Canonical @keyframes ---------- */

/* Modal enter — fade-in + scale 0.96 → 1 + slight rise. */
@keyframes dnuv-modal-in {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* Modal exit — fade-out + scale 1 → 0.96. Mirrors modal-in. */
@keyframes dnuv-modal-out {
  from {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  to {
    opacity: 0;
    transform: translateY(4px) scale(0.96);
  }
}

/* Modal backdrop fade — alpha-only, plays alongside modal-in/out. */
@keyframes dnuv-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes dnuv-backdrop-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Toast enter — slide in from right + fade. */
@keyframes dnuv-toast-in {
  from {
    opacity: 0;
    transform: translateX(16px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* Toast exit — slide out to right + fade. */
@keyframes dnuv-toast-out {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(16px);
  }
}

/* Page transition — fade between routes. */
@keyframes dnuv-page-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Notification badge pulse — single nudge on count increment. */
@keyframes dnuv-badge-pulse {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* Notification critical pulse — persistent attention signal. */
@keyframes dnuv-badge-critical {
  0%, 100% { transform: scale(1);    opacity: 1; }
  50%      { transform: scale(1.08); opacity: 0.85; }
}

/* Canonical skeleton shimmer — referenced by .skel* classes in
   pages.css and index.html. Cheap (background-position only). */
@keyframes dnuv-skeleton-shimmer {
  0%   { background-position:  200% 0; }
  100% { background-position: -200% 0; }
}

/* ---------- Page transition ---------- */
/* Applied by app.js after route render. Lasts --dur-fast for snappy
   navigation. Skipped under reduced-motion (see global rule below). */
#route-root.dnuv-page-enter {
  animation: dnuv-page-in var(--dur-fast) var(--ease-out);
}

/* ---------- Camera status flip (R229 WS-driven) ----------
   The audit's P0-3 flagged that .cam-state and .cam-thumb.offline had no
   transition declared, so a live online→offline (or vice versa) flip was
   a hard color jump that looked like a re-render glitch. We add a soft
   color + background fade — short enough not to delay the user's
   perception of the new state, long enough to feel like a state change. */
.cam-state,
.cam-state .dot,
.cam-thumb {
  transition:
    color var(--dur-base) var(--ease-out),
    background-color var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}

/* ---------- Modal exit hook ---------- */
/* When modals.js sets the data-closing="true" attribute, the modal
   plays the exit animation before being torn down. */
.modal-backdrop.dnuv-closing {
  animation: dnuv-backdrop-out var(--dur-base) var(--ease-in) forwards;
}
.modal-backdrop.dnuv-closing .modal {
  animation: dnuv-modal-out var(--dur-base) var(--ease-in) forwards;
}

/* ---------- Notification badge ---------- */
/* Triggered by toggling .dnuv-pulse on the badge element from JS
   when the count increments. Auto-removes after one cycle. */
.dnuv-badge-pulse {
  animation: dnuv-badge-pulse var(--dur-base) var(--ease-spring);
  transform-origin: center;
  will-change: transform;
}

/* Persistent pulse for critical alerts — applied by setting
   data-priority="critical" on the bell icon. Infinite loop.
   R323 (W10-review P2-21) — Selector requires BOTH the `.dnuv-badge-critical`
   class AND `data-priority="critical"`. Two-step contract is intentional:
     - class flags the element as critical-pulse-CAPABLE
     - data-priority="critical" arms the animation
   Callers must stamp both. The companion `dnuv-badge-pulse` class
   handles single-cycle nudges and doesn't require the data-attribute. */
.dnuv-badge-critical[data-priority="critical"] {
  animation: dnuv-badge-critical 1.8s ease-in-out infinite;
  transform-origin: center;
}

/* ---------- Global prefers-reduced-motion ---------- */
/* WCAG 2.3.3 (Animation from Interactions, AAA) + 2.3.1 (Three
   Flashes, A) — respect the user's motion preference globally.

   Strategy: cap every animation + transition to a near-zero
   duration (0.01ms). Using !important guarantees we beat
   component-level rules. The near-zero duration (rather than
   `animation: none`) preserves layout — keyframes that toggle
   opacity 0→1 still complete their final frame so content
   doesn't end up invisible.

   Exemptions:
     - Opacity-only fades are kept (informational, not motion).
       Components opting back in declare `.dnuv-motion-allow-fade`.
     - The cam-status flip (R229) is a state change, not
       decoration — handled by an explicit transition on the
       .cam-state / .cam-thumb.offline selectors with its own
       opacity-only rule.

   Confetti / particle effects must use a static replacement
   under reduced-motion (already handled in W8-onboarding-finish). */
@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;
  }

  /* Persistent decorative pulses — disable outright instead of
     looping at 0.01ms (which would still flicker the GPU).
     Wave-E: dropped the dead `.ab-health-dot::after` selector — the appbar
     health pill (and its pulsing dot) was removed from the live Topbar in
     R396, so the rule targeted nothing in the served shell. */
  .cam-stage .live-chip .dot,
  .mosaic .tile .live-chip .dot,
  .fs-head .title .live-chip .dot,
  .dnuv-badge-critical[data-priority="critical"] {
    animation: none !important;
  }

  /* Skeleton shimmer — disable the gradient SWEEP (keyframe) but
     keep the gradient itself so the placeholder still reads as a
     loading state. R323 (W10-review P2-20) — Previously we set
     `background-image: none !important` which flattened the skeleton
     to a single background-color, making distinct skeleton shapes
     (text line vs circle vs thumb) lose their visual differentiation
     under reduced-motion. By only stopping the `animation` and not
     the gradient itself, the muted shimmer pattern is still painted
     (statically) and the user still sees the "this is loading"
     placeholder structure. */
  .skel,
  .skel-text,
  .skel-line,
  .skel-circle,
  .skel-thumb,
  .skel-row,
  .skel-card,
  .skeleton {
    animation: none !important;
  }

  /* Spinners — replace the rotation with a static, lower-amplitude
     dot pattern. Keep the spinner element visible (it still signals
     "loading") but stop the rotation that triggers vestibular issues. */
  .spin,
  .scan-status .spin,
  [class*="loader"] {
    animation: none !important;
  }

  /* Page transition — skip the fade. */
  #route-root.dnuv-page-enter {
    animation: none !important;
  }
}
