/* ============================================================================
   navi-motion.css  -  one motion grammar for every Navi Founders page
   ----------------------------------------------------------------------------
   Written 18-09-2026, settled with Matt the same day (lab: motion-lab.html).
   The home page already had a grammar (hero stagger, the stage panels,
   .of-rise, the FAQ panel): opacity 0 -> 1 plus a 16px rise on
   cubic-bezier(.2,.8,.2,1). This file names that grammar as tokens and gives
   every page the same moves through four attributes:

     data-rise           the element rises once when it scrolls into view
     data-rise="fade"    a photo: it never moves, it develops through the dots
     data-rise-group     its direct children rise one after another (60ms apart)
     data-hero           the page's first screen: [data-hero-copy] children
                         arrive on load 80ms apart, [data-hero-media] develops

   navi-motion.js adds html.anim before first paint, .in on scroll-in (once,
   never replayed: NN/g "element persistence"), .is-ready on the hero, and
   .settled after the entrance so transitions are off. Without JS, or under
   prefers-reduced-motion, everything is simply present.

   His picks, 18-09-2026: text rises as one block, the photo arrives through a
   travelling dot front (navi-develop.js; "crazy like that", then "arcade" for
   the first uniform version), base speed, 1px lift on buttons, the ghost
   arrow walks, photos zoom 3% under the hand. The html[data-m-*] attributes
   are the lab's switches; absent (production) means the picks above.

   Sources the numbers rest on (all read 18-09-2026):
     NN/g, Executing UX Animations: 100-500ms, ease-out on entry.
     NN/g, Scroll Fading 101: fade 100-400ms, >500ms reads as slow, one element
       type at a time, animate once.
     NN/g, Scroll-Triggered Text Animations Delay Users: never the main body of
       text; secondary content only.
     Emil Kowalski, animate skill: transform and opacity only, hover gated on
       (hover:hover) and (pointer:fine), stagger 30-80ms, reduced motion is
       "fewer and gentler, not zero".
     W3C, Understanding SC 2.3.3: motion from interaction can be disabled.
   ========================================================================== */

:root{
  /* durations */
  --m-fast:150ms;    /* hover colour, links, the press of a button          */
  --m-base:260ms;    /* tabs, FAQ panels, a state that changes in place     */
  --m-enter:420ms;   /* a block arriving on scroll                          */
  --m-hero:560ms;    /* the page's first screen, once per load              */
  /* distances and gaps */
  --m-rise:16px;     /* how far a block rises; never more                   */
  --m-step:60ms;     /* the gap between siblings in a group                 */
  --m-hero-step:80ms;
  /* curves */
  --e-out:cubic-bezier(.2,.8,.2,1);    /* the site's own: fast in, soft landing. Entrances. */
  --e-move:cubic-bezier(.65,0,.35,1);  /* scroll-linked things that travel and stay (the stage rail) */
  --e-pop:cubic-bezier(.34,1.56,.5,1); /* the dot pop under "real". Decoration only, never layout. */
}

/* ---- lab-only overrides ---- */
html[data-m-speed="quick"]{--m-enter:300ms;--m-hero:420ms;--m-step:45ms;--m-hero-step:60ms}
html[data-m-speed="slow"]{--m-enter:560ms;--m-hero:700ms;--m-step:80ms;--m-hero-step:100ms}

/* ============================================================================
   1. Scroll-in. One block, once. Opacity and transform only (GPU, no layout).
   ========================================================================== */
html.anim [data-rise]{opacity:0;transform:translateY(var(--m-rise))}
html.anim [data-rise="fade"]{transform:none}
html.anim [data-rise].in{opacity:1;transform:none;
  transition:opacity var(--m-enter) ease,transform var(--m-enter) var(--e-out)}

html.anim [data-rise-group] > *{opacity:0;transform:translateY(var(--m-rise))}
html.anim [data-rise-group].in > *{opacity:1;transform:none;
  transition:opacity var(--m-enter) ease,transform var(--m-enter) var(--e-out)}
