/**
 * Structure for every hosted site.
 *
 * Only layout, rhythm and shape live here; colour and type come from the theme file and then from
 * the organiser's brand, in that order (App\Domain\Sites\ThemeTokens). A theme is therefore a small
 * file, and a site's own colours survive switching between them.
 *
 * Everything below is written against the same handful of custom properties, and the ones a theme
 * does not know about are derived from the ones it does — so a theme written before this sheet
 * still renders, and a new one still only has to name colours.
 */

/*
 * A class that sets `display` beats the user-agent rule for `[hidden]`, so make it explicit.
 *
 * This sheet is full of `display: flex` on things the checkout hides and shows — the voucher lines,
 * the payment step — and without this the attribute is silently ignored and the buyer reads a
 * "paid by voucher: 0.00" line on a booking no voucher touched.
 */
[hidden] {
	display: none !important;
}

:root {
	--shell: 68rem;
	--shell-narrow: 44rem;
	--space: 1.25rem;
	--radius: 10px;

	--surface: #ffffff;
	--surface-sunken: #f6f7f9;
	--border: #e3e6ec;
	--text: #171a21;
	--text-muted: #5f6878;
	--accent: #4a4fdc;
	--on-accent: #ffffff;

	--font-heading: system-ui, sans-serif;
	--font-body: system-ui, sans-serif;

	/* Tokens a theme moves without a rule being edited (App\Domain\Sites\ThemeTokens). Defaults
	   here so a page still renders if a theme says nothing about them. */
	--heading-weight: 700;
	--heading-case: none;
	--heading-tracking: -0.015em;
	--button-radius: var(--radius);
	--shadow: none;

	/*
	 * Derived, not chosen.
	 *
	 * A theme names seven colours and three shapes; everything else on the page is worked out from
	 * those. That is what keeps a theme a small file, and it is why adding a card style here never
	 * means editing six theme files afterwards.
	 */
	--radius-lg: calc(var(--radius) * 1.9);
	--radius-pill: 999px;
	--accent-soft: color-mix(in srgb, var(--accent) 12%, var(--surface));
	--accent-line: color-mix(in srgb, var(--accent) 32%, var(--border));
	--surface-veil: color-mix(in srgb, var(--surface) 70%, transparent);
	--hairline: color-mix(in srgb, var(--border) 70%, transparent);
	--lift: 0 1px 2px color-mix(in srgb, var(--text) 6%, transparent),
		0 18px 40px -22px color-mix(in srgb, var(--text) 45%, transparent);
	--lift-strong: 0 2px 4px color-mix(in srgb, var(--text) 8%, transparent),
		0 30px 60px -28px color-mix(in srgb, var(--text) 55%, transparent);
	--ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

body {
	margin: 0;
	font-family: var(--font-body);
	font-size: 1rem;
	line-height: 1.65;
	color: var(--text);
	background: var(--surface);
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 {
	margin: 0 0 0.4em;
	font-family: var(--font-heading);
	font-weight: var(--heading-weight);
	line-height: 1.1;
	letter-spacing: var(--heading-tracking);
	text-transform: var(--heading-case);
	text-wrap: balance;
}

h1 { font-size: clamp(2.25rem, 6vw, 4rem); }
h2 { font-size: clamp(1.6rem, 3.8vw, 2.4rem); }
/* The step that was missing. h2 lands at 38px and h3 at 19px, so every heading between those two
   jobs — a card's title against a section's title — had to pick a side, and the hierarchy went flat
   at exactly the level a buyer scans. */
h3 { font-size: clamp(1.2rem, 1.6vw, 1.45rem); }
h4 { font-size: 1.0625rem; }

/* `pretty` rather than `balance` below a heading: balancing a paragraph evens its lines, which is
   wrong for running text; `pretty` only refuses to leave one word alone on the last line. */
p { margin: 0 0 1em; text-wrap: pretty; }
a { color: inherit; }
img { max-inline-size: 100%; block-size: auto; display: block; }
hr { border: none; }
:is(button, input, select, textarea) { font: inherit; color: inherit; }

/* Never rely on the browser default alone; a themed site is where it most often goes missing. */
:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 3px;
	border-radius: 3px;
}

/*
 * Out of sight, still read aloud.
 *
 * Used where a control's label would be noise on the screen and is essential to a screen reader —
 * the number picker beside an add-on already sits under its name, but a bare select announced as
 * "combo box" tells somebody nothing about which one.
 */
.visually-hidden {
	position: absolute;
	inline-size: 1px;
	block-size: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.skip {
	position: absolute;
	inset-inline-start: -9999px;
}

.skip:focus {
	inset-inline-start: 1rem;
	inset-block-start: 1rem;
	z-index: 20;
	padding: 0.6rem 1rem;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
}

.shell {
	/* The gutter narrows on a phone rather than staying at 40px: on a 360px screen a fixed gutter
	   is a ninth of the page spent on nothing, and the thing it takes it from is the poster. */
	inline-size: min(100% - clamp(2rem, 5vw, 2.5rem), var(--shell));
	margin-inline: auto;
}

.section { padding-block: clamp(2.25rem, 6vw, 4.5rem); }
.section--tight { padding-block: clamp(1rem, 2.5vw, 1.75rem); }

.muted { color: var(--text-muted); }

/*
 * A heading with a line under it and room around it.
 *
 * The listing pages are the ones people scroll, and a run of sections with nothing between them is
 * the thing that makes a site read as a document rather than as a shop.
 */
.section__head {
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	margin-block-end: clamp(1.25rem, 3vw, 2rem);
}

.section__title { margin: 0; }

.eyebrow {
	display: block;
	margin-block-end: 0.5rem;
	font-size: 0.78rem;
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--text-muted);
}

/* ============================================================================= masthead */

.masthead {
	position: sticky;
	inset-block-start: 0;
	z-index: 10;
	border-block-end: 1px solid var(--hairline);
	background: var(--surface-veil);
	backdrop-filter: blur(14px) saturate(1.4);
	-webkit-backdrop-filter: blur(14px) saturate(1.4);
}

.masthead__inner {
	display: flex;
	align-items: center;
	gap: 1rem 1.5rem;
	flex-wrap: wrap;
	padding-block: 0.9rem;
}

.brand { text-decoration: none; margin-inline-end: auto; }
.brand__logo { max-block-size: 2.5rem; inline-size: auto; }

.brand__name {
	font-family: var(--font-heading);
	font-size: 1.2rem;
	font-weight: 700;
	letter-spacing: -0.02em;
}

.nav {
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem 0.5rem;
}

.nav__link {
	padding: 0.4rem 0.7rem;
	border-radius: var(--radius-pill);
	text-decoration: none;
	color: var(--text-muted);
	font-size: 0.95rem;
	transition: color 140ms var(--ease), background-color 140ms var(--ease);
}

.nav__link:hover { color: var(--text); background: var(--accent-soft); }

/*
 * The language menu.
 *
 * It used to live in the footer, on the argument that the locale is resolved from the visitor
 * before the page renders (ADR-0005 §4), so this is a correction rather than a first choice. That
 * argument is still true and it is still the wrong place for it: a correction nobody can find is
 * not a correction. It sits in the header now, where every ticketing site people already use puts
 * it, and it is a `<details>` rather than a script — a language menu that needs JavaScript to open
 * is a language menu that fails for exactly the visitor most likely to need it.
 *
 * Each language is written in itself: somebody who cannot read the current language cannot read a
 * translated list of languages either.
 */
.langs { position: relative; }

.langs__button {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0.4rem 0.75rem;
	border: 1px solid var(--border);
	border-radius: var(--radius-pill);
	background: var(--surface);
	color: var(--text);
	font-size: 0.9rem;
	cursor: pointer;
	list-style: none;
}

.langs__button::-webkit-details-marker { display: none; }
.langs__button:hover { border-color: var(--accent-line); }

.langs__caret { transition: transform 140ms var(--ease); }
.langs[open] .langs__caret { transform: rotate(180deg); }

.langs__menu {
	position: absolute;
	inset-block-start: calc(100% + 0.5rem);
	inset-inline-end: 0;
	z-index: 12;
	min-inline-size: 11rem;
	padding: 0.35rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
	box-shadow: var(--lift-strong);
}

.langs__link,
.langs__current {
	display: block;
	padding: 0.45rem 0.7rem;
	border-radius: calc(var(--radius) * 0.7);
	font-size: 0.925rem;
	text-decoration: none;
	white-space: nowrap;
}

.langs__link { color: var(--text-muted); }
.langs__link:hover { color: var(--text); background: var(--accent-soft); }
.langs__current { color: var(--text); font-weight: 600; background: var(--accent-soft); }

/* ================================================================================== hero
 *
 * The band across the top of a page: a picture, a name, a sentence.
 *
 * With no picture it falls back to the site's own accent rather than to a grey box — a page should
 * be presentable before anybody has uploaded anything, because the first time an organiser looks at
 * their site is before they have uploaded anything.
 */

.hero {
	position: relative;
	display: flex;
	align-items: flex-end;
	overflow: hidden;
	isolation: isolate;
	padding-block: clamp(2rem, 6vw, 4.5rem);
	color: #fff;
	background:
		radial-gradient(120% 140% at 12% 0%, color-mix(in srgb, var(--accent) 85%, #fff) 0%, transparent 55%),
		linear-gradient(160deg, color-mix(in srgb, var(--accent) 92%, #000), color-mix(in srgb, var(--accent) 45%, #000));
}

/* `dvh`, not `vh`: on a phone `100vh` is the height with the browser's toolbars *hidden*, so a
   hero sized in it is cropped by exactly the toolbar that is on screen when the page loads. */
.hero--short { min-block-size: clamp(12rem, 26dvh, 17rem); }
.hero--tall { min-block-size: clamp(19rem, 52dvh, 30rem); }
.hero--full { min-block-size: min(84dvh, 46rem); }

/*
 * A height is for a photograph. With no photograph there is nothing to give room to.
 *
 * The default page a new site is provisioned with has a `tall` hero and no artwork, which rendered
 * as 480px of gradient carrying a venue name that the masthead already says forty pixels above it —
 * so the first screen of a ticket shop sold nothing, and on a phone the whole first screen went to
 * it. Sized to its own content instead, with enough padding to still read as a band.
 *
 * Only the height is dropped. The colour, the scrim and the type are untouched, so an organiser who
 * uploads a picture this afternoon gets the full band back with no setting to find.
 */
.hero:not(:has(.hero__art)) { min-block-size: 0; }

.hero__art {
	position: absolute;
	inset: 0;
	z-index: -2;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

.hero::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	background: linear-gradient(180deg, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.72) 100%);
}

.hero--center { text-align: center; }
.hero--center .hero__body { margin-inline: auto; }
.hero--center .hero__subtitle { margin-inline: auto; }

.hero__body { position: relative; max-inline-size: 46rem; }
.hero__title { margin: 0; color: #fff; text-shadow: 0 2px 30px rgba(0, 0, 0, 0.45); }

.hero__subtitle {
	margin: 0.9rem 0 0;
	max-inline-size: 38rem;
	font-size: clamp(1rem, 1.6vw, 1.2rem);
	color: rgba(255, 255, 255, 0.88);
}

/* ================================================================================ blocks */

.prose { max-inline-size: var(--shell-narrow); font-size: 1.05rem; }
.prose:last-child { margin-block-end: 0; }
.prose ul, .prose ol { padding-inline-start: 1.4rem; }
.prose table { inline-size: 100%; border-collapse: collapse; }
.prose th, .prose td { padding: 0.5rem 0.6rem; border-block-end: 1px solid var(--border); text-align: start; }

.block-heading { margin-block-end: 0.6em; }

.image { margin: 0; padding-inline: 0; }
.image--content img, .image--content figcaption { inline-size: min(100% - 2.5rem, var(--shell)); margin-inline: auto; }
.image--wide img { inline-size: min(100% - 1.5rem, 90rem); margin-inline: auto; }
.image--full img { inline-size: 100%; }
.image img { border-radius: var(--radius-lg); }
.image--full img { border-radius: 0; }

.image figcaption {
	margin-block-start: 0.6rem;
	font-size: 0.875rem;
	color: var(--text-muted);
}

.buttons { display: flex; flex-wrap: wrap; gap: 0.75rem; }

.button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
	min-block-size: 3rem;
	padding-inline: 1.5rem;
	border-radius: var(--button-radius);
	border: 1px solid var(--accent);
	background: var(--accent);
	color: var(--on-accent);
	font-weight: 600;
	text-decoration: none;
	cursor: pointer;
	transition: transform 120ms var(--ease), box-shadow 140ms var(--ease), filter 140ms var(--ease);
}

.button:hover { filter: brightness(1.08); box-shadow: var(--lift); }
.button:active { transform: translateY(1px); }

.button--secondary {
	background: transparent;
	color: var(--text);
	border-color: var(--border);
}

.button--secondary:hover { border-color: var(--accent-line); background: var(--accent-soft); filter: none; }

.button--block { inline-size: 100%; }

.divider {
	border-block-start: 1px solid var(--border);
	margin-block: 1rem;
}

.faq__item {
	border-block-end: 1px solid var(--hairline);
	padding-block: 1rem;
}

.faq__item summary {
	cursor: pointer;
	font-weight: 600;
	font-family: var(--font-heading);
	list-style: none;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
}

.faq__item summary::-webkit-details-marker { display: none; }

.faq__item summary::after {
	content: '+';
	font-size: 1.4rem;
	line-height: 1;
	color: var(--text-muted);
}

.faq__item[open] summary::after { content: '−'; }

.venue__address {
	font-style: normal;
	margin-block-end: 1rem;
}

.notice {
	padding: 1.1rem 1.35rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface-sunken);
	color: var(--text-muted);
}

/*
 * What coming here has got somebody.
 *
 * A card rather than a line in the margin: it is the answer to the question a loyalty scheme is
 * asking, and an answer nobody can find is a scheme nobody joins.
 */
.points {
	padding: 1.25rem 1.4rem;
	margin-block-end: 1.5rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
}

.points__head {
	display: flex;
	flex-wrap: wrap;
	gap: 1rem;
	align-items: center;
	justify-content: space-between;
}

.points__balance {
	margin-block: 0.25rem;
	font-size: 1.35rem;
	font-weight: 600;
}

/*
 * A night being rehearsed, said where a visitor cannot miss it.
 *
 * Warm rather than red: nothing is wrong, and an alarm would send an organiser looking for the
 * fault. It has to be louder than the page around it all the same, because the whole purpose of
 * the sentence is that nobody mistakes this page for the real shop.
 */
.notice--rehearsal {
	/* Mixed into the theme's own surface rather than stated outright, so a dark theme or an
	   organiser's own palette gets a banner that belongs to it. Amber is not a theme token
	   because no other element on a buyer's page has ever needed one. */
	border-color: color-mix(in srgb, #b45309 45%, var(--border));
	background: color-mix(in srgb, #b45309 10%, var(--surface));
	color: var(--text);
}

.notice--rehearsal strong {
	margin-inline-end: 0.35rem;
}

/* The organiser's own sentence under the refusal above it — an explanation, not a second alarm. */
.notice--quiet {
	margin-block-start: 0.6rem;
	border-style: dashed;
	background: transparent;
}

/* ================================================================================= chips */

.chip {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	padding: 0.3rem 0.7rem;
	border-radius: var(--radius-pill);
	font-size: 0.78rem;
	font-weight: 600;
	letter-spacing: 0.03em;
	line-height: 1.4;
	white-space: nowrap;
}

.chip--kind { background: var(--accent); color: var(--on-accent); }
.chip--quiet { background: var(--accent-soft); color: var(--text); }

.chip--gone {
	background: color-mix(in srgb, var(--text) 82%, transparent);
	color: var(--surface);
}

/* ============================================================================== the cover
 *
 * An event with no artwork still gets a poster: a hue taken from its name and its own initials set
 * large. Every organiser starts with nothing uploaded, and the first thing they see of their own
 * website should not look unfinished. The hue is a hash of the name, so it never reshuffles.
 */

.cover {
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	overflow: hidden;
	background: var(--surface-sunken);
	isolation: isolate;
}

.cover img {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

.cover--drawn {
	background:
		radial-gradient(110% 120% at 12% 8%, hsl(var(--art-h) 82% 62% / 0.95), transparent 58%),
		radial-gradient(110% 120% at 88% 92%, hsl(calc(var(--art-h) + 52) 76% 46% / 0.95), transparent 60%),
		linear-gradient(155deg, hsl(var(--art-h) 48% 24%), hsl(calc(var(--art-h) + 26) 58% 12%));
}

/*
 * Lettering on a drawn tile is a texture, not a headline.
 *
 * Three things share this box — the date, the category and these initials — and at 14cqi the
 * initials were the loudest of them on a card whose actual title sits underneath in 19px. The date
 * is what somebody scanning a programme is looking for, so the date wins and this recedes.
 *
 * It stays legible rather than disappearing: on a card with no artwork the tile is all there is,
 * and an empty coloured rectangle reads as a picture that failed to load.
 */
.cover__initials {
	font-family: var(--font-heading);
	font-size: clamp(1.75rem, 8cqi, 3.25rem);
	font-weight: 800;
	letter-spacing: 0.06em;
	color: rgba(255, 255, 255, 0.66);
	text-shadow: 0 2px 18px rgba(0, 0, 0, 0.3);
	user-select: none;
}

/* ============================================================================== events */

/*
 * `auto-fit`, not `auto-fill`.
 *
 * `auto-fill` reserves every track the measure has room for whether or not anything goes in it, so
 * a venue with two nights on sale got a three-track grid: two cards at 292px and a 292px hole where
 * the third would have been. `auto-fit` collapses the empty track and the two cards share the row.
 *
 * The hole is not a rare case. It is what every venue's programme looks like in its first week and
 * in its quiet month, which are exactly the weeks somebody is deciding whether this shop looks
 * finished.
 */
.events--cards {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
	gap: clamp(1rem, 2.2vw, 1.6rem);
}

/* The other end of the same correction: one card stretched across the whole measure is not a
   feature, it is a card that has lost its proportions. */
.events--cards > .event-card:only-child { max-inline-size: 26rem; }

.events--list { display: flex; flex-direction: column; gap: 0.75rem; }

.event-card {
	container-type: inline-size;
	position: relative;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow);
	background: var(--surface);
	text-decoration: none;
	transition: transform 160ms var(--ease), box-shadow 160ms var(--ease), border-color 160ms var(--ease);
}

.event-card:hover {
	border-color: var(--accent-line);
	box-shadow: var(--lift-strong);
	transform: translateY(-3px);
}

.event-card__art {
	position: relative;
	aspect-ratio: 3 / 2;
	inline-size: 100%;
}

.event-card__art::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(180deg, rgba(0, 0, 0, 0.35) 0%, transparent 38%, transparent 62%, rgba(0, 0, 0, 0.4) 100%);
}

/* The date, on the poster rather than beside it: it is the first thing anybody looks for. */
.event-card__when {
	position: absolute;
	inset-block-start: 0.75rem;
	inset-inline-start: 0.75rem;
	z-index: 2;
	display: grid;
	place-items: center;
	inline-size: 3.1rem;
	padding-block: 0.35rem;
	border-radius: calc(var(--radius) * 0.9);
	background: var(--surface);
	color: var(--text);
	line-height: 1.05;
	box-shadow: var(--lift);
}

.event-card__day { font-size: 1.35rem; font-weight: 700; font-family: var(--font-heading); font-variant-numeric: tabular-nums; }
.event-card__month { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); }

.event-card__kind {
	position: absolute;
	inset-block-start: 0.75rem;
	inset-inline-end: 0.75rem;
	z-index: 2;
}

.event-card__gone {
	position: absolute;
	inset-block-end: 0.75rem;
	inset-inline-start: 0.75rem;
	z-index: 2;
}

.event-card__body {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	padding: 1rem 1.1rem 0;
	min-inline-size: 0;
}

.event-card__name { margin: 0; font-size: 1.1rem; }
.event-card__meta { margin: 0; font-size: 0.9rem; color: var(--text-muted); }

.event-card__foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.9rem 1.1rem 1.1rem;
}

.event-card__price { margin: 0; font-weight: 700; font-variant-numeric: tabular-nums; }

.event-card__cta {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	font-size: 0.9rem;
	font-weight: 600;
	color: var(--accent);
}

.event-card__cta::after {
	content: '';
	inline-size: 0.5rem;
	block-size: 0.5rem;
	border-block-start: 2px solid currentColor;
	border-inline-end: 2px solid currentColor;
	transform: rotate(45deg);
	transition: transform 160ms var(--ease), translate 160ms var(--ease);
}

/* Mirrored rather than re-rotated: one transform to reason about, and it cannot point downwards. */
[dir="rtl"] .event-card__cta::after { transform: scaleX(-1) rotate(45deg); }
.event-card:hover .event-card__cta::after { translate: 3px 0; }
[dir="rtl"] .event-card:hover .event-card__cta::after { translate: -3px 0; }

.event-card--gone { opacity: 0.72; }

/* The list layout: the same card lying down, for a season of forty dates. */
.events--list .event-card { flex-direction: row; align-items: stretch; }
.events--list .event-card__art { inline-size: clamp(7rem, 22%, 12rem); aspect-ratio: auto; flex: none; }
.events--list .event-card__when { inset-block-start: 0.5rem; inset-inline-start: 0.5rem; inline-size: 2.75rem; }
.events--list .event-card__kind { display: none; }
.events--list .event-card__body { justify-content: center; padding: 1rem 1.25rem; }
.events--list .event-card__foot { flex-direction: column; align-items: flex-end; justify-content: center; gap: 0.3rem; padding-inline-end: 1.25rem; }

/*
 * Spotlight: the first date large, the rest beside it.
 *
 * A season usually has one thing it is actually selling, and a grid of identical cards is a grid
 * that says every night is the same night.
 */
.events--spotlight {
	display: grid;
	gap: clamp(1rem, 2.2vw, 1.6rem);
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
}

.events--spotlight .event-card--lead { grid-column: 1 / -1; }

/*
 * Three columns only when there is a lead card to build them around.
 *
 * The spotlight layout is two-plus-one: a large card taking two tracks and the rest beside it. That
 * shape needs exactly three tracks — but the lead card only appears once there are three or more
 * dates, and below that the same rule was still forcing three tracks onto two cards. The result was
 * two cards at 292px and a 292px hole, which is what every venue's programme looks like in its
 * first week and in its quiet month.
 *
 * With no lead, the `auto-fit` above takes over and the cards share the row.
 */
@media (min-width: 60rem) {
	.events--spotlight:has(.event-card--lead) { grid-template-columns: repeat(3, 1fr); }
	.events--spotlight .event-card--lead { grid-column: span 2; grid-row: span 2; }
	.events--spotlight .event-card--lead .event-card__art { aspect-ratio: 16 / 10; }
	.events--spotlight .event-card--lead .event-card__name { font-size: clamp(1.4rem, 2.4vw, 2rem); }
	.events--spotlight .event-card--lead .event-card__when { inline-size: 3.6rem; }
}

/* ========================================================================== event hero */

.event-hero {
	position: relative;
	display: flex;
	align-items: flex-end;
	min-block-size: clamp(17rem, 52dvh, 30rem);
	padding-block: clamp(2rem, 6vw, 4rem);
	overflow: hidden;
	isolation: isolate;
	color: #fff;
}

.event-hero .cover { z-index: -2; place-items: center end; }

/*
 * On a card the initials are the artwork; behind a title they are texture, and at card opacity
 * they compete with the name of the show. Pushed to the trailing edge and faded to a watermark.
 */
.event-hero .cover__initials {
	font-size: clamp(7rem, 28vw, 20rem);
	opacity: 0.14;
	padding-inline-end: 3vw;
	line-height: 0.85;
}

.event-hero::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	background:
		linear-gradient(180deg, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0.15) 35%, rgba(0, 0, 0, 0.82) 100%);
}

