/*
 * Job Fairs — foundation styles.
 * =============================================================================
 *
 * Mobile-first: base rules describe the small screen, and every media query
 * adds from there. There are no literal colours in this file — every one comes
 * from tokens.css. If you find yourself typing a hex value, add a token.
 *
 * Contents
 *   0. The typeface
 *   1. Reset and base
 *   2. Typography
 *   3. Layout primitives
 *   4. Buttons and forms
 *   5. Skip link
 *   6. Header and navigation
 *   7. Homepage hero and routes
 *   8. Prose, lists, pagination
 *   9. 404
 *  10. Footer
 *  11. Event search bar
 *  12. Event grid and tiles
 *  13. The tickets-taken bar
 *  14. Event page
 *  15. FAQ
 *  15a. The candidate FAQ page
 *  16. Motion preferences
 */

/* =========================================================================
 * 0. The typeface
 *
 * Google Sans Flex, self-hosted from assets/fonts/ under the SIL Open Font
 * Licence (the licence travels with the files, in assets/fonts/OFL.txt).
 *
 * Self-hosted rather than loaded from Google's font host, which would put a
 * blocking cross-origin request — DNS, TLS, CSS, then the file itself — in
 * front of the LCP that requirement 2 gives 2.5 seconds. Same origin, the
 * browser is already connected and the preload in inc/assets.php starts the
 * download before the stylesheet has finished parsing.
 *
 * One variable file per subset covering 400 to 700, rather than three static
 * weights: the theme uses regular, medium and bold, and three files would be
 * three requests and about twice the bytes.
 *
 * font-display: swap, so text is readable in the fallback from the first paint
 * and never invisible while the font arrives. The stack in --jf-font is what
 * that first paint uses, which is why it is a real stack and not a lone name.
 *
 * The latin-ext file is fetched only if a character needs it — that is what
 * unicode-range is for — so it costs nothing on a page of English.
 * ====================================================================== */

@font-face {
	font-family: "Google Sans Flex";
	font-style: normal;
	font-weight: 400 700;
	font-display: swap;
	src: url("../fonts/google-sans-flex-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "Google Sans Flex";
	font-style: normal;
	font-weight: 400 700;
	font-display: swap;
	src: url("../fonts/google-sans-flex-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* =========================================================================
 * 1. Reset and base
 * ====================================================================== */

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

html {
	-webkit-text-size-adjust: 100%;
	scroll-behavior: smooth;
}

/*
 * A column the height of the viewport, so a short page still puts its footer
 * at the bottom of the screen rather than leaving it floating mid-page with
 * blank space underneath. .jf-main takes up the slack.
 */
body {
	margin: 0;
	min-height: 100vh;
	display: flex;
	flex-direction: column;
	background: var(--jf-ground);
	color: var(--jf-text);
	font-family: var(--jf-font);
	font-size: var(--jf-size-base);
	line-height: var(--jf-leading-normal);
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

/*
 * Images are block-level and fluid by default. The second rule is the one that
 * matters for CLS: an <img> that carries width and height attributes — which
 * wp_get_attachment_image() always emits — keeps its aspect ratio while the
 * file is still loading, so nothing below it moves when it arrives.
 */
img,
picture,
video,
canvas,
svg,
iframe {
	display: block;
	max-width: 100%;
}

img {
	height: auto;
}

/* Anything that can overflow gets to scroll inside itself rather than pushing
 * the page sideways. */
table,
pre {
	display: block;
	max-width: 100%;
	overflow-x: auto;
}

/* =========================================================================
 * 2. Typography
 * ====================================================================== */

h1,
h2,
h3,
h4,
h5,
h6 {
	margin: 0;
	font-weight: var(--jf-weight-bold);
	line-height: var(--jf-leading-tight);
	text-wrap: balance;
}

p,
ul,
ol,
dl,
figure,
blockquote {
	margin: 0;
}

a {
	color: var(--jf-brand-dark);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 0.15em;
}

a:hover {
	text-decoration-thickness: 2px;
}

strong,
b {
	font-weight: var(--jf-weight-bold);
}

/* Requirement 22: a visible focus state, and the same one everywhere. */
:focus-visible {
	outline: var(--jf-focus-width) solid var(--jf-focus);
	outline-offset: var(--jf-focus-offset);
	border-radius: var(--jf-radius-sm);
}

/* Icons.
 *
 * Sized in em so an icon is always the size of the text it sits beside, and
 * stroked in currentColor so it takes that text's colour unless something says
 * otherwise. inc/icons.php prints them; every one is decorative. */
.jf-icon {
	flex: none;
	width: 1.1em;
	height: 1.1em;
	stroke: currentColor;
}

/* The sprite itself is never seen — it only holds the <symbol> definitions the
 * icons above point at. Taken out of the flow at zero size rather than hidden
 * with display:none, which is the more cautious way to keep <use> references
 * resolvable. */
.jf-sprite {
	position: absolute;
	width: 0;
	height: 0;
	overflow: hidden;
}

/* Visually hidden but still announced. */
.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* =========================================================================
 * 3. Layout primitives
 * ====================================================================== */

.jf-container {
	width: 100%;
	max-width: var(--jf-container);
	margin-inline: auto;
	padding-inline: var(--jf-gutter);
}

.jf-main {
	flex: 1;
}

/* =========================================================================
 * 4. Buttons and forms
 * ====================================================================== */

.jf-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--jf-space-2xs);
	min-height: 48px;
	padding: var(--jf-space-xs) var(--jf-space-md);
	border: 2px solid transparent;
	border-radius: var(--jf-radius-md);
	font-family: inherit;
	font-size: var(--jf-size-base);
	font-weight: var(--jf-weight-bold);
	line-height: 1.2;
	text-align: center;
	text-decoration: none;
	cursor: pointer;
	transition: background-color var(--jf-duration) var(--jf-ease),
		border-color var(--jf-duration) var(--jf-ease),
		color var(--jf-duration) var(--jf-ease);
}

/* The key button colour, per the branding guidelines. */
.jf-button--primary {
	background: var(--jf-brand);
	border-color: var(--jf-brand);
	color: var(--jf-ground);
}

.jf-button--primary:hover,
.jf-button--primary:focus-visible {
	background: var(--jf-brand-dark);
	border-color: var(--jf-brand-dark);
	color: var(--jf-ground);
}

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

.jf-button--secondary:hover,
.jf-button--secondary:focus-visible {
	border-color: var(--jf-text);
	color: var(--jf-text);
}

.jf-button--ghost {
	background: transparent;
	border-color: transparent;
	color: var(--jf-brand-dark);
	padding-inline: var(--jf-space-2xs);
}

input[type="search"],
input[type="text"],
input[type="email"],
input[type="tel"],
select,
textarea {
	width: 100%;
	min-height: 48px;
	padding: var(--jf-space-2xs) var(--jf-space-xs);
	border: 1px solid var(--jf-line-strong);
	border-radius: var(--jf-radius-md);
	background: var(--jf-ground);
	color: var(--jf-text);
	font-family: inherit;
	font-size: var(--jf-size-base);
}

.jf-search {
	display: flex;
	flex-direction: column;
	gap: var(--jf-space-2xs);
	max-width: var(--jf-container-narrow);
}

.jf-search__label {
	font-size: var(--jf-size-sm);
	font-weight: var(--jf-weight-medium);
	color: var(--jf-text-muted);
}

.jf-search__row {
	display: flex;
	gap: var(--jf-space-2xs);
}

.jf-search__submit {
	flex: none;
}

/* =========================================================================
 * 5. Skip link
 * ====================================================================== */

.jf-skip-link {
	position: absolute;
	top: 0;
	left: var(--jf-space-2xs);
	z-index: 100;
	transform: translateY(-120%);
	padding: var(--jf-space-2xs) var(--jf-space-sm);
	background: var(--jf-text);
	color: var(--jf-ground);
	border-radius: 0 0 var(--jf-radius-md) var(--jf-radius-md);
	text-decoration: none;
	font-weight: var(--jf-weight-bold);
	transition: transform var(--jf-duration) var(--jf-ease);
}

.jf-skip-link:focus {
	transform: translateY(0);
	color: var(--jf-ground);
}

/* =========================================================================
 * 6. Header and navigation
 * ====================================================================== */

.jf-header {
	position: sticky;
	top: 0;
	z-index: 30;
	border-bottom: 1px solid var(--jf-line);
	background: var(--jf-ground);
}

.jf-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--jf-space-sm);
	min-height: 72px;
	padding-block: var(--jf-space-2xs);
}

.jf-brand {
	display: inline-flex;
	align-items: center;
	text-decoration: none;
	color: var(--jf-text);
}

.jf-brand--text {
	font-size: var(--jf-size-lg);
	font-weight: var(--jf-weight-bold);
	letter-spacing: -0.01em;
}

.jf-brand__logo {
	max-height: 48px;
	width: auto;
}

/* --- The toggle. Hidden entirely once the menu fits on one line. --- */

/* The word "Menu" is announced but not printed, so the button is square: an
 * icon on its own in a box sized for a label reads as a gap beside the cart. */
.jf-nav__toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 48px;
	min-height: 48px;
	padding: var(--jf-space-2xs);
	border: 1px solid var(--jf-line-strong);
	border-radius: var(--jf-radius-md);
	background: transparent;
	color: var(--jf-text);
	font-family: inherit;
	font-size: var(--jf-size-sm);
	font-weight: var(--jf-weight-bold);
	cursor: pointer;
}

.jf-nav__toggle-bars,
.jf-nav__toggle-bars::before,
.jf-nav__toggle-bars::after {
	display: block;
	width: 18px;
	height: 2px;
	background: currentColor;
	content: "";
}

.jf-nav__toggle-bars {
	position: relative;
}

.jf-nav__toggle-bars::before {
	position: absolute;
	top: -6px;
}

.jf-nav__toggle-bars::after {
	position: absolute;
	top: 6px;
}

