/**
 * UTV Slideshow — block styling (editor + front end).
 *
 * File Name:    style.css
 * Version:      0.6.7
 * Last Modified: 2026-09-11
 * Package:      utventures-extensions
 *
 * Layout mirrors UTRF's Genesis Responsive Slider (utrf.tennessee.edu):
 * excerpt beside the featured image, fade crossfade, arrows at the card
 * edges, no dot pager. Slides are stacked via CSS Grid (each in the same
 * grid-area) rather than absolute positioning, so the card's height tracks
 * its tallest slide with no fixed aspect-ratio guesswork. On narrow
 * viewports the card stacks image-over-excerpt instead of hiding (UTRF hides
 * its slider on mobile; this site keeps it visible).
 *
 * Text-panel color treatment instead mirrors a different UTRF page — the
 * technology category archive's hero card (e.g. utrf.tennessee.edu
 * /technology-category/agriculture/): a dark ~46%-opacity black panel
 * (#00000075, confirmed via computed styles, 2026-09-11) with white text,
 * sitting directly on the background rather than inside a light/white card.
 * Per David, layout stays as-is (re-skin only, not UTRF's overlay-on-photo
 * structure) — only .utv-slideshow__content's own panel changed; the track
 * itself is transparent, so the Cover block's own dark background shows
 * through directly, same as UTRF's card has no surround of its own either.
 *
 * BEM naming under a single .utv-slideshow namespace; no !important; logical
 * properties throughout.
 */

.utv-slideshow {
	--utv-slideshow-gap: clamp(1.25rem, 3vw, 2.5rem);
	/* Side padding and the heading/image gap, each halved from --utv-slideshow-gap per David. */
	--utv-slideshow-side-gap: clamp(0.625rem, 1.5vw, 1.25rem);
	--utv-slideshow-column-gap: clamp(0.625rem, 1.5vw, 1.25rem);

	container-type: inline-size;
}

.utv-slideshow__track {
	/*
	 * No background of its own (was an 85%-opaque white card) — per David,
	 * re-skinned to match UTRF's technology-category card, where the "card"
	 * treatment lives on the text panel only and the surrounding area is
	 * just the Cover block's own dark background/image, not a separate box.
	 */
	display: grid;
	padding-block: var(--utv-slideshow-gap);
	padding-inline: var(--utv-slideshow-side-gap);
	position: relative;
	touch-action: pan-y;
}

.utv-slideshow__slide {
	align-items: center;
	display: flex;
	gap: var(--utv-slideshow-column-gap);
	grid-area: 1 / 1;
	/*
	 * A grid item's default min-inline-size is `auto`, which refuses to
	 * shrink below its content's own minimum — for this flex row, that
	 * content minimum is its children's combined size. Harmless normally
	 * (content/media can shrink), but in the tween zone below both become
	 * fixed, non-shrinking sizes (22rem + ~40.2rem), and their sum becomes a
	 * hard floor the row can't shrink past — overflowing the track instead
	 * of overlapping as intended. Confirmed via getBoundingClientRect on
	 * staging, 2026-09-12: the row was rendering ~66px wider than its own
	 * track. `0` here removes that floor everywhere, not just in the tween
	 * zone — harmless in the default/mobile layouts, where nothing demands
	 * more than the available space anyway.
	 */
	min-inline-size: 0;
	opacity: 0;
	/*
	 * Clears the arrow glyphs (2.75rem click target + controls' own small
	 * inset) — a functional floor, not decorative breathing room. Fixed
	 * rem value, not vw-scaled: the arrow itself is a constant size now
	 * (not the old viewport-scaled circle/rectangle), and a still-scaling
	 * clearance value was the source of a few px of residual drift in the
	 * image-to-arrow gap David flagged in the tween zone — confirmed via
	 * getBoundingClientRect on staging, 2026-09-12 (varied +9.5px to -4px
	 * across the zone with the old clamp()). Fixed values here and on
	 * .utv-slideshow__controls's own padding-inline make that gap exactly
	 * constant instead of merely close.
	 */
	padding-inline: 3.25rem;
	pointer-events: none;
}

