/*
 * GENERATED FILE - DO NOT EDIT.
 *
 * Source:     assets/css/media-atoms/*.css
 * Regenerate: node scripts/generate-media-stylesheet.mjs
 * Gate:       node scripts/generate-media-stylesheet.mjs --check
 *
 * Edit the partial that owns the rule, never this file. A hand edit here is
 * silently discarded by the next build.
 */

/* ── _base.css ───────────────────────────────────────────────────────── */
/*
 * L4 — the media-element stylesheet. THE RULES LIVE HERE; NOTHING ELSE HOLDS THEM.
 * ==============================================================================
 *
 * The editor canvas and the front end each set VALUES. Neither owns a rule.
 * That is the whole point: a rule written twice is a rule that drifts, and the
 * canvas/front-end divergence this layer exists to remove came from exactly
 * that (`container/edit.js` hand-builds a style object while
 * `class-sgs-container-wrapper.php` builds the front-end CSS separately, and
 * its own comments record the bill twice).
 *
 * So there are three participants and only one of them writes CSS:
 *
 *   this file            every rule, once
 *   render.php (PHP)     custom-property VALUES, in a scoped <style>
 *   edit.js (JS)         the same custom-property VALUES, for the canvas
 *
 * A fixture asserts the two value-setters emit byte-identical declarations for
 * a fixed attribute set. That is the achievable claim — they cannot "run the
 * same function", one is PHP and one is JS.
 *
 * ⛔ LOADED IN BOTH REALMS, deliberately. `class-sgs-blocks.php` enqueues this
 * on `enqueue_block_assets` (the canvas iframe) and on the front end, mirroring
 * `extensions.css`. A shared layer has no block.json to hang a `style:` entry
 * on, so the dual enqueue is the mechanism.
 *
 * ⛔ THE CANVAS RESOLVES TIERS BY CONSTRUCTION. The editor canvas is an iframe
 * whose width is the device-preview width, so these media queries resolve
 * against the preview — which is what the device switcher WANTS. Never compute
 * a preview tier in JS and inline the winner; that is the bug this replaces.
 *
 * ── BREAKPOINTS ──────────────────────────────────────────────────────────────
 * Mobile ≤ 767, tablet ≤ 1023, desktop above. These match `SGS_Breakpoints`
 * (`includes/class-sgs-breakpoints.php`: MOBILE_MAX 767, TABLET_MAX 1023) and
 * `SGS_BREAKPOINTS` (`src/utils/responsive.js`).
 *
 * CSS cannot read a PHP constant, so these four numbers are the one place the
 * layer restates them — and `scripts/check-media-breakpoints.js` fails the build
 * if they ever stop matching the source. Do not hand-tune them.
 *
 * ⛔ Do NOT copy the pair used by the Image Controls block in `extensions.css`
 * (`min-width:769px` / `max-width:768px`). That pair is off by one against this
 * project's device-tier standard. It is a recorded bug, not a precedent.
 *
 * ── THE CASCADE ──────────────────────────────────────────────────────────────
 * A tier inherits UPWARD when it sets nothing: mobile falls back to tablet,
 * tablet to desktop. Expressed as nested var() fallbacks rather than as
 * enumerated rules, because enumerating the combinations is what produced the
 * tablet-set/mobile-empty bug that fell through to desktop instead of tablet.
 *
 * @package
 */

/* ── Scope: TWO markers, and which one an atom uses is declared ───────────────
 *
 * Both are MARKER classes, never layout classes — neither carries appearance of
 * its own, so adding one can never change how an element looks until an atom
 * sets a value.
 *
 *   .sgs-media-el   the REPLACED element itself — the <img> or <video>.
 *   .sgs-media-box  the container around it.
 *
 * ⛔ ONE MARKER IS NOT ENOUGH, and the reason is a browser rule rather than a
 * preference. `overlay` paints through `::after`, and a replaced element
 * GENERATES NO PSEUDO-ELEMENT. Measured 2026-08-31 in Chromium: the identical
 * ::after rule on a 100x100 <div> painted red, on a 100x100 <img> painted
 * nothing. `source` has the same shape — an <img> takes its picture from its
 * `src`, never from `background-image`.
 *
 * ⚠ getComputedStyle( img, '::after' ) reports the rule as fully applied. It
 * resolves the cascade whether or not a pseudo-element exists, so it returns
 * the same red for both. Verify this with a pixel sample, never a computed
 * style — a computed-style check here reports a control that works.
 *
 * The mirror-image constraint runs the other way: `object-fit` and
 * `object-position` are replaced-element properties and do nothing on a
 * container, so they must never key on `.sgs-media-box`.
 *
 * Each atom DECLARES which marker it needs via `attachesTo: 'element' | 'box'`
 * in registry.js, and `check-media-atom-purity.js` fails the build if a
 * partial keys on the other one.
 *
 * ⛔ `attachesTo` is a SEPARATE axis from `scope`. `scope`
 * (element | backdrop | both) selects an atom's VOCABULARY — object-fit's
 * element enum is cover/contain/fill/none/scale-down, its backdrop enum is
 * cover/contain/auto. `attachesTo` selects a DOM NODE. Collapsing two
 * orthogonal axes into one enum is how a control ends up on the wrong element.
 *
 * Custom properties INHERIT, which is what lets one scoped rule feed both
 * markers: the renderer sets `--sgs-media-*` on the scope class, and whichever
 * marker sits at or below it reads them.
 *
 * The renderer also emits `data-sgs-media-src="<atom>@<prefix>"` on the same
 * element, so "which descriptor produced this rule?" is answerable from the
 * page rather than by tracing descriptor → filter → schema → renderer.
 */

.sgs-media-el,
.sgs-media-box {
	/* Desktop is the base tier: it reads its own property with no fallback
	 * chain, because there is no tier above it to inherit from. */
	--sgs-media-tier: desktop;
}

@media (max-width: 1023px) {
	.sgs-media-el,
	.sgs-media-box {
		--sgs-media-tier: tablet;
	}
}

@media (max-width: 767px) {
	.sgs-media-el,
	.sgs-media-box {
		--sgs-media-tier: mobile;
	}
}