.event-hero__body { position: relative; }

.event-hero__name {
	margin: 0.6rem 0 0;
	color: #fff;
	text-shadow: 0 2px 30px rgba(0, 0, 0, 0.5);
}

.event-hero__facts {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem 0.75rem;
	margin: 1.25rem 0 0;
	padding: 0;
	list-style: none;
}

/* A night that used to be another night, said where the date is rather than in a banner. */
.event-hero__moved {
	margin-inline-start: 0.45rem;
	opacity: 0.75;
	font-size: 0.85em;
}

.event-hero__fact {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	padding: 0.5rem 0.9rem;
	border-radius: var(--radius-pill);
	border: 1px solid rgba(255, 255, 255, 0.22);
	background: rgba(0, 0, 0, 0.35);
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
	font-size: 0.925rem;
}

.event-hero__fact svg { inline-size: 1rem; block-size: 1rem; flex: none; opacity: 0.85; }
.event-hero__fact strong { font-weight: 700; }

/* ============================================================================= booking */

.event-about { max-inline-size: var(--shell-narrow); }

/*
 * The picker's own section.
 *
 * Given a surface of its own rather than dropped onto the page: buying is a different job from
 * reading about the evening, and the join between the two is where people lose their place.
 */
/*
 * What this site's own sticky masthead costs anything that sticks underneath it.
 *
 * Read by the seat picker, which is shared with somebody else's WordPress theme and with the
 * panel's box office and therefore cannot know what is above it. Without this the summary card —
 * the running total and the button that reserves the seats — parked itself behind the header.
 */
