/**
 * Front-end styles for the DHCL Card Carousel.
 *
 * Structure + layout only; colors, radii, shadow, per-view and image height
 * are set per instance via the renderer's scoped <style> as CSS custom
 * properties. Low specificity, no resets beyond box-sizing, no !important.
 */

.dhcl-cards {
	--dhcl-cd-per: 3;
	--dhcl-cd-gap: 24px;
	--dhcl-cd-radius: 12px;
	--dhcl-cd-img-h: 220px;

	/* Card body rhythm. One spacing token for every gap inside the body, and a
	   reserved height per zone so the rows line up across cards whatever the
	   content length. The reserved heights are calc()'d from the same
	   font-size / line-height pairs the zones render at, so a theme that
	   overrides the type scale keeps its zones in step. */
	--dhcl-cd-pad: 22px;
	--dhcl-cd-row-gap: 12px;
	--dhcl-cd-tag-fs: 12px;
	--dhcl-cd-tag-lh: 1.25;
	--dhcl-cd-title-fs: 18px;
	--dhcl-cd-title-lh: 1.3;
	--dhcl-cd-title-lines: 2;
	--dhcl-cd-desc-fs: 14px;
	--dhcl-cd-desc-lh: 1.5;
	--dhcl-cd-desc-lines: 2;
	/* Pill height: one line of text + 3px padding + 1px border, top and bottom. */
	--dhcl-cd-tag-h: calc(var(--dhcl-cd-tag-fs) * var(--dhcl-cd-tag-lh) + 8px);
	--dhcl-cd-title-h: calc(
		var(--dhcl-cd-title-fs) * var(--dhcl-cd-title-lh) * var(--dhcl-cd-title-lines)
	);
	--dhcl-cd-desc-h: calc(
		var(--dhcl-cd-desc-fs) * var(--dhcl-cd-desc-lh) * var(--dhcl-cd-desc-lines)
	);

	/* Filter transition, retunable without touching the script. The JS reads
	   these off the root to know when each phase has finished, so overriding one
	   here or in Custom CSS retimes the animation and the sequencing together.
	   Changing them in a theme is supported; removing them is not, since the
	   script falls back to these same numbers only if the property is absent. */
	--dhcl-cd-fx-out: 160ms;
	--dhcl-cd-fx-in: 220ms;
	--dhcl-cd-fx-out-stagger: 20ms;
	--dhcl-cd-fx-in-stagger: 45ms;
	--dhcl-cd-fx-shift: 14px;

	position: relative;
	box-sizing: border-box;
}

.dhcl-cards *,
.dhcl-cards *::before,
.dhcl-cards *::after {
	box-sizing: border-box;
}

/* ---- Filter bar -------------------------------------------------------- */

.dhcl-cards__filters {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-bottom: 20px;
}

/*
 * Filter bar alignment, from the `filter_align` field.
 *
 * The default, `left`, emits no class and therefore no declaration, so the row
 * keeps the `justify-content: normal` it has always computed to and an existing
 * instance is untouched. Only these two exist.
 *
 * One class rather than a custom property, and deliberately at single-class
 * specificity: a client stylesheet scoped to the instance is (0,2,0) and can
 * still override the alignment without needing !important.
 *
 * flex-wrap is already on, so each wrapped line aligns on its own. That is what
 * makes centre read as centred rather than ragged when the buttons overflow.
 */
.dhcl-cards__filters--center {
	justify-content: center;
}

.dhcl-cards__filters--right {
	justify-content: flex-end;
}

/*
 * These three carry the .dhcl-scope prefix for the reason the arrows and dots
 * below already do: the scoped normalize resets every button at
 * `.dhcl-scope button`, which is (0,1,1) and beats a bare single-class rule.
 * Without the prefix this whole block lost silently, and the chips rendered
 * with the theme's inherited type and no padding, radius, border or fill.
 *
 * The state rules list :hover, :focus, :focus-visible and :active together and
 * restate border, background and color rather than only the hover tint. The
 * normalize has a second block at `.dhcl-scope button:hover, :focus, :active`
 * which is (0,2,1), so a prefixed base rule at (0,2,0) still loses in those
 * states: prefixing alone gave a correct chip at rest that dropped its border,
 * fill and text colour the moment it was hovered or focused. Measured, not
 * assumed. At (0,3,0) these win.
 *
 * .is-active is last so it beats the state rule at equal specificity, which
 * keeps an active chip blue while hovered, and it deliberately sets only
 * border-color so the width and style come from the rules above.
 */
