@font-face {
	font-family: 'Playfair Display';
	src: url('../fonts/playfair-display-semibold.woff2') format('woff2');
	font-weight: 600;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'Playfair Display';
	src: url('../fonts/playfair-display-bold.woff2') format('woff2');
	font-weight: 700;
	font-style: normal;
	font-display: swap;
}

/* Open Sauce One — SIL OFL 1.1, see ../fonts/open-sauce-one-OFL.txt.
   Upstream ships TTF only (github.com/marcologous/Open-Sauce-Fonts); these
   were converted to woff2 locally, which is what the rest of the theme's
   vendored faces use. Same three weights Hanken Grotesk occupied, so no
   font-weight anywhere else in this file had to change. */
@font-face {
	font-family: 'Open Sauce One';
	src: url('../fonts/open-sauce-one-regular.woff2') format('woff2');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'Open Sauce One';
	src: url('../fonts/open-sauce-one-medium.woff2') format('woff2');
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'Open Sauce One';
	src: url('../fonts/open-sauce-one-semibold.woff2') format('woff2');
	font-weight: 600;
	font-style: normal;
	font-display: swap;
}

:root {
	/* Brand palette (see docs/DESIGN.md) */
	--color-primary: #4A3728;
	--color-secondary: #8C6239;
	--color-tertiary: #D9CDC1;
	--color-neutral: #6B6764; /* darkened from the source #787674 swatch for a real AA margin on small text (~6:1 vs 4.52:1) */
	--color-bg: #FAF7F3;
	--color-line: #E7DFD3;
	--color-error: #B3261E;

	/* Added for the 2026 Stitch mockup refresh (docs/DESIGN.md) — a warmer,
	   higher-contrast accent for primary CTAs and a near-black "deep earth"
	   for dark sections, replacing the ad hoc #3a2b20 literal previously
	   scattered across the footer/CTA banners. */
	--color-clay: #A67B5B;
	--color-clay-hover: #8f6749;
	--color-deep-earth: #4a4233;

	/* A tint one step warmer than --color-bg, for a card that needs to lift
	   off the page without becoming an accent section. Added 2026-08-30 for
	   the Services accordion's open entry; --color-tertiary would have been
	   far too strong and is spoken for (one accent section per page, see
	   docs/DESIGN.md "Decoration & color rhythm"). */
	--color-bg-soft: #F7EFEA;

	--font-body: 'Open Sauce One', -apple-system, sans-serif;
	--font-heading: 'Playfair Display', Georgia, serif;

	--color-text: var(--color-primary);
	--color-accent: var(--color-secondary);

	/* Motion system — added 2026-09-04. One easing curve everywhere (the same
	   cubic-bezier the accordion already used before this existed), and one
	   duration per category rather than a scattered literal per rule. */
	--motion-fast: 180ms;
	--motion-base: 260ms;
	--motion-reveal: 650ms;
	--motion-cinematic: 1400ms;
	--motion-ease: cubic-bezier(0.4, 0, 0.2, 1);

	--max-width: 1200px;
	--gutter: 1.5rem;

	/* Type scale — strict 1.25 (Major Third) ratio, see docs/DESIGN.md.
	   Every heading/label size in this file should come from these tokens
	   rather than a one-off value. */
	--text-xs: 0.875rem;  /* 14px */
	--text-sm: 1rem;      /* 16px */
	--text-md: 1.25rem;   /* 20px */
	--text-lg: 1.5625rem; /* 25px */
	--text-xl: 1.9375rem; /* 31px */
	--text-2xl: 2.4375rem;/* 39px */
	--text-3xl: 3.0625rem;/* 49px */
	/* Display steps — the same strict 1.25 ratio continued past --text-3xl
	   (49 × 1.25 = 61.25, × 1.25 = 76.56, × 1.25 = 95.7) for the editorial
	   display type on the About page. Added rather than hardcoding a one-off
	   96px hero size — see docs/DESIGN.md "Typography scale": do not introduce
	   a new pixel value without adding it here first. Always wrap these in
	   clamp() so they stay responsive. */
	--text-4xl: 3.8125rem;/* 61px */
	--text-5xl: 4.75rem;  /* 76px */
	--text-6xl: 5.9375rem;/* 95px */

	/* Spacing scale — strict 8px base, see docs/DESIGN.md. */
	--space-1: 0.5rem;  /* 8px */
	--space-2: 1rem;    /* 16px */
	--space-3: 1.5rem;  /* 24px */
	--space-4: 2rem;    /* 32px */
	--space-5: 3rem;    /* 48px */
	--space-6: 4rem;    /* 64px */
	--space-7: 6rem;    /* 96px */
	/* Editorial section gaps — continues the same 8px-based scale past
	   --space-7 for the About page's generous section rhythm (docs/DESIGN.md
	   "Spacing scale"). Use only for section-level vertical rhythm. */
	--space-8: 8rem;    /* 128px */
	--space-9: 12rem;   /* 192px */
}

/* Shared scroll-reveal primitive — assets/js/motion.js observes every
   [data-reveal] element and adds .is-revealed the first time it enters the
   viewport (never re-hides on scroll back up). Gated behind
   html.js-motion-ready so that if the script never runs (blocked, errors,
   old browser without IntersectionObserver) content is never stuck at
   opacity:0 — the unmodified element is the no-JS fallback, same pattern
   service-accordion.js already uses for its panels. */
html.js-motion-ready [data-reveal] {
	opacity: 0;
	transform: translateY(26px);
	transition: opacity var(--motion-reveal) var(--motion-ease),
		transform var(--motion-reveal) var(--motion-ease);
}

html.js-motion-ready [data-reveal].is-revealed {
	opacity: 1;
	transform: translateY(0);
}

/* Scale-in variant for the one section (final CTA) that asks for
   "fade + scale" rather than the default fade + rise. Declared after the
   base rule above so it wins the transform property at equal (0,1,0)
   specificity — same ordering constraint main.css already documents for
   .filter-bar-plain vs .filter-bar. */
html.js-motion-ready [data-reveal-variant="scale"] {
	transform: scale(0.97);
}

html.js-motion-ready [data-reveal-variant="scale"].is-revealed {
	transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
	html.js-motion-ready [data-reveal] {
		opacity: 1;
		transform: none;
		transition: none;
	}
}

/* Structural boxes created at runtime by motion.js's line-splitter — see
   assets/js/motion.js splitIntoLines(). display:block is what turns the
   split spans into one box per visual line so each can be revealed on its
   own. Deliberately no overflow:hidden here: the translateY is applied to
   .reveal-line itself rather than to an inner wrapper, so clipping never
   produced the intended "slides up out of a mask" effect — it only sliced
   the descenders (g, y, p) off every heading it was applied to. */
.reveal-line {
	display: block;
}

.reveal-word {
	display: inline-block;
}

* {
	box-sizing: border-box;
}

body {
	margin: 0;
	font-family: var(--font-body);
	font-size: var(--text-sm);
	color: var(--color-text);
	background: var(--color-bg);
	line-height: 1.6;
}

h1, h2, h3, h4 {
	font-family: var(--font-heading);
	font-weight: 600;
	line-height: 1.2;
	margin: 0 0 1rem;
	color: var(--color-primary);
}

h1 {
	font-size: var(--text-2xl);
}

h2 {
	font-size: var(--text-xl);
}

h3 {
	font-size: var(--text-md);
}

h4 {
	font-size: var(--text-sm);
}

a {
	color: var(--color-accent);
}

a:visited {
	color: #6b4a2c;
}

img {
	max-width: 100%;
	display: block;
}

.container {
	max-width: var(--max-width);
	margin: 0 auto;
	padding: 0 var(--space-3);
}

/* Skip link: visually hidden until keyboard focus lands on it. */
.skip-link {
	position: absolute;
	top: -100px;
	left: 0.5rem;
	z-index: 1000;
	background: var(--color-primary);
	color: #fff;
	padding: 0.75rem 1.25rem;
	border-radius: 2px;
	text-decoration: none;
	font-weight: 500;
}

.skip-link:focus {
	top: 0.5rem;
}

:focus-visible {
	outline: 2px solid var(--color-secondary);
	outline-offset: 2px;
}

/* Sticky, blurred nav bar per the Stitch mockup refresh — full-width so the
   blur/shadow reach the viewport edges, with .site-header (a .container)
   nested inside for the centered content row. */
.site-header-bar {
	position: sticky;
	top: 0;
	z-index: 50;
	background: rgba(250, 247, 243, 0.85);
	backdrop-filter: blur(16px);
	-webkit-backdrop-filter: blur(16px);
	box-shadow: 0 1px 8px rgba(36, 26, 18, 0.05);
}

.site-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: var(--space-3) 0;
}

/* Logo: SVG house/flame mark + wordmark + tagline, ported from the v2
   mockup (docs/superpowers/design/igneo-v2/Main.dc.html). Rules live here
   as classes rather than the mockup's inline styles so header.php/footer.php
   stay markup-only, per the theme's existing convention. Shared between
   .site-header and .site-footer-brand (footer uses a white variant, see
   the Footer section below). */
.site-logo {
	display: flex;
	align-items: center;
	gap: var(--space-1);
	text-decoration: none;
}

.site-logo-icon {
	display: block;
	flex-shrink: 0;
	fill: none;
	stroke: var(--color-primary);
	stroke-width: 2.2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.site-logo-icon circle {
	fill: var(--color-primary);
	stroke: none;
}

.site-logo-text {
	display: flex;
	flex-direction: column;
	line-height: 1;
}

.site-logo-name {
	font-family: var(--font-heading);
	font-weight: 700;
	font-size: var(--text-md);
	letter-spacing: 0.05em;
	color: var(--color-primary);
}

/* No token below --text-xs (14px) exists for a micro-label this small —
   the mockup's tagline is 9px, well under the type scale's smallest step.
   Kept as a one-off literal rather than stretching the scale for a single
   decorative use. */
.site-logo-tagline {
	font-size: 0.5625rem;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--color-secondary);
	margin-top: 0.125rem;
}

.site-header-actions {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	flex-wrap: wrap;
}

.site-header nav ul {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2) var(--space-4);
	list-style: none;
	margin: 0;
	padding: 0;
}

/* One underline mechanism drives both hover and the active-page indicator,
   so "which page am I on" and "what am I hovering" read as the same kind
   of mark rather than two different treatments (a static border vs. a
   sliding line) competing on the same row. */
.site-header nav a {
	position: relative;
	text-decoration: none;
	color: var(--color-primary);
}

.site-header nav a::after {
	content: '';
	position: absolute;
	left: 0;
	bottom: -2px;
	width: 100%;
	height: 2px;
	background: var(--color-secondary);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--motion-fast) var(--motion-ease);
}

.site-header nav a:hover::after,
.site-header nav a:focus-visible::after,
.site-header nav a[aria-current="page"]::after {
	transform: scaleX(1);
}

.site-header nav a[aria-current="page"] {
	font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
	.site-header nav a::after {
		transition: none;
	}
}

/* Defensive: no submenu is configured in the primary menu today, but WP
   core renders standard .sub-menu markup the moment one is added — this
   activates itself with no further change then. */
.site-header nav .sub-menu {
	position: absolute;
	top: 100%;
	left: 0;
	min-width: 12rem;
	margin: 0;
	padding: var(--space-2);
	list-style: none;
	background: #fff;
	border: 1px solid var(--color-line);
	opacity: 0;
	visibility: hidden;
	transform: translateY(-6px);
	transition: opacity var(--motion-base) var(--motion-ease),
		transform var(--motion-base) var(--motion-ease),
		visibility 0s linear var(--motion-base);
}

.site-header nav li:hover > .sub-menu,
.site-header nav li:focus-within > .sub-menu {
	opacity: 1;
	visibility: visible;
	transform: translateY(0);
	transition: opacity var(--motion-base) var(--motion-ease),
		transform var(--motion-base) var(--motion-ease),
		visibility 0s linear 0s;
}

@media (prefers-reduced-motion: reduce) {
	.site-header nav .sub-menu {
		transition: none;
	}
}

/* Hamburger trigger — hidden on desktop, shown once the inline nav gives
   way to the overlay below .site-header-breakpoint. */
.menu-toggle {
	display: none;
	flex-direction: column;
	justify-content: center;
	gap: 5px;
	width: 2.25rem;
	height: 2.25rem;
	padding: 0;
	border: 0;
	background: none;
	cursor: pointer;
}

.menu-toggle-bar {
	display: block;
	width: 100%;
	height: 2px;
	background: var(--color-primary);
	border-radius: 1px;
}

/* Dropdown nav panel, matching the client reference: a half-screen sheet
   from the top of the viewport, off-white ground, a list with hairline
   dividers, and the same "Start a Project" CTA the desktop header carries.
   The rest of the page stays visible beneath it rather than a full-screen
   takeover. */
.mobile-menu {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	max-height: 60vh;
	z-index: 1000;
	display: flex;
	flex-direction: column;
	background: var(--color-bg);
	padding: var(--space-2);
	overflow-y: auto;
	box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
	border-bottom-left-radius: 12px;
	border-bottom-right-radius: 12px;
	opacity: 0;
	transform: translateY(-12px);
	transition: opacity var(--motion-base) var(--motion-ease),
		transform var(--motion-base) var(--motion-ease);
}

.mobile-menu.is-open {
	opacity: 1;
	transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
	.mobile-menu {
		transition: none;
	}
}

.mobile-menu[hidden] {
	display: none;
}

.mobile-menu-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding-bottom: var(--space-1);
	border-bottom: 1px solid var(--color-line);
}

.mobile-menu-header-actions {
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

.mobile-menu-header-actions .language-switcher {
	padding: 0.4rem 0.75rem;
	border: 1px solid var(--color-line);
	border-radius: 6px;
}

.mobile-menu-close {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 2.5rem;
	height: 2.5rem;
	border: 1px solid var(--color-line);
	border-radius: 6px;
	background: var(--color-bg-soft);
	color: var(--color-primary);
	cursor: pointer;
}

.mobile-menu-nav {
	padding: var(--space-1) 0;
}

.mobile-menu-nav ul {
	list-style: none;
	margin: 0;
	padding: 0;
}

.mobile-menu-nav li {
	border-bottom: 1px solid var(--color-line);
}

.mobile-menu-nav a {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	padding: 0.4rem 0;
	text-decoration: none;
	color: var(--color-primary);
	font-family: var(--font-heading);
	font-size: var(--text-md);
}

.mobile-menu-nav a::after {
	content: '\2197';
	margin-left: auto;
	font-size: var(--text-md);
	color: var(--color-neutral);
}

.mobile-menu-cta {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-1);
	margin-top: var(--space-1);
	padding: 0.6rem var(--space-3);
	background: var(--color-primary);
	color: #fff;
	text-decoration: none;
	font-weight: 600;
	font-size: var(--text-xs);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	border-radius: 6px;
}

.mobile-menu-cta svg {
	transition: transform var(--motion-fast) var(--motion-ease);
}

.mobile-menu-cta:hover svg {
	transform: translate(4px, -4px);
}

