/*
 * Job Board — front-end styles.
 *
 * ---------------------------------------------------------------------------
 * WHAT THIS FILE IS RESPONSIBLE FOR, AND WHAT IT IS NOT.
 *
 * The plugin owns the job markup (VJH_Render), so it has to own the CSS that makes
 * that markup a card, a filter panel and a search bar. It does not own the site's
 * brand. Those are two different jobs and this file only does the first: layout,
 * rhythm, states, and the accessibility affordances that live in CSS rather than in
 * markup.
 *
 * Every colour, radius, shadow and type size resolves through a `--vjh-*` custom
 * property with a fixed default. The theme sets the brand by redeclaring those
 * properties on `:root` — nothing else. That is the §24 seam in one sentence: the
 * shipped theme paints navy and gold over this, a different theme paints something
 * else, and neither has to touch a selector.
 *
 * WHY THE DEFAULTS ARE FIXED VALUES AND NOT `var(--wp--preset--color--accent)`.
 * It reads well and it is a contrast lottery. Inheriting a host theme's accent means
 * a theme whose accent is pale pink gets white button text on pale pink, and the
 * plugin has no way to know. Fixed, checked defaults plus one documented override
 * point is predictable; automatic adoption is only predictable until it is not.
 *
 * WHY THERE IS NO `prefers-color-scheme` BLOCK. The page's light or dark is the
 * theme's decision, not the OS's — a plugin that darkens its cards because the
 * visitor's laptop is in dark mode produces dark cards on a light theme's white
 * page. The theme flips the tokens if it wants that.
 *
 * NO JAVASCRIPT IS REQUIRED BY ANYTHING HERE. The drawer is the one exception and it
 * is opt-in from the other direction: the rules that hide the filter panel are all
 * scoped under `.vjh-js`, which only exists once assets/js/jobs.js has run. With JS
 * off or broken, every filter stays on screen as a plain form.
 * ---------------------------------------------------------------------------
 */

:root {
	/* --- ink ------------------------------------------------------------- */
	--vjh-ink: #14181f;
	--vjh-ink-soft: #4b5563;
	--vjh-ink-faint: #6b7280;

	/* --- surfaces -------------------------------------------------------- */
	--vjh-surface: #ffffff;
	--vjh-surface-2: #f6f7f9;
	--vjh-line: #e3e6ea;
	--vjh-line-strong: #cdd2d9;

	/*
	 * Primary is a deep navy rather than a bright blue. At 20 cards a page the
	 * button colour repeats 20 times; a saturated accent at that density stops
	 * reading as emphasis and starts reading as decoration (§31).
	 */
	--vjh-accent: #12284c;
	--vjh-accent-ink: #ffffff;
	--vjh-accent-hover: #1b3768;

	/* The one button on the site that has to win: Apply (§10). Its own token so
	   the theme can make it the brand colour without repainting every button. */
	--vjh-cta: var(--vjh-accent);
	--vjh-cta-ink: var(--vjh-accent-ink);
	--vjh-cta-hover: var(--vjh-accent-hover);

	--vjh-link: #12284c;

	/* --- meaning --------------------------------------------------------- */
	--vjh-good: #0a6847;      /* remote, new — a fact, not a promotion */
	--vjh-good-bg: #ecfdf5;
	--vjh-salary: #14532d;    /* the most-scanned chip on a card */
	--vjh-salary-bg: #f0fdf4;
	--vjh-neutral-bg: #f2f4f7;

	/* --- geometry -------------------------------------------------------- */
	--vjh-radius: 12px;
	--vjh-radius-sm: 8px;
	--vjh-radius-pill: 999px;
	--vjh-gap: 16px;
	--vjh-shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 1px 3px rgba(16, 24, 40, .06);
	--vjh-shadow-lift: 0 4px 12px rgba(16, 24, 40, .08), 0 1px 3px rgba(16, 24, 40, .06);
	--vjh-focus: #12284c;

	/* --- type ------------------------------------------------------------ */
	/* Sizes, not families. Families come from the theme, so a card title inherits
	   whatever theme.json set for h3 and only the scale is overridden here. */
	--vjh-title-size: 1.0625rem;
	--vjh-body-size: .9375rem;
	--vjh-small-size: .8125rem;

	/* --- the employer mark ------------------------------------------------ */
	/* Real logos arrive as 128px PNGs on white or transparent backgrounds, so the
	   chip behind them is light even when the page is dark. Keeping the initials
	   tile in the same family is what makes a mixed grid look deliberate. */
	--vjh-logo-chip: #ffffff;
	--vjh-logo-size: 56px;

	/* --- layout hooks the theme sets ------------------------------------- */
	/* Offset for anything sticky, so a sticky site header does not sit on top of
	   the sticky filter panel. The theme knows its own header height. */
	--vjh-sticky-top: 1rem;
}

/*
 * The hairlines and muted inks are derived from the ink and surface tokens where the
 * browser can do it, so a theme that redeclares two properties gets a coherent set
 * rather than navy text on default grey borders. The fixed values above stay as the
 * fallback: color-mix() is widely supported, but a declaration a browser cannot parse
 * is a declaration that drops, and a card with no border is worse than one with a
 * slightly wrong border.
 */