/* ── Atom rules ───────────────────────────────────────────────────────────────
 *
 * Each atom appends its own block below, in registry order, using the shape:
 *
 *   .sgs-media-el { <property>: var( --sgs-media-<prop>, <initial> ); }
 *   @media (max-width: 1023px) {
 *     .sgs-media-el {
 *       <property>: var( --sgs-media-<prop>-tablet, var( --sgs-media-<prop>, <initial> ) );
 *     }
 *   }
 *   @media (max-width: 767px) {
 *     .sgs-media-el {
 *       <property>: var( --sgs-media-<prop>-mobile,
 *                    var( --sgs-media-<prop>-tablet,
 *                     var( --sgs-media-<prop>, <initial> ) ) );
 *     }
 *   }
 *
 * ⛔ The nesting order in the mobile rule is load-bearing: mobile → tablet →
 * desktop → initial. Reversing the middle two makes an unset mobile fall through
 * to desktop and silently ignore a tablet value the client did set.
 *
 * ⛔ Only tiered properties get the two @media blocks. A property nothing varies
 * per device gets the base rule alone — see MEDIA_TIERED_BASES.
 *
 * ⛔ An SVG is not a replaced element. `object-fit` and `object-position` do
 * nothing to an inline <svg>, so the SVG path uses `preserveAspectRatio` or a
 * sized wrapper. Never widen a fit selector to cover it.
 */

/* ── Composed animation (svg presentation plus a reserved second position) ───
 *
 * `svg-presentation.css` (pulse/float/wave) writes only to its own uniquely
 * named custom properties (`--sgs-media-svg-animation-*`). It declares no real
 * `animation-*` property: that ownership lives here, once, as the element's
 * base animation (`--sgs-base-animation-*`, registered in the motion
 * stylesheet), and the real longhands read it. Every list holds two positions,
 * matched by list order. Position 2 reads `--sgs-media-motion-animation-*`,
 * which nothing sets, so it stays at its inert default (`none`/`0s`).
 *
 * A Spec 38A entrance on the same element (a product card image with a scroll
 * Fade up, say) plays through its own rule, which puts these lists first and
 * its channels after them: the entrance keeps its own duration, one iteration
 * and normal direction, wins any property both animate while it plays, and the
 * loop runs on underneath it and after it, never restarted. A new position
 * goes into every list, the timeline included.
 */

@media ( prefers-reduced-motion: no-preference ) {
	.sgs-media-el {
		--sgs-base-animation-name: var( --sgs-media-svg-animation-name, none ), var( --sgs-media-motion-animation-name, none );
		--sgs-base-animation-duration: var( --sgs-media-svg-animation-duration, 0s ), var( --sgs-media-motion-animation-duration, 20s );
		--sgs-base-animation-timing-function: var( --sgs-media-svg-animation-timing-function, ease-in-out ), var( --sgs-media-motion-animation-timing-function, ease-in-out );
		--sgs-base-animation-delay: 0s, 0s;
		--sgs-base-animation-iteration-count: var( --sgs-media-svg-animation-iteration-count, infinite ), var( --sgs-media-motion-animation-iteration-count, infinite );
		--sgs-base-animation-direction: var( --sgs-media-svg-animation-direction, alternate ), var( --sgs-media-motion-animation-direction, alternate );
		--sgs-base-animation-fill-mode: none, var( --sgs-media-motion-animation-fill-mode, none );
		--sgs-base-animation-timeline: auto, auto;
		animation-name: var( --sgs-base-animation-name );
		animation-duration: var( --sgs-base-animation-duration );
		animation-timing-function: var( --sgs-base-animation-timing-function );
		animation-iteration-count: var( --sgs-base-animation-iteration-count );
		animation-direction: var( --sgs-base-animation-direction );
		animation-fill-mode: var( --sgs-base-animation-fill-mode );
	}
}

/* ── Cross-atom rule: composed opacity (`opacity` + `svg-presentation`) ───────
 *
 * Found live 2026-09-01, the `opacity` atom's first real deploy: both
 * `opacity.css` (`--sgs-media-opacity`) and `svg-presentation.css`
 * (`--sgs-media-svg-opacity`) declared a bare `opacity:` property on
 * `.sgs-media-el` — identical specificity (0,1,0), so whichever partial the
 * alphabet loads last (`svg-presentation` sorts after `opacity`) always won
 * outright, silently discarding the general opacity control's value on
 * every element: an authored `--sgs-media-opacity:0.5` computed as `1`.
 * Same collision class as the animation rule above, on a different
 * property.
 *
 * Fix: composed here, once, by MULTIPLYING both custom properties (each
 * defaulting to 1 when its owning atom hasn't set one) rather than
 * comma-listing — opacity is not a multi-value CSS property the way
 * `animation-name` is, but it IS naturally multiplicative: two 0.5-opacity
 * layers stacked visually combine to 0.25, which is the physically correct
 * result, not an arbitrary tie-break. An element carrying only one of the
 * two atoms is unaffected, since the absent one's factor is 1.
 *
 * If a THIRD atom ever needs `.sgs-media-el` opacity, extend this
 * `calc()` with a third multiplied factor — do not give it its own bare
 * `opacity:` declaration elsewhere.
 */
.sgs-media-el {
	opacity: calc( var( --sgs-media-opacity, 1 ) * var( --sgs-media-svg-opacity, 1 ) );
}

/* ── Cross-atom rule: composed min-height (`box-shape` + `svg-presentation`) ──
 *
 * THE THIRD instance of this exact collision class, found 2026-09-07 by a
 * dead-control audit. `box-shape.css` declared `min-height` (base + tablet +
 * mobile) and `svg-presentation.css` declared a bare, UNCONDITIONAL
 * `min-height: var( --sgs-media-svg-min-height, 0 )` on the SAME
 * `.sgs-media-el` selector at the same specificity. Partials are concatenated
 * `_base.css` first then ALPHABETICALLY (generate-media-stylesheet.mjs), so
 * `svg-presentation` sorts after `box-shape` and won outright — including over
 * box-shape's @media tiers, since a media query changes nothing about
 * specificity and the svg rule sits later in the file. Every authored
 * min-height computed as `0`.
 *
 * Identical in shape to the composed `opacity` and `animation-name` rules
 * above, both found live on 2026-09-01. `min-height` was missed then.
 *
 * Composition is `max()`, because both properties are FLOORS: the binding
 * floor is the larger of the two, and an unset atom contributes `0px` which is
 * the identity for `max`. (Opacity multiplies; animation-name comma-lists;
 * min-height maxes. The right composition is per-property, not one pattern.)
 *
 * ⛔ Do NOT reintroduce a bare `min-height:` on `.sgs-media-el` in any partial.
 * A fourth atom needing it extends THIS rule.
 *
 * `0px` not `0` deliberately — `max()` resolves against <length>, and a
 * unitless zero is not a length in that context.
 */