@media (prefers-reduced-motion: reduce) {
	.mobile-menu-cta svg {
		transition: none;
	}
}

body.mobile-menu-open {
	overflow: hidden;
}

/* Footer: 4-column grid (brand/Services/Our Project/Contact) + bottom bar,
   ported from the v2 mockup. Background is the same darkened-primary literal
   already used for .btn-primary:hover (#3a2b20) — no dedicated token exists
   for it, so the existing literal is reused rather than adding a new one. */
.site-footer {
	margin-top: var(--space-6);
	padding: var(--space-5) 0 var(--space-3);
	background: var(--color-deep-earth);
	color: rgba(255, 255, 255, 0.85);
}

.site-footer a {
	color: rgba(255, 255, 255, 0.75);
}

.site-footer-grid {
	display: grid;
	grid-template-columns: 1.4fr 1fr 1fr 1fr;
	gap: var(--space-4);
}

.site-footer-brand {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

/* White variant of the shared .site-logo mark for the dark footer bg. */
.site-footer-logo-icon,
.site-footer-logo-icon circle {
	stroke: #fff;
}

.site-footer-logo-icon circle {
	fill: #fff;
}

.site-footer-logo .site-logo-name {
	color: #fff;
}

.site-footer-logo .site-logo-tagline {
	color: rgba(255, 255, 255, 0.55);
}

.site-footer-desc {
	font-size: var(--text-xs);
	line-height: 1.7;
	color: rgba(255, 255, 255, 0.6);
	max-width: 260px;
}

.site-footer-col {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	font-size: var(--text-xs);
}

.site-footer-col-title {
	font-weight: 700;
	color: #fff;
	margin-bottom: 0.25rem;
}

.site-footer-col a,
.site-footer-contact-line {
	color: rgba(255, 255, 255, 0.65);
}

.site-footer-col a:hover {
	color: #fff;
}

.site-footer-bottom {
	margin-top: var(--space-4);
	padding-top: var(--space-2);
	border-top: 1px solid rgba(255, 255, 255, 0.12);
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-2);
	font-size: var(--text-xs);
	color: rgba(255, 255, 255, 0.5);
}

/* .social-links is also used standalone elsewhere; these rules only apply
   inside the footer bottom bar. */
.site-footer-bottom .social-links {
	display: flex;
	gap: var(--space-2);
	list-style: none;
	margin: 0;
	padding: 0;
}

.site-footer-bottom .social-links a {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	color: rgba(255, 255, 255, 0.65);
	transition: color var(--motion-fast) var(--motion-ease);
}

.site-footer-bottom .social-links-icon {
	display: inline-flex;
	transition: transform var(--motion-fast) var(--motion-ease);
}

.site-footer-bottom .social-links-icon svg {
	width: 16px;
	height: 16px;
}

.site-footer-bottom .social-links a:hover {
	color: #fff;
}

.site-footer-bottom .social-links a:hover .social-links-icon,
.site-footer-bottom .social-links a:focus-visible .social-links-icon {
	transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
	.site-footer-bottom .social-links a,
	.site-footer-bottom .social-links-icon {
		transition: none;
	}
}

/* Shared "no real photography yet" hatch-pattern background — every
   placeholder-photo element in the theme (service/project/news cards, the
   about-page evolution images, the construction teaser, the home services/projects
   grids) used to repeat this identical gradient pair; consolidated here so
   there's one place to change the placeholder look. Each selector below
   still owns its own sizing/layout rules in its own section. */
.service-accordion-media,
.home-projects-item-image,
.construction-teaser-placeholder,
.about-evolution-image,
.about-evolution-image-offset,
.about-partner-portrait,
.service-card-image,
.service-focus-image,
.service-project-tile-image,
.news-featured-image,
.news-card-image {
	/* Both layers in ONE background-image: the hatch first (drawn on top),
	   the tertiary wash underneath. Declaring `background:` and then
	   `background-image:` separately silently dropped the wash — the
	   shorthand's image is overwritten by the longhand that follows it,
	   which left every placeholder on the site as a 6%-opacity hatch on a
	   transparent background, i.e. almost invisible. */
	background-color: var(--color-tertiary);
	background-image:
		repeating-linear-gradient(45deg, rgba(74, 55, 40, 0.06) 0 2px, transparent 2px 22px),
		linear-gradient(135deg, var(--color-tertiary) 0%, #efe8dc 45%, var(--color-tertiary) 100%);
}

.card-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
	gap: var(--space-4);
}

/* Service card, per the client reference (2026-08-30): photo, service-type
   label, serif title, description, "Lihat Detail". No white fill and no
   border — the reference sets the cards straight on the page ground, which is
   what lets a row of three read as one editorial block rather than three
   boxes. The photo carries the card's edge instead. */
.service-card {
	background: transparent;
	border: 0;
	border-radius: 0;
}

.service-card a {
	display: block;
	color: inherit;
	text-decoration: none;
}

.service-card-body {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-1);
	padding-top: var(--space-3);
}

.service-card-label {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--color-clay);
}

.service-card h3 {
	font-size: var(--text-lg);
	margin: 0;
	color: var(--color-primary);
	transition: color 150ms ease-out;
}

.service-card a:hover h3 {
	color: var(--color-secondary);
}

.service-card p {
	margin: 0;
	color: var(--color-neutral);
	font-size: var(--text-sm);
	line-height: 1.6;
}

.service-card-link {
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	margin-top: var(--space-2);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--color-clay);
}

.service-card-arrow {
	transition: transform 200ms ease-out;
}

.service-card a:hover .service-card-arrow {
	transform: translateX(5px);
}

/* Same shape as the .contact-tile-arrow and .service-accordion-arrow
   overrides further down: kill both the slide and the easing so the arrow
   simply sits still on hover. */
@media (prefers-reduced-motion: reduce) {
	.service-card-arrow {
		transition: none;
	}

	.service-card a:hover .service-card-arrow {
		transform: none;
	}
}

/* Three across, matching the reference. The shared .card-grid auto-fills to
   four at this width, which is why the services listing overrides it rather
   than changing a grid the project and news archives also use. */
.service-grid {
	grid-template-columns: repeat(3, 1fr);
}

@media (max-width: 900px) {
	.service-grid {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (max-width: 560px) {
	.service-grid {
		grid-template-columns: 1fr;
	}
}


/* Below 780px the inline nav gives way entirely to the hamburger overlay
   (see .mobile-menu above) rather than wrapping or stacking in place. */
@media (max-width: 780px) {
	.site-header-nav,
	.site-header-actions {
		display: none;
	}

	.menu-toggle {
		display: flex;
	}
}

@media (max-width: 780px) {
	.site-footer-grid {
		grid-template-columns: 1fr 1fr;
	}
}

@media (max-width: 500px) {
	.site-footer-grid {
		grid-template-columns: 1fr;
	}

	.site-footer-bottom {
		flex-direction: column;
		align-items: flex-start;
	}
}

/* Icon-only floating action button — desktop and mobile share this one
   style, per the reference: a plain circle carrying the glyph, no label. */
.whatsapp-float {
	position: fixed;
	bottom: 1.5rem;
	right: 1.5rem;
	z-index: 999;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 3.25rem;
	height: 3.25rem;
	background: #25d366;
	color: #fff;
	border-radius: 50%;
	text-decoration: none;
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.language-switcher {
	display: flex;
	gap: 0.75rem;
	list-style: none;
	margin: 0;
	padding: 0;
	text-transform: uppercase;
	font-size: var(--text-xs);
}

.language-switcher .pll-parent-menu-item a,
.language-switcher a {
	text-decoration: none;
	color: inherit;
}

/* Hero: full-bleed image with the headline overlaid directly on the photo,
   bottom-anchored behind a dark scrim — per the 2026 Stitch mockup refresh
   (docs/DESIGN.md). Replaces the earlier "photo, then opaque copy block
   below it" treatment: .hero-copy is absolutely positioned against .hero
   (the nearest positioned ancestor), so it overlays .hero-image without
   needing markup changes. */
.hero {
	position: relative;
}

.hero-image {
	position: relative;
	overflow: hidden;
	min-height: 640px;
	background: linear-gradient(135deg, var(--color-tertiary) 0%, #efe8dc 45%, var(--color-tertiary) 100%);
}

/* Homepage hero: sized to the first screen so the copy block — which is
   anchored to the hero's bottom edge — and its buttons are visible without
   scrolling.

   The bug this fixes: .hero-image had only a min-height, so `height: 100%`
   on the <img> resolved to auto and the photo rendered at its full natural
   ratio (measured 1060px tall at a 1900px viewport). `object-fit: cover`
   never engaged, because there was no definite box for it to cover, and the
   copy sat 220px below the fold.

   --igneo-header-h tracks the sticky header, which is in normal flow above
   the hero. Its height is content-driven (the nav wraps at ~865px, then the
   bar stacks at 600px), so the values below are deliberately rounded *up* and
   their breakpoints set wide: over-estimating shortens the hero slightly,
   which keeps the buttons on screen, while under-estimating would push them
   off it. Scoped to .hero-home so the About hero keeps its own proportions. */
:root {
	--igneo-header-h: 108px;
}

@media (max-width: 900px) {
	:root {
		--igneo-header-h: 160px;
	}
}

@media (max-width: 600px) {
	:root {
		--igneo-header-h: 250px;
	}
}

.hero-home {
	--hero-fit: calc(100svh - var(--igneo-header-h));
}

.hero-home .hero-image {
	/* The clamp keeps the hero usable at both extremes: never so short that
	   the photo becomes a strip, never so tall on a large monitor that the
	   page below stops hinting at itself. */
	min-height: 0;
	height: clamp(420px, var(--hero-fit), 900px);
}

.hero-home.hero-h-tall .hero-image {
	height: clamp(400px, calc(var(--hero-fit) * 0.8), 760px);
}

.hero-home.hero-h-medium .hero-image {
	height: clamp(320px, calc(var(--hero-fit) * 0.6), 600px);
}

.hero-image::after {
	content: '';
	position: absolute;
	z-index: 0;
	inset: 0;
	background-image: repeating-linear-gradient(45deg, rgba(74, 55, 40, 0.06) 0 2px, transparent 2px 22px);
}

/* Dark scrim over a real photo so the overlaid headline stays legible —
   the hatch pattern above still shows through on the no-photo fallback
   since the scrim's gradient fades to transparent above 55% height. */
.hero-image::before {
	content: '';
	position: absolute;
	z-index: 2;
	inset: 0;
	/* The mid stop carries the whole copy block, not just the headline: the
	   eyebrow sits ~57% up the hero, where the previous 0.15-at-55% stop left
	   light tan text on a light background. Matches the Stitch comp's
	   from-80% / via-30% / to-transparent ramp. */
	background: linear-gradient(0deg, rgba(36, 26, 18, 0.88) 0%, rgba(36, 26, 18, 0.45) 50%, rgba(36, 26, 18, 0) 100%);
}

/* Two independently-animated properties compose into one transform:
   --hero-scale (the one-time cinematic zoom-out on load, eased over
   --motion-cinematic) and --hero-parallax (a continuous, un-eased scroll
   offset set every frame by motion.js). @property gives each one its own
   transition so a scroll-driven --hero-parallax change never inherits the
   zoom's 1.4s easing — without it, transitioning the `transform` shorthand
   directly would smear every parallax update across 1.4s of lag. */
@property --hero-scale {
	syntax: '<number>';
	inherits: false;
	initial-value: 1.045;
}

@property --hero-parallax {
	syntax: '<length>';
	inherits: false;
	initial-value: 0px;
}

.hero-image img {
	position: relative;
	z-index: 1;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.hero-home .hero-image img {
	transform: translateY(var(--hero-parallax)) scale(var(--hero-scale));
	transition: --hero-scale var(--motion-cinematic) var(--motion-ease);
	will-change: transform;
}

.hero-home.is-zoomed .hero-image img {
	--hero-scale: 1;
}

@media (prefers-reduced-motion: reduce) {
	.hero-home .hero-image img {
		--hero-scale: 1;
		--hero-parallax: 0px;
		transition: none;
	}
}

.hero-copy {
	position: absolute;
	z-index: 3;
	inset: auto 0 0 0;
	padding: var(--space-3) var(--space-3) var(--space-5);
}

.hero-copy h1 {
	color: #fff;
	font-weight: 600;
	font-size: clamp(var(--text-2xl), 5vw, 4rem);
	line-height: 1.05;
	max-width: 40rem;
}

.hero-copy p {
	color: rgba(255, 255, 255, 0.85);
	max-width: 32rem;
}

.breadcrumb-light,
.breadcrumb-light a {
	color: rgba(255, 255, 255, 0.65);
}

.breadcrumb-light .current {
	color: #fff;
}

/* Monochrome variant used by the About hero — a desaturated, sepia-toned
   architectural photo per the mockup, instead of the homepage hero's full-
   color image. */
.hero-mono .hero-image img {
	filter: grayscale(0.6) sepia(0.15) contrast(1.05);
}

.hero-mono .hero-copy h1 {
	text-transform: uppercase;
	/* Scaled up to the new display steps so the About hero reads at the
	   editorial size the Stitch mockup calls for (~96px at desktop) while
	   still collapsing cleanly on narrow screens. */
	font-size: clamp(var(--text-2xl), 8vw, var(--text-6xl));
	line-height: 1.02;
	letter-spacing: -0.01em;
	max-width: 14ch;
}

.hero-actions {
	display: flex;
	/* The two buttons together exceed a 390px phone's content width, and
	   without wrapping they pushed the whole document into horizontal scroll. */
	flex-wrap: wrap;
	gap: var(--space-2);
	margin-top: var(--space-3);
}

.hero-stats {
	display: flex;
	justify-content: flex-end;
	flex-wrap: wrap;
	gap: var(--space-4);
	padding: var(--space-2) 0 0;
	font-size: var(--text-xs);
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-neutral);
}

.eyebrow {
	display: inline-block;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--color-accent);
}

.eyebrow-light {
	color: var(--color-tertiary);
}

/* Buttons: componentized 2026-08-26 when the homepage hero needed real CTAs
   (previously "plain links only", see docs/DESIGN.md). Pill radius added
   2026-08-31 to match the contact-tile / filter-bar pill treatment — see
   docs/DESIGN.md Decisions Log. */
.btn {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	padding: var(--space-2) var(--space-3);
	border-radius: 999px;
	font-family: var(--font-body);
	font-weight: 600;
	font-size: var(--text-sm);
	letter-spacing: 0.02em;
	border: 1px solid transparent;
	text-decoration: none;
	white-space: nowrap;
	transition: background-color var(--motion-fast) var(--motion-ease),
		border-color var(--motion-fast) var(--motion-ease),
		color var(--motion-fast) var(--motion-ease);
}

.btn-primary {
	background: var(--color-primary);
	color: #fff;
}

.btn-primary:hover {
	background: #3a2b20;
}

.btn-outline {
	background: transparent;
	border-color: var(--color-primary);
	color: var(--color-primary);
}

.btn-outline:hover {
	background: var(--color-primary);
	color: #fff;
}

.btn-outline-light {
	background: transparent;
	border-color: rgba(255, 255, 255, 0.55);
	color: #fff;
}

.btn-outline-light:hover {
	border-color: #fff;
}

/* Accent CTA — the clay-terracotta fill used for the mockups' primary
   "Jelajahi Karya Kami" / "Get a Quote" buttons, distinct from the darker
   .btn-primary used for the site's own brand-primary actions. */
.btn-accent {
	background: var(--color-clay);
	color: #fff;
}

.btn-accent:hover {
	background: var(--color-clay-hover);
}

/* Size modifiers. These MUST stay below `.btn` — they were previously declared
   up in the header block, where `.btn`'s own padding, coming later in the file
   at equal specificity, silently overrode both of them. `.btn-sm` had been
   rendering at full size everywhere it was used (the header action, the
   project-card link) for as long as it has existed; found 2026-08-31 when
   `.btn-lg` refused to grow. */
.btn-sm {
	padding: var(--space-1) var(--space-2);
	font-size: var(--text-xs);
}

/* The other end of the same scale — the closing CTA's action, which the
   client's comp draws noticeably larger than an inline button. Only the box
   grows; the type size stays on the scale. */
.btn-lg {
	padding: var(--space-3) var(--space-4);
	gap: 0.75rem;
}

/* The horizontal value repeats .container's own padding rather than zeroing
   it. This element carries both classes, and the shorthand here overrides the
   one .container sets — which went unnoticed while the section's content had
   no background, because above 1200px the container's centring margin hides
   the missing gutter. Below that the section ran edge to edge. */
.home-services {
	padding: var(--space-7) var(--space-3);
}

.home-services-head {
	display: flex;
	/* Without wrapping, the headline block and the "Lihat Semua Layanan" link
	   together overflow a phone's content width and scroll the whole page
	   sideways. Same fix as .hero-actions. */
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: flex-end;
	gap: var(--space-3);
	margin-bottom: var(--space-5);
}

.home-services-head h2 {
	margin-top: var(--space-2);
	font-size: var(--text-2xl);
	max-width: 36rem;
}

/* Shared "quiet" underline link used for every homepage section's
   "view all" affordance — clay-terracotta text on a matching underline. */
.home-section-link {
	display: inline-block;
	flex-shrink: 0;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-clay);
	border-bottom: 1px solid var(--color-clay);
	padding-bottom: 0.125rem;
}

.home-section-link:hover {
	color: var(--color-clay-hover);
	border-color: var(--color-clay-hover);
}

/* Services accordion — a numbered list whose open entry becomes a tinted card
   with its photo alongside, per the client reference (2026-08-30). Shared by
   the homepage teaser, the Services archive and the Service Type archive.

   Each item is one grid in two configurations rather than two separate
   layouts. Open: heading, description and link stack down the left while the
   photo spans the full height on the right. Closed: a single row with the
   heading on the left and the link on the right. Keeping one DOM for both is
   what lets a panel open without the numbers or titles being re-rendered. */
.service-accordion {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
}

/* overflow:hidden is what lets service-accordion.js animate this element's
   height without the photo and description spilling out mid-transition.

   The transparent borders are load-bearing: the closed state draws hairline
   rules top and bottom, and if the open state had no border at all, every
   open/close would also change the box by 2px. Declaring them here means only
   the *colour* changes between states, so the height animation is the only
   thing moving. */
.service-accordion-item {
	position: relative;
	z-index: 0;
	display: grid;
	grid-template-columns: 1fr 1fr;
	background: var(--color-bg-soft);
	border-top: 1px solid transparent;
	border-bottom: 1px solid transparent;
	border-radius: 4px;
	overflow: hidden;
	transition: background-color 320ms ease, border-color 320ms ease,
		border-radius 320ms ease;
}

/* Closed rows are the quiet state: no fill, a hairline rule, and everything
   on one line. The reference draws them as separated list rows rather than as
   smaller versions of the open card. */
.service-accordion.is-enhanced .service-accordion-item:not(.is-open) {
	grid-template-columns: 1fr;
	background: transparent;
	border-radius: 0;
	border-top-color: var(--color-line);
	border-bottom-color: var(--color-line);
}

.service-accordion.is-enhanced .service-accordion-item:not(.is-open) .service-accordion-panel {
	display: none;
}

/* .service-accordion-media stays in the DOM (not display:none) when closed so
   its own clip-path + opacity rules can animate/render it for the open-reveal
   transition and the closed-row hover preview below — but that hiding only
   applies once .is-enhanced is present; the unqualified base rule below
   defaults to fully visible, which is what keeps every photo showing in the
   no-JS fallback the docblock above promises. clip-path/opacity alone don't
   remove a box from layout, though — left as a normal grid item, its
   min-height:320px would still reserve a second row under every closed row
   even while fully invisible. Taking it out of grid flow with
   position:absolute (item above gets position:relative + z-index:0 as its
   containing block/stacking context) removes that reserved space; z-index:-1
   keeps it behind the row's own heading text so a hover peek reads as a hint
   behind the title, not a cover over it. */
.service-accordion.is-enhanced .service-accordion-item:not(.is-open) .service-accordion-media {
	position: absolute;
	inset: 0;
	min-height: 0;
	z-index: -1;
	clip-path: inset(0 0 100% 0);
	opacity: 0;
}

/* Open: a centred column beside the photo. Closed: one row, link pushed
   right. Both states are this one element changing direction. */
.service-accordion-body {
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--space-3);
	padding: var(--space-6) var(--space-5);
	min-width: 0;
}

.service-accordion.is-enhanced .service-accordion-item:not(.is-open) .service-accordion-body {
	flex-direction: row;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-4);
}

