/* Modern Animations & Effects */

/* Scroll-triggered animations */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes slideInLeft {
  from {
    opacity: 0;
    transform: translateX(-30px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideInRight {
  from {
    opacity: 0;
    transform: translateX(30px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes float {
  0%,
  100% {
    transform: translateY(0px);
  }
  50% {
    transform: translateY(-20px);
  }
}

/* Scroll reveal classes */
html.has-js [data-animate] {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: opacity, transform;
}

/* Directional variants */
html.has-js [data-animate='left'] {
  transform: translateX(-30px);
}

html.has-js [data-animate='right'] {
  transform: translateX(30px);
}

html.has-js [data-animate='scale'] {
  transform: scale(0.92);
}

html.has-js [data-animate].scroll-reveal {
  opacity: 1;
  transform: none;
}

/* animations.js adds .is-revealed once the reveal transition has settled. The
   compositor hint is only worth paying for while the reveal is in flight —
   held past that, every revealed element keeps a layer for an animation that
   already finished. */
html.has-js [data-animate].is-revealed {
  will-change: auto;
}

/* Failsafe. [data-animate] starts hidden and only animations.js brings it
   back, so a module that fails to load or parse would strand the page blank
   with nothing left to recover it. This restores the revealed state from CSS
   alone, one second behind the 3s safety net in animations.js — so when the
   module is alive it is always beaten to the punch and the reveal looks
   exactly as before, and a real reveal removes the animation before it can
   start. Deliberately not applied to .stagger-item: that class is added by
   animations.js itself, so a module that never runs leaves nothing hidden,
   and firing this on unstaggered items would reveal below-fold rows before
   the visitor ever scrolls to them. */
@keyframes revealFailsafe {
  to {
    opacity: 1;
    transform: none;
  }
}

html.has-js [data-animate]:not(.scroll-reveal) {
  animation: revealFailsafe 1ms linear 4s forwards;
}

/* The animated blue/teal hero wash lived here and is gone (2026-09-03). It was
   loaded after tokens.css and won the cascade on every hero, so the warm-paper
   canon this system is built on — --gradient-hero, "Warm Minimal Clubhouse" —
   never rendered at the one moment that sets identity. Two colors in it
   (rgba(93,173,226) and rgba(58,143,196)) belonged to no token and existed
   nowhere else. The class is removed from all eight page heroes; do not
   reintroduce a background here, because anything in this file outranks the
   token layer by load order alone. */

/* Floating elements */
.float-element {
  animation: float 6s ease-in-out infinite;
}

/* Stagger animation for card grids */
html.has-js .stagger-item {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}

html.has-js .stagger-item.is-staggered {
  opacity: 1;
  transform: none;
}

/* Reduce motion for accessibility.
   Scope note: this block owns the declarative half only. The timer-driven
   motion — the home hero's clock in assets/js/hero/, and the count-up — is out
   of a media query's reach, so those modules read the same query and gate
   themselves. (The rotating headline and the demo carousel used to be named
   here too; both went with the old hero, and .rotating-text and the carousel
   rules were deleted in OCC-1196.) The rule below is the catch-all for anything
   animated that they don't know about; the named rules after it say what we
   actually intend. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }

  /* The decorative loop in this file, stopped on purpose rather than left to
     the catch-all above. */
  .float-element {
    animation: none;
  }

  /* Keep opacity transitions (non-motion) but make them instant.
     No transform means no layer to promote, so don't ask for one. */
  html.has-js [data-animate] {
    opacity: 1 !important;
    transition: opacity 0.01ms !important;
    transform: none !important;
    will-change: auto;
  }

  html.has-js .stagger-item {
    opacity: 1 !important;
    transition: opacity 0.01ms !important;
    transform: none !important;
  }
}
