/**
 * Component styles.
 *
 * Tokens live in theme.json — reference them via var(--wp--preset--*) and
 * var(--wp--custom--*). No hardcoded hex or px values below: if a value isn't
 * in theme.json, it belongs in theme.json, not here.
 */

/* ---------------------------------------------------------------
   Header
   --------------------------------------------------------------- */

/* --wp--custom--header--height is the header's TOTAL height, and everything
   that has to clear the fixed header (.jl-main's offset on non-hero pages,
   .jl-pagehead, the hero's top padding) calcs off it. So the header must
   actually be that tall — no block padding adding to it behind the token's
   back. It previously carried 1rem block padding on top of a 76px inner
   min-height, i.e. a 76px token describing a 126px header; every consumer of
   the token was quietly out by 50px. Height comes from the inner min-height
   alone now, so the token and reality cannot drift apart. */
/*
 * SHRINK ON SCROLL — one scale, driving the whole header.
 *
 * `.is-stuck` already existed and only changed COLOUR (and cross-faded the logo
 * variants); the header stayed 126px tall however far you scrolled. That is a
 * lot of permanent furniture — and this header is tall because the logo is,
 * not because the design wanted it.
 *
 * This theme is the easiest of the three to scale, and it is worth saying why so
 * nobody "consolidates" it toward the other two: the mobile breakpoint here
 * rebases --wp--custom--header--height on :root (88px, below), rather than
 * re-declaring min-height on the element the way devbristol does. So ONE
 * multiplication of the token covers both breakpoints automatically — there is
 * no compact rule to keep in step, because there is no compact rule.
 *
 * 0.78 matches pobroofing and devbristol exactly. Three sites, one client, one
 * scroll behaviour. 126 -> 98px here.
 */
.jl-header {
	--jl-header-scale: 1;

	padding-block: 0;
	padding-inline: clamp(20px, 5vw, 64px);
}

.jl-header.is-stuck {
	--jl-header-scale: 0.78;
}

.jl-header__inner {
	max-width: var(--wp--style--global--wide-size);
	margin-inline: auto;
	min-height: calc(var(--wp--custom--header--height) * var(--jl-header-scale));
	transition: min-height 0.3s var(--wp--custom--ease--out, ease);
}

@media (prefers-reduced-motion: reduce) {
	.jl-header__inner,
	.jl-header__logo img {
		transition: none;
	}
}

/* The logo is the tallest thing in the header, so it's what the token has to
   accommodate — sized to sit inside it with breathing room rather than
   setting the height itself. */
@media (max-width: 781px) {
	:root {
		--wp--custom--header--height: 88px;
	}
}

/* ---------------------------------------------------------------
   Header logo — two variants, one slot
   ---------------------------------------------------------------

   Both SVGs occupy the same grid cell and cross-fade on header state.
   Stacking beats toggling `display` because it keeps the header's width
   stable (no reflow on scroll) and lets the swap be a transition rather than
   a jump. See patterns/header-logo.php for why site-logo can't do this. */

.jl-header__logo a {
	display: grid;
	line-height: 0;
}

.jl-header__logo img {
	grid-area: 1 / 1;
	display: block;
	/* Height-driven, not width-driven: the header's height is fixed by the
	   token, so the logo has to be sized by the dimension that could overflow
	   it. Width follows from the SVG's own aspect ratio.
	   Scaled by --jl-header-scale so it shrinks with the header — and because
	   width is auto, the wordmark scales proportionally with no second rule. */
	height: calc(clamp(56px, 7vw, 94px) * var(--jl-header-scale, 1));
	width: auto;
	/* ⚠ opacity MUST stay in this list — it is the logo cross-fade between the
	   navy and reversed variants (below), not decoration. Height is added
	   alongside it; dropping either breaks a different thing. */
	transition:
		opacity 0.3s ease,
		height 0.3s var(--wp--custom--ease--out, ease);
}

/* Default: navy wordmark on the solid white header. */
.jl-header__logo-navy { opacity: 1; }
.jl-header__logo-rev  { opacity: 0; }

/* Transparent over a photo hero: swap to the reversed mark. */
.has-hero .jl-header:not(.is-stuck) .jl-header__logo-navy { opacity: 0; }
.has-hero .jl-header:not(.is-stuck) .jl-header__logo-rev  { opacity: 1; }

.jl-header__right {
	gap: var(--wp--preset--spacing--40);
}

/* The phone number must never wrap — a broken-up number is unreadable and
   unusable, and it's the primary conversion action on the site. */
.jl-header__cta .wp-block-button__link {
	white-space: nowrap;
}

.jl-header__links a {
	color: var(--wp--preset--color--navy);
	font-weight: 600;
	text-decoration: none;
}

.jl-header__links a:hover,
.jl-header__links a:focus-visible {
	color: var(--wp--preset--color--terracotta);
}

/* The fixed header sits over content; push the page down to compensate.
   Pages with a photo hero opt out — the hero sits under the header by design. */
body:not(.has-hero) .jl-main {
	padding-block-start: var(--wp--custom--header--height);
}

/* Core gives every top-level child of .wp-site-blocks a 24px block-start
   margin. On a hero page that margin lands above the hero, so the full-bleed
   photo started 24px down the page with a white strip above it and the fixed
   header straddling the seam. Core's rule is wrapped in :where(), so it has
   zero specificity and this single class beats it. */
.jl-main {
	margin-block-start: 0;
}

/* Same core rule, same 24px, second victim: the footer template part. It was
   opening a 24px white seam between the closing CTA band and the footer —
   present since the CTA landed, just easy to miss as a white line between a
   navy band and a navy footer. Now that the accreditation strip introduces the
   footer on a pale ground, the seam reads as a gap between two bands and is
   obvious. Both parts are full-bleed by design and must butt against what
   precedes them, so the margin is wrong for every template part here, not just
   the footer. */
.wp-site-blocks > .wp-block-template-part {
	margin-block-start: 0;
}

/* THIRD VICTIM, and the one that was actually on screen — found at 390px.
   ---------------------------------------------------------------------
   The two rules above fix .wp-site-blocks' own children. They do nothing for
   the children of .entry-content, which is where every page's actual sections
   live. Those are laid out by core's FLOW LAYOUT, a different rule with the
   same 24px:

       :where(.is-layout-flow) > * { margin-block-start: 24px }

   So every section on every page was separated from the next by a 24px band of
   page background. Between two white sections that is invisible, which is
   exactly why it survived this long — but the moment a section has a colour it
   is a white stripe across the page. Measured on Home: a white bar between the
   pale-blue "Areas We Cover" terrace band and the wheat section under it, and
   another above the navy CTA band.

   Every top-level child here is a full-bleed pattern that carries its own
   vertical padding — the section rhythm is theirs, and this margin is a second,
   competing rhythm on top of it. Zero is correct.

   :where() gives core's rule zero specificity, so (0,2,1) below is far more
   than needed — but it matches the two rules above and is honest about what it
   overrides. If a future page needs a bare paragraph at top level in the
   editor, give it a wrapper section; don't reintroduce a blanket margin. */