.service-accordion-heading {
	margin: 0;
	font-size: inherit;
	font-weight: inherit;
	min-width: 0;
}

/* The whole heading is the toggle. It resets the button chrome rather than
   wrapping a button around text, so the hit area covers the full row. */
.service-accordion-trigger {
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
	width: 100%;
	padding: 0;
	border: 0;
	background: none;
	text-align: left;
	cursor: pointer;
	color: inherit;
	font: inherit;
}

.service-accordion-trigger:focus-visible {
	outline: 2px solid var(--color-clay);
	outline-offset: -2px;
}

.service-accordion-number {
	flex-shrink: 0;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.08em;
	color: var(--color-clay);
}

/* font-size is interpolable, so the title grows and shrinks with the panel
   rather than snapping between the two sizes at the start of the animation. */
.service-accordion-title {
	font-family: var(--font-heading);
	font-weight: 600;
	font-size: var(--text-xl);
	line-height: 1.15;
	color: var(--color-primary);
	transition: color 200ms ease-out, font-size 320ms ease;
}

.service-accordion.is-enhanced .service-accordion-item:not(.is-open) .service-accordion-title {
	font-size: var(--text-lg);
}

.service-accordion-trigger:hover .service-accordion-title {
	color: var(--color-secondary);
}

/* A "+" rotated 45deg reads as "x" — one SVG, one transform, no second icon
   needed for the closed vs. open state. */
.service-accordion-icon {
	display: inline-flex;
	flex-shrink: 0;
	align-self: center;
	margin-left: auto;
	color: var(--color-clay);
	transition: transform var(--motion-base) var(--motion-ease);
}

.service-accordion-item.is-open .service-accordion-icon {
	transform: rotate(45deg);
}

@media (prefers-reduced-motion: reduce) {
	.service-accordion-icon {
		transition: none;
	}
}

.service-accordion-panel {
	min-width: 0;
}

.service-accordion-description {
	margin: 0;
	max-width: 34rem;
	color: var(--color-neutral);
	font-size: var(--text-sm);
	line-height: 1.7;
}

.service-accordion-link {
	align-self: flex-start;
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	margin-top: var(--space-2);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	white-space: nowrap;
	color: var(--color-clay);
	text-decoration: none;
}

.service-accordion.is-enhanced .service-accordion-item:not(.is-open) .service-accordion-link {
	align-self: center;
	margin-top: 0;
}

.service-accordion-arrow {
	transition: transform 200ms ease-out;
}

.service-accordion-link:hover {
	color: var(--color-secondary);
}

.service-accordion-link:hover .service-accordion-arrow {
	transform: translateX(5px);
}

.service-accordion-link:focus-visible {
	outline: 2px solid var(--color-clay);
	outline-offset: 3px;
}

/* The photo fills its half edge to edge — the reference runs it to the card
   boundary with no inset, which is what makes the open entry read as a card
   rather than as a row with a picture in it. */
/* Height comes from a min-height, never from an aspect-ratio. A stretched
   grid item with an aspect-ratio derives its *width* from the row height, and
   that computed width overflowed the 1fr track and pushed the whole page into
   horizontal scroll. The image is absolutely positioned inside, so it fills
   whatever height the row settles at. */
/* Visible by default: without JS (no .is-enhanced on the ancestor) every
   panel is server-rendered open, per the docblock's no-JS contract, so every
   row's photo must show. Only the .is-enhanced-scoped rule above — the same
   gating the position:absolute layout fix also uses — hides a closed row's
   photo once the script is actually collapsing rows. */
.service-accordion-media {
	position: relative;
	align-self: stretch;
	min-height: 320px;
	display: flex;
	align-items: center;
	justify-content: center;
	clip-path: inset(0 0 0% 0);
	opacity: 1;
	transition: clip-path 380ms var(--motion-ease), opacity 380ms var(--motion-ease);
}

.service-accordion-media img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transform: scale(1.05);
	transition: transform 380ms var(--motion-ease);
}

.service-accordion-item.is-open .service-accordion-media img {
	transform: scale(1);
}

@media (prefers-reduced-motion: reduce) {
	.service-accordion-media,
	.service-accordion-media img {
		transition: none;
	}
}

/* A closed row's photo already exists in the DOM (service-accordion.php
   renders .service-accordion-media for every item, open or not); CSS alone
   hides it when closed. Hovering a closed row peeks a low-opacity sliver of
   it via the same clip-path technique, capped small so it reads as a hint,
   not a second open state. */
@media (hover: hover) {
	.service-accordion.is-enhanced .service-accordion-item:not(.is-open):hover .service-accordion-media {
		clip-path: inset(0 0 85% 0);
		opacity: 0.35;
	}
}

@media (prefers-reduced-motion: reduce) {
	.service-accordion.is-enhanced .service-accordion-item:not(.is-open):hover .service-accordion-media {
		clip-path: inset(0 0 100% 0);
		opacity: 0;
	}
}

.service-accordion-placeholder {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-primary);
	opacity: 0.6;
}

@media (max-width: 860px) {
	/* The photo stops earning half the width once that half is under ~300px;
	   below this the open entry stacks with the photo on top, which is also
	   the order the reference reads in at a narrow width. */
	.service-accordion-item {
		grid-template-columns: 1fr;
	}

	.service-accordion-media {
		order: -1;
		min-height: 0;
		aspect-ratio: 3 / 2;
	}

	.service-accordion-body {
		padding: var(--space-4);
	}

	.service-accordion-title {
		font-size: var(--text-lg);
	}
}

@media (max-width: 520px) {
	/* A closed row cannot fit a title and an uppercase link side by side at
	   this width, so the link drops beneath its heading. */
	.service-accordion.is-enhanced .service-accordion-item:not(.is-open) .service-accordion-body {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--space-2);
	}

	.service-accordion.is-enhanced .service-accordion-item:not(.is-open) .service-accordion-link {
		align-self: flex-start;
	}
}

/* service-accordion.js already skips the height animation here; these cover
   the CSS half, which would otherwise keep easing the fill, corners and title
   size after the script had stopped animating the box. */
@media (prefers-reduced-motion: reduce) {
	.service-accordion-item,
	.service-accordion-title,
	.service-accordion-arrow {
		transition: none;
	}

	.service-accordion-link:hover .service-accordion-arrow {
		transform: none;
	}
}

/* Featured Projects: up to five projects in one horizontal scroll-snap track,
   two equal cards per screen, each a full-bleed photo with its title, meta and
   year set over the image — per the client reference (2026-08-30), which
   replaced the earlier caption-under-photo treatment ported from
   design/home_igneo_architecture_updated/code.html.

   Equal widths rather than the previous 8/4 alternation: an overlay caption
   needs enough photo beneath it to stay legible, and a 4-of-12 card is too
   narrow to carry a project title at display size.

   Scrolling, snapping and touch swipe are all CSS — assets/js/project-slider.js
   only layers arrows, dots and the photo cross-fade on top, so the section
   still works with no JS.

   No --color-tertiary fill here: docs/DESIGN.md "Decoration & color rhythm"
   allows exactly one accent section per page, and on the homepage that is
   the construction teaser. */
.home-projects {
	padding: var(--space-7) 0;
	--projects-gutter: var(--space-4);
	--projects-card: calc((100% - var(--projects-gutter)) / 2);
}

.home-projects-head {
	margin-bottom: var(--space-6);
}

.home-projects-head h2 {
	margin-top: var(--space-2);
	font-size: var(--text-2xl);
	max-width: 36rem;
}

.home-projects-track {
	display: flex;
	gap: var(--projects-gutter);
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scroll-behavior: smooth;
	/* The cards carry their own captions; the extra line keeps a focus ring
	   on the track itself off the card edges. */
	padding-bottom: 2px;
	/* A scrollbar under an editorial image row reads as chrome, and the
	   arrows/dots below carry the affordance instead. */
	scrollbar-width: none;
	-ms-overflow-style: none;
}

.home-projects-track::-webkit-scrollbar {
	display: none;
}

.home-projects-track:focus-visible {
	outline: 2px solid var(--color-clay);
	outline-offset: 4px;
}

/* The card is the photo. A larger radius than the theme's usual 2px hairline
   is deliberate here and matches the reference: at this size a 2px corner on a
   full-bleed image reads as an un-styled rectangle rather than a card. */
.home-projects-item {
	position: relative;
	display: block;
	text-decoration: none;
	color: #fff;
	scroll-snap-align: start;
	flex: 0 0 var(--projects-card);
	aspect-ratio: 3 / 2;
	overflow: hidden;
	border-radius: 8px;
	background: var(--color-tertiary);
	isolation: isolate;
}

.home-projects-item:focus-visible {
	outline: 2px solid var(--color-clay);
	outline-offset: 3px;
}

.home-projects-item-media,
.project-tile-media {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* Every frame is stacked in the same box; the cross-fade is a swap of which
   one is .is-active. The slow fade is what keeps this ambient rather than a
   slideshow demanding attention. */
.project-frame {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0;
	transition: opacity 900ms ease-in-out, transform 700ms ease-out;
}

.project-frame.is-active {
	opacity: 1;
}

@media (hover: hover) {
	.home-projects-item:hover .project-frame.is-active,
	.project-tile-link:hover .project-frame.is-active {
		transform: scale(1.04);
	}
}

.project-frame-placeholder {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-primary);
	opacity: 0.6;
}

/* The scrim exists so the caption stays legible over an arbitrary photo —
   a bright sky behind white type is the failure case it prevents. It sits
   between the photo and the caption, hence the z-index pair. */
