/*
 * pm-reveal — section blocks rise and fade in on scroll (WO-99, superseding
 * WO-51's horizontal slide).
 *
 * Source: docs/design/motion/99-prototype-motion.md, the extract generated
 * from the designer's running prototype. `Poortman Showcases.dc.html` is the
 * reference file — the one the client pointed at on 30 September — and not
 * the Homepage file that sits beside it. Frame 103:812, which WO-51 built
 * the horizontal slide from, is superseded: the prototype is the newer
 * artefact and it is what the client asked to be aligned with. Nobody should
 * restore the slide later because a frame still describes it (MISTAKES.md
 * §C16).
 *
 * No widget carries this markup. assets/js/reveal.js stamps
 * `data-pm-reveal="left|right"` on the blocks in its own SELECTOR_MAP, and
 * `html.pm-reveal` on the root element once it has run. This file styles
 * ONLY those two hooks; it never targets a component class, so it depends on
 * nothing but the tokens sheet (WO-79 registration: `poortman-tokens` only,
 * the same as button.css).
 *
 * TWO PATHS, and exactly one of them is ever live on an element:
 *
 *   - Scrubbed, inside `@supports (animation-timeline: view())`. Progress is
 *     tied to scroll position rather than to the clock, so scrolling back up
 *     reverses the reveal. This is what the prototype does, and it is the
 *     substantive difference from WO-51, which PLAYED the motion once on
 *     entry and never took it back.
 *   - Transitioned, everywhere else. reveal.js toggles `.pm-reveal--in` from
 *     an IntersectionObserver, exactly as WO-51 built it. Same distance,
 *     same duration, same easing — the reveal plays instead of scrubbing.
 *
 * Measured on this machine, 30 September: Chrome and WebKit take the scrubbed
 * path, Firefox the transitioned one. Safari gained view timelines in 26, so
 * the transitioned path is Firefox's today — not, as it would have been a year
 * ago, "everything except Chrome". Both are live browser behaviour and both
 * have to be readable; which of them the client wants EVERY browser to get is
 * open question 3.
 *
 * reveal.js runs `CSS.supports('animation-timeline','view()')` and adds
 * `.pm-reveal--in` only when that is false, so the two paths can never both
 * drive one element. It keeps stamping the attribute and adding
 * `html.pm-reveal` either way, because every selector in this file needs
 * both of them.
 *
 * Distance, duration and easing are tokens — --pm-motion-distance-rise,
 * --pm-motion-duration-reveal, --pm-motion-ease-reveal. Not one of them is
 * typed here: tokens/tokens.json is the source, which is the house rule and
 * WO-99 AC 1. WO-51's `--pm-reveal-distance` is gone with the slide it
 * clamped — a rise this short, and vertical, cannot overflow a viewport
 * sideways, so there is nothing left to clamp (ticket 54 AC 1).
 *
 * Compiled to assets/css/components/reveal.css by src/build-tokens.mjs — no
 * @bp marker in this file, nothing here is breakpoint-specific.
 *
 * TODO(open-question): whether the designer intends the prototype to replace
 * frame 103:812, or whether it was an exploration, is WO-99 open question 2
 * and Eleonor's to answer. The values are hers either way.
 * TODO(open-question): if Safari or Firefox cannot scrub acceptably, whether
 * every browser should get the transitioned version instead is WO-99 open
 * question 3 — measured by the Tester, decided by the client.
 */

/*
 * The one animation this ticket ships, transcribed from the prototype's own
 * `@keyframes pm-rise` — byte-identical in both prototype files. The offset
 * reads the token, so the keyframe body carries no measurement of its own.
 */
@keyframes pm-rise {
	from {
		opacity: 0;
		transform: translateY(var(--pm-motion-distance-rise));
	}

	to {
		opacity: 1;
		transform: none;
	}
}

/* ---------------------------------------------------------------------------
 * The transitioned path — every browser without a view timeline, which on the
 * day this was written means Firefox. reveal.js drives it by toggling
 * `.pm-reveal--in`.
 * ------------------------------------------------------------------------ */

/*
 * The transition lives on the base rule so it applies whichever direction
 * the toggle moves the element — hiding it and animating it back in both use
 * the same declaration.
 */
html.pm-reveal [data-pm-reveal] {
	transition:
		transform var(--pm-motion-duration-reveal) var(--pm-motion-ease-reveal),
		opacity var(--pm-motion-duration-reveal) var(--pm-motion-ease-reveal);
}

/*
 * Rest state. The rise is vertical, so left-side and right-side blocks share
 * it: WO-51's `[data-pm-reveal='right']` sign flip is gone with the
 * horizontal slide, and so is the `.pm-reveal-clip` rule that contained the
 * overflow that flip produced. The attribute keeps its `right` value because
 * other tickets read it (WO-99 Contracts) and because reveal.js still tells
 * the two sides apart — not because the motion differs any more.
 */