.sgs-media-el {
	min-height: max( var( --sgs-media-min-height, 0px ), var( --sgs-media-svg-min-height, 0px ) );
}

@media ( max-width: 1023px ) {
	.sgs-media-el {
		min-height: max(
			var( --sgs-media-min-height-tablet, var( --sgs-media-min-height, 0px ) ),
			var( --sgs-media-svg-min-height, 0px )
		);
	}
}

@media ( max-width: 767px ) {
	.sgs-media-el {
		min-height: max(
			var(
				--sgs-media-min-height-mobile,
				var( --sgs-media-min-height-tablet, var( --sgs-media-min-height, 0px ) )
			),
			var( --sgs-media-svg-min-height, 0px )
		);
	}
}

/* ── Box shape sizing mode "Fill" (2026-09-29) ───────────────────────────
 * Lives here, not in box-shape.css: box-shape attaches to the ELEMENT, and
 * this rule sizes the FRAME marker this file owns (check-media-atom-purity).
 * The frame (`.sgs-media-box--fill`, added by SGS_Media_Element::box_classes()
 * / fills_box() and the JS twin fillsBox()) takes its size from the layout —
 * its half of a two-column row, a stretched grid cell — and never shorter than
 * Minimum height; the picture covers it and never sizes it. The picture's own
 * proportions would otherwise set the row's height (a portrait photo made a
 * split section 1,500px tall). Height/Aspect ratio do not apply in this mode.
 */
.sgs-media-box--fill {
	position: relative;
	height: 100%;
	min-height: var( --sgs-media-min-height, 0px );
}

.sgs-media-box--fill .sgs-media-el {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	max-width: none;
	max-height: none;
	aspect-ratio: auto;
}

@media ( max-width: 1023px ) {
	.sgs-media-box--fill {
		min-height: var( --sgs-media-min-height-tablet, var( --sgs-media-min-height, 0px ) );
	}
}

@media ( max-width: 767px ) {
	.sgs-media-box--fill {
		min-height: var(
			--sgs-media-min-height-mobile,
			var( --sgs-media-min-height-tablet, var( --sgs-media-min-height, 0px ) )
		);
	}
}

/* ── box-shape.css ───────────────────────────────────────────────────── */
/*
 * box-shape — sizing mode (auto/height/ratio), named shape, min/max/width,
 * and the media element's own border (width/style/colour/radius).
 *
 * Custom-property names match `box-shape.js`/`box-shape.php`'s `css()`
 * output exactly: `--sgs-media-height[-tablet|-mobile]`,
 * `--sgs-media-aspect-ratio`, `--sgs-media-width[-tablet|-mobile]`,
 * `--sgs-media-min-height[-tablet|-mobile]`, `--sgs-media-max-width`,
 * `--sgs-media-max-height`,
 * `--sgs-media-clip-path`,
 * `--sgs-media-border-{top-left|top-right|bottom-right|bottom-left}-radius[-tablet|-mobile]`
 * (one per corner, each emitted only for a corner the client set at that tier),
 * `--sgs-media-border-width`, `--sgs-media-border-style`,
 * `--sgs-media-border-color`, `--sgs-media-border-image`,
 * `--sgs-media-border-color-hover`, `--sgs-media-border-image-hover`.
 *
 * SHAPE IS `clip-path` for the CLIP itself, NEVER the block WRAPPER's
 * `border-radius` — that property still belongs to `SgsBorderControl`
 * (44 blocks) and native `__experimentalBorder` (`sgs/media`). A second
 * writer targeting THAT node is a bug (plugins/sgs-blocks/CLAUDE.md,
 * "Border controls"). See `box-shape.js`'s module docblock for the full
 * none/square/rounded/circle -> clip-path mapping.
 *
 * THE BORDER'S OWN PAINT (width/style/colour/radius) is a SEPARATE,
 * UNGATED-by-shape capability (added 2026-09-02) — `SgsBorderControl` fed
 * this atom's own attribute names with zero custom logic. `--sgs-media-
 * border-color`/`-image` are mutually exclusive (gradient wins, same rule
 * as `overlay`'s colour/gradient pair) — a gradient rides `border-image`
 * because a single custom property cannot carry the masked-::before-ring
 * technique a full gradient border normally needs, and `border-image-
 * slice:1` is a harmless companion when no gradient is set (`none 1` is a
 * standard, non-`initial`/`unset`/`revert` no-op value).
 *
 * Height/Width/BorderRadius/max-width/max-height (2026-09-08) follow the
 * mobile -> tablet -> desktop -> initial fallback chain `_base.css`
 * documents; min-height does the same. Aspect-ratio/clip-path/border-
 * width/border-style/border-color/border-image are single-value (no device
 * tiers — per-device border WIDTH is cancelled outright, not merely
 * untiered, per CLAUDE.md "Border controls"; the others have no per-device
 * use case). Earlier revisions of this comment cited "MEDIA_TIERED_BASES
 * does not carry them" as the reason max-width/max-height stayed
 * single-value — that array governs media SOURCE attrs (ImageId/ImageUrl/…)
 * and was never the relevant mechanism for box-shape sizing tiers at all;
 * height/width/border-radius were already tiered without being in it.
 */

