/*
 * Purpose: Theme components written for WordPress, plus the state classes that
 * replaced inline styles when the static build's jQuery was ported to vanilla JS.
 * Tokens only — never hardcode a value here, add it to design-system.css first.
 */

/* ── Nav: whole menu row is a click target (was li.style.cursor in JS) ── */
.menu-item.is-clickable {
	cursor: pointer;
}

/* ── Custom cursor hover state (was inline .css() in JS) ───────────────── */
#mouse .circle {
	transition:
		transform var(--duration-base) var(--ease-out),
		background-color var(--duration-base) var(--ease-out);
}

#mouse .circle.is-hovering {
	transform: scale(0.5);
	background-color: rgb(var(--grey-rgb) / 50%);
}

#mouse[hidden] {
	display: none !important;
}

/* ── WordPress admin bar must not sit on top of the fixed header ───────── */
body.admin-bar .header {
	top: 32px;
}

@media screen and (max-width: 782px) {
	body.admin-bar .header {
		top: 46px;
	}
}

/* ── Body scroll lock while the mega-menu is open ──────────────────────── */
body.menu-open {
	overflow: hidden;
}

/* ── Footer curtain: let clicks reach it ───────────────────────────────
   The footer is fixed behind the content, and JS reserves its height at the
   bottom of #smooth-content so nothing is covered. That reserved strip is part
   of the content box, so it sat on top of the footer and swallowed every click
   on the Products / Who-we-work-with / legal links (the static build has the
   same bug). Turning off pointer events on the two wrappers — and back on for
   their children — means real content still takes its clicks while the empty
   reserved strip passes them through to the footer underneath. */
#smooth-wrapper,
#smooth-content {
	pointer-events: none;
}

/* `#smooth-wrapper > *` also matches #smooth-content itself. Both selectors
   score (1,0,0), so the later one won and handed #smooth-content its pointer
   events straight back — the rule above was cancelling itself and the strip
   kept eating clicks. Exclude it here so only its children are re-enabled. */
#smooth-wrapper > *:not(#smooth-content),
#smooth-content > * {
	pointer-events: auto;
}

/* ── Footer curtain: only where the viewport can hold it ────────────────
   vendor/turii.css pins .site_foot to the bottom of the viewport. On a phone
   the footer is ~1250px tall against an ~840px viewport, so its top third —
   logo, socials, the first product links — sits above the fold and cannot be
   reached by scrolling at all (the static build has the same bug). Below the
   desktop breakpoint the footer goes back into normal flow and simply scrolls. */
@media (max-width: 1023px) {
	.site_foot {
		position: relative;
	}
}

/* ── Cookie bar must not cover the footer's legal line ─────────────────
   The bar is fixed to the bottom of the viewport; cookie-bar.js publishes its
   height as --cookie-bar-h while it is up, and the footer reserves that much
   extra room so Privacy / Terms stay clickable. */
.site_foot > .container {
	padding-bottom: calc(20px + var(--cookie-bar-h, 0px));
}

/* ── WordPress core alignment + caption classes ────────────────────────── */
.alignleft {
	float: left;
	margin: 0 var(--space-6) var(--space-4) 0;
}

.alignright {
	float: right;
	margin: 0 0 var(--space-4) var(--space-6);
}

.aligncenter {
	display: block;
	margin-inline: auto;
}

.alignwide,
.alignfull {
	max-width: 100%;
}

.wp-caption {
	max-width: 100%;
}

.wp-caption-text,
.wp-element-caption {
	margin-top: var(--space-2);
	color: var(--color-text-muted);
	font-family: var(--font-body);
	font-size: var(--text-sm);
	line-height: var(--leading-normal);
}

.wp-block-image img,
.wp-caption img {
	max-width: 100%;
	height: auto;
}

/* ── Editor-authored content inside a section ──────────────────────────── */
.wysiwyg > :first-child {
	margin-top: 0;
}

.wysiwyg > :last-child {
	margin-bottom: 0;
}

.wysiwyg ul,
.wysiwyg ol {
	margin: 0 0 var(--space-6);
	padding-left: var(--space-6);
}

.wysiwyg li {
	margin-bottom: var(--space-2);
	font-family: var(--font-body);
	line-height: var(--leading-normal);
}

