/* ==========================================================================
   Iryst — motion layer
   --------------------------------------------------------------------------
   Hand-written on purpose. Tailwind only emits classes it can see in source
   files and it cannot scan .cshtml, so every class here is written by hand and
   this file is NOT generated by sync-mvc-css.mjs. Nothing in the Next build
   needs to change for any of it.

   Everything is gated behind `html.js-motion`, which an inline script in
   _Layout.cshtml adds before first paint and removes again if motion.js has
   not signalled ready within 3 seconds. That means:
     - JavaScript disabled  -> class never added -> page renders fully visible
     - motion.js fails      -> failsafe removes it -> page renders fully visible
     - reduced motion       -> class never added -> page renders fully visible
   Content is never left hidden by a script that did not run.

   Direction: precise and engineered, not playful. Things draw, sweep, align
   and count. Nothing bounces.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Motion tokens — deliberately a small set so the whole site shares a rhythm.
   Durations sit alongside the site's existing --dur-* scale in iryst.css.
   -------------------------------------------------------------------------- */
:root {
  --mo-rise: 620ms;
  --mo-sweep: 720ms;
  --mo-quick: 260ms;
  --mo-ease: cubic-bezier(.16, 1, .3, 1);   /* strong ease-out, arrives settled */
  --mo-ease-soft: cubic-bezier(.4, 0, .2, 1);
  --mo-stagger: 70ms;
}

/* --------------------------------------------------------------------------
   1. Reveal primitives
   iryst.css already ships `.js-motion [data-reveal]{opacity:0}` — 454 hooks
   across 21 views are already in the markup. These add the movement.
   -------------------------------------------------------------------------- */
.js-motion [data-reveal] {
  opacity: 0;
  will-change: opacity, transform;
}

/* Cleared once the element has arrived, so long pages do not hold hundreds of
   composited layers alive. */
.js-motion [data-reveal].mo-in {
  opacity: 1;
  will-change: auto;
}

/* --------------------------------------------------------------------------
   2. Headline word mask
   Each word sits in its own overflow-hidden box and rises out of it. Word
   boxes are inline-block so the heading still wraps naturally at any width.
   -------------------------------------------------------------------------- */
.mo-w {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* Descenders (g, y, p) would be clipped by overflow-hidden, so the box is
     grown downwards and pulled back with a negative margin. */
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
.mo-w > i {
  display: inline-block;
  font-style: inherit;
}
/* The starting offset MUST be gated behind .js-motion. Ungated, dropping the
   gate — the failsafe, or a mid-session switch to reduced motion — would leave
   every split headline parked below its mask and permanently invisible. The
   words are already in the DOM by then, so the gate is the only thing that can
   put them back. */
.js-motion .mo-w > i {
  transform: translateY(110%);
  will-change: transform;
}
.js-motion .mo-head.mo-in .mo-w > i {
  transform: none;
  will-change: auto;
  transition: transform var(--mo-sweep) var(--mo-ease);
  transition-delay: var(--mo-d, 0ms);
}
/* The heading itself must not stay at opacity 0 once split — the words carry
   the animation from here. */
.js-motion .mo-head[data-reveal] { opacity: 1; }

/* --------------------------------------------------------------------------
   3. Scroll + navigation progress
   One hairline serves both: it tracks scroll depth, and on an internal link
   click it detaches and runs forward as a navigation indicator. Full page
   loads otherwise give no feedback at all between click and paint.
   -------------------------------------------------------------------------- */
.mo-progress {
  position: fixed;
  inset-inline: 0;
  top: 0;
  height: 2px;
  z-index: 60;
  background: linear-gradient(90deg, var(--primary), var(--teal));
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
  will-change: transform;
}
.mo-progress.mo-nav {
  transition: transform 1.4s cubic-bezier(.2, .8, .3, 1);
  opacity: 1;
}

/* --------------------------------------------------------------------------
   4. Pointer spotlight on dark surface cards
   A soft brand-warm wash that follows the cursor. Reads as depth on the near
   black background without adding a border or a shadow.
   -------------------------------------------------------------------------- */
.mo-spot { position: relative; }
.mo-spot::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    22rem circle at var(--mo-x, 50%) var(--mo-y, 0%),
    color-mix(in oklab, var(--primary) 13%, transparent),
    transparent 62%
  );
  opacity: 0;
  transition: opacity var(--mo-quick) var(--mo-ease-soft);
  pointer-events: none;
  z-index: 0;
}
.mo-spot:hover::before,
.mo-spot:focus-within::before { opacity: 1; }
/* Keep real content above the wash. */
.mo-spot > * { position: relative; z-index: 1; }

