/* ==========================================================================
   Motion — keyframes, loader, page curtain, cursor, reveal start states.
   Start states only apply under html.motion, which is set in <head> and
   removed again by animation.js if GSAP fails to load.
   ========================================================================== */

@keyframes spin { to { transform: rotate(360deg); } }
@keyframes ping { 0% { transform: scale(0.6); opacity: 1; } 100% { transform: scale(2.4); opacity: 0; } }
@keyframes drip { 0% { transform: translateY(-100%); } 60%, 100% { transform: translateY(100%); } }
@keyframes slide { 0% { transform: translateX(-100%); } 60%, 100% { transform: translateX(100%); } }
@keyframes dash { to { stroke-dashoffset: -16; } }
@keyframes fadeUp { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes blink { 50% { opacity: 0; } }
@keyframes float { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-6px) rotate(-0.6deg); } }
@keyframes popIn { from { opacity: 0; transform: scale(0.6); } to { opacity: 1; transform: none; } }
/* Organic blob: corners re-shape continuously */
@keyframes morph {
  0%   { border-radius: 42% 58% 63% 37% / 41% 44% 56% 59%; }
  33%  { border-radius: 63% 37% 45% 55% / 55% 62% 38% 45%; }
  66%  { border-radius: 38% 62% 33% 67% / 62% 35% 65% 38%; }
  100% { border-radius: 55% 45% 58% 42% / 38% 58% 42% 62%; }
}
@keyframes drift { 0% { translate: 0 0; rotate: 0deg; } 50% { translate: -6% 8%; rotate: 25deg; } 100% { translate: 5% -4%; rotate: -15deg; } }

/* ---------- Loader (first visit per session) ---------- */
.loader {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: none;
  flex-direction: column;
  justify-content: space-between;
  padding: 1.6rem var(--gutter);
  background: var(--ink);
  color: var(--on-ink);
}
.motion.is-first-visit .loader { display: flex; }
.loader__top, .loader__bottom { display: flex; justify-content: space-between; color: var(--on-ink-dim); }
.loader__count {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(8rem, 30vw, 26rem);
  line-height: 0.8;
  font-variant-numeric: tabular-nums;
}
.loader__count span { color: var(--signal); }
.loader__bar { height: 2px; background: var(--rule-ink); position: relative; margin-bottom: 1rem; }
.loader__bar i { position: absolute; inset: 0; background: var(--signal); transform-origin: left; transform: scaleX(0); }
.loader__cursor { animation: blink 0.8s steps(1) infinite; }

/* ---------- Page-change curtain ---------- */
.curtain {
  position: fixed;
  inset: 0;
  z-index: 9400;
  pointer-events: none;
  background: var(--signal);
  transform: scaleY(0);
  transform-origin: top;
}
/* Curved leading edge so the curtain sweeps like a wave */
.curtain::before, .curtain::after {
  content: "";
  position: absolute;
  left: -10%;
  width: 120%;
  height: 22vh;
  background: inherit;
  border-radius: 50%;
}
.curtain::before { top: -11vh; }
.curtain::after { bottom: -11vh; }
.curtain__mark {
  position: absolute;
  left: var(--gutter);
  bottom: 1.6rem;
  color: var(--black);
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(3rem, 9vw, 8rem);
  line-height: 0.8;
  text-transform: uppercase;
  opacity: 0;
}
/* Arriving from an internal link: start covered, animation.js lifts it */
.motion.is-arriving .curtain { transform: scaleY(1); transform-origin: bottom; }
.motion.is-arriving .curtain__mark { opacity: 1; }

