/* ==========================================================================
   ANIMATIONS
   All keyframes live here. Every animated component also has a
   prefers-reduced-motion fallback defined at its call site.
   ========================================================================== */

@keyframes blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* Slow ambient drift for .bg-texture (layout.css) — one full 40px grid
   tile every 50s, so it loops with no visible seam. */
@keyframes bg-drift {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 40px 40px, 40px 40px; }
}

/* ---------- Scroll-reveal ----------
   Elements marked [data-reveal] in the HTML start faded/offset and settle
   into place as they enter the viewport (see initScrollReveal in main.js,
   which adds [data-revealed] via IntersectionObserver).

   Entirely opt-in via prefers-reduced-motion: with reduced motion requested,
   this block never applies, so [data-reveal] elements are simply visible by
   default. Note this whole site renders its content via JS (see render.js),
   so there's no meaningful no-JS case to protect for visibility here — but
   initScrollReveal in main.js still carries a timeout fallback in case the
   observer itself misbehaves, so a bug there can't strand content hidden. */
@media (prefers-reduced-motion: no-preference) {
  [data-reveal] {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity var(--dur-slow) var(--ease-out),
                transform var(--dur-slow) var(--ease-out);
  }

  [data-reveal][data-revealed] {
    opacity: 1;
    transform: translateY(0);
  }
}