@supports (color: color-mix(in srgb, red 50%, blue)) {
	:root {
		--vjh-ink-soft: color-mix(in srgb, var(--vjh-ink) 72%, var(--vjh-surface));
		--vjh-ink-faint: color-mix(in srgb, var(--vjh-ink) 58%, var(--vjh-surface));
		--vjh-surface-2: color-mix(in srgb, var(--vjh-ink) 4%, var(--vjh-surface));
		--vjh-line: color-mix(in srgb, var(--vjh-ink) 12%, var(--vjh-surface));
		--vjh-line-strong: color-mix(in srgb, var(--vjh-ink) 22%, var(--vjh-surface));
		--vjh-neutral-bg: color-mix(in srgb, var(--vjh-ink) 6%, var(--vjh-surface));
	}
}

/*
 * The core visually-hidden utility. The plugin's own markup uses it for the hero
 * search labels, and a plugin cannot assume the active theme defines it — an
 * undefined .screen-reader-text is a visible "Job title, keyword or company" label
 * stacked on top of the placeholder that already says it.
 */
.screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* --------------------------------------------------------------------- buttons */

.vjh-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: .5em;
	padding: .7em 1.25em;
	border: 1px solid transparent;
	border-radius: var(--vjh-radius-sm);
	background: var(--vjh-neutral-bg);
	color: var(--vjh-ink);
	font-size: var(--vjh-body-size);
	font-weight: 600;
	line-height: 1.2;
	text-decoration: none;
	cursor: pointer;
	transition: background-color .15s ease, border-color .15s ease, box-shadow .15s ease;
}

.vjh-btn:hover,
.vjh-btn:focus {
	text-decoration: none;
}

.vjh-btn--primary {
	background: var(--vjh-accent);
	color: var(--vjh-accent-ink);
}

.vjh-btn--primary:hover {
	background: var(--vjh-accent-hover);
	color: var(--vjh-accent-ink);
}

.vjh-btn--ghost {
	background: transparent;
	border-color: var(--vjh-line-strong);
	color: var(--vjh-ink);
}

.vjh-btn--ghost:hover {
	background: var(--vjh-surface-2);
}

/* Apply is the one call to action a visitor came for, so it is the only button
   allowed to be full width and a size larger. */
.vjh-btn--apply {
	background: var(--vjh-cta);
	color: var(--vjh-cta-ink);
	padding: .9em 1.75em;
	font-size: 1rem;
	width: 100%;
}

.vjh-btn--apply:hover {
	background: var(--vjh-cta-hover);
	color: var(--vjh-cta-ink);
}

/* Focus is never removed, only replaced. :focus-visible so a mouse click on Apply
   does not leave a ring behind, while a keyboard user always sees where they are. */
.vjh-btn:focus-visible,
.vjh-card__link:focus-visible,
.vjh-search__input:focus-visible,
.vjh-field__input:focus-visible,
.vjh-field__select:focus-visible,
.vjh-sort__select:focus-visible,
.vjh-termlinks__list a:focus-visible,
.vjh-pagination a:focus-visible,
.vjh-option input:focus-visible {
	outline: 2px solid var(--vjh-focus);
	outline-offset: 2px;
}

/* ---------------------------------------------------------------- §4 hero search */

/*
 * One field group that reads as a single control: two inputs and a button sharing a
 * border, divided by hairlines. It is still a plain GET form with two named inputs —
 * the appearance of a single search bar is entirely CSS, so nothing about it can
 * intercept a submit.
 */
.vjh-search {
	display: grid;
	grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto;
	align-items: stretch;
	gap: 0;
	background: var(--vjh-surface);
	border: 1px solid var(--vjh-line-strong);
	border-radius: var(--vjh-radius);
	padding: 6px;
	box-shadow: var(--vjh-shadow);
}

.vjh-search__field {
	display: flex;
	align-items: center;
	min-width: 0; /* without this a long placeholder widens the grid track and the
	                 whole bar overflows the page — §25's horizontal scroll, from CSS */
}

.vjh-search__field--where {
	border-left: 1px solid var(--vjh-line);
}

.vjh-search__input {
	width: 100%;
	min-width: 0;
	border: 0;
	background: transparent;
	color: var(--vjh-ink);
	font-size: 1rem;
	line-height: 1.4;
	padding: .85em 1em;
	border-radius: var(--vjh-radius-sm);
	-webkit-appearance: none;
	appearance: none;
}

.vjh-search__input::placeholder {
	color: var(--vjh-ink-faint);
	opacity: 1; /* Firefox dims placeholders by default; the token already decides how faint */
}

/* The focus ring goes on the input, not the bar, so it is obvious which of the two
   fields has the caret. */
.vjh-search__input:focus {
	outline: none;
}

.vjh-search__submit {
	white-space: nowrap;
	padding-inline: 1.6em;
}

