/* Self-hosted fonts (pulled from the Figma design: Roboto body copy, Montserrat ExtraBold for nav/buttons/labels) */
@font-face {
	font-family: 'Roboto';
	font-style: normal;
	font-weight: 300;
	font-display: swap;
	src: url('../fonts/roboto-300.woff2') format('woff2');
}

/* Without this, italic text at the new 300 body weight (e.g. .work__tagline
   em, .blog-prose blockquote) had no matching italic face to use — the only
   one loaded was 500-italic, so the browser rendered it with that instead,
   making italics look noticeably BOLDER than the regular 300-weight text
   around them rather than lighter. */
@font-face {
	font-family: 'Roboto';
	font-style: italic;
	font-weight: 300;
	font-display: swap;
	src: url('../fonts/roboto-300-italic.woff2') format('woff2');
}

@font-face {
	font-family: 'Roboto';
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url('../fonts/roboto-400.woff2') format('woff2');
}

@font-face {
	font-family: 'Roboto';
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url('../fonts/roboto-500.woff2') format('woff2');
}

@font-face {
	font-family: 'Roboto';
	font-style: italic;
	font-weight: 500;
	font-display: swap;
	src: url('../fonts/roboto-500-italic.woff2') format('woff2');
}

@font-face {
	font-family: 'Roboto';
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url('../fonts/roboto-600.woff2') format('woff2');
}

@font-face {
	font-family: 'Montserrat';
	font-style: normal;
	font-weight: 800;
	font-display: swap;
	src: url('../fonts/montserrat-800.woff2') format('woff2');
}

:root {
	--color-ink: #1b1b1b;
	--color-white: #ffffff;
	--color-offwhite: #fafafa;
	--color-accent: #ff9500;
	--color-teal-dark: #025c65;
	--color-teal-light: #a1d7dd;
	--color-orange-red: #e94e1b;

	--font-body: 'Roboto', sans-serif;
	--font-display: 'Montserrat', sans-serif;

	--container-width: 1440px;
	--gutter: 50px;
}

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

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

body {
	margin: 0;
	font-family: var(--font-body);
	/* One weight lighter than Roboto's Regular (400) — the base for all
	   running/prose copy site-wide, unless a specific element overrides it
	   for its own reason (headings, nav, buttons, badges are unaffected,
	   set on their own selectors). */
	font-weight: 300;
	color: var(--color-ink);
	background: var(--color-white);
	line-height: 1.5;
}

/* --- Global heading baseline (mobile) ---
   Every h1/h2/h3 on the site is sized by its own component class, each
   tuned individually per Figma — there's never been one shared default.
   This is that default for mobile specifically: h1 3.1rem, h2 2.3rem, h3
   1.7rem, as a fallback for any heading that isn't ALSO given a more
   specific override below (a bare element selector like this loses to any
   class selector automatically, regardless of source order, so it only
   ever reaches a heading nothing else already sizes). Component classes
   below are being brought in line with these same values where that suits
   their design; a handful of intentionally large (hero) or intentionally
   small (eyebrow/label) headings are deliberately left alone — see each
   file's own comments. */
@media (max-width: 900px) {
	h1 {
		font-size: clamp(2.25rem, 13vw, 3.1rem);
	}

	h2 {
		font-size: clamp(1.75rem, 10vw, 2.3rem);
	}

	h3 {
		font-size: clamp(1.375rem, 7.5vw, 1.7rem);
	}
}

/* Toggled by site.js while .primary-menu (the full-screen mega menu) is
   open, so the actual page underneath can't scroll behind it. */
body.has-menu-open {
	overflow: hidden;
}

/* Contact page is full-bleed dark video, no footer — without this, rubber-
   band overscroll (trackpad/iOS bounce) exposes the default white body
   background above/below it instead of a seamless dark edge. */
body.is-contact-page {
	background: var(--color-ink);
}

img,
svg,
video {
	max-width: 100%;
	display: block;
}

a {
	color: inherit;
	text-decoration: none;
}

button {
	font: inherit;
	color: inherit;
	background: none;
	border: 0;
	cursor: pointer;
}

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

.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(1px, 1px, 1px, 1px);
	white-space: nowrap;
}

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