.booking { --seatmap-sticky-top: 5.25rem; }

.booking {
	padding: clamp(1rem, 2.5vw, 1.75rem);
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface);
	box-shadow: var(--shadow);
}

/*
 * Measure belongs to what is in it.
 *
 * Prose wants 60–75 characters and the listing measure suits a row of cards, but a seating plan is
 * the product on this page, and holding it to the width of a paragraph makes a buyer pan around a
 * diagram that would have fitted. So the booking surface is allowed past the shell on a screen wide
 * enough to be worth it, and is unchanged below that — a phone has no width to give it.
 */
@media (min-width: 78rem) {
	.shell:has(> .booking) { inline-size: min(100% - 2.5rem, 78rem); }
}

.booking .seatmap-widget { margin-block: 0; }

/* ============================================================================== footer */

.footer {
	margin-block-start: clamp(3rem, 8vw, 5rem);
	border-block-start: 1px solid var(--hairline);
	background: var(--surface-sunken);
}

/*
 * A section already ends with its own padding. Adding the footer's margin on top of it measured
 * 152px of nothing between the last card and the rule above the footer — a gap neither author
 * intended and which reads as the page having run out rather than having finished.
 *
 * Keyed off `main` rather than off the section, because the footer is a sibling of `main` and never
 * of the section inside it. The margin is kept for the case it was written for: a page whose last
 * block brings its own edge right up to the footer.
 */