/* A search bar in a sidebar or a 404 page: same markup, less presence. */
.vjh-search--inline {
	box-shadow: none;
	border-radius: var(--vjh-radius-sm);
	padding: 4px;
}

.vjh-search--inline .vjh-search__input {
	font-size: var(--vjh-body-size);
	padding: .6em .8em;
}

.vjh-search--inline .vjh-search__submit {
	padding: .6em 1.1em;
	font-size: var(--vjh-small-size);
}

@media (max-width: 782px) {
	/*
	 * Stacked, because three controls in a row on a phone gives each of them about
	 * 90px and a search bar you cannot read what you typed into is not a search bar.
	 */
	.vjh-search {
		grid-template-columns: 1fr;
		padding: 8px;
		gap: 2px;
	}

	.vjh-search__field--where {
		border-left: 0;
		border-top: 1px solid var(--vjh-line);
	}

	.vjh-search__submit {
		margin-top: 6px;
		width: 100%;
	}
}

/* ------------------------------------------------------------------- §8 job card */

.vjh-cards {
	display: grid;
	grid-template-columns: 1fr;
	gap: 12px;
}

.vjh-card {
	position: relative; /* anchor for the stretched title link below */
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: var(--vjh-gap);
	padding: 16px;
	background: var(--vjh-surface);
	border: 1px solid var(--vjh-line);
	border-radius: var(--vjh-radius);
	box-shadow: var(--vjh-shadow);
	transition: box-shadow .15s ease, border-color .15s ease, transform .15s ease;
}

.vjh-card:hover {
	border-color: var(--vjh-line-strong);
	box-shadow: var(--vjh-shadow-lift);
}

.vjh-card__body {
	min-width: 0; /* lets long single-word titles and URLs wrap instead of stretching */
}

.vjh-card__title {
	margin: 0 0 4px;
	font-size: var(--vjh-title-size);
	line-height: 1.35;
	font-weight: 650;
	/* No font-family: the heading level is a real h2/h3/h4 so it inherits the
	   theme's heading face, and only the card's scale is overridden here. */
}

.vjh-card__link {
	color: var(--vjh-ink);
	text-decoration: none;
}

.vjh-card__link:hover {
	color: var(--vjh-link);
	text-decoration: underline;
	text-underline-offset: 2px;
}

/*
 * The whole card is the click target, via a stretched pseudo-element on the title
 * link. Done this way rather than with a wrapping <a> or a JS click handler because
 * the accessible name of the link stays the job title, the card keeps its heading
 * for the document outline, and the employer link inside it is still its own link.
 */
.vjh-card__link::after {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: inherit;
}

.vjh-card__meta {
	margin: 0;
	font-size: var(--vjh-body-size);
	color: var(--vjh-ink-soft);
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
}

.vjh-card__company {
	font-weight: 600;
	color: var(--vjh-ink);
}

/* Above the stretched link, or the employer archive is unreachable by mouse. */
.vjh-card__company a {
	position: relative;
	z-index: 1;
	color: inherit;
	text-decoration: none;
}

.vjh-card__company a:hover {
	text-decoration: underline;
	text-underline-offset: 2px;
}

/* The separator belongs to the pair, not to either half: a job with no employer
   name must not start with a floating dot. */
.vjh-card__company + .vjh-card__location::before {
	content: "\00b7";
	margin: 0 .5em;
	color: var(--vjh-ink-faint);
}

.vjh-card__chips {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin: 10px 0 0;
	padding: 0;
	list-style: none;
}

.vjh-chip {
	display: inline-block;
	padding: 3px 9px;
	border-radius: var(--vjh-radius-pill);
	background: var(--vjh-neutral-bg);
	color: var(--vjh-ink-soft);
	font-size: var(--vjh-small-size);
	font-weight: 600;
	line-height: 1.5;
	white-space: nowrap;
}

/* Salary and remote are the two chips people scan for, so they are the two that
   carry colour. Type and hybrid stay neutral — four coloured chips per card is a
   card with no emphasis at all. */
.vjh-chip--salary {
	background: var(--vjh-salary-bg);
	color: var(--vjh-salary);
}

.vjh-chip--remote {
	background: var(--vjh-good-bg);
	color: var(--vjh-good);
}

