/* ============================================================================
   XARQ — keyframes and scroll-reveal states

   Everything here animates transform, opacity or filter only. Nothing animates
   a property that triggers layout, which is why the page holds 60fps while the
   WebGL hero is also drawing.
   ========================================================================== */

/* ------------------------------------------------------------- ambient loops */
@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(8vw, 10vh, 0) scale(1.18); }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-10vw, 14vh, 0) scale(0.92); }
}
@keyframes drift-c {
  from { transform: translate3d(0, 0, 0) scale(0.95); }
  to   { transform: translate3d(12vw, -12vh, 0) scale(1.2); }
}

/* Film grain. Stepped, not eased — a smoothly interpolated grain reads as a
   sliding texture instead of noise. */
@keyframes grain {
  0%   { transform: translate3d(0, 0, 0); }
  10%  { transform: translate3d(-5%, -10%, 0); }
  20%  { transform: translate3d(-15%, 5%, 0); }
  30%  { transform: translate3d(7%, -25%, 0); }
  40%  { transform: translate3d(-5%, 25%, 0); }
  50%  { transform: translate3d(-15%, 10%, 0); }
  60%  { transform: translate3d(15%, 0, 0); }
  70%  { transform: translate3d(0, 15%, 0); }
  80%  { transform: translate3d(3%, 35%, 0); }
  90%  { transform: translate3d(-10%, 10%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

@keyframes float-y {
  from { transform: translateY(-8px); }
  to   { transform: translateY(8px); }
}

/* The cursor light's idle state: a slow swell, so it reads as a live light
   source rather than a decal pinned to the pointer. Deliberately small — the
   hover state is what should be noticed. See .cursor in components.css. */
@keyframes cursor-breathe {
  from { scale: 0.94; }
  to   { scale: 1.06; }
}

@keyframes spin-slow {
  to { transform: rotate(360deg); }
}

@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-100%, 0, 0); }
}

@keyframes pulse-dot {
  0%   { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.55); }
  70%  { box-shadow: 0 0 0 9px rgba(34, 197, 94, 0); }
  100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
}

@keyframes scroll-hint {
  0%   { transform: scaleY(0); transform-origin: 50% 0; }
  45%  { transform: scaleY(1); transform-origin: 50% 0; }
  55%  { transform: scaleY(1); transform-origin: 50% 100%; }
  100% { transform: scaleY(0); transform-origin: 50% 100%; }
}

@keyframes load-bar {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* The preloader's self-dismiss. See .preload in sections.css — this is what
   makes the cover leave even when the JavaScript never runs. */
@keyframes preload-timeout {
  to {
    opacity: 0;
    visibility: hidden;
  }
}

/* ================================================================== reveals
   Declared as CSS state, not as inline styles written by GSAP. js/reveal.js
   only adds .is-in — so if the script fails, is-in is never added and the
   fallback below (.no-js / reduced motion) still shows the content. */
[data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 760ms var(--ease-out), transform 900ms var(--ease-out);
  transition-delay: var(--d, 0ms);
}
[data-reveal='fade'] {
  transform: none;
}
[data-reveal='left'] {
  transform: translateX(-32px);
}
[data-reveal='right'] {
  transform: translateX(32px);
}
[data-reveal='scale'] {
  transform: scale(0.94);
}
[data-reveal='blur'] {
  filter: blur(14px);
  transform: translateY(18px);
  transition-property: opacity, transform, filter;
}
[data-reveal].is-in {
  opacity: 1;
  transform: none;
  filter: none;
}

/* Line-clip headline reveal (see .line in sections.css). */
.line > span {
  transform: translateY(105%);
  transition: transform 1s var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.is-in .line > span,
.line.is-in > span {
  transform: none;
}

/* ------------------------------------------------------------- no-JS / a11y
   Two escape hatches. .no-js is on <html> until main.js removes it; reduced
   motion keeps the reveals but makes them instant rather than absent. */
html.no-js [data-reveal],
html.no-js .line > span {
  opacity: 1;
  transform: none;
  filter: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal],
  .line > span {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
  .bg-aurora span,
  .bg-noise,
  .art::before,
  .art__obj,
  .cursor > span,
  .vis-orbit__ring,
  .vis-orbit__core,
  .vis-idea__product,
  .next__tag i,
  .preload__mark,
  .hero__scroll i,
  .badge__dot {
    animation: none !important;
  }
  .marquee__track {
    animation-play-state: paused;
  }
}