/* --- Arrow-in-circle icon: "fills in" on hover of the icon itself ---
   Every reuse of arrow-icon.svg/arrow-icon-white.svg (Recent Projects,
   Closing Statement, Our Insights hero/cards, the video row, the blog
   "back" link) adds this shared class rather than each hand-rolling its
   own hover treatment. An <img> loaded via src can't be recoloured or
   given a background-fill with CSS filters alone, so this crossfades to a
   second, solid-filled SVG (arrow-icon-filled-dark/white.svg) on top
   instead — the outline icon stays as the base <img>, unchanged.
   Where an instance already rotates its <img> to point a different
   direction, add the same rotation to this ::after too (see each
   instance's own CSS) so the filled version points the same way. */
.arrow-circle {
	position: relative;
}

.arrow-circle::after {
	content: '';
	position: absolute;
	inset: 0;
	background-repeat: no-repeat;
	background-position: center;
	background-size: contain;
	opacity: 0;
	transition: opacity 0.25s ease;
}

.arrow-circle:hover::after,
.arrow-circle:focus-visible::after {
	opacity: 1;
}

.arrow-circle--dark::after {
	background-image: url('../images/recent-projects/arrow-icon-filled-dark.svg');
}

.arrow-circle--light::after {
	background-image: url('../images/recent-projects/arrow-icon-filled-white.svg');
}

/* The base (non-hover) outline icon is a single hardcoded-dark-stroke SVG
   (arrow-icon.svg, passed in as the component's own <img>) — every
   existing use of --light sits on a dark footer/photo where a dark
   outline would be invisible, so it needs inverting to white. There's no
   separate white-outline asset; a filter is simpler than exporting one
   just to swap a single stroke colour. */
.arrow-circle--light img {
	filter: invert(1);
}

@media (max-width: 782px) {
	:root {
		--gutter: 24px;
	}
}

/* --- Site header / nav (used on every page) --- */

.site-header {
	/* Just a semantic wrapper — the fixed positioning + blend mode live on
	   .site-header__inner instead (see below for why). */
	position: relative;
}

/* position:fixed always creates its own stacking context, and mix-blend-mode
   on a DESCENDANT of that context can only blend against content painted
   inside it — it can't reach through to the real page/video behind. It has
   to sit on the fixed element itself, so the whole bar blends as one unit
   against its actual parent's backdrop (the real page). That's also why
   .primary-menu (the dropdown) is pulled out to its own independent fixed
   position below — it deliberately stays solid/unblended, not swept into
   this bar's blending group.

   .site-header__inner is the full-width fixed bar (background + blend live
   here); .site-header__row is the .container-constrained content row
   inside it — keeping them separate means the white background actually
   spans the full viewport instead of being capped at the 1340px container. */
.site-header__inner {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 100;
	/* Starts roomier at the top of the page, then settles down to the
	   tighter --scrolled padding-top (see below) after a small scroll
	   distance (site.js), and back again on the way up. Only padding-top
	   animates — no transition on background-color/mix-blend-mode, since
	   blend-mode can't animate and a fading background while it snaps
	   instantly is exactly what caused a flash before. */
	padding-top: 56px;
	padding-bottom: 10px;
	transition: padding-top 0.35s ease;
	mix-blend-mode: exclusion;
}

.site-header__inner--scrolled {
	padding-top: 31.6px;
}

@media (max-width: 900px) {
	/* The roomy-start-then-settle padding animation isn't needed on a small
	   screen (there's no big hero-peek gap for it to be easing into), and
	   resizing a position:fixed + mix-blend-mode:exclusion bar mid-scroll is
	   a known trigger for compositing glitches on mobile browsers — visible
	   here as a stray rectangle flashing over the bar during the transition.
	   Locking both states to the same value removes the resize entirely
	   (and with it, the glitch) rather than just hiding the symptom. */
	.site-header__inner,
	.site-header__inner--scrolled {
		padding-top: 31.6px;
	}
}

/* Toggled by JS (see site.js) depending on which section is currently
   behind the header — transparent + exclusion blend looks great over the
   video/photo sections, but scrolled over a plain white section it makes
   the logo/icon fight visually with body text underneath. This swaps to a
   solid white bar with normal (non-inverted) dark icons instead. */
.site-header__inner--solid {
	padding-bottom: 16px;
	background: var(--color-white);
	mix-blend-mode: normal;
}

