/* ==========================================================================
 * MOTION: reveals, tabs and bars, declared by a drawing and run by the
 * platform (D227).
 *
 * ⚠️ THE ARRIVING IS ACSS'S. `on-visible--fade`, `on-visible--sink` and
 * `on-visible--stagger` do scroll-driven reveals in pure CSS through
 * `animation-timeline: view()`, with a `@supports not` block that shows
 * everything where the browser cannot. There is a switch per effect in the
 * ACSS panel, so turning motion off for a whole site is a setting rather than
 * a deploy. This file had its own observer and its own hidden-by-default
 * stylesheet for about an hour; ACSS already had it, safer (D227).
 *
 * What is left here is the three things ACSS has no answer for: a heading that
 * arrives a LINE at a time, a TAB strip, and a BAR whose length is a datum.
 *
 * ⚠️ A DRAWING CANNOT CARRY A SCRIPT. The design contract rejects `<script>`
 * outright, so each of those is a mark the platform knows, and the drawing
 * says WHERE while the platform says HOW.
 *
 * ⚠️ MOTION IS AN ENHANCEMENT, NEVER A PREREQUISITE. Nothing below hides
 * anything until `html.oview-motion` exists, and motion.js sets that. A
 * blocked script leaves the page whole.
 *
 * ⚠️ AND IT OBEYS `prefers-reduced-motion`. Not "less motion": none. Someone
 * who asks for that is often telling you movement makes them ill.
 * ========================================================================== */

:root {
	/*
	 * ⚠️ SLOWER THAN A HOVER, ON THE SAME CURVE. `--transition` is .15s, which
	 * is right for a colour that must have already changed and far too quick
	 * for something travelling 16px into place: at .15s a reveal reads as a
	 * flicker. The curve is shared so the site has one sense of timing.
	 */
	--reveal-distance: 1.6rem;
	--reveal-duration: .55s;
	--reveal-stagger: 60ms;
}

/* --- arriving ------------------------------------------------------------ */


/*
 * A heading that arrives a line at a time. motion.js wraps each line in a span
 * once it has measured where the browser broke them, so this never guesses.
 */
html.oview-motion .oview-line {
	display: block;
	overflow: hidden;
}

html.oview-motion .oview-line__inner {
	display: block;
	transform: translate3d(0, 100%, 0);
	transition: transform var(--reveal-duration) var(--transition-timing, ease-out);
	transition-delay: var(--reveal-delay, 0ms);
}

html.oview-motion [data-oview-reveal="lines"].is-in .oview-line__inner {
	transform: none;
}

/* --- a measure that grows ------------------------------------------------ */

/*
 * The bar in a ranked row: a track, and a fill that runs to its share. The
 * WIDTH is the datum and comes from the markup (`--bar`), so a bar that never
 * animates is still the right length - the animation is only how it arrives.
 */
.oview-bar {
	position: relative;
	display: block;
	inline-size: 100%;
	block-size: var(--bar-height, 1.4rem);
	border-radius: var(--radius-s);
	background: var(--color-surface-sunk);
	overflow: hidden;
}

/*
 * ⚠️ A SCALE, NOT A WIDTH, AND THAT IS NOT A PREFERENCE.
 *
 * This was `inline-size: var(--bar)` transitioned from 0. Chrome would not
 * interpolate the LOGICAL property: the width sat at 0 for the whole duration
 * and then stayed there, so every bar on a tab a reader opened was empty while
 * the film names and the scores beside them were right. Nothing errored, and
 * the one tab that happened to be open on load looked fine.
 *
 * `transform` also costs nothing to animate - it runs on the compositor,
 * where width forces the browser to lay the row out again on every frame.
 *
 * The fill has no corner of its own: the track is `overflow: hidden` with the
 * inset radius, so the visible shape comes from there and a scaled box cannot
 * distort a radius it does not have.
 */
.oview-bar__fill {
	display: block;
	block-size: 100%;
	inline-size: 100%;
	transform-origin: left center;
	transform: scaleX(var(--bar, 0));
	background: var(--bar-ink, var(--color-border-strong));
}

/*
 * ⚠️ THE LEADER IS COLOURED, AND THAT IS NOT DECORATION.
 *
 * A ranked board of scores out of ten is a row of nearly identical bars: the
 * best films of a year land between 7.8 and 9.5, so every bar is between 78%
 * and 95% long and the ranking is invisible at a glance. The number beside it
 * is the precise answer; the colour is what makes the shape of the list
 * readable without reading it.
 *
 * ⚠️ AND IT IS A SELECTOR, NOT A CLASS THE DRAWING PUTS ON ROW ONE. A looped
 * row is drawn once and repeated, so a drawing has no way to say "the first
 * one" - it would have to hard-code which film leads, which is a claim about
 * data that changes (D219).
 */
.oview-tabs__panel > :first-child .oview-bar__fill,
.oview-bar--lead .oview-bar__fill {
	background: var(--bar-ink-lead, var(--color-accent-ink));
}

/*
 * ⚠️ AN ANIMATION, NOT A TRANSITION, AND THE RESTING LENGTH IS ALWAYS RIGHT.
 *
 * A transition needs its starting value committed before the end value
 * arrives, and there is no reliable moment to do that for a bar: it may be
 * revealed by scrolling, by opening the tab it lives in, or be on screen when
 * the page loads. Three paths, each with its own timing, and the failure was
 * always the same - the bar stayed at scaleX(0) while the score beside it read
 * 8.3. Measured on all three.
 *
 * A keyframe animation has no such requirement: it declares both ends. And the
 * resting state above is the TRUE length, so a bar that is never animated -
 * script blocked, reduced motion, an older browser - is still exactly as long
 * as its number says. The animation is how it arrives, never whether it is
 * right.
 */
