/* ==========================================================================
   LFA MOTION LAYER  ·  rebuild-final  ·  assets/motion.css
   ==========================================================================
   Additive motion vocabulary. Pages opt in via classes; nothing here
   changes the design system's look. Companion engine: assets/motion.js
   (load it synchronously in <head>; it stamps html.m-js).

   Safety contract:
   .  Every hidden state is gated on html.m-js, which motion.js only adds
      when scripting runs AND prefers-reduced-motion is not set. No JS,
      no class, no hiding.
   .  motion.js force-reveals everything 1.2s after it executes, in step
      with the site's existing .rv failsafe.
   .  Reduced motion: fully static (explicit neutralization at the foot
      of this file, plus the .m-js gate never engaging).
   .  Budget: transform / opacity / clip-path only. will-change is held
      only while an element is animating; .m-done releases it.

   Vocabulary:
     m-lines / m-line-box / m-line   masked line reveals (typeset-in)
     m-hero  (+ root .m-hero-go)     hero settle on load
     m-track                         eyebrow letter-spacing ease-in
     m-curtain (-l / -r)             image curtain reveals on scroll
     m-depth (-2 / -3 / -4, -up)     scroll-driven depth drift
     m-band / m-band-content         navy band paint-in
     m-rule / m-rule-top             hairline rules draw
     m-bracket                       monogram bracket devices draw
     m-chrome (+ root .m-chrome-in)  fixed header settles into place
     m-stagger                       sibling stagger group (JS sets --m-i)
     m-in                            fired state (IO or failsafe)
     m-loader (-in / -out)           site entrance overlay (ledger M3;
                                     JS-injected, first entry per session)
   ========================================================================== */

:root {
  --m-ease: cubic-bezier(0.16, 1, 0.3, 1);   /* long expo-out */
  --m-stagger: 100ms;                        /* per-line / per-sibling */
  --m-dur-line: 1100ms;
  --m-dur-curtain: 1000ms;
  --m-dur-settle: 2400ms;
}

/* --------------------------------------------------------------------------
   1. MASKED LINE REVEALS  (.m-lines)
   Display type rises out of an overflow-clipped line box. motion.js splits
   the element into true rendered lines (Range geometry) and indexes each
   with --m-i. Until the split lands, the whole block is masked by a
   clip-path so nothing half-set ever paints.
   -------------------------------------------------------------------------- */

.m-js .m-lines:not(.m-split):not(.m-in) {
  clip-path: inset(0 0 100% 0);
}

.m-lines .m-line-box {
  display: inline-block;
  overflow: clip;
  vertical-align: bottom;
  /* room for Didot descenders while the line is in motion */
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

.m-js .m-lines .m-line {
  display: inline-block;
  transform: translateY(112%);
  transition: transform var(--m-dur-line) var(--m-ease);
  transition-delay: calc(var(--m-i, 0) * var(--m-stagger));
  will-change: transform;
}

.m-js .m-lines.m-in {
  clip-path: none;
}

.m-js .m-lines.m-in .m-line {
  transform: none;
}

/* --------------------------------------------------------------------------
   2. HERO SETTLE ON LOAD  (.m-hero on the hero section)
   Artwork settles from scale(1.045) to 1 over 2.4s while the title lines
   mask-reveal (put .m-lines on .hero-title) and the tracked eyebrow eases
   its letter-spacing home (.m-track on .hero-est or any eyebrow).
   motion.js stamps .m-hero-go on <html> on the second frame after ready.
   -------------------------------------------------------------------------- */

.m-js .m-hero .hero-media img {
  transform: scale(1.045);
  transition: transform var(--m-dur-settle) var(--m-ease);
  will-change: transform;
}

.m-js.m-hero-go .m-hero .hero-media img {
  transform: scale(1);
}

/* release the compositing hint once the settle has landed */
.m-js.m-settled .m-hero .hero-media img {
  will-change: auto;
}

/* Tracked eyebrow: starts +0.06em wider than its resting tracking and
   eases home. motion.js records the computed resting value into
   --m-ls-rest and stamps data-m-ls, so the widened state can never apply
   before the true value is known. Letter-spacing is the one deliberate
   exception to the transform-only budget: single small element, load only. */

.m-js .m-track[data-m-ls] {
  letter-spacing: calc(var(--m-ls-rest, 0.12em) + 0.06em);
  transition: letter-spacing var(--m-dur-settle) var(--m-ease);
}

.m-js .m-track[data-m-ls].m-in,
.m-js.m-hero-go .m-hero .m-track[data-m-ls] {
  letter-spacing: var(--m-ls-rest, 0.12em);
}

/* --------------------------------------------------------------------------
   3. IMAGE CURTAIN REVEALS  (.m-curtain on the figure / media wrapper)
   The frame unveils via clip-path inset while the image inside settles
   from scale(1.06). Default curtain rises bottom-up; -l and -r wipe from
   the side, per page choreography. Once per element.
   -------------------------------------------------------------------------- */

.m-js .m-curtain {
  clip-path: inset(100% 0 0 0);
  transition: clip-path var(--m-dur-curtain) var(--m-ease);
  transition-delay: calc(var(--m-i, 0) * var(--m-stagger));
  will-change: clip-path;
}

.m-js .m-curtain.m-curtain-l { clip-path: inset(0 100% 0 0); }
.m-js .m-curtain.m-curtain-r { clip-path: inset(0 0 0 100%); }

.m-js .m-curtain img {
  transform: scale(1.06);
  transition: transform calc(var(--m-dur-curtain) + 200ms) var(--m-ease);
  transition-delay: calc(var(--m-i, 0) * var(--m-stagger));
  will-change: transform;
}

.m-js .m-curtain.m-in,
.m-js .m-curtain.m-curtain-l.m-in,
.m-js .m-curtain.m-curtain-r.m-in {
  clip-path: inset(0 0 0 0);
}

.m-js .m-curtain.m-in img {
  transform: scale(1);
}

/* --------------------------------------------------------------------------
   4. SCROLL-DRIVEN DEPTH  (.m-depth)
   Ghost numerals, ghost monograms and hero media drift 2 to 4 percent
   relative to scroll. Native CSS scroll-driven animation where supported;
   motion.js provides a rAF translate fallback for fine-pointer devices
   elsewhere. Fully absent under reduced motion.
   -------------------------------------------------------------------------- */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .m-depth {
      --m-depth: 3%;
      animation: m-depth-drift linear both;
      animation-timeline: view();
    }

    .m-depth-2 { --m-depth: 2%; }
    .m-depth-3 { --m-depth: 3%; }
    .m-depth-4 { --m-depth: 4%; }

    .m-depth-up { animation-name: m-depth-drift-up; }

    @keyframes m-depth-drift {
      from { transform: translateY(var(--m-depth)); }
      to   { transform: translateY(calc(-1 * var(--m-depth))); }
    }

    @keyframes m-depth-drift-up {
      from { transform: translateY(calc(-1 * var(--m-depth))); }
      to   { transform: translateY(var(--m-depth)); }
    }
  }
}

