/* ════════════════════════════════════════════════════════
   MOTION.CSS — Langage de mouvement
   MateXP · Durées, courbes d'accélération, révélations au
   scroll et keyframes. Toutes les transitions du projet
   puisent leurs valeurs ici.
   ════════════════════════════════════════════════════════ */

:root {
  /* ─── Durées ─── */
  --dur-instant: 80ms;
  --dur-micro:   120ms;
  --dur-fast:    180ms;
  --dur-base:    260ms;
  --dur-slow:    400ms;
  --dur-enter:   540ms;
  --dur-scene:   800ms;

  /* ─── Courbes d'accélération ─── */
  --ease-linear: linear;
  --ease-out:    cubic-bezier(0.0, 0, 0.2, 1);
  --ease-in:     cubic-bezier(0.4, 0, 1, 1);
  --ease-inout:  cubic-bezier(0.4, 0, 0.2, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-expo:   cubic-bezier(0.16, 1, 0.3, 1);
  --ease-back:   cubic-bezier(0.36, 0, 0.66, -0.56);
}

/* ════════════════════════════════════════════════════════
   RÉVÉLATIONS AU SCROLL
   État initial caché → classe .visible posée par
   l'IntersectionObserver (theme.js).
   ════════════════════════════════════════════════════════ */
.reveal {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity   var(--dur-enter) var(--ease-expo),
    transform var(--dur-enter) var(--ease-expo);
  will-change: opacity, transform;
}
.reveal.visible {
  opacity: 1;
  transform: translateY(0);
}

/* Variantes directionnelles */
.reveal-left {
  transform: translateX(-20px) translateY(0);
}
.reveal-right {
  transform: translateX(20px) translateY(0);
}
.reveal-scale {
  transform: scale(0.96) translateY(0);
}
/* Tous reviennent à l'identité une fois visibles */
.reveal-left.visible,
.reveal-right.visible,
.reveal-scale.visible {
  transform: translateX(0) translateY(0) scale(1);
}

/* ─── Cascade : délais progressifs sur les enfants directs ─── */
.stagger > *:nth-child(1) { transition-delay: 40ms; }
.stagger > *:nth-child(2) { transition-delay: 80ms; }
.stagger > *:nth-child(3) { transition-delay: 120ms; }
.stagger > *:nth-child(4) { transition-delay: 160ms; }
.stagger > *:nth-child(5) { transition-delay: 200ms; }
.stagger > *:nth-child(6) { transition-delay: 240ms; }
.stagger > *:nth-child(7) { transition-delay: 280ms; }
.stagger > *:nth-child(8) { transition-delay: 320ms; }

/* ─── Délais locaux : enchaîner un .reveal APRÈS une cascade .stagger
   (même échelle 40ms — dN = N × 40ms). Délai SEUL : l'état visible et la
   sécurité reduced-motion restent portés par .reveal — aucune de ces
   classes n'est à ajouter à la kill-list. ─── */
.reveal-d1 { transition-delay: 40ms; }
.reveal-d2 { transition-delay: 80ms; }
.reveal-d3 { transition-delay: 120ms; }
.reveal-d4 { transition-delay: 160ms; }
.reveal-d5 { transition-delay: 200ms; }
.reveal-d6 { transition-delay: 240ms; }
.reveal-d7 { transition-delay: 280ms; }
.reveal-d8 { transition-delay: 320ms; }

/* ════════════════════════════════════════════════════════
   KEYFRAMES RÉUTILISABLES
   ════════════════════════════════════════════════════════ */

/* Pulsation — points de statut (pill, online dot) */
@keyframes pulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.5; transform: scale(0.85); }
}

/* Rebond doux — indicateur de scroll du hero */
@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(8px); }
}

/* Rotation — spinners de chargement */
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Apparition simple — toasts, éléments injectés en JS */
@keyframes fade-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Balayage lumineux — squelettes de chargement (skeletons) */
@keyframes shimmer {
  from { background-position: -200% 0; }
  to   { background-position:  200% 0; }
}

/* ─── Spinner utilitaire ─── */
.spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--border-2);
  border-top-color: var(--accent);
  border-radius: var(--r-full);
  animation: spin 0.7s var(--ease-linear) infinite;
}

/* ─── Squelette de chargement ─── */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--bg-2) 25%,
    var(--bg-3) 50%,
    var(--bg-2) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.4s var(--ease-linear) infinite;
  border-radius: var(--r-sm);
}

/* ════════════════════════════════════════════════════════
   RESPECT DE prefers-reduced-motion
   On neutralise tout mouvement mais on garde l'état final
   visible (pas de contenu bloqué en opacity:0).
   ════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* Sécurité : le contenu à révéler reste visible */
  .reveal,
  .reveal-left,
  .reveal-right,
  .reveal-scale {
    opacity: 1;
    transform: none;
  }
}