.jf-nav__list {
	display: none;
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * Open state. Driven by aria-expanded on the button rather than a separate
 * class, so the visual state and the state announced to a screen reader cannot
 * disagree — there is only one thing to set.
 */
.jf-nav__toggle[aria-expanded="true"] + .jf-nav__list {
	display: flex;
	position: absolute;
	top: 100%;
	left: 0;
	right: 0;
	z-index: 20;
	flex-direction: column;
	gap: var(--jf-space-3xs);
	padding: var(--jf-space-sm) var(--jf-gutter) var(--jf-space-md);
	background: var(--jf-ground);
	border-bottom: 1px solid var(--jf-line);
	box-shadow: var(--jf-shadow-md);
}

/* The open menu anchors to .jf-header, not to .jf-nav, so it spans the full
 * width of the header rather than the width of the toggle. */
.jf-nav {
	position: static;
}

/*
 * The links sit with the cart on the right, not between the brand and it.
 * An auto margin rather than a change of justification, because the header
 * carries two, three or one child depending on the audience — an auto margin
 * takes the free space wherever the nav happens to be, and a header with no
 * nav still puts the cart on the right.
 */
.jf-header__inner > .jf-nav {
	margin-left: auto;
}

/*
 * Below the toggle's own breakpoint, the basket sits to its left rather than
 * its right. `order` alone would leave the free space split between the two
 * auto margins, one on each side of the pair, since the swap moves which of
 * them is now first; `:has()` finds the nav that has a basket after it, so
 * only that pairing hands the margin across rather than every nav on the site.
 */
@media (max-width: 47.99em) {
	.jf-header__inner > .jf-nav:has(+ .jf-cart-link) {
		order: 2;
		margin-left: 0;
	}

	.jf-header__inner > .jf-nav + .jf-cart-link {
		order: 1;
		margin-left: auto;
	}
}

.jf-nav__list a {
	display: block;
	padding: var(--jf-space-2xs) 0;
	color: var(--jf-text);
	font-weight: var(--jf-weight-medium);
	text-decoration: none;
}

.jf-nav__list a:hover,
.jf-nav__list .current-menu-item > a {
	color: var(--jf-brand-dark);
	text-decoration: underline;
	text-decoration-thickness: 2px;
}

@media (min-width: 48em) {
	.jf-nav__toggle {
		display: none;
	}

	/*
	 * On a wide screen the list is always visible, whatever aria-expanded says.
	 * Both selectors are needed: the toggle is display:none but still present,
	 * so its state would otherwise keep applying.
	 */
	.jf-nav__list,
	.jf-nav__toggle[aria-expanded="true"] + .jf-nav__list {
		display: flex;
		position: static;
		flex-direction: row;
		align-items: center;
		gap: var(--jf-space-md);
		padding: 0;
		background: transparent;
		border: 0;
		box-shadow: none;
	}
}

/* =========================================================================
 * 7. Homepage hero and routes
 * ====================================================================== */

.jf-hero {
	position: relative;
	display: grid;
	isolation: isolate;
}

/*
 * Media and content occupy the same grid cell so the copy sits over the
 * photograph without either being taken out of flow. The section's height comes
 * from the content, not from the viewport — a 100vh hero pushes the two buttons
 * off the first screen on a phone, which are the only two things on this page.
 */
.jf-hero__media,
.jf-hero__inner {
	grid-area: 1 / 1;
}

.jf-hero__media {
	position: relative;
	overflow: hidden;
}

.jf-hero__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * A scrim, so the heading stays legible over whatever photograph is uploaded.
 * Sits between the image and the copy.
 */
.jf-hero__media::after {
	position: absolute;
	inset: 0;
	background: linear-gradient(
		to bottom,
		var(--jf-scrim-top),
		var(--jf-scrim-bottom)
	);
	content: "";
}

.jf-hero__inner {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--jf-space-lg);
	padding-block: var(--jf-space-2xl);
	min-height: 60vh;
}

.jf-hero__heading {
	max-width: 20ch;
	font-size: var(--jf-size-2xl);
	color: var(--jf-ground);
}

/*
 * No photograph set. The section becomes a solid block of the secondary brand
 * colour rather than a pale stand-in, so the page looks finished while the
 * hero photography is still being shot. Height comes from the content, since
 * there is no image to give the section a shape of its own.
 */
.jf-hero--no-image {
	background: var(--jf-hero-ground);
}

.jf-hero--no-image .jf-hero__inner {
	min-height: 0;
	padding-block: var(--jf-space-xl);
}

.jf-hero--no-image .jf-hero__heading {
	color: var(--jf-hero-ground-text);
}

.jf-routes {
	display: grid;
	gap: var(--jf-space-sm);
}

@media (min-width: 40em) {
	.jf-routes {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--jf-space-md);
		max-width: 900px;
	}
}

/*
 * The two routes are the page. They are cards because they are genuinely two
 * separate objects being chosen between — the only place in the foundation
 * where a card is the right call.
 */
.jf-route {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--jf-space-sm);
	padding: var(--jf-space-md);
	background: var(--jf-ground);
	border-radius: var(--jf-radius-md);
	box-shadow: var(--jf-shadow-md);
}

/*
 * The heading is a wrapper for the button, not a line of type in its own
 * right, so it carries no font styling — the button supplies all of it.
 */
.jf-route__title {
	display: block;
	width: 100%;
	font-size: inherit;
}

.jf-route__blurb {
	color: var(--jf-text-muted);
	font-size: var(--jf-size-md);
	line-height: var(--jf-leading-snug);
}

.jf-route .jf-button {
	width: 100%;
	font-size: var(--jf-size-md);
}

/* =========================================================================
 * 8. Prose, lists, pagination
 * ====================================================================== */

.jf-prose {
	padding-block: var(--jf-space-xl);
}

.jf-prose > * + * {
	margin-top: var(--jf-space-sm);
}

.jf-prose p,
.jf-prose li {
	max-width: var(--jf-measure);
}

.jf-prose h2 {
	margin-top: var(--jf-space-lg);
	font-size: var(--jf-size-xl);
}

.jf-prose h3 {
	margin-top: var(--jf-space-md);
	font-size: var(--jf-size-lg);
}

.jf-page-title {
	font-size: var(--jf-size-xl);
}

.jf-prose ul,
.jf-prose ol {
	padding-left: 1.25rem;
}

.jf-list {
	display: grid;
	gap: var(--jf-space-md);
	margin-top: var(--jf-space-lg);
}

.jf-list__title {
	font-size: var(--jf-size-lg);
}

.pagination,
.nav-links {
	display: flex;
	flex-wrap: wrap;
	gap: var(--jf-space-2xs);
	margin-top: var(--jf-space-lg);
}

.pagination .page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	padding-inline: var(--jf-space-2xs);
	border: 1px solid var(--jf-line);
	border-radius: var(--jf-radius-md);
	text-decoration: none;
	color: var(--jf-text);
}

.pagination .page-numbers.current {
	background: var(--jf-brand);
	border-color: var(--jf-brand);
	color: var(--jf-ground);
}

/* =========================================================================
 * 9. 404
 * ====================================================================== */

.jf-404__lede {
	font-size: var(--jf-size-md);
	color: var(--jf-text-muted);
}

.jf-404__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--jf-space-2xs);
	margin-top: var(--jf-space-md);
}

.jf-404__search {
	margin-top: var(--jf-space-xl);
	padding-top: var(--jf-space-md);
	border-top: 1px solid var(--jf-line);
}

/* =========================================================================
 * 10. Footer
 * ====================================================================== */

/* No rule across the top: the footer's own ground is a shade off the page's, and
 * that change of colour is the edge. A line as well draws it twice. */
.jf-footer {
	padding-block: var(--jf-space-lg);
	background: var(--jf-ground-raised);
	font-size: var(--jf-size-sm);
	color: var(--jf-text-muted);
}

.jf-footer__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--jf-space-xs) var(--jf-space-md);
	margin: 0 0 var(--jf-space-md);
	padding: 0;
	list-style: none;
}

.jf-footer__list a {
	color: var(--jf-text);
	text-decoration: none;
	font-weight: var(--jf-weight-medium);
}

.jf-footer__list a:hover {
	text-decoration: underline;
}

.jf-footer__cards {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--jf-space-xs);
	margin: 0 0 var(--jf-space-md);
	padding: 0;
	list-style: none;
}

.jf-footer__cards img {
	display: block;
	border: 1px solid var(--jf-line);
	border-radius: var(--jf-radius-sm);
}

/* Already its own badge, card frame included. */
.jf-footer__cards img.jf-footer__badge {
	border: 0;
}

.jf-footer__legal {
	display: flex;
	flex-direction: column;
	gap: var(--jf-space-3xs);
}

.jf-footer__legal-line {
	max-width: var(--jf-measure);
	line-height: var(--jf-leading-snug);
}

.jf-footer__sep {
	display: inline-block;
	margin-inline: var(--jf-space-3xs);
	color: var(--jf-line-strong);
}

/* A button that looks like a link, because it opens a dialog rather than
 * navigating — but should read as the same kind of thing as Privacy Notice. */
.jf-footer__cookies {
	padding: 0;
	border: 0;
	background: none;
	color: var(--jf-brand-dark);
	font: inherit;
	text-decoration: underline;
	text-underline-offset: 0.15em;
	cursor: pointer;
}

/* =========================================================================
 * 11. Page band and event search bar
 * ====================================================================== */

/* The wash behind the heading. The one gradient in the design, deepening
 * toward the bottom right. Full bleed, so it is outside the container and
 * puts its own inside. */
.jf-band {
	background: linear-gradient(135deg, var(--jf-band-from), var(--jf-band-to));
}

.jf-band__inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--jf-space-sm);
	padding-block: var(--jf-space-xl);
	text-align: center;
}