/* --------------------------------------------------------------------------
   5. Rule draw
   Hairlines that draw themselves left to right. Used on the process list rows
   and on section eyebrows — the "blueprint" gesture of the whole system.
   -------------------------------------------------------------------------- */
.mo-rule { position: relative; }
.mo-rule::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 1px;
  width: 100%;
  background: linear-gradient(90deg, var(--primary), transparent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--mo-sweep) var(--mo-ease);
  transition-delay: 160ms;
  pointer-events: none;
}
.mo-rule.mo-in::after { transform: scaleX(1); }

/* The small monospace eyebrow gets a short leading rule that draws in. */
.js-motion .mo-eyebrow { position: relative; }
.js-motion .mo-eyebrow::before {
  content: "";
  display: inline-block;
  width: 1.75rem;
  height: 1px;
  margin-right: .6rem;
  vertical-align: middle;
  background: currentColor;
  opacity: .5;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--mo-quick) var(--mo-ease);
}
.js-motion .mo-eyebrow.mo-in::before { transform: scaleX(1); }

/* --------------------------------------------------------------------------
   6. Stat counters
   The hero figures count up; the grid's hairline borders sweep open first so
   the numbers land into a finished frame rather than an empty one.
   -------------------------------------------------------------------------- */
.mo-stats {
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--mo-sweep) var(--mo-ease);
}
.mo-stats.mo-in { clip-path: inset(0 0 0 0); }

/* --------------------------------------------------------------------------
   7. Chip cascade
   The stack section's pills deal in quickly, like cards off a deck.
   -------------------------------------------------------------------------- */
.js-motion .mo-chip {
  opacity: 0;
  transform: translateY(10px) scale(.97);
}
.js-motion .mo-chip.mo-in {
  opacity: 1;
  transform: none;
  transition: opacity var(--mo-quick) var(--mo-ease),
              transform var(--mo-quick) var(--mo-ease);
  transition-delay: var(--mo-d, 0ms);
}

/* --------------------------------------------------------------------------
   8. FAQ disclosure
   <details> snaps open natively. This gives it a measured height transition
   without taking the element away from the browser's own semantics.
   -------------------------------------------------------------------------- */
/* Applied only for the length of the open animation. The panel is otherwise
   completely untouched, so a details element whose animation never runs still
   behaves like plain HTML. */
.mo-panel.mo-clip { overflow: hidden; }

/* --------------------------------------------------------------------------
   9. Page entrance
   A short rise on <main> so a full Razor page load does not simply appear.
   -------------------------------------------------------------------------- */
.js-motion .mo-page {
  opacity: 0;
  transform: translateY(10px);
}
.js-motion .mo-page.mo-in {
  opacity: 1;
  transform: none;
  transition: opacity 500ms var(--mo-ease), transform 500ms var(--mo-ease);
}

/* --------------------------------------------------------------------------
   10. Magnetic button
   JS writes the offset; the transition here smooths the release.
   -------------------------------------------------------------------------- */
.mo-magnet { transition: transform var(--mo-quick) var(--mo-ease); }
.mo-magnet.mo-pulling { transition: none; }

/* --------------------------------------------------------------------------
   11. Parallax
   -------------------------------------------------------------------------- */
.js-motion [data-parallax] { will-change: transform; }

/* --------------------------------------------------------------------------
   Reduced motion
   The gate class is never added when reduce is set, so most of the above is
   already inert. This is the belt-and-braces pass for anything that might be
   applied before the preference is read, and for a mid-session change.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .js-motion [data-reveal],
  .js-motion .mo-chip,
  .js-motion .mo-page {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
  }
  .mo-w > i { transform: none !important; }
  .mo-stats { clip-path: none !important; }
  .mo-rule::after,
  .js-motion .mo-eyebrow::before { transform: scaleX(1) !important; }
  .js-motion [data-parallax] { transform: none !important; }
  .mo-progress { display: none; }
  .mo-magnet { transition: none; }
  * { scroll-behavior: auto !important; }
}
