/* ==========================================================================
   ETHELIUM — scroll motion (home page, 2026-10-26)
   --------------------------------------------------------------------------
   Every hidden state lives under html.mo, which motion.js sets only when the
   visitor allows motion and the browser has IntersectionObserver. Without it
   (no JS, reduced motion, an old browser, a crawler that doesn't run JS) the
   page is simply the page — nothing here can hide content.

   The language matches the site: opacity and short vertical travel only,
   one easing curve, no bounce, no spin. Headlines rise out of a mask word by
   word; screenshots wipe open; grids arrive one cell after the next.
   ========================================================================== */

:root {
  --mo-ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --mo-dur: 0.9s;
}

/* ---- scroll progress: a 2px ink line across the top of the viewport ---- */
.mo-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 50;
  background: var(--ink, #0a0a0b);
  transform: scaleX(0); transform-origin: 0 50%;
  pointer-events: none;
}

/* ---- generic reveal: fade + rise ---- */
.mo [data-mo] {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition: opacity var(--mo-dur) var(--mo-ease), transform var(--mo-dur) var(--mo-ease);
  transition-delay: var(--mo-delay, 0s);
}
.mo [data-mo].is-in { opacity: 1; transform: none; }
.mo [data-mo="fade"] { transform: none; }

/* ---- headline words: each word rises out of its own mask ---- */
.mo .mo-words .w { display: inline-block; overflow: hidden; vertical-align: top; padding-bottom: 0.08em; margin-bottom: -0.08em; }
.mo .mo-words .w > span {
  display: inline-block;
  transform: translate3d(0, 105%, 0);
  transition: transform 1s var(--mo-ease);
  transition-delay: calc(var(--mo-delay, 0s) + var(--i, 0) * 55ms);
}
.mo .mo-words.is-in .w > span { transform: none; }

/* ---- case-study screenshots: wipe open, then the phone slides up ---- */
.mo .work-shot[data-mo-shot] {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 1.25s var(--mo-ease);
}
.mo .work.reverse .work-shot[data-mo-shot] { clip-path: inset(0 0 0 100%); }
/* .reverse needs its own revealed rule: its hidden rule above is more
   specific than the plain .is-in one, which left NexFlow clipped shut */
.mo .work-shot[data-mo-shot].is-in,
.mo .work.reverse .work-shot[data-mo-shot].is-in { clip-path: inset(0 0 0 0); }
/* zoom settles slowly on reveal (transform); scroll drift is a separate
   property (translate) so the two never fight over one transition */
.mo .work-shot[data-mo-shot] > img:not(.mob) {
  transform: scale(1.1);
  translate: 0 var(--mo-par, 0px);
  transition: transform 1.6s var(--mo-ease);
  will-change: transform, translate;
}
.mo .work-shot[data-mo-shot].is-in > img:not(.mob) { transform: scale(1.05); }
.mo .work-shot[data-mo-shot] > .mob {
  opacity: 0;
  translate: 0 40px;
  transition: opacity 0.9s var(--mo-ease) 0.55s, translate 1s var(--mo-ease) 0.55s;
}
.mo .work-shot[data-mo-shot].is-in > .mob { opacity: 1; translate: 0 0; }

/* ---- the old price gets struck through by a drawn line ---- */
.mo .hero-price .was { text-decoration: none; position: relative; }
.mo .hero-price .was::after {
  content: ""; position: absolute; left: -2px; right: -2px; top: 52%; height: 1.5px;
  background: rgba(224, 145, 60, 0.85);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform 0.7s var(--mo-ease) 0.95s;
}
.mo .hero-price.is-in .was::after { transform: scaleX(1); }

/* ---- add-on previews settle in slightly after their card ---- */
.mo .menu .item[data-mo] .pv { transform: scale(0.94); opacity: 0.4; transition: transform 1s var(--mo-ease), opacity 1s var(--mo-ease); transition-delay: calc(var(--mo-delay, 0s) + 0.15s); }
.mo .menu .item[data-mo].is-in .pv { transform: none; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .mo-progress { display: none; }
}
