/**
 * JS Carousel.
 *
 * The shared carousel engine ported from cci-core (cci-video-testimonials +
 * cci-image-carousel): a centre-mode track of cards with fully-visible edge
 * peeks, loop cloning, drag/swipe, autoplay with hover-pause and arrows, all
 * driven by the companion js-carousel.js. No Swiper / PowerPack dependency.
 *
 * The Joe Shew carousel is the "--image" variant with a caption layer added
 * (Figma 22989:580 / 23001:6809 / 23001:5650): a full-bleed image, a gradient
 * scrim, and an uppercase title at the bottom of each card.
 */

.js-carousel {
	--js-carousel-per-view: 4;
	--js-carousel-gap: 24px;
	--js-carousel-edge-scale: 0.95;
	--js-carousel-fade-opacity: 0.2;
	/* Width of the soft edge on the trailing side — see the mask below. */
	--js-carousel-edge-fade: 76px;
	/* joeshew cards are borderless by default; the Border controls can add one. */
	--js-carousel-border-width: 0px;
	--js-carousel-border: transparent;
	/*
	 * Width of a single card slot. Slides Per View is the number of full,
	 * active cards; one edge card on each side fills the section to full width,
	 * so the viewport shows per-view + 2 slots.
	 *
	 * Card Width overrides that as the fallback value of the var rather than as
	 * a competing rule, so it wins wherever the slot is computed instead of
	 * depending on which stylesheet loads last. Some designs size the card and
	 * let the peek fall where it may — the event strip is 450 wide in a 716
	 * window — which no whole number of slides per view can express.
	 */
	--js-carousel-slot: var(
		--js-carousel-card-width,
		calc(
			(100% - (var(--js-carousel-gap) * (var(--js-carousel-per-view) + 1))) /
				(var(--js-carousel-per-view) + 2)
		)
	);
	position: relative;
	width: 100%;
	box-sizing: border-box;
}

/*
 * Trailing-edge fade. Where the strip is clipped, the design scrims the cut-off
 * card instead of slicing it (tablet 22899:5524 at 76px, mobile 22899:5458 at
 * 39), so the row reads as continuing past the edge rather than ending at it.
 *
 * Only where the viewport actually clips: with Fade Edge Slides on it is
 * overflow:visible so the neighbours can peek past it, and a mask there would
 * crop the very cards that effect exists to show.
 */
.js-carousel:not(.js-carousel--fade-edges) .js-carousel__viewport {
	-webkit-mask-image: linear-gradient(
		to right,
		#000 calc(100% - var(--js-carousel-edge-fade)),
		transparent
	);
	mask-image: linear-gradient(
		to right,
		#000 calc(100% - var(--js-carousel-edge-fade)),
		transparent
	);
}

@media (max-width: 767px) {
	.js-carousel {
		--js-carousel-edge-fade: 39px;
	}
}

/*
 * Loop off (desktop only): fit exactly Slides Per View cards in the row, all
 * active. The slot divides the container by per-view (minus the inter-card
 * gaps) instead of the centre-mode per-view + 2, so there are no edge peeks —
 * every visible card is full-size. On tablet/mobile the original peek UI is
 * kept (this rule doesn't apply below the desktop breakpoint). Card Width still
 * wins here, because it comes through as the var's fallback rather than as a
 * rule this one could out-specify. Full Details and Landscape size their own
 * wide single-card slot (with peeks), and Portrait caps its own width, so
 * they're excluded.
 */
@media (min-width: 1025px) {
	.js-carousel.js-carousel[data-loop="0"]:not(
			.js-carousel--full-details
		):not(.js-carousel--landscape):not(
			.js-carousel--portrait
		) {
		--js-carousel-slot: var(
			--js-carousel-card-width,
			calc(
				(100% - (var(--js-carousel-gap) * (var(--js-carousel-per-view) - 1))) /
					var(--js-carousel-per-view)
			)
		);
	}
}

/*
 * Portrait video cards: no Card Width / Card Height controls — the size is
 * calculated. The active card fills the row by Slides Per View, capped at a max
 * width; the height follows the 290:515 portrait ratio (so it caps at 515px when
 * the width is at its 290px max). The thumbnail covers the fixed-ratio card.
 */