.sgs-media-el {
	height: var( --sgs-media-height, auto );
	/* Unset, the width is the element's own width attribute (the browser's presentation mapping, which this
	   rule would otherwise cancel): a not-yet-loaded image has no intrinsic size, so a bare `auto` painted it
	   0px wide and the box below it collapsed until the file arrived. `max-width: 100%` still clamps it. */
	width: var( --sgs-media-width, attr( width px, auto ) );
	/* Unset, the ratio is the element's own width and height attributes (what the browser maps them to):
	   a lazy image with sizes="auto" is size-contained in Chrome, so a bare `auto` would leave it at
	   core's 1500px contain-intrinsic-size. An element without the attributes resolves to `auto`. */
	aspect-ratio: var( --sgs-media-aspect-ratio, auto attr( width type( <number> ) ) / attr( height type( <number> ) ) );
	max-height: var( --sgs-media-max-height, none );
	clip-path: var( --sgs-media-clip-path, none );
	border-top-left-radius: var( --sgs-media-border-top-left-radius, 0 );
	border-top-right-radius: var( --sgs-media-border-top-right-radius, 0 );
	border-bottom-right-radius: var( --sgs-media-border-bottom-right-radius, 0 );
	border-bottom-left-radius: var( --sgs-media-border-bottom-left-radius, 0 );
	border-width: var( --sgs-media-border-width, 0 );
	border-style: var( --sgs-media-border-style, none );
	border-color: var( --sgs-media-border-color, currentcolor );
	border-image-source: var( --sgs-media-border-image, none );
	border-image-slice: 1;
	/* Fallback 100%, NOT `none` (2026-09-07, D998) — twin of the same line in
	 * media-element.css. `none` cancelled core's `img { max-width: 100% }` from a
	 * shared (0,1,0) rule loading after every block stylesheet, so any media
	 * element that had not set the custom property rendered at intrinsic size and
	 * escaped its container. An explicit `--sgs-media-max-width` still wins. This
	 * used to be declared TWICE at equal specificity (the second, later
	 * declaration always won, making this comment sit on dead CSS) — collapsed to
	 * ONE declaration when `maxWidthPercent` was deleted (2026-09-07): it was the
	 * only reason a second `max-width` rule existed at all. */
	max-width: var( --sgs-media-max-width, 100% );
}

/* Hover colour pair (2026-09-07) — colour-only, no hover variant for
 * width/style/radius, matching sgs/button's/sgs/container's own
 * states.hover.attrMap convention. Same fallback-chain shape as
 * `overlay.css`'s `:hover`/`:has(:focus-visible)` rule: an unset hover custom
 * property leaves the resting value in place. `:focus-visible` (not
 * `:focus-within`) because this rule targets the media element itself,
 * which can itself be the focusable node (e.g. when wrapped by the `link`
 * atom), not a container with a focusable descendant.
 *
 * ⚠ Deliberately UNGUARDED against touch-hover-stuck, matching this atom
 * family's only existing precedent (`overlay.css`'s hover rule, shipped
 * 2026-09-01) — `scripts/hover-guard/` does not scan assets/css/media-atoms/
 * at all (it only covers build/blocks/*.css and PHP render surfaces), so
 * neither this rule nor its sibling can be validated by that tooling today.
 * This is a known, named gap shared by the whole atom family, not something
 * introduced here — flagged for a future follow-up, not fixed in this pass.
 */
.sgs-media-el:hover,
.sgs-media-el:focus-visible:where(:not(.sgs-focus-rests, .sgs-focus-rests *)) {
	border-color: var( --sgs-media-border-color-hover, var( --sgs-media-border-color, currentcolor ) );
	border-image-source: var( --sgs-media-border-image-hover, var( --sgs-media-border-image, none ) );
}

@media ( max-width: 1023px ) {
	.sgs-media-el {
		height: var( --sgs-media-height-tablet, var( --sgs-media-height, auto ) );
		width: var( --sgs-media-width-tablet, var( --sgs-media-width, attr( width px, auto ) ) );
		border-top-left-radius: var( --sgs-media-border-top-left-radius-tablet, var( --sgs-media-border-top-left-radius, 0 ) );
		border-top-right-radius: var( --sgs-media-border-top-right-radius-tablet, var( --sgs-media-border-top-right-radius, 0 ) );
		border-bottom-right-radius: var( --sgs-media-border-bottom-right-radius-tablet, var( --sgs-media-border-bottom-right-radius, 0 ) );
		border-bottom-left-radius: var( --sgs-media-border-bottom-left-radius-tablet, var( --sgs-media-border-bottom-left-radius, 0 ) );
		max-height: var( --sgs-media-max-height-tablet, var( --sgs-media-max-height, none ) );
		max-width: var( --sgs-media-max-width-tablet, var( --sgs-media-max-width, 100% ) );
	}
}

@media ( max-width: 767px ) {
	.sgs-media-el {
		height: var(
			--sgs-media-height-mobile,
			var( --sgs-media-height-tablet, var( --sgs-media-height, auto ) )
		);
		width: var(
			--sgs-media-width-mobile,
			var( --sgs-media-width-tablet, var( --sgs-media-width, attr( width px, auto ) ) )
		);
		border-top-left-radius: var(
			--sgs-media-border-top-left-radius-mobile,
			var( --sgs-media-border-top-left-radius-tablet, var( --sgs-media-border-top-left-radius, 0 ) )
		);
		border-top-right-radius: var(
			--sgs-media-border-top-right-radius-mobile,
			var( --sgs-media-border-top-right-radius-tablet, var( --sgs-media-border-top-right-radius, 0 ) )
		);
		border-bottom-right-radius: var(
			--sgs-media-border-bottom-right-radius-mobile,
			var( --sgs-media-border-bottom-right-radius-tablet, var( --sgs-media-border-bottom-right-radius, 0 ) )
		);
		border-bottom-left-radius: var(
			--sgs-media-border-bottom-left-radius-mobile,
			var( --sgs-media-border-bottom-left-radius-tablet, var( --sgs-media-border-bottom-left-radius, 0 ) )
		);
		max-height: var(
			--sgs-media-max-height-mobile,
			var( --sgs-media-max-height-tablet, var( --sgs-media-max-height, none ) )
		);
		max-width: var(
			--sgs-media-max-width-mobile,
			var( --sgs-media-max-width-tablet, var( --sgs-media-max-width, 100% ) )
		);
	}
}

/*
 * WooCommerce pages: WooCommerce's layout sheet sizes every image there
 * (`.woocommerce img, .woocommerce-page img { height: auto; max-width: 100% }`,
 * specificity 0-1-1), which outranks `.sgs-media-el` (0-1-0), so a height or
 * maximum width set on an SGS media element was ignored on every shop page.
 * These rules re-assert the two properties at 0-1-2 (`html` + `img` + the class;
 * `:where()` adds nothing): enough to beat WooCommerce's 0-1-1 whatever the load
 * order, and still below a block's own 0-2-0 sizing rule such as
 * `.product-card .product-card-img`, which keeps its 220px default. Their
 * fallbacks equal WooCommerce's own values, so nothing changes unless a size is set.
 */
