/* =============================================================================
 * components.css — the header, footer, buttons and page furniture.
 *
 * Pattern and template components are added here as they are built (steps 3–6 of the
 * build order). Everything reads --wp--preset--* and nothing re-declares a palette
 * value.
 * ========================================================================== */

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

.jsk-btn {
	display: inline-flex;
	align-items: center;
	gap: var(--wp--preset--spacing--2);
	padding: 0.85rem 1.6rem;
	border: 2px solid transparent;
	border-radius: var(--jsk-radius-control);
	font-family: var(--wp--preset--font-family--sans);
	font-size: var(--wp--preset--font-size--base);
	font-weight: 800;
	line-height: 1;
	text-decoration: none;
	cursor: pointer;
	transition: background-color 0.18s, border-color 0.18s, transform 0.18s, color 0.18s;
}

/*
 * Amber is a fill, never text on a light ground: #F2A900 on white is 2.01:1 and fails
 * AA. On --color-on-accent it is 8.13:1. There is deliberately no .jsk-btn--text
 * variant in amber.
 */
.jsk-btn--primary {
	background: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--on-accent);
}

.jsk-btn--primary:hover {
	background: var(--wp--preset--color--accent);
	filter: brightness(1.08);
}

.jsk-btn--ghost {
	border-color: var(--wp--preset--color--border-strong);
	color: var(--wp--preset--color--text);
	background: transparent;
}

.jsk-btn--ghost:hover {
	border-color: var(--wp--preset--color--brand);
	color: var(--wp--preset--color--brand);
}

/*
 * on-band-border is rgba(255,255,255,.16), which composites to 1.6:1 on the band. It is
 * the right token for the hairlines it was made for — the header rule, the footer rule,
 * the panel outline — and the wrong one for anything that bounds a control, which WCAG
 * 1.4.11 holds to 3:1. Measured off rendered pixels: the declared colour reads as pure
 * white until it is composited, which is how this got through the stage 7 sweep.
 */
.jsk-band .jsk-btn--ghost,
.jsk-hero .jsk-btn--ghost {
	border-color: var(--wp--preset--color--on-band-muted);
	color: var(--wp--preset--color--on-band);
}

.jsk-band .jsk-btn--ghost:hover,
.jsk-hero .jsk-btn--ghost:hover {
	border-color: var(--wp--preset--color--on-band);
	color: var(--wp--preset--color--on-band);
}

@media (prefers-reduced-motion: no-preference) {
	.jsk-btn:hover {
		transform: translateY(-2px);
	}
}

/* ------------------------------------------------------------------- header */

/*
 * The header follows the page.
 *
 * Three things have to come with it, or a sticky header is a net loss:
 *
 *   - Anchors. A jump to #liability puts the heading at scroll position zero, which is
 *     now behind the header. scroll-padding-top on the scroll container fixes every
 *     anchor on the site at once, including the outline in the rail.
 *   - The rail. Its own sticky offset was measured from the viewport top; it now starts
 *     below the header instead of underneath it.
 *   - The open menu. Below 941px the nav expands the header to 419px, so a short
 *     viewport would be almost entirely header. It scrolls within itself instead, and
 *     the links inside are already tab stops, so the region is keyboard-reachable.
 */
:root {
	--jsk-header-h: 103px;
}

@media (min-width: 941px) {

	:root {
		--jsk-header-h: 79px;
	}
}

html {
	scroll-padding-top: calc(var(--jsk-header-h) + var(--wp--preset--spacing--3));
}

.site-header {
	position: sticky;
	top: 0;
	z-index: 60;
	max-block-size: 100dvh;
	overflow-y: auto;
	overscroll-behavior: contain;
	background: var(--wp--preset--color--band);
	color: var(--wp--preset--color--on-band);
	border-bottom: 1px solid var(--wp--preset--color--on-band-border);
}

.site-header__in {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--6);
	min-height: 78px;
}

.site-header .jsk-logo {
	width: auto;
	height: 40px;
}

.brand {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--3);
	text-decoration: none;
	color: var(--wp--preset--color--on-band);
}

/* ---------------------------------------------------------------- main nav */

/*
 * Every class handed to wp_nav_menu is styled here.
 *
 * An unassigned menu location makes has_nav_menu() false, so nothing renders and a
 * missing rule ships unnoticed. The fallback below covers the case where no menu has
 * been assigned yet, so the header is never a bare bulleted list.
 */
.main-nav {
	margin-left: auto;
}

.main-nav ul {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--5);
	margin: 0;
	padding: 0;
	list-style: none;
}

.main-nav a {
	display: inline-block;
	padding: var(--wp--preset--spacing--2) 0;
	border-bottom: 2px solid transparent;
	color: var(--wp--preset--color--on-band);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 700;
	text-decoration: none;
}

.main-nav a:hover {
	border-bottom-color: var(--wp--preset--color--band-ramp-to);
}

.main-nav a[aria-current] {
	border-bottom-color: var(--wp--preset--color--accent);
}

/*
 * The engine panel is a nested list with a CSS-only disclosure — :focus-within keeps it
 * reachable by keyboard, and with JavaScript off it still works. No JS is loaded for it.
 */
.main-nav .menu-item-has-children {
	position: relative;
}

.main-nav .sub-menu {
	position: absolute;
	left: -1rem;
	top: calc(100% + 0.55rem);
	display: none;
	min-width: 460px;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--wp--preset--spacing--5);
	margin: 0;
	padding: var(--wp--preset--spacing--5);
	background: var(--wp--preset--color--band-2);
	border: 1px solid var(--wp--preset--color--on-band-border);
	border-radius: var(--jsk-radius-card);
	box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
}

.main-nav .menu-item-has-children:hover > .sub-menu,
.main-nav .menu-item-has-children:focus-within > .sub-menu {
	display: grid;
}

.main-nav .sub-menu a {
	border-bottom: 0;
}

@media (max-width: 940px) {
	.site-header__in {
		flex-wrap: wrap;
		min-height: 0;
		padding-block: var(--wp--preset--spacing--3);
	}

	.main-nav {
		order: 3;
		width: 100%;
		margin-left: 0;
	}

	.site-header .jsk-btn {
		margin-left: auto;
		padding: 0.6rem 1.1rem;
		font-size: var(--wp--preset--font-size--sm);
	}

	.main-nav .sub-menu {
		position: static;
		display: grid;
		grid-template-columns: 1fr;
		min-width: 0;
		margin-top: var(--wp--preset--spacing--3);
	}
}

/* -------------------------------------------------------------- page hero */

.jsk-page-hero {
	background:
		radial-gradient(90rem 40rem at 88% -20%, rgba(0, 160, 214, 0.3), transparent 60%),
		radial-gradient(70rem 36rem at 8% 0%, rgba(67, 56, 202, 0.42), transparent 62%),
		var(--wp--preset--color--band);
	color: var(--wp--preset--color--on-band);
	padding-block: var(--wp--preset--spacing--8);
}

.jsk-page-hero h1 {
	color: var(--wp--preset--color--on-band);
	max-width: 20ch;
}

/* ----------------------------------------------------------- breadcrumbs */

.jsk-crumbs ol {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--2);
	margin: 0 0 var(--wp--preset--spacing--4);
	padding: 0;
	list-style: none;
	font-size: var(--wp--preset--font-size--xs);
}

.jsk-crumbs a {
	color: var(--wp--preset--color--on-band-muted);
	text-decoration: none;
}

.jsk-crumbs a:hover {
	color: var(--wp--preset--color--on-band);
	text-decoration: underline;
}

.jsk-crumbs li + li::before {
	content: "/";
	margin-right: var(--wp--preset--spacing--2);

	/*
	 * Rendered text, not a border. At .16 alpha this separator was 1.6:1 — present in
	 * the markup and invisible on the screen, which makes a breadcrumb trail read as a
	 * run-on line of links.
	 */
	color: var(--wp--preset--color--on-band-muted);
}

/* --------------------------------------------------------------- entry */

.jsk-entry {
	padding-block: var(--wp--preset--spacing--8);
}

.jsk-entry > * {
	max-width: var(--jsk-measure);
}

.jsk-entry > .alignwide,
.jsk-entry > .alignfull,
.jsk-entry > figure,
.jsk-entry > table,
.jsk-entry > .wp-block-table {
	max-width: none;
}

/* --------------------------------------------------------------- footer */

.site-footer {
	background: var(--wp--preset--color--band-2);
	color: var(--wp--preset--color--on-band);
	padding-block: var(--wp--preset--spacing--8) var(--wp--preset--spacing--5);
}

.site-footer a {
	color: var(--wp--preset--color--on-band);
	text-decoration: none;
}

.site-footer a:hover {
	text-decoration: underline;
}

.site-footer .jsk-logo {
	width: auto;
	height: 44px;
}

.footer-grid {
	display: grid;
	gap: var(--wp--preset--spacing--6);
	grid-template-columns: 1fr;
}

@media (min-width: 780px) {
	.footer-grid {
		grid-template-columns: 1.4fr 1fr 1fr 1fr;
	}
}

.footer-col h2 {
	margin-bottom: var(--wp--preset--spacing--3);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 800;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--wp--preset--color--on-band-muted);
}

.footer-col ul {
	display: grid;
	gap: var(--wp--preset--spacing--2);
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: var(--wp--preset--font-size--sm);
}

