/* ==========================================================================
   Kinetic — structure

   Layout for three sections and one header layout:

     .u-slider      grab-and-throw rail
     .u-sidescroll  a pinned section the page scrolls through sideways
     .u-wall        two photo rows drifting in opposite directions
     .u-header-overlay + .u-overlay   centred header, full-screen menu

   This is structure, not skin. It loads in every demo because it is what
   makes a rail a rail — the Kinetic demo's *look* lives in
   style-kinetic.css and only loads when that demo is on.

   Two rules held throughout:

     Nothing here hard codes a colour, radius, shadow or blur. They all read
     the custom properties the theme prints at runtime from your palette, so
     these sections change colour with the rest of the site.

     Every effect has a reduced-motion fallback that is a real layout, not a
     broken one. The sidescroll becomes a vertical list and the wall stops.
     Neither degrades into an empty box.

   Contents
   2. Draggable slider
   3. Horizontal scroll
   4. Photo wall
   5. Overlay header
   6. Overlay menu
   7. Reduced motion, reduced transparency, contrast, print
   ========================================================================== */

/* 2. Draggable slider
   --------------------------------------------------------------------------
   The rail is a real overflow-x scroller and stays one. Script drags it by
   writing scrollLeft, so the browser and the code agree on one number.

   The earlier version switched overflow off and drove a transform instead,
   which meant any scroll the browser performed on its own — focusing a
   card, find-in-page, a trackpad swipe — moved the rail by an amount the
   transform did not know about. That was the glitch. `is-draggable` now
   only affects the cursor and the scrollbar; it never touches overflow.
   ========================================================================== */

.u-slider-section {
	overflow: hidden; /* The rail runs past the viewport edge on purpose. */
}

.u-slider {
	position: relative;
	margin-top: clamp(28px, 4vw, 48px);
}

.u-slider-rail {
	display: flex;
	gap: clamp(16px, 2vw, 28px);

	/* Aligns the first card with the container, and lets the last one clear
	   the right edge. A trailing pseudo-element is used for the end padding
	   because padding-right on a flex scroll container is dropped from
	   scrollWidth in several browsers, which makes the last card
	   unreachable — a very good imitation of a broken slider. */
	padding-left: max(24px, calc((100% - var(--u-container)) / 2));
	padding-block: 8px 24px;

	overflow-x: auto;
	overflow-y: hidden;
	overscroll-behavior-x: contain;
	-webkit-overflow-scrolling: touch;

	/* Vertical page scroll must keep working when a touch starts on a card. */
	touch-action: pan-y;
}

.u-slider-rail::after {
	content: "";
	flex: 0 0 max(24px, calc((100% - var(--u-container)) / 2));
}

/* Script is driving. Hide the scrollbar and show the grab affordance —
   the bar underneath already reports position. */
.u-slider.is-draggable .u-slider-rail {
	cursor: grab;
	scrollbar-width: none;
}

.u-slider.is-draggable .u-slider-rail::-webkit-scrollbar {
	display: none;
}

.u-slider.is-dragging .u-slider-rail {
	cursor: grabbing;
	user-select: none;
	scroll-behavior: auto; /* Never smooth-scroll a drag. */
}

/* No script: keep the scrollbar. It is the only cue that the row scrolls. */
.u-slider:not(.is-draggable) .u-slider-rail {
	scrollbar-width: thin;
}

.u-slide {
	position: relative;
	flex: 0 0 auto;
	width: clamp(240px, 30vw, 380px);
	display: flex;
	flex-direction: column;
	gap: 14px;
	padding: clamp(20px, 2.4vw, 30px);
	border: 1px solid var(--u-border);
	border-radius: var(--u-radius-lg);
	background: var(--u-surface);
	box-shadow: var(--u-shadow-sm);
	scroll-snap-align: start;
	text-decoration: none;
	color: inherit;
	transition:
		transform var(--u-response-fast) var(--u-ease-out),
		box-shadow var(--u-response-fast) var(--u-ease-out);
}