.home-projects-item-media::after,
.project-tile-media::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(
		to top,
		rgba(0, 0, 0, 0.78) 0%,
		rgba(0, 0, 0, 0.45) 22%,
		rgba(0, 0, 0, 0) 55%
	);
	z-index: 1;
}

.home-projects-item-caption {
	position: absolute;
	inset: auto 0 0 0;
	z-index: 2;
	padding: var(--space-4);
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	transform: translateY(4px);
	transition: transform var(--motion-fast) var(--motion-ease);
}

@media (hover: hover) {
	.home-projects-item:hover .home-projects-item-caption {
		transform: translateY(0);
	}
}

@media (prefers-reduced-motion: reduce) {
	.home-projects-item-caption {
		transition: none;
	}
}

/* Sans, not the heading serif: the reference sets project names in the same
   uppercase grotesk as the meta line beneath them, which reads as a caption
   on a photograph rather than as a page heading. */
.home-projects-item-caption h3 {
	font-family: var(--font-body);
	font-weight: 400;
	font-size: clamp(var(--text-md), 2.2vw, var(--text-xl));
	line-height: 1.1;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: #fff;
	margin: 0;
	text-wrap: balance;
}

.home-projects-item-line {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-2);
}

/* A long "type · location" pair would otherwise push into the year on a
   narrow card; truncating the meta keeps the year pinned to the right edge
   where the reference puts it. */
.home-projects-item-meta {
	font-size: var(--text-xs);
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.75);
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.home-projects-item-year {
	font-size: var(--text-xs);
	font-weight: 500;
	letter-spacing: 0.08em;
	color: rgba(255, 255, 255, 0.75);
	flex-shrink: 0;
}

/* Slider controls. Dots on the left, arrows on the right, sharing one
   baseline under the track — the section already carries a centred "Lihat
   Seluruh Portofolio" link below, so centring these too would stack three
   centred things in a row. */
.home-projects-nav {
	margin-top: var(--space-4);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
}

.home-projects-nav[hidden] {
	display: none;
}

.home-projects-dots {
	display: flex;
	gap: var(--space-1);
}

.home-projects-dot {
	width: 40px;
	height: 2px;
	padding: 0;
	border: 0;
	/* A 2px target is unhittable, so the visible rule sits inside a taller
	   transparent hit area rather than being made bigger. */
	background: transparent;
	border-top: 8px solid transparent;
	border-bottom: 8px solid transparent;
	background-clip: padding-box;
	box-sizing: content-box;
	cursor: pointer;
	position: relative;
}

.home-projects-dot::after {
	content: '';
	position: absolute;
	inset: 0;
	background: var(--color-primary);
	opacity: 0.2;
	transition: opacity 200ms ease-out;
}

.home-projects-dot:hover::after {
	opacity: 0.45;
}

.home-projects-dot[aria-current='true']::after {
	background: var(--color-clay);
	opacity: 1;
}

.home-projects-dot:focus-visible {
	outline: 2px solid var(--color-clay);
	outline-offset: 2px;
}

.home-projects-arrows {
	display: flex;
	gap: var(--space-1);
}

.home-projects-arrow {
	width: 44px;
	height: 44px;
	display: flex;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--color-line);
	border-radius: 50%;
	background: transparent;
	color: var(--color-primary);
	font-size: var(--text-sm);
	line-height: 1;
	cursor: pointer;
	transition: background-color 200ms ease-out, border-color 200ms ease-out, color 200ms ease-out;
}

.home-projects-arrow:hover:not(:disabled) {
	background: var(--color-clay);
	border-color: var(--color-clay);
	color: #fff;
}

.home-projects-arrow:focus-visible {
	outline: 2px solid var(--color-clay);
	outline-offset: 2px;
}

.home-projects-arrow:disabled {
	opacity: 0.3;
	cursor: default;
}

.home-projects-foot {
	margin-top: var(--space-6);
	display: flex;
	justify-content: center;
}

/* Someone who has asked for less motion still gets the slider — they just
   get an instant jump instead of a smooth glide. */
@media (prefers-reduced-motion: reduce) {
	.home-projects-track {
		scroll-behavior: auto;
	}

	.project-frame,
	.home-projects-arrow,
	.home-projects-dot::after {
		transition: none;
	}

	.home-projects-item:hover .project-frame.is-active,
	.project-tile-link:hover .project-frame.is-active {
		transform: none;
	}
}

@media (max-width: 780px) {
	/* One card at a time with the next one peeking, so the track reads as
	   swipeable without any control being visible. Two-up does not survive a
	   phone: a half-width card leaves no room under the title for the meta
	   line, which is the whole point of the overlay treatment. */
	.home-projects-item {
		flex-basis: 82%;
		/* A 3:2 card is only ~187px tall at phone width — barely more than the
		   caption sitting on it. The taller crop gives the photograph room to
		   register as the subject rather than as a strip behind the text. */
		aspect-ratio: 4 / 3;
	}

	.home-projects-item-caption {
		padding: var(--space-3);
	}

	.home-projects-nav {
		margin-top: var(--space-3);
	}
}

/* Our Clients: an infinite logo carousel — client logos, each carrying that
   client's testimonial beneath it, drifting continuously and looping without a
   visible seam. The reference is a logo-only carousel; the quotes are the
   addition asked for, which is why the items are tall cards rather than a bare
   row of marks.

   No JavaScript: home-clients.php lays several copies of the client set end to
   end and the track translates by exactly one set, so when the animation
   restarts the copy behind is already in the identical position. Deliberately
   NOT the Featured Projects slider — that one stays a manual, arrowed,
   snap-scrolling track, and this one has no controls at all. */
.home-clients {
	padding: var(--space-7) 0;
	--clients-gutter: var(--space-4);
	/* Four across at desktop, matching the reference's row of four. Measured
	   against the container width the heading uses, not the full-bleed
	   viewport, so the cards keep the page's column rhythm. */
	--clients-card: calc((min(100vw, var(--max-width)) - 2 * var(--gutter) - 3 * var(--clients-gutter)) / 4);
}

.home-clients-head {
	text-align: center;
	margin-bottom: var(--space-6);
}

.home-clients-head h2 {
	font-size: var(--text-2xl);
	margin: 0;
}

.home-clients-head p {
	color: var(--color-neutral);
	max-width: 42rem;
	margin: var(--space-2) auto 0;
}

/* Full-bleed window on the moving track. overflow:hidden rather than auto —
   the strip is not something to scroll, it moves on its own. */
.home-clients-viewport {
	overflow: hidden;
	/* The track is wider than the page; without this the animation's
	   off-screen half would extend the document's scrollable width. */
	max-width: 100%;
	/* Soften both ends so cards enter and leave rather than being sliced off
	   at the viewport edge — that hard cut is what makes a marquee read as a
	   clipped box instead of a strip running past the screen. Unsupported
	   browsers simply get the hard edge. */
	-webkit-mask-image: linear-gradient(
		to right,
		transparent 0,
		#000 6%,
		#000 94%,
		transparent 100%
	);
	mask-image: linear-gradient(
		to right,
		transparent 0,
		#000 6%,
		#000 94%,
		transparent 100%
	);
}

.home-clients-track {
	display: flex;
	gap: var(--clients-gutter);
	/* Sized by content, not by the viewport, so every copy of the set keeps
	   its full width instead of being squeezed to fit. */
	width: max-content;
	animation: igneo-marquee var(--clients-duration, 36s) linear infinite;
	/* The animation only ever moves this element; promoting it up front stops
	   the browser re-rasterising a very wide box on every frame. */
	will-change: transform;
}

/* Pause so a testimonial can actually be read. Hover covers pointers,
   focus-within covers the keyboard reaching a client's website link. */
.home-clients-viewport:hover .home-clients-track,
.home-clients-viewport:focus-within .home-clients-track {
	animation-play-state: paused;
}

/* Shift by exactly one set, which is where the seam becomes invisible.
   The track holds N sets separated by N-1 gaps, so its width is
   N*set + (N-1)*gutter, and 100%/N is one gutter/N short of a whole set —
   hence the second term. Getting this wrong by even a gutter makes the loop
   visibly jump every cycle. */
@keyframes igneo-marquee {
	from {
		transform: translate3d(0, 0, 0);
	}

	to {
		transform: translate3d(
			calc(-100% / var(--clients-sets, 2) - var(--clients-gutter) / var(--clients-sets, 2)),
			0,
			0
		);
	}
}

.home-clients-item {
	flex: 0 0 var(--clients-card);
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	margin: 0;
	gap: var(--space-3);
}

/* A fixed logo box, not a fixed image size: client logos arrive at wildly
   different aspect ratios, and only a shared box makes them sit on one
   optical line. object-fit keeps each mark inside it without distortion. */
.home-clients-logo {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 120px;
}

.home-clients-logo img {
	max-width: 78%;
	max-height: 100%;
	width: auto;
	height: auto;
	object-fit: contain;
}

/* Fallback when a client has no logo uploaded — their name set as a mark,
   rather than a broken-image gap in the row. */
.home-clients-name {
	font-family: var(--font-heading);
	font-size: var(--text-md);
	color: var(--color-primary);
	opacity: 0.75;
}

/* Stretched-link: the whole logo box becomes the click target when a website
   is set, without nesting the quote inside an anchor. */
.home-clients-link::after {
	content: '';
	position: absolute;
	inset: 0;
}

.home-clients-link:focus-visible::after {
	outline: 2px solid var(--color-clay);
	outline-offset: 4px;
}

.home-clients-quote {
	margin: 0;
}

.home-clients-quote p {
	font-family: var(--font-heading);
	font-style: italic;
	font-size: var(--text-sm);
	line-height: 1.6;
	color: var(--color-primary);
	margin: 0;
}

/* Typographic quotes in CSS so the editor never has to type them, and a
   client who wraps their own quote in marks does not end up double-quoted. */
.home-clients-quote p::before {
	content: '\201C';
}

.home-clients-quote p::after {
	content: '\201D';
}

/* margin-top:auto pins the attribution to the bottom of the card. The track
   is a row flex, so cards already stretch to the tallest one; without this the
   attributions followed their own quote and landed at four different heights,
   which read as a broken row rather than varying quote lengths. */
.home-clients-attribution {
	margin-top: auto;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-clay);
}

@media (max-width: 1024px) {
	.home-clients {
		--clients-card: calc((min(100vw, var(--max-width)) - 2 * var(--gutter) - var(--clients-gutter)) / 2);
	}
}

@media (max-width: 640px) {
	/* A bit over one card per screen, so a second is always partly in frame
	   and the strip reads as continuous rather than as one card blinking to
	   the next. Four across would leave each logo about 70px wide, below the
	   point where a wordmark is legible at all. */
	.home-clients {
		--clients-card: 74vw;
		--clients-gutter: var(--space-3);
	}

	.home-clients-logo {
		height: 96px;
	}
}

/* Motion that starts on its own and never stops is exactly what this setting
   exists to switch off. The track then sits still, and because it is wider
   than the window the viewport becomes scrollable so every client is still
   reachable — the content is never trapped off-screen. */
@media (prefers-reduced-motion: reduce) {
	.home-clients-track {
		animation: none;
	}

	.home-clients-viewport {
		overflow-x: auto;
	}
}

/* Contact: dark panel with an editorial headline over a grid of contact
   tiles, per the client reference (2026-08-30). Replaced the "Mulai
   Percakapan." copy-plus-CF7-form layout below, whose rules are kept for now
   in case the form is reinstated — nothing in the templates uses them.

   The reference's ground is a neutral near-black; this uses the palette's
   --color-deep-earth instead so the section still belongs to the warm brown
   family the footer and CTA banners established. */
.contact-links {
	position: relative;
	overflow: hidden;
	padding: var(--space-7) 0;
	background: var(--color-deep-earth);
	color: #fff;
}

/* Ambient warmth, not a spotlight: capped at 0.15 peak opacity and blurred
   heavily so it reads as a slow shift in the ground itself. Sits behind
   .container via z-index, not in normal flow, so it never affects layout. */
.contact-links::before {
	content: '';
	position: absolute;
	top: -20%;
	left: 50%;
	width: 640px;
	height: 640px;
	margin-left: -320px;
	background: radial-gradient(circle, var(--color-clay) 0%, transparent 70%);
	opacity: 0.15;
	filter: blur(60px);
	pointer-events: none;
	z-index: 0;
	animation: igneo-orb-drift 10s var(--motion-ease) infinite alternate;
}

.contact-links > .container {
	position: relative;
	z-index: 1;
}

@keyframes igneo-orb-drift {
	from {
		transform: translate(-40px, -20px) scale(1);
	}
	to {
		transform: translate(40px, 20px) scale(1.08);
	}
}

@media (prefers-reduced-motion: reduce) {
	.contact-links::before {
		animation: none;
	}
}

.contact-links-eyebrow {
	color: rgba(255, 255, 255, 0.55);
}

/* Sans, not the heading serif, and large: the reference sets this as one
   long light-weight sentence running most of the panel width. Open Sauce is
   vendored at 400 as its lightest cut, which is where this sits. */
.contact-links-heading {
	font-family: var(--font-body);
	font-weight: 400;
	font-size: clamp(var(--text-2xl), 6.5vw, var(--text-5xl));
	line-height: 1.02;
	letter-spacing: -0.02em;
	color: #fff;
	margin: var(--space-3) 0 var(--space-6);
	max-width: 18ch;
}

/* Flex-wrap, not a grid: in the revised reference (2026-08-30) each pill is
   only as wide as its own content and the row centres whatever fits, which is
   why "TikTok" and the address end up alone on a second line. A grid would
   have to be told that; wrapping works it out, and adding or removing a
   contact detail rearranges the rows on its own with no spans to maintain. */
.contact-tiles {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-3);
}

/* Outline pill, no fill. The previous revision recessed the tiles because a
   white veil washes out on the mid-brown --color-deep-earth; the revised
   reference solves the same problem differently, by dropping the fill
   entirely and letting a hairline ring carry the shape. */
/* The internal measures are tuned so three pills clear the 1152px container
   and the set wraps 3-then-2 as the reference does. At the previous padding
   and icon size they totalled ~2060px and broke into three ragged rows. */
.contact-tile {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
	min-height: 92px;
	border: 1px solid rgba(255, 255, 255, 0.25);
	border-radius: 999px;
	background: transparent;
	text-decoration: none;
	color: #fff;
	transition: background-color 200ms ease-out, border-color 200ms ease-out;
}

.contact-tile:hover {
	background: rgba(255, 255, 255, 0.05);
	border-color: rgba(255, 255, 255, 0.55);
}

.contact-tile:focus-visible {
	outline: 2px solid var(--color-clay);
	outline-offset: 3px;
}

.contact-tile-mark {
	display: flex;
	flex-shrink: 0;
	color: #fff;
}

.contact-tile-mark svg {
	width: 26px;
	height: 26px;
}

.contact-tile-text {
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
	min-width: 0;
}

.contact-tile-label {
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.13em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.6);
	white-space: nowrap;
}