/* Press & Awards single (see invanity_body_classes()) — its featured
   image bleeds behind the nav on the right, so the solid white bar there
   would otherwise cover it. Same white/normal-blend fill, just hard-cut at
   the same fraction as .press-single__grid's own left column (790/1440)
   instead of spanning full width — the row of content inside (logo, menu
   toggle) is untouched, only this background is narrowed. */
body.has-split-header .site-header__inner--solid {
	background: linear-gradient(
		to right,
		var(--color-white) 0,
		var(--color-white) calc(790 / 1440 * 100%),
		transparent calc(790 / 1440 * 100%),
		transparent 100%
	);
}

.site-header__inner--solid .site-header__logo img,
.site-header__inner--solid .site-header__menu-icon-bars {
	filter: invert(1);
}

.site-header__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
}

.site-header__logo img {
	height: 18px;
	width: auto;
}

.site-header__menu-toggle {
	display: flex;
	align-items: center;
	padding: 8px;
}

/* Just the hamburger for now, no open/close icon swap — revisit once a
   close-icon design is settled. */
.site-header__menu-icon {
	position: relative;
	display: block;
	width: 28px;
	height: 18px;
}

/* A colour-based hover state here (e.g. the site's orange) would need to
   escape .site-header__inner's own mix-blend-mode:exclusion (further up) —
   isolation:isolate does NOT do that (it only scopes blending among an
   element's OWN descendants, not whether an ancestor sweeps it into ITS
   grouping), and no source colour reliably excludes-to-orange against a
   constantly changing video backdrop anyway. Scale is a hover cue that
   sidesteps the problem entirely, since it doesn't touch colour at all. */
.site-header__menu-icon-bars {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	transition: transform 0.25s ease;
}

.site-header__menu-toggle:hover .site-header__menu-icon-bars {
	transform: scale(1.15);
}

/* --- Full-screen mega menu (Figma node 3275:13058) ---
   Slides in from the right while .site-header (z-index:100) stays fixed
   and visible on top of it (this sits at 99). visibility only flips to
   hidden once the slide-out transition finishes (the 0.5s delay, only on
   the closed/base state — opening has no delay), so it drops out of the
   tab order and stops being hit-testable exactly when it's actually gone,
   not the instant the animation starts. */
.primary-menu {
	position: fixed;
	inset: 0;
	z-index: 99;
	visibility: hidden;
	overflow-y: auto;
	transform: translateX(100%);
	transition: transform 0.5s ease, visibility 0s linear 0.5s;
}

.primary-menu.is-open {
	visibility: visible;
	transform: translateX(0);
	transition: transform 0.5s ease, visibility 0s linear 0s;
}

/* Frosted backdrop lives on its own layer, not .primary-menu itself — the
   same reasoning as .insights-badge::before further up: mix-blend-mode
   applied directly to an element with real content composites that
   content into the blend too, rather than just blending the backdrop
   against the real page behind it. A real element (header.php), not a
   ::before — a ::before here didn't respect an overridden `top` the same
   way a real element does (confirmed live: an identical real div, added
   temporarily in the same spot, positioned correctly; the pseudo-element
   version of the exact same rule did not — a genuine engine quirk specific
   to a position:fixed ::before of a transformed ancestor, not a cascade or
   specificity issue), which is exactly what the @media (hover: none)
   override further down needs to do. */
.primary-menu__backdrop {
	position: fixed;
	inset: 0;
	z-index: -1;
	background: var(--color-white);
	-webkit-backdrop-filter: blur(42.25px);
	backdrop-filter: blur(42.25px);
	mix-blend-mode: hard-light;
}

/* Column, not the categories/reveal row directly — .primary-menu__footer
   (social/divider/address/CTA) needs to anchor to the bottom of the panel
   below that row, not just follow directly after its own content height. */
.primary-menu__inner {
	display: flex;
	flex-direction: column;
	min-height: 100%;
	padding-top: clamp(100px, 12vw, 160px);
	padding-bottom: clamp(24px, 6vh, 80px);
}