/* Only lift on hover for pointers that can actually hover. On touch this
   would fire on tap and stick. */
@media (hover: hover) {
	a.u-slide:hover {
		transform: translate3d(0, -6px, 0);
		box-shadow: var(--u-shadow-lg);
	}
}

.u-slide:focus-visible {
	outline: 2px solid var(--u-primary);
	outline-offset: 4px;
}

.u-slide-num {
	font-family: var(--u-font-heading);
	font-size: 0.82rem;
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.14em;
	color: var(--u-muted);
}

.u-slide-media {
	aspect-ratio: 4 / 3;
	border-radius: var(--u-radius);
	overflow: hidden;
	background: rgba(var(--u-text-rgb), 0.04);
}

.u-slide-media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	/* A dragged card must not start a native image drag. */
	-webkit-user-drag: none;
	user-select: none;
}

.u-slide-body h3 {
	margin: 0 0 8px;
	font-size: clamp(1.15rem, 2vw, 1.5rem);
	line-height: 1.15;
}

.u-slide-body p {
	margin: 0;
	color: var(--u-muted);
	font-size: 0.96rem;
	line-height: 1.6;
}

.u-slider-hint {
	display: flex;
	align-items: center;
	gap: 14px;
	width: min(var(--u-container), 92vw);
	margin: 4px auto 0;
	padding-inline: 24px;
}

.u-slider-bar {
	position: relative;
	flex: 1;
	height: 2px;
	background: var(--u-border);
	border-radius: 2px;
	overflow: hidden;
}

.u-slider-bar > span {
	position: absolute;
	inset: 0;
	background: var(--u-text);
	transform: scaleX(0);
	transform-origin: left center;
}

.u-slider-hint em {
	font-style: normal;
	font-size: 0.78rem;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--u-muted);
}


/* 3. Horizontal scroll
   --------------------------------------------------------------------------
   The wrapper is tall (height set by script from the measured track width),
   the inner panel sticks, and the track is translated by scroll position.
   No wheel interception anywhere — the scrollbar keeps telling the truth.
   ========================================================================== */

.u-sidescroll {
	position: relative;
}

.u-sidescroll-pin {
	position: sticky;
	top: 0;
	height: 100vh;
	height: 100svh; /* Avoids the mobile URL bar resize jump. */
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: clamp(20px, 3vw, 40px);
	overflow: hidden;
}

.u-sidescroll-head {
	flex: 0 0 auto;
}

.u-sidescroll-head .u-section-head {
	margin-bottom: 0;
}

.u-sidescroll-track {
	display: flex;
	align-items: stretch;
	gap: clamp(20px, 3vw, 44px);
	padding-inline: max(24px, calc((100vw - var(--u-container)) / 2));
	will-change: transform;
}

.u-side-panel {
	flex: 0 0 auto;
	width: clamp(260px, 34vw, 460px);
	display: flex;
	flex-direction: column;
	gap: 14px;
	padding: clamp(24px, 3vw, 40px);
	border: 1px solid var(--u-border);
	border-radius: var(--u-radius-lg);
	background: var(--u-surface);
	box-shadow: var(--u-shadow-sm);
}

.u-side-num {
	font-family: var(--u-font-heading);
	font-size: clamp(2.6rem, 6vw, 4.4rem);
	line-height: 0.9;
	font-variant-numeric: tabular-nums;
	letter-spacing: -0.04em;
	color: transparent;
	-webkit-text-stroke: 1.5px var(--u-border);
}

.u-side-panel h3 {
	margin: 0;
	font-size: clamp(1.3rem, 2.4vw, 1.9rem);
	line-height: 1.1;
}

.u-side-panel p {
	margin: 0;
	color: var(--u-muted);
	line-height: 1.65;
}

.u-side-meta {
	margin-top: auto;
	padding-top: 12px;
	font-size: 0.8rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--u-primary);
}

.u-sidescroll-bar {
	position: relative;
	width: min(var(--u-container), 92vw);
	height: 2px;
	margin: 0 auto;
	background: var(--u-border);
	border-radius: 2px;
	overflow: hidden;
	flex: 0 0 auto;
}

