/* ==========================================================================
   animations.css — Keyframes and scroll-reveal state
   Every animation is composited (transform / opacity / background-position
   only) so the compositor handles it and scrolling stays at 60fps.
   Durations are capped at --transition-slow (400ms); the longer times below
   belong to ambient loops (float, spin, gradientShift), not to UI feedback.
   ========================================================================== */

/* ==========================================================================
   Keyframes
   ========================================================================== */

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

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

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0.9);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

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

@keyframes slideInStaggered {
  from {
    opacity: 0;
    transform: translateY(24px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes gradientShift {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

@keyframes pulse {
  0%, 100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.55;
    transform: scale(1.25);
  }
}

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

@keyframes spin {
  to { transform: rotate(360deg); }
}

@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  40%      { transform: translateY(-6px); }
  60%      { transform: translateY(-2px); }
}

@keyframes ripple {
  to {
    transform: scale(2.6);
    opacity: 0;
  }
}

/* ==========================================================================
   Scroll reveal
   The .reveal class ships the element hidden; js/scroll-animations.js adds
   .is-visible via IntersectionObserver, and the stagger delay is set inline
   as a custom property (--reveal-delay) by the same module.
   ========================================================================== */

/* Reveals are opt-in, gated on the `js` class that main.js puts on <html>
   before first paint.

   Hiding .reveal unconditionally is what makes a JS failure catastrophic:
   every card, heading and paragraph below the hero ships at opacity 0 and
   only JavaScript can bring it back. If the module fails to parse, is
   blocked (file://, a CSP, an extension) or simply has not run yet, the
   page renders blank. Gating on .js means the content is visible by
   default and animation is the enhancement — never the other way round. */
/* No `will-change` here on purpose. It was previously set on all 29 reveal
   elements for the whole life of the page, which keeps a compositor layer
   alive for each one long after its animation has finished. The reveal is a
   one-shot opacity/transform animation and the compositor promotes it for
   the duration anyway. */
.js .reveal {
  opacity: 0;
}

.js .reveal.is-visible {
  animation: slideUp var(--transition-slow) var(--ease-out) forwards;
  animation-delay: var(--reveal-delay, 0ms);
}

.js .reveal--fade.is-visible {
  animation-name: fadeIn;
}

.js .reveal--scale.is-visible {
  animation-name: scaleIn;
}

.js .reveal--left.is-visible {
  animation-name: slideInLeft;
}

.js .reveal--stagger.is-visible {
  animation-name: slideInStaggered;
}

/* Hero content animates on load rather than on scroll — it is already
   in view, so waiting for an intersection would look broken. */
.js .animate-on-load {
  opacity: 0;
  animation: slideUp var(--transition-slow) var(--ease-out) forwards;
  animation-delay: var(--load-delay, 0ms);
}

/* Safety net: if the observer never fires (very old browser, or an error
   after the .js class lands), un-hide everything a few seconds in rather
   than leaving the page stranded at opacity 0. */
@media (scripting: enabled) {
  .js .reveal {
    animation: revealFallback 0s linear 4s forwards;
  }

  .js .reveal.is-visible {
    animation: slideUp var(--transition-slow) var(--ease-out) forwards;
    animation-delay: var(--reveal-delay, 0ms);
  }
}

@keyframes revealFallback {
  to { opacity: 1; }
}

/* ==========================================================================
   Hover & focus micro-interactions
   ========================================================================== */

.hover-lift {
  transition:
    transform var(--transition-base) var(--ease-spring),
    box-shadow var(--transition-base) var(--ease-out);
}

.hover-lift:hover {
  transform: translateY(-8px);
  box-shadow: var(--shadow-lg);
}

.hover-scale {
  transition: transform var(--transition-base) var(--ease-spring);
}

.hover-scale:hover {
  transform: scale(1.05);
}

.hover-bounce:hover {
  animation: bounce var(--transition-slow) var(--ease-out);
}

/* Animated underline for inline links */
.link-underline {
  position: relative;
}

.link-underline::after {
  content: "";
  position: absolute;
  inset-block-end: -2px;
  inset-inline-start: 0;
  width: 100%;
  height: 1.5px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--transition-base) var(--ease-out);
}

.link-underline:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* ==========================================================================
   Reduced motion
   Ambient loops are switched off entirely — a 40-second spin is exactly the
   kind of perpetual movement the setting exists to stop. Revealed content is
   forced visible so nothing is stranded at opacity 0.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .animate-on-load {
    opacity: 1 !important;
    animation: none !important;
    transform: none !important;
  }

  .hero__product,
  .hero__ring,
  .rule,
  .cta,
  .badge__dot,
  .hero__title .gradient-text {
    animation: none !important;
  }

  .hover-lift:hover,
  .hover-scale:hover,
  .card:hover,
  .pillar:hover,
  .testimonial:hover,
  .btn:hover {
    transform: none !important;
  }

  .ripple {
    display: none !important;
  }
}