.wysiwyg a {
	color: var(--color-primary);
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

.wysiwyg blockquote {
	margin: var(--space-8) 0;
	padding-left: var(--space-6);
	border-left: 2px solid var(--color-primary);
	font-family: var(--font-heading);
	font-style: italic;
}

/* ── Pagination ────────────────────────────────────────────────────────── */
.pagination {
	display: flex;
	gap: var(--space-2);
	justify-content: center;
	margin-top: var(--space-16);
}

.pagination .page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 2.75rem;
	height: 2.75rem;
	padding: 0 var(--space-3);
	border: var(--border-thin);
	color: var(--color-text);
	font-family: var(--font-body);
	font-size: var(--text-sm);
	letter-spacing: var(--tracking-wide);
	text-decoration: none;
	transition: background-color var(--duration-base) var(--ease-out),
		color var(--duration-base) var(--ease-out);
}

.pagination .page-numbers:hover,
.pagination .page-numbers.current {
	background-color: var(--color-primary);
	color: var(--color-text-invert);
}

/* ── Footer column headings ────────────────────────────────────────────
   Rendered as <h3> so the heading order never skips a level; the vendor
   layer styles the <h4> it used to be, so mirror that rule here. */
.site_foot .foot_col h3 {
	margin: 0 0 1.6rem;
	color: var(--gold);
	font-family: var(--main-font);
	font-size: 0.78rem;
	font-weight: var(--weight-regular);
	letter-spacing: 0.22em;
	text-transform: uppercase;
}

/* ── Hover lists: dim the rows that are not hovered (was inline opacity) ─ */
.box_anim {
	transition: opacity var(--duration-base) var(--ease-out);
}

.box_anim.is-dimmed {
	opacity: 0.25;
}

/* Capability rows are buttons, not links — strip the UA button styling so
   the .box_anim rules from the vendor layer apply unchanged. */
button.box_anim {
	appearance: none;
	border: 0;
	background: none;
	color: inherit;
	cursor: pointer;
	font: inherit;
	text-align: inherit;
}

/* ── Lightbox-wrapped gallery tiles ────────────────────────────────────
   Gallery tiles are anchors so the whole tile opens the lightbox, but the
   vendor sizing rule is `picture > img`, which an anchor does not match.
   Restore it verbatim for the anchor case, or the images lose their box. */
a.pd_pic > img,
a.ab_gal_pic > img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* ── Projects grid: filter + paging state (was inline styles in JS) ───── */
.pj_card.is-hidden {
	display: none;
}

.pj_grid.is-fading-in {
	animation: pj-fade-in var(--duration-slow) var(--ease-out);
}

@keyframes pj-fade-in {
	from { opacity: 0; }
	to   { opacity: 1; }
}

.pj_pager[hidden] {
	display: none;
}

.pj_filters {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2);
	margin-bottom: var(--space-8);
}

.pj_filter_btn {
	padding: var(--space-2) var(--space-5);
	border: var(--border-thin);
	background: none;
	color: var(--color-text-muted);
	cursor: pointer;
	font-family: var(--font-body);
	font-size: var(--text-sm);
	letter-spacing: var(--tracking-wide);
	text-transform: uppercase;
	transition: color var(--duration-base) var(--ease-out),
		border-color var(--duration-base) var(--ease-out);
}

.pj_filter_btn:hover,
.pj_filter_btn.is-active {
	border-color: var(--color-primary);
	color: var(--color-primary);
}

/* ── Centred narrow column (was inline margin-left/right auto) ─────────── */
.u-center-column {
	margin-inline: auto;
}

/* ── About split: the offset second image (was an inline style) ───────── */
.about_split__offset {
	margin-top: calc(40vw * 0.5);
}

.about_split__offset .founder_pic {
	max-width: 100%;
}

/* ── Cookie bar ────────────────────────────────────────────────────────── */
.cookie_bar {
	position: fixed;
	inset-inline: 0;
	bottom: 0;
	z-index: var(--z-toast);
	border-top: 1px solid rgb(var(--gold-rgb) / 45%);
	background-color: rgb(var(--black-rgb) / 94%);
	backdrop-filter: blur(12px);
}

.cookie_bar[hidden] {
	display: none;
}