main:has(> .section:last-child) + .footer { margin-block-start: 0; }

.footer__inner {
	display: flex;
	flex-wrap: wrap;
	gap: 1.5rem 2.5rem;
	justify-content: space-between;
	padding-block: clamp(2rem, 5vw, 3rem);
}

.footer__name { margin: 0; font-weight: 700; font-family: var(--font-heading); font-size: 1.05rem; }
.footer__tagline { margin: 0.2rem 0 0; color: var(--text-muted); font-size: 0.925rem; }
.nav--footer { flex-direction: column; gap: 0.15rem; }
.nav--footer .nav__link { padding-inline: 0; }

@media (prefers-reduced-motion: reduce) {
	* { transition: none !important; animation: none !important; }
	.event-card:hover { transform: none; }
}

/* ============================================================================ checkout */

.checkout__grid {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: clamp(1.5rem, 3vw, 2.5rem);
}

.checkout__form { flex: 1 1 24rem; min-inline-size: 0; }

.checkout__step {
	padding: clamp(1.1rem, 2.4vw, 1.6rem);
	margin-block-end: 1rem;
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface);
	box-shadow: var(--shadow);
}

.checkout__step h2 {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	font-size: 1.1rem;
	margin-block: 0 1.1rem;
}

.checkout__step h2::before {
	counter-increment: step;
	content: counter(step);
	display: grid;
	place-items: center;
	inline-size: 1.75rem;
	block-size: 1.75rem;
	border-radius: 50%;
	background: var(--accent);
	color: var(--on-accent);
	font-size: 0.9rem;
	font-weight: 700;
	flex: none;
}

.checkout__form { counter-reset: step; }

.field { margin-block-end: 1rem; }
.field:last-child { margin-block-end: 0; }

.field label {
	display: block;
	margin-block-end: 0.35rem;
	font-size: 0.9rem;
	font-weight: 600;
}

.field input {
	inline-size: 100%;
	min-block-size: 3rem;
	/* Never below 16px. Safari on iOS zooms the whole page into any field smaller than that, and
	   a checkout that jumps and stays zoomed is a checkout people abandon. */
	font-size: max(1rem, 16px);
	padding-inline: 0.9rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
	color: var(--text);
	transition: border-color 140ms var(--ease), box-shadow 140ms var(--ease);
}

.field input:focus {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}

.field__hint { margin: 0.35rem 0 0; font-size: 0.85rem; color: var(--text-muted); }
.field__error { margin: 0.35rem 0 0; font-size: 0.85rem; color: #b3261e; }

/* A payment method is a decision, so it gets a target big enough to be one. */
.pay {
	display: flex;
	align-items: flex-start;
	gap: 0.75rem;
	padding: 1rem 1.1rem;
	margin-block-end: 0.6rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	cursor: pointer;
	transition: border-color 140ms var(--ease), background-color 140ms var(--ease);
}

.pay:last-of-type { margin-block-end: 0; }
.pay:hover { border-color: var(--accent-line); }
.pay:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.pay input { margin-block-start: 0.35rem; accent-color: var(--accent); }
.pay span { display: block; }
.pay strong { display: block; }
.pay .muted { font-size: 0.9rem; }

.checkout__submit { margin-block-start: 1.25rem; }

.checkout__summary {
	flex: 1 1 19rem;
	min-inline-size: 0;
	padding: clamp(1.1rem, 2.4vw, 1.5rem);
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface-sunken);
	position: sticky;
	inset-block-start: 5.5rem;
}

.checkout__summary h2 {
	margin-block-start: 0;
	font-size: 0.78rem;
	text-transform: uppercase;
	letter-spacing: 0.12em;
	color: var(--text-muted);
}

/* The arrival window, on a timed-entry booking: the more important half of what was reserved,
   so it is set apart from the seat list rather than being one line in it. */
.checkout__entry {
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
	margin: 0 0 0.9rem;
	padding: 0.65rem 0.8rem;
	border-radius: var(--radius);
	background: var(--surface);
	border: 1px solid var(--hairline);
}

.checkout__entry strong {
	font-size: 0.72rem;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	color: var(--text-muted);
	font-weight: 600;
}

.summary-lines { margin: 0 0 0.75rem; padding: 0; list-style: none; }

.summary-lines li {
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	padding-block: 0.5rem;
	border-block-end: 1px solid var(--hairline);
	font-size: 0.925rem;
	font-variant-numeric: tabular-nums;
}

.summary-lines li:last-child { border-block-end: none; }

.summary-total {
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	margin: 0;
	padding-block-start: 0.7rem;
	border-block-start: 1px solid var(--border);
	font-weight: 700;
	font-size: 1.1rem;
	font-variant-numeric: tabular-nums;
}

.summary-hold {
	margin-block-start: 0.9rem;
	padding: 0.6rem 0.8rem;
	border-radius: calc(var(--radius) * 0.8);
	background: var(--accent-soft);
	font-size: 0.85rem;
}

/* Giving one ticket away, closed until somebody wants to. */
.give { flex-basis: 100%; margin-block-start: 0.4rem; }
.give > summary { cursor: pointer; font-size: 0.85rem; color: var(--text-muted); }
.give[open] > summary { margin-block-end: 0.6rem; }

.give__fields {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
	gap: 0 0.75rem;
}

/* The rest of a run, on the night's own page. */
.dates__title { margin: 0 0 0.75rem; font-family: var(--font-heading); font-size: 1.15rem; }

.dates { margin: 0; padding: 0; list-style: none; display: grid; gap: 0.4rem; }

.dates__link {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.65rem 0.9rem;
	border: 1px solid var(--hairline);
	border-radius: calc(var(--radius) * 0.8);
	color: inherit;
	text-decoration: none;
}

.dates__link:hover { border-color: var(--accent); }
.dates__state { font-size: 0.85rem; color: var(--text-muted); }

/*
 * Where, on a run that moves.
 *
 * Given the middle of the row and allowed to shrink: on a run in one building it is one repeated
 * line and should not shout, and on a tour it is the half of the row somebody is reading for.
 */