.jl-main .entry-content > * {
	margin-block-start: 0;
}

/* The phone stays on mobile — it is the highest-value action on the site for
   someone holding a phone, so it gets compacted, not dropped.
   (A `display:none` rule lived here and never worked: core ships
   `.wp-block-buttons{display:flex}` via the is-layout-flex class at equal
   specificity but later in the cascade, so the button was showing at every
   width regardless. Keeping it is the deliberate call; the dead rule is gone
   rather than resurrected with !important.) */
@media (max-width: 781px) {
	.jl-header__cta .wp-block-button__link {
		padding: 0.6rem 0.95rem;
		font-size: var(--wp--preset--font-size--small);
	}
}

/* ---------------------------------------------------------------
   Page head
   --------------------------------------------------------------- */

.jl-pagehead {
	/* Clears the fixed header. */
	padding-block-start: calc(var(--wp--custom--header--height) + var(--wp--preset--spacing--60)) !important;
}

/* ---------------------------------------------------------------
   Footer
   --------------------------------------------------------------- */

.jl-footer a {
	color: var(--wp--preset--color--surface);
	text-decoration: none;
}

.jl-footer a:hover,
.jl-footer a:focus-visible {
	text-decoration: underline;
}

.jl-footer__logo img {
	display: block;
	width: 170px;
	max-width: 100%;
	height: auto;
	margin-block-end: var(--wp--preset--spacing--30);
}

.jl-footer__h {
	margin-block-end: var(--wp--preset--spacing--30);
	opacity: 0.7;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	font-family: var(--wp--preset--font-family--body);
	font-weight: 600;
}

/* The source footer's strapline. Sits directly under the reversed logo and
   reads as part of the lockup, so it gets the wordmark's own weight rather
   than the blurb's muted body treatment. */
.jl-footer__strap {
	margin-block: 0 var(--wp--preset--spacing--20);
	font-weight: 600;
	letter-spacing: 0.04em;
}

.jl-footer__blurb {
	opacity: 0.75;
	max-width: 34ch;
}

.jl-footer__legal {
	opacity: 0.6;
}

.jl-footer__legal a {
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

.jl-footer__links {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 0.6rem;
	font-size: var(--wp--preset--font-size--small);
}

.jl-footer__links a {
	opacity: 0.85;
}

.jl-footer__links a:hover,
.jl-footer__links a:focus-visible {
	opacity: 1;
}

/* Flags a link that leaves the page for a document, rather than letting a PDF
   open unannounced. */
.jl-footer__pdf {
	opacity: 0.55;
	font-size: 0.85em;
}

.jl-footer__rule {
	border: 0;
	opacity: 0.15;
}

/* ---------------------------------------------------------------
   Hero — photo + navy scrim + big type. inc/hero.php renders it.
   --------------------------------------------------------------- */

.jl-hero {
	position: relative;
	display: flex;
	align-items: flex-end;
	isolation: isolate;
	overflow: hidden;
	background: var(--wp--preset--color--navy);
	padding-block: calc(var(--wp--custom--header--height) + var(--wp--preset--spacing--60)) var(--wp--preset--spacing--60);
	padding-inline: clamp(20px, 5vw, 64px);
}

.jl-hero--tall {
	min-height: var(--wp--custom--hero--tall);
}

.jl-hero--compact {
	min-height: var(--wp--custom--hero--compact);
}

.jl-hero__media,
.jl-hero__scrim {
	position: absolute;
	inset: 0;
	z-index: -1;
}

.jl-hero__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* The image can be soft when it's been upscaled; the scrim is what makes the
   headline legible regardless. Navy-tinted rather than neutral black so the
   photo sits inside the brand rather than next to it.

   Two gradients, not one:
   - the main bottom-up scrim carries the headline;
   - a second, shorter top-down band darkens the strip the floating header
     sits in. Without it, white nav links and the reversed logo landed on
     open sky at ~0.30 navy and were barely legible — measured on the Home
     hero, not guessed. The band is short enough that it reads as sky
     vignetting rather than a header bar. */
.jl-hero__scrim {
	background:
		linear-gradient(to bottom, rgba(41, 46, 37, 0.42) 0%, rgba(41, 46, 37, 0.12) 60%, transparent 100%) top / 100% 200px no-repeat,
		var(--wp--custom--hero--scrim);
}

/* Scrim weights are measured, not eyeballed — and the eyeball was wrong.
   Sampling the home hero photo behind the headline gives rgb(106,113,105).
   Composited under brand navy, white headline text scores:
       0.20 -> 5.81:1 (AA)    0.58 -> 7.70:1 (AAA)    0.94 -> 9.99:1 (AAA)
   The scrim was originally 0.94 at the headline: it bought 2.3 contrast
   points over 0.58 and cost the entire photograph — the Bristol terraces
   were rendered invisible under flat navy. 0.58 is already AAA, so the
   image gets to do its job. If you swap the hero photo, re-measure: a paler
   image (luminance >0.5 behind the headline) will need more, and guessing
   here is how the photo got buried the first time. */

.jl-hero__inner {
	position: relative;
	width: 100%;
	max-width: var(--wp--style--global--wide-size);
	margin-inline: auto;
}

.jl-hero__title {
	color: var(--wp--preset--color--surface);
	margin-block: 0 var(--wp--preset--spacing--30);
	max-width: 18ch;
	text-wrap: balance;
}

.jl-hero--compact .jl-hero__title {
	font-size: var(--wp--preset--font-size--xx-large);
	max-width: 24ch;
}

.jl-hero__lead {
	color: rgba(255, 255, 255, 0.88);
	font-size: var(--wp--preset--font-size--large);
	max-width: 56ch;
	margin-block: 0;
	text-wrap: pretty;
}

.jl-hero__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--30);
	margin-block-start: var(--wp--preset--spacing--50);
}

/* ---------------------------------------------------------------
   Buttons — plain <a>s in PHP patterns, matched to the block button
   --------------------------------------------------------------- */

.jl-btn {
	display: inline-block;
	padding: 0.85rem 1.75rem;
	border-radius: 100px;
	font-weight: 600;
	text-decoration: none;
	line-height: 1.2;
	transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}

