/* ═══════════════════════════════════════════════════════════════════════════
   css/motion.css — the mobile motion layer. design/mobile-motion.md §B + the
   binding critic amendments. Owner: the engine (js/app.js). Loaded async with a
   <noscript> twin; every from-state below is inert unless js/app.js has set
   <html data-motion="on">, so a no-JS, late-CSS or reduced-motion page is always
   the final, fully visible state.

   ───────────────────────────────────────────────────────────────────────────
   THE ATTRIBUTE CONTRACT (what section markup may use — nothing else exists)
   ───────────────────────────────────────────────────────────────────────────
   ROOT (engine writes, read-only for everyone):
     html[data-motion="on"]   engine armed; every from-state is keyed off this.
                              Removed on any failure / reduced motion.
                              document fires `minetly:motion` {on} on every change.
     html[data-m-lite]        set ONCE at arm on saveData / 2g / <=2 cores. Gate
                              every scroll-linked rule on :not([data-m-lite]),
                              NEVER on data-tier (the §7 destroy flips data-tier).
     html[data-press="on"]    press feedback enabled.

   TRIGGERED REVEALS (text, prices, controls — once, time-based):
     data-m="fade"    opacity 0→1, y 12px→0, 350ms ease-out. Default for copy.
     data-m="rise"    opacity 0→1, y 24px→0, 500ms ease-out. Groups, rails, CTAs.
     data-m="card"    opacity 0→1, y 16px + scale .94→1, 600ms expo ("focus pull").
     data-m="lines"   word-mask rise. Latin Bebas display lines ONLY, <=14 words,
                      text-only (<br> allowed). The engine REFUSES and falls back
                      to fade on Tamil (U+0B80-0BFF), lang="ta*", element
                      children, >14 words or background-clip:text. Split by
                      WORD only; accessible text kept in .m-sr.
     data-m="roll"    whole-element mask (one wrapper span, never split). Refused
                      -> fade on Tamil and on flex/grid hosts.
     data-m="clip"    image develop: clip-path inset(10% 14%)→0 and the first
                      direct child <img> scale 1.12→1, 800ms expo. Max 2 per page.
                      The engine waits for that <img> to decode (<=1500ms), so it
                      never develops an empty box.
     data-m="pop"     opacity 0→1, translate(var(--m-pop-x),var(--m-pop-y))
                      scale .72→1, 560ms spring, origin var(--m-origin).
     data-m-delay="ms"      0-600 (a .cta is clamped to 200).
     data-m-start="px"      trigger line above the viewport bottom. Default 112.
     data-m-follow="sel|parent|prev"  not observed; reveals when its leader does
                      (+ its own delay). A selector resolves inside the nearest
                      section/header/footer/[data-m-scope]. Invalid -> observed.
     data-m-scope     bounds data-m-follow selector lookup.

   GROUPS:
     data-m-group="fade|rise|card|pop"   container never hides; each descendant
                      [data-m-item] is observed on its own and inherits the
                      effect. Items entering in the same callback stagger 80ms in
                      DOM order, index capped at 7.
     data-m-item      group member; may carry its own data-m.
     data-m-stagger="ms"   30-120.   data-m-order="grid"   (row+col) wave.

   SCROLL-LINKED (decorative only: aria-hidden or alt="", never text/controls,
   no overflow:hidden ancestor — use overflow:clip):
     data-m-scrub="dolly"    scale 1.10→1 over cover 0%→60%.
     data-m-scrub="lights"   opacity 1→0 over entry. Rest state opacity 0.
     (data-m-depth and data-m-scrub="settle" are WITHDRAWN — amendment 22.)

   NUMBERS / CTAS / TOUCH:
     data-countup       counts once on first entry; width locked, tabular-nums.
     data-m-shimmer="enter|load|rise"   one light pass; .cta ONLY; one at a time,
                        >=4s apart, never looping.
     data-m-press       press feedback (scale .97) on a non-.cta tappable.

   WRITTEN BY THE ENGINE (never author these):
     data-m-armed, data-m-in, data-m-instant, data-m-done, data-m-shake,
     data-m-shimmer-run, style="--m-delay / --m-word", spans .m-sr .m-split .m-w
     .m-wi .m-roll. Event `minetly:reveal` {effect, instant} bubbles from each
     revealed element. window.MINETLY.motion = { reveal(el), refresh() }.

   PROPERTY LANES (one property, one owner):
     opacity + transform ... triggered reveals      scale ... press, dolly
     rotate ............... section static tilts    translate ... section layout
     clip-path ............ data-m="clip", §6 leaders
     So: NEVER declare `transform` on an element carrying data-m (use rotate /
     translate / scale). While an element is armed and not yet data-m-done the
     shared transition list below replaces the section's own `transition`; at
     data-m-done (reveal + duration + 100ms) the section's own list applies again.

   §8 INSTRUCTOR — the lead's contract (amendment 21 wins over §C.08):
     <figure class="teacher__figure" data-m-scope>          (no data-reveal)
       <div class="teacher__frame" data-m="clip" data-m-start="140">  overflow:clip
         <img class="teacher__photo" …>                      first direct child
       </div>
       <a class="teacher__reel teacher__reel--a" href="#showcase" data-m="pop"
          data-m-follow=".teacher__frame" data-m-delay="260"
          style="--m-pop-x:-18px;--m-pop-y:18px;--m-origin:0% 100%">   top-right
       <a class="teacher__reel teacher__reel--b" … data-m-delay="380"
          style="--m-pop-x:18px;--m-pop-y:-18px;--m-origin:100% 0%">   bottom-left
       <a class="teacher__handle" … data-m="rise" data-m-follow=".teacher__frame"
          data-m-delay="560" data-m-press>    centred with translate:-50% 50%
     No transform on reels/handle (tilt = rotate), no data-m-depth, no countup.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ── engine-owned structure (all modes: the split must read the same with or
      without motion, and the tilt pivot is layout, not motion) ─────────────── */