html :where(.woocommerce, .woocommerce-page) img.sgs-media-el {
	height: var( --sgs-media-height, auto );
	max-width: var( --sgs-media-max-width, 100% );
}

@media ( max-width: 1023px ) {
	html :where(.woocommerce, .woocommerce-page) img.sgs-media-el {
		height: var( --sgs-media-height-tablet, var( --sgs-media-height, auto ) );
		max-width: var( --sgs-media-max-width-tablet, var( --sgs-media-max-width, 100% ) );
	}
}

@media ( max-width: 767px ) {
	html :where(.woocommerce, .woocommerce-page) img.sgs-media-el {
		height: var(
			--sgs-media-height-mobile,
			var( --sgs-media-height-tablet, var( --sgs-media-height, auto ) )
		);
		max-width: var(
			--sgs-media-max-width-mobile,
			var( --sgs-media-max-width-tablet, var( --sgs-media-max-width, 100% ) )
		);
	}
}

/* ── caption.css ─────────────────────────────────────────────────────── */
/*
 * caption — no CSS. Caption text/tag are HTML content and structure, never
 * stylesheet values (this atom's own `css()`/`sgs_media_atom_caption_css()`
 * both always return an empty array). This partial exists purely so
 * `generate-media-stylesheet.mjs`'s per-atom ownership convention holds —
 * every atom owns a partial, even an empty one — and so a future reader
 * finds this atom's file here rather than concluding it was skipped.
 */

/* ── focal-point.css ─────────────────────────────────────────────────── */
/* ── focal-point atom ─────────────────────────────────────────────────────
 *
 * Element scope: object-position, a crop focus for a replaced element
 * (<img>/<video>) — TIERED, because art direction genuinely differs per
 * device (`MEDIA_TIERED_BASES` carries `ObjectPosition`). Backdrop scope:
 * background-position/-repeat/-attachment for a painted background box —
 * NOT tiered (none of the three backdrop bases are in `MEDIA_TIERED_BASES`).
 *
 * The mobile rule nests mobile -> tablet -> desktop -> initial, per _base.css
 * — reversing the middle two would let an unset mobile value fall through to
 * desktop and silently ignore a tablet value the client did set.
 *
 * `initial` resets each property to its own CSS default (`50% 50%` for
 * *-position, `repeat` for *-repeat, `scroll` for *-attachment) — identical
 * to what paints when this rule is absent, so an unset atom never changes
 * rendering.
 */
.sgs-media-el {
	object-position: var( --sgs-media-object-position, center center );
	background-position: var( --sgs-media-background-position, center center );
	background-repeat: var( --sgs-media-background-repeat, no-repeat );
	background-attachment: var( --sgs-media-background-attachment, scroll );
}

@media (max-width: 1023px) {
	.sgs-media-el {
		object-position: var(
			--sgs-media-object-position-tablet,
			var( --sgs-media-object-position, center center )
		);
	}
}

@media (max-width: 767px) {
	.sgs-media-el {
		object-position: var(
			--sgs-media-object-position-mobile,
			var(
				--sgs-media-object-position-tablet,
				var( --sgs-media-object-position, center center )
			)
		);
	}
}

/* ── intrinsic.css ───────────────────────────────────────────────────── */
/*
 * intrinsic atom — no rules.
 *
 * `ImageWidth`/`ImageHeight` reach the page as HTML `width`/`height`
 * ATTRIBUTES on the rendered `<img>` (the browser reserves layout space from
 * those before the image loads — the actual CLS-prevention mechanism), never
 * as a CSS property. There is nothing for this atom to paint, so it
 * contributes no rule to the shared stylesheet.
 *
 * This file exists so `generate-media-stylesheet.mjs`'s partial roster stays
 * complete and self-documenting — an atom with genuinely no CSS still gets a
 * partial explaining why, rather than a silent gap someone re-investigates
 * later.
 */

/* ── link.css ────────────────────────────────────────────────────────── */
/*
 * link — no CSS. The link is an anchor wrapper, never a stylesheet value
 * (this atom's own `css()`/`sgs_media_atom_link_css()` both always return an
 * empty array). This partial exists purely so
 * `generate-media-stylesheet.mjs`'s per-atom ownership convention holds.
 */

/* ── meaning.css ─────────────────────────────────────────────────────── */
/*
 * meaning atom — no rules.
 *
 * Alt text and the decorative flag reach the page as HTML attributes
 * (`alt=""`, `aria-hidden="true"`, `role="presentation"`), never as CSS.
 * There is nothing for this atom to paint, so it contributes no rule to the
 * shared stylesheet.
 *
 * This file exists so `generate-media-stylesheet.mjs`'s partial roster stays
 * complete and self-documenting — an atom with genuinely no CSS still gets a
 * partial explaining why, rather than a silent gap someone re-investigates
 * later.
 */

/* ── media-padding.css ───────────────────────────────────────────────── */
/*
 * media-padding — the media element's own inner spacing, tiered per side.
 *
 * Custom-property names match `media-padding.js`/`media-padding.php`'s
 * `css()` output exactly:
 * `--sgs-media-padding-{top|right|bottom|left}[-tablet|-mobile]`, each emitted
 * only for a side the client set at that tier. Follows the
 * mobile -> tablet -> desktop -> initial fallback chain `_base.css`
 * documents, same as every other tiered atom; each side chains on its own, so
 * setting one side on a narrower tier leaves the other sides on the wider
 * tier's value.
 */

.sgs-media-el {
	padding-top: var( --sgs-media-padding-top, 0 );
	padding-right: var( --sgs-media-padding-right, 0 );
	padding-bottom: var( --sgs-media-padding-bottom, 0 );
	padding-left: var( --sgs-media-padding-left, 0 );
}

@media ( max-width: 1023px ) {
	.sgs-media-el {
		padding-top: var( --sgs-media-padding-top-tablet, var( --sgs-media-padding-top, 0 ) );
		padding-right: var( --sgs-media-padding-right-tablet, var( --sgs-media-padding-right, 0 ) );
		padding-bottom: var( --sgs-media-padding-bottom-tablet, var( --sgs-media-padding-bottom, 0 ) );
		padding-left: var( --sgs-media-padding-left-tablet, var( --sgs-media-padding-left, 0 ) );
	}
}