.vjh-card__excerpt {
	margin: 10px 0 0;
	font-size: var(--vjh-body-size);
	line-height: 1.6;
	color: var(--vjh-ink-soft);
	/* Two lines is enough to tell whether the job is worth opening, and a fixed
	   clamp keeps a 20-card page from becoming a 20-paragraph page. */
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.vjh-card__footer {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 10px;
	margin: 12px 0 0;
	font-size: var(--vjh-small-size);
	color: var(--vjh-ink-faint);
}

.vjh-card__new {
	color: var(--vjh-good);
	background: var(--vjh-good-bg);
	padding: 2px 8px;
	border-radius: var(--vjh-radius-pill);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .04em;
	font-size: .6875rem;
}

.vjh-card__closed {
	color: var(--vjh-ink-faint);
	background: var(--vjh-neutral-bg);
	padding: 2px 8px;
	border-radius: var(--vjh-radius-pill);
	font-weight: 600;
}

/* A closed job is still a real page and still gets read; it is quieter, not hidden.
   No strikethrough — the title is not wrong, the vacancy is gone. */
.vjh-card--expired {
	background: var(--vjh-surface-2);
	box-shadow: none;
}

.vjh-card--expired .vjh-card__title,
.vjh-card--expired .vjh-card__company {
	color: var(--vjh-ink-soft);
}

.vjh-card--expired .vjh-logo {
	opacity: .6;
}

/* Compact: the related-jobs strip and any 3-column grid. Same card, no excerpt,
   tighter box — set by the caller passing compact, not by a media query. */
.vjh-card--compact {
	padding: 13px 14px;
	gap: 12px;
}

.vjh-card--compact .vjh-card__title {
	font-size: var(--vjh-body-size);
}

.vjh-card--compact .vjh-logo {
	--vjh-logo-size: 40px;
}

/* --------------------------------------------------------------------- §9 logo */

.vjh-logo {
	flex: none;
	width: var(--vjh-logo-size);
	height: var(--vjh-logo-size);
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
	border-radius: var(--vjh-radius-sm);
	background: var(--vjh-logo-chip);
	border: 1px solid var(--vjh-line);
}

.vjh-logo__img {
	width: 100%;
	height: 100%;
	/* `contain`, not `cover`. A wordmark cropped to a square is unrecognisable, and
	   these are 128px squares of wildly different aspect ratios inside them. */
	object-fit: contain;
	padding: 6px;
	box-sizing: border-box;
	display: block;
}

/*
 * The initials tile. Colours arrive as two custom properties on the element, set from
 * VJH_Logos::color_for() — a checked pair per employer, stable across page loads.
 * The stylesheet knows the shape, the markup knows the employer.
 */
.vjh-logo--initials {
	background: var(--vjh-logo-bg, var(--vjh-neutral-bg));
	color: var(--vjh-logo-fg, var(--vjh-ink-soft));
	border-color: transparent;
	font-weight: 700;
	font-size: calc(var(--vjh-logo-size) * .34);
	letter-spacing: .02em;
	line-height: 1;
	-webkit-user-select: none;
	user-select: none;
}

/* ------------------------------------------------------------ multi-column lists */

/*
 * [vjh_jobs columns="3"] and the equivalent block. The column count is an editor's
 * intent for a wide screen, not a promise at every width — below the breakpoints the
 * grid collapses on its own, because a 3-column grid of job cards on a tablet gives
 * each card 200px and the salary chip wraps onto its own line.
 */
.vjh-joblist--cols-2 .vjh-cards,
.vjh-joblist--cols-3 .vjh-cards,
.vjh-joblist--cols-4 .vjh-cards {
	gap: 14px;
}

@media (min-width: 783px) {
	.vjh-joblist--cols-2 .vjh-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.vjh-joblist--cols-3 .vjh-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.vjh-joblist--cols-4 .vjh-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1100px) {
	.vjh-joblist--cols-3 .vjh-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
	.vjh-joblist--cols-4 .vjh-cards { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ------------------------------------------------------------- §17/§18 filters */

.vjh-filters {
	background: var(--vjh-surface);
	border: 1px solid var(--vjh-line);
	border-radius: var(--vjh-radius);
	padding: 4px 18px 18px;
}

/* Sticky on wide screens: the filters stay reachable through a 20-card scroll
   without a "back to top" trip. `--vjh-sticky-top` is the theme's header height. */
@media (min-width: 861px) {
	.vjh-filters {
		position: sticky;
		top: var(--vjh-sticky-top);
		max-height: calc(100vh - var(--vjh-sticky-top) - 1rem);
		overflow-y: auto;
	}
}

.vjh-filters__head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 12px;
	padding: 14px 0 10px;
	border-bottom: 1px solid var(--vjh-line);
	margin-bottom: 4px;
}

.vjh-filters__title {
	margin: 0;
	font-size: var(--vjh-body-size);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .05em;
	color: var(--vjh-ink-soft);
}

.vjh-filters__clear {
	font-size: var(--vjh-small-size);
	color: var(--vjh-link);
	white-space: nowrap;
}

.vjh-field {
	padding: 14px 0 0;
	border: 0;
	margin: 0;
	min-width: 0;
}

.vjh-field--group {
	padding-top: 16px;
}

.vjh-field__label {
	display: block;
	padding: 0;
	margin-bottom: 7px;
	font-size: var(--vjh-small-size);
	font-weight: 700;
	color: var(--vjh-ink);
}

.vjh-field__input,
.vjh-field__select {
	width: 100%;
	max-width: 100%;
	box-sizing: border-box;
	padding: .6em .7em;
	border: 1px solid var(--vjh-line-strong);
	border-radius: var(--vjh-radius-sm);
	background: var(--vjh-surface);
	color: var(--vjh-ink);
	font-size: var(--vjh-body-size);
	line-height: 1.4;
}

.vjh-field__note {
	margin: 6px 0 0;
	font-size: .75rem;
	color: var(--vjh-ink-faint);
	line-height: 1.5;
}

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

.vjh-options__item {
	margin: 0;
}

/*
 * The whole row is the label, so the hit area is the text and not just the 13px
 * radio. On a phone that is the difference between a filter panel that works and one
 * that needs three attempts per option.
 */
.vjh-option {
	display: flex;
	align-items: center;
	gap: 9px;
	padding: 6px 0;
	font-size: var(--vjh-body-size);
	color: var(--vjh-ink-soft);
	cursor: pointer;
	line-height: 1.4;
}

.vjh-option:hover {
	color: var(--vjh-ink);
}

.vjh-option input {
	flex: none;
	margin: 0;
}

/* A checked option is stated in text weight as well as by the control, so it
   survives being screenshotted, printed, or viewed by someone who cannot pick the
   filled radio out of a column of empty ones. */
.vjh-option input:checked + span {
	color: var(--vjh-ink);
	font-weight: 600;
}

.vjh-filters__submit {
	width: 100%;
	margin-top: 18px;
}

/* ------------------------------------------------------- the mobile drawer (§17) */

/*
 * Everything in this section is scoped under `.vjh-js`, added by jobs.js. The toggle
 * button and the close button are also created by jobs.js. Without the script none of
 * these selectors match, the panel is simply a form in the page, and every filter
 * still works — which is the whole reason the filters are a GET form.
 */
.vjh-filters__toggle,
.vjh-filters__close {
	display: none;
}

@media (max-width: 860px) {
	.vjh-js .vjh-filters__toggle {
		display: inline-flex;
		width: 100%;
		justify-content: space-between;
	}

	.vjh-js .vjh-filters__close {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		width: 32px;
		height: 32px;
		padding: 0;
		border: 1px solid var(--vjh-line);
		border-radius: var(--vjh-radius-sm);
		background: var(--vjh-surface);
		color: var(--vjh-ink);
		font-size: 18px;
		line-height: 1;
		cursor: pointer;
	}

	/* Closed: the panel is not in the layout. The state lives on the button's
	   aria-expanded, so there is exactly one source of truth and the script does not
	   have to keep a class and an ARIA attribute in agreement. */
	.vjh-js .vjh-filters__toggle[aria-expanded="false"] + .vjh-filters {
		display: none;
	}

	.vjh-js .vjh-filters__toggle[aria-expanded="true"] + .vjh-filters {
		position: fixed;
		inset: 0;
		z-index: 10000;
		margin: 0;
		max-height: none;
		border: 0;
		border-radius: 0;
		overflow-y: auto;
		-webkit-overflow-scrolling: touch;
		padding: 0 18px 18px;
	}

	/* The head stays put while the options scroll under it, so "Clear all" and the
	   close button are always reachable in a long panel. */
	.vjh-js .vjh-filters__toggle[aria-expanded="true"] + .vjh-filters .vjh-filters__head {
		position: sticky;
		top: 0;
		z-index: 1;
		background: var(--vjh-surface);
		padding-top: 16px;
	}

	/* And Apply stays on screen, because a filter panel whose submit button is 600px
	   below the fold is a panel people abandon half-set. */
	.vjh-js .vjh-filters__toggle[aria-expanded="true"] + .vjh-filters .vjh-filters__submit {
		position: sticky;
		bottom: 0;
		margin-bottom: 0;
	}
}

/* Set on <html> while the drawer is open. Locking the scroll on the element that
   actually scrolls avoids the iOS bug where locking <body> jumps to the top. */
.vjh-locked {
	overflow: hidden;
}

/* ------------------------------------------------------ sort, count, pagination */

/* The count line and the sort control share one row, pushed apart. They wrap to two
   lines on a narrow screen rather than shrinking the select, because a sort control
   narrower than its longest option truncates the option the visitor is looking for. */
.vjh-results__head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 10px 18px;
	margin: 0 0 18px;
	padding-bottom: 14px;
	border-bottom: 1px solid var(--vjh-line);
}

.vjh-results__count {
	margin: 0;
	font-size: var(--vjh-body-size);
	color: var(--vjh-ink-soft);
}

.vjh-sort {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0;
}

.vjh-sort__label {
	font-size: var(--vjh-small-size);
	color: var(--vjh-ink-faint);
	white-space: nowrap;
}

.vjh-sort__select {
	padding: .45em 1.9em .45em .6em;
	border: 1px solid var(--vjh-line-strong);
	border-radius: var(--vjh-radius-sm);
	background: var(--vjh-surface);
	color: var(--vjh-ink);
	font-size: var(--vjh-small-size);
	max-width: 100%;
}

.vjh-pagination {
	margin: 28px 0 0;
}

.vjh-pagination__list {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin: 0;
	padding: 0;
	list-style: none;
	align-items: center;
}

.vjh-pagination__item {
	margin: 0;
}

/* `.page-numbers` is paginate_links()' own class — styled here rather than rewritten
   in PHP, because core generates correct rel/aria markup and the only thing missing
   is the appearance. */
.vjh-pagination .page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 40px;
	height: 40px;
	padding: 0 .7em;
	border: 1px solid var(--vjh-line);
	border-radius: var(--vjh-radius-sm);
	background: var(--vjh-surface);
	color: var(--vjh-ink);
	font-size: var(--vjh-body-size);
	text-decoration: none;
}