html.anim [data-rise-group].in > :nth-child(2){transition-delay:var(--m-step)}
html.anim [data-rise-group].in > :nth-child(3){transition-delay:calc(var(--m-step)*2)}
html.anim [data-rise-group].in > :nth-child(4){transition-delay:calc(var(--m-step)*3)}
html.anim [data-rise-group].in > :nth-child(5){transition-delay:calc(var(--m-step)*4)}
html.anim [data-rise-group].in > :nth-child(n+6){transition-delay:calc(var(--m-step)*5)}

/* ============================================================================
   2. Load-in. The first screen arrives once: heading, lede, buttons, then the
   photo on its own clock. Ported from the home hero (.is-ready).
   ========================================================================== */
html.anim [data-hero] [data-hero-copy] > *{opacity:0;transform:translateY(var(--m-rise))}
html.anim [data-hero] [data-hero-media]{opacity:0}
html.anim [data-hero].is-ready [data-hero-copy] > *{opacity:1;transform:none;
  transition:opacity var(--m-hero) ease,transform var(--m-hero) var(--e-out)}
html.anim [data-hero].is-ready [data-hero-copy] > :nth-child(2){transition-delay:var(--m-hero-step)}
html.anim [data-hero].is-ready [data-hero-copy] > :nth-child(3){transition-delay:calc(var(--m-hero-step)*2)}
html.anim [data-hero].is-ready [data-hero-copy] > :nth-child(4){transition-delay:calc(var(--m-hero-step)*3)}
html.anim [data-hero].is-ready [data-hero-media]{opacity:1;
  transition:opacity calc(var(--m-hero) + 140ms) ease 40ms}

/* Settled: the entrance is over and the element is ordinary again. Also what
   a page opened in a background tab gets at once (navi-motion.js showAll), so
   no transition is left half-run for the moment the tab is fronted. */
html.anim [data-rise].in.settled,html.anim [data-rise-group].in.settled > *,
html.anim [data-hero].is-ready.settled [data-hero-copy] > *,
html.anim [data-hero].is-ready.settled [data-hero-media]{transition:none}

/* ============================================================================
   3. The develop (his picks, 18-09-2026). A photo arrives through a travelling
   dot front drawn by navi-develop.js: bottom to top, ramp 80% of the height,
   900ms, a very uneven coastline, eases out, dots grow softly. The old CSS
   version (every dot growing at once) was cut the same day: "arcade".
   The whole figure is hidden until its cover canvas is on (same task, no
   flash) - the figure, not only the image, because the figure's placeholder
   background (m-100) otherwise sits there as a light-blue box (his screenshot,
   18-09: "as if there was nothing there"). With the lab's data-m-photo="fade"
   it simply fades instead.
   ========================================================================== */
html.anim:not([data-m-photo="fade"]) [data-rise="fade"]{opacity:1;transition:none}
html.anim:not([data-m-photo="fade"]) [data-rise="fade"]{visibility:hidden}
html.anim:not([data-m-photo="fade"]) [data-rise="fade"].in{visibility:visible}
html.anim:not([data-m-photo="fade"]) [data-hero] [data-hero-media]{opacity:1;transition:none}
html.anim:not([data-m-photo="fade"]) [data-hero] [data-hero-media]{visibility:hidden}
html.anim:not([data-m-photo="fade"]) [data-hero].is-ready [data-hero-media]{visibility:visible}

/* ============================================================================
   4. Hover and press. Only on a device that can hover (a tap fires a false
   hover on touch). Colour is the site's existing feedback; on top of it the
   1px lift (his pick), a 2% give on press, the ghost arrow walking 3px, and
   a slow 3% zoom on any photo under the hand (his pick, 18-09).
   A photo that ships with its own zoom writes it as --z:1.25 inline instead
   of transform:scale(1.25), so the hover multiplies it rather than losing it.
   ========================================================================== */
