/* ============================================================================
   TJ Garden Services — bespoke tenant design system.
   A standalone stylesheet (the Morgan's pattern): loaded only inside TjChrome,
   so it never leaks to other tenants. Reproduces the WordPress/Bricks original
   1:1, but zero-JS — the accordion is <details>, the gallery filter and mobile
   nav are :has() + hidden radios/checkbox, the reviews slider is scroll-snap.

   Tokens taken verbatim from the original AutomaticCSS palette:
     --primary #EC008C · --neutral #050505 · --neutral-ultra-dark #121212
   ========================================================================= */

/* ── Fonts ───────────────────────────────────────────────────────────────
   DELIBERATELY NONE. The WordPress original declares @font-face for "Trade Gothic Bold
   Condensed" and "AntarcticanMono" but never applies them: on the live site both
   --heading-font-family and --text-font-family compute to plain `sans-serif`, so every
   heading, nav item and paragraph renders in the system sans (Arial on Windows).

   Mark's call (2026-07-21) is to match the original exactly rather than "fix" it, so the
   rebuild is indistinguishable from the site it replaces. The woff2 files are kept in
   /tj/fonts/ — unreferenced, so never downloaded — ready if the real faces are ever
   switched on. To enable them: restore the @font-face blocks and point --display /
   --body at the families below.
   ──────────────────────────────────────────────────────────────────────── */

/* ── Tokens ─────────────────────────────────────────────────────────────── */
/* Values below are taken VERBATIM from the original site's AutomaticCSS build — palette,
   radii, fluid space/type scales. Don't substitute "nicer" numbers: the point is that this
   renders identically to the WordPress site it replaces. */
:root {
	/* Palette */
	--pink: #ec008c; /* --primary */
	--pink-hover: #ff0f9f; /* --primary-hover */
	--pink-dark: #b3006b; /* --primary-semi-dark */
	--on-pink: #ffe6f5; /* --primary-ultra-light (button label colour) */
	--ink: #050505; /* --neutral */
	--panel: #121212; /* --neutral-ultra-dark */
	--line: #404040; /* --neutral-dark (real borders are solid, not translucent) */
	--line-soft: rgba(255, 255, 255, 0.07);
	--text: #f2f2f2; /* --neutral-ultra-light */
	--muted: #a6a6a6; /* --neutral-semi-light */
	--muted-2: #8a8a8a;

	/* Both resolve to the system sans, exactly as the original does. Kept as two separate
	   tokens so the display/body split stays intact if the real faces are switched on. */
	--display: sans-serif;
	--body: sans-serif;

	/* Radii — the original's scale. Buttons use --radius-s (3.3px), NOT pills. */
	--radius-s: 3.3333px;
	--radius-m: 5px;
	--radius-l: 7.5px;
	--radius-xl: 11.25px;

	/* Fluid space scale */
	--space-xs: clamp(0.8441720333rem, calc(-0.0172384889vw + 0.8480506933rem), 0.8333333333rem);
	--space-s: clamp(1.1252813203rem, calc(0.1983597291vw + 1.0806503813rem), 1.25rem);
	--space-m: clamp(1.5rem, calc(0.5964214712vw + 1.365805169rem), 1.875rem);
	--space-l: clamp(1.9995rem, calc(1.2930417495vw + 1.7085656064rem), 2.8125rem);

	/* Fluid type scale — read off the original's :root, not approximated. */
	--text-s: clamp(0.8125rem, calc(0.0500373602vw + 0.8012415939rem), 0.8439609902rem);
	--text-m: clamp(1rem, calc(0.1988071571vw + 0.9552683897rem), 1.125rem);
	--text-l: clamp(1.2rem, calc(0.4765407555vw + 1.09277833rem), 1.499625rem);
	--text-xl: clamp(1.44rem, calc(0.8890658052vw + 1.2399601938rem), 1.999000125rem);
	--text-xxl: clamp(1.728rem, calc(1.489729092vw + 1.3928109543rem), 2.6646671666rem);
	--h1-size: clamp(2.376rem, calc(1.4008992857vw + 2.0607976607rem), 3.2568154259rem);
	--h2-size: clamp(1.98rem, calc(0.7367353877vw + 1.8142345378rem), 2.443222375rem);
	--h3-size: clamp(1.65rem, calc(0.2908548708vw + 1.5845576541rem), 1.832875rem);
	--h4-size: 1.375rem;

	/* Gap between hero elements (heading / lede / CTA) — 43.9px at a 1265 viewport. */
	--content-gap: clamp(1.9995rem, calc(1.2930417495vw + 1.7085656064rem), 2.8125rem);
	--container-gap: clamp(2.6653335rem, calc(2.4706425447vw + 2.1094389274rem), 4.21875rem);
	/* The hero's heading + lede both measure exactly 820px wide on the original. */
	--hero-measure: 820px;
	/* Home feature cards cap their text column at 546px (forces 2-line headings). */
	--feature-measure: 546px;

	/* Layout. The original's content container measures 1100px, and the fixed header bar
	   is inset 16px from the top of the viewport. */
	--wrap: 1100px;
	--gutter: 20px;
	--header-inset: 16px;
	--section-y: clamp(56px, 8vw, 104px);
}

/* Text selection — brand magenta, not the platform default. (The root fix in
   hooks.server.ts already scopes ::selection per tenant; this pins it for the bespoke
   dark design, where the accent-on-paper pairing needs a light label.) */
::selection {
	background: var(--pink);
	color: #ffffff;
}
::-moz-selection {
	background: var(--pink);
	color: #ffffff;
}

/* ── Reset / base ───────────────────────────────────────────────────────── */
*,
*::before,
*::after {
	box-sizing: border-box;
}
html {
	-webkit-text-size-adjust: 100%;
}
body {
	margin: 0;
	background: var(--ink);
	color: var(--text);
	font-family: var(--body);
	font-weight: 400;
	/* Measured on the original: 17.83px at a 1280 viewport, line-height 24.48 (≈1.373). */
	font-size: var(--text-m);
	line-height: 1.373;
	-webkit-font-smoothing: antialiased;
}
img {
	max-width: 100%;
	display: block;
}
a {
	color: inherit;
	text-decoration: none;
}
h1,
h2,
h3,
h4 {
	font-family: var(--display);
	font-weight: 700;
	text-transform: uppercase;
	line-height: 1.05;
	letter-spacing: normal; /* the original tracks headings normally */
	margin: 0;
}
h1 {
	font-size: var(--h1-size);
	line-height: 1.116;
}
/* Section headings measure 41.35px at a 1265 viewport on the original — larger than the
   raw --h2 token, because the Bricks components size themselves. Calibrated to match. */
h2 {
	font-size: clamp(1.75rem, 3.27vw, 2.585rem);
	line-height: 1.13;
}
h3 {
	font-size: var(--h3-size);
	line-height: 1.21;
}
h4 {
	font-size: var(--h4-size);
}
p {
	margin: 0 0 1rem;
	color: var(--muted);
}
p:last-child {
	margin-bottom: 0;
}
:focus-visible {
	outline: 2px solid var(--pink);
	outline-offset: 3px;
	border-radius: 3px;
}

.wrap {
	width: 100%;
	max-width: var(--wrap);
	margin-inline: auto;
	padding-inline: var(--gutter);
}
.section {
	padding-block: var(--section-y);
}
.section.tight {
	padding-block: clamp(40px, 5vw, 64px);
}

/* Shared type helpers ---------------------------------------------------- */
/* Eyebrow ("WHAT OUR CLIENTS SAY"). The original rule is only:
   `font-weight: 600; color: var(--primary); text-transform: uppercase` — no family
   override, so it renders in the BODY font, not the condensed display face. */