.vjh-pagination a.page-numbers:hover {
	border-color: var(--vjh-line-strong);
	background: var(--vjh-surface-2);
}

.vjh-pagination .page-numbers.current {
	background: var(--vjh-accent);
	border-color: var(--vjh-accent);
	color: var(--vjh-accent-ink);
	font-weight: 700;
}

.vjh-pagination .page-numbers.dots {
	border-color: transparent;
	background: none;
	color: var(--vjh-ink-faint);
	min-width: 24px;
}

/* ------------------------------------------------------------ empty and expired */

.vjh-empty {
	padding: 40px 24px;
	border: 1px dashed var(--vjh-line-strong);
	border-radius: var(--vjh-radius);
	background: var(--vjh-surface-2);
	text-align: center;
}

.vjh-empty__title {
	margin: 0 0 8px;
	font-size: 1.0625rem;
	font-weight: 700;
	color: var(--vjh-ink);
}

.vjh-empty__body {
	margin: 0 auto;
	max-width: 38rem;
	font-size: var(--vjh-body-size);
	color: var(--vjh-ink-soft);
	line-height: 1.6;
}

.vjh-empty__actions {
	margin: 18px 0 0;
}

.vjh-expired {
	padding: 18px;
	border: 1px solid var(--vjh-line-strong);
	border-left-width: 4px;
	border-radius: var(--vjh-radius-sm);
	background: var(--vjh-surface-2);
}