.footer-note {
	margin-top: var(--wp--preset--spacing--6);
	padding-top: var(--wp--preset--spacing--4);
	border-top: 1px solid var(--wp--preset--color--on-band-border);
	font-size: var(--wp--preset--font-size--xs);
	color: var(--wp--preset--color--on-band-muted);
}

.footer-note p {
	max-width: none;
	margin-bottom: var(--wp--preset--spacing--2);
	font-size: var(--wp--preset--font-size--xs);
	color: var(--wp--preset--color--on-band-muted);
}

/* ----------------------------------------------------------- search form */

/*
 * The search form at 320px.
 *
 * An <input type="search"> carries a default intrinsic width of about 20 characters, and
 * flex:1 grows it but does not let it shrink below that — so at 320px the input held its
 * ground and pushed the submit button 11px past the viewport. min-width:0 is what allows
 * a flex item to go below its intrinsic size; wrapping catches the case where the button
 * label is translated into something longer.
 */
.search-form {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--2);
	max-width: 32rem;
}

.search-form input[type="search"] {
	flex: 1 1 12rem;
	min-width: 0;
	padding: 0.6rem 0.8rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--jsk-radius-control);
	background: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--text);
	font: inherit;
	font-size: var(--wp--preset--font-size--sm);
}

/* --------------------------------------------------------------- 404 */

.jsk-404__options {
	display: grid;
	gap: var(--wp--preset--spacing--5);
	grid-template-columns: 1fr;
	margin-top: var(--wp--preset--spacing--6);
}

@media (min-width: 780px) {
	.jsk-404__options {
		grid-template-columns: repeat(3, 1fr);
	}
}

.jsk-404__card {
	padding: var(--wp--preset--spacing--6);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--surface);
	box-shadow: var(--wp--preset--shadow--card);
}

.jsk-404__card h2 {
	font-size: var(--wp--preset--font-size--xl);
}

.jsk-404__card p {
	margin-bottom: var(--wp--preset--spacing--4);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* ------------------------------------------------- child-page card grid */

.jsk-child-cards {
	display: grid;
	gap: var(--wp--preset--spacing--5);
	grid-template-columns: 1fr;
	margin-top: var(--wp--preset--spacing--7);
	padding: 0;
	list-style: none;
}

@media (min-width: 760px) {
	.jsk-child-cards {
		grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
	}
}

.jsk-child-cards li {
	margin: 0;
}

.jsk-child-cards a {
	display: block;
	height: 100%;
	padding: var(--wp--preset--spacing--5);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--surface);
	text-decoration: none;
	color: var(--wp--preset--color--text);
}

.jsk-child-cards a:hover {
	border-color: var(--wp--preset--color--brand);
}

.jsk-child-cards strong {
	display: block;
	font-size: var(--wp--preset--font-size--lg);
	letter-spacing: -0.02em;
}

.jsk-child-cards span {
	display: block;
	margin-top: var(--wp--preset--spacing--2);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* --------------------------------------------------------- trust block */

.jsk-trust {
	display: grid;
	gap: var(--wp--preset--spacing--3);
	margin-top: var(--wp--preset--spacing--7);
	padding: var(--wp--preset--spacing--5);
	border: 1px solid var(--wp--preset--color--on-band-border);
	border-radius: var(--jsk-radius-card);
}

.jsk-trust dl {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--2) var(--wp--preset--spacing--6);
	margin: 0;
}

.jsk-trust dt {
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--wp--preset--color--on-band-muted);
}

.jsk-trust dd {
	margin: 0 0 0 var(--wp--preset--spacing--2);
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--on-band);
}

/* ------------------------------------------------------------ pagination */

.jsk-pagination {
	margin-top: var(--wp--preset--spacing--7);
}

/*
 * paginate_links( 'type' => 'list' ) returns <ul class="page-numbers"> wrapping <li>s
 * whose links ALSO carry .page-numbers. The pill rule below matched both, so the list
 * itself was given the pill's border, min-width and inline-grid — and its items stacked
 * one per row inside it. Visible only on search, the one view here with enough results
 * to paginate.
 *
 * The list is laid out first, and the pill styling is scoped to the items themselves.
 */
.jsk-pagination ul.page-numbers {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.jsk-pagination li {
	display: flex;
}

.jsk-pagination a.page-numbers,
.jsk-pagination span.page-numbers {
	display: inline-grid;
	place-items: center;
	min-width: 2.5rem;
	height: 2.5rem;
	padding-inline: 0.5rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--jsk-radius-control);
	color: var(--wp--preset--color--text);
	font-weight: 700;
	text-decoration: none;
}

.jsk-pagination span.page-numbers.current {
	background: var(--wp--preset--color--brand);
	border-color: var(--wp--preset--color--brand);
	color: var(--wp--preset--color--on-brand);
}

/* ==========================================================================
 * Cards — offers, cases, products.
 *
 * The gradient rule across the top is direction C's indigo-to-cyan ramp. It is the one
 * place the ramp appears at card scale, and it is what ties the cards to the hero wash
 * and the brand mark without repeating the gradient as a background.
 * ====================================================================== */

.jsk-cards {
	display: grid;
	gap: var(--wp--preset--spacing--5);
	grid-template-columns: 1fr;
}

@media (min-width: 700px) {
	.jsk-cards {
		grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
	}
}

.jsk-card {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--3);
	overflow: hidden;
	padding: var(--wp--preset--spacing--6);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--canvas);
	box-shadow: var(--wp--preset--shadow--card);
	transition: box-shadow 0.2s, transform 0.2s;
}

.jsk-card::before {
	content: "";
	position: absolute;
	inset: 0 0 auto 0;
	height: 5px;
	background: var(--wp--preset--gradient--brand-ramp-h);
}

@media (prefers-reduced-motion: no-preference) {
	.jsk-card:hover {
		box-shadow: var(--wp--preset--shadow--card-hover);
		transform: translateY(-4px);
	}
}

.jsk-card__title {
	margin: var(--wp--preset--spacing--2) 0 0;
	font-size: var(--wp--preset--font-size--xl);
}

.jsk-card__title a {
	color: inherit;
	text-decoration: none;
}

.jsk-card__title a:hover {
	color: var(--wp--preset--color--brand);
}

.jsk-card__text {
	margin: 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

.jsk-card__meta {
	margin: 0;
	max-width: none;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--text);
}

.jsk-card .jsk-tag {
	margin-top: auto;
	align-self: flex-start;
}

/* The differentiator offer is marked, because it is the argument for choosing JSK. */
.jsk-card--wedge {
	border-color: var(--wp--preset--color--accent);
}

/* -------------------------------------------------------------------- tags */

.jsk-tag {
	display: inline-block;
	padding: 0.3rem 0.6rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--jsk-radius-pill);
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	white-space: nowrap;
}

.jsk-tag--free {
	border-color: var(--wp--preset--color--success);
	color: var(--wp--preset--color--success);
}

.jsk-tag--retainer {
	border-color: var(--wp--preset--color--brand);
	color: var(--wp--preset--color--brand);
}

/* --------------------------------------------------------- editor markers */

/*
 * Only ever rendered for logged-in editors — see jsk_todo(). Deliberately loud: it marks
 * a gap that must be closed before launch, and stage 7 requires the count to reach zero.
 */
.jsk-todo {
	display: inline-block;
	padding: 0.25rem 0.5rem;
	border: 1px dashed var(--wp--preset--color--danger);
	border-radius: 4px;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	color: var(--wp--preset--color--danger);
}

/* -------------------------------------------------------------- the offer */

.jsk-page-hero .jsk-facts {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--3) var(--wp--preset--spacing--7);
	margin: var(--wp--preset--spacing--5) 0;
}

.jsk-page-hero .jsk-facts dt {
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 700;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
}

.jsk-page-hero .jsk-facts dd {
	margin: var(--wp--preset--spacing--1) 0 0;
	font-size: var(--wp--preset--font-size--lg);
	font-weight: 700;
	color: var(--wp--preset--color--on-band);
}

.jsk-offer__outcome {
	color: var(--wp--preset--color--on-band-muted);
	max-width: 46ch;
}

.jsk-offer__cta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--3);
	margin: var(--wp--preset--spacing--5) 0 0;
	max-width: none;
}

.jsk-offer__grid {
	display: grid;
	gap: var(--wp--preset--spacing--6);
	grid-template-columns: 1fr;
	margin-top: var(--wp--preset--spacing--7);
}

@media (min-width: 820px) {
	.jsk-offer__grid {
		grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
	}
}

.jsk-offer__list h2 {
	font-size: var(--wp--preset--font-size--lg);
}

.jsk-offer__list ul {
	display: grid;
	gap: var(--wp--preset--spacing--2);
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: var(--wp--preset--font-size--sm);
}

.jsk-offer__list li {
	position: relative;
	padding-left: 1.3rem;
}

.jsk-offer__list li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.55em;
	width: 0.55rem;
	height: 0.55rem;
	border-radius: 50%;
	background: var(--wp--preset--color--brand);
}

/* Colour is not the only signal here: the shape changes too. */
.jsk-offer__list--negative li::before {
	top: 0.72em;
	height: 2px;
	width: 0.7rem;
	border-radius: 0;
	background: var(--wp--preset--color--muted);
}

.jsk-offer__list--negative li {
	color: var(--wp--preset--color--muted);
}

/* ------------------------------------------------------- the engine groups */

.jsk-engine-group {
	margin-bottom: var(--wp--preset--spacing--8);
}