/* --------------------------------------------------------------------------
   5. NAVY BANDS PAINT IN  (.m-band on .navy-band)
   The band's ink wipes up: a shallow top clip releases as the band
   enters. Content inside mask-reveals 120ms later: use .m-lines on the
   type, or .m-band-content for a masked rise of a whole block.
   -------------------------------------------------------------------------- */

.m-js .m-band {
  clip-path: inset(1.5% 0 0 0);
  transition: clip-path 900ms var(--m-ease);
}

.m-js .m-band.m-in {
  clip-path: inset(0 0 0 0);
}

.m-js .m-band-content {
  clip-path: inset(0 0 100% 0);
  transform: translateY(0.9rem);
  transition:
    clip-path 900ms var(--m-ease),
    transform 900ms var(--m-ease);
  transition-delay: 120ms;
  will-change: transform, clip-path;
}

.m-js .m-band.m-in .m-band-content,
.m-js .m-band-content.m-in {
  clip-path: inset(0 0 0 0);
  transform: none;
}

/* --------------------------------------------------------------------------
   6. RULE LINES DRAW  (.m-rule / .m-rule-top / .m-bracket)
   Hairlines draw in via scaleX; the monogram bracket devices draw out of
   their corner points via scale on the existing pseudo-elements.
   -------------------------------------------------------------------------- */

/* An element that IS the rule (hr, divider div) */
.m-js .m-rule {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1100ms var(--m-ease);
  transition-delay: calc(var(--m-i, 0) * var(--m-stagger));
}

.m-js .m-rule.m-in {
  transform: scaleX(1);
}

/* An element whose top border is the rule: the border is quieted and a
   1px overlay draws in its place. Override the color with --m-rule-color
   where the resting rule is not the standard hairline. The transparent
   border is scoped to no-preference so a mid-session switch to reduced
   motion restores the authored rule untouched. */
@media (prefers-reduced-motion: no-preference) {
  .m-js .m-rule-top {
    position: relative;
    border-top-color: transparent;
  }
}

.m-js .m-rule-top::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--m-rule-color, var(--hairline, currentColor));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1100ms var(--m-ease);
}

.m-js .m-rule-top.m-in::before {
  transform: scaleX(1);
}

/* Bracket devices: works on .bracket-corner, .bracket-frame and .marker,
   whose pseudos already carry the border arms. Each arm pair draws
   outward from its corner. */
.m-js .bracket-corner.m-bracket::before,
.m-js .bracket-frame.m-bracket::before {
  transform: scale(0);
  transform-origin: top left;
  transition: transform 800ms var(--m-ease) 100ms;
}

.m-js .bracket-frame.m-bracket::after {
  transform: scale(0);
  transform-origin: bottom right;
  transition: transform 800ms var(--m-ease) 100ms;
}