html.oview-motion .oview-bar.is-in .oview-bar__fill {
	animation: oview-bar-grow .9s var(--transition-timing, ease-out) both;
	animation-delay: var(--reveal-delay, 0ms);
}

@keyframes oview-bar-grow {
	from { transform: scaleX(0); }
	to   { transform: scaleX(var(--bar, 0)); }
}

/* --- tabs ---------------------------------------------------------------- */

/*
 * ⚠️ REAL BUTTONS AND REAL PANELS, with `aria-selected` and `hidden` doing the
 * work. A tab strip built from divs and a class is unreachable by keyboard and
 * silent to a screen reader, and it looks identical.
 */
.oview-tabs__list {
	display: inline-flex;
	gap: var(--space-xs);
	padding: var(--space-xs);
	border-radius: var(--radius-full);
	background: var(--color-surface-sunk);
	border: var(--border-hair) solid var(--color-border);
}

.oview-tabs__tab {
	position: relative;
	z-index: 1;
	appearance: none;
	border: 0;
	cursor: pointer;
	padding: var(--control-px-s) var(--control-px);
	min-height: var(--control-h-s);
	border-radius: var(--radius-full);
	background: transparent;
	color: var(--color-text-muted);
	font: inherit;
	font-size: var(--text-s);
	font-weight: 600;
	white-space: nowrap;
	transition: color var(--transition), background var(--transition);
}

.oview-tabs__tab:hover {
	color: var(--hover-ink);
}

.oview-tabs__tab[aria-selected="true"] {
	background: var(--color-surface);
	color: var(--color-text);
}

/*
 * ⚠️ `!important`, AND IT IS THE ONE IN THIS FILE.
 *
 * `hidden` is a promise the browser makes about whether a thing exists on the
 * page, and any rule that sets `display` beats it: a drawing whose panel is a
 * flex column - which every stacked panel is - silently un-hides all of them.
 * Measured on site five's home page: four panels, all four laid out, the
 * section 1,746px tall instead of 403, and the tabs appearing to do nothing
 * because every year was already on screen. Nothing errored.
 *
 * A layout declaration must not be able to overrule an existence one, and no
 * amount of specificity in motion.css can stop the NEXT drawing doing it.
 */
.oview-tabs__panel[hidden],
[data-oview-tabs] [hidden] {
	display: none !important;
}

/*
 * The panel that has just been shown fades its rows in rather than swapping
 * them, so a tab click reads as the same list rearranging.
 */
html.oview-motion .oview-tabs__panel:not([hidden]) > * {
	animation: oview-fade-up var(--reveal-duration) var(--transition-timing, ease-out) backwards;
}

@keyframes oview-fade-up {
	from { opacity: 0; transform: translate3d(0, .6rem, 0); }
	to   { opacity: 1; transform: none; }
}

/* --- a reader who asked for stillness ------------------------------------ */

/*
 * ⚠️ NONE, NOT LESS. Everything lands in its finished state instantly, and the
 * bar is still the right length because its width was never the animation.
 */
@media (prefers-reduced-motion: reduce) {
	html.oview-motion .oview-line__inner,
	html.oview-motion .oview-tabs__panel:not([hidden]) > * {
		opacity: 1;
		transform: none;
		transition: none;
		animation: none;
	}

	html.oview-motion .oview-bar.is-in .oview-bar__fill {
		animation: none;
		transform: scaleX(var(--bar, 0));
	}

	/*
	 * The vote and accord bars, whose length is already on the element: no
	 * animation and no transform, so each one is simply the width it was given.
	 */
	html.oview-motion .oview-vote__track.is-in .oview-vote__fill,
	html.oview-motion [data-oview-grow].is-in > * {
		animation: none;
		transform: none;
	}
}

/* --- a bar whose length is already on the element ------------------------ */

/*
 * A SECOND KIND OF BAR, AND IT IS THE COMMON ONE.
 *
 * `.oview-bar` above is a length this platform computes, so its keyframe ends
 * at `scaleX(var(--bar))`. A vote bar is not that: the width is already on the
 * element as a percentage, written there by whatever rendered it, and all this
 * adds is the arriving. So it ends at `scaleX(1)` and the element keeps the
 * width it was given.
 *
 * A site opts its own bars in with `data-oview-grow` on the TRACK, because the
 * platform cannot name a site's class.
 *
 * ⚠️ `transform`, NEVER `width`. Chrome will not interpolate `inline-size`, so
 * the first bar this project animated sat at 0 for its whole duration and then
 * appeared (D227). A transform also runs on the compositor, which matters when
 * fourteen of them arrive on one page.
 *
 * ⚠️ AND THE RESTING STATE IS THE TRUE LENGTH. No class means no transform
 * means full width, so a bar that never animates still reads exactly as long
 * as its number says.
 *
 * ⚠️ NOT `transition: width`, WHICH IS ALREADY ON `.oview-vote__fill` and
 * belongs to somebody else: casting a vote changes the width and that change
 * should slide. Two properties, two jobs, no conflict.
 */
@keyframes oview-fill-grow {
	from { transform: scaleX(0); }
	to   { transform: scaleX(1); }
}

.oview-vote__fill,
[data-oview-grow] > * {
	transform-origin: left center;
}

html.oview-motion .oview-vote__track.is-in .oview-vote__fill,
html.oview-motion [data-oview-grow].is-in > * {
	animation: oview-fill-grow .7s var(--transition-timing, ease-out) both;
}