.jsk-engine-group > h2 {
	padding-bottom: var(--wp--preset--spacing--3);
	margin-bottom: var(--wp--preset--spacing--5);
	border-bottom: 1px solid var(--wp--preset--color--border);
	font-size: var(--wp--preset--font-size--xxl);
}

.jsk-engine-group > h2 a {
	color: inherit;
	text-decoration: none;
}

.jsk-engine-group > h2 a:hover {
	color: var(--wp--preset--color--brand);
}

/* --------------------------------------------------------- the CTA ladder */

.jsk-section--soft,
.wp-block-group.is-style-jsk-soft {
	background: var(--wp--preset--color--surface);
}

.jsk-section__head {
	max-width: var(--jsk-measure);
	margin-bottom: var(--wp--preset--spacing--7);
}

.jsk-ladder {
	display: grid;
	gap: var(--wp--preset--spacing--5);
	grid-template-columns: 1fr;
}

@media (min-width: 860px) {
	.jsk-ladder {
		grid-template-columns: repeat(3, 1fr);
	}
}

.jsk-rung {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--3);
	padding: var(--wp--preset--spacing--6);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--canvas);
}

/*
 * The middle rung carries the ramp as a fill. Its text is a literal white rather than a
 * scheme token, because the gradient underneath does not change with the colour scheme —
 * the same reasoning as the fixed band tokens.
 */
.jsk-rung--mid {
	border-color: transparent;

	/*
	 * The ramp, under a 16% scrim.
	 *
	 * White on the ramp alone is 3.82:1 where the cyan end reaches the paragraph, which
	 * fails AA for text this size — so making the text white was necessary and not
	 * sufficient. Measured on the rendered pixels rather than against the two stop
	 * colours, because what matters is the blend actually behind the words: 0.12 clears
	 * the bar at 4.68:1 and 0.16 gives 5.01:1 for a scrim still too slight to read as a
	 * different colour. The ramp token itself is untouched — it is shared with the card
	 * rules and the hero wash, where nothing sits on top of it.
	 */
	background:
		linear-gradient(0deg, rgba(3, 12, 24, 0.16), rgba(3, 12, 24, 0.16)),
		var(--wp--preset--gradient--brand-ramp);
	color: #fff;
}

.jsk-rung--mid .jsk-btn--primary {
	background: #fff;
	color: var(--wp--preset--color--band);
}

.jsk-rung__tier {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

.jsk-rung h3 {
	margin: 0;
}

.jsk-rung p {
	margin: 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/*
 * After the base rules, not before them.
 *
 * `.jsk-rung--mid p` and `.jsk-rung p` are both (0,1,1) — one class, one element — so
 * specificity does not separate them and source order decides. Written above the base
 * rule, the white override lost, and "A published band, a fixed scope and a date." was
 * rendering in the muted slate meant for a white card on top of the indigo-to-cyan
 * gradient: 1.34:1 in light mode, unreadable, and identical in both schemes because the
 * gradient is a fixed brand token that does not follow the scheme.
 */
.jsk-rung--mid h3,
.jsk-rung--mid p,
.jsk-rung--mid .jsk-rung__tier {
	color: #fff;
}

.jsk-rung .jsk-btn {
	margin-top: auto;
	justify-content: center;
}

/* ==========================================================================
 * Cases, products and filters — build-order step 4.
 * ====================================================================== */

.jsk-card__eyebrow {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
}

/* A case study leads with its strongest number, because that is what earns the click. */
.jsk-card__result {
	margin: 0;
	max-width: none;
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--1);
}

.jsk-card__result strong {
	font-size: var(--wp--preset--font-size--xxl);
	font-weight: 800;
	line-height: 1.1;
	letter-spacing: -0.02em;
	color: var(--wp--preset--color--brand);
}

.jsk-card__result span {
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* ------------------------------------------------------------- results band */

.jsk-results__list {
	display: grid;
	gap: var(--wp--preset--spacing--6);
	grid-template-columns: 1fr;
	margin: 0;
	padding: 0;
	list-style: none;
}

@media (min-width: 700px) {
	.jsk-results__list {
		grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
	}
}

.jsk-results__list li {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--1);
}

.jsk-band .jsk-results__value {
	font-size: var(--wp--preset--font-size--xxxl);
	font-weight: 800;
	line-height: 1.05;
	letter-spacing: -0.03em;
	color: var(--wp--preset--color--accent);
	font-variant-numeric: tabular-nums;
}

.jsk-band .jsk-results__label {
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--on-band-muted);
}

/* ------------------------------------------------------------------ quote */

.jsk-quote {
	margin: var(--wp--preset--spacing--7) 0;
	padding-left: var(--wp--preset--spacing--5);
	border-left: 4px solid var(--wp--preset--color--accent);
}

.jsk-quote blockquote {
	margin: 0;
}

.jsk-quote blockquote p {
	margin: 0;
	font-size: var(--wp--preset--font-size--xl);
	font-weight: 700;
	line-height: 1.35;
	letter-spacing: -0.02em;
}

.jsk-quote figcaption {
	margin-top: var(--wp--preset--spacing--3);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* ------------------------------------------------------------ filter chips */

.jsk-chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--2);
	margin-bottom: var(--wp--preset--spacing--6);
}

.jsk-chip {
	display: inline-block;
	padding: 0.45rem 1rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--jsk-radius-pill);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
	text-decoration: none;
	color: var(--wp--preset--color--text);
}

.jsk-chip:hover {
	border-color: var(--wp--preset--color--brand);
	color: var(--wp--preset--color--brand);
}

/*
 * The active filter is marked by weight and a filled background, not by colour alone —
 * colour is never the only signal.
 */
.jsk-chip.is-current {
	background: var(--wp--preset--color--brand);
	border-color: var(--wp--preset--color--brand);
	color: var(--wp--preset--color--on-brand);
	font-weight: 800;
}

/* --------------------------------------------------------- misc alignment */

.jsk-entry > .jsk-cards,
.jsk-entry > .jsk-engine-group,
.jsk-entry > .jsk-chips,
.jsk-entry > .jsk-offer__grid,
.jsk-entry > .jsk-pagination,
.jsk-entry > .jsk-quote {
	max-width: none;
}

/* ==========================================================================
 * The homepage — build-order step 5.
 * ====================================================================== */

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

/*
 * Two radial washes over the fixed band colour, in the indigo-to-cyan direction. Written
 * as rgba literals because these are gradient stops, which theme.json has no syntax for —
 * the colours are the ramp tokens, and they do not change with the colour scheme, so a
 * literal here is honest rather than a leak.
 */
.jsk-hero {
	background:
		radial-gradient(90rem 40rem at 88% -20%, rgba(0, 160, 214, 0.3), transparent 60%),
		radial-gradient(70rem 36rem at 8% 0%, rgba(67, 56, 202, 0.42), transparent 62%),
		var(--wp--preset--color--band);
	color: var(--wp--preset--color--on-band);
	padding-block: var(--wp--preset--spacing--9) var(--wp--preset--spacing--8);
	overflow: hidden;
}

.jsk-hero__grid {
	display: grid;
	gap: var(--wp--preset--spacing--8);
	grid-template-columns: 1fr;
	align-items: center;
}

@media (min-width: 980px) {
	.jsk-hero__grid {
		grid-template-columns: 1.05fr 0.95fr;
	}
}

.jsk-hero h1 {
	max-width: 13ch;
	color: var(--wp--preset--color--on-band);
	font-size: clamp(2.5rem, 7vw, 4.75rem);
}

/*
 * The second half of the headline carries the ramp. background-clip: text has no
 * fallback in older engines, so @supports paints it amber instead — visible either way,
 * never invisible.
 */
.jsk-hero h1 em {
	font-style: normal;

	/*
	 * Its own ramp, not the brand one, because this ramp IS the text.
	 *
	 * brand-ramp-h starts at #4338CA, which is 2.21:1 on the hero band -- fine as a fill
	 * behind white text, and a failure as 76px of headline. Measured off the rendered
	 * glyphs: no contrast tool reports this, because the element's `color` is transparent
	 * and the ink is the gradient.
	 *
	 * #A5B4FC is the same indigo at the lightness the dark scheme already uses for brand
	 * text; the cyan end is unchanged. 8.70:1 and 5.81:1 on the band, against the 3:1
	 * large-text threshold. Written as fixed values for the same reason the band tokens
	 * are: this surface does not follow the scheme, so a colour on it cannot either.
	 */
	background: linear-gradient(90deg, #A5B4FC 0%, #00A0D6 100%);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

@supports not (background-clip: text) {
	.jsk-hero h1 em {
		color: var(--wp--preset--color--accent);
	}
}

.jsk-hero .jsk-eyebrow {
	color: var(--wp--preset--color--accent);
}

.jsk-hero__sub {
	max-width: 44ch;
	margin-bottom: var(--wp--preset--spacing--6);
	font-size: var(--wp--preset--font-size--xl);
	color: var(--wp--preset--color--on-band-muted);
}

.jsk-hero__cta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--3);
	max-width: none;
	margin-bottom: var(--wp--preset--spacing--5);
}

.jsk-hero__note {
	margin: 0;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--on-band-muted);
}

.jsk-hero__image {
	width: 100%;
	height: auto;
	border-radius: var(--jsk-radius-card);
}

/* ------------------------------------------------------------ buyer chips */

.jsk-buyers {
	padding-block: var(--wp--preset--spacing--7);
}

