/* =========================================================================
   Fiumara Motion System — motion.css
   Shared motion layer for every Fiumara Designs site (main + demos + client).
   Pairs with assets/motion.js. No build step, no framework, no runtime deps.

   USAGE
   -----
   <link rel="stylesheet" href="assets/motion.css">   (after the site stylesheet)
   <script src="assets/vendor/lenis.min.js" defer></script>
   <script src="assets/motion.js" defer></script>

   SCROLL REVEALS   add a variant class to any element
     .m-fade-up  .m-fade-in  .m-clip  .m-scale-in
     Stagger children:  <div data-m-stagger> … </div>   (children 1-8 auto-offset)
     Tune per element:  style="--m-i: 3"  to force a stagger index.

   PARALLAX          .m-parallax + a depth class
     .m-depth-1 (subtle) … .m-depth-4 (strong). translate only, never
     background-attachment: fixed.

   PINNED SECTIONS   <div class="m-pin-track"> <div class="m-pin"> … </div> </div>
     The track publishes a view-timeline named --m-pin; any descendant can
     drive an animation from it via `animation-timeline: --m-pin`.
     Set track length with --m-pin-length (default 300vh).

   TEXT REVEAL       <h1 class="m-split"> … </h1>
     motion.js splits it into lines and animates each line out of a mask.
     Without JS the heading is plain, fully visible text.

   MICRO-INTERACTION .m-lift  .m-zoom-frame (with .m-zoom-img inside)
                     .m-underline  .m-press
   LOAD CHOREOGRAPHY .m-enter on hero children; runs once on load.

   SAFETY CONTRACT (do not break this)
   -----------------------------------
   Every element is FULLY VISIBLE in the base cascade. Motion is only ever
   ADDED, inside @supports + @media (prefers-reduced-motion: no-preference).
   So: no JS, old browser, script error, or reduced-motion  =>  static,
   complete, attractive page. Never author a bare `opacity: 0` in this file.
   ========================================================================= */

:root {
  /* Easing tokens */
  --m-ease-out: cubic-bezier(0.16, 1, 0.3, 1);          /* expo out, primary */
  --m-ease-out-soft: cubic-bezier(0.25, 1, 0.5, 1);     /* quart out */
  --m-ease-in-out: cubic-bezier(0.76, 0, 0.24, 1);      /* quart in-out */
  --m-ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);    /* slight overshoot */

  /* Duration tokens */
  --m-dur-fast: 160ms;
  --m-dur-base: 300ms;
  --m-dur-slow: 620ms;
  --m-dur-reveal: 900ms;

  /* Reveal geometry */
  --m-shift: 26px;   /* fade-up travel */
  --m-stagger: 90ms; /* per-item delay on the JS fallback path */
}

/* =========================================================================
   1. REVEALS — base state is visible. Motion added conditionally below.
   ========================================================================= */

/* Stagger index: pure CSS, so both the native and fallback paths share it. */
[data-m-stagger] > *  { --m-i: 0; }
[data-m-stagger] > *:nth-child(2) { --m-i: 1; }
[data-m-stagger] > *:nth-child(3) { --m-i: 2; }
[data-m-stagger] > *:nth-child(4) { --m-i: 3; }
[data-m-stagger] > *:nth-child(5) { --m-i: 4; }
[data-m-stagger] > *:nth-child(6) { --m-i: 5; }
[data-m-stagger] > *:nth-child(7) { --m-i: 6; }
[data-m-stagger] > *:nth-child(8) { --m-i: 7; }

@keyframes m-fade-up {
  from { opacity: 0; transform: translate3d(0, var(--m-shift), 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
@keyframes m-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes m-scale-in {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes m-clip {
  from { opacity: 1; clip-path: inset(0 100% 0 0); }
  to   { opacity: 1; clip-path: inset(0 0 0 0); }
}

/* ---- Native path: CSS scroll-driven animation, no JS, GPU-composited ---- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    html:not(.m-observe) :is(.m-fade-up, .m-fade-in, .m-scale-in, .m-clip) {
      animation-duration: 1ms; /* ignored on a scroll timeline; keeps Firefox happy */
      animation-timing-function: var(--m-ease-out);
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry calc(var(--m-i, 0) * 8%) entry calc(62% + var(--m-i, 0) * 8%);
      will-change: opacity, transform;
    }
    html:not(.m-observe) .m-fade-up   { animation-name: m-fade-up; }
    html:not(.m-observe) .m-fade-in   { animation-name: m-fade-in; }
    html:not(.m-observe) .m-scale-in  { animation-name: m-scale-in; }
    html:not(.m-observe) .m-clip      { animation-name: m-clip; animation-timing-function: var(--m-ease-in-out); }
  }
}

/* ---- Fallback path: IntersectionObserver. `.m-observe` is only ever set by
   motion.js, and only when native support is absent AND motion is allowed. -- */
html.m-observe :is(.m-fade-up, .m-fade-in, .m-scale-in, .m-clip) {
  transition:
    opacity var(--m-dur-reveal) var(--m-ease-out),
    transform var(--m-dur-reveal) var(--m-ease-out),
    clip-path var(--m-dur-reveal) var(--m-ease-in-out);
  transition-delay: calc(var(--m-i, 0) * var(--m-stagger));
}
html.m-observe .m-fade-up  { opacity: 0; transform: translate3d(0, var(--m-shift), 0); }
html.m-observe .m-fade-in  { opacity: 0; }
html.m-observe .m-scale-in { opacity: 0; transform: scale(0.94); }
html.m-observe .m-clip     { clip-path: inset(0 100% 0 0); }

html.m-observe :is(.m-fade-up, .m-fade-in, .m-scale-in).is-visible {
  opacity: 1;
  transform: none;
}
html.m-observe .m-clip.is-visible { clip-path: inset(0 0 0 0); }

/* =========================================================================
   2. PARALLAX — transform only. Depth levels move at different rates.
   ========================================================================= */
.m-depth-1 { --m-depth: 14px; }
.m-depth-2 { --m-depth: 32px; }
.m-depth-3 { --m-depth: 58px; }
.m-depth-4 { --m-depth: 88px; }

@keyframes m-parallax {
  from { transform: translate3d(0, var(--m-depth, 32px), 0); }
  to   { transform: translate3d(0, calc(var(--m-depth, 32px) * -1), 0); }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .m-parallax {
      animation: m-parallax linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
      will-change: transform;
    }
  }
}