/* ---------- Custom cursor (fine pointers only) ---------- */
.cursor, .cursor-ring { display: none; }
@media (hover: hover) and (pointer: fine) {
  .has-cursor, .has-cursor a, .has-cursor button, .has-cursor label, .has-cursor select { cursor: none; }
  .has-cursor input, .has-cursor textarea { cursor: text; }
  .cursor, .cursor-ring {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 9600;
    pointer-events: none;
    will-change: transform;
    opacity: 0;
    transition: opacity 0.3s;
  }
  .has-moved .cursor, .has-moved .cursor-ring { opacity: 1; }
  .cursor { width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%; background: var(--signal); }
  .cursor-ring {
    width: 44px;
    height: 44px;
    margin: -22px 0 0 -22px;
    display: grid;
    place-items: center;
  }
  .cursor-ring::before {
    content: "";
    position: absolute;
    inset: 0;
    border: 1px solid var(--on-ink);
    border-radius: 50%;
    transition: transform 0.45s var(--ease-out), background 0.3s, border-color 0.3s;
  }
  .cursor-ring__label {
    position: relative;
    font-family: var(--font-mono);
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--white);
    opacity: 0;
    transition: opacity 0.3s;
  }
  .cursor-ring.is-link::before { transform: scale(1.5); border-color: var(--signal); background: rgba(255, 77, 28, 0.1); }
  .cursor-ring.is-label::before { transform: scale(2); border-color: var(--black); background: var(--black); }
  .cursor-ring.is-label .cursor-ring__label { opacity: 1; }
  .cursor-ring.is-hidden::before { transform: scale(0); }
  .cursor-ring.is-down::before { transform: scale(0.7); }
}

/* ---------- Reveal start states ---------- */
.motion [data-reveal] { opacity: 0; transform: translateY(48px) scale(0.96); }
.motion [data-reveal="fade"] { transform: none; }
.motion [data-reveal="clip"] { opacity: 1; transform: none; clip-path: inset(100% 0 0 0); }
.motion [data-reveal="clip-x"] { opacity: 1; transform: none; clip-path: inset(0 100% 0 0); }
.motion [data-split] { visibility: hidden; }
.motion [data-split].is-split { visibility: visible; }
.motion .hero__title .line > span, .motion .pbanner h1 .line > span { transform: translateY(110%); }
.motion .hero__media { clip-path: inset(100% 0 0 0); }
.motion .spec__bar i { transform: scaleX(0); }
.motion .step__rule, .motion .timeline__line { transform: scaleX(0); }

/* Split text pieces */
.split-line { display: inline-block; vertical-align: top; overflow: hidden; padding-bottom: 0.08em; margin-bottom: -0.08em; }
.split-inner { display: inline-block; will-change: transform; }

/* ---------- Soft ambient blobs behind the hero and page banners ---------- */
.hero::before, .hero::after, .pbanner::before, .pbanner::after {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  filter: blur(60px);
  opacity: 0.55;
  animation: morph 16s var(--ease-soft) infinite alternate, drift 22s var(--ease-soft) infinite alternate;
}
.hero::before, .pbanner::before {
  width: clamp(280px, 38vw, 620px);
  aspect-ratio: 1;
  right: -8%;
  top: -12%;
  background: radial-gradient(circle at 35% 35%, rgba(255, 77, 28, 0.55), rgba(255, 140, 90, 0.22) 55%, transparent 72%);
}
.hero::after, .pbanner::after {
  width: clamp(240px, 30vw, 480px);
  aspect-ratio: 1;
  left: -10%;
  bottom: -18%;
  background: radial-gradient(circle at 60% 40%, rgba(138, 143, 92, 0.45), rgba(221, 216, 202, 0.5) 55%, transparent 72%);
  animation-duration: 19s, 26s;
  animation-direction: alternate-reverse;
}
.pbanner::before { opacity: 0.4; }
/* Keep the banner's lower blob clear of its clipped bottom edge */
.pbanner::after { bottom: auto; top: 18%; left: -14%; opacity: 0.4; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}

/* Quote words rise in when a quote becomes active */
.quote.is-active .w { animation: fadeUp 0.9s var(--ease-out) both; animation-delay: calc(var(--i) * 30ms); }

/* The reticle is positioned by script; without motion it stays hidden */
html:not(.motion) .reticle { display: none; }