/* On a non-hover device the Contact us bar becomes fixed to the bottom of
   the screen (see the @media (hover: none) block further down) rather than
   following on from .primary-menu__spacer here, so this reserves that same
   real, measured height as extra bottom padding instead -- real, empty
   buffer at the very end of the scrollable content, so on a very short
   screen the last link/category can never be scrolled up underneath that
   fixed bar; there's simply nothing left to scroll into that space. Uses
   max() rather than replacing the padding above outright, so the existing
   breathing room still applies on a hover-capable device (where
   --site-menu-footer-height is never set). */
@media (hover: none) {
	.primary-menu__inner {
		padding-bottom: max(clamp(24px, 6vh, 80px), var(--site-menu-footer-height, 96px));
	}
}

/* A real flex-grow spacer, not a vh guess — grows to fill whatever's left
   over between the top content and .primary-menu__footer, capped at 13rem
   (Figma's own gap from the bottom menu item to the divider), and shrinks
   toward 0 FIRST, ahead of anything else, if the screen is too short to
   fit that much. Lets the browser's own layout resolve the exact amount
   for any content height instead of a vh clamp that has to guess at it —
   which is what left the footer hanging off a short laptop screen even
   after accounting for viewport height, since 20vh was still too generous
   there. */
.primary-menu__spacer {
	flex: 1 1 0;
	max-height: 13rem;
	min-height: 0;
}

.primary-menu__top {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: clamp(24px, 5vw, 80px);
	width: 100%;
	/* Only .primary-menu__spacer shrinks under pressure — this keeps its
	   natural size and lets the panel scroll (already set up to) rather
	   than squishing the category labels if even a 0 spacer isn't enough. */
	flex-shrink: 0;
}

.primary-menu__categories {
	display: flex;
	flex-direction: column;
	gap: clamp(8px, 1.5vw, 20px);
	margin: 0;
	padding: 0;
	list-style: none;
}

.primary-menu__category-trigger {
	display: block;
	padding: 0;
	border: none;
	background: none;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: clamp(2.25rem, 6vw, 4.875rem);
	line-height: 1.1;
	color: var(--color-ink);
	text-align: left;
	cursor: pointer;
	transition: opacity 0.3s ease;
}

/* Non-active categories sit dimmed (opacity, not a colour swap — same
   "de-emphasise, don't hide" idea as the arrow-circle hover components)
   once one has an active category — see .primary-menu__reveal below for
   the panel each one reveals. Gated on the menu actually HAVING an active
   category (rather than a flat, unconditional opacity:0.35 default) since
   site.js only ever sets one on load for hover-capable devices — on
   touch, nothing is active until the first tap, and everything should
   read at full strength until then, not pre-dimmed with nothing yet
   "chosen" to contrast against. */
.primary-menu[data-active-menu] .primary-menu__category-trigger {
	opacity: 0.35;
}

.primary-menu[data-active-menu] .primary-menu__category-trigger[aria-expanded="true"] {
	opacity: 1;
}

/* Same sticky-window/track-shift mechanism as .making-it-happen__reveal —
   a fixed-height window with a taller inner track that shifts by exactly
   --row-height per active category, so switching category "scrolls" the
   right panel into view rather than just swapping its contents outright. */
.primary-menu__reveal {
	/* Shared uniform height for every category's panel — the track shifts
	   by exactly this much per category (see translateY rules below), so
	   it has to fit the tallest one. "About us" is now 4 links (added
	   Contact), which is the tallest panel. */
	--row-height: 15rem;
	flex: 1 1 320px;
	height: var(--row-height);
	overflow: hidden;
	margin-top: clamp(8px, 2vw, 28px);
	/* Right-aligns the links so their right edge lines up with the menu
	   toggle button above (both sit against .container's own right padding
	   edge — this panel fills the rest of that same row). */
	text-align: right;
}

.primary-menu__reveal-track {
	transform: translateY(0);
	transition: transform 0.4s ease;
}

.primary-menu[data-active-menu="what-we-do"] .primary-menu__reveal-track {
	transform: translateY(calc(var(--row-height) * -1));
}

.primary-menu[data-active-menu="making-it-happen"] .primary-menu__reveal-track {
	transform: translateY(calc(var(--row-height) * -2));
}

.primary-menu__links {
	height: var(--row-height);
	margin: 0;
	padding: 0;
	list-style: none;
}

.primary-menu__links li + li {
	margin-top: 12px;
}