.cookie_bar__inner {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	max-width: var(--container-2xl);
	margin-inline: auto;
	padding: var(--space-5) var(--space-6);
}

.cookie_bar__text {
	margin: 0;
	color: rgb(var(--white-rgb) / 72%);
	font-family: var(--main-font);
	font-size: 0.72rem;
	font-weight: 300;
	letter-spacing: 0.14em;
	line-height: var(--leading-normal);
	text-transform: uppercase;
}

.cookie_bar__sep {
	margin-inline: 0.5em;
	color: rgb(var(--gold-rgb) / 70%);
}

.cookie_bar__link {
	/* foundation.css makes every anchor a block, which drops the policy link
	   onto its own line and breaks the sentence. */
	display: inline;
	color: var(--gold);
	text-decoration: underline;
	text-underline-offset: 0.35em;
	text-decoration-color: rgb(var(--gold-rgb) / 45%);
	transition: text-decoration-color var(--duration-base) var(--ease-out);
}

.cookie_bar__link:hover {
	text-decoration-color: var(--gold);
}

.cookie_bar__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-3);
}

/*
 * The buttons borrow the vendor pill CTA so they read as the site's own
 * buttons. Only the parts a <button> does not inherit are reset here.
 */
.cookie_bar__btn {
	appearance: none;
	background-color: transparent;
	cursor: pointer;
	font-family: var(--main-font);
}

.cookie_bar__btn--ghost {
	border-color: rgb(var(--white-rgb) / 28%);
	color: rgb(var(--white-rgb) / 70%);
}

.cookie_bar__btn--ghost:hover {
	border-color: rgb(var(--white-rgb) / 55%);
	background-color: transparent;
	color: var(--white);
}

.cookie_bar__btn--ghost svg path {
	fill: rgb(var(--white-rgb) / 55%);
}

.cookie_bar__btn--ghost:hover svg path {
	fill: var(--white);
}

@media (min-width: 768px) {
	.cookie_bar__inner {
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-8);
		padding-block: var(--space-4);
	}

	.cookie_bar__actions {
		flex-shrink: 0;
		flex-wrap: nowrap;
	}
}

/* ── Contact Form 7 shim ───────────────────────────────────────────────────
   The form template is the theme's own markup, but Contact Form 7 wraps each
   control in a <span> and adds status output of its own. Its stylesheet is
   dequeued (inc/cf7.php), so the few rules that plugin sheet was carrying —
   hiding the screen-reader block and the empty response box — are restored
   here, in the brand's language. */

