/* ===========================================================================
   Municorn design system · 4 · motion
   ---------------------------------------------------------------------------
   The point of motion is that the page builds itself: parts arrive in install
   order, then it is still.

   Two rules hold this file together.

   1. Every authored offset is multiplied by var(--motion). Calm mode sets
      --motion:0, which zeroes the movement and leaves the order and timing
      intact. Anything that loops or travels is switched off explicitly at the
      bottom of the file. js/*.js multiplies its own offsets by MU.motion.scale
      for the same reason.

   2. Nothing is invisible at rest without a guaranteed reveal. Two gates carry
      that: html.js (set in the boot script, so anything hidden is hidden only
      when JavaScript is running) and html.anime (added by js/motion.js only once
      the animation library has actually loaded, so the parts js/build.js
      choreographs are never hidden by a library that never arrived). Pages that
      load no build script — class B — keep .rise and lose nothing.

   The OS reduced-motion flag is deliberately not the switch — see js/motion.js.
   =========================================================================== */

/* Registering the animated custom properties keeps interpolation numeric and
   lets the browser skip a full style recalc on change. */
@property --rw{syntax:'<number>';inherits:false;initial-value:1}
@property --ro{syntax:'<number>';inherits:false;initial-value:0}
@property --fill{syntax:'<number>';inherits:false;initial-value:1}

html.calm{--motion:0}

@keyframes fadein{from{opacity:0}to{opacity:1}}
@keyframes fade{
  from{opacity:0;transform:translateY(calc(9px * var(--motion)))}
  to{opacity:1;transform:none}
}
@keyframes slideIn{
  from{opacity:0;transform:translateX(calc(-12px * var(--motion)))}
  to{opacity:1;transform:none}
}

/* === hero =================================================================
   The masked lines rise; the logo, the description and the button follow. */
.hero h1 .l>i{animation:up 1.1s var(--ease-reveal) .15s both}
.hero h1 .l.it>i{animation-delay:.29s}
@keyframes up{
  from{transform:translateY(calc(112% * var(--motion)))}
  to{transform:translateY(0)}
}
.hero__logo{opacity:0;animation:fade .8s var(--ease-reveal) .3s both}
.hero__low{animation:fade .8s var(--ease-reveal) .85s both}
.hero__desc{opacity:0;animation:slideIn .8s var(--ease-reveal) .95s both}

/* === the wall goes up =====================================================
   Courses lay in from alternating sides, top down, with a brass flash as each
   joint sets. No library: it plays even if the CDN never answers.
   While the wall is going up the base is a dark bed rather than bare mortar —
   mortar beige would read as a pale flash on load. Once built the base is plain
   mortar, since the courses cover it completely and a second fixed copy of the
   wall underneath would fight the drift. */
.js .hero__bg::before{background-image:none;background-color:var(--bed)}
.js .hero__bg.built::before{background-image:none;background-color:var(--mortar)}
.crs--lay{animation:lay .72s var(--ease-set) var(--d) both}
.crs--lay::after{animation:setjoint .5s linear calc(var(--d) + .40s) both}
@keyframes lay{
  from{transform:translateX(calc(var(--from) * var(--motion)))}
  to{transform:translateX(0)}
}
@keyframes setjoint{0%{opacity:0}22%{opacity:.85}100%{opacity:0}}

/* Ambient loop: the course drift. Once the wall is up every course drifts a couple of
   pixels on its own long cycle, so the running bond shears and closes
   continuously. Compositor-only transforms, paused whenever the hero is off
   screen. The cycle starts and ends at zero so a course picks the drift up from
   exactly where the build left it, and courses desynchronise through differing
   durations and a short positive delay rather than a negative one, which would
   drop them mid-cycle. */
.crs--drift{animation:drift var(--dur) ease-in-out var(--del) infinite}
@keyframes drift{
  0%,100%{transform:translate3d(0,0,0)}
  25%{transform:translate3d(var(--amp),0,0)}
  75%{transform:translate3d(calc(var(--amp) * -1),0,0)}
}
.courses--paused .crs--drift{animation-play-state:paused}

/* === ticker ===============================================================
   A marquee, not a reveal: it is metadata, and it pauses on hover. */
.tick__t{animation:mq 42s linear infinite}
.tick:hover .tick__t,.tick--paused .tick__t{animation-play-state:paused}
@keyframes mq{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* === generic reveals ======================================================
   .rise for a block, .stag for its children in sequence. Both driven by the
   shared observer in js/reveal.js, both caught by its scroll sweep. */
.js .rise{opacity:0;transform:translateY(calc(16px * var(--motion)));
  transition:opacity .75s var(--ease-reveal),transform .75s var(--ease-reveal)}
.js .rise.in{opacity:1;transform:none}
.js .stag>*{opacity:0;transform:translateY(calc(14px * var(--motion)));
  transition:opacity .65s var(--ease-reveal),transform .65s var(--ease-reveal)}
.js .stag.in>*{opacity:1;transform:none}
.js .stag.in>*:nth-child(2){transition-delay:.08s}
.js .stag.in>*:nth-child(3){transition-delay:.16s}
.js .stag.in>*:nth-child(4){transition-delay:.24s}
.js .stag.in>*:nth-child(5){transition-delay:.32s}
.js .stag.in>*:nth-child(6){transition-delay:.4s}

/* === start states for the choreographed parts =============================
   Gated on html.anime, not html.js: js/build.js is the only thing that animates
   these to rest, so if anime.js never loads they must never have been hidden.
   The offsets are written here as well as there so the first paint matches the
   first frame. */
.anime .r{opacity:0;--rw:0}
.anime .dm,.anime .dm u{opacity:0}
.anime .win__lintel{opacity:0;transform:scaleX(calc(1 - .78 * var(--motion)));
  transform-origin:center}
.anime .win__casing{opacity:0;
  transform:translateY(calc(-9px * var(--motion))) scale(calc(1 - .015 * var(--motion)))}
.anime .win__glass img{--fill:0;transform:scale(calc(1 + .07 * var(--motion)))}
.anime .win__sill,.anime .win__apron{opacity:0;transform:scaleX(calc(1 - .82 * var(--motion)))}
.anime .win__cap{opacity:0;transform:translateY(calc(13px * var(--motion)))}

/* one-shot glint the moment a pane is glazed */
@keyframes glint{from{left:-70%}to{left:140%}}
.win--glint .win__glass::after{transition:none;animation:glint 1.05s cubic-bezier(.32,0,.2,1)}

/* === calm mode ============================================================
   --motion:0 has already removed every offset above. What remains is switching
   off the two ambient loops and the travelling highlights, and restoring a
   plain cross-fade where the movement was the whole reveal. */
html.calm .crs--drift{animation:none}
html.calm .tick__t{animation:none}
html.calm .win__glass::after{display:none}
html.calm .win--glint .win__glass::after{animation:none}
html.calm .crs--lay{animation:fadein .5s linear var(--d) both}
html.calm .crs--lay::after{animation:none}
html.calm .hero h1 .l>i{animation:fadein .7s linear .15s both}