.primary-menu__links a {
	display: inline-block;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: 1.875rem;
	color: var(--color-ink);
	text-decoration: none;
	transition: color 0.25s ease, transform 0.25s ease;
}

.primary-menu__links a:hover,
.primary-menu__links a:focus-visible {
	color: var(--color-accent);
	transform: translateX(-8px);
}

.primary-menu__links-empty {
	font-family: var(--font-body);
	font-size: 1.875rem;
	color: var(--color-ink);
	opacity: 0.5;
}

/* Non-hover devices — the desktop reveal is a mouse-era pattern in two
   ways at once: it needs a hover target to switch panels at all, AND it
   pre-selects "About us" on load so a mouse user always sees SOMETHING
   without having to click first. Neither holds up on touch: there's no
   hover to switch on, and the whole point here (see site.js's own
   comment on this) is that NOTHING is expanded until a category is
   actually tapped. Forced regardless of viewport width, same as every
   other hover-gated section on this site — a large non-hover touchscreen
   gets this exact treatment too, not just narrow phones.

   Each category's own link panel is flattened out from its shared,
   right-aligned, vertically-sliding "filmstrip" track (.primary-menu__
   reveal/-track, both display:contents here) so it can sit as an ordinary
   flex item directly under its OWN trigger instead — same
   display:contents + order interleaving technique used for the how-we-
   think section (service.css) and the homepage's specialties CTA, just
   pairing UL SIBLINGS from one former parent with LI SIBLINGS from
   another rather than reordering one flat list. nth-of-type still
   resolves against each element's real DOM parent (.primary-menu__
   categories for the LIs, .primary-menu__reveal-track for the ULs)
   regardless of the contents-flattening elsewhere, which is what makes
   this safe. */
