/* =============================================
   Shared Animation Definitions — UI-0102, UI-0106
   ============================================= */

/* =============================================
   Page Section Fade-In (UI-0102)
   Triggered by Intersection Observer in scroll-animate.js
   ============================================= */

/* Initial hidden state — applied to elements before they enter viewport */
.fade-in-up {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--duration-slow, 500ms) var(--ease-out),
              transform var(--duration-slow, 500ms) var(--ease-out);
}

/* Visible state — added by IntersectionObserver when threshold 0.1 is reached */
.fade-in-up.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Grid children stagger (100ms per item) */
.fade-in-up:nth-child(1)  { transition-delay: 0ms; }
.fade-in-up:nth-child(2)  { transition-delay: 100ms; }
.fade-in-up:nth-child(3)  { transition-delay: 200ms; }
.fade-in-up:nth-child(4)  { transition-delay: 300ms; }
.fade-in-up:nth-child(5)  { transition-delay: 400ms; }
.fade-in-up:nth-child(6)  { transition-delay: 500ms; }

/* Override delay when not in a stagger context */
.no-stagger.fade-in-up,
.no-stagger .fade-in-up {
  transition-delay: 0ms !important;
}

/* Fade only (no movement) */
.fade-in {
  opacity: 0;
  transition: opacity var(--duration-slow, 500ms) var(--ease-out);
}

.fade-in.is-visible {
  opacity: 1;
}

/* Fade + slide from left */
.fade-in-left {
  opacity: 0;
  transform: translateX(-20px);
  transition: opacity var(--duration-slow, 500ms) var(--ease-out),
              transform var(--duration-slow, 500ms) var(--ease-out);
}

.fade-in-left.is-visible {
  opacity: 1;
  transform: translateX(0);
}

/* Fade + slide from right */
.fade-in-right {
  opacity: 0;
  transform: translateX(20px);
  transition: opacity var(--duration-slow, 500ms) var(--ease-out),
              transform var(--duration-slow, 500ms) var(--ease-out);
}

.fade-in-right.is-visible {
  opacity: 1;
  transform: translateX(0);
}

/* Scale in */
.scale-in {
  opacity: 0;
  transform: scale(0.95);
  transition: opacity var(--duration-normal, 300ms) var(--ease-out),
              transform var(--duration-normal, 300ms) var(--ease-spring);
}

.scale-in.is-visible {
  opacity: 1;
  transform: scale(1);
}

/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .fade-in-up,
  .fade-in,
  .fade-in-left,
  .fade-in-right,
  .scale-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* =============================================
   Status Pulse Animation (UI-0106)
   8px green circle cycling opacity 1 → 0.5 → 1
   ============================================= */
.status-pulse {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--color-success, #10B981);
  animation: status-pulse 2s ease infinite;
  flex-shrink: 0;
}

.status-pulse--active {
  background-color: var(--color-success, #10B981);
}

.status-pulse--warning {
  background-color: var(--color-warning, #F59E0B);
}

.status-pulse--error {
  background-color: var(--color-error, #EF4444);
}

@keyframes status-pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.5; }
}

/* =============================================
   General Purpose Keyframes
   ============================================= */

/* Spin (used by spinner.css) */
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Bounce (subtle) */
@keyframes bounce-subtle {
  0%, 100% { transform: translateY(0); }
  50%       { transform: translateY(-4px); }
}

.bounce {
  animation: bounce-subtle 1.5s ease infinite;
}

/* Ping / Ripple (notification dot) */
@keyframes ping {
  75%, 100% {
    transform: scale(2);
    opacity: 0;
  }
}

.ping {
  position: relative;
}

.ping::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background-color: currentColor;
  animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