/*
 * Deliberately not nested as `&--active` inside .utv-slideshow__slide above:
 * native CSS nesting silently drops a nested selector starting with `&--`
 * (ambiguous with custom-property syntax) — Chrome parses the whole block
 * away with no console warning. Confirmed via computed styles + CSSOM
 * inspection on staging, 2026-09-11: the rule was entirely absent from
 * document.styleSheets. Every BEM `--modifier` in this file must stay flat.
 */
.utv-slideshow__slide--active {
	opacity: 1;
	pointer-events: auto;
}

@media (prefers-reduced-motion: no-preference) {
	.utv-slideshow__slide {
		transition: opacity 0.8s ease;
	}

	/*
	 * Eases each of the six stepped opacity jumps in the tween zone
	 * (.utv-slideshow__content's ::before, further down) instead of
	 * snapping. A plain top-level rule, not scoped inside the @container
	 * block that defines the pseudo-element itself — it simply has no
	 * effect outside that range, since the element doesn't exist there.
	 */
	.utv-slideshow__content::before {
		transition: opacity 0.3s ease;
	}
}

.utv-slideshow__content {
	/*
	 * The dark panel treatment, lifted from UTRF's technology-category hero
	 * card: #00000075 (~46% opaque black) background with white text. This
	 * now happens to match the Cover block's own default light-on-dark text
	 * color, but is still stated explicitly — it must hold regardless of
	 * whatever text color the surrounding Cover block happens to be set to.
	 */
	background: #00000075;
	color: #fff;
	flex: 0 0 clamp(14rem, 30%, 20rem);
	padding: clamp(0.75rem, 2vw, 1.25rem) clamp(1rem, 2.5vw, 1.5rem);
}