@media (hover: none) {
	/* Desktop's own active-category rule (base.css, further up) just goes
	   to full opacity — the accent colour here is scoped to touch only,
	   marking which category the user has actually tapped open, rather
	   than changing the shared hover-driven panel's own look on desktop. */
	.primary-menu[data-active-menu] .primary-menu__category-trigger[aria-expanded="true"] {
		color: var(--color-accent);
	}

	/* .primary-menu__top's own gap (clamp(24px, 5vw, 80px), further up) is
	   sized for the desktop side-by-side layout — the space between the
	   category list and its shared reveal panel, not for rows stacked
	   directly on top of each other. Once display:contents (below)
	   flattens categories and reveal panels into this same flex column,
	   that gap becomes the vertical rhythm between every collapsed
	   category too, which reads far too loose for adjacent nav rows. */
	.primary-menu__top {
		flex-direction: column;
		gap: 0.75rem;
		text-align: left;
	}

	.primary-menu__categories,
	.primary-menu__reveal,
	.primary-menu__reveal-track {
		display: contents;
	}

	/* .primary-menu__reveal's own text-align:right (desktop's own rule,
	   right-aligning links against the toggle button above) is INHERITED
	   by its actual DOM children regardless of display:contents removing
	   its box — display:contents drops the box, not the cascade, so that
	   right-align was still reaching every link's text here even after
	   .primary-menu__top's own text-align:left above (an ancestor further
	   up, but .primary-menu__reveal's own explicit value wins over
	   whatever it would otherwise have inherited). Confirmed live: shorter
	   link labels were visibly indented relative to longer ones, which is
	   exactly what right-aligned text of varying width inside a full-width
	   box looks like. Overriding it directly on the actual text-holding
	   element, not just an ancestor, is what actually reaches it. */
	.primary-menu__links {
		text-align: left;
	}

	.primary-menu__category:nth-of-type(1) { order: 1; }
	.primary-menu__links:nth-of-type(1) { order: 2; }
	.primary-menu__category:nth-of-type(2) { order: 3; }
	.primary-menu__links:nth-of-type(2) { order: 4; }
	.primary-menu__category:nth-of-type(3) { order: 5; }
	.primary-menu__links:nth-of-type(3) { order: 6; }

	/* This (not .primary-menu itself) is what needs to clear the header and
	   own the scroll on a non-hover device — moved here after discovering a
	   real engine quirk: .primary-menu is ALSO .primary-menu__backdrop's
	   containing block (via its own transform), and once an element is
	   both a transform-based containing block AND has overflow-y:auto,
	   a position:fixed descendant gets clipped to ITS scrollport at PAINT
	   time regardless of its own top/inset values — confirmed live,
	   .primary-menu__backdrop's getBoundingClientRect() reported full
	   viewport coverage (top:0) while visibly still cut off exactly at the
	   header's height, both with a shifted .primary-menu top AND with a
	   plain real element (not just the original ::before) in the same
	   spot. Keeping .primary-menu itself unshifted and un-clipping (no
	   overflow, inset:0 always) is what lets .primary-menu__backdrop (its
	   other direct child, sibling to this) cover the full screen behind
	   the header, unaffected by whatever this element's own scroll is
	   doing. margin-top + a matching height reduction confines THIS
	   element's own box (and therefore its own overflow-y:auto scrollport)
	   to start below the header, so it structurally can't scroll content
	   up underneath it, the same protection the old approach had — just
	   moved one level deeper, where it no longer drags the backdrop along
	   with it. min-height:0 overrides the unconditional min-height:100%
	   above, which would otherwise force this back to full height despite
	   the explicit height here. */
	.primary-menu__inner {
		margin-top: var(--site-header-height, 60px);
		min-height: 0;
		height: calc(100% - var(--site-header-height, 60px));
		overflow-y: auto;
		/* margin-top above already accounts for the header — this still
		   needs reducing by the same amount so the two combined (margin
		   above the border box, padding inside it — both add to the visual
		   gap before "About us") land on the same total, unshifted clamp()
		   value as everywhere else, not that value PLUS the header height
		   on top of it. */
		padding-top: calc(clamp(100px, 12vw, 160px) - var(--site-header-height, 60px));
	}

	/* Collapsed by default: no height, and hidden from view/focus/tab
	   order — not just clipped, since an invisible-but-still-focusable
	   link inside a max-height:0 box is still reachable by keyboard.
	   max-height itself is set to the exact measured content height by
	   site.js when a category opens (not a fixed guessed cap) — a
	   generous fixed guess only animates smoothly once its value drops
	   below the real content height, so most of the "opening" distance
	   would just sit there doing nothing before a late, sudden jump to
	   the real size (confirmed as a real, visible bug on the case-study
	   cards' own accordion — see service.css's fuller writeup). The
	   translateX+opacity slide-in runs at the same time, independent of
	   that height reveal. */
	.primary-menu__links {
		height: auto;
		max-height: 0;
		overflow: hidden;
		opacity: 0;
		visibility: hidden;
		transform: translateX(-24px);
		transition: max-height 0.4s ease, opacity 0.35s ease, transform 0.35s ease, visibility 0s linear 0.4s;
	}

	/* Tighter than the desktop 12px (further up) — the links here sit in
	   their own stacked block below the parent category rather than
	   spanning a wide shared row, so they read fine closer together. */
	.primary-menu__links li + li {
		margin-top: 6px;
	}

	.primary-menu[data-active-menu="about-us"] .primary-menu__links:nth-of-type(1),
	.primary-menu[data-active-menu="what-we-do"] .primary-menu__links:nth-of-type(2),
	.primary-menu[data-active-menu="making-it-happen"] .primary-menu__links:nth-of-type(3) {
		/* On top of .primary-menu__top's own gap (further up) — bigger than
		   the tightened gap between links above so the whole block reads as
		   grouped under its own category, with clearly more space before
		   the next category than within itself. */
		margin-top: 0.5rem;
		margin-bottom: 0.5rem;
		opacity: 1;
		visibility: visible;
		transform: translateX(0);
		transition: max-height 0.4s ease, opacity 0.35s ease, transform 0.35s ease 0.05s, visibility 0s linear 0s;
	}

	.primary-menu__links a:hover,
	.primary-menu__links a:focus-visible {
		transform: none;
	}

	@media (prefers-reduced-motion: reduce) {
		.primary-menu__links {
			transition: max-height 0.4s ease, visibility 0s linear 0.4s;
		}

		.primary-menu[data-active-menu="about-us"] .primary-menu__links:nth-of-type(1),
		.primary-menu[data-active-menu="what-we-do"] .primary-menu__links:nth-of-type(2),
		.primary-menu[data-active-menu="making-it-happen"] .primary-menu__links:nth-of-type(3) {
			transition: max-height 0.4s ease, visibility 0s linear 0s;
		}
	}
}