.u-sidescroll-bar > span {
	position: absolute;
	inset: 0;
	background: var(--u-text);
	transform: scaleX(0);
	transform-origin: left center;
}

/* Unpinned: script could not run, or reduced motion is on. Becomes an
   ordinary stacked list, which is a real layout rather than a fallback. */
.u-sidescroll.is-static,
.u-sidescroll:not(.is-pinned) {
	height: auto !important;
}

.u-sidescroll.is-static .u-sidescroll-pin,
.u-sidescroll:not(.is-pinned) .u-sidescroll-pin {
	position: static;
	height: auto;
	padding-block: clamp(48px, 8vw, 100px);
	overflow: visible;
}

.u-sidescroll.is-static .u-sidescroll-track,
.u-sidescroll:not(.is-pinned) .u-sidescroll-track {
	flex-wrap: wrap;
	transform: none !important;
	width: min(var(--u-container), 92vw);
	margin-inline: auto;
	padding-inline: 24px;
}

.u-sidescroll.is-static .u-side-panel,
.u-sidescroll:not(.is-pinned) .u-side-panel {
	width: 100%;
	max-width: 640px;
}

.u-sidescroll.is-static .u-sidescroll-bar,
.u-sidescroll:not(.is-pinned) .u-sidescroll-bar {
	display: none;
}


/* 4. Photo wall
   --------------------------------------------------------------------------
   Two rows, images doubled in the markup so the modulo wrap never shows a
   gap. Driven by scroll position rather than a timer, so it rewinds when
   you scroll back up instead of crawling on its own.
   ========================================================================== */

.u-wall {
	padding-block: clamp(48px, 7vw, 96px);
	overflow: hidden;
}

.u-wall-row {
	margin-top: clamp(12px, 1.6vw, 20px);
	overflow: hidden;
}

.u-wall-track {
	display: flex;
	gap: clamp(10px, 1.4vw, 18px);
	width: max-content;
	will-change: transform;
}

/* Rows start offset from each other so the two never line up into an
   accidental grid. */
.u-wall-row-1 .u-wall-track {
	margin-left: -6vw;
}

.u-wall-item {
	flex: 0 0 auto;
	margin: 0;
	width: clamp(150px, 20vw, 300px);
	aspect-ratio: 4 / 3;
	border-radius: var(--u-radius);
	overflow: hidden;
	background: rgba(var(--u-text-rgb), 0.04);
}

.u-wall-row-1 .u-wall-item {
	aspect-ratio: 1 / 1;
}

.u-wall-item img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}


/* 5. Overlay header
   --------------------------------------------------------------------------
   Three columns with the wordmark dead centre. Grid rather than flex with
   space-between, because with flex the logo drifts off-centre whenever the
   two sides have different widths — which they always do.
   ========================================================================== */

.u-header-overlay .u-header-bar {
	background: transparent;
	border-bottom: 1px solid transparent;
	transition: background var(--u-response) var(--u-ease-out),
		border-color var(--u-response) var(--u-ease-out);
}

.u-header-overlay.is-scrolled .u-header-bar {
	background: var(--u-glass-bg);
	border-bottom-color: var(--u-border);
	backdrop-filter: blur(var(--u-blur)) saturate(var(--u-saturate));
	-webkit-backdrop-filter: blur(var(--u-blur)) saturate(var(--u-saturate));
}

.u-header-overlay .u-header-row {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	align-items: center;
	gap: 16px;
	min-height: 76px;
}

.u-header-overlay .u-header-start {
	display: flex;
	align-items: center;
	gap: 8px;
	justify-self: start;
}

.u-header-overlay .u-header-mid {
	justify-self: center;
}

.u-header-overlay .u-header-end {
	display: flex;
	align-items: center;
	gap: 14px;
	justify-self: end;
}

/* The toggle: two rules that become an X. Animated with transform only —
   animating height or margin here would relayout the header every frame. */
.u-overlay-toggle {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	padding: 10px 4px 10px 12px;
	background: none;
	border: 0;
	color: inherit;
	font: inherit;
	cursor: pointer;
}