.js-carousel.js-carousel--portrait {
	/* Width fills the row by per-view, capped at the 290px max. */
	--js-carousel-slot: min(
		calc(
			(100% - (var(--js-carousel-gap) * (var(--js-carousel-per-view) - 1))) /
				var(--js-carousel-per-view)
		),
		290px
	);
}

/*
 * Fixed 515px card height (a definite pixel value — a percentage- or
 * ratio-derived height resolves against the slide's undefined height and gets
 * dropped, collapsing the card). The thumbnail covers the fixed-height card.
 */
.js-carousel.js-carousel--portrait .js-carousel__card {
	height: 515px;
}

.js-carousel.js-carousel--portrait .js-carousel__thumb {
	height: 100% !important;
	object-fit: cover;
}

/*
 * Fade Edge Slides on: the active cards keep their size and the faded neighbour
 * slides bleed past the container toward the screen edges. The viewport drops
 * its overflow clip; the page/section is expected to clip horizontal overflow at
 * the screen edge. With fade off the viewport keeps overflow:hidden.
 *
 * Full Details is a single wide card with peeks and gets the same bleed so its
 * neighbour cards run past the (often boxed) container edge instead of being
 * clipped flush to it. The slot is capped to the container (see the Full Details
 * slot below), so only the peeks bleed — the active card stays fully visible.
 */
.js-carousel--portrait.js-carousel--fade-edges .js-carousel__viewport,
.js-carousel--full-details.js-carousel--fade-edges .js-carousel__viewport {
	overflow: visible;
}

/*
 * When every slide fits (count <= per-view) the carousel doesn't loop, so the
 * cards would otherwise sit left-aligned with empty space — centre them. JS adds
 * --fits and keeps the track offset at 0, so justify-content centres them.
 */
.js-carousel--fits .js-carousel__track {
	justify-content: center;
}

/*
 * Full Details has no Portrait sizing — preserve the former fixed card height
 * (the Card Height control is gone) and the cover thumbnail across breakpoints.
 */
.js-carousel--full-details .js-carousel__card {
	height: 515px;
}

.js-carousel--full-details .js-carousel__thumb {
	height: 100% !important;
	object-fit: cover;
}

.js-carousel *,
.js-carousel *::before,
.js-carousel *::after {
	box-sizing: border-box;
}

.js-carousel__viewport {
	width: 100%;
	overflow: hidden;
	/* Draggable: grab cursor at rest, grabbing while dragging (set by JS). */
	cursor: grab;
}

.js-carousel--dragging .js-carousel__viewport {
	cursor: grabbing;
	/* Don't select text while dragging the carousel. */
	-webkit-user-select: none;
	user-select: none;
}

/* Stop the thumbnail's native drag so a swipe over it moves the carousel. */
.js-carousel__thumb {
	-webkit-user-drag: none;
	user-select: none;
}