.jf-band__eyebrow {
	margin: 0;
	padding: var(--jf-space-3xs) var(--jf-space-sm);
	border-radius: var(--jf-radius-pill);
	background: var(--jf-band-pill);
	color: var(--jf-brand);
	font-size: var(--jf-size-xs);
	font-weight: var(--jf-weight-bold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.jf-band__title {
	max-width: 18ch;
	margin: 0;
	font-size: var(--jf-size-2xl);
	line-height: var(--jf-leading-tight);
	letter-spacing: -0.02em;
}

.jf-band__standfirst {
	max-width: 46ch;
	margin: 0;
	color: var(--jf-text-muted);
	font-size: var(--jf-size-md);
}

/* Not a button. It states a fact about every event on the page, so it is a
 * line of type in a pill rather than something to press. */
.jf-band__flag {
	margin: var(--jf-space-2xs) 0 0;
	padding: var(--jf-space-2xs) var(--jf-space-md);
	border-radius: var(--jf-radius-pill);
	background: var(--jf-ink);
	color: var(--jf-ink-text);
	font-size: var(--jf-size-xs);
	font-weight: var(--jf-weight-bold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* The search sits on white between the band and the grid. No rule under it: the
 * band's own edge above and the field's border are enough to place it, and a
 * third line across the page was one more than the layout needed. */
.jf-search-bar {
	background: var(--jf-ground);
}

.jf-filters {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--jf-space-sm);
	padding-block: var(--jf-space-md);
}

.jf-filters__field {
	position: relative;
	flex: 1 1 18rem;
	min-width: 0;
}

/* The magnifier sits inside the field, and the field's padding is what keeps
 * the text clear of it. Centred on the field's own height rather than on a
 * line box, so it stays put whatever the font does. */
.jf-filters__icon {
	position: absolute;
	top: 50%;
	left: var(--jf-space-sm);
	width: 1.15rem;
	height: 1.15rem;
	transform: translateY(-50%);
	color: var(--jf-text-faint);
	pointer-events: none;
}

/* The border draws the field, with no shadow under it. The bar already sits on
 * white between two other bands, so a raised edge was doing the separating
 * twice; 2px is what carries the shape on its own. */
.jf-filters__field input {
	width: 100%;
	padding: var(--jf-space-xs) var(--jf-space-sm) var(--jf-space-xs) 2.9rem;
	border: 2px solid var(--jf-line);
	border-radius: var(--jf-radius-lg);
	background: var(--jf-ground);
	color: var(--jf-text);
	font: inherit;
	font-size: var(--jf-size-md);
	/* 52px, a comfortable target and the height the design draws. */
	min-height: 3.25rem;
}

.jf-filters__field input::placeholder {
	color: var(--jf-text-faint);
}

.jf-filters__field input:focus-visible {
	border-color: var(--jf-brand);
	outline-color: var(--jf-brand);
}

.jf-filters__actions {
	display: flex;
	flex: none;
	align-items: center;
	gap: var(--jf-space-sm);
}

/* With the browser filtering as the visitor types, submitting would do
 * nothing that has not already happened. events.js adds this class, and the
 * button that would lie about having an effect goes away. */
.jf-filters--live .jf-filters__actions button[type="submit"] {
	display: none;
}

.jf-filters__clear {
	color: var(--jf-text-muted);
	font-size: var(--jf-size-sm);
}

/* The filters that arrived on the URL, each one its own way of being removed.
 * The typed term is not among them — it is visible in the box above. */
.jf-chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--jf-space-2xs);
	margin: var(--jf-space-md) 0 0;
	padding: 0;
	list-style: none;
}

.jf-chip {
	display: inline-flex;
	align-items: center;
	gap: var(--jf-space-2xs);
	padding: var(--jf-space-3xs) var(--jf-space-xs);
	border: 1px solid var(--jf-line-strong);
	border-radius: var(--jf-radius-pill);
	background: var(--jf-ground);
	color: var(--jf-text);
	font-size: var(--jf-size-sm);
	text-decoration: none;
}

.jf-chip:hover,
.jf-chip:focus-visible {
	border-color: var(--jf-brand);
	color: var(--jf-brand-dark);
}

.jf-chip__remove {
	font-size: var(--jf-size-md);
	line-height: 1;
	color: var(--jf-text-faint);
}

.jf-chip:hover .jf-chip__remove {
	color: var(--jf-brand-dark);
}

/* =========================================================================
 * 12. Event grid and cards
 *
 * Two across on a phone and a tablet, four on a desktop, with the jump at
 * 65em rather than 64em so that a landscape tablet — 1024px, exactly 64em —
 * lands on the tablet side of it. There is no three-column step.
 * ====================================================================== */

/* The heading for the grid, and the count, on one line. */
/* The heading sits close under the search, because the two belong together: you
 * type in the box and read the count that answers it. The space above is the
 * same as the space below, so the head reads as the top of the list rather than
 * as something adrift between the bar and the grid. */
.jf-section-head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--jf-space-2xs) var(--jf-space-md);
	margin-block: var(--jf-space-md);
}

.jf-section-head__title {
	margin: 0;
	font-size: var(--jf-size-xl);
	letter-spacing: -0.01em;
}

.jf-section-head__count {
	margin: 0;
	color: var(--jf-text-muted);
	font-size: var(--jf-size-sm);
	font-weight: var(--jf-weight-medium);
}

.jf-month {
	margin-bottom: var(--jf-space-xl);
}

.jf-month__heading {
	margin: 0 0 var(--jf-space-md);
	color: var(--jf-text-muted);
	font-size: var(--jf-size-sm);
	font-weight: var(--jf-weight-bold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.jf-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--jf-space-xs);
	margin: 0;
	padding: 0;
	list-style: none;
}

@media (min-width: 34em) {
	.jf-grid {
		gap: var(--jf-space-md);
	}
}

@media (min-width: 65em) {
	.jf-grid {
		grid-template-columns: repeat(4, 1fr);
	}
}

/* The card. A drawn edge as well as a shadow: on white, a shadow alone leaves
 * the card's boundary to be inferred, and a grid of them reads as a field of
 * floating content rather than as a set of separate things. The edge darkens
 * on hover, which is the part that says it can be clicked. */
.jf-event-tile {
	display: flex;
	flex-direction: column;
	position: relative;
	overflow: hidden;
	border: 1px solid var(--jf-card-line);
	border-radius: var(--jf-radius-lg);
	background: var(--jf-ground);
	box-shadow: var(--jf-shadow-sm);
	transition: border-color var(--jf-duration) var(--jf-ease),
		box-shadow var(--jf-duration) var(--jf-ease),
		transform var(--jf-duration) var(--jf-ease);
}

/* Green on hover rather than a darker grey: --jf-line-strong is five values
 * off --jf-card-line and the change would not be visible, and tinting the edge
 * with the brand says "this responds to you" in the same breath as the title
 * going green beside it. */
.jf-event-tile:hover {
	border-color: var(--jf-secondary);
	box-shadow: var(--jf-shadow-md);
	transform: translateY(-2px);
}

/* The whole card is clickable, via the title's link rather than a wrapper —
 * so the accessible name of that link is the event name, not the entire card
 * read out as one string. Anything that needs to stay clickable in its own
 * right lifts itself above it below. */
.jf-event-tile__link::after {
	content: "";
	position: absolute;
	inset: 0;
}

.jf-event-tile:focus-within {
	border-color: var(--jf-secondary);
	box-shadow: var(--jf-shadow-md);
}

.jf-event-media {
	position: relative;
	aspect-ratio: 16 / 10;
	background: var(--jf-secondary-tint);
}

.jf-event-media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* No photograph yet. The town does the work of the image until one exists. */
.jf-event-media__placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 100%;
	padding: var(--jf-space-sm);
	background: var(--jf-secondary-tint);
	text-align: center;
}

.jf-event-media__town {
	color: var(--jf-brand);
	font-size: var(--jf-size-lg);
	font-weight: var(--jf-weight-bold);
	letter-spacing: 0.06em;
	line-height: var(--jf-leading-tight);
	text-transform: uppercase;
}

/* The promotional label, over the top-right corner of the image.
 *
 * Four colours rather than one, because "tickets available" and "sold out"
 * are not the same message. jobfairs_badge_tone() decides which; the pairs
 * are in tokens.css and every one clears 4.5:1, which is why the amber has
 * dark text where the others have white. */
.jf-label {
	position: absolute;
	top: var(--jf-space-2xs);
	right: var(--jf-space-2xs);
	/* "Tickets nearly sold out" is 23 characters against a 150px card. Room
	 * to wrap, and a radius that survives being two lines tall. */
	max-width: calc(100% - var(--jf-space-sm));
	margin: 0;
	padding: var(--jf-space-3xs) var(--jf-space-xs);
	border-radius: var(--jf-radius-md);
	font-size: var(--jf-size-xs);
	font-weight: var(--jf-weight-bold);
	letter-spacing: 0.04em;
	line-height: var(--jf-leading-snug);
	text-transform: uppercase;
}

.jf-label--available {
	background: var(--jf-label-available-bg);
	color: var(--jf-label-available-text);
}

.jf-label--going {
	background: var(--jf-label-going-bg);
	color: var(--jf-label-going-text);
}

.jf-label--urgent {
	background: var(--jf-label-urgent-bg);
	color: var(--jf-label-urgent-text);
}

.jf-label--closed {
	background: var(--jf-label-closed-bg);
	color: var(--jf-label-closed-text);
}

.jf-label--recruiter {
	background: var(--jf-label-recruiter-bg);
	color: var(--jf-label-recruiter-text);
}

/* The countdown, in a band between the photograph and the name. Tabular
 * figures so the numbers do not jitter sideways as the seconds tick. */
.jf-event-countdown {
	margin: 0;
	padding: var(--jf-space-2xs) var(--jf-space-xs);
	background: var(--jf-brand);
	color: var(--jf-ink-text);
	font-size: var(--jf-size-sm);
	font-weight: var(--jf-weight-bold);
	font-variant-numeric: tabular-nums;
	text-align: center;
}

/*
 * A query container, so the stand controls inside can answer to the width they
 * actually have. A card is not widest on the widest screen — four across a
 * desktop container is narrower than two across a tablet — so a media query
 * would light up in the wrong places. This element's content box is exactly the
 * width the form is laid out in, which makes `cqw` here the same number the
 * flex line is working with.
 */
.jf-event-tile__body {
	container-type: inline-size;
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--jf-space-2xs);
	padding: var(--jf-space-xs);
}

@media (min-width: 34em) {
	.jf-event-tile__body {
		padding: var(--jf-space-sm);
	}
}

/* One step down the scale from the event page's heading: a tile name carries a
 * town and a date and is read four across on a desktop, so it does not need the
 * size a page title does. */
.jf-event-tile__name {
	margin: 0 0 var(--jf-space-3xs);
	font-size: var(--jf-size-md);
	line-height: var(--jf-leading-snug);
	letter-spacing: -0.01em;
}

.jf-event-tile__link {
	color: var(--jf-text);
	text-decoration: none;
}

.jf-event-tile:hover .jf-event-tile__link {
	color: var(--jf-brand);
}

.jf-event-facts {
	margin: 0;
	font-size: var(--jf-size-sm);
}

.jf-event-facts__row {
	padding-block: 0.15rem;
}

.jf-event-facts dd {
	margin: 0;
	min-width: 0;
	color: var(--jf-text-muted);
}

/* Inside a card the facts are an icon and a value, not a written label and a
 * value — which is how the brief specifies the card, and the only thing that
 * fits two across on a phone: a 56px label out of 130px of usable width
 * leaves a venue name wrapping over four lines.
 *
 * The label stays in the markup and stays announced. The icon is what tells
 * the three lines apart on screen, and it is decorative — an icon is not a
 * word, so it never replaces the one underneath it. */