.vjh-expired__title {
	margin: 0 0 4px;
	font-weight: 700;
	color: var(--vjh-ink);
}

.vjh-expired__reason {
	margin: 0;
	font-size: var(--vjh-small-size);
	color: var(--vjh-ink-soft);
}

.vjh-expired__actions {
	margin: 14px 0 0;
}

/* ------------------------------------------------------- §29/§37 breadcrumbs */

/*
 * A quiet line, not a feature. It sits above the page heading and its job is to be
 * available when someone looks for it, so it is one step down in size and colour
 * from body text and carries no background, border or box of its own.
 */
.vjh-crumbs {
	margin: 0 0 14px;
	font-size: var(--vjh-small-size);
	color: var(--vjh-ink-faint);
}

.vjh-crumbs__list {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 2px 8px;
	margin: 0;
	padding: 0;
	/* Safari drops list semantics from a list styled this way, which is why the
	   markup carries an explicit role="list" — see VJH_Render::breadcrumbs(). */
	list-style: none;
}

.vjh-crumbs__item {
	display: flex;
	align-items: center;
	gap: 8px;
	min-width: 0;
	margin: 0;
}

/* The separator is generated content with empty alternative text, so screen
   readers do not read "slash" between every crumb. Browsers that do not support
   the alt syntax fall back to reading the character, which is survivable; the
   alternative — a real <span> per gap — is markup that exists only for CSS. */
.vjh-crumbs__item + .vjh-crumbs__item::before {
	content: "/" / "";
	color: var(--vjh-line-strong);
}

.vjh-crumbs a {
	color: inherit;
	text-decoration: none;
	border-bottom: 1px solid transparent;
}

.vjh-crumbs a:hover,
.vjh-crumbs a:focus-visible {
	color: var(--vjh-link);
	border-bottom-color: currentColor;
}

/* The current page is the one crumb that is not a link, so it gets the weight the
   links do not — otherwise the trail reads as three links, one of them broken. */
.vjh-crumbs__item--current > span {
	color: var(--vjh-ink-soft);
	font-weight: 500;
}

/*
 * A single job's last crumb is its full title, which on a narrow screen can be
 * longer than everything before it put together. Clamped to one line there and
 * allowed to wrap on a wide one, because a wrapped title is fine in a sidebar-free
 * column and a three-line breadcrumb on a phone is not.
 */