.tj-eyebrow {
	font-family: var(--body);
	font-weight: 600;
	text-transform: uppercase;
	color: var(--pink);
	font-size: var(--text-m); /* 17.83px — same size as body copy on the original */
	line-height: 1.373;
	margin: 0 0 0.75rem;
}
.tj-eyebrow.center,
.center {
	text-align: center;
}
.lede {
	color: var(--muted);
	max-width: 62ch;
}
.center .lede,
.lede.center {
	margin-inline: auto;
}
.skip-link {
	position: absolute;
	left: -9999px;
	top: 0;
	background: var(--pink);
	color: #fff;
	padding: 10px 16px;
	z-index: 999;
}
.skip-link:focus {
	left: 8px;
	top: 8px;
}

/* ── Buttons ────────────────────────────────────────────────────────────── */
/* Ported from the original's `.bricks-button` + `.btn--primary`:
     border-radius: var(--btn-radius) → var(--radius-s)   ← barely rounded, NOT a pill
     padding: .75em 1.5em · font-size: --text-m · font-weight: 400 · line-height: 1
     min-height: 3.125rem · border-width: 2px
     colours: bg/border --primary, label --primary-ultra-light, hover --primary-hover */
.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5em;
	font-family: var(--body);
	font-weight: 400;
	font-size: 1.25rem; /* measured 20px on the original's buttons */
	line-height: 1;
	padding: 0.6em 1.25em;
	min-height: 3.125rem;
	border-radius: var(--radius-s);
	border: 2px solid transparent;
	cursor: pointer;
	text-align: center;
	transition:
		background-color 0.18s ease,
		color 0.18s ease,
		border-color 0.18s ease;
}
.btn-solid {
	background: var(--pink);
	border-color: var(--pink);
	color: var(--on-pink);
}
.btn-solid:hover {
	background: var(--pink-hover);
	border-color: var(--pink-hover);
}
/* `.btn--primary.btn--outline` in the original */
.btn-ghost {
	background: transparent;
	border-color: var(--pink);
	color: var(--pink);
}
.btn-ghost:hover {
	background: var(--pink-hover);
	border-color: var(--pink-hover);
	color: var(--on-pink);
}
.btn-line {
	border-color: var(--line);
	color: var(--text);
}
.btn-line:hover {
	border-color: var(--pink);
	color: var(--pink);
}
.btn-arrow {
	width: 1.1em;
	height: 1.1em;
	flex: 0 0 auto;
}

/* ── Header ─────────────────────────────────────────────────────────────── */
/* The original's header is `position: fixed` with a TRANSPARENT wrapper — the hero photo
   runs to the very top of the page and the dark bar floats over it, inset 16px. (It is not
   sticky-in-flow: that would push the hero down and leave a black band above it.)
   The bar itself: #121212, 2px solid #404040, radius 7.5px — the same treatment as the
   "Let's Talk" panel — padding `--space-xs --space-m`, logo 168×82. */
.site-header {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 998;
	padding: var(--header-inset) var(--gutter) 0;
	background: none;
	pointer-events: none; /* let clicks through the transparent gutter to the hero */
}
.site-header .bar {
	pointer-events: auto;
	max-width: var(--wrap);
	margin-inline: auto;
	background: var(--panel);
	border: 2px solid var(--line);
	border-radius: var(--radius-l);
	display: flex;
	align-items: center;
	gap: var(--space-m);
	padding: var(--space-xs) var(--space-m);
}
.brand {
	display: flex;
	align-items: center;
	flex: 0 0 auto;
}
.brand img {
	width: 168px;
	height: auto;
}
/* Tighter than the original's 29.4px gap (Mark's call): pulling the items together shrinks
   the nav block, and since it's centred with `margin-inline: auto` the reclaimed width goes
   to both sides — more air after the logo and before the Get a Quote button. */
.nav {
	display: flex;
	align-items: center;
	gap: var(--space-s);
	margin-inline: auto;
}
/* 16.15px / uppercase as the original, but lighter than its 700 (Mark's call). */
.nav a,
.nav .sub-toggle {
	font-family: var(--display);
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: normal;
	font-size: 1.01rem;
	padding: 0.4em 0.7em;
	border-radius: var(--radius-s);
	border: 2px solid transparent;
	color: var(--text);
	background: none;
	cursor: pointer;
	white-space: nowrap;
	transition:
		color 0.18s ease,
		border-color 0.18s ease;
}
.nav a:hover,
.nav .sub-toggle:hover {
	color: var(--pink);
}
.nav a.active,
.nav .sub-toggle.active {
	color: var(--pink);
	border-color: var(--pink);
}
.header-cta {
	flex: 0 0 auto;
}

/* Services dropdown — hover/focus only, no JS */
.has-sub {
	position: relative;
}
.has-sub > .sub-toggle {
	display: inline-flex;
	align-items: center;
	gap: 0.35em;
}
.has-sub > .sub-toggle svg {
	width: 10px;
	height: 10px;
	transition: transform 0.18s ease;
}
.submenu {
	position: absolute;
	top: calc(100% + 10px);
	left: 0;
	min-width: 232px;
	background: var(--panel);
	border: 1px solid var(--line-soft);
	border-radius: var(--radius-m);
	padding: 8px;
	display: grid;
	gap: 2px;
	opacity: 0;
	visibility: hidden;
	transform: translateY(-6px);
	transition:
		opacity 0.18s ease,
		transform 0.18s ease,
		visibility 0.18s;
}
.has-sub:hover .submenu,
.has-sub:focus-within .submenu {
	opacity: 1;
	visibility: visible;
	transform: none;
}
.has-sub:hover > .sub-toggle svg,
.has-sub:focus-within > .sub-toggle svg {
	transform: rotate(180deg);
}
.submenu a {
	display: block;
	padding: 0.6em 0.8em;
	border-radius: 6px;
	font-size: 0.9rem;
}
.submenu a:hover {
	background: rgba(255, 255, 255, 0.05);
	color: var(--pink);
}

/* Mobile nav — checkbox toggle, zero JS */
#navToggle {
	display: none;
}
.nav-toggle {
	display: none;
	flex-direction: column;
	justify-content: center;
	gap: 5px;
	width: 44px;
	height: 44px;
	padding: 0 10px;
	background: none;
	border: 1px solid var(--line);
	border-radius: 8px;
	cursor: pointer;
}
.nav-toggle span {
	display: block;
	height: 2px;
	background: #fff;
	border-radius: 2px;
}

/* Drawer chrome is desktop-hidden. */
.nav-head,
.nav-actions,
.nav-backdrop,
.nav-close {
	display: none;
}

