/**
 * The device screen turning on — one effect, four framed screens.
 *
 * Four places in this theme draw the design's device: a photograph inside the
 * black bezel, rounded by the one radius this otherwise square-cornered design
 * allows itself, and lifted by the `device` shadow. This sheet turns all four
 * of them **on**, as they scroll into view: the screen arrives black, a bright
 * line ignites across its middle, and the picture unfurls out of that line to
 * the top and bottom edges — a CRT finding its raster.
 *
 * The four frames, and where each one's own CSS lives:
 *
 * | Frame                                       | Its stylesheet                            |
 * | ------------------------------------------- | ----------------------------------------- |
 * | `kg-product/sticky-list`'s panel screens    | `src/blocks/sticky-list/style.scss`       |
 * | `kg-product/text-media-carousel`'s rail     | `src/blocks/text-media-carousel/style.scss` |
 * | `kg-product/case-slider`'s slides           | `src/blocks/case-slider/style.scss`       |
 * | `core/media-text`'s `is-style-screen`       | `assets/css/blocks/media-text-screen.css` |
 *
 * **The effect is written once, here, and not four times in those files.** It is
 * one design behaviour rather than four coincidences, and the only thing that
 * differs between the four is the box — which is why the geometry below is the
 * common case (a frame that is `position: relative`, clips its overflow, and
 * whose padding box *is* the screen) and `media-text-screen.css` overrides it
 * for the one frame that is not: there the bezel is on the `<img>` itself, and
 * the image deliberately overflows its figure.
 *
 * Registered as `kg-product-block-screen-power-on` by
 * `includes/blocks/screen-power-on.php`. The three custom blocks name that
 * handle in their `block.json` `style` array, ahead of their own sheet, so their
 * CSS can outrank this one; `media-text-screen.css` takes it as a `$deps` entry,
 * which puts it earlier on the page for the same reason.
 *
 * ## Two pseudo-elements, and why the picture itself is never touched
 *
 * The obvious implementation clips the `<img>` — `clip-path: inset(50% 0)`
 * opening to `inset(0)` — and it is wrong for this design, because on
 * `core/media-text` the bezel is a *border on the image*, so clipping the
 * picture clips the device away with it. Everything happens in overlays instead,
 * and the image is left exactly as the block drew it:
 *
 * - **`::after` — the two black bands.** Two background layers, one anchored to
 *   the top edge and one to the bottom, and the whole unfurl is `background-size`
 *   going from half the box each to nothing. A single `inset()` cannot describe
 *   two bands retracting *away* from the middle, and two layers of one background
 *   can, in one interpolatable property.
 *
 *   `calc(50% + 1px)` and not `50%`: two boxes of exactly half the height meet on
 *   a subpixel boundary at any fractional frame height, and the seam shows as a
 *   hairline of the photograph across the middle of a screen that is meant to be
 *   off. A pixel of overlap costs nothing and there is nothing to see.
 *
 *   It repaints rather than composites, which a `translate` on two real elements
 *   would not — and that is the trade: those elements would have to slide *out*
 *   of the frame, so every frame would need to clip them, and `is-style-screen`'s
 *   figure cannot clip anything without cropping the overhang the style exists
 *   for. A background never leaves its own box. The paint is a solid fill over
 *   about 600 × 1100px, which is cheap enough for the swap to be worth it.
 *
 * - **`::before` — the ignition line, and the bloom.** A white gradient bar
 *   centred on the screen: a few pixels tall at ignition, which reads as the
 *   line, then growing to the full height while it fades, which reads as the
 *   raster blooming. It paints *under* `::after` — pseudo-element order, no
 *   `z-index` — so it only ever shows in the gap the bands have opened, and the
 *   line and the opening picture are the same edge.
 *
 *   The line is white rather than a brightened picture (`filter: brightness()`
 *   on the image) because a photograph with a dark row across its middle would
 *   ignite dim, and which row that is depends on the photograph.
 *
 * ## The timeline is named, and declared on the frame
 *
 * `animation-timeline: view()` on the image cannot work here and fails silently:
 * an anonymous view timeline takes **the nearest ancestor scroll container**,
 * whichever axis it scrolls, and every one of these frames sits inside one —
 * `overflow: hidden` on the frame itself for three of them, and the sliders'
 * horizontally scrolling viewports besides. Measured on this theme: a subject
 * inside `overflow-x: auto` (with `overflow-y` at `clip` *or* left to compute to
 * `auto`) gets a timeline whose source is that scroller and whose `currentTime`
 * is pinned at 50% for ever — so the animation reports `finished`, the fill
 * applies, and every screen is simply on. No error, no clue.
 *
 * `view-timeline-name` on the frame is the fix, and it fixes the sliders too:
 * the container is looked up from the *declaring* element's ancestors, so a
 * frame's own `overflow: hidden` is invisible to it, and a scroller that
 * declares a timeline for its own box gets the document. `case-slider` and
 * `text-media-carousel` therefore declare it on their viewports — the box that
 * scrolls — rather than on the frames inside them.
 *
 * **One name for all four, and it is not ambiguous.** A named timeline is in
 * scope for the declaring element, its descendants and its *following siblings*,
 * which is what makes a static name on a block's root element a latent bug
 * (`includes/blocks/split-reveal.php` has the evidence). Every declaration here
 * is nested at least one level below its block's root, so no two of them are
 * ever in each other's scope — and where sibling frames do both declare it, as
 * `sticky-list`'s panels do, the nearer declaration wins. Verified: each frame's
 * animation resolves to a timeline whose subject is that frame.
 *
 * `view-timeline-inset: 0` is not decoration. It defaults to `auto`, which means
 * the scrollport's `scroll-padding` — and `assets/css/global/reset.css` sets
 * `scroll-padding-top` to the admin bar's height with `!important`, so every
 * offset below would be out by the admin bar for a logged-in editor and right
 * for a visitor.
 *
 * ## Why the range ends at `entry 100%`
 *
 * A scroll-driven animation applies no `backwards` fill before its range, so the
 * off state has to be the elements' own declared value — and, the other way
 * round, an element that is **already in view when the page loads** shows
 * whatever state its progress happens to land on. `entry 100%` is the moment the
 * screen's bottom edge reaches the bottom of the window, so anything fully
 * visible on first paint is *past* the end of the range and holds the finished
 * state under `forwards`. That is what keeps the home hero's carousel from
 * loading as a row of half-lit screens, and it is why the range is bounded by
 * `entry` at both ends rather than running on into `contain`, which would have
 * put the ignition higher up the window.
 *
 * The cost is that ignition happens low: at 12% into the range the screen is 47%
 * of the way in, so the line strikes within a few dozen pixels of the fold and
 * rises into view already lit. Everything after it — the hold, the unfurl, the
 * bloom — plays out in the clear.
 */

