/**
 * Naos Agenda — front-end views.
 *
 * The card and filter STRUCTURE follows the client's visual reference: a
 * continuous chronological grid, image on top, type above the title, place and
 * dates in a footer, filters in a bar above the results.
 *
 * The LOOK is driven entirely by the custom properties in the first block, so
 * rebranding for another site is a variable swap, not a rewrite. The values
 * shipped here match the NAOS HAL Publications plugin, so the two bricks read
 * as one system wherever both are installed.
 *
 * Nothing here declares a font-family: titles are real headings and everything
 * else inherits the theme's body font, so changing the typeface in theme
 * options moves ours with it.
 */

.npx-events,
.npx-modal,
.npx-event-single {
	--npx-accent: #e09901;
	--npx-accent-text: #8f6200; /* amber that clears AA on all three light surfaces: white 5.4:1, the grey card 4.7:1, the soft-accent chip 5.0:1. The brand amber is 2.4:1 on white and is for fills and rules, not for text. */
	--npx-accent-soft: #fff6e3;
	/*
	 * The accent lightened for a dark ground, where the brand colour itself is
	 * usually too dark to read — the mirror of `--npx-accent-text`, which is it
	 * darkened for a light one. Written out here so a site that chooses nothing
	 * gets exactly the amber that shipped; a site that chooses has this worked
	 * out from its own colour.
	 */
	--npx-accent-lift: #ffc860;
	/*
	 * The programme blue of the targeted-project pages, where it drives the
	 * buttons, the pill links and the panels (.npxdst-* rules in the page CSS).
	 * White sits on it at ~13:1, so it needs no darkened variant.
	 */
	--npx-blue: #16227a;
	--npx-blue-soft: #eceef8;

	--npx-ink: #141618;
	--npx-ink-2: #3b4045;
	--npx-ink-3: #6b7280;
	--npx-ink-4: #9aa1a9;

	--npx-bg: #ffffff;
	--npx-bg-2: #f7f7f8;
	--npx-bg-3: #eeeff1;
	--npx-line: #e2e4e8;
	--npx-line-hover: #d3d7dd;

	/* The card block: a light grey body under a full-bleed image, per the
	   client's visual reference. */
	--npx-card-bg: #eeeff1;
	--npx-card-bg-hover: #e5e7ea;
	--npx-card-media-bg: #e2e4e8;

	/*
	 * Cover view: one even veil over the image.
	 *
	 * Taken from the site's own page headers, which overlay #1b1d1f
	 * (.block-bg-overlay.style-color-rgdb-bg) — so a cover card reads like the
	 * headers it sits under rather than inventing a second convention. The
	 * strengths bracket what those headers use; this is the middle one, and
	 * what the block falls back to when no strength is set.
	 */
	--npx-cover-veil: rgba(27, 29, 31, 0.73);
	--npx-cover-veil-hover: rgba(27, 29, 31, 0.81);
	--npx-overlay: rgba(20, 22, 24, 0.62);

	--npx-radius: 10px;
	--npx-radius-sm: 6px;
	--npx-gap: 1.5rem;
	/* Overridden per placement by the [data-gap] rules further down. */
	--npx-cols: 3;

	--npx-shadow: 0 1px 2px rgba(20, 22, 24, 0.05), 0 4px 16px -6px rgba(20, 22, 24, 0.1);
	--npx-shadow-lift: 0 2px 4px rgba(20, 22, 24, 0.06), 0 12px 32px -10px rgba(20, 22, 24, 0.18);

	color: var(--npx-ink);
}

.npx-events,
.npx-events *,
.npx-events *::before,
.npx-events *::after,
.npx-modal,
.npx-modal *,
.npx-modal *::before,
.npx-modal *::after,
.npx-event-single,
.npx-event-single *,
.npx-event-single *::before,
.npx-event-single *::after {
	box-sizing: border-box;
}

/* Several rules below set a display value; keep [hidden] winning over them. */
.npx-events [hidden],
.npx-events[hidden],
.npx-modal [hidden],
.npx-modal[hidden] {
	display: none !important;
}

/*
 * The grid responds to the width of the COLUMN it sits in, not the viewport —
 * dropped into a narrow WPBakery column a viewport breakpoint fires far too
 * late. Same approach as the HAL plugin's filter bar.
 */
.npx-events {
	/*
	 * Named, because there are two containers in this stylesheet and the card
	 * is the other one. An unnamed query resolves against whichever is nearest
	 * the element it styles, so a single block of rules could answer to the
	 * block's width for one selector and the card's for the next — which is
	 * exactly what happened to Spotlight: the grid stayed in two columns while
	 * the hero inside it laid itself out as though it were narrow.
	 *
	 * Naming changes nothing for the unnamed queries further down; they still
	 * match the nearest container whatever it is called.
	 */
	container: npx-block / inline-size;
}

.npx-events--cols-1 { --npx-cols: 1; }
.npx-events--cols-2 { --npx-cols: 2; }
.npx-events--cols-4 { --npx-cols: 4; }
.npx-events--cols-5 { --npx-cols: 5; }
.npx-events--cols-6 { --npx-cols: 6; }

/* ---------------------------------------------------------------- Filters */

.npx-filters {
	/*
	 * The anchor for a dropdown once the pills stop being one (see the narrow
	 * container below). Inert while they are: a pill is a nearer positioned
	 * ancestor, so the panel still hangs off the pill that opened it.
	 */
	position: relative;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem 1.25rem;
	padding: 0.6rem 0.65rem;
	margin: 0 0 1.5rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius);
	background: var(--npx-bg);
	box-shadow: var(--npx-shadow);
}

/*
 * The bar has exactly two children: the filters and the status. `flex: 0 0 auto`
 * stops this one shrinking, so wrapping can only produce two outcomes — both on
 * one line, or the status dropping as a complete second row. Without it the
 * group shrinks and the pills peel off one at a time into ragged lines.
 *
 * Same structure as the HAL Publications bar, and it needs no breakpoint: the
 * break is driven by the width actually available.
 */
/*
 * Wrapped state, set by the script the moment the status drops below the pills.
 *
 * `space-between` is right while everything is on one line, but once the bar
 * wraps it strands the count against the left edge under a full-width row of
 * pills. Centring both rows keeps them reading as one block.
 *
 * The script measures instead of this being a width breakpoint, because the
 * wrap point is not a fixed number: it moves with the length of the translated
 * labels, and the bar is as often inside a narrow column as across a page.
 */
.npx-filters.is-stacked {
	justify-content: center;
}

.npx-filters.is-stacked .npx-filters__groups,
.npx-filters.is-stacked .npx-filters__status {
	justify-content: center;
}

/* Through to the full agenda, for a block that shows only the next few. */
.npx-events__cta {
	display: flex;
	justify-content: center;
	margin-top: 1.5rem;
}

/*
 * The count is switched off, but filtering still has to announce its result to
 * a screen reader, so the live region stays in the page and only leaves the
 * layout.
 */
.npx-filters--no-count .npx-filters__results {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
}

/* The optical nudge for a right-aligned status is wrong once it is centred. */
.npx-filters.is-stacked .npx-filters__status {
	padding-right: 0;
}

.npx-filters__groups {
	display: flex;
	flex: 0 0 auto;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem;
}

.npx-filter {
	position: relative;
}

/* One visual spec for every control in the bar, matching the HAL pills. */
.npx-filter__trigger {
	display: inline-flex;
	height: 34px;
	align-items: center;
	gap: 0.4rem;
	margin: 0;
	padding: 0 0.7rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius-sm);
	background: var(--npx-bg-2);
	color: var(--npx-ink-2);
	font: inherit;
	font-size: 0.82rem;
	font-weight: 600;
	letter-spacing: 0;
	line-height: 1;
	text-transform: none;
	white-space: nowrap;
	cursor: pointer;
	transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.npx-filter__trigger::after {
	content: "";
	width: 6px;
	height: 6px;
	margin-left: 0.1rem;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: translateY(-2px) rotate(45deg);
	transition: transform 0.15s ease;
}

.npx-filter__trigger:hover {
	border-color: var(--npx-ink-4);
}

.npx-filter__trigger:focus-visible {
	outline: 2px solid var(--npx-accent);
	outline-offset: 2px;
}

.npx-filter__trigger[aria-expanded="true"] {
	border-color: var(--npx-accent);
	background: var(--npx-bg);
}

.npx-filter__trigger[aria-expanded="true"]::after {
	transform: translateY(1px) rotate(-135deg);
}

.npx-filter__count {
	display: inline-flex;
	min-width: 18px;
	height: 18px;
	align-items: center;
	justify-content: center;
	padding: 0 5px;
	border-radius: 9px;
	background: var(--npx-accent);
	color: #fff;
	font-size: 0.66rem;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
}

.npx-filter__panel {
	position: absolute;
	z-index: 20;
	top: calc(100% + 6px);
	left: 0;
	min-width: 250px;
	max-height: 340px;
	overflow-y: auto;
	padding: 0.35rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius);
	background: var(--npx-bg);
	box-shadow: var(--npx-shadow-lift);
}

.npx-filter__list {
	margin: 0;
	padding: 0;
	list-style: none;
}

.npx-filter__item {
	margin: 0;
	padding: 0;
	list-style: none;
}

.npx-filter__item label {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	padding: 0.4rem 0.5rem;
	border-radius: var(--npx-radius-sm);
	color: var(--npx-ink-2);
	font-size: 0.84rem;
	font-weight: 500;
	line-height: 1.3;
	cursor: pointer;
}

.npx-filter__item label:hover {
	background: var(--npx-bg-2);
}

.npx-filter__item input {
	flex: none;
	width: 15px;
	height: 15px;
	margin: 0;
	accent-color: var(--npx-accent);
}

.npx-filter__name {
	flex: 1 1 auto;
}

.npx-filter__tally {
	flex: none;
	color: var(--npx-ink-4);
	font-size: 0.74rem;
	font-variant-numeric: tabular-nums;
}

.npx-filter__item--empty label {
	opacity: 0.45;
}

/* ---- date range panel --------------------------------------------------- */

/*
 * Wider and shorter rather than narrow and tall: a tall dropdown runs past the
 * fold and makes the visitor scroll inside a filter, which is worse than using
 * the horizontal room that is already there.
 */
.npx-filter__panel--dates {
	display: flex;
	min-width: 430px;
	max-width: 480px;
	max-height: min(72vh, 480px);
	flex-direction: column;
	gap: 0.9rem;
	padding: 0.9rem;
}

.npx-chipset {
	min-width: 0;
	margin: 0;
	padding: 0;
	border: 0;
}

.npx-chipset + .npx-chipset {
	padding-top: 0.9rem;
	border-top: 1px solid var(--npx-line);
}

.npx-chipset__legend {
	padding: 0 0 0.5rem;
	color: var(--npx-ink-3);
	font-size: 0.7rem;
	font-weight: 800;
	letter-spacing: 0.07em;
	text-transform: uppercase;
}

.npx-chipset__options {
	display: flex;
	flex-wrap: wrap;
	gap: 0.3rem;
}

/* Six across, so the twelve months take two rows instead of three. */
.npx-chipset__options--months {
	display: grid;
	grid-template-columns: repeat(6, minmax(0, 1fr));
}

/* "Any month" heads the grid on its own row. */
.npx-chipset__options--months .npx-chip--any {
	grid-column: 1 / -1;
}

/*
 * A chip is a styled radio, not a fake one: the input keeps its place in the
 * accessibility tree and its own keyboard behaviour (arrow keys move within
 * the group), only its default rendering is replaced.
 */
.npx-chip {
	position: relative;
	display: flex;
	margin: 0;
	cursor: pointer;
}

.npx-chip input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	opacity: 0;
	pointer-events: none;
}

.npx-chip__label {
	display: flex;
	width: 100%;
	height: 30px;
	align-items: center;
	justify-content: center;
	padding: 0 0.6rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius-sm);
	background: var(--npx-bg-2);
	color: var(--npx-ink-2);
	font-size: 0.78rem;
	font-weight: 600;
	line-height: 1;
	text-align: center;
	white-space: nowrap;
	transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.npx-chip:hover .npx-chip__label {
	border-color: var(--npx-ink-4);
}

.npx-chip input:checked + .npx-chip__label {
	border-color: var(--npx-accent);
	background: var(--npx-accent);
	color: #fff;
	font-weight: 700;
}

.npx-chip input:focus-visible + .npx-chip__label {
	outline: 2px solid var(--npx-accent);
	outline-offset: 2px;
}

/* No event would survive this choice — still selectable, just played down. */
.npx-chip--empty .npx-chip__label {
	opacity: 0.4;
}

.npx-chipset--range {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 0.6rem 0.9rem;
}

.npx-chipset--range .npx-filter__field {
	flex-direction: column;
	align-items: flex-start;
	gap: 0.3rem;
}

.npx-chipset--range .npx-filter__control,
.npx-chipset--range input[type="date"] {
	width: 100%;
	min-width: 0;
}

.npx-filter__field {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
	margin: 0;
	font-weight: 400;
}

.npx-filter__field-label {
	color: var(--npx-ink-3);
	font-size: 0.7rem;
	font-weight: 800;
	letter-spacing: 0.07em;
	text-transform: uppercase;
}

.npx-filter__field input[type="date"] {
	width: auto;
	min-width: 150px;
	max-width: 100%;
	height: 34px;
	margin: 0;
	padding: 0 0.6rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius-sm);
	background: var(--npx-bg-2);
	color: var(--npx-ink);
	font: inherit;
	font-size: 0.82rem;
	line-height: 1;
}

/*
 * An empty date field prints the BROWSER's format hint — "mm/dd/yyyy" on an
 * en-US browser — which contradicts the site's own format and appears on the
 * French page too. It cannot be restyled or relabelled: the control is drawn
 * by the browser and ignores WordPress, the page language and any attribute.
 *
 * So the hint is hidden while the field is empty, leaving the calendar button
 * and the field's own "From" / "To" label to say what it is. As soon as a date
 * is picked the value appears normally.
 *
 * WebKit and Blink only; Firefox draws its hint with no equivalent hook, but
 * renders it in the visitor's own locale rather than a fixed US order.
 */
/*
 * The value inside a date input is drawn by the browser, in the browser's own
 * locale — "10/01/2026" for the first of October on an en-US browser. It
 * ignores WordPress, the page language and every attribute, and it cannot be
 * restyled.
 *
 * So where the browser lets us, its text is hidden outright and replaced by an
 * overlay we control: the placeholder while empty, the date written out in the
 * site's language once chosen. Hiding it unconditionally — rather than handing
 * back on focus — is deliberate: focus changes hands while the picker opens
 * and closes, and anything conditional made the field flicker between formats
 * or go blank at the moment a date was picked.
 *
 * Guarded by @supports because Firefox offers no such hook: there the native
 * rendering stays, which at least follows the visitor's own locale.
 */
@supports selector(::-webkit-datetime-edit) {
	.npx-filter__field input[type="date"]::-webkit-datetime-edit {
		opacity: 0;
	}

	.npx-filter__field input[type="date"] {
		min-width: 190px;
		/* The whole field opens the picker (see the script), so it reads as a
		   control rather than a text box. */
		cursor: pointer;
	}

	.npx-filter__control {
		position: relative;
		display: inline-flex;
		align-items: center;
	}

	/* The overlay: placeholder, or the chosen date in the site's language. */
	.npx-filter__hint {
		position: absolute;
		top: 50%;
		left: 0.6rem;
		color: var(--npx-ink-4);
		font-size: 0.82rem;
		line-height: 1;
		white-space: nowrap;
		pointer-events: none;
		transform: translateY(-50%);
	}

	/* A chosen date is a value, not a placeholder: it takes the normal ink. */
	.npx-filter__field:not(.npx-filter__field--empty) .npx-filter__hint {
		color: var(--npx-ink);
	}
}

/* Without the hook, the browser draws its own value and the overlay stays out. */
@supports not selector(::-webkit-datetime-edit) {
	.npx-filter__hint {
		display: none;
	}
}

.npx-filter__field input[type="date"]:focus {
	border-color: var(--npx-accent);
	background: var(--npx-bg);
	outline: none;
	/* The accent at a sixth, rather than the accent's channels written out. */
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--npx-accent) 15%, transparent);
}

.npx-filter__clear,
.npx-filters__reset {
	align-self: flex-start;
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	color: var(--npx-accent-text);
	font: inherit;
	font-size: 0.8rem;
	font-weight: 600;
	text-decoration: underline;
	text-underline-offset: 3px;
	cursor: pointer;
}

.npx-filters__status {
	display: flex;
	flex: 0 0 auto;
	align-items: center;
	gap: 1rem;
	padding-right: 0.25rem;
}

.npx-filters__results {
	margin: 0;
	color: var(--npx-ink-3);
	font-size: 0.8rem;
	font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ Grid */

.npx-events__grid {
	display: grid;
	grid-template-columns: repeat(var(--npx-cols), minmax(0, 1fr));
	gap: var(--npx-gap);
	margin: 0;
	padding: 0;
	list-style: none;
}

.npx-events__section + .npx-events__past-switch {
	margin-top: 2.5rem;
}

.npx-events__past-switch {
	display: flex;
	justify-content: center;
}

/*
 * How far below the top of the window the button should come to rest when the
 * past events are opened. The script adds whatever the theme is holding up
 * there to this.
 */
[data-npx-events] {
	--npx-past-stick: 16px;

	/*
	 * The rule above "Past events". Not `--npx-line`, which is a card-border
	 * grey and vanishes on anything but white: measured, it read 1.27:1 on a
	 * white page and 1.11:1 on the theme's grey section, which is an absent
	 * line rather than a quiet one. This holds on both — 1.68:1 and 1.46:1 —
	 * without becoming a divider anybody has to look at.
	 */
	--npx-past-rule: #c2c8d1;
}

/*
 * And on a dark section, where white-on-dark carries far further: the shared
 * token measured 4.09:1 there against 1.27:1 on light, so the same stroke was
 * three times the weight depending on the ground. A tenth of white brings it
 * back into the same register.
 */
[data-scheme="dark"] {
	--npx-past-rule: rgba(255, 255, 255, 0.1);
}

/*
 * Closer than it was. Two and a half rem above the rule and the same below put
 * eighty-one pixels between the button and the heading it belongs to, which
 * read as two unrelated things rather than one hand-off.
 */
.npx-events__section--past {
	margin-top: 1.5rem;
	padding-top: 1.5rem;
	border-top: 1px solid var(--npx-past-rule, var(--npx-line));
}

.npx-events .npx-events__heading {
	margin: 0 0 1.5rem;
	color: var(--npx-ink);
	font-size: 1.25rem;
	font-weight: 600;
	letter-spacing: -0.01em;
}

.npx-events__empty {
	margin: 0;
	padding: 2.5rem 0;
	color: var(--npx-ink-3);
	font-size: 0.95rem;
}

/* ------------------------------------------------------------------ Card */

/*
 * A card is a filled block on the page, following the client's visual
 * reference: image full-bleed on top, a light grey body beneath it, no border,
 * no shadow and square corners. The tint and the gutter do the separating, so
 * the grid stays quiet enough for a long agenda to be scanned.
 */
.npx-card {
	position: relative;
	container: npx-card / inline-size;
	display: flex;
	height: 100%;
	flex-direction: column;
	align-items: stretch;
	min-width: 0;
	overflow: hidden;
	border: 0;
	border-radius: 0;
	background: var(--npx-card-bg);
	transition: background-color 0.2s, transform 0.2s, box-shadow 0.2s;
}

.npx-card:hover {
	background: var(--npx-card-bg-hover);
}

.npx-card__aside {
	position: relative;
	flex-shrink: 0;
	width: 100%;
	overflow: hidden;
	background: var(--npx-card-media-bg);
	aspect-ratio: 2 / 1;
}

.npx-card__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.npx-card__image--placeholder {
	background:
		repeating-linear-gradient(45deg, transparent 0 6px, rgba(255, 255, 255, 0.5) 6px 12px),
		var(--npx-accent-soft);
}

/* Reads as a badge, not a sticker: same spec as the HAL doc-type badge. */
/*
 * Solid programme blue: the badge sits on top of photographs, so it needs a
 * fill rather than a tint.
 */
/*
 * Marks the events the organizer runs itself. Third-party events carry no
 * badge at all, so the blue chip reads as a statement rather than a category.
 */
.npx-card__cartouche {
	position: absolute;
	/* Above the Cover view's veil, which is a pseudo-element of the same
	   wrapper and would otherwise paint over the badge and dim it. */
	z-index: 2;
	top: 0.55rem;
	left: 0.55rem;
	margin: 0;
	/* The same scale as the project chips: it names who runs the event, which
	   is a label on the picture rather than something to read first. */
	padding: 0.18rem 0.42rem;
	border-radius: 4px;
	background: var(--npx-blue);
	color: #fff;
	font-size: 0.58rem;
	font-weight: 800;
	letter-spacing: 0.06em;
	line-height: 1.3;
	text-transform: uppercase;
}

/*
 * Type is sized from the CARD's own width (cqi), not the viewport: a card in a
 * narrow theme column needs the smaller size just as much as one on a phone,
 * and the column count alone cannot tell us how wide a card ended up.
 */
.npx-card__main {
	display: flex;
	flex: 1;
	flex-direction: column;
	align-items: stretch;
	padding: clamp(0.85rem, 3.4cqi, 1.15rem) clamp(0.9rem, 4cqi, 1.25rem);
	text-align: center;
}

.npx-card__type {
	order: 1;
	margin: 0;
	/* The brand accent itself, matching the accent used for the hover underline. */
	color: var(--npx-accent);
	font-size: clamp(0.6rem, 3.4cqi, 0.68rem);
	margin-bottom: 0.45rem;
	font-weight: 800;
	letter-spacing: 0.07em;
	line-height: 1.3;
	text-transform: uppercase;
}

/*
 * `h3` is in the selector for weight, not for tidiness. The theme colours
 * headings from a rule of exactly this specificity, printed after the plugin's
 * stylesheet, so a tie went to the theme and the title came out #303133 — dark
 * grey, which passes unnoticed on a white page and is unreadable on a dark
 * one. Naming the element wins on specificity rather than on source order,
 * which is not ours to control.
 *
 * The scoping under .npx-events is a second, older fight: the theme sizes
 * headings with `h3:not([class*="fontsize-"])`, which outranks a bare class.
 */
.npx-events h3.npx-card__title {
	order: 2;
	flex-grow: 1;
	margin: 0;
	color: var(--npx-ink);
	font-size: clamp(0.9rem, 5.2cqi, 1.08rem);
	font-weight: 600;
	letter-spacing: -0.01em;
	line-height: 1.33;
}

/*
 * width:100% matters: a <button> is inline-block, so it hugs its own text and
 * the inherited centring would only centre the text inside that hug — leaving
 * short titles sitting against the left edge of the card.
 */
.npx-card__trigger {
	/*
	 * A long word in a narrow card used to run straight out of it — measured
	 * at 16px past the edge in a six-column grid. Breaking the word is the
	 * lesser evil, and it is what lets a card stay intact at any width.
	 */
	overflow-wrap: break-word;
	/*
	 * The theme transitions colour over 0.4s on a curve that barely moves for
	 * its first third, which makes the hover look like it did not fire. This
	 * is short and even so the change registers with the pointer.
	 */
	transition: color 0.15s ease;
	display: -webkit-box;
	width: 100%;
	margin: 0;
	padding: 0;
	overflow: hidden;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-align: inherit;
	cursor: pointer;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 3;
}

/* The trigger covers the whole card: one focus stop, one click target. */
.npx-card__trigger::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 2;
}

/*
 * The title takes the brand accent on hover, as asked. Worth knowing: #e09901
 * is 2.2:1 on the grey card body, under the 4.5:1 that body-sized text is held
 * to, so the hover title is decorative emphasis rather than something to rely
 * on for reading. The resting colour does the work at 15:1, and the underline
 * marks the state independently of colour.
 */
.npx-card:hover .npx-card__trigger,
.npx-card__trigger:focus-visible {
	color: var(--npx-accent);
	text-decoration: underline;
	text-decoration-color: var(--npx-accent);
	text-underline-offset: 2px;
}

.npx-card__trigger:focus-visible {
	outline: none;
}

/*
 * The ring belongs to the trigger, which has none of its own (above): it is a
 * stretched, invisible button, so an outline on it would trace the card's
 * whole box from the inside. The card wears it instead.
 *
 * `:has(… :focus-visible)` and not `:focus-within`. `:focus-within` answers to
 * plain `:focus`, which a mouse click sets — so clicking a card drew a ring
 * nobody asked for and nobody needed, since the pointer already says what was
 * clicked. `:focus-visible` is the browser's own read of "this focus needs
 * showing", and it withholds it from a pointer click on a button.
 *
 * Scoped to the trigger rather than any descendant: the Details button and the
 * links inside the preview carry their own rings, and a card-wide outline
 * around one of those would be a second ring for the same focus.
 */
.npx-card:has(.npx-card__trigger:focus-visible:not([data-npx-quiet-focus])) {
	outline: 2px solid var(--npx-accent);
	outline-offset: 3px;
}

/*
 * The gap above the chips is set here rather than left to the title's
 * flex-grow: a short title donates its leftover space and looks airy either
 * way, but a title running to three lines fills its box and would otherwise
 * leave the chips crowding right underneath it.
 */
/*
 * The button sits above the projects so that the chips are always the last
 * thing before the footer rule: with the button underneath, a card without a
 * link put its chips at a different height from its neighbours.
 */
.npx-card__action {
	order: 3;
	/* Above the trigger that covers the card, so the link is followable. */
	position: relative;
	z-index: 3;
	margin: 0.9rem 0 0;
	order: 4;
}

.npx-card__projects {
	order: 4;
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.35rem;
	margin: 0.8rem 0 0;
	order: 5;
}

/* Whichever comes first carries the wide gap under the title; the second
   element only needs to clear the first. */
.npx-card__action + .npx-card__projects {
	margin-top: 0.8rem;
}

.npx-card__footer {
	order: 6;
	margin-top: 0.45rem;
	padding-top: 0.7rem;
	border-top: 1px solid var(--npx-line);
}

/*
 * The date is the more important of the two, so it takes the weight and the
 * darker ink; the place is still there to be read, just second.
 *
 * Rows is deliberately left out of this: it already states the date twice over
 * in the block down its left edge, so emphasising it again in the footer would
 * say the same thing three times.
 */
.npx-card__place {
	margin: 0;
	color: var(--npx-ink-3);
	font-size: clamp(0.74rem, 4.1cqi, 0.82rem);
	font-weight: 400;
	line-height: 1.5;
}

.npx-card__dates {
	margin: 0.15rem 0 0;
	color: var(--npx-ink-2);
	font-size: clamp(0.76rem, 4.2cqi, 0.84rem);
	font-weight: 600;
	font-variant-numeric: tabular-nums;
	line-height: 1.5;
}


/*
 * The date sits directly under the title, where the eye reaches it while
 * reading the event rather than after the rule at the foot of the card.
 *
 * The min-height is two lines' worth whether or not there is a time. Without
 * it the button and everything below it ride up on an event that has no time,
 * and the cards in a row stop agreeing on where anything sits — which is the
 * whole reason the button was made uniform in the first place. The clamps are
 * the same ones the two lines use, so the reservation tracks the container.
 */
.npx-card__when {
	order: 3;
	margin: 0.5rem 0 0;
}

/*
 * An event with no time gets a blank line of exactly the size the time would
 * have been, so the button and everything under it sit at the same height on
 * every card in the row.
 *
 * A real line box rather than a min-height in pixels: the height of a line
 * depends on the font's own metrics, so any number computed from the font size
 * lands a pixel or two out, and the cards drift apart again.
 */
.npx-card__when:not(:has(.npx-card__time))::after {
	content: "\00a0";
	display: block;
	font-size: clamp(0.74rem, 4.1cqi, 0.82rem);
	line-height: 1.5;
}

/*
 * The time sits under the date rather than beside it. It stays in the darker
 * ink because it is part of "when", but drops the weight: the date leads.
 */
.npx-card__time {
	margin: 0;
	color: var(--npx-ink-2);
	font-size: clamp(0.74rem, 4.1cqi, 0.82rem);
	font-variant-numeric: tabular-nums;
	font-weight: 400;
	line-height: 1.5;
}

/*
 * Sat on the foot of the image rather than in the body of the card.
 *
 * A child of the image block, so it follows the picture's real bottom edge
 * whatever aspect ratio the image is given — positioning it from the card
 * would mean repeating that ratio here, and the two would drift apart the
 * first time one of them changed.
 */
.npx-card__projects--over {
	position: absolute;
	right: 0.6rem;
	bottom: 0.6rem;
	left: 0.6rem;
	z-index: 2;
	margin: 0;
}

/*
 * Opaque, and with a shadow of its own: these sit on a picture the plugin has
 * never seen, which may be pale, dark or busy under any given chip.
 */
.npx-card__projects--over .npx-badge--project {
	background: #fff;
	color: var(--npx-ink);
	box-shadow: 0 1px 3px rgba(20, 22, 24, 0.3);
}

/*
 * The same hover as Cover: an accent ring, a small lift and a warm glow.
 *
 * The ring is a pseudo-element for two reasons. The card has no border to
 * recolour, and adding one on hover would widen its box and nudge every
 * neighbour in the grid; and the image is a child painted over the card's own
 * background, so an inset shadow would disappear behind it along the top of
 * the card. White cards has a real border already and recolours that instead.
 */
[data-layout="cards"] .npx-card::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 2;
	border: 1px solid transparent;
	transition: border-color 0.2s;
	pointer-events: none;
}

[data-layout="cards"] .npx-card:hover::after {
	border-color: var(--npx-hover-line);
}

[data-layout="cards"] .npx-card:hover,
[data-layout="white"] .npx-card:hover {
	transform: translateY(-3px);
}

/* ----------------------------------------------------- the gap it leaves */

/*
 * 0rem, not 0: the carousel's track width subtracts this from a percentage,
 * and a unitless zero is a <number> there, not a <length>. That makes the
 * whole calc invalid, and the cards collapsed to their minimum width.
 */
[data-gap="none"] {
	--npx-gap: 0rem;
}

[data-gap="tiny"] {
	--npx-gap: 0.35rem;
}

[data-gap="small"] {
	--npx-gap: 0.8rem;
}

[data-gap="normal"] {
	--npx-gap: 1.5rem;
}

[data-gap="large"] {
	--npx-gap: 2.5rem;
}

/* ---------------------------------------------------------- the carousel */

/*
 * The same grid, laid on one line that scrolls. The column count keeps its
 * meaning — it becomes how many are visible at once — so the container queries
 * that step it down on narrow screens go on working untouched.
 *
 * Scroll snapping and a scroll container are the whole mechanism: the script
 * only adds the arrows and works out whether they are needed. Nothing here
 * depends on the script, so without it the row is still a scrollable strip
 * rather than a broken one.
 */
[data-carousel="yes"][data-carousel-direction="horizontal"] .npx-events__grid {
	grid-auto-flow: column;
	grid-template-columns: none;
	grid-auto-columns: calc(
		(100% - (var(--npx-cols) - 1) * var(--npx-gap)) / var(--npx-cols)
	);
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	overscroll-behavior-x: contain;
}