.wpcf7 .screen-reader-response {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	border: 0;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* The control wrapper has to disappear from the layout: the grid styles the
   field, and the input must still fill it. */
.bd_field > .wpcf7-form-control-wrap {
	display: block;
}

.bd_check .wpcf7-form-control-wrap,
.bd_check .wpcf7-acceptance,
.bd_check .wpcf7-list-item {
	display: inline-flex;
	margin: 0;
}

/* Per-field validation message. */
.bd_form .wpcf7-not-valid-tip {
	display: block;
	margin-top: 0.5rem;
	color: var(--gold2);
	font-family: var(--main-font);
	font-size: 0.85rem;
	font-weight: 300;
}

.bd_form .wpcf7-not-valid {
	border-bottom-color: var(--gold2);
}

/* The response line replaces the theme form's `.bd_form_thanks`. */
.bd_form .wpcf7-response-output {
	margin: clamp(1.6rem, 3vw, 2.2rem) 0 0;
	padding: 0;
	border: 0;
	color: var(--gold2);
	font-family: var(--title-font);
	font-size: clamp(1.4rem, 2.4vw, 2rem);
	font-style: italic;
}

.bd_form.init .wpcf7-response-output,
.bd_form.resetting .wpcf7-response-output,
.bd_form.submitting .wpcf7-response-output {
	display: none;
}

/* Anything that is not a successful send reads as a correction, not a flourish. */
.bd_form.invalid .wpcf7-response-output,
.bd_form.unaccepted .wpcf7-response-output,
.bd_form.payment-required .wpcf7-response-output,
.bd_form.failed .wpcf7-response-output,
.bd_form.aborted .wpcf7-response-output,
.bd_form.spam .wpcf7-response-output {
	color: rgb(var(--white-rgb) / 70%);
	font-family: var(--main-font);
	font-size: 0.95rem;
	font-style: normal;
	font-weight: 300;
}

.bd_form .wpcf7-spinner {
	display: inline-block;
	width: 1.5em;
	height: 0.6em;
	margin-left: 0.6em;
	background:
		radial-gradient(circle 2px, var(--gold) 99%, transparent 0) 0 50%,
		radial-gradient(circle 2px, var(--gold) 99%, transparent 0) 50% 50%,
		radial-gradient(circle 2px, var(--gold) 99%, transparent 0) 100% 50%;
	background-repeat: no-repeat;
	visibility: hidden;
	opacity: 0.7;
}

.bd_form.submitting .wpcf7-spinner {
	visibility: visible;
	animation: turii-form-pulse 1s var(--ease-inout) infinite;
}

@keyframes turii-form-pulse {
	50% { opacity: 0.25; }
}

@media (prefers-reduced-motion: reduce) {
	.bd_form.submitting .wpcf7-spinner { animation: none; }
}

/* ── Empty state ───────────────────────────────────────────────────────── */
.no-results {
	padding: var(--space-24) 0;
	text-align: center;
}

.no-results__title {
	font-family: var(--font-heading);
	font-size: var(--text-2xl);
}

.no-results__text {
	margin-top: var(--space-4);
	color: var(--color-text-muted);
	font-family: var(--font-body);
}

/* ── Hamburger must survive a phone-width header ───────────────────────
   The header row is logo + "Get a quotation" pill + hamburger. The pill is
   `white-space: nowrap` at ~190px, so on a phone the row runs out of width and
   the only thing left to give is the burger. foundation.css puts
   `max-width: 100%` on every <svg>, and that percentage resolves against
   whatever slack the flex row has left — so the icon collapsed to 12px at
   390px wide and to literally 0px at 320-375px. It was still there, still
   clickable, just painted at zero size, which reads as "the hamburger is gone"
   (the static build has the same bug).

   Below 480px: trim the pill so the row fits, and size the icon from the
   viewport instead of from the leftover slack. */
@media (max-width: 479px) {
	.header .head_cta {
		margin-right: 0.75rem;
	}

	.header .head_cta .link.pill_cta {
		font-size: 0.64rem;
		letter-spacing: 0.1em;
		padding: 0.85em 1.2em;
	}

	#hamburger .menu_ico,
	#hamburger .close_ico {
		max-width: none;
		width: clamp(1.55rem, 6.6vw, 2.3rem);
	}
}

/* ── Phone: nav sections that own a mega-menu panel ────────────────────
   Products / Spaces / Who we work with have no href — on desktop they switch
   the panel in #menu_anim, which is display:none below 1024px. So on a phone
   the tap did nothing at all. nav-menu.js moves each panel's link list into
   its own <li> under the phone query; these rules turn it into an accordion.
   Applies below 1024px, where that panel is hidden — the query has to match
   #menu_anim's own 1024px switch, or the band between leaves the taps dead. */