:root {
	/*
	 * When it happens, in one place because four selectors say it.
	 *
	 * 40% of the screen's own height into its entry, running to the moment it is
	 * fully in view: about 250px of scroll for a 628px screen, which is a flick
	 * of the wheel rather than a section to be scrubbed through.
	 */
	--kg-tv-range: entry 40% entry 100%;

	/* The ignition line's height, before it blooms. */
	--kg-tv-line: 5px;
}

/*
 * The unfurl. Two layers, one `background-size`.
 *
 * The first 12% is the screen still off, because the *approach* wants to be
 * black — the line has to strike, not fade up. `16%` and `30%` are the same
 * value on purpose: the line is struck between 12% and 16% and then held, which
 * is the beat that makes it read as a device coming on rather than as a wipe.
 */
@keyframes kg-tv-unfurl {
	0%,
	12% {
		background-size: 100% calc(50% + 1px);
	}

	16% {
		background-size: 100% calc(50% - 1.5px);
	}

	30% {
		background-size: 100% calc(50% - 1.5px);
		/*
		 * Out of the hold fast and into the edges gently, which is how a raster
		 * opens. The theme's flattened spring has no overshoot, and it must not
		 * have one here: `background-size` clamps at zero, so an easing that
		 * undershoot would freeze the last few percent of the opening.
		 */
		animation-timing-function: var(
			--wp--custom--transition-spring-flattened
		);
	}

	72%,
	100% {
		background-size: 100% 0%;
	}
}

/*
 * The line, and then the bloom.
 *
 * `scale` is the horizontal snap: the line arrives as a short dash and stretches
 * to the full width of the screen over the same four percent the bands part in.
 * Y stays at 1 throughout, because scaling this box vertically would scale the
 * gradient inside it and the bright core with it.
 *
 * The fade is deliberately finished at 80% rather than at 100%. A screen that is
 * only *partly* in view when the page loads holds a mid-range state for ever,
 * and the last fifth of the range is the state most likely to be frozen into —
 * so it is the clean picture rather than a white wash over it.
 */