/*
 * ⚠ THIS SECTION'S HEADING CLAIMED THESE WERE "matched to the block button".
 * THEY WERE NOT, AND THIS IS THE HALF THAT WAS MISSING.
 *
 * .jl-btn above is a plain <a> for the PHP patterns and has always had a
 * transition. Core's wp:button block renders .wp-element-button, styled entirely
 * from theme.json — and theme.json CANNOT declare a transition: there is no
 * styles.elements.button.transition key, and its ":hover" (terracotta -> navy)
 * only emits the end state. So the two "matched" buttons behaved differently:
 * the pattern one eased over 250ms, the block one snapped instantly. Same site,
 * same page, two buttons.
 *
 * Identical values to .jl-btn, on purpose — the point is one button behaviour,
 * not two that nearly match.
 *
 * ⚠ NO transform/lift here, unlike pobroofing and devbristol, where their own
 * hand-built buttons already lifted and the block button was made to match them.
 * .jl-btn does not lift, so adding one would invent a motion this theme does not
 * have anywhere else. Matching the theme's own idiom beats matching the sibling
 * sites — PLAN §1a: three independent themes, no shared design system.
 */
.wp-element-button,
.wp-block-button__link {
	transition: background-color 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}

@media (prefers-reduced-motion: reduce) {
	.jl-btn,
	.wp-element-button,
	.wp-block-button__link {
		transition: none;
	}
}

/* ---------------------------------------------------------------
   Content width — one number, and it is NOT here
   --------------------------------------------------------------- */

/*
 * ⚠⚠ CONTENT WIDTH LIVES IN theme.json: contentSize == wideSize == 1240px.
 *    DO NOT ADD A SECOND WIDTH HERE. Read this before "fixing" a page that
 *    looks too wide.
 *
 * Toby's rule, stated twice: "we want the content width to be the same
 * throughout the site, because it just looks odd having certain text sections
 * being a different width." One width, no exceptions.
 *
 * contentSize was 760 while wideSize was 1240, so anything that said
 * `align:"wide"` got 1240 and everything else silently got 760 — the hero title
 * running 1240 with body copy at 760 underneath it, on the same page. The fix
 * people reach for is to widen the one page in front of them; that is what the
 * block that used to be here did (a per-template contentSize plus a 90ch cap),
 * and it fixed a page while leaving every pattern on the site still at 760.
 * The default was the bug. Cloud9 took several passes for the same reason.
 *
 * Still deliberately narrow, and not exceptions to the rule: .jl-form (760px) —
 * a form is a component, not a text section — and single short lines that are
 * typography rather than content columns (hero lead 34ch, section head 62ch).
 */

.jl-btn--fill {
	background: var(--wp--preset--color--terracotta);
	color: var(--wp--preset--color--surface);
	border: 1px solid var(--wp--preset--color--terracotta);
}

.jl-btn--fill:hover,
.jl-btn--fill:focus-visible {
	background: var(--wp--preset--color--navy);
	border-color: var(--wp--preset--color--navy);
	color: var(--wp--preset--color--surface);
}

/* Ghost buttons only ever sit on the hero scrim, so white is safe here. */
.jl-btn--ghost {
	background: rgba(255, 255, 255, 0.1);
	color: var(--wp--preset--color--surface);
	border: 1px solid rgba(255, 255, 255, 0.55);
	backdrop-filter: blur(6px);
}

.jl-btn--ghost:hover,
.jl-btn--ghost:focus-visible {
	background: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--navy);
	border-color: var(--wp--preset--color--surface);
}

/* ---------------------------------------------------------------
   Eyebrow pill
   --------------------------------------------------------------- */

.jl-eyebrow {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	margin-block: 0 var(--wp--preset--spacing--30);
	padding: 0.4rem 0.9rem;
	border-radius: 100px;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	line-height: 1;
	background: rgba(255, 255, 255, 0.14);
	border: 1px solid rgba(255, 255, 255, 0.28);
	color: var(--wp--preset--color--surface);
	backdrop-filter: blur(6px);
}

/*
 * The eyebrow's mark is a HOUSE, not a dot.
 *
 * It was a 6px circle — the generic "status dot" that ships on every SaaS
 * template and says nothing about the business. Toby's note across all three
 * sites: the dot goes; a shape that means something (a house here, a roof on
 * pobroofing) is welcome.
 *
 * A house is the right shape for this one specifically: Just Lets is a letting
 * agent, the brand already owns the motif (assets/img/justlets-terrace.svg is a
 * row of terraces and patterns/terrace.php runs it as a section device), and the
 * 'managed' icon in inc/icons.php is already a roof gable. So this is the
 * brand's own vocabulary at 9px, not a new idea.
 *
 * clip-path, not an SVG: one element, no HTTP request, no markup change in the
 * patterns that already render <span class="jl-eyebrow__dot">. The pentagon is
 * roof-peak at 42% — shallow enough to read as a house rather than a spike at
 * this size, and it keeps a square-ish body under it.
 *
 * ⚠ currentColor is LOAD-BEARING and must stay. The eyebrow has two variants:
 * white on a photo hero (.jl-eyebrow) and terracotta on a light tint
 * (.jl-eyebrow--light). The mark follows the pill's own colour by inheriting
 * it, so both variants stay correct with no second rule. Give this a literal
 * colour and one of the two breaks.
 *
 * The class name still says "dot". Left alone deliberately: renaming it means
 * touching every pattern that emits it, for no behaviour change. Not worth the
 * diff — this comment is the pointer.
 */
.jl-eyebrow__dot {
	width: 9px;
	height: 9px;
	background: currentColor;
	clip-path: polygon(50% 0%, 100% 42%, 100% 100%, 0% 100%, 0% 42%);
	flex: none;
}

/* On light sections the same pill inverts to terracotta-on-tint. */
.jl-eyebrow--light {
	background: rgba(115, 59, 26, 0.08);
	border-color: rgba(115, 59, 26, 0.22);
	color: var(--wp--preset--color--terracotta);
	backdrop-filter: none;
}

/* ---------------------------------------------------------------
   Sections
   --------------------------------------------------------------- */

.jl-section {
	padding-block: var(--wp--preset--spacing--70);
	padding-inline: clamp(20px, 5vw, 64px);
}

.jl-section__inner {
	max-width: var(--wp--style--global--wide-size);
	margin-inline: auto;
}

.jl-section__head {
	max-width: 62ch;
	margin-block-end: var(--wp--preset--spacing--60);
}

.jl-section__head h2 {
	margin-block: 0 var(--wp--preset--spacing--30);
	text-wrap: balance;
}

.jl-section__head > p:last-child {
	margin-block-end: 0;
	color: var(--wp--preset--color--muted);
	font-size: var(--wp--preset--font-size--large);
	text-wrap: pretty;
}

/* ---------------------------------------------------------------
   Split — image beside prose
   --------------------------------------------------------------- */

.jl-split {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: clamp(2rem, 5vw, 5rem);
	align-items: center;
}

.jl-split--reverse .jl-split__media {
	order: 2;
}

.jl-split__media img {
	display: block;
	width: 100%;
	height: 100%;
	max-height: 560px;
	object-fit: cover;
	border-radius: var(--wp--custom--radius--lg);
}

.jl-split__body > :first-child {
	margin-block-start: 0;
}