@media (max-width: 1023px) {
	/* The row is a flex line of counter + label; let the panel wrap below it. */
	/* Top-level rows read a size too large on a handset — custom.css sets
	   1.625em against a viewport-scaled root. Bring both the label and its
	   counter down a step; the sub-items keep their own larger size. */
	#main_nav .head_menu li a {
		font-size: 1.4em;
	}

	#main_nav .head_menu > li::before {
		font-size: 1.5em;
	}

	#main_nav .head_menu li.has_sub {
		flex-wrap: wrap;
	}

	/* Chevron on the row itself — the link's ::after is the hover underline. */
	#main_nav .head_menu li.has_sub::after {
		content: "";
		width: 0.55em;
		height: 0.55em;
		margin-left: auto;
		align-self: center;
		border-right: 2px solid var(--gold);
		border-bottom: 2px solid var(--gold);
		transform: rotate(45deg) translate(-0.1em, -0.1em);
		transform-origin: center;
		transition: transform var(--duration-base, 250ms) var(--ease-out, ease);
	}

	#main_nav .head_menu li.has_sub.is-open::after {
		transform: rotate(-135deg) translate(-0.15em, -0.15em);
	}

	/* ── The panel starts under the header and the menu scrolls ──────
	   The nav was centred in the viewport and clipped to 100dvh, so opening
	   a section re-centred the whole list (it jumped 111px up the screen)
	   and anything past the fold was simply unreachable. Anchor it under the
	   header instead and let the nav itself scroll, so an open section can
	   push the list as long as it needs. */
	#main_nav {
		position: fixed;
		inset: 0;
		height: 100dvh;
		overflow-y: auto;
		overscroll-behavior: contain;
		-webkit-overflow-scrolling: touch;
		align-items: flex-start;
	}

	#main_nav .menu_wrapper {
		position: static;
		/* This element paints the menu's dark background, so it still has to
		   fill the screen when the list is short — height:auto alone left it
		   ending partway down. min-height keeps it full-bleed while letting an
		   open section push it past the fold. */
		height: auto;
		min-height: 100dvh;
		box-sizing: border-box;
		align-items: flex-start;
		padding-top: var(--header-h, 76px);
		/* The cookie bar is fixed to the bottom of the viewport and was
		   covering the last sub-item on a 360px screen. cookie-bar.js already
		   publishes its height as --cookie-bar-h for the footer; reserve the
		   same room here so every row stays tappable while the bar is up. */
		padding-bottom: calc(3rem + var(--cookie-bar-h, 0px));
	}

	#main_nav .head_menu li.has_sub > .mobile_sub {
		/* The chevron is the row's ::after, so in source order it trails the
		   panel. Ordering the panel last keeps counter + label + chevron
		   together on line one and wraps the links onto line two — no magic
		   offsets to keep in sync with the row's type scale. */
		order: 1;
		flex: 0 0 100%;
		max-height: 0;
		overflow: hidden;
		opacity: 0;
		transition: max-height 400ms var(--ease-out, ease), opacity 250ms ease, margin-top 400ms var(--ease-out, ease);
	}

	/* max-height is set to the panel's own scrollHeight by nav-menu.js — no
	   cap here, so a long list is never clipped. */
	#main_nav .head_menu li.has_sub.is-open > .mobile_sub {
		opacity: 1;
		margin-top: 0.75rem;
	}

	/* Reset the parent row's link styling — these are sub-items, not headings.
	   min-height 48px keeps every row a comfortable thumb target (WCAG 2.5.8
	   asks for 24px, Apple and Google both recommend 44-48). */
	#main_nav .head_menu li.has_sub > .mobile_sub a {
		display: flex;
		align-items: center;
		min-height: 48px;
		margin-left: 2ch;
		padding: 0.35rem 0;
		font-family: var(--main-font);
		font-size: 1.15rem;
		font-weight: 300;
		line-height: 1.3;
		letter-spacing: 0.02em;
		text-transform: none;
		white-space: normal;
		color: rgb(var(--white-rgb) / 82%);
	}

	#main_nav .head_menu li.has_sub > .mobile_sub a:hover,
	#main_nav .head_menu li.has_sub > .mobile_sub a:focus-visible {
		color: var(--gold);
	}

	/* The sub-items are small; the sweeping underline reads as noise here. */
	#main_nav .head_menu li.has_sub > .mobile_sub a::after {
		display: none;
	}
}

@media (max-width: 1023px) and (prefers-reduced-motion: reduce) {
	#main_nav .head_menu li.has_sub::after,
	#main_nav .head_menu li.has_sub > .mobile_sub {
		transition: none;
	}
}

/* ── Phone: "Made while you build" — text was outgrowing its video ─────
   .text_enter is `font-size: clamp(4rem, 15vw, 12vw)` (custom.css). That 4rem
   floor never lets the heading shrink, so on a 390px phone it rendered at 64px
   across four lines — a 283px block of type over a video only 197px tall. The
   heading was 143% of the video, which is why the video read as tiny.

   Two changes: drop the floor so the heading scales with the viewport, and
   give the video a 4:5 box so it fills the phone screen properly. The source
   is 16:9, so `cover` trims the sides — the factory line stays in frame.
   Phones only; 768px and up keep the existing treatment. */