.js-carousel__track {
	display: flex;
	flex-wrap: nowrap;
	align-items: stretch;
	gap: var(--js-carousel-gap);
	will-change: transform;
	transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/*
 * The viewport holds per-view full cards plus a fully-visible edge card on
 * each side: per-view + 2 slots, with a gap between each of the per-view + 1
 * boundaries. The edge cards are scaled down (not clipped) — see .is-faded.
 */
.js-carousel__slide {
	flex: 0 0 var(--js-carousel-slot);
	max-width: var(--js-carousel-slot);
	transition:
		opacity 0.4s ease,
		transform 0.4s ease;
}

/*
 * Edge cards: fully visible but scaled down and dimmed relative to the
 * full-size active cards (JS adds .is-faded to the first/last visible slides).
 */
.js-carousel--fade-edges .js-carousel__slide.is-faded {
	opacity: var(--js-carousel-fade-opacity);
	transform: scale(var(--js-carousel-edge-scale));
}

/* While dragging, show every card full (no dim/scale) so peeks don't pass
   through the centre dimmed; the edges re-dim when the drag snaps. */
.js-carousel--dragging.js-carousel--fade-edges
	.js-carousel__slide.is-faded {
	opacity: 1;
	transform: none;
}

/*
 * Instant (no-animation) jumps — initial layout and the seamless loop reset.
 * JS toggles this class so the track move AND the edge/active state changes
 * snap without a flicker.
 */
.js-carousel--no-anim .js-carousel__track,
.js-carousel--no-anim .js-carousel__slide {
	transition: none !important;
}

/*
 * Card height follows the image height by default — the thumbnail sits in
 * normal flow at its natural ratio, and the caption is overlaid on top of it.
 * Setting a fixed Card Height switches the image to cover-fill (see the
 * height control's selectors in the widget).
 */
.js-carousel__card {
	position: relative;
	display: block;
	width: 100%;
	overflow: hidden;
	border-radius: 16px;
	/*
	 * Gradient border that respects the rounded corners: the card background is
	 * the dark fill clipped to the padding box, and the border colour/gradient
	 * is a second layer clipped to the border box.
	 */
	border: var(--js-carousel-border-width) solid transparent;
	background:
		linear-gradient(#000e22, #000e22) padding-box,
		var(--js-carousel-border) border-box;
}

.js-carousel__media {
	position: relative;
	width: 100%;
	height: 100%;
}

.js-carousel__thumb {
	display: block;
	width: 100%;
	height: auto;
	object-fit: cover;
	transition: opacity 0.4s ease;
}

/*
 * When an explicit Aspect Ratio is chosen (any value other than Auto), the
 * card sizes from aspect-ratio and the image must fill it (cover) instead of
 * dictating the height. The js-carousel-ratio-* class comes from the control's
 * prefix_class. !important beats the theme's img { height: auto }.
 */
[class*="js-carousel-ratio-"]:not(.js-carousel-ratio-auto) .js-carousel__media {
	position: absolute;
	inset: 0;
	height: 100%;
}

[class*="js-carousel-ratio-"]:not(.js-carousel-ratio-auto) .js-carousel__thumb {
	height: 100% !important;
	object-fit: cover;
}

/* Gradient so the caption is legible over the image. */
.js-carousel__card::after {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(
		to top,
		rgba(0, 5, 16, 0.92) 0%,
		rgba(0, 5, 16, 0.45) 32%,
		rgba(0, 5, 16, 0) 60%
	);
	pointer-events: none;
	z-index: 1;
	transition: opacity 0.4s ease;
}

.js-carousel__play {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 2;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	padding: 0;
	/* Override theme button styling that would otherwise show a background. */
	background: none !important;
	border: none;
	box-shadow: none;
	-webkit-appearance: none;
	appearance: none;
	cursor: pointer;
	transition:
		transform 0.2s ease,
		width 0.2s ease,
		height 0.2s ease,
		opacity 0.4s ease;
}

.js-carousel__play-icon {
	width: 100%;
	height: 100%;
	display: block;
	object-fit: contain;
	pointer-events: none;
}

/*
 * Play icon hover effects — gated by the widget's js-carousel-play-hover-* prefix
 * class (Play Icon Hover Effect control).
 *
 * Grow: enlarge on card hover. The exact hover size comes from the Elementor
 * control; this is the fallback matching its default.
 */
[class*="js-carousel-play-hover-grow"]
	.js-carousel__card--playable:hover
	.js-carousel__play {
	width: 50px;
	height: 50px;
}

/*
 * Reveal: hide the icon until the card is hovered. Applies only on devices
 * with a real hover (desktop) — touch devices keep the icon visible.
 * Excludes is-playing so it never resurfaces over a playing video.
 */
@media (hover: hover) {
	[class*="js-carousel-play-hover-reveal"] .js-carousel__play {
		opacity: 0;
	}

	[class*="js-carousel-play-hover-reveal"]
		.js-carousel__card--playable:not(.is-playing):hover
		.js-carousel__play {
		opacity: 1;
	}
}

.js-carousel__caption {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 2;
	display: flex;
	flex-direction: column;
	gap: 20px;
	padding: 28px 20px;
	pointer-events: none;
	transition: opacity 0.4s ease;
}

.js-carousel__title {
	color: #ffffff;
	font-family: "Inter", sans-serif;
	font-size: 18px;
	font-weight: 500;
	line-height: 28px;
}

.js-carousel__name {
	color: #ffffff;
	font-family: "Inter", sans-serif;
	font-size: 14px;
	font-weight: 600;
	line-height: 20px;
}

/* Inline player injected by JS on play — fades in once the video is ready. */
.js-carousel__iframe {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
	z-index: 3;
	opacity: 0;
	transition: opacity 0.5s ease;
}

/* On play: fade the thumbnail, play button, gradient and caption out. */
.js-carousel__card.is-playing .js-carousel__thumb,
.js-carousel__card.is-playing .js-carousel__play,
.js-carousel__card.is-playing::after,
.js-carousel__card.is-playing .js-carousel__caption {
	opacity: 0;
	pointer-events: none;
}

/* Fade the player in after it finishes loading. */
.js-carousel__card.is-video-ready .js-carousel__iframe {
	opacity: 1;
}

/* Loading spinner: shown from the play tap until the embed is ready to play, so
   a slow-loading player shows a clear "loading" state instead of a blank card.
   On the media's ::before (its ::after is used for the caption scrim in some
   variants). Removed as soon as the card flips to .is-video-ready. */
.js-carousel__card.is-playing:not(.is-video-ready) .js-carousel__media::before {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 46px;
	height: 46px;
	margin: -23px 0 0 -23px;
	border-radius: 50%;
	border: 3px solid rgba(255, 255, 255, 0.28);
	border-top-color: #ffffff;
	animation: js-carousel-spin 0.8s linear infinite;
	z-index: 4;
	pointer-events: none;
}

@keyframes js-carousel-spin {
	to {
		transform: rotate(360deg);
	}
}

/* "Tap for sound" only appears once the player is ready (i.e. after the loading
   spinner clears), never over the still-loading frame. */
.js-carousel__card:not(.is-video-ready) .js-carousel__unmute {
	display: none;
}

/* iOS "Tap for sound": iOS starts embedded video muted from a tap, so a centred
   pill lets a second tap (a direct gesture on the playing video) turn sound on.
   Added by the engine over a muted iOS playback; z-index sits above the iframe. */
.js-carousel__unmute {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 4;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	margin: 0;
	padding: 11px 18px;
	border: 0;
	border-radius: 999px;
	background: rgba(0, 5, 16, 0.82);
	color: #ffffff;
	font-family: "Inter", sans-serif;
	font-size: 14px;
	font-weight: 600;
	line-height: 1;
	white-space: nowrap;
	cursor: pointer;
	-webkit-appearance: none;
	appearance: none;
	box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
	-webkit-backdrop-filter: blur(4px);
	backdrop-filter: blur(4px);
}

.js-carousel__unmute:hover {
	background: rgba(0, 5, 16, 0.92);
}

.js-carousel__unmute-icon {
	width: 18px;
	height: 18px;
	display: block;
	flex: 0 0 auto;
}

/* On-load init-flash guard. The video variants run their JS on load (Delay JS
   is switched off on their pages for sound-on-tap), so the server-rendered
   slides would briefly show as a plain, mis-sized row before the engine lays
   them out. Hold the track hidden until the engine signals --ready, with a
   failsafe reveal so it can never stay hidden if the script fails. The Text /
   Image carousel variants keep Delay JS and are intentionally excluded so they
   never blank-flash. */
.js-carousel--portrait .js-carousel__track,
.js-carousel--full-details .js-carousel__track,
.js-carousel--landscape .js-carousel__track,
.js-carousel--popular .js-carousel__track {
	opacity: 0;
	animation: js-carousel-reveal 0s linear 2s forwards;
}

.js-carousel--ready.js-carousel--portrait .js-carousel__track,
.js-carousel--ready.js-carousel--full-details .js-carousel__track,
.js-carousel--ready.js-carousel--landscape .js-carousel__track,
.js-carousel--ready.js-carousel--popular .js-carousel__track {
	opacity: 1;
	animation: none;
	/* Keep the slide (transform) transition — a bare `opacity` transition here
	   would replace it and make the track jump instead of slide. The seamless
	   loop reset still overrides both via `--no-anim { transition: none }`. */
	transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.25s ease;
}

@keyframes js-carousel-reveal {
	to {
		opacity: 1;
	}
}

/* =========================================
   Arrows
========================================= */
.js-carousel__arrow {
	position: absolute;
	top: 50%;
	/* Horizontal position (left) is set by JS to the active card edges. */
	left: 0;
	transform: translate(-50%, -50%);
	/*
	 * Hidden until the JS initialises (--ready) — it positions the arrows and
	 * decides whether they're needed at all. Prevents the unpositioned arrows
	 * flashing at the left edge while the script loads.
	 */
	visibility: hidden;
	z-index: 5;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	color: #002c56;
	background: #ffffff;
	border: none;
	/* !important so Elementor's kit/page button border-radius can't override it. */
	border-radius: 12px !important;
	box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
	cursor: pointer;
	transition:
		opacity 0.2s ease,
		transform 0.2s ease;
}

/* Rendered icon (icon font or SVG) inside the arrow button. */
.js-carousel__arrow i {
	font-size: 25px;
	line-height: 1;
	color: inherit;
}

.js-carousel__arrow svg {
	width: 25px;
	height: 25px;
	fill: currentColor;
}

.js-carousel__arrow:hover {
	transform: translate(-50%, -50%) scale(1.08);
}

.js-carousel--ready .js-carousel__arrow {
	visibility: visible;
}

.js-carousel__arrow[disabled] {
	opacity: 0.3;
	cursor: default;
}

[data-drag="0"] .js-carousel__viewport {
	cursor: default;
}

/* -------- Background video --------

   A card can carry a silent looping video behind its caption instead of a
   still. The <img> stays and stays visible underneath: it is the poster while
   the player loads, and the whole card if the video never plays — a blocked
   autoplay, a reduced-motion preference, a provider that is down.

   Covering a portrait card with 16:9 footage is the whole trick here. Height
   drives it — 100% of the card, with the width following from aspect-ratio, so
   the video overflows sideways and the card's overflow crops it. min-width and
   min-height cover the other case, a card wider than 16:9, so this holds
   whatever shape the card is set to.
   ========================================= */
.js-carousel__video {
	position: absolute;
	top: 50%;
	left: 50%;
	z-index: 0;
	display: block;
	height: 100%;
	aspect-ratio: 16 / 9;
	min-width: 100%;
	min-height: 100%;
	transform: translate(-50%, -50%);
	opacity: 0;
	transition: opacity 0.6s ease;
	pointer-events: none;
}

/* Faded in once the player has actually mounted, so a card never flashes black
   between the still going and the video arriving. */
.js-carousel__video.is-ready {
	opacity: 1;
}

.js-carousel__video iframe {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
	pointer-events: none;
}

/* The scrim and caption sit above the video, as they do above the still. */
.js-carousel__scrim,
.js-carousel__caption {
	z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
	.js-carousel__video {
		transition: none;
	}
}

/* =========================================
   Responsive
========================================= */
@media (max-width: 1024px) {
	.js-carousel__title {
		font-size: 16px;
	}

	/*
	 * Full bleed. Below desktop the strip is a row the reader drags rather than
	 * a block of content, so the container's gutters do not apply to it: the
	 * cards should run to the screen edges and fade there, not stop short and
	 * be cut. 100vw with the gutters pulled back out — 50% is half the padded
	 * container, 50vw half the screen, so the difference is exactly one gutter
	 * on each side whatever the container's padding happens to be.
	 *
	 * The theme clips the document's horizontal overflow, so 100vw cannot
	 * introduce a scrollbar of its own.
	 *
	 * Not for Fade Edge Slides. That variant already runs past the container
	 * on purpose — overflow: visible, with the theme clipping at the screen
	 * edge — and sizes its slots from the container width. Widening it to the
	 * screen would resize every card in it to solve a problem it does not
	 * have.
	 */
	.js-carousel:not(.js-carousel--fade-edges) {
		width: 100vw;
		margin-inline: calc(50% - 50vw);
	}

	/*
	 * And a fade at both ends rather than only the trailing one. Once the strip
	 * reaches the screen edges there is a card running off the leading side
	 * too, and it was being cut hard while its opposite number faded.
	 */
	.js-carousel:not(.js-carousel--fade-edges) .js-carousel__viewport {
		-webkit-mask-image: linear-gradient(
			to right,
			transparent 0,
			#000 var(--js-carousel-edge-fade),
			#000 calc(100% - var(--js-carousel-edge-fade)),
			transparent 100%
		);
		mask-image: linear-gradient(
			to right,
			transparent 0,
			#000 var(--js-carousel-edge-fade),
			#000 calc(100% - var(--js-carousel-edge-fade)),
			transparent 100%
		);
	}
}

@media (max-width: 767px) {
	.js-carousel__caption {
		padding: 14px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.js-carousel__track,
	.js-carousel__slide,
	.js-carousel__arrow,
	.js-carousel__play {
		transition: none;
	}
}

/* =========================================
   Full Details (desktop only)

   On desktop the carousel shows one wide card per view, laid out side by side:
   video on the left, the full title and description on the right, with small
   dimmed peeks of the neighbouring cards each edge. On tablet and mobile it
   falls back to the regular card UI — the side panel and the video name overlay
   are hidden and the standard overlay caption is shown instead.

   The Slides Per View / Card Width controls are hidden in this mode, so the
   per-view and slot values are set here per breakpoint (matching the regular
   defaults below desktop).
========================================= */

/* Base = tablet/mobile fallback: regular card sizing, full-details extras off. */
.js-carousel--full-details {
	--js-carousel-per-view: 1;
	--js-carousel-slot: 290px;
}

.js-carousel--full-details .js-carousel__details,
.js-carousel--full-details .js-carousel__media-caption {
	display: none;
}

@media (min-width: 768px) and (max-width: 1024px) {
	.js-carousel--full-details {
		--js-carousel-per-view: 2;
	}
}

/* Desktop: the wide side-by-side Full Details layout. */
@media (min-width: 1025px) {
	.js-carousel--full-details {
		--js-carousel-per-view: 1;
		/*
		 * Slide max width comes from the Slide Max Width control (default 1000px),
		 * capped to the container so the card and its edge peeks never overflow
		 * the parent on narrower screens (matches the Landscape single-wide-card
		 * mode). Without the cap a fixed slot wider than the container bleeds the
		 * active card past the parent's edges.
		 */
		--js-carousel-slot: min(var(--js-carousel-full-slot, 1000px), 90%);
	}

	.js-carousel--full-details .js-carousel__card {
		display: flex;
		align-items: stretch;
		text-align: left;
	}

	/*
	 * The card-wide legibility gradient is replaced by a media-only gradient so
	 * the text panel keeps the flat card background.
	 */
	.js-carousel--full-details .js-carousel__card::after {
		display: none;
	}

	.js-carousel--full-details .js-carousel__media {
		flex: 0 0 46%;
		max-width: fit-content;
	}

	.js-carousel--full-details .js-carousel__media::after {
		content: "";
		position: absolute;
		inset: 0;
		background: linear-gradient(
			to top,
			rgba(0, 5, 16, 0.92) 0%,
			rgba(0, 5, 16, 0.35) 34%,
			rgba(0, 5, 16, 0) 62%
		);
		pointer-events: none;
		z-index: 1;
		transition: opacity 0.4s ease;
	}

	.js-carousel--full-details .js-carousel__thumb {
		height: 100%;
		object-fit: cover;
	}

	/* Name block overlaid on the video (bottom-left): "Watch The Story" + name. */
	.js-carousel--full-details .js-carousel__media-caption {
		position: absolute;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 2;
		display: flex;
		flex-direction: column;
		gap: 16px;
		padding: 24px 22px;
		pointer-events: none;
		transition: opacity 0.4s ease;
	}

	.js-carousel__watch {
		color: #ffffff;
		font-family: "Inter", sans-serif;
		font-size: 20px;
		font-weight: 700;
		line-height: 28px;
	}

	/* Text panel on the right: full title, gold divider, description. */
	.js-carousel--full-details .js-carousel__details {
		position: relative;
		z-index: 2;
		display: flex;
		flex: 1 1 auto;
		flex-direction: column;
		justify-content: center;
		gap: 22px;
		padding: 0px 40px;
	}

	.js-carousel--full-details .js-carousel__full-title {
		margin: 0;
		font-family: "Inter", sans-serif;
		font-size: 28px;
		font-weight: 700;
		line-height: 36px;
		/* Brand accent, clipped to the text. */
		background: var(--js-carousel-title-accent, currentColor) !important;
		-webkit-background-clip: text !important;
		background-clip: text !important;
		-webkit-text-fill-color: transparent !important;
		color: transparent !important;
	}

	.js-carousel__details-divider {
		display: block;
		width: 100%;
		height: 1px;
		background: var(--js-carousel-border);
	}

	.js-carousel__description {
		color: #ffffff;
		font-family: "Inter", sans-serif;
		font-size: 18px;
		font-weight: 400;
		line-height: 28px;
	}

	.js-carousel__description p {
		margin: 0 0 1em;
	}

	.js-carousel__description p:last-child {
		margin-bottom: 0;
	}

	/* Show the desktop-only extras; hide the standard overlay caption. */
	.js-carousel--full-details .js-carousel__caption {
		display: none;
	}

	/* On play: fade the media overlay (gradient + name) out, like the caption. */
	.js-carousel--full-details
		.js-carousel__card.is-playing
		.js-carousel__media::after,
	.js-carousel--full-details
		.js-carousel__card.is-playing
		.js-carousel__media-caption {
		opacity: 0;
		pointer-events: none;
	}
}
/**
 * Image-card variant
 *
 * Image cards in the shared carousel engine (an "--image" variant of
 * js-carousel). The active cards fill the carousel's parent
 * container by Slides Per View and the faded edge cards overflow past it toward
 * the screen edges — the same behaviour as the Portrait video testimonials and
 * the testimonials row. The card height comes from the Card Height control.
 * This file only swaps the card content for a cover image — the track, edge
 * fade, arrows and swipe all come from js-carousel.css.
 *
 * Width model: the active cards fill 100% of the parent (e.g. a 1200px column);
 * the edge cards (and the loop clones) bleed out past the parent. The viewport
 * drops its overflow clip so they can show, and the theme's
 * html/body { overflow-x: hidden } clips them at the screen edge — so they peek
 * out toward the edges without adding a horizontal scrollbar. Put the widget in
 * a column of the width you want the active cards to fill.
 *
 * The card height is a definite pixel value (the Card Height control), not an
 * aspect-ratio: a ratio-derived height resolves against the flex slide's
 * undefined height and collapses to zero. The base Portrait video cards use a
 * fixed height for the same reason.
 */
/*
 * Doubled class (0,2,0) so these override the base .js-carousel
 * defaults (0,1,0) regardless of stylesheet load order — js-carousel.css
 * is registered without a dependency on js-carousel.css, so it can
 * load before the base, in which case an equal-specificity rule would lose the
 * cascade (this is why the active cards came out at the centre-mode width). The
 * base uses the same doubled-class trick for its own slot overrides.
 */
.js-carousel.js-carousel--image {
	--js-carousel-card-height: 500px;
	/*
	 * Active cards fill the parent by Slides Per View, capped at a max width
	 * (mirrors the Text Testimonials variant). The centre-mode default instead
	 * fits per-view + 2 cards INSIDE the box, shrinking the active cards to a
	 * fraction of the width.
	 */
	--js-carousel-slot: min(
		calc(
			(100% - (var(--js-carousel-gap) * (var(--js-carousel-per-view) - 1))) /
				var(--js-carousel-per-view)
		),
		400px
	);
}

/* Tablet/mobile: same fill, smaller max width (mirrors the Text Testimonials
   variant) so the cards don't read as over-thick next to the text row. */
@media (max-width: 1024px) {
	/* Never wider than the row allows, never wider than the card is on desktop,
	   and never more than 320.
	   The middle term is what keeps the two image carousels apart below
	   desktop: the feature strip sets no Card Width, so it falls back to the
	   285 its desktop card measures and stays 285 on a phone (23158:689,
	   23158:733); the event strip sets 450, so the 320 ceiling catches it
	   instead (22899:5428, 22899:5494). A flat 320 gave the feature cards 35px
	   they do not have in any frame. */
	.js-carousel.js-carousel--image {
		--js-carousel-slot: min(
			calc(
				(100% - (var(--js-carousel-gap) * (var(--js-carousel-per-view) - 1))) /
					var(--js-carousel-per-view)
			),
			var(--js-carousel-card-width, 285px),
			320px
		);
	}
}

/*
 * When every slide fits (count <= per-view) size the cards by the actual count
 * so the few cards fill the full width rather than sitting at the narrower
 * per-view width. --js-carousel-fit-count is set by the JS.
 */
.js-carousel--image.js-carousel--fits {
	--js-carousel-slot: calc(
		(100% - (var(--js-carousel-gap) * (var(--js-carousel-fit-count) - 1))) /
			var(--js-carousel-fit-count)
	);
}

/*
 * Let the edge cards / clones overflow the parent toward the screen edges. The
 * theme clips html/body overflow-x, so this can't add a horizontal scrollbar.
 * With Fade Edge Slides off the viewport keeps overflow:hidden (no overflow).
 */
.js-carousel--image.js-carousel--fade-edges .js-carousel__viewport {
	overflow: visible;
}

/* Definite card height (see file header); the image covers it. */
.js-carousel--image .js-carousel__card {
	height: var(--js-carousel-card-height);
}

/* No legibility gradient — image cards have no caption overlaid on them. */
.js-carousel--image .js-carousel__card::after {
	display: none;
}

/* The image covers the fixed-height card. !important on the dimensions +
   object-fit beats a theme's generic `img { height: auto }` / `max-width: 100% }`
   reset, which would otherwise collapse the cover image (mirrors the base
   js-carousel thumb hardening). The card clips via overflow:hidden. */
.js-carousel--image .js-carousel__image {
	display: block;
	width: 100% !important;
	height: 100% !important;
	max-width: none !important;
	object-fit: cover !important;
	-webkit-user-drag: none;
	user-select: none;
}

/* =========================================================================
   Caption layer (Joe Shew).

   The image variant gains a gradient scrim and a bottom-anchored caption:
   Figma 22989:580 — 285x505 card, radius 12 on #111827, title Inter Bold
   16/24 uppercase centred, caption padding 0/20/28 with a 20px gap.
   ========================================================================= */

.js-carousel--image .js-carousel__card {
	background: var(--js-carousel-card-bg, var(--js-color-primary, #111827));
	isolation: isolate;
}

/* Lifts the caption off the photo. The design supplies this as a flattened
   asset; reproduced as a CSS gradient in the card's own colour so it scales
   with the card and costs no request. */
.js-carousel__scrim {
	position: absolute;
	inset: 0;
	z-index: 1;
	background: linear-gradient(
		180deg,
		rgba(17, 24, 39, 0) 45%,
		rgba(17, 24, 39, 0.75) 78%,
		rgba(17, 24, 39, 0.95) 100%
	);
	pointer-events: none;
}

.js-carousel__caption {
	position: absolute;
	right: 0;
	bottom: 0;
	left: 0;
	z-index: 2;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 20px;
	padding: 0 20px 28px;
}

.js-carousel__card-title {
	margin: 0;
	color: var(--js-carousel-title, var(--js-color-white, #ffffff));
	font-family: "Inter", sans-serif;
	font-size: 16px;
	font-weight: 700;
	line-height: 24px;
	letter-spacing: 0;
	text-align: center;
	text-transform: uppercase;
	overflow-wrap: anywhere;
}

.js-carousel__card-sub {
	margin: 0;
	color: rgba(255, 255, 255, 0.8);
	font-family: "Inter", sans-serif;
	font-size: 14px;
	font-weight: 400;
	line-height: 20px;
	text-align: center;
}

/* -------- Event-strip variant (Figma 22460:22494) --------

   The poster centres a shouty uppercase title; the event strip puts the venue
   name in the bottom-left, as typed. Both are the same card — these two
   modifiers are all that separates them. */
.js-carousel--caption-left .js-carousel__caption {
	align-items: flex-start;
	justify-content: flex-end;
}

.js-carousel--caption-left .js-carousel__card-title,
.js-carousel--caption-left .js-carousel__card-sub {
	text-align: left;
}

.js-carousel--title-plain .js-carousel__card-title {
	font-size: 18px;
	font-weight: 500;
	line-height: 28px;
	text-transform: none;
}

/* A linked card is an <a>; keep it looking like the card, not a link. */
a.js-carousel__card {
	display: block;
	text-decoration: none;
}

a.js-carousel__card:focus-visible {
	outline: 2px solid var(--js-color-highlight, #b28b2c);
	outline-offset: 3px;
}