@media ( max-width: 767px ) {
	.sgs-media-el {
		padding-top: var(
			--sgs-media-padding-top-mobile,
			var( --sgs-media-padding-top-tablet, var( --sgs-media-padding-top, 0 ) )
		);
		padding-right: var(
			--sgs-media-padding-right-mobile,
			var( --sgs-media-padding-right-tablet, var( --sgs-media-padding-right, 0 ) )
		);
		padding-bottom: var(
			--sgs-media-padding-bottom-mobile,
			var( --sgs-media-padding-bottom-tablet, var( --sgs-media-padding-bottom, 0 ) )
		);
		padding-left: var(
			--sgs-media-padding-left-mobile,
			var( --sgs-media-padding-left-tablet, var( --sgs-media-padding-left, 0 ) )
		);
	}
}

/* ── media-type.css ──────────────────────────────────────────────────── */
/*
 * media-type atom — no rules.
 *
 * `media-type` is a MARKUP discriminator: it decides which element the
 * renderer emits (`<img>`, `<video>`, or an inline `<svg>`), never a
 * paintable CSS property. Both value-setters
 * (`src/components/media/atoms/media-type.js` `css()` and
 * `includes/media/atoms/media-type.php`
 * `sgs_media_atom_media_type_css()`) return an empty array by design — there
 * is nothing here for `.sgs-media-el` to read via `var()`.
 *
 * This file exists so the generator (`scripts/generate-media-stylesheet.mjs`)
 * has a partial to concatenate, matching every other atom's shape, and so a
 * future reader finds an explicit "intentionally empty" note rather than a
 * missing file.
 */

/* ── object-fit.css ──────────────────────────────────────────────────── */
/* ── object-fit atom ──────────────────────────────────────────────────────
 *
 * One client-facing question ("how should this media fill its box?") backing
 * TWO properties depending on scope:
 *   element scope  -> object-fit        (replaced elements: <img>/<video>)
 *   backdrop scope -> background-size   (a painted background box)
 *
 * ── ELEMENT scope is tiered (Bean-directed, 2026-09-01, reversing the
 * "neither is tiered" decision this comment used to record) ───────────────
 * Different media genuinely needs a different fit mode per device — a video
 * that's `cover` on desktop but `contain` on a small mobile screen, so the
 * subject isn't cropped out of frame. `MEDIA_TIERED_BASES`
 * (MediaElementControls.js) now carries `ObjectFit`, so `object-fit` gets the
 * same tablet/mobile fallback chain as `focal-point`'s `object-position`
 * below. The BACKDROP scope's `background-size` (the `Size` base) stays
 * untiered — `Size` is not in `MEDIA_TIERED_BASES`, and Bean's ask was about
 * element media, not painted backgrounds.
 *
 * The mobile rule nests mobile -> tablet -> desktop -> initial, same reason
 * as `focal-point.css`: reversing the middle two would let an unset mobile
 * value fall through to desktop and silently ignore a tablet value the
 * client did set.
 *
 * `initial` as the var() fallback resets each property to its own CSS
 * initial value (`fill` for object-fit, `auto` for background-size) — the
 * same value the browser paints when this rule is absent entirely, so an
 * unset atom never changes rendering.
 *
 * ⛔ Not scoped to `--svg`. object-fit/background-size are meaningless on an
 * inline <svg>'s own geometry either way (a surface adopting this atom for an
 * SVG tier gets no visible effect, which is correct — see registry.js).
 */
.sgs-media-el {
	/* ⛔ The fallback is `cover`, NOT `initial`, and that is measured rather than
	   chosen. All four surfaces carrying this atom default to cover:
	   sgs/media objectFit, sgs/hero splitMediaObjectFit and backgroundSize, and
	   sgs/container backgroundSize (census `presentation`).

	   `initial` would be a silent visible regression. This rule is (0,1,0) and
	   fires unconditionally, so it beats sgs/media's own (0,0,0)
	   `:where( .sgs-media__img ){ object-fit: cover }` default — every existing
	   block that never set an object-fit would have flipped from cover to
	   `initial` (= fill) and stretched its image, with no attribute changed and
	   nothing to grep for. A rule that LOSES is indistinguishable from one that
	   is absent; a rule that silently WINS is worse. */
	object-fit: var( --sgs-media-object-fit, cover );
	background-size: var( --sgs-media-background-size, cover );
}

@media (max-width: 1023px) {
	.sgs-media-el {
		object-fit: var(
			--sgs-media-object-fit-tablet,
			var( --sgs-media-object-fit, cover )
		);
	}
}

@media (max-width: 767px) {
	.sgs-media-el {
		object-fit: var(
			--sgs-media-object-fit-mobile,
			var(
				--sgs-media-object-fit-tablet,
				var( --sgs-media-object-fit, cover )
			)
		);
	}
}

/* ── opacity.css ─────────────────────────────────────────────────────── */
/*
 * opacity — the media element's own transparency.
 *
 * Custom-property name matches `opacity.js`/`opacity.php`'s `css()` output
 * exactly: `--sgs-media-opacity`. Not tiered — MEDIA_TIERED_BASES does not
 * carry `Opacity`, matching the atom's own pre-existing single-value shape
 * (the hand-rolled control it replaces had no Tablet/Mobile siblings either).
 *
 * ⛔ NO BARE `opacity:` DECLARATION HERE. `svg-presentation.css` also
 * declares `opacity` on this SAME selector (`--sgs-media-svg-opacity`), and
 * two atom partials both declaring the same real property at the same
 * specificity (0,1,0) is unresolvable by cascade alone — whichever partial
 * the alphabet loads last always wins outright, silently discarding this
 * atom's value on any non-svg element (measured live 2026-09-01: an
 * authored `--sgs-media-opacity:0.5` computed as `1` because
 * `svg-presentation.css` loads after this file and unconditionally wins).
 * Same collision class `_base.css`'s "Cross-atom rule: composed animation"
 * section already documents and fixes for motion/svg-presentation. Fixed
 * the same way: this atom still writes only to its OWN uniquely named
 * custom property (unchanged above), but the REAL `opacity` property is
 * composed ONCE, in `_base.css`, by multiplying both atoms' custom
 * properties (opacity is naturally multiplicative — two 0.5 layers combine
 * to 0.25, which is the physically correct visual result). See `_base.css`'s
 * "Cross-atom rule: composed opacity" section for the combining rule itself.
 */