@media (max-width: 767px) {
	#s3_home .to_big .mwyb_media {
		aspect-ratio: 4 / 5;
		height: auto;
		object-fit: cover;
	}

	#s3_home .text_enter {
		font-size: clamp(1.75rem, 8.5vw, 3rem);
		bottom: 1.5rem;
		padding: 0 6%;
	}
}

/* ── "Made while you build" video — sub-pixel edge seam ────────────────
   Same root cause as the seam fix in vendor/turii.css: ScrollSmoother
   translates #smooth-content on fractional pixels, so this video's box lands
   on half-pixels while the page moves. The compositor then antialiases the
   outermost row/column of video against the dark page behind it, drawing a
   thin light line down the left/right edge and across the top. It is worst on
   devices whose DPR is not a whole number — measured +14 luminance at DPR
   2.625 — which is why it shows on a phone but not in a desktop browser.

   turii.css fixes its cases by bleeding the media 1px past an overflow:hidden
   parent. That does not transfer here: .to_big is sized *by* this video, so
   growing the video grows the box with it and there is nothing to clip
   against. Clipping the paint a couple of pixels inside the border box gets
   the same result — the antialiased edge is simply never drawn. No layout or
   animation impact; 2px of a 439px-tall video is not perceptible.

   Clip the video only. Clipping .filter_bt alongside it pulls the bottom
   gradient back off the video's bottom edge and the seam gets dramatically
   worse — 48 of 108 sampled edges versus 0. 1px left an occasional residual;
   2px measured clean across repeated runs. */
#s3_home .to_big .mwyb_media {
	clip-path: inset(2px);
}

/* ── Sub-pixel seam at full-bleed video section edges ──────────────────
   ScrollSmoother translates #smooth-content on fractional pixels, so a
   section that clips full-bleed media with `overflow: hidden` has its clip
   boundary land on a half device-pixel while the page moves. The compositor
   then antialiases that boundary row — one row of video blended with the dark
   page behind it — and it reads as a thin light line across the section edge.
   It only shows at non-integer DPRs, which is why it turns up on real screens
   and not in a desktop browser at 1x/2x.

   vendor/turii.css already documents this and tries to fix it by bleeding the
   media 1px past the clip. Measuring says that does not work here: with the
   hero's video bled 2px past the section on every side the line was still
   there on 33 of 36 sampled frames. The bleed cannot help, because it is the
   *clip* that is on a fractional pixel, not the media inside it — anything
   drawn inside the clipped layer, media or a solid strip, gets the same
   blended boundary row.

   What does work is covering the boundary from OUTSIDE the clipped section,
   with a strip in the page background colour. Both surfaces already fade to
   exactly this colour at their edge, so the strip is invisible. Verified 0/36
   frames after, against 29-34/36 before.

   `main` and every section are already `position: relative`, so these
   pseudo-elements introduce no new containing block. */
.s1 + section::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: -3px;
	height: 6px;
	background: var(--black);
	z-index: 6;
	pointer-events: none;
}

main::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	/* Only as deep as the seam. cta_banner's video is still slightly brighter
	   than the page at this point, so a 5px strip read as a faint dark band
	   (-9.3 luminance against its surroundings); 2px covers the boundary row
	   and measures invisible. */
	bottom: -1px;
	height: 3px;
	background: var(--black);
	z-index: 5;
	pointer-events: none;
}

/* ── Back-to-top button ────────────────────────────────────────────────
   custom.css gives #back_top `bottom`/`right` but never a `position`, so it
   stayed `static`: the offsets did nothing and the button sat in normal flow
   at the end of <body> — bottom-left of the page on a phone, and thousands of
   pixels off-screen on desktop, because body carries ScrollSmoother's
   transform. Pin it properly.

   It also paints above the fixed footer (z-index 0) and #smooth-wrapper
   (z-index 1), so it needs to clear both. The cookie bar is fixed to the same
   corner, so reserve its published height the way the footer and the mobile
   menu already do. */
#back_top {
	position: fixed;
	bottom: calc(1rem + var(--cookie-bar-h, 0px));
	right: 1rem;
	z-index: 320;
	align-items: center;
	justify-content: center;
	/* The button sits over whatever is on screen — a translucent ground keeps
	   the arrow legible against a bright photo as well as the dark page. */
	background: rgb(var(--black-rgb) / 70%);
	backdrop-filter: blur(6px);
	color: var(--gold);
}