.utv-slideshow__title {
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 3;
	display: -webkit-box;
	font-size: clamp(1.1rem, 1.5vw + 0.5rem, 1.75rem);
	font-weight: 600;
	line-height: 1.2;
	margin-block-end: 0.5em;
	overflow: hidden;

	a {
		color: inherit;
		font-weight: 600;
		text-decoration: none;

		&:focus-visible {
			outline: 2px solid var(--wp--preset--color--primary, #007a89);
			outline-offset: 2px;
		}
	}
}

.utv-slideshow__excerpt {
	font-size: clamp(0.85rem, 0.3vw + 0.75rem, 1rem);
	line-height: 1.5;
	margin: 0 0 0.75em;
}

.utv-slideshow__more {
	color: var(--wp--preset--color--custom-ut-orange, #f7941d);
	font-size: 0.85rem;
	font-weight: 600;
	letter-spacing: 0.02em;
	text-decoration: none;
	text-transform: uppercase;

	&:hover {
		text-decoration: underline;
	}

	&:focus-visible {
		outline: 2px solid var(--wp--preset--color--primary, #007a89);
		outline-offset: 2px;
	}
}

.utv-slideshow__media {
	/*
	 * flex-basis 0% (not `auto` + inline-size:100%): with an aspect-ratio box
	 * whose container's own size is itself content-driven, `flex: 1 1 auto`
	 * plus an explicit inline-size produced an indeterminate-basis case that
	 * left this item far narrower than its share of the row. Confirmed via
	 * getBoundingClientRect on staging, 2026-09-11 — image rendered at ~40%
	 * of the available width instead of filling it.
	 */
	aspect-ratio: 950 / 440;
	display: block;
	flex: 1 1 0%;
	overflow: clip;
}

.utv-slideshow__image {
	/*
	 * Also forced here, not just on the .utv-slideshow__media wrapper: for
	 * an image whose own HTML width/height attributes imply a different
	 * ratio than 950/440 (an SVG featured image, in one confirmed case —
	 * WordPress cannot raster-crop SVGs so it returns the original at its
	 * own dimensions), Chromium's flex automatic-height computation for the
	 * column/mobile layout was sourcing from the image's OWN natural ratio
	 * instead of the parent's aspect-ratio, giving that slide a taller image
	 * box than its siblings. Confirmed on staging, 2026-09-11 — the wrapper
	 * reported the correct computed aspect-ratio while its rendered height
	 * still matched the child's natural ratio exactly. Restating the ratio
	 * here removes the ambiguity.
	 */
	aspect-ratio: 950 / 440;
	block-size: 100%;
	display: block;
	inline-size: 100%;
	object-fit: cover;
}

.utv-slideshow__image--placeholder {
	background: var(--wp--preset--color--contrast, #2c2c2c);
}

.utv-slideshow__controls {
	align-items: center;
	display: flex;
	inset: 0;
	justify-content: space-between;
	/* Fixed, not vw-scaled — see .utv-slideshow__slide's padding-inline comment. */
	padding-inline: 0.5rem;
	pointer-events: none;
	position: absolute;
}

.utv-slideshow__arrow {
	/*
	 * Bare glyph, no button chrome — per David, the full-height dark bar
	 * (first pass, 2026-09-12) read as "weird" and was dropped entirely.
	 * Click/tap target stays comfortable (2.75rem) even with nothing visible
	 * outside the chevron itself.
	 */
	align-items: center;
	background: transparent;
	block-size: 2.75rem;
	border: none;
	color: var(--wp--preset--color--custom-ut-orange, #f7941d);
	cursor: pointer;
	display: inline-flex;
	font-size: 1.75rem;
	inline-size: 2.75rem;
	justify-content: center;
	line-height: 1;
	pointer-events: auto;

	&:hover {
		color: var(--wp--preset--color--base, #fff);
	}

	&:focus-visible {
		outline: 2px solid var(--wp--preset--color--custom-ut-orange, #f7941d);
		outline-offset: 2px;
	}
}

/*
 * Tween zone: 40rem-70rem container width. Per David: rather than let the
 * image keep shrinking as the row narrows below the point where it's
 * roughly the same height as the text panel (measured on staging,
 * 2026-09-12: container ~70.5rem / 1128px, content 304px tall / 22rem wide,
 * media 298px tall), freeze both boxes at those dimensions and instead slide
 * the image left, under the content panel, as the container keeps narrowing
 * toward the mobile breakpoint. min-width excludes the mobile stack range
 * below so the two zones' rules never fight over the same container width.
 *
 * Content's own panel (a ::before, see below) gets more solid as the image
 * disappears behind it, from 46% opacity at 70rem up to 94% at 40rem,
 * rather than staying constant — per David, proposed as "might be too
 * clever" and kept after seeing it live. Stepped across six @container
 * breakpoints rather than a continuous calc(), after three different
 * calc()-based structures each failed in Firefox — see the ::before rule's
 * own comment for the detail.
 *
 * Content's own frozen 19rem/22rem sizing landed by feel, calibrated so
 * the interpolation is continuous (no visual jump) at the 70rem boundary
 * against the row layout's own rendered size at that width. Both
 * dimensions are frozen, not just width — content's height was left
 * unfrozen in an earlier pass and David flagged it as the source of a
 * "jumpy" feel (the excerpt rewrapping to more lines as content's
 * flex-basis kept shrinking through its 30%-of-container range).
 */
@container (min-width: 40.001rem) and (max-width: 70rem) {
	.utv-slideshow__track {
		/*
		 * Fixed, not var(--utv-slideshow-side-gap) (still vw-based): the
		 * media-to-arrow gap is exactly equal to this value (worked out
		 * algebraically and confirmed via getBoundingClientRect on staging,
		 * 2026-09-12 — arrowLeft - mediaRight == track's own padding-inline
		 * precisely, at two different widths), so a still-scaling value here
		 * was the last source of the gap not being truly static.
		 *
		 * 4.25rem, not 1rem: absorbs .utv-slideshow__slide's own former
		 * padding-inline (3.25rem, the arrow-clearance value) — moved here
		 * so the row's overflow:clip clips media at the same edge content
		 * actually starts at. `overflow: clip` clips at an element's
		 * PADDING edge (its outer boundary), so as long as the clearance
		 * padding lived on the row itself, media was only being clipped at
		 * the row's outer edge (by the arrows) — a good ~3.25rem past
		 * content's own start, exactly where David saw the image still
		 * visible. Confirmed via screenshot on staging, 2026-09-12.
		 */
		padding-inline: 4.25rem;
	}

	.utv-slideshow__slide {
		/*
		 * Rebuilt on CSS Grid rather than flex + a hand-tuned negative
		 * margin — the margin approach (first two passes) needed the exact
		 * overlap distance solved algebraically for the fit constraint, and
		 * even then only fixed the RIGHT (media-to-arrow) relationship;
		 * content's own position was a side-effect of the same formula, so
		 * the LEFT (content-to-arrow) relationship still drifted, and
		 * getting the numbers wrong pushed content off-screen entirely
		 * (confirmed on staging, 2026-09-12: rect.x of -113px).
		 *
		 * Grid gives both children their own independent anchor with no
		 * arithmetic at all: content and media share one grid cell
		 * (grid-area 1/1), each pinned to an opposite edge of it via
		 * justify-self. Both edges are then simply always flush with that
		 * cell's own start/end — which is a fixed distance from the arrows
		 * (track's and this row's own padding, both already fixed values,
		 * not vw-scaled) — so both arrow relationships are static by
		 * construction, for any container width, with nothing to solve or
		 * re-tune. The overlap amount falls out automatically: whenever the
		 * cell is narrower than content's width + media's width combined,
		 * the two simply overlap by the difference — no separate margin
		 * formula needed, no explicit "progress" driving position (still
		 * used below, only for opacity).
		 */
		display: grid;
		/*
		 * minmax(0, 1fr), not bare 1fr: a bare `1fr` track implicitly means
		 * `minmax(auto, 1fr)`, and that `auto` minimum resolves to the
		 * widest item placed in it — media's full frozen intrinsic width
		 * (~643px), regardless of actual available space. The column (and
		 * everything positioned against its edges via justify-self) then
		 * refused to shrink below that, pushing media past the right arrow
		 * exactly the same way the row itself overflowed before
		 * min-inline-size:0 was added above. Confirmed via
		 * getComputedStyle(slide).gridTemplateColumns on staging,
		 * 2026-09-12 — it resolved to a literal "643.402px".
		 */
		grid-template-columns: minmax(0, 1fr);
		/*
		 * Reset to 0: this clearance now lives on .utv-slideshow__track
		 * instead (see its own comment) so the clip edge below and
		 * content's actual start edge are the same position.
		 */
		padding-inline: 0;
		/* Clips media at the cell's own start edge — see .utv-slideshow__media. */
		overflow: clip;
	}

	.utv-slideshow__content {
		/*
		 * The dark panel is a ::before, not this element's own background —
		 * see the ::before rule below for why (Firefox). Explicitly cleared
		 * here so the base rule's fixed #00000075 doesn't also apply
		 * underneath it, double-stacking two semi-transparent layers.
		 */
		background: transparent;
		block-size: 19rem;
		grid-area: 1 / 1;
		inline-size: 22rem;
		justify-self: start;
		overflow: hidden;
		position: relative;
		z-index: 2;

		&::before {
			/*
			 * Opacity is a plain literal here (46%, the widest step) with no
			 * calc() at all — see the stepped @container blocks just below
			 * this rule for why. Three straight attempts at a continuous
			 * calc()-driven ramp each failed in Firefox in a different way
			 * (an @property registration rejected outright; then, once that
			 * was removed, a silent fallback to opacity's initial value of
			 * 1; then, once flattened into one expression with no
			 * intermediate custom property, an explicit "Invalid property
			 * value" error — confirmed via Firefox DevTools screenshots each
			 * time, 2026-09-12). Every attempt kept the same core
			 * sub-expression: `(70rem - 100cqi) / 30rem` — a length built
			 * from mixed rem/cqi units, then divided by another length to
			 * reduce it to a plain number. Since that's the one thing common
			 * to every failure regardless of where it was used, it's most
			 * likely that specific operation Firefox won't do, not the
			 * surrounding structure — so rather than keep guessing at
			 * calc() variations, the ramp is now six discrete
			 * @container steps (below), each a plain opacity literal.
			 * Costs some smoothness for a value that has nothing left for
			 * any browser to reject. A transition eases each step change
			 * instead of snapping (see the top-level @media block near
			 * .utv-slideshow__slide's own crossfade transition, further
			 * down) — real usage is someone loading the page at a given
			 * width, not continuously dragging the browser edge, so a step
			 * is rarely even crossed live; when it is (a resize, a tablet
			 * rotation), this softens it. Kept as a separate top-level rule
			 * rather than nesting @media inside this already-nested
			 * &::before — deep nesting is exactly the category of thing
			 * that kept surprising Firefox above.
			 */
			background: #000;
			content: "";
			inset: 0;
			opacity: 0.46;
			position: absolute;
			z-index: -1;
		}
	}

	/*
	 * Six steps from 46% (here, at the 70rem boundary) to 94% (at 40rem),
	 * +8% per 5rem of container width — approximates the continuous ramp
	 * three calc()-based attempts couldn't get Firefox to accept (see the
	 * ::before rule's own comment above). Later, narrower-max-width blocks
	 * naturally override earlier ones at equal specificity, so each just
	 * states its own step's value.
	 */
	@container (max-width: 65rem) {
		.utv-slideshow__content::before {
			opacity: 0.54;
		}
	}

	@container (max-width: 60rem) {
		.utv-slideshow__content::before {
			opacity: 0.62;
		}
	}

	@container (max-width: 55rem) {
		.utv-slideshow__content::before {
			opacity: 0.7;
		}
	}

	@container (max-width: 50rem) {
		.utv-slideshow__content::before {
			opacity: 0.78;
		}
	}

	@container (max-width: 45rem) {
		.utv-slideshow__content::before {
			opacity: 0.86;
		}
	}

	@container (max-width: 40.001rem) {
		.utv-slideshow__content::before {
			opacity: 0.94;
		}
	}

	.utv-slideshow__media {
		/*
		 * justify-self:end pins media's right edge to the cell's own end —
		 * same mechanism as content's start-pin above, so it can extend
		 * arbitrarily far left as the cell narrows (media's own width is
		 * frozen, unlike the cell). Per David: it shouldn't peek out past
		 * content's own left edge once fully "under" it — the slide's own
		 * overflow:clip (above) handles that for free, since content's
		 * start-pinned edge and the cell's own start edge are the same
		 * position; anything of media to the left of that is clipped away
		 * rather than spilling out.
		 */
		block-size: 18.625rem;
		grid-area: 1 / 1;
		inline-size: auto;
		justify-self: end;
		position: relative;
		z-index: 1;
	}
}

@container (max-width: 40rem) {
	.utv-slideshow__slide {
		/*
		 * `align-items` on the desktop row is `center`, which cross-axis
		 * aligns by HEIGHT in a row — but the same value in a column
		 * direction cross-axis-aligns by WIDTH, so it was shrinking each
		 * child (esp. the image) to its own intrinsic size instead of
		 * stretching full-width. Confirmed on staging, 2026-09-11: a JPG
		 * already cropped to the block's own aspect ratio filled edge to
		 * edge by coincidence, while an SVG featured image (a different
		 * natural size — WordPress cannot raster-crop SVGs) rendered small
		 * and off-center. `stretch` makes every slide's image and text
		 * column full-width regardless of that slide's own image asset.
		 */
		align-items: stretch;
		flex-direction: column;
		/*
		 * Reset to 0: this padding exists solely to clear the arrow glyphs
		 * (see the base rule's own comment), but .utv-slideshow__controls
		 * is display:none below in this same mobile breakpoint — so it was
		 * pure leftover dead space here, on top of the track's own side
		 * padding. Per David: side space should match the stacking gap
		 * between media and content. Confirmed via computed styles on
		 * staging, 2026-09-12: track's own padding and the stacking gap
		 * were already equal (10px, same shared clamp()) — this leftover
		 * 52px was the entire discrepancy.
		 */
		padding-inline: 0;
	}

	.utv-slideshow__content {
		flex-basis: auto;
		order: 2;
	}

	.utv-slideshow__media {
		/*
		 * `flex: 1 1 0%` (the row-layout rule above) shares MAIN-axis space —
		 * horizontal on desktop, but vertical once flex-direction flips to
		 * column here. Left unset, it competed with the text block for
		 * height, overriding aspect-ratio and giving each slide's image a
		 * different height depending on that slide's own text length.
		 * Confirmed via getBoundingClientRect on staging, 2026-09-11 (256px
		 * vs 231px for two slides at the same stretched width). `0 0 auto`
		 * takes it out of that competition so height comes from
		 * aspect-ratio against the now fully-stretched width instead.
		 */
		flex: 0 0 auto;
		order: 1;
	}

	/* Replaced by swipe/touch gestures (view.js) at this width — see controls-hide comment there. */
	.utv-slideshow__controls {
		display: none;
	}
}
