/* The vetted motion — the stylesheet half of motion.js.                WEL-363
 *
 * Served by src/pages/wi/motion-[stamp].css.ts as the bytes in this file, and
 * linked from the renderer's `head` only on a template that wrote a
 * `data-motion` attribute. kit/motion/motion.css is a byte-identical copy.
 *
 * 🔴 THE HIDDEN "BEFORE" STATE IS GATED TWICE, AND BOTH GATES FAIL TOWARDS
 * A VISIBLE PAGE.
 *   (scripting: enabled)               — no JavaScript, nothing is ever hidden.
 *                                        A browser that predates the query
 *                                        treats the whole block as false and
 *                                        draws the still page.
 *   (prefers-reduced-motion: no-preference) — a visitor who asked for
 *                                        stillness gets it.
 * And a third wall for the day the script does not arrive (a blocked request,
 * a network that dropped it, a script that throws): the `wi-unhide` animation
 * shows every element still hidden after 2.5 s. `.wi-in` — the class the
 * script adds — removes the rule, so an element that arrived on time never
 * meets it.
 *
 * 🔴 THE NET IS FOR A PAGE WITH NO SCRIPT, AND STANDS DOWN ONCE THERE IS ONE
 * (WEL-668). It is scoped to `body:not(.wi-live)`, and `wi-live` is the class
 * `start()` in motion.js puts on the body as the last step of taking over:
 * after its observer is watching every reveal, and after it has marked as
 * arrived whatever the net had already shown. Until that line runs the net is
 * whole, so a script that is blocked, 404s, is refused, throws on the way or
 * never gets as far as watching leaves a page that shows itself. After it,
 * the observer is what shows an element, when the visitor reaches it, with
 * the fade. Before WEL-668 the timer kept running under a working script, so
 * everything below the fold was already drawn 2.5 s after load (8 s for the
 * lead form) and nothing faded for a visitor who scrolled at a human pace.
 * A reveal written into the page AFTER the script took over is not watched
 * and has no net; nothing does that today (the lightbox, the quick view and
 * consent build their own markup and none of it carries `data-motion`).
 *
 * ⚠️ Opacity and transform only (`pin` and `scrub`, below, are the two
 * exceptions), and the
 * transform is dropped once the element
 * has arrived (a transform is a containing block for `position: fixed`
 * descendants, so it must not outlive the reveal). An element that carries its
 * own `transform` should not be a reveal target — wrap it: the hidden rule is
 * (0,2,1) and beats a template's single-class `transform`, so the two would
 * fight on arrival.
 *
 * ⚠️ The hidden rule is scoped under `body`: the old renderer writes a
 * page-wide `data-motion="reveal"` on `<html>` (`Site.astro`, gone at cutover)
 * and this stylesheet must never hide a whole document if the two ever meet.
 *
 * Print gets the final state outright — a printer does not scroll.
 *
 * THE MOTION LEVEL (WEL-639). A version the importer marked carries the house
 * Motion setting, and its page carries `--t-motion-level` on the root: 1
 * (Full), 0.5 (Gentle) or 0 (None, and always 0 under reduced motion). The
 * hidden state reads it with a fallback of 1, and every other page — every
 * version the importer never marked, including one with a setting of its own
 * called `motion` — has no such name, so it computes exactly what it always
 * did: hidden at opacity 0, 1.875rem down, over 1 s. Gentle keeps the fade
 * and drops the rise; None hides nothing, so there is nothing to show. A
 * browser without max() drops the declaration and draws the element, the same
 * way the gates above fail. The script reads the same name for what CSS cannot
 * do alone: below 1 it adds `wi-still` to the body and runs no parallax or
 * scroll video, and at 0 it settles the page as it does for reduced motion.
 *
 * THE FADE'S NUMBERS (WEL-668). 1 s, a 1.875rem (30 px) rise, on
 * cubic-bezier(0.25, 0.46, 0.45, 0.94): half the travel takes about 0.3 s and
 * the last tenth lands at about 0.7 s, so the movement is seen. It was 0.7 s
 * and 1.25rem on the house curve (0.2, 0.8, 0.2, 1), which does most of its
 * travel in the first fifth of a second and read as a blink. The curve is the
 * reveal's own and no longer the house token `--t-motion-ease`, which stays
 * what it was for a template's hovers.
 *
 * STRENGTH (WEL-668). `data-motion-strength` on a reveal or a stagger scales
 * the two numbers through `--wi-motion-time` and `--wi-motion-rise`, plain
 * multipliers:
 *   subtle   0.6 s, 0.75rem (12 px)
 *   (none)   1 s,   1.875rem (30 px)   `normal` is the same as no attribute
 *   strong   1.4 s, 3rem (48 px)
 * Any other value matches no rule and is normal. A stagger's children take
 * their parent's; both names are set back to 1 on every reveal and stagger,
 * so a strength never leaks into a reveal nested inside it. The motion level
 * multiplies on top, as before. */