.jf-event-tile .jf-event-facts dt {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.jf-event-tile .jf-event-facts dd {
	display: flex;
	align-items: flex-start;
	gap: var(--jf-space-2xs);
	color: var(--jf-text);
	font-weight: var(--jf-weight-medium);
}

/* The icon keeps its own colour whatever the line beside it does. */
.jf-event-tile .jf-event-facts .jf-icon {
	color: var(--jf-icon);
	/* Optical alignment with the first line of text rather than its box. */
	margin-top: 0.15em;
}

/* The headliner, in a tinted panel with a rule down its leading edge. It is
 * the one piece of a card that is about who is there rather than where and
 * when, so it is the one piece that sits on its own ground. */
.jf-event-headliner {
	display: flex;
	flex-direction: column;
	gap: var(--jf-space-3xs);
	margin: var(--jf-space-2xs) 0 0;
	padding: var(--jf-space-2xs) var(--jf-space-xs);
	border-left: 3px solid var(--jf-secondary);
	border-radius: 0 var(--jf-radius-sm) var(--jf-radius-sm) 0;
	background: var(--jf-brand-tint);
	color: var(--jf-text);
	font-size: var(--jf-size-sm);
}

/* The label is the quieter of the two lines: it says the same thing on every
 * card, and the name is what differs. */
.jf-event-headliner__label {
	font-size: var(--jf-size-xs);
	color: var(--jf-text-muted);
}

.jf-event-headliner__name {
	line-height: var(--jf-leading-snug);
}

/* The call to action sits at the foot of every card whatever is above it, so
 * a row of cards has its buttons on one line. */
.jf-event-tile__cta,
.jf-event-tile__soldout {
	position: relative;
	z-index: 1;
	margin-top: auto;
	padding-top: var(--jf-space-2xs);
}

.jf-event-tile__cta {
	justify-content: center;
	margin-top: auto;
	padding: var(--jf-space-xs) var(--jf-space-sm);
	font-size: var(--jf-size-sm);
	letter-spacing: 0.04em;
	text-align: center;
	text-transform: uppercase;
}

.jf-event-tile__soldout {
	margin-bottom: 0;
	padding: var(--jf-space-xs) var(--jf-space-sm);
	border-radius: var(--jf-radius-md);
	background: var(--jf-ground-raised);
	color: var(--jf-text-faint);
	font-weight: var(--jf-weight-bold);
	font-size: var(--jf-size-sm);
	letter-spacing: 0.04em;
	text-align: center;
	text-transform: uppercase;
	/* Not a control. It sits where the button would, and takes no clicks. */
	pointer-events: none;
}

/* Sold out: the brief asks for the image to blur and the ticket route to
 * close. The card stays legible and stays clickable — the event page still
 * has the date, the venue and a route to the fairs that are not full. */
.jf-event-tile--sold-out .jf-event-media img,
.jf-event-tile--sold-out .jf-event-media__placeholder {
	filter: blur(4px) grayscale(0.6);
	opacity: 0.65;
}

.jf-event-tile--sold-out .jf-event-media {
	/* The blur would otherwise bleed past the corner radius. */
	overflow: hidden;
}

.jf-event-tile--sold-out .jf-event-countdown {
	background: var(--jf-label-closed-bg);
}

/* Live search hides cards and empty month sections with the hidden attribute.
 * The UA sheet's [hidden] { display: none } loses to the display values set
 * on these, so it is restated here where it can win. */
.jf-event-tile[hidden],
.jf-month[hidden],
.jf-empty[hidden] {
	display: none;
}

.jf-empty {
	max-width: var(--jf-container-narrow);
	margin-block: var(--jf-space-xl);
	padding: var(--jf-space-lg);
	border-radius: var(--jf-radius-lg);
	background: var(--jf-ground-raised);
}

.jf-empty__heading {
	margin: 0 0 var(--jf-space-2xs);
	font-size: var(--jf-size-lg);
}

/* =========================================================================
 * 13. The tickets-claimed bar
 * ====================================================================== */

.jf-progress {
	display: flex;
	flex-direction: column;
	gap: var(--jf-space-3xs);
	margin-top: var(--jf-space-2xs);
}

/*
 * A floor under the gap between the bar and the control below it.
 *
 * That control is pushed to the foot of the card with `margin-top: auto`, so on
 * a card with spare height the gap is already generous — measured at over 100px
 * on the tallest in the grid. On a card with none, the auto margin resolves to
 * nothing and all that is left is the body's own 8px, which reads as the button
 * being stuck to the bar. This is the minimum the auto margin cannot go below;
 * it changes nothing on the cards that already had room.
 *
 * On the bar rather than on the control, because there are two of those — the
 * booking form and the link a booked or sold-out card shows instead — and a
 * padding on either would have grown the button itself rather than the space
 * above it.
 */
.jf-event-tile .jf-progress {
	margin-bottom: var(--jf-space-2xs);
}

/*
 * The noun and the figure at either end of a line above the bar. Sentence
 * case: "Tickets claimed" is read, not scanned as a label, and the tracking
 * goes with the capitals it was there to open up.
 */
.jf-progress__head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--jf-space-2xs);
	margin: 0;
	font-size: var(--jf-size-xs);
	font-weight: var(--jf-weight-bold);
}

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

.jf-progress__figure {
	color: var(--jf-text);
	font-variant-numeric: tabular-nums;
}

.jf-progress--full .jf-progress__figure {
	color: var(--jf-status-urgent);
}

.jf-progress__track {
	height: 0.5rem;
	overflow: hidden;
	border-radius: var(--jf-radius-pill);
	background: var(--jf-progress-track);
}

.jf-progress__fill {
	display: block;
	width: var(--jf-progress, 0%);
	height: 100%;
	border-radius: inherit;
	background: var(--jf-progress-fill);
}

.jf-progress--full .jf-progress__fill {
	background: var(--jf-progress-fill-full);
}

/* =========================================================================
 * 14. Event page
 * ====================================================================== */

/* A height, not an aspect ratio. An aspect-ratio box with a max-height keeps
 * the ratio by narrowing itself instead of stopping at the cap, which leaves
 * the banner short of the right-hand edge of the page. */
.jf-event__media {
	position: relative;
	height: clamp(11rem, 30vw, 26rem);
	overflow: hidden;
	background: var(--jf-secondary-tint);
}

.jf-event__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.jf-event__media .jf-event-media__placeholder {
	height: 100%;
}

.jf-event__body {
	padding-block: var(--jf-space-lg) var(--jf-space-2xl);
}

.jf-event__header {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--jf-space-2xs);
	max-width: var(--jf-measure);
	margin-bottom: var(--jf-space-lg);
}

/* The label is absolutely positioned over an image inside a tile; here it is
 * an ordinary block in the flow above the heading. */
.jf-event__header .jf-label {
	position: static;
}

.jf-event__title {
	margin: 0;
}

.jf-event__headliner {
	margin: 0;
	color: var(--jf-text-muted);
	font-size: var(--jf-size-md);
}

.jf-event__header .jf-progress {
	width: 100%;
	max-width: 22rem;
	margin-top: var(--jf-space-2xs);
}

.jf-event__columns {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--jf-space-xl);
	align-items: start;
}

@media (min-width: 56em) {
	.jf-event__columns {
		grid-template-columns: minmax(0, 1fr) 20rem;
	}
}

.jf-event__subhead {
	margin: 0 0 var(--jf-space-sm);
	font-size: var(--jf-size-lg);
}

/* On the event page the facts are the content, not a tile caption, so the
 * labels are visible here and each pair gets a rule above it. */
.jf-event-facts--stacked {
	font-size: var(--jf-size-base);
}

.jf-event-facts--stacked .jf-event-facts__row {
	padding-block: var(--jf-space-2xs);
	border-top: 1px solid var(--jf-line);
}

.jf-event-facts--stacked dt {
	font-size: var(--jf-size-xs);
	font-weight: var(--jf-weight-bold);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--jf-text-faint);
}

.jf-event-facts--stacked dd {
	color: var(--jf-text);
}

.jf-event__address {
	margin: 0;
	font-style: normal;
}

.jf-event__venue-links {
	display: flex;
	flex-wrap: wrap;
	gap: var(--jf-space-sm);
	margin: var(--jf-space-3xs) 0 0;
	font-size: var(--jf-size-sm);
}

/* The panel beside the detail: tickets for a candidate, stands for an
 * exhibitor. One block with two fillings, because the two say the same kind of
 * thing in the same place and a second copy of this would drift.
 *
 * On a wide screen it sits beside the detail and stays in view while the detail
 * scrolls; on a phone it follows the detail, which is the order someone reads
 * in anyway. */
.jf-panel {
	padding: var(--jf-space-md);
	border: 1px solid var(--jf-line);
	border-radius: var(--jf-radius-md);
	background: var(--jf-ground-raised);
}

@media (min-width: 56em) {
	.jf-event__aside {
		position: sticky;
		top: var(--jf-space-md);
	}
}

.jf-panel__heading {
	margin: 0;
	font-size: var(--jf-size-md);
}

.jf-panel__figure {
	margin: 0 0 var(--jf-space-xs);
	color: var(--jf-status-good);
	font-size: var(--jf-size-xl);
	font-weight: var(--jf-weight-bold);
	line-height: var(--jf-leading-tight);
}

.jf-panel__cta {
	width: 100%;
	justify-content: center;
	text-align: center;
}

.jf-panel__closed {
	margin: 0 0 var(--jf-space-2xs);
	font-weight: var(--jf-weight-bold);
}

.jf-panel__note {
	margin: var(--jf-space-xs) 0 0;
	color: var(--jf-text-muted);
	font-size: var(--jf-size-sm);
}

/* WooCommerce styles the notice itself; this only gives it room inside the
 * panel and strips the list markers its markup carries. */
.jf-notices {
	margin-bottom: var(--jf-space-xs);
	font-size: var(--jf-size-sm);
}