.dates__where {
	flex: 1 1 auto;
	min-width: 0;
	font-size: 0.85rem;
	color: var(--text-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.dates__item--gone .dates__link { opacity: 0.55; }

/* "and four more nights", on the card rather than as four more cards. */
.event-card__run { margin: 0.3rem 0 0; font-size: 0.85rem; color: var(--text-muted); }

/* The queue for a sold-out night. */
.waitlist {
	padding: clamp(1.25rem, 4vw, 2rem);
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface);
}

.waitlist__title { margin: 0 0 0.4rem; font-family: var(--font-heading); }
.waitlist__lead { margin: 0 0 1.25rem; color: var(--text-muted); }

.waitlist__fields {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
	gap: 0 1rem;
	align-items: start;
}

.waitlist__fields .field--narrow { max-inline-size: 8rem; }

.waitlist__note { margin-block-start: 0.9rem; font-size: 0.85rem; color: var(--text-muted); }

/*
 * What else is on the counter.
 *
 * One row per thing, the number picked on the right. Deliberately quiet: a buyer came for tickets,
 * and a programme shouted at is a programme nobody buys twice.
 */
.addons {
	display: flex;
	flex-direction: column;
	border: 1px solid var(--hairline);
	border-radius: var(--radius);
	overflow: hidden;
}

.addon {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem 1rem;
	align-items: center;
	justify-content: space-between;
	padding: 0.9rem 1rem;
	border-block-start: 1px solid var(--hairline);
}

.addon:first-child { border-block-start: none; }
.addon--gone { opacity: 0.55; }

.addon__what {
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
	flex: 1 1 14rem;
	min-inline-size: 0;
}

.addon__price { font-size: 0.9rem; }
.addon__price .muted { margin-inline-start: 0.35rem; }

.addon__gone,
.addon__fixed {
	font-size: 0.9rem;
	color: var(--text-muted);
	white-space: nowrap;
}

.addon__pick select {
	min-block-size: 2.5rem;
	padding-inline: 0.7rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
	color: var(--text);
	font: inherit;
}

/* A donation box is a number, and a short one: nobody types eight figures on purpose. */
.field--narrow input { max-inline-size: 10rem; }

/*
 * The receipt for what was not a ticket.
 *
 * Below the tickets and visibly apart from them, because nobody is admitted on a programme and a
 * list that mixed the two would invite somebody to try.
 */
.also {
	margin-block-start: clamp(1.5rem, 4vw, 2.5rem);
	padding: clamp(1rem, 3vw, 1.5rem);
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface);
}

.also__title { margin: 0 0 0.75rem; font-family: var(--font-heading); font-size: 1.05rem; }

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

.also__list li {
	display: flex;
	gap: 1rem;
	justify-content: space-between;
	padding-block: 0.4rem;
	border-block-start: 1px solid var(--hairline);
}

.also__list li:first-child { border-block-start: none; }

/*
 * "I have a code" — the box that stands where the seat plan will be.
 *
 * Deliberately the same furniture as the waiting-list card below it: both are a thing offered in
 * place of a booking, and a visitor who meets one and then the other should not feel they have
 * arrived at a different website.
 */
.unlock {
	padding: clamp(1.25rem, 4vw, 2rem);
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface);
}

.unlock .notice { margin-block: 0 0.4rem; }

.unlock__form { margin-block-start: 1.25rem; }

.unlock__label {
	display: block;
	margin-block-end: 0.4rem;
	font-size: 0.85rem;
	font-weight: 600;
}

.unlock__row {
	display: flex;
	flex-wrap: wrap;
	gap: 0.6rem;
	align-items: stretch;
}

.unlock__input {
	flex: 1 1 12rem;
	min-inline-size: 0;
	min-block-size: 3rem;
	padding-inline: 0.9rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
	color: var(--text);
	font: inherit;
	/* A code is read off a flyer and typed without looking; the letters may as well be even. */
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.unlock__input:focus {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}

.unlock__said:empty { display: none; }

.unlock__said {
	margin-block: 0.75rem 0;
	font-size: 0.9rem;
	color: #b3261e;
}

/* "I need an invoice", closed until somebody does. */
.invoice-ask > summary {
	cursor: pointer;
	font-weight: 600;
	font-family: var(--font-heading);
}

.invoice-ask[open] > summary { margin-block-end: 0.9rem; }

/* A booking fee, or the tax already inside the price. */
.summary-lines__extra { color: var(--text-muted); }

/* Who a ticket is for, under the seat it belongs to. */
.summary-lines__note {
	display: block;
	font-size: 0.8125rem;
	color: var(--text-muted);
}

/* A discount code, in the summary where the total it changes is. */
.summary-lines__off { color: var(--ok, var(--accent)); font-weight: 600; }

.promo { margin-block-start: 0.9rem; }

.promo__label {
	display: block;
	margin-block-end: 0.35rem;
	font-size: 0.82rem;
	color: var(--text-muted);
}

.promo__row { display: flex; gap: 0.5rem; }

.promo__row input {
	flex: 1 1 auto;
	min-inline-size: 0;
	/* Codes are printed on posters in capitals and typed however people type. */
	text-transform: uppercase;
}

.promo--applied {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
	flex-wrap: wrap;
	padding: 0.6rem 0.8rem;
	border: 1px dashed var(--border);
	border-radius: calc(var(--radius) * 0.8);
}

.promo__held { margin: 0; display: flex; gap: 0.4rem; align-items: baseline; }

/*
 * Credit, which is not typed and cannot be removed: it belongs to the address the buyer signed in
 * with. The same box as an applied code, without the button, because it is the same fact.
 */
.promo--credit {
	display: flex;
	gap: 0.4rem;
	align-items: baseline;
	flex-wrap: wrap;
	padding: 0.6rem 0.8rem;
	border: 1px dashed var(--border);
	border-radius: calc(var(--radius) * 0.8);
}

/*
 * A voucher, under the total rather than among the lines above it — because it did not change what
 * the booking cost, only how much of it is still owed. "Left to pay" is the number the buyer is
 * about to be charged, so it is the heavier of the two.
 */
.summary-paid {
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	margin: 0;
	padding-block-start: 0.5rem;
	color: var(--ok, var(--accent));
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

/* Reads like the total above it without being one. */
.summary-due {
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	margin: 0;
	padding-block-start: 0.35rem;
	font-weight: 700;
	font-size: 1.1rem;
	font-variant-numeric: tabular-nums;
}

/* What stands where the payment methods were, once there is nothing left to pay for. */
.checkout__settled {
	margin: 0;
	padding: 0.7rem 0.9rem;
	border-radius: calc(var(--radius) * 0.8);
	background: color-mix(in srgb, var(--ok, var(--accent)) 12%, transparent);
	color: var(--text);
}

/* ============================================================================= the queue */

/*
 * The door on a big sale. Deliberately plain and deliberately large: the person reading it is
 * anxious, on a phone, and wants one number.
 */
.room {
	padding: 2rem 1.5rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
	text-align: center;
}

.room__title { margin: 0 0 0.5rem; font-size: 1.6rem; font-variant-numeric: tabular-nums; }

.room__line { margin: 0 0 1.2rem; font-size: 1.05rem; }

/* Honest decoration: the width is the share of the queue now behind this person. */
.room__meter {
	block-size: 0.5rem;
	inline-size: min(28rem, 100%);
	margin: 0 auto 1.2rem;
	border-radius: 999px;
	background: var(--surface-sunken);
	overflow: hidden;
}

.room__meter > span {
	display: block;
	block-size: 100%;
	inline-size: 8%;
	border-radius: 999px;
	background: var(--accent);
	transition: inline-size 0.6s ease;
}

.room__hint { margin: 0 0 1.2rem; color: var(--text-muted); font-size: 0.9rem; }

.room__out { margin: 0; }

/* ============================================================================= seasons */

/*
 * Subscriptions. The offer sits on a night's own page, because the person looking at one Tuesday
 * is exactly the person who might rather have all twelve, and this is the only moment they are
 * asked.
 */
.season__offers,
.season__nights {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 0.6rem;
}

.season__offer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
	padding: 0.85rem 1rem;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
}

.season__offer-what { display: grid; gap: 0.15rem; }

.season__offer-saving,
.season__saving {
	color: var(--ok, var(--accent));
	font-weight: 600;
}

.season__saving { margin-block: 0.4rem 1.2rem; font-size: 1.05rem; }

.season__subhead {
	margin-block: 1.6rem 0.7rem;
	font-size: 1.05rem;
}

.season__night {
	padding: 0.7rem 0.9rem;
	border: 1px solid var(--border);
	border-radius: calc(var(--radius) * 0.8);
}

.season__night > span,
.season__pick > span { display: grid; gap: 0.1rem; }

.season__pick {
	display: flex;
	align-items: flex-start;
	gap: 0.7rem;
	cursor: pointer;
}

/* One block per night on the confirmation, because one night is what a person turns up to. */
.season__booked {
	margin-block-start: 2rem;
	padding-block-start: 1.4rem;
	border-block-start: 1px solid var(--border);
}

.season__booked:first-of-type { border-block-start: none; }

/* ============================================================================= tickets */

.done {
	display: flex;
	align-items: center;
	gap: 1rem;
	margin-block-end: 0.5rem;
}

.done__mark {
	display: grid;
	place-items: center;
	inline-size: 3rem;
	block-size: 3rem;
	flex: none;
	border-radius: 50%;
	background: var(--accent);
	color: var(--on-accent);
}

.done__mark svg { inline-size: 1.5rem; block-size: 1.5rem; }

.done__actions { margin-block: 1.25rem 0; }

.tickets {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
	gap: 1rem;
	margin-block-start: 1.75rem;
}

/*
 * A ticket, drawn as one: two notches where a stub would be torn off.
 *
 * Recognisable at arm's length on a phone in a queue, which is the only place this page is ever
 * looked at.
 */
.ticket {
	position: relative;
	padding: 1.1rem;
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface);
	box-shadow: var(--lift);
	overflow: hidden;
}