.m-sr{
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.m-w{
  display: inline-block;
  vertical-align: top;
  padding-bottom: .08em;
  margin-bottom: -.08em;
}
.m-wi{ display: inline-block; }
[data-m="pop"]{ transform-origin: var(--m-origin, 50% 50%); }


@media (prefers-reduced-motion: no-preference){

  /* ── triggered reveals: from-states ─────────────────────────────────── */
  [data-motion="on"] [data-m-armed]:not([data-m-in]){ opacity: 0; }
  [data-motion="on"] [data-m-armed][data-m="fade"]:not([data-m-in]){ transform: translate(0,var(--mo-d2)); }
  [data-motion="on"] [data-m-armed][data-m="rise"]:not([data-m-in]){ transform: translate(0,var(--mo-d3)); }
  [data-motion="on"] [data-m-armed][data-m="card"]:not([data-m-in]){ transform: translate(0,16px) scale(var(--mo-scale-in)); }
  [data-motion="on"] [data-m-armed][data-m="pop"]:not([data-m-in]){
    transform: translate(var(--m-pop-x,0),var(--m-pop-y,0)) scale(.72); }
  [data-motion="on"] [data-m-armed][data-m="clip"]:not([data-m-in]){
    opacity: 1; clip-path: inset(10% 14% 10% 14% round var(--r-lg)); }
  [data-motion="on"] [data-m-armed][data-m="clip"]:not([data-m-in]) > img{ transform: scale(1.12); }
  [data-motion="on"] [data-m-armed][data-m="lines"]:not([data-m-in]),
  [data-motion="on"] [data-m-armed][data-m="roll"]:not([data-m-in]){ opacity: 1; }
  /* 105% of the word, plus the mask's own .16em bottom allowance (padding
     + clip margin) so no sliver of a cap peeks under the line before it rises. */
  [data-motion="on"] [data-m-armed][data-m="lines"]:not([data-m-in]) .m-wi{ transform: translate(0,calc(105% + .16em)); }
  [data-motion="on"] [data-m-armed][data-m="roll"]:not([data-m-in]) .m-roll{ transform: translate(0,100%); }

  /* clip end state (amendment 5): inset() → inset(0) interpolates; → none would snap. */
  [data-motion="on"] [data-m-armed][data-m="clip"][data-m-in]:not([data-m-done]){
    clip-path: inset(0 0 0 0 round var(--r-lg)); }

  /* ── per-effect parameters ─────────────────────────────────────────────── */
  [data-m="fade"]{ --m-dur: var(--mo-base);   --m-ease: var(--ease-out); }
  [data-m="rise"]{ --m-dur: var(--mo-slow);   --m-ease: var(--ease-out); }
  [data-m="card"]{ --m-dur: var(--mo-card);   --m-ease: var(--ease-expo); }
  [data-m="pop"] { --m-dur: var(--mo-spring); --m-ease: var(--ease-spring); }

  /* ── ONE transition list per armed element (reveal + press lane + colours).
     Applies only until data-m-done, so a finished reveal never keeps its delay. */
  [data-motion="on"] [data-m-armed]:not([data-m-done]){
    transition: opacity   var(--m-dur,var(--mo-base)) var(--m-ease,var(--ease-out)) var(--m-delay,0ms),
                transform var(--m-dur,var(--mo-base)) var(--m-ease,var(--ease-out)) var(--m-delay,0ms),
                scale     var(--m-press-t,var(--mo-spring)) var(--m-press-e,var(--ease-spring)),
                background-color var(--mo-quick) var(--ease-out),
                border-color var(--mo-quick) var(--ease-out),
                color var(--mo-quick) var(--ease-out); }

  /* CINEMA only: §8.1 blur, `card` only (amendment 19: a full list, not
     transition-property, so durations stay paired). */
  [data-tier="cinema"][data-motion="on"] [data-m-armed][data-m="card"]:not([data-m-in]){ filter: blur(6px); }
  [data-tier="cinema"][data-motion="on"] [data-m-armed][data-m="card"]:not([data-m-done]){
    transition: opacity   var(--mo-card) var(--ease-expo) var(--m-delay,0ms),
                transform var(--mo-card) var(--ease-expo) var(--m-delay,0ms),
                filter    var(--mo-card) var(--ease-expo) var(--m-delay,0ms),
                scale     var(--m-press-t,var(--mo-spring)) var(--m-press-e,var(--ease-spring)),
                background-color var(--mo-quick) var(--ease-out),
                border-color var(--mo-quick) var(--ease-out),
                color var(--mo-quick) var(--ease-out); }

  /* lines: words rise inside their own masks, line by line */
  [data-motion="on"] [data-m-armed][data-m="lines"]:not([data-m-done]) .m-w{
    overflow: clip; overflow-clip-margin: .08em; }
  [data-motion="on"] [data-m-armed][data-m="lines"]:not([data-m-done]) .m-wi{
    transition: transform var(--mo-mask) var(--ease-expo)
      calc(var(--m-delay,0ms) + var(--l,0) * var(--mo-line) + var(--i,0) * var(--m-word, var(--mo-word))); }

  /* roll: the whole content rises inside the host */
  [data-motion="on"] [data-m-armed][data-m="roll"]:not([data-m-done]){ overflow: clip; }
  [data-motion="on"] [data-m-armed][data-m="roll"]:not([data-m-done]) .m-roll{
    transition: transform var(--mo-card) var(--ease-expo) var(--m-delay,0ms); }

  /* clip: the frame develops, the photo racks focus */
  [data-motion="on"] [data-m-armed][data-m="clip"]:not([data-m-done]){
    transition: clip-path var(--mo-clip) var(--ease-expo) var(--m-delay,0ms),
                scale     var(--m-press-t,var(--mo-spring)) var(--m-press-e,var(--ease-spring)); }
  [data-motion="on"] [data-m-armed][data-m="clip"]:not([data-m-done]) > img{
    transition: transform var(--mo-clip) var(--ease-expo) var(--m-delay,0ms); }

  /* revealed by a failsafe / in view at arm: no transition, for two frames */
  [data-motion="on"] [data-m-instant],
  [data-motion="on"] [data-m-instant] :is(.m-wi,.m-roll),
  [data-motion="on"] [data-m-instant][data-m="clip"] > img{ transition: none !important; }

  /* ── press (lane: scale). :active only swaps the variables, never the list.
     Pressables that are not mid-reveal get this list (amendment 18: it carries
     the colour transitions base.css .cta declares, so none are lost). ─────── */
  [data-press="on"] :is(.cta, [data-m-press]):not([data-m-armed]:not([data-m-done])){
    transition: scale var(--m-press-t,var(--mo-spring)) var(--m-press-e,var(--ease-spring)),
                background-color var(--mo-quick) var(--ease-out),
                border-color var(--mo-quick) var(--ease-out),
                color var(--mo-quick) var(--ease-out),
                /* lead, §8: the reel cards straighten their resting tilt on hover
                   (rotate lane). No other pressable rotates, so this is inert elsewhere. */
                rotate var(--mo-base) var(--ease-out); }
  [data-press="on"] :is(.cta, [data-m-press]):active{
    scale: var(--mo-scale-press); --m-press-t: var(--mo-instant); --m-press-e: var(--ease-out); }

  /* ── shimmer: one-shot, .cta only (amendment 23). The engine sets
     data-m-shimmer-run and removes it on animationend. ───────────────────── */
  [data-motion="on"] .cta[data-m-shimmer]{ position: relative; overflow: clip; isolation: isolate; }
  [data-motion="on"] .cta[data-m-shimmer]::before{
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    border-radius: inherit;
    background: linear-gradient(105deg, transparent 38%, rgba(255,255,255,.26) 50%, transparent 62%);
    translate: -120% 0; }
  [data-motion="on"] .cta[data-m-shimmer-run]::before{
    animation: m-shimmer var(--mo-shimmer) var(--ease-in-out) 1 both; }
  @keyframes m-shimmer{ from{ translate: -120% 0; } to{ translate: 120% 0; } }

  /* ── invalid-field shake (engine sets data-m-shake, removes on animationend) */
  [data-motion="on"] [data-m-shake]{ animation: m-shake 320ms var(--ease-out) 1; }
  @keyframes m-shake{ 0%,100%{ transform: none; } 20%{ transform: translateX(-6px); }
                      40%{ transform: translateX(6px); } 60%{ transform: translateX(-4px); }
                      80%{ transform: translateX(4px); } }
}


/* ── scroll-linked decoration: compositor-driven, native only. No JS fallback:
      the rest state IS the final state. Gated on data-m-lite (set once at arm),
      never on data-tier (amendment 1). ────────────────────────────────────── */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    [data-motion="on"]:not([data-m-lite]) [data-m-scrub="dolly"]{
      animation: m-dolly linear both; animation-timeline: view(); animation-range: cover 0% cover 60%; }
    [data-motion="on"]:not([data-m-lite]) [data-m-scrub="lights"]{
      animation: m-lights linear both; animation-timeline: view(); animation-range: entry 0% entry 100%; }
    @keyframes m-dolly { from{ scale: 1.10; } to{ scale: 1; } }
    @keyframes m-lights{ from{ opacity: 1; } to{ opacity: 0; } }
  }
}

/* rest state for scrubbed decoration when nothing animates */
[data-m-scrub="lights"]{ opacity: 0; }

@media print{
  [data-m-armed], [data-m-armed] :is(.m-wi,.m-roll), [data-m-armed] > img{
    opacity: 1 !important; transform: none !important; clip-path: none !important; }
}