@keyframes kg-tv-glow {
	0%,
	12% {
		opacity: 0;
		block-size: var(--kg-tv-line);
		scale: 0.4 1;
	}

	16% {
		opacity: 1;
		block-size: var(--kg-tv-line);
		scale: 1 1;
	}

	30% {
		opacity: 1;
		block-size: var(--kg-tv-line);
		scale: 1 1;
		animation-timing-function: var(
			--wp--custom--transition-spring-flattened
		);
	}

	46% {
		opacity: 0.45;
		block-size: 30%;
		scale: 1 1;
	}

	66% {
		opacity: 0.1;
		block-size: 80%;
		scale: 1 1;
	}

	80%,
	100% {
		opacity: 0;
		block-size: 100%;
		scale: 1 1;
	}
}

/*
 * Everything below is inside both guards, and the nesting is the point: the off
 * state lives in here with the animation that ends it, so a browser without
 * scroll-driven animation and a reader who has asked for less motion both get
 * the finished screen the design draws, rather than a screen frozen off.
 */
@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		/*
		 * The subjects. Three frames track themselves; the two sliders track
		 * their viewport, because the frames inside it are in a scroll container
		 * and a timeline declared in there would never move. See the file's note.
		 */
		.kg-sticky-list.has-device-frame .kg-sticky-list__screen,
		.kg-carousel.has-device-frame .kg-carousel__viewport,
		.kg-case__viewport,
		.wp-block-media-text.is-style-screen
			> figure.wp-block-media-text__media {
			view-timeline-name: --kg-tv;
			view-timeline-inset: 0;
		}

		/*
		 * The overlays, and the animation they share.
		 *
		 * `animation-duration: auto` is what hands the timing to the timeline;
		 * `linear` is the default easing because the shape of the motion is in
		 * the keyframes' own spacing and in the two `animation-timing-function`
		 * declarations inside them.
		 */
		.kg-sticky-list.has-device-frame .kg-sticky-list__screen::before,
		.kg-sticky-list.has-device-frame .kg-sticky-list__screen::after,
		.kg-carousel.has-device-frame .kg-carousel__frame::before,
		.kg-carousel.has-device-frame .kg-carousel__frame::after,
		.kg-case__frame::before,
		.kg-case__frame::after,
		.wp-block-media-text.is-style-screen
			> figure.wp-block-media-text__media::before,
		.wp-block-media-text.is-style-screen
			> figure.wp-block-media-text__media::after {
			content: "";
			position: absolute;
			inset-inline: 0;
			pointer-events: none;
			animation-duration: auto;
			animation-fill-mode: forwards;
			animation-timing-function: linear;
			animation-timeline: --kg-tv;
			animation-range: var(--kg-tv-range);
		}

		/*
		 * The black. Both layers are one flat colour written as a gradient,
		 * because `background-size` is what animates and a `background-color`
		 * has no size to give.
		 *
		 * The declared size is the off state — see the file's note on backwards
		 * fill — and the black is the palette's, so a screen that is off is the
		 * same black as the bezel it is inside.
		 */
		.kg-sticky-list.has-device-frame .kg-sticky-list__screen::after,
		.kg-carousel.has-device-frame .kg-carousel__frame::after,
		.kg-case__frame::after,
		.wp-block-media-text.is-style-screen
			> figure.wp-block-media-text__media::after {
			inset-block: 0;
			background-image: linear-gradient(
					var(--wp--preset--color--black),
					var(--wp--preset--color--black)
				),
				linear-gradient(
					var(--wp--preset--color--black),
					var(--wp--preset--color--black)
				);
			background-position: top, bottom;
			background-repeat: no-repeat;
			background-size: 100% calc(50% + 1px);
			animation-name: kg-tv-unfurl;
		}

		/*
		 * The line. Centred by its own half-height rather than by `inset-block`,
		 * so the height the keyframes animate stays free — and it grows from the
		 * middle in both directions, which is the point.
		 *
		 * The gradient's soft ends are what keep a five-pixel bar from looking
		 * like a border: the bright core is the middle quarter of it, and the
		 * rest is the halo. At full height the same gradient is the bloom.
		 */
		.kg-sticky-list.has-device-frame .kg-sticky-list__screen::before,
		.kg-carousel.has-device-frame .kg-carousel__frame::before,
		.kg-case__frame::before,
		.wp-block-media-text.is-style-screen
			> figure.wp-block-media-text__media::before {
			inset-block-start: 50%;
			translate: 0 -50%;
			block-size: var(--kg-tv-line);
			opacity: 0;
			background-image: linear-gradient(
				to bottom,
				rgb(255 255 255 / 0%),
				var(--wp--preset--color--white) 38%,
				var(--wp--preset--color--white) 62%,
				rgb(255 255 255 / 0%)
			);
			animation-name: kg-tv-glow;
		}
	}
}