/* --- Menu footer: socials, divider, address + "Let's talk" — pinned to
   the bottom of the panel by .primary-menu__inner's own space-between,
   not by anything here. Same icons/links/address copy as the site
   footer (site-footer__* in this same file) — reused, not duplicated. */

.primary-menu__footer {
	width: 100%;
	flex-shrink: 0;
}

.primary-menu__socials {
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: 27px;
	margin-bottom: clamp(12px, 3vh, 24px);
}

.primary-menu__socials img {
	display: block;
	height: 1.7rem;
	width: auto;
}

.primary-menu__divider {
	margin: 0 0 clamp(12px, 3vh, 24px);
	border: none;
	border-top: 1px solid var(--color-ink);
}

.primary-menu__footer-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 24px;
}

.primary-menu__address {
	margin: 0;
	max-width: 49.4375rem;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: 1.125rem;
	line-height: 1.39;
	color: var(--color-ink);
}

/* Links to Google Maps (header.php/footer.php) — plain text otherwise,
   same treatment as the tel/mailto links right next to it in the footer
   (.site-footer__contact a below), not a default blue underlined link. */
.primary-menu__address a,
.site-footer__address a {
	color: inherit;
	text-decoration: none;
}

.primary-menu__cta {
	flex-shrink: 0;
}

/* Socials + address dropped entirely on a non-hover device, leaving just
   the Contact us button — frees up real vertical space for the category
   list/panels above, the same screen-height pressure that .primary-menu's
   own scroll (further up) exists to handle. Both are already reachable
   from the main site footer, so nothing is actually lost, just not
   duplicated here too. Placed after the unconditional rules above (not
   grouped with the earlier accordion hover:none block) — a media query
   adds no specificity of its own, so an earlier hover:none override here
   would otherwise still lose to .primary-menu__socials's own unconditional
   display:flex further up the file, which comes later in source order. */
@media (hover: none) {
	.primary-menu__socials,
	.primary-menu__divider,
	.primary-menu__address {
		display: none;
	}

	/* Pinned to the actual bottom of the screen instead of following on
	   from .primary-menu__spacer in normal flow. "position: fixed" here
	   resolves against .primary-menu's OWN box, not the true browser
	   viewport -- .primary-menu has a CSS transform (its slide-in), and a
	   transformed ancestor becomes the containing block for any fixed
	   descendant. .primary-menu's own bottom edge is still the real screen
	   bottom (only its top is shifted, for the header clearance above), so
	   this lands in the right place regardless. min-height is a floor, not
	   a fixed size -- keeps a comfortable tap target if the button's own
	   content were ever shorter than expected, without forcing extra
	   height when it isn't. */
	.primary-menu__footer {
		position: fixed;
		left: 0;
		right: 0;
		bottom: 0;
		min-height: 4.75rem;
		display: flex;
		align-items: center;
		padding: 1.5rem var(--gutter) calc(1.5rem + env(safe-area-inset-bottom, 0px));
	}
}

@media (max-width: 700px) {
	.primary-menu__top {
		flex-direction: column;
	}

	.primary-menu__footer-row {
		flex-direction: column;
		align-items: flex-start;
		gap: 24px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.primary-menu,
	.primary-menu.is-open,
	.primary-menu__reveal-track,
	.site-header__menu-icon-bars {
		transition: none;
	}
}

/* --- Global pill button (used site-wide, not just on the homepage) ---
   Label is duplicated inside a clipped track; hovering slides the track up
   by half its own height, so the visible copy exits the top edge while the
   duplicate slides in from the bottom to replace it. */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 14rem;
	padding: 17px 33px;
	border: 0.7px solid currentColor;
	border-radius: 40px;
	color: var(--color-white);
	background: transparent;
	transition: background-color 0.3s ease;
}

.btn:hover,
.btn:focus-visible {
	background: var(--color-ink);
}

/* For a light/white page background — base .btn's white-on-transparent
   default only reads on a dark section. Hover still fills with ink, so
   the text needs to flip to white there or it disappears into its own
   background. */
.btn--dark {
	color: var(--color-ink);
}

.btn--dark:hover,
.btn--dark:focus-visible {
	background: var(--color-ink);
	color: var(--color-white);
}