.m-js .marker.m-bracket::before {
  transform: scale(0);
  transform-origin: left bottom;
  transition: transform 800ms var(--m-ease) 100ms;
}

.m-js .bracket-corner.m-bracket.m-in::before,
.m-js .bracket-frame.m-bracket.m-in::before,
.m-js .bracket-frame.m-bracket.m-in::after,
.m-js .marker.m-bracket.m-in::before {
  transform: scale(1);
}

/* --------------------------------------------------------------------------
   7. CHROME  (.m-chrome on .site-header)
   The fixed header fades down 8px into place once the hero settle has
   begun (root .m-chrome-in, stamped 150ms after .m-hero-go). The nav
   underline draw in site.css is untouched.
   -------------------------------------------------------------------------- */

.m-js .m-chrome {
  opacity: 0;
  transform: translateY(-8px);
  transition:
    opacity 700ms var(--m-ease),
    transform 700ms var(--m-ease);
}

.m-js.m-chrome-in .m-chrome {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------
   8. CLEANUP
   motion.js stamps .m-done after an element's transitions have landed;
   compositing hints are released and split lines are restored to their
   original markup by the engine.
   -------------------------------------------------------------------------- */

.m-done,
.m-done .m-line,
.m-done img {
  will-change: auto !important;
}

/* --------------------------------------------------------------------------
   8b. SITE ENTRANCE  (.m-loader)  ·  ledger M3
   Injected by motion.js only, so no-JS visitors never receive the element,
   and only on the session's first entry (sessionStorage flag; internal
   navigation never re-shows it). The LFA monogram alone on a paper field:
   the mark draws on via a per-glyph clip reveal (~700ms), holds a beat,
   then the overlay exits with one clean upward wipe (~500ms) whose
   completion triggers the hero settle (root .m-hero-go). motion.js
   hard-removes the element from the DOM by 2s regardless of load state.
   Nothing app-like: no spinner, no percentages, no progress.
   -------------------------------------------------------------------------- */

.m-loader { display: none; }

.m-js .m-loader {
  position: fixed;
  inset: 0;
  z-index: 300;                       /* above .site-header (100) */
  display: grid;
  place-items: center;
  background: var(--paper, #faf8f3);  /* paper field hands off into the heroes */
  clip-path: inset(0 0 0 0);
}

.m-js .m-loader svg {
  width: clamp(76px, 9vw, 120px);
  height: auto;
}

/* Draw-on: each glyph unveils left to right in writing order, L then F
   then A, 460ms each at a 120ms stagger, so the full mark lands ~700ms.
   The clip resolves against each path's own fill box. */
.m-js .m-loader path {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 460ms var(--m-ease);
  will-change: clip-path;
}

.m-js .m-loader path:nth-of-type(2) { transition-delay: 120ms; }
.m-js .m-loader path:nth-of-type(3) { transition-delay: 240ms; }

.m-js .m-loader.m-loader-in path {
  clip-path: inset(0 0 0 0);
}

/* Exit: a single upward wipe. The bottom edge rises like a scrim while
   the mark drifts up with it. */
.m-js .m-loader.m-loader-out {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 500ms var(--m-ease);
  will-change: clip-path;
}

.m-js .m-loader.m-loader-out svg {
  transform: translateY(-1.25rem);
  transition: transform 500ms var(--m-ease);
}

/* --------------------------------------------------------------------------
   9. REDUCED MOTION: EVERYTHING VISIBLE AND STATIC, NO EXCEPTIONS
   Belt and braces: motion.js never adds .m-js under reduced motion, so
   the gated states above are already inert. This block also covers a
   preference change mid-session.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .m-lines,
  .m-lines .m-line,
  .m-hero .hero-media img,
  .m-track,
  .m-curtain,
  .m-curtain img,
  .m-depth,
  .m-band,
  .m-band-content,
  .m-rule,
  .m-chrome,
  .bracket-corner.m-bracket::before,
  .bracket-frame.m-bracket::before,
  .bracket-frame.m-bracket::after,
  .marker.m-bracket::before {
    animation: none !important;
    transition: none !important;
    transform: none !important;
    clip-path: none !important;
    opacity: 1 !important;
  }

  .m-rule-top::before {
    content: none !important;
  }

  /* Reduced motion gets no entrance overlay at all (motion.js also never
     injects it; this covers a preference change mid-session). */
  .m-loader {
    display: none !important;
  }
}

/* M6: tactile turntable roll (JS-driven when the engine runs) */
html.m-js .roll-tactile .roll-track { animation: none !important; will-change: transform; }
html.m-js .roll-tactile { cursor: grab; touch-action: pan-y; }
html.m-js .roll-tactile.is-scrubbing { cursor: grabbing; }
html.m-js .roll-tactile.is-scrubbing .roll-track img { pointer-events: none; }