@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  [data-motion="reveal"],
  [data-motion="stagger"] {
    --wi-motion-time: 1;
    --wi-motion-rise: 1;
  }
  [data-motion="reveal"][data-motion-strength="subtle"],
  [data-motion="stagger"][data-motion-strength="subtle"] {
    --wi-motion-time: 0.6;
    --wi-motion-rise: 0.4;
  }
  [data-motion="reveal"][data-motion-strength="strong"],
  [data-motion="stagger"][data-motion-strength="strong"] {
    --wi-motion-time: 1.4;
    --wi-motion-rise: 1.6;
  }
  [data-motion="reveal"],
  [data-motion="stagger"] > * {
    transition: opacity calc(1s * var(--wi-motion-time, 1) * min(1, 2 * var(--t-motion-level, 1))) cubic-bezier(0.25, 0.46, 0.45, 0.94), transform calc(1s * var(--wi-motion-time, 1) * min(1, 2 * var(--t-motion-level, 1))) cubic-bezier(0.25, 0.46, 0.45, 0.94);
  }
  body [data-motion="reveal"]:not(.wi-in),
  body [data-motion="stagger"] > :not(.wi-in) {
    opacity: max(0, 1 - 2 * var(--t-motion-level, 1));
    transform: translateY(calc(1.875rem * var(--wi-motion-rise, 1) * max(0, 2 * var(--t-motion-level, 1) - 1)));
  }
  /* Under a live script a waiting element rests WITHOUT its rise, where it will land, and nothing
   * on it transitions (WEL-668 review). The observers in motion.js measure the box on the page,
   * rise and all, so a short element at the bottom of the screen or of a part that clips (a
   * license line under a hero, a caption in a card, a bar fixed to the bottom) was pushed out of
   * sight by its own rise and never reached. On arrival the script adds `wi-rise`, which puts the
   * rise back unseen at the hidden opacity, reads the style once, then adds `wi-in`: the fade
   * runs from the rise as before. Before the script takes over the rise stays, for the net. */
  html body.wi-live [data-motion="reveal"]:not(.wi-in),
  html body.wi-live [data-motion="stagger"] > :not(.wi-in) {
    transition: none;
  }
  html body.wi-live [data-motion="reveal"]:not(.wi-in):not(.wi-rise),
  html body.wi-live [data-motion="stagger"] > :not(.wi-in):not(.wi-rise) {
    transform: none;
  }
  /* The net: only while the script has not taken over (see the top of this file). */
  html body:not(.wi-live) [data-motion="reveal"]:not(.wi-in),
  html body:not(.wi-live) [data-motion="stagger"] > :not(.wi-in) {
    animation: wi-unhide 0.6s ease 2.5s forwards;
  }
  /* WEL-666: a reveal or stagger that is, holds or sits in a part the page must show (the lead
   * form, the map, the calculator, the compliance strip, a license or contact line) is stamped
   * `data-wi-safe` by the renderer. If the script has not taken over (`body.wi-live`, which
   * `start()` sets once its observer is watching) the net shows it after 0.8 s instead of 2.5 s,
   * so a script that is blocked, 404s, throws or is refused never leaves a form invisible for long.
   * Once the script is live this net stands down with the other one: the observer shows a form
   * below the fold when the visitor reaches it, with the fade. There is no timer after that
   * (WEL-668: the 8 s one that used to follow drew the form before a visitor scrolled to it, which
   * is the fade nobody saw). What stands behind the observer instead is in the script: anything
   * that is really on the screen and still hidden is shown a moment later, whatever the observer's
   * own margin made of it, so a working script never leaves a form on screen and invisible. */
  html body:not(.wi-live) [data-motion="reveal"][data-wi-safe]:not(.wi-in),
  html body:not(.wi-live) [data-motion="stagger"][data-wi-safe] > :not(.wi-in) {
    animation: wi-unhide 0.3s ease 0.5s forwards;
  }
  [data-motion="parallax"] {
    will-change: transform;
  }
}