/* custom.css fills `svg polygon`, but the markup is a <path> — so the rule
   never matched and the icon rendered with the SVG default fill, solid black
   on a dark button. This icon is stroked, not filled. */
#back_top svg {
	width: 62%;
	height: 62%;
	fill: none;
	stroke: currentColor;
}

/* Touch devices have no hover, but they DO latch :hover after a tap — the
   button was staying gold-filled after being pressed, which reads as a stuck
   or dead control. Give the filled treatment to real pointers only. */
@media (hover: hover) {
	#back_top:hover {
		background: var(--gold);
		color: var(--black);
		border-color: var(--gold);
	}

	#back_top:hover svg {
		transform: translateY(-2px);
	}
}

#back_top:focus-visible {
	background: var(--gold);
	color: var(--black);
	border-color: var(--gold);
	outline: 2px solid var(--white);
	outline-offset: 3px;
}

/* Press feedback that works on touch, where :hover never fires. */
#back_top:active {
	background: var(--gold);
	color: var(--black);
	border-color: var(--gold);
	transform: scale(0.94);
}

#back_top svg {
	transition: transform var(--duration-base, 250ms) var(--ease-out, ease);
}

@media (prefers-reduced-motion: reduce) {
	#back_top,
	#back_top svg {
		transition: none;
	}

	#back_top:active {
		transform: none;
	}
}

/* Smaller box than custom.css's 2.5rem, which rendered ~47px. Its `.5em`
   padding is dropped too — with padding in play the arrow was squeezed to 11px
   inside a 38px button; the svg's own 62% sizing gives a better ratio. */
#back_top {
	width: 2.05rem;
	padding: 0;
}

@media (max-width: 991px) {
	#back_top {
		width: 34px;
	}
}

/* The visible square is now below the 44px minimum for a touch target, so
   extend the *hit* area past the border without changing what is drawn. The
   button still looks small; a thumb still gets a comfortable 46px to land on. */
#back_top::before {
	content: "";
	position: absolute;
	inset: -6px;
}

/* ── Form submit uses the site's pill component ────────────────────────
   .bd_submit used to be a bare transparent button with a single arrow, and
   its arrow had no fill rule at all so it painted solid black. Rather than
   restyle and re-animate it here, the markup now carries `link pill_cta` and
   renders arrow/label/arrow like turii_the_pill_cta() — so it inherits the
   real component: the pill, the gold arrow, the hover fill, and the arrow
   swap animation. Only the vendor `.bd_submit` resets need overriding, since
   that rule sets a transparent background and no border. */
.bd_submit.link.pill_cta {
	background-color: transparent;
	font-family: var(--main-font);
	font-weight: 400;
}

@media (hover: hover) {
	.bd_submit.link.pill_cta:hover {
		background-color: var(--gold);
	}
}

.bd_submit.link.pill_cta:active {
	background-color: var(--gold);
	border-color: var(--gold);
	color: var(--black);
}

.bd_submit.link.pill_cta:focus-visible {
	outline: 2px solid var(--gold);
	outline-offset: 3px;
}

/* ── About hero: group the heading with its standfirst on phones ───────
   Below 768px .ab_hero_in becomes a column, but .ab_hero_left keeps the
   two-column `flex: 1 1 55%`. In a column that basis is read against the
   *height*, and flex-grow then stretches the block to 503px for about 155px
   of text — which is what pushed the standfirst all the way to the bottom of
   the hero, a photo's height away from the heading it belongs to.

   Let the block size to its content and bottom-align the stack instead: the
   standfirst keeps the position it already had, and the eyebrow and heading
   come down to sit directly above it. */
@media (max-width: 767px) {
	#about_hero .ab_hero_in {
		justify-content: flex-end;
		/* Bottom-aligning the stack put the standfirst under the cookie bar —
		   108px of it at 360px wide. The stretch this replaced had been
		   absorbing that gap by accident. Reserve the bar's published height,
		   the same way the footer and the mobile menu do. */
		padding-bottom: var(--cookie-bar-h, 0px);
		transition: padding-bottom 0.3s ease;
	}

	/* Both blocks size to their content — .ab_hero_right carries `flex: 1 1 auto`
	   at this width, so leaving it alone just moved the stretch to the other
	   block and sent the standfirst to the top instead. */
	#about_hero .ab_hero_left,
	#about_hero .ab_hero_right {
		flex: 0 0 auto;
	}
}