@media (max-width: 600px) {
	.vjh-crumbs__item--current > span {
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
}

/* -------------------------------------------------------------- §10 job header */

/*
 * The identity strip on a single job: logo, title, employer, location, and the
 * labelled facts. Grid rather than flex so the logo column is a fixed track and a
 * long job title wraps against it instead of squeezing it.
 */
.vjh-jobhead {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: 18px;
	align-items: start;
	margin: 0 0 24px;
}

/* No logo column at all when the logo is switched off — otherwise an empty track
   leaves the title indented for no reason. */
.vjh-jobhead:not(:has(.vjh-jobhead__logo)) {
	grid-template-columns: minmax(0, 1fr);
}

/* Bigger than on a card, set through the token rather than width/height so the
   initials tile's letters scale with the box — its font-size is a calc() on this. */
.vjh-jobhead__logo .vjh-logo {
	--vjh-logo-size: 72px;
}

.vjh-jobhead__title {
	margin: 0 0 8px;
	line-height: 1.15;
}

.vjh-jobhead__org {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px 14px;
	margin: 0;
	font-size: 1rem;
	color: var(--vjh-ink-soft);
}

.vjh-jobhead__company {
	font-weight: 600;
	color: var(--vjh-ink);
}

/* A dot between employer and location, drawn by CSS so it is not read aloud as a
   character by a screen reader and not selected when the line is copied. */
.vjh-jobhead__location::before {
	content: "";
	display: inline-block;
	width: 4px;
	height: 4px;
	margin-right: 12px;
	border-radius: 50%;
	background: var(--vjh-line-strong);
	vertical-align: middle;
}

.vjh-jobhead__company:first-child + .vjh-jobhead__location::before,
.vjh-jobhead__location:first-child::before {
	display: none;
}

.vjh-jobhead__flag {
	padding: 2px 8px;
	border-radius: var(--vjh-radius-pill);
	font-size: var(--vjh-small-size);
	font-weight: 600;
	line-height: 1.6;
}

.vjh-jobhead__flag--new {
	background: var(--vjh-neutral-bg);
	color: var(--vjh-ink);
}

.vjh-jobhead__flag--closed {
	border: 1px solid var(--vjh-line-strong);
	color: var(--vjh-ink-faint);
}

/*
 * The facts. `auto-fit` with a floor rather than fixed columns: four facts sit in a
 * row on a desktop job page and stack into two on a phone with no breakpoint, and a
 * job that only publishes a posted date gets one cell instead of three empty ones.
 */
.vjh-jobhead__facts {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(140px, auto));
	gap: 14px 28px;
	margin: 20px 0 0;
	padding: 16px 18px;
	border: 1px solid var(--vjh-line);
	border-radius: var(--vjh-radius);
	background: var(--vjh-surface-2);
}

.vjh-fact__label {
	margin: 0 0 2px;
	font-size: var(--vjh-small-size);
	font-weight: 600;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--vjh-ink-faint);
}

.vjh-fact__value {
	margin: 0;
	font-size: var(--vjh-body-size);
	font-weight: 600;
	color: var(--vjh-ink);
}

/* Salary is the row people came for, so it is the one that is allowed to be
   louder. Nothing else in the strip changes weight or colour. */
.vjh-fact--salary .vjh-fact__value {
	font-size: 1.0625rem;
	color: var(--vjh-salary);
}

@media (max-width: 600px) {
	.vjh-jobhead {
		gap: 14px;
	}

	.vjh-jobhead__logo .vjh-logo {
		--vjh-logo-size: 56px;
	}

	.vjh-jobhead__facts {
		grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
		gap: 12px 20px;
	}
}

/* ------------------------------------------------------------------- §10 apply */

.vjh-apply {
	margin: 0;
}

.vjh-apply__host {
	margin: 8px 0 0;
	font-size: var(--vjh-small-size);
	color: var(--vjh-ink-faint);
	line-height: 1.5;
	text-align: center;
}

.vjh-apply__host span {
	color: var(--vjh-ink-soft);
	font-weight: 600;
}

.vjh-apply__none {
	margin: 0;
	padding: 12px 14px;
	border: 1px solid var(--vjh-line);
	border-radius: var(--vjh-radius-sm);
	background: var(--vjh-surface-2);
	font-size: var(--vjh-body-size);
	color: var(--vjh-ink-soft);
}

/* --------------------------------------------------- §30 related, §29 term links */

.vjh-related {
	margin: 40px 0 0;
	padding-top: 28px;
	border-top: 1px solid var(--vjh-line);
}

.vjh-related__title,
.vjh-termlinks__title {
	margin: 0 0 14px;
	font-size: 1.125rem;
}