.ticket::before,
.ticket::after {
	content: '';
	position: absolute;
	inline-size: 1.1rem;
	block-size: 1.1rem;
	border-radius: 50%;
	background: var(--surface-sunken);
	border: 1px solid var(--hairline);
	inset-block-end: 3.4rem;
}

.ticket::before { inset-inline-start: -0.6rem; }
.ticket::after { inset-inline-end: -0.6rem; }

.ticket__seat { margin: 0 0 0.35rem; font-weight: 700; font-family: var(--font-heading); }

/* Whose ticket it is, when the event sells more than one kind. */
.ticket__type {
	margin: 0 0 0.35rem;
	font-size: 0.8125rem;
	color: var(--text-muted);
}

.ticket__code {
	margin: 0;
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	font-size: 0.7rem;
	color: var(--text-muted);
	letter-spacing: 0.02em;
	/* A ticket token is long and has no spaces, so it has to be told where it may break. */
	word-break: break-all;
}

.ticket__qr {
	inline-size: 100%;
	max-inline-size: 200px;
	block-size: auto;
	margin-block-end: 0.9rem;
	border-radius: calc(var(--radius) * 0.6);
	background: #fff;
	padding: 6px;
}


/* Searching the programme. A row that wraps rather than a sidebar: on a phone the search box has
   to be the width of the phone, and on a desktop it should not be the width of the desk. */
.finder {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: 0.75rem;
	margin-block-end: 1.5rem;
}

.finder__field { display: flex; flex-direction: column; gap: 0.3rem; flex: 1 1 16rem; }
.finder__field--narrow { flex: 0 1 12rem; }

.finder__label {
	font-size: 0.78rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--text-muted);
}

.finder input,
.finder select {
	inline-size: 100%;
	min-block-size: 3rem;
	padding-inline: 0.9rem;
	border: 1px solid var(--border);
	border-radius: var(--button-radius);
	background: var(--surface);
	color: var(--text);
	font: inherit;
}

.finder input:focus-visible,
.finder select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.finder__clear { align-self: center; color: var(--text-muted); font-size: 0.9rem; }

/* Quiet next to the price and the date, because it is a second thought — but present, because
   the most useful thing to do with an event page is put it where you will see it again. */
.event-hero__calendar {
	display: inline-flex;
	align-items: center;
	gap: 0.45rem;
	margin-block-start: 0.75rem;
	padding: 0.5rem 0.9rem;
	/* The same glass the facts above it are on: this sits over the artwork, where a light surface
	   token would be a pale box on a dark photograph. */
	border: 1px solid rgba(255, 255, 255, 0.22);
	border-radius: var(--radius-pill);
	background: rgba(0, 0, 0, 0.35);
	backdrop-filter: blur(6px);
	color: inherit;
	text-decoration: none;
	font-size: 0.9rem;
}

.event-hero__calendar:hover { border-color: rgba(255, 255, 255, 0.45); background: rgba(0, 0, 0, 0.5); }
.event-hero__calendar svg { inline-size: 1rem; block-size: 1rem; }

/* --- a buyer's own page -------------------------------------------------------------------
 *
 * Signing in with Google gives this site an address it can trust, and the address is the whole
 * account: everything below is "the orders bought with it". So the page has two states and no
 * settings — sign in, and here is what you bought.
 */

.nav__link--account {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	border: 1px solid var(--hairline);
	color: var(--text);
}

.account__out {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	flex-wrap: wrap;
	font-size: 0.9rem;
}

.button--quiet {
	min-block-size: 2.4rem;
	padding-inline: 1rem;
	background: transparent;
	border-color: var(--border);
	color: var(--text);
	font-weight: 500;
}

.button--quiet:hover { background: var(--surface-veil); filter: none; }

.signin {
	max-inline-size: 34rem;
	padding: clamp(1.5rem, 4vw, 2.5rem);
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface);
	box-shadow: var(--lift);
}

/* Google's own button, to Google's own guidance: their mark, on white, with our text beside it. */
.button--google {
	margin-block: 1.25rem 0.5rem;
	background: #fff;
	border-color: #dadce0;
	color: #3c4043;
	font-weight: 500;
}

.button--google:hover { filter: none; background: #f8f9fa; box-shadow: var(--lift); }
.google-g { flex: none; }

.orders { display: grid; gap: 1rem; }

.order {
	padding: clamp(1rem, 3vw, 1.5rem);
	border: 1px solid var(--hairline);
	border-radius: var(--radius-lg);
	background: var(--surface);
	box-shadow: var(--lift);
}

.order__head {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: space-between;
	gap: 0.75rem;
}

.order__event { margin: 0; font-size: 1.15rem; }
.order__head p { margin: 0.2rem 0 0; font-size: 0.9rem; }
.order__figures { display: flex; align-items: center; gap: 0.6rem; }
.order__total { font-weight: 650; font-variant-numeric: tabular-nums; }

.pillbox {
	padding: 0.15rem 0.6rem;
	border-radius: var(--radius-pill);
	background: var(--accent-soft);
	color: var(--text);
	font-size: 0.8rem;
}

.pillbox--used { background: var(--surface-sunken); color: var(--text-muted); }

.order__lines {
	margin: 1rem 0 0;
	padding: 0;
	list-style: none;
	border-block-start: 1px solid var(--hairline);
}

.order__lines li {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.55rem 0;
	border-block-end: 1px solid var(--hairline);
}

.order__lines li:last-child { border-block-end: none; }

.order__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.75rem 1rem;
	margin-block-start: 1rem;
}

.order__actions .field__hint { margin: 0; max-inline-size: 26rem; }

@media print {
	.masthead, .footer, .skip { display: none; }
	.ticket { box-shadow: none; break-inside: avoid; }
}

/* A field that is not there.

   Not `display: none`: a form-filler that only fills what it can see would skip it, and the point
   is that a script which fills everything gives itself away. Off the page, out of the tab order,
   and `aria-hidden` in the markup so no screen reader ever meets it. */
.nowhere {
	position: absolute;
	inset-inline-start: -9999px;
	inline-size: 1px;
	block-size: 1px;
	overflow: hidden;
}

/* A refusal that is about the booking rather than about one field. */
.checkout__refusal {
	margin-block: var(--space-3, 0.75rem);
}

/* The limit, above the seat map rather than at the checkout. Quiet, because it is information and
   not a refusal — the refusal only ever happens to somebody who ignored it. */
