/* ═══════════════════════════════════════════════════════════════════════════
   MOTION — Option 03 "Quiet Confidence"

   The register is "settle, don't snap": things arrive quickly and ease to a
   stop on one strongly-decelerating curve. Nothing bounces; overshoot would
   undercut the whole system.

   Three hard rules, enforced below:

   1. prefers-reduced-motion removes MOVEMENT, never INFORMATION. Every
      reveal resolves to its final visible state; the mic keeps its halo; the
      waveform keeps its bars. Nothing is left at opacity:0.

   2. The .purpose-band is never animated from opacity:0. It is the OAuth
      verification surface and must be static HTML that a crawler sees
      immediately. Reveal classes are deliberately absent from that section.

   3. Reveals are opt-in via [data-reveal], and js/motion.js only adds the
      hidden state AFTER confirming IntersectionObserver support. If the JS
      never runs, the page renders fully visible.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Scroll reveal ────────────────────────────────────────────────────────
   .motion-ready is set by JS. Without it these selectors don't match, so
   content is visible by default — no-JS and crawler safe.                  */
.motion-ready [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}

.motion-ready [data-reveal="left"] {
  transform: translateX(-22px);
}
.motion-ready [data-reveal="right"] {
  transform: translateX(22px);
}
.motion-ready [data-reveal="scale"] {
  transform: scale(0.965);
}

.motion-ready [data-reveal].is-in {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* ── Header: condense once the page scrolls ──────────────────────────── */
.site-header {
  transition:
    padding var(--dur-base) var(--ease),
    box-shadow var(--dur-base) var(--ease),
    background var(--dur-base) var(--ease);
}

.site-header.is-stuck {
  box-shadow: var(--elev-2);
  background: color-mix(in srgb, var(--bg) 94%, transparent);
}

.site-header.is-stuck .nav {
  padding-block: 0.55rem;
}

.nav {
  transition: padding var(--dur-base) var(--ease);
}

/* ── Scroll progress rail ─────────────────────────────────────────────── */
.scroll-rail {
  position: fixed;
  inset: 0 0 auto 0;
  height: 3px;
  z-index: 60;
  background: transparent;
  pointer-events: none;
}

.scroll-rail > i {
  display: block;
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--primary), var(--accent));
  transform-origin: left center;
  transition: width 90ms linear;
}

/* ── Lift on hover ────────────────────────────────────────────────────── */
.lift {
  transition:
    transform var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

.lift:hover {
  transform: translateY(-4px);
  box-shadow: var(--elev-3);
}

@media (hover: none) {
  .lift:hover {
    transform: none;
  }
}

/* ── The breathing mic ────────────────────────────────────────────────────
   4s at rest — near a resting breath. 2.4s while "recording": the same
   gesture, more awake. Scale + opacity only, so it stays on the compositor. */
@keyframes lv-breathe {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.16;
  }
  50% {
    transform: scale(1.19);
    opacity: 0.05;
  }
}

.lv-halo {
  animation: lv-breathe 4s ease-in-out infinite;
}

.lv-micwrap--rec .lv-halo,
.is-recording .lv-halo {
  animation-duration: 2.4s;
}

/* ── Waveform ─────────────────────────────────────────────────────────────
   Only ever animates while the sequence is on the "recording" beat. A
   waveform that moves when nothing is listening is fake feedback, and this
   system forbids it — see the Don't list in the spec.                      */
@keyframes lv-eq {
  0%,
  100% {
    height: 22%;
  }
  50% {
    height: 100%;
  }
}

.is-recording .lv-wave i {
  animation: lv-eq 1.4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

.lv-wave i:nth-child(1) { animation-delay: 0ms; }
.lv-wave i:nth-child(2) { animation-delay: 110ms; }
.lv-wave i:nth-child(3) { animation-delay: 220ms; }
.lv-wave i:nth-child(4) { animation-delay: 330ms; }
.lv-wave i:nth-child(5) { animation-delay: 440ms; }
.lv-wave i:nth-child(6) { animation-delay: 550ms; }
.lv-wave i:nth-child(7) { animation-delay: 660ms; }
.lv-wave i:nth-child(8) { animation-delay: 770ms; }
.lv-wave i:nth-child(9) { animation-delay: 880ms; }
.lv-wave i:nth-child(10) { animation-delay: 990ms; }
.lv-wave i:nth-child(11) { animation-delay: 1100ms; }
.lv-wave i:nth-child(12) { animation-delay: 1210ms; }

/* ── Progress fill animates to its value when revealed ────────────────── */
.lv-progress > i {
  transition: width 1.1s var(--ease-out);
}

.motion-ready .lv-progress > i {
  width: 0;
}

.motion-ready .is-in .lv-progress > i,
.motion-ready .lv-progress.is-in > i {
  width: var(--fill, 64%);
}

/* ── The record → review → synced sequence ────────────────────────────────
   One phone, three states, driven by js/motion.js. Only the active pane is
   in flow; the others are removed from the a11y tree via [hidden].         */
.seq {
  position: relative;
}

.seq-pane {
  transition:
    opacity var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
}

.seq-pane[hidden] {
  display: none;
}

.seq-pane.is-leaving {
  opacity: 0;
  transform: translateY(-10px);
}

.seq-pane.is-entering {
  opacity: 0;
  transform: translateY(12px);
}

.seq-dots {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 1.1rem;
}

.seq-dot {
  width: 8px;
  height: 8px;
  border-radius: 999px;
  border: 0;
  padding: 0;
  background: var(--border-strong);
  cursor: pointer;
  transition:
    width var(--dur-base) var(--ease),
    background var(--dur-base) var(--ease);
}

.seq-dot.is-on {
  width: 26px;
  background: var(--primary);
}

.seq-dot:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}

/* ── Count-up figures ─────────────────────────────────────────────────── */
.tick {
  font-variant-numeric: tabular-nums;
}

/* ── Showcase rail: drag / scroll affordance ──────────────────────────── */
.showcase-rail {
  scroll-snap-type: x proximity;
}

.showcase-rail > * {
  scroll-snap-align: center;
}

/* ── Gentle float for decorative art ──────────────────────────────────── */
@keyframes lv-float {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-9px);
  }
}

.float {
  animation: lv-float 7s ease-in-out infinite;
}

.float--slow {
  animation-duration: 10s;
}

/* ── Link underline that draws in ─────────────────────────────────────── */
.link-draw {
  position: relative;
  text-decoration: none;
}

.link-draw::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1.5px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform var(--dur-base) var(--ease);
}

.link-draw:hover {
  text-decoration: none;
}

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

/* ═══ Reduced motion ══════════════════════════════════════════════════════
   Remove movement, keep information. Everything lands in its final visible
   state; the halo stays as a static ring; the waveform keeps its bars.     */
@media (prefers-reduced-motion: reduce) {
  .motion-ready [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  .lv-halo,
  .is-recording .lv-halo {
    animation: none !important;
    opacity: 0.14;
    transform: none;
  }

  .is-recording .lv-wave i {
    animation: none !important;
    height: 62%;
  }

  .float {
    animation: none !important;
  }

  .motion-ready .lv-progress > i {
    width: var(--fill, 64%);
    transition: none !important;
  }

  .seq-pane {
    transition: none !important;
  }

  .scroll-rail {
    display: none;
  }
}
