/* =============================================================
   BBI - Animations
   assets/css/animations.css

   Keyframes and transition utility classes. Authored once here
   and referenced by intro state markup, ticker, and any
   interactive element that fades / slides.

   prefers-reduced-motion is honoured by setting motion-token
   durations to 0ms in tokens.css. Everything below already
   reads those tokens, so reduced-motion users get instant state
   changes for free.
   ============================================================= */

/* -------------------------------------------------------------
   1. Intro state -> hero state (Home page)

   The intro overlay starts on top of everything. After the hold
   period, intro-animation.js measures the small header logo
   position and writes --logo-scale / --logo-tx / --logo-ty onto
   the big wordmark, then adds .is-zooming. CSS then morphs ONLY
   the big wordmark (FLIP-style) into the small-logo position
   while the rest of the intro chrome (topbar, photo, ticker,
   background) fades out, revealing the home page underneath.
   ------------------------------------------------------------- */

@keyframes bbi-intro-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes bbi-intro-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

@keyframes bbi-intro-photo-out {
  from {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  to {
    opacity: 0;
    transform: translateY(-12%) scale(0.96);
  }
}

@keyframes bbi-hero-fade-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Fallback shrink used when JS can't measure a small-logo target
   (no header rendered, hidden, dimensionless image, etc.). */
@keyframes bbi-intro-fallback-shrink {
  from {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
  to {
    opacity: 0;
    transform: scale(0.18) translateY(-30%);
  }
}

/* -------------------------------------------------------------
   2. Category intro state -> content state (Category page)
   Big typographic name zooms out instead of a logo, but the
   keyframes are functionally identical.
   ------------------------------------------------------------- */

@keyframes bbi-category-name-zoom-out {
  from {
    opacity: 1;
    transform: scale(1);
    letter-spacing: 0;
  }
  to {
    opacity: 0;
    transform: scale(0.3);
    letter-spacing: 0.2em;
  }
}

/* -------------------------------------------------------------
   3. Intro state classes - applied by JS
   ------------------------------------------------------------- */

.bbi-intro {
  position: fixed;
  inset: 0;
  z-index: var(--z-top);
  background-color: var(--color-background);
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}

.bbi-intro.is-active {
  opacity: 1;
  pointer-events: auto;
  animation: bbi-intro-fade-in 350ms var(--easing-standard) forwards;
}

/* While zooming out:
   - Background fades to transparent (delayed so the morph is on
     a white canvas for the first half of the animation, then the
     home page reveals through during the second half).
   - The whole overlay also receives pointer-events: none so the
     home page underneath becomes clickable as soon as the morph
     starts. */
.bbi-intro.is-zooming {
  pointer-events: none;
  background-color: transparent;
  transition: background-color
              calc(var(--intro-zoom-speed) * 0.6)
              var(--easing-standard)
              calc(var(--intro-zoom-speed) * 0.4);
}

/* Topbar text (MENU / CHARTS) lines up exactly with the real
   header underneath. Fading early keeps the transition seamless
   because the header below has identical typography in the same
   spot. */
.bbi-intro.is-zooming .bbi-intro__topbar {
  opacity: 0;
  transition: opacity calc(var(--intro-zoom-speed) * 0.4) var(--easing-standard);
}

/* Featured photo fades + lifts a touch so the eye is drawn up
   toward where the home hero will appear. */
.bbi-intro.is-zooming .bbi-intro__featured {
  animation: bbi-intro-photo-out var(--intro-zoom-speed) var(--easing-emphasised) forwards;
}

/* Ticker fades evenly. */
.bbi-intro.is-zooming .bbi-intro__ticker {
  opacity: 0;
  transition: opacity calc(var(--intro-zoom-speed) * 0.7) var(--easing-standard);
}

/* THE morph itself. CSS variables get written by JS; if they're
   missing we fall back to a centred shrink so the page never
   freezes with a stuck giant logo. */
.bbi-intro.is-zooming .bbi-intro__wordmark-img,
.bbi-intro.is-zooming .bbi-intro__wordmark {
  transition: transform var(--intro-zoom-speed) var(--easing-emphasised);
  transform: translate(var(--logo-tx, 0px), var(--logo-ty, -38vh))
             scale(var(--logo-scale, 0.15));
  transform-origin: center center;
  will-change: transform;
}

/* When JS fails to measure (no header on the page, image broke,
   etc.) intro-animation.js adds .bbi-intro--no-flip. We then run
   a simple shrink animation on the entire overlay so users still
   see SOMETHING animate and the page never gets stuck. */
.bbi-intro--no-flip.is-zooming {
  animation: bbi-intro-fallback-shrink var(--intro-zoom-speed) var(--easing-emphasised) forwards;
  transform-origin: 50% calc(var(--header-height) / 2);
}

.bbi-intro--no-flip.is-zooming .bbi-intro__wordmark-img,
.bbi-intro--no-flip.is-zooming .bbi-intro__wordmark {
  transition: none;
  transform: none;
}

/* Main content crossfades up while the intro morphs. Delayed by
   30% of the zoom duration so the first half is dominated by the
   logo morph and the second half by the reveal of the page below. */
.bbi-main {
  opacity: 0;
  pointer-events: none;
  transition: opacity
              calc(var(--intro-zoom-speed) * 0.7)
              var(--easing-standard)
              calc(var(--intro-zoom-speed) * 0.3);
}

.bbi-main.is-visible {
  opacity: 1;
  pointer-events: auto;
}

/* Category-specific intro big name. */
.bbi-category-intro-name {
  display: block;
  text-align: center;
}

.bbi-category-intro-name.is-zooming {
  animation: bbi-category-name-zoom-out var(--intro-zoom-speed) var(--easing-emphasised) forwards;
}

/* -------------------------------------------------------------
   4. Ticker scroll
   Two identical rows of items animate from 0 to -100% so the
   loop is seamless. Pause on hover for accessibility.
   ------------------------------------------------------------- */

@keyframes bbi-ticker-slide {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

.bbi-ticker__track {
  width: max-content;
  animation: bbi-ticker-slide var(--ticker-duration, 60s) linear infinite;
}

.bbi-ticker:hover .bbi-ticker__track,
.bbi-ticker:focus-within .bbi-ticker__track {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .bbi-ticker__track {
    animation: none;
  }
}

/* -------------------------------------------------------------
   5. Generic fade / slide-in (sections animating on viewport)
   ------------------------------------------------------------- */

@keyframes bbi-fade-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.bbi-fade-in {
  animation: bbi-fade-in var(--transition-slow) var(--easing-standard) both;
}

/* -------------------------------------------------------------
   6. Menu overlay open / close
   ------------------------------------------------------------- */

@keyframes bbi-menu-in {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes bbi-menu-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-8px); }
}

.bbi-menu-overlay.is-open  { animation: bbi-menu-in  var(--transition-base) var(--easing-standard) forwards; }
.bbi-menu-overlay.is-closing { animation: bbi-menu-out var(--transition-base) var(--easing-standard) forwards; }

/* -------------------------------------------------------------
   7. Dropdown reveal (author dropdown on article detail)
   ------------------------------------------------------------- */

@keyframes bbi-dropdown-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.bbi-dropdown {
  animation: bbi-dropdown-in var(--transition-fast) var(--easing-standard);
}

/* -------------------------------------------------------------
   8. Spinner (used by load-more while waiting on AJAX)
   ------------------------------------------------------------- */

@keyframes bbi-spinner {
  to { transform: rotate(360deg); }
}

.bbi-spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: bbi-spinner 700ms linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .bbi-spinner { animation-duration: 1800ms; }
}

/* -------------------------------------------------------------
   9. Reduced-motion safety net
   tokens.css already collapses durations; this kills any animation
   that bypasses them via hard-coded values.
   ------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration:  0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior:     auto !important;
  }
}