.booking__limit {
	margin-block-end: var(--space-3, 0.75rem);
	color: var(--muted, #6b7280);
	font-size: 0.9375rem;
}

/* The one question at a checkout that is not about the booking. Given room rather than tucked
   under the button: a box somebody has to hunt for is a box they did not really answer. */
.consent {
	display: flex;
	align-items: flex-start;
	gap: var(--space-2, 0.5rem);
	margin-block: var(--space-4, 1rem);
	line-height: 1.4;
}

.consent input {
	margin-block-start: 0.15rem;
	inline-size: 1.1rem;
	block-size: 1.1rem;
	flex: none;
}

/* ====================================================== the page modules a venue actually needs */

/*
 * The slideshow.
 *
 * A scroller first: the track is a row of slides with scroll-snap on it, so a phone swipes it and a
 * browser running no JavaScript at all still gets every picture. The arrows and dots are appended
 * by the block's own script, which is why nothing here assumes they exist.
 */
.slides { padding-block-end: clamp(1rem, 2.5vw, 1.75rem); }

.slides__track {
	display: flex;
	gap: 0.75rem;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	padding-inline: max(1.25rem, calc((100% - var(--shell)) / 2));
	/* The scrollbar is the fallback control, so it is left alone rather than hidden: on a browser
	   where the script did not run it is the only way to reach picture nine. */
	scrollbar-width: thin;
}

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

.slides__slide {
	flex: 0 0 min(100%, var(--shell));
	scroll-snap-align: center;
	margin: 0;
}

.slides__track--short .slides__slide img { aspect-ratio: 21 / 9; }
.slides__track--tall .slides__slide img { aspect-ratio: 16 / 9; }

.slides__slide img {
	display: block;
	inline-size: 100%;
	max-inline-size: 100%;
	block-size: auto;
	object-fit: cover;
	border-radius: var(--radius-lg);
	background: var(--surface-sunken);
}

.slides__link { display: block; }

.slides__caption {
	margin-block-start: 0.6rem;
	font-size: 0.875rem;
	color: var(--text-muted);
}

.slides__nav {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.75rem;
	margin-block-start: 0.9rem;
}

.slides__arrow {
	inline-size: 2.25rem;
	block-size: 2.25rem;
	flex: none;
	border: 1px solid var(--border);
	border-radius: var(--radius-pill);
	background: var(--surface);
	color: var(--text);
	cursor: pointer;
}

.slides__arrow::before {
	content: '';
	display: block;
	inline-size: 0.5rem;
	block-size: 0.5rem;
	margin-inline: auto;
	border-block-start: 2px solid currentColor;
	border-inline-end: 2px solid currentColor;
}

/* Turned by logical direction, so the arrow that means "onwards" points the reader's own way. */
.slides__arrow--on::before { transform: rotate(45deg); }
.slides__arrow--back::before { transform: rotate(-135deg); }
[dir='rtl'] .slides__arrow--on::before { transform: rotate(-135deg); }
[dir='rtl'] .slides__arrow--back::before { transform: rotate(45deg); }

.slides__arrow:hover { border-color: var(--accent-line); color: var(--accent); }

.slides__dots { display: flex; align-items: center; gap: 0.4rem; }

.slides__dot {
	inline-size: 0.55rem;
	block-size: 0.55rem;
	padding: 0;
	border: 0;
	border-radius: var(--radius-pill);
	background: var(--border);
	cursor: pointer;
}

.slides__dot.is-here { background: var(--accent); transform: scale(1.25); }
.slides__dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/*
 * The film.
 *
 * A still with a play button over it until somebody asks for the film: loading a third party's
 * player on a page about buying a ticket hands every visitor to them before they have shown any
 * interest in watching anything, and it is most of a megabyte.
 */
.video,
.video__player {
	position: relative;
	display: block;
	inline-size: 100%;
	max-inline-size: 100%;
	aspect-ratio: 16 / 9;
	border-radius: var(--radius-lg);
	overflow: hidden;
	background: var(--surface-sunken) center / cover no-repeat;
}

.video__player { background-color: #000; }

.video__play {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	inline-size: 100%;
	block-size: 100%;
	border: 0;
	background: color-mix(in srgb, #000 28%, transparent);
	color: #fff;
	cursor: pointer;
}

.video__play svg {
	inline-size: 4rem;
	block-size: 4rem;
	filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.45));
}

.video__play:hover svg { transform: scale(1.08); }
.video__play:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }

.video__frame { inline-size: 100%; block-size: 100%; border: 0; }

.video__away {
	position: absolute;
	inset-block-end: 1rem;
	inset-inline-start: 1rem;
	color: #fff;
}

.video__caption { margin-block-start: 0.6rem; font-size: 0.875rem; }

/*
 * The facts.
 *
 * Two columns where there is room for two, because somebody reading this is looking for one row —
 * the age limit, the running time — and a single column of twelve makes them read all twelve.
 */
.specs {
	display: grid;
	gap: 0.1rem 1.5rem;
	margin: 0;
	max-inline-size: 60rem;
}

.specs__row {
	display: grid;
	grid-template-columns: minmax(8rem, 14rem) 1fr;
	gap: 0.25rem 1rem;
	padding-block: 0.7rem;
	border-block-end: 1px solid var(--border);
}

.specs__row:last-child { border-block-end: 0; }
.specs__label { margin: 0; color: var(--text-muted); }
.specs__value { margin: 0; }

@media (max-width: 34rem) {
	.specs__row { grid-template-columns: 1fr; }
}

/*
 * The conditions of sale.
 *
 * A `details` rather than a script: it opens for a reader with no JavaScript, it prints open, and
 * the browser's own find-on-page reaches inside it.
 */
.terms {
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 0.9rem 1.1rem;
	background: var(--surface);
	max-inline-size: var(--shell-narrow);
}

.terms__summary { cursor: pointer; font-weight: 600; }
.terms__summary::marker { color: var(--text-muted); }
.terms__title { margin: 0 0 0.5rem; font-size: 1.15rem; }
.terms__body { margin-block-start: 0.8rem; font-size: 0.9375rem; }
.terms--open .terms__body { margin-block-start: 0; }

/*
 * Buy this one, from anywhere on the site.
 *
 * A strip rather than a card, because it is an offer and not an article: the night on one side, the
 * price and the button on the other, and on a phone the button underneath where a thumb is.
 */
.buy {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 1.1rem 1.25rem;
	border: 1px solid var(--accent-line);
	border-radius: var(--radius-lg);
	background: var(--accent-soft);
	box-shadow: var(--shadow);
}

.buy__what { flex: 1 1 18rem; }
.buy__title { margin: 0; font-size: clamp(1.1rem, 2.2vw, 1.45rem); }
.buy__when { margin: 0.3rem 0 0; color: var(--text-muted); }
.buy__note { margin: 0.4rem 0 0; font-size: 0.9375rem; }

.buy__act {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.6rem 0.9rem;
}

.buy__price { font-weight: 600; }
.buy__closed { font-weight: 600; }

@media (max-width: 40rem) {
	.buy__act { inline-size: 100%; }
	.buy__act .button { flex: 1 1 auto; text-align: center; }
}

/* ============================================================== the question before the measuring */

/*
 * A bar at the foot of the page rather than a curtain over it.
 *
 * A modal that has to be dismissed before the page can be read is a modal that gets whatever answer
 * makes it go away, which is not consent — and on a phone it sits over the one button the visitor
 * came for.
 */
.cookiebar {
	position: sticky;
	inset-block-end: 0;
	z-index: 40;
	border-block-start: 1px solid var(--border);
	background: var(--surface);
	box-shadow: 0 -6px 24px rgba(16, 20, 30, 0.07);
}

.cookiebar__inner {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem 1.5rem;
	padding-block: 0.9rem;
}

.cookiebar__text { margin: 0; flex: 1 1 22rem; font-size: 0.9375rem; }
.cookiebar__acts { display: flex; flex-wrap: wrap; gap: 0.6rem; }

@media (max-width: 40rem) {
	.cookiebar__acts { inline-size: 100%; }
	.cookiebar__acts .button { flex: 1 1 auto; text-align: center; }
}

/* =========================================================== a shop that fits in a hand
 *
 * Everything below is here because a ticket is bought on a phone. Not "works on a phone" in the
 * sense that it does not overflow — that was already true — but *made for one*: a header that folds
 * into a button, a checkout that puts the total above the form where a thumb can see it before it
 * types, a reserve button that stays on the screen while the plan is being dragged, and nothing
 * ever sitting under the home indicator.
 *
 * The breakpoint is 52rem throughout, and it is one number on purpose: a stylesheet with four
 * breakpoints is a stylesheet nobody can hold in their head, and every layout here has the same
 * question to answer — is there room for a row, or does this become a column?
 */

/* -------------------------------------------------------------------- the folding header */

/*
 * Phone-first, and no-JavaScript-first.
 *
 * The panel is an ordinary row of links by default: that is what a visitor with no script gets, and
 * it is what every screen wide enough gets too. Only when the script has revealed the button —
 * `.menu--folds` — and only on a narrow screen does it become a dropdown that the button opens.
 * A header whose navigation depends on a script is a header with no navigation in it.
 */
.menu {
	position: relative;
	display: flex;
	align-items: center;
	margin-inline-start: auto;
}