@keyframes wi-unhide {
  to {
    opacity: 1;
    transform: none;
  }
}

/* `slider` — the element's children are a sideways strip. Touch and no script already carries
 * it, UNCONDITIONALLY — no @supports wall, no media query, because none of this depends on
 * scripting or on prefers-reduced-motion: a template that ships no motion.js at all still gets a
 * swipeable strip. A template's own rule of equal weight, linked after this stylesheet, wins by
 * source order and may replace either rule below. Placed here, before `pin`, rather than beside
 * `scrub` below: the test that holds the rest of this file to opacity/transform reads a slice
 * that starts at `pin`'s own media query, and the disabled button below is the one rule in this
 * file that is neither.
 *
 * The script (motion.js) inserts a controls bar as the element's NEXT SIBLING, for 2+ children —
 * `.wi-slider-nav` and its two buttons and the count span. ⚠️ UNLIKE every other value here, this
 * is set up under prefers-reduced-motion: reduce too, because it is an accessibility aid, not
 * decoration, so its styling below is never gated behind (scripting: enabled) or
 * (prefers-reduced-motion: no-preference) either — it exists exactly when the script that builds
 * it ran, in both branches. Minimal and neutral on purpose (no colour of its own: `currentColor`
 * and `inherit` only), so it looks deliberate next to any template and is easy to restyle. */
[data-motion="slider"] {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
[data-motion="slider"] > * {
  flex: 0 0 100%;
  scroll-snap-align: start;
}
.wi-slider-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
}
.wi-slider-prev,
.wi-slider-next {
  appearance: none;
  min-width: 44px;
  min-height: 44px;
  font: inherit;
  color: inherit;
  background: transparent;
  border: 1px solid currentColor;
  cursor: pointer;
}
/* .35, not 0.35: this stylesheet's own hidden-state test reads any "opacity: 0…" outside the
 * gated block as a "before" state hiding content, and a disabled button dimming is not that. */
.wi-slider-prev[disabled],
.wi-slider-next[disabled] {
  opacity: .35;
}
.wi-slider-count {
  font-variant-numeric: tabular-nums;
}

/* `header` — motion.css adds NOTHING for this value, on purpose: the script (motion.js) toggles
 * the class `wi-scrolled` on the element once the page has scrolled past a small threshold, and
 * what that actually draws — a background fading in, a border, a shorter logo — is the
 * template's own rule on `.wi-scrolled`. Without the script the header simply stays in whichever
 * state it was first drawn in, so a template using it should make that first state legible on its
 * own, not only the scrolled one. */

/* `menu` — the element is a <details> whose <summary> is the menu button: opening and closing is
 * native, no script needed for that part. UNCONDITIONAL, like `slider` above and for the same
 * reason — this is not decoration gated on scripting or on prefers-reduced-motion, it is the page
 * behind the menu not scrolling while the menu covers it, which is exactly as true with the
 * script absent as with it present. The full-screen panel itself is the template's own CSS
 * (typically `position: fixed; inset: 0;` on a child of the `<details>`); this file draws nothing
 * of the panel, only stops the page under it from moving. */
html:has(details[data-motion="menu"][open]) {
  overflow: hidden;
}

/* `pin` — the photograph stays where it is on the screen while the page scrolls
 * over it: the section above lifts away and the picture is underneath. The
 * element is the window (clip-path cuts it; overflow would not clip a fixed
 * child) and its direct <img> is pinned to the viewport, showing through.
 * CSS only — the script never looks for it — and the one value that sets size
 * and position, on purpose and only on these two selectors; the test that
 * holds the rest to opacity and transform names them.
 *
 * ⚠️ Never inside another data-motion element, nor under any ancestor with a
 * transform, filter, backdrop-filter, perspective, contain or will-change of
 * its own: each makes a containing block, and the "pinned" picture scrolls
 * with the page like any other (the gate refuses the first; the rest it
 * cannot see). Reduced motion, a browser without clip-path (the @supports
 * wall — an unclipped fixed picture would cover the page) and print all get
 * an ordinary photograph in the flow. The window is 70vh and its rule is
 * plain: the template's own stylesheet is linked after this one, so a rule of
 * its own on the element can make it taller or shorter. The picture's rule is
 * !important for the same reason — a template's img rule of equal weight
 * would win by order and un-pin it; print, later still, wins in turn. top and
 * left, not inset: inset is newer than the clip-path wall.
 *
 * Not under `wi-still`, the class the script puts on the body of a page whose
 * motion level is below 1 (WEL-639): Gentle and None have no pin. :where()
 * adds nothing to the weight, so the window is still the template's to size.
 * With no script at all the class never arrives and the picture pins, as it
 * always has. */