@media (max-width: 1040px) {
	.site-header .bar {
		gap: 12px;
		padding: var(--space-xs) var(--space-s);
	}
	.nav-toggle {
		display: flex;
		margin-left: auto;
	}
	/* The original hides the header CTA on mobile (`.header-9__btn { display: none }`) —
	   the bar is just logo + hamburger; the quote CTA moves into the drawer. */
	.header-cta {
		display: none;
	}

	/* ── Off-canvas drawer, sliding in from the LEFT ─────────────────────── */
	.nav {
		position: fixed;
		inset: 0 auto 0 0;
		z-index: 1001;
		width: min(414px, 88vw);
		margin: 0;
		display: flex;
		flex-direction: column;
		align-items: stretch;
		gap: 0;
		background: var(--ink);
		padding: var(--space-m) var(--space-m) var(--space-l);
		overflow-y: auto;
		overscroll-behavior: contain;
		transform: translateX(-100%);
		visibility: hidden;
		transition:
			transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
			visibility 0.32s;
	}
	#navToggle:checked ~ .nav {
		transform: none;
		visibility: visible;
	}
	.nav-backdrop {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 1000;
		background: rgba(0, 0, 0, 0.5);
		opacity: 0;
		visibility: hidden;
		transition:
			opacity 0.32s ease,
			visibility 0.32s;
	}
	#navToggle:checked ~ .nav-backdrop {
		opacity: 1;
		visibility: visible;
	}

	.nav-head {
		display: flex;
		align-items: flex-start;
		justify-content: space-between;
		gap: 1rem;
		margin-bottom: var(--space-l);
	}
	.nav-head img {
		width: 140px;
		height: auto;
	}
	.nav-close {
		display: grid;
		place-items: center;
		width: 40px;
		height: 40px;
		margin: -6px -6px 0 0;
		font-size: 1.9rem;
		line-height: 1;
		color: var(--text);
		cursor: pointer;
	}
	.nav-close:hover {
		color: var(--pink);
	}

	.nav a,
	.nav .sub-toggle {
		padding: 0.62em 0;
		border: 0;
		white-space: normal;
		text-align: left;
		justify-content: flex-start;
		font-size: 1.2rem;
	}
	.has-sub .submenu {
		position: static;
		opacity: 1;
		visibility: visible;
		transform: none;
		border: 0;
		background: none;
		padding: 0 0 0 14px;
	}
	.has-sub > .sub-toggle svg {
		display: inline-block;
		width: 12px;
		height: 12px;
	}
	.nav-actions {
		display: grid;
		gap: 12px;
		margin-top: var(--space-l);
	}
	.nav-actions .btn {
		width: 100%;
	}
}

/* Reduced motion: appear instantly rather than sliding. */
@media (prefers-reduced-motion: reduce) {
	.nav,
	.nav-backdrop {
		transition: none;
	}
}
@media (max-width: 560px) {
	.brand img {
		width: 118px;
	}
	.header-cta {
		font-size: 1rem;
		padding: 0.6em 1em;
		min-height: 2.6rem;
	}
}

/* ── Page hero ──────────────────────────────────────────────────────────── */
/* Starts at the very top of the page: the fixed header floats over it, so the photo runs
   full-bleed behind the bar.
   Ported exactly from the original's `.hero-70`: full viewport height, flex column with the
   content centred, and `padding-top: 160px` reserving room for the floating header. Because
   the content centres in the space BELOW that padding, the heading clears the bar by ~80px
   rather than butting up against it. */
.page-hero {
	position: relative;
	isolation: isolate;
	height: 100vh;
	min-height: 560px;
	max-height: 900px;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	text-align: center;
	padding: 160px 0 0;
	overflow: hidden;
}
/* `.hero-70-sub` on the original — service pages and About run at 70% viewport height. */
.page-hero.sub {
	height: 70vh;
	min-height: 440px;
	max-height: 660px;
}
/* Hero children (heading / lede / CTA) are spaced by --content-gap, not margins, and the
   text column is capped at 820px — the measure the original wraps its H1 and lede to. */
.page-hero > .wrap {
	max-width: calc(var(--hero-measure) + 2 * var(--gutter));
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--content-gap);
}
.page-hero > .wrap > * {
	margin: 0;
	width: 100%;
}
/* ...except the CTA, which hugs its label. */
.page-hero > .wrap > .btn {
	width: auto;
}
/* The bar wraps and shortens below 1040px, so the clearance can come down with it. */
/* Mobile: the original switches the hero from a viewport-height box to a content-sized one
   with `padding: 240px 0 96px` — that 240px is what gives the heading real breathing room
   under the floating bar (measured gap: 111px, vs 31px at my old 130px). */
@media (max-width: 1040px) {
	.page-hero {
		padding-top: 240px;
		padding-bottom: 96px;
		height: auto;
		min-height: 0;
		max-height: none;
	}
	.page-hero.sub {
		height: auto;
		min-height: 0;
		max-height: none;
	}
}
.page-hero .hero-bg {
	position: absolute;
	inset: 0;
	z-index: -2;
}
.page-hero .hero-bg img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* Matches the original: biases the crop upward so more sky shows above the horizon,
	   which matters most on mobile where the frame is tall and narrow. */
	object-position: 50% 10%;
}
/* Ends at FULLY OPAQUE --ink so the photo melts into the black section beneath it with no
   visible seam. (Stopping at 0.95 left a faint band where the hero met the next section.) */
.page-hero::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	/* The original uses a flat rgba(0,0,0,0.5) scrim. We keep a gradient so the photo still
	   melts into the black section beneath it, but hold it near 0.5 for most of the frame
	   (was 0.72→0.88, which greyed the photo out) and only ramp to opaque at the very
	   bottom. Net effect: the original's colour, plus the seamless join. */
	background: linear-gradient(
		to bottom,
		rgba(5, 5, 5, 0.5) 0%,
		rgba(5, 5, 5, 0.42) 50%,
		rgba(5, 5, 5, 0.55) 80%,
		var(--ink) 100%
	);
}
.page-hero h1 {
	margin-bottom: 1rem;
	color: #fff; /* pure white over the photo; section headings elsewhere are #f2f2f2 */
}
/* No ch cap here: the hero lede wraps to the 820px measure set on .wrap, matching the
   original's 4 lines. A narrower cap added extra lines and pushed the block off-centre. */
.page-hero .lede {
	max-width: none;
	color: rgba(255, 255, 255, 0.9); /* hero copy sits on the photo — near-white, not muted */
}
/* Plain hero (gallery / contact / legal) — the original has no hero section on these pages,
   just a heading block, so it sizes to content. Keeps the 160px header clearance. */
/* 160px only clears the bar itself (which ends at 129px), leaving the heading almost
   touching it. The photo heroes get an ~80px breathing gap because their content centres in
   a 100vh box; plain heroes have no such box, so that clearance is added explicitly here. */
.page-hero.plain {
	height: auto;
	min-height: 0;
	max-height: none;
	padding-top: 210px;
	padding-bottom: clamp(40px, 6vw, 70px);
}
@media (max-width: 1040px) {
	.page-hero.plain {
		padding-top: 170px;
	}
}
.page-hero.plain > .wrap {
	gap: var(--space-m);
}
.page-hero.plain::after {
	background: none;
}

/* ── Home: stacked service feature cards ────────────────────────────────── */
/* Cards are 1100×387 on the original with a 66px gap (= --container-gap) and 65.4px of
   internal padding. The text column is capped at 546px — that cap is what makes every
   card heading wrap onto two lines. */
.feature-stack {
	display: grid;
	gap: var(--container-gap);
}
.feature {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	border-radius: var(--radius-l);
	border: 2px solid var(--line);
	min-height: 387px;
	display: flex;
	align-items: flex-end;
	padding: var(--container-gap);
}
.feature img {
	position: absolute;
	inset: 0;
	z-index: -2;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.feature::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	background: linear-gradient(105deg, rgba(5, 5, 5, 0.9) 0%, rgba(5, 5, 5, 0.55) 60%, rgba(5, 5, 5, 0.3) 100%);
}
.feature-body {
	display: grid;
	gap: 0.9rem;
	max-width: 46ch;
}
/* The home feature cards carry the largest type on the site — 56.55px at 1265, bigger even
   than the hero H1. Calibrated to the original rather than the generic h2 scale. */
.feature h2 {
	font-size: clamp(2rem, 4.5vw, 3.535rem);
	line-height: 1.108;
}
.feature .btn {
	justify-self: start;
}
@media (min-width: 861px) {
	.feature {
		align-items: center;
	}
	.feature-body {
		display: grid;
		grid-template-columns: minmax(0, var(--feature-measure)) 1fr;
		grid-template-areas: 'h .' 'p cta';
		align-items: center;
		gap: var(--space-m) var(--container-gap);
		max-width: none;
		width: 100%;
	}
	/* Both capped at the original's 546px text column, which is what wraps every card
	   heading onto two lines. */
	.feature-body h2 {
		grid-area: h;
		max-width: var(--feature-measure);
	}
	.feature-body p {
		grid-area: p;
		max-width: var(--feature-measure);
	}
	.feature-body .btn {
		grid-area: cta;
		justify-self: end;
	}
}