.jsk-buyer-chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--3);
	margin: 0;
	padding: 0;
	list-style: none;
}

.jsk-buyer-chips li {
	padding: var(--wp--preset--spacing--2) var(--wp--preset--spacing--5);
	border: 1px solid var(--wp--preset--color--on-band-border);
	border-radius: var(--jsk-radius-pill);
	background: rgba(255, 255, 255, 0.06);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 700;
	color: var(--wp--preset--color--on-band);
}

/* On a light ground the same list needs the light tokens. */
.jsk-section--soft .jsk-buyer-chips li,
.jsk-entry .jsk-buyer-chips li {
	border-color: var(--wp--preset--color--border-strong);
	background: transparent;
	color: var(--wp--preset--color--text);
}

/* ---------------------------------------------------------------- problem */

.jsk-problem {
	display: grid;
	gap: var(--wp--preset--spacing--7);
	grid-template-columns: 1fr;
}

@media (min-width: 900px) {
	.jsk-problem {
		grid-template-columns: 1fr 1fr;
	}
}

.jsk-problem__q {
	margin: 0;
	padding: 0;
	border: 0;
}

.jsk-problem__q p {
	margin: 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--xxl);
	font-weight: 800;
	line-height: 1.2;
	letter-spacing: -0.03em;
}

/* ---------------------------------------------------------------- engines */

.jsk-engines {
	display: grid;
	gap: var(--wp--preset--spacing--5);
	grid-template-columns: 1fr;
}

@media (min-width: 820px) {
	.jsk-engines {
		grid-template-columns: repeat(3, 1fr);
	}
}

.jsk-engine {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--3);
	overflow: hidden;
	padding: var(--wp--preset--spacing--6);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--canvas);
	box-shadow: var(--wp--preset--shadow--card);
	transition: box-shadow 0.2s, transform 0.2s;
}

.jsk-engine::before {
	content: "";
	position: absolute;
	inset: 0 0 auto 0;
	height: 5px;
	background: var(--wp--preset--gradient--brand-ramp-h);
}

@media (prefers-reduced-motion: no-preference) {
	.jsk-engine:hover {
		box-shadow: var(--wp--preset--shadow--card-hover);
		transform: translateY(-4px);
	}
}

.jsk-engine__n {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 800;
	color: var(--wp--preset--color--brand);
}

.jsk-engine h3 {
	margin: 0;
}

.jsk-engine p {
	margin: 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

.jsk-engine ul {
	display: grid;
	gap: var(--wp--preset--spacing--2);
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
}

.jsk-engine ul a {
	text-decoration: none;
}

.jsk-engine ul a:hover {
	text-decoration: underline;
}

.jsk-engine ul li::before {
	content: "\2192";
	margin-right: 0.4rem;
	color: var(--wp--preset--color--brand);
}

.jsk-engine__link {
	margin-top: auto;
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 800;
	text-decoration: none;
}

.jsk-engine__link:hover {
	text-decoration: underline;
}

/* ------------------------------------------------------------------ proof */

.jsk-proof__products {
	display: grid;
	gap: var(--wp--preset--spacing--6);
	grid-template-columns: 1fr;
}

@media (min-width: 900px) {
	.jsk-proof__products {
		grid-template-columns: repeat(2, 1fr);
	}
}

.jsk-product {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--4);
}

.jsk-product__shot {
	display: block;
	overflow: hidden;
	border: 1px solid var(--wp--preset--color--on-band-border);
	border-radius: var(--jsk-radius-card);
	box-shadow: 0 30px 70px rgba(0, 0, 0, 0.45);
}

.jsk-product__shot img {
	display: block;
	width: 100%;
	height: auto;
}

.jsk-band .jsk-product__tag {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 800;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--wp--preset--color--accent);
}

.jsk-product h3 {
	margin: 0;
	font-size: var(--wp--preset--font-size--xxl);
	color: var(--wp--preset--color--on-band);
}

.jsk-product h3 a {
	color: inherit;
	text-decoration: none;
}

.jsk-product h3 a:hover {
	text-decoration: underline;
}

.jsk-product p {
	margin: 0;
	color: var(--wp--preset--color--on-band-muted);
}

.jsk-product__out {
	align-self: flex-start;
	border-bottom: 2px solid var(--wp--preset--color--band-ramp-to);
	color: var(--wp--preset--color--on-band);
	font-weight: 800;
	text-decoration: none;
}

.jsk-proof__cases,
.jsk-proof__quotes {
	margin-top: var(--wp--preset--spacing--7);
}

.jsk-proof__gap {
	margin-top: var(--wp--preset--spacing--6);
	max-width: none;
}

/*
 * Cards sitting on a dark band take the band's surface, not the light one.
 *
 * Keyed to .jsk-band, which is the thing that makes the surface dark, and not to
 * .jsk-proof, which is what one section on the homepage happens to be called. The pillar
 * pages put the same cards on the same band without that second class, so they kept the
 * opaque light card background while .jsk-band went on setting the text white — white on
 * white, 1.00:1, in light mode only, because in dark mode the light surface token is
 * already dark and the bug is invisible.
 */
.jsk-band .jsk-card {
	background: rgba(255, 255, 255, 0.05);
	border-color: var(--wp--preset--color--on-band-border);
	box-shadow: none;
}

.jsk-band .jsk-card__title a,
.jsk-band .jsk-card__result strong {
	color: var(--wp--preset--color--on-band);
}

.jsk-band .jsk-card__text,
.jsk-band .jsk-card__eyebrow,
.jsk-band .jsk-card__result span {
	color: var(--wp--preset--color--on-band-muted);
}

/* ----------------------------------------------------------- testimonials */

.jsk-proof__quotes {
	display: grid;
	gap: var(--wp--preset--spacing--5);
	grid-template-columns: 1fr;
}

@media (min-width: 820px) {
	.jsk-proof__quotes {
		grid-template-columns: repeat(2, 1fr);
	}
}

.jsk-testimonial {
	margin: 0;
	padding: var(--wp--preset--spacing--5);
	border: 1px solid var(--wp--preset--color--on-band-border);
	border-radius: var(--jsk-radius-card);
}

.jsk-testimonial blockquote {
	margin: 0;
}

.jsk-testimonial blockquote p {
	margin: 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--lg);
	color: var(--wp--preset--color--on-band);
}

.jsk-testimonial figcaption {
	margin-top: var(--wp--preset--spacing--3);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--on-band-muted);
}

.jsk-testimonial figcaption strong {
	display: block;
	color: var(--wp--preset--color--on-band);
}

/* ---------------------------------------------------------------- process */

.jsk-steps {
	display: grid;
	gap: var(--wp--preset--spacing--4);
	grid-template-columns: 1fr;
	margin: 0;
	padding: 0;
	list-style: none;
}

@media (min-width: 760px) {
	.jsk-steps {
		grid-template-columns: repeat(5, 1fr);

		/*
		 * The five cards share one set of row tracks, so every title starts on the same
		 * line and every deliverable panel starts on the same line — whatever the copy
		 * in any one of them does. Each card is its own grid otherwise, and a per-card
		 * `1fr` only bottom-aligns the panels, which leaves their tops as ragged as the
		 * paragraphs above caused them to be.
		 */
		grid-template-rows: auto auto 1fr auto;
		gap: var(--wp--preset--spacing--3);
	}

}

.jsk-steps li {
	display: grid;

	/*
	 * Number, title, description, deliverable. The description takes 1fr so the
	 * deliverable panel is pushed to the bottom of every card and the five line up,
	 * rather than each floating wherever its own paragraph happens to end.
	 */
	grid-template-rows: auto auto 1fr auto;
	gap: var(--wp--preset--spacing--2);
	align-content: stretch;
	padding-top: var(--wp--preset--spacing--4);
	border-top: 4px solid var(--wp--preset--color--border);
}

/*
 * After the base rule, not before it — @media does not add specificity, so the earlier
 * block lost to the `grid-template-rows` above it and every card kept sizing its own
 * tracks. The single-column layout below 760px wants the per-card rows; the five-column
 * layout wants the shared ones.
 */
@media (min-width: 760px) {

	.jsk-steps li {
		grid-row: span 4;
		grid-template-rows: subgrid;
	}
}

/*
 * The five rules step across the ramp from indigo to cyan, so the sequence reads as a
 * progression rather than five identical boxes. Position is also encoded in the number,
 * so the colour is never the only signal.
 */
.jsk-steps li:nth-child(1) { border-top-color: var(--wp--preset--color--band-ramp-from); }
.jsk-steps li:nth-child(2) { border-top-color: color-mix(in srgb, var(--wp--preset--color--band-ramp-from) 75%, var(--wp--preset--color--band-ramp-to)); }
.jsk-steps li:nth-child(3) { border-top-color: color-mix(in srgb, var(--wp--preset--color--band-ramp-from) 50%, var(--wp--preset--color--band-ramp-to)); }
.jsk-steps li:nth-child(4) { border-top-color: color-mix(in srgb, var(--wp--preset--color--band-ramp-from) 25%, var(--wp--preset--color--band-ramp-to)); }
.jsk-steps li:nth-child(5) { border-top-color: var(--wp--preset--color--band-ramp-to); }

.jsk-steps__n {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 800;
	color: var(--wp--preset--color--muted);
}

.jsk-steps h3 {
	margin: 0;
	font-size: var(--wp--preset--font-size--lg);
}