.dhcl-scope .dhcl-cards__filter {
	appearance: none;
	border: 1px solid #e2e8f0;
	background: #fff;
	color: #475569;
	border-radius: 999px;
	padding: 7px 16px;
	font-size: 14px;
	font-weight: 500;
	cursor: pointer;
	transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
}

.dhcl-scope .dhcl-cards__filter:hover,
.dhcl-scope .dhcl-cards__filter:focus,
.dhcl-scope .dhcl-cards__filter:focus-visible,
.dhcl-scope .dhcl-cards__filter:active {
	border: 1px solid #cbd5e1;
	background: #fff;
	color: #475569;
}

.dhcl-scope .dhcl-cards__filter.is-active {
	background: var(--dhcl-cd-btn-bg, #0b63b6);
	border-color: var(--dhcl-cd-btn-bg, #0b63b6);
	color: #fff;
}

/* ---- Carousel ---------------------------------------------------------- */

.dhcl-cards__viewport {
	overflow: hidden;
	width: 100%;
}

.dhcl-cards__track {
	display: flex;
	/* Every card in the row takes the height of the tallest one, which is what
	   lets the per-card zones below line up horizontally. */
	align-items: stretch;
	gap: var(--dhcl-cd-gap);
	will-change: transform;
	transition: transform 400ms ease;
}

.dhcl-cards__card {
	flex: 0 0
		calc((100% - (var(--dhcl-cd-per) - 1) * var(--dhcl-cd-gap)) / var(--dhcl-cd-per));
	max-width: calc(
		(100% - (var(--dhcl-cd-per) - 1) * var(--dhcl-cd-gap)) / var(--dhcl-cd-per)
	);
	display: flex;
	flex-direction: column;
	/* Height stays auto on purpose: the track's align-items:stretch is what
	   grows every card to the tallest in the row, and stretch only applies
	   while the cross size is auto. Setting height:100% here would opt the card
	   out of stretching and drop it back to its own content height. */
	background: var(--dhcl-cd-card-bg, #fff);
	border-radius: var(--dhcl-cd-radius);
	box-shadow: var(--dhcl-cd-shadow, none);
	overflow: hidden;
}

.dhcl-cards--border .dhcl-cards__card {
	border: 1px solid var(--dhcl-cd-border-color, #eef2f6);
}

/* A hidden (filtered-out) card is removed from the flex flow. */
.dhcl-cards__card[hidden] {
	display: none;
}

/* ---- Filter transition --------------------------------------------------

   A sequenced slide: the outgoing set leaves, and only once it has gone does
   the incoming set arrive. Not a crossfade. The two phases never overlap,
   which is the whole difference between this reading as a movement and reading
   as the flicker the entry-only version produced.

   Direction is one continuous upward motion: the old cards lift away and the
   new ones rise into the space they left. Picking one axis and one direction
   for both halves makes it read as a single gesture rather than two unrelated
   ones, and upward is the conventional direction for a list refreshing in
   place. The shift is --dhcl-cd-fx-shift, far enough to register as travel.

   Easing is asymmetric on purpose: ease-in on the way out, so departure
   accelerates away, and ease-out on the way in, so arrival decelerates into
   position. No overshoot anywhere.

   Only opacity and transform are animated. Neither participates in layout, so
   this cannot disturb the pinned card heights the adaptive engine writes, and
   it cannot reach the reserved-zone grid inside .dhcl-cards__body: the display
   value of every zone is untouched, and the card animating here is a flex item
   of the track, not a track of that grid.

   dhcl-cards.js owns the sequencing. It adds one of the two classes, sets
   --dhcl-cd-i per card for the stagger, and reads the durations above so the
   phase boundary follows whatever they are set to. */
@keyframes dhcl-cards-exit {
	from {
		opacity: 1;
		transform: none;
	}
	to {
		opacity: 0;
		transform: translateY(calc(var(--dhcl-cd-fx-shift) * -1));
	}
}

@keyframes dhcl-cards-enter {
	from {
		opacity: 0;
		transform: translateY(var(--dhcl-cd-fx-shift));
	}
	to {
		opacity: 1;
		transform: none;
	}
}

.dhcl-cards--fx-out .dhcl-cards__card {
	animation: dhcl-cards-exit var(--dhcl-cd-fx-out) ease-in both;
	animation-delay: calc(var(--dhcl-cd-i, 0) * var(--dhcl-cd-fx-out-stagger));
}

.dhcl-cards--fx-in .dhcl-cards__card {
	animation: dhcl-cards-enter var(--dhcl-cd-fx-in) ease-out both;
	animation-delay: calc(var(--dhcl-cd-i, 0) * var(--dhcl-cd-fx-in-stagger));
}

/* Reduced motion skips both phases outright rather than shortening them: the
   set simply changes. The script checks the same preference and runs the swap
   in one step, so this is the backstop rather than the mechanism. */
@media (prefers-reduced-motion: reduce) {
	.dhcl-cards--fx-out .dhcl-cards__card,
	.dhcl-cards--fx-in .dhcl-cards__card {
		animation: none;
	}
}

/* ---- Media + badge ----------------------------------------------------- */

.dhcl-cards__media {
	position: relative;
	width: 100%;
	height: var(--dhcl-cd-img-h);
	/* Never absorb or give up height when the card stretches, so the body below
	   starts at the same Y in every card. */
	flex: 0 0 auto;
}

/*
 * Prefixed for the same reason as the filter chip: the scoped normalize resets
 * every image at `.dhcl-scope img` with `height: auto`, which is (0,1,1) and
 * beat this rule's `height: 100%`. The image therefore sized to its own aspect
 * ratio instead of filling the fixed-height media box, so object-fit had no box
 * to fit and a portrait image overflowed the frame and covered the tag and
 * title beneath it. Uniformly sized images hide this completely, which is why
 * it survived: it only shows when one instance mixes aspect ratios.
 */
.dhcl-scope .dhcl-cards__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.dhcl-cards--fit-contain .dhcl-cards__img {
	object-fit: contain;
}

.dhcl-cards__badge {
	position: absolute;
	top: 12px;
	left: 12px;
	background: var(--dhcl-cd-badge-bg, #0b63b6);
	color: var(--dhcl-cd-badge-text, #fff);
	font-size: 12px;
	font-weight: 600;
	padding: 4px 10px;
	border-radius: 999px;
}

/* ---- Body -------------------------------------------------------------- */

/* The body is a fixed six-row grid and each zone is pinned to its own row by
   class, not by document order:

     1 category tag   2 title   3 description   4 meta   5 slack   6 CTA

   Pinning by class is what makes content variance harmless. The renderer omits
   an empty tag / description / meta list entirely, and a grid track keeps its
   reserved height whether or not an element occupies it — so a card with no
   description does not pull the meta rows up, and the Date row lands at the
   same Y in every card. Row 5 (1fr) swallows the leftover height, which puts
   the CTA in row 6 on the same baseline no matter how much sits above it.

   Every track is minmax(reserved, auto): the reservation is a floor, never a
   cap, so unusual content grows the card instead of clipping. */
.dhcl-cards__body {
	display: grid;
	grid-template-rows:
		minmax(var(--dhcl-cd-tag-h), auto)
		minmax(var(--dhcl-cd-title-h), auto)
		minmax(var(--dhcl-cd-desc-h), auto)
		auto
		1fr
		auto;
	row-gap: var(--dhcl-cd-row-gap);
	padding: var(--dhcl-cd-pad) var(--dhcl-cd-pad) calc(var(--dhcl-cd-pad) + 2px);
	flex: 1 1 auto;
}

.dhcl-cards__tag {
	grid-row: 1;
	/* Block axis is the grid's row axis here, so the pill is held to its own
	   width with justify-self (align-self would stretch it). */
	justify-self: start;
	align-self: start;
	font-size: var(--dhcl-cd-tag-fs);
	line-height: var(--dhcl-cd-tag-lh);
	font-weight: 600;
	letter-spacing: 0.02em;
	color: var(--dhcl-cd-cat, #334155);
	border: 1px solid currentColor;
	border-radius: 999px;
	padding: 3px 10px;
}

.dhcl-cards--cat-solid .dhcl-cards__tag {
	color: #fff;
	background: var(--dhcl-cd-cat, #334155);
	border-color: var(--dhcl-cd-cat, #334155);
}

/* Two lines reserved, two lines maximum: a one-line title occupies the same
   space as a two-line one, and an outlier is ellipsised rather than allowed to
   push the rows below it out of line. Typical titles fit well inside this. */
.dhcl-cards__title {
	grid-row: 2;
	margin: 0;
	font-size: var(--dhcl-cd-title-fs);
	line-height: var(--dhcl-cd-title-lh);
	min-height: var(--dhcl-cd-title-h);
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: var(--dhcl-cd-title-lines);
	line-clamp: var(--dhcl-cd-title-lines);
	overflow: hidden;
	color: var(--dhcl-cd-title, #0f172a);
}

/* Events source: the image + title link to the event's public page. Links
   inherit the card look; only the title underlines on hover. */
.dhcl-cards__title-link {
	color: inherit;
	text-decoration: none;
}

.dhcl-cards__title-link:hover {
	text-decoration: underline;
}

.dhcl-cards__media-link {
	display: block;
	width: 100%;
	height: 100%;
}

/* Same deal as the title: the zone is worth two lines whether the description
   is long, one word, or missing entirely, so the meta block below always starts
   at the same Y across the row. */
.dhcl-cards__desc {
	grid-row: 3;
	margin: 0;
	font-size: var(--dhcl-cd-desc-fs);
	line-height: var(--dhcl-cd-desc-lh);
	min-height: var(--dhcl-cd-desc-h);
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: var(--dhcl-cd-desc-lines);
	line-clamp: var(--dhcl-cd-desc-lines);
	overflow: hidden;
	color: var(--dhcl-cd-text, #475569);
}

/* ---- Meta rows --------------------------------------------------------- */

/* No ad-hoc margins: the space above the block and between its rows both come
   from the one row-gap token, so the Date row of every card and the Time row of
   every card that has one sit on the same lines. A card with extra rows grows
   downward into the slack row and leaves the CTA where it is. */
.dhcl-cards__meta {
	grid-row: 4;
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--dhcl-cd-row-gap);
}

.dhcl-cards__meta-row {
	display: flex;
	align-items: center;
	gap: 10px;
	font-size: 13px;
	line-height: 1.4;
}

.dhcl-cards__meta-icon {
	display: inline-flex;
	color: var(--dhcl-cd-meta-icon, #0b63b6);
	flex: 0 0 auto;
}

.dhcl-cards__meta-label {
	color: var(--dhcl-cd-meta-label, #64748b);
	font-weight: 600;
	letter-spacing: 0.03em;
}

.dhcl-cards__meta-value {
	color: var(--dhcl-cd-meta-value, #334155);
	margin-left: auto;
	text-align: right;
}

/* ---- Button ------------------------------------------------------------ */

/* Last row, under the 1fr slack row, .dhcl-scope so it is bottom-aligned across the whole
   row of cards. margin-top:auto is belt-and-braces for the same result. */
.dhcl-cards__btn{
	grid-row: 6;
	align-self: end;
	margin-top: auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	padding: 10px 18px;
	border-radius: 8px;
	border: 2px solid transparent;
	font-size: 14px;
	font-weight: 600;
	text-decoration: none;
	cursor: pointer;
}

.dhcl-cards__ext {
	display: inline-flex;
}

/* ---- Arrows ------------------------------------------------------------ */

.dhcl-scope .dhcl-cards__arrow {
	position: absolute;
	/* Center on the full card row (not the image), and inset just inside the
	   viewport edges so the circular buttons are never clipped. */
	top: 50%;
	transform: translateY(-50%);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border: 1px solid #e2e8f0;
	background: #fff;
	color: #0f172a;
	border-radius: 999px;
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
	z-index: 2;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}

.dhcl-scope .dhcl-cards__arrow:hover {
	background: #f8fafc;
}

.dhcl-scope .dhcl-cards__arrow:disabled {
	opacity: 0.35;
	cursor: default;
}

/* The engine hides the chrome when there is only one page. These rules are
   needed because the display values above would otherwise beat the hidden
   attribute's default. */
.dhcl-scope .dhcl-cards__arrow[hidden],
.dhcl-scope .dhcl-cards__dots[hidden] {
	display: none;
}

.dhcl-scope .dhcl-cards__arrow--prev {
	left: 8px;
}

.dhcl-scope .dhcl-cards__arrow--next {
	right: 8px;
}

/* ---- Dots -------------------------------------------------------------- */

.dhcl-scope .dhcl-cards__dots {
	display: flex;
	justify-content: center;
	gap: 8px;
	margin-top: 16px;
}

.dhcl-scope .dhcl-cards__dot {
	width: 8px;
	height: 8px;
	padding: 0;
	border: 0;
	border-radius: 999px;
	background: #cbd5e1;
	cursor: pointer;
}

.dhcl-scope .dhcl-cards__dot[aria-current="true"] {
	background: var(--dhcl-cd-btn-bg, #0b63b6);
	transform: scale(1.25);
}

/* ---- Dragging ---------------------------------------------------------- */

.dhcl-cards--dragging .dhcl-cards__track {
	transition: none;
	cursor: grabbing;
}

.dhcl-cards--draggable .dhcl-cards__viewport {
	cursor: grab;
	touch-action: pan-y;
}

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

/* ---- Empty state (events source, no matches) --------------------------- */

.dhcl-cards--empty {
	padding: 32px 24px;
	border: 1px dashed #e2e8f0;
	border-radius: var(--dhcl-cd-radius, 12px);
}

.dhcl-cards__empty {
	margin: 0;
	text-align: center;
	color: #64748b;
	font-size: 15px;
}
