/*
KB Carousel.

snap    native scroll container, snapping, arrows, dots, autoplay, mouse drag
marquee duplicated track, CSS keyframe, no snapping, no controls

Slider column counts come from the core Grid layout controls. The shared
script resolves the selected maximum columns, minimum column width and
computed block gap into the variables used below.

Everything static lives here once, so multiple instances on a page share
one copy. The block template only prints per instance variables.
*/

.kb-carousel {
	--kb-carousel-slides: 1;
	--kb-carousel-gap: 0rem;
	--kb-carousel-align: center;
	--kb-carousel-color: currentColor;
	--kb-carousel-dot-size: 0.5rem;
	--kb-carousel-ratio: 0.5625;
	position: relative;
}

.kb-carousel__container {
	display: flex;
	gap: var(--kb-carousel-gap);
}

/* Longhands because Safari before 11 drops the whole flex shorthand
   when the basis is a calc(). */
.kb-carousel__slide {
	display: flex;
	flex-grow: 0;
	flex-shrink: 0;
	flex-basis: calc(
		(100% - (var(--kb-carousel-slides) - 1) * var(--kb-carousel-gap))
		/ var(--kb-carousel-slides)
	);
	min-width: 0;
	position: relative;
	overflow: hidden;
	aspect-ratio: 1 / var(--kb-carousel-ratio);
}

/* Height guarantee that does not depend on aspect-ratio support. The
   zero width padded flex item enforces the same ratio as aspect-ratio,
   so on modern browsers they agree and on old Safari it props the
   slide open instead of collapsing to the text height. The Safari
   calc() bug is specific to flex-basis, calc() in padding is safe back
   to Safari 6, so the fallback can follow the configurable ratio. */
.kb-carousel__slide::before {
	content: "";
	width: 0;
	padding-top: calc(var(--kb-carousel-ratio, 0.5625) * 100%);
}

.kb-carousel .item-media {
	display: block;
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.kb-carousel .item-text {
	position: relative;
	display: flex;
	flex-direction: column;
}

.kb-carousel .item-text > * {
	margin-top: 0;
	margin-bottom: 0;
}

/* Content position. Scoped to the slide so every block using the
   carousel (slider, logos, testimonials) gets them. */
.kb-carousel__slide.is-position-top-left { justify-content: flex-start; align-items: flex-start; }
.kb-carousel__slide.is-position-top-center { justify-content: center; align-items: flex-start; }
.kb-carousel__slide.is-position-top-right { justify-content: flex-end; align-items: flex-start; }
.kb-carousel__slide.is-position-center-left { justify-content: flex-start; align-items: center; }
.kb-carousel__slide.is-position-center-center { justify-content: center; align-items: center; }
.kb-carousel__slide.is-position-center-right { justify-content: flex-end; align-items: center; }
.kb-carousel__slide.is-position-bottom-left { justify-content: flex-start; align-items: flex-end; }
.kb-carousel__slide.is-position-bottom-center { justify-content: center; align-items: flex-end; }
.kb-carousel__slide.is-position-bottom-right { justify-content: flex-end; align-items: flex-end; }

/* Snap mode. The browser owns transport: touch momentum, trackpad,
   wheel, keyboard and scrollbar all come free. */
.kb-carousel--snap .kb-carousel__container {
	overflow-x: auto;
	overflow-y: hidden;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	/* Stops a horizontal swipe at either end triggering the iOS back
	   gesture or a pull to refresh. */
	overscroll-behavior-x: contain;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}

.kb-carousel--snap .kb-carousel__container::-webkit-scrollbar {
	display: none;
}

.kb-carousel--snap .kb-carousel__slide {
	scroll-snap-align: var(--kb-carousel-align);
	scroll-snap-stop: always;
}

.kb-carousel--snap .kb-carousel__container:focus-visible {
	outline: 0.125rem solid var(--kb-carousel-color);
	outline-offset: 0.25rem;
}

/* Only the cursor is gated by pointer capability. Everything the drag
   depends on functionally is set inline from JavaScript instead. */
.kb-carousel--snap.is-dragging .kb-carousel__slide {
	pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
	.kb-carousel--snap .kb-carousel__container {
		cursor: grab;
	}

	.kb-carousel--snap.is-dragging .kb-carousel__container {
		cursor: grabbing;
	}
}

/* Marquee mode. The track is duplicated in PHP, so translating by half
   its width lands on an identical frame and the reset is invisible.
   Composited off the main thread, unlike a per frame JS transform. */
.kb-carousel--marquee {
	overflow: hidden;
	container-type: inline-size;
}

.kb-carousel--marquee .kb-carousel__container {
	width: max-content;
	/* The gap lives on the slides as margin so half the track width is
	   exactly one full set of slides, letting the keyframe use a plain
	   -50% that Safari can animate. Longhands because WebKit mishandles
	   var() in the animation shorthand; JS overrides the duration
	   inline. */
	gap: 0;
	animation-name: kb-marquee;
	animation-duration: 30s;
	animation-timing-function: linear;
	animation-iteration-count: infinite;
	will-change: transform;
}

.kb-carousel--marquee.is-backward .kb-carousel__container {
	animation-direction: reverse;
}

/* A percentage basis cannot resolve inside a max-content track. The
   width comes from a JS set pixel variable, with container query units
   as the no JS fallback for browsers that support them. */
.kb-carousel--marquee .kb-carousel__slide {
	flex-grow: 0;
	flex-shrink: 0;
	flex-basis: calc(
		(var(--kb-carousel-viewport, 100cqi) - (var(--kb-carousel-slides) - 1) * var(--kb-carousel-gap))
		/ var(--kb-carousel-slides)
	);
	margin-right: var(--kb-carousel-gap);
	/* The ::before percentage cannot resolve against a max-content
	   track, so without aspect-ratio the slide collapses. JS measures
	   the slide and writes this in pixels. */
	min-height: var(--kb-carousel-slide-height, 0px);
}

/* Scoped to the track, not the root. The controls sit inside the root,
   so pausing on root focus or hover means the pause button pauses the
   marquee just by being clicked, and pressing play again does nothing
   until focus and pointer both leave. */
.kb-carousel--marquee .kb-carousel__container:focus-within,
.kb-carousel--marquee.is-paused .kb-carousel__container,
.kb-carousel--marquee.is-offscreen .kb-carousel__container {
	animation-play-state: paused;
}

/* iOS keeps :hover applied after a tap until the user taps elsewhere,
   which stalls the marquee for good. Mouse only. */
@media (hover: hover) and (pointer: fine) {
	.kb-carousel--marquee .kb-carousel__container:hover {
		animation-play-state: paused;
	}
}

@keyframes kb-marquee {
	from {
		transform: translate3d(0, 0, 0);
	}

	to {
		transform: translate3d(-50%, 0, 0);
	}
}

/* Controls. Touch targets meet the 44px minimum before anything else. */
.kb-carousel__controls {
	display: flex;
	gap: 0.5rem;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
	margin-top: 0.75rem;
	color: var(--kb-carousel-color);
}

:root body .kb-carousel__button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 2.75rem;
	min-height: 2.75rem;
	padding: 0;
	border: 0;
	background: transparent;
	color: inherit;
	font: inherit;
	font-size: 2.5rem;
	line-height: 1;
	cursor: pointer;
	touch-action: manipulation;
}