.vjh-termlinks__list {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.vjh-termlinks__item {
	margin: 0;
}

.vjh-termlinks__list a {
	display: inline-flex;
	align-items: baseline;
	gap: 7px;
	padding: 7px 13px;
	border: 1px solid var(--vjh-line);
	border-radius: var(--vjh-radius-pill);
	background: var(--vjh-surface);
	color: var(--vjh-ink);
	font-size: var(--vjh-body-size);
	text-decoration: none;
}

.vjh-termlinks__list a:hover {
	border-color: var(--vjh-line-strong);
	background: var(--vjh-surface-2);
}

.vjh-termlinks__count {
	font-size: .75rem;
	font-weight: 600;
	color: var(--vjh-ink-faint);
	font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------- §20 employer directory */

/* The A–Z bar. Sticky, because the whole point of a jump bar is being reachable
   from the middle of a long list. */
.vjh-dir__az {
	position: sticky;
	top: var(--vjh-sticky-top, 0);
	z-index: 2;
	margin: 0 0 32px;
	padding: 12px 0;
	background: var(--vjh-surface);
	border-bottom: 1px solid var(--vjh-line);
}

.vjh-dir__azlist {
	display: flex;
	flex-wrap: wrap;
	gap: 4px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.vjh-dir__azitem {
	margin: 0;
}

.vjh-dir__azlist a,
.vjh-dir__azitem--none span {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 32px;
	height: 32px;
	border-radius: var(--vjh-radius-sm);
	font-size: var(--vjh-small-size);
	font-weight: 700;
	text-decoration: none;
}

.vjh-dir__azlist a {
	border: 1px solid var(--vjh-line);
	background: var(--vjh-surface);
	color: var(--vjh-ink);
}

.vjh-dir__azlist a:hover {
	border-color: var(--vjh-accent);
	background: var(--vjh-accent);
	color: var(--vjh-accent-ink);
}

/* An empty letter: present, so the bar does not reflow between imports, but plainly
   not a target. No border, so it never reads as a button that failed. */
.vjh-dir__azitem--none span {
	color: var(--vjh-ink-faint);
	opacity: .45;
}

.vjh-dir__all {
	margin: 10px 0 0;
	font-size: var(--vjh-small-size);
}

.vjh-dir__group {
	margin: 0 0 36px;
	/* The sticky bar would otherwise cover the letter heading it jumped to. */
	scroll-margin-top: calc(var(--vjh-sticky-top, 0px) + 60px);
}

.vjh-dir__letter {
	margin: 0 0 14px;
	padding-bottom: 8px;
	border-bottom: 2px solid var(--vjh-line);
	font-size: 1.125rem;
	color: var(--vjh-ink-soft);
	letter-spacing: .04em;
}

.vjh-dir__list {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
	gap: 10px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.vjh-dir__item {
	margin: 0;
}

.vjh-dir__link {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	grid-template-rows: auto auto;
	align-items: center;
	gap: 0 12px;
	padding: 10px 12px;
	border: 1px solid var(--vjh-line);
	border-radius: var(--vjh-radius-sm);
	background: var(--vjh-surface);
	color: var(--vjh-ink);
	text-decoration: none;
}

.vjh-dir__link:hover {
	border-color: var(--vjh-line-strong);
	background: var(--vjh-surface-2);
}

.vjh-dir__link .vjh-logo {
	--vjh-logo-size: 36px;
	grid-row: 1 / span 2;
}

.vjh-dir__name {
	font-weight: 600;
	font-size: var(--vjh-body-size);
	overflow-wrap: anywhere;
}

.vjh-dir__count {
	font-size: .75rem;
	color: var(--vjh-ink-faint);
	font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------- §32 stats */

/*
 * No boxes. These are four counted numbers and the restraint is the point — a strip
 * of gradient tiles reads as marketing, and the numbers here are small and true
 * rather than large and rounded up.
 */
.vjh-stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
	gap: 4px 28px;
}

.vjh-stat {
	padding: 6px 0;
}

.vjh-stat__value {
	display: block;
	font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem);
	font-weight: 700;
	line-height: 1.1;
	color: var(--vjh-ink);
	font-variant-numeric: tabular-nums;
}

.vjh-stat__label {
	display: block;
	margin-top: 2px;
	font-size: var(--vjh-small-size);
	color: var(--vjh-ink-faint);
}

/* ------------------------------------------------------------- attribution */

.vjh-attribution {
	margin: 0;
	font-size: .75rem;
	color: var(--vjh-ink-faint);
	line-height: 1.6;
}

.vjh-attribution a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 2px;
}

/* --------------------------------------------------------------- preferences */

@media (prefers-reduced-motion: reduce) {
	/*
	 * Only the plugin's own transitions. A blanket `* { transition: none }` would
	 * reach into the theme and every other plugin from a stylesheet that has no
	 * business doing that.
	 */
	.vjh-card,
	.vjh-btn,
	.vjh-logo,
	.vjh-termlinks__list a {
		transition: none;
	}
}

@media (forced-colors: active) {
	/*
	 * In forced-colors mode every background is replaced, so anything that carries
	 * meaning only through a background colour disappears: the chips, the initials
	 * tile, the current page in the pagination. Giving them borders puts the
	 * boundary back without overriding the user's colours.
	 */
	.vjh-chip,
	.vjh-logo--initials,
	.vjh-card__new,
	.vjh-card__closed,
	.vjh-pagination .page-numbers.current {
		border: 1px solid CanvasText;
	}
}

/* -------------------------------------------------------------------- print */

/*
 * People print job descriptions to read or to take into an interview. Two things
 * matter: the controls should not be on the paper, and the apply link must survive
 * the loss of the hyperlink.
 */
@media print {
	.vjh-filters,
	.vjh-filters__toggle,
	.vjh-sort,
	.vjh-pagination,
	.vjh-search {
		display: none !important;
	}

	.vjh-card,
	.vjh-apply__none,
	.vjh-expired {
		box-shadow: none;
		border-color: #999;
		break-inside: avoid;
	}

	.vjh-btn--apply {
		width: auto;
		border: 1px solid #333;
		background: none;
		color: #000;
	}

	/* The URL, printed, because a paper button does nothing. */
	.vjh-btn--apply::after {
		content: " (" attr(href) ")";
		font-weight: 400;
		font-size: .75rem;
		word-break: break-all;
	}
}