@media (prefers-reduced-motion: no-preference) {
  @supports (clip-path: inset(0)) {
    :where(body:not(.wi-still)) [data-motion="pin"] {
      position: relative; /* a caption the template positions absolutely lands in the window */
      height: 70vh;
      clip-path: inset(0);
    }
    :where(body:not(.wi-still)) [data-motion="pin"] > img {
      position: fixed !important;
      top: 0 !important;
      left: 0 !important;
      width: 100% !important;
      height: 100vh !important;
      object-fit: cover !important;
    }
  }
}

/* `scrub` — the element's direct <img> children are frames of a scroll-driven "video": as the
 * visitor scrolls past, the script (motion.js) paints them to a canvas keyed to how far the
 * element has scrolled, not to time. Always, with no script and no media query at all: every
 * frame but the LAST is `display: none` (so a lazy-loaded one is never even fetched) and the
 * last one — the still — fills the element with `object-fit: cover`. That is the whole page
 * without JavaScript: one photograph, at the element's ordinary height.
 *
 * ⚠️ TWO CLASSES, BOTH THE SCRIPT'S, AND BOTH FAIL TOWARDS THE STILL:
 *   .wi-scrub — the element qualified (2+ frames) and the script has begun. Only THIS class
 *               turns the element into a tall scroll track (300vh here — a rule of the
 *               template's own, linked after this stylesheet, can make it taller or shorter,
 *               the same way `pin`'s window height can be changed) with a sticky inner view
 *               pinned to the top of the viewport while the track scrolls past underneath.
 *               Without it the element is simply its ordinary height, with the still in it —
 *               never a 300vh empty track waiting for a script that did not arrive.
 *   .wi-in    — reused from reveal/stagger: added once the first frame has actually painted.
 *               Only then does the canvas show and the still hide: if the canvas never gets a
 *               frame, the still stays exactly where it already was.
 *
 * `display: none` is otherwise refused in this file — an unconditional way to hide that no
 * animation can undo — but a hidden frame here is never a "before" state waiting on the script:
 * it is permanently off-screen either way, by design, and the visible page never depended on it.
 *
 * An overlay (a headline) is anything in the element besides the <img>s, written AFTER them so
 * it paints on top. The element is a one-cell grid and every direct child sits in that cell, so
 * the overlay lies over the still with no script and over the canvas with one — the template
 * writes one layout, not two. The frames are picked by `of-type`, not `child`: with an overlay
 * after it, the last frame is not the last child. */
/* minmax(0, 1fr): a canvas is as wide as its pixel buffer unless the track says otherwise, and
 * an `auto` track would grow to fit it — then the script would size the next buffer to that. */
[data-motion="scrub"] {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}
[data-motion="scrub"] > * {
  grid-area: 1 / 1;
}
[data-motion="scrub"] > img:not(:last-of-type) {
  display: none;
}
[data-motion="scrub"] > img:last-of-type {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  [data-motion="scrub"].wi-scrub {
    height: 300vh;
  }
  /* Every child — canvas, still, overlay — rides the same pinned screen. */
  [data-motion="scrub"].wi-scrub > *,
  [data-motion="scrub"].wi-scrub > img:last-of-type {
    position: sticky;
    top: 0;
    height: 100vh;
    align-self: start;
  }
  [data-motion="scrub"].wi-scrub > canvas {
    display: none;
    width: 100%;
    object-fit: cover;
    /* A picture, never a target: a click or a text selection goes to whatever the template put
     * over or under it (a headline, an editor's double-click), not to the canvas. */
    pointer-events: none;
  }
  [data-motion="scrub"].wi-scrub.wi-in > canvas {
    display: block;
  }
  [data-motion="scrub"].wi-scrub.wi-in > img:last-of-type {
    display: none;
  }
}

@media print {
  [data-motion="reveal"],
  [data-motion="stagger"] > *,
  [data-motion="parallax"] {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
  [data-motion="pin"] {
    height: auto !important;
    clip-path: none !important;
  }
  [data-motion="pin"] > img {
    position: static !important;
    height: auto !important;
  }
  [data-motion="scrub"],
  [data-motion="scrub"] > * {
    position: static !important;
    height: auto !important;
  }
}
