/* ==========================================================================
   ANIMATION UTILITIES
   Scroll reveals are driven by IntersectionObserver in main.js toggling
   `.is-visible` on elements carrying `data-reveal`. Keeping the actual
   transition in CSS keeps it GPU-cheap and easy to tune per spring-physics
   guidance (transform + opacity only).
   ========================================================================== */

[data-reveal] {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.8s var(--ease-out-soft), transform 0.8s var(--ease-out-soft);
  will-change: transform, opacity;
}
[data-reveal].is-visible {
  opacity: 1;
  transform: translateY(0);
}

[data-reveal="left"] { transform: translateX(-36px); }
[data-reveal="left"].is-visible { transform: translateX(0); }

[data-reveal="right"] { transform: translateX(36px); }
[data-reveal="right"].is-visible { transform: translateX(0); }

[data-reveal="scale"] { transform: scale(0.92); }
[data-reveal="scale"].is-visible { transform: scale(1); }

[data-reveal="fade"] { transform: none; }

/* On narrow viewports, collapse horizontal slide-ins to a simple fade-up.
   Prevents any possibility of contributing to horizontal scroll/jank on
   touch devices, and reads better on mobile anyway. */
@media (max-width: 640px) {
  [data-reveal="left"],
  [data-reveal="right"] {
    transform: translateY(20px);
  }
  [data-reveal="left"].is-visible,
  [data-reveal="right"].is-visible {
    transform: translateY(0);
  }
}

/* stagger children via inline --d custom property set in JS, or data-delay attr in ms */
[data-reveal][data-delay] { transition-delay: var(--reveal-delay, 0s); }

@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* Hero text line reveal (masked slide) */
.line-reveal {
  overflow: hidden;
  display: block;
}
.line-reveal > span {
  display: inline-block;
  transform: translateY(110%);
  transition: transform 0.9s var(--ease-out-expo);
}
.line-reveal.is-visible > span {
  transform: translateY(0);
}

/* Fade Up keyframes for elements entering on load (hero) */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}
.fade-up-in {
  animation: fadeUp 0.8s var(--ease-out-soft) both;
}

/* Marquee (used for tech-stack / client strip) */
.marquee {
  overflow: hidden;
  position: relative;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee-track {
  display: flex;
  width: max-content;
  gap: var(--space-2xl);
  animation: marquee 28s linear infinite;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Magnetic elements — JS sets transform via inline style with spring easing;
   this just ensures a smooth base transition when JS isn't attached (touch) */
[data-magnetic] {
  transition: transform 0.3s var(--ease-out-soft);
}

/* Page transition overlay */
.page-transition {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 10001;
  transform-origin: bottom;
  transform: scaleY(0);
  pointer-events: none;
}
.page-transition.is-active {
  animation: page-wipe-in 0.5s var(--ease-in-out) forwards;
}
@keyframes page-wipe-in {
  from { transform: scaleY(0); }
  to { transform: scaleY(1); }
}

/* Skeleton shimmer for lazy images */
.img-skeleton {
  position: relative;
  background: var(--bg-raised-2);
  overflow: hidden;
}
.img-skeleton::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgba(255,255,255,0.06) 50%, transparent 70%);
  animation: shimmer 1.6s infinite;
}
@keyframes shimmer {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}