/* ── Service page: card grid ────────────────────────────────────────────── */
/* Two columns on the original (524px cards inside the 1100px container), never three.
   Column gap 43.888px = --content-gap, row gap 29.4871px = --space-m. */
.svc-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-m) var(--content-gap);
	align-items: stretch; /* both cards in a row take the height of the taller one */
}
@media (max-width: 720px) {
	.svc-grid {
		grid-template-columns: minmax(0, 1fr);
	}
}
/* `.sub-services__container` on the original: 528×338, radius 7.5, 2px #404040 border and
   65.4px of padding — the card type is much larger than a generic card scale suggests. */
.svc-card {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	border-radius: var(--radius-l);
	border: 2px solid var(--line);
	min-height: 338px;
	display: flex;
	flex-direction: column;
	/* Content sits at the TOP, so headings line up across a row whatever the copy length;
	   any extra height on the taller card falls as empty space at the bottom. (Was
	   flex-end, which pinned copy to the base and left the headings ragged.) */
	justify-content: flex-start;
	gap: var(--space-m);
	padding: var(--container-gap);
}
.svc-card img {
	position: absolute;
	inset: 0;
	z-index: -2;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.svc-card::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	background: linear-gradient(to top, rgba(5, 5, 5, 0.94) 10%, rgba(5, 5, 5, 0.6) 55%, rgba(5, 5, 5, 0.35) 100%);
}
/* 45.92px / lh 51.62 at a 1265 viewport — measured, not guessed. */
.svc-card h2 {
	font-size: clamp(1.75rem, 3.63vw, 2.87rem);
	line-height: 1.124;
	margin: 0;
}
.svc-card p {
	font-size: var(--text-m);
	color: rgba(255, 255, 255, 0.8);
	margin: 0;
}

/* ── Intro / prose block ────────────────────────────────────────────────── */
.prose {
	max-width: 74ch;
	margin-inline: auto;
}
.prose h3 {
	color: var(--pink);
	text-align: center;
	margin-bottom: 2.4rem;
}
.prose p {
	text-align: left;
}
.prose p + p {
	margin-top: 1.1rem;
}
.prose .btn {
	margin-top: 0.6rem;
}
.prose-actions {
	display: flex;
	justify-content: center;
	margin-top: 2.6rem;
}

/* Left-aligned article body (About) */
.article {
	max-width: 66ch;
	margin-inline: auto;
}
.article p {
	margin-bottom: 1.1rem;
}
.article a {
	color: var(--pink);
	text-decoration: underline;
	text-underline-offset: 3px;
}
.article ul {
	list-style: none;
	margin: 0 0 1.3rem;
	padding: 0;
	display: grid;
	gap: 0.45rem;
}
.article li {
	position: relative;
	padding-left: 1.2em;
	color: var(--muted);
}
.article li::before {
	content: '';
	position: absolute;
	left: 0;
	top: 0.62em;
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--pink);
}

/* ── Trust strip ────────────────────────────────────────────────────────── */
/* Centred as a row on desktop; stacks left-aligned on mobile where centred list items
   read as ragged. */
.trust {
	background: var(--panel);
	border-radius: var(--radius-l);
	padding: var(--space-m);
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-s) var(--content-gap);
	text-align: start;
}
@media (max-width: 760px) {
	.trust {
		justify-content: flex-start;
	}
	.trust ul {
		display: grid;
		gap: var(--space-s);
		justify-items: start;
	}
}
.trust li {
	display: flex;
	align-items: center;
	gap: 0.6em;
	font-size: var(--text-m);
	color: var(--muted);
}
.trust ul {
	display: contents;
	list-style: none;
	margin: 0;
	padding: 0;
}
.trust svg {
	width: 17px;
	height: 17px;
	flex: 0 0 auto;
	color: var(--pink);
}
/* About page variant (`.cta-24__container`): FULL content width, square corners, and a
   linear gradient fading to black over the bottom 40% — not the rounded, capped-width
   panel used on the service pages. */
.trust.grid-2 {
	max-width: none;
	margin-inline: 0;
	border-radius: 0;
	padding: 43.7px 29.4px;
	background-color: var(--panel);
	background-image: linear-gradient(var(--panel) 60%, rgba(0, 0, 0, 0.6));
	justify-content: center;
}
@media (min-width: 700px) {
	.trust.grid-2 ul {
		display: grid;
		grid-template-columns: auto auto;
		justify-content: center;
		gap: 29.4px 43.7px;
	}
}

/* ── Approach steps ─────────────────────────────────────────────────────── */
/* Ported from the original's `.process-22__row`: a 40px badge column beside the content,
   with a 1px DASHED magenta line running down through the badges to connect the steps. */
.steps {
	position: relative;
	display: grid;
	gap: 0;
	margin-top: var(--content-gap);
	list-style: none;
	padding: 0;
}
.step {
	position: relative;
	display: grid;
	grid-template-columns: 40px minmax(0, 1fr);
	column-gap: 19.83px;
	align-items: start;
	padding-bottom: var(--content-gap);
}
.step:last-child {
	padding-bottom: 0;
}
/* Each step draws the segment spanning its OWN full box (content + bottom padding), which
   lands exactly on the next badge — so consecutive segments join into one unbroken line,
   with the opaque badges sitting on top. Anchored to `.step` rather than the badge column
   because that column only wraps the badge, leaving a gap before the next step.
   The last step draws nothing, which is what stops the line AT badge 04 instead of
   trailing past it down the final step's text. */
.step::before {
	content: '';
	position: absolute;
	top: 0;
	bottom: 0;
	left: 20px; /* centre of the 40px badge column */
	border-right: 1px dashed var(--pink);
}
.step:last-child::before {
	display: none;
}
/* The badge column: the connector is a dashed border behind the badge, drawn full-height
   so consecutive steps join up. The last step stops the line at its own badge. */
.step-n {
	position: relative;
	grid-row: 1 / -1;
	align-self: stretch;
	display: flex;
	justify-content: center;
}
.step-n span {
	position: relative;
	z-index: 1;
	width: 40px;
	height: 40px;
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--pink);
	color: #fff;
	border-radius: var(--radius-m);
	font-size: var(--text-m);
	font-weight: 400;
	line-height: 1;
}
.step h3 {
	font-size: var(--h3-size);
	margin-bottom: 0.6rem;
}
.step p {
	margin: 0;
	grid-column: 2;
	font-size: var(--text-m);
}

/* ── FAQ (native <details>, zero JS) ────────────────────────────────────── */
.faq-layout {
	display: grid;
	gap: clamp(28px, 4vw, 48px);
	align-items: start;
}
@media (min-width: 900px) {
	.faq-layout {
		grid-template-columns: 1fr 1.05fr;
	}
}
.faq-intro h2 {
	margin-bottom: 1rem;
}
/* `.card-faq-15` on the original: a DOTTED-outline panel on pure black holding the note and
   the CTA together — 2px dotted #404040, radius 5px, --space-m padding, text in
   --primary-semi-light at --text-s. */
