/* ==========================================================================
   fse-motion — shared motion foundation (front-end)
   Platform-level, consumed by all five reference sites. No site-specific rules.

   DESIGN CONTRACT — read before adding anything here.

   1. CONTENT IS NEVER HIDDEN BY DEFAULT. The pre-reveal state below is scoped
      to `.ewo-motion`, a class that `fse-motion.js` puts on <html> ONLY when
      JavaScript runs, the visitor has not asked for reduced motion, the browser
      has IntersectionObserver, and the page has at least one band to reveal.
      With no JS, with JS that failed, with `prefers-reduced-motion: reduce` or
      without IntersectionObserver, the script exits before touching the
      document: the class never appears and every section renders at full
      opacity in its final position. Motion can therefore never delay or
      withhold access to content.

   2. COMPOSITOR PROPERTIES ONLY. Only `opacity` and `transform` are animated.
      Neither participates in layout, so nothing here can cause a layout shift,
      reflow or horizontal overflow. `translateY` only — never `translateX`,
      which could push a full-bleed band past the viewport edge.

   3. NO SCROLL-JACKING, NO PARALLAX, NO INFINITE ANIMATION. A section fades and
      rises once, 12px, and is then done. Scrolling is never intercepted.

   4. THE FIRST SECTION IS NEVER ANIMATED. `fse-motion.js` skips it, because the
      first band is the hero: above the fold, and the primary content of the
      page. It is painted immediately.

   5. NO EXTERNAL LIBRARY. One IntersectionObserver, no dependencies.

   6. SCOPE, AS IMPLEMENTED. A band is a direct child of `.wp-block-post-content`
      (fallback `main`) after the first, taller than 40px; each gets the classes
      `ewo-reveal` and, once seen, `is-revealed`. Inside each band only the
      FIRST `.wp-block-columns` element, at any depth, can stagger, and only its
      direct children, and only when there are 2–6 of them. A row of seven or
      more is skipped whole (it reveals with its band), not capped at six.

   7. ONE INLINE WRITE — STYLE TEXT IS NOT A CONTRACT. On each stagger item the
      script adds the class `ewo-stagger-item` and sets `--ewo-stagger-index`
      through the CSSOM (`element.style.setProperty`). That is the only thing
      motion writes into a `style` attribute; bands get classes only, and no
      other element is touched. A CSSOM write makes the browser re-serialise
      the item's WHOLE `style` attribute: `flex-basis:58%` becomes
      `flex-basis: 58%; --ewo-stagger-index: 0;` (the same in Chromium, Firefox
      and WebKit, measured 2026-10-06). The computed style does not change; the
      text does. Therefore a site MUST NOT select on, or compare, the text of a
      `style` attribute (`[style*="flex-basis:58%"]`, `getAttribute('style')`
      === …); it targets a class, a block attribute's class, or the computed
      value. BoxDirect's grid did select on that text and collapsed to
      one-twelfth columns once motion ran (fixed in the site, b08773d).

      KNOWN SERIALISATION RISK — NO CURRENT RUNTIME FAILURE. A `var()` shorthand
      overridden per side (`border-color:var(--x);border-top-color:…`, as on the
      VakantieParkInStyle cards) re-serialises with EMPTY side longhands
      (`border-right-color: ;`). The rendered border is unchanged, because the
      browser keeps the declared values; only code that re-parses the attribute
      text (a clone built from `outerHTML`, an editor) would lose the side
      colours. Nothing in EWO does that today.

   The contract above is pinned in a real browser by
   tests/js/motion-style-contract.test.js; it asserts semantics (computed
   styles, classes, the custom property's value), never the serialised text.
   ========================================================================== */

/* Duration/distance are literals rather than theme.json tokens: the platform
   declares no motion presets, and inventing a token nothing else consumes would
   be a dead reference (the D3 defect class). Kept here as the single source. */
:root {
	--ewo-motion-distance: 12px;
	--ewo-motion-duration: 520ms;
	--ewo-motion-easing: cubic-bezier(0.22, 0.61, 0.36, 1); /* ease-out-quad: settles, never overshoots */
	--ewo-motion-stagger: 70ms;
}

/* -- Section reveal ------------------------------------------------------- */

.ewo-motion .ewo-reveal {
	opacity: 0;
	transform: translateY(var(--ewo-motion-distance));
	transition:
		opacity var(--ewo-motion-duration) var(--ewo-motion-easing),
		transform var(--ewo-motion-duration) var(--ewo-motion-easing);
	will-change: opacity, transform;
}

.ewo-motion .ewo-reveal.is-revealed {
	opacity: 1;
	transform: none;
}

/* Once a section has arrived, stop promoting it to its own layer. */
.ewo-motion .ewo-reveal.is-revealed {
	will-change: auto;
}

/* -- Optional stagger for grid children ----------------------------------- */

/*
 * Applied by the script only to the direct children of a band's FIRST
 * `.wp-block-columns` row, and only when that row has 2–6 of them: past six the
 * last tile would wait long enough to read as a delay rather than as rhythm, so
 * the script skips a longer row entirely (it does not stagger the first six).
 * The index arrives as an inline custom property; see contract 7.
 */
.ewo-motion .ewo-reveal .ewo-stagger-item {
	opacity: 0;
	transform: translateY(var(--ewo-motion-distance));
	transition:
		opacity var(--ewo-motion-duration) var(--ewo-motion-easing),
		transform var(--ewo-motion-duration) var(--ewo-motion-easing);
	transition-delay: calc(var(--ewo-stagger-index, 0) * var(--ewo-motion-stagger));
}

.ewo-motion .ewo-reveal.is-revealed .ewo-stagger-item {
	opacity: 1;
	transform: none;
}

/* -- Reduced motion ------------------------------------------------------- */

/*
 * Belt and braces. The script already declines to add `.ewo-motion` when the
 * user prefers reduced motion, so these rules should never be needed — but the
 * preference can change after load (a visitor toggling it in the OS), and in
 * that case the already-applied classes must resolve to the static state
 * immediately rather than animating.
 */
@media (prefers-reduced-motion: reduce) {
	.ewo-motion .ewo-reveal,
	.ewo-motion .ewo-reveal .ewo-stagger-item {
		opacity: 1;
		transform: none;
		transition: none;
		will-change: auto;
	}
}
