/* ==========================================================================
   NOIR ÉLIXIR — Animation layer
   --------------------------------------------------------------------------
   Foundation only. Keyframes live here alongside the "pre-animation" resting
   states that GSAP takes over at runtime. The choreography itself is built in
   js/animations.js so timing stays in one place.

   Contract: every element carrying a `data-animate` hook starts hidden ONLY
   when `html.has-js` is present, so the page degrades to fully visible
   content when scripting is unavailable.
   ========================================================================== */

/* ==========================================================================
   01. KEYFRAMES
   ========================================================================== */

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

@keyframes ne-rise {
  from {
    opacity: 0;
    transform: translate3d(0, 1.5rem, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

/* Slow breath used by holding states (preloader, scroll cue). */
@keyframes ne-breathe {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}

/* Vertical travel for the hero scroll indicator. */
@keyframes ne-scroll-hint {
  0% {
    transform: scaleY(0);
    transform-origin: top;
  }
  45% {
    transform: scaleY(1);
    transform-origin: top;
  }
  55% {
    transform: scaleY(1);
    transform-origin: bottom;
  }
  100% {
    transform: scaleY(0);
    transform-origin: bottom;
  }
}

/* Gold sweep reserved for wordmarks and rules. */
@keyframes ne-shimmer {
  from {
    background-position: -150% 0;
  }
  to {
    background-position: 250% 0;
  }
}

/* ==========================================================================
   02. RESTING STATES FOR SCROLL REVEALS
   ========================================================================== */

.has-js [data-animate] {
  opacity: 0;
  will-change: opacity, transform;
}

.has-js [data-animate="rise"] {
  transform: translate3d(0, 2rem, 0);
}

.has-js [data-animate="fade"] {
  transform: none;
}

/* Masked line reveal — the mask is `.line`, the traveller is `.line__inner`. */
.has-js .hero__title .line__inner {
  opacity: 0;
  transform: translate3d(0, 100%, 0);
  will-change: transform;
}

.has-js .hero__content .eyebrow,
.has-js .hero__tagline,
.has-js .hero__actions,
.has-js .hero__media,
.has-js .hero__scroll {
  opacity: 0;
}

/* Section heads animate their parts, not the block, so the title can reveal
   word by word while the label and lede drift up behind it. */
.has-js [data-reveal="head"] .eyebrow,
.has-js [data-reveal="head"] .lede,
.has-js [data-reveal="quote"] .quote__cite {
  opacity: 0;
}

.has-js [data-reveal="figure"],
.has-js [data-stagger] > * {
  opacity: 0;
}

/* Hidden until the splitter has wrapped the words — otherwise the unsplit
   text would flash before the masks exist. */
.has-js [data-split] {
  opacity: 0;
}

.has-js [data-split].is-split {
  opacity: 1;
}

/* Word masks built at runtime by the splitter. */
.word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.word__inner {
  display: inline-block;
  will-change: transform;
}

/* GSAP clears these once a reveal has played. */
[data-animate].is-revealed {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* ==========================================================================
   03. AMBIENT / IDLE ANIMATIONS
   ========================================================================== */

.preloader__meta {
  animation: ne-breathe 2.6s var(--ease-in-out) infinite;
}

.hero__scroll-line {
  animation: ne-scroll-hint 2.8s var(--ease-in-out) infinite;
}

.hero__scroll-label {
  animation: ne-fade-in var(--dur-slow) var(--ease-out) 1.2s both;
}

/* ==========================================================================
   04. HOVER & INTERACTION TRANSITIONS
   ========================================================================== */

/* Gold fill that wipes up from the base of a primary button. */
.btn--gold::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--accent);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform var(--dur-mid) var(--ease-out);
}

.btn--gold:hover::before,
.btn--gold:focus-visible::before {
  transform: scaleY(1);
}

/* Underline wipe shared by editorial text links. */
.link-wipe {
  position: relative;
  display: inline-block;
}

.link-wipe::after {
  content: "";
  position: absolute;
  inset: auto 0 -0.15em 0;
  height: 1px;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--dur-mid) var(--ease-out);
}

.link-wipe:hover::after,
.link-wipe:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* ==========================================================================
   05. REDUCED MOTION
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .has-js [data-animate],
  .has-js [data-split],
  .has-js [data-reveal] *,
  .has-js [data-stagger] > *,
  .has-js .hero__title .line__inner,
  .has-js .hero__content .eyebrow,
  .has-js .hero__tagline,
  .has-js .hero__actions,
  .has-js .hero__media,
  .has-js .hero__scroll,
  .has-js [data-reveal="figure"],
  .has-js .manifesto__body,
  .has-js .atelier__body,
  .has-js .signup {
    opacity: 1;
    transform: none;
  }

  .preloader__meta,
  .hero__scroll-line,
  .hero__scroll-label {
    animation: none;
  }
}