.faq-note {
	margin: var(--content-gap) 0 0;
	border: 2px dotted var(--line);
	border-radius: var(--radius-m);
	background: #000;
	padding: var(--space-m);
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--content-gap);
}
.faq-note p {
	margin: 0;
	color: #ff4db8; /* --primary-semi-light */
	font-size: var(--text-s);
}
.faq-list {
	display: grid;
	gap: 12px;
}
.faq-item {
	background: var(--panel);
	border: 1px solid var(--line-soft);
	border-radius: var(--radius-m);
	overflow: hidden;
}
/* Questions are bold on the original: 17.83px / weight 700 / uppercase, colour #d9d9d9. */
.faq-item summary {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	cursor: pointer;
	padding: var(--space-s) var(--space-m);
	font-family: var(--display);
	text-transform: uppercase;
	font-size: var(--text-m);
	font-weight: 700;
	color: #d9d9d9;
	letter-spacing: normal;
	list-style: none;
}
.faq-item summary::-webkit-details-marker {
	display: none;
}
.faq-item summary::after {
	content: '';
	flex: 0 0 auto;
	width: 11px;
	height: 11px;
	background: var(--pink);
	clip-path: polygon(45% 0, 55% 0, 55% 45%, 100% 45%, 100% 55%, 55% 55%, 55% 100%, 45% 100%, 45% 55%, 0 55%, 0 45%, 45% 45%);
	transition: transform 0.2s ease;
}
.faq-item[open] summary::after {
	transform: rotate(45deg);
}
.faq-item .faq-a {
	padding: 0 1.2rem 1.15rem;
	font-size: 0.92rem;
	color: var(--muted);
}
.faq-item .faq-a > :first-child {
	margin-top: 0;
}

/* ── Projects strip ─────────────────────────────────────────────────────── */
/* `.service-gallery__container` on the original: #121212, radius 7.5px, ~97px vertical
   padding, overflow hidden, square 256px thumbs. The row auto-scrolls right-to-left and is
   masked at both edges so the images appear to pass behind the panel. */
.proj-strip {
	background: var(--panel);
	border-radius: var(--radius-l);
	padding: clamp(56px, 7.7vw, 97.45px) 0;
	overflow: hidden;
	text-align: center;
}
.proj-strip > .tj-eyebrow,
.proj-strip > h2 {
	padding-inline: var(--space-m);
}
.proj-marquee {
	margin: var(--content-gap) 0;
	overflow: hidden;
	-webkit-mask-image: linear-gradient(to right, transparent, #000 10%, #000 90%, transparent);
	mask-image: linear-gradient(to right, transparent, #000 10%, #000 90%, transparent);
}
.proj-track {
	display: flex;
	width: max-content;
	animation: tj-marquee 96s linear infinite;
}
/* Spacing lives on the items (not `gap`) so each duplicated half is exactly 50% of the
   track — otherwise the loop stutters by one gap on every cycle. Tiles are wrapped in an
   anchor in the first (interactive) half, so both selectors carry the sizing. */
.proj-track > a,
.proj-track > img {
	width: 256px;
	height: 256px;
	flex: 0 0 auto;
	margin-right: var(--space-m);
	border-radius: var(--radius-m);
	overflow: hidden;
}
.proj-track > a {
	display: block;
	cursor: zoom-in;
}
/* Scoped to the anchor: a bare `.proj-track img` also matched the duplicate half's DIRECT
   child images and overrode their 256px with 100%, so the two halves came out different
   widths and translateX(-50%) no longer lined up. */
.proj-track > a img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: var(--radius-m);
}
.proj-track > img {
	object-fit: cover;
}
.proj-marquee:hover .proj-track {
	animation-play-state: paused;
}
@keyframes tj-marquee {
	to {
		transform: translateX(-50%);
	}
}
@media (prefers-reduced-motion: reduce) {
	.proj-track {
		animation: none;
	}
	.proj-marquee {
		overflow-x: auto;
	}
}
.proj-actions {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	justify-content: center;
}

/* ── Reviews (CSS scroll-snap slider) ───────────────────────────────────── */
/* The reviews band sits on --neutral-ultra-dark with the same three-radial wash as the
   "Let's Talk" panel, so it reads LIGHTER than the page while the cards read darker. */
.section.reviews {
	background-color: var(--panel);
	background-image:
		radial-gradient(90% 70% at 20% 25%, color-mix(in srgb, var(--pink) 2%, transparent) 0%, transparent 45%),
		radial-gradient(80% 60% at 80% 30%, var(--ink) 0%, transparent 60%),
		radial-gradient(100% 80% at 50% 85%, var(--ink) 0%, transparent 65%);
}
.reviews-head {
	text-align: center;
	margin-bottom: var(--content-gap);
}
.reviews-head .lede {
	margin-inline: auto;
}
/* Slider: a real horizontal scroll container with snap points. A transformed track had no
   scrollable element, so there was nothing for a finger (or trackpad) to drag — hence "can't
   swipe on mobile". Native scrolling gives touch swipe, trackpad drag, keyboard arrows and
   momentum for free, all without JavaScript. */
.review-viewport {
	overflow-x: auto;
	overflow-y: hidden;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	overscroll-behavior-x: contain;
	scrollbar-width: none;
	-ms-overflow-style: none;
	/* Snapped slides sit flush; the dots below act as the visible affordance. */
	scroll-padding: 0;
}
.review-viewport::-webkit-scrollbar {
	display: none;
}
.review-track {
	display: flex;
	align-items: stretch;
}
.review {
	flex: 0 0 100%;
	min-width: 0;
	scroll-snap-align: center;
	scroll-snap-stop: always;
	background: var(--ink); /* darker than the section it sits on */
	border: 1px solid var(--line-soft);
	border-radius: var(--radius-l);
	padding: clamp(22px, 3vw, 34px);
	display: grid;
	gap: clamp(20px, 3vw, 34px);
	align-items: center;
}
@media (min-width: 860px) {
	.review {
		grid-template-columns: 1fr 0.7fr;
	}
}
/* Stacked on mobile: photo above the quote. The markup keeps the text first (it's the
   substance of the testimonial, and the right reading order for screen readers on the
   two-column desktop layout), so this is a visual reorder only. */
@media (max-width: 859px) {
	.review-photo {
		order: -1;
	}
}
/* The quote column carries its own padding on top of the card's, so the copy gets real
   breathing room without insetting the photo (which is a separate grid column and would
   otherwise shrink away from the card edge). */
.review-text {
	padding: var(--space-m);
}
.review-stars {
	display: flex;
	gap: 7px;
	margin-bottom: var(--space-m);
	color: var(--pink);
}
.review-stars svg {
	width: 27px;
	height: 27px;
}
.review h3 {
	font-size: clamp(1.25rem, 1.865vw, 1.474rem); /* 23.58px at 1265 */
	margin-bottom: 0.9rem;
}
/* Extra space between the quote and the attribution below it. */
.review blockquote {
	margin: 0 0 var(--space-l);
	color: rgba(217, 217, 217, 0.8); /* the original's quote colour */
	font-size: var(--text-m);
}
.review cite {
	font-style: normal;
	font-weight: 700;
	color: var(--text);
	font-size: var(--text-m);
}
/* Fixed 4:3 on every review photo. Previously `height: 100%` stretched the image to fill
   the card, so its shape changed with the length of each quote — long testimonials got a
   tall image, short ones a squat one. A declared aspect-ratio makes all four identical
   regardless of the copy. */
.review-photo img {
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	border-radius: var(--radius-m);
}
.review-dots {
	display: flex;
	gap: 10px;
	margin-top: var(--space-s);
}
/* Dots are anchors into the scroll container. Because the track is the nearest scrollable
   ancestor and each slide is fully visible within it vertically, following one scrolls the
   TRACK horizontally rather than the page. Padded hit area for touch. */
.review-dots a {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: var(--line);
	cursor: pointer;
	background-clip: content-box;
	box-sizing: content-box;
	padding: 6px;
	margin: -6px;
	transition: background-color 0.2s ease;
}
.review-dots a:hover {
	background-color: var(--muted);
}
/* Active dot — set by reviews.js from the container's scroll position, so it stays correct
   whether the slide changed by dot, swipe or keyboard. */
.review-dots a[aria-current='true'] {
	background-color: #fff;
}
.review-dots a:focus-visible {
	outline: 2px solid var(--pink);
	outline-offset: 2px;
}

/* ── Gallery (pure-CSS filtering via :has()) ────────────────────────────── */
/* "Sort images by" is a fixed column on the LEFT; the pills fill the rest and wrap among
   themselves rather than dropping below the label. */
.gal-filters {
	background: var(--panel);
	border: 1px solid var(--line-soft);
	border-radius: var(--radius-l);
	padding: var(--space-m);
	display: flex;
	flex-wrap: nowrap;
	align-items: center;
	gap: var(--space-m);
}
.gal-filters h2 {
	flex: 0 0 auto;
	font-size: var(--text-m);
	line-height: 1.2;
	letter-spacing: normal;
	max-width: 7em;
}
.gal-filters .opts {
	flex: 1 1 auto;
	display: flex;
	flex-wrap: wrap;
	gap: 10px var(--space-m);
}
@media (max-width: 620px) {
	.gal-filters {
		flex-wrap: wrap;
	}
	.gal-filters h2 {
		max-width: none;
	}
}
/* NOT a pill: the original uses --radius-m (5px), padding .5em 1em, at body size. */
.gal-filters label {
	display: inline-flex;
	align-items: center;
	padding: 0.5em 1em;
	border-radius: var(--radius-m);
	border: 1px solid transparent;
	font-size: var(--text-m);
	color: var(--muted);
	cursor: pointer;
	transition:
		background-color 0.18s ease,
		color 0.18s ease;
}
.gal-filters label:hover {
	color: #fff;
}
.gal input[type='radio'] {
	position: absolute;
	opacity: 0;
	pointer-events: none;
	width: 0;
	height: 0;
}
/* Three columns, square crops (the original renders 347×347 tiles), gap --space-m. */
.gal-grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--space-m);
	margin-top: var(--content-gap);
}
@media (max-width: 900px) {
	.gal-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}