.jf-notices ul {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* -------------------------------------------------------------------------
 * The cart in the header, and the booked state on a card.
 *
 * Both answer the same question from opposite ends: how many events have I
 * chosen, and is this one of them. An exhibitor working through ninety fairs
 * should be able to see both without opening anything.
 * ---------------------------------------------------------------------- */

.jf-cart-link {
	display: inline-flex;
	align-items: center;
	gap: var(--jf-space-3xs);
	padding: var(--jf-space-2xs);
	border-radius: var(--jf-radius-md);
	color: var(--jf-text);
	text-decoration: none;
	font-weight: var(--jf-weight-medium);
}

.jf-cart-link:hover {
	color: var(--jf-brand);
}

.jf-cart-link:focus-visible {
	outline: var(--jf-focus-width) solid var(--jf-focus);
	outline-offset: var(--jf-focus-offset);
}

.jf-cart-link__icon {
	width: 1.25rem;
	height: 1.25rem;
}

.jf-cart-link__count {
	min-width: 1.5rem;
	padding: 0 var(--jf-space-3xs);
	border-radius: var(--jf-radius-pill);
	background: var(--jf-brand);
	color: var(--jf-ink-text);
	font-size: var(--jf-size-xs);
	font-weight: var(--jf-weight-bold);
	line-height: 1.5rem;
	text-align: center;
}

/*
 * A booked card. The tick and the word together rather than either alone: an
 * icon is not a word, and "Added" on its own is easy to skim past in a grid
 * where every other card carries a button of the same size.
 *
 * The quiet treatment, not the brand one. A green button beside eighty-nine
 * other green buttons would say nothing; this is the card that is done, so it
 * steps back and lets the ones still to choose stand out.
 */
.jf-button--added {
	border-color: var(--jf-brand);
	background: var(--jf-brand-tint);
	color: var(--jf-brand);
}

.jf-button--added:hover,
.jf-button--added:focus {
	border-color: var(--jf-brand-dark);
	background: var(--jf-secondary-tint);
	color: var(--jf-brand-dark);
}

.jf-button--added .jf-icon {
	width: 1.1em;
	height: 1.1em;
}

/* Above the card's stretched link, for the reason the form beside it is. */
.jf-event-tile .jf-button--added {
	position: relative;
	z-index: 1;
}

/* -------------------------------------------------------------------------
 * Booking a stand from a tile.
 *
 * One button, and the whole card's width for it. The three packages used to sit
 * beside it in a select about 130px wide, which is what the trolley icon, the
 * drawn chevron and a container query all existed to cope with; they are chosen
 * on the cart now, so all of that has gone and what is left is a form with a
 * button in it.
 * ---------------------------------------------------------------------- */

.jf-tile-stand {
	display: flex;
	margin-top: auto;

	/*
	 * Above the card's stretched link. .jf-event-tile__link::after covers the
	 * whole tile so that anywhere on it opens the event, which is why the booked
	 * state above carries the same two lines. Without them the button is under
	 * that overlay and a click on it opens the event page instead of booking,
	 * which is exactly what it looks like: a control that does not work.
	 *
	 * That one got through the first time because the flow was only ever tested
	 * by calling form.submit(), which bypasses the click entirely. Anything
	 * added to a tile needs testing with a real click.
	 */
	position: relative;
	z-index: 1;
}

.jf-tile-stand__add {
	/* The form is the flex child pushed to the bottom of the card now, so the
	 * button inside it fills the form rather than doing that itself. */
	flex: 1 1 auto;
	margin-top: 0;
}

/*
 * The confirmation after booking from a card, floated over the page rather
 * than pushed into it.
 *
 * **It does not fade out on a timer**, which is the one thing a toast usually
 * does. Right now this notice holds the only link to the cart anywhere on the
 * site: there is none in the header, so a toast that removed itself after a few
 * seconds would take the exhibitor's only route to their basket with it. It
 * clears on the next navigation, which is soon enough. Add a cart link to the
 * header and this can start dismissing itself.
 *
 * Bottom of the screen rather than the top, so it never sits over the search
 * bar an exhibitor is about to type into, and centred there rather than in a
 * corner: it is about the grid as a whole rather than about any one card, and
 * the middle is where an eye scanning a page of ninety of them already is.
 * Full width on a phone and a fixed measure on anything larger.
 */
/* WooCommerce's notices carry a bottom margin meant for a page they are stacked
 * on. Inside a panel or a toast that is a strip of empty background under the
 * message, so it goes. */
.jf-notices .woocommerce-message,
.jf-notices .woocommerce-error,
.jf-notices .woocommerce-info {
	margin-bottom: 0;
}

.jf-notices--toast {
	position: fixed;
	z-index: 100;
	/* The inline variant's margin would otherwise add itself to `bottom`. */
	margin-bottom: 0;
	right: var(--jf-space-sm);
	bottom: var(--jf-space-sm);
	left: var(--jf-space-sm);
	max-width: 34rem;
	/*
	 * Centred, by insetting both sides and letting the auto margins share what
	 * is left. The gutters do the work on a phone, where the toast is narrower
	 * than its own maximum and the margins have nothing to share.
	 */
	margin-inline: auto;
	overflow: hidden;
	border-radius: var(--jf-radius-lg);
	box-shadow: var(--jf-shadow-lg);
	background: var(--jf-ground);
}

/*
 * A notice is the theme's, not WooCommerce's, wherever it appears.
 *
 * WooCommerce dresses a message with a coloured rule across the top, an icon
 * from its own font, and the left padding that icon sits in, on a grey ground.
 * All of it goes: the greens and reds are not this site's, and the mark below
 * carries what the rule was carrying.
 *
 * **A confirmation and a refusal are the same notice with a different mark in
 * it.** WooCommerce styles them as opposites; they say the same *kind* of thing
 * about the same action, so they are laid out identically and differ in exactly
 * two values, the mark and its colour.
 *
 * A grid rather than a flex row, because a refusal is a <ul> and can carry more
 * than one reason: the mark takes the first column and every line of the
 * message stacks in the second, so nothing ever wraps back underneath it.
 *
 * Not scoped to the toast, which is the whole point of it being here: the cart
 * shows a confirmation floating and a refusal in the flow, and one shape of
 * message should not depend on which of those it happened to be given.
 */
.jf-notices .woocommerce-message,
.jf-notices .woocommerce-error {
	display: grid;
	grid-template-columns: auto 1fr;
	align-items: center;
	gap: var(--jf-space-2xs);
	padding: var(--jf-space-xs) var(--jf-space-sm);
	border: 0;
	background: var(--jf-ground);
	color: var(--jf-text);
	font-weight: var(--jf-weight-medium);
}

/*
 * The mark: a solid disc with the glyph knocked out of it in white.
 *
 * Two layers on one element rather than one image of both. The disc is a
 * background colour and a border-radius, so it is a token; the glyph is a
 * background image over the top of it. A single two-colour image would have had
 * to carry its own copy of --jf-brand, and editing that token would never have
 * reached it.
 *
 * The glyph is Lucide's, drawn in CSS rather than taken from the sprite, for
 * the reason the select's chevron is: one of the two paths that produce this
 * notice is WooCommerce's own rendered markup, so there is nowhere to put an
 * <svg> that both would share.
 *
 * **WooCommerce positions its own notice icon absolutely** and pads the message
 * out of its way, so the position has to be given back or the mark lands on top
 * of the first word — which is exactly what it did the first time.
 *
 * Centred against the message, except a refusal's list, which stays pinned to
 * the top of its first line — a centred mark would drift as the reasons stack.
 */
.jf-notices .woocommerce-message::before,
.jf-notices .woocommerce-error::before {
	content: "";
	position: static;
	grid-column: 1;
	grid-row: 1;
	width: 1.25rem;
	height: 1.25rem;
	border-radius: var(--jf-radius-pill);
	background-position: center;
	background-repeat: no-repeat;
	/* The glyph inside the disc, with the disc's edge left clear around it. */
	background-size: 0.8125rem;
}

.jf-notices .woocommerce-message::before {
	background-color: var(--jf-brand);
	background-image: var(--jf-check-mark);
}

/*
 * The refusal's red is the one the rest of the site closes things with — the
 * sold-out label, the strip across a full card, the full bar — rather than
 * WooCommerce's, so a refusal looks like this site saying no.
 */
.jf-notices .woocommerce-error::before {
	align-self: start;
	margin-top: 0.0625rem;
	background-color: var(--jf-sold-out);
	background-image: var(--jf-x-mark);
}

/* Every reason in the second column, under the one above it rather than back
 * under the mark. */
.jf-notices .woocommerce-error li {
	grid-column: 2;
}

/* No mark in a toast, just the message. */
.jf-notices--toast .woocommerce-message,
.jf-notices--toast .woocommerce-error {
	grid-template-columns: 1fr;
}

.jf-notices--toast .woocommerce-message::before,
.jf-notices--toast .woocommerce-error::before,
.jf-notices--toast .woocommerce-message::after,
.jf-notices--toast .woocommerce-error::after {
	display: none;
}

.jf-notices--toast .woocommerce-error li {
	grid-column: 1;
}

/*
 * A notice in the flow sits on a tint, because a white card on a white page is
 * not a card at all. The toast needs none: it already has the ground and the
 * shadow that make it read as something over the page rather than in it.
 */
.jf-notices:not(.jf-notices--toast) .woocommerce-message,
.jf-notices:not(.jf-notices--toast) .woocommerce-error {
	background: var(--jf-ground-raised);
	border-radius: var(--jf-radius-md);
}

/*
 * On its way out, five seconds after it arrived.
 *
 * Down as well as out, so the movement reads as the notice leaving rather than
 * the page dimming. assets/js/notices.js removes the element on transitionend,
 * with a timer of its own behind that for the cases where the event does not
 * arrive — a backgrounded tab throttles both. Under reduced motion the rule at
 * the foot of this file cuts the duration to nothing, so the toast goes at
 * once, which is the right answer there.
 */
.jf-notices--toast {
	transition: opacity var(--jf-duration) var(--jf-ease),
		transform var(--jf-duration) var(--jf-ease);
}

.jf-notices--toast.is-leaving {
	opacity: 0;
	transform: translateY(var(--jf-space-2xs));
}

/* =========================================================================
 * 15. The cart, and the checkout's summary of it
 *
 * Two different things, which is why this section is in two halves.
 *
 * The **cart** is a template this theme owns, woocommerce/cart/cart.php, and
 * everything below it is the theme's own markup. It is a template rather than
 * the Cart block because a package is now chosen on the row it belongs to, and
 * the block has no per-line extension point to put a control in.
 *
 * The **checkout** is still the block, and its order summary is read-only by
 * design: there is nothing to choose by then. So it keeps the corrections it
 * always had, which are about the same thing the cart is — on this site a line
 * is a booking for an event, and WooCommerce is built for a shop where a line
 * is a product.
 * ====================================================================== */

/* --- The cart page --- */

/*
 * The empty-cart notice, scoped to this page rather than every
 * `.woocommerce-info` on the site: WooCommerce's coloured rule across the top
 * belongs to no part of this site, the same reasoning section 15 already
 * gives for the messages on this page that are the theme's own.
 */
.woocommerce-cart .woocommerce-info {
	border-top: 0;
	border-radius: var(--jf-radius-md);
}

/*
 * And its icon, drawn from the same shape the header's basket uses rather
 * than left as WooCommerce's own icon font. `content: ""` clears the font's
 * character before the mask is drawn over it; the position is WooCommerce's
 * own, since nothing else here needs to move.
 */
.woocommerce-cart .woocommerce-info::before {
	content: "";
	width: 1.25rem;
	height: 1.25rem;
	background-color: var(--jf-text-muted);
	-webkit-mask: var(--jf-cart-icon-mask) center / contain no-repeat;
	mask: var(--jf-cart-icon-mask) center / contain no-repeat;
}

/* No express checkout on the cart: it pays straight from a browser sheet,
 * skipping past the "choose a stand" prompt. Appears on the checkout instead. */
.woocommerce-cart #wc-stripe-express-checkout-element {
	display: none;
}