/* The pill grows to fit its value, so the cap is what keeps a long street
   address from stretching one pill across the whole row — the reference
   truncates it too. */
.contact-tile-value {
	font-size: var(--text-md);
	line-height: 1.2;
	color: #fff;
	max-width: 22ch;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* The arrow's own ring. Sits at the pill's right end with the padding above
   tightened on that side so the circle is optically inset, not crowded. */
.contact-tile-go {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	margin-left: var(--space-1);
	border: 1px solid rgba(255, 255, 255, 0.25);
	border-radius: 50%;
	color: rgba(255, 255, 255, 0.7);
	transition: border-color 200ms ease-out, color 200ms ease-out, background-color 200ms ease-out;
}

.contact-tile:hover .contact-tile-go {
	border-color: rgba(255, 255, 255, 0.6);
	color: #fff;
}

.contact-tile-arrow {
	transition: transform 200ms ease-out;
}

.contact-tile:hover .contact-tile-arrow {
	transform: translate(4px, -4px);
}

.contact-links-empty {
	color: rgba(255, 255, 255, 0.65);
	max-width: 42rem;
}

@media (max-width: 780px) {
	.contact-links-heading {
		max-width: none;
	}
}

/* One pill per row, and only on a phone. Content-width pills at this size are
   each a different length and the centred wrap reads as a ragged stack; full
   width also gives the long address room before it truncates.

   Deliberately 600px rather than the 780px block above: at 768 this stretched
   a single pill to 720px, which looks like a broken button rather than a
   contact chip. Between 600 and 780 the pills keep their own width and wrap. */
@media (max-width: 600px) {
	.contact-tiles {
		gap: var(--space-2);
	}

	.contact-tile {
		width: 100%;
		min-height: 84px;
		padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
	}

	.contact-tile-text {
		flex: 1;
	}

	.contact-tile-value {
		font-size: var(--text-sm);
		max-width: none;
	}

	.contact-tile-go {
		width: 40px;
		height: 40px;
		margin-left: var(--space-1);
	}
}

@media (prefers-reduced-motion: reduce) {
	.contact-tile,
	.contact-tile-arrow {
		transition: none;
	}

	.contact-tile:hover .contact-tile-arrow {
		transform: none;
	}
}

/* Contact: dark "deep earth" panel, per the Stitch mockup's "Mulai
   Percakapan" section — copy on the left, the existing CF7 form re-themed
   for dark ground on the right. Unused since the contact section became
   .contact-links above; kept so reinstating the form is a template change
   rather than a restyle. */
.contact-cta {
	padding: var(--space-7) 0;
	background: var(--color-deep-earth);
	color: #fff;
}

.contact-cta-inner {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: var(--space-6);
}

.contact-cta-copy h2 {
	color: #fff;
	font-size: clamp(var(--text-xl), 4vw, 3.5rem);
	line-height: 1.1;
}

.contact-cta-copy p {
	color: rgba(255, 255, 255, 0.7);
	max-width: 28rem;
	line-height: 1.7;
}

.contact-cta-form .wpcf7-form label {
	display: block;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.55);
	margin-bottom: var(--space-3);
}

.contact-cta-form .wpcf7-form input[type="text"],
.contact-cta-form .wpcf7-form input[type="email"],
.contact-cta-form .wpcf7-form input[type="tel"],
.contact-cta-form .wpcf7-form textarea {
	display: block;
	width: 100%;
	background: transparent;
	border: none;
	border-bottom: 1px solid rgba(255, 255, 255, 0.25);
	color: #fff;
	font-family: var(--font-body);
	font-size: var(--text-sm);
	padding: 0.5rem 0;
	margin-top: 0.375rem;
	border-radius: 0;
	resize: vertical;
}

.contact-cta-form .wpcf7-form input:focus,
.contact-cta-form .wpcf7-form textarea:focus {
	outline: none;
	border-bottom-color: var(--color-clay);
}

.contact-cta-form .wpcf7-form input[type="submit"] {
	align-self: flex-start;
	background: transparent;
	border: none;
	border-bottom: 1px solid var(--color-clay);
	color: var(--color-clay);
	font-family: var(--font-body);
	font-weight: 600;
	font-size: var(--text-xs);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	padding: 0 0 0.25rem;
	margin-top: var(--space-2);
	cursor: pointer;
	transition: color 150ms ease-out, border-color 150ms ease-out;
}

.contact-cta-form .wpcf7-form input[type="submit"]:hover {
	color: #fff;
	border-color: #fff;
}

@media (max-width: 780px) {
	.contact-cta-inner {
		grid-template-columns: 1fr;
	}
}

/* Badge: small uppercase pill, used for the Construction teaser's
   "Expanding Soon" flag. Solid primary fill on the tertiary card per
   docs/DESIGN.md — no dashed border, the badge alone signals "upcoming". */
.badge {
	display: inline-block;
	width: fit-content;
	padding: 0.25rem var(--space-2);
	border-radius: 999px;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
}

.badge-primary {
	background: var(--color-primary);
	color: #fff;
}

/* Construction teaser: the homepage's one --color-tertiary accent section
   (docs/DESIGN.md "Decoration & color rhythm" — exactly one per page, solid
   fill, never more than one in a row). Sits last on the homepage, directly
   above the footer, so the forward-looking note closes the page rather than
   interrupting the services → portfolio → contact run. Symmetric padding
   keeps it from crowding the dark contact panel above it — the short bottom
   value plus .site-footer's own --space-6 margin lands on the same 96px. */
.construction-teaser {
	padding: var(--space-7) 0 var(--space-4);
}

.construction-teaser-card {
	border-radius: 2px;
	padding: var(--space-4);
	display: grid;
	grid-template-columns: 1.1fr 1fr;
	gap: var(--space-4);
	align-items: center;
	background: var(--color-tertiary);
}

.construction-teaser-copy {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
}

.construction-teaser-copy h2 {
	font-size: var(--text-xl);
	margin: 0;
}

.construction-teaser-copy p {
	color: var(--color-primary);
	opacity: 0.8;
	font-size: var(--text-sm);
	line-height: 1.6;
	max-width: 460px;
	margin: 0;
}

/* Placeholder art: same "no real photography yet" treatment used
   elsewhere in this file, reused rather than reinventing a new pattern. */
.construction-teaser-placeholder {
	height: 220px;
	border-radius: 2px;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-primary);
	opacity: 0.6;
}

@media (max-width: 780px) {
	.construction-teaser-card {
		grid-template-columns: 1fr;
	}
}

/* Filter pills — outline buttons with a solid-fill active state, per the
   Services/Projects archive mockups (replaces the earlier underlined-text
   filter style). Only used on the Services/Projects archive and taxonomy
   templates.

   Slimmed on 2026-08-30 at the client's request — the filters read as too
   rigid: squared corners, heavy padding and a 600 weight gave a row of small
   category links the visual force of primary buttons, so they competed with
   the project grid they exist to filter. Now a lighter, tighter pill; the
   type size is unchanged because --text-xs is the floor of the scale. */
.filter-bar {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-1);
	list-style: none;
	margin: var(--space-2) 0 var(--space-3);
	padding: 0;
}

.filter-bar a {
	display: inline-flex;
	align-items: center;
	text-decoration: none;
	color: var(--color-primary);
	font-size: var(--text-xs);
	font-weight: 500;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	padding: 0.3rem var(--space-2);
	border: 1px solid var(--color-line);
	border-radius: 999px;
	transition: color 150ms ease-out, border-color 150ms ease-out, background-color 150ms ease-out;
}

.filter-bar a:hover {
	border-color: var(--color-primary);
}

.filter-bar a.is-active,
.filter-bar a[aria-current="page"] {
	background: var(--color-deep-earth);
	border-color: var(--color-deep-earth);
	color: #fff;
}

/* Plain variant: uppercase text with an underline on the current view, per
   the 2026-08-30 Services reference. A modifier rather than a replacement,
   because the Projects archive still uses the pill treatment docs/DESIGN.md
   specifies and was not part of that revision.

   These rules must sit *after* the .filter-bar ones above: both are (0,2,0),
   so source order is what decides, and placed earlier in the file the
   modifier silently lost and the pills stayed. */
.filter-bar-plain {
	gap: var(--space-4);
}

.filter-bar-plain a {
	padding: 0 0 var(--space-1);
	border: 0;
	border-radius: 0;
	border-bottom: 1px solid transparent;
	letter-spacing: 0.12em;
	color: var(--color-neutral);
}

.filter-bar-plain a:hover {
	color: var(--color-primary);
	border-bottom-color: var(--color-line);
}

.filter-bar-plain a.is-active,
.filter-bar-plain a[aria-current="page"] {
	background: transparent;
	color: var(--color-primary);
	border-bottom-color: var(--color-primary);
}

/* Empty states: warmth + a way back, never a bare dead end. */
.empty-state {
	grid-column: 1 / -1;
	text-align: left;
	padding: var(--space-4);
	border: 1px dashed var(--color-line);
	border-radius: 2px;
	background: #fff;
	max-width: 480px;
}

.empty-state p {
	color: var(--color-neutral);
	margin: 0 0 1rem;
}

.empty-state a {
	font-weight: 600;
	color: var(--color-primary);
}

/* Contact Form 7 states */
.contact-cta .wpcf7-response-output {
	border-radius: 2px;
	padding: var(--space-2) var(--space-3);
	font-size: var(--text-sm);
	border: 1px solid transparent;
}

.contact-cta form.sent .wpcf7-response-output {
	background: var(--color-tertiary);
	color: var(--color-primary);
	border-color: var(--color-secondary);
}

.contact-cta form.failed .wpcf7-response-output,
.contact-cta form.aborted .wpcf7-response-output,
.contact-cta form.spam .wpcf7-response-output,
.contact-cta form.invalid .wpcf7-response-output {
	background: #fbeceb;
	color: var(--color-error);
	border-color: var(--color-error);
}

.contact-cta .wpcf7-not-valid {
	border-color: var(--color-error) !important;
}

.contact-cta .wpcf7-not-valid-tip {
	color: var(--color-error);
	font-size: var(--text-xs);
}

/* Breadcrumbs */
.breadcrumb {
	font-size: var(--text-xs);
	color: var(--color-neutral);
	margin-bottom: 1rem;
}

.breadcrumb a {
	color: var(--color-neutral);
}

.breadcrumb .current {
	color: var(--color-primary);
	font-weight: 500;
}

/* Pagination (WP core paginate_links()/the_posts_pagination() markup) */
.page-numbers {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-2);
	list-style: none;
	margin: var(--space-5) 0 0;
	padding: 0;
	font-size: var(--text-xs);
}

a.page-numbers,
span.page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 2.25rem;
	height: 2.25rem;
	padding: 0 var(--space-2);
	border: 1px solid var(--color-line);
	border-radius: 2px;
	color: var(--color-primary);
	text-decoration: none;
	transition: border-color 150ms ease-out, background-color 150ms ease-out;
}

a.page-numbers:hover {
	border-color: var(--color-secondary);
}

span.page-numbers.current {
	background: var(--color-primary);
	border-color: var(--color-primary);
	color: #fff;
}

.page-numbers.dots {
	border: none;
	color: var(--color-neutral);
}

/* News & Update's own pagination row (igneo_news_pagination()): Prev/Next
   pinned to the outer edges with the page numbers centered, rather than
   the_posts_pagination()'s single inline list. Reuses a.page-numbers /
   span.page-numbers.current for the numbers themselves. */
.news-pagination {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-3);
	margin-top: var(--space-5);
}

.news-pagination-numbers {
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

.news-pagination-edge {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--color-primary);
	text-decoration: none;
}

.news-pagination-edge:hover {
	color: var(--color-secondary);
}

.news-pagination-edge.is-disabled {
	color: var(--color-neutral);
	opacity: 0.5;
	pointer-events: none;
}

/* About page (page-about-us.php) — rebuilt 2026-08-27 against the Stitch
   "about_us_igneo_architecture_updated" export. Structure/composition only:
   the numbered section markers, the 5/7 asymmetric split, the staggered dark
   Core Values grid and the sticky Leadership column are ported; the Stitch
   palette, its Tailwind/Google-Fonts/Material-Symbols CDN dependencies and the
   scroll-triggered stat count-up are deliberately NOT (brand tokens are kept,
   and docs/DESIGN.md's Motion section rules out scroll-linked effects). Reuses
   .container, .hero/.hero-mono, .eyebrow, .btn* and .cta-banner from elsewhere
   in this file. */

/* Numbered section marker + hairline rule that fills the remaining width —
   the signature editorial device of this design, shared by all three
   sections; .section-marker-light is the on-dark variant. */
.section-marker {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	margin: 0 0 var(--space-3);
}

.section-marker-label {
	flex: 0 0 auto;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--color-clay);
}

.section-marker-rule {
	flex: 1 1 auto;
	height: 1px;
	background: var(--color-line);
}

.section-marker-light .section-marker-label {
	color: var(--color-tertiary);
}

.section-marker-light .section-marker-rule {
	background: rgba(255, 255, 255, 0.18);
}

/* 01 / The Evolution — asymmetric split, 5 columns of copy against 7 of
   image, matching the Stitch grid. */
.about-evolution {
	padding: var(--space-9) 0;
}

.about-evolution-inner {
	display: grid;
	grid-template-columns: 5fr 7fr;
	gap: var(--space-7);
	align-items: center;
}

.about-evolution-copy h2 {
	text-transform: uppercase;
	font-size: clamp(var(--text-xl), 3.4vw, var(--text-2xl));
	margin: 0 0 var(--space-3);
}

.about-evolution-body p {
	color: var(--color-neutral);
	line-height: 1.75;
	margin: 0 0 var(--space-2);
}

/* Inline stats — they sit inside this section above a hairline rather than in
   the separate full-width dark strip the previous build used. Numbers come
   from the stat_projects/stat_years settings and are rendered statically:
   no count-up animation (docs/DESIGN.md, Motion). */
.about-evolution-stats {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-5);
	margin-top: var(--space-4);
	padding-top: var(--space-4);
	border-top: 1px solid var(--color-line);
}

.about-stat-number {
	display: block;
	font-family: var(--font-heading);
	font-size: clamp(var(--text-2xl), 5vw, var(--text-4xl));
	line-height: 1.05;
	color: var(--color-clay);
}

.about-stat-label {
	display: block;
	margin-top: var(--space-1);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--color-neutral);
}

/* Large portrait image with a smaller one overlapping its bottom-left corner
   (surface-colored border, per Stitch). Both take the shared "no real
   photography yet" hatch-pattern placeholder defined further up this file. */
.about-evolution-media {
	position: relative;
}

.about-evolution-image {
	position: relative;
	aspect-ratio: 4 / 5;
	border-radius: 2px;
	overflow: hidden;
}

/* Real photo (ACF `evolution_image`) covers the hatch placeholder entirely —
   same "image on top of the placeholder box" approach as
   .service-card-image. The caption stays above it (absolutely positioned). */