.jl-split__body > :last-child {
	margin-block-end: 0;
}

@media (max-width: 781px) {
	.jl-split {
		grid-template-columns: 1fr;
	}

	.jl-split--reverse .jl-split__media {
		order: 0;
	}
}

/* ---------------------------------------------------------------
   Cards
   --------------------------------------------------------------- */

.jl-card {
	padding: var(--wp--preset--spacing--50);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: 12px;
	background: var(--wp--preset--color--surface);
}

/* ---------------------------------------------------------------
   Numbered cards — the design reference's 01/02/03 device
   --------------------------------------------------------------- */

.jl-numgrid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
	gap: var(--wp--preset--spacing--40);
	/* Stretch, so cards holding lists of different lengths still line up. */
	align-items: stretch;
}

/* For a small number of text-heavy cards (the two landlord services), which
   need more width than the 260px tracks give them. */
.jl-numgrid--wide {
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
	gap: var(--wp--preset--spacing--50);
}

.jl-numgrid > .jl-card > :first-child {
	margin-block-start: 0;
}

.jl-numcard {
	padding: var(--wp--preset--spacing--50);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--wp--custom--radius--md);
	background: var(--wp--preset--color--surface);
	transition: border-color 0.3s ease, transform 0.3s var(--wp--custom--ease--out);
}

.jl-numcard:hover {
	border-color: var(--wp--preset--color--terracotta);
	transform: translateY(-4px);
}

.jl-numcard__n {
	display: block;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 700;
	line-height: 1;
	color: var(--wp--preset--color--terracotta);
	opacity: 0.35;
	margin-block-end: var(--wp--preset--spacing--30);
}

.jl-numcard__t {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--large);
	font-weight: 600;
	color: var(--wp--preset--color--navy);
	margin-block: 0 var(--wp--preset--spacing--20);
	line-height: 1.25;
}

.jl-numcard p {
	margin-block: 0;
	color: var(--wp--preset--color--muted);
}

/* ---------------------------------------------------------------
   Checklist — the service bullet lists, given some air
   --------------------------------------------------------------- */

.jl-checklist {
	list-style: none;
	padding: 0;
	margin: 0;
	display: grid;
	gap: 0.75rem;
}

.jl-checklist li {
	position: relative;
	padding-inline-start: 1.9rem;
	line-height: 1.5;
}

/* Decorative tick drawn in CSS — no icon font, no SVG request, and it can't
   be announced to a screen reader as content. */
.jl-checklist li::before {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 0.42em;
	width: 0.72rem;
	height: 0.4rem;
	border-inline-start: 2px solid var(--wp--preset--color--terracotta);
	border-block-end: 2px solid var(--wp--preset--color--terracotta);
	transform: rotate(-45deg);
}

/* ---------------------------------------------------------------
   Quote / testimonial
   --------------------------------------------------------------- */

.jl-quotes {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	gap: var(--wp--preset--spacing--40);
}

/*
 * ⚠⚠ `margin: 0` IS LOAD-BEARING. THIS CARD IS A <figure>.
 *
 * Every UA stylesheet ships `figure { margin: 1em 40px }`, and nothing here
 * reset it — so each card lost 80px of its grid track to margin and gained a
 * 1em block margin, on top of the grid's own gap. The visual gutter between two
 * cards was gap + 40 + 40, i.e. three times what .jl-quotes asks for, and the
 * card was correspondingly narrower and therefore taller.
 *
 * ⚠ THE NEAR MISS, WHICH IS THE INTERESTING PART: `.jl-quote blockquote
 *   { margin: 0 }` below is correct and has always been there. Someone already
 *   knew about the UA margin — they reset it on the INNER blockquote and missed
 *   the <figure> wrapping it. One level up, same rule, same 40px.
 *
 * The identical defect was found on pobroofing at the same time, where the card
 * itself is a <blockquote> and was never reset at all. Toby measured it there
 * first ("weird size, not very wide, big gaps in the middle"); this site's
 * temp URL is down so it has not been eyeballed — the fix is the same, and the
 * arithmetic is not in question.
 *
 * box-sizing for the same reason as pobroofing's: WP ships no global
 * border-box reset, and this is a padded, bordered box.
 */
.jl-quote {
	margin: 0;
	box-sizing: border-box;
	padding: var(--wp--preset--spacing--50);
	border-radius: var(--wp--custom--radius--md);
	background: var(--wp--preset--color--surface);
	border: 1px solid var(--wp--preset--color--line);
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--40);
}

.jl-quote__mark {
	font-family: var(--wp--preset--font-family--display);
	font-size: 3rem;
	line-height: 0.6;
	color: var(--wp--preset--color--terracotta);
	opacity: 0.28;
}

.jl-quote blockquote {
	margin: 0;
	border: 0;
	padding: 0;
	font-size: var(--wp--preset--font-size--large);
	line-height: 1.55;
	color: var(--wp--preset--color--ink);
	text-wrap: pretty;
}

.jl-quote figcaption {
	font-weight: 600;
	color: var(--wp--preset--color--navy);
	margin-block-start: auto;
}

/* ---------------------------------------------------------------
   Fee schedule
   --------------------------------------------------------------- */

.jl-fees {
	display: grid;
	gap: var(--wp--preset--spacing--40);
}

.jl-fee {
	display: grid;
	grid-template-columns: 1fr auto;
	gap: var(--wp--preset--spacing--40);
	align-items: start;
	padding-block-end: var(--wp--preset--spacing--40);
	border-block-end: 1px solid var(--wp--preset--color--line);
}

.jl-fee:last-child {
	border-block-end: 0;
	padding-block-end: 0;
}

.jl-fee__t {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 600;
	font-size: var(--wp--preset--font-size--large);
	color: var(--wp--preset--color--navy);
	margin-block: 0 0.4rem;
	line-height: 1.3;
}

.jl-fee__d {
	margin-block: 0;
	color: var(--wp--preset--color--muted);
	max-width: 68ch;
}

.jl-fee__d + .jl-fee__d {
	margin-block-start: 0.6rem;
}

/* The amount is the thing people came for — it gets the emphasis. */
.jl-fee__v {
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: var(--wp--preset--font-size--large);
	color: var(--wp--preset--color--terracotta);
	white-space: nowrap;
	text-align: right;
	line-height: 1.3;
}

.jl-fee__v small {
	display: block;
	font-family: var(--wp--preset--font-family--body);
	font-weight: 600;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--muted);
	white-space: normal;
}

@media (max-width: 600px) {
	.jl-fee {
		grid-template-columns: 1fr;
		gap: var(--wp--preset--spacing--20);
	}

	.jl-fee__v {
		text-align: left;
	}

	.jl-fee__v small {
		display: inline;
	}
}

.jl-fees__group {
	margin-block-start: var(--wp--preset--spacing--60);
}

.jl-fees__group:first-child {
	margin-block-start: 0;
}