/*
 * The bookings on the left, the total on the right, as the block cart had it.
 *
 * A cart is a list and a figure, and the figure is what the exhibitor is
 * deciding on: putting it under a list of five bookings means scrolling past
 * the thing you are checking to find the number you are checking it against.
 *
 * `align-items: start` so the totals sit at the top of their column rather than
 * stretching down beside the rows. The sidebar is a fixed 21rem because it holds
 * three short lines and a button, and there is no width at which it wants more.
 *
 * **Two breakpoints, deliberately, and they are not the same question.** This
 * one asks whether there is room for a column beside the list; the 40em one
 * further down asks whether there is room for three columns inside a row. The
 * sidebar has to go first, because the list is the wider of the two things and
 * needs the whole page before its own rows start folding.
 */
.jf-cart-layout {
	display: grid;
	gap: var(--jf-space-xl);
	align-items: start;
}

@media (min-width: 60em) {
	.jf-cart-layout {
		grid-template-columns: minmax(0, 1fr) 21rem;
	}
}

/*
 * A list of bookings, not a spreadsheet.
 *
 * The base rule near the top of this file makes every <table> a block that
 * scrolls inside itself, so a wide table cannot push the page sideways. That is
 * right for a table someone typed into a page and wrong for this one, which is
 * a layout: display:block takes the rows out of table layout and they shrink to
 * their content instead of filling the column.
 */
/*
 * The selectors below carry `table.` and a parent because WooCommerce's do:
 * `.woocommerce table.shop_table td` is two classes and two elements, and a
 * pair of classes on their own loses to it. Matching that shape is cheaper than
 * an !important, which the next override would have to beat as well. main.css
 * loads after woocommerce.css, so an equal specificity is enough to win.
 */
.jf-cart table.jf-cart__table {
	display: table;
	width: 100%;
	margin: 0;
	/* WooCommerce draws a rounded box round a shop table. This one is a list of
	 * rows on the page rather than a panel sitting on it. */
	border: 0;
	border-radius: 0;
	border-collapse: collapse;
	overflow-x: visible;
}

/*
 * A row is a rule and then some air, which is all the separation three or four
 * bookings need. No zebra striping and no cell borders: the columns are a
 * picture, a booking and a price, and lines between them would be drawing a
 * grid around content that is already in three obvious places.
 */
.jf-cart table.jf-cart__table td {
	padding: var(--jf-space-md) 0;
	border: 0;
	border-top: 1px solid var(--jf-line);
	vertical-align: top;
	background: none;
}

.jf-cart table.jf-cart__table tr:first-child td {
	border-top: 0;
}

/*
 * The venue photograph, square and cropped, exactly as the checkout's summary
 * crops the same picture below.
 *
 * The column is sized here rather than by the image, because on the live site
 * every venue arrives with no photograph at all and the placeholder has its own
 * proportions. A fixed column keeps the row of them level either way.
 */
.jf-cart table.jf-cart__table .jf-cart__media {
	width: 96px;
	padding-right: var(--jf-space-sm);
}

.jf-cart table.jf-cart__table .jf-cart__media img {
	display: block;
	width: 96px;
	height: auto;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	background: var(--jf-secondary-tint);
	border-radius: var(--jf-radius-md);
}

/*
 * The event leads the row and the package sits under it.
 *
 * That is the inversion this whole page makes. "The Original" can be on every
 * line in the cart, so it says nothing about which booking a row is; the fair
 * is the only thing telling two of them apart, and it is what the exhibitor is
 * actually buying.
 *
 * It is an <h2> because it is the heading of what the row is about, and it is
 * sized like body copy because a cart of five would otherwise be five headlines
 * down the page. The page's own <h1> stays the biggest thing on it.
 */
h2.jf-cart__event {
	margin: 0;
	font-size: var(--jf-size-base);
	font-weight: var(--jf-weight-medium);
	line-height: var(--jf-leading-snug);
}

.jf-cart__event a {
	color: var(--jf-text);
	text-decoration: none;
}

.jf-cart__event a:hover,
.jf-cart__event a:focus {
	color: var(--jf-brand-dark);
	text-decoration: underline;
}

/*
 * A row with nothing chosen yet, where its price would be.
 *
 * The quiet treatment rather than a warning colour. Nothing has gone wrong: the
 * exhibitor has added an event and has not yet said which stand they want, which
 * is the ordinary state of a row a moment after it appears. Red here would tell
 * them off for following the flow.
 */
.jf-cart__pending {
	color: var(--jf-text-faint);
	font-style: italic;
}

/*
 * And under the checkout button, saying why it cannot be pressed. It is what
 * `aria-describedby` on that button points at, so it is the reason whether the
 * exhibitor is looking at the page or listening to it.
 */
.jf-cart__blocked {
	margin: var(--jf-space-sm) 0 0;
	color: var(--jf-text-muted);
	font-size: var(--jf-size-sm);
	line-height: var(--jf-leading-snug);
}

/* The package as plain text, on a site with no shop configured to choose from. */
.jf-cart__package {
	margin: var(--jf-space-2xs) 0 0;
	color: var(--jf-text-muted);
	font-size: var(--jf-size-sm);
}

/*
 * The price column: one figure, and the way out under it.
 *
 * Right-aligned because a column of money is read down its last digit, and
 * narrow because there is only ever one number in it — the quantity is fixed at
 * one, so the price and the subtotal are the same figure and the row prints it
 * once.
 */
.jf-cart table.jf-cart__table .jf-cart__price {
	width: 8rem;
	padding-left: var(--jf-space-sm);
	text-align: right;
	white-space: nowrap;
	font-size: var(--jf-size-sm);
}

/*
 * Remove is a link and looks like one, in the red this site closes things with.
 * Not a button: it sits under a price in a narrow column, and a second button
 * on a page whose one green button is "Proceed to Checkout" would compete with
 * the thing the exhibitor came to press.
 */
/*
 * Specificity again: WooCommerce's is `.woocommerce a.remove`. And its colour
 * is the one place in this stylesheet where `!important` is unavoidable rather
 * than lazy — WooCommerce flags both the resting colour and the hover, and an
 * author `!important` can only be beaten by another author `!important`. There
 * is no specificity to reach for. Everything else in this rule wins on its own,
 * which is why only the two colours carry it.
 */
.jf-cart a.jf-cart__remove {
	/* Its own line under the price, rather than beside it: the column is 8rem
	 * wide and a figure and a word sharing that is a row with no air in it. */
	display: block;
	/* WooCommerce's own .remove is a 1.5em square holding a bold red X, with a
	 * full border-radius, sized for the glyph it usually is. This one is the
	 * word, so the box round it goes. */
	width: auto;
	height: auto;
	border-radius: 0;
	text-align: right;
	margin-top: var(--jf-space-2xs);
	color: var(--jf-sold-out) !important;
	font-size: var(--jf-size-sm);
	font-weight: var(--jf-weight-normal);
	line-height: var(--jf-leading-snug);
	text-decoration: underline;
	white-space: nowrap;
}

/*
 * Hover. WooCommerce's rule here is `color: #fff !important` with a red
 * background, written for the small red circle it normally draws: white on red.
 * This control is a word on the page, so that turned it white on nothing and it
 * vanished under the cursor.
 *
 * The background is not flagged, so `background: none` wins on its own; only
 * the colour needs the flag, for the reason above.
 */
.jf-cart a.jf-cart__remove:hover,
.jf-cart a.jf-cart__remove:focus {
	color: var(--jf-sold-out-dark) !important;
	background: none;
}

/* -------------------------------------------------------------------------
 * The package chooser on a row.
 *
 * The same control the tiles used to carry, moved to where it belongs and given
 * the room it never had there. A select rather than three radios, because the
 * cart is a table of bookings and a row is a row; the prices are in the option
 * text, so the choice can be read and made without leaving the control.
 * ---------------------------------------------------------------------- */

.jf-package {
	display: block;
	margin-top: var(--jf-space-2xs);
}

/*
 * As wide as its content and no wider. On a card this stretched to fill the
 * tile because the tile had nothing else on the row; here the row is wide and a
 * select spanning it would be a control the size of a paragraph.
 */
.jf-package__field {
	position: relative;
	display: inline-block;
	max-width: 100%;
}

/*
 * The chevron, drawn rather than left to the browser.
 *
 * Chrome pins a native select's arrow to the right edge of the border box and
 * ignores padding-right entirely, so there is no way to give it room without
 * taking the arrow over: appearance: none, and the shape drawn here instead.
 * It is the same Lucide chevron the FAQ questions use.
 *
 * A mask with currentColor rather than a background-image, because a data URI
 * has to carry its own colour and every colour in this file is a token. The
 * black inside the mask is an alpha channel rather than a colour: the visible
 * one is the select's, so it follows --jf-text without a second copy of it.
 */
.jf-package__field::after {
	position: absolute;
	top: 50%;
	right: var(--jf-space-xs);
	width: 1rem;
	height: 1rem;
	margin-top: -0.5rem;
	background-color: currentColor;
	-webkit-mask: var(--jf-chevron-mask) center / contain no-repeat;
	mask: var(--jf-chevron-mask) center / contain no-repeat;
	content: "";
	pointer-events: none;
}