.about-evolution-image img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Scrim behind the caption. Without it the caption sits directly on the
   photo the client uploads and becomes unreadable over anything light or
   busy — the Stitch comp handles this the same way, with a dark gradient
   and light text. Rendered unconditionally so it reads correctly over both
   a real photo and the hatch placeholder. */
.about-evolution-image::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	/* Fixed height, not a percentage: at 45% of a ~770px-tall image the scrim
	   covered a third of the frame and read as a grey smear rather than a
	   caption backing. It only needs to cover the caption. */
	height: 9rem;
	background: linear-gradient(to top, rgba(36, 26, 18, 0.85) 0%, rgba(36, 26, 18, 0) 100%);
	pointer-events: none;
}

.about-evolution-caption {
	position: absolute;
	z-index: 1;
	/* Bottom-RIGHT: the offset accent image overlaps the bottom-left corner,
	   which is where this caption used to sit — the two collided. */
	right: var(--space-3);
	bottom: var(--space-3);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: #fff;
}

.about-evolution-image-offset {
	position: absolute;
	left: calc(var(--space-5) * -1);
	bottom: calc(var(--space-6) * -1);
	width: 16rem;
	height: 20rem;
	border: 8px solid var(--color-bg);
	border-radius: 2px;
	overflow: hidden;
}

.about-evolution-image-offset img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* 02 / Core Values — full-width dark section. */
.about-values {
	padding: var(--space-9) 0;
	background: var(--color-deep-earth);
}

.about-values-head {
	max-width: 46rem;
	margin-bottom: var(--space-7);
}

.about-values-head h2 {
	color: #fff;
	text-transform: uppercase;
	font-size: clamp(var(--text-xl), 3.4vw, var(--text-2xl));
	margin: 0;
}

.about-values-grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--space-5);
	/* room for the staggered third column to hang below the others */
	padding-bottom: var(--space-8);
}

/* Vertical stagger — each column steps further down, which is what stops the
   grid reading as three identical boxes. Desktop only; flattened below. */
.about-values-grid > :nth-child(2) {
	margin-top: var(--space-6);
}

.about-values-grid > :nth-child(3) {
	margin-top: var(--space-8);
}

/* On-dark variant of the hatch placeholder (the shared light one would blow
   out against --color-deep-earth). */
.about-value-image {
	aspect-ratio: 4 / 5;
	margin-bottom: var(--space-3);
	border-radius: 2px;
	background-color: rgba(255, 255, 255, 0.06);
	background-image: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.07) 0 2px, transparent 2px 22px);
	overflow: hidden;
}

/* Real photo (ACF `value_*_image`) fills the 4:5 frame and hides the on-dark
   hatch behind it. */
.about-value-image img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.about-value h3 {
	color: var(--color-tertiary);
	text-transform: uppercase;
	font-size: var(--text-md);
	margin: 0 0 var(--space-1);
}

.about-value p {
	color: rgba(255, 255, 255, 0.72);
	line-height: 1.75;
	margin: 0;
}

/* 03 / Leadership — sticky intro column beside stacked partner rows. Driven
   by the team_member CPT; the whole section is skipped when none exist. */
.about-leadership {
	padding: var(--space-9) 0;
}

.about-leadership-inner {
	display: grid;
	grid-template-columns: 1fr 2fr;
	gap: var(--space-7);
	align-items: start;
}

.about-leadership-intro {
	position: sticky;
	top: var(--space-7);
}

.about-leadership-intro h2 {
	text-transform: uppercase;
	font-size: clamp(var(--text-xl), 3.4vw, var(--text-2xl));
	margin: 0 0 var(--space-2);
}

.about-leadership-intro p {
	color: var(--color-neutral);
	line-height: 1.75;
	margin: 0;
}

.about-leadership-list {
	display: flex;
	flex-direction: column;
	gap: var(--space-7);
}

.about-partner {
	display: grid;
	grid-template-columns: 16rem 1fr;
	gap: var(--space-4);
	align-items: start;
}

.about-partner-portrait {
	aspect-ratio: 3 / 4;
	border-radius: 2px;
	overflow: hidden;
}

/* Same desaturated treatment as the .hero-mono photo, so a real portrait
   sits in the same tonal family as the rest of the page. */
.about-partner-portrait img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	filter: grayscale(0.6) sepia(0.15) contrast(1.05);
}

.about-partner-head {
	margin-bottom: var(--space-2);
	padding-bottom: var(--space-2);
	border-bottom: 1px solid var(--color-line);
}

.about-partner-head h3 {
	font-size: var(--text-lg);
	margin: 0;
}

.about-partner-role {
	display: block;
	margin-top: var(--space-1);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--color-clay);
}

.about-partner-copy p {
	color: var(--color-neutral);
	line-height: 1.75;
	margin: 0;
}

/* Awaiting-content state: the section renders its layout before any
   `team_member` posts exist, using bracketed placeholders. Muted so the
   brackets read as "to be filled" rather than as real partner copy.
   Publishing team members removes this class automatically. */
.about-partner.is-placeholder .about-partner-head h3,
.about-partner.is-placeholder .about-partner-copy p {
	color: var(--color-neutral);
	opacity: 0.75;
}

/* Closing CTA banner (template-parts/cta-banner.php) — the About page and the
   Project / Service / News single templates all end on this one component.

   Rebuilt 2026-08-31 from the client's comp: an inset rounded panel on the
   cream page rather than a full-bleed dark band, carrying the studio mark in a
   ringed circle, a hairline divider, a two-line serif heading over a
   supporting line, and a large clay pill action. The brief was "less rigid,
   more appealing" — the old band was a hard-edged full-width rectangle with a
   single line of type in it, which is the most rigid shape a section can be.
   Class renamed from .about-cta (it was never About-only). */
.cta-banner {
	padding: var(--space-7) 0 var(--space-8);
}

.cta-banner-panel {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	display: flex;
	align-items: center;
	gap: var(--space-5);
	padding: var(--space-6) var(--space-6) calc(var(--space-6) + var(--space-1));
	/* Deliberately far past the theme's 2px hairline radius and its 8px card
	   radius. This panel is the one place the site rounds hard, which is what
	   makes it read as a soft object sitting on the page rather than as
	   another band — the whole point of the comp. */
	border-radius: 24px;
	background: var(--color-deep-earth);
}

/* Warm bloom behind the mark. Ambient only — it carries no information, so it
   sits under the content and never darkens the type it passes behind. */
.cta-banner-panel::before {
	content: '';
	position: absolute;
	top: 50%;
	/* Centred on the mark (panel padding + half the circle), not on the
	   panel edge — the bloom is meant to read as light coming off the mark. */
	left: calc(var(--space-6) + 3.75rem);
	width: 20rem;
	height: 20rem;
	transform: translate(-50%, -50%);
	background: radial-gradient(circle, rgba(166, 123, 91, 0.38) 0%, rgba(166, 123, 91, 0) 68%);
	pointer-events: none;
	z-index: 0;
}

.cta-banner-mark,
.cta-banner-copy,
.cta-banner-action {
	position: relative;
	z-index: 1;
}

.cta-banner-mark {
	flex-shrink: 0;
	display: grid;
	place-items: center;
	width: 7.5rem;
	height: 7.5rem;
	border-radius: 50%;
	border: 1px solid rgba(217, 205, 193, 0.25);
}

.cta-banner-mark svg {
	width: 3.25rem;
	height: auto;
	fill: none;
	stroke: var(--color-clay);
	stroke-width: 2.2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.cta-banner-mark svg circle {
	fill: var(--color-clay);
	stroke: none;
}

/* The divider is the copy block's own left border rather than an extra
   element, so it can never fall out of step with the text height. */
.cta-banner-copy {
	flex: 1 1 auto;
	min-width: 0;
	padding-left: var(--space-5);
	border-left: 1px solid rgba(217, 205, 193, 0.22);
}

.cta-banner-copy h2 {
	margin: 0;
	color: #fff;
	font-size: clamp(var(--text-xl), 3vw, var(--text-2xl));
	line-height: 1.15;
	/* Holds the heading to the comp's two lines instead of letting it run the
	   full panel width and collide with the action. */
	max-width: 14ch;
}

.cta-banner-copy p {
	margin: var(--space-2) 0 0;
	color: var(--color-tertiary);
	font-size: var(--text-sm);
	line-height: 1.6;
	max-width: 26ch;
}

.cta-banner-action {
	flex-shrink: 0;
	margin-left: auto;
}

/* Flatten the asymmetric splits, the value stagger and the sticky column
   before they get cramped. */
@media (max-width: 980px) {
	.about-evolution,
	.about-values,
	.about-leadership {
		padding: var(--space-8) 0;
	}

	.about-evolution-inner,
	.about-leadership-inner {
		grid-template-columns: 1fr;
		gap: var(--space-5);
	}

	.about-evolution-image-offset {
		display: none;
	}

	.about-leadership-intro {
		position: static;
	}

	.about-values-head {
		margin-bottom: var(--space-5);
	}

	.about-values-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		padding-bottom: 0;
	}

	.about-values-grid > :nth-child(2),
	.about-values-grid > :nth-child(3) {
		margin-top: 0;
	}
}

@media (max-width: 780px) {
	.about-evolution,
	.about-values,
	.about-leadership {
		padding: var(--space-6) 0;
	}

	.about-values-grid {
		grid-template-columns: 1fr;
	}

	.about-leadership-list {
		gap: var(--space-6);
	}

	.about-partner {
		grid-template-columns: 1fr;
	}

	.about-partner-portrait {
		max-width: 16rem;
	}

	/* Below this the mark, the copy and the action stop fitting on one line.
	   Stacking them drops the vertical divider — a horizontal rule under the
	   mark would be a different piece of decoration, not the same one — so the
	   mark's own ring carries the separation instead. */
	.cta-banner-panel {
		flex-direction: column;
		align-items: flex-start;
		gap: var(--space-4);
		padding: var(--space-5);
	}

	.cta-banner-copy {
		padding-left: 0;
		border-left: 0;
	}

	.cta-banner-copy h2,
	.cta-banner-copy p {
		max-width: none;
	}

	.cta-banner-action {
		margin-left: 0;
	}
}

@media (max-width: 500px) {
	.about-evolution-stats {
		grid-template-columns: 1fr;
		gap: var(--space-3);
	}
}

/* Services archive / taxonomy (archive-service.php, taxonomy-service_type.php)
   — matches docs/superpowers/design/igneo-v2/Services.dc.html. Reuses
   .container, .breadcrumb, .filter-bar, .card-grid and .btn/.btn-outline
   from elsewhere in this file; only the intro section and the service-card
   internals (photo + icon box + button, replacing the old bare icon/title/
   description link) are new. */
.services-intro {
	max-width: 35rem;
	padding: var(--space-2) 0 var(--space-4);
}

.services-intro h1 {
	margin-top: var(--space-1);
}

.services-intro p {
	color: var(--color-neutral);
	line-height: 1.6;
	margin-top: var(--space-3);
}

.services-archive {
	padding-bottom: var(--space-7);
}

/* Services archive's filter-bar-group puts the pill row and the results
   count on one line — distinct from the Projects archive's stacked two-row
   group (Project Type + Service Type filters), which keeps the default
   block layout defined above. */
.services-archive .filter-bar-group {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: center;
	gap: var(--space-3);
}

.services-archive .filter-bar {
	margin: 0;
}

.filter-bar-count {
	font-size: var(--text-xs);
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-neutral);
	text-align: right;
}

/* The figure is set large in the reference — it reads as a statistic sitting
   opposite the filters, not as a small count appended to them. */
.filter-bar-count strong {
	display: block;
	font-family: var(--font-heading);
	font-size: var(--text-2xl);
	line-height: 1;
	margin-bottom: 0.25rem;
	color: var(--color-primary);
}

/* Full-bleed banner hero used by the Services/Projects archive intros —
   shorter than the homepage's .hero and with no per-archive photo source
   (falls back to the shared hatch-pattern texture), per the mockups. */
.services-hero .hero-image,
.projects-hero .hero-image {
	min-height: 420px;
}

.services-hero .hero-copy h1,
.projects-hero .hero-copy h1 {
	text-transform: uppercase;
	font-size: clamp(var(--text-2xl), 5vw, 4rem);
}

.services-hero .hero-copy p,
.projects-hero .hero-copy p {
	margin-top: var(--space-2);
}

.service-card {
	display: flex;
	flex-direction: column;
}

.service-card > a {
	display: flex;
	flex-direction: column;
	flex: 1;
	padding-bottom: 0;
}

/* Same "no real photography yet" hatch-pattern placeholder used by
   .hero-image / .about-evolution-image — reused here at the service card's
   own size. Displaced automatically once a
   featured image is set (has_post_thumbnail() in content-service-card.php). */
/* Portrait 4:5 frame — the Stitch Services grid uses tall service cards
   rather than a squat landscape crop; the taller image is what lets three
   cards across still read as an editorial grid instead of a list. */
/* Landscape, per the 2026-08-30 reference. The earlier 4/5 portrait crop came
   from the Stitch mockup this revision replaces. */
.service-card-image {
	position: relative;
	aspect-ratio: 4 / 3;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
}

.service-card-image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.service-card-image span {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-primary);
	opacity: 0.6;
}

.service-card-body {
	display: flex;
	flex-direction: column;
	flex: 1;
	gap: var(--space-2);
	padding: var(--space-3) var(--space-3) var(--space-4);
}

.service-card-body h3,
.service-card-body p {
	padding: 0;
}

.service-card-body p {
	flex-grow: 1;
	color: var(--color-neutral);
	font-size: var(--text-xs);
	line-height: 1.6;
}

.service-card-label {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--color-clay);
}

.service-card-link {
	align-self: flex-start;
}

/* Service Single (single-service.php) — rebuilt 2026-08-27 against the Stitch
   export design/detail_services_igneo_architecture/. Reuses .hero/.hero-image/
   .hero-copy (the same full-bleed scrim hero as the homepage and About),
   .section-marker, .eyebrow, .btn and .cta-banner; new here are the split hero
   copy, the sticky-heading focus grid, the tertiary methodology band — this
   page's one accent section, per docs/DESIGN.md's colour-rhythm rule — and the
   staggered related-project tiles. */
.service-detail-hero .hero-image {
	min-height: 560px;
	height: 78vh;
	max-height: 820px;
}

/* Deeper scrim than the shared .hero-image::before. This hero is taller, so
   its copy block (breadcrumb included) starts higher up the ramp — at the
   shared 0.45-at-50% stop the breadcrumb's white-at-65% links disappeared
   against the light hatch placeholder. */
.service-detail-hero .hero-image::before {
	background: linear-gradient(0deg, rgba(36, 26, 18, 0.9) 0%, rgba(36, 26, 18, 0.65) 45%, rgba(36, 26, 18, 0) 100%);
}

/* Split hero copy: headline left, lead + CTA right behind a hairline. The
   comp's 7/5 ratio is what keeps a two-word service title from stretching
   into a full-width line at display size. */
.service-detail-hero-grid {
	display: grid;
	grid-template-columns: 7fr 5fr;
	gap: var(--space-5);
	align-items: end;
	margin-top: var(--space-4);
}