@media (max-width: 560px) {
	.gal-grid {
		grid-template-columns: minmax(0, 1fr);
	}
}
.gal-grid figure {
	margin: 0;
	border-radius: var(--radius-m);
	overflow: hidden;
	background: var(--panel);
}
.gal-grid figure > a {
	display: block;
	cursor: zoom-in;
}
.gal-grid img {
	width: 100%;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	transition: transform 0.35s ease;
}
.gal-grid figure:hover img {
	transform: scale(1.04);
}

/* ── Lightbox (pure CSS, :target) ───────────────────────────────────────── */
/* Clicking a tile jumps to #lb-N, which reveals the matching overlay. Closing links back
   to #gallery-top. No JS, so the gallery page keeps csr = false like every other page. */
.lightbox {
	position: fixed;
	inset: 0;
	z-index: 1000;
	display: none;
	place-items: center;
	background: rgba(5, 5, 5, 0.92);
	padding: clamp(16px, 4vw, 48px);
}
.lightbox:target {
	display: grid;
}
.lightbox figure {
	margin: 0;
	max-width: min(1100px, 100%);
	max-height: 100%;
	display: flex;
	flex-direction: column;
	gap: var(--space-s);
}
.lightbox img {
	max-width: 100%;
	max-height: 78vh;
	width: auto;
	height: auto;
	margin-inline: auto;
	object-fit: contain;
	border-radius: var(--radius-m);
}
.lightbox figcaption {
	color: var(--text);
	text-align: center;
	font-size: var(--text-m);
	max-width: 68ch;
	margin-inline: auto;
}
/* Full-bleed backdrop link: clicking anywhere outside the figure closes it. */
.lightbox .lb-close-bg {
	position: absolute;
	inset: 0;
	z-index: -1;
}
/* Position counter, top-left, mirroring the close button opposite it. */
.lightbox .lb-count {
	position: absolute;
	top: clamp(12px, 2vw, 24px);
	left: clamp(12px, 2vw, 24px);
	display: grid;
	place-items: center;
	min-width: 44px;
	height: 44px;
	padding: 0 0.7em;
	border-radius: var(--radius-m);
	background: rgba(255, 255, 255, 0.08);
	color: #fff;
	font-size: var(--text-m);
	font-variant-numeric: tabular-nums;
	line-height: 1;
}
.lightbox .lb-close {
	position: absolute;
	top: clamp(12px, 2vw, 24px);
	right: clamp(12px, 2vw, 24px);
	width: 44px;
	height: 44px;
	display: grid;
	place-items: center;
	border-radius: var(--radius-m);
	background: rgba(255, 255, 255, 0.08);
	color: #fff;
	font-size: 1.5rem;
	line-height: 1;
}
.lightbox .lb-close:hover {
	background: var(--pink);
}
.lightbox .lb-prev,
.lightbox .lb-next {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	width: 48px;
	height: 48px;
	display: grid;
	place-items: center;
	color: #fff;
	font-size: 2rem;
	line-height: 1;
	border-radius: var(--radius-m);
}
.lightbox .lb-prev {
	left: clamp(4px, 1.5vw, 20px);
}
.lightbox .lb-next {
	right: clamp(4px, 1.5vw, 20px);
}
.lightbox .lb-prev:hover,
.lightbox .lb-next:hover {
	background: var(--pink);
}

/* Active pill: each radio is rendered immediately before its own label, so this is a plain
   adjacent-sibling rule — no :has() involved, and it can't get out of step with the radio. */
.gal-filters input:checked + label {
	background: var(--pink);
	color: #fff;
}
/* Keyboard: the radio itself is visually hidden, so surface focus on its label. */
.gal-filters input:focus-visible + label {
	outline: 2px solid var(--pink);
	outline-offset: 3px;
}

/* Grid filtering — one rule per category. `All` has no rule: nothing is hidden. */
.gal:has(#f-landscaping:checked) figure:not([data-cat='landscaping']),
.gal:has(#f-maintenance:checked) figure:not([data-cat='maintenance']),
.gal:has(#f-tree:checked) figure:not([data-cat='tree']) {
	display: none;
}

/* ── Cross-sell cards ───────────────────────────────────────────────────── */
.cross-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	gap: 20px;
	margin-top: 2.4rem;
}
.cross {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	border-radius: var(--radius-l);
	border: 1px solid var(--line-soft);
	min-height: 180px;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: clamp(20px, 2.6vw, 30px);
}
.cross img {
	position: absolute;
	inset: 0;
	z-index: -2;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.cross::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	background: linear-gradient(to right, rgba(5, 5, 5, 0.9), rgba(5, 5, 5, 0.45));
}
.cross h2 {
	font-size: clamp(1.3rem, 2.2vw, 1.7rem);
}
.cross .arrow {
	flex: 0 0 auto;
	width: 34px;
	height: 34px;
	display: grid;
	place-items: center;
	color: #fff;
}
.cross:hover .arrow {
	color: var(--pink);
}

/* ── Contact cards ──────────────────────────────────────────────────────── */
.contact-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	gap: var(--space-m);
	align-items: stretch; /* equal-height cards so the values can line up */
}
.contact-card {
	background: var(--panel);
	border: 1px solid var(--line-soft);
	border-radius: var(--radius-l);
	padding: var(--space-m);
	display: flex;
	flex-direction: column;
}
.contact-card h2 {
	display: flex;
	align-items: center;
	gap: 0.6em;
	font-size: 1.3rem;
	margin-bottom: 0.9rem;
}
.contact-card h2 svg {
	width: 22px;
	height: 22px;
	color: var(--pink);
}
/* Pushed to the bottom of the card so the email and phone line up across both, regardless
   of how many lines their descriptions run to. */
.contact-card .val {
	display: inline-block;
	align-self: flex-start;
	margin-top: auto;
	padding-top: var(--space-m);
	color: var(--pink);
	font-weight: 500;
}
.contact-card .val:hover {
	text-decoration: underline;
}