.jsk-steps p {
	margin: 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* ==========================================================================
 * Insights articles — build-order step 6.
 *
 * The one place direction C's dark spine is dropped on purpose. Everything here is
 * about reading a 2,800-word article rather than scanning a sales page: light ground,
 * a measure near 65 characters, a larger body size and more line height.
 * ====================================================================== */

.jsk-article {
	background: var(--wp--preset--color--canvas);
	padding-block: var(--wp--preset--spacing--8);
}

.jsk-article__body {
	max-width: 68ch;
	font-size: var(--wp--preset--font-size--lg);
	line-height: 1.75;
}

/*
 * An article with a rail beside it.
 *
 * The cap sits on the column, not on the grid, because `ch` is not one length.
 *
 * A first attempt handed the railed grid `minmax(0, 68ch)` and measured 60.8ch of prose.
 * `ch` resolves against the font size of the element it is written on: the grid container
 * inherits the body's 17px, the article is set in 19px, so 68ch on the container is 705px
 * while 68ch on the prose is 788px. The same declaration, eight characters apart -- and
 * the narrower one wins, because a grid column clamps its child whatever the child's own
 * max-width says.
 *
 * Written here it resolves against .jsk-article__body's own 19px, which is the only font
 * size for which "68ch" means 68 characters of this article. Specificity 0,2,0, so it
 * does not depend on sitting below `.jsk-entry > *` to take effect.
 */
.jsk-entry--article > .jsk-entry__main {
	max-width: 68ch;
}

/*
 * .jsk-article already opens the canvas section with its own vertical padding, and
 * .jsk-entry adds the same spacing step again. One of them has to yield, and it is the
 * inner one -- the background belongs to the article, so the padding should too.
 */
.jsk-article > .jsk-entry {
	padding-block: 0;
}

/*
 * Headings inside an article are smaller than the page-level scale. An h2 sized for a
 * landing-page section is far too loud inside prose, and makes a long article read as a
 * series of announcements.
 */
.jsk-article__body h2 {
	margin-top: var(--wp--preset--spacing--7);
	font-size: var(--wp--preset--font-size--xxl);
}

.jsk-article__body h3 {
	margin-top: var(--wp--preset--spacing--6);
	font-size: var(--wp--preset--font-size--xl);
}

.jsk-article__body p,
.jsk-article__body ul,
.jsk-article__body ol {
	max-width: none;
	margin-bottom: var(--wp--preset--spacing--5);
}

.jsk-article__body li + li {
	margin-top: var(--wp--preset--spacing--2);
}

.jsk-article__body blockquote {
	margin: var(--wp--preset--spacing--6) 0;
	padding-left: var(--wp--preset--spacing--5);
	border-left: 4px solid var(--wp--preset--color--brand);
	font-size: var(--wp--preset--font-size--xl);
	font-weight: 600;
	line-height: 1.4;
}

.jsk-article__body blockquote p:last-child {
	margin-bottom: 0;
}

.jsk-article__body figure {
	margin: var(--wp--preset--spacing--6) 0;
}

.jsk-article__body figcaption {
	margin-top: var(--wp--preset--spacing--2);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* Code and tables scroll inside their own box; the page never scrolls sideways. */
.jsk-article__body pre,
.jsk-article__body .wp-block-table {
	overflow-x: auto;
	max-width: 100%;
}

.jsk-article__body pre {
	padding: var(--wp--preset--spacing--4);
	border-radius: var(--jsk-radius-control);
	background: var(--wp--preset--color--surface);
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--sm);
	line-height: 1.6;
}

.jsk-article__body a {
	text-decoration-thickness: 1px;
}

/* An article card's title is an h2, so it needs the card scale rather than the page one. */
.jsk-cards h2.jsk-card__title {
	margin: var(--wp--preset--spacing--2) 0 0;
	font-size: var(--wp--preset--font-size--xl);
	letter-spacing: -0.02em;
}

/* ==========================================================================
 * Measured vs directional results.
 *
 * A measured figure is set large and numeric. A directional line is set as prose under
 * its label. The difference is deliberate and it is the point: a description in 38px
 * bold reads as a statistic, and "150+ clients" with nothing behind it is exactly what
 * this rebuild removed from the old site.
 * ====================================================================== */

.jsk-results__list li {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--1);
}

/* Measured: value first and large, label beneath. */
.jsk-band .jsk-results__value {
	order: 2;
	font-size: var(--wp--preset--font-size--xxxl);
	font-weight: 800;
	line-height: 1.05;
	letter-spacing: -0.03em;
	color: var(--wp--preset--color--accent);
	font-variant-numeric: tabular-nums;
}

.jsk-band .jsk-results__label {
	order: 1;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--wp--preset--color--on-band-muted);
}

/* Directional: label stays the eyebrow, value drops to body prose. */
.jsk-band .jsk-results__list li.is-directional .jsk-results__value {
	font-size: var(--wp--preset--font-size--base);
	font-weight: 600;
	line-height: 1.5;
	letter-spacing: 0;
	color: var(--wp--preset--color--on-band);
}

.jsk-results__list--directional {
	grid-template-columns: 1fr;
}

@media (min-width: 820px) {
	.jsk-results__list--directional {
		grid-template-columns: repeat(3, 1fr);
	}
}

.jsk-results__note {
	margin-top: var(--wp--preset--spacing--5);
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--on-band-muted);
}

/* ------------------------------------------------- offers held back on price */

/*
 * Shown where offers would be, when every offer is a draft because its price band is
 * still proposed. An empty section with no explanation reads as a broken page; this
 * reads as a page that is waiting for one specific thing.
 */
.jsk-held {
	padding: var(--wp--preset--spacing--6);
	border: 1px dashed var(--wp--preset--color--border-strong);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--surface);
}

.jsk-held p {
	margin: 0;
	color: var(--wp--preset--color--muted);
}

.jsk-held p + p {
	margin-top: var(--wp--preset--spacing--3);
}

/* ==========================================================================
 * Pricing table and the team grid.
 * ====================================================================== */

.jsk-price-table {
	width: 100%;
	min-width: 34rem;
	border-collapse: collapse;
	font-size: var(--wp--preset--font-size--sm);
}

.jsk-price-table thead th {
	border-top: 0;
	border-bottom: 1px solid var(--wp--preset--color--border-strong);
	background: var(--wp--preset--color--surface);
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
	white-space: nowrap;
}

.jsk-price-table tbody th {
	font-size: var(--wp--preset--font-size--base);
	font-weight: 700;
}

.jsk-price-table tbody th a {
	text-decoration: none;
}

.jsk-price-table tbody th a:hover {
	text-decoration: underline;
}

.jsk-price-table tbody tr:hover {
	background: var(--wp--preset--color--surface);
}

/* Figures line up so two bands can be compared at a glance. */
.jsk-price-table .jsk-num {
	font-family: var(--wp--preset--font-family--mono);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

/* ------------------------------------------------------------------- team */

.jsk-team {
	display: grid;
	gap: var(--wp--preset--spacing--5);
	grid-template-columns: 1fr;
	margin: 0;
	padding: 0;
	list-style: none;
}

@media (min-width: 640px) {
	.jsk-team {
		grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
	}
}

.jsk-person {
	position: relative;
	overflow: hidden;
	padding: var(--wp--preset--spacing--5);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--canvas);
}

.jsk-person::before {
	content: "";
	position: absolute;
	inset: 0 0 auto 0;
	height: 4px;
	background: var(--wp--preset--gradient--brand-ramp-h);
}

.jsk-person__name {
	margin: var(--wp--preset--spacing--2) 0 0;
	font-size: var(--wp--preset--font-size--lg);
	letter-spacing: -0.02em;
}

.jsk-person__role {
	margin: var(--wp--preset--spacing--1) 0 0;
	max-width: none;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--brand);
}

.jsk-person__focus {
	margin: var(--wp--preset--spacing--3) 0 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

.jsk-person__bio {
	margin: var(--wp--preset--spacing--3) 0 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
}

/* ==========================================================================
 * Inline navigation rows.
 *
 * pages.md writes these as *[See software →]* · *[See AI →]* — a row of sideways links
 * closing a section, distinct from the buttons that close a page. They are links rather
 * than buttons on purpose: a section that offers three equal directions should not put
 * three amber calls to action on the screen and make the reader choose a winner.
 * ====================================================================== */

.jsk-inline-links {
	max-width: none;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--2) var(--wp--preset--spacing--4);
	margin-top: var(--wp--preset--spacing--5);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 700;
}

.jsk-inline-links a {
	color: var(--wp--preset--color--brand);
	text-decoration: none;
	border-bottom: 2px solid transparent;
}

.jsk-inline-links a:hover {
	border-bottom-color: var(--wp--preset--color--brand);
}

.jsk-band .jsk-inline-links a,
.jsk-hero .jsk-inline-links a {
	color: var(--wp--preset--color--on-band);
}

/*
 * The secondary button in a copy CTA row.
 *
 * parse_pages.py gives every button after the first the jsk-btn--ghost class, so the
 * rule that already describes a ghost button is reused rather than restated. The class
 * lands on the block wrapper, which is why the link inside it is the target here.
 */
.wp-block-button.jsk-btn--ghost .wp-block-button__link {
	background: transparent;
	border: 1px solid var(--wp--preset--color--border-strong);
	color: var(--wp--preset--color--text);
}

.wp-block-button.jsk-btn--ghost .wp-block-button__link:hover {
	border-color: var(--wp--preset--color--brand);
	color: var(--wp--preset--color--brand);
}

