/*
 * animations.css — Motion & Transition Definitions
 * Teaching Portfolio — Dr. Christopher Teh Boon Sung
 *
 * Uses AOS (Animate On Scroll) from CDN for scroll-triggered reveals.
 * CSS-only animations for ambient effects.
 * Respects prefers-reduced-motion.
 */

/* ============================================================
   PAGE LOAD — Staggered fade-in
   Apply .stagger-in to a parent; children receive indexed delays
   ============================================================ */
.stagger-in > * {
  opacity: 0;
  transform: translateY(16px);
  animation: fade-up var(--transition-slow) forwards;
}

.stagger-in > *:nth-child(1)  { animation-delay: 0ms; }
.stagger-in > *:nth-child(2)  { animation-delay: 80ms; }
.stagger-in > *:nth-child(3)  { animation-delay: 160ms; }
.stagger-in > *:nth-child(4)  { animation-delay: 240ms; }
.stagger-in > *:nth-child(5)  { animation-delay: 320ms; }
.stagger-in > *:nth-child(6)  { animation-delay: 400ms; }
.stagger-in > *:nth-child(7)  { animation-delay: 480ms; }
.stagger-in > *:nth-child(8)  { animation-delay: 560ms; }
.stagger-in > *:nth-child(n+9){ animation-delay: 640ms; }

@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ============================================================
   GRID DRIFT — Slowly drifting graph-paper grid in hero backgrounds
   The scientific atlas signature of this portfolio
   ============================================================ */
@keyframes grid-drift {
  0%   { background-position: 0 0, 0 0; }
  100% { background-position: 60px 60px, 60px 60px; }
}

/* ============================================================
   HERO ANIMATION — Clip-path curtain reveal on landing page load
   Text rises from a slot — editorial, cinematic, unexpected.
   ============================================================ */
.hero-animate {
  animation: hero-reveal 1.1s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  opacity: 0;
  /* Ensures clip-path is defined before animation starts */
  clip-path: inset(0 -5% 100% -5%);
  /* Overflow visible so descenders (g, y, p…) are never clipped */
  overflow: visible;
}

/* These nth-child delays serve as fallback if inline style= not present */
.hero-animate:nth-child(1) { animation-delay: 80ms; }
.hero-animate:nth-child(2) { animation-delay: 260ms; }
.hero-animate:nth-child(3) { animation-delay: 440ms; }
.hero-animate:nth-child(4) { animation-delay: 620ms; }
.hero-animate:nth-child(5) { animation-delay: 800ms; }

@keyframes hero-reveal {
  from {
    opacity: 0;
    clip-path: inset(0 -5% 100% -5%);
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    /* -20% bottom extends the clip area below the element box,
       ensuring descenders (g, y, p…) are never cropped */
    clip-path: inset(0 -5% -20% -5%);
    transform: translateY(0);
  }
}

/* ============================================================
   PLACEHOLDER BREATHING PULSE
   Placeholders pulse gently to indicate "reserved space"
   ============================================================ */
.block-placeholder-text,
.block-placeholder-figure {
  animation: placeholder-breathe 4s ease-in-out infinite;
}

@keyframes placeholder-breathe {
  0%, 100% { border-color: var(--color-accent-dim);      opacity: 1;    }
  50%       { border-color: var(--color-accent-dim);      opacity: 0.88; }
}

/* ============================================================
   STAT COUNTER — Entry animation for stat blocks
   ============================================================ */
.block-stat-value {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.block-stat-value.revealed {
  opacity: 1;
  transform: translateY(0);
}

/* ============================================================
   SIDEBAR SLIDE IN
   ============================================================ */
.chapter-sidebar {
  transition: transform var(--transition-base), opacity var(--transition-base);
}

/* Sidebar open state handled in components.css via .open class */

/* ============================================================
   FIGURE HOVER ZOOM
   Applied to .block-figure img
   ============================================================ */
.block-figure img {
  transition: transform var(--transition-slow);
}

.block-figure:hover img {
  transform: scale(1.03);
}

/* ============================================================
   AOS CUSTOM OVERRIDES
   AOS is loaded from CDN. Override its default timings here.
   ============================================================ */
[data-aos] {
  transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1) !important;
}

[data-aos="fade-up"] {
  transition-property: opacity, transform !important;
}

/* ============================================================
   PAGE TRANSITION — Fade between page renders
   ============================================================ */
#page-root {
  transition: opacity var(--transition-fast);
}

#page-root.transitioning {
  opacity: 0;
  pointer-events: none;
}

/* ============================================================
   MEDIA LOAD STATES — Skeleton and error for figures and videos
   Applies to section page figures (media column + photo-collection)
   and local video blocks. YouTube iframes are excluded — they
   render their own error screen and cannot be detected reliably.
   ============================================================ */

/* Skeleton: shown while loading */
figure.is-loading,
.block-video.is-loading {
  position: relative;
  min-height: 180px;
  background: var(--color-surface-low);
  overflow: hidden;
}

figure.is-loading::before,
.block-video.is-loading::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--color-surface-high) 50%,
    transparent 100%
  );
  animation: skeleton-shimmer 1.6s ease-in-out infinite;
  z-index: 1;
}

figure.is-loading::after {
  content: 'Loading image\2026';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-label);
  font-size: var(--text-label-sm);
  color: var(--color-on-surface-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  white-space: nowrap;
  pointer-events: none;
  z-index: 2;
}

.block-video.is-loading::after {
  content: 'Loading video\2026';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-label);
  font-size: var(--text-label-sm);
  color: var(--color-on-surface-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  white-space: nowrap;
  pointer-events: none;
  z-index: 2;
}

/* Hide image and its lightbox link while loading */
figure.is-loading img,
figure.is-loading a {
  opacity: 0;
  pointer-events: none;
}

.block-video.is-loading video {
  opacity: 0;
}

@keyframes skeleton-shimmer {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(200%); }
}

/* Error state */
figure.is-error,
.block-video.is-error {
  min-height: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-surface-low);
  border: 1px dashed var(--color-border);
}

figure.is-error img,
figure.is-error a,
figure.is-error figcaption,
.block-video.is-error video {
  display: none;
}

.media-error-msg {
  font-family: var(--font-label);
  font-size: var(--text-label-sm);
  color: var(--color-on-surface-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  text-align: center;
  padding: var(--space-4);
}

/* ============================================================
   PREFERS-REDUCED-MOTION — Honour accessibility setting
   All animations disabled when user has requested reduced motion
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .stagger-in > * {
    opacity: 1;
    transform: none;
  }

  .hero-animate {
    opacity: 1;
    transform: none;
    clip-path: none;
  }

  .block-placeholder-text,
  .block-placeholder-figure {
    animation: none;
  }
}