/* =========================================================================
   3. PINNED / STICKY SECTION SCAFFOLDING
   The track is tall and publishes a view-timeline; the pin sticks inside it.
   Descendants of the track can reference `--m-pin` directly.
   ========================================================================= */
.m-pin-track { position: relative; }
.m-pin { position: sticky; top: 0; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .m-pin-track {
      view-timeline-name: --m-pin;
      view-timeline-axis: block;
      min-height: var(--m-pin-length, 300vh);
    }
    .m-pin {
      top: var(--m-pin-top, 0px);
      min-height: var(--m-pin-height, 100vh);
      display: flex;
      flex-direction: column;
      justify-content: center;
    }
  }
}

/* Progress rail driven by the pin timeline. Content stays legible at 0%. */
@keyframes m-rail { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.m-rail-fill { transform-origin: left center; }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .m-rail-fill {
      animation: m-rail linear both;
      animation-timeline: --m-pin;
      animation-range: contain 0% contain 100%;
    }
  }
}

/* =========================================================================
   4. TEXT REVEAL — line masks. motion.js builds the .m-line wrappers.
   Unsplit (no JS) the heading renders as ordinary text.
   ========================================================================= */
/* Padding + negative margin keeps descenders (y, g, p) and any inline accent
   inside the mask's padding box, so nothing is visually clipped at rest. */
.m-line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
.m-line-in { display: block; will-change: transform; }

@keyframes m-line-rise {
  from { transform: translate3d(0, 120%, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

@media (prefers-reduced-motion: no-preference) {
  /* Load-time line reveal (hero headings). Only runs on split, JS-built lines. */
  .m-split.is-split .m-line-in {
    animation: m-line-rise var(--m-dur-reveal) var(--m-ease-out) both;
    animation-delay: calc(var(--m-line-i, 0) * 85ms + var(--m-split-delay, 120ms));
  }
}

/* =========================================================================
   5. MICRO-INTERACTIONS
   ========================================================================= */
.m-lift {
  transition:
    transform var(--m-dur-base) var(--m-ease-out),
    box-shadow var(--m-dur-base) var(--m-ease-out),
    border-color var(--m-dur-base) var(--m-ease-out);
}
@media (prefers-reduced-motion: no-preference) {
  .m-lift:hover { transform: translate3d(0, -6px, 0); }
}
.m-lift:active { transform: translate3d(0, -1px, 0); }

/* Image zoom inside a fixed frame */
.m-zoom-frame { overflow: hidden; }
.m-zoom-img {
  transition: transform var(--m-dur-slow) var(--m-ease-out);
  transform-origin: center center;
}
@media (prefers-reduced-motion: no-preference) {
  .m-zoom-frame:hover .m-zoom-img,
  .m-zoom-frame:focus-within .m-zoom-img { transform: scale(1.06); }
}

/* Underline draw — background-size animation, no layout cost */
.m-underline {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 2px;
  transition: background-size var(--m-dur-base) var(--m-ease-out);
  text-decoration: none;
  padding-bottom: 2px;
}
.m-underline:hover,
.m-underline:focus-visible { background-size: 100% 2px; }

/* Button press feedback */
.m-press { transition: transform var(--m-dur-fast) var(--m-ease-out), background-color var(--m-dur-base) var(--m-ease-out), color var(--m-dur-base) var(--m-ease-out), border-color var(--m-dur-base) var(--m-ease-out); }
@media (prefers-reduced-motion: no-preference) {
  .m-press:hover { transform: translate3d(0, -2px, 0); }
}
.m-press:active { transform: translate3d(0, 1px, 0); }

/* =========================================================================
   6. PAGE-LOAD CHOREOGRAPHY — runs once. `.m-ready` is set by motion.js;
   if JS never runs the elements simply stay in their visible base state.
   ========================================================================= */
@keyframes m-enter {
  from { opacity: 0; transform: translate3d(0, 18px, 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}
@media (prefers-reduced-motion: no-preference) {
  html.m-ready .m-enter {
    animation: m-enter var(--m-dur-slow) var(--m-ease-out) both;
    animation-delay: calc(var(--m-enter-i, 0) * 110ms + 60ms);
  }
}

/* =========================================================================
   7. LENIS BASE — required by the vendored smooth-scroll engine. These
   classes are added to <html> by Lenis itself, so they are inert until (and
   unless) motion.js actually starts it.
   ========================================================================= */
html.lenis,
html.lenis body { height: auto; }

html.lenis.lenis-smooth { scroll-behavior: auto !important; }
html.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
html.lenis.lenis-stopped { overflow: hidden; }

/* =========================================================================
   8. GLOBAL REDUCED-MOTION BACKSTOP
   Belt and braces: every rule above is already gated on no-preference, but if
   a future site adds motion carelessly this neutralises it.
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .m-pin-track { min-height: 0 !important; }
  .m-pin { position: static !important; min-height: 0 !important; }
  .m-rail-fill { transform: scaleX(1) !important; }
}