.kb-carousel__button:focus-visible {
	outline: 0.125rem solid currentColor;
	outline-offset: 0.125rem;
}

.kb-carousel__button[aria-disabled="true"] {
	opacity: 0.35;
	cursor: default;
}

/* Pause toggle. Pressed means paused, so the glyphs swap on state
   rather than on a JS written icon name. */
.kb-carousel__play-icon,
.kb-carousel__pause[aria-pressed="true"] .kb-carousel__pause-icon {
	display: none;
}

.kb-carousel__pause[aria-pressed="true"] .kb-carousel__play-icon {
	display: inline;
}

/* Arrows are redundant where the primary gesture is a swipe. */
.kb-carousel__prev,
.kb-carousel__next {
	display: none;
}

/* display: flex beats the UA rule for the hidden attribute, so JS
   hiding spare dots needs this to take effect. */
.kb-carousel__dots[hidden],
.kb-carousel__dot[hidden] {
	display: none;
}

.kb-carousel__dots {
	display: flex;
	gap: 0.25rem;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
}

.kb-carousel__dot {
	position: relative;
	width: 2.75rem;
	min-height: 2.75rem;
	padding: 0;
	border: 0;
	background: transparent;
	cursor: pointer;
	touch-action: manipulation;
}

.kb-carousel__dot::after {
	content: "";
	position: absolute;
	inset: 50% auto auto 50%;
	width: var(--kb-carousel-dot-size);
	height: var(--kb-carousel-dot-size);
	transform: translate(-50%, -50%);
	border-radius: 50%;
	background: var(--kb-carousel-color);
	opacity: 0.3;
	transition: opacity 0.2s ease;
}

.kb-carousel__dot[aria-current="true"]::after {
	opacity: 1;
}

.kb-carousel__dot:focus-visible {
	outline: 0.125rem solid currentColor;
	outline-offset: -0.25rem;
}

.kb-carousel__fraction {
	margin: 0;
	font-variant-numeric: tabular-nums;
}

.kb-carousel__progress {
	position: relative;
	width: 100%;
	height: 0.25rem;
	margin-top: 0.75rem;
	overflow: hidden;
	background: rgba(0, 0, 0, 0.2);
	background: color-mix(in srgb, var(--kb-carousel-color) 20%, transparent);
}

.kb-carousel__progress-fill {
	position: absolute;
	inset: 0;
	background: var(--kb-carousel-color);
	transform: scaleX(0);
	transform-origin: left center;
}

.kb-carousel__live {
	position: absolute;
	width: 0.0625rem;
	height: 0.0625rem;
	margin: -0.0625rem;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

@media (min-width: 768px) {
	.kb-carousel__prev,
	.kb-carousel__next {
		display: inline-flex;
	}

	.kb-carousel__controls {
		gap: 1rem;
	}
}

@media (prefers-reduced-motion: reduce) {
	.kb-carousel--snap .kb-carousel__container {
		scroll-behavior: auto;
	}

	.kb-carousel--marquee .kb-carousel__container {
		animation: none;
	}

	.kb-carousel__pause {
		display: none;
	}

	.kb-carousel__dot::after {
		transition: none;
	}
}