.jl-fees__group > h2 {
	font-size: var(--wp--preset--font-size--x-large);
	margin-block: 0 var(--wp--preset--spacing--40);
	padding-block-end: var(--wp--preset--spacing--30);
	border-block-end: 2px solid var(--wp--preset--color--navy);
}

/* ===============================================================
   FAQ accordion — patterns/faq-tenants.php, patterns/faq-landlords.php.
   <details>/<summary>, no JS — same no-JS-dependency convention as
   .jl-mnav (mobile nav). The marker is hidden and replaced with a rotating
   chevron so open/closed state doesn't rely on a browser's default styling.
   =============================================================== */

.jl-faq {
	display: grid;
	gap: var(--wp--preset--spacing--20);
	max-width: 860px;
}

.jl-faq__item {
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--wp--custom--radius--md);
	background: var(--wp--preset--color--surface);
}

.jl-faq__q {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--30);
	padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
	font-weight: 600;
	color: var(--wp--preset--color--navy);
	cursor: pointer;
	list-style: none;
}

.jl-faq__q::-webkit-details-marker {
	display: none;
}

.jl-faq__icon {
	flex-shrink: 0;
	inline-size: 12px;
	block-size: 12px;
	border-inline-end: 2px solid currentColor;
	border-block-end: 2px solid currentColor;
	transform: rotate(45deg);
	transition: transform 0.2s ease;
}

.jl-faq__item[open] .jl-faq__icon {
	transform: rotate(-135deg);
}

.jl-faq__a {
	padding: 0 var(--wp--preset--spacing--40) var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--muted);
}

.jl-faq__a p {
	margin: 0;
}

/* ---------------------------------------------------------------
   Accreditation strip
   --------------------------------------------------------------- */

/* The strip is a sitewide footer band (parts/footer.html), not a page
   section — it runs directly between the closing CTA and the navy footer on
   every page. Full section padding made it read as a content section in its
   own right and pushed the footer a screenful down; it's tightened to a band
   that belongs to the footer it introduces. */
.jl-accred-section {
	padding-block: var(--wp--preset--spacing--50);
}

.jl-accred {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: clamp(1.5rem, 4vw, 3.5rem);
}

.jl-accred img {
	display: block;
	height: 46px;
	width: auto;
	max-width: 100%;
	/* The source logos are inconsistent crops at ~100px tall; desaturating at
	   rest evens them out, and colour on hover rewards the deliberate look. */
	filter: grayscale(1);
	opacity: 0.62;
	transition: filter 0.3s ease, opacity 0.3s ease;
}

.jl-accred a:hover img,
.jl-accred a:focus-visible img,
.jl-accred img:hover {
	filter: none;
	opacity: 1;
}

.jl-accred__label {
	text-align: center;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--wp--preset--color--muted);
	margin-block: 0 var(--wp--preset--spacing--50);
}

/* ---------------------------------------------------------------
   CTA band
   --------------------------------------------------------------- */

.jl-cta {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	background: var(--wp--preset--color--navy);
	color: var(--wp--preset--color--surface);
	text-align: center;
	padding-block: var(--wp--preset--spacing--70);
	padding-inline: clamp(20px, 5vw, 64px);
}

.jl-cta__media,
.jl-cta__scrim {
	position: absolute;
	inset: 0;
	z-index: -1;
}

.jl-cta__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 50% 40%;
	display: block;
}

/* Heavier than the hero scrim: this band is centred text with no safe corner
   to sit in, and it has to hold up against the busiest part of the photo. */
.jl-cta__scrim {
	background: linear-gradient(to bottom, rgba(45, 62, 108, 0.9) 0%, rgba(45, 62, 108, 0.82) 100%);
}

.jl-cta h2 {
	color: var(--wp--preset--color--surface);
	margin-block: 0 var(--wp--preset--spacing--30);
	text-wrap: balance;
}

.jl-cta p {
	color: rgba(255, 255, 255, 0.85);
	font-size: var(--wp--preset--font-size--large);
	max-width: 54ch;
	margin-inline: auto;
	margin-block: 0;
}

.jl-cta__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--30);
	justify-content: center;
	margin-block-start: var(--wp--preset--spacing--50);
}

/* ---------------------------------------------------------------
   Contact
   --------------------------------------------------------------- */

.jl-contact {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
	gap: var(--wp--preset--spacing--40);
}

.jl-contact__card {
	padding: var(--wp--preset--spacing--50);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--wp--custom--radius--md);
	background: var(--wp--preset--color--surface);
}

.jl-contact__card h2 {
	font-size: var(--wp--preset--font-size--large);
	margin-block: 0 var(--wp--preset--spacing--30);
}

.jl-contact__card p {
	margin-block: 0;
}

.jl-contact__big {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 700;
	line-height: 1.2;
}

.jl-contact__big a {
	text-decoration: none;
	color: var(--wp--preset--color--terracotta);
}

.jl-contact__big a:hover {
	text-decoration: underline;
}

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

.jl-hours li {
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	padding-block: 0.45rem;
	border-block-end: 1px solid var(--wp--preset--color--line);
}

.jl-hours li:last-child {
	border-block-end: 0;
}

.jl-hours time {
	color: var(--wp--preset--color--muted);
	font-variant-numeric: tabular-nums;
}

.jl-map {
	border: 0;
	width: 100%;
	height: 420px;
	display: block;
	border-radius: var(--wp--custom--radius--md);
}

/* ---------------------------------------------------------------
   Focus — visible, always. Never remove without a replacement.
   --------------------------------------------------------------- */

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
	outline: 2px solid var(--wp--preset--color--navy);
	outline-offset: 2px;
	border-radius: 2px;
}

/* Skip link needs to clear the fixed header when focused. */
.skip-link:focus {
	top: var(--wp--preset--spacing--30) !important;
}

/* The office email is one long unbreakable token; in a narrow footer column it
   was splitting mid-domain ("...co.uk" onto its own line). Let it break at the
   punctuation instead of anywhere. */
.jl-footer__contact a[href^="mailto:"] {
	word-break: break-word;
	hyphens: none;
}

/* ---------------------------------------------------------------
   Enquiry form — CF7, styled to the theme rather than to CF7
   ---------------------------------------------------------------

   CF7 ships almost no CSS of its own, which is the good news: everything below
   is ours. It does wrap each tag in a <span class="wp-block-form-input__label">
   -ish structure (actually .wp-element-... varies by version), so selectors
   here target our own classes from bin/provision-form.php wherever possible and
   only reach for CF7's own class names for the response/validation output,
   which we don't control. */

.jl-form {
	max-width: 760px;
	margin-inline: auto;
}

.jl-form__grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0 var(--wp--preset--spacing--40);
}

/* One column on mobile. minmax(0,1fr) above matters: 1fr alone floors at the
   content's min-width, and a long placeholder or a validation message would
   push the grid wider than its container and scroll the page sideways. */