/* ── overlay.css ─────────────────────────────────────────────────────── */
/*
 * overlay — colour/gradient tint painted over a media element or backdrop,
 * with opacity and blend mode, resting and hover.
 *
 * A pseudo-element layer (`::after`) rather than a direct property on
 * `.sgs-media-box` itself: a background-colour tint would sit BEHIND an
 * `<img>`'s own painted pixels and never be visible, whereas an absolutely
 * positioned, full-bleed `::after` paints ON TOP — the same technique
 * `sgs_overlay_decls()`'s two existing callers (`class-sgs-container-wrapper.php`,
 * `hero/render.php`) already rely on for their own overlay layers.
 *
 * Custom-property names match `overlay.js`/`overlay.php`'s `css()` output
 * exactly: `--sgs-media-overlay-colour`, `--sgs-media-overlay-gradient`,
 * `--sgs-media-overlay-colour-hover`, `--sgs-media-overlay-gradient-hover`,
 * `--sgs-media-overlay-opacity`, `--sgs-media-overlay-blend`.
 *
 * `position: relative` on `.sgs-media-box` is the isolation `::after` needs to
 * anchor `inset: 0` against — inert on every element that carries no overlay
 * (no custom properties set means the pseudo-element paints fully
 * transparent and `pointer-events: none` keeps it out of the interaction
 * path).
 *
 * ── `--sgs-media-overlay-opacity` is TIERED (2026-09-03) ───────────────────
 * Only opacity varies per device (a heavier scrim on a small screen) —
 * colour/gradient/blend-mode stay untiered, matching
 * `class-sgs-container-wrapper.php:417-422`'s own rationale. The tablet/
 * mobile `@media` cascade below mirrors `object-fit.css`'s nesting EXACTLY:
 * same breakpoints (`max-width:1023px` tablet, `max-width:767px` mobile),
 * same mobile -> tablet -> desktop -> fallback nesting order, so an unset
 * mobile value falls through to tablet rather than jumping straight past it
 * to desktop.
 */

.sgs-media-box {
	position: relative;
}

.sgs-media-box::after {
	content: '';
	position: absolute;
	inset: 0;
	pointer-events: none;
	background-color: var( --sgs-media-overlay-colour, transparent );
	background-image: var( --sgs-media-overlay-gradient, none );
	opacity: var( --sgs-media-overlay-opacity, 1 );
	mix-blend-mode: var( --sgs-media-overlay-blend, normal );
	transition: background-color var(--wp--custom--duration--medium, 0.3s) var(--wp--custom--easing--ease, ease), opacity var(--wp--custom--duration--medium, 0.3s) var(--wp--custom--easing--ease, ease);
}

:where(:root:not(.sgs-touch-input)) .sgs-media-box:hover::after,
.sgs-media-box:has(:focus-visible):where(:not(.sgs-focus-rests, .sgs-focus-rests *))::after {
	background-color: var( --sgs-media-overlay-colour-hover, var( --sgs-media-overlay-colour, transparent ) );
	background-image: var( --sgs-media-overlay-gradient-hover, var( --sgs-media-overlay-gradient, none ) );
}

@media (max-width: 1023px) {
	.sgs-media-box::after {
		opacity: var(
			--sgs-media-overlay-opacity-tablet,
			var( --sgs-media-overlay-opacity, 1 )
		);
	}
}

@media (max-width: 767px) {
	.sgs-media-box::after {
		opacity: var(
			--sgs-media-overlay-opacity-mobile,
			var(
				--sgs-media-overlay-opacity-tablet,
				var( --sgs-media-overlay-opacity, 1 )
			)
		);
	}
}

/* ── shadow.css ──────────────────────────────────────────────────────── */
/*
 * shadow — the media element's own box shadow, resting and hover.
 *
 * Custom-property names match `shadow.js`/`shadow.php`'s `css()` output
 * exactly: `--sgs-media-box-shadow`, `--sgs-media-box-shadow-hover`.
 * `:focus-visible` (the element itself) and `:has(:focus-visible)` (a caption/link wrapper inside it) ride alongside `:hover`; a tap
 * or click on that link does not paint it, keyboard focus does.
 */

.sgs-media-el {
	box-shadow: var( --sgs-media-box-shadow, none );
	/* LONGHAND, not `transition: box-shadow 0.3s ease` (2026-09-10 — the hero
	 * hover-spill-scale build). A `transition` SHORTHAND always resets
	 * `transition-property` to its own value, so ANY other rule on this same
	 * element declaring its own `transition-property: transform` (e.g. the
	 * shared hover-spill-scale capability, `class-sgs-container-wrapper.php`)
	 * gets silently overwritten the moment this partial's rule wins the
	 * cascade — the scale effect then jumps instantly instead of animating,
	 * with nothing to grep for (both rules look correct in isolation).
	 * Longhand scopes this atom to the ONE property it actually owns and
	 * leaves every other property's transition alone, for any current or
	 * future `.sgs-media-el` consumer, not just hero. */
	transition-property: box-shadow;
	transition-duration: var(--wp--custom--duration--medium, 0.3s);
	transition-timing-function: var(--wp--custom--easing--ease, ease);
}

/* Pointer hover carries the touch guard of includes/helpers-hover-state.php (active only when the
 * site chose "Desktop only"); keyboard focus stays unguarded and is keyed on :focus-visible and :has(:focus-visible),
 * so a tap or click does not paint it. */
:where(:root:not(.sgs-touch-input)) .sgs-media-el:hover,
.sgs-media-el:focus-visible:where(:not(.sgs-focus-rests, .sgs-focus-rests *)),
.sgs-media-el:has(:focus-visible):where(:not(.sgs-focus-rests, .sgs-focus-rests *)) {
	box-shadow: var( --sgs-media-box-shadow-hover, var( --sgs-media-box-shadow, none ) );
}

/* ── source.css ──────────────────────────────────────────────────────── */
/*
 * source atom — background-image, BACKDROP scope only.
 *
 * Element-scope surfaces (an `<img>`/`<video>` tag) never rely on this rule —
 * the chosen media reaches those via markup (`src`), not CSS. This rule only
 * paints when the `.sgs-media-box` marker sits on a painted background box.
 *
 * Video and SVG sources are not paintable as a CSS `background-image` and are
 * intentionally NOT emitted by `sgs_media_atom_source_css()`/`css()` — they
 * need their own DOM (a `<video>` element or inline `<svg>`) in a backdrop
 * context, which is outside this atom's declared scope.
 *
 * Tiered per `_base.css`'s documented cascade: mobile -> tablet -> desktop ->
 * initial. `ImageUrl`/`ImageUrlTablet`/`ImageUrlMobile` are all in
 * MEDIA_TIERED_BASES, so all three tiers are real.
 */