.jsk-band .wp-block-button.jsk-btn--ghost .wp-block-button__link,
.jsk-hero .wp-block-button.jsk-btn--ghost .wp-block-button__link {
	border-color: var(--wp--preset--color--on-band-border);
	color: var(--wp--preset--color--on-band);
}

/* ==========================================================================
 * The FAQ.
 *
 * Native <details>, from the FAQ pattern. It works with JavaScript off and is keyboard
 * operable and announced correctly without any help, which is why it was chosen over a
 * scripted accordion — but unstyled it is a browser triangle and a bare line of text,
 * and it was the only pattern that looked like nobody had designed it.
 *
 * The marker rotates only where motion is welcome; the open state is carried by the
 * border and the marker's direction as well, so movement is never the only signal.
 * ====================================================================== */

.wp-block-details {
	padding: var(--wp--preset--spacing--4) 0;
	border-bottom: 1px solid var(--wp--preset--color--border);
}

.wp-block-details summary {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--4);
	cursor: pointer;
	list-style: none;
	font-size: var(--wp--preset--font-size--lg);
	font-weight: 700;
	letter-spacing: -0.01em;
}

/* Safari keeps its own triangle unless this is said explicitly. */
.wp-block-details summary::-webkit-details-marker {
	display: none;
}

.wp-block-details summary::after {
	content: "+";
	flex: 0 0 auto;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xl);
	font-weight: 400;
	line-height: 1;
	color: var(--wp--preset--color--brand);
}

.wp-block-details[open] summary::after {
	content: "−";
}

.wp-block-details[open] summary {
	color: var(--wp--preset--color--brand);
}

.wp-block-details > :not(summary) {
	margin-top: var(--wp--preset--spacing--3);
	color: var(--wp--preset--color--muted);
}

@media (prefers-reduced-motion: no-preference) {
	.wp-block-details summary::after {
		transition: transform 0.18s ease;
	}

	.wp-block-details summary:hover::after {
		transform: scale(1.15);
	}
}

/* ==========================================================================
 * Featured images.
 *
 * The ratios here are the registered crops, stated again as aspect-ratio so the space is
 * reserved before the file arrives. Without that, adding one case study screenshot
 * reflows every card below it while it loads, which is a CLS score nobody can explain
 * from looking at the page — the images are not there yet on the site as it stands.
 *
 * 960x600 and 1200x750 are both 8:5, so one ratio covers the cards.
 * ====================================================================== */

.jsk-card__media,
.jsk-leaf__media {
	margin: 0 0 var(--wp--preset--spacing--4);
	overflow: hidden;
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--surface);
}

.jsk-card__media {
	aspect-ratio: 8 / 5;
}

/* 1600x1200 is 4:3. */
.jsk-leaf__media {
	aspect-ratio: 4 / 3;
	margin-bottom: var(--wp--preset--spacing--6);
}

/*
 * A generated article card is 1200x630, and every pixel of it is placed on purpose.
 *
 * Shown in the 4:3 box above, object-fit: cover scaled it to fill the height and took a
 * third of the width off both sides -- the engine label lost everything but its last three
 * letters and the wordmark lost the word "Business". Matching the box to the card leaves
 * cover with nothing to crop.
 */
.jsk-leaf__media--card {
	aspect-ratio: 1200 / 630;
}

.jsk-card__media img,
.jsk-leaf__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * A band that is not a number.
 *
 * "Priced on request" sits in the same column as "$2,400 – $6,000", and the tabular
 * numeral treatment that lines figures up makes prose look like a data error. It reads
 * as what it is: a note, in the muted colour, at the body face.
 */
.jsk-on-request {
	font-family: var(--wp--preset--font-family--sans);
	font-variant-numeric: normal;
	color: var(--wp--preset--color--muted);
	white-space: nowrap;
}

.jsk-held-note {
	max-width: none;
	margin-top: var(--wp--preset--spacing--6);
	padding: var(--wp--preset--spacing--4) var(--wp--preset--spacing--5);
	border-left: 3px solid var(--wp--preset--color--accent);
	background: var(--wp--preset--color--surface);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* The India-rate note under the price table. A quiet aside, not a second offer. */
.jsk-inr-note {
	max-width: none;
	margin-top: var(--wp--preset--spacing--5);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* ---------------------------------------------------------------- job openings */

.jsk-jobs {
	display: grid;
	gap: var(--wp--preset--spacing--4);
	margin: 0;
	padding: 0;
	list-style: none;
}

.jsk-job {
	padding: var(--wp--preset--spacing--5);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--canvas);
}

.jsk-job__title {
	margin: 0;
	font-size: var(--wp--preset--font-size--lg);
}

.jsk-job__title a {
	text-decoration: none;
}

.jsk-job__title a:hover {
	text-decoration: underline;
}

.jsk-job__meta {
	margin: var(--wp--preset--spacing--2) 0 0;
	max-width: none;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--wp--preset--color--brand);
}

.jsk-job__summary {
	margin: var(--wp--preset--spacing--3) 0 0;
	max-width: none;
}

.jsk-job__closes {
	margin: var(--wp--preset--spacing--3) 0 0;
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* ==========================================================================
 * The mobile navigation disclosure.
 *
 * Below 941px the nav collapses behind a labelled toggle. Above it, the toggle is gone
 * and the nav is a plain row — the desktop layout is unchanged.
 *
 * The checkbox is the mechanism and is never seen; the label is the control. That is what
 * lets the menu open with JavaScript disabled, which is the rule the rest of this theme
 * already follows.
 * ====================================================================== */

.jsk-nav-toggle {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.jsk-nav-toggle__label {
	display: none;
	align-items: center;
	gap: var(--wp--preset--spacing--2);
	margin-left: auto;
	padding: 0.55rem 0.9rem;

	/* A control boundary, so 3:1 — see the ghost button note. */
	border: 1px solid var(--wp--preset--color--on-band-muted);
	border-radius: var(--jsk-radius-control);
	color: var(--wp--preset--color--on-band);
	font-family: var(--wp--preset--font-family--sans);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 700;
	cursor: pointer;
	/* A 44px target: the WCAG 2.5.8 minimum, and the size a thumb actually hits. */
	min-height: 44px;
}

/* The focus ring has to be on the LABEL, because the checkbox it belongs to is hidden. */
.jsk-nav-toggle:focus-visible + .jsk-nav-toggle__label {
	outline: 3px solid var(--wp--preset--color--focus-on-band);
	outline-offset: 3px;
}

.jsk-nav-toggle__bars,
.jsk-nav-toggle__bars::before,
.jsk-nav-toggle__bars::after {
	display: block;
	width: 18px;
	height: 2px;
	background: currentColor;
	content: "";
}

.jsk-nav-toggle__bars {
	position: relative;
}

.jsk-nav-toggle__bars::before { position: absolute; top: -6px; }
.jsk-nav-toggle__bars::after  { position: absolute; top: 6px; }

@media (max-width: 940px) {
	.jsk-nav-toggle__label {
		display: inline-flex;
	}

	/* Closed by default, so the header is a logo, a call to action and a toggle. */
	#jsk-primary-nav {
		display: none;
		order: 4;
		width: 100%;
	}

	.jsk-nav-toggle:checked ~ #jsk-primary-nav {
		display: block;
	}

	#jsk-primary-nav ul {
		flex-direction: column;
		align-items: stretch;
		gap: 0;
	}

	#jsk-primary-nav li {
		border-top: 1px solid var(--wp--preset--color--on-band-border);
	}

	#jsk-primary-nav a {
		display: block;
		padding: 0.85rem 0;
		border-bottom: 0;
	}

	/* Open state: say so with the label, not only with the icon. */
	.jsk-nav-toggle:checked + .jsk-nav-toggle__label .jsk-nav-toggle__bars::before {
		transform: translateY(6px) rotate(45deg);
	}

	.jsk-nav-toggle:checked + .jsk-nav-toggle__label .jsk-nav-toggle__bars::after {
		transform: translateY(-6px) rotate(-45deg);
	}

	.jsk-nav-toggle:checked + .jsk-nav-toggle__label .jsk-nav-toggle__bars {
		background: transparent;
	}
}

@media (prefers-reduced-motion: no-preference) {
	.jsk-nav-toggle__bars::before,
	.jsk-nav-toggle__bars::after {
		transition: transform 0.18s ease;
	}
}

@media (max-width: 940px) {
	/*
	 * Two rows, tightly.
	 *
	 * The logo and the toggle fill the first row at 390px; the call to action wraps to
	 * the second. It could be made to fit on one row by hiding the CTA behind the
	 * toggle, and it is not: "Book a 20-min call" is the primary conversion element on
	 * the site and the audit's whole finding was that the old site buried the next step.
	 * A second row costs 40px. Burying it costs enquiries.
	 *
	 * So the rows are tightened rather than removed.
	 */
	.site-header__in {
		row-gap: var(--wp--preset--spacing--2);
		padding-block: var(--wp--preset--spacing--2);
	}

	.site-header .jsk-btn {
		padding-block: 0.5rem;
	}
}

/*
 * Above the breakpoint the disclosure does not exist, so neither does its checkbox.
 *
 * Leaving the checkbox in the accessibility tree while its <label> is display:none
 * orphans it: axe reported a critical "label" violation on every page, because a form
 * control whose only label is hidden has no accessible name. It passed at 390px, where
 * the label is visible, and failed at 1280px — which is why testing one width would have
 * missed it.
 */