.u-overlay-toggle-label {
	position: relative;
	display: inline-grid;
	font-size: 0.82rem;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	overflow: hidden;
	height: 1.2em;
}

.u-overlay-word {
	grid-area: 1 / 1;
	transition: transform 320ms var(--u-ease-out), opacity 220ms linear;
}

.u-overlay-word-close {
	transform: translateY(100%);
	opacity: 0;
}

.u-overlay-toggle[aria-expanded="true"] .u-overlay-word-open {
	transform: translateY(-100%);
	opacity: 0;
}

.u-overlay-toggle[aria-expanded="true"] .u-overlay-word-close {
	transform: translateY(0);
	opacity: 1;
}

.u-overlay-toggle-bars {
	position: relative;
	display: block;
	width: 26px;
	height: 12px;
}

.u-overlay-toggle-bars span {
	position: absolute;
	left: 0;
	width: 100%;
	height: 1.5px;
	background: currentColor;
	border-radius: 2px;
	transition: transform 380ms var(--u-ease-out);
}

.u-overlay-toggle-bars span:nth-child(1) { top: 2px; }
.u-overlay-toggle-bars span:nth-child(2) { bottom: 2px; }

.u-overlay-toggle[aria-expanded="true"] .u-overlay-toggle-bars span:nth-child(1) {
	transform: translateY(4px) rotate(45deg);
}

.u-overlay-toggle[aria-expanded="true"] .u-overlay-toggle-bars span:nth-child(2) {
	transform: translateY(-4px) rotate(-45deg);
}


/* 6. Overlay menu
   --------------------------------------------------------------------------
   Full viewport. Opens with a clip-path wipe rather than a fade, because a
   fade over live page content leaves both readable at once for 300ms and
   both look wrong. clip-path also stays on the compositor.
   ========================================================================== */

.u-overlay {
	position: fixed;
	inset: 0;
	z-index: 900;

	/* Fully opaque. This covers live page content — headlines, photographs,
	   other cards — so any transparency here means two layers of text on
	   top of each other and neither one readable. The blur is a second
	   line of defence for browsers that ignore the paint order. */
	background: var(--u-bg);
	backdrop-filter: blur(28px) saturate(1.4);
	-webkit-backdrop-filter: blur(28px) saturate(1.4);

	overflow-y: auto;
	overscroll-behavior: contain;
	clip-path: inset(0 0 100% 0);
	transition: clip-path 520ms var(--u-ease-out);
}

.u-overlay[hidden] {
	display: none;
}

.u-overlay.is-open {
	clip-path: inset(0 0 0 0);
}

:root.u-overlay-open,
:root.u-overlay-open body {
	overflow: hidden;
}

/*
 * The header has to outrank the panel while it is open.
 *
 * The overlay is fixed at z-index 900 and the header sits at 100, so
 * without this the panel covers its own toggle: the button that closes it
 * is underneath it, and the menu/close word swap happens out of sight.
 * Raising the header only while open keeps the normal stacking otherwise.
 */
:root.u-overlay-open .u-header {
	z-index: 950;
}

.u-overlay-inner {
	min-height: 100%;
	display: flex;
	align-items: center;
	padding-block: clamp(90px, 12vh, 160px) clamp(40px, 8vh, 90px);
}

.u-overlay-inner > .u-container {
	display: grid;
	grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
	gap: clamp(32px, 6vw, 90px);
	align-items: start;
	width: 100%;
}

.u-overlay-nav .u-menu {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: clamp(4px, 0.8vw, 10px);
}

.u-overlay-nav .u-menu > li > a {
	display: block;
	padding: clamp(4px, 0.8vw, 10px) 0;
	font-family: var(--u-font-heading);
	font-size: clamp(2rem, 6.5vw, 4.6rem);
	line-height: 1.02;
	letter-spacing: -0.035em;
	text-decoration: none;
	color: var(--u-text);

	/* Each item starts low and rises in. The delay is set per item below
	   rather than by script, so it works even if script only toggles the
	   class. */
	opacity: 0;
	transform: translate3d(0, 34px, 0);
	transition:
		opacity 420ms var(--u-ease-out),
		transform 520ms var(--u-ease-out),
		color 200ms linear;
}