@media (max-width: 781px) {
	.jl-form__grid {
		grid-template-columns: 1fr;
	}
}

.jl-form__field {
	display: grid;
	gap: 0.4rem;
	margin-block: 0 var(--wp--preset--spacing--30);
}

.jl-form__field label {
	font-weight: 600;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink);
}

/* Required marker. Terracotta on white is 7.4:1 — it does not carry the
   meaning alone (the field is also [tel*]/[email*], so CF7 sets aria-required
   and the browser enforces it), it's a visual affordance only, hence
   aria-hidden in the markup. */
.jl-form__req {
	color: var(--wp--preset--color--terracotta);
}

.jl-form input[type="text"],
.jl-form input[type="tel"],
.jl-form input[type="email"],
.jl-form select,
.jl-form textarea {
	/* border-box is NOT inherited here — CF7's controls are outside any reset
	   this theme applies, so width:100% + padding + border overflowed the
	   container by 11px and scrolled the whole page sideways at 360px. Caught by
	   measuring scrollWidth against clientWidth on every page, not by looking:
	   11px of horizontal scroll is invisible in a screenshot and unmistakable
	   on a phone. */
	box-sizing: border-box;
	width: 100%;
	padding: 0.75rem 0.9rem;
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--wp--custom--radius--sm);
	background: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--ink);
	font-family: var(--wp--preset--font-family--body);
	/* 1rem, never smaller: iOS Safari zooms the viewport on focus for any input
	   under 16px, and the zoom is not reversible by the user. A "neat" 14px
	   field is how a mobile form starts fighting the person filling it in. */
	font-size: 1rem;
	line-height: 1.4;
	transition: border-color 0.2s ease;
}

.jl-form textarea {
	resize: vertical;
	min-height: 8rem;
}

.jl-form input:focus,
.jl-form select:focus,
.jl-form textarea:focus {
	border-color: var(--wp--preset--color--navy);
}

.jl-form .jl-btn {
	cursor: pointer;
	font-family: var(--wp--preset--font-family--body);
	font-size: 1rem;
}

/* CF7's own output. Validation text is terracotta (7.4:1 on white) and the
   response block gets a left rule rather than a colour fill, so the message is
   legible without relying on colour to carry the meaning. */
.jl-form .wpcf7-not-valid-tip {
	color: var(--wp--preset--color--terracotta);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.jl-form .wpcf7-response-output {
	margin: var(--wp--preset--spacing--40) 0 0;
	padding: var(--wp--preset--spacing--30);
	border: 1px solid var(--wp--preset--color--line);
	border-inline-start: 4px solid var(--wp--preset--color--navy);
	border-radius: var(--wp--custom--radius--sm);
	background: var(--wp--preset--color--surface);
}

.jl-form .wpcf7-spinner {
	margin-inline-start: var(--wp--preset--spacing--20);
}

.jl-form__fallback {
	padding: var(--wp--preset--spacing--40);
	border: 1px solid var(--wp--preset--color--line);
	border-radius: var(--wp--custom--radius--md);
	background: var(--wp--preset--color--surface);
	text-align: center;
}

/* ---------------------------------------------------------------
   Mobile nav overlay — MUST be styled independently of header state
   ---------------------------------------------------------------

   THE BUG THIS FIXES (found on a 390px viewport, not on desktop):
   the overlay opened as a BLANK WHITE SCREEN. Links were present, laid out
   and clickable — and completely invisible. Primary navigation, gone, on the
   one device where it's the only navigation.

   Two rules from different stylesheets collided:

     core:  .wp-block-navigation:not(.has-background)
              .wp-block-navigation__responsive-container.is-menu-open
              { background-color: #fff }
     ours:  .has-hero .jl-header:not(.is-stuck) .jl-header__links a
              { color: #fff }

   Ours exists to make the nav legible over the photo hero while the header is
   transparent. But the overlay is a MODAL: it covers the hero with its own
   white panel, and the header is still "not stuck" (nothing scrolled), so the
   white-link rule keeps firing over a white background. White on white.

   The reverse failure was one edit away: painting the overlay navy and relying
   on the .has-hero rule for white text would break every NON-hero page, where
   links are navy — navy on navy, same bug, inverted, and only reachable from
   /contact/ on a phone.

   So the overlay does not inherit header state at all. It is a self-contained
   navy panel with white links, on every page, scrolled or not. State-dependent
   colour is what caused this; the fix is to remove the dependency, not to add
   a second state to it.

   ON THE SPECIFICITY: the selectors below are deliberately long. Core's
   background rule is (0,4,0) and our own white-link rule is (0,4,1) in
   motion.css, which loads AFTER this file — so a tie loses. These are (0,5,0)
   and (0,5,1) respectively and win on specificity alone, independent of load
   order. Don't shorten them without re-checking both. */

.jl-header .jl-header__links.wp-block-navigation .wp-block-navigation__responsive-container.is-menu-open {
	background-color: var(--wp--preset--color--navy);
	color: var(--wp--preset--color--surface);
}

.has-hero .jl-header .jl-header__links .wp-block-navigation__responsive-container.is-menu-open a,
.jl-header .jl-header__links .wp-block-navigation__responsive-container.is-menu-open a {
	color: var(--wp--preset--color--surface);
}

/* Generous tap targets — this is the only nav on a phone. */
.jl-header .jl-header__links .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item a {
	font-size: var(--wp--preset--font-size--large);
	padding-block: 0.35rem;
}

/* Hover/focus inside the overlay: terracotta on navy is 2.4:1 — fine for the
   200-contrast of a hover cue on a large target, but NOT enough to carry the
   link text itself, so the colour change is paired with an underline rather
   than replacing legibility with it. */
.jl-header .jl-header__links .wp-block-navigation__responsive-container.is-menu-open a:hover,
.jl-header .jl-header__links .wp-block-navigation__responsive-container.is-menu-open a:focus-visible {
	color: var(--wp--preset--color--surface);
	text-decoration: underline;
	text-underline-offset: 0.25em;
}

/* The close (X) sits on the navy panel, so it is always white. Core defaults it
   to currentcolor, which the rule above already sets — this is belt and braces
   against a core change, and it's cheap. */
.jl-header .jl-header__links .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-close {
	color: var(--wp--preset--color--surface);
}

/* Focus ring inside the overlay: the global ring is navy, which is invisible on
   the navy panel. */
.jl-header .jl-header__links .wp-block-navigation__responsive-container.is-menu-open :where(a, button):focus-visible {
	outline-color: var(--wp--preset--color--surface);
}

/* ---------------------------------------------------------------
   Hamburger — the OPEN button follows header state; the overlay doesn't
   ---------------------------------------------------------------

   Separate bug, same family: the burger is core's own button, not an <a>, so
   `.jl-header__links a { color: navy }` never reached it. It fell back to the
   inherited ink (#292E25) and sat there as a near-black glyph on the hero
   photograph — the logo beside it is white, the phone button beside it is
   terracotta, and the one control that opens the site's entire navigation on a
   phone was the least visible thing in the header.

   Unlike the overlay, this button IS over the hero, so here the header state is
   genuinely the right input. */

.jl-header .wp-block-navigation__responsive-container-open {
	color: var(--wp--preset--color--navy);
}

.has-hero .jl-header:not(.is-stuck) .wp-block-navigation__responsive-container-open {
	color: var(--wp--preset--color--surface);
}

/* 24px is core's icon size and it is under the 44px minimum for a touch target.
   Padding rather than width/height so the icon itself doesn't scale. */
@media (max-width: 781px) {
	.jl-header .wp-block-navigation__responsive-container-open {
		padding: 10px;
		margin: -10px;
	}
}

/* ---------------------------------------------------------------
   Drawn icons — see inc/icons.php for why these aren't a library
   --------------------------------------------------------------- */

.jl-icon {
	display: block;
	width: 44px;
	height: 44px;
	overflow: visible;
	fill: none;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* Structure. currentColor is NOT used: these sit on white and on surface-cool,
   and the card's text colour is ink — navy is the deliberate choice for line
   work so the icon reads as illustration rather than as bolder text. */
.jl-icon__line {
	stroke: var(--wp--preset--color--navy);
	stroke-width: 1.6;
}

.jl-icon__line-thin {
	stroke: var(--wp--preset--color--navy);
	stroke-width: 1.8;
}

.jl-icon__accent {
	stroke: var(--wp--preset--color--terracotta);
	stroke-width: 1.6;
}

/* The terracotta roof/clip — the one filled shape, and the bit that ties the
   set back to the terrace and the logo. */
.jl-icon__accent-fill {
	fill: var(--wp--preset--color--terracotta);
	stroke: none;
}

/* Icon card — the numcard's sibling. Same box, an icon instead of a numeral. */
.jl-iconcard__icon {
	display: grid;
	place-items: center;
	width: 68px;
	height: 68px;
	margin-block-end: var(--wp--preset--spacing--30);
	border-radius: var(--wp--custom--radius--md);
	background: var(--wp--preset--color--surface-cool);
}

/* On a surface-cool section the tile would vanish into the ground — flip it. */
.jl-section[style*="surface-cool"] .jl-iconcard__icon,
.has-surface-cool-background-color .jl-iconcard__icon {
	background: var(--wp--preset--color--surface);
}

/* ---------------------------------------------------------------
   Icon motion — rides the EXISTING stagger, adds no new system
   ---------------------------------------------------------------

   The roof drops onto the house and the accent settles, one beat behind its
   card. It reuses [data-stagger]'s own --i so an icon can never drift out of
   step with the text beside it — there is no second timeline to keep in sync.

   Transform only. The parent card already handles opacity, and animating both
   here would double up and read as a fade-in-fade-in. */

@media (prefers-reduced-motion: no-preference) {
	[data-stagger] .jl-icon__accent-fill,
	[data-stagger] .jl-icon__accent {
		transform: translateY(-6px);
		transform-box: fill-box;
		transform-origin: 50% 100%;
		opacity: 0;
		transition:
			transform 0.5s var(--wp--custom--ease--pop, cubic-bezier(0.34, 1.56, 0.64, 1)),
			opacity 0.3s ease-out;
		transition-delay: calc((var(--i, 0) * 80ms) + 160ms);
	}

	[data-stagger].is-in .jl-icon__accent-fill,
	[data-stagger].is-in .jl-icon__accent {
		transform: none;
		opacity: 1;
	}
}

/* No-JS: .is-in never arrives, so the accent would sit at opacity:0 forever and
   every icon would lose its roof. Same failure mode motion.css's .no-js block
   exists to prevent — the rule is only in the no-preference query above, but
   that query is TRUE for a no-JS visitor, so it must be neutralised here too. */
.no-js .jl-icon__accent-fill,
.no-js .jl-icon__accent {
	transform: none;
	opacity: 1;
}

/* Numeral + icon on one line — tenants-security pairs both (see that file for
   why it keeps its numerals where landlords-services drops them). The icon is
   pushed to the far end so the numerals stay in a scannable column down the
   left edge and the icons don't shove them out of alignment. */
.jl-numcard__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--30);
	margin-block-end: var(--wp--preset--spacing--20);
}

