/* =============================================================
   zheenga · Motion System
   A unifying motion language layered on top of styles.css.
   - One ease for everything → settled, dance-like feel
   - Scroll reveals with per-element stagger
   - Hero word-stagger reveal (Newsreader italics drift)
   - Cursor-following glow on cards
   - Hero grain overlay (editorial print feel)
   - Per-page energy registers (atmospheric / kinetic / peak / etc.)
   - Custom View Transitions timing
   ============================================================= */

:root {
  /* One ease to rule them all — expo-out, the "settle" curve */
  --ease-z:       cubic-bezier(0.16, 1, 0.3, 1);
  --ease-z-soft:  cubic-bezier(0.22, 1, 0.36, 1);
  --ease-z-snap:  cubic-bezier(0.34, 1.2, 0.64, 1); /* used very sparingly */

  --dur-enter:    720ms;
  --dur-hover:    260ms;
  --dur-press:     90ms;
}

/* Honour the user — silence everything for reduced-motion */
@media (prefers-reduced-motion: reduce) {
  :root { --dur-enter: 0ms; --dur-hover: 0ms; }
  .zh-word, [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* ---------- 1. Page enter — replaces styles.css .page animation ------- */
.page {
  animation: zPageIn var(--dur-enter) var(--ease-z) both !important;
}
@keyframes zPageIn {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---------- 2. Scroll-reveal ------------------------------------------ */
/* Default state — hidden + offset, until .in-view is added by motion.js. */
[data-reveal] {
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity   520ms var(--ease-z),
    transform 600ms var(--ease-z);
  transition-delay: calc(var(--reveal-i, 0) * 45ms);
  will-change: opacity, transform;
}
[data-reveal].in-view {
  opacity: 1;
  transform: translateY(0);
}

/* ---------- 3. Hero word-stagger -------------------------------------- */
.zh-words { display: inline; }
.zh-word {
  display: inline-block;
  opacity: 0;
  /* Opacity-only — no Y translate. Combined with the page-fade translate,
     a per-word Y read as wobble. Tight stagger reads as a single bloom. */
  transition: opacity 480ms var(--ease-z);
  transition-delay: calc(var(--zh-word-i, 0) * 18ms);
  will-change: opacity;
}
.zh-word.zh-space { width: 0.28em; opacity: 1; }
/* Italic accent words trail the rest by a hair — just enough to feel
   articulated, not enough to feel like a separate event. */
.zh-word.zh-em {
  transition-delay: calc(var(--zh-word-i, 0) * 18ms + 60ms);
}
.zh-words-in .zh-word,
.zh-words-in .zh-word.zh-em { opacity: 1; }

/* ---------- 4. Card cursor-glow --------------------------------------- */
/* motion.js injects <span class="zh-glow"> as the last child of each card.
   The span is positioned absolutely behind content; --mx/--my are updated
   from pointer events. Uses screen blend so it lifts warm color through
   any underlying photograph. */
.event-card, .trend-row, .artist-card, .explore-card,
.city-row, .lineup-item, .spotlight-hero {
  position: relative;
}
.zh-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(
    280px circle at var(--mx, 50%) var(--my, 50%),
    rgba(243, 190, 98, 0.32),
    rgba(223, 99, 55, 0.12) 38%,
    transparent 70%
  );
  opacity: 0;
  transition: opacity 320ms var(--ease-z);
  z-index: 0;
}
[data-scheme="cool"] .zh-glow {
  background: radial-gradient(
    280px circle at var(--mx, 50%) var(--my, 50%),
    rgba(126, 221, 221, 0.30),
    rgba(15, 108, 109, 0.14) 38%,
    transparent 70%
  );
}
.event-card:hover .zh-glow,
.trend-row:hover .zh-glow,
.artist-card:hover .zh-glow,
.explore-card:hover .zh-glow,
.city-row:hover .zh-glow,
.lineup-item:hover .zh-glow,
.spotlight-hero:hover .zh-glow { opacity: 1; }

/* Make sure original card content sits above the glow */
.event-card > *:not(.zh-glow),
.trend-row > *:not(.zh-glow),
.artist-card > *:not(.zh-glow),
.explore-card > *:not(.zh-glow),
.city-row > *:not(.zh-glow),
.lineup-item > *:not(.zh-glow),
.spotlight-hero > *:not(.zh-glow) { position: relative; z-index: 1; }

/* ---------- 5. Card lift-with-pivot ---------------------------------- */
@media (hover: hover) {
  .event-card, .trend-row, .artist-card, .explore-card, .city-row {
    transition:
      transform 380ms var(--ease-z),
      box-shadow 380ms var(--ease-z),
      border-color 380ms var(--ease-z);
  }
  .event-card:hover,
  .trend-row:hover,
  .artist-card:hover {
    transform: translateY(-3px);
  }
  .event-card.spotlight--hero:hover {
    transform: translateY(-4px);
    box-shadow:
      0 32px 80px rgba(38, 22, 21, 0.28),
      0 6px 20px rgba(38, 22, 21, 0.18);
  }
  /* Subtle warmth lift on photographs inside cards. Pairs with the
     glow + ken-burns so hover registers without being garish. */
  .event-card .event-card-media img,
  .trend-row .trend-row-photo img,
  .artist-card .artist-card-photo img {
    transition: filter 480ms var(--ease-z);
    will-change: filter;
  }
  .event-card:hover .event-card-media img,
  .trend-row:hover .trend-row-photo img,
  .artist-card:hover .artist-card-photo img {
    filter: saturate(1.08) contrast(1.04);
  }
}

/* ---------- 6. Hero grain -------------------------------------------- */
/* motion.js injects <span class="zh-grain"> at top of every hero. */
.zh-grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.05;
  z-index: 1;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.92' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.7 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  background-size: 180px 180px;
  mix-blend-mode: overlay;
}
.hero > *:not(.zh-grain):not(.hero-candles),
.detail-hero > *:not(.zh-grain),
.artist-hero > *:not(.zh-grain) { position: relative; z-index: 2; }

/* ---------- 7. Sticky nav — border fades in as user scrolls ---------- */
.nav {
  transition:
    border-color 320ms var(--ease-z),
    background-color 320ms var(--ease-z);
}
.nav:not(.is-scrolled) { border-bottom-color: transparent; }

/* ---------- 8. Buttons — weight on press, arrow extends further ------ */
.btn { will-change: transform; }
.btn:active {
  transform: scale(0.985);
  transition: transform var(--dur-press) ease-out;
}
.btn .arrow {
  transition: transform 340ms var(--ease-z);
}
.btn:hover .arrow { transform: translateX(5px); }
.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 38px rgba(223, 99, 55, 0.42);
  transition:
    transform 280ms var(--ease-z),
    box-shadow 280ms var(--ease-z),
    background-color 280ms var(--ease-z);
}