/* will-change keeps each photo on its own GPU layer for good. Without it the
   browser lifts the photo onto a layer only while the zoom runs, draws it from
   a soft snapshot, then repaints it sharp at the end: the blur on hover in and
   out, his flag 22-09. */
[data-rise="fade"] img,[data-hero-media] img{transform:scale(var(--z,1));
  will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden}
@media (hover:hover) and (pointer:fine){
  .btn,.navbtn{transition:background var(--m-fast),transform var(--m-fast) var(--e-out),
    box-shadow var(--m-fast) ease}
  html:not([data-m-btn="colour"]):not([data-m-btn="scale"]) .btn:hover,
  html:not([data-m-btn="colour"]):not([data-m-btn="scale"]) .navbtn:hover{
    transform:translateY(-1px);box-shadow:0 8px 18px -10px rgba(10,26,78,.45)}
  html:not([data-m-btn="colour"]):not([data-m-btn="scale"]) .btn-ghost:hover{
    box-shadow:inset 0 0 0 1px var(--n-300),0 8px 18px -10px rgba(10,26,78,.3)}
  html[data-m-btn="scale"] .btn:hover,html[data-m-btn="scale"] .navbtn:hover{transform:scale(1.02)}
  /* the press: a button that gives under the finger, then springs back on release */
  .btn:active,.navbtn:active{transform:translateY(0) scale(.98);transition-duration:60ms}
  /* the arrow on a ghost button walks 3px right; needs the arrow in <span class="arr"> */
  .btn .arr{display:inline-block;transition:transform var(--m-base) var(--e-out)}
  html:not([data-m-arrow="still"]) .btn:hover .arr{transform:translateX(3px)}
  /* photo hover: a slow 3% zoom on the photo under the hand */
  [data-rise="fade"] img,[data-hero-media] img{transition:transform 700ms var(--e-out)}
  html:not([data-m-zoom="off"]) figure[data-rise="fade"]:hover img,
  html:not([data-m-zoom="off"]) [data-rise="fade"] figure:hover img,
  html:not([data-m-zoom="off"]) [data-hero-media]:hover img{transform:scale(calc(var(--z,1) * 1.03))}
}

/* ============================================================================
   5. Reduced motion (WCAG 2.3.3, sufficient technique C39). Everything is
   present, nothing travels. Hover keeps its colour change; the lift, the
   press, the walk, the zoom and the develop are off. html.rm is the lab's
   simulation of the OS setting and mirrors this block line for line.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  html.anim [data-rise],html.anim [data-rise-group] > *,
  html.anim [data-hero] [data-hero-copy] > *,html.anim [data-hero] [data-hero-media]{
    opacity:1;transform:none;transition:none}
  html.anim [data-rise="fade"],html.anim [data-hero] [data-hero-media]{visibility:visible!important}
  .btn:hover,.navbtn:hover,.btn:active,.navbtn:active,.btn:hover .arr{transform:none;box-shadow:none}
  .btn-ghost:hover{box-shadow:inset 0 0 0 1px var(--n-300)}
  figure[data-rise="fade"]:hover img,[data-rise="fade"] figure:hover img,[data-hero-media]:hover img{transform:scale(var(--z,1))}
}
html.rm.anim [data-rise],html.rm.anim [data-rise-group] > *,
html.rm.anim [data-hero] [data-hero-copy] > *,html.rm.anim [data-hero] [data-hero-media]{
  opacity:1;transform:none;transition:none}
html.rm.anim [data-rise="fade"],html.rm.anim [data-hero] [data-hero-media]{visibility:visible!important}
html.rm .btn:hover,html.rm .navbtn:hover,html.rm .btn:active,html.rm .btn:hover .arr{transform:none;box-shadow:none}
html.rm .btn-ghost:hover{box-shadow:inset 0 0 0 1px var(--n-300)}
html.rm figure[data-rise="fade"]:hover img,html.rm [data-rise="fade"] figure:hover img,html.rm [data-hero-media]:hover img{transform:scale(var(--z,1))}