html.pm-reveal [data-pm-reveal]:not(.pm-reveal--in) {
	opacity: 0;
	transform: translateY(var(--pm-motion-distance-rise));
}

/*
 * Settled state, once reveal.js adds the class on intersect. Written
 * explicitly (rather than relying only on the :not() above) so the rest
 * state and the in state each read as a complete, independent block.
 */
html.pm-reveal [data-pm-reveal].pm-reveal--in {
	opacity: 1;
	transform: none;
}

/*
 * The snap paths: keyboard focus landing inside a still-dormant block, and
 * the restored-viewport case (scroll restoration, or an anchor that lands on
 * a block that is already intersecting). Transition off, so the settled
 * state applies with no motion and no flash.
 */
html.pm-reveal [data-pm-reveal].pm-reveal--no-motion {
	transition: none;
}

/* ---------------------------------------------------------------------------
 * The scrubbed path — the prototype's own mechanism.
 * ------------------------------------------------------------------------ */

/*
 * `animation-range` is the scrub. It is what maps animation progress onto
 * scroll position, and it is the one parameter the two prototype files share
 * no value for at all — seven distinct ranges each, and not one in common.
 * The range below is the Showcases file's, on its `pm-rise` block container.
 *
 * How the prototype assigns a range: PER ANIMATION, NOT PER ELEMENT. The
 * Homepage file is the proof — all but one of its `pm-rise` elements carry
 * one single identical range between them, spread across `div`, `p`, `a` and
 * `span` tags, and `pm-fade`, `pm-line`, `pm-rule` and `pm-clip` each have
 * their own one range in the same way. One animation means one range, and
 * this ticket ships one animation.
 *
 * Which of the Showcases file's three `pm-rise` ranges: the one on its
 * `div`, because block-level containers are what reveal.js's SELECTOR_MAP
 * targets — the other two sit on a body paragraph and on a button. It is
 * also the median cover of that file's eight scroll-driven elements.
 *
 * NOT the Homepage file's dominant rise range, although that is the
 * designer's most-used one by a wide margin. Different file; the client
 * pointed at Showcases; and the two files' range sets do not intersect at
 * all. Recorded here as a decision rather than left looking like an
 * oversight (WO-99 AC 2a).
 *
 * The range is the only literal in this file. It is a pair of scroll
 * positions, not a measurement of the design — there is no token shape for
 * it and inventing one would hide it from the next reader.
 *
 * `animation-timeline` and `animation-range` come AFTER the `animation`
 * shorthand deliberately: the shorthand resets both of them.
 */
@supports (animation-timeline: view()) {
	html.pm-reveal [data-pm-reveal] {
		/*
		 * The transition belongs to the other path. Off here, so a
		 * `.pm-reveal--in` arriving from anywhere cannot fight the
		 * animation over the same two properties.
		 */
		transition: none;
		animation: pm-rise var(--pm-motion-duration-reveal) var(--pm-motion-ease-reveal) both;
		animation-timeline: view();
		animation-range: entry 5% cover 28%;
	}

	/*
	 * The snap paths again, in this path's language. A filling animation
	 * beats a plain `opacity`/`transform` declaration, so the animation
	 * itself has to be switched off by name before an end state can apply.
	 *
	 * This is the keyboard case and it is not cosmetic: a dormant block
	 * keeps its focusable descendants in the tab order on purpose
	 * (MISTAKES.md §B8), so focus can land inside a block the scrub is
	 * still holding at zero opacity.
	 */
	html.pm-reveal [data-pm-reveal].pm-reveal--no-motion {
		animation: none;
		opacity: 1;
		transform: none;
	}
}

/* ---------------------------------------------------------------------------
 * Reduce Motion beats everything above it. This is a content-visibility
 * requirement, not a polish one (WO-99 AC 4).
 * ------------------------------------------------------------------------ */

/*
 * reveal.js tests the same query first and never adds `html.pm-reveal` at
 * all, so this block is normally unreachable. It exists so that nothing in
 * this file can hide or move content if that check is ever bypassed — a root
 * class restored from a cache, a setting changed after load, a future caller
 * that stamps the attribute itself.
 *
 * Two things the WO-51 version of this block got wrong, either of which
 * leaves a reader looking at a blank section:
 *
 *   - `opacity: 1; transform: none; transition: none` does not stop an
 *     animation. A filling `animation … both` goes on overriding all three
 *     at every scroll position, so the animation has to be switched off by
 *     name. This is the case WO-99's first draft would have shipped broken.
 *   - the rest-state selector above carries one class more than this block's
 *     did, so it beat this block on specificity and kept `opacity: 0`.
 *     Both selector shapes are listed here now, and this block is last, so
 *     it wins on either.
 */
@media (prefers-reduced-motion: reduce) {
	html.pm-reveal [data-pm-reveal],
	html.pm-reveal [data-pm-reveal]:not(.pm-reveal--in) {
		animation: none;
		opacity: 1;
		transform: none;
		transition: none;
	}
}