/* ---------- 9. View Transitions — custom timing ---------------------- */
/* The image leads the swap. The old page exits quickly so it doesn't
   linger over the morphing image, the new page enters quietly behind it.
   Then the destination's hero composes in via the cascade above. */
@keyframes zVtFadeOut { to   { opacity: 0; } }
@keyframes zVtFadeIn  { from { opacity: 0; } }

::view-transition-old(root) {
  animation: zVtFadeOut 280ms cubic-bezier(0.33, 1, 0.68, 1) both;
}
::view-transition-new(root) {
  /* New root fades in behind the morphing image. The destination hero
     cascade then takes over to compose the headline + meta. */
  animation: zVtFadeIn 560ms cubic-bezier(0.33, 1, 0.68, 1) 220ms both;
}
::view-transition-group(vt-event-image),
::view-transition-group(vt-artist-image),
::view-transition-image-pair(vt-event-image),
::view-transition-image-pair(vt-artist-image) {
  animation-duration: 820ms;
  animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ============================================================
   PER-PAGE ENERGY REGISTERS
   Set by App.jsx as document.body.dataset.energy on route change.
   ============================================================ */

/* Home — atmospheric.
   No per-word stagger. Headline settles into form: letter-spacing closes,
   optical-size axis widens, opacity rises. Then lede / buttons / stats
   cascade in opacity-only — like reading down the page. */
body[data-energy="atmospheric"] {
  --dur-enter: 760ms;
}
/* Disable any per-word treatment — we animate the headline as one. */
body[data-energy="atmospheric"] .zh-word,
body[data-energy="atmospheric"] .zh-word.zh-em {
  opacity: 1 !important;
  filter: none !important;
  transform: none !important;
  transition: none !important;
}
body[data-energy="atmospheric"] .hero h1 {
  animation: zhHeadlineSettle 1100ms cubic-bezier(0.33, 1, 0.68, 1) 160ms both;
}
@keyframes zhHeadlineSettle {
  from {
    opacity: 0;
    letter-spacing: 0.045em;
    font-variation-settings: "opsz" 18;
  }
  to {
    opacity: 1;
    letter-spacing: -0.025em;
    font-variation-settings: "opsz" 60;
  }
}
/* Cascade the supporting hero content. Opacity-only — no motion. */
body[data-energy="atmospheric"] .hero .lede {
  animation: zhFadeQuiet 760ms cubic-bezier(0.33, 1, 0.68, 1) 520ms both;
}
body[data-energy="atmospheric"] .hero .hero-actions {
  animation: zhFadeQuiet 680ms cubic-bezier(0.33, 1, 0.68, 1) 760ms both;
}
body[data-energy="atmospheric"] .hero .hero-stats {
  animation: zhFadeQuiet 680ms cubic-bezier(0.33, 1, 0.68, 1) 920ms both;
}
@keyframes zhFadeQuiet {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Events list — kinetic, excited, faster cadence.
   Paired reveals: cards in the same row arrive together (cinematic), not
   one-by-one (kinetic feels like a checklist when cascaded linearly). */
body[data-energy="kinetic"] {
  --dur-enter: 540ms;
}
body[data-energy="kinetic"] [data-reveal] {
  /* Round down to nearest 2 — paired arrival. */
  transition-delay: calc(floor(var(--reveal-i, 0) / 2) * 55ms);
}
@media (hover: hover) {
  body[data-energy="kinetic"] .event-card:hover,
  body[data-energy="kinetic"] .artist-card:hover {
    transform: translateY(-4px);
  }
}
body[data-energy="kinetic"] .zh-word {
  transition-delay: calc(var(--zh-word-i, 0) * 14ms);
}
body[data-energy="kinetic"] .zh-word.zh-em {
  transition-delay: calc(var(--zh-word-i, 0) * 14ms + 50ms);
}

/* Event detail — peak.
   Image leads via View Transitions; supporting text composes in after. */
body[data-energy="peak"] {
  --dur-enter: 680ms;
}
body[data-energy="peak"] .zh-word,
body[data-energy="peak"] .zh-word.zh-em {
  opacity: 1 !important;
  filter: none !important;
  transform: none !important;
  transition: none !important;
}
/* Edition eyebrow first, then the headline settles, then the meta line. */
body[data-energy="peak"] .detail-hero-meta .edition {
  animation: zhFadeQuiet 540ms cubic-bezier(0.33, 1, 0.68, 1) 280ms both;
}
body[data-energy="peak"] .detail-hero-meta h1 {
  animation: zhHeadlineSettle 1100ms cubic-bezier(0.33, 1, 0.68, 1) 420ms both;
}
body[data-energy="peak"] .detail-hero-meta .when {
  animation: zhFadeQuiet 640ms cubic-bezier(0.33, 1, 0.68, 1) 760ms both;
}
body[data-energy="peak"] [data-reveal] {
  transition-delay: calc(var(--reveal-i, 0) * 40ms);
}

/* Artists list — editorial, considered */
body[data-energy="editorial"] {
  --dur-enter: 700ms;
}
body[data-energy="editorial"] [data-reveal] {
  transition-delay: calc(var(--reveal-i, 0) * 55ms);
}

/* Artist profile — intimate.
   Portrait composes in slowly; text settles alongside. */
body[data-energy="intimate"] {
  --dur-enter: 800ms;
}
body[data-energy="intimate"] .zh-word,
body[data-energy="intimate"] .zh-word.zh-em {
  opacity: 1 !important;
  filter: none !important;
  transform: none !important;
  transition: none !important;
}
body[data-energy="intimate"] .artist-hero .eyebrow {
  animation: zhFadeQuiet 560ms cubic-bezier(0.33, 1, 0.68, 1) 240ms both;
}
body[data-energy="intimate"] .artist-hero h1 {
  animation: zhHeadlineSettle 1200ms cubic-bezier(0.33, 1, 0.68, 1) 420ms both;
}
body[data-energy="intimate"] .artist-hero .lede {
  animation: zhFadeQuiet 760ms cubic-bezier(0.33, 1, 0.68, 1) 820ms both;
}
body[data-energy="intimate"] .artist-hero .artist-hero-meta {
  animation: zhFadeQuiet 680ms cubic-bezier(0.33, 1, 0.68, 1) 1040ms both;
}
body[data-energy="intimate"] .artist-hero .hero-actions {
  animation: zhFadeQuiet 640ms cubic-bezier(0.33, 1, 0.68, 1) 1200ms both;
}
body[data-energy="intimate"] .artist-portrait img {
  animation: zhPortraitIn 1200ms cubic-bezier(0.33, 1, 0.68, 1) both;
}
@keyframes zhPortraitIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* About — hushed, slowest. */
body[data-energy="hushed"] {
  --dur-enter: 860ms;
}
body[data-energy="hushed"] [data-reveal] {
  transition-delay: calc(var(--reveal-i, 0) * 70ms);
}

/* Auth / Profile — calm, minimal. */
body[data-energy="calm"] {
  --dur-enter: 460ms;
}
body[data-energy="calm"] [data-reveal] {
  transition-delay: calc(var(--reveal-i, 0) * 24ms);
}

/* ---------- 10. Pause heavy ambients during view-transition ---------- */
.vt-active .zh-glow,
.vt-active .zh-grain { opacity: 0 !important; }