.service-detail-hero-copy h1 {
	color: #fff;
	font-weight: 600;
	font-size: clamp(var(--text-2xl), 7vw, var(--text-5xl));
	line-height: 1.02;
	letter-spacing: -0.01em;
	max-width: 12ch;
	margin: var(--space-1) 0 0;
}

.service-detail-hero-lead {
	border-left: 1px solid rgba(255, 255, 255, 0.25);
	padding-left: var(--space-3);
	padding-bottom: var(--space-1);
}

.service-detail-hero-lead p {
	color: rgba(255, 255, 255, 0.85);
	font-size: var(--text-md);
	line-height: 1.6;
	margin: 0 0 var(--space-3);
	max-width: none;
}

/* Overview — the `long_description` wysiwyg. Measure-capped rather than
   full-bleed: this is the one block on the page an editor writes as flowing
   prose, so it gets a reading column, not a layout. */
.service-overview {
	padding: var(--space-8) 0 0;
}

.service-overview-body {
	max-width: 52rem;
	margin-top: var(--space-4);
	color: var(--color-neutral);
	font-size: var(--text-md);
	line-height: 1.75;
}

.service-overview-body h2,
.service-overview-body h3 {
	color: var(--color-primary);
	margin-top: var(--space-4);
}

.service-overview-body p {
	margin-top: var(--space-2);
}

/* Block editor output — this section renders the post's own content, so it
   has to handle whatever an editor inserts: images, galleries, captions,
   quotes and lists, none of which the ACF wysiwyg fallback could produce. */
.service-overview-body img {
	width: 100%;
	height: auto;
	border-radius: 2px;
}

.service-overview-body figure {
	margin: var(--space-4) 0;
}

.service-overview-body figcaption {
	margin-top: var(--space-1);
	font-size: var(--text-xs);
	color: var(--color-neutral);
}

.service-overview-body ul,
.service-overview-body ol {
	margin-top: var(--space-2);
	padding-left: var(--space-3);
}

.service-overview-body li {
	margin-bottom: var(--space-1);
}

.service-overview-body blockquote {
	margin: var(--space-4) 0;
	padding-left: var(--space-3);
	border-left: 2px solid var(--color-tertiary);
	font-family: var(--font-heading);
	font-size: var(--text-lg);
	line-height: 1.35;
	color: var(--color-primary);
}

.service-overview-body .wp-block-gallery {
	margin: var(--space-4) 0;
}

/* Areas of focus — sticky heading column against a two-up item grid, the
   comp's 4/8 split. The heading stays in view while the grid scrolls, which
   is what stops the section from losing its label on a long service. */
.service-focus {
	padding: var(--space-8) 0;
}

.service-focus-inner {
	display: grid;
	grid-template-columns: 4fr 8fr;
	gap: var(--space-5) var(--space-6);
	align-items: start;
}

.service-focus-head {
	position: sticky;
	top: var(--space-6);
}

.service-focus-head h2 {
	font-size: clamp(var(--text-xl), 4vw, var(--text-3xl));
	line-height: 1.05;
	max-width: 9ch;
	margin: var(--space-3) 0 0;
}

.service-focus-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--space-6) var(--space-4);
}

/* Same "no real photography yet" hatch-pattern placeholder used by
   .service-card-image / .about-evolution-image (declared once, near the top
   of this file) — displaced automatically once a focus image is uploaded. */
.service-focus-image {
	aspect-ratio: 4 / 3;
	margin-bottom: var(--space-3);
	overflow: hidden;
	border-radius: 2px;
	display: flex;
	align-items: center;
	justify-content: center;
}

.service-focus-image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* Lightly desaturated, matching .hero-mono's treatment on the About page:
	   it ties four unrelated photos into one grid. Static, not a hover
	   reveal — these items are not links, and the motion rules in
	   docs/DESIGN.md allow movement only on things you can click. */
	filter: grayscale(0.35) sepia(0.08) contrast(1.02);
}

.service-focus-image span {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-primary);
	opacity: 0.6;
}

.service-focus-item h3 {
	font-size: var(--text-lg);
	margin: 0 0 var(--space-1);
}

.service-focus-item p {
	color: var(--color-neutral);
	margin: 0;
}

/* Methodology — the page's single tertiary accent band (docs/DESIGN.md,
   "Decoration & color rhythm": exactly one per page, never two in a row).
   Body copy on the tan fill is --color-primary rather than --color-neutral,
   which only clears ~3.4:1 against #D9CDC1. */
.service-methodology {
	background: var(--color-tertiary);
	padding: var(--space-8) 0;
}

.service-methodology-head {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: var(--space-5);
	align-items: end;
	margin-bottom: var(--space-6);
}

.service-methodology-title h2 {
	font-size: clamp(var(--text-xl), 4vw, var(--text-3xl));
	line-height: 1.05;
	margin: var(--space-3) 0 0;
}

.service-methodology-intro {
	color: var(--color-primary);
	font-size: var(--text-md);
	line-height: 1.6;
	margin: 0;
}

.service-methodology-grid {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: var(--space-4);
}

/* Hairline above each step, not a card — the rule is the only decoration
   the comp gives them, and it reads as a timeline across the row. */
.service-step {
	border-top: 1px solid rgba(74, 55, 40, 0.25);
	padding-top: var(--space-3);
}

.service-step-number {
	display: block;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	color: var(--color-secondary);
	margin-bottom: var(--space-2);
}

.service-step h3 {
	font-size: var(--text-md);
	margin: 0 0 var(--space-1);
}

.service-step p {
	color: var(--color-primary);
	margin: 0;
}

.service-related-projects {
	padding: var(--space-8) 0;
}

/* auto-fit, not repeat(3, …): a service often has only one or two projects
   cross-tagged to it, and a fixed 3-up grid leaves those as a lonely tile
   beside two empty columns. Fewer tiles simply widen to fill the row. */
.service-projects-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
	gap: var(--space-1);
}

/* Vertical stagger — the same device as the About page's values grid: each
   column steps further down so the row reads as a composition rather than
   three equal boxes. */
.service-projects-grid > :nth-child(2) {
	margin-top: var(--space-5);
}

.service-projects-grid > :nth-child(3) {
	margin-top: var(--space-7);
}

.service-project-tile > a {
	position: relative;
	display: block;
	color: inherit;
	text-decoration: none;
}

/* Same hatch placeholder family as the focus images above. */
.service-project-tile-image {
	position: relative;
	height: 32rem;
	overflow: hidden;
	display: flex;
	align-items: center;
	justify-content: center;
}

.service-project-tile-image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Permanent bottom scrim. The comp fades the caption in on hover, which
   leaves touch devices with three unlabelled photos — the gradient stays on
   so the project name is always readable. */
.service-project-tile-image::after {
	content: '';
	position: absolute;
	inset: auto 0 0 0;
	height: 65%;
	background: linear-gradient(0deg, rgba(36, 26, 18, 0.85) 0%, rgba(36, 26, 18, 0) 100%);
}

.service-project-tile-image span {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-primary);
	opacity: 0.6;
}

.service-project-tile-caption {
	position: absolute;
	inset: auto 0 0 0;
	padding: var(--space-3);
}

.service-project-tile-meta {
	display: block;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--color-tertiary);
	margin-bottom: var(--space-1);
}

.service-project-tile-caption h3 {
	color: #fff;
	font-size: var(--text-lg);
	margin: 0;
}

/* Hover affordance without movement: the comp's `scale(1.05)` zoom is one of
   the Stitch effects this theme deliberately doesn't take (docs/DESIGN.md,
   "Deliberately NOT taken from Stitch"). The scrim deepens and the title
   underlines instead — enough to read as clickable, nothing moves. */
.service-project-tile-image::after {
	transition: background 150ms ease-out;
}

.service-project-tile:hover .service-project-tile-image::after {
	background: linear-gradient(0deg, rgba(36, 26, 18, 0.95) 0%, rgba(36, 26, 18, 0.1) 100%);
}

.service-project-tile:hover .service-project-tile-caption h3 {
	text-decoration: underline;
	text-underline-offset: 4px;
}

.section-head {
	display: flex;
	justify-content: space-between;
	align-items: flex-end;
	gap: var(--space-3);
	margin-bottom: var(--space-4);
}

.section-head h2 {
	font-size: var(--text-xl);
	margin-top: var(--space-1);
}

.section-head a {
	font-weight: 600;
	font-size: var(--text-xs);
	color: var(--color-primary);
	white-space: nowrap;
}

/* Tablet: the sticky focus heading and the two asymmetric splits stop
   earning their keep once the columns get narrow. */
@media (max-width: 980px) {
	.service-detail-hero-grid,
	.service-focus-inner,
	.service-methodology-head {
		grid-template-columns: 1fr;
	}

	.service-focus-head {
		position: static;
	}

	.service-detail-hero-lead {
		border-left: 0;
		padding-left: 0;
	}

	.service-methodology-grid {
		grid-template-columns: repeat(2, 1fr);
	}

	.service-projects-grid {
		grid-template-columns: repeat(2, 1fr);
	}

	.service-projects-grid > :nth-child(2),
	.service-projects-grid > :nth-child(3) {
		margin-top: 0;
	}

	.service-project-tile-image {
		height: 24rem;
	}
}

@media (max-width: 780px) {
	.service-focus-grid,
	.service-methodology-grid,
	.service-projects-grid {
		grid-template-columns: 1fr;
	}

	.service-overview,
	.service-focus,
	.service-methodology,
	.service-related-projects {
		padding-top: var(--space-6);
		padding-bottom: var(--space-6);
	}

	.section-head {
		flex-direction: column;
		align-items: flex-start;
	}
}

/* Projects archive / taxonomy (archive-project.php, taxonomy-project_type.php).
   Reuses .container, .breadcrumb, .filter-bar and .card-grid from elsewhere in
   this file, mirroring the Services archive's breadcrumb + intro + wrapped
   archive-section structure; only the composed filter-bar grouping and the
   .project-tile block further down are new. The cards no longer follow
   docs/superpowers/design/igneo-v2/Projects.dc.html — they follow the
   homepage's Featured Projects cards instead (2026-08-30, see docs/DESIGN.md). */
.projects-intro,
.services-intro {
	max-width: 35rem;
	padding: var(--space-2) 0 var(--space-4);
}

.projects-intro h1,
.services-intro h1 {
	margin-top: var(--space-1);
}

.projects-intro p,
.services-intro p {
	color: var(--color-neutral);
	line-height: 1.6;
	margin-top: var(--space-3);
}

.projects-archive {
	padding-bottom: var(--space-7);
}

/* Groups the Project Type tab row and the Service Type filter row from the
   two composed WP_Query filters (see archive-project.php) into one visual
   unit, matching the mockup's stacked filter bar with a bottom hairline. */
.filter-bar-group {
	border-bottom: 1px solid var(--color-line);
	padding-bottom: var(--space-2);
	margin-bottom: var(--space-4);
}

.filter-bar-secondary {
	margin-top: 0;
}

.filter-bar-label {
	color: var(--color-neutral);
	font-size: var(--text-xs);
	align-self: center;
}

/* Tertiary-fill badge variant (docs/DESIGN.md "Badges use --color-tertiary
   fill + --color-primary text") for the project type flag on each card —
   distinct from .badge-primary, which is reserved for the solid-fill
   "Expanding Soon" emphasis badge on the homepage. */
.badge-tertiary {
	background: var(--color-tertiary);
	color: var(--color-primary);
}

@media (max-width: 780px) {
	.filter-bar-group {
		display: flex;
		flex-direction: column;
		gap: var(--space-2);
	}
}

/* Project tiles — the Our Projects / Project Type archive card
   (template-parts/content-project-card.php). Deliberately the same object as
   the homepage Featured Projects card: the photo is the card, the caption
   sits on a scrim over its lower edge, and the photo stack cross-fades
   through the project's gallery. Everything visual those two share lives on
   .project-frame / .*-media up in the homepage block; only the grid, the
   caption and the Project Type flag are below.

   Replaced the earlier masonry column layout (two CSS columns of portrait
   cards with a title, meta row and "View Project" button underneath) on
   2026-08-30 at the client's request — see docs/DESIGN.md. */
.projects-archive .card-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-4);
}

.project-tile {
	position: relative;
	/* Matches the homepage card exactly, including the 8px corner: at this
	   size the theme's usual 2px hairline radius reads as an un-styled
	   rectangle rather than a card. */
	aspect-ratio: 3 / 2;
	overflow: hidden;
	border-radius: 8px;
	background: var(--color-tertiary);
	isolation: isolate;
}

.project-tile-link {
	position: absolute;
	inset: 0;
	display: block;
	text-decoration: none;
	color: #fff;
}

.project-tile-link:focus-visible {
	outline: 2px solid var(--color-clay);
	outline-offset: -4px;
}

/* The Project Type flag. Tertiary fill + primary text is this theme's badge
   treatment (docs/DESIGN.md "Badges"), which over a photograph also gives the
   label its own opaque ground rather than leaving it to fight the image. It
   sits above the scrim, and is inert to the pointer so the whole tile stays
   one click target. */
.project-tile-type {
	position: absolute;
	top: var(--space-2);
	left: var(--space-2);
	z-index: 3;
	padding: 0.3rem 0.75rem;
	border-radius: 999px;
	background: var(--color-tertiary);
	color: var(--color-primary);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	pointer-events: none;
}

.project-tile-caption {
	position: absolute;
	inset: auto 0 0 0;
	z-index: 2;
	padding: var(--space-4);
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
}

/* Sans, not the heading serif — same reasoning as the homepage caption: a
   project name set over a photograph reads as a caption, not a page heading. */
.project-tile-caption h3 {
	font-family: var(--font-body);
	font-weight: 400;
	font-size: clamp(var(--text-md), 2.2vw, var(--text-xl));
	line-height: 1.1;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: #fff;
	margin: 0;
	text-wrap: balance;
}

.project-tile-line {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-2);
}

.project-tile-meta {
	font-size: var(--text-xs);
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.75);
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.project-tile-year {
	font-size: var(--text-xs);
	font-weight: 500;
	letter-spacing: 0.08em;
	color: rgba(255, 255, 255, 0.75);
	flex-shrink: 0;
}

.projects-archive .card-grid .empty-state {
	grid-column: 1 / -1;
}

@media (max-width: 640px) {
	.projects-archive .card-grid {
		grid-template-columns: minmax(0, 1fr);
	}

	/* Same call as the homepage card at this width: a 3:2 tile is barely
	   taller than the caption sitting on it, so the crop goes taller to let
	   the photograph register as the subject. */
	.project-tile {
		aspect-ratio: 4 / 3;
	}

	.project-tile-caption {
		padding: var(--space-3);
	}
}

/* Project Single (single-project.php) — matches
   docs/superpowers/design/igneo-v2/ProjectSingle.dc.html. Reuses .container,
   .breadcrumb, .eyebrow, .badge/.badge-tertiary, .btn/.btn-outline-light,
   .cta-banner (for the closing CTA banner) and .card-grid/.service-card (via
   content-service-card.php, same partial the Services archive uses) for
   consistency with the rest of the site; only the gallery wrapper, the
   hero-copy/key-facts split and the tertiary-fill testimonial card below
   are new to this template. */