.menu__button {
	display: inline-grid;
	place-items: center;
	inline-size: 2.75rem;
	block-size: 2.75rem;
	padding: 0;
	border-radius: var(--radius);
	border: 1px solid var(--border);
	background: var(--surface);
	color: var(--text);
	cursor: pointer;
}

.menu__button:hover { border-color: var(--accent-line); }

.menu__shut { display: none; }
.menu__button[aria-expanded="true"] .menu__open { display: none; }
.menu__button[aria-expanded="true"] .menu__shut { display: block; }

.menu__panel {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 1.25rem;
}

@media (max-width: 51.999rem) {
	/*
	 * The panel a phone gets: under the header, and nothing behind it to mis-tap.
	 *
	 * Not a fixed overlay. An overlay has to trap focus, has to be closed again, and has to fight
	 * the address bar for the bottom of the screen; a sheet that pushes nothing and covers nothing
	 * has none of those problems and is dismissed by the same button that opened it.
	 */
	.menu--folds .menu__panel {
		position: absolute;
		inset-block-start: calc(100% + 0.65rem);
		inset-inline-end: 0;
		z-index: 12;
		flex-direction: column;
		align-items: stretch;
		flex-wrap: nowrap;
		gap: 0.35rem;
		min-inline-size: min(16rem, calc(100vw - 2.5rem));
		padding: 0.5rem;
		border: 1px solid var(--border);
		border-radius: var(--radius-lg);
		background: var(--surface);
		box-shadow: var(--lift-strong);
	}

	.menu--folds .menu__button[aria-expanded="false"] + .menu__panel { display: none; }

	.menu--folds .menu__panel .nav { flex-direction: column; gap: 0.15rem; }

	/* A menu is a list of decisions, and on a touch screen a decision needs a target. Aligned to
	   the reading edge, not centred: a column of centred links has no edge to run the eye down,
	   and the coarse-pointer rule further down would otherwise centre every one of them. */
	.menu--folds .menu__panel .nav__link {
		display: flex;
		align-items: center;
		justify-content: flex-start;
		gap: 0.5rem;
		min-block-size: 2.75rem;
		padding-inline: 0.85rem;
		font-size: 1rem;
	}

	.menu--folds .menu__panel .langs { margin-block-start: 0.25rem; }

	.menu--folds .menu__panel .langs__button {
		inline-size: 100%;
		justify-content: space-between;
		min-block-size: 2.75rem;
	}
}

/* Wide enough for a row, so the button has nothing to fold. */
@media (min-width: 52rem) {
	.menu__button { display: none; }
}

/* The notch, and the room under it. */
.masthead__inner { padding-block-start: max(0.9rem, env(safe-area-inset-top)); }

/* ------------------------------------------------------------------------- the offer to install */

.install {
	position: sticky;
	inset-block-end: 0;
	z-index: 38;
	border-block-start: 1px solid var(--border);
	background: var(--surface);
	box-shadow: 0 -6px 24px rgba(16, 20, 30, 0.07);
}

.install__inner {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem 1.5rem;
	padding-block: 0.9rem;
	padding-block-end: max(0.9rem, env(safe-area-inset-bottom));
}

/*
 * One bar at a time.
 *
 * The consent question and the install offer are both pinned to the foot of the page, and two
 * stacked bars over the button somebody came to press is worse than either. Consent goes first: it
 * is a question the visitor is owed an answer to, and the offer will still be there afterwards.
 */
body:has([data-consent]:not([hidden])) .install { display: none; }

.install__text { margin: 0; flex: 1 1 20rem; font-size: 0.9375rem; }
.install__hint { display: block; color: var(--text-muted); }
.install__acts { display: flex; flex-wrap: wrap; gap: 0.6rem; }

/* ------------------------------------------------------------------ bars that reach the bottom */

/*
 * Anything pinned to the foot of the screen clears the home indicator.
 *
 * Without this the "yes" on the consent bar sits exactly where an iPhone's swipe-up handle is, and
 * roughly half the people who try to press it go to their home screen instead.
 */
.cookiebar__inner { padding-block-end: max(0.9rem, env(safe-area-inset-bottom)); }

/* --------------------------------------------------------------------------- touch targets */

/*
 * Anything a finger presses is at least 44px tall.
 *
 * Not a rule invented here: it is the smallest target that most adults hit reliably first time,
 * and a shop where the second tap is the one that works is a shop that feels broken.
 */
@media (pointer: coarse) {
	.button,
	.nav__link,
	.finder__clear {
		min-block-size: 2.75rem;
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}

	/* `flex`, not `inline-flex`: these are the rows of a menu, and inline boxes would line them up
	   beside one another rather than under one another. */
	.langs__link,
	.langs__current {
		display: flex;
		align-items: center;
		min-block-size: 2.75rem;
	}

	.finder input,
	.finder select,
	.promo__row input,
	.addon__pick select,
	.unlock__input {
		min-block-size: 2.75rem;
		font-size: max(1rem, 16px);
	}

	/* A grey flash over a card the moment it is touched reads as a fault, not as feedback. */
	a, button, summary { -webkit-tap-highlight-color: transparent; }
}

/* ------------------------------------------------------------------ what a phone sees instead */

@media (max-width: 52rem) {
	/*
	 * The total, above the form.
	 *
	 * Stacked, the summary used to land under two hundred pixels of name and address fields, so
	 * the first time anybody saw what they were about to pay was after they had typed everything.
	 * It goes first on a phone, and it stops being sticky — a box pinned to the top of a screen
	 * that is already only 700px tall is a box that leaves no room for the thing it describes.
	 */
	.checkout__summary {
		order: -1;
		position: static;
		flex-basis: 100%;
	}

	.checkout__form { flex-basis: 100%; }

	/* A poster lying on its side is a poster with nothing on it at this width. */
	.events--list .event-card { flex-direction: column; }
	.events--list .event-card__art { inline-size: 100%; aspect-ratio: 16 / 10; }
	.events--list .event-card__body { padding: 1rem 1.1rem 0; }

	.events--list .event-card__foot {
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
		padding: 0.9rem 1.1rem 1.1rem;
	}

	.event-hero__facts { gap: 0.4rem; }
	.event-hero__fact { font-size: 0.875rem; padding: 0.4rem 0.7rem; }

	.order__head { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
	.order__actions { flex-direction: column; align-items: stretch; }
	.order__actions .button { justify-content: center; }

	.waitlist__fields { flex-direction: column; }
	.waitlist__fields .field--narrow { max-inline-size: none; }

	.footer__inner { flex-direction: column; gap: 1.5rem; }

	/* One ticket per row: two 150px QR codes side by side are two codes a scanner argues with. */
	.tickets { grid-template-columns: 1fr; }

	.done__actions { display: flex; flex-direction: column; gap: 0.6rem; }
	.done__actions .button { justify-content: center; }
}

/*
 * Room for a thumb at the foot of every page.
 *
 * `100dvh` on a phone excludes the browser's toolbar, and the last element of a page otherwise ends
 * exactly where that toolbar sits. Applied to the footer rather than to `body`, so a full-bleed
 * hero still bleeds.
 */
.footer { padding-block-end: max(clamp(1.5rem, 4vw, 2.5rem), env(safe-area-inset-bottom)); }

/* --------------------------------------------------------------------------- a little polish */

/*
 * The one thing every page has in common is its buttons, so they are worth getting right.
 *
 * A press that moves is a press that was felt. The transition is short enough not to be an
 * animation and long enough to read as a surface rather than as a repaint.
 */
.button {
	min-block-size: 2.75rem;
	transition: box-shadow 160ms var(--ease), transform 90ms var(--ease), filter 160ms var(--ease);
}

/* Written out rather than left implicit: the bar that asks about measurement and the one that
   offers an install both say `--primary`, and a modifier that does nothing is a modifier that
   somebody eventually redefines by accident. */
.button--primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

.event-card {
	transition: transform 180ms var(--ease), box-shadow 180ms var(--ease), border-color 180ms var(--ease);
}

@media (hover: hover) {
	.event-card:hover { transform: translateY(-3px); }
}

/* A card being pressed on a touch screen has no hover to show for it, so it says so by moving. */
@media (pointer: coarse) {
	.event-card:active { transform: scale(0.99); }
}

@media (prefers-reduced-motion: reduce) {
	.event-card,
	.button {
		transition: none;
	}

	.event-card:hover,
	.event-card:active {
		transform: none;
	}
}
