/* =====================================================================
   AI-abled motion layer  (motion.css + motion.js)
   ---------------------------------------------------------------------
   THE IDEA. The whole company sells measurement that resolves an unknown
   into a confident answer. So nothing here drifts, floats or fades in
   softly: elements ARRIVE, hard-edged, and settle once. That is why the
   entrance material is clip-path (a wipe against the neo-brutalist
   border) rather than the usual opacity-and-rise, and why the easing is
   ease-out-expo, which decelerates hard and stops with conviction.

   THE RULE THAT SHAPES THE FILE. Content is visible by DEFAULT. Every
   animation is opt-in behind `html.mo`, a class motion.js adds only when
   it has confirmed the browser can animate and the visitor has not asked
   it not to. If the script never runs, never loads, or the visitor sets
   prefers-reduced-motion, the page renders complete and readable, with
   movement removed and only a short opacity fade kept: fewer and gentler,
   not zero. The previous inline reveal did the
   opposite (it set opacity:0 from JS and waited for an observer), which
   means a headless renderer or a failed script left whole sections
   blank. Never gate content on a transition.

   SCOPE. Every selector is prefixed .mo- or sits under html.mo, so this
   file cannot alter the existing design when the layer is off.
   ===================================================================== */

:root{
  /* Decelerating curves only. No bounce, no elastic: they draw attention
     to the animation instead of the content. */
  --mo-out: cubic-bezier(0.16, 1, 0.3, 1);      /* expo, the signature */
  --mo-fast: 150ms;   /* press + hover feedback, inside the 100-160ms band */
  --mo-base: 340ms;   /* reveals. Was 420ms, which is over the 300ms UI ceiling:
                         shorter genuinely reads as more responsive, and a reveal
                         the reader is scrolling past does not need the extra time. */
  --mo-slow: 480ms;   /* the hero overture only. Was 620ms and lingered. */
}

/* ---------------------------------------------------------------------
   1. REVEALS
   The from-state lives ONLY under html.mo, so it can never strand
   content. `.mo-in` is what the observer adds; the transition is on the
   base element so the return trip is defined too.
   ------------------------------------------------------------------ */
/* The reveal drives a CUSTOM PROPERTY, never `transform` directly.
   Writing `transform:none` on the settled state looked harmless and was
   not: `html.mo [data-mo].mo-in` scores (0,3,1) and outranks
   `.tbcard:hover` at (0,2,0), so the card hover lift silently stopped
   working the moment a card revealed. Owning a variable instead leaves
   `transform` free for hover, and any future hover, to claim.

   clip-path is likewise gone. It clips PAINT, not just layout, so it
   erased the hard offset shadow that carries this brand, and an
   un-rounded inset() against a 22px radius left a square shadow nub
   poking out of every card corner. The wipe is not worth the signature. */
html.mo [data-mo]{
  opacity: 0;
  /* `translate` and `scale` are INDEPENDENT properties, not shorthands for
     `transform`. The browser composes all three, so the page's own
     `.tbcard:hover{transform:translateY(-6px)}` keeps working untouched.
     Declaring `transform` here instead, at any specificity that beats a
     bare `.card:hover`, silently killed that hover: the first attempt at
     this file did exactly that, and moving the declaration from .mo-in to
     the base rule did not help, because the base rule outranks it too.
     The only real fix is to stop competing for the property at all. */
  translate: var(--mo-x, 0) var(--mo-y-in, 0);
  scale: var(--mo-s, 1);
  transition:
    opacity var(--mo-base) var(--mo-out) var(--mo-delay, 0ms),
    translate var(--mo-base) var(--mo-out) var(--mo-delay, 0ms),
    scale var(--mo-base) var(--mo-out) var(--mo-delay, 0ms);
  /* will-change is armed by .mo-arm just before the element reaches the
     viewport, not here. Declaring it on the base rule promoted every
     tagged element to its own compositor layer at load, on card-heavy
     pages dozens at once, none of them animating yet: exactly the memory
     pressure will-change exists to avoid. */
}
html.mo [data-mo].mo-in{ opacity: 1; --mo-x: 0px; --mo-y-in: 0px; --mo-s: 1; }
html.mo [data-mo].mo-arm{ will-change: opacity, translate; }

/* Differentiated per content type, which is the point: a stat band, an
   ordered sequence and a row of cards should not arrive the same way.
   Travel stays small; long travel reads as a slideshow. */
html.mo [data-mo="rise"]{ --mo-y-in: 18px; }
html.mo [data-mo="fall"]{ --mo-y-in: -14px; }
/* Cards enter laterally, across the reading direction, so a row of them
   reads as one motion rather than four things bobbing. */
html.mo [data-mo="wipe"]{ --mo-x: -22px; }
html.mo [data-mo="wipe-up"]{ --mo-y-in: 22px; }
/* The markers in a numbered sequence scale up in order. */
/* .92 rather than .82: nothing in the real world appears from almost nothing,
   and the harder pop drew attention to itself instead of to the content. */
html.mo [data-mo="pop"]{ --mo-s: .92; }