@media (min-width: 941px) {
	.jsk-nav-toggle {
		display: none;
	}
}

/* ==========================================================================
 * Social profiles, in the footer.
 *
 * A horizontal row of marks rather than a vertical list of names: four words stacked in
 * a footer column read as navigation the visitor is expected to consider, and these are
 * not that — they are the reciprocal links that make Organization.sameAs mean something,
 * and a place to go if somebody wants to look the company up.
 *
 * Each link is 44x44 even though the mark is 20px, because that is the WCAG 2.5.8 target
 * minimum and a footer icon is exactly the control people miss on a phone. The visible
 * mark stays small; the hittable area does not.
 * ====================================================================== */

/*
 * `.footer-col ul` sets display:grid and is (0,1,1) to this rule's (0,1,0), so a bare
 * `.jsk-social` lost and the icons stacked in four rows — the exact layout this replaced.
 * Scoping to the column outranks it at (0,2,0) without reaching for !important.
 */
.footer-col .jsk-social {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--1);
	margin: 0;
	padding: 0;
	list-style: none;
}

.jsk-social__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border-radius: var(--jsk-radius-control);
	border-bottom: 0;
	color: var(--wp--preset--color--on-band-muted);
}

.jsk-social__icon {
	display: block;
	width: 20px;
	height: 20px;
}

.jsk-social__link:hover,
.jsk-social__link:focus-visible {
	color: var(--wp--preset--color--on-band);
	background: rgba(255, 255, 255, 0.08);
}

/*
 * Colour is never the only signal. The background change carries the hover state on its
 * own, so the icon brightening is reinforcement rather than the whole message — the same
 * rule the rest of the theme follows.
 */
@media (prefers-reduced-motion: no-preference) {
	.jsk-social__link {
		transition: color 0.15s ease, background-color 0.15s ease;
	}
}

/*
 * The "Elsewhere" heading sits inside the brand column rather than owning one.
 *
 * As a fifth item in a four-column grid it wrapped to its own row with three empty
 * columns beside it. Four icons do not need a column — under the brand line is where
 * they already read as belonging.
 */
.footer-col__sub {
	margin-top: var(--wp--preset--spacing--6);
}

/* ==========================================================================
 * The enquiry form.
 * ====================================================================== */

.jsk-form {
	max-width: 40rem;
}

.jsk-form__row {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--2);
	margin: 0 0 var(--wp--preset--spacing--5);
	max-width: none;
}

.jsk-form__row label {
	font-weight: 700;
	font-size: var(--wp--preset--font-size--sm);
}

.jsk-form__optional {
	margin-left: 0.4em;
	font-weight: 400;
	color: var(--wp--preset--color--muted);
}

.jsk-form__help {
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/*
 * Every input except the ones that are not text boxes.
 *
 * This was an allowlist -- input[type="text"] and input[type="email"] -- written when the
 * only form on the site asked for those two. The careers form then added type="tel" and
 * type="file", and both fell straight through to the browser default: a 21px white box,
 * 13px text, on a form whose every other field is 52px on a dark ground. Same class of
 * error as the CSS escape list and the placeholder vocabulary -- an allowlist enumerates
 * what somebody had already thought of, and the next member is always the one missed.
 *
 * Inverted, so a type nobody has used yet is styled by default and only the controls that
 * genuinely are not text boxes opt out.
 */
.jsk-form input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="file"]),
.jsk-form select,
.jsk-form textarea {
	width: 100%;
	/* Below a flex item's intrinsic width, so the form survives 320px. */
	min-width: 0;
	padding: 0.7rem 0.9rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	/*
	 * NOT --jsk-radius-control, which is 999px.
	 *
	 * That token is the pill shape for buttons, and on a six-row textarea it produced a
	 * rounded blob with the text floating in the middle of it. A field is a place to
	 * type, and it should look like a box.
	 */
	border-radius: 8px;
	background: var(--wp--preset--color--canvas);
	color: var(--wp--preset--color--text);
	font: inherit;
	font-size: var(--wp--preset--font-size--base);
}

.jsk-form textarea {
	resize: vertical;
	line-height: 1.6;
}

/*
 * The honeypot.
 *
 * Positioned off-screen rather than display:none. Some bots skip fields that are
 * display:none or hidden, which is exactly the population this is meant to catch.
 */
.jsk-form__hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.jsk-form__status {
	max-width: 40rem;
	margin-bottom: var(--wp--preset--spacing--5);
	padding: var(--wp--preset--spacing--4) var(--wp--preset--spacing--5);
	border-left: 3px solid var(--wp--preset--color--brand);
	background: var(--wp--preset--color--canvas);
}

/* Colour is never the only signal: the border side and the wording carry it too. */
.jsk-form__status--error {
	border-left-color: var(--wp--preset--color--accent);
}

.jsk-form__note,
.jsk-form__fallback {
	max-width: 40rem;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

.jsk-form__fallback {
	margin-top: var(--wp--preset--spacing--5);
	padding-top: var(--wp--preset--spacing--5);
	border-top: 1px solid var(--wp--preset--color--border);
}

/*
 * The deliverable under each process step — what the client holds, not how long it took.
 *
 * Was a hairline rule and a full-width mono label, which had two problems. The rules sat
 * at five different heights because the descriptions above them are five different
 * lengths, so the row read as ragged rather than as a set; and the deliverable was
 * typographically the same weight as the description above it, so the one thing worth
 * scanning for looked like more prose. It is now a panel, pinned to the bottom of its
 * card by the 1fr row above it, so all five align whatever the copy does.
 */
.jsk-steps__have {
	margin-top: var(--wp--preset--spacing--4);
	padding: var(--wp--preset--spacing--3);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--surface-soft);
	max-width: none;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--text);
}

.jsk-steps__have-label {
	display: flex;
	align-items: center;
	gap: 0.4em;
	margin-bottom: var(--wp--preset--spacing--2);
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--brand);
}

.jsk-steps__tick {
	flex: none;
}

.jsk-steps__note {
	max-width: none;
	margin-top: var(--wp--preset--spacing--5);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

/* ==========================================================================
 * The entry rail.
 *
 * Prose stays at --jsk-measure; the rail takes the band that was empty beside it.
 * Below 1080px the grid collapses and the rail follows the prose — nothing unique is
 * hidden on a phone, only the outline, which the <details> above the prose repeats.
 * ====================================================================== */

.jsk-toc-compact {
	max-width: var(--jsk-measure);
	margin-block-end: var(--wp--preset--spacing--6);
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--jsk-radius-card);
	padding: var(--wp--preset--spacing--4);
}

.jsk-toc-compact > summary {
	cursor: pointer;
	font-weight: 600;
}

.jsk-toc-compact ol {
	margin: var(--wp--preset--spacing--3) 0 0;
	padding-inline-start: 1.2em;
	display: grid;
	gap: var(--wp--preset--spacing--1);
}

/*
 * The compact outline is the phone's copy, so its rows are thumb targets. A bare inline
 * link measured 24px — the WCAG 2.5.8 floor exactly, and half what the rest of this site
 * gives a tappable row. Padding on the anchor, not margin on the item, so the padding is
 * part of the target rather than a gap beside it.
 */
.jsk-toc-compact li a {
	display: block;
	padding-block: 0.6rem;
}

.jsk-rail__h {
	font-size: var(--wp--preset--font-size--sm);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	margin: 0 0 var(--wp--preset--spacing--3);
	color: var(--wp--preset--color--muted);
}

.jsk-rail__card,
.jsk-rail__toc {
	margin-block-end: var(--wp--preset--spacing--5);
}

.jsk-rail__card {
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--jsk-radius-card);
	padding: var(--wp--preset--spacing--4);
}

.jsk-rail ol,
.jsk-rail ul {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--wp--preset--spacing--3);
}

.jsk-rail__toc ol {
	counter-reset: jsk-toc;
	border-inline-start: 2px solid var(--wp--preset--color--border-strong);
	padding-inline-start: var(--wp--preset--spacing--4);
}

/*
 * The compact outline is a sibling of the rail, not a child, so it missed this and shipped
 * with browser-default link colours — blue, and purple once visited — on a dark band.
 */
/*
 * :not(.jsk-btn) is doing real work here.
 *
 * Without it this rule set border-block-end: 1px solid transparent on every link in the
 * rail, the call-to-action button included — and `.jsk-rail a` (0,1,1) outranks
 * `.jsk-btn--ghost` (0,1,0), so the button lost the bottom edge of its 2px border and
 * rendered as an open U. The underline-on-hover idiom is for text links; a button draws
 * its own border and must be left alone.
 */
.jsk-rail a:not(.jsk-btn),
.jsk-toc-compact a:not(.jsk-btn) {
	color: var(--wp--preset--color--brand);
	text-decoration: none;
	border-block-end: 1px solid transparent;
}

.jsk-rail a:not(.jsk-btn):hover,
.jsk-rail a:not(.jsk-btn):focus-visible,
.jsk-toc-compact a:not(.jsk-btn):hover,
.jsk-toc-compact a:not(.jsk-btn):focus-visible {
	border-block-end-color: currentColor;
}

.jsk-rail__facts dl {
	margin: 0;
	display: grid;
	gap: var(--wp--preset--spacing--1);
}

.jsk-rail__facts dt {
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--muted);
}