/*
 * Room for the shadow inside the track, given back to the layout at once.
 *
 * A scroll container clips, and asking for one on a single axis buys the other
 * axis too: `overflow-x: auto` forces `overflow-y` from `visible` to `auto`.
 * So a card in a carousel cast its shadow into a clip. Measured with a black
 * shadow at strong, the least missable there is, a hovered card changed 10,800
 * pixels in the strip below itself with the carousel off and none at all with
 * it on — while `getComputedStyle` read back the same shadow either way, which
 * is why nothing in the cascade looked wrong.
 *
 * 32px because that is where the shadow actually stops mattering. Worked out
 * on paper it reaches 18px — offset, less spread, plus half the blur — and
 * that was wrong: photographed with 60px of room and read a row at a time, the
 * darkest shadow the settings allow still marks the page 42 rows down, because
 * a blur does not end at half its radius. It is faint long before that. At 26
 * the cut fell where the tail was still 6.6 of 255, a hard line at about two
 * and a half percent; at 32 it falls at 2.8, a little over one percent, which
 * cannot be made out. 42 was not worth it: the room is bought with a
 * negative margin, so every pixel is one the track overlaps its neighbours by.
 *
 * Strength scales the alpha and not the geometry, so no setting reaches
 * further than the black-at-strong this was measured with.
 *
 * The block axis only. Inline padding would come out of the width the column
 * arithmetic divides up, and the shadow reaches about 2px sideways in any case.
 * Rail and the timelines are left out: neither answers a hover with this
 * shadow, and Rail measures its own track to place the cursor.
 */
[data-carousel="yes"][data-carousel-direction="horizontal"]:not([data-layout="rail"]):not([data-layout|="timeline"]) .npx-events__grid {
	padding-block: 32px;
	margin-block: -32px;
}

/*
 * A track cannot move its cards along its own axis.
 *
 * A card here is a snap target, and the area a snap looks at is the card's
 * transformed box. Grow scales it about its centre, dragging its start edge
 * sideways, and mandatory snapping pulls the whole track over to catch up.
 * Parked at the far end of the arrangement this site uses, the row slid 5px the
 * instant the pointer reached the card before the last — `scrollLeft` 2844 to
 * 2839 — and that was enough for the next arrow to decide there was more to
 * come and light up with nothing left to show. Only the card the track was
 * snapped to did it; Lift, Slide and None never moved it at all.
 *
 * Slide is here too. It did not drag at the far end, but only because a
 * rightward nudge there has nowhere to pull to; at the near end it is the same
 * fault the other way about.
 *
 * So both go on what the card holds, exactly as they do on a Timeline. The
 * card's own box then never changes, which is also what keeps a swelling card
 * from being pushed out of either end of the track — measured before this, the
 * first card lost 4.8px off its left edge.
 *
 * Lift stays on the card: a vertical nudge moves nothing that a start-aligned
 * snap on a horizontal track is looking at. The Timelines are left out because
 * they already do this for themselves, with origins chosen for the spine.
 */
[data-carousel="yes"][data-carousel-direction="horizontal"]:not([data-layout|="timeline"]):is([data-card-motion="grow"], [data-card-motion="slide"]) .npx-events__grid > .npx-card:not(.npx-card--lead):hover {
	transform: none;
}

[data-carousel="yes"][data-carousel-direction="horizontal"][data-card-motion="grow"]:not([data-layout|="timeline"]) .npx-events__grid > .npx-card:not(.npx-card--lead):hover > * {
	transform: scale(1.014);
}

[data-carousel="yes"][data-carousel-direction="horizontal"][data-card-motion="slide"]:not([data-layout|="timeline"]) .npx-events__grid > .npx-card:not(.npx-card--lead):hover > * {
	transform: translateX(6px);
}

/*
 * Vertical: the ordinary grid, given a height and taught to snap. Nothing
 * about the placement of the cards changes — the column count still means
 * columns — so a vertical carousel of three across is a window onto the grid
 * the block would have drawn anyway.
 *
 * The height is the whole difficulty. A row has the container's width to work
 * with; a column has no natural height, because the block grows with whatever
 * it holds. So the height is measured in the script from the first few cards
 * and written here as a pixel value. Without the script no height is set, the
 * grid shows everything, and the block degrades to the plain list it started
 * as — which is a better failure than a clipped one.
 */
[data-carousel="yes"][data-carousel-direction="vertical"] .npx-events__grid {
	/*
	 * Rows sized from their content, and not a fraction of the box. Left at
	 * auto they resolve against the container once it has a definite height —
	 * so the height the script set squashed the very rows it had measured, and
	 * every re-measure squashed them further. Content-sized rows make the
	 * height a clip and nothing more, which is what lets the measurement be
	 * run again and land on the same answer.
	 */
	grid-auto-rows: max-content;
	align-content: start;
	overflow-y: auto;
	scroll-snap-type: y mandatory;
	overscroll-behavior-y: contain;
}

[data-carousel="yes"] .npx-events__grid {
	scrollbar-width: none;
}

[data-carousel="yes"] .npx-events__grid::-webkit-scrollbar {
	display: none;
}

[data-carousel="yes"] .npx-events__grid > * {
	scroll-snap-align: start;
}

/* ---- the fade at the ends ------------------------------------------------ */

/*
 * A row that scrolls has to say so. The usual answer is a soft edge at both
 * ends of the track, which is easy and slightly dishonest: a row faded where it
 * in fact stops is telling you there is more when there is not.
 *
 * So each end is faded only while its own arrow can still move. Reach the last
 * card and the right-hand edge goes hard, which is the row saying you have got
 * to the end — the same fact the arrow going grey is telling you, said where
 * the eye already is. At the start, the same in reverse. A track short enough
 * to need no scrolling has neither.
 *
 * It is a mask rather than a gradient painted over the top, because a gradient
 * has to fade to a colour and this block does not know what it is sitting on —
 * a theme section, a photograph, a dark band. A mask fades to whatever is
 * behind it and cannot be wrong.
 */
@property --npx-fade-a {
	syntax: "<length>";
	inherits: false;
	initial-value: 0px;
}

@property --npx-fade-b {
	syntax: "<length>";
	inherits: false;
	initial-value: 0px;
}

/*
 * Registered, so the two can be transitioned. A custom property the browser
 * knows the type of animates like any other length, and the mask is recomputed
 * as it goes — which is the difference between an edge that softens as you
 * leave it and one that snaps on. Where `@property` is not supported the
 * transition is simply skipped and the fade steps, which is the same
 * information arriving less gracefully.
 */
[data-carousel="yes"][data-carousel-fade]:not([data-carousel-fade="no"]) .npx-events__grid {
	transition: --npx-fade-a 0.24s ease, --npx-fade-b 0.24s ease;
}

[data-carousel="yes"][data-carousel-fade]:not([data-carousel-fade="no"])[data-carousel-direction="horizontal"] .npx-events__grid {
	-webkit-mask-image: linear-gradient(
		to right,
		transparent 0,
		#000 var(--npx-fade-a),
		#000 calc(100% - var(--npx-fade-b)),
		transparent 100%
	);
	mask-image: linear-gradient(
		to right,
		transparent 0,
		#000 var(--npx-fade-a),
		#000 calc(100% - var(--npx-fade-b)),
		transparent 100%
	);
}

[data-carousel="yes"][data-carousel-fade]:not([data-carousel-fade="no"])[data-carousel-direction="vertical"] .npx-events__grid {
	-webkit-mask-image: linear-gradient(
		to bottom,
		transparent 0,
		#000 var(--npx-fade-a),
		#000 calc(100% - var(--npx-fade-b)),
		transparent 100%
	);
	mask-image: linear-gradient(
		to bottom,
		transparent 0,
		#000 var(--npx-fade-a),
		#000 calc(100% - var(--npx-fade-b)),
		transparent 100%
	);
}

/*
 * `start` means there is track behind you, so the near edge fades; `end` means
 * there is track ahead. The script writes one of the four from the same two
 * facts it uses to grey the arrows out, so the two can never disagree.
 */
/*
 * How far the softening reaches. Light is a hint at the very edge; strong is
 * about half a card at three across, which stops reading as an edge and
 * starts reading as a vignette — which is the point of having three.
 */
[data-carousel-fade="light"] .npx-events__grid {
	--npx-fade-w: 3rem;
}

[data-carousel-fade="medium"] .npx-events__grid {
	--npx-fade-w: 6rem;
}

[data-carousel-fade="strong"] .npx-events__grid {
	--npx-fade-w: 10rem;
}

[data-carousel-fade] .npx-events__grid[data-npx-edge="start"],
[data-carousel-fade] .npx-events__grid[data-npx-edge="both"] {
	--npx-fade-a: var(--npx-fade-w, 0px);
}

[data-carousel-fade] .npx-events__grid[data-npx-edge="end"],
[data-carousel-fade] .npx-events__grid[data-npx-edge="both"] {
	--npx-fade-b: var(--npx-fade-w, 0px);
}

@media (prefers-reduced-motion: reduce) {
	[data-carousel="yes"][data-carousel-fade]:not([data-carousel-fade="no"]) .npx-events__grid {
		transition: none;
	}
}

/* Smooth for a click on an arrow, immediate for anyone who asked for that. */
@media (prefers-reduced-motion: no-preference) {
	[data-carousel="yes"] .npx-events__grid {
		scroll-behavior: smooth;
	}
}

/*
 * The shell exists so the arrows have something to be placed against. It is a
 * column, so the two "below" placements are a matter of order alone.
 */
.npx-carousel {
	position: relative;
	display: flex;
	flex-direction: column;
}

.npx-carousel__nav {
	display: flex;
	justify-content: flex-end;
	gap: 0.4rem;
	margin: 0 0 0.75rem;
}

/* ---- placements ---- */

/*
 * Floating: the arrows sit over the first and last card, half off the edge of
 * the row, which is the shape that says "there is more this way". They are
 * given a solid fill and a shadow because what is behind them is a photograph
 * the plugin has never seen. The bar itself lets pointers through so it does
 * not swallow clicks meant for the cards under it.
 */
[data-arrows="sides"] .npx-carousel__nav {
	position: absolute;
	z-index: 3;
	top: 50%;
	/*
	 * The offset follows the button's own size, so however big the arrows get
	 * they always sit the same short distance over the card. A fixed offset
	 * meant a large arrow reached far enough in to cover the wording.
	 */
	/*
	 * How far out it can go is limited by the page's own margin, not by the
	 * container's width — a wide column on a barely wider window has no room
	 * to lend even though the column itself is large. That margin is half of
	 * whatever the viewport has that the container does not, which container
	 * query units can state directly: (100vw - 100cqw) / 2.
	 *
	 * 1.25rem of that is left alone, partly as breathing room and partly
	 * because 100vw counts the scrollbar and the margin is really that much
	 * narrower. The max() keeps it sane when the container fills the window.
	 */
	--npx-arrow-out: min(
		calc((var(--npx-arrow) * 1.2) - 0.55rem),
		max(0.5rem, calc((100vw - 100cqw) / 2 - 1.25rem))
	);
	right: calc(-1 * var(--npx-arrow-out));
	left: calc(-1 * var(--npx-arrow-out));
	margin: 0;
	transform: translateY(-50%);
	justify-content: space-between;
	pointer-events: none;
}

/* A shade larger where they float over a picture and need to hold their own. */
[data-arrows="sides"] .npx-carousel__arrow {
	width: calc(var(--npx-arrow) * 1.2);
	height: calc(var(--npx-arrow) * 1.2);
	box-shadow: 0 2px 10px -2px rgba(20, 22, 24, 0.35);
	pointer-events: auto;
}

/* Hidden rather than dimmed out here: a disabled arrow floating over a card
   is clutter, where in a bar of its own it is a useful "you are at the end". */
[data-arrows="sides"] .npx-carousel__arrow:disabled {
	opacity: 0;
	pointer-events: none;
}

/*
 * Floating, vertically: at the top and bottom edges of the window rather than
 * its sides, and half over each the way the horizontal ones are. Sitting
 * wholly inside the track put the whole button on a card and made the wording
 * underneath it unreadable.
 *
 * Where the horizontal ones borrow the page's own margin to hang out into,
 * there is nothing to borrow above and below — only whatever the page put
 * there, which is not ours to cover. So the block reserves it instead: the
 * shell takes exactly the margin the overhang needs, and the arrows hang into
 * space that belongs to the carousel. That holds at every arrow size without
 * a cap, which capping the offset would not have — a large arrow would still
 * have been mostly on the card.
 */
[data-carousel-direction="vertical"][data-arrows="sides"] .npx-carousel {
	--npx-arrow-out-y: calc(var(--npx-arrow) * 0.6);
	margin-block: var(--npx-arrow-out-y);
}

[data-carousel-direction="vertical"][data-arrows="sides"] .npx-carousel__nav {
	top: calc(-1 * var(--npx-arrow-out-y));
	right: auto;
	bottom: calc(-1 * var(--npx-arrow-out-y));
	left: 50%;
	flex-direction: column;
	padding: 0;
	transform: translateX(-50%);
}

/*
 * The air above the buttons is padding, not a margin, and that is not a matter
 * of taste. The theme carries
 *
 *     .main-container .row-container .row-parent .uncont > … { margin-top: 0 !important }
 *
 * which matches this element, so a margin-top here is discarded whatever it is
 * set to and whatever it is set with — measured on the live page: the plugin
 * asked for 0.9rem, the browser reported 0px, and the arrows sat flush against
 * the track. Padding is not in that rule's reach and produces the same gap.
 */
[data-arrows="below"] .npx-carousel__nav,
[data-arrows="split"] .npx-carousel__nav {
	order: 1;
	margin: 0;
	padding-top: 0.9rem;
}

/*
 * A little more room under a timeline than under a grid of cards. Every other
 * display puts its events on a panel with padding of its own, so the wording
 * stops well inside the edge of the track; a timeline card has no panel, and
 * under the pointer the hover fill is the card and runs to the last pixel of
 * it. A round rem rather than something derived — the difference from the
 * 0.9rem above is small on purpose, because the fill only needs to stop
 * touching the buttons, not stand well back from them.
 */
[data-layout|="timeline"][data-arrows="below"] .npx-carousel__nav,
[data-layout|="timeline"][data-arrows="split"] .npx-carousel__nav,
[data-layout="rail"][data-arrows="below"] .npx-carousel__nav,
[data-layout="rail"][data-arrows="split"] .npx-carousel__nav {
	padding-top: 1rem;
}

[data-arrows="below"] .npx-carousel__nav {
	justify-content: center;
}

[data-arrows="split"] .npx-carousel__nav {
	justify-content: space-between;
}

/*
 * One measurement drives the button and the chevron inside it, so a size
 * change keeps its proportions instead of leaving a small mark on a big
 * button.
 */
.npx-events {
	--npx-arrow: 2rem;
	--npx-arrow-mark: 0.5rem;
}

[data-arrow-size="small"] {
	--npx-arrow: 1.5rem;
	--npx-arrow-mark: 0.36rem;
}

[data-arrow-size="large"] {
	--npx-arrow: 3.25rem;
	--npx-arrow-mark: 0.8rem;
}

.npx-carousel__arrow {
	display: inline-flex;
	width: var(--npx-arrow);
	height: var(--npx-arrow);
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 1px solid var(--npx-line);
	border-radius: 50%;
	background: var(--npx-bg);
	color: var(--npx-ink-2);
	cursor: pointer;
	transition: border-color 0.15s, color 0.15s;
}

.npx-carousel__arrow:hover:not(:disabled) {
	border-color: var(--npx-accent);
	color: var(--npx-ink);
}

.npx-carousel__arrow:disabled {
	opacity: 0.35;
	cursor: default;
}

/* A chevron drawn from a rotated square: no icon font, no SVG file. */
.npx-carousel__arrow::before {
	content: "";
	width: var(--npx-arrow-mark);
	height: var(--npx-arrow-mark);
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
}

.npx-carousel__arrow--prev::before {
	transform: translateX(1px) rotate(135deg);
}

.npx-carousel__arrow--next::before {
	transform: translateX(-1px) rotate(-45deg);
}

/* The same corner, turned a quarter further: up for back, down for on. */
[data-carousel-direction="vertical"] .npx-carousel__arrow--prev::before {
	transform: translateY(1px) rotate(-135deg);
}

[data-carousel-direction="vertical"] .npx-carousel__arrow--next::before {
	transform: translateY(-1px) rotate(45deg);
}

/* ------------------------------------------------- the date under a hover */

/*
 * A small acknowledgement from the date when the pointer reaches the card.
 * Off by default: it is a flourish, and a page of them is a page that fidgets.
 *
 * Both elements are named because which one holds the date depends on the
 * view — Rows and Poster lead with the block, the others write it into the
 * wording — and only one of the two is ever on screen at a time.
 */
/*
 * The date box is a `time` element, which is inline by default, where it used
 * to be a paragraph. Several displays set a display of their own and still do;
 * this is only the box `p` was giving it for nothing.
 */
.npx-card__datebox {
	display: block;
}

.npx-card__datebox,
.npx-card__dates {
	transition: transform 0.18s ease, color 0.18s ease;
}

[data-date-motion="lift"] .npx-card:hover .npx-card__datebox,
[data-date-motion="lift"] .npx-card:hover .npx-card__dates {
	transform: translateY(-3px);
}

/*
 * Anchored to the left edge rather than the centre, so the numeral grows into
 * the card's own margin instead of drifting sideways under the wording.
 */
[data-date-motion="grow"] .npx-card:hover .npx-card__datebox,
[data-date-motion="grow"] .npx-card:hover .npx-card__dates {
	transform: scale(1.07);
	transform-origin: left center;
}

[data-date-motion="grow"][data-layout="cards"] .npx-card:hover .npx-card__dates,
[data-date-motion="grow"][data-layout="white"] .npx-card:hover .npx-card__dates,
[data-date-motion="grow"][data-layout="cover"] .npx-card:hover .npx-card__dates {
	transform-origin: center;
}

/*
 * The one movement a date has more room for than a card does: it sits in a
 * gutter of its own, so it can travel without arriving anywhere. Four pixels
 * rather than the card's six, and rightwards into that gutter's padding —
 * 12.8px of it on a Timeline, so the numeral never reaches the line.
 */
[data-date-motion="slide"] .npx-card:hover .npx-card__datebox,
[data-date-motion="slide"] .npx-card:hover .npx-card__dates {
	transform: translateX(4px);
}

/*
 * At the end of the Rail the label is hung the other way round, so its date and
 * its title share their RIGHT edge. Both of the horizontal date animations are
 * written for a shared left edge: `grow` holds the left edge still and expands
 * rightwards, `slide` steps right. Against a right-aligned label each of them
 * carries the date out past the title — 11.8px and 4px respectively, and past
 * the hover fill with it.
 *
 * Mirrored rather than suppressed: the movement is the point, and reflecting it
 * is what the label's own alignment already does to everything else in it.
 */
[data-layout="rail"][data-date-motion="grow"] .npx-card[data-npx-edge="end"]:hover .npx-card__datebox,
[data-layout="rail"][data-date-motion="grow"] .npx-card[data-npx-edge="end"]:focus-within .npx-card__datebox {
	transform-origin: right center;
}

[data-layout="rail"][data-date-motion="slide"] .npx-card[data-npx-edge="end"]:hover .npx-card__datebox,
[data-layout="rail"][data-date-motion="slide"] .npx-card[data-npx-edge="end"]:focus-within .npx-card__datebox {
	transform: translateX(-4px);
}

[data-date-motion="accent"] .npx-card:hover .npx-card__datebox,
[data-date-motion="accent"] .npx-card:hover .npx-card__day,
[data-date-motion="accent"] .npx-card:hover .npx-card__dates {
	color: var(--npx-accent);
}

/* --------------------------------------------------------- project chips */

/*
 * Hidden rather than left out of the markup: the preview keeps its own chips,
 * and this is about the card being quieter, not about the event losing its
 * projects. The filter still works on them — it reads an attribute, not a chip.
 */
[data-badges="no"] .npx-card__projects {
	display: none;
}

/* ------------------------------------------------------- the card shadow */

/*
 * One tint drives every card shadow, so the choice in the element reaches all
 * four views from a single place. The alphas stay fixed: a shadow reads as a
 * shadow because of how faint and how spread it is, and letting the colour
 * choice change that too would turn one setting into four different effects.
 */
.npx-events {
	--npx-shadow-tint: 224, 153, 1;
	/*
	 * Strength is a multiplier on the alphas rather than a second set of
	 * shadows: the geometry that makes a shadow believable — how far it falls,
	 * how far it spreads — stays put, and only its weight changes.
	 */
	--npx-shadow-scale: 1;
}

[data-shadow-strength="light"] {
	--npx-shadow-scale: 0.5;
}

[data-shadow-strength="strong"] {
	--npx-shadow-scale: 1.5;
}

[data-shadow="grey"] {
	--npx-shadow-tint: 107, 114, 128;
}

[data-shadow="dark-grey"] {
	--npx-shadow-tint: 59, 64, 69;
}

[data-shadow="black"] {
	--npx-shadow-tint: 20, 22, 24;
}

[data-shadow="accent"] {
	--npx-shadow-tint: 224, 153, 1;
}

/*
 * "Custom" declares nothing: the chosen colour arrives as an inline
 * `--npx-shadow-tint` on the block, which is the same token every named choice
 * above sets, so the shadows themselves need no second code path. If the
 * colour ever fails to parse the inline style is left off and the default tint
 * on `.npx-events` stands.
 */

/* Every view answers a hover with the same lifted shadow. */
.npx-card:hover {
	box-shadow: 0 12px 28px -12px rgba(var(--npx-shadow-tint), calc(0.55 * var(--npx-shadow-scale)));
}

/*
 * A flush row is separated by a rule rather than by a surface, so it has no
 * shadow to lift: the shared hover shadow above would only smudge the
 * separator. Spaced rows are the exception and are handled where the spacing
 * is (see the Rows layout).
 */
[data-layout="rows"] .npx-card,
[data-layout="rows"] .npx-card:hover {
	box-shadow: none;
}

/*
 * "None" switches off the resting shadow as well as the hover one.
 *
 * It carries `.npx-events` purely for weight: without it this ties with
 * `[data-layout="white"] .npx-card` and loses on source order, which is
 * exactly what happened — White cards kept its shadow with None selected.
 */
.npx-events[data-shadow="none"] .npx-card,
.npx-events[data-shadow="none"] .npx-card:hover {
	box-shadow: none;
}

/* The date block belongs to the Rows layout; the others keep it out of the way. */
.npx-card__datebox {
	display: none;
}

/* ==================================================== White cards layout */

/*
 * The same grid as Cards, but each card is a white panel that is separated by
 * an outline and a shadow rather than by its own fill. It suits a page whose
 * background is already tinted, where a grey card would barely register, and
 * artwork with a pale background, which meets a white card without a seam.
 *
 * Only the surface changes: everything about the structure comes from Cards.
 */
[data-layout="white"] .npx-card {
	--npx-card-bg: var(--npx-bg);
	--npx-card-bg-hover: var(--npx-bg);
	--npx-card-media-bg: var(--npx-bg-2);
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius);
	box-shadow:
		0 1px 2px rgba(var(--npx-shadow-tint), calc(0.06 * var(--npx-shadow-scale))),
		0 4px 16px -6px rgba(var(--npx-shadow-tint), calc(0.12 * var(--npx-shadow-scale)));
	transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}

/*
 * The card cannot answer a hover with its fill the way a grey card does, so it
 * lifts instead.
 */
[data-layout="white"] .npx-card:hover {
	border-color: var(--npx-hover-line);
	box-shadow:
		0 2px 4px rgba(var(--npx-shadow-tint), calc(0.08 * var(--npx-shadow-scale))),
		0 12px 32px -10px rgba(var(--npx-shadow-tint), calc(0.45 * var(--npx-shadow-scale)));
}

/*
 * The chips keep the shared grey fill here. Cards inverts them to white
 * because there they would otherwise disappear into the card; on a white card
 * it is the grey that separates them.
 */

/* =========================================================== Cover layout */

/*
 * The image fills the card and the details sit on top of it.
 *
 * The darkening is a scrim behind the text, not a wash over the whole picture:
 * a plugin has no idea what it will be given, and dimming everything ruins the
 * logos and flat artwork that many sites use for events while barely helping
 * photographs. A gradient anchored to the bottom keeps the top of the image
 * untouched and still guarantees contrast where the words are.
 */
[data-layout="cover"] .npx-card {
	/*
	 * There is no image band to leave room for here — the picture is the card —
	 * so this is only a floor for the text over it, not space that has to be
	 * filled. Trimmed by a quarter from the 400px it started at.
	 */
	min-height: 300px;
	transition: transform 0.2s, box-shadow 0.2s;
}

[data-layout="cover"] .npx-card__aside {
	position: absolute;
	inset: 0;
	width: auto;
	aspect-ratio: auto;
}

/*
 * One even veil over the whole picture, rather than a gradient behind the text.
 * Uniform keeps the image readable as an image and the card as a single
 * surface; a gradient draws attention to its own edge.
 */
/*
 * The four strengths. Measured against the hardest case there is — a picture
 * that is white where the wording falls — they put white text at 4.8, 6.8,
 * 10.6 and 14.3 to one, so every step clears AA whatever the picture turns
 * out to be. The top step all but replaces the image with a ghost
 * of itself, which is the point: some pictures cannot be made to work behind
 * words any other way, and a legible card beats a visible photograph.
 *
 * Medium is not declared here. It is the root default above, so a block with
 * no strength set and one set to medium cannot drift apart.
 */
[data-overlay="light"] {
	--npx-cover-veil: rgba(27, 29, 31, 0.62);
	--npx-cover-veil-hover: rgba(27, 29, 31, 0.7);
}

[data-overlay="strong"] {
	--npx-cover-veil: rgba(27, 29, 31, 0.85);
	--npx-cover-veil-hover: rgba(27, 29, 31, 0.91);
}

[data-overlay="very-strong"] {
	--npx-cover-veil: rgba(27, 29, 31, 0.94);
	--npx-cover-veil-hover: rgba(27, 29, 31, 0.97);
}

/*
 * Cover has to have this or its text sits straight on the picture; on Cards
 * and White cards it is optional, which is what "none" is for, and why none is
 * the default there.
 */
[data-layout="cover"]:not([data-overlay="none"]) .npx-card__aside::after,
[data-layout="cards"]:not([data-overlay="none"]) .npx-card__aside::after,
[data-layout="white"]:not([data-overlay="none"]) .npx-card__aside::after {
	content: "";
	position: absolute;
	inset: 0;
	background: var(--npx-cover-veil);
	transition: background-color 0.2s;
}

/* The card answers a hover, so the veil over its image answers with it. */
[data-layout="cards"]:not([data-overlay="none"]) .npx-card:hover .npx-card__aside::after,
[data-layout="white"]:not([data-overlay="none"]) .npx-card:hover .npx-card__aside::after {
	background: var(--npx-cover-veil-hover);
}

/*
 * Two clusters, not five evenly spaced items: the type and title sit together
 * at the top, everything else sits together at the foot, and the single gap
 * between them takes up whatever height is left.
 */
[data-layout="cover"] .npx-card__main {
	position: relative;
	z-index: 1;
	justify-content: flex-start;
	min-height: 0;
	/* Clears the origin badge pinned to the top-left corner. */
	padding-top: 3.1rem;
	background: none;
}

[data-layout="cover"] .npx-card__title,
[data-layout="cover"] .npx-card__trigger,
[data-layout="cover"] .npx-card__place {
	color: rgba(255, 255, 255, 0.82);
}

/*
 * A light, even veil cannot guarantee contrast over a pale photograph, so the
 * text carries a soft shadow of its own. It is invisible on a dark image and
 * does the work on a bright one.
 */
[data-layout="cover"] .npx-card__main {
	text-shadow: 0 1px 4px rgba(10, 12, 18, 0.55);
}

/* The button has its own solid fill and needs no help; the inherited shadow
   only made its label look smudged. */
[data-layout="cover"] .npx-button {
	text-shadow: none;
}

/*
 * Cover brightens its title instead of ambering it. Measured over the palest
 * image the veil has to cope with, white is 3.3:1, the brand amber 1.4:1 and
 * the lighter #ffc860 2.1:1 — recolouring would cost legibility exactly where
 * it is already tightest. Going from 82% white to solid is the same gesture
 * without that cost, and the ring, lift and underline carry the rest.
 */
[data-layout="cover"] .npx-card:hover .npx-card__trigger,
[data-layout="cover"] .npx-card__trigger:focus-visible {
	color: #fff;
}

[data-layout="cover"] .npx-card__dates {
	color: #fff;
}

[data-layout="cover"] .npx-card__time {
	color: rgba(255, 255, 255, 0.9);
}

[data-layout="cover"] .npx-card__type {
	color: var(--npx-accent-lift);
	text-shadow: 0 1px 3px rgba(12, 14, 20, 0.9);
}

[data-layout="cover"] .npx-card__footer {
	border-top-color: rgba(255, 255, 255, 0.28);
}

/*
 * Cover's chips are body chips: `render.php` puts them over the artwork only
 * for Cards and White cards, and Cover is not in that list. So the plate comes
 * from the base rule and the dark-section rule, the same grey the other six
 * body-chip displays use, and only the shadow is stated here — the chips sit
 * inside `.npx-card__main`, which is shadowed so the wording can be read off
 * the picture, and a chip with its own opaque plate wants none of it.
 *
 * Grey rather than white for the reason this file already gives further down:
 * a white plate is the brightest thing on the card, and on Cover the title is
 * solid white over a photograph, so a white chip competes with the name of the
 * event more here than anywhere else. Both greys are opaque, so the chip is no
 * more at the mercy of the picture than a white one was.
 */
[data-layout="cover"] .npx-badge--project {
	text-shadow: none;
}

/* The title grows, so the block stays anchored to the bottom of the image. */
[data-layout="cover"] .npx-card__type {
	margin-bottom: 0.8rem;
}

[data-layout="cover"] .npx-card__title {
	margin: 0;
}

/*
 * The leftover height is split rather than dumped in one place: the title's box
 * takes two parts of it and the button's box one, which drops the button about
 * two thirds of the way down the gap — clear of the title, and not sitting on
 * top of the chips.
 *
 * Growing the boxes rather than nudging with margins means a card with no
 * button, or no chips, still distributes what it has instead of collapsing.
 */
/*
 * The date block takes the growth, not the title. Growing the title's own box
 * would push the date away from the heading it belongs to; growing the box
 * that starts with the date keeps the two together and opens the gap below
 * them instead.
 */
[data-layout="cover"] .npx-card__when {
	flex-grow: 2;
}

[data-layout="cover"] .npx-card__action {
	flex-grow: 1;
	margin-top: 0;
}

/* Nothing grows below the button, so these stay together at the foot. */
[data-layout="cover"] .npx-card__projects {
	margin-top: 0;
}

[data-layout="cover"] .npx-card__footer {
	margin-top: 0.7rem;
}



/*
 * The accent line lives on a pseudo-element stacked above the picture, not on
 * the card itself. The image is positioned over the card's whole box, so a
 * border or an inset shadow on the card is painted underneath it and never
 * shows — measured, not assumed: the glow appeared and the line did not.
 *
 * A pseudo-element also means no layout shift, which a real border would cause
 * by widening the card's box on hover and nudging its neighbours.
 */
[data-layout="cover"] .npx-card::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 2;
	border: 1px solid transparent;
	transition: border-color 0.2s;
	pointer-events: none;
}

[data-layout="cover"] .npx-card:hover::after {
	border-color: var(--npx-hover-line);
}

/*
 * Which is what Hover fill sets here.
 *
 * Every other display answers a hover with a fill. Cover cannot: the picture is
 * painted over the card's whole box, so a fill behind it is never seen — the
 * same reason this line lives on a pseudo-element stacked above the image. So
 * the four choices colour the line instead, and None leaves the card with none,
 * which is the one the lift and the shadow already speak for.
 *
 * Grey is written out rather than taken from `--npx-bg-2`. That token is what
 * Grey means on a page surface, and it flips to `rgba(255, 255, 255, 0.06)` in
 * a dark section — six per cent of white, which as a hairline over a
 * photograph is nothing at all. A Cover card is a photograph whatever the page
 * around it is doing, so the neutral is stated for the picture and not for the
 * page.
 */

[data-layout="cover"] .npx-card:hover {
	transform: translateY(-3px);
}