/* ── "From a door factory / to turii" — keep it to its two authored lines ──
   The copy carries an explicit <br>, so the heading is meant to break in two.
   .ab_head h2 is `clamp(2.2rem, 4.8vw, 4rem)`, and below roughly 735px the
   2.2rem floor wins — a fixed 35px that no longer fits "From a door factory"
   once the column drops under ~350px. On a 320-375px handset the first line
   wrapped and the heading rendered as three lines.

   Give the floor room to move on the narrowest screens so the authored break
   is the only one. Note textAnim splits this heading into fixed line wrappers
   at load, so this has to come from the stylesheet — a size applied after load
   does not reflow it. */
@media (max-width: 389px) {
	#ab_timeline .ab_head h2 {
		font-size: clamp(1.6rem, 8.6vw, 2.2rem);
	}
}

/* ── "I accept the Privacy Policy" must stay on one line ───────────────
   .bd_check already sets `white-space: nowrap`, but that cannot help here:
   foundation.css carries a global `a { display: block }`, so the Privacy
   Policy link was its own block box and dropped to a second line no matter
   what the wrapping rules said. Put the link back in the inline flow. */
.bd_check a {
	display: inline;
}

/* ── Balance the "Your project in 3 steps" heading ─────────────────────
   Centred and left to wrap on its own it split 253px / 83px — everything on
   line one and "steps" orphaned underneath. text-wrap: balance evens the two
   lines instead. It only engages when the text actually wraps, so the wider
   viewports that fit it on one line are unaffected. */
#s3_general .text h2.h1 {
	text-wrap: balance;
}

/* ── Projects hero: keep the authored two-line break ───────────────────
   The copy is "Projects that<br>hold the line." — two lines by design.
   .ab_hero_h is `clamp(2.6rem, 6vw, 6rem)` and below roughly 700px the
   2.6rem floor wins, a fixed ~41px that no longer fits "hold the line." once
   the column drops under ~340px. The result was three lines at 375px and
   four at 320-360px, with "line." stranded on its own.

   Lower the floor on the narrowest screens so both authored lines fit.
   Scoped to this page: .ab_hero_h is shared with the About hero, whose
   heading wraps naturally and was not asked to change. */
@media (max-width: 389px) {
	/* vendor/turii.css sets this as `#about_hero .ab_hero_h`, so the ID has to
	   be matched here too or the override never lands. */
	body.page-id-7 #about_hero .ab_hero_h {
		font-size: clamp(2rem, 10.6vw, 2.6rem);
	}
}

/* ── Pointer target size (WCAG 2.5.8) ──────────────────────────────────
   Three standalone controls render below the 24x24 minimum: the gallery
   pagination dots (16px), the "VIEW PROJECT" link (18px tall) and the
   "All projects" back link (13px tall). Each gets an invisible pseudo
   element sized to the minimum instead of padding, so the hit area grows
   without moving anything on the page.

   .proj_link::after is already the hover underline in vendor/turii.css, so
   that one takes ::before. */
#nav_dots .swiper-pagination-bullet {
	touch-action: manipulation;
}

#nav_dots .swiper-pagination-bullet::after,
.proj_link::before,
.pd_back::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	width: 100%;
	min-width: 24px;
	height: 100%;
	min-height: 24px;
}

.proj_link,
.pd_back {
	position: relative;
}

/* ── Colour contrast (WCAG 1.4.3) ──────────────────────────────────────
   vendor/turii.css paints two pieces of small text at 45% white, which
   composites to #827b73 on the footer's #241c15 (4.02:1) and #867e75 on the
   services card's #2b2118 (3.93:1). Both need 4.5:1. 54% clears it at 5.19
   and 5.01 while staying visually the same weight of grey.

   The footer rule is matched at its own two-class specificity and the
   services one keeps the #sec_servizi id, or neither override lands. */
.site_foot .foot_bottom,
.site_foot .foot_bottom a {
	color: rgb(var(--white-rgb) / 54%);
}

#sec_servizi .svc_no {
	color: rgb(var(--white-rgb) / 54%);
}