.jf-package__select {
	max-width: 100%;
	min-height: 44px;
	/* Room for the chevron: its width, and the same gap on both sides of it. */
	padding: var(--jf-space-2xs) calc(var(--jf-space-xs) * 2 + 1rem) var(--jf-space-2xs) var(--jf-space-xs);
	border: 1px solid var(--jf-line-strong);
	border-radius: var(--jf-radius-md);
	background: var(--jf-ground);
	color: var(--jf-text);
	font-family: inherit;
	font-size: var(--jf-size-sm);
	-webkit-appearance: none;
	appearance: none;
	text-overflow: ellipsis;
}

.jf-package__select:focus-visible {
	outline: var(--jf-focus-width) solid var(--jf-focus);
	outline-offset: var(--jf-focus-offset);
}

/* -------------------------------------------------------------------------
 * The discount code, inside the totals.
 *
 * A row of the totals table rather than a block under the bookings, because it
 * is a thing that changes the total: it sits directly under the subtotal, with
 * whatever it produces on the line beneath it.
 *
 * The field and the button are the shape the site's search bar already uses,
 * because they are the same thing: something short to type and one button to
 * press. In a 21rem column they sit side by side and wrap on their own if the
 * wording of either ever grows.
 * ---------------------------------------------------------------------- */

/* The full selector again, to match `.woocommerce table.shop_table td`. */
.jf-cart__collaterals table.shop_table .jf-coupon-row td {
	padding-top: var(--jf-space-sm);
	padding-bottom: var(--jf-space-sm);
	/* The totals' cells are right-aligned, which is right for a column of money
	 * and wrong for the one cell holding a label and a control. */
	text-align: left;
}

/*
 * Below 768px, WooCommerce's own woocommerce-smallscreen.css turns this table
 * responsive and flags every cell `text-align: right !important`, written for
 * a column of numbers stacked into blocks. That right-aligns the toggle and
 * the form here instead, so the coupon box no longer matches its desktop
 * layout. Only a matching `!important` beats another author's, whatever the
 * two selectors' specificity.
 */
.jf-cart__collaterals table.shop_table_responsive .jf-coupon-row td {
	text-align: left !important;
}

/*
 * That same stylesheet prints `attr(data-title) ": "` before every cell, to
 * label a stacked row on mobile. Every other row's td carries a data-title
 * for it to read; this one does not, because the toggle already names itself,
 * so the attr() resolves empty and only the bare ": " was left floating in
 * front of it. No `!important` here: this rule has none to beat.
 */
.jf-cart__collaterals table.shop_table_responsive .jf-coupon-row td::before {
	content: none;
}

/*
 * The same stylesheet also stripes every even row `rgba(0,0,0,.025)`, written
 * for a long list of numbers. It lands on whichever of these two rows the
 * count of rows above happens to put on an even line, so it comes and goes
 * with how many fees or tax lines are showing rather than meaning anything.
 * `.shop_table` alongside `.shop_table_responsive` is the extra class needed
 * to clear WooCommerce's selector rather than tie it, since a tie would leave
 * the winner to source order.
 */
.jf-cart__collaterals table.shop_table.shop_table_responsive tr.order-total td,
.jf-cart__collaterals table.shop_table.shop_table_responsive tr.jf-coupon-row td {
	background: none;
}

/*
 * The control is a <summary>, so the browser's own marker has to be turned off
 * twice: list-style for everything current, and the -webkit- pseudo-element for
 * Safari, which still draws its own. The Lucide chevron goes in its place, as
 * on the FAQs, so the one arrow on this page is drawn by the same hand as every
 * other icon on the site.
 *
 * Quiet: the size of the totals' own labels and in the muted text colour, so a
 * question most people will not answer does not compete with the figures beside
 * it. It reads as a control rather than as a line of text because it carries the
 * chevron and turns brand-coloured under the cursor.
 */
.jf-coupon__toggle {
	display: inline-flex;
	align-items: center;
	gap: var(--jf-space-3xs);
	color: var(--jf-text-muted);
	font-size: var(--jf-size-sm);
	font-weight: var(--jf-weight-medium);
	cursor: pointer;
	list-style: none;
}

.jf-coupon__toggle::-webkit-details-marker {
	display: none;
}

.jf-coupon__toggle:hover {
	color: var(--jf-brand-dark);
}

.jf-coupon__toggle:focus-visible {
	outline: var(--jf-focus-width) solid var(--jf-focus);
	outline-offset: var(--jf-focus-offset);
}

.jf-coupon__chevron {
	width: 1em;
	height: 1em;
	color: var(--jf-brand);
	transition: transform var(--jf-duration) var(--jf-ease);
}

[open] > .jf-coupon__toggle .jf-coupon__chevron {
	transform: rotate(180deg);
}

/* The field appears under the control that asked for it, not beside it. */
.jf-coupon__form {
	margin-top: var(--jf-space-2xs);
}

.jf-coupon__row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--jf-space-2xs);
}

.jf-coupon__input {
	flex: 1 1 8rem;
	min-width: 0;
}

.jf-coupon__apply {
	flex: none;
}

/*
 * The applied code's own remove, on its own line under the amount and worded
 * and coloured like the one on a booking, so the two ways of taking something
 * off this page read as one kind of thing.
 */
.jf-cart__collaterals .woocommerce-remove-coupon {
	display: block;
	margin-top: var(--jf-space-3xs);
	color: var(--jf-sold-out);
	font-size: var(--jf-size-sm);
	text-decoration: underline;
}

.jf-cart__collaterals .woocommerce-remove-coupon:hover,
.jf-cart__collaterals .woocommerce-remove-coupon:focus {
	color: var(--jf-sold-out-dark);
}

/*
 * The row of actions under the table, and the button in it.
 *
 * With scripting, changing a select submits the form on its own and this button
 * is the step that no longer has to be taken, so it goes. Behind `.jf-js`,
 * which inc/woocommerce.php puts on the root element before the body is parsed,
 * so it is never painted and then taken away.
 *
 * Without scripting it is the whole mechanism, which is why it is in the markup
 * rather than added by the script: a cart that needed JavaScript to change a
 * package would be a cart that needed JavaScript.
 */
.jf-cart table.jf-cart__table .jf-cart__actions {
	padding-top: var(--jf-space-sm);
	text-align: right;
}

.jf-js .jf-cart table.jf-cart__table .jf-cart__actions {
	display: none;
}

/*
 * The totals fill their column, whether that column is the sidebar above 60em or
 * the full width below it.
 *
 * WooCommerce floats this to the right at 48% of a full-width page, which is its
 * answer to the same problem and the wrong one here: the column already places
 * it, so the float only makes it half as wide as the space it was given. The
 * selector matches `.woocommerce .cart-collaterals .cart_totals` in length so it
 * can win on order.
 */
.jf-cart__collaterals {
	width: 100%;
	padding: var(--jf-space-sm);
	background: var(--jf-ground-raised);
	border-radius: var(--jf-radius-lg);
}

.jf-cart-layout .jf-cart__collaterals .cart_totals {
	float: none;
	width: 100%;
}

.jf-cart__collaterals table.shop_table {
	display: table;
	width: 100%;
	margin: 0 0 var(--jf-space-sm);
	border: 0;
	border-radius: 0;
	border-collapse: collapse;
	overflow-x: visible;
}

.jf-cart__collaterals table.shop_table th,
.jf-cart__collaterals table.shop_table td {
	padding: var(--jf-space-xs) 0;
	border: 0;
	border-top: 1px solid var(--jf-line);
	background: none;
	font-size: var(--jf-size-sm);
	font-weight: var(--jf-weight-normal);
	text-align: left;
}

.jf-cart__collaterals table.shop_table td {
	text-align: right;
}

/* The order total is the figure the exhibitor is deciding on, so it is the one
 * thing in this block set larger than the rest of it. */
.jf-cart__collaterals table.shop_table .order-total th,
.jf-cart__collaterals table.shop_table .order-total td {
	font-size: var(--jf-size-md);
	font-weight: var(--jf-weight-medium);
}

/* "Total payable" is two words at the row's larger size, and the label
 * column is narrow enough to wrap it onto two lines. */
.jf-cart__collaterals table.shop_table .order-total th {
	white-space: nowrap;
}

/*
 * Proceed to Checkout, in the brand green. WooCommerce ships its own purple,
 * which belongs to no part of this site, and the reasoning is the one below:
 * green is what a visitor is asked to press and red is what has closed, so the
 * two steps of one purchase match.
 *
 * Two elements, one shape. It is a link when it can be followed and a button
 * when it cannot, and the pair are sized and spaced together here so the
 * control does not change size the moment the last package is chosen.
 *
 * The selectors carry the element because WooCommerce's do:
 * `.woocommerce:where(...) a.button.alt` is three classes and an element once
 * `:where()` is discounted, and anything shorter loses to it.
 */
.jf-cart__collaterals a.button.checkout-button,
.jf-cart__collaterals button.checkout-button {
	display: block;
	width: 100%;
	min-height: 48px;
	padding: var(--jf-space-xs) var(--jf-space-sm);
	border: 0;
	border-radius: var(--jf-radius-md);
	background-color: var(--jf-brand);
	color: var(--jf-ink-text);
	font-family: inherit;
	font-size: var(--jf-size-base);
	font-weight: var(--jf-weight-medium);
	line-height: var(--jf-leading-snug);
	text-align: center;
	text-decoration: none;
}

.jf-cart__collaterals a.button.checkout-button:hover,
.jf-cart__collaterals a.button.checkout-button:focus {
	background-color: var(--jf-brand-dark);
	color: var(--jf-ink-text);
}

/*
 * Waiting on a choice: present, readable, and plainly not pressable.
 *
 * Grey rather than a faded green, because a washed-out version of the live
 * colour reads as a rendering fault rather than as a state. This is the line
 * colour the cart already rules its rows with, so the button steps back into
 * the furniture of the page until it has something to do.
 *
 * The text stays --jf-text-muted rather than fading with the ground: measured at
 * 4.97:1 on that grey, so the label is still read rather than guessed at. A
 * disabled control that cannot be read is a control that cannot be understood,
 * which is most of the reason they are a poor pattern to begin with.
 *
 * It keeps its focus ring — the button is focusable on purpose, so that the
 * sentence it points at can be announced.
 */