.u-overlay.is-open .u-menu > li > a {
	opacity: 1;
	transform: none;
}

/* Stagger. Seven is enough for any sane primary menu; anything past it
   simply arrives with the seventh rather than never arriving. */
.u-overlay .u-menu > li:nth-child(1) > a { transition-delay: 90ms; }
.u-overlay .u-menu > li:nth-child(2) > a { transition-delay: 140ms; }
.u-overlay .u-menu > li:nth-child(3) > a { transition-delay: 190ms; }
.u-overlay .u-menu > li:nth-child(4) > a { transition-delay: 240ms; }
.u-overlay .u-menu > li:nth-child(5) > a { transition-delay: 290ms; }
.u-overlay .u-menu > li:nth-child(6) > a { transition-delay: 340ms; }
.u-overlay .u-menu > li:nth-child(n+7) > a { transition-delay: 390ms; }

@media (hover: hover) {
	.u-overlay-nav .u-menu > li > a:hover {
		color: var(--u-primary);
	}
}

.u-overlay-nav .u-menu > li > a:focus-visible {
	outline: 2px solid var(--u-primary);
	outline-offset: 6px;
}

/* Submenus are flattened into a small inline list. A full-screen menu with
   hover flyouts inside it is two navigation systems fighting. */
.u-overlay-nav .sub-menu {
	list-style: none;
	margin: 6px 0 14px;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 6px 20px;
}

.u-overlay-nav .sub-menu a {
	color: var(--u-muted);
	text-decoration: none;
	font-size: 0.98rem;
}

.u-overlay-nav .sub-menu a:hover {
	color: var(--u-text);
}

.u-overlay-aside {
	display: grid;
	gap: clamp(18px, 2.5vw, 30px);
	opacity: 0;
	transform: translate3d(0, 20px, 0);
	transition:
		opacity 420ms var(--u-ease-out) 340ms,
		transform 520ms var(--u-ease-out) 340ms;
}

.u-overlay.is-open .u-overlay-aside {
	opacity: 1;
	transform: none;
}

.u-overlay-block p,
.u-overlay-block a {
	margin: 0;
	color: var(--u-text);
	text-decoration: none;
	font-size: 1.02rem;
	line-height: 1.6;
}

.u-overlay-block a:hover {
	color: var(--u-primary);
}

.u-overlay-label {
	display: block;
	margin-bottom: 6px;
	font-size: 0.74rem;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--u-muted);
}

.u-overlay-block .u-social-menu {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 8px 18px;
}

/* A full-bleed dismiss target behind the content. Sits at the back so it
   never intercepts a click meant for a link. */
.u-overlay-dismiss {
	position: absolute;
	inset: 0;
	z-index: -1;
	background: none;
	border: 0;
	cursor: default;
}

@media (max-width: 860px) {
	.u-overlay-inner > .u-container {
		grid-template-columns: minmax(0, 1fr);
		gap: 36px;
	}

	.u-header-overlay .u-header-row {
		min-height: 64px;
	}

	/* On a phone the wordmark takes the left, because a centred logo with a
	   toggle beside it leaves too little room for either. */
	.u-header-overlay .u-header-row {
		grid-template-columns: auto 1fr;
	}

	.u-header-overlay .u-header-start {
		display: none;
	}

	.u-header-overlay .u-header-mid {
		justify-self: start;
	}
}