.project-gallery {
	padding: var(--space-3) 0 var(--space-5);
}

.project-gallery img {
	border-radius: 2px;
}

/* Featured image standing in as the lead photo (single-project.php renders it
   only when the content carries no image of its own). It fills the column
   rather than sitting at its natural size, so a project with one photo still
   opens on a full-width image instead of a small tile floating left. */
.project-gallery > img.wp-post-image {
	width: 100%;
	max-height: 34rem;
	object-fit: cover;
}

.project-gallery .wp-block-gallery {
	gap: var(--space-2);
}

.project-hero {
	display: grid;
	grid-template-columns: 2fr 1fr;
	gap: var(--space-6);
	align-items: start;
	padding-bottom: var(--space-7);
}

.project-hero-copy {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-3);
}

.project-hero-copy h1 {
	margin: 0;
}

.project-hero-copy p {
	color: var(--color-neutral);
	font-size: var(--text-sm);
	line-height: 1.75;
}

.project-delivered-under {
	font-size: var(--text-xs);
	color: var(--color-neutral);
}

.project-delivered-under a {
	font-weight: 600;
	margin-left: 0.25rem;
}

/* Same card treatment as the homepage/service key-facts panels — white
   card, hairline border, no shadow at rest (shadow reserved for
   hover-interactive cards per docs/DESIGN.md). */
.project-key-facts {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	background: #fff;
	border: 1px solid var(--color-line);
	border-radius: 2px;
	padding: var(--space-4);
	height: fit-content;
}

.project-key-facts dl {
	display: flex;
	flex-direction: column;
	gap: var(--space-3);
	margin: 0;
}

.project-key-facts .fact {
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

.project-key-facts .fact svg {
	flex-shrink: 0;
	color: var(--color-secondary);
}

.project-key-facts dt {
	font-size: var(--text-xs);
	color: var(--color-neutral);
}

.project-key-facts dd {
	margin: 0;
	font-size: var(--text-sm);
	color: var(--color-primary);
	font-weight: 500;
}

/* Tertiary-fill quote card — the pattern docs/DESIGN.md's Components
   section calls "already established on the Project Single template",
   later reused for the homepage testimonial strip. */
.project-testimonial {
	padding-bottom: var(--space-7);
}

.project-testimonial-card {
	display: flex;
	align-items: flex-start;
	gap: var(--space-4);
	background: var(--color-tertiary);
	border-radius: 2px;
	padding: var(--space-5);
}

.project-testimonial-icon {
	flex-shrink: 0;
	margin-top: 0.25rem;
	color: var(--color-primary);
}

.project-testimonial-card p {
	font-family: var(--font-heading);
	font-style: italic;
	font-size: var(--text-md);
	line-height: 1.6;
	color: var(--color-primary);
	margin: 0;
}

.project-testimonial-card span {
	display: block;
	margin-top: var(--space-2);
	font-size: var(--text-xs);
	font-weight: 600;
	color: var(--color-primary);
	opacity: 0.75;
}

.project-related-services {
	padding-bottom: var(--space-7);
}

@media (max-width: 780px) {
	.project-hero {
		grid-template-columns: 1fr;
	}

	.project-testimonial-card {
		flex-direction: column;
		gap: var(--space-3);
	}
}

/* Label that only assistive tech reads — the slider/lightbox controls are
   icon-only, and an aria-label alone would leave the icon unnamed for
   translation tooling. Clip rather than display:none so it stays in the
   accessibility tree. */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* Project photo gallery — the "Material & Light Study" section rendered by
   template-parts/project-gallery.php, after
   design/detail_our_projects_igneo_architecture/screen.png: an asymmetric
   mosaic (one large photo beside two stacked ones, captions in a chip over
   the bottom-left corner) with the remaining photos in a scroll-snapping
   slider below it, and any photo opening in a lightbox.

   Reuses .container, .section-head and .eyebrow; only the mosaic grid, the
   slider and the lightbox below are new. The 400ms photo zoom on hover is a
   deliberate addition to docs/DESIGN.md's motion list — this is the one
   section where the photography is the subject rather than card furniture,
   and the reference mockup calls for it. */
.project-photo-study {
	padding-bottom: var(--space-7);
}

.project-photo-study .section-head {
	align-items: baseline;
}

.project-photo-mosaic {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	grid-template-rows: repeat(2, minmax(0, 1fr));
	gap: var(--space-2);
	/* A fixed block height is what makes the mosaic asymmetric: the lead
	   photo fills two rows while the stacked pair splits them. Clamped
	   rather than fixed at the mockup's 600px so it tracks the viewport. */
	height: clamp(22rem, 42vw, 37.5rem);
}

.project-photo-mosaic .project-photo:first-child {
	grid-column: span 2;
	grid-row: span 2;
}

/* One or two photos: no stack to sit beside, so the lead widens instead of
   leaving a hole in the grid. */
.project-photo-mosaic.has-1 .project-photo:first-child {
	grid-column: span 3;
}

.project-photo-mosaic.has-2 .project-photo:nth-child(2) {
	grid-row: span 2;
}

.project-photo {
	position: relative;
	overflow: hidden;
	border-radius: 2px;
	margin: 0;
	min-height: 0;
	/* Shows through while a lazy photo loads, instead of a white flash. */
	background: var(--color-tertiary);
}

.project-photo-link {
	display: block;
	width: 100%;
	height: 100%;
}

.project-photo img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: transform 400ms ease-out;
}

@media (hover: hover) {
	.project-photo-link:hover img {
		transform: scale(1.04);
	}
}

.project-photo-caption {
	position: absolute;
	left: var(--space-2);
	bottom: var(--space-2);
	padding: 0.35rem 0.75rem;
	background: rgba(250, 247, 243, 0.9);
	color: var(--color-primary);
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	/* The whole tile is one link — the caption must not swallow the click. */
	pointer-events: none;
}

.project-photo-slider {
	position: relative;
	margin-top: var(--space-2);
}

.project-photo-track {
	display: flex;
	gap: var(--space-2);
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	/* The arrows and the trackpad/touch swipe are the controls here; a
	   scrollbar under the strip would cut into the photo edge. */
	scrollbar-width: none;
	-ms-overflow-style: none;
}

.project-photo-track::-webkit-scrollbar {
	display: none;
}

.project-photo-track > li {
	flex: 0 0 calc((100% - 2 * var(--space-2)) / 3);
	scroll-snap-align: start;
}

.project-photo-track .project-photo {
	aspect-ratio: 4 / 3;
}

.project-photo-slider-nav {
	position: absolute;
	top: 50%;
	z-index: 2;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* 44px — the minimum touch target in docs/DESIGN.md's a11y baseline. */
	width: 2.75rem;
	height: 2.75rem;
	transform: translateY(-50%);
	border: 1px solid var(--color-line);
	border-radius: 50%;
	background: rgba(250, 247, 243, 0.94);
	color: var(--color-primary);
	cursor: pointer;
	transition: background-color 150ms ease-out, border-color 150ms ease-out, opacity 150ms ease-out;
}

.project-photo-slider-nav.is-prev {
	left: calc(-1 * var(--space-2));
}

.project-photo-slider-nav.is-next {
	right: calc(-1 * var(--space-2));
}

.project-photo-slider-nav:hover:not(:disabled) {
	background: #fff;
	border-color: var(--color-secondary);
}

.project-photo-slider-nav:disabled {
	opacity: 0.3;
	cursor: default;
}

/* Lightbox. Kept in the markup and toggled with [hidden], so display must be
   re-asserted as none — a display:flex rule would otherwise win over it. */
.project-lightbox {
	position: fixed;
	inset: 0;
	z-index: 1000;
	display: flex;
	align-items: center;
	justify-content: center;
}

.project-lightbox[hidden] {
	display: none;
}

body.has-lightbox-open {
	overflow: hidden;
}

.project-lightbox-backdrop {
	position: absolute;
	inset: 0;
	background: rgba(36, 26, 18, 0.94);
}

.project-lightbox-dialog {
	position: relative;
	display: flex;
	align-items: center;
	gap: var(--space-2);
	width: min(100%, var(--max-width));
	padding: var(--space-6) var(--space-3) var(--space-4);
}

.project-lightbox-figure {
	flex: 1 1 auto;
	min-width: 0;
	margin: 0;
}

.project-lightbox-figure img {
	display: block;
	margin: 0 auto;
	max-width: 100%;
	max-height: 76vh;
	object-fit: contain;
	border-radius: 2px;
}

.project-lightbox-figure figcaption {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: center;
	gap: var(--space-2);
	margin-top: var(--space-3);
	font-size: var(--text-xs);
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--color-tertiary);
}

.project-lightbox-counter {
	opacity: 0.65;
}

.project-lightbox-btn {
	flex-shrink: 0;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	border: 1px solid rgba(217, 205, 193, 0.35);
	border-radius: 50%;
	background: transparent;
	color: var(--color-tertiary);
	cursor: pointer;
	transition: background-color 150ms ease-out, border-color 150ms ease-out;
}

.project-lightbox-btn:hover {
	background: rgba(217, 205, 193, 0.14);
	border-color: var(--color-tertiary);
}

.project-lightbox-btn.is-close {
	position: absolute;
	top: var(--space-2);
	right: var(--space-3);
}

/* Tablet: three photos across the slider get too narrow to read. */
@media (max-width: 980px) {
	.project-photo-track > li {
		flex-basis: calc((100% - var(--space-2)) / 2);
	}
}

@media (max-width: 780px) {
	/* The mosaic's whole point is the side-by-side asymmetry; in one column
	   it is just a stack, so the photos take their own aspect ratio rather
	   than splitting a fixed height. */
	.project-photo-mosaic {
		display: flex;
		flex-direction: column;
		height: auto;
	}

	.project-photo-mosaic .project-photo {
		aspect-ratio: 3 / 2;
	}

	/* A part-visible next tile is the swipe affordance at this width, which
	   is why the arrows step aside. */
	.project-photo-track > li {
		flex-basis: 82%;
	}

	.project-photo-slider-nav {
		display: none;
	}

	.project-lightbox-dialog {
		gap: var(--space-1);
		padding: var(--space-6) var(--space-2) var(--space-3);
	}

	.project-lightbox-figure img {
		max-height: 68vh;
	}
}

@media (prefers-reduced-motion: reduce) {
	.project-photo img,
	.project-photo-slider-nav,
	.project-lightbox-btn {
		transition: none;
	}

	.project-photo-link:hover img {
		transform: none;
	}
}

/* News & Update (index.php, single.php) — matches
   docs/superpowers/design/igneo-v2/News.dc.html for the archive view; no
   dedicated mockup exists for the single-post view, so its treatment
   follows the same breadcrumb/hero/prose/CTA shape as single-service.php
   and single-project.php. Reuses .container, .breadcrumb, .badge/
   .badge-primary/.badge-tertiary, .btn/.btn-outline/.btn-outline-light,
   .cta-banner (closing CTA banner), .card-grid and .empty-state; only the
   featured-post split card, the post-card internals and the article prose
   typography below are new. */
.news-intro {
	max-width: 35rem;
	padding: var(--space-2) 0 var(--space-4);
}

.news-intro h1 {
	margin-top: var(--space-1);
}

.news-intro p {
	color: var(--color-neutral);
	line-height: 1.6;
	margin-top: var(--space-3);
}

.news-archive {
	padding-bottom: var(--space-7);
}

.news-date {
	display: block;
	font-size: var(--text-xs);
	color: var(--color-neutral);
}

.news-card-category {
	display: block;
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--color-neutral);
}

/* Featured post: larger split card (photo + copy), the first post in the
   loop on each archive page. */
.news-featured {
	display: grid;
	grid-template-columns: 1.1fr 1fr;
	background: #fff;
	border: 1px solid var(--color-line);
	border-radius: 2px;
	overflow: hidden;
	margin-bottom: var(--space-4);
	transition: border-color 150ms ease-out;
}

.news-featured:hover {
	border-color: var(--color-secondary);
}

.news-featured-image {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 280px;
}

.news-featured-image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.news-featured-image span {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-primary);
	opacity: 0.6;
}

.news-featured-body {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-2);
	padding: var(--space-5);
	justify-content: center;
}

.news-featured-body h2 {
	font-size: var(--text-lg);
}

.news-featured-body p {
	color: var(--color-neutral);
	font-size: var(--text-xs);
	line-height: 1.65;
}

/* Standard 3-up post cards below the featured post — same bordered/hover
   treatment as .service-card. */
.news-card {
	display: flex;
	flex-direction: column;
	background: #fff;
	border: 1px solid var(--color-line);
	border-radius: 2px;
	overflow: hidden;
	transition: border-color 150ms ease-out;
}

.news-card:hover {
	border-color: var(--color-secondary);
}

.news-card-image {
	display: flex;
	align-items: center;
	justify-content: center;
	height: 190px;
}

.news-card-image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.news-card-image span {
	font-size: var(--text-xs);
	font-weight: 600;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-primary);
	opacity: 0.6;
}

.news-card-body {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-2);
	padding: var(--space-3) var(--space-3) var(--space-4);
	flex: 1;
}

.news-card-body h3 {
	font-size: var(--text-md);
}

.news-card-body p {
	color: var(--color-neutral);
	font-size: var(--text-xs);
	line-height: 1.6;
	flex-grow: 1;
}

.news-card-link {
	margin-top: auto;
}

/* Single post (single.php) — no dedicated mockup; follows the
   breadcrumb/hero/content/CTA shape of single-service.php and
   single-project.php. */
.news-single-image {
	padding-top: var(--space-3);
}

.news-single-image img {
	border-radius: 2px;
	width: 100%;
	max-height: 480px;
	object-fit: cover;
}

.news-single-head {
	max-width: 47rem;
	padding: var(--space-4) 0 var(--space-3);
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--space-2);
}

.news-single-head h1 {
	margin-top: var(--space-1);
}

/* Article prose: DESIGN.md's body-copy rule is line-height 1.5–1.75 at
   16px — set explicitly here since long-form content needs a comfortable
   reading measure (max-width) that the rest of the theme's --text-xs card
   captions don't need. */
.news-content {
	max-width: 47rem;
	padding-bottom: var(--space-7);
	font-size: var(--text-sm);
	line-height: 1.75;
	color: var(--color-primary);
}

.news-content p,
.news-content ul,
.news-content ol,
.news-content blockquote {
	margin: 0 0 var(--space-3);
}

.news-content h2,
.news-content h3 {
	margin-top: var(--space-5);
}

.news-content img {
	border-radius: 2px;
	margin: var(--space-3) 0;
}

.news-content blockquote {
	border-left: 3px solid var(--color-secondary);
	padding-left: var(--space-3);
	color: var(--color-neutral);
	font-style: italic;
}

@media (max-width: 780px) {
	.news-featured {
		grid-template-columns: 1fr;
	}
}