/* ── "Let's talk" band ──────────────────────────────────────────────────── */
/* `.footer-22__container-email` from the original, verbatim. The panel is NOT a pink fade:
   it's --neutral-ultra-dark with three radial gradients — one carrying just 2% primary,
   and two --neutral washes that darken the right and bottom. */
.talk {
	background-color: var(--panel);
	background-image:
		radial-gradient(90% 70% at 20% 25%, color-mix(in srgb, var(--pink) 2%, transparent) 0%, transparent 45%),
		radial-gradient(80% 60% at 80% 30%, var(--ink) 0%, transparent 60%),
		radial-gradient(100% 80% at 50% 85%, var(--ink) 0%, transparent 65%);
	border: 2px solid var(--line);
	border-radius: var(--radius-l);
	padding: var(--space-m);
}
/* `.footer-22__email-heading` */
.talk h2 {
	font-size: var(--text-l);
	font-weight: 500;
	color: var(--text);
	margin-bottom: var(--space-s);
}
/* `.footer-22__phone-link` */
.talk a {
	display: inline-flex;
	align-items: center;
	gap: var(--space-s);
	font-family: var(--display);
	font-size: var(--h2-size);
	font-weight: 700;
	color: var(--pink);
	line-height: 1;
}
.talk a:hover {
	color: var(--pink-hover);
}
/* The original sizes this icon independently of the number (`--text-xxl`), so it reads as a
   near-equal partner to the digits rather than a small superscript arrow. */
.talk svg {
	width: var(--text-xxl);
	height: var(--text-xxl);
	flex: 0 0 auto;
	transition: transform 0.25s var(--ease, cubic-bezier(0.22, 1, 0.36, 1));
}
/* The arrow points up-right at rest; on hover it swings down to horizontal (→). */
.talk a:hover svg,
.talk a:focus-visible svg {
	transform: rotate(45deg);
}

/* ── Footer ─────────────────────────────────────────────────────────────── */
.site-footer {
	padding-block: var(--space-l);
}
/* The original's `.footer-22__container` separates the phone panel from the link columns
   with `row-gap: var(--space-l)` — without it the panel sits right on top of them. */
.footer-main {
	display: grid;
	gap: clamp(28px, 4vw, 40px);
	align-items: start;
	margin-top: var(--space-l);
	padding-bottom: var(--space-m);
}
@media (min-width: 860px) {
	.footer-main {
		grid-template-columns: 1.4fr repeat(3, minmax(0, 0.8fr));
	}
}
/* Mobile: the three link groups sit side by side rather than stacking into one long
   column, with the logo spanning the full width above them. Drops to two columns on
   narrow phones, where three would wrap the longer labels. */
@media (max-width: 859px) {
	.footer-main {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: var(--space-m);
	}
	.footer-main > :first-child {
		grid-column: 1 / -1;
	}
}
@media (max-width: 520px) {
	.footer-main {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
	/* The Social group is last and would sit alone in a ~155px column — too narrow for the
	   two pills, so they wrapped anyway. Give it the full row so they fit side by side. */
	.footer-main > .footer-col:last-child {
		grid-column: 1 / -1;
	}
}
.footer-main img.f-logo {
	width: 215px; /* measured on the original: 215×105 */
	max-width: 100%;
	height: auto;
}
.footer-col h2 {
	font-size: 1rem;
	letter-spacing: 0.06em;
	margin-bottom: 1rem;
}
.footer-col ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 0.7rem;
}
.footer-col a {
	color: var(--muted);
	font-size: 0.88rem;
}
.footer-col a:hover {
	color: var(--pink);
}
.footer-social {
	display: grid;
	gap: 10px;
	justify-items: start;
}
.footer-social a {
	background: var(--pink);
	color: #fff;
	font-size: 0.82rem;
	font-weight: 500;
	padding: 0.5em 1.1em;
	border-radius: 5px;
}
.footer-social a:hover {
	background: var(--pink-hover);
	color: #fff;
}
/* Mobile: side by side rather than stacked — two short pills fit on one line and the
   stacked pair left an awkward gap. Must sit AFTER the base `.footer-social` rule above,
   or equal specificity means source order wins and the grid stays. */
@media (max-width: 859px) {
	.footer-social {
		display: flex;
		flex-wrap: wrap;
		gap: 10px;
	}
}
.footer-fine {
	border-top: 1px solid var(--line-soft);
	padding-top: 20px;
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: 10px 24px;
	font-size: 0.8rem;
	color: var(--muted-2);
}
.footer-fine p {
	margin: 0;
	color: inherit;
}
.footer-fine nav {
	display: flex;
	gap: 18px;
}
.footer-fine a:hover {
	color: var(--pink);
}
.footer-credit a {
	color: var(--muted-2);
}
.footer-credit a:hover {
	color: var(--pink);
}

/* ── Legal pages ────────────────────────────────────────────────────────── */
.legal {
	max-width: 72ch;
	margin-inline: auto;
}
.legal h2 {
	font-size: 1.4rem;
	margin: 2rem 0 0.8rem;
}
.legal h3 {
	font-size: 1.1rem;
	margin: 1.5rem 0 0.6rem;
}
.legal ul {
	color: var(--muted);
	padding-left: 1.2rem;
}
.legal a {
	color: var(--pink);
	text-decoration: underline;
}

/* ── Reduced motion ─────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

/* ── Enquiry form ───────────────────────────────────────────────────────────
   A single branched form: pick a service, answer only that service's questions.
   Branch reveal is pure CSS :has() (see BRANCHING below) so the page stays zero-JS
   and the answers are folded into the message server-side (request-fields.ts). */
/* Full content width, inside a bordered card (matches the original site's form + the contact
   cards above it). */
.tj-form {
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--radius-l);
	padding: var(--space-l);
	/* When the wizard scrolls a step (or the thank-you) into view, clear the fixed header with
	   comfortable breathing room rather than tucking the card under it. */
	scroll-margin-top: 130px;
}
/* Honeypot — off-screen, never shown, ignored by real people. */
.tj-hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}
.tj-step {
	border: 0;
	margin: 0 0 var(--space-l);
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--space-m);
}
.tj-step-title {
	font-size: var(--text-l);
	font-weight: 600;
	color: var(--text);
	margin: 0 0 var(--space-xs);
}
.tj-field {
	display: flex;
	flex-direction: column;
	gap: 0.45em;
}
.tj-row {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-m);
}
.tj-label {
	font-weight: 600;
	color: var(--text);
	font-size: var(--text-m);
}
.tj-req {
	color: var(--pink);
}
.tj-form input:not([type='radio']),
.tj-form textarea {
	width: 100%;
	/* Match the page background (not the card) so the fields read as inset. */
	background: var(--ink);
	border: 1px solid var(--line);
	border-radius: var(--radius-m);
	color: var(--text);
	padding: 0.75em 0.9em;
	font: inherit;
	font-size: var(--text-m);
}
.tj-form input::placeholder,
.tj-form textarea::placeholder {
	color: var(--muted-2);
}
.tj-form input:not([type='radio']):focus,
.tj-form textarea:focus {
	outline: none;
	border-color: var(--pink);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--pink) 25%, transparent);
}
.tj-form textarea {
	resize: vertical;
	min-height: 7em;
}
/* Phone field: intl-tel-input (real flag images + searchable dropdown), themed dark to match
   the form. Variables cover the button/borders; the dropdown + search need explicit overrides. */