/* 7. Reduced motion, reduced transparency, contrast, print
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
	/* Everything below is a real layout, not a disabled effect. The menu
	   still opens, the rail still scrolls, the panels still read. */


	.u-overlay {
		clip-path: none;
		transition: opacity 1ms linear;
		opacity: 0;
	}

	.u-overlay.is-open {
		opacity: 1;
	}

	.u-overlay-nav .u-menu > li > a,
	.u-overlay-aside {
		opacity: 1;
		transform: none;
		transition: none;
		transition-delay: 0s;
	}

	.u-slide,
	.u-overlay-word,
	.u-overlay-toggle-bars span {
		transition: none;
	}

	.u-wall-track {
		transform: none !important;
	}

	/* The rail is already a plain scroller, so nothing needs returning —
	   but the scrollbar comes back, because without momentum or a visible
	   drag cue it is the only remaining indication that the row moves. */
	.u-slider.is-draggable .u-slider-rail {
		scrollbar-width: thin;
	}

	.u-slider.is-draggable .u-slider-rail::-webkit-scrollbar {
		display: block;
	}
}

@media (prefers-reduced-transparency: reduce) {
	.u-header-overlay.is-scrolled .u-header-bar {
		background: var(--u-surface);
		backdrop-filter: none;
		-webkit-backdrop-filter: none;
	}
}

@media (prefers-contrast: more) {
	.u-slide,
	.u-side-panel,

	.u-side-num {
		color: var(--u-text);
		-webkit-text-stroke: 0;
	}
}

@media print {
	.u-overlay,
	.u-slider-hint,
	.u-sidescroll-bar,
	.u-wall {
		display: none !important;
	}

	/* Printing must not clip a rail to one visible card. */
	.u-slider-rail,
	.u-sidescroll-track {
		display: block;
		transform: none !important;
		overflow: visible;
	}

	.u-sidescroll {
		height: auto !important;
	}

	.u-sidescroll-pin {
		position: static;
		height: auto;
	}

	.u-slide,
	.u-side-panel {
		width: auto;
		break-inside: avoid;
		box-shadow: none;
	}
}


/* 8. Centred hero
   --------------------------------------------------------------------------
   Structure only — the palette and the type belong to whichever style layer
   is active. Lives here rather than in a demo layer because the layout is a
   setting (Colours & Design → Hero layout) available in every demo.

   The scrim is the important part. Text over an arbitrary photograph has no
   contrast guarantee: the image can be swapped for a paler one at any time
   and the headline silently becomes unreadable. The overlay makes
   legibility a property of the design rather than of whichever picture
   happens to be uploaded, which is why it is not optional and why the
   setting has a floor documented in the dashboard.
   ========================================================================== */

.u-hero-centre {
	position: relative;
	display: grid;

	/*
	 * align-content, NOT place-items.
	 *
	 * place-items: center is the obvious way to centre a hero and it is
	 * wrong here, because it also sets justify-items: center — which sizes
	 * the grid item to its own content instead of letting it stretch. The
	 * container then collapses to the width of the widest line inside it
	 * and the whole hero renders at roughly half the screen.
	 *
	 * align-content centres the row vertically and leaves horizontal
	 * sizing alone, which is the only part that was ever wanted. The
	 * horizontal centring is the container's own margin-inline: auto plus
	 * text-align, exactly as everywhere else in the theme.
	 */
	align-content: center;
	justify-items: stretch;

	min-height: clamp(520px, 78vh, 860px);
	padding-block: clamp(90px, 12vh, 150px);
	overflow: hidden;
	text-align: center;
	isolation: isolate;
}

.u-hero-centre .u-hero-inner {
	max-width: 760px;
	margin-inline: auto;
	text-align: center;
	align-items: center;
	justify-items: center;
}

.u-hero-centre .u-hero-actions { justify-content: center; }

/* The decorative stage belongs to the split layout. Two focal points in one
   hero is one too many. */
.u-hero-centre .u-hero-stage,
.u-hero-centre .u-hero-panel,
.u-hero-centre .u-orbit,
.u-hero-centre .u-hero-bars { display: none; }

.u-hero-bg {
	position: absolute;
	inset: 0;
	z-index: -1;

	/*
	 * No image set: a warm gradient wash rather than an empty box. This is
	 * a designed fallback, not a placeholder — a site can ship without ever
	 * uploading a photograph and still look finished.
	 */
	background:
		radial-gradient(120% 90% at 20% 0%, rgba(var(--u-primary-rgb), 0.18), transparent 60%),
		radial-gradient(100% 80% at 85% 20%, rgba(var(--u-accent-rgb), 0.16), transparent 62%),
		linear-gradient(180deg, var(--u-surface), var(--u-bg));
}