/* Stagger. Set --i on each sibling; the cap stops a long list turning
   into a queue the reader waits on. 10 x 55ms = 550ms worst case. */
html.mo [data-mo][style*="--i"]{ --mo-delay: calc(min(var(--i, 0), 9) * 45ms); }

/* ---------------------------------------------------------------------
   2. THE HERO OVERTURE
   One rehearsed entrance, played once, on the only screen where the
   visitor is not yet reading. Runs on load rather than on scroll: the
   hero is already in view, so an observer would fire instantly anyway.
   ------------------------------------------------------------------ */
html.mo [data-mo-hero]{
  opacity: 0;
  animation: moHero var(--mo-slow) var(--mo-out) forwards;
  /* 65ms, not 90ms: past roughly 80ms a stagger stops reading as one
     gesture and starts reading as items queuing up. This also puts the
     hero in the same rhythmic family as the 45ms body stagger. */
  animation-delay: calc(var(--i, 0) * 65ms);
}
@keyframes moHero{
  from{ opacity: 0; transform: translate3d(0, 20px, 0); }
  to  { opacity: 1; transform: none; }
}
/* The headline arrives with more travel and a touch of scale, so the
   biggest type on the page lands rather than fades. It used to use a
   clip-path wipe; that is gone for the same reason it left the cards.
   The animation is fill-mode:forwards, so the to-state persists for the
   life of the page, and a persisting clip-path clips PAINT: the lime
   highlight and any shadow behind the headline would be permanently
   cropped to the text box. A reveal must not leave a trap behind it. */
html.mo [data-mo-hero="head"]{ animation-name: moHeroHead; }
@keyframes moHeroHead{
  from{ opacity: 0; transform: translate3d(0, 26px, 0) scale(.985); }
  to  { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------------
   3. PRESS PHYSICS  (deliberately absent)
   Every page already defines its own, and they are TUNED: index.html uses
   .btn:active{transform:translate(3px,3px);box-shadow:2px 2px} against a
   6px shadow, so the surface travels almost exactly as far as the shadow
   retracts and the shadow's outer corner stays planted. That is what makes
   the button read as pressing INTO the page.

   This file previously shipped a generic .mo-press that loaded later at the
   same specificity, won the tie, and moved the surface 1px while the page's
   own rule still retracted the shadow 4px. The shadow detached and the
   button read as shrinking away from a sticker behind it: soft and rubbery,
   the opposite of the personality it was supposed to reinforce.

   The lesson is that a shared layer must not compete with a design system
   that already solved the problem per page. Reveals are what this file adds.
   ------------------------------------------------------------------ */

/* ---------------------------------------------------------------------
   5. NUMBER COUNT-UP
   Tabular figures stop the width jittering while the digits change,
   which is the thing that makes most count-ups look cheap.
   ------------------------------------------------------------------ */
html.mo [data-mo-count]{ font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------
   PRINT
   Printing does not scroll, so an observer-driven reveal never fires and
   whole sections print blank. Verified: a headless print before the
   failsafe dropped the entire stat band, the four-step journey and the
   toolbox cards out of the PDF. Print always shows everything.
   ------------------------------------------------------------------ */
@media print{
  html.mo [data-mo],
  html.mo [data-mo].mo-in,
  html.mo [data-mo-hero]{
    opacity: 1 !important;
    translate: none !important;
    scale: none !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }
}

/* ---------------------------------------------------------------------
   6. REDUCED MOTION
   Not a downgrade: the same page, arriving instantly. Everything is
   reset rather than merely sped up, so no from-state can survive.
   motion.js also refuses to add html.mo, so this is belt and braces.
   ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce){
  /* Reduced motion means FEWER AND GENTLER animations, not none. Killing
     everything was the wrong reading: a soft opacity fade carries no motion
     signal, cannot trigger vestibular discomfort, and still tells the reader
     that a new block has arrived. What has to go is MOVEMENT, so translate
     and scale are neutralised while the fade is kept and shortened. */
  html.mo [data-mo]{
    translate: none !important;
    scale: none !important;
    transform: none !important;
    transition: opacity 180ms var(--mo-out) !important;
  }
  html.mo [data-mo].mo-in{ opacity: 1 !important; }

  /* The hero keyframes move as well as fade, so they are replaced outright
     by a fade of the same shape rather than merely disabled. */
  html.mo [data-mo-hero]{
    /* opacity:1 !important is load-bearing. A CSS animation declaration
       outranks the base opacity AND any inline style, so without this the
       block's own !important animation defeated the JS hero failsafe: with
       a stalled clock (prerender, hidden tab, headless capture) the
       headline painted at opacity 0 with nothing left to rescue it. The
       @media print block above already asserts this; this one did not. */
    opacity: 1 !important;
    animation: moFadeOnly 180ms var(--mo-out) forwards !important;
    animation-delay: calc(var(--i, 0) * 40ms) !important;
    transform: none !important;
    clip-path: none !important;
  }
  @keyframes moFadeOnly{ from{ opacity: 0 } to{ opacity: 1 } }

}