.jf-cart__collaterals button.checkout-button[aria-disabled="true"] {
	background-color: var(--jf-line-strong);
	color: var(--jf-text-muted);
	cursor: not-allowed;
}

/*
 * A phone has no room for three columns, so the row becomes two: the
 * photograph, and everything else stacked beside it. The price drops under the
 * booking rather than staying in a column narrower than the number in it.
 *
 * A media query rather than a container query, because this table is the width
 * of the page: unlike a card in a grid, there is no container whose width can
 * differ from the screen's.
 */
@media (max-width: 40em) {
	.jf-cart table.jf-cart__table,
	.jf-cart table.jf-cart__table tbody,
	.jf-cart table.jf-cart__table tr,
	.jf-cart table.jf-cart__table td {
		display: block;
	}

	.jf-cart table.jf-cart__table tr {
		display: flex;
		flex-wrap: wrap;
		gap: 0 var(--jf-space-sm);
		padding: var(--jf-space-md) 0;
		border-top: 1px solid var(--jf-line);
	}

	.jf-cart table.jf-cart__table tr:first-child {
		border-top: 0;
	}

	.jf-cart table.jf-cart__table td {
		padding: 0;
		border-top: 0;
	}

	.jf-cart table.jf-cart__table .jf-cart__media,
	.jf-cart table.jf-cart__table .jf-cart__media img {
		width: 72px;
	}

	.jf-cart table.jf-cart__table .jf-cart__detail {
		flex: 1 1 10rem;
	}

	.jf-cart table.jf-cart__table .jf-cart__price {
		flex: 1 1 100%;
		width: auto;
		margin-top: var(--jf-space-xs);
		padding-left: 0;
		text-align: left;
	}

	/* Beside the price rather than under it, now the price has the width of
	 * the row to itself. */
	.jf-cart a.jf-cart__remove {
		display: inline-block;
		margin-top: 0;
		margin-left: var(--jf-space-sm);
		text-align: left;
	}

	.jf-cart table.jf-cart__table .jf-cart__actions {
		display: block;
		text-align: left;
	}

}

/* --- The checkout's order summary --- */

/*
 * The event is the most important thing on the line and arrived as the least.
 * WooCommerce sets item data at 0.8125em, below the product name, which is
 * right when it is holding a size or a colour and wrong here: "The Original"
 * can appear three times in one order, and the fair is the only thing telling
 * those lines apart. So it is set above the product name rather than below it,
 * which is the hierarchy the line actually has.
 */
.wc-block-components-order-summary .wc-block-components-product-details {
	margin-top: var(--jf-space-3xs);
	font-size: var(--jf-size-base);
	line-height: var(--jf-leading-snug);
}

.wc-block-components-order-summary .wc-block-components-product-name {
	font-size: var(--jf-size-sm);
	line-height: var(--jf-leading-snug);
}

.wc-block-components-order-summary .wc-block-components-product-details__name {
	color: var(--jf-text-muted);
	font-weight: var(--jf-weight-normal);
}

.wc-block-components-order-summary .wc-block-components-product-details__value {
	color: var(--jf-text);
	font-weight: var(--jf-weight-medium);
}

/*
 * The venue photograph: one square, filled, whatever shape the original is.
 *
 * These attachments were uploaded before WooCommerce was, so none of them has
 * its `woocommerce_thumbnail` size and the block falls back to whatever exists.
 * That means the real files vary from 300x160 to 300x300, and a row of them
 * came out 64x34 beside 64x64. Cropping in CSS rather than relying on the file
 * makes the column uniform now and stays right if the sizes are ever
 * regenerated.
 *
 * object-fit rather than a CSS background, which is the older way of doing the
 * same crop: the photograph stays an <img>, so it keeps the alt text that names
 * the venue and needs no script to move a URL out of the markup.
 *
 * The tint behind it is the one the event tiles use, so a photograph with
 * transparency sits on the same colour it would there.
 */
.wc-block-components-order-summary-item img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	background: var(--jf-secondary-tint);
	border-radius: var(--jf-radius-md);
}

/*
 * The button that completes a purchase, matched to the cart's own Proceed to
 * Checkout button rather than left in WooCommerce's block styling. Same brand
 * green, same corner, same type: the two are the two steps of one purchase
 * and should read as the same control.
 *
 * Green rather than the branding note's red, and not only because the design
 * won that argument for every other button: red is already spoken for here. It
 * is the sold-out label, the strip across a sold-out card and the full tickets
 * bar. Green is what a visitor is asked to press and red is what has closed,
 * and a red checkout button would ask them to press the colour that elsewhere
 * means they cannot.
 *
 * The radius is --jf-radius-md, which is what .jf-button uses, so it sits at the
 * same corner as every button the theme draws itself. It already carries
 * WooCommerce's 48px minimum height, which happens to be .jf-button's too, so
 * that alone is left alone.
 *
 * The rest is not: the block gives it 18px/30px of its own padding and, since
 * it sits in `.wc-block-checkout__actions_row`, inherits that row's 14px and
 * whatever weight the block's own type happens to be, both smaller and
 * lighter than the cart button's text. Matched here to the same values.
 */
.wc-block-components-checkout-place-order-button {
	padding: var(--jf-space-xs) var(--jf-space-sm);
	background-color: var(--jf-brand);
	border-radius: var(--jf-radius-md);
	font-size: var(--jf-size-base);
	font-weight: var(--jf-weight-medium);
	line-height: var(--jf-leading-snug);
}

.wc-block-components-checkout-place-order-button:hover,
.wc-block-components-checkout-place-order-button:focus {
	background-color: var(--jf-brand-dark);
}


/* =========================================================================
 * 16. FAQ
 *
 * <details> and <summary>, styled. No script: the browser already knows how
 * to open a disclosure, announce its state and find text inside a closed one.
 * ====================================================================== */

.jf-faq {
	max-width: var(--jf-measure);
	margin-top: var(--jf-space-2xl);
}

.jf-faq__heading {
	margin: 0 0 var(--jf-space-sm);
	font-size: var(--jf-size-lg);
}

.jf-faq__item {
	border-top: 1px solid var(--jf-line);
}

.jf-faq__item:last-of-type {
	border-bottom: 1px solid var(--jf-line);
}

/*
 * The disclosure triangle is replaced by a Lucide chevron, so the one arrow on
 * the page is drawn by the same hand as every other icon. The browser's own
 * marker has to be turned off twice: list-style for everything current, and
 * the -webkit- pseudo-element for Safari, which still draws its own.
 */
.jf-faq__question {
	display: flex;
	align-items: flex-start;
	gap: var(--jf-space-2xs);
	padding: var(--jf-space-sm) 0;
	font-size: var(--jf-size-md);
	font-weight: var(--jf-weight-bold);
	cursor: pointer;
	list-style: none;
}

.jf-faq__question::-webkit-details-marker {
	display: none;
}

/* Nudged down to sit on the first line of a question that wraps, rather than
 * centred on a row whose height depends on how long the question is. */
.jf-faq__chevron {
	margin-top: 0.25em;
	color: var(--jf-brand);
	transition: transform var(--jf-duration) var(--jf-ease);
}

[open] > .jf-faq__question .jf-faq__chevron {
	transform: rotate(180deg);
}

/*
 * The answer lines up with the question rather than with the chevron: the
 * indent is the icon's own width — 1.1em of the question's size — plus the gap
 * beside it, so the two stay aligned if either the size or the gap changes.
 */
.jf-faq__answer {
	padding-bottom: var(--jf-space-sm);
	padding-left: calc(var(--jf-size-md) * 1.1 + var(--jf-space-2xs));
	color: var(--jf-text-muted);
}

/* The reset takes every paragraph's margin away, so an answer of four
 * paragraphs would otherwise be one block of text. Spacing between the parts,
 * and none before the first or after the last. */
.jf-faq__answer > * + * {
	margin-top: var(--jf-space-xs);
}

.jf-faq__answer ul {
	padding-left: 1.25rem;
}

.jf-faq__answer li + li {
	margin-top: var(--jf-space-3xs);
}

.jf-faq__answer > :first-child {
	margin-top: 0;
}

.jf-faq__answer > :last-child {
	margin-bottom: 0;
}

.jf-faq__more {
	margin-top: var(--jf-space-md);
}

/* =========================================================================
 * 15a. The candidate FAQ page
 *
 * The same question rows as the event page's block — .jf-faq__item and its two
 * children are shared, so a question looks the same wherever a visitor meets
 * one. What is added here is the page around them: the search box, the count,
 * and the closing note.
 * ====================================================================== */

/*
 * The page is the archive's three bands — .jf-band, .jf-search-bar, then
 * .jf-container — so it needs no width of its own. Everything below styles what
 * sits inside the third one.
 */

/* A row spans the container, as a tile does, but an answer is prose and stops
 * at a readable measure. Without this a paragraph would run the full width of a
 * desktop and be unreadable at exactly the moment someone is reading it. */
.jf-faqs .jf-faq__answer {
	max-width: var(--jf-measure);
}

.jf-faqs__list {
	margin-bottom: var(--jf-space-xl);
}

/* The rule between two questions belongs to the row above it, so a hidden row
 * takes its rule with it and a filtered list has no gaps in its edges. */
.jf-faqs__list .jf-faq__item:first-of-type {
	border-top: 1px solid var(--jf-line);
}

/*
 * The question is a heading and a summary at once, and it takes the row's size
 * and weight rather than a heading's — thirty-nine headings at heading size is
 * a list nobody can scan. Inheriting rather than setting its own size is what
 * keeps it and the chevron beside it in step.
 */
.jf-faq__question-text {
	font-size: inherit;
	font-weight: inherit;
}

.jf-faqs__closing {
	max-width: var(--jf-measure);
	margin-bottom: var(--jf-space-2xl);
	padding: var(--jf-space-md);
	border-radius: var(--jf-radius-md);
	background: var(--jf-brand-tint);
}

.jf-faqs__closing-heading {
	margin-bottom: var(--jf-space-2xs);
	font-size: var(--jf-size-lg);
}

.jf-faqs__closing p + p {
	margin-top: var(--jf-space-2xs);
}

.jf-faqs__closing .jf-button {
	margin-top: var(--jf-space-sm);
}

/* =========================================================================
 * 17. Motion preferences
 * ====================================================================== */

@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}

	/* ::backdrop is in the list because it is not a descendant of anything and
	 * `*` does not reach it. Without it the stand dialog would snap into place
	 * over a scrim that was still fading. */
	*,
	*::before,
	*::after,
	*::backdrop {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}