[data-layout="cover"] .npx-card:hover .npx-card__aside::after {
	background: var(--npx-cover-veil-hover);
}

/* ========================================================= Group headings */

/*
 * One mechanism, offered on every display with room for it: insert a heading
 * wherever the group changes, and hide the ones the filters empty.
 *
 * The heading is a child of the grid rather than a wrapper around each run of
 * cards. That is the whole trick: the cards keep one flow, the grid keeps
 * placing them, and the filters, the empty state and the card CSS all go on
 * addressing `.npx-card` with nothing to relearn. A wrapper per group would
 * mean a grid inside a grid and a second set of column rules.
 */
.npx-events__group {
	grid-column: 1 / -1;
	display: flex;
	align-items: baseline;
	gap: 0.7rem;
	margin: 0;
	padding-bottom: 0.35rem;
	border-bottom: 1px solid var(--npx-line);
}

/* Every heading after the first opens a new stretch, so it needs air above. */
.npx-events__group ~ .npx-events__group {
	margin-top: 1rem;
}

.npx-events__group-name {
	color: var(--npx-ink-2);
	font-size: 0.72rem;
	font-weight: 800;
	letter-spacing: 0.09em;
	text-transform: uppercase;
}

.npx-events__group-count {
	margin-left: auto;
	color: var(--npx-ink-4);
	font-size: 0.68rem;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

/*
 * Rows carries its own rule under every event, so a hairline heading would
 * read as one more of them. It takes the accent instead, which is the one mark
 * on the page that means "a new stretch starts here".
 */
[data-layout="rows"] .npx-events__group {
	border-bottom-color: var(--npx-accent);
	border-bottom-width: 2px;
}

[data-layout="rows"] .npx-events__group-name {
	color: var(--npx-ink);
}

/* ======================================================= Spotlight layout */

/*
 * One event carries the block and the rest queue beside it. Every other display
 * gives nine events equal weight; on a home page the next one is the story and
 * the others are context, which is a thing no amount of ordering can say.
 *
 * Two columns, and the hero is simply the first card left in column one while
 * everything after it is pushed into column two. `grid-row: 1 / -1` is not used
 * to make the hero span: with rows created implicitly, `-1` still points at the
 * last explicit line, so it would span exactly one row and look like a bug.
 */
[data-layout="spotlight"] .npx-events__grid {
	display: grid;
	grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
	align-items: start;
	gap: var(--npx-gap);
}

/* The queue stacks on its own, independent of how tall the hero is. */
.npx-events__queue {
	display: flex;
	flex-direction: column;
}

[data-layout="spotlight"] .npx-events__queue .npx-card {
	--npx-card-bg: transparent;
	display: grid;
	grid-template-columns: 3rem minmax(0, 1fr);
	gap: 0.7rem;
	align-items: center;
	height: auto;
	/*
	 * Room at the sides, for the reason Rows has it: under the pointer the
	 * fill paints the whole row, so wording that starts at the row's edge
	 * starts at the fill's edge with nothing between them.
	 *
	 * It showed here more than anywhere because this first column is exactly
	 * as wide as its widest contents: a date range inks at 48px in a 48px
	 * column, so the date met the fill with no air at all on the left and
	 * only the grid's gap on the right.
	 *
	 * Half a rem and not the three quarters Rows uses. This is a narrow
	 * column beside a hero rather than a list across the page, so every
	 * pixel of inset comes straight out of the titles, which are long and
	 * already wrap. Enough to part the wording from the edge is the whole
	 * job here; more would be paid for in wrapping.
	 *
	 * Bleeding the fill outwards instead was the other way to buy the room,
	 * and there is none to bleed into: the queue's right edge sits flush
	 * with the grid, and the gap to the hero on the left is 12.8px on one of
	 * the two blocks measured, which a bleed would close entirely.
	 *
	 * The column itself is left alone — inset, it has this padding on one
	 * side and the grid's 0.7rem gap on the other.
	 */
	padding: 0.72rem 0.5rem;
	border: 0;
	border-bottom: 1px solid var(--npx-line);
	border-radius: 0;
	box-shadow: none;
	text-align: left;
}

[data-layout="spotlight"] .npx-events__queue .npx-card:last-child {
	border-bottom: 0;
}

/* ---- the queue ---------------------------------------------------------- */

[data-layout="spotlight"] .npx-events__queue .npx-card__aside,
[data-layout="spotlight"] .npx-events__queue .npx-card__projects,
[data-layout="spotlight"] .npx-events__queue .npx-card__footer,
[data-layout="spotlight"] .npx-events__queue .npx-card__when {
	display: none;
}

[data-layout="spotlight"] .npx-events__queue .npx-card__datebox {
	display: block;
	order: 0;
	margin: 0;
	text-align: center;
}

[data-layout="spotlight"] .npx-events__queue .npx-card__day {
	display: block;
	color: var(--npx-ink);
	font-size: 1.04rem;
	font-weight: 700;
	line-height: 1;
	font-variant-numeric: tabular-nums;
}

[data-layout="spotlight"] .npx-events__queue .npx-card__month {
	display: block;
	margin-top: 0.16rem;
	color: var(--npx-ink-4);
	font-size: 0.58rem;
	font-weight: 700;
	letter-spacing: 0.09em;
	text-transform: uppercase;
}

[data-layout="spotlight"] .npx-events__queue .npx-card__main {
	min-height: 0;
	padding: 0;
	align-items: flex-start;
	justify-content: center;
	text-align: left;
}

[data-layout="spotlight"] .npx-events__queue .npx-card__type {
	margin-bottom: 0.14rem;
	font-size: 0.6rem;
}

[data-layout="spotlight"] .npx-events__queue h3.npx-card__title {
	flex-grow: 0;
	font-size: clamp(0.86rem, 4.4cqi, 0.98rem);
}

[data-layout="spotlight"] .npx-events__queue .npx-card__trigger {
	-webkit-line-clamp: 2;
}

/* ---- the hero ----------------------------------------------------------- */

/*
 * Everything the queue gives up, the hero takes back. It is the same card and
 * the same markup; only the rules below are addressed to it.
 */
/*
 * The picture sits beside the words, not above them. Stacked, the hero's height
 * was the image plus the body — 457px at 1100px wide, and capping the image to
 * fix that only turned it into a letterbox, which crops a logo to nothing and
 * half this agenda is logos. Side by side, the height is the taller of the two
 * instead of the sum, and the image keeps a shape worth looking at.
 */
[data-layout="spotlight"] .npx-events__grid > .npx-card {
	display: grid;
	grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
	/*
	 * Two rows: the picture and the head sit side by side in the first, and the
	 * text spans both columns in the second. `auto 1fr` gives the first row
	 * exactly what it needs and hands everything left to the text, which is how
	 * the card fills the block without the picture growing.
	 */
	grid-template-rows: auto minmax(0, 1fr) auto;
	align-items: stretch;
	/*
	 * The card runs the full height of the block; the picture inside it does
	 * not. Those are two different things and the first attempt conflated them:
	 * stretching the card stretched the image with it, which changed the
	 * composition when all that was asked for was more content in it. The
	 * picture is pinned to a set height below and the words take the rest.
	 */
	align-self: stretch;
	height: 100%;
	gap: 0;
	padding: 0;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius);
	overflow: hidden;
	background: var(--npx-bg-2);
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__aside {
	grid-column: 1;
	grid-row: 1;
	display: block;
	position: relative;
	/*
	 * A set height, and `align-self: start` so the stretch above stops at the
	 * card and never reaches the picture.
	 */
	align-self: start;
	height: 210px;
	overflow: hidden;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__datebox {
	display: none;
}

/*
 * The head is its own little grid: the kind of event on the left and the flag
 * pushed to the far end of the same line, with everything after it running the
 * full width underneath. A flex column could not put two of these side by side
 * without a wrapper element, and the card markup is shared with seven other
 * displays that do not want one.
 */
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__main {
	grid-column: 2;
	grid-row: 1;
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-content: start;
	min-height: 0;
	padding: 0.62rem 0.85rem 0.7rem;
	text-align: left;
}

/*
 * Both sit on the same baseline rather than being centred in the row. Centring
 * lines up the boxes, and the flag's box is a padded plate while the type is
 * bare text — so their letters ended up a couple of pixels apart, which is
 * exactly the amount that reads as a mistake rather than a decision.
 */
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__type {
	grid-column: 1;
	grid-row: 1;
	align-self: baseline;
	margin: 0;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__flag {
	grid-column: 2;
	grid-row: 1;
	justify-self: end;
	align-self: baseline;
	margin: 0;
}

/* Everything below that line takes the whole head. */
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__title,
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__when,
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__projects,
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__footer {
	grid-column: 1 / -1;
}

/*
 * Uneven gaps on purpose. One spacing repeated down the card packs five lines
 * into a list with nothing to hold on to; these group them — the kind of event
 * belongs to the title, the time belongs under it, and the chips and the place
 * are a separate footnote that gets the widest gap of the set.
 *
 * Every vertical gap in the head is set here and nowhere else. Three separate
 * blocks were setting these margins at the same specificity by the time the
 * layout settled, so the last one in the file silently won and edits to the
 * other two did nothing at all. If a gap needs changing, change it here.
 */
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__title {
	margin-top: 0.55rem;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__when {
	margin-top: 0.45rem;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__projects {
	margin-top: 1.45rem;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__footer {
	margin-top: 0.8rem;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__projects,
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__footer {
	display: flex;
}

/*
 * And hidden when they are asked to be. The rule above lays the chips out from
 * four attributes and classes deep; the one that hides them is two deep and
 * lost, so on this display alone the setting did nothing at all — measured,
 * the same two chips painted whether it was on or off. Written here, beside
 * the rule that outranked it, rather than as weight added to the general one:
 * this is the only display that lays these out itself.
 */
[data-layout="spotlight"][data-badges="no"] .npx-events__grid > .npx-card .npx-card__projects {
	display: none;
}

/*
 * No Details button here. The lead card carries the event's own links at the
 * foot instead, and those say where they go — a generic button beside them
 * would be a second, vaguer version of the same offer. The card still opens
 * the preview when clicked anywhere that is not a link.
 */
[data-layout="spotlight"] .npx-card__action {
	display: none;
}

/* The date and the time are two lines here, as everywhere else. */
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__when {
	display: block;
}

/* Spacing for these two lives with the rest of the head's rhythm, above. */
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__projects {
	justify-content: flex-start;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__footer {
	padding-top: 0;
	border-top: 0;
	justify-content: flex-start;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__type {
	font-size: 0.6rem;
}

/*
 * The hero title carries the hierarchy now that the card itself is no longer
 * doing it by sheer size. Left at 1.16rem it sat only 1.2x the queue's title,
 * which is close enough to read as a mistake rather than a decision.
 */
[data-layout="spotlight"] .npx-events__grid > .npx-card h3.npx-card__title {
	/*
	 * No growing. On Cards the title stretches so that every button in a row
	 * lands at the same height; the hero is one card with nothing to line up
	 * against, so all that stretch did was open a hole above its button.
	 */
	flex-grow: 0;
	font-size: clamp(1.02rem, 5cqi, 1.3rem);
}

/*
 * Same reasoning for the blank line the `when` block reserves when an event
 * has no time: it exists to keep a row of cards level, and there is no row.
 */
[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__when:not(:has(.npx-card__time))::after {
	content: none;
}

[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__trigger {
	-webkit-line-clamp: 3;
}

/*
 * The third part of the lead card: what the event is about, across the whole
 * width, under the picture and under the chips alike.
 *
 * It takes the row the grid hands it and clips what will not fit, so a long
 * description fills the space the list beside it leaves and stops there rather
 * than pushing the card past the block. Its own padding, because it is a child
 * of the card rather than of the body and has none to inherit.
 */
.npx-card__summary {
	grid-column: 1 / -1;
	grid-row: 2;
	min-height: 3.2em;
	margin: 0;
	padding: 0.7rem 0.9rem 0.85rem;
	overflow: hidden;
	color: var(--npx-ink-2);
	font-size: 0.82rem;
	line-height: 1.55;
}

/*
 * The flag says why this card is the big one. Accent on a soft accent plate,
 * which is the pair the type labels already use, so it reads as part of the
 * same family rather than as a sticker.
 */
.npx-card__flag {
	align-self: flex-start;
	display: flex;
	align-items: baseline;
	gap: 0.4rem;
	margin: 0 0 0.35rem;
	padding: 0.16rem 0.5rem;
	border-radius: 4px;
	background: var(--npx-accent-soft);
	color: var(--npx-accent-text);
	font-size: 0.58rem;
	font-weight: 800;
	letter-spacing: 0.09em;
	text-transform: uppercase;
}

/*
 * The countdown is the same plate but not the same voice: it is a fact about
 * this one event, where the label beside it is a fact about its position in
 * the list. Lighter and un-capitalised so the two read as label and value
 * rather than as a two-word heading.
 */
.npx-card__flag-when {
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: none;
	opacity: 0.85;
}

.npx-card__flag-when::before {
	content: "·";
	margin-right: 0.4rem;
	opacity: 0.6;
}

/*
 * Covers the lead card's picture and nothing else. The card itself opens
 * nothing, so this is the one click target on it — which is why it is a real
 * button with a name rather than a click handler on the image: a keyboard
 * reaches it, and a screen reader is told what it does.
 *
 * No visible box of its own; the cursor and the focus ring say it is there.
 */
.npx-card__zoom {
	position: absolute;
	inset: 0;
	z-index: 3;
	padding: 0;
	border: 0;
	background: none;
	cursor: zoom-in;
}

.npx-card__zoom:focus-visible {
	outline: 2px solid var(--npx-accent);
	outline-offset: -4px;
}

/*
 * The picture on its own has the dialog to itself, so it may take the height
 * the preview version has to share.
 */
.npx-preview__media--alone .npx-preview__image {
	max-height: 78vh;
}

/*
 * Centred under the text, and the last thing on the card.
 *
 * `z-index` above the trigger that covers the whole card: without it the
 * overlay takes the click and opens the preview instead of following the link,
 * which is the same trap the Details button had to be lifted out of.
 */
.npx-card__links {
	position: relative;
	z-index: 3;
	grid-column: 1 / -1;
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.45rem;
	margin: 0;
	padding: 0 0.9rem 0.9rem;
}

/*
 * One column below the width where a hero and a queue can sit side by side:
 * squeezed narrower than this the hero stops being a hero and the queue stops
 * being readable, and two bad columns are worse than one good one.
 */
@container npx-block (max-width: 620px) {
	[data-layout="spotlight"] .npx-events__grid {
		grid-template-columns: minmax(0, 1fr);
	}

	/*
	 * Too narrow for two columns anywhere, the hero included.
	 *
	 * The placements have to be released as well as the template: a child still
	 * asking for `grid-column: 2` conjures an implicit second column and sits
	 * in it, so the head stayed beside the picture at 560px with the template
	 * saying one column.
	 */
	[data-layout="spotlight"] .npx-events__grid > .npx-card {
		grid-template-columns: minmax(0, 1fr);
		grid-template-rows: none;
	}

	[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__aside,
	[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__main {
		grid-column: auto;
		grid-row: auto;
	}

	/* The flag drops under the type rather than being squeezed beside it. */
	[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__main {
		grid-template-columns: minmax(0, 1fr);
	}

	[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__type,
	[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__flag {
		grid-column: 1;
		grid-row: auto;
		justify-self: start;
	}

	/*
	 * Kind of event first, then the flag. Stacked, auto-placement follows the
	 * markup, which puts the flag on top — the reverse of how the same card
	 * reads when the two share a line.
	 */
	[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__type {
		order: -1;
	}

	[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__flag {
		margin-bottom: 0.35rem;
	}

	[data-layout="spotlight"] .npx-events__grid > .npx-card .npx-card__aside {
		aspect-ratio: 16 / 9;
		height: auto;
		max-height: 200px;
	}

	/*
	 * One column: the hero is above the list, not beside it, so there is no
	 * leftover height to absorb. Four whole lines instead, which is what it
	 * would otherwise be clipped to at random.
	 */
	.npx-card__summary {
		grid-column: 1;
		grid-row: auto;
		display: -webkit-box;
		-webkit-box-orient: vertical;
		-webkit-line-clamp: 4;
		min-height: 0;
		padding-top: 0;
	}


	[data-layout="spotlight"] .npx-events__grid {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* ======================================================== Timeline layout */

/*
 * Rows puts events in order; this puts them in time. The difference is the
 * spine: it grows where nothing is happening, so a quiet summer reads as quiet
 * instead of as the next line down.
 *
 * No new card markup. The date block, the type, the title and the chips are
 * the same ones every view renders — the date moves into a gutter, and the
 * body takes a left border that becomes the spine.
 */
[data-layout|="timeline"] .npx-events__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0;
}

/*
 * The spine is the display. It was drawn in `--npx-line`, which is the hairline
 * used for dividers on a white card — #e2e4e8. Dropped onto the grey the theme
 * uses for a section, that is 1.06:1 against its own ground: not a faint line,
 * an absent one. On a dark section it fared little better at 1.74:1.
 *
 * So it has a value of its own, chosen to hold on all three grounds a block can
 * land on. Measured: 3.1:1 on white, 2.6:1 on the theme's grey, 2.5:1 on the
 * dark section. Enough to follow with the eye, not so much that a column of
 * events reads as a table with a rule down it.
 */
[data-layout|="timeline"],
[data-layout="rail"] {
	--npx-spine: #8e939a;
	--npx-spine-dot: var(--npx-ink-3);
	/*
	 * A quieter line, and a value of its own rather than `--npx-line`. The
	 * hairline the rest of the plugin divides things with measures 1.06:1
	 * against the grey a theme section uses — the note above the default
	 * records it as not a faint line but an absent one, which is exactly why
	 * the default is not that colour. Offering it under a name would have been
	 * shipping the same fault with a label on it. This is 1.9:1 on white and
	 * still there on the grey: quiet, which is what was asked for.
	 */
	--npx-spine-faint: #b9bec4;
	/*
	 * What the line and the dots become as the trail reaches them. The accent
	 * is what this display has always lit up in, and it stays the default;
	 * these exist so it does not have to be the only answer.
	 *
	 * The ring around the dot under the pointer needs its colour a second
	 * time, as channels. It is a halo that fades as it opens, so its keyframes
	 * carry an alpha — and an alpha needs the three numbers, not the colour
	 * they make. The line's glow escapes that by fading the box rather than
	 * the colour, which is why only one of the two is written twice.
	 */
	--npx-spine-lit: var(--npx-accent);
	--npx-spine-dot-lit: var(--npx-accent);
	--npx-dot-ring-rgb: 224, 153, 1;
	/*
	 * How much heavier a lit segment is, as a factor: (2px + 1px) / 2px. Set
	 * here rather than beside the one rule that reads it, because the three
	 * parts of the line that scale are siblings of the card and not children
	 * of it — a property declared on the card would never reach them.
	 */
	--npx-spine-grow: 1.5;
}

[data-scheme="dark"][data-layout|="timeline"],
[data-scheme="dark"][data-layout="rail"] {
	--npx-spine: rgba(255, 255, 255, 0.28);
	--npx-spine-faint: rgba(255, 255, 255, 0.16);
}

/*
 * The four named schemes, written as tokens rather than as colours: each of
 * them already flips on a dark section, so an author who picks one keeps
 * that behaviour, while a custom colour is a fixed value and stops adapting
 * — which is the honest difference between the two and is what the field's
 * own wording says.
 *
 * Keyed on the block's own attribute so they weigh 0-2-0, and placed after
 * the dark-scheme rule above, which weighs 0-2-0 too and would otherwise win
 * on source order. Measured with them the other way round: on a dark section
 * the dots took the scheme and the line did not, because the dark rule only
 * redefines one of the two properties. A plain [data-spine] would be 0-1-0
 * and fail the same way — the worst kind of half-working.
 */
[data-npx-events][data-spine="faint"] {
	--npx-spine: var(--npx-spine-faint);
}

[data-npx-events][data-spine="ink"] {
	--npx-spine: var(--npx-ink-2);
}

[data-npx-events][data-spine="accent"] {
	--npx-spine: var(--npx-accent);
}

[data-npx-events][data-dot="faint"] {
	--npx-spine-dot: var(--npx-spine-faint);
}

[data-npx-events][data-dot="ink"] {
	--npx-spine-dot: var(--npx-ink-2);
}

[data-npx-events][data-dot="accent"] {
	--npx-spine-dot: var(--npx-accent);
}

[data-npx-events][data-spine-hover="ink"] {
	--npx-spine-lit: var(--npx-ink-2);
}

[data-npx-events][data-dot-hover="ink"] {
	--npx-spine-dot-lit: var(--npx-ink-2);
	--npx-dot-ring-rgb: 59, 64, 69;
}

/*
 * Ink flips to near-white on a dark section, and the ring's channels have to
 * follow it there or the halo is drawn in the light-mode ink and disappears
 * into the ground it is meant to stand out from.
 */
[data-scheme="dark"][data-dot-hover="ink"] {
	--npx-dot-ring-rgb: 255, 255, 255;
}

/*
 * A pale hover fill turns one row light for as long as the pointer is on it,
 * and a white spine would disappear into it — the same trade the wording makes
 * two rules further down.
 */
[data-layout|="timeline"][data-hover-ink="dark"] .npx-card:hover {
	--npx-spine: rgba(20, 22, 24, 0.42);
	--npx-spine-dot: #3b4045;
}

[data-layout|="timeline"][data-hover-ink="light"] .npx-card:hover {
	--npx-spine: rgba(255, 255, 255, 0.45);
	--npx-spine-dot: rgba(255, 255, 255, 0.86);
}

[data-layout|="timeline"] .npx-card {
	--npx-card-bg: transparent;
	display: grid;
	grid-template-columns: 4.4rem minmax(0, 1fr);
	align-items: start;
	height: auto;
	padding: 0;
	border: 0;
	border-radius: 0;
	box-shadow: none;
	text-align: left;
}

[data-layout|="timeline"] .npx-card__aside {
	display: none;
}

/* ---- the date, in the gutter -------------------------------------------- */

[data-layout|="timeline"] .npx-card__datebox {
	display: block;
	order: 0;
	margin: 0;
	padding: 0.55rem 0.8rem 0.55rem 0;
	text-align: right;
}

/*
 * The date column had no colour of its own: a dark grey day over a mid grey
 * month, beside a body that is also greys. On a timeline the dates are the
 * axis, not another line of detail, so they take the deep end of the brand
 * amber — the one mixed for text (5.4:1 on white) rather than the bright one
 * used for fills and labels. Related to the type label above the title, and
 * plainly not the same thing.
 */
[data-layout|="timeline"],
[data-layout="rail"] {
	--npx-date-ink: var(--npx-accent-text);
}

[data-scheme="dark"][data-layout|="timeline"],
[data-scheme="dark"][data-layout="rail"] {
	--npx-date-ink: var(--npx-accent-lift);
}

[data-layout|="timeline"] .npx-card__day,
[data-layout="rail"] .npx-card__day {
	display: block;
	color: var(--npx-date-ink);
	font-size: 0.92rem;
	font-weight: 700;
	line-height: 1.2;
	letter-spacing: -0.01em;
	font-variant-numeric: tabular-nums;
}

[data-layout|="timeline"] .npx-card__month,
[data-layout="rail"] .npx-card__month {
	display: block;
	/*
	 * `--npx-ink-4` measured 2.17:1 against the grey a theme section uses,
	 * which is under the floor for any wording however small. This is the
	 * month of the event, not a decoration.
	 */
	color: var(--npx-ink-3);
	font-size: 0.62rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

/* ---- the spine ---------------------------------------------------------- */

/*
 * The border is the spine and the dot is a pseudo-element on the same box, so
 * they cannot drift apart: one element owns both, and the dot is pulled onto
 * the line by exactly half its own width whatever the border does.
 */
[data-layout|="timeline"] .npx-card__main {
	position: relative;
	min-height: 0;
	/*
	 * `--npx-edge` is how far the countdown badge keeps off the right-hand edge
	 * of a card. Every other display gives it that room through the card's own
	 * padding — 13 to 15px, measured — but a Timeline card has none on the right
	 * at all, so the badge finished hard against the edge on two placements out
	 * of four. Named rather than repeated, because the two placements that need
	 * it live a long way apart in this file.
	 */
	--npx-edge: 0.85rem;
	padding: 0.5rem 0 1.1rem 1.3rem;
	border-left: 2px solid var(--npx-spine);
	align-items: flex-start;
	justify-content: flex-start;
	text-align: left;
}

[data-layout|="timeline"] .npx-card__main::before {
	content: "";
	position: absolute;
	top: 0.72rem;
	/*
	 * Offsets here start at the padding box, which the 2px border has already
	 * pushed past the line: the line's centre is 1px back from it, and the dot
	 * has to start half its own width before that. 9 - 2 gives the half-pixel,
	 * and an odd dot on an even line cannot avoid one.
	 */
	left: -5.5px;
	width: 9px;
	height: 9px;
	border-radius: 50%;
	/* Stronger than the line it sits on: the events are the point, the spine
	   is what carries them. 4.0:1 on the theme grey against the spine's 2.6. */
	background: var(--npx-spine-dot);
}

/* ---- the dot the pointer is on ------------------------------------------ */

/*
 * Every other display answers a hover by moving the card. A Timeline cannot:
 * the spine is drawn by the cards, each one contributing the segment of line
 * beside it, so a card that moves takes its piece of the axis with it. Held
 * hovered and measured, Lift opened a 3px hole under the segment, Slide put it
 * 6px to the right of the two it sits between and Grow pulled it 6.9px left —
 * a line that comes apart wherever the pointer goes, which reads as a fault
 * rather than as a flourish. So the setting is not offered here, and the
 * answer is given by the one part of the card that can move without taking the
 * line with it.
 *
 * The dot is already amber by the time the pointer reaches it — the trail has
 * seen to that — so colour cannot be what marks it. Size and a ring can: the
 * ring opens outward once, then settles, which says "here" on arrival without
 * a list of events that pulses at you for as long as you read it.
 *
 * The amber is written out rather than taken from `--npx-accent`, as the halo
 * on the first upcoming event already is: keyframes need the channels, not the
 * colour.
 */
[data-layout|="timeline"] .npx-card:hover .npx-card__main::before {
	transform: scale(1.2);
	animation: npx-dot-wake 0.85s ease-out 1 both;
}

/*
 * The line pulses with the dot, and it is the same pulse: a glow thrown
 * outward that fades as it opens and settles into a halo, on the one segment
 * the pointer is on. Same 0.85s and same easing as the ring, so arriving at
 * an event reads as one gesture rather than as two things happening near
 * each other. The extra pixel of weight is separate and belongs to the whole
 * lit run; this belongs to the event you are on.
 *
 * It needs a box of its own. A glow is a shadow with spread, and a spread
 * shadow surrounds whatever box it is on — put on the body, it would ring
 * the entire card rather than the line down its edge. So the line gets a
 * second, narrow element laid exactly over it, painting nothing of its own
 * and carrying only the glow.
 *
 * The colour comes straight from the property, with no channels needed: what
 * fades here is the box, through `opacity`, rather than the alpha of a
 * colour. The overlay paints nothing but the shadow, so fading it fades only
 * the glow — and that is what lets this follow whatever the line lights in,
 * including a colour an author typed in.
 */
[data-layout|="timeline"] .npx-card:hover .npx-card__main::after {
	content: "";
	position: absolute;
	top: 0;
	bottom: 0;
	left: -2px;
	width: 2px;
	pointer-events: none;
	animation: npx-spine-pulse 0.85s ease-out 1 both;
}

[data-layout="timeline-h"] .npx-card:hover .npx-card__main::after {
	top: -2px;
	right: 0;
	bottom: auto;
	left: 0;
	width: auto;
	height: 2px;
}

@keyframes npx-spine-pulse {
	0% {
		box-shadow: 0 0 0 0 var(--npx-spine-lit);
		opacity: 0.5;
	}

	70% {
		box-shadow: 0 0 0 8px var(--npx-spine-lit);
		opacity: 0;
	}

	100% {
		box-shadow: 0 0 0 3px var(--npx-spine-lit);
		opacity: 0.26;
	}
}

@keyframes npx-dot-wake {
	0% {
		box-shadow: 0 0 0 0 rgba(var(--npx-dot-ring-rgb), 0.5);
	}

	70% {
		box-shadow: 0 0 0 11px rgba(var(--npx-dot-ring-rgb), 0);
	}

	100% {
		box-shadow: 0 0 0 5px rgba(var(--npx-dot-ring-rgb), 0.26);
	}
}

/*
 * The first upcoming event is the one a visitor is looking for, and it is the
 * only card on the page that can be identified without knowing anything about
 * the content — so it gets a halo, and nothing else does.
 *
 * It is drawn in the colour the trail lights in, not in a third colour of its
 * own. Hard-coded to the accent it was the one mark on the display an author
 * could not reach: a block whose line and dots had been recoloured answered
 * with an amber that appeared nowhere in the settings. Both properties default
 * to the accent, so a block that sets nothing looks exactly as it did.
 *
 * Not on a block showing only past events. The leading section is the same
 * element either way, so without this the halo marked the most recent past
 * event as though it were the next one to come.
 */
[data-layout|="timeline"]:not([data-past="only"]) [data-npx-section="upcoming"] .npx-card:first-of-type .npx-card__main::before {
	background: var(--npx-spine-dot-lit);
	box-shadow: 0 0 0 4px rgba(var(--npx-dot-ring-rgb), 0.18);
}

[data-layout|="timeline"] .npx-card__type {
	margin-bottom: 0.2rem;
}

[data-layout|="timeline"] h3.npx-card__title {
	flex-grow: 0;
	font-size: clamp(0.9rem, 4.6cqi, 1.02rem);
}

[data-layout|="timeline"] .npx-card__when {
	display: none;
}

[data-layout|="timeline"] .npx-card__action {
	display: none;
}

[data-layout|="timeline"] .npx-card__projects {
	margin-top: 0.4rem;
	justify-content: flex-start;
}

[data-layout|="timeline"] .npx-card__footer {
	margin-top: 0.3rem;
	padding-top: 0;
	border-top: 0;
	justify-content: flex-start;
}

/* ---- the quiet stretches ------------------------------------------------ */

/*
 * A dotted continuation of the spine, sitting in the same second column, with
 * the label in the gutter beside it. Three heights rather than a length
 * proportional to the gap: proportional turns a two-year archive into a screen
 * of empty line, and the reader only needs to see that this stretch is longer
 * than that one.
 */
.npx-events__gap {
	display: grid;
	grid-template-columns: 4.4rem minmax(0, 1fr);
	align-items: center;
	margin: 0;
}

.npx-events__gap::after {
	content: "";
	grid-column: 2;
	grid-row: 1;
	align-self: stretch;
	width: 2px;
	/*
	 * Flush with the column, not pulled off it. The dashes and the solid line
	 * are the same line, and a `-1px` here put them on different centres —
	 * 70.4 against 71.4 — so the axis stepped sideways by a pixel every time a
	 * quiet stretch began and again where it ended.
	 *
	 * The dashes are a mask over a plain colour, not a gradient of one. Drawn
	 * as a gradient the stretch was the one part of the spine that could not
	 * join the trail: `background-image` does not transition between two
	 * gradients here, so it changed the moment the pointer moved and skipped
	 * its delay with the fade — measured at 26ms, while the segment directly
	 * above it was still 300ms from arriving. Behind a mask the thing that
	 * changes is a colour, which behaves like every other part of the line.
	 */
	background-color: var(--npx-spine);
	-webkit-mask-image: linear-gradient(#000 55%, transparent 0);
	mask-image: linear-gradient(#000 55%, transparent 0);
	-webkit-mask-size: 2px 7px;
	mask-size: 2px 7px;
	-webkit-mask-repeat: repeat-y;
	mask-repeat: repeat-y;
}

.npx-events__gap-label {
	grid-column: 1;
	grid-row: 1;
	padding-right: 0.8rem;
	color: var(--npx-ink-3);
	font-size: 0.6rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.npx-events__gap[data-size="short"] { min-height: 2rem; }
.npx-events__gap[data-size="medium"] { min-height: 3.4rem; }
.npx-events__gap[data-size="long"] { min-height: 5rem; }

/*
 * The spine has to reach the dotted stretch and leave it again, so the card
 * above a gap gives up the padding that would otherwise break the line.
 */
[data-layout|="timeline"] .npx-card:has(+ .npx-events__gap) .npx-card__main {
	padding-bottom: 0.4rem;
}

/* ---- month markers ------------------------------------------------------ */

/*
 * Timeline's month markers are the shared group headings, locked to one axis.
 * They line up with the spine rather than the page so the eye keeps one edge.
 */
[data-layout|="timeline"] .npx-events__group {
	position: relative;
	padding-left: 4.4rem;
	padding-bottom: 0.3rem;
	margin-bottom: 0.5rem;
	/* The same reasoning as the spine: `--npx-line` vanished on a grey page. */
	border-bottom-color: var(--npx-spine);
}

/*
 * The spine is drawn by the events, so wherever there is no event there is no
 * line — and a month marker is not an event. Every one of them punched a hole
 * in the axis: measured at 53px of nothing between the last event of one month
 * and the first of the next, made of the marker's own height and the air on
 * either side of it. A timeline whose line stops at every month is a stack of
 * months, which is the one thing this display exists not to be.
 *
 * So the marker carries its own segment, spanning its box and the margins that
 * separate it from its neighbours, and the axis becomes continuous. It is the
 * marker that reaches out to meet the events rather than the events that close
 * up around it: the spacing around a month name is what makes it read as a
 * heading, and it stays exactly as it was.
 */
[data-layout|="timeline"] .npx-events__group::before {
	content: "";
	position: absolute;
	top: 0;
	/*
	 * Offsets here are measured from the padding box, and the marker's rule is
	 * a 1px border below it — so reaching only as far as the margin leaves the
	 * border's own width unpainted: one pixel of hole at the foot of every
	 * month.
	 */
	bottom: calc(-0.5rem - 1px);
	left: 4.4rem;
	width: 2px;
	background: var(--npx-spine);
}

/*
 * Reaching up as well, for every marker that has something above it. The first
 * one has nothing to join, and the same offset there would hang the line off
 * the top of the list.
 */
[data-layout|="timeline"] .npx-events__group ~ .npx-events__group::before {
	top: -1.2rem;
}

[data-layout|="timeline"] .npx-events__group-name {
	color: var(--npx-ink);
}

[data-layout|="timeline"] .npx-events__group ~ .npx-events__group {
	margin-top: 1.2rem;
}

@container (max-width: 380px) {
	[data-layout|="timeline"] .npx-card,
	.npx-events__gap {
		grid-template-columns: 3.2rem minmax(0, 1fr);
	}

	[data-layout|="timeline"] .npx-events__group {
		padding-left: 3.2rem;
	}

	[data-layout|="timeline"] .npx-events__group::before {
		left: 3.2rem;
	}
}

/* ---- the trail ----------------------------------------------------------- */

/*
 * Hovering an event lights the spine from the top of the list down to it, so
 * the line reads as time already passed rather than as a rule someone drew.
 *
 * The run is marked by the script, one class on each child of the grid up to
 * and including the one under the pointer. `:has(~ :hover)` said the same
 * thing in CSS alone and had to go, for a reason worth writing down.
 *
 * The delay below is read at the instant a colour changes, and both of them
 * come from the pointer — so they have to arrive in the same style
 * recalculation, or the transition starts under the timing that belonged to
 * the previous position. With `:hover` doing the lighting they did not: the
 * hovered state landed first and the script's number a frame later, so every
 * segment took the leftover delay, which was zero. Measured, the whole trail
 * arrived at 193ms — every delay correctly computed, none of them used, and a
 * line that lights in one flat instant however carefully the stagger is
 * worked out.
 *
 * A class and a number written by the same line of script cannot separate.
 *
 * Without a stagger the whole run changes colour in one go, which reads as a
 * state rather than as something travelling — so each child carries its
 * position in `--npx-step`, written by PHP because CSS can find the run but
 * cannot count it.
 *
 * What that position is measured against is the whole of the timing. Against
 * the top of the list it is an absolute index, and the light takes longer to
 * arrive the further down the page you are: stepping from the first event to
 * the second is instant, stepping from the seventh to the eighth waits out
 * seven delays first. Same code, two visibly different speeds, and the motion
 * stops feeling like one line being drawn.
 *
 * So it is measured against the pointer instead. `--npx-at` is the position of
 * the event under the cursor, and the delay is the distance back to it — the
 * light is drawn from the cursor to the start of the list at a fixed speed per
 * event, wherever in the list that happens. Extending the trail by one event
 * costs one event's worth of travel at every point on the page, and a segment
 * the trail has already reached never animates again.
 *
 * `max()` keeps the arithmetic off the far side of the cursor: everything below
 * it unlights together instead of taking a negative delay.
 */
[data-layout|="timeline"] .npx-card__main,
[data-layout|="timeline"] .npx-card__main::before,
[data-layout|="timeline"] .npx-events__gap::after,
[data-layout|="timeline"] .npx-events__group,
[data-layout|="timeline"] .npx-events__group::before,
[data-layout|="timeline"] .npx-events__group-name {
	/* `transform` for the dot the pointer is on; every other dot keeps its
	   own size, so the trail's delay never has anything to hold up here. */
	/*
	 * Every property the trail paints has to be on this list. One left off
	 * does not merely skip the fade — it skips the delay with it and arrives
	 * ahead of the segment above it, which is what a quiet stretch used to do
	 * while it was still drawn as a gradient.
	 */
	transition: border-color 0.22s ease, background-color 0.22s ease, color 0.22s ease, transform 0.2s ease,
		box-shadow 0.22s ease;
	transition-delay: calc(max(0, var(--npx-at, 0) - var(--npx-step, 0)) * 30ms);
}

/*
 * A lit segment is a pixel heavier than an unlit one, and the pixel is added
 * outside the line rather than by widening it. The spine is a border, and a
 * border that grows moves the wording beside it — so this is a shadow flush
 * against the line's outer edge, which paints in the gutter where there is
 * nothing to disturb. Two pixels becomes three, the trail's own transition
 * carries it, and nothing in the card shifts by so much as a subpixel.
 *
 * The dashes and the month segment grow by scaling instead: they are drawn
 * boxes rather than borders, and a scale takes no layout with it either. A
 * scale, specifically, because these two are masked: a shadow outside a masked
 * box is clipped away by the mask, and widening the box without widening the
 * mask tile paints nothing at all, `mask-repeat` tiling in one direction only.
 * Both grow away from the card, so all three parts of the line keep one
 * centre — a stretch that thickened the other way would step sideways
 * wherever a quiet stretch began.
 *
 * The two techniques agree at one width and one only. A shadow adds a pixel;
 * a scale adds half again. They both give three from two — which is the spine
 * this file draws, in five separate places — but a four-pixel spine would read
 * five where a card carries it and six where a quiet stretch does. Hence the
 * factor below being named rather than written into the transform: if the
 * spine's width ever moves, this is the number that has to move with it.
 */
[data-layout|="timeline"] .npx-events__grid > .npx-card.npx-lit .npx-card__main {
	border-color: var(--npx-spine-lit);
	box-shadow: -1px 0 0 0 var(--npx-spine-lit);
}

[data-layout="timeline-h"] .npx-events__grid > .npx-card.npx-lit .npx-card__main {
	box-shadow: 0 -1px 0 0 var(--npx-spine-lit);
}

[data-layout|="timeline"] .npx-events__grid > .npx-card.npx-lit .npx-card__main::before {
	background: var(--npx-spine-dot-lit);
}

[data-layout|="timeline"] .npx-events__grid > .npx-events__gap.npx-lit::after {
	background-color: var(--npx-spine-lit);
	transform: scaleX(var(--npx-spine-grow, 1.5));
	transform-origin: right center;
}

[data-layout="timeline-h"] .npx-events__grid > .npx-events__gap.npx-lit::after {
	transform: scaleY(var(--npx-spine-grow, 1.5));
	transform-origin: center bottom;
}

/*
 * The month markers are on the same trail: passing the third event means
 * passing everything above it, its heading included.
 */
[data-layout|="timeline"] .npx-events__grid > .npx-events__group.npx-lit {
	border-color: var(--npx-spine-lit);
}

[data-layout|="timeline"] .npx-events__grid > .npx-events__group.npx-lit::before {
	background: var(--npx-spine-lit);
	transform: scaleX(var(--npx-spine-grow, 1.5));
	transform-origin: right center;
}

/*
 * Lying down the month marker has no width of its own — the tick is all it
 * is — so there is no segment of line to thicken and nothing to scale. The
 * cards either side of it carry the line through, and they have already
 * done it.
 */
[data-layout="timeline-h"] .npx-events__grid > .npx-events__group.npx-lit::before {
	transform: none;
}

[data-layout|="timeline"] .npx-events__grid > .npx-events__group.npx-lit .npx-events__group-name {
	color: var(--npx-accent-text);
}

[data-scheme="dark"][data-layout|="timeline"] .npx-events__grid > .npx-events__group.npx-lit .npx-events__group-name {
	color: var(--npx-accent-lift);
}

/*
 * A trail that travels is motion. Asked not to move, it still lights up — the
 * information is the extent, not the animation — but it does so at once.
 */
@media (prefers-reduced-motion: reduce) {
	[data-layout|="timeline"] .npx-card__main,
	[data-layout|="timeline"] .npx-card__main::before,
	[data-layout|="timeline"] .npx-events__gap::after,
	[data-layout|="timeline"] .npx-events__group,
	[data-layout|="timeline"] .npx-events__group::before,
	[data-layout|="timeline"] .npx-events__group-name {
		transition: none;
		transition-delay: 0s;
	}
}

/* ================================================== Timeline across ======= */

/*
 * The same display through ninety degrees. Nothing about how it is built
 * changed: every card still owns the piece of spine it sits against, which is
 * what keeps the trail, the waking dot and the halo on the next event working
 * unaltered — they are written against the family above and never mention a
 * direction.
 *
 * Two things had to be rebuilt, and they are the two that were a column and
 * are now a row: the track, and the markers standing between the cards.
 */
[data-layout="timeline-h"] .npx-events__grid {
	display: flex;
	align-items: stretch;
	/*
	 * No gap between the slots. The spine is each card's top border, so a gap
	 * is a break in the line — the vertical display answers this the same way,
	 * with the air coming from padding inside the card instead of from between
	 * the cards.
	 */
	gap: 0;
	overflow-x: auto;
	overflow-y: hidden;
	overscroll-behavior-x: contain;
	/*
	 * No snapping, for the reason the Rail gives where it asks for none: snapping
	 * has the last word over anything the script computes, and there are now two
	 * things computing landings here. The arrows will not step over an event, and
	 * a hover on a card the track has cut closes the gap by exactly the length
	 * that is hidden.
	 *
	 * Measured on the same card both ways: the hover asks for 164px, which is the
	 * shortfall to the pixel, and gets it with snapping off. With snapping on the
	 * browser rounded the landing to the nearest card boundary and moved 656 —
	 * the card ended up whole, having travelled four times as far as it needed
	 * to, which reads as the row jumping rather than closing a gap.
	 *
	 * What snapping was for was a trackpad coming to rest on a card. A press does
	 * that now by arithmetic, and a free scroll that stops mid-card is answered
	 * by the hover on the card it stopped on.
	 */
	scroll-snap-type: none;
	scrollbar-width: none;
	/* Room over the line for the month names, which stand above it. */
	padding-top: 1.35rem;
}

[data-layout="timeline-h"] .npx-events__grid::-webkit-scrollbar {
	display: none;
}

/*
 * How wide a slot is. The column count keeps the meaning it has everywhere
 * else, except that there are no rows to wrap onto — so it says how many
 * events are in view rather than how many fit before the next line.
 */
[data-layout="timeline-h"] .npx-events__grid > * {
	--npx-slot: calc(100% / var(--npx-cols));
	--npx-slot-pad: 1.1rem;
	/*
	 * How far along its slot the dot sits, and — the same number, deliberately —
	 * where the card's wording begins. The two are declared once here because
	 * they are one measurement: the card reads as hanging from its own mark on
	 * the line, rather than as a block of text that happens to start near it.
	 *
	 * It also gives the first card of the row something the others get for free.
	 * Every card's breathing room on the left is the previous card's padding on
	 * the right, and the first one has no previous card — so without this its
	 * wording sits hard against the edge of the track.
	 */
	--npx-dot-in: 0.7rem;
	/*
	 * And how far under the line the wording starts. Its own measurement rather
	 * than a number folded into the paddings below, because it is the one thing
	 * that decides whether a row reads as hanging from the line or as pressed
	 * up against it.
	 */
	--npx-line-gap: 1.15rem;
}

/*
 * How much of the card is picture, and how far the picture hangs below the
 * line. Both are nothing until the row is large enough to carry one, and both
 * are counted into the padding that clears them — so the date sits the same
 * distance under the picture as it sits under the line when there is none.
 */
[data-layout="timeline-h"] {
	--npx-band: 0rem;
	--npx-band-top: 0rem;
}

[data-layout="timeline-h"][data-row-size="large"] {
	--npx-band: 4rem;
	/*
	 * A rem of visible air, which is a rem plus the line's own two pixels: the
	 * picture is laid in the same grid cell as the body, so it measures from the
	 * card's top edge — where the line starts, not where it ends.
	 */
	--npx-band-top: calc(1rem + 2px);
}

/*
 * The card is one column, and the date, the picture and the body are laid over
 * each other in a single cell rather than stacked in three. That is what lets
 * the body own the spine: the border has to be at the very top of the card,
 * above the picture and above the date, and a box that is third in the stack
 * cannot draw a line above the two before it.
 */
[data-layout="timeline-h"] .npx-card {
	flex: 0 0 var(--npx-slot);
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	grid-template-rows: minmax(0, 1fr);
	height: auto;
	overflow: visible;
	scroll-snap-align: start;
}

[data-layout="timeline-h"] .npx-card__datebox,
[data-layout="timeline-h"] .npx-card__aside,
[data-layout="timeline-h"] .npx-card__main {
	grid-area: 1 / 1;
}

[data-layout="timeline-h"] .npx-card__datebox {
	align-self: start;
	padding: calc(var(--npx-line-gap) + var(--npx-band-top) + var(--npx-band)) var(--npx-slot-pad) 0 var(--npx-dot-in);
	text-align: left;
}

/*
 * The padding on the left does not move the line or the dot with it: the border
 * spans the border box, and an absolutely positioned child measures from the
 * padding box's edge, which padding sits inside rather than displaces. So the
 * line still runs the full width of the slot and the dot stays on its mark.
 */
[data-layout="timeline-h"] .npx-card__main {
	border-left: 0;
	border-top: 2px solid var(--npx-spine);
	/*
	 * The date block is 2.05rem of that: the body starts below it, and both are
	 * measured from the same gap so they move together if it changes.
	 */
	padding: calc(var(--npx-line-gap) + 2.05rem + var(--npx-band-top) + var(--npx-band)) var(--npx-slot-pad) 1rem var(--npx-dot-in);
}

/*
 * Half the dot's width back from the line's centre, exactly as the vertical
 * display does it — with top and left exchanged, because the line is now the
 * top border and the offsets are measured from a padding box the border has
 * already pushed past.
 */
[data-layout="timeline-h"] .npx-card__main::before {
	top: -5.5px;
	left: var(--npx-dot-in);
}

/*
 * The vertical display closes the air under a card that precedes a quiet
 * stretch, so the line reaches the dashes. Lying down the line is a border and
 * runs the whole width of the slot whatever the padding does, so the card
 * keeps its foot.
 */
[data-layout="timeline-h"] .npx-card:has(+ .npx-events__gap) .npx-card__main {
	padding-bottom: 1rem;
}

/* ---- how tall a row is, and what fills it ------------------------------- */

/*
 * Height on its own is air, so each step adds something to the card and the
 * height follows it. A minimum rather than a height: a row of one-line titles
 * should still be the size the author chose, and one with a long title is
 * allowed to be taller than it.
 */
[data-layout="timeline-h"][data-row-size="small"] .npx-card {
	min-height: 10rem;
}

[data-layout="timeline-h"][data-row-size="medium"] .npx-card {
	min-height: 14rem;
}

[data-layout="timeline-h"][data-row-size="large"] .npx-card {
	min-height: 17.5rem;
}

/*
 * The parts of a card body are ordered rather than stacked in markup order —
 * type 1, title 2, the date line 3, the buttons and chips after — so a new
 * one without a number of its own goes to the front of the queue. Measured:
 * the summary rendered above the type and over the picture.
 */
[data-layout="timeline-h"] .npx-card__summary--row {
	order: 3;
	display: none;
	/*
	 * The lead card's summary is a band with padding of its own; this one is a
	 * line of the body and has none. Left inherited, that padding sat inside
	 * the height below — which is measured in whole lines and so had 24.8px
	 * less to give than it thought. Measured: the third line arrived cut in
	 * half with the chips over the top of it.
	 */
	min-height: 0;
	margin: 0.45rem 0 0;
	padding: 0;
	color: var(--npx-ink-3);
	font-size: 0.78rem;
	line-height: 1.45;
}

/*
 * Cut to whole lines rather than clamped. `-webkit-line-clamp` needs the box to
 * be a `-webkit-box`, and the card body is a flex container — a flex item is
 * blockified whatever its display says, so the clamp was computing to
 * `flow-root` and doing nothing. Measured: four lines where two were asked for.
 *
 * A height in line multiples cuts on a line boundary instead, which is the only
 * part of the clamp that mattered here: the wording is already ellipsised by
 * the renderer when it is longer than a summary should be.
 */
[data-layout="timeline-h"][data-row-size="medium"] .npx-card__summary--row,
[data-layout="timeline-h"][data-row-size="large"] .npx-card__summary--row {
	display: block;
	max-height: calc(var(--npx-sum-lines, 3) * 1.45 * 0.78rem);
	overflow: hidden;
}

/* At the large size the picture has taken a line of it, which is the trade. */
[data-layout="timeline-h"][data-row-size="large"] .npx-card__summary--row {
	--npx-sum-lines: 2;
}

/*
 * The picture, at the large size only, as a band across the top of the card
 * under the line. It keeps clear of the slot's right-hand padding so that two
 * pictures side by side are two pictures and not one strip.
 */
/*
 * `width: auto`, and it has to be said: the card's picture well is `width: 100%`
 * everywhere else, and a percentage width is not reduced by margins — it is the
 * full width of the cell and the margins push the box out of it. Measured with
 * the width left inherited: the picture ran 11.2px past its own card, which is
 * exactly the indent on its left, and every picture in the row overlapped the
 * one after it.
 *
 * The gap above is measured from the dot rather than from the line. The dot
 * hangs 3.5px below the line it sits on, so a band flush under the line is
 * clipped by it; six pixels leaves the mark whole and puts a hairline of the
 * card's own ground between the line and the picture, which reads better than
 * a photograph butted against the axis.
 */
[data-layout="timeline-h"][data-row-size="large"] .npx-card__aside {
	display: block;
	align-self: start;
	/*
	 * And the ratio has to go with the width. The picture well is 2:1 elsewhere,
	 * which decided the width the moment it stopped being a percentage and the
	 * height stayed fixed — measured, the band came out 128px, twice its 64px
	 * height, instead of filling the card. Here the band is as wide as the card
	 * and as tall as the row size says.
	 */
	width: auto;
	aspect-ratio: auto;
	height: var(--npx-band);
	/*
	 * The full width of the slot, unlike the wording under it. A picture that
	 * stops where the text starts leaves two ragged edges per card and reads as
	 * a thumbnail someone forgot to size; run to both edges it reads as the
	 * card's own top, and a row of them reads as a strip.
	 */
	margin: var(--npx-band-top) 0 0;
	overflow: hidden;
}

[data-layout="timeline-h"] .npx-card__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

[data-layout="timeline-h"] .npx-card__cartouche {
	display: none;
}

/* ---- the month, as a marker between two cards --------------------------- */

/*
 * Narrow on purpose. Given a slot of its own it would be as wide as a card —
 * a quarter of the window spent on the word "October" — so it takes just
 * enough width to carry the tick, and its name is hung over the line where
 * there is nothing else.
 */
[data-layout="timeline-h"] .npx-events__group {
	position: relative;
	/*
	 * No width at all. Given a slot of its own the marker pushed every card
	 * along by its 2.4rem — which at one column across left the first card
	 * 38px off the right-hand edge before anything had been scrolled. With
	 * no width the tick sits on the seam between the two cards, which is
	 * where the month begins anyway, and its name hangs over the band above
	 * them where there is nothing to collide with.
	 */
	flex: 0 0 0;
	width: 0;
	align-self: stretch;
	display: block;
	margin: 0;
	padding: 0;
	border: 0;
	border-top: 2px solid var(--npx-spine);
	/*
	 * Never a snap point. The arrows should always come to rest on an event,
	 * because the row is read from its left-hand edge — landing on a marker
	 * would open the window on the word "October" and the first card half off
	 * the far side of it.
	 */
	scroll-snap-align: none;
}

[data-layout="timeline-h"] .npx-events__group::before,
[data-layout="timeline-h"] .npx-events__group ~ .npx-events__group::before {
	top: -6px;
	right: auto;
	bottom: auto;
	left: 50%;
	width: 1px;
	height: 15px;
}

[data-layout="timeline-h"] .npx-events__group ~ .npx-events__group {
	margin-top: 0;
}

/*
 * Measured: at 0.3rem the name's box cleared the line by 2.8px, which with the
 * descender space inside it read as the line going through the wording. The
 * band above the cards is 1.35rem and the name is 13.4px of it, so there is
 * room to stand it properly clear.
 */
[data-layout="timeline-h"] .npx-events__group-name {
	position: absolute;
	bottom: calc(100% + 0.5rem);
	left: 50%;
	transform: translateX(-50%);
	white-space: nowrap;
	font-size: 0.6rem;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/*
 * Mandatory snapping means the track must always come to rest on a snap point,
 * and a marker is not one — so with the row opening on a month, the browser
 * scrolled straight past it to the first card and the run began in October.
 * Measured: the track sat 38.4px in, and "Sep 2026" was never on screen.
 *
 * Whatever opens the row is therefore a resting place too. It costs nothing:
 * every other stop is still a card, so an arrow press lands on an event and
 * the only extra position is the very beginning of the list.
 */
[data-layout="timeline-h"] .npx-events__grid > :first-child {
	scroll-snap-align: start;
}

/*
 * And the first marker's name is hung to the right of its tick rather than
 * across it, since there is nothing to its left but the edge of the track.
 */
[data-layout="timeline-h"] .npx-events__grid > .npx-events__group:first-child .npx-events__group-name {
	left: 0;
	transform: none;
}

/* Not enough room beside a name that is already abbreviated. */
[data-layout="timeline-h"] .npx-events__group-count {
	display: none;
}

/* ---- the quiet stretches, as a narrower slot ---------------------------- */

/*
 * A third of a card, or a quarter, or a half. Not a measurement — the label
 * says the number of days and the width only says "longer than that one" —
 * but a stretch that is worth marking is worth seeing, and a marker as wide
 * as an event would read as an event.
 */
[data-layout="timeline-h"] .npx-events__gap {
	position: relative;
	display: block;
	grid-template-columns: none;
	align-self: stretch;
	min-height: 0;
	margin: 0;
	scroll-snap-align: none;
}

[data-layout="timeline-h"] .npx-events__gap[data-size="short"] {
	flex: 0 0 calc(var(--npx-slot) / 4);
}

[data-layout="timeline-h"] .npx-events__gap[data-size="medium"] {
	flex: 0 0 calc(var(--npx-slot) / 3);
}

[data-layout="timeline-h"] .npx-events__gap[data-size="long"] {
	flex: 0 0 calc(var(--npx-slot) / 2);
}

/*
 * The dashes, turned. Still a mask over a plain colour and not a gradient, for
 * the reason the vertical display gives: a gradient cannot transition, so the
 * stretch would be the one part of the spine that arrived ahead of the trail
 * instead of taking its turn in it.
 */
[data-layout="timeline-h"] .npx-events__gap::after {
	position: absolute;
	top: 0;
	right: 0;
	left: 0;
	width: auto;
	height: 2px;
	align-self: auto;
	-webkit-mask-image: linear-gradient(90deg, #000 55%, transparent 0);
	mask-image: linear-gradient(90deg, #000 55%, transparent 0);
	-webkit-mask-size: 7px 2px;
	mask-size: 7px 2px;
	-webkit-mask-repeat: repeat-x;
	mask-repeat: repeat-x;
}

[data-layout="timeline-h"] .npx-events__gap-label {
	display: block;
	padding: 1.5rem 0.2rem 0;
	text-align: center;
	line-height: 1.25;
}

/* ================================================================ the Rail = */

/*
 * No cards. The events are dots on one line and the line is the display, which
 * is what lets it be exactly to scale: nothing on it needs room to be legible,
 * so six days apart can be six days apart.
 *
 * It is still the same markup as every other display — the same article, the
 * same preview trigger, the same data attributes — so the filters, the count
 * and the modal work here without knowing this display exists. Everything the
 * card would show is hidden, everything left is positioned, and the position
 * is the meaning.
 */
[data-layout="rail"] {
	/*
	 * How much room a dot needs on either side of it for its readout — the
	 * date and the countdown — to be read without the readout having to leave
	 * the mark it names. The badge is centred on the dot, so this is half its
	 * width and a little air: measured across every event on the site and on
	 * the test rails the widest is 166px, "9–11 Dec 2026  in 3 months", and a
	 * hundred and four covers half of that with room for a longer wording in
	 * another language.
	 *
	 * In pixels, and it has to stay in pixels: the carousel reads it straight
	 * off the computed style to work out where an arrow may land, and a custom
	 * property is handed back the way it was written.
	 */
	--npx-rail-room: 104px;

	/*
	 * The empty run before the first event and after the last, which is that
	 * same room: at either end of the travel the outermost dot is as far in as
	 * its readout needs, so the readout is whole and still on its mark.
	 */
	--npx-rail-lead: var(--npx-rail-room);
	--npx-rail-tail: var(--npx-rail-room);
	/*
	 * Four kinds of wording share this line and none of them may collide, so
	 * the block is laid out as five bands rather than by eye: names above,
	 * then the month scale, then the line, then the quiet stretches, then the
	 * names below. The two offsets are what separate them.
	 */
	/*
	 * The chip that names the day under the pointer. Stated as its own pair
	 * rather than borrowed from the ink token, because it needs a background
	 * and a legible text colour together, and on a dark section both change.
	 */
	--npx-rail-chip: #141618;
	--npx-rail-chip-ink: #ffffff;

	/*
	 * Far enough above the line that a label's fill clears the readout badge,
	 * which rides just under the labels: 50.4px of gap, less 10px of fill,
	 * leaves seven clear of the badge's top edge.
	 */
	--npx-rail-up: 3.15rem;
	/*
	 * A little more below than above, because below is where the fill runs out
	 * of room: the dot hangs 8.5px under the line and a label under the pointer
	 * now carries ten pixels of fill beyond its own edge, so the two met at the
	 * shipped value and the leader between them all but disappeared.
	 */
	--npx-rail-down: 1.85rem;
	/*
	 * Where the line sits inside the block, and how much block there is.
	 *
	 * The two move together. Above the line goes a date and a title of up to two
	 * lines, which measured 52px against the 48px the first value used to leave
	 * — so the date, which is the outermost thing on that side, lost its top
	 * four pixels to the box. Both numbers went up by the same half a rem, which
	 * buys the room above without taking any from the names below.
	 */
	/*
	 * And low enough that the fill above it is inside the block. The reservation
	 * has to hold the tallest label, which is a date over a two-line title — 52px,
	 * not the 37px a one-line title measures — plus the fill beyond it. Sized for
	 * the short kind, the two-line labels had their fill cut off at the top.
	 */
	--npx-rail-mid: 7.6rem;
	/*
	 * Tall enough for a label under the pointer, not merely for the label.
	 * `overflow-y` on the track is `hidden` and cannot be anything else — a
	 * container that scrolls in one axis clips in the other — so anything the
	 * fill reaches past this is simply cut off, which is what was happening to
	 * every label below the line.
	 */
	--npx-rail-height: 13.75rem;
	--npx-rail-label: 10.5rem;
}

/* On a dark section the chip turns over, so it stays the readable one. */
[data-scheme="dark"][data-layout="rail"] {
	--npx-rail-chip: #ffffff;
	--npx-rail-chip-ink: #141618;
}

/*
 * The arrows cap the ends of the line, at the line's own level.
 *
 * Everywhere else these float half over the first and last card. Here there are
 * no cards to float over — the track is a line with dots on it — so instead of
 * floating over the drawing they stand at either end of it, and the drawing
 * runs between them.
 *
 * That is done by insetting the track, not by shortening the line. The arrows
 * belong to the shell and the line to the track scrolling inside it: a shorter
 * line would clear them at one scroll position and slide back under them at the
 * next. Given a margin the width of a button at each end, the track simply
 * never reaches them.
 *
 * The container query that works out the scale is on the track, so the drawing
 * refits itself to the narrower run without being told.
 */
[data-layout="rail"][data-arrows="sides"] .npx-carousel {
	--npx-rail-arm: calc(var(--npx-arrow) * 1.2 + 0.5rem);

	padding-inline: var(--npx-rail-arm);
}

/*
 * On the line, and at the two ends of the shell rather than hung outside it.
 * `--npx-rail-mid` is where the line sits; the nav keeps its own
 * `translateY(-50%)`, so this is a centre and not an edge.
 */
[data-layout="rail"][data-arrows="sides"] .npx-carousel__nav {
	top: calc(var(--npx-rail-mid) + 1px);
	right: 0;
	left: 0;
}

/*
 * A container, so the scale can be worked out in the stylesheet rather than
 * measured in the browser. One day is a share of the visible width, and the
 * two zooms differ only in what that width is divided by — the whole period,
 * or the number of days the author asked to see at once.
 */
[data-layout="rail"] .npx-events__grid {
	position: relative;
	display: block;
	container-type: inline-size;
	height: var(--npx-rail-height);
	/*
	 * How far past its own border box a card paints, and so how far an arrow
	 * has to keep it from the edge of the track. Two things set it, neither of
	 * them in `offsetWidth`:
	 *
	 *   the dot, which straddles the anchor rather than sitting inside the box
	 *   — nine pixels of paint in a seventeen-pixel target, grown a fifth under
	 *   the pointer, so 10.2px past the box at its largest;
	 *
	 *   the hover fill, a ten-pixel ring spread outside the box.
	 *
	 * Fourteen: enough for the larger of those with a little air, so that a dot
	 * comes to rest near the edge rather than against it. The script reads this
	 * rather than carrying its own copy, and measures the dot itself on top, so
	 * a bigger mark cannot silently outgrow the reservation.
	 *
	 * In pixels, and it has to stay in pixels: the carousel reads it straight
	 * off the computed style, and a custom property is handed back the way it
	 * was written, so "0.875rem" here would reach the arrows as the number
	 * nought-point-eight-seven-five.
	 */
	--npx-rail-guard: 14px;
	gap: 0;
	overflow-x: auto;
	overflow-y: hidden;
	overscroll-behavior-x: contain;
	scroll-snap-type: none;
	scrollbar-width: none;
}

/*
 * And it means it, over a carousel that says otherwise. The rule above asks
 * for no snapping and has never once got it: the generic horizontal-carousel
 * rule carries one attribute more and so outranks it, which left the Rail
 * coming to rest on card edges like any list of cards.
 *
 * It is worth being plain about why that is wrong here. A card's left edge is
 * a fine place for a row of cards to stop. On a display whose whole argument
 * is that it is drawn to scale, the only meaningful position is a date, and
 * being pulled to an edge contradicts the drawing. It also undid the work of
 * landing on whole labels: a press aimed at a position measured clean was
 * pulled ninety-three pixels back onto one where a name sat half off the far
 * side, because aligning a card with the left of the track says nothing about
 * what straddles the right.
 */
[data-layout="rail"][data-carousel="yes"][data-carousel-direction="horizontal"] .npx-events__grid {
	scroll-snap-type: none;
}

/*
 * And Timeline Across, for the same reason and by the same arithmetic.
 *
 * Asking for no snapping in that display's own block is not enough: the rule
 * there carries one attribute and a class, the carousel's carries two and a
 * class, and the carousel wins. Measured with the carousel on, a hover meaning
 * to close a 164px gap moved 656 because the browser rounded the landing to a
 * card edge; with the carousel off, the same hover moved 164.
 *
 * Written beside the Rail's rather than beside the display's own rules, because
 * it is the same decision about the same conflict and the two should be read
 * together.
 */
[data-layout="timeline-h"][data-carousel="yes"][data-carousel-direction="horizontal"] .npx-events__grid {
	scroll-snap-type: none;
}

[data-layout="rail"][data-carousel="yes"] .npx-events__grid > * {
	scroll-snap-align: none;
}

[data-layout="rail"] .npx-events__grid::-webkit-scrollbar {
	display: none;
}

/*
 * The Rail's own gap under the block.
 *
 * The shared 2.5rem is measured from the bottom of the box, and this box is a
 * reservation rather than content: it is now tall enough to hold the fill a
 * label takes under the pointer, which leaves a stretch of it empty whenever
 * nothing is being pointed at. Measured from the wording — which is what a
 * reader sees the display end at — the shared value would put the button half
 * as far again below the Rail as below every other display. This is the same
 * distance as everywhere else, counted from the ink instead of from the edge.
 */
[data-layout="rail"] .npx-events__section + .npx-events__past-switch {
	margin-top: 1.5rem;
}

/*
 * Nothing below the line, so no room kept for it.
 *
 * The height above is fixed because it has to be: every event on the Rail is
 * positioned by its date, so none of them weighs anything in the flow and the
 * box cannot take its size from what it holds. Ten rem is the room a name and
 * its date need under the line — correct whenever one is drawn there, and dead
 * air whenever none is. Sides alternate from the first event, so "none" means
 * a section holding exactly one; a rail of one event was reserving seventy-five
 * pixels for a label it does not have, and everything under it — the button
 * that opens the past events, the rest of the page — was pushed down by that
 * much.
 *
 * Asked of the line's own side rather than of the number of events, so it stays
 * true whatever else changes about how they are placed.
 */
[data-layout="rail"] .npx-events__grid:not(:has(.npx-card[data-npx-side="below"])) {
	height: calc(var(--npx-rail-mid) + 0.85rem);
}

[data-layout="rail"] .npx-events__grid > * {
	--npx-pxd: calc(
		(100cqw - var(--npx-rail-lead) - var(--npx-rail-tail)) / var(--npx-rail-days, 1)
	);
	--npx-x: calc(var(--npx-rail-lead) + var(--npx-at, 0) * var(--npx-pxd));
}

/*
 * The axis is a real element and stays in the flow, which is the whole reason
 * it is one: every event on the Rail is positioned rather than laid out, so
 * without something of the track's own width in the flow the scroll container
 * would have nothing to scroll and a fixed zoom would simply crop the year.
 */
/* ---- the pointer, reading the axis -------------------------------------- */

/*
 * Two answers to the same movement. The line lights as far as the pointer,
 * which says how much has been crossed as a length; and a chip names the exact
 * day, which says it as a date. A Rail is drawn to scale
 * and that is the whole of its argument — but a scale with nothing on it
 * between the events is a scale a reader has to take on trust. This is how the
 * empty stretches become readable too.
 *
 * The lit colour is the one the other two timelines use for their trail, so a
 * reader who has met one has met all three.
 */
[data-layout="rail"] .npx-events__axis::after {
	content: '';
	position: absolute;
	top: 0;
	/*
	 * From the start of the line, not from the first event. Measuring elapsed
	 * time from the first event is the truer reading, and it is not what a
	 * reader sees: they see one line, and a line whose first stretch never
	 * lights looks broken rather than precise. The run before the first event
	 * grew to make room for that event's readout, which made the unlit head
	 * twice as long and the fault twice as plain.
	 */
	left: 0;
	height: 100%;
	width: max(0px, var(--npx-rail-cursor, 0px));
	background: var(--npx-spine-lit);
	/*
	 * Heavier as well as brighter, by the same factor and to the same three
	 * pixels from two as both Timelines: a trail that only changes colour is a
	 * weaker mark than one that also changes weight, and a reader who has met
	 * one of the three displays should recognise this.
	 *
	 * From the bottom edge, so the extra pixel goes above the line as it does
	 * there, and so the growth lands on whole pixels — centred it would fall on
	 * halves and come up soft at both edges.
	 */
	scale: 1 var(--npx-spine-grow, 1.5);
	transform-origin: center bottom;
	opacity: 0;
	transition: opacity 0.12s ease;
}

[data-layout="rail"] .npx-events__grid[data-npx-cursor] .npx-events__axis::after {
	opacity: 1;
}

/*
 * A dot on the line rather than a rule through the block. A full-height
 * hairline crossed the names above and below to say something about neither of
 * them; the mark belongs on the axis, which is the thing being read. It takes
 * no pointer events of its own, so the events under it still answer a hover
 * and a click.
 */
.npx-rail-cursor {
	position: absolute;
	top: calc(var(--npx-rail-mid) + 1px);
	left: var(--npx-rail-cursor, 0px);
	z-index: 5;
	width: 9px;
	height: 9px;
	margin: -4.5px 0 0 -4.5px;
	border-radius: 50%;
	/*
	 * The dot's colour and not the line's. They are two settings and an author
	 * may well have parted them — the shipped pair does — and this mark is a
	 * dot that comes to rest around other dots, so it is theirs it should
	 * match. The line keeps `--npx-spine-lit` for the span it fills behind.
	 */
	background: var(--npx-spine-dot-lit);
	opacity: 0;
	pointer-events: none;
	transition: opacity 0.12s ease, width 0.12s ease, height 0.12s ease,
		margin 0.12s ease, background 0.12s ease;
}

/*
 * The pulse. Slow and faint, because it is a position and not an alarm — and
 * it is the only moving thing on a display whose whole argument is that it
 * holds still and is to scale.
 */
.npx-rail-cursor::before {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: var(--npx-spine-dot-lit);
	animation: npx-rail-pulse 1.7s ease-out infinite;
}

@keyframes npx-rail-pulse {
	0% {
		transform: scale(0.8);
		opacity: 0.4;
	}

	70% {
		transform: scale(2.6);
		opacity: 0;
	}

	100% {
		transform: scale(2.6);
		opacity: 0;
	}
}

/*
 * On an event, the dot opens into a ring around the event's own mark instead
 * of sitting on top of it. Nineteen pixels to the dot's seventeen, so the ring
 * reads as an answer to that dot rather than as a second one.
 */
[data-npx-snapped] .npx-rail-cursor {
	width: 19px;
	height: 19px;
	margin: -9.5px 0 0 -9.5px;
	background: transparent;
	border: 2px solid var(--npx-spine-dot-lit);
}

[data-npx-cursor] .npx-rail-cursor {
	opacity: 1;
}

/*
 * Just above the line, in the band the month scale uses. Low enough to clear
 * the names above — they start 2.2rem up and this ends well under that — so
 * the only thing it ever covers is a month tick, which is the coarser way of
 * saying what the chip is saying exactly.
 */
.npx-rail-cursor__chip {
	position: absolute;
	/*
	 * Measured from the dot's own centre — `bottom: 50%` lands there whatever
	 * the dot's size — so the chip does not move when the dot opens into a ring.
	 */
	bottom: calc(50% + 0.8rem);
	left: 50%;
	/*
	 * Centred on the dot, then nudged back in by however much that would have
	 * hung it off the end of the track. The nudge is worked out where the width
	 * is known, which is in the script.
	 */
	transform: translateX(calc(-50% + var(--npx-chip-shift, 0px)));
	padding: 0.1rem 0.4rem;
	border-radius: 3px;
	background: var(--npx-rail-chip);
	color: var(--npx-rail-chip-ink);
	/*
	 * Never wider than the track it is read against. The grid is a query
	 * container, so `cqw` is the width on screen rather than the width of the
	 * drawing — which is the one that matters, and the one a scrolled Rail
	 * makes different. Without this a badge wider than a narrow column could
	 * not be placed anywhere without hanging out of the scroll box, and a
	 * badge hanging out of the scroll box makes a fitted Rail scroll.
	 *
	 * What gives way is the tail. The date is the answer and the countdown
	 * repeats what the date already implies, so the date is pinned at its
	 * natural width and the badge beside it is the part that runs out of room.
	 */
	max-width: calc(100cqw - 2 * var(--npx-rail-guard, 14px));
	overflow: hidden;
	display: flex;
	align-items: center;
	gap: 0.35rem;
	font-size: 0.6rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	line-height: 1.55;
	white-space: nowrap;
	transition: background 0.12s ease, color 0.12s ease;
}

/*
 * On an event the readout drops its ground and the date takes the accent, so
 * the state is read before the words are: away from an event it is a chip you
 * notice, on one it is a caption belonging to the mark under it.
 *
 * `--npx-accent-text` and not the brand amber. The amber is a fill and a rule
 * colour — it measures 2.4:1 on white and would be unreadable as wording — and
 * this token is the one the plugin keeps for accent-coloured type: 5.4:1 on
 * white, 4.7:1 on the grey card. It turns into the brand amber on a dark
 * section, where that is the readable one.
 */
[data-npx-snapped] .npx-rail-cursor__chip {
	background: none;
	color: var(--npx-accent-text);
	box-shadow: none;
	padding-left: 0;
	padding-right: 0;
}

/*
 * The countdown is the card's own badge, not a copy of it — same class, so it
 * takes every rule the other displays give it, including the ones for a dark
 * section, and cannot drift from them. The Rail hides it on its labels, where
 * there is no room; here there is.
 */
/*
 * The month scale steps back while a date is being read.
 *
 * The readout sits in the band the scale occupies, and with no ground of its
 * own it has nothing to mask with — the opening month tick and the first
 * event's date land in the same place and print over each other. Dimming
 * rather than moving because the two are saying the same thing at two
 * coarsenesses: while the exact day is on screen, the month it falls in is the
 * one thing on the Rail the reader does not need told.
 */
[data-layout="rail"] .npx-events__group {
	transition: opacity 0.12s ease;
}

[data-layout="rail"] .npx-events__grid[data-npx-snapped] .npx-events__group {
	opacity: 0.2;
}

@media (prefers-reduced-motion: reduce) {
	[data-layout="rail"] .npx-events__group {
		transition: none;
	}
}

.npx-rail-cursor__when {
	flex: 0 0 auto;
}

[data-layout="rail"] .npx-rail-cursor__chip .npx-card__countdown {
	flex: 0 1 auto;
	min-width: 0;
	display: inline-block;
	/*
	 * On a card the badge waits for the pointer to reach the card. Here the
	 * readout only exists while the pointer is on the line, so the waiting is
	 * already done — without this it keeps the nought it starts at and paints
	 * an empty gap the width of its own wording.
	 */
	opacity: 1;
}

.npx-rail-cursor__chip:not([data-npx-has-countdown]) .npx-card__countdown {
	display: none;
}

@media (prefers-reduced-motion: reduce) {
	[data-layout="rail"] .npx-events__axis::after,
	.npx-rail-cursor {
		transition: none;
	}

	/* The dot still marks the place; it just stops breathing. */
	.npx-rail-cursor::before {
		animation: none;
		opacity: 0.3;
	}
}

[data-layout="rail"] .npx-events__axis {
	position: relative;
	display: block;
	width: calc(
		var(--npx-rail-lead) + var(--npx-rail-span, 1) * var(--npx-pxd) + var(--npx-rail-tail)
	);
	height: 2px;
	/*
	 * Carried down to the line's height, not margined down to it.
	 *
	 * This is the first child of its grid, and the theme has a rule for first
	 * children — `.main-container .row-container .row-parent .uncont > … {
	 * margin-top: 0 !important }` — which reaches it on some blocks and not
	 * others, depending where the block sits in the page. Where it reached, the
	 * line was drawn across the top of the block with its own dots a hundred and
	 * twenty pixels below it.
	 *
	 * The element stays in the flow, which is the whole reason it exists: it is
	 * what gives the scroll container a width to scroll. A vertical translate
	 * moves the paint and leaves that width alone.
	 *
	 * `translate` and not `transform`, so that anything wanting a transform here
	 * later composes with this rather than replacing it.
	 */
	translate: 0 var(--npx-rail-mid);
	background: var(--npx-spine);
}

/* ---- an event, as a point with a name ----------------------------------- */

[data-layout="rail"] .npx-card {
	position: absolute;
	left: var(--npx-x);
	width: var(--npx-rail-label);
	height: auto;
	min-width: 0;
	padding: 0;
	border: 0;
	background: none;
	box-shadow: none;
	overflow: visible;
	display: flex;
	gap: 0.05rem;
	text-align: left;
}

/*
 * Above the line, then below it, then above it again. Two neighbours only have
 * to clear each other when they share a side, which doubles the room a label
 * has without touching the scale — and the scale is the one thing on this
 * display that cannot be traded.
 *
 * The title is the part nearest the line on both sides, so the eye runs dot,
 * name, date outwards whichever way it is going. That is what `column-reverse`
 * is for: the markup order is date then title, and below the line it is read
 * the other way round.
 */
[data-layout="rail"] .npx-card[data-npx-side="above"] {
	bottom: calc(100% - var(--npx-rail-mid) + var(--npx-rail-up));
	flex-direction: column;
	justify-content: flex-end;
}

[data-layout="rail"] .npx-card[data-npx-side="below"] {
	top: calc(var(--npx-rail-mid) + var(--npx-rail-down));
	flex-direction: column-reverse;
	justify-content: flex-end;
}

/*
 * The last event has the tail of the track to its right and nothing else, so
 * its label is hung the other way rather than off the end of the line.
 *
 * Held by a margin and not by a transform, because a transform is exactly what
 * a hover motion wants too, and there is only one of them per element. The
 * card_motion rules are `[data-card-motion="…"] .npx-card:not(.npx-card--lead):hover`
 * — a class heavier than this — so with a motion set, hovering the last event
 * replaced this anchor with the motion's own six-pixel step and threw the label
 * a full label-width to the right, past the end of the track, where the scroll
 * container cut it in half. A margin cannot collide with a transform: the
 * motion now moves the label from where it is hung instead of from where it
 * would have been. The card is absolutely positioned, so the negative margin
 * shifts it and nothing else.
 */
[data-layout="rail"] .npx-card[data-npx-edge="end"] {
	margin-left: calc(-1 * var(--npx-rail-label));
	text-align: right;
}

/* The dot, on the line, and the leader that reaches it. */
[data-layout="rail"] .npx-card::before,
[data-layout="rail"] .npx-card::after {
	content: "";
	position: absolute;
	pointer-events: none;
}

[data-layout="rail"] .npx-card::before {
	left: -8.5px;
	/*
	 * Seventeen, not nine. `box-sizing` is border-box here, so a width of nine
	 * with a four-pixel border on each side leaves one pixel of paint —
	 * measured, and the dots vanished. The border is transparent and clipped
	 * out of the background, so the mark is nine pixels and the thing a pointer
	 * has to hit is seventeen.
	 */
	width: 17px;
	height: 17px;
	border: 4px solid transparent;
	border-radius: 50%;
	background: var(--npx-spine-dot);
	background-clip: padding-box;
}

[data-layout="rail"] .npx-card::after {
	left: -0.5px;
	width: 1px;
	background: var(--npx-line);
}

[data-layout="rail"] .npx-card[data-npx-edge="end"]::before {
	left: auto;
	right: -8.5px;
}

[data-layout="rail"] .npx-card[data-npx-edge="end"]::after {
	left: auto;
	right: -0.5px;
}

[data-layout="rail"] .npx-card[data-npx-side="above"]::before {
	bottom: calc(-1 * var(--npx-rail-up) - 9.5px);
}

[data-layout="rail"] .npx-card[data-npx-side="above"]::after {
	bottom: calc(-1 * var(--npx-rail-up) + 0.1rem);
	height: calc(var(--npx-rail-up) - 0.1rem);
}

[data-layout="rail"] .npx-card[data-npx-side="below"]::before {
	top: calc(-1 * var(--npx-rail-down) - 7.5px);
}

[data-layout="rail"] .npx-card[data-npx-side="below"]::after {
	top: calc(-1 * var(--npx-rail-down) + 0.1rem);
	height: calc(var(--npx-rail-down) - 0.1rem);
}

/* ---- the names there is no room for ------------------------------------ */

/*
 * Fitted to a column, a busy season puts its events closer together than a
 * name can be written, and the arithmetic behind this is in the renderer: an
 * event whose neighbour on the same side is nearer than a sixth of the window
 * keeps its dot and gives up its wording.
 *
 * Given up, not thrown away. The pointer brings it back, and so does the
 * keyboard — the title is still a button, still in the tab order, and still
 * opens the same preview as everywhere else. What is lost is only the
 * wording a reader could not have read anyway.
 */
/*
 * How many names the Rail can carry is a question about the column, and a
 * column is what a stylesheet knows and a renderer does not. So the
 * renderer gives every event the narrowest tier its name survives — 3 fits
 * anywhere, 0 fits nowhere — and the three steps below decide how narrow
 * this block actually turned out to be.
 *
 * One custom property carries the answer, so the appearance is written once
 * and each step only has to move the number. Written the other way — a
 * selector list repeated per tier — the hover state, the leader line and
 * the reduced-motion guard would each have had to be repeated three times.
 */
[data-layout="rail"] .npx-card {
	--npx-named: 1;
}

[data-layout="rail"] .npx-card[data-npx-label="0"] {
	--npx-named: 0;
	pointer-events: none;
}

@container (max-width: 900px) {
	[data-layout="rail"] .npx-card[data-npx-label="1"] {
		--npx-named: 0;
		pointer-events: none;
	}
}

@container (max-width: 640px) {
	[data-layout="rail"] .npx-events__grid > * {
		--npx-rail-label: 8.5rem;
	}

	[data-layout="rail"] .npx-card[data-npx-label="2"] {
		--npx-named: 0;
		pointer-events: none;
	}
}

/*
 * Given up, not thrown away. The pointer brings the name back and so does
 * the keyboard — the title is still a button, still in the tab order, and
 * still opens the same preview as everywhere else. What is lost is only
 * wording a reader could not have read.
 *
 * The dot keeps its own clicks whatever the label is doing: it is the way
 * back in, and a nine-pixel mark inside a seventeen-pixel target.
 */
[data-layout="rail"] .npx-card > *,
[data-layout="rail"] .npx-card::after {
	opacity: var(--npx-named);
	transition: opacity 0.15s ease;
}

[data-layout="rail"] .npx-card::before {
	pointer-events: auto;
}

[data-layout="rail"] .npx-card:hover,
[data-layout="rail"] .npx-card:focus-within {
	--npx-named: 1;
	z-index: 4;
	pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
	[data-layout="rail"] .npx-card > *,
	[data-layout="rail"] .npx-card::after {
		transition: none;
	}
}

/*
 * The first upcoming event, marked the way the Timeline marks it — the one
 * card on the page that can be picked out without knowing anything about the
 * content.
 */
[data-layout="rail"]:not([data-past="only"]) [data-npx-section="upcoming"] .npx-card:first-of-type::before {
	background: var(--npx-spine-dot-lit);
	box-shadow: 0 0 0 4px rgba(var(--npx-dot-ring-rgb), 0.18);
}

/*
 * The Rail has no trail to thicken — its line never turns, because there is
 * no run of events "before" a dot on a scale, only a position. So the family
 * resemblance is kept where it can be: the dot under the pointer takes the
 * accent and grows by the same fifth the timelines' does.
 */
[data-layout="rail"] .npx-card::before {
	transition: background-color 0.22s ease, transform 0.2s ease;
}

[data-layout="rail"] .npx-card:hover::before {
	background: var(--npx-spine-dot-lit);
	transform: scale(1.2);
}

/* Everything a card would say that a line has no room for. */
[data-layout="rail"] .npx-card__aside,
[data-layout="rail"] .npx-card__type,
[data-layout="rail"] .npx-card__when,
[data-layout="rail"] .npx-card__action,
[data-layout="rail"] .npx-card__projects,
[data-layout="rail"] .npx-card__footer,
[data-layout="rail"] .npx-card__summary,
[data-layout="rail"] .npx-card__countdown {
	display: none;
}

/*
 * The date line is pinned, not inherited.
 *
 * This box carries two inline spans and nothing else, so its height came from
 * the strut — the font-size and line-height the theme happens to hand down —
 * and the plugin sets neither. That made the label's height a property of the
 * theme rather than of the display: measured here at 21px with Uncode's
 * `normal`, but 26px at a line-height of 1.6 and 32px at 2.0, and the block
 * that holds it is a fixed reservation. At 1.8 the label's fill ran out of the
 * bottom of the block; at 2.0 it ran out of both ends. The same clipping, from
 * a direction no amount of reserving would have covered.
 *
 * 1.3rem is what it already measured on this site, so nothing moves here — it
 * is now simply the same everywhere.
 */
[data-layout="rail"] .npx-card__datebox {
	display: block;
	order: 0;
	margin: 0;
	padding: 0;
	font-size: 0.6rem;
	line-height: 1.3rem;
	text-align: inherit;
}

[data-layout="rail"] .npx-card__day,
[data-layout="rail"] .npx-card__month {
	display: inline;
	font-size: 0.6rem;
	font-weight: 600;
	letter-spacing: 0.05em;
	line-height: 1.4;
}

[data-layout="rail"] .npx-card__month {
	text-transform: uppercase;
}

[data-layout="rail"] .npx-card__main {
	display: block;
	min-height: 0;
	padding: 0;
	border: 0;
	text-align: inherit;
}

[data-layout="rail"] h3.npx-card__title {
	margin: 0;
	font-size: 0.78rem;
	line-height: 1.2;
}

/*
 * Two lines, and the limit goes on the button rather than on the heading around
 * it. A heading whose only child is a button has one line box — the button's —
 * so clamping the heading clamps nothing. Measured: a three-line conference
 * name ran straight out of the block.
 */
[data-layout="rail"] .npx-card__trigger {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
	text-align: inherit;
}

/* ---- the month ticks ---------------------------------------------------- */

/*
 * On a scale a month tick belongs where the month begins, not at the first
 * event of it — otherwise September's mark lands on the seventh and the ruler
 * is a week out.
 */
[data-layout="rail"] .npx-events__group {
	position: absolute;
	top: calc(var(--npx-rail-mid) - 0.45rem);
	left: var(--npx-x);
	display: block;
	width: 0;
	margin: 0;
	padding: 0;
	border: 0;
}

[data-layout="rail"] .npx-events__group::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	width: 1px;
	height: 1.05rem;
	background: var(--npx-spine);
}

[data-layout="rail"] .npx-events__group-name {
	position: absolute;
	bottom: 0.55rem;
	left: 0.3rem;
	color: var(--npx-ink-3);
	font-size: 0.55rem;
	font-weight: 700;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	white-space: nowrap;
}

[data-layout="rail"] .npx-events__group-count {
	display: none;
}

/* ---- a quiet stretch, as a length of the line --------------------------- */

/*
 * Here the gap needs no room made for it: the distance is already on the axis,
 * drawn to scale, and this only names it. The rule sits under the line rather
 * than on it, because the line through a quiet stretch is not broken — there
 * simply is nothing on it.
 */
/*
 * Between the line and the names below it, on a band of its own. Measured
 * before this: an event's name is drawn from its dot rightwards, so the last
 * name before a quiet stretch runs straight into the stretch's own label.
 */
[data-layout="rail"] .npx-events__gap {
	position: absolute;
	top: calc(var(--npx-rail-mid) + 0.2rem);
	left: var(--npx-x);
	display: block;
	width: calc(var(--npx-len, 0) * var(--npx-pxd));
	grid-template-columns: none;
	min-height: 0;
	margin: 0;
	pointer-events: none;
}

[data-layout="rail"] .npx-events__gap::after {
	position: absolute;
	top: 0;
	right: 0;
	left: 0;
	width: auto;
	height: 1px;
	align-self: auto;
	background-color: var(--npx-line);
	-webkit-mask-image: linear-gradient(90deg, #000 55%, transparent 0);
	mask-image: linear-gradient(90deg, #000 55%, transparent 0);
	-webkit-mask-size: 5px 1px;
	mask-size: 5px 1px;
	-webkit-mask-repeat: repeat-x;
	mask-repeat: repeat-x;
}

[data-layout="rail"] .npx-events__gap-label {
	display: block;
	padding: 0.2rem 0 0;
	font-size: 0.55rem;
	text-align: center;
	white-space: nowrap;
}

/* ------------------------------------------------- the card under a hover */

/*
 * The date already has this; the card is the same idea applied to the whole
 * row, so a wide card moves as one thing instead of twitching in one corner.
 * Turn accent is not repeated: on a card that is the hover fill, and it has a
 * setting of its own.
 *
 * The transform goes on the card, which already transitions it for the lift
 * every view answers a hover with — so this is a value, not a new animation.
 */
[data-card-motion="lift"] .npx-card:not(.npx-card--lead):hover {
	transform: translateY(-3px);
}

/*
 * A shade over one per cent. Scaling a row widens it as well as heightening
 * it, so the edges move against whatever the block sits in — small enough here
 * that a full-width section never shows it, and a narrow column shows only a
 * suggestion.
 */
[data-card-motion="grow"] .npx-card:not(.npx-card--lead):hover {
	transform: scale(1.014);
}

/*
 * The horizontal one, and the only one that cannot disturb the list: a row is
 * wide and short, sideways is the direction it has room in, and a step right
 * leaves every height on the page exactly where it was.
 */
[data-card-motion="slide"] .npx-card:not(.npx-card--lead):hover {
	transform: translateX(6px);
}

/* ---- the same three, on a Timeline -------------------------------------- */

/*
 * A Timeline cannot move its cards. The spine is drawn by them — each one
 * contributes the segment of line beside it, as a left border — so a card that
 * moves takes its piece of the axis with it. Held hovered and measured: Lift
 * opened a 3px hole below the segment, Slide put it 6px to the right of the
 * two it sits between, and Grow pulled it 6.9px to the left.
 *
 * So here the transform goes on what the card contains rather than on the card
 * itself: the date in the gutter, and each line of the body. The border and
 * the dot belong to `.npx-card__main`, which is not touched, so the axis holds
 * still while the row moves against it — which is the better reading of the
 * gesture on this display anyway. A timeline whose line drifts is a broken
 * timeline; a timeline whose events lean out from a fixed line is a timeline.
 *
 * Translating each child separately is the same as translating the group. Only
 * Grow is not, and there the origins are set so that both halves of the row
 * swell away from the spine rather than across it.
 */
[data-layout|="timeline"][data-card-motion="lift"] .npx-card:hover .npx-card__datebox,
[data-layout|="timeline"][data-card-motion="lift"] .npx-card:hover .npx-card__aside,
[data-layout|="timeline"][data-card-motion="lift"] .npx-card:hover .npx-card__main > * {
	transform: translateY(-3px);
}

[data-layout|="timeline"][data-card-motion="slide"] .npx-card:hover .npx-card__datebox,
[data-layout|="timeline"][data-card-motion="slide"] .npx-card:hover .npx-card__aside,
[data-layout|="timeline"][data-card-motion="slide"] .npx-card:hover .npx-card__main > * {
	transform: translateX(6px);
}

[data-layout|="timeline"][data-card-motion="grow"] .npx-card:hover .npx-card__datebox {
	transform: scale(1.014);
	transform-origin: right center;
}

[data-layout|="timeline"][data-card-motion="grow"] .npx-card:hover .npx-card__aside,
[data-layout|="timeline"][data-card-motion="grow"] .npx-card:hover .npx-card__main > * {
	transform: scale(1.014);
	transform-origin: left center;
}

/*
 * Except the row that carries the countdown, which spans the whole card.
 *
 * Scaling a full-width row from its left edge moves its right edge outward by
 * a hundredth and a half of the card's width — 17.1px on the 1318px card this
 * site draws — and the badge rides at that end. The layout reserves 0.85rem for
 * it, which is 13.6px, so the growth eats the reservation and 3.4px besides:
 * measured, the badge sat 13.6px inside the card at rest and 3.4px outside it
 * under the pointer.
 *
 * So the row holds still and its contents grow instead, each away from the edge
 * it sits against. Same gesture, same size, without having to leave the card to
 * find the room.
 */
[data-layout|="timeline"][data-card-motion="grow"] .npx-card:hover .npx-card__main > .npx-card__type {
	transform: none;
}

[data-layout|="timeline"][data-card-motion="grow"] .npx-card:hover .npx-card__type > * {
	transform: scale(1.014);
	transform-origin: left center;
}

[data-layout|="timeline"][data-card-motion="grow"] .npx-card:hover .npx-card__type > .npx-card__countdown {
	transform-origin: right center;
}

/*
 * Lying down, both halves swell to the right. The rule above sends the date the
 * other way, and it is right to on the vertical display: there the date is
 * right-aligned against the spine and grows away from it, into a gutter with
 * room to spare. Turned, the date block starts at the card's left edge instead,
 * so the same origin grows it out of the track — and the track is a scroll
 * container, so what leaves it on the left cannot be scrolled back to.
 *
 * Measured on the first card of the row, which is the one with nothing to its
 * left: the date block moved from the track's edge to 3.4px outside it, and the
 * first letter of the month went with it.
 */
[data-layout="timeline-h"][data-card-motion="grow"] .npx-card:hover .npx-card__datebox {
	transform-origin: left center;
}

/*
 * And the card itself stays where it is, whichever of the three is chosen.
 * These rules weigh more than the general ones above them and come after, so
 * the transform never reaches the box that carries the line.
 */
[data-layout|="timeline"][data-card-motion] .npx-card:not(.npx-card--lead):hover {
	transform: none;
}

/*
 * The lines move, so they need to be able to. The date block already
 * transitions its transform for the date animation; the body's lines did not.
 */
[data-layout|="timeline"] .npx-card__main > * {
	transition: transform 0.18s ease;
}

/* ------------------------------------------------------ the countdown badge */

/*
 * How far off the event is, under the pointer. The wording arrives from the
 * script — see `data-npx-countdown` — because a number written into the markup
 * is true for one day and this site is cached; and because an event that has
 * already started has nothing to count, in which case the script leaves the
 * badge hidden and nothing is drawn at all.
 *
 * Revealed rather than inserted: it is laid out whether or not the pointer is
 * there, so arriving on a card never reflows the card. `:focus-within` puts it
 * on the same footing for a reader arriving by keyboard, who has no pointer to
 * hover with.
 */
/*
 * The same label the rest of the plugin uses — uppercase, heavy, tracked, a
 * 4px corner — because that is what a small mark means here: the type label
 * above the title, the length of a quiet stretch on a Timeline, and Spotlight's
 * own "Next up" flag are all written this way, and a sentence-case pill was the
 * one thing that was not.
 *
 * A step tighter than the flag, though, and deliberately. The flag sits on a
 * hero card with room to spare; this has to fit a date gutter 70px wide. At the
 * flag's own 0.58rem and 0.09em, "dans 128 jours" measures 99px; at 0.55rem and
 * 0.06em it is 86px, and "in 16 days" comes down from 70px to 64px — inside the
 * gutter on one line, which it was not before.
 */
.npx-card__countdown {
	display: inline-block;
	flex: none;
	padding: 0.14rem 0.4rem;
	/*
	 * An edge of its own, because the badge cannot rely on its fill to be
	 * different from what is behind it. The soft accent hover fill is the same
	 * `--npx-accent-soft` the badge is: measured on a row under the pointer,
	 * both read rgb(255, 246, 227), so the chip vanished and left its wording
	 * floating on the row. A custom fill chosen anywhere near that colour does
	 * the same thing, and no rule can test for it.
	 *
	 * The accent ink at a third, so it draws the shape without competing with
	 * the wording inside it. `box-sizing` is border-box throughout, so the
	 * border comes out of the badge rather than adding to it — the fit measured
	 * for every placement still holds.
	 */
	/*
	 * Written as the ink rather than as the ink's channels: those digits are
	 * the amber's derived accent text, frozen. A site that chose a blue kept
	 * a brown edge on this chip, which is the one place the accent work
	 * missed.
	 */
	border: 1px solid color-mix(in srgb, var(--npx-accent-text) 34%, transparent);
	border-radius: 4px;
	background: var(--npx-accent-soft);
	color: var(--npx-accent-text);
	font-size: 0.55rem;
	font-weight: 800;
	letter-spacing: 0.06em;
	line-height: 1.35;
	white-space: nowrap;
	text-transform: uppercase;
	/*
	 * Stated, not inherited. The chip can be slotted next to the date, the
	 * type, the footer or in a column of its own, and two of those sit inside
	 * wording that is shadowed so it can be read off a photograph — the type on
	 * Cover carries the heaviest of them. A chip with its own opaque plate
	 * needs none of that, and took it as a smudge across its label.
	 */
	text-shadow: none;
	opacity: 0;
	transition: opacity 0.16s ease;
	pointer-events: none;
}

.npx-card:hover .npx-card__countdown,
.npx-card:focus-within .npx-card__countdown {
	opacity: 1;
}

/* ---- in the date box ---------------------------------------------------- */

/*
 * Below the date, not on a line of its own inside it. The gutter is the
 * narrowest column on the card — 72px on Rows, 70px on Timeline — and the
 * wording is longer than that in French, so it has to be free to wrap. In flow
 * a wrapped badge would grow the date block, and on Rows the date block is
 * centred against the row, so growing it would shift the date sideways and
 * down the instant the pointer arrived. Taken out of flow it can wrap as much
 * as it likes and nothing else on the card knows.
 */
.npx-card[data-npx-cd="date"] .npx-card__datebox {
	position: relative;
}

/*
 * Two things this has to hold to, and the gutter fights both.
 *
 * It must not touch the sides: at `max-width: 100%` the badge was exactly as
 * wide as the column it sits in, finishing flush against the card's left edge.
 * So it stops short of both.
 *
 * And it must be one box whether it says TODAY or IN 3 MONTHS. Nothing fits
 * this column on one line in every wording — "dans 59 jours" needs 86px and
 * the gutter has 70 — so the badge wraps, and left alone it was one line for
 * some events and two for others in the same list, which reads as two
 * different badges rather than one. A floor of two lines settles it: the short
 * wordings sit centred in the same box the long ones need, and because the
 * badge is out of flow, a wording that needs a third line still moves nothing.
 */
.npx-card[data-npx-cd="date"] .npx-card__datebox .npx-card__countdown {
	position: absolute;
	top: 100%;
	left: 0;
	right: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	width: max-content;
	max-width: calc(100% - 0.8rem);
	/*
	 * The gap above is small because what is below it is not: a Rows card is
	 * 123px tall with the date block centred in it, which left the badge seven
	 * pixels clear of the bottom edge — and the edge added to the badge took
	 * two of them. Measured after: eight clear at every width.
	 */
	margin: 0.2rem auto 0;
	white-space: normal;
	text-align: center;
}

/*
 * And only on the two displays whose date column is narrower than the wording.
 * Poster and Band lead with a date block wide enough to hold any of it on one
 * line, so a floor there would do nothing but make the badge taller than it
 * needs to be.
 */
[data-layout="rows"] .npx-card[data-npx-cd="date"] .npx-card__datebox .npx-card__countdown,
[data-layout|="timeline"] .npx-card[data-npx-cd="date"] .npx-card__datebox .npx-card__countdown {
	min-height: 2.5em;
}

/*
 * A row whose picture fills it is the one card where the date block is not a
 * block at all: it stretches the whole height of the row, so "under the date"
 * is under the card. Measured, the badge finished 26px past the bottom edge and
 * was cut off there. Inside the panel instead, at the foot of it.
 */
[data-row-image="background"] .npx-card[data-npx-cd="date"] .npx-card__datebox .npx-card__countdown {
	top: auto;
	bottom: 0.6rem;
	margin-top: 0;
}

/*
 * Spotlight's queue is the other card with no padding of its own on the right.
 */
[data-layout="spotlight"] .npx-events__queue .npx-card__main {
	--npx-edge: 0.85rem;
}

/* ---- at the foot, right ------------------------------------------------- */

/*
 * Opposite the place name: where at one end of the line, when at the other.
 * The foot is a plain block on most displays and only as wide as its contents
 * on Timeline, so both are settled here rather than relying on what each
 * layout happens to have set.
 */
.npx-card[data-npx-cd="foot"] .npx-card__footer {
	display: flex;
	align-items: baseline;
	gap: 0.6rem;
	width: 100%;
	align-self: stretch;
}

.npx-card[data-npx-cd="foot"] .npx-card__footer .npx-card__countdown {
	margin-left: auto;
	margin-right: var(--npx-edge, 0px);
}

/* ---- on the type line --------------------------------------------------- */

/*
 * Where Spotlight already puts this exact wording beside its featured event,
 * so the countdown reads from the same place on every display.
 *
 * `align-self` because Timeline lays its body out with `align-items:
 * flex-start`, which shrinks every line to its own width — including the one
 * the badge is supposed to be pushed to the far end of.
 */
/*
 * Keyed to the card and not to the block, which is what carries the resolved
 * placement — a card that could not use the one the author picked is styled
 * for the one it actually got. It also settles a specificity fight for free:
 * Rows sets `display: inline` on the type label and `padding: 0` on the body
 * further down the file, and a block-level attribute selector tied with both
 * and lost on source order.
 */
.npx-card[data-npx-cd="type"] .npx-card__type {
	padding-right: var(--npx-edge, 0px);
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 0.6rem;
	align-self: stretch;
}

/* ---- the right-hand column ---------------------------------------------- */

/*
 * Centred against the whole row rather than sitting on any one line, which is
 * what makes it read as a status. Only Rows and Timeline are offered it — the
 * tiled displays have no empty right-hand side, and there the setting resolves
 * to the foot before it ever reaches the stylesheet.
 *
 * The body is given room for it: a badge floating over a long title is not a
 * placement, it is a collision.
 */
.npx-card[data-npx-cd="column"] .npx-card__main {
	position: relative;
}

.npx-card[data-npx-cd="column"] .npx-card__main > .npx-card__countdown {
	position: absolute;
	right: 0.9rem;
	top: 50%;
	transform: translateY(-50%);
}

.npx-card[data-npx-cd="column"] .npx-card__main {
	padding-right: 6.5rem;
}

/*
 * And none of that on the Rail.
 *
 * The Rail draws a date and a title, and says so a few hundred lines above by
 * hiding everything else a card would carry. Three of the placements over-rule
 * it: `[data-npx-cd="type"]` puts the type line back, `"foot"` and `"column"`
 * put the footer back, and each is one selector heavier than
 * `[data-layout="rail"] .npx-card__type` — so the Rail got a part it has no
 * room for. Measured: a label went from 52px to 71px with the type line and to
 * 89px with the footer, inside a block whose height is a fixed reservation, and
 * the difference was cut off at the bottom edge.
 *
 * Written here rather than up in the Rail's own section because these are ties
 * at four selectors deep, and the later rule takes a tie.
 *
 * The countdown still reaches the reader: it is in the readout the pointer
 * carries along the line, which names the event's date and how far off it is.
 * That is where a Rail has room for it.
 */
[data-layout="rail"] .npx-card[data-npx-cd] :is(.npx-card__type, .npx-card__footer),
[data-layout="rail"] .npx-card[data-npx-cd] .npx-card__countdown {
	display: none;
}

/* The padding that placement reserved for a badge that is not drawn here. */
[data-layout="rail"] .npx-card[data-npx-cd="column"] .npx-card__main {
	padding-right: 0;
}

/* ============================================================ Band layout */

/*
 * One restricted set as a strip under a heading. Not a view that groups: the
 * element can already be restricted to a project, so a page that wants three
 * programmes side by side stacks three of these and keeps control of their
 * order, spacing and arrows — which an in-element grouping could not give it.
 *
 * The card is stripped to what a strip can carry at this size: when, what kind,
 * and what it is called. The picture, the place and the chips all go; they are
 * in the preview, which the whole card opens.
 */
.npx-events__band {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.35rem 0.7rem;
	margin: 0 0 0.7rem;
}

/*
 * For a heading naming several projects in a narrow column. A flex item's
 * default `min-width: auto` pins the name at its min-content width, so it
 * wraps into a tall thin stack while the rule and count keep their room
 * beside it — seven lines in a 180px column, measured. `min-width: 0` lets
 * it take the full width instead, and `flex-wrap` above sends the rule and
 * count to their own line when the name needs all of it.
 *
 * Nothing here changes a one-name band: identical boxes at 200, 260, 340 and
 * 600px, since a short name never wanted the shrinking in the first place.
 */
.npx-events__band-name {
	min-width: 0;
	color: var(--npx-ink);
	font-size: 0.98rem;
	font-weight: 700;
	letter-spacing: -0.01em;
}

/*
 * The rule takes the room the heading does not, so a short name and a long one
 * both reach the count. Two thirds opacity because it is a divider, not a
 * mark: at full strength the accent competes with the type labels below it.
 */
.npx-events__band-rule {
	flex: 1 1 auto;
	height: 2px;
	min-width: 1rem;
	background: var(--npx-accent);
	opacity: 0.4;
}

.npx-events__band-count {
	flex: none;
	color: var(--npx-ink-4);
	font-size: 0.72rem;
	font-weight: 600;
	letter-spacing: 0.07em;
	text-transform: uppercase;
	font-variant-numeric: tabular-nums;
}

[data-layout="band"] .npx-card {
	/*
	 * The token, not the property. `background` here would tie with
	 * `.npx-card:hover` on specificity and win on source order, leaving a card
	 * that never answers the pointer.
	 */
	--npx-card-bg: var(--npx-bg-2);
	height: auto;
	padding: 0.7rem 0.8rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius);
	text-align: left;
}

[data-layout="band"] .npx-card__aside,
[data-layout="band"] .npx-card__action,
[data-layout="band"] .npx-card__place,
[data-layout="band"] .npx-card__cartouche {
	display: none;
}

/*
 * The date leads, as a line rather than as a block: a strip card is three
 * short lines and a second big numeral would be one thing too many.
 */
[data-layout="band"] .npx-card__datebox {
	display: block;
	order: 0;
	margin: 0 0 0.35rem;
	text-align: left;
}

[data-layout="band"] .npx-card__day,
[data-layout="band"] .npx-card__month {
	display: inline;
	color: var(--npx-ink-3);
	font-size: 0.72rem;
	font-weight: 700;
	letter-spacing: 0.02em;
	font-variant-numeric: tabular-nums;
}

[data-layout="band"] .npx-card__month::before {
	content: " ";
}

[data-layout="band"] .npx-card__main {
	min-height: 0;
	padding: 0;
	align-items: flex-start;
	justify-content: flex-start;
	text-align: left;
}

[data-layout="band"] .npx-card__type {
	margin-bottom: 0.25rem;
	font-size: 0.6rem;
}

[data-layout="band"] h3.npx-card__title {
	flex-grow: 0;
	font-size: clamp(0.78rem, 4.4cqi, 0.9rem);
}

[data-layout="band"] .npx-card__trigger {
	-webkit-line-clamp: 2;
}

/*
 * Chips stay, because the switch that hides them is offered on every display
 * and a control that quietly does nothing on one of them is worse than a busy
 * card. They are set smaller here instead: a strip card is narrow, and an
 * event in six projects would otherwise spend more height on its chips than on
 * its name.
 */
[data-layout="band"] .npx-card__projects {
	gap: 0.2rem;
	margin-top: 0.45rem;
	justify-content: flex-start;
}

[data-layout="band"] .npx-badge--project {
	padding: 0.1rem 0.3rem;
	font-size: 0.52rem;
	letter-spacing: 0.05em;
}

[data-layout="band"] .npx-card__footer {
	margin-top: 0.4rem;
	padding-top: 0;
	border-top: 0;
	justify-content: flex-start;
}

/*
 * The time rides on the date's line instead — see `__clock` below — so this
 * is empty here. It is hidden rather than left to collapse because it carries
 * a reserved blank line that keeps the Details buttons level on the other
 * displays, and Band has no Details button to keep level with.
 */
[data-layout="band"] .npx-card__when {
	display: none;
}

/*
 * Under the title, a time read as a third line of the title, two lines below
 * the date it belongs to. On the date's line the whole "when" is one thing and
 * the strip loses a row. A narrow column wraps it under the date, which is the
 * same words in the same order.
 *
 * The separator is generated rather than written into the markup, the way the
 * space between day and month is above: it is punctuation between two spans,
 * and a reader announcing "28 Sep 2026 14:00 to 15:30" is the reading wanted.
 */
[data-layout="band"] .npx-card__clock {
	color: var(--npx-ink-3);
	font-size: 0.72rem;
	font-weight: 400;
	font-variant-numeric: tabular-nums;
}

[data-layout="band"] .npx-card__clock::before {
	content: " · ";
}

/* No panel of its own on a dark section, for the same reason Poster darkens. */
[data-scheme="dark"][data-layout="band"] .npx-card {
	--npx-card-bg: rgba(255, 255, 255, 0.06);
	--npx-card-bg-hover: rgba(255, 255, 255, 0.1);
	/* The chosen fill wins over the surface above; see the hover rules. */
	--npx-bg-3: rgba(255, 255, 255, 0.14);
}

[data-scheme="dark"][data-layout="band"] .npx-card__type {
	color: var(--npx-accent-lift);
}

/* ========================================================== Poster layout */

/*
 * No picture at all: the date is the design. Written for the events whose
 * artwork is a logo rather than a photograph — cropped to fill a card band a
 * logo is unrecognisable, and half the agenda is logos.
 *
 * The date block is not new markup. It is rendered for every view already and
 * hidden by CSS everywhere but Rows; here it is simply let out and given the
 * top of the tile.
 */
[data-layout="poster"] .npx-card {
	--npx-card-bg: var(--npx-bg-2);
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius);
}

[data-layout="poster"] .npx-card__aside {
	display: none;
}

[data-layout="poster"] .npx-card__datebox {
	display: block;
	margin: 0;
	padding: clamp(0.9rem, 4cqi, 1.25rem) clamp(0.9rem, 4cqi, 1.25rem) 0;
	text-align: left;
}

/*
 * Set in the container's own width rather than the viewport's, so a tile in a
 * narrow column shrinks its numeral instead of breaking it across two lines —
 * "20–21" is one word here and has to stay one.
 */
[data-layout="poster"] .npx-card__day {
	display: block;
	color: var(--npx-ink);
	font-size: clamp(1.9rem, 15cqi, 3.1rem);
	font-weight: 700;
	letter-spacing: -0.045em;
	line-height: 0.86;
	white-space: nowrap;
	font-variant-numeric: tabular-nums;
}

[data-layout="poster"] .npx-card__month {
	display: block;
	margin-top: 0.45rem;
	padding-bottom: 0.7rem;
	border-bottom: 1px solid var(--npx-line);
	color: var(--npx-ink-3);
	font-size: clamp(0.58rem, 3.2cqi, 0.68rem);
	font-weight: 800;
	letter-spacing: 0.14em;
	text-transform: uppercase;
}

[data-layout="poster"] .npx-card__main {
	justify-content: flex-start;
	padding: 0.85rem clamp(0.9rem, 4cqi, 1.25rem) clamp(0.9rem, 4cqi, 1.25rem);
	text-align: left;
	align-items: flex-start;
}

/*
 * The title takes the space the picture would have had, which is what keeps a
 * short title and a three-line one on the same tile height.
 */
[data-layout="poster"] .npx-card__title {
	flex-grow: 1;
}

[data-layout="poster"] .npx-card__trigger {
	-webkit-line-clamp: 3;
}

[data-layout="poster"] .npx-card__projects,
[data-layout="poster"] .npx-card__footer {
	justify-content: flex-start;
	text-align: left;
}

[data-layout="poster"] .npx-card__footer {
	border-top: 1px solid var(--npx-line);
}

/* ---- a tile on a dark section -------------------------------------------- */

/*
 * The tile carries its own surface, so unlike Rows it needs no scale swap —
 * only the surface itself has to stop being a pale grey on a dark band, or the
 * grid reads as a wall of light boxes rather than as part of the section.
 */
[data-scheme="dark"][data-layout="poster"][data-hover="grey"] .npx-card:hover {
	background: var(--npx-card-bg-hover);
}

/* ============================================================ Rows layout */

/*
 * Not the card turned sideways: a row leads with the date, the way an agenda
 * is actually scanned. The panel, the shadow and the footer rule all go — the
 * separator between rows is the structure — and the thumbnail shrinks to a
 * marker rather than being the subject.
 */
[data-layout="rows"] .npx-events__grid {
	grid-template-columns: minmax(0, 1fr);
	gap: var(--npx-gap);
	border-top: 1px solid var(--npx-line);
}

/*
 * Rows are the one view whose "none" is a look of its own rather than an
 * absence: butted together and divided by a rule, which is the list this view
 * started as. Every other step opens them up from there, and the scale then
 * means the same thing here as it does under a grid of cards.
 *
 * Once the rows are apart, a rule under each one is no longer separating
 * anything — the space is. Keeping both reads as a line belonging to the row
 * above it rather than as a divider between two.
 *
 * But the space alone leaves nothing to say where a row begins and ends, so
 * the rule is traded for an outline all the way round: a spaced row is an
 * object rather than an entry in a list, and it is drawn like one.
 *
 * An outline rather than a filled surface, deliberately. A fill would have to
 * be light, and on a dark section that turns the row into a white card while
 * its wording is still being coloured for a dark background — white on white.
 * A border takes its colour from the same token either scheme has already set.
 */
[data-layout="rows"]:not([data-gap="none"]) .npx-events__grid {
	border-top: 0;
}

[data-layout="rows"]:not([data-gap="none"]) .npx-card {
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius);
	transition: background-color 0.15s, border-color 0.2s, box-shadow 0.2s;
}

[data-layout="rows"]:not([data-gap="none"]) .npx-card:hover {
	border-color: var(--npx-line-hover);
}

/*
 * And with edges of its own it can cast a shadow, which is why the shadow
 * settings are offered here at all. A flush row still cannot: its shadow would
 * fall across the separator under it and smudge the one thing dividing the
 * list.
 *
 * `:not([data-shadow="none"])` rather than leaning on specificity — this rule
 * would otherwise outrank the None switch on attribute count alone.
 */
[data-layout="rows"]:not([data-gap="none"]):not([data-shadow="none"]) .npx-card {
	box-shadow:
		0 1px 2px rgba(var(--npx-shadow-tint), calc(0.06 * var(--npx-shadow-scale))),
		0 4px 16px -6px rgba(var(--npx-shadow-tint), calc(0.12 * var(--npx-shadow-scale)));
}

[data-layout="rows"]:not([data-gap="none"]):not([data-shadow="none"]) .npx-card:hover {
	box-shadow: 0 12px 28px -12px rgba(var(--npx-shadow-tint), calc(0.55 * var(--npx-shadow-scale)));
}

[data-layout="rows"] .npx-card {
	display: grid;
	grid-template-columns: 4.5rem 5.5rem minmax(0, 1fr);
	align-items: center;
	gap: 0 1.25rem;
	height: auto;
	padding: 0.9rem 0.75rem;
	--npx-card-bg: transparent;
	border: 0;
	border-bottom: 1px solid var(--npx-line);
	border-radius: 0;
	box-shadow: none;
	transition: background-color 0.15s;
}

/*
 * The same four words, resolved for a line.
 *
 * Four displays answer a hover with a line: Cards and White cards outline the
 * panel, Cover outlines the picture, and Spotlight marks the event under the
 * pointer in its queue. A line is not a fill — a wash pale enough to sit under
 * wording is not a colour anyone would draw a hairline in — so the choice
 * resolves into a token of its own and each display takes the one it can show.
 *
 * Grey is the ink scale's lightest step, which is a grey meant to be seen on a
 * page surface.
 */
.npx-events {
	--npx-hover-line: var(--npx-ink-4);
}

[data-hover="grey"] {
	--npx-hover-line: var(--npx-ink-4);
}

[data-hover="accent"] {
	--npx-hover-line: var(--npx-accent);
}

[data-hover="custom"] {
	--npx-hover-line: var(--npx-hover-fill, var(--npx-accent));
}

[data-hover="none"] {
	--npx-hover-line: transparent;
}

/*
 * Except on Cover, whose card is a photograph rather than a page surface.
 * `--npx-ink-4` is picked to read on a panel and disappears on artwork, and in
 * a dark section it becomes a half-transparent white, which over a picture is
 * not a grey at all. So the neutral is stated here for the surface it is
 * actually drawn on.
 */
[data-layout="cover"][data-hover="grey"] {
	--npx-hover-line: rgba(255, 255, 255, 0.82);
}

/*
 * Spotlight joins the three list displays here, but only for the queue beside
 * the hero — hence `:not(.npx-card--lead)`. The lead card is not a row, and a
 * fill sweeping across a block that size would promise one. It is no longer
 * true that it cannot be reached, as this comment used to say: its title links
 * to the event, and it answers the pointer with a line of its own further up.
 *
 * Before this the queue took the base card hover, whose `--npx-card-bg-hover`
 * is #e5e7ea and is never redefined for a dark section — so on dark the row
 * turned near-white under white wording and the date vanished into it. Every
 * fill below comes from a token the dark scale does redefine.
 */
:is([data-layout="rows"], [data-layout="poster"], [data-layout="band"], [data-layout="spotlight"], [data-layout|="timeline"], [data-layout="rail"]) .npx-card:not(.npx-card--lead):hover {
	--npx-hover-paint: var(--npx-bg-2);
	background: var(--npx-hover-paint);
}

/*
 * The line goes on the featured event, and the queue keeps its fill.
 *
 * The queue already answers the pointer. The hero answered it with nothing at
 * all — every hover rule here is written `:not(.npx-card--lead)`, on the
 * grounds set out below that it is not clickable. That stopped being true when
 * its title became `<a class="npx-card__away">` pointing at the event's own
 * page: the one card on a Spotlight that goes somewhere was the one card that
 * gave no sign of it.
 *
 * A line and not a fill, for the reason that same comment gives — a wash
 * sweeping across a block this size promises a row that is not there. On a
 * pseudo-element rather than a border, as Cards does, so the box does not
 * widen and shove the queue beside it.
 */
[data-layout="spotlight"] .npx-card--lead::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 2;
	border: 1px solid transparent;
	transition: border-color 0.2s;
	pointer-events: none;
}

[data-layout="spotlight"] .npx-card--lead:hover::after {
	border-color: var(--npx-hover-line);
}

/*
 * The warm alternative to that grey, matching the tint HAL Publications uses
 * for the same job. A row has no border and no shadow, so the hover fill is
 * the only thing that says which row is under the pointer: on a page that
 * already carries a lot of grey, the accent tint reads where the grey does
 * not.
 */
:is([data-layout="rows"], [data-layout="poster"], [data-layout="band"], [data-layout="spotlight"], [data-layout|="timeline"], [data-layout="rail"])[data-hover="accent"] .npx-card:not(.npx-card--lead):hover {
	--npx-hover-paint: var(--npx-accent-soft);
	background: var(--npx-hover-paint);
}

/*
 * Any colour the author picked, handed over as an inline `--npx-hover-fill` on
 * the block. The fallback is the grey fill, so a colour the picker returned in
 * a form we could not read leaves the row with its neutral hover rather than
 * with none at all.
 */
:is([data-layout="rows"], [data-layout="poster"], [data-layout="band"], [data-layout="spotlight"], [data-layout|="timeline"], [data-layout="rail"])[data-hover="custom"] .npx-card:not(.npx-card--lead):hover {
	--npx-hover-paint: var(--npx-hover-fill, var(--npx-bg-2));
	background: var(--npx-hover-paint);
}

/*
 * And no fill at all, for a list that says which row is under the pointer some
 * other way — a Timeline, where the line itself follows it — or for a page
 * where a band of colour sweeping down a long list is more than is wanted.
 *
 * It has to name the resting fill rather than simply not apply: every card
 * takes `--npx-card-bg-hover` from the shared rule, and dropping this one
 * would hand these five displays that #e5e7ea instead of nothing. Naming the
 * token each layout already sets is what makes one rule right for all of them
 * — transparent on a Timeline and a row, the section grey on a poster tile.
 */
:is([data-layout="rows"], [data-layout="poster"], [data-layout="band"], [data-layout="spotlight"], [data-layout|="timeline"], [data-layout="rail"])[data-hover="none"] .npx-card:not(.npx-card--lead):hover {
	--npx-hover-paint: var(--npx-card-bg);
	background: var(--npx-hover-paint);
}

/*
 * And the Rail, which wants the colour but not the box.
 *
 * It has to sit below the four rules above rather than beside them: they are
 * `:is(…) .npx-card:not(.npx-card--lead):hover`, four selectors deep, and an
 * override written up in the Rail's own section was a selector short and lost
 * every time. Three of the four carry a `[data-hover="…"]` of their own and are
 * a selector heavier again, which is why `[data-hover]` is named here without a
 * value — it is always on the block, so it costs nothing and buys the weight. `--npx-hover-paint` is already resolved by the time we get here,
 * whichever of the four answered, so the colour arrives chosen and only the
 * surface it lands on changes.
 *
 * A Rail label is a column ten and a half rem wide holding two short lines, so
 * filling its box would lay a band across the block far wider than the wording
 * that earned it. The fill goes on the lines instead, and the spread stands in
 * for the padding they have not got.
 */
/*
 * One box behind the whole label.
 *
 * The fill went on the label's two parts at first, so that it would hug the
 * wording rather than the column — and that is what made it look wrong: a date
 * of one width and a title of another, each with a rounded rectangle of its
 * own, meeting in a step down the middle. A card is a card because it has one
 * edge, and every other display draws it that way.
 *
 * The bleed is a spread rather than padding, so nothing moves when the pointer
 * arrives: padding would grow the box, and every mark on this display — the
 * dot, the leader that reaches down to it — is placed off that box's edges. Six
 * pixels against the thirty-five the dot sits away, so the two never meet.
 *
 * Written at this weight, and below the shared fills, for the same reason the
 * suppression it replaces was: those rules are four and five selectors deep,
 * and anything lighter loses to them.
 */
[data-layout="rail"][data-hover] .npx-card:not(.npx-card--lead):hover,
[data-layout="rail"][data-hover] .npx-card:not(.npx-card--lead):focus-within {
	border-radius: 4px;
	background: var(--npx-hover-paint);
	box-shadow: 0 0 0 10px var(--npx-hover-paint);
}

/*
 * None means none here, and not the resting fill.
 *
 * The shared rule answers "none" with `--npx-card-bg`, which is the right
 * answer for a display whose cards have a fill to return to — a poster tile
 * keeps its section grey. A Rail label has none: it is wording laid straight on
 * the page, so the token resolves to a grey that was never behind it, and
 * choosing None painted a fill instead of removing one.
 */
[data-layout="rail"][data-hover="none"] .npx-card:not(.npx-card--lead):hover,
[data-layout="rail"][data-hover="none"] .npx-card:not(.npx-card--lead):focus-within {
	--npx-hover-paint: transparent;
}

/*
 * With a picture on the card the fill should begin where the picture does, not
 * at the line. Everywhere else the card starts at its own top edge and filling
 * from there is right; here there is a rem of deliberate air between the axis
 * and the top of the image, and colouring that strip reattaches the card to the
 * line the gap was put there to separate it from.
 *
 * The fill is painted by a layer of the card's own rather than by its
 * background, because a background cannot start partway down a box. `isolate`
 * is what keeps that layer inside the card: sent behind the content with a
 * negative index and no stacking context to sit in, it would pass behind the
 * block itself and out of sight.
 */
[data-layout="timeline-h"][data-row-size="large"] .npx-card {
	isolation: isolate;
}

/*
 * The wording has the slot's right-hand padding to grow into; the picture does
 * not, because it runs to both edges. Slide and Grow therefore carry it a few
 * pixels over the card that follows, and it has to be the hovered card that
 * wins that overlap — left to source order the next card paints over the one
 * under the pointer, which reads as the picture being clipped rather than
 * moved. `isolate` above is what makes this a comparison between cards rather
 * than a number thrown at the whole page.
 */
[data-layout="timeline-h"][data-row-size="large"][data-card-motion] .npx-card:hover {
	z-index: 1;
}

[data-layout="timeline-h"][data-row-size="large"] .npx-card:not(.npx-card--lead):hover {
	background: none;
}

[data-layout="timeline-h"][data-row-size="large"] .npx-card:not(.npx-card--lead):hover::before {
	content: "";
	position: absolute;
	z-index: -1;
	inset: var(--npx-band-top) 0 0;
	background: var(--npx-hover-paint);
}

/*
 * A fill dark enough that the page's own ink would disappear on it takes the
 * light scale for as long as the pointer is there — the same trade the soft
 * accent makes in reverse on a dark section. Which way round it goes is not
 * guessed here: it is worked out in PHP from the colour's luminance, and
 * arrives as `data-hover-ink`.
 *
 * The brand amber goes with them. It is a mid-tone: readable on the near-black
 * of a dark section, but only 1.9:1 against a mid-blue fill, so the type label
 * and the hovered title were the two things left unreadable. The lighter tint
 * is the one Cover already uses for exactly this, over a photograph.
 *
 * Rows with the picture behind them are excluded. Their hover fill is
 * suppressed (it would be under the image), so there is nothing for the ink to
 * be read against — and the date block beside the picture keeps the page's own
 * white surface, which a light scale would wipe out.
 */
:is([data-layout="rows"], [data-layout="poster"], [data-layout="band"], [data-layout="spotlight"], [data-layout|="timeline"], [data-layout="rail"])[data-hover-ink="light"]:not([data-row-image="background"]) .npx-card:not(.npx-card--lead):hover {
	--npx-ink: #ffffff;
	--npx-ink-2: rgba(255, 255, 255, 0.86);
	--npx-ink-3: rgba(255, 255, 255, 0.72);
	--npx-ink-4: rgba(255, 255, 255, 0.5);
	--npx-line: rgba(255, 255, 255, 0.24);
	--npx-accent: var(--npx-accent-lift);
}

/*
 * The project chip is a white plate, not wording, and it keeps being one: it
 * is the same light island the filter bar and the date block are, so it takes
 * dark ink rather than the light scale above. Without this it inherited white
 * text onto its own white fill and was the one thing on the row that vanished.
 */
:is([data-layout="rows"], [data-layout="poster"], [data-layout="band"], [data-layout="spotlight"], [data-layout|="timeline"], [data-layout="rail"])[data-hover-ink="light"]:not([data-row-image="background"]) .npx-card:not(.npx-card--lead):hover .npx-badge--project {
	color: #141618;
}

/* ---- the date block ------------------------------------------------------ */

[data-layout="rows"] .npx-card__datebox {
	display: block;
	margin: 0;
	text-align: center;
}

[data-layout="rows"] .npx-card__day {
	display: block;
	color: var(--npx-ink);
	font-size: 1.5rem;
	font-weight: 700;
	letter-spacing: -0.02em;
	line-height: 1.1;
	font-variant-numeric: tabular-nums;
}

[data-layout="rows"] .npx-card__month {
	display: block;
	margin-top: 0.15rem;
	color: var(--npx-ink-3);
	font-size: 0.68rem;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

/* ---- thumbnail: a marker, not the subject -------------------------------- */

/*
 * A square marker, so the whole picture is shown rather than a square cut out
 * of it: at this size a cropped logo is unrecognisable, and the row's job is
 * to identify the event, not to display artwork.
 */
[data-layout="rows"] .npx-card__aside {
	width: 5.5rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius-sm);
	background: var(--npx-bg);
	aspect-ratio: 1 / 1;
}

[data-layout="rows"] .npx-card__image {
	object-fit: contain;
	padding: 0.3rem;
}

[data-layout="rows"] .npx-card__cartouche {
	/* No room for it beside a 5.5rem thumbnail; the origin stays in the filter
	   and the preview. */
	display: none;
}

/* ---- the middle column --------------------------------------------------- */

[data-layout="rows"] .npx-card__main {
	display: block;
	min-height: 0;
	padding: 0;
	text-align: left;
}

[data-layout="rows"] .npx-card__type {
	display: inline;
	margin: 0;
	font-size: 0.66rem;
}

[data-layout="rows"] .npx-card__title {
	margin: 0.2rem 0 0;
	font-size: clamp(1rem, 3cqi, 1.2rem);
}

/* A row has the width to show a title in full. */
[data-layout="rows"] .npx-card__trigger {
	display: block;
	-webkit-line-clamp: none;
}

/* Place and dates read as one quiet line under the title. */
[data-layout="rows"] .npx-card__footer {
	display: flex;
	flex-wrap: wrap;
	gap: 0 0.5rem;
	margin: 0.3rem 0 0;
	padding: 0;
	border: 0;
}

[data-layout="rows"] .npx-card__place,
[data-layout="rows"] .npx-card__dates,
[data-layout="rows"] .npx-card__time {
	margin: 0;
	color: var(--npx-ink-3);
	font-size: 0.82rem;
	font-weight: 400;
}

/*
 * One separator rule for whatever the footer line happens to hold. It used to
 * be a dot after the place and another before the time, which produced two in
 * a row once the date was dropped from this line, and a leading one whenever
 * the place was missing.
 */
/*
 * Not the countdown. It is pushed to the far end of this line rather than
 * following the place along it, so a separator in front of it separates
 * nothing — and being its own `::before`, the dot landed inside the badge,
 * which since the badge grew an outline is visibly inside the chip.
 */
[data-layout="rows"] .npx-card__footer > *:not(:first-child):not(.npx-card__countdown)::before {
	content: "·";
	margin-right: 0.5rem;
	color: var(--npx-ink-4);
}

[data-layout="rows"] .npx-card__projects {
	justify-content: flex-start;
	margin: 0.4rem 0 0;
}

[data-layout="rows"] .npx-badge--project {
	padding: 0.15rem 0.4rem;
	font-size: 0.6rem;
}

/*
 * No button in a row: it doubles the height of every line and the row already
 * opens the preview, which carries the event's links. The row is an index, the
 * preview is where you act.
 */
[data-layout="rows"] .npx-card__action {
	display: none;
}

/* ---- a row on a dark section --------------------------------------------- */

/*
 * Rows are the one view whose wording sits straight on the page: the row has
 * no surface of its own, only a rule under it. On a dark section that wording
 * was #141618 on near-black and simply vanished — the title and the day were
 * gone from the card entirely.
 *
 * `data-scheme` is set by the script from the colour actually painted behind
 * the block. The theme's own class was tried first and is not safe: Uncode
 * marks a row `.style-dark` to mean it uses the dark colour scheme, and one
 * such row on this site is painted white, which turned the wording white on
 * white. What is behind the block is the only thing worth asking.
 *
 * The other views need none of this: their card carries its own light
 * surface, so their text is already on the right background.
 */
[data-scheme="dark"] {
	--npx-ink: #ffffff;
	--npx-ink-2: rgba(255, 255, 255, 0.84);
	--npx-ink-3: rgba(255, 255, 255, 0.68);
	--npx-ink-4: rgba(255, 255, 255, 0.45);
	--npx-line: rgba(255, 255, 255, 0.18);
	--npx-line-hover: rgba(255, 255, 255, 0.3);
	--npx-bg-2: rgba(255, 255, 255, 0.06);
}

/*
 * Everything inside the block that keeps its own light surface keeps the light
 * scale with it. Re-declaring the tokens is enough: they cascade to everything
 * within.
 */
[data-scheme="dark"] .npx-button,
[data-scheme="dark"] .npx-carousel__arrow,
[data-scheme="dark"][data-row-image="background"] .npx-card__datebox {
	--npx-ink: #141618;
	--npx-ink-2: #3b4045;
	--npx-ink-3: #6b7280;
	--npx-ink-4: #9aa1a9;
	--npx-line: #e2e4e8;
	--npx-line-hover: #d3d7dd;
	/*
	 * The pale fills belong here too, not only the ink. These elements change
	 * surface when hovered or opened, and the dark scheme's --npx-bg-2 is a
	 * near-transparent white: over a dark section the button turned black
	 * under the pointer while its label stayed dark, and vanished.
	 */
	--npx-bg: #ffffff;
	--npx-bg-2: #f7f7f8;
	--npx-bg-3: #eeeff1;
}

/*
 * A pale hover fill — the soft accent, or a light colour the author picked —
 * is the one moment a row on a dark section is light: the wording has to come
 * back to dark ink for as long as the pointer is on it, or it is white on
 * cream and gone.
 *
 * The grey hover needs none of this — in a dark scheme that fill is itself
 * dark, and the white wording still reads over it — which is exactly why the
 * attribute is absent for grey rather than set to a value.
 */
[data-scheme="dark"]:is([data-layout="rows"], [data-layout="poster"], [data-layout="band"], [data-layout="spotlight"], [data-layout|="timeline"], [data-layout="rail"])[data-hover-ink="dark"]:not([data-row-image="background"]) .npx-card:not(.npx-card--lead):hover {
	--npx-ink: #141618;
	--npx-ink-2: #3b4045;
	--npx-ink-3: #6b7280;
	--npx-ink-4: #9aa1a9;
	--npx-line: #e2e4e8;
}

/*
 * A pale button on a dark section needs a hover that carries. The usual shift
 * from white to #f7f7f8 is eight values of grey: on a white page the border
 * darkening sells it, but here the button is a white block on black and the
 * change simply does not read. The soft accent is unmistakable, and it is the
 * same cue the rows themselves use.
 *
 * The primary button is left out: it has its own inverse hover, and this rule
 * would otherwise outrank it.
 */
[data-scheme="dark"] .npx-button:not(.npx-button--primary):hover,
[data-scheme="dark"] .npx-button:not(.npx-button--primary):focus {
	border-color: var(--npx-accent);
	background: var(--npx-accent-soft);
	color: var(--npx-accent-text);
}

/*
 * A light island: the chip keeps dark ink on a pale plate rather than joining
 * the flipped scale, because on the three views below it is sitting on the
 * event's own artwork, where a tinted chip would be at the mercy of the photo.
 */
[data-scheme="dark"] .npx-badge--project,
[data-scheme="dark"] .npx-card__projects--over .npx-badge--project {
	background: #ffffff;
	color: #141618;
}

/*
 * Six displays put their chips on the card's own surface, not on a picture —
 * and there a white plate is the brightest thing on the card. It
 * matches the white title in value while covering far more of it, so the
 * projects read before the name of the event. This drops the plate to grey and
 * leaves the title alone: measured, it sits at 60% of the title's luminance
 * with the label still at 11.2:1.
 *
 * A translucent tint was tried first and rejected. These three views take a
 * hover fill, that fill can be pale, and a translucent chip composites over
 * whatever is under it — on the soft-accent fill the label landed at 1.1:1.
 * A flat plate has no such dependency, which is the whole reason it wins here.
 *
 * `.npx-card__projects` is in the selector to carry weight, not to narrow the
 * match: Rows inverts its chip to `--npx-bg` further down the file at the same
 * specificity this rule would otherwise have, and would win on source order.
 */
/*
 * The list started at three because three displays existed that put their
 * chips in the body. Spotlight queues its events beside the hero and the
 * three timelines hang theirs off a line; none of them has a photograph
 * under the chip either, and all four were taking the white plate meant for
 * one.
 *
 * Cover was left out of the list on the strength of a comment saying `--over`
 * meant Cards, White cards and Cover. It does not: `render.php` sets that flag
 * for Cards and White cards only, so Cover's chips are body chips like these
 * and were the last ones still taking the white plate. The rule above is right
 * for exactly the two displays that anchor a chip to the artwork.
 */
[data-scheme="dark"]:is([data-layout="rows"], [data-layout="poster"], [data-layout="band"], [data-layout="spotlight"], [data-layout|="timeline"], [data-layout="rail"], [data-layout="cover"]) .npx-card__projects .npx-badge--project {
	background: #c8cbd0;
}

/*
 * A card the track is cutting does not answer the pointer.
 *
 * A scroll container clips — nothing inside one can paint past its edge — so an
 * event at the edge of a track is cut whatever else is done. It need not also
 * be lit while cut, and that is the part that reads as damage: a fill painted
 * across two thirds of a card and stopped dead by the container looks broken,
 * where the same card unlit reads as simply the next one along.
 *
 * Fading the edge was tried first. It makes the cut gentler and the card is
 * still cut, so it is not an answer on its own.
 *
 * The card is still reachable: one press of the arrow brings it in whole, which
 * the landing arithmetic now guarantees, and there it takes its hover normally.
 *
 * `.npx-events[data-hover]` is in the selector for weight and nothing else. The
 * custom hover fill is five selectors deep, and anything lighter than this ties
 * with it and loses on source order.
 */
.npx-events[data-hover] .npx-events__grid > .npx-card[data-npx-part]:hover,
.npx-events[data-hover] .npx-events__grid > .npx-card[data-npx-part]:focus-within {
	background: var(--npx-card-bg);
	box-shadow: none;
	transform: none;
}

.npx-events[data-hover] .npx-events__grid > .npx-card[data-npx-part]:hover::after {
	border-color: transparent;
}

/* ---- a card that carries its own surface --------------------------------- */

/*
 * Rows has no surface: its wording sits on the section, so the scale above is
 * all it needs. Every other view brings a panel, and a panel left pale under
 * the flipped ink is white on near-white — so the surface darkens with the
 * text rather than the two being decided separately.
 *
 * Cover is absent on purpose. Its picture fills the card and its wording is
 * already white over a veil, so it reads the same on either ground.
 */
[data-scheme="dark"][data-layout="cards"] .npx-card,
[data-scheme="dark"][data-layout="white"] .npx-card,
[data-scheme="dark"][data-layout="poster"] .npx-card {
	--npx-card-bg: rgba(255, 255, 255, 0.06);
	--npx-card-bg-hover: rgba(255, 255, 255, 0.1);
	--npx-card-media-bg: rgba(255, 255, 255, 0.1);
	--npx-bg-3: rgba(255, 255, 255, 0.14);
}

/*
 * The type label warms on a dark surface, the way Cover's already does over a
 * photograph. Set on the label and not on --npx-accent: that token also fills
 * the primary button, where a lighter amber under white text drops the contrast
 * from a poor 2.4:1 to an unusable 1.5:1. The brand amber is a fill colour and
 * a wording colour at once, and only one of those two wants lifting here.
 */
[data-scheme="dark"][data-layout="cards"] .npx-card__type,
[data-scheme="dark"][data-layout="white"] .npx-card__type,
[data-scheme="dark"][data-layout="poster"] .npx-card__type {
	color: var(--npx-accent-lift);
}

/* ---- the filter bar on a dark section ------------------------------------ */

/*
 * The bar is the one part of the block that belongs to the page rather than to
 * the events: it is a strip of controls laid on the section, not an object
 * sitting on it. Left light it was a white slab across a dark band — the first
 * thing the eye landed on, and the loudest thing in the block.
 *
 * So it takes the dark scale, and it does so for every view rather than for
 * rows alone: the bar looks the same whatever is under it, and a card keeping
 * its own white surface below a dark bar is the normal relationship between a
 * page and the things on it.
 *
 * Because the rest of the dark scale is declared for rows only, the tokens are
 * repeated here in full rather than inherited — this has to stand on its own
 * under a grid of white cards.
 *
 * `color-scheme` is what the browser reads, not us: it is what turns the date
 * field's calendar button and the checkboxes light instead of leaving black
 * native furniture on a dark panel.
 */
[data-scheme="dark"] .npx-filters,
[data-scheme="dark"] .npx-filter__panel {
	--npx-ink: #ffffff;
	--npx-ink-2: rgba(255, 255, 255, 0.86);
	--npx-ink-3: rgba(255, 255, 255, 0.7);
	--npx-ink-4: rgba(255, 255, 255, 0.5);
	--npx-line: rgba(255, 255, 255, 0.2);
	--npx-line-hover: rgba(255, 255, 255, 0.34);
	--npx-bg: rgba(255, 255, 255, 0.05);
	--npx-bg-2: rgba(255, 255, 255, 0.1);
	--npx-bg-3: rgba(255, 255, 255, 0.14);
	/*
	 * The dark amber that reads on white is far too dim here; on a dark
	 * surface the brand colour is itself the legible one.
	 */
	--npx-accent-text: var(--npx-accent);
	color-scheme: dark;
}

/*
 * A panel floats over the events, so it needs a floor. `--npx-page-bg` is the
 * colour the script measured behind the block — the same measurement that
 * chose the scheme — with the panel's own tint laid over it. The fallback is
 * only for the case where nothing opaque was found before the document.
 */
[data-scheme="dark"] .npx-filter__panel {
	--npx-bg: rgba(255, 255, 255, 0.16);
	background-color: var(--npx-page-bg, #16191d);
	background-image: linear-gradient(
		rgba(255, 255, 255, 0.08),
		rgba(255, 255, 255, 0.08)
	);
}

/* Open should lift, as it does on a light page, not sink back into the bar. */
[data-scheme="dark"] .npx-filter__trigger[aria-expanded="true"] {
	background: rgba(255, 255, 255, 0.18);
}

/* ---- where the image sits in the row ------------------------------------- */

/*
 * The default above is a small square holding a whole logo, which is why it
 * uses object-fit: contain. The alternatives below crop to fill instead — a
 * photograph in a letterbox with white bands down its sides is worse than a
 * photograph cropped.
 */

/* ---- 2 & 3. Filling one end of the row ----------------------------------- */

/*
 * The same picture, on whichever end reads better against what is beside the
 * block. Left puts it before the date, so the row opens on the image; right
 * ends on it, so the eye meets the date and the wording first.
 */
[data-layout="rows"][data-row-image="left"] .npx-card,
[data-layout="rows"][data-row-image="right"] .npx-card {
	align-items: stretch;
}

[data-layout="rows"][data-row-image="left"] .npx-card {
	grid-template-columns: 12rem 4.5rem minmax(0, 1fr);
}

[data-layout="rows"][data-row-image="right"] .npx-card {
	grid-template-columns: 4.5rem minmax(0, 1fr) 12rem;
}

/*
 * Placed explicitly: the markup runs date, image, body, so left to itself the
 * grid would fill the columns in that order and neither arrangement would come
 * out right.
 *
 * The row has to be pinned as well as the column. Auto-placement never moves
 * backwards, so sending the image to column 3 and then the body to column 2
 * put the body on a second row — the date sat alone at the top with the
 * wording stranded below it.
 */
[data-layout="rows"][data-row-image="left"] .npx-card__aside {
	grid-row: 1;
	grid-column: 1;
}

[data-layout="rows"][data-row-image="left"] .npx-card__datebox {
	grid-row: 1;
	grid-column: 2;
	align-self: center;
}

[data-layout="rows"][data-row-image="left"] .npx-card__main {
	grid-row: 1;
	grid-column: 3;
	align-self: center;
}

[data-layout="rows"][data-row-image="right"] .npx-card__datebox {
	grid-row: 1;
	grid-column: 1;
	align-self: center;
}

[data-layout="rows"][data-row-image="right"] .npx-card__main {
	grid-row: 1;
	grid-column: 2;
	align-self: center;
}

[data-layout="rows"][data-row-image="right"] .npx-card__aside {
	grid-row: 1;
	grid-column: 3;
}

[data-layout="rows"][data-row-image="left"] .npx-card__aside,
[data-layout="rows"][data-row-image="right"] .npx-card__aside {
	width: auto;
	min-height: 5.5rem;
	border: 0;
	border-radius: 0;
	aspect-ratio: auto;
}

/*
 * The negative margins pull the picture out through the row's own padding so
 * it meets the separator above and below and the edge of the row beside it.
 * Left inside the padding it read as a floating thumbnail rather than as the
 * end of the row. Only the outer edge is released: the inner one keeps the
 * column gap, which is the breathing space before the date or after the body.
 */
[data-layout="rows"][data-row-image="left"] .npx-card__aside {
	margin: -0.9rem 0 -0.9rem -0.75rem;
}

[data-layout="rows"][data-row-image="right"] .npx-card__aside {
	margin: -0.9rem -0.75rem -0.9rem 0;
}

/*
 * Laid over the block rather than sitting in it. `height: 100%` against a
 * parent whose own height is being decided by the grid resolves to auto — the
 * image's intrinsic height — so the picture ended up setting the row height
 * instead of filling it. Out of flow, the block has nothing to measure and
 * takes the height the row already has.
 */
[data-layout="rows"][data-row-image="left"] .npx-card__image,
[data-layout="rows"][data-row-image="right"] .npx-card__image {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	padding: 0;
}

/* ---- 4. Behind the whole row --------------------------------------------- */

/* The date block stays clear of the picture. */
[data-layout="rows"][data-row-image="background"] .npx-card {
	/*
	 * The date column is as wide as the row is tall, so it reads as a square
	 * block rather than a narrow strip, and the gap goes so the picture starts
	 * where that block ends. The body keeps its own padding for the breathing
	 * room the gap used to provide.
	 */
	grid-template-columns: 8rem minmax(0, 1fr);
	gap: 0;
	min-height: 8rem;
	overflow: hidden;
}

/*
 * Taken out of the grid and stretched over the row. The left offset is the
 * card's own padding plus the date column plus the gap, which is where the
 * body starts — absolute positioning measures from the border edge, so the
 * padding has to be counted back in.
 */
[data-layout="rows"][data-row-image="background"] .npx-card__aside {
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	left: calc(0.75rem + 8rem);
	width: auto;
	border: 0;
	border-radius: 0;
	background: var(--npx-ink);
	aspect-ratio: auto;
}

[data-layout="rows"][data-row-image="background"] .npx-card__image {
	object-fit: cover;
	padding: 0;
}

/*
 * The strengths, on their own setting rather than the Cards one: there the
 * veil is decoration and defaults to none, here the wording sits on the
 * picture, so the default has to be a veil that works.
 *
 * Qualified by the layout, and that is load-bearing rather than tidiness. Both
 * scales write the same token, `data-row-overlay` is on every block whatever
 * its view, and these rules come later in the file — so unqualified they beat
 * `[data-overlay]` on a tie and every Cover and Cards strength painted this
 * scale's medium instead of the one that was chosen.
 */
[data-layout="rows"][data-row-overlay="light"] {
	--npx-cover-veil: rgba(27, 29, 31, 0.62);
	--npx-cover-veil-hover: rgba(27, 29, 31, 0.7);
}

[data-layout="rows"][data-row-overlay="medium"] {
	--npx-cover-veil: rgba(27, 29, 31, 0.73);
	--npx-cover-veil-hover: rgba(27, 29, 31, 0.81);
}

[data-layout="rows"][data-row-overlay="strong"] {
	--npx-cover-veil: rgba(27, 29, 31, 0.85);
	--npx-cover-veil-hover: rgba(27, 29, 31, 0.91);
}

[data-layout="rows"][data-row-overlay="very-strong"] {
	--npx-cover-veil: rgba(27, 29, 31, 0.94);
	--npx-cover-veil-hover: rgba(27, 29, 31, 0.97);
}

/* "None" leaves the picture alone; the wording keeps only its text-shadow. */
[data-layout="rows"][data-row-image="background"]:not([data-row-overlay="none"]) .npx-card__aside::after {
	content: "";
	position: absolute;
	inset: 0;
	background: var(--npx-cover-veil);
	transition: background-color 0.2s;
}

[data-layout="rows"][data-row-image="background"] .npx-card:hover .npx-card__aside::after {
	background: var(--npx-cover-veil-hover);
}

/* The row's own hover fill would be behind the picture, so it does nothing. */
[data-layout="rows"][data-row-image="background"] .npx-card:hover,
[data-layout="rows"][data-row-image="background"][data-hover="accent"] .npx-card:hover,
[data-layout="rows"][data-row-image="background"][data-hover="custom"] .npx-card:hover {
	background: transparent;
}

/*
 * The body starts exactly where the picture does, so without this the wording
 * is pressed against the edge of the image. Rows sets the body's padding to 0
 * because normally it has the grid gap to its left; over a picture there is no
 * gap, only the picture.
 */
[data-layout="rows"][data-row-image="background"] .npx-card__main {
	position: relative;
	z-index: 1;
	padding: 0.15rem 1.1rem;
	text-shadow: 0 1px 4px rgba(10, 12, 18, 0.55);
}

[data-layout="rows"][data-row-image="background"] .npx-card__title,
[data-layout="rows"][data-row-image="background"] .npx-card__trigger,
[data-layout="rows"][data-row-image="background"] .npx-card__dates,
[data-layout="rows"][data-row-image="background"] .npx-card__time {
	color: #fff;
}

[data-layout="rows"][data-row-image="background"] .npx-card__place {
	color: rgba(255, 255, 255, 0.82);
}

[data-layout="rows"][data-row-image="background"] .npx-card__type {
	color: var(--npx-accent-lift);
}

/*
 * The chip is white whatever is behind the row, so its text is stated outright
 * rather than taken from the ink scale. On a dark section that scale is white,
 * and this rule sits after the one that flips it — the chips came out white on
 * white.
 */
[data-layout="rows"][data-row-image="background"] .npx-badge--project {
	background: #fff;
	color: #141618;
	text-shadow: none;
}

/*
 * The date block sits outside the picture, so it keeps the page's own ink and
 * gets a surface of its own to sit on. The outline appears on hover: on a
 * white page the panel alone says nothing until then.
 */
[data-layout="rows"][data-row-image="background"] .npx-card__datebox {
	/*
	 * A full-height column, not a panel floating in the middle of the row.
	 * `align-self` overrides the row's own centring, and the negative margins
	 * pull it through the row's vertical padding so it meets the separators
	 * above and below — the same bleed the picture beside it uses. The date
	 * itself stays centred inside that column.
	 */
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-self: stretch;
	margin: -0.9rem 0;
	padding: 0.45rem 0.3rem;
	border: 1px solid transparent;
	border-radius: 0;
	background: var(--npx-bg);
	transition: border-color 0.2s;
}

[data-layout="rows"][data-row-image="background"] .npx-card:hover .npx-card__datebox {
	border-color: var(--npx-hover-line);
}

/* ---- narrow containers --------------------------------------------------- */

@container (max-width: 640px) {
	[data-layout="rows"] .npx-card {
		grid-template-columns: 3.5rem minmax(0, 1fr);
		gap: 0 0.9rem;
	}

	[data-layout="rows"] .npx-card__aside {
		display: none;
	}

	/*
	 * The two-column fallback above is written for the default row, and the
	 * end-filling modes outrank it — they carry an extra attribute, so their
	 * three-column template and their pinned columns survived down here and
	 * left the row with a dead 12rem gutter where the hidden picture used to
	 * be, with the wording crushed into what was left.
	 *
	 * A container query adds no specificity of its own, so the override has to
	 * match theirs selector for selector.
	 */
	[data-layout="rows"][data-row-image="left"] .npx-card,
	[data-layout="rows"][data-row-image="right"] .npx-card {
		grid-template-columns: 3.5rem minmax(0, 1fr);
	}

	[data-layout="rows"][data-row-image="left"] .npx-card__datebox,
	[data-layout="rows"][data-row-image="right"] .npx-card__datebox {
		grid-column: 1;
	}

	[data-layout="rows"][data-row-image="left"] .npx-card__main,
	[data-layout="rows"][data-row-image="right"] .npx-card__main {
		grid-column: 2;
	}

	/*
	 * With the picture gone the wording is back on the page's own background,
	 * so the light colours it was given have to go with it.
	 */
	[data-layout="rows"][data-row-image="background"] .npx-card__main {
		text-shadow: none;
	}

	[data-layout="rows"][data-row-image="background"] .npx-card__title,
	[data-layout="rows"][data-row-image="background"] .npx-card__trigger {
		color: var(--npx-ink);
	}

	[data-layout="rows"][data-row-image="background"] .npx-card__dates,
	[data-layout="rows"][data-row-image="background"] .npx-card__time,
	[data-layout="rows"][data-row-image="background"] .npx-card__place {
		color: var(--npx-ink-3);
	}

	[data-layout="rows"][data-row-image="background"] .npx-card__type {
		color: var(--npx-accent);
	}

	[data-layout="rows"][data-row-image="background"] .npx-badge--project {
		background: var(--npx-bg-3);
		color: var(--npx-ink-2);
	}

	[data-layout="rows"] .npx-card__day {
		font-size: 1.25rem;
	}
}

/* --------------------------------------------------------------- Buttons */

.npx-events .npx-button,
.npx-modal .npx-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.32rem;
	padding: 0.4rem 0.75rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius-sm);
	background: var(--npx-bg);
	color: var(--npx-ink-2);
	font: inherit;
	font-size: 0.78rem;
	font-weight: 600;
	line-height: 1.4;
	text-decoration: none;
	cursor: pointer;
	transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.npx-events .npx-button:hover,
.npx-events .npx-button:focus,
.npx-modal .npx-button:hover,
.npx-modal .npx-button:focus {
	border-color: var(--npx-ink-3);
	background: var(--npx-bg-2);
	color: var(--npx-ink-2);
	text-decoration: none;
}

.npx-events .npx-button:focus-visible,
.npx-modal .npx-button:focus-visible {
	outline: 2px solid var(--npx-accent);
	outline-offset: 2px;
}

/*
 * The Details button opens the preview too, so it also catches the focus
 * handed back when the preview closes — see the card ring above.
 *
 * `outline: none`, not merely skipping the rule above: leave the outline
 * undeclared and the browser draws its own default focus ring instead, which
 * is the same border in a different hand. Measured: 1px auto, still visible.
 */
.npx-events .npx-button[data-npx-quiet-focus]:focus-visible,
.npx-modal .npx-button[data-npx-quiet-focus]:focus-visible {
	outline: none;
}

/*
 * The first link of an event is its primary action: solid at rest, dropping to
 * the outline on hover — the inverse of the secondary buttons, as in HAL.
 */
.npx-events .npx-button--primary,
.npx-modal .npx-button--primary {
	border-color: var(--npx-accent);
	background: var(--npx-accent);
	color: #fff;
	font-weight: 700;
}

.npx-events .npx-button--primary:hover,
.npx-events .npx-button--primary:focus,
.npx-modal .npx-button--primary:hover,
.npx-modal .npx-button--primary:focus {
	border-color: var(--npx-accent);
	background: var(--npx-bg);
	color: var(--npx-accent);
}

/*
 * The theme paints content links with an !important accent colour, so a link
 * styled as a button comes out amber-on-amber and unreadable. Specificity
 * cannot beat !important, so these four declarations match it — kept to the
 * colour alone, and only for anchors inside a view.
 */
.npx-events a.npx-button {
	color: var(--npx-ink-2) !important;
}

.npx-events a.npx-button:hover,
.npx-events a.npx-button:focus {
	color: var(--npx-ink-2) !important;
}

.npx-events a.npx-button--primary {
	color: #fff !important;
}

.npx-events a.npx-button--primary:hover,
.npx-events a.npx-button--primary:focus {
	color: var(--npx-accent) !important;
}

/*
 * The card title is a link now, which walks it into the fight above: the theme
 * paints content links with an !important accent colour, and specificity does
 * not beat !important. Four declarations, matching the four the title sets for
 * itself, kept to the colour and the underline and scoped to anchors inside a
 * view so nothing else is touched.
 *
 * The underline on hover is left to the rule that owns it — that selector is
 * more specific than this one, so it still wins where it applies.
 */
.npx-events a.npx-card__trigger,
.npx-events a.npx-card__away {
	color: inherit !important;
	text-decoration: none;
}

.npx-events .npx-card:hover a.npx-card__trigger,
.npx-events a.npx-card__trigger:focus-visible {
	color: var(--npx-accent) !important;
}

.npx-events[data-layout="cover"] a.npx-card__trigger {
	color: rgba(255, 255, 255, 0.82) !important;
}

.npx-events[data-layout="cover"] .npx-card:hover a.npx-card__trigger,
.npx-events[data-layout="cover"] a.npx-card__trigger:focus-visible {
	color: #fff !important;
}

/*
 * The lead card's title goes somewhere rather than opening something, so it
 * gets the underline a link deserves only when the pointer is on it.
 */
.npx-events a.npx-card__away:hover,
.npx-events a.npx-card__away:focus-visible {
	text-decoration: underline;
	text-decoration-color: var(--npx-accent);
	text-underline-offset: 2px;
}

/* ----------------------------------------------------------------- Modal */

.npx-modal {
	position: fixed;
	z-index: 2147483000;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
}

.npx-modal__overlay {
	position: absolute;
	inset: 0;
	background: var(--npx-overlay);
}

.npx-modal__dialog {
	position: relative;
	width: 100%;
	max-width: 720px;
	max-height: calc(100vh - 48px);
	overflow-y: auto;
	border-radius: var(--npx-radius);
	background: var(--npx-bg);
	box-shadow: var(--npx-shadow-lift);
}

.npx-modal__close {
	position: absolute;
	z-index: 3;
	top: 12px;
	right: 12px;
	display: flex;
	width: 36px;
	height: 36px;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 1px solid var(--npx-line);
	border-radius: 50%;
	background: var(--npx-bg);
	color: var(--npx-ink-2);
	cursor: pointer;
	box-shadow: var(--npx-shadow);
	transition: border-color 0.15s, background 0.15s;
}

.npx-modal__close:hover {
	border-color: var(--npx-ink-3);
	background: var(--npx-bg-2);
}

.npx-modal__close:focus-visible {
	outline: 2px solid var(--npx-accent);
	outline-offset: 2px;
}

.npx-modal__close-icon {
	font-size: 1.5rem;
	line-height: 1;
}

/*
 * The preview shows the visual whole — no crop. Intrinsic sizing keeps the
 * aspect ratio at every size, and the height cap stops a square poster from
 * pushing the event's details below the fold.
 */
.npx-preview__media {
	display: flex;
	align-items: center;
	justify-content: center;
	margin: 0;
	padding: 0;
	background: var(--npx-bg-3);
}

.npx-preview__image {
	display: block;
	width: auto;
	max-width: 100%;
	height: auto;
	max-height: 46vh;
}

.npx-preview__body {
	padding: 1.5rem 1.75rem 1.75rem;
}

.npx-preview__badges {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
	margin: 0 0 0.9rem;
}

.npx-badge {
	display: inline-flex;
	align-items: center;
	padding: 0.24rem 0.5rem;
	border-radius: 4px;
	font-size: 0.66rem;
	font-weight: 800;
	letter-spacing: 0.07em;
	line-height: 1.3;
	text-transform: uppercase;
	white-space: nowrap;
}

.npx-badge--type {
	background: var(--npx-accent-soft);
	color: var(--npx-accent-text);
}

/*
 * Smaller than the type badge. These are secondary labels and there can be six
 * of them on one card, so they read better as a quiet cluster than as six
 * things competing with the title. This is the size Rows already used.
 */
.npx-badge--project {
	padding: 0.18rem 0.42rem;
	background: var(--npx-bg-3);
	color: var(--npx-ink-2);
	font-size: 0.6rem;
	letter-spacing: 0.06em;
}

/*
 * On a card the chip sits on the grey body, where the neutral fill above would
 * be the same colour: it inverts to white so the chips stay legible as chips.
 */
[data-layout="cards"] .npx-card__projects .npx-badge--project,
[data-layout="rows"] .npx-card__projects .npx-badge--project {
	background: var(--npx-bg);
}

.npx-badge--external {
	background: var(--npx-blue-soft);
	color: var(--npx-blue);
}

.npx-badge--numpex {
	background: var(--npx-bg-3);
	color: var(--npx-ink-2);
}

.npx-modal .npx-preview__title {
	margin: 0 0 1.1rem;
	color: var(--npx-ink);
	font-size: 1.5rem;
	font-weight: 600;
	letter-spacing: -0.01em;
	line-height: 1.25;
}

.npx-preview__meta {
	margin: 0 0 1.1rem;
	padding: 0.9rem 0;
	border-top: 1px solid var(--npx-line);
	border-bottom: 1px solid var(--npx-line);
}

.npx-preview__meta-row {
	display: flex;
	gap: 1rem;
	padding: 0.2rem 0;
}

.npx-preview__meta dt {
	flex: none;
	width: 72px;
	margin: 0;
	color: var(--npx-ink-3);
	font-size: 0.7rem;
	font-weight: 800;
	letter-spacing: 0.07em;
	line-height: 1.7;
	text-transform: uppercase;
}

.npx-preview__meta dd {
	margin: 0;
	color: var(--npx-ink);
	font-size: 0.95rem;
	line-height: 1.5;
}

.npx-preview__text {
	margin: 0 0 1.25rem;
	color: var(--npx-ink-2);
	font-size: 0.95rem;
	line-height: 1.65;
}

/*
 * Left by default in the markup, so the row lines up with the wording above
 * it; the setting moves it. Centring is the placement's choice rather than a
 * fixed rule because it depends on how many links an event carries — one
 * button centred reads as a call to action, four centred read as a ragged
 * cluster.
 */
.npx-preview__actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	margin: 0;
}

[data-preview-actions="center"] .npx-preview__actions {
	justify-content: center;
}

[data-preview-actions="right"] .npx-preview__actions {
	justify-content: flex-end;
}

/* ------------------------------------------------------------ Responsive */

/*
 * Column count follows the container, not the viewport (see above).
 *
 * These rules target the grid, not .npx-events: an element cannot query its
 * own container, so a rule that both declares the container and is styled by
 * the query never matches. The grid is a child, so it queries .npx-events.
 */
@container (max-width: 900px) {
	.npx-events__grid {
		--npx-cols: 2;
	}

	.npx-events--cols-1 .npx-events__grid {
		--npx-cols: 1;
	}
}

@container (max-width: 560px) {
	.npx-events__grid {
		--npx-cols: 1;
	}

	/*
	 * One column needs less air between cards than three do, but only where
	 * the spacing was left at its default — a placement that asked for none,
	 * or for large, means it.
	 */
	[data-gap="normal"] .npx-events__grid {
		--npx-gap: 1.25rem;
	}

	/*
	 * Narrow column. The four pills no longer fit on one line here, so giving
	 * the count a row of its own would make three. Dropping the group's box
	 * lets all five items flow as one wrapping run, and the count then fills
	 * whatever space is left on the pills' last row — two rows, not three.
	 *
	 * There is a floor: below roughly 245px of inner width, four readable
	 * labels plus the count genuinely cannot fit in two rows, and it becomes
	 * three. Turning the count off in the element is the way out of that.
	 */
	.npx-filters {
		justify-content: center;
		column-gap: 0.5rem;
	}

	.npx-filters__groups {
		display: contents;
	}

	.npx-filters__status {
		padding-right: 0;
	}

	/*
	 * A panel this wide cannot hang off its own pill here — there is no room
	 * to the right of it — so it spans the bar instead, which means dropping
	 * the pill as the anchor.
	 *
	 * The bar has to be the anchor that replaces it, explicitly. Without that
	 * the panel measured `top: 100%` against `.npx-events`, which is a
	 * containing block whether or not it is positioned: `container-type`
	 * carries layout containment with it. The options opened below the entire
	 * agenda, a screen away from the pill that had just been pressed.
	 */
	.npx-filter {
		position: static;
	}

	.npx-filter__panel,
	.npx-filter__panel--dates {
		right: 0;
		left: 0;
		min-width: 0;
		max-width: none;
	}

	.npx-chipset__options--months {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}

	.npx-chipset--range {
		grid-template-columns: 1fr;
	}
}

@media (max-width: 640px) {
	.npx-modal {
		padding: 0;
	}

	.npx-modal__dialog {
		height: 100%;
		max-width: none;
		max-height: 100vh;
		border-radius: 0;
	}

	.npx-preview__body {
		padding: 1.25rem 1.1rem 1.75rem;
	}

	.npx-preview__title {
		font-size: 1.3rem;
	}
}

@media (prefers-reduced-motion: reduce) {
	.npx-card,
	.npx-card__datebox,
	.npx-card__dates,
	.npx-chip__label,
	.npx-filter__trigger,
	.npx-filter__trigger::after,
	.npx-events .npx-button,
	.npx-modal .npx-button {
		transition: none;
	}

	/*
	 * Every selector here has to weigh as much as the one it is cancelling.
	 * A media query adds nothing to specificity, so the three plain selectors
	 * this block used to carry — `.npx-card:hover` and the two date elements —
	 * lost outright to `[data-date-motion="lift"] .npx-card:hover
	 * .npx-card__datebox`, and the date went on lifting for readers who had
	 * asked their system for less motion. Measured with the setting on: the
	 * date still moved 3px, and still grew by 7%.
	 *
	 * The attribute selectors below match without naming a value, which weighs
	 * the same as naming one and covers every value at once — including any
	 * motion added later, which would otherwise arrive with the same hole.
	 */
	.npx-card:hover,
	[data-card-motion] .npx-card:not(.npx-card--lead):hover,
	.npx-card:hover .npx-card__datebox,
	.npx-card:hover .npx-card__dates,
	[data-date-motion] .npx-card:hover .npx-card__datebox,
	[data-date-motion] .npx-card:hover .npx-card__dates,
	[data-layout|="timeline"][data-card-motion] .npx-card:hover .npx-card__datebox,
	[data-layout|="timeline"][data-card-motion] .npx-card:hover .npx-card__main > * {
		transform: none;
	}

	/*
	 * The ring stays — it is a mark, not a movement, and it is the only thing
	 * telling a Timeline reader which event they are on. What goes is the
	 * opening of it.
	 */
	[data-layout|="timeline"] .npx-card:hover .npx-card__main::before {
		transform: none;
		animation: none;
		box-shadow: 0 0 0 5px rgba(var(--npx-dot-ring-rgb), 0.26);
	}

	/*
	 * Same trade on the line as on the ring: the halo is a mark and stays,
	 * the throw that opened it does not. The extra pixel of weight is not an
	 * animation at all — it arrives with the trail — so it is untouched here.
	 */
	[data-layout|="timeline"] .npx-card:hover .npx-card__main::after {
		animation: none;
		box-shadow: 0 0 0 3px var(--npx-spine-lit);
		opacity: 0.26;
	}

	[data-layout="rail"] .npx-card:hover::before {
		transform: none;
	}
}

/* Fallback for themes that do not define the WordPress screen-reader class. */
.npx-events .screen-reader-text,
.npx-modal .screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(1px, 1px, 1px, 1px);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
	padding: 0;
	margin: -1px;
}

/* ==========================================================================
 * The event's own page.
 *
 * Everything above styles a list of events. This styles one event, and it is
 * the only screen in the plugin that a theme hands a bare content column to —
 * no card, no grid, no container of ours. So this block has to supply what the
 * displays get for free: a measure, a rhythm, and somewhere for the page to
 * end.
 *
 * The vocabulary is deliberately the preview modal's, one size up. The modal
 * shows exactly this content in a smaller frame, and a reader who opens an
 * event from a card should arrive somewhere recognisable rather than somewhere
 * new — same rules above and below the facts, same uppercase micro-labels, same
 * buttons.
 * ========================================================================== */

.npx-event-single {
	/*
	 * The page's own container. Themes vary from a full-bleed column to a
	 * narrow one, and this has to look deliberate in both: `min()` takes the
	 * gutter when the column is wide and yields when it is already narrow.
	 */
	width: 100%;
	max-width: 58rem;
	margin-inline: auto;
	padding: clamp(1.75rem, 4vw, 3.5rem) clamp(1rem, 5vw, 2rem);
	color: var(--npx-ink);
	font-size: 1rem;
	line-height: 1.6;
}

.npx-event-single .npx-event {
	margin: 0;
}

/* --- the head ---------------------------------------------------------- */

.npx-event-single .npx-event__header {
	margin: 0 0 1.75rem;
}

.npx-event-single .npx-event__title {
	margin: 0;
	color: var(--npx-ink);
	/*
	 * Scales with the column rather than the viewport alone, so a long title in
	 * a narrow theme column does not arrive at the same size as a short one in
	 * a wide page. `balance` keeps a two-line title from leaving one word alone.
	 */
	font-size: clamp(1.7rem, 1.15rem + 2.1vw, 2.6rem);
	font-weight: 700;
	letter-spacing: -0.02em;
	line-height: 1.14;
	text-wrap: balance;
}

.npx-event-single .npx-event__badges {
	display: flex;
	flex-wrap: wrap;
	gap: 0.4rem;
	margin: 1rem 0 0;
	padding: 0;
	list-style: none;
}

/* --- the picture ------------------------------------------------------- */

.npx-event-single .npx-event__image {
	margin: 0 0 2rem;
	border-radius: var(--npx-radius);
	background: var(--npx-bg-3);
	overflow: hidden;
}

.npx-event-single .npx-event__image-img {
	display: block;
	width: 100%;
	height: auto;
	/*
	 * A banner and a portrait poster both land here. Capping the height stops
	 * a tall image from pushing the facts off the first screen, and `contain`
	 * keeps a wide banner whole rather than cropping its text.
	 */
	max-height: 26rem;
	object-fit: contain;
}

/* --- the facts --------------------------------------------------------- */

/*
 * Ruled above and below rather than boxed, which is the modal's treatment and
 * the one that survives a theme with its own opinions about panels. The grid
 * gives each fact its own column while there is room and stacks them when
 * there is not, without a media query having to guess where that is.
 */
.npx-event-single .npx-event__details {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
	gap: 1.15rem 2rem;
	margin: 0 0 2rem;
	padding: 1.3rem 0;
	/*
	 * The accent rather than the quiet grey the modal uses. On a page there is
	 * only one band of facts and it is the thing a reader came for, so the two
	 * rules are doing more work here than a hairline between rows in a list.
	 */
	border-top: 1px solid var(--npx-accent);
	border-bottom: 1px solid var(--npx-accent);
}

.npx-event-single .npx-event__detail {
	margin: 0;
	min-width: 0;
}

/* The same micro-label as the badges, so the page has one voice for labels. */
.npx-event-single .npx-event__details dt {
	margin: 0 0 0.35rem;
	color: var(--npx-ink-3);
	font-size: 0.66rem;
	font-weight: 800;
	letter-spacing: 0.07em;
	line-height: 1.3;
	text-transform: uppercase;
}

.npx-event-single .npx-event__details dd {
	margin: 0;
	color: var(--npx-ink);
	font-size: 1.05rem;
	font-weight: 600;
	line-height: 1.4;
	overflow-wrap: break-word;
}

.npx-event-single .npx-event__location--online {
	color: var(--npx-accent-text);
}

/* --- the description --------------------------------------------------- */

/*
 * The only part of the page whose markup is out of our hands: whatever the
 * editor produced. Held to a reading measure and given a rhythm through the
 * lobotomised owl, so any tag order comes out evenly spaced without every
 * element needing its own margin.
 */
.npx-event-single .npx-event__content {
	max-width: 65ch;
	margin: 0 0 2.25rem;
	color: var(--npx-ink-2);
	font-size: 1.02rem;
	line-height: 1.7;
}

.npx-event-single .npx-event__content > * {
	margin-top: 0;
	margin-bottom: 0;
}

.npx-event-single .npx-event__content > * + * {
	margin-top: 1.15em;
}

.npx-event-single .npx-event__content h2,
.npx-event-single .npx-event__content h3,
.npx-event-single .npx-event__content h4 {
	color: var(--npx-ink);
	font-weight: 700;
	letter-spacing: -0.01em;
	line-height: 1.3;
}

.npx-event-single .npx-event__content h2 { font-size: 1.4rem; }
.npx-event-single .npx-event__content h3 { font-size: 1.18rem; }
.npx-event-single .npx-event__content h4 { font-size: 1.04rem; }

/* A heading after prose needs more air above it than below. */
.npx-event-single .npx-event__content > * + h2,
.npx-event-single .npx-event__content > * + h3,
.npx-event-single .npx-event__content > * + h4 {
	margin-top: 2em;
}

.npx-event-single .npx-event__content ul,
.npx-event-single .npx-event__content ol {
	padding-left: 1.3em;
}

.npx-event-single .npx-event__content li + li {
	margin-top: 0.4em;
}

.npx-event-single .npx-event__content blockquote {
	padding-left: 1.1rem;
	border-left: 3px solid var(--npx-accent);
	color: var(--npx-ink-2);
	font-style: normal;
}

.npx-event-single .npx-event__content img {
	max-width: 100%;
	height: auto;
	border-radius: var(--npx-radius-sm);
}

.npx-event-single .npx-event__content a {
	color: var(--npx-accent-text);
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

/* Wide things must scroll inside themselves, never widen the page. */
.npx-event-single .npx-event__content table,
.npx-event-single .npx-event__content pre {
	display: block;
	max-width: 100%;
	overflow-x: auto;
}

/* --- the links --------------------------------------------------------- */

.npx-event-single .npx-event__links {
	display: flex;
	flex-wrap: wrap;
	gap: 0.6rem;
	margin: 0 0 2rem;
	padding: 0;
	list-style: none;
}

.npx-event-single .npx-event__links-item {
	margin: 0;
}

.npx-event-single .npx-event__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.32rem;
	padding: 0.55rem 1rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius-sm);
	background: var(--npx-bg);
	font: inherit;
	font-size: 0.88rem;
	font-weight: 600;
	line-height: 1.4;
	text-decoration: none;
	transition: border-color 0.15s, background 0.15s, color 0.15s;
}

/*
 * The theme paints content links with an `!important` accent colour, which a
 * link dressed as a button loses to — amber text on an amber fill. Specificity
 * cannot beat `!important`, so it is matched here, and only on the colour.
 * Same defence the displays already carry.
 */
.npx-event-single a.npx-event__link {
	color: var(--npx-ink-2) !important;
}

.npx-event-single .npx-event__link:hover,
.npx-event-single .npx-event__link:focus {
	border-color: var(--npx-ink-3);
	background: var(--npx-bg-2);
	text-decoration: none;
}

.npx-event-single .npx-event__link:focus-visible {
	outline: 2px solid var(--npx-accent);
	outline-offset: 2px;
}

/* The first link is the one the event wants read. */
.npx-event-single .npx-event__links-item:first-child .npx-event__link {
	border-color: var(--npx-accent);
	background: var(--npx-accent);
	font-weight: 700;
}

.npx-event-single .npx-event__links-item:first-child a.npx-event__link {
	color: #fff !important;
}

.npx-event-single .npx-event__links-item:first-child .npx-event__link:hover,
.npx-event-single .npx-event__links-item:first-child .npx-event__link:focus {
	background: var(--npx-bg);
}

.npx-event-single .npx-event__links-item:first-child a.npx-event__link:hover,
.npx-event-single .npx-event__links-item:first-child a.npx-event__link:focus {
	color: var(--npx-accent-text) !important;
}

/* --- the way back ------------------------------------------------------ */

.npx-event-single .npx-event__back {
	margin: 2.5rem 0 0;
	padding-top: 1.4rem;
	border-top: 1px solid var(--npx-line);
}

.npx-event-single .npx-event__back-link {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	font-size: 0.9rem;
	font-weight: 600;
	text-decoration: none;
}

.npx-event-single a.npx-event__back-link {
	color: var(--npx-ink-3) !important;
}

.npx-event-single a.npx-event__back-link:hover,
.npx-event-single a.npx-event__back-link:focus {
	color: var(--npx-accent-text) !important;
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

/* An arrow that belongs to the link rather than to the sentence. */
.npx-event-single .npx-event__back-link::before {
	content: "\2190";
	font-size: 1em;
	line-height: 1;
}

.npx-event-single .npx-event__back-link:focus-visible {
	outline: 2px solid var(--npx-accent);
	outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
	.npx-event-single .npx-event__link {
		transition: none;
	}
}

/*
 * A description written in the page builder arrives wrapped in the builder's
 * own column — and that column carries a gutter, which pushed the prose in from
 * the title and the fact band above it. The page looked indented for no reason
 * a reader could see.
 *
 * Only the outermost wrapper is unpadded, through a chain of direct children:
 * a genuine multi-column row further down is still the author's layout and
 * keeps its own gutters.
 */
.npx-event-single .npx-event__content > .wpb_column,
.npx-event-single .npx-event__content > .wpb_column > .uncol,
.npx-event-single .npx-event__content > .wpb_column > .uncol > .uncoltable,
.npx-event-single .npx-event__content > .wpb_column > .uncol > .uncoltable > .uncell,
.npx-event-single .npx-event__content > .wpb_column > .uncol > .uncoltable > .uncell > .uncont {
	width: 100%;
	max-width: none;
	padding-left: 0;
	padding-right: 0;
}

/*
 * The builder's own background follows its column. Left alone it paints a
 * white plate behind the prose that the rest of the page does not have.
 */
.npx-event-single .npx-event__content > .wpb_column > .uncol {
	background: transparent;
}

/* --- an event with no picture ------------------------------------------ */

/*
 * Forty per cent of a real calendar arrives with no image, and a page that
 * simply starts with a headline looks like it is missing something a reader
 * cannot name. The answer is not a stock photograph in the image slot: that
 * repeats across every one of those pages, says nothing about the event, and
 * teaches a reader that the slot is filler — which costs the events that do
 * have a real picture.
 *
 * So it is the header that changes, not the image. The band is built from the
 * plugin's own ink, tinted by the event's type, so the repetition carries a
 * signal instead of being wallpaper.
 */
/*
 * The tint arrives as a variable on the article, set from the event's type. The
 * default belongs in the `var()` calls below rather than as a declaration here:
 * declaring it on the header would override the value inherited from the
 * article — a child's own declaration beats what it inherits — and every event
 * would open in the accent no matter what type it was.
 */
.npx-event-single .npx-event--no-image .npx-event__header {
	position: relative;
	margin: 0 0 2rem;
	padding: clamp(2rem, 6vw, 3.5rem) clamp(1.25rem, 5vw, 2.5rem);
	border-radius: var(--npx-radius);
	/*
	 * One light in the corner rather than a wash, so the band has a direction.
	 * `color-mix` keeps the ground almost black whatever the tint is: a fully
	 * saturated hue across the whole panel would fight the page it opens.
	 */
	/*
	 * The project's colour IS the band, rather than a light thrown across a
	 * near-black one. These six are dark muted grounds: measured as a 34% glow
	 * on near-black, five of them differ from it by under 1.3:1, which is to
	 * say they would not be seen. As the ground they carry their own weight,
	 * and the highlight is plain white at a tenth so it works on all six.
	 */
	background:
		radial-gradient( 120% 140% at 88% 8%, rgba(255, 255, 255, 0.13), transparent 58% ),
		linear-gradient( 155deg, color-mix(in srgb, var(--npx-type-tint, #1d2126) 88%, #fff) 0%, var(--npx-type-tint, #1d2126) 62% );
	overflow: hidden;
	isolation: isolate;
}

/*
 * A faint rule texture — the same gesture that separates rows in the displays,
 * turned off the horizontal and taken almost to nothing. It gives the band a
 * material without giving it a subject.
 */
.npx-event-single .npx-event--no-image .npx-event__header::before {
	content: "";
	position: absolute;
	inset: -40%;
	z-index: -1;
	background-image: repeating-linear-gradient( 68deg, color-mix(in srgb, var(--npx-type-ink, #fff) 7%, transparent) 0 1px, transparent 1px 34px );
}

/* White on five of the six, near-black on the pale one; worked out in PHP
   against the exact colour rather than assumed. */
.npx-event-single .npx-event--no-image .npx-event__title {
	color: var(--npx-type-ink, #fff);
}

/*
 * A light island, which is the treatment the displays already give this chip on
 * a dark ground: near-black on a pale plate, rather than joining the flipped
 * scale. Every one of the six clears 13:1 diluted this far.
 */
.npx-event-single .npx-event--no-image .npx-badge--type {
	background: color-mix(in srgb, var(--npx-type-tint, var(--npx-accent)) 18%, #fff);
	color: #141618;
}

/*
 * The origin chip, on a page that has a picture and so no band. Without this it
 * is the only badge on the page with no plate at all — the type and project
 * chips are styled by the shared rules, this one only had the band's override.
 */
.npx-event-single .npx-badge--origin {
	background: var(--npx-bg-3);
	color: var(--npx-ink-2);
}

/*
 * The secondary chips borrow the ink instead of assuming white, so they follow
 * the title onto the one pale ground where white would not do.
 */
.npx-event-single .npx-event--no-image .npx-badge--project,
.npx-event-single .npx-event--no-image .npx-badge--origin {
	background: color-mix(in srgb, var(--npx-type-ink, #fff) 16%, transparent);
	color: var(--npx-type-ink, #fff);
}

/* The band already ends the header, so the badges need no gap beneath them. */
.npx-event-single .npx-event--no-image .npx-event__badges {
	margin-bottom: 0;
}

@media (prefers-contrast: more) {
	.npx-event-single .npx-event--no-image .npx-event__header::before {
		display: none;
	}
}

/* --- filter options as chips -------------------------------------------- */

/*
 * The same control the date panel already uses, brought to the taxonomy
 * filters. A ticked box says "on" in a corner of the row; a filled chip says it
 * with the whole option, which is what you want when the panel is a grid of
 * choices rather than a form.
 *
 * The input is styled, not replaced: it keeps its place in the accessibility
 * tree and its own keyboard behaviour, and only its default rendering goes.
 */
/*
 * One per row, not a wrapped grid. The chip is the right control — it says "on"
 * with the whole option instead of with a tick in the corner — but the options
 * are words of very different lengths, and a wrapped grid makes the eye hunt
 * for where the next one starts. A column gives every option the same left edge
 * and the same place to look for its count.
 */
/*
 * The panel supplies the inset; the list adds none of its own.
 *
 * Written against the panel as well as the list because the theme re-imposes a
 * list's default `padding-inline-start` — measured at 18px on the left and 0 on
 * the right, which is what made every chip sit further from the left edge of
 * the dropdown than from the right.
 */
.npx-events .npx-filter__panel .npx-filter__list {
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
	margin: 0;
	padding: 0;
}

.npx-events .npx-filter__item {
	position: relative;
	display: flex;
	min-width: 0;
}

.npx-events .npx-filter__item input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: 0;
	opacity: 0;
	pointer-events: none;
}

.npx-events .npx-filter__item label {
	display: flex;
	width: 100%;
	align-items: center;
	/* Label left, count right — the count is then always in one place. */
	justify-content: space-between;
	gap: 0.75rem;
	min-height: 30px;
	padding: 0 0.6rem;
	border: 1px solid var(--npx-line);
	border-radius: var(--npx-radius-sm);
	background: var(--npx-bg-2);
	color: var(--npx-ink-2);
	font-size: 0.78rem;
	font-weight: 600;
	line-height: 1.2;
	cursor: pointer;
	transition: border-color 0.15s, background 0.15s, color 0.15s;
}

.npx-events .npx-filter__item label:hover {
	border-color: var(--npx-ink-4);
	background: var(--npx-bg-2);
}

.npx-events .npx-filter__item label:has( input:checked ) {
	border-color: var(--npx-accent);
	background: var(--npx-accent);
	color: #fff;
	font-weight: 700;
}

.npx-events .npx-filter__item label:has( input:focus-visible ) {
	outline: 2px solid var(--npx-accent);
	outline-offset: 2px;
}

.npx-events .npx-filter__name {
	flex: 1 1 auto;
	min-width: 0;
}

/* The tally rides inside the chip, and has to stay legible once the chip
   fills — white at 60% on the amber, not the grey it uses on the plate. */
.npx-events .npx-filter__item label:has( input:checked ) .npx-filter__tally {
	color: rgba(255, 255, 255, 0.75);
}

.npx-events .npx-filter__item--empty label {
	opacity: 0.4;
}

@media (prefers-reduced-motion: reduce) {
	.npx-events .npx-filter__item label {
		transition: none;
	}
}

/*
 * The Clear lines up with the words on the chips above it, not with their
 * edges. A chip insets its label by its own border and padding, so the button —
 * which has neither — has to be given the same distance explicitly, written as
 * the sum it actually is rather than as a number that happens to match.
 */
.npx-events .npx-filter__panel > .npx-filter__clear {
	padding-left: calc(0.6rem + 1px);
}

/*
 * The filter bar sets its own text alignment rather than inheriting the
 * section's.
 *
 * A view dropped into a centre-aligned row was inheriting `text-align: center`
 * straight into the chips, so the same dropdown read left-aligned on one page
 * and centred on another — same markup, same widths, only the inherited value
 * differing. Alignment inside a control belongs to the control.
 *
 * `start` rather than `left` so a right-to-left site still reads correctly,
 * matching the logical properties used elsewhere in this stylesheet.
 */
.npx-events .npx-filters,
.npx-events .npx-filter__panel,
.npx-events .npx-filter__item label,
.npx-events .npx-filter__name {
	text-align: start;
}

/* --- a card has a size it stops growing past ---------------------------- */

/*
 * Cards and White cards carry a picture whose height follows their width, so a
 * card set to one or two columns keeps growing as the page does. Measured on a
 * 912px container: three columns gives a 288x367 card, two gives 444x432, and
 * one gives 912x678 with a 910x455 picture — one event filling most of a
 * laptop screen.
 *
 * `--npx-row-max` is the width the row wants: as many cards as the site asked
 * for, none wider than the cap, with the gaps between them. Everything that
 * belongs to the row is measured against it — the cards, the filter bar above
 * them and the arrows — so the whole block reads as one column of content
 * rather than a narrow row of cards under a full-width bar.
 *
 * Poster is measured the same way. It carries a picture that fills the card
 * rather than sitting above the text, so it grows the same way and needs the
 * same limit — most visibly at one column, where it spanned the whole row.
 *
 * At three columns and beyond it is wider than the container and never binds,
 * so nothing that already looked right moves.
 */
.npx-events {
	--npx-card-max: 26rem;
}

[data-layout="cards"],
[data-layout="white"],
[data-layout="poster"] {
	--npx-row-max: calc(
		var(--npx-cols) * var(--npx-card-max) + (var(--npx-cols) - 1) * var(--npx-gap)
	);
}

/*
 * The limit goes on the track, not on the card and not on the grid, and that
 * distinction is the whole fix. A card declares `container: npx-card /
 * inline-size`, so giving it a `max-width` makes its own size depend on the
 * size it is being asked to report — tried, and it collapsed the card to 2px.
 */
[data-layout="cards"] .npx-events__grid,
[data-layout="white"] .npx-events__grid,
[data-layout="poster"] .npx-events__grid {
	grid-template-columns: repeat( var(--npx-cols), minmax(0, var(--npx-card-max)) );
}

/*
 * Centred only when the row is not a carousel.
 *
 * `justify-content: center` on a scroll container whose content overflows
 * splits the overflow across both ends, so the first card sits outside the
 * visible area on the left — measured at -117px — while `scrollLeft` is already
 * 0, so nothing can bring it back. The first event could not be reached.
 */
[data-layout="cards"]:not([data-carousel="yes"]) .npx-events__grid,
[data-layout="white"]:not([data-carousel="yes"]) .npx-events__grid,
[data-layout="poster"]:not([data-carousel="yes"]) .npx-events__grid {
	justify-content: center;
}

/*
 * The filter bar belongs to the row, so it is the row's width.
 *
 * `.npx-events` is a block, so an auto margin centres this safely. The same
 * trick on the grid inside a carousel would not be safe — see below.
 */
[data-layout="cards"] .npx-filters,
[data-layout="white"] .npx-filters,
[data-layout="poster"] .npx-filters {
	max-width: var(--npx-row-max);
	margin-inline: auto;
}

/*
 * And the same limit for a carousel — applied to the strip, not to the slide.
 *
 * A carousel's whole contract is that exactly `--npx-cols` cards are visible at
 * once; its `grid-auto-columns` divides the strip by that number. Capping the
 * slide instead broke the contract: a 416px slide in a 912px strip showed two
 * and a bit cards where the site had asked for one. Capping the strip lets the
 * existing division do the rest untouched.
 *
 * No `margin-inline: auto` here, and that matters: this grid's parent is a flex
 * container, so an auto margin would make it shrink-to-fit, and the min-content
 * width of a scroll container is nearly zero. Tried, and it collapsed the strip
 * to 80px. The parent centres it instead — and with `align-items`, because that
 * parent is a column and its `justify-content` runs the other way.
 */
[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="cards"] .npx-events__grid,
[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="white"] .npx-events__grid,
[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="poster"] .npx-events__grid {
	/* A strip starts at its start, so the first card is never out of reach. */
	justify-content: start;
	/*
	 * `width: 100%` before the cap, and it is load-bearing. Centring this in
	 * its flex parent takes it out of `stretch`, and without a definite width
	 * a scroll container then shrinks to its min-content, which is nearly
	 * nothing — measured at 192px. Asking for the full width first gives the
	 * cap something to cut down.
	 */
	width: 100%;
	max-width: var(--npx-row-max);
}

[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="cards"] .npx-carousel,
[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="white"] .npx-carousel,
[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="poster"] .npx-carousel {
	align-items: center;
}

/*
 * A vertical carousel scrolls down rather than across, so its grid keeps real
 * tracks and takes the cap above like a static grid — but it was left packed
 * to one edge while the filter bar over it was centred.
 */
[data-carousel="yes"][data-carousel-direction="vertical"][data-layout="cards"] .npx-events__grid,
[data-carousel="yes"][data-carousel-direction="vertical"][data-layout="white"] .npx-events__grid,
[data-carousel="yes"][data-carousel-direction="vertical"][data-layout="poster"] .npx-events__grid {
	justify-content: center;
}

/* The arrows sit at the ends of the row they scroll, not at the ends of the
   space the row happens to sit in. */
/*
 * Not the floating placement. With `arrows="sides"` the nav is positioned over
 * the row rather than laid out above it, and its box is already described by
 * `left`/`right`; giving it a width as well fights that and moves the arrows
 * off the ends they are supposed to mark.
 */
[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="cards"]:not([data-arrows="sides"]) .npx-carousel__nav,
[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="white"]:not([data-arrows="sides"]) .npx-carousel__nav,
[data-carousel="yes"][data-carousel-direction="horizontal"][data-layout="poster"]:not([data-arrows="sides"]) .npx-carousel__nav {
	width: 100%;
	max-width: var(--npx-row-max);
}
