/* ==========================================================================
   NOVAX — Motion
   Scroll-reveal is progressive enhancement: everything is fully visible
   by default, JS only adds the starting state once it's confirmed to run.
   Directional variants (data-reveal="left|right|zoom") give staggered,
   cinematic entrances without a JS animation library.
   ========================================================================== */

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes scrollcue {
  0% {
    transform: translateY(-100%);
    opacity: 0;
  }
  30% {
    opacity: 1;
  }
  100% {
    transform: translateY(100%);
    opacity: 0;
  }
}

@keyframes floatSlow {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-14px);
  }
}

@keyframes fadeUp {
  from {
    opacity: 0;
    transform: translateY(28px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Slow continuous drift behind the active hero slide — the "Ken Burns" pan/zoom. */
@keyframes kenBurns {
  from {
    transform: scale(1) translate3d(0, 0, 0);
  }
  to {
    transform: scale(1.12) translate3d(-1%, -1%, 0);
  }
}

/* Whole-page opening fade — pure CSS, no JS dependency, so it always runs. */
@keyframes pageOpen {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

body {
  animation: pageOpen 0.7s ease both;
}

/* JS adds .js-anim to <html> once it can run the observer, then
   applies the resting state below to [data-reveal] elements. */
.js-anim [data-reveal] {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.9s var(--ease-cinematic), transform 0.9s var(--ease-cinematic), filter 0.9s var(--ease-cinematic);
}

.js-anim [data-reveal="left"] {
  transform: translateX(-44px);
}

.js-anim [data-reveal="right"] {
  transform: translateX(44px);
}

.js-anim [data-reveal="zoom"] {
  transform: scale(0.94);
}

/* A photo resolving into focus, like a project coming together. */
.js-anim [data-reveal="blur"] {
  transform: scale(1.04);
  filter: blur(16px);
}

.js-anim [data-reveal].is-visible {
  opacity: 1;
  transform: none;
  filter: none;
}

.js-anim [data-reveal-group] > * {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.75s var(--ease-cinematic), transform 0.75s var(--ease-cinematic);
}

.js-anim [data-reveal-group].is-visible > * {
  opacity: 1;
  transform: translateY(0);
}

.js-anim [data-reveal-group].is-visible > *:nth-child(1) { transition-delay: 0.05s; }
.js-anim [data-reveal-group].is-visible > *:nth-child(2) { transition-delay: 0.14s; }
.js-anim [data-reveal-group].is-visible > *:nth-child(3) { transition-delay: 0.23s; }
.js-anim [data-reveal-group].is-visible > *:nth-child(4) { transition-delay: 0.32s; }
.js-anim [data-reveal-group].is-visible > *:nth-child(5) { transition-delay: 0.41s; }
.js-anim [data-reveal-group].is-visible > *:nth-child(6) { transition-delay: 0.5s; }
.js-anim [data-reveal-group].is-visible > *:nth-child(7) { transition-delay: 0.59s; }
.js-anim [data-reveal-group].is-visible > *:nth-child(8) { transition-delay: 0.68s; }

.badge-float {
  animation: floatSlow 6s ease-in-out infinite;
}

/* ==========================================================================
   Hero logo — built, not faded in. Each of the 21 building paths first
   draws its own outline (stroke-dasharray/dashoffset, measured per-path in
   JS via getTotalLength() so every shape — from the tallest tower to the
   thinnest window-stripe — draws at the correct rate), then its solid fill
   locks in shortly after. js/main.js sets the actual dasharray/dashoffset/
   delay values; this just declares how those inline-style changes animate.
   Runs as a normal part of the hero loading — nothing waits on it.
   ========================================================================== */
.hero-logo-svg path {
  fill-opacity: 0;
  stroke: #fff;
  stroke-opacity: 0.95;
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

/* Draw-in + fill-in are keyframe animations, not transitions. A transition
   started by setting stroke-dashoffset via inline style in the same tick
   the delay/duration are (re)configured does not reliably honor
   transition-delay in Chromium for this property — it was observed
   snapping straight to the end value, verified with actual pixel
   screenshots, not just computed style. @keyframes sidesteps this
   entirely: the "undrawn" state is the element's own resting value
   (matches the implicit 0% keyframe), so animation-delay correctly holds
   it there with no double-rAF or paint-ordering games required. */
@keyframes drawPath {
  to {
    stroke-dashoffset: 0;
  }
}

@keyframes fillPath {
  to {
    fill-opacity: 1;
  }
}

/* The N's signature moment: a brief glow right as its fill locks solid —
   js/main.js times this animation-delay to land exactly when the fill
   finishes, so the glow reads as the N "arriving," not a random flourish
   disconnected from the build. */
@keyframes nGlow {
  0% {
    filter: drop-shadow(0 0 0 rgba(255, 255, 255, 0));
  }
  40% {
    filter: drop-shadow(0 0 26px rgba(255, 255, 255, 0.95));
  }
  100% {
    filter: drop-shadow(0 0 0 rgba(255, 255, 255, 0));
  }
}

/* `animation` is a shorthand — a second rule setting it doesn't merge with
   this one, it replaces it. So the N path (which also needs nGlow) gets
   its own selector listing all three keyframes; higher specificity
   (3 classes vs 2) makes it win over the rule below deterministically. */
.js-anim .hero-logo-svg path {
  animation: drawPath 1s var(--ease-cinematic) both, fillPath 0.5s ease-out both;
}

.js-anim .hero-logo-svg path.hero-n-stroke {
  animation: drawPath 1s var(--ease-cinematic) both, fillPath 0.5s ease-out both, nGlow 1s ease-out both;
}

/* Hero entrance — a longer, layered sequence (~4s total): the logo is
   still under construction when the headline starts assembling itself
   letter by letter, so several distinct motions overlap rather than
   running as one flat fade. Paragraph and button stay simple fades —
   letter-by-letter is for the one line that needs to grab attention, not
   for body copy people need to actually read. */
.js-anim .hero-content > * {
  animation: fadeUp 1.1s var(--ease-cinematic) both;
}

/* The eyebrow and headline opt out of the block-level fade above — their
   individual .letter spans (built by js/main.js) animate instead, so the
   container itself just needs to stay visible. */
.js-anim .hero-content .eyebrow,
.js-anim .hero-content h1 {
  opacity: 1;
  animation: none;
}

.js-anim .hero-content p { animation-delay: 3.05s; }
.js-anim .hero-content .hero-cta { animation-delay: 3.35s; }

/* Each letter pops up into place — a bit of overshoot for energy, kept
   short per-letter so the *sequence* reads as long and rich without any
   single letter feeling slow. */
@keyframes letterIn {
  0% {
    opacity: 0;
    transform: translateY(26px) rotate(7deg) scale(0.72);
  }
  55% {
    opacity: 1;
  }
  100% {
    opacity: 1;
    transform: translateY(0) rotate(0deg) scale(1);
  }
}

/* Each word is one no-wrap unit so a line only ever breaks between words,
   never inside one — letters remain individually animatable either way. */
.word {
  display: inline-block;
  white-space: nowrap;
}

.letter {
  display: inline-block;
  will-change: transform, opacity;
}

.js-anim .letter {
  animation: letterIn 0.6s cubic-bezier(0.2, 0.9, 0.32, 1.15) both;
}

@media (prefers-reduced-motion: reduce) {
  body,
  .badge-float,
  .hero-slide,
  .hero-n-stroke,
  .js-anim .hero-content > *,
  .js-anim .letter {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .hero-slide {
    transition: opacity 0.3s linear !important;
  }
  .js-anim [data-reveal],
  .js-anim [data-reveal-group] > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .hero-logo-svg path {
    fill-opacity: 1 !important;
    stroke-opacity: 0 !important;
    animation: none !important;
  }
}