.btn__roll {
	display: block;
	height: 22px;
	overflow: hidden;
}

.btn__roll-track {
	display: flex;
	flex-direction: column;
	transition: transform 0.4s ease;
}

.btn:hover .btn__roll-track,
.btn:focus-visible .btn__roll-track {
	transform: translateY(-50%);
}

.btn__roll-text {
	display: block;
	font-family: var(--font-display);
	font-weight: 800;
	font-size: 18px;
	line-height: 22px;
	text-align: center;
	white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
	.btn__roll-track {
		transition: none;
	}
}

/* --- Site footer --- */
.site-footer {
	background: var(--color-ink);
	color: var(--color-white);
}

.site-footer__inner {
	padding-block: clamp(32px, 4vw, 50px) clamp(20px, 2.5vw, 33px);
	display: flex;
	flex-direction: column;
}

.site-footer__top {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: flex-start;
	gap: 32px;
}

.site-footer__brand {
	display: flex;
	flex-direction: column;
	gap: 36px;
}

.site-footer__logo {
	/* Matches .site-header__logo img exactly (18px) — same file, same
	   aspect ratio. The actual stretching wasn't from the size mismatch —
	   .site-footer__brand is a column flexbox with no align-items set, so
	   it defaults to "stretch", and for a COLUMN container that stretches
	   children along the WIDTH axis, overriding width:auto and distorting
	   the logo sideways while height stayed fixed at 18/24px. align-self
	   here opts this element out of that stretch. */
	align-self: flex-start;
	height: 18px;
	width: auto;
}

.site-footer__contact {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 11px;
}

.site-footer__contact a {
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 18px;
	color: var(--color-white);
	text-decoration: none;
}

.site-footer__socials {
	display: flex;
	align-items: center;
	gap: 12px;
}

.site-footer__socials img {
	display: block;
	height: 22px;
	width: auto;
}

/* Instagram used to be reassembled from 3 separately-exported Figma layers
   (frame/lens/dot) via CSS inset positioning — fragile in practice (two
   rounds of layout bugs from it) since replaced elements (img) don't size
   themselves the way plain boxes do when both inset and width/height are
   in play. assets/images/footer/instagram.svg now combines all three
   shapes into one real file with a shared coordinate system, same as
   linkedin.svg/youtube.svg, so it's just a plain icon like the others —
   no special-case CSS needed at all beyond .site-footer__socials img above. */

.site-footer__badges {
	display: flex;
	align-items: center;
	gap: 35px;
}

.site-footer__badges img {
	display: block;
	height: clamp(52px, 6vw, 85px);
	width: auto;
}

.site-footer__badges .site-footer__badge-drum {
	/* .site-footer__badges img (class+element) outranks a bare
	   .site-footer__badge-drum (single class) regardless of source order —
	   same specificity trap as the Instagram icon fix earlier. Chaining
	   both classes here (0,2,0) unambiguously beats it (0,1,1). ~30%
	   smaller than the shared badge size above (Clutch stays as-is). */
	height: clamp(36px, 4.2vw, 59px);
}

.site-footer__middle {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: flex-end;
	gap: 32px;
	margin-top: clamp(32px, 5vw, 65px);
}

.site-footer__address {
	margin: 0;
	max-width: 321px;
	font-family: var(--font-body);
	font-weight: 500;
	font-size: 18px;
	line-height: 1.61;
}

.site-footer__newsletter {
	display: inline-flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	width: clamp(260px, 27vw, 387px);
	padding-bottom: 12px;
	border-bottom: 1px solid var(--color-white);
	font-family: var(--font-body);
	font-weight: 600;
	font-size: 16px;
	color: var(--color-white);
	text-decoration: none;
}

.site-footer__newsletter-arrow {
	flex-shrink: 0;
	display: block;
	width: 14px;
}

.site-footer__newsletter-arrow img {
	display: block;
	width: 100%;
	height: auto;
	transform: rotate(-90deg);
}

.site-footer__nav {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 16px 32px;
	margin-top: clamp(32px, 5vw, 50px);
}

.site-footer__nav a,
.site-footer__copyright {
	margin: 0;
	font-family: var(--font-display);
	font-weight: 800;
	font-size: 16px;
	color: var(--color-white);
	text-decoration: none;
	white-space: nowrap;
}