.u-hero.has-image .u-hero-bg {
	background-image: var(--u-hero-image);
	background-size: cover;
	background-position: center;
	background-repeat: no-repeat;
}

/*
 * The scrim. A separate layer rather than a semi-transparent background on
 * the image itself, so the darkness can be tuned without touching the
 * photograph, and so it composites on the GPU.
 *
 * --u-hero-tint lets a style layer warm or cool it. Neutral black over a
 * warm photograph greys it out, which is rarely what anyone wants.
 */
.u-hero.has-image .u-hero-bg::after {
	content: "";
	position: absolute;
	inset: 0;
	background:
		linear-gradient(
			180deg,
			rgba(var(--u-hero-tint, 12, 14, 18), calc(var(--u-hero-scrim, 0.5) * 1.15)) 0%,
			rgba(var(--u-hero-tint, 12, 14, 18), var(--u-hero-scrim, 0.5)) 45%,
			rgba(var(--u-hero-tint, 12, 14, 18), calc(var(--u-hero-scrim, 0.5) * 1.3)) 100%
		);
}

/* Video. Covers the hero the same way the image does, sits under the same
   scrim, and is invisible until it can actually play — a black rectangle
   fading in over the poster is worse than never showing the video. */
.u-hero-video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0;
	transition: opacity 600ms var(--u-ease-out);
	pointer-events: none;
}

.u-hero.is-playing .u-hero-video { opacity: 1; }

/*
 * YouTube / Vimeo background.
 *
 * An iframe cannot be told to object-fit: cover — the player inside keeps
 * its own 16:9 no matter what the frame is sized to, so a frame stretched
 * to the hero would letterbox with black bars top and bottom. The frame is
 * instead made deliberately larger than the hero in whichever direction is
 * needed and centred, so the overflow is cropped exactly the way cover
 * crops a photograph.
 *
 * min-width/min-height with aspect-ratio does that in one rule: the frame
 * is never smaller than the hero on either axis and always 16:9, so the
 * larger axis spills and is clipped by the hero's overflow: hidden.
 */
.u-hero-embed {
	position: absolute;
	inset: 0;
	overflow: hidden;

	/* The player has its own background and it is not yours. Hiding it
	   until the first frame has had time to arrive means the hero never
	   flashes someone else's logo on a black rectangle. */
	opacity: 0;
	transition: opacity 700ms var(--u-ease-out);
}

.u-hero.is-playing .u-hero-embed { opacity: 1; }

.u-hero-embed iframe {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	width: auto;
	height: auto;
	min-width: 100%;
	min-height: 100%;
	aspect-ratio: 16 / 9;
	border: 0;

	/*
	 * Not interactive, and this is not cosmetic. A live player sitting
	 * behind the headline would swallow clicks meant for the buttons in
	 * front of it, and YouTube's chrome offers links out of your site from
	 * underneath your own copy.
	 */
	pointer-events: none;
}

/* Older engines without aspect-ratio fall back to a viewport-relative
   version of the same idea. Slightly less exact when the hero is not full
   height, and still better than black bars. */
@supports not (aspect-ratio: 16 / 9) {
	.u-hero-embed iframe {
		width: 100vw;
		height: 56.25vw;
		min-height: 100%;
		min-width: 177.78vh;
	}
}

/* With a video the scrim has to exist even when no poster was set, or the
   text sits directly on the footage. */
.u-hero.has-video .u-hero-bg::after {
	content: "";
	position: absolute;
	inset: 0;
	background:
		linear-gradient(
			180deg,
			rgba(var(--u-hero-tint, 12, 14, 18), calc(var(--u-hero-scrim, 0.5) * 1.15)) 0%,
			rgba(var(--u-hero-tint, 12, 14, 18), var(--u-hero-scrim, 0.5)) 45%,
			rgba(var(--u-hero-tint, 12, 14, 18), calc(var(--u-hero-scrim, 0.5) * 1.3)) 100%
		);
	z-index: 1;
}

