/* ==========================================================================
   Unique Air Travel — Reusable Animation Utilities
   These classes are generic and page-agnostic. Add them to any element on
   any page; main.js wires up the scroll-trigger behavior globally.
   ========================================================================== */

/* ---- Scroll-reveal base states ----
   Elements start hidden/offset. main.js's IntersectionObserver adds
   `.is-visible` when the element enters the viewport, which triggers the
   transition defined on each variant below. */

.reveal-up,
.reveal-fade,
.reveal-left,
.reveal-right {
  opacity: 0;
  transition: opacity 600ms ease-out, transform 600ms ease-out;
  will-change: opacity, transform;
}

.reveal-up {
  transform: translateY(24px);
}

.reveal-fade {
  transform: translateY(0);
}

.reveal-left {
  transform: translateX(-32px);
}

.reveal-right {
  transform: translateX(32px);
}

.reveal-up.is-visible,
.reveal-fade.is-visible,
.reveal-left.is-visible,
.reveal-right.is-visible {
  opacity: 1;
  transform: translate(0, 0);
}

/* Stagger helper: apply alongside a reveal class + nth-child delay steps */
.reveal-stagger > * {
  transition-delay: calc(var(--stagger-index, 0) * 80ms);
}

/* Reveal-on-scroll for cards inside a horizontal scroll-snap carousel
   (e.g. .horizontal-scroll-track). Put THIS class on the scrolling wrapper
   itself, not on the individual cards: cards past the initially-visible
   width are clipped out by the carousel's own overflow-x, so
   IntersectionObserver correctly never reports them as intersecting and a
   per-card `.reveal-up` on them would never fire. Observing the wrapper
   instead reveals every card together once the carousel itself scrolls
   into the page's vertical viewport; give each card its own inline
   `transition-delay` for the staggered cascade. */
.horizontal-scroll-track.reveal-children-up > * {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 600ms ease-out, transform 600ms ease-out;
}

.horizontal-scroll-track.reveal-children-up.is-visible > * {
  opacity: 1;
  transform: translateY(0);
}

/* "Draw" a horizontal connector (e.g. the line between numbered steps):
   scales in from the left rather than fading, so it reads as being drawn. */
.reveal-draw {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 700ms ease-out;
}

.reveal-draw.is-visible {
  transform: scaleX(1);
}

/* ---- Fade animations (promo strip dismissal, generic use) ---- */
@keyframes fadeOutStrip {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
    height: 0;
    padding: 0;
    overflow: hidden;
  }
}

.animate-fade-out {
  animation: fadeOutStrip 350ms ease forwards;
}

/* ---- Success checkmark pop (newsletter, modal, forms) ---- */
@keyframes successPop {
  0% {
    opacity: 0;
    transform: scale(0.7);
  }
  60% {
    opacity: 1;
    transform: scale(1.08);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

.animate-success-pop {
  animation: successPop 450ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

/* ---- Modal entrance / exit (fade + scale) ---- */
@keyframes modalScaleIn {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes modalScaleOut {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0;
    transform: scale(0.95);
  }
}

.modal.fade .modal-dialog {
  transform: scale(0.95);
  transition: transform 250ms ease-out, opacity 250ms ease-out;
}

.modal.show .modal-dialog {
  transform: scale(1);
}

/* ---- Pulsing ring (floating call button) ---- */
@keyframes pulseRing {
  0% {
    box-shadow: 0 0 0 0 rgba(186, 167, 99, 0.55);
  }
  100% {
    box-shadow: 0 0 0 18px rgba(186, 167, 99, 0);
  }
}

.animate-pulse-ring {
  animation: pulseRing 2s infinite;
}

/* ---- Gentle scale pulse (discount badges, attention-grabbing pills) ----
   Distinct from .animate-pulse-ring above: that one's an expanding box-shadow
   ring meant for circular buttons; this is a subtle breathing scale for any
   flat badge/pill (e.g. offers.html's "SAVE 20%" discount badge). */
@keyframes pulseScale {
  0%, 100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.06);
  }
}

.animate-pulse-scale {
  animation: pulseScale 1.8s ease-in-out infinite;
}

/* ---- Cookie bar slide-up ---- */
@keyframes slideUpBar {
  from {
    transform: translateY(100%);
  }
  to {
    transform: translateY(0);
  }
}

/* ---- Generic fade-in (utility) ---- */
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.animate-fade-in {
  animation: fadeIn 400ms ease forwards;
}

/* ---- Spin (loading indicators, if ever needed) ---- */
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.animate-spin {
  animation: spin 900ms linear infinite;
}

/* ---- Overshoot bounce-in (icon rows, badges) ----
   Pairs with the same IntersectionObserver as .reveal-up — add class
   `.reveal-bounce` to any element and it animates in with a slight
   overshoot once scrolled into view. */
@keyframes bounceInOvershoot {
  0% {
    opacity: 0;
    transform: scale(0.6) translateY(20px);
  }
  60% {
    opacity: 1;
    transform: scale(1.08) translateY(-4px);
  }
  80% {
    transform: scale(0.97) translateY(1px);
  }
  100% {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

.reveal-bounce {
  opacity: 0;
}

.reveal-bounce.is-visible {
  animation: bounceInOvershoot 650ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

/* ---- Horizontal marquee loop (feature strip, logo walls) ----
   Wrap a doubled-content track in `.marquee-track` inside an
   `overflow:hidden` container; it scrolls left continuously and pauses
   on hover. Duplicate the track's children once in markup so the loop
   is seamless (see index.html usage). */
@keyframes marqueeScroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

.marquee-track {
  display: flex;
  width: max-content;
  animation: marqueeScroll 22s linear infinite;
}

.marquee-track:hover {
  animation-play-state: paused;
}

/* ---- Slow ambient zoom (hero background photos) ---- */
@keyframes ambientZoom {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(1.04);
  }
}

.animate-hero-zoom {
  animation: ambientZoom 20s ease-in-out infinite alternate;
}

/* ---- Shake (inline form validation, no alert()) ---- */
@keyframes shakeX {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(3px); }
}

.animate-shake {
  animation: shakeX 400ms ease-in-out;
}

/* ---- Dropdown fade + slide down (utility bar account menu) ---- */
@keyframes dropdownFadeSlide {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.dropdown-menu.show {
  animation: dropdownFadeSlide 200ms ease-out;
}

/* ---- Skeleton loading shimmer (placeholder result rows/cards) ----
   Generic loading-state utility: apply `.skeleton-shimmer` to any block
   that stands in for real content while waiting on a backend response
   (e.g. flights.html's #flight-results, before a real flight search API
   exists). A gradient sweeps left-to-right on a loop. */
@keyframes skeletonShimmer {
  0% {
    background-position: -400px 0;
  }
  100% {
    background-position: 400px 0;
  }
}

.skeleton-shimmer {
  background-image: linear-gradient(90deg, var(--color-light-bg) 25%, var(--color-border) 37%, var(--color-light-bg) 63%);
  background-size: 400px 100%;
  animation: skeletonShimmer 1.4s ease-in-out infinite;
}

/* ---- Reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
  .reveal-up,
  .reveal-fade,
  .reveal-left,
  .reveal-right,
  .reveal-bounce,
  .reveal-draw,
  .animate-pulse-ring,
  .animate-pulse-scale,
  .animate-fade-in,
  .animate-success-pop,
  .animate-hero-zoom,
  .marquee-track,
  .skeleton-shimmer {
    animation: none !important;
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}