.tj-form .iti {
	display: block;
	width: 100%;
	--iti-border-color: var(--line);
	--iti-country-selector-bg: rgba(255, 255, 255, 0.06);
	--iti-hover-color: rgba(255, 255, 255, 0.08);
	--iti-icon-color: var(--muted);
}
.tj-form .iti__selected-dial-code {
	color: var(--text);
}
.tj-form .iti__country-list {
	background: var(--panel);
	border: 1px solid var(--line);
	color: var(--text);
}
.tj-form .iti__country {
	color: var(--text);
}
.tj-form .iti__country.iti__highlight {
	background: rgba(255, 255, 255, 0.08);
}
.tj-form .iti__search-input {
	background: var(--ink);
	color: var(--text);
	border-bottom: 1px solid var(--line);
	/* Clear the magnifying-glass icon so the text isn't on top of it. !important because the
	   library sets the search input's padding at a higher priority than a plain class rule. */
	padding-left: 40px !important;
}
.tj-form .iti__search-input::placeholder {
	color: var(--muted-2);
}
/* Drag-and-drop upload zone (the native file input is visually hidden inside the label; a click
   or tap still opens the picker, so it works with JS off; enquiry.js adds drag + the filename). */
.tj-dropzone {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 0.5em;
	padding: 1.8em 1em;
	border: 2px dashed var(--line);
	border-radius: var(--radius-m);
	background: var(--ink);
	color: var(--muted);
	cursor: pointer;
	text-align: center;
	transition:
		border-color 0.15s ease,
		background-color 0.15s ease,
		color 0.15s ease;
}
.tj-dropzone:hover,
.tj-dropzone.is-drag {
	border-color: var(--pink);
	color: var(--text);
	background: color-mix(in srgb, var(--pink) 8%, var(--ink));
}
.tj-dropzone-icon {
	width: 1.9em;
	height: 1.9em;
	color: var(--pink);
}
.tj-dropzone-text {
	font-size: var(--text-m);
}
.tj-dropzone-file {
	font-size: var(--text-m);
	font-weight: 600;
	color: var(--text);
	word-break: break-all;
}
.tj-dropzone-input {
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	overflow: hidden;
}
/* Radio "chips" — the option groups (service, units, remove/prune, …). */
.tj-choice {
	display: flex;
	flex-wrap: wrap;
	gap: 0.6em;
}
.tj-opt input {
	position: absolute;
	opacity: 0;
	width: 0;
	height: 0;
}
.tj-opt span {
	display: inline-block;
	padding: 0.6em 1em;
	border: 2px solid var(--line);
	border-radius: var(--radius-s);
	color: var(--muted);
	cursor: pointer;
	transition:
		border-color 0.15s ease,
		background-color 0.15s ease,
		color 0.15s ease;
}
.tj-opt span:hover {
	border-color: var(--pink);
	color: var(--text);
}
.tj-opt input:checked + span {
	border-color: var(--pink);
	background: color-mix(in srgb, var(--pink) 14%, transparent);
	color: var(--text);
}
.tj-opt input:focus-visible + span {
	outline: 2px solid var(--pink);
	outline-offset: 2px;
}
.tj-field-error,
.tj-form-error {
	color: #ff6b9d;
	font-size: var(--text-s);
}
.tj-form-error {
	padding: 0.8em 1em;
	border: 1px solid #ff6b9d;
	border-radius: var(--radius-m);
	margin-bottom: var(--space-m);
}
.tj-turnstile {
	margin: var(--space-m) 0;
}
.tj-submit {
	margin-top: var(--space-s);
}
.tj-hint {
	color: var(--muted);
	font-size: var(--text-s);
	margin-top: var(--space-s);
}
.tj-form-done {
	background: var(--panel);
	border: 1px solid var(--line);
	border-radius: var(--radius-l);
	padding: var(--space-l);
}
.tj-form-done h2 {
	color: var(--pink);
	margin-bottom: var(--space-s);
}
.tj-form-done a {
	color: var(--pink);
}

@media (min-width: 640px) {
	.tj-row {
		grid-template-columns: 1fr 1fr;
	}
}

.tj-field-error[hidden],
[hidden] {
	display: none !important;
}

/* ── STEP-LEVEL branching for the NO-JS fallback ─────────────────────────────
   With JS off the form is one stacked page; once a service is chosen, the OTHER
   services' steps are hidden with :has(). Scoped to :not(.tj-wizard) so it never
   fights the JS wizard, which controls step visibility itself. Where :has() is
   unsupported the "hide" rule never applies — a longer form, but a working one. */
.tj-form:not(.tj-wizard):has(input[name='service']:checked) [data-step-service] {
	display: none;
}
.tj-form:not(.tj-wizard):has(#svc-land:checked) [data-step-service~='land'],
.tj-form:not(.tj-wizard):has(#svc-maint:checked) [data-step-service~='maint'],
.tj-form:not(.tj-wizard):has(#svc-tree:checked) [data-step-service~='tree'] {
	display: flex;
}

/* ── WITHIN-STEP branching (Tree Work's tree/hedge split) — active in BOTH modes ──
   These sub-fields live inside the tree step; the wizard shows that step, and this
   :has() reveals the right sub-set. Hidden by default, shown by the chosen radio. */
.tj-form:has(input[name='tree_type']:checked) .tj-branch {
	display: none;
}
.tj-form:has(#tw-tree:checked) .tj-branch[data-when~='tree-work'],
.tj-form:has(#tw-hedge:checked) .tj-branch[data-when~='hedge'] {
	display: flex;
}
.tj-form:has(#tw-hedge:checked) .tj-row.tj-branch[data-when~='hedge'] {
	display: grid;
}
/* Sub-conditions, scoped tighter than the reveal so they win: the pro-drawing question
   only when they lack a professional drawing; the desired height only for a reduction. */
.tj-form:has(#draw-pro:checked) [data-cond='prodraw'] {
	display: none;
}
/* Hide the upload when they've said they have no drawing. Default visible (fallback-safe) — the
   field is optional — so it only disappears once "No" is explicitly chosen. */
.tj-form:has(#draw-no:checked) [data-cond='hasdrawing'] {
	display: none;
}
.tj-form:has(#tw-hedge:checked):has(#hedge-cut:checked) .tj-branch[data-cond='reduction'] {
	display: none;
}

/* ── WIZARD (JS on) ──────────────────────────────────────────────────────────
   The script adds .tj-wizard, hides all but the current [data-step] via the hidden
   attribute, and injects the progress bar + Back/Next/Submit nav. */
.tj-wizard .tj-step {
	margin-bottom: 0;
}
.tj-wz-progress {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-m);
	margin-bottom: var(--space-l);
}
.tj-wz-counter {
	color: var(--muted);
	font-size: var(--text-s);
	font-weight: 600;
	white-space: nowrap;
}
.tj-wz-dots {
	display: flex;
	gap: 0.55em;
}
.tj-wz-dot {
	width: 0.7em;
	height: 0.7em;
	border-radius: 50%;
	background: var(--line);
	transition: background-color 0.15s ease, transform 0.15s ease;
}
.tj-wz-dot.is-done {
	background: var(--pink-dark);
}
.tj-wz-dot.is-current {
	background: var(--pink);
	transform: scale(1.35);
}
.tj-wz-nav {
	display: flex;
	/* Next/Submit always pinned right; Back (when shown) is pushed left by its auto margin, so
	   the primary button never jumps from left to right between steps. */
	justify-content: flex-end;
	align-items: center;
	gap: var(--space-m);
	margin-top: var(--space-l);
}
.tj-wz-nav .tj-wz-back {
	margin-right: auto;
}
.tj-wz-nav .tj-submit,
.tj-wz-nav .tj-wz-next {
	margin: 0;
}
/* A field flagged invalid by the per-step check. */
.tj-invalid input:not([type='radio']):not([type='checkbox']),
.tj-invalid textarea {
	border-color: #ff6b9d;
}
.tj-invalid .tj-label {
	color: #ff6b9d;
}