/*
 * .u-hero-bg sits at z-index: -1, which only stays inside the hero if the
 * hero is a stacking context. .u-hero-centre isolates itself; the split
 * hero does not, so without this the background escapes upward and paints
 * behind the page instead — visible as a hero that looks completely
 * unchanged no matter what image is set.
 */
.u-hero.has-image,
.u-hero.has-video { isolation: isolate; }

/* A photograph is atmosphere enough. */
.u-hero-centre .u-aurora,
.u-hero.has-image .u-hero-glow,
.u-hero.has-video .u-hero-glow,
.u-hero.has-image .u-aurora,
.u-hero.has-video .u-aurora { display: none; }

/* Two focal points in one hero is one too many, whichever layout is on. */
.u-hero.has-image .u-hero-stage,
.u-hero.has-image .u-hero-panel,
.u-hero.has-image .u-orbit,
.u-hero.has-image .u-hero-bars,
.u-hero.has-video .u-hero-stage,
.u-hero.has-video .u-hero-panel,
.u-hero.has-video .u-orbit,
.u-hero.has-video .u-hero-bars { display: none; }

/*
 * Text over media.
 *
 * This has to live here, at the structural level, rather than in a style
 * layer. The palette a demo sets is dark ink on a light page; put a
 * photograph behind it and the headline is dark-on-dark and effectively
 * gone. Ember shipped its own version of this rule, which was fine while
 * the media was Ember-only — but the background is a setting available in
 * every demo, so the contrast rule has to be too. Otherwise turning on a
 * hero image in Studio produces an invisible headline and no explanation.
 *
 * White rather than a palette variable on purpose: the scrim underneath is
 * a known darkness, so white is the one value guaranteed to clear contrast
 * against it no matter which palette is loaded.
 */
.u-hero.has-image,
.u-hero.has-video,
.u-hero.has-image .u-hero-title,
.u-hero.has-video .u-hero-title {
	color: #fff;
}

.u-hero.has-image .u-lead,
.u-hero.has-image .u-eyebrow,
.u-hero.has-video .u-lead,
.u-hero.has-video .u-eyebrow {
	color: rgba(255, 255, 255, 0.88);
}

.u-hero.has-image .u-eyebrow::before,
.u-hero.has-video .u-eyebrow::before {
	background: #fff;
}

/* The ghost button borrows the page border colour, which is a pale hairline
   meant for a light surface and disappears entirely over a photograph. */
.u-hero.has-image .u-btn-ghost,
.u-hero.has-video .u-btn-ghost {
	color: #fff;
	border-color: rgba(255, 255, 255, 0.45);
}

@media (hover: hover) {
	.u-hero.has-image .u-btn-ghost:hover,
	.u-hero.has-video .u-btn-ghost:hover {
		background: rgba(255, 255, 255, 0.12);
		border-color: rgba(255, 255, 255, 0.7);
	}
}

@media (max-width: 720px) {
	.u-hero-centre {
		min-height: clamp(440px, 68vh, 620px);
		padding-block: clamp(70px, 10vh, 110px);
	}
}

@media (prefers-contrast: more) {
	/* Whatever the setting says, the floor rises — this is the only thing
	   keeping the headline readable over an arbitrary image. */
	.u-hero.has-image,
	.u-hero.has-video { --u-hero-scrim: 0.72; }
}

@media (prefers-reduced-motion: reduce) {
	/* The script already declines to load either kind, but if one somehow
	   arrived it must not move. */
	.u-hero-video,
	.u-hero-embed { display: none; }
}

@media print {
	.u-hero-centre { min-height: 0; }
	.u-hero-bg,
	.u-hero-embed { display: none; }
	.u-hero.has-image,
	.u-hero.has-video,
	.u-hero.has-image .u-hero-title,
	.u-hero.has-video .u-hero-title { color: #000; }
}