.jsk-rail__facts dd {
	margin: 0 0 var(--wp--preset--spacing--3);
	font-weight: 600;
}

.jsk-rail__facts dd:last-child {
	margin-block-end: 0;
}

.jsk-rail__cta p {
	margin-block: 0 var(--wp--preset--spacing--4);
	font-size: var(--wp--preset--font-size--sm);
}

.jsk-rail__cta .jsk-btn {
	width: 100%;
	justify-content: center;
}

/* Wide screens: two columns, and the rail is the point of them. */
@media (min-width: 1080px) {

	.jsk-entry--railed {
		display: grid;
		grid-template-columns: minmax(0, var(--jsk-measure)) minmax(0, 24rem);
		justify-content: space-between;

		/*
		 * NOT align-items: start.
		 *
		 * A sticky element travels inside its own parent's box, and nowhere else. With
		 * `start` the rail shrank to the height of its content — 715px beside 8,015px of
		 * terms — so .jsk-rail__inner had 715px of room to stick in and scrolled away
		 * with everything else the moment the page moved. Stretching the rail to the row
		 * gives the sticky child the whole column to travel down.
		 */
		align-items: stretch;
	}

	.jsk-entry--railed > .jsk-toc-compact {
		display: none;
	}

	.jsk-entry--railed > .jsk-entry__main {
		grid-column: 1;
	}

	/*
	 * The article variant: a 1fr main column and a 20rem rail, not 60ch and 24rem.
	 *
	 * The width the article wants is 68ch of 19px prose = 788px, and 788 + 384 + the gap
	 * does not fit the 1200px wrap. So the rail gives up 4rem rather than the article
	 * giving up eight characters -- the rail is derived navigation and the prose is the
	 * page. 1fr lets the column reach 788px, and the cap on .jsk-entry__main stops it
	 * going past.
	 */
	.jsk-entry--article.jsk-entry--railed {
		grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
		column-gap: var(--wp--preset--spacing--7);
	}

	/*
	 * The wide variant, for entries carrying a card grid or a data table.
	 *
	 * The main column takes what is left instead of exactly one measure, so /work/ keeps
	 * three case cards to a row and /about-us/data-handling/ keeps its table readable —
	 * while both still get the same rail as every other page. Prose does not widen with
	 * the column: the cap moves inward onto the children, so a paragraph is still 60ch
	 * with the extra space to its right, which is the one place on these pages where a
	 * ragged right edge is the correct answer rather than the problem.
	 */
	.jsk-entry--wide {
		grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
		column-gap: var(--wp--preset--spacing--7);
	}

	/*
	 * The base `.jsk-entry > *` rule caps every direct child at one measure, and
	 * .jsk-entry__main is a direct child — so without this the 1fr column above resolves
	 * to 622px and the grids stay exactly as narrow as they were. The cap is not removed,
	 * it moves one level in, onto the children that actually hold the prose.
	 */
	.jsk-entry--wide > .jsk-entry__main {
		max-width: none;
	}

	.jsk-entry--wide > .jsk-entry__main > * {
		max-width: var(--jsk-measure);
	}

	.jsk-entry--wide > .jsk-entry__main > .alignwide,
	.jsk-entry--wide > .jsk-entry__main > .alignfull,
	.jsk-entry--wide > .jsk-entry__main > figure,
	.jsk-entry--wide > .jsk-entry__main > table,
	.jsk-entry--wide > .jsk-entry__main > .wp-block-table,
	.jsk-entry--wide > .jsk-entry__main > .jsk-cards,
	.jsk-entry--wide > .jsk-entry__main > .jsk-engine-group,
	.jsk-entry--wide > .jsk-entry__main > .jsk-offer__grid,
	.jsk-entry--wide > .jsk-entry__main > .jsk-chips,
	.jsk-entry--wide > .jsk-entry__main > .jsk-pagination {
		max-width: none;
	}

	.jsk-entry--railed > .jsk-rail {
		grid-column: 2;
	}

	/*
	 * Sticky, but never taller than the viewport: a rail that outgrows the screen and
	 * sticks anyway pins its own bottom off-screen, and the last few links become
	 * unreachable. Scrolling it needs a tabbable container to satisfy WCAG 2.1.1, which
	 * is what tabindex="-1" plus the overflow gives — the links inside are already in
	 * the tab order and carry it there.
	 */
	.jsk-rail__inner {
		position: sticky;
		top: calc(var(--jsk-header-h) + var(--wp--preset--spacing--5));
		max-height: calc(100vh - var(--jsk-header-h) - var(--wp--preset--spacing--7));
		overflow-y: auto;
		overscroll-behavior: contain;
	}
}

/* Narrow screens: one column, rail after the prose, outline handled by the <details>. */
@media (max-width: 1079px) {

	.jsk-entry--railed > .jsk-rail .jsk-rail__toc {
		display: none;
	}

	.jsk-entry--railed > .jsk-rail {
		margin-block-start: var(--wp--preset--spacing--7);
		max-width: var(--jsk-measure);
	}
}

/* ==========================================================================
 * The colour-scheme toggle.
 *
 * Sits on the dark header band in both schemes — the band tokens are deliberately fixed
 * across light and dark — so it takes the on-band colours rather than the page ones.
 * ====================================================================== */

/*
 * Absent without scripts. .jsk-js is set by the inline boot script in the <head>, so the
 * button is either there from the first paint or never — it does not appear late.
 */
.jsk-theme-toggle {
	display: none;
	place-items: center;

	/*
	 * 44px, the WCAG 2.5.5 target size, even though the icon inside is 18px. An 18px
	 * hit area next to an 18px hit area is a coin toss on a phone.
	 */
	inline-size: 44px;
	block-size: 44px;
	padding: 0;
	/*
	 * on-band-muted, not on-band-border.
	 *
	 * The border token is rgba(255,255,255,.16), which composites to 1.64:1 on the header
	 * band. That is fine for the hairlines it was made for — a footer divider is
	 * decoration — but this is the visible boundary of a control, and WCAG 1.4.11 asks
	 * for 3:1. Measured off the rendered pixels rather than the declared colour, because
	 * a translucent border reads as its stated white until it is composited.
	 */
	border: 1px solid var(--wp--preset--color--on-band-muted);
	border-radius: var(--jsk-radius-control);
	background: transparent;
	color: var(--wp--preset--color--on-band);
	cursor: pointer;
	transition: border-color 0.18s, color 0.18s;
}

.jsk-js .jsk-theme-toggle {
	display: inline-grid;
}

.jsk-theme-toggle:hover {
	border-color: var(--wp--preset--color--on-band);
}

/*
 * One icon at a time, stacked in the same cell so the button never changes size when it
 * swaps. The sun means "you are in dark and this gives you light".
 */
.jsk-theme-toggle > svg {
	grid-area: 1 / 1;
}

.jsk-theme-toggle__sun {
	display: none;
}

[data-theme="dark"] .jsk-theme-toggle__sun {
	display: block;
}

[data-theme="dark"] .jsk-theme-toggle__moon {
	display: none;
}

@media (prefers-color-scheme: dark) {

	:root:not([data-theme="light"]) .jsk-theme-toggle__sun {
		display: block;
	}

	:root:not([data-theme="light"]) .jsk-theme-toggle__moon {
		display: none;
	}
}

/* ==========================================================================
 * /contact-us/ — the two ways in, beside the form.
 * ====================================================================== */

.jsk-contact {
	padding-block: var(--wp--preset--spacing--8);
	display: grid;
	gap: var(--wp--preset--spacing--7);
}

.jsk-contact__copy > * {
	max-width: var(--jsk-measure);
}

/*
 * The form reads as a panel rather than as more page, so the eye can tell the thing you
 * fill in from the column that explains it. Its own section wrapper is skipped — see the
 * `bare` argument in template-parts/enquiry-form.php.
 */
.jsk-form-panel {
	padding: var(--wp--preset--spacing--6);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--jsk-radius-card);
	background: var(--wp--preset--color--surface-soft);
}

.jsk-form-panel .jsk-section__head {
	margin-bottom: var(--wp--preset--spacing--5);
}

.jsk-form-panel .jsk-form {
	max-width: none;
}

@media (min-width: 1000px) {

	.jsk-contact {
		/*
		 * The copy keeps the measure; the form takes a fixed share rather than 1fr, so
		 * the fields stay a comfortable width instead of stretching with the viewport.
		 */
		grid-template-columns: minmax(0, var(--jsk-measure)) minmax(0, 30rem);
		justify-content: space-between;
		align-items: start;
	}
}

/*
 * The file field.
 *
 * Excluded from the rule above because a file input is two controls in one: a button the
 * browser draws and a filename it prints beside it. Giving it the text-field padding
 * boxes the button in oddly, so it gets the same frame and its own treatment inside.
 */
.jsk-form input[type="file"] {
	width: 100%;
	min-width: 0;
	padding: 0.55rem 0.6rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: 8px;
	background: var(--wp--preset--color--canvas);
	color: var(--wp--preset--color--text);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--base);
}

.jsk-form input[type="file"]::file-selector-button {
	margin-inline-end: var(--wp--preset--spacing--3);
	padding: 0.45rem 0.9rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--jsk-radius-control);
	background: transparent;
	color: var(--wp--preset--color--text);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 700;
	cursor: pointer;
}

.jsk-form input[type="file"]::file-selector-button:hover {
	border-color: var(--wp--preset--color--brand);
	color: var(--wp--preset--color--brand);
}