/* ===============================================================
   Mobile navigation — patterns/mobile-nav.php. <details>, no JS.
   ===============================================================

   Replaces core/navigation's overlay below 782px (core's own nav breakpoint,
   which is where .jl-header__links hands off to the hamburger).

   ⚠ THE TOGGLE IS THE <summary>, AND IT IS ALSO THE CLOSE BUTTON. When open it
   repositions to the panel's top-right and draws as an X. One control, so the
   states cannot desynchronise and closing needs no JS. Do not add a second
   close button — it would need a script, and a no-JS visitor could then open
   the menu and not close it. */

.jl-mnav {
	display: none;
}

@media (max-width: 781px) {
	.jl-mnav {
		display: block;
	}

	/* Core's nav is out of the header markup now; this stops any nav that
	   reappears from rendering beside ours. */
	.jl-header .jl-header__links {
		display: none;
	}

	/* The header's phone button is the panel's foot CTA on mobile now. Two
	   phone numbers in a 375px header row is what made it cramped. */
	.jl-header .jl-header__cta {
		display: none;
	}
}

.jl-mnav__toggle {
	display: flex;
	align-items: center;
	justify-content: center;
	inline-size: 44px;
	block-size: 44px;
	cursor: pointer;
	/* Both are needed: Safari uses the -webkit- pseudo, everything else honours
	   list-style. Without them the browser prints a disclosure triangle. */
	list-style: none;
	position: relative;
	z-index: 120;
	color: var(--wp--preset--color--navy);
}

.jl-mnav__toggle::-webkit-details-marker {
	display: none;
}

/* One element, three bars: the middle IS the element, the outer two are its
   pseudos — so the X is two rotations of things that already exist. */
.jl-mnav__bars,
.jl-mnav__bars::before,
.jl-mnav__bars::after {
	display: block;
	inline-size: 24px;
	block-size: 2px;
	background: currentColor;
	border-radius: 2px;
	transition: transform 0.25s var(--wp--custom--ease--out, ease);
}

.jl-mnav__bars::before,
.jl-mnav__bars::after {
	content: "";
	position: absolute;
	inset-inline-start: 10px;
}

.jl-mnav__bars::before { transform: translateY(-7px); }
.jl-mnav__bars::after  { transform: translateY(7px); }