.sgs-media-box {
	background-image: var( --sgs-media-background-image, none );
}

@media (max-width: 1023px) {
	.sgs-media-box {
		background-image: var(
			--sgs-media-background-image-tablet,
			var( --sgs-media-background-image, none )
		);
	}
}

@media (max-width: 767px) {
	.sgs-media-box {
		background-image: var(
			--sgs-media-background-image-mobile,
			var(
				--sgs-media-background-image-tablet,
				var( --sgs-media-background-image, none )
			)
		);
	}
}

/* ── svg-presentation.css ────────────────────────────────────────────── */
/*
 * svg-presentation — position (z-index layering), animation, opacity,
 * text-shadow and min-height for an inline SVG media element.
 *
 * Custom-property names match `svg-presentation.js`/`svg-presentation.php`'s
 * `css()` output exactly: `--sgs-media-svg-zindex`,
 * `--sgs-media-svg-animation-name`, `--sgs-media-svg-animation-duration`,
 * `--sgs-media-svg-opacity`, `--sgs-media-svg-text-shadow`,
 * `--sgs-media-svg-min-height`.
 *
 * NONE of these six bases are tiered (MEDIA_TIERED_BASES does not carry any
 * of them) — no @media blocks needed here, unlike box-shape/focal-point.
 *
 * PREFERS-REDUCED-MOTION IS A NEW, DEDICATED GUARD — this selector
 * (`.sgs-media-el`) is not covered by the existing guards in
 * `hero/style.css`/`container/style.css`/`parallax.js`, which are scoped to
 * THOSE blocks' own selectors. Mirrors their pattern rather than inventing a
 * new one: the whole animation rule sits inside
 * `@media (prefers-reduced-motion: no-preference)`, so a client with reduced
 * motion enabled sees the SVG fully static — animation-name/-duration
 * simply never apply. (The real `animation-name`/`-duration` properties now
 * live in `_base.css`'s combined rule — see the note below — which is ALSO
 * wrapped in this same `@media` guard, so that still holds.)
 *
 * ✅ CROSS-ATOM COLLISION RESOLVED (2026-09-01, Bean's direction). This atom
 * used to declare `animation-name`/`-duration`/`-timing-function`/
 * `-iteration-count`/`-direction` directly on `.sgs-media-el`, and so did
 * `motion.css` — identical specificity (0,1,0), whichever partial loaded
 * last (alphabetically after `_base.css`) always won, so `motion`'s ken-
 * burns/parallax silently never animated on any element that could also
 * carry an SVG animation. Bean's direction: this atom KEEPS sole ownership
 * of the pulse/float/wave SVG vocabulary — nothing about that changes —
 * but the two atoms' animations must be able to run simultaneously rather
 * than one silently winning. The fix: this atom still writes only to its
 * OWN uniquely named custom properties (unchanged below), but the REAL
 * `animation-*` properties are now composed ONCE, in `_base.css`, using
 * CSS's native comma-separated multi-value animation syntax — each atom
 * occupies its own position in the list. See `_base.css`'s "Cross-atom
 * rule: composed animation" section for the combining rule itself.
 *
 * ⛔ NO BARE `opacity:` DECLARATION HERE EITHER (2026-09-01, same session).
 * The `opacity` atom (`opacity.css`) ALSO declares `opacity` on this exact
 * selector via its own `--sgs-media-opacity` custom property — the identical
 * collision class the animation fix above already documents, just on a
 * different property. This atom still writes only to its own uniquely
 * named `--sgs-media-svg-opacity`; the REAL `opacity` property is composed
 * ONCE in `_base.css` by multiplying both atoms' values. See `_base.css`'s
 * "Cross-atom rule: composed opacity" section.
 */

/*
 * z-index fallback is `auto`, not -1 (2026-09-24). `.sgs-media-el` is the
 * replaced img/video (class-sgs-media-element.php::CLASS_ELEMENT); the SVG
 * position control is only offered for SVG media (atom `types`), so a -1
 * default reached only images and videos no client could re-layer. Where the
 * element is a flex or grid item with no stacking context above it, -1 painted
 * it BEHIND its section's background: the optician client's brand strip showed 16
 * loaded logos as a blank white band. A layer applies only when chosen.
 */
.sgs-media-el {
	z-index: var( --sgs-media-svg-zindex, auto );
	text-shadow: var( --sgs-media-svg-text-shadow, none );
}

@media ( prefers-reduced-motion: no-preference ) {
	@keyframes sgs-media-svg-pulse {
		from {
			transform: scale( 1 );
		}
		to {
			transform: scale( 1.05 );
		}
	}

	@keyframes sgs-media-svg-float {
		from {
			transform: translateY( 0 );
		}
		to {
			transform: translateY( -12px );
		}
	}

	@keyframes sgs-media-svg-wave {
		0% {
			transform: rotate( -2deg );
		}
		50% {
			transform: rotate( 2deg );
		}
		100% {
			transform: rotate( -2deg );
		}
	}
}

/* ── video-behaviour.css ─────────────────────────────────────────────── */
/*
 * video-behaviour atom — no rules.
 *
 * Playback behaviour (autoplay/loop/muted/controls/plays-inline/lazy-load,
 * captions) is HTML element state — attributes and properties on `<video>`,
 * or query-string params baked into a YouTube/Vimeo iframe embed — never a
 * paintable CSS property. Both value-setters
 * (`src/components/media/atoms/video-behaviour.js` `css()` and
 * `includes/media/atoms/video-behaviour.php`
 * `sgs_media_atom_video_behaviour_css()`) return an empty array by design.
 *
 * The real work this atom does — enforcing `VideoAutoplay -> [VideoMuted,
 * VideoPlaysInline]` so a browser can actually play what it's told to
 * autoplay — lives in `sgs_media_atom_video_behaviour_requires()`, not here.
 *
 * This file exists so the generator (`scripts/generate-media-stylesheet.mjs`)
 * has a partial to concatenate, matching every other atom's shape.
 */