/*
 * Over the photo hero the header's controls go white, like the nav does.
 *
 * ⚠ `:not([open])` IS THE POINT OF THIS RULE. Without it, it also matches the X
 *   INSIDE the white panel — white on white, on the only control that closes the
 *   menu. It is a specificity fight the naive version loses:
 *       .has-hero .jl-header:not(.is-stuck) .jl-mnav__toggle   (0,4,0)
 *       .jl-mnav[open] .jl-mnav__toggle                        (0,3,0)
 *   This exact bug was found and measured on pobroofing before it shipped, and
 *   it is the same family as the invisible-overlay bug this theme's own header
 *   notes describe. :where() keeps the specificity at (0,4,0) so nothing else in
 *   the cascade moves — the rule just stops claiming the open state.
 */
.has-hero .jl-header:not(.is-stuck) :where(.jl-mnav:not([open])) .jl-mnav__toggle {
	color: var(--wp--preset--color--surface);
}

/* Always navy when open, stated twice — the :not([open]) above is the fix, this
   is the guard rail. Doubled to (0,5,0) so it outranks the over-photo rule even
   if an optimiser reorders the file. A menu you can open and not close is not a
   cosmetic bug. */
.jl-mnav[open] .jl-mnav__toggle,
.has-hero .jl-header .jl-mnav[open] .jl-mnav__toggle {
	position: fixed;
	inset-block-start: 14px;
	inset-inline-end: clamp(12px, 4vw, 28px);
	z-index: 210;
	color: var(--wp--preset--color--navy);
}

.jl-mnav[open] .jl-mnav__bars { background: transparent; }
.jl-mnav[open] .jl-mnav__bars::before { transform: translateY(0) rotate(45deg); }
.jl-mnav[open] .jl-mnav__bars::after  { transform: translateY(0) rotate(-45deg); }

.jl-mnav__panel {
	position: fixed;
	inset: 0;
	z-index: 200;
	background: var(--wp--preset--color--surface);
	display: grid;
	/* head / scroller / foot — only the scroller flexes, which is what keeps the
	   phone number on screen at all times. */
	grid-template-rows: auto minmax(0, 1fr) auto;
	/* ⚠ dvh, not vh. On iOS Safari 100vh EXCLUDES the address bar, so a vh panel
	   is taller than the window and the foot — the phone number — sits under the
	   chrome, permanently just off-screen, on the device this exists for. */
	block-size: 100dvh;
	max-block-size: 100dvh;
	animation: jl-mnav-in 0.28s var(--wp--custom--ease--out, ease) both;
}

@keyframes jl-mnav-in {
	from { opacity: 0; transform: translateY(-8px); }
	to   { opacity: 1; transform: none; }
}

.jl-mnav__head {
	padding: var(--wp--preset--spacing--30) clamp(20px, 5vw, 64px);
	padding-inline-end: 72px; /* clears the X */
	border-block-end: 1px solid var(--wp--preset--color--line);
}

.jl-mnav__logo { display: block; line-height: 0; }

.jl-mnav__logo img {
	block-size: 46px;
	inline-size: auto;
}

/*
 * ⚠ A MASK, NOT TWO GRADIENT OVERLAYS. An absolutely-positioned gradient at
 * each end sits ON TOP of the list and stops the first and last item taking
 * taps — on a phone, on the items people reach for. A mask fades the element's
 * own alpha, so there is nothing to intercept the touch.
 */
.jl-mnav__scroll {
	overflow-y: auto;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
	padding: var(--wp--preset--spacing--40) clamp(20px, 5vw, 64px);
	mask-image: linear-gradient(to bottom, transparent 0, #000 12px, #000 calc(100% - 28px), transparent 100%);
}

.jl-mnav__nav { display: grid; gap: var(--wp--preset--spacing--40); }

.jl-mnav__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
}

.jl-mnav__list a {
	display: flex;
	align-items: center;
	padding-block: 0.6rem;
	color: var(--wp--preset--color--navy);
	text-decoration: none;
	font-weight: 600;
	/* 44px min target, met with padding not a fixed height, so a two-line label
	   grows rather than clipping. */
	min-block-size: 44px;
}

.jl-mnav__list a:hover,
.jl-mnav__list a:focus-visible {
	color: var(--wp--preset--color--terracotta);
}

/* Top-level items carry top-level weight. */
.jl-mnav__list--lead a,
.jl-mnav__list--rest a {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 700;
	line-height: 1.15;
}

.jl-mnav__list--rest {
	border-block-start: 1px solid var(--wp--preset--color--line);
	padding-block-start: var(--wp--preset--spacing--30);
}

.jl-mnav__group {
	border-block-start: 1px solid var(--wp--preset--color--line);
	padding-block-start: var(--wp--preset--spacing--30);
}

.jl-mnav__grouph {
	margin: 0 0 0.35rem;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--terracotta);
}

.jl-mnav__grouph a { color: inherit; text-decoration: none; }

/* Second tier — sized as a list, not as display type. */
.jl-mnav__list--sub a {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 500;
	color: var(--wp--preset--color--muted);
}

.jl-mnav__list--sub a:hover,
.jl-mnav__list--sub a:focus-visible {
	color: var(--wp--preset--color--terracotta);
}

.jl-mnav__foot {
	padding: var(--wp--preset--spacing--30) clamp(20px, 5vw, 64px);
	/* The iPhone home indicator covers the bottom ~34px; without this the email
	   link is under it and untappable. */
	padding-block-end: max(var(--wp--preset--spacing--30), env(safe-area-inset-bottom));
	border-block-start: 1px solid var(--wp--preset--color--line);
	background: var(--wp--preset--color--surface-cool);
	text-align: center;
}

/* Terracotta fill + white label is the theme's own primary button (.jl-btn--fill);
   reused rather than a new colour, and it is AAA per PLAN §5c. */
.jl-mnav__cta {
	display: block;
	background: var(--wp--preset--color--terracotta);
	color: var(--wp--preset--color--surface);
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--x-large);
	font-weight: 700;
	text-decoration: none;
	border-radius: 100px;
	padding: 0.7rem 1rem;
	white-space: nowrap;
	transition: background-color 0.25s ease;
}

.jl-mnav__cta:hover,
.jl-mnav__cta:focus-visible {
	background: var(--wp--preset--color--navy);
}

.jl-mnav__email {
	margin: var(--wp--preset--spacing--20) 0 0;
	font-size: var(--wp--preset--font-size--small);
}

.jl-mnav__email a { color: var(--wp--preset--color--muted); }

.jl-mnav__foota {
	margin: 0.35rem 0 0;
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
}

.jl-mnav__foota a { color: var(--wp--preset--color--terracotta); }

/* Set on <html> by app.js. Enhancement: without it the page behind scrolls,
   which is untidy and not broken. */
.jl-mnav-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
	.jl-mnav__panel { animation: none; }

	.jl-mnav__bars,
	.jl-mnav__bars::before,
	.jl-mnav__bars::after,
	.jl-mnav__cta {
		transition: none;
	}
}
