/*
 * NEWKID V2 — page-specific composition.
 *
 * Home, Work and Single Work, per page (docs/wordpress-architecture.md §37).
 */

/* ─── Home ───────────────────────────────────────────────────────────────── */

:root {
	/* Home-specific spacing/typography — component-level judgement against
	   the approved mockup, not sourced from an explicit design-guidelines.md
	   value (same situation as the Header/Footer's own spacing tokens). */
	--home-ui-footer-gap: clamp(24px, 3vw, 48px);
	--home-title-gap: 6px;
	/* Bumped ~18-20% from the first pass (20/2.4vw/28 and 14/1.1vw/17) per
	   visual feedback — more presence, same hierarchy/ratio between the two. */
	--home-title-active-size: clamp(24px, 2.8vw, 33px);
	--home-title-inactive-size: clamp(17px, 1.3vw, 20px);

	/* Dedicated title-transition timing — deliberately NOT --transition-ui
	   (200ms, shared with Header/Footer hover states) so slowing this down
	   doesn't touch their feel. ~50% longer than the previous 200ms swap,
	   and close to the 650ms media crossfade (--home-transition-duration,
	   set inline per-page) so the two read as one coherent transition
	   without literally coupling their durations together. */
	--home-title-transition: 600ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Home only — see docs comment in front-page.php. WP's native 'home' body
   class (added by get_body_class() for is_front_page()) scopes this without
   a global no-scroll rule. */
body.home {
	overflow: hidden;
	height: 100%;
}

.v2-home {
	position: relative;
	height: 100dvh;
	min-height: 100dvh;
	overflow: hidden;
	display: flex;
	flex-direction: column;
}

/* ─── Background media ───────────────────────────────────────────────────── */

.v2-home__media {
	position: absolute;
	inset: 0;
	z-index: 0;
}

/* Pre-paint state for Home's very subtle media entrance (home-entrance.js
   animates this to opacity:1/scale:1 via gsap.to()) — NOT the shared
   `.v2-reveal` system (base.css): the media stays clearly visible from the
   very first frame (brief: "media should remain available immediately, do
   not hide it"), just barely dimmed/scaled, so this is its own dedicated
   rule rather than the fully-hidden opacity:0 the shared class establishes.
   Same `html.motion-enabled` gate/specificity reasoning as `.v2-reveal`. */
html.motion-enabled .v2-home__media {
	opacity: 0.85;
	transform: scale(1.01);
}

@media (prefers-reduced-motion: reduce) {
	html.motion-enabled .v2-home__media {
		opacity: 1;
		transform: none;
	}
}

.v2-home__slide {
	position: absolute;
	inset: 0;
	opacity: 0;
	transition: opacity var(--home-transition-duration, 650ms) ease;
}

.v2-home__slide.is-active {
	opacity: 1;
}

.v2-home__poster,
.v2-home__video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.v2-home__video {
	opacity: 0;
	transition: opacity var(--home-transition-duration, 650ms) ease;
}

.v2-home__video.is-playing {
	opacity: 1;
}

/* Subtle bottom-weighted readability gradient — protects the title
   list/progress text without visibly recoloring the media (design-
   guidelines.md §"Media overlays"). */
.v2-home__overlay {
	position: absolute;
	inset: 0;
	z-index: 1;
	background: linear-gradient(to top, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 45%);
	pointer-events: none;
}

/* ─── Slider UI (titles + progress) ─────────────────────────────────────── */

.v2-home__ui {
	position: relative;
	z-index: 2;
	margin-top: auto;
	margin-bottom: var(--home-ui-footer-gap);
	padding: 0 var(--page-margin);
	display: flex;
	flex-direction: column;
	gap: 14px;
}

.v2-home__titles {
	display: flex;
	flex-direction: column;
	gap: var(--home-title-gap);
}

.v2-home__title {
	display: inline-block;
	align-self: flex-start;
	background: none;
	border: none;
	padding: 0;
	margin: 0;
	font-family: var(--font-base);
	font-weight: 400;
	font-size: var(--home-title-inactive-size);
	line-height: 1.3;
	letter-spacing: -0.02em;
	color: var(--color-fg);
	opacity: var(--opacity-muted);
	text-align: left;
	text-decoration: none;
	cursor: pointer;
	/* font-weight dropped from the transition list — browsers don't
	   interpolate between static font-weight values, so it was only ever
	   jumping regardless of duration; opacity + font-size carry the motion. */
	transition: opacity var(--home-title-transition), font-size var(--home-title-transition);
}

.v2-home__title.is-active {
	font-weight: 500;
	font-size: var(--home-title-active-size);
	opacity: 1;
}

.v2-home__progress {
	display: flex;
	align-items: center;
	gap: 10px;
}

.v2-home__progress-current,
.v2-home__progress-total {
	font-family: var(--font-base);
	font-size: var(--type-utility-size);
	font-weight: var(--weight-body);
	color: var(--color-fg);
}

.v2-home__progress-current {
	opacity: 1;
}

.v2-home__progress-total {
	opacity: var(--opacity-muted);
}

.v2-home__progress-track {
	position: relative;
	flex: 1;
	max-width: 220px;
	height: 1px;
	background: rgba(240, 242, 228, 0.25);
}

.v2-home__progress-fill {
	position: absolute;
	inset: 0;
	background: var(--color-fg);
	transform: scaleX(0);
	transform-origin: left;
}

/* ─── Cursor-follow "view work" CTA (desktop only) ──────────────────────────
   One shared element (assets/js/home-cursor.js positions/shows/hides it),
   not one per slide. Fixed + pointer-events:none so it can never intercept
   the hover/click it's reacting to; opacity is animated entirely by GSAP
   (home-cursor.js), not a CSS transition, so the two never fight over the
   same property (design-guidelines.md §73 "one clear owner per element") —
   this rule only ever needs to set the pre-interaction hidden state. */
.v2-home-project-cursor {
	position: fixed;
	top: 0;
	left: 0;
	z-index: 15; /* above media(0)/overlay(1)/ui(2), below the header(20) */
	pointer-events: none;
	opacity: 0;
	font-family: var(--font-base);
	font-weight: var(--weight-body);
	font-size: clamp(14px, 1vw, 16px);
	letter-spacing: -0.01em;
	color: var(--color-fg);
	white-space: nowrap;
	will-change: transform, opacity;
}

/* Belt-and-suspenders alongside home-cursor.js's own capability check — this
   feature must never show on touch/coarse pointers (brief §4). */
@media (hover: none), (pointer: coarse) {
	.v2-home-project-cursor {
		display: none;
	}
}

@media (hover: hover) and (pointer: fine) {
	.v2-home__media {
		cursor: pointer;
	}
}

/* Footer sits above the media layer within the fullscreen Home viewport
   (docs/project-context.md "Home" — "no separate footer section requiring
   vertical scroll"). No visual change to the footer itself. */
.v2-home > .v2-footer {
	position: relative;
	z-index: 2;
}

/* ─── Mobile ─────────────────────────────────────────────────────────────── */

@media (max-width: 767px) {
	.v2-home__ui {
		margin-bottom: clamp(16px, 4vw, 24px);
		gap: 10px;
	}

	.v2-home__progress-track {
		max-width: none;
	}
}

/* ─── Reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
	.v2-home__slide,
	.v2-home__video,
	.v2-home__title,
	.v2-work__filter-icon {
		transition-duration: 1ms !important;
	}
}

/* ─── Work ───────────────────────────────────────────────────────────────── */

:root {
	/* Work-specific spacing/sizing — component-level judgement against the
	   approved mockup (same situation as Header/Footer/Home's own tokens). */
	--work-content-top: clamp(96px, 10vw, 136px); /* clears the fixed .v2-header */
	--work-intro-max-width: 720px; /* wide enough that the heading doesn't wrap at desktop widths — 660px left ~0 margin at wide desktop sizes (font-size clamp maxes at 38px), causing wraps in practice */
	--work-intro-text-max-width: 480px; /* the supporting paragraph reads better narrower than the heading */
	--work-intro-gap: clamp(10px, 1.2vw, 16px); /* heading → supporting text */
	--work-controls-top: clamp(40px, 5vw, 72px); /* intro → filters/view row */
	--work-controls-bottom: clamp(18px, 2vw, 26px); /* filters/view row → grid/list — reduced ~35% per visual feedback */
	--work-section-bottom: clamp(64px, 7vw, 104px); /* grid/list → footer */
	--work-card-info-gap: 10px;
	--work-card-meta-gap: 4px;
	--work-row-padding-y: clamp(14px, 1.4vw, 18px);

	/* Sticky toolbar. --work-toolbar-content-offset is where the filter row
	   itself visually sits — derived from the real header token
	   (--header-top-spacing, components.css) rather than a disconnected magic
	   number, so it tracks the header's own responsive top offset. The +26px
	   approximates the header's rendered content height (logo line-box,
	   ~20px, plus a hair of breathing room) — there's no explicit
	   --header-height token to read yet.
	   --work-toolbar-own-height approximates .v2-work__controls' own rendered
	   height at desktop widths (a 14px control + 4px top/bottom pill padding
	   ≈ 25px, plus the toolbar's own 12px top/bottom padding ≈ 49px) — same
	   component-level-judgement pattern as the offset above, measured against
	   the live page rather than derived from a token (no --control-height
	   token exists yet either). Together with --work-toolbar-backdrop-extra
	   (extra breathing room below the toolbar's own bottom edge, per visual
	   feedback), they size the single shared backdrop below — still exactly
	   one element/one blur pass, just taller. */
	--work-toolbar-content-offset: calc(var(--header-top-spacing) + 26px);
	--work-toolbar-own-height: 49px;
	--work-toolbar-backdrop-extra: 35px;
	--work-toolbar-backdrop-height: calc(var(--work-toolbar-content-offset) + var(--work-toolbar-own-height) + var(--work-toolbar-backdrop-extra));
	/* Intensity refinement pass: now resolves to the shared --sticky-veil-*
	   tokens (base.css) instead of its own hardcoded 14px/0.4 — same values
	   Info's own backdrop uses, so the two pages read as one consistent
	   sticky-veil language. Kept as Work's own named token (rather than
	   replacing every reference below with the shared name directly) so
	   nothing else in this file needs to change. */
	--work-toolbar-blur: var(--sticky-veil-blur);
	--work-toolbar-bg: var(--sticky-veil-bg);

	/* Shared 14px scale for Header nav / Work filters / Grid-List — reuses
	   --type-nav-size (base.css) rather than a parallel token, since that's
	   already the "navigation/control text" token and is also applied to the
	   Header nav links this same value now drives. */

	/* Active-filter icon micro-interaction (enter/exit) — deliberately NOT
	   --transition-ui (200ms, shared with Header/Footer/filter hover states):
	   this is a slightly slower, eased "premium" feel rather than a snappy
	   UI-feedback one. Same easing curve family as --home-title-transition
	   for consistency across the theme's few eased (non-linear) transitions. */
	--work-filter-icon-transition: 260ms cubic-bezier(0.4, 0, 0.2, 1);
}

.v2-work {
	flex: 1;
	padding-top: var(--work-content-top);
	padding-bottom: var(--work-section-bottom);
}

/* ─── Intro ──────────────────────────────────────────────────────────────── */

.v2-work__intro {
	max-width: var(--work-intro-max-width);
	margin: 0 auto;
	padding: 0 var(--page-margin);
	text-align: center;
}

.v2-work__intro-title {
	margin: 0;
	font-family: var(--font-base);
	font-size: clamp(28px, 2.3vw, 38px);
	font-weight: 500;
	line-height: 1.15;
	letter-spacing: -0.025em;
	color: var(--color-fg);
}

.v2-work__intro-text {
	max-width: var(--work-intro-text-max-width);
	margin: var(--work-intro-gap) auto 0;
	font-family: var(--font-base);
	font-size: clamp(13px, 1vw, 15px);
	font-weight: var(--weight-body);
	line-height: 1.5;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
}

.v2-work__intro-text p {
	margin: 0;
}

.v2-work__intro-text p + p {
	margin-top: 1em;
}

.v2-work__intro-text strong {
	font-weight: 600;
}

/* ─── Filters / Grid-List controls ──────────────────────────────────────── */

/* ONE shared sticky backdrop behind both the Header and the Work toolbar
   (visual-correction pass — replaces an earlier version of this file that
   painted background+backdrop-filter on .v2-header AND .v2-work__controls
   AND this bridge separately: three independent backdrop-filter passes,
   each blurring/darkening only its own box. Where they overlapped that
   stacked into visibly darker patches, and even where their edges merely
   touched with zero gap, two adjacent-but-separate backdrop-filter regions
   still read as a seam — each is its own independent render pass with its
   own blur sampling at its own edge, so they never blend into one surface.
   The fix: exactly one element does the blurring, sized to cover the full
   combined Header + toolbar zone; .v2-header and .v2-work__controls are
   now fully transparent again and simply sit on top of it (both already
   have z-index:20/15 respectively, above this layer's z-index:10).
   `position: fixed`, not sticky — never occupies document-flow space (no
   added gap anywhere), always sits at the viewport top.
   `mask-image` fades the bottom ~35% of this ONE backdrop — full strength
   through Header + controls (0-65%), then a multi-stop curve (not a plain
   2-stop linear ramp) easing it out to fully transparent by 100%. The extra
   stops exist so the fade itself reads as gradual/atmospheric rather than a
   ramp with a perceptible start — a straight black→transparent line still
   has a visible "kink" where the drop begins, even though it ends at 0.
   This masks the backdrop's own blur+background together (both fade as one,
   not a separate dark gradient layered on top), so what's underneath
   genuinely regains sharpness rather than just getting less dark.

   Top-clean / scroll-fade refinement pass (harmonized with Info's own
   backdrop, pages.css "─── Hero ───" section): this used to be always-on at
   desktop (only mobile faded in via body.is-scrolled) and used a much
   stronger blur/veil (14px / 0.4, now the shared --sticky-veil-* tokens
   above, 7px / 0.18). Both the "always visible" desktop behavior and the
   strong intensity read as too present — this now starts fully transparent
   at EVERY viewport width and only fades in on body.is-scrolled (assets/js/
   work.js's existing scroll-state listener, unchanged), same threshold and
   same one-CSS-transition approach as before, just no longer gated behind
   the mobile-only @media block. Still exactly one element/one blur pass —
   Header and .v2-work__controls remain fully transparent and simply sit on
   top of it, so there is still no seam between them, only now the whole
   unified surface itself fades in/out as one. */
.page-template-page-work-php .v2-work::before {
	content: '';
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	height: var(--work-toolbar-backdrop-height);
	z-index: 10;
	background: var(--work-toolbar-bg);
	backdrop-filter: blur(var(--work-toolbar-blur));
	-webkit-backdrop-filter: blur(var(--work-toolbar-blur));
	-webkit-mask-image: linear-gradient(
		to bottom,
		black 0%,
		black 65%,
		rgba(0, 0, 0, 0.85) 76%,
		rgba(0, 0, 0, 0.5) 87%,
		rgba(0, 0, 0, 0.18) 96%,
		transparent 100%
	);
	        mask-image: linear-gradient(
		to bottom,
		black 0%,
		black 65%,
		rgba(0, 0, 0, 0.85) 76%,
		rgba(0, 0, 0, 0.5) 87%,
		rgba(0, 0, 0, 0.18) 96%,
		transparent 100%
	);
	pointer-events: none;
	opacity: 0;
	transition: opacity 220ms ease;
}

.page-template-page-work-php.is-scrolled .v2-work::before {
	opacity: 1;
}

.v2-work__controls {
	/* Sticky: sits below the fixed .v2-header (z-index:20, components.css) and
	   stays on screen while the grid/list scroll underneath — brief §9/§13.
	   No background/blur of its own — it sits on the single shared backdrop
	   above (.v2-work::before), which is sized to end exactly at this
	   element's own bottom edge (--work-toolbar-own-height). */
	position: sticky;
	top: var(--work-toolbar-content-offset);
	z-index: 15;
	margin-top: var(--work-controls-top);
	margin-bottom: var(--work-controls-bottom);
	padding: 12px var(--page-margin);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	flex-wrap: wrap;
}

.v2-work__filters {
	display: flex;
	align-items: center;
	gap: 6px;
	flex-wrap: wrap;
}

/* Structural pill look shared verbatim with Info's shared section-label
   component (.v2-section-pill — About Us/Workflow/Services/Clients/
   Commitment, pages.css "─── Info ───" section) — grouped into this same
   rule rather than copied, so every Info section pill is guaranteed pixel-
   identical to Work's active filter by construction, not by matching
   numbers by hand. Work's own rendering is unaffected: every property/value
   below is exactly what this rule already declared; only interactive-only
   properties (background, cursor, default opacity, transition) stay
   isolated in the Work-only rule further down, since Info's labels must
   never look or behave clickable. */
.v2-work__filter,
.v2-section-pill {
	display: inline-flex;
	align-items: center;
	/* No `gap` here — the icon's own width + margin-right carry that space
	   instead, so both can animate to/from 0 together (a flex `gap` doesn't
	   collapse just because an item's own width does). */
	font-family: var(--font-base);
	font-size: var(--type-nav-size);
	font-weight: var(--type-nav-weight);
	line-height: 1.2;
	color: var(--color-fg);
	border: none;
	border-radius: 4px;
	/* Vertical padding trimmed slightly to compensate for the larger 14px
	   label (was 11.2px) — keeps the pill roughly as compact as before
	   instead of growing with the type-scale standardization. */
	padding: 4px 11px;
	margin: 0;
	white-space: nowrap;
}

.v2-work__filter {
	background: rgba(240, 242, 228, 0.07);
	cursor: pointer;
	opacity: var(--opacity-muted);
	transition: opacity var(--transition-ui), background-color var(--transition-ui);
}

.v2-work__filter:hover {
	opacity: var(--opacity-secondary);
}

/* The ACTIVE filter's look — exactly what every Info section pill uses
   permanently (they have no inactive/hover state at all, see above). */
.v2-work__filter.is-active,
.v2-section-pill {
	opacity: 1;
	background: rgba(240, 242, 228, 0.16);
}

/* Base icon look shared verbatim with Info's shared pill icon
   (.v2-section-pill-icon) — same source asset, same mask technique, same
   height. Only the Work-only enter/exit animation (width/margin-right/
   opacity/transform defaults + transition, below) stays isolated to
   .v2-work__filter-icon alone: Info's icons are never toggled, they only
   ever need the fully-shown end state (grouped further below). */
.v2-work__filter-icon,
.v2-section-pill-icon {
	display: inline-block;
	height: 13px;
	background-color: var(--color-fg);
	-webkit-mask-image: url('../images/mark.svg');
	        mask-image: url('../images/mark.svg');
	-webkit-mask-repeat: no-repeat;
	        mask-repeat: no-repeat;
	-webkit-mask-position: center;
	        mask-position: center;
	-webkit-mask-size: contain;
	        mask-size: contain;
	flex-shrink: 0;
}

.v2-work__filter-icon {
	/* Always in flow (no display:none/block toggle) so the enter/exit is a
	   genuine transition, not a hard cut — width/margin-right animate the
	   space it occupies open and shut (margin-right, not -left: the gap
	   this needs to animate is between the icon and the label text that
	   follows it, not between the icon and the button's own left padding),
	   opacity+translateX animate the mark itself fading and sliding in from
	   the left, matching the requested micro-interaction. `width` is an
	   explicit px value (not `auto`, which can't be transitioned) — 18px is
	   13px's own height scaled by the mark's aspect ratio (538.97/382.05),
	   i.e. the same visual size as before, just reachable as a transition
	   endpoint. */
	width: 0;
	margin-right: 0;
	opacity: 0;
	transform: translateX(-6px);
	transition:
		width var(--work-filter-icon-transition),
		margin-right var(--work-filter-icon-transition),
		opacity var(--work-filter-icon-transition),
		transform var(--work-filter-icon-transition);
}

/* The ACTIVE filter's fully-shown icon state — exactly what Info's icons
   use permanently (no transition, no toggle, see above). */
.v2-work__filter.is-active .v2-work__filter-icon,
.v2-section-pill-icon {
	width: 18px;
	margin-right: 6px;
	opacity: 1;
	transform: translateX(0);
}

/* Mobile stale-filter-state fix — root cause: .v2-work__filter's background/
   opacity transition (--transition-ui, 200ms) and .v2-work__filter-icon's
   width/margin-right/opacity/transform transition (--work-filter-icon-
   transition, 260ms above) are symmetric — they animate the SAME duration
   whether a button is gaining or LOSING .is-active. On a touch/coarse-pointer
   device, switching filters (especially across the two wrapped rows this
   list uses at mobile widths) removes .is-active from the old button and
   adds it to the new one in the same tick, but the old button's icon/
   background then visibly fades out over its own ~200-260ms — during that
   window the previous pill still shows a partial icon and a brighter-than-
   inactive background at the same time the new pill is already fully active,
   reading as a leftover/duplicate active fragment (worse when the two
   buttons sit on different wrapped rows). Repeated/rapid taps only stack
   more of these overlapping fade-outs.
   Fine-pointer/hover devices keep the exact existing animated transition
   (desktop is unaffected, per brief). Touch/coarse-pointer devices collapse
   the same transitions to effectively instant, so a tap's visual state
   always matches the actual active filter immediately — same technique the
   prefers-reduced-motion rule above already uses for this exact icon. */
@media (hover: none), (pointer: coarse) {
	.v2-work__filter,
	.v2-work__filter-icon {
		transition-duration: 1ms;
	}
}

/* Defensive alignment fixes — NOT part of the reused Work-filter look above,
   and deliberately NOT declared on the shared .v2-section-pill class itself.
   Info's pills sit in several different flex/grid wrapper contexts across
   the 5 sections, and a blanket `align-self` would be wrong for at least
   one of them:
     - About: fixed at the wrapper level (.v2-info__about-grid .v2-info__
       label-col, "─── Info ───" section below) — that grid item defaulted
       to `stretch`, which is what turned the pill into a tall vertical
       block.
     - Commitment: .v2-info__commitment-left is a column flex container with
       no `align-items` override (defaults to `stretch`), which would
       equally stretch the pill to the container's full width — fixed here,
       scoped to the pill only inside that one wrapper.
     - Workflow/Services/Clients: their pill lives in its own
       .v2-info__label-col, a row-direction flex container sized to its own
       single-line content — no stretch occurs either way, nothing to fix.
       (Clients' pill previously lived inside .v2-info__clients-top, whose
       `align-items: center` centered it along with the intro line — a
       structural bug, not an intentional design; the pill now has its own
       preceding .v2-info__label-col, same as Workflow/Services, and
       .v2-info__clients-top wraps only the centered intro line.) */
.v2-info__commitment-left .v2-section-pill {
	align-self: flex-start;
}

.v2-work__view-toggle {
	display: flex;
	align-items: center;
	gap: 10px;
}

.v2-work__view-btn {
	font-family: var(--font-base);
	font-size: var(--type-nav-size);
	font-weight: var(--type-nav-weight);
	line-height: 1.2;
	color: var(--color-fg);
	background: none;
	border: none;
	padding: 0;
	margin: 0;
	cursor: pointer;
	opacity: var(--opacity-muted);
	transition: opacity var(--transition-ui);
}

.v2-work__view-btn:hover {
	opacity: var(--opacity-secondary);
}

.v2-work__view-btn.is-active {
	opacity: 1;
}

.v2-work__view-track {
	position: relative;
	display: inline-block;
	width: 30px;
	height: 14px;
	border: 1px solid rgba(240, 242, 228, 0.3);
	border-radius: 999px;
	cursor: pointer;
}

.v2-work__view-knob {
	position: absolute;
	top: 1px;
	left: 1px;
	width: 10px;
	height: 10px;
	border-radius: 50%;
	background: var(--color-fg);
	transition: transform var(--transition-ui);
}

.v2-work__view-toggle--list .v2-work__view-knob {
	transform: translateX(16px);
}

/* ─── Grid ───────────────────────────────────────────────────────────────── */

.v2-work__grid {
	display: grid;
	grid-template-columns: repeat(var(--grid-columns-desktop), 1fr);
	gap: var(--grid-gutter);
	padding: 0 var(--page-margin);
}

.v2-work-card {
	grid-column: span 4;
	min-width: 0; /* grid items default to min-width:auto, which can force the track wider than available space */
}

.v2-work-card__link {
	display: block;
	color: inherit;
	text-decoration: none;
}

.v2-work-card__media {
	position: relative;
	width: 100%;
	aspect-ratio: var(--ratio-work);
	overflow: hidden;
	border-radius: var(--v2-media-radius);
	background: rgba(240, 242, 228, 0.04);
}

.v2-work-card__image,
.v2-work-card__video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.v2-work-card__video {
	opacity: 0;
	transition: opacity var(--transition-ui);
}

.v2-work-card__video.is-playing {
	opacity: 1;
}

.v2-work-card__info {
	margin-top: var(--work-card-info-gap);
}

.v2-work-card__title {
	margin: 0;
	font-family: var(--font-base);
	font-size: clamp(15px, 1.1vw, 18px);
	font-weight: 400;
	line-height: 1.25;
	letter-spacing: -0.02em;
	color: var(--color-fg);
}

.v2-work-card__meta {
	display: block;
	margin-top: var(--work-card-meta-gap);
	font-family: var(--font-base);
	font-size: var(--type-utility-size);
	font-weight: var(--weight-body);
	color: var(--color-fg);
	opacity: var(--opacity-muted);
}

/* ─── List ───────────────────────────────────────────────────────────────── */

.v2-work__list {
	padding: 0 var(--page-margin);
}

.v2-work-row {
	display: grid;
	grid-template-columns: repeat(var(--grid-columns-desktop), 1fr);
	align-items: center;
	gap: var(--grid-gutter);
	padding: var(--work-row-padding-y) 0;
	border-bottom: 1px solid rgba(240, 242, 228, 0.12);
	color: var(--color-fg);
	text-decoration: none;
	transition: opacity var(--transition-ui);
}

.v2-work-row:hover {
	opacity: var(--opacity-secondary);
}

.v2-work-row__col {
	min-width: 0; /* grid items default to min-width:auto — with white-space:nowrap that equals the full unwrapped text width, which forces the row (and the whole page) wider than the viewport instead of letting the ellipsis truncate */
	font-family: var(--font-base);
	font-size: var(--type-utility-size);
	font-weight: var(--weight-body);
	line-height: 1.3;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.v2-work-row__col--title {
	grid-column: span 5;
	/* Now an <h3> (heading-semantics pass, matches Grid's card title) — margin
	   neutralizes the browser default h3 margin, which this row's alignment
	   with its sibling columns (none of which carry margin) can't absorb. */
	margin: 0;
	/* Overrides the lighter --weight-body above — the project title stays at
	   its existing weight (project hierarchy, not supporting metadata). */
	font-weight: 500;
	letter-spacing: -0.01em;
	text-transform: uppercase;
	white-space: normal;
}

.v2-work-row__col--director {
	grid-column: span 3;
	text-transform: uppercase;
	opacity: var(--opacity-secondary);
}

.v2-work-row__col--duration {
	grid-column: span 2;
	opacity: var(--opacity-secondary);
}

.v2-work-row__col--category {
	grid-column: span 2;
	text-align: right;
	text-transform: uppercase;
	opacity: var(--opacity-muted);
}

/* ─── Empty state ────────────────────────────────────────────────────────── */

.v2-work__empty {
	padding: 0 var(--page-margin);
	font-family: var(--font-base);
	font-size: var(--type-nav-size);
	opacity: var(--opacity-muted);
}

/* Motion: `.v2-reveal`/`.v2-reveal--transition`/`.v2-reveal--tight` and their
   `html.motion-enabled` gate now live in base.css — shared with Home's
   entrance, not Work-specific (see that file for the full rationale). */

/* ─── Tablet / intermediate ──────────────────────────────────────────────── */

@media (max-width: 1024px) {
	.v2-work-card {
		grid-column: span 6;
	}
}

/* ─── Mobile ─────────────────────────────────────────────────────────────── */

@media (max-width: 767px) {
	.v2-work {
		padding-top: clamp(84px, 20vw, 104px);
	}

	/* The shared Header+toolbar backdrop's top-clean/scroll-fade behavior
	   (opacity 0 → 1 on body.is-scrolled) is no longer mobile-only — it's
	   declared once, unconditionally, on the base .v2-work::before rule
	   above (Filters / Grid-List controls section) — so nothing needs
	   overriding here. Kept as a placeholder comment rather than silently
	   removing the subsection so a future mobile-only tweak has an obvious
	   home. */

	/* --work-toolbar-own-height (49px) was measured against desktop's
	   single-row toolbar. On mobile .v2-work__controls stacks to column
	   layout (below) and the filters wrap across ~3 rows, so the toolbar is
	   actually ~110px tall here — without this override the backdrop ended
	   ~37px short of the toolbar's real bottom edge, leaving its last row
	   (Grid/List) sitting on unblurred content. Same single --work-toolbar-*
	   token system, just corrected for this breakpoint's real layout. */
	:root {
		--work-toolbar-own-height: 112px;

		/* Mobile-only extra breathing room between the fixed header and the
		   sticky controls once scrolled — same derivation as the desktop
		   value (--header-top-spacing, components.css) so it still tracks the
		   header's own responsive top offset, just with a larger constant
		   (was +26px) for a more comfortable gap at mobile widths. Backdrop
		   height (--work-toolbar-backdrop-height above) is calculated FROM
		   this token, so the blur/background automatically extends to match
		   — no separate change needed there. */
		--work-toolbar-content-offset: calc(var(--header-top-spacing) + 50px);
	}

	.v2-work-card {
		grid-column: span 12;
	}

	/* Mobile-only vertical breathing room between one-column Work cards —
	   row-gap raised from the shared --grid-gutter (16px) to 36px so each
	   card (image + title + category) reads as its own unit;
	   column-gap stays on --grid-gutter (irrelevant at 1 column, kept so
	   this never drifts from the shared horizontal value). Grid mode only —
	   .v2-work-row (List) is untouched. */
	.v2-work__grid {
		row-gap: 36px;
		column-gap: var(--grid-gutter);
	}

	.v2-work__controls {
		flex-direction: column;
		align-items: center;
		gap: 14px;
	}

	/* Mobile-only: center each wrapped filter row (rather than left-aligning
	   against the container's widest row) so the control area reads as one
	   centered block under the already-centered hero above (.v2-work__intro).
	   width:100% is required for justify-content:center to have real room to
	   center wrapped lines within — .v2-work__filter and its flex-wrap/gap
	   behavior above are untouched, this only repositions the rows as a
	   whole. */
	.v2-work__filters {
		width: 100%;
		justify-content: center;
	}

	.v2-work-row {
		grid-template-columns: 1fr;
		gap: 4px;
		padding: 14px 0;
	}

	.v2-work-row__col {
		white-space: normal;
	}

	.v2-work-row__col--title {
		grid-column: 1;
	}

	.v2-work-row__col--director {
		grid-column: 1;
		order: 2;
		font-size: 11px;
	}

	/* Duration is the least essential List column at this width — dropped
	   rather than forcing a horizontal scroll (this phase's brief §36). */
	.v2-work-row__col--duration {
		display: none;
	}

	.v2-work-row__col--category {
		grid-column: 1;
		order: 3;
		text-align: left;
		font-size: 11px;
	}
}

/* ─── Single Work ────────────────────────────────────────────────────────── */

:root {
	/* Same "clears the fixed header" clearance as Work's own token — kept as
	   a separate token (not a reference to --work-content-top) since the two
	   pages' top spacing is a per-page visual decision, even though the
	   values start out matching (same precedent as --header-top-spacing vs
	   --footer-bottom-spacing in components.css). */
	--single-work-content-top: clamp(96px, 10vw, 136px);
	--single-work-section-bottom: clamp(64px, 7vw, 104px);
	--single-work-info-columns: 4;
	--single-work-media-columns: 8;
	--single-work-group-gap: clamp(18px, 2vw, 26px);
	--single-work-nav-gap: clamp(96px, 10vw, 136px);

	/* Header sticky-backdrop (consistency fix): Single Work was missing the
	   validated Header blur/veil Work/Info/Legal already have — same root
	   cause as Info's own fix (see ".page-template-page-info-php .v2-info::
	   before" comment, "─── About Us ───" section above): Work's version is
	   scoped to Work's own page-template body class and sized to cover
	   Work's Header+toolbar combined, neither of which exist here. This is
	   the same mechanism transplanted onto Single Work's own wrapper class,
	   sized to the Header's own zone only (no toolbar), same as Info/Legal
	   — copied, not reinvented, so the fade quality matches exactly. */
	--single-work-header-backdrop-height: calc(var(--work-toolbar-content-offset) + var(--work-toolbar-backdrop-extra));
	--single-work-header-backdrop-blur: var(--sticky-veil-blur);
	--single-work-header-backdrop-bg: var(--sticky-veil-bg);
}

.v2-single-work {
	flex: 1;
	padding-top: var(--single-work-content-top);
	padding-bottom: var(--single-work-section-bottom);
}

/* Fades in only once body.is-scrolled is set (assets/js/header-scroll.js —
   the same generic scroll-state script Info/Legal use, now also enqueued
   for this template in inc/enqueue.php). `.single` (WP core body class for
   any CPT singular) scopes this to Single Work only, since there is no
   shared page-template class across the three project CPTs. Identical
   mask-image curve, z-index, pointer-events and transition to Work's/
   Info's/Legal's own rule. `position: fixed`, so it never occupies flow
   space. */
.single .v2-single-work::before {
	content: '';
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	height: var(--single-work-header-backdrop-height);
	z-index: 10;
	background: var(--single-work-header-backdrop-bg);
	backdrop-filter: blur(var(--single-work-header-backdrop-blur));
	-webkit-backdrop-filter: blur(var(--single-work-header-backdrop-blur));
	-webkit-mask-image: linear-gradient(
		to bottom,
		black 0%,
		black 65%,
		rgba(0, 0, 0, 0.85) 76%,
		rgba(0, 0, 0, 0.5) 87%,
		rgba(0, 0, 0, 0.18) 96%,
		transparent 100%
	);
	        mask-image: linear-gradient(
		to bottom,
		black 0%,
		black 65%,
		rgba(0, 0, 0, 0.85) 76%,
		rgba(0, 0, 0, 0.5) 87%,
		rgba(0, 0, 0, 0.18) 96%,
		transparent 100%
	);
	pointer-events: none;
	opacity: 0;
	transition: opacity 220ms ease;
}

.single.is-scrolled .v2-single-work::before {
	opacity: 1;
}

.v2-single-work__grid {
	display: grid;
	grid-template-columns: repeat(var(--grid-columns-desktop), 1fr);
	gap: var(--grid-gutter);
	padding: 0 var(--page-margin);
	align-items: start;
}

.v2-single-work__info {
	grid-column: span var(--single-work-info-columns);
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: var(--single-work-group-gap);
}

.v2-single-work__media {
	grid-column: span var(--single-work-media-columns);
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 14px;
	/* Sticky player (desktop/tablet two-column layout only — undone in the
	   mobile breakpoint below, where the layout is a single stacked column).
	   `top` reuses --single-work-content-top, the same "clears the fixed
	   .v2-header" clearance this page already pads its own top with (see
	   the :root block above), so the sticky player rests at exactly the
	   position it started in — no jump — and never slides under the header.
	   The grid container's own `align-items: start` (.v2-single-work__grid)
	   keeps this item's containing block equal to the grid row's full
	   height (the taller .v2-single-work__info column when info is long),
	   so native sticky naturally stops at that row's bottom edge — i.e. it
	   never escapes .v2-single-work__grid into the section's own bottom
	   padding or the footer beyond it. */
	position: sticky;
	top: var(--single-work-content-top);
}

/* ─── Info column ────────────────────────────────────────────────────────── */

/* Main project title — kept at its existing weight (not --weight-body):
   this is a major heading (design-guidelines.md "Single Work project
   title"), not supporting copy. */
.v2-single-work__title {
	margin: 0;
	font-family: var(--font-base);
	font-size: clamp(28px, 2.4vw, 40px);
	font-weight: 400;
	line-height: 1.1;
	letter-spacing: -0.025em;
	color: var(--color-fg);
}

.v2-single-work__group {
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.v2-single-work__label {
	font-family: var(--font-base);
	font-size: var(--type-meta-size);
	font-weight: var(--weight-body);
	line-height: 1.3;
	color: var(--color-fg);
	opacity: var(--opacity-muted);
}

.v2-single-work__value {
	font-family: var(--font-base);
	/* 15px, matching Info's own confirmed body-copy size (pages.css
	   .v2-info__about-text) rather than the fluid --type-body-size token
	   (base.css, clamp(13px,1vw,16px), left untouched for the other pages
	   still using it) — typography-consistency pass. */
	font-size: 15px;
	font-weight: var(--weight-body);
	line-height: 1.4;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
}

.v2-single-work__description {
	font-family: var(--font-base);
	font-size: 15px;
	font-weight: var(--weight-body);
	line-height: 1.55;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
}

.v2-single-work__description p {
	margin: 0;
}

.v2-single-work__description p + p {
	margin-top: 1em;
}

.v2-single-work__categories {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 4px clamp(14px, 1.6vw, 22px);
}

.v2-single-work__category {
	font-family: var(--font-base);
	/* Was --type-meta-size (the label token, clamp(11px,0.9vw,13px)) — same
	   root cause as .v2-single-work__value/__description above: this is a
	   value/content row, not a label, so it should match their 15px, not
	   the smaller label size. */
	font-size: 15px;
	font-weight: var(--weight-body);
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
	white-space: nowrap;
}

/* ─── Media column: Back to Work / Next Work ────────────────────────────── */

.v2-single-work__nav {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--single-work-nav-gap);
}

.v2-single-work__nav-link {
	font-family: var(--font-base);
	font-size: var(--type-nav-size);
	font-weight: var(--type-nav-weight);
	letter-spacing: var(--type-nav-tracking);
	line-height: 1.2;
	color: var(--color-fg);
	text-decoration: none;
	opacity: var(--opacity-muted);
	transition: opacity var(--transition-ui);
}

.v2-single-work__nav-link:hover,
.v2-single-work__nav-link:focus-visible {
	opacity: 1;
}

.v2-single-work__player-wrap {
	width: 100%;
}

/* ─── Tablet / intermediate ──────────────────────────────────────────────── */

@media (max-width: 1024px) {
	.v2-single-work__info {
		grid-column: span 5;
	}

	.v2-single-work__media {
		grid-column: span 7;
	}
}

/* ─── Mobile ─────────────────────────────────────────────────────────────── */
/*
 * Desktop's left/right columns become one vertical sequence, revised per
 * mobile-refinement feedback: Back to Work / Next Work, then the player,
 * THEN the title/info block (title → description → information rows →
 * categories, its existing internal order unchanged) — media before
 * information, not after, on narrow layouts.
 *
 * Achieved with `order` only — .v2-single-work__media and
 * .v2-single-work__info keep their existing DOM order (info first, matching
 * desktop's visual left/right reading order); flexbox `order` on this
 * existing column-direction flex container (below) is enough to flip their
 * visual sequence on mobile without touching the shared markup or desktop's
 * grid-column placement.
 */

@media (max-width: 767px) {
	.v2-single-work {
		padding-top: clamp(84px, 20vw, 104px);
	}

	.v2-single-work__grid {
		display: flex;
		flex-direction: column;
		gap: clamp(28px, 7vw, 40px);
		/* The desktop rule's `align-items: start` (above) is a CSS Grid
		   row-alignment value (don't stretch the two columns to match
		   height) — it's never re-declared for this breakpoint's flex
		   context, so it was still cascading through and, in flexbox,
		   `align-items: start` means "don't stretch items to the
		   container's width, shrink-wrap each to its own content" instead.
		   That's what was shrinking .v2-single-work__media/__info (and the
		   100%-width player inside) down to a small content-sized box
		   instead of the full content width. Reset back to the flex
		   default here. */
		align-items: stretch;
	}

	.v2-single-work__media {
		order: -1;
		/* Desktop/tablet-only behavior (rule above) — mobile keeps the
		   current stacked structure, player scrolls normally with the rest
		   of the page. */
		position: static;
	}

	.v2-single-work__info {
		order: 1;
	}

	.v2-single-work__nav {
		gap: 24px;
	}
}

/* ─── Contact ────────────────────────────────────────────────────────────── */
/*
 * Simplified static composition (2026-09, against the approved desktop
 * mockup): centred heading (animated greeting + static text), centred
 * horizontal Label/Value rows, then a grid of ALL locations. No slider.
 *
 * Scroll model: the page flows and scrolls natively at every size
 * (min-height 100svh; `.v2-contact__main { flex: 1 }` keeps the footer at
 * the bottom when the content is short, and pushes it down when it is long).
 * `.v2-contact` clips horizontally as a guard. Lenis is intentionally NOT
 * loaded here (inc/enqueue.php) — native scroll only.
 *
 * Proportions are measured from the mockup and expressed in vw: cards are
 * ≈22.6vw wide with ≈3.7vw between them (grid ≈75vw, centred).
 */

:root {
	--contact-card-w: clamp(220px, 22.6vw, 440px);
	--contact-card-gap: clamp(24px, 3.7vw, 72px);
	--contact-grid-cols: 3;

	--contact-top: 88px;                             /* header clearance (fixed header) */
	--contact-group-gap: clamp(28px, 4.6vw, 80px);   /* heading → rows */
	--contact-block-gap: clamp(40px, 6vw, 104px);    /* rows → locations */
	--contact-row-gap: 18px;                         /* row ↔ row when wrapped */
	--contact-row-space: clamp(36px, 5vw, 88px);     /* row ↔ row side by side */
	--contact-col-gap: clamp(16px, 2.4vw, 40px);     /* label ↔ value */
	--contact-greeting-size: clamp(24px, 2.2vw, 42px);
	--contact-value-size: clamp(17px, 1.6vw, 30px);
	--contact-name-size: clamp(16px, 1.5vw, 27px);
	--contact-line: rgba(240, 242, 228, 0.42);
}

.v2-contact {
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
	overflow-x: clip;
}

/* ─── Content ────────────────────────────────────────────────────────────── */

.v2-contact__main {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--contact-block-gap);
	/* Clears the fixed, transparent header. */
	padding: var(--contact-top) var(--page-margin) 32px;
}

/* Desktop/tablet: the whole group is centred in the space between the
   header clearance and the footer. When the content is taller than that
   space the padding above keeps it clear and the page scrolls naturally. */
@media (min-width: 768px) {
	.v2-contact__main {
		justify-content: center;
	}
}

.v2-contact__intro {
	min-width: 0;
	max-width: 100%;
}

.v2-contact__block {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--contact-group-gap);
	text-align: center;
}

.v2-contact__greeting {
	margin: 0;
	font-family: var(--font-base);
	font-size: var(--contact-greeting-size);
	font-weight: 400;
	line-height: 1.2;
	letter-spacing: -0.025em;
	color: var(--color-fg);
	will-change: transform, opacity;
}

/* ─── Information rows ───────────────────────────────────────────────────── */
/* Rows sit side by side, centred; they wrap onto their own lines when the
   width runs out. Works for any row count. */

.v2-contact__rows {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--contact-row-gap) var(--contact-row-space);
}

.v2-contact__row {
	display: flex;
	align-items: baseline;
	gap: var(--contact-col-gap);
	max-width: 100%;
}

.v2-contact__label {
	font-family: var(--font-base);
	font-size: var(--type-meta-size);
	font-weight: var(--weight-body);
	line-height: 1.3;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
}

.v2-contact__value {
	/* min-width: 0 lets a long unbroken value wrap instead of widening the page. */
	min-width: 0;
	font-family: var(--font-base);
	font-size: var(--contact-value-size);
	font-weight: var(--weight-body);
	line-height: 1.2;
	letter-spacing: -0.01em;
	text-align: left;
	color: var(--color-fg);
	text-decoration: none;
	transition: opacity var(--transition-ui);
	overflow-wrap: anywhere;
}

a.v2-contact__value:hover,
a.v2-contact__value:focus-visible {
	opacity: 0.7;
}

.v2-contact__value--static {
	cursor: default;
}

/* ─── Animated first word ────────────────────────────────────────────────── */

/* The wrapper is as wide as the greeting currently shown (its natural
   width; contact-greeting.js tweens it between two greetings' widths), and
   the static text follows with a constant gap, whichever greeting is up. */
.v2-contact__hello {
	display: inline-block;
	white-space: nowrap;
	vertical-align: baseline;
}

.v2-contact__hello-word {
	display: inline-block;
	will-change: transform, opacity;
}

.v2-contact__heading-text {
	/* Constant gap (was a literal space); absent entirely when the text is
	   empty, so a greeting-only heading has no trailing gap. */
	margin-left: 0.28em;
}

.v2-contact__hello:empty + .v2-contact__heading-text,
.v2-contact__heading-text:first-child {
	margin-left: 0;
}

/* ─── Locations grid ─────────────────────────────────────────────────────── */
/*
 * The container is capped at `--contact-grid-cols` cards wide; `auto-fit`
 * tracks then collapse when there are fewer locations (so 1-2 stay
 * centred), and any extra locations wrap onto new rows in backend order.
 */

.v2-contact__locations {
	width: 100%;
	min-width: 0;
	max-width: calc(var(--contact-grid-cols) * var(--contact-card-w) + (var(--contact-grid-cols) - 1) * var(--contact-card-gap));
}

.v2-contact__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, var(--contact-card-w));
	justify-content: center;
	gap: clamp(36px, 4.6vw, 80px) var(--contact-card-gap);
	margin: 0;
	padding: 0;
	list-style: none;
}

.v2-contact__item {
	min-width: 0;
}

.v2-contact__card {
	display: block;
	color: var(--color-fg);
	text-decoration: none;
	-webkit-tap-highlight-color: transparent;
}

.v2-contact__card:focus-visible {
	outline: 1px solid var(--contact-line);
	outline-offset: 4px;
}

.v2-contact__media {
	display: block;
	aspect-ratio: 27 / 16;
	overflow: hidden;
	border-radius: var(--v2-media-radius);
	background: #0d0d0d;
}

.v2-contact__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.v2-contact__location-name {
	display: block;
	margin-top: clamp(18px, 2.15vw, 34px);
	font-family: var(--font-base);
	font-size: var(--contact-name-size);
	font-weight: var(--weight-body);
	line-height: 1.2;
	letter-spacing: -0.01em;
}

.v2-contact__location-address {
	display: block;
	margin-top: 8px;
	font-family: var(--font-base);
	font-size: var(--type-meta-size);
	font-weight: var(--weight-body);
	line-height: 1.45;
	opacity: var(--opacity-secondary);
}

/* ─── Tablet: two per row ────────────────────────────────────────────────── */

@media (max-width: 1099px) {
	:root {
		--contact-grid-cols: 2;
		--contact-card-w: clamp(220px, 32vw, 380px);
		--contact-card-gap: clamp(24px, 5vw, 56px);
	}
}

/* ─── Mobile: one per row ────────────────────────────────────────────────── */

@media (max-width: 767px) {
	:root {
		--contact-grid-cols: 1;
		--contact-card-w: 100%;
		--contact-top: 104px;
		--contact-group-gap: 27px;
		--contact-block-gap: 48px;
		--contact-row-gap: 18px;
		--contact-col-gap: 20px;
		--contact-greeting-size: clamp(26px, 8.9vw, 44px);
		--contact-value-size: clamp(18px, 6.2vw, 26px);
		--contact-name-size: 18px;
	}

	.v2-contact__locations {
		max-width: none;
	}

	.v2-contact__grid {
		grid-template-columns: minmax(0, 1fr);
		row-gap: 40px;
	}

	/* Mockup image is wider/shorter on phones (≈2:1). */
	.v2-contact__media {
		aspect-ratio: 2 / 1;
	}

	.v2-contact__label {
		font-size: 12px;
	}

	.v2-contact__location-name {
		margin-top: 21px;
	}

	.v2-contact__location-address {
		margin-top: 7px;
		font-size: 12px;
	}
}

/* ─── Reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
	.v2-contact__value {
		transition-duration: 1ms !important;
	}
}

/* ─── Info ───────────────────────────────────────────────────────────────── */
/*
 * Long-form editorial page (design-guidelines.md §38-45) — normal scrolling,
 * same as Work, not fullscreen/no-scroll like Home/Contact. Section order:
 * Hero → About Us → Workflow → Services → Clients → Commitment → Footer.
 */

:root {
	/* Hero height is a first-pass visual estimate against the mockup ("large
	   fullscreen / near-fullscreen", brief §2) — not a value handed down by
	   design-guidelines.md, since Info's exact hero proportions aren't
	   codified there yet. Same "tune during visual review" precedent as
	   Header/Footer/Home's own component-level spacing tokens. */
	--info-hero-height: 90vh;
	--info-hero-min-height: 520px;
	--info-hero-content-bottom: clamp(28px, 4vw, 56px);
	--info-hero-content-gap: 14px;

	/* Header sticky-backdrop restoration (regression fix): reuses Work's
	   exact offset/fade-extra tokens (pages.css "Filters / Grid-List
	   controls") to size the covered HEIGHT — --work-toolbar-content-offset
	   (the header's own bottom edge) + --work-toolbar-backdrop-extra (the
	   same soft-fade breathing room Work's backdrop uses), just without
	   adding --work-toolbar-own-height's toolbar-height term, since Info has
	   no sticky toolbar to also cover. */
	--info-header-backdrop-height: calc(var(--work-toolbar-content-offset) + var(--work-toolbar-backdrop-extra));
	/* Now resolves to the same shared --sticky-veil-* tokens (base.css) Work's
	   own --work-toolbar-blur/-bg resolve to — Work was brought down to
	   Info's already-refined intensity in a later pass, so both pages now
	   read as one consistent sticky-veil language instead of two hand-
	   matched sets of numbers. Kept as Info's own named tokens (rather than
	   referencing --sticky-veil-* directly below) so nothing else in this
	   file needs to change. */
	--info-header-backdrop-blur: var(--sticky-veil-blur);
	--info-header-backdrop-bg: var(--sticky-veil-bg);

	/* Standardized Info body/supporting-copy size (typography pass, round 2:
	   font size only, weight already handled). Flat 14px, not the shared
	   fluid --type-body-size (base.css, clamp(13px,1vw,16px)) — that token is
	   also used by Single Work, which this change must not touch, so Info
	   gets its own page-scoped token instead of repointing a global one.
	   Same 14px the About body text already used explicitly; this just gives
	   the rest of Info's supporting copy (hero scroll prompt, Workflow/
	   Services/Commitment body text) one shared value instead of each
	   inheriting the global fluid token independently. Titles/headings and
	   the section pills are untouched — they never referenced this token. */
	--info-body-size: 14px;

	/* Two distinct gaps, both feeding .v2-info__section's margin-top rules
	   below (inter-section spacing pass): --info-hero-section-gap is the
	   ORIGINAL value, unchanged, for Hero → About only (brief: "if the
	   Hero → About transition already visually works, do NOT blindly add
	   the same huge spacing there"). --info-section-gap is the NEW, much
	   larger chapter-to-chapter gap (About → Workflow → Services → Clients →
	   Commitment) — see the `.v2-info__section + .v2-info__section` rule,
	   which is what actually applies it only between two content sections,
	   never after Hero. */
	--info-hero-section-gap: clamp(72px, 8vw, 128px);
	/* Correction pass: the previous ×3-4 value (clamp(260px, 24vw, 440px))
	   read as excessive — halved here per feedback, landing roughly midway
	   between that and the original --info-hero-section-gap-style value
	   this replaced. Still a clearly generous chapter break, just not to
	   the previous extreme.
	   Final polish pass (+~22%, was clamp(130px, 12vw, 220px)): the halved
	   value above was validated as correct in kind, just asked for a last
	   restrained nudge — explicitly NOT a return toward the earlier ×3-4
	   oversized version. */
	--info-section-gap: clamp(160px, 14.5vw, 270px);
	--info-label-col-span: 2;

	/* About's text block is deliberately NOT a simple "span the remaining
	   columns" fill (brief round 2, §1-2/§10): it starts well past the
	   label column, leaving a large intentional empty gap, and stops short
	   of stretching to fill that whole track — --info-about-text-max-width
	   is what actually caps its rendered width (§9: ~300-380px per column
	   at 1920px), the grid placement below only controls where it starts.
	   Start moved from line 6 to line 8 (round 4: still too far left —
	   line 6 put the block at ~42% of the usable width; line 8 lands at
	   ~59%, matching the brief's "55-60%" target) and span shrunk from 7 to
	   5 to match (line 8 to the last line, 13, is exactly 5 tracks — this
	   keeps the block's right edge flush with the existing page margin
	   instead of overshooting it). --info-about-text-max-width is untouched;
	   at this narrower 5-column track the max-width clamp and the track
	   width now interact to naturally keep the block from ever overflowing,
	   at any viewport, without a separate size change. */
	--info-about-text-start: 8;
	--info-about-text-span: 5;
	--info-about-text-max-width: clamp(560px, 42vw, 820px);
	--info-about-column-gap: 56px;

	/* Shared "pill → content" breathing-room gap — the space below each Info
	   section's pill/label before its actual content starts (Workflow/
	   Services/Clients/Commitment/About-mobile all read this one value, see
	   each section's own rule below), kept deliberately distinct from
	   --info-section-gap (the much larger section-to-section chapter break)
	   and --info-divider-gradient's own compact divider→pill gap
	   (.v2-info__divider, ~18-28px, untouched). Renamed from
	   --info-workflow-gap-top (it outgrew that name once Services/Clients
	   adopted it) and doubled — was clamp(20px, 2.4vw, 32px) — per the
	   pill→content spacing pass (brief: "~2x, clearly more air"); the fluid
	   clamp() scales this up on mobile too without a separate breakpoint
	   override. */
	--info-pill-content-gap: clamp(40px, 4.8vw, 64px);

	/* Workflow-image visual refinement pass (image height/gap/radius/title-gap
	   only — brief explicitly scoped to these 4 properties). Each token is
	   dedicated to Workflow and deliberately NOT shared with Work Grid's own
	   --ratio-work/--work-card-info-gap or the shared --grid-gutter, so this
	   pass cannot leak into Work (untouched) even though the two sections
	   look similar. */
	--info-workflow-ratio: 4 / 3; /* was var(--ratio-work), 16/9 — moderately taller, matches the approved mockup's crop */
	--info-workflow-column-gap: clamp(28px, 3.2vw, 48px); /* was var(--grid-gutter), 16px — desktop only, see tablet override below */
	--info-workflow-title-gap: 20px; /* was var(--work-card-info-gap), 10px — ~2x per brief */
	--info-workflow-radius: 6px;

	--info-service-padding-y: clamp(16px, 1.6vw, 22px);

	/* Services accordion internal grid (layout-correction pass): the number
	   stays at the left page edge, the title/description group starts well
	   past it, matching the mockup's "large intentional empty space" — same
	   shared-token-driven approach as About's grid-column placement above,
	   so the title (.v2-info-service__title, inside .v2-info-service__
	   toggle) and its description (.v2-info-service__panel, a structurally
	   separate sibling element below) are guaranteed to share the same left
	   edge by construction rather than by matching a hardcoded padding value
	   (the previous implementation's `padding-left: 54px` — an arbitrary
	   number-width + gap sum — is removed). Desktop start (line 6 of 12,
	   ~42% of the content width) intentionally sits left of About's own
	   text-block start (line 8, ~59%) — About's brief specifically asked for
	   a more extreme rightward push for its large two-column block; Services
	   is a single-line title per row and the mockup's own proportions read
	   closer to a more moderate offset. Mobile overrides these to a small
	   gap instead (see the mobile breakpoint below). */
	--info-service-number-span: 2;
	--info-service-title-start: 6;
	--info-service-title-span: 7;

	--info-clients-logo-height: clamp(24px, 2.6vw, 38px);
	/* +~17.5% over the previous clamp(28px, 3vw, 44px) — modest size bump
	   (brief target: +15-20%), same shared token every logo already reads
	   its height from, so aspect ratio/alignment/spacing between the 3
	   logos is untouched by construction.
	   Second pass (+15% again, brief §5/§6): clamp(38px, 4vw, 60px) — still
	   the one shared token, so proportions/spacing stay untouched. */
	--info-commitment-logo-height: clamp(38px, 4vw, 60px);

	/* Marquee spacing pass (brief: "significantly more breathing room in
	   every direction", logo size/speed explicitly untouched). One shared
	   token for the horizontal logo-to-logo gap — read by both
	   .v2-info-clients__track's `gap` and its `padding-right` (below), which
	   must always match exactly: `gap` spaces logos apart within a copy,
	   `padding-right` spaces the last logo of one copy from the first logo
	   of the next duplicated copy (the loop seam). Keeping both on one
	   token makes that equality a structural guarantee instead of two
	   numbers that could drift. Exactly 2x the previous
	   clamp(24px, 2.4vw, 40px) — mobile override below is also an exact 2x
	   (20px → 40px) so the animation-duration adjustment below (also ~1.25x,
	   derived from this same 2x gap increase) stays valid at every
	   breakpoint. */
	--info-clients-gap: clamp(48px, 4.8vw, 80px);

	/* Vertical space between the 3 marquee rows — was a flat 18px. Fluid so
	   it also scales down gracefully on narrow viewports without a separate
	   mobile override.
	   Correction pass (brief: "current is slightly too spacious vertically,
	   ~20-30% less, don't go back to the old value"): -25%, was
	   clamp(32px, 3.4vw, 56px). Floor is still 24px vs. the original flat
	   18px, so mobile stays noticeably more open than before this whole
	   refinement started. Horizontal --info-clients-gap above is
	   deliberately untouched by this pass. */
	--info-clients-row-gap: clamp(24px, 2.5vw, 42px);

	/* Breathing room above the first row (title → marquee) and below the
	   third row (marquee → end of the Clients section box). Deliberately
	   one shared token for both so the marquee reads as vertically
	   centered/balanced inside its own section (brief §5: "roughly balanced
	   visual breathing room around the logo area") — NOT the same token as
	   --info-section-gap, which stays untouched so the global Workflow /
	   Services / Clients / Commitment chapter rhythm is unaffected (brief
	   §11-12).
	   Correction pass (same brief as the row-gap note above): -25%, was
	   clamp(64px, 7vw, 112px), which itself was clamp(40px, 5vw, 72px)
	   before the top-level marquee spacing pass (the above-marquee gap
	   only; below-marquee space didn't exist before that pass — the section
	   had no padding-bottom). Floor is still 48px vs. that original 40px,
	   so this stays more open than the pre-refinement state. */
	--info-clients-marquee-space: clamp(48px, 5vw, 84px);

	/* Section-divider gradients. Unification pass (brief: "all major Info
	   section dividers use exactly the Clients gradient"): every major
	   section's own intro divider (About/Workflow/Services/Clients/
	   Commitment, consumed via .v2-info__divider's --info-divider-gradient
	   below) now points at this one token instead of each section having
	   its own — the previous per-section variants (-about/-workflow/
	   -services/-commitment) are removed rather than left dangling and
	   unread, so there's no stale rule that could silently reintroduce a
	   different color later. --info-divider-gradient-accordion is a
	   deliberately SEPARATE, untouched token — it feeds .v2-info-service's
	   border-image (the Services accordion's internal row dividers, a
	   structurally different element/selector), which this brief
	   explicitly excludes from the unification. */
	/* Pink stop swapped for #BEC1F0 (brief: accordion color tweak only) —
	   direction, other two colors, and all stop positions unchanged. */
	--info-divider-gradient-accordion: linear-gradient(90deg, #BEC1F0 0%, #6A3288 50%, #020B34 100%);
	--info-divider-gradient-clients: linear-gradient(90deg, #1BA4FA 0%, #FEC9EC 50%, #BEC1F0 100%);
}

.v2-info {
	flex: 1;
}

.v2-info__section {
	/* Base/default: only ever actually applies to About, the one
	   .v2-info__section that has no preceding .v2-info__section sibling
	   (its previous sibling is .v2-info-hero) — so Hero → About keeps
	   exactly its original, already-working spacing. */
	margin-top: var(--info-hero-section-gap);
	padding: 0 var(--page-margin);
}

/* The actual "large breathing room" chapter break (brief §1-3): every
   content section that follows ANOTHER .v2-info__section — About → Workflow,
   Workflow → Services, Services → Clients, Clients → Commitment — gets the
   much larger gap here instead, via the adjacent-sibling combinator rather
   than a per-section override, so all four transitions share one rule/value
   by construction. Higher specificity than the base rule above (two classes
   vs one), so it wins without !important. The separator's own small gap to
   the pill below it (.v2-info__divider's margin-bottom) is untouched — this
   only ever adds space ABOVE the section box, i.e. above the separator. */
.v2-info__section + .v2-info__section {
	margin-top: var(--info-section-gap);
}

/* Commitment → Footer: same chapter-break rhythm as the section-to-section
   gaps above, reusing the identical --info-section-gap token (desktop/
   tablet/mobile values all inherited automatically, no separate responsive
   rule needed) — just as margin-bottom instead of margin-top, since
   Commitment is the last .v2-info__section and the global Footer that
   follows it is a plain sibling of <main>, not another .v2-info__section,
   so the adjacent-sibling rule above can't reach it. No separator/pill is
   introduced here — this only adds the breathing room itself. */
.v2-info__commitment {
	margin-bottom: var(--info-section-gap);
	--info-divider-gradient: var(--info-divider-gradient-clients);
}

.v2-info__label-col {
	display: flex;
}

/* Shared "Info subtitle" level (typography pass, round 3): Workflow item
   titles, Services accordion numbers/titles, and the Clients/Commitment
   editorial headings all belong to one consistent secondary-heading
   hierarchy — more presence than the 14px/Light body copy (--info-body-size/
   --weight-body), much lighter than the Hero H1. Grouped into one rule
   (rather than a new class added to markup) so every listed selector reads
   the exact same declared font-size/weight/line-height, not five
   hand-matched copies — the same "share via grouped selectors" approach
   already used for .v2-section-pill above. Each element's own rule below
   keeps only what's specific to it (margin, letter-spacing, opacity,
   transition, min-width, ...); their previous font-size/font-weight/line-
   height declarations were removed there to avoid this rule being silently
   overridden by a later, conflicting one of equal specificity. True Poppins
   Regular (400) — not --weight-body (300) — so no specificity fight with
   the body-copy rules exists in the first place: they target entirely
   different selectors (e.g. .v2-info-service__panel, not .v2-info-service__
   title), so no !important is needed. */
.v2-info-workflow-item__title,
.v2-info-service__number,
.v2-info-service__title,
.v2-info__clients-intro,
.v2-info__commitment-title {
	font-family: var(--font-base);
	font-size: 28px;
	font-weight: 400;
	line-height: 1.15;
	color: var(--color-fg);
}

/* .v2-section-pill / .v2-section-pill-icon: the one shared section-label
   component for every Info pill (About Us/Workflow/Services/Clients/
   Commitment) — styled entirely via the grouped Work-filter selectors in
   the "─── Work ───" section above (.v2-work__filter, .v2-work__filter.
   is-active, .v2-work__filter-icon, .v2-work__filter.is-active .v2-work__
   filter-icon), so every instance is pixel-identical to Work's active
   filter by construction. No separate rule needed here; a prior
   .v2-info__pill class (a hand-matched approximation, not a true share) has
   been removed now that all 5 sections use this one component. */

/* ─── Hero ───────────────────────────────────────────────────────────────── */

.v2-info-hero {
	position: relative;
	height: var(--info-hero-height);
	min-height: var(--info-hero-min-height);
	overflow: hidden;
	display: flex;
	flex-direction: column;
}

.v2-info-hero__media {
	position: absolute;
	inset: 0;
	z-index: 0;
}

.v2-info-hero__poster,
.v2-info-hero__video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Poster sits behind the video in DOM order, but must stay visible until the
   video actually starts playing (autoplay can be delayed/blocked) — the
   video is only ever painted on top once it has a frame to show, same
   "poster is the permanent safe layer" precedent as Home's slider. */
.v2-info-hero__video {
	background: transparent;
}

/* Video reveal: starts hidden (opacity 0, gated on the same pre-paint
   `motion-enabled` class base.css's `.v2-reveal` uses — see base.css
   "Motion: shared pre-paint reveal state" — so there is never a frame where
   the video is visible before this rule applies) and fades in only once
   info-hero.js adds `.is-playing` on the native `playing` event, the same
   readiness signal home-slider.js already uses for Home's background video.
   Without JS (`motion-enabled` never gets added) the video stays at its
   default full opacity — progressive enhancement, no dependency on this
   working. Poster (behind it in DOM order) is the visible layer underneath
   for the entire time the video is hidden, so there's no black gap even
   with no poster: `.v2-info-hero` still sits on the page's own black
   background (base.css `--color-bg`). Opacity-only, no transform — a video
   background must never move (would expose edges past `object-fit: cover`)
   — 1s sits in the same range as this hero's own title/scroll entrance
   (1.2s / 0.9s, info-entrance.js) rather than inventing an unrelated value. */
html.motion-enabled .v2-info-hero__video {
	opacity: 0;
	transition: opacity 1s var(--ease-premium);
}

html.motion-enabled .v2-info-hero__video.is-playing {
	opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
	html.motion-enabled .v2-info-hero__video {
		transition: none;
	}
}

.v2-info-hero__overlay {
	position: absolute;
	inset: 0;
	z-index: 1;
	background: linear-gradient(to top, rgba(0, 0, 0, 0.55) 0%, rgba(0, 0, 0, 0) 45%);
	pointer-events: none;
}

.v2-info-hero__content {
	position: relative;
	z-index: 2;
	margin-top: auto;
	padding: 0 var(--page-margin) var(--info-hero-content-bottom);
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--info-hero-content-gap);
	max-width: 720px;
}

.v2-info-hero__title {
	margin: 0;
	font-family: var(--font-base);
	font-size: clamp(28px, 2.3vw, 38px);
	font-weight: 500;
	line-height: 1.15;
	letter-spacing: -0.025em;
	color: var(--color-fg);
	text-align: left;
}

.v2-info-hero__scroll {
	margin: 0;
	font-family: var(--font-base);
	/* Was --type-utility-size (clamp(10px,0.8vw,12px)) — standardized to the
	   shared Info body size (typography pass, round 2). */
	font-size: var(--info-body-size);
	font-weight: var(--weight-body);
	color: var(--color-fg);
	opacity: var(--opacity-muted);
}

/* Header sticky-backdrop (regression fix, then an intensity/timing
   refinement pass): Info was missing the validated Header blur/veil Work
   already has — root cause was that Work's version
   (.page-template-page-work-php .v2-work::before, "Filters / Grid-List
   controls" section above) is scoped to Work's own body/page-wrapper
   classes and sized to cover Work's Header+toolbar combined, neither of
   which exist on Info (.v2-info has no toolbar). This is the same
   mechanism transplanted onto Info's own page/wrapper classes — same
   mask-image fade curve, z-index and pointer-events — but with its OWN,
   much lighter --info-header-backdrop-blur/-bg tokens (above) rather than
   Work's, and sized only to the Header's own zone via
   --info-header-backdrop-height since there's no toolbar here to also
   cover. `position: fixed` — never occupies flow space, always sits at the
   viewport top, exactly like Work's.

   Unlike Work (always-on at desktop, by design), Info's backdrop starts
   fully transparent at EVERY viewport width and only fades in once
   body.is-scrolled is set (assets/js/header-scroll.js — the same scroll-
   state class/threshold this already used on mobile only; now applied
   universally instead of gated behind the mobile @media block, so desktop
   gets the identical top-of-page-is-clean behavior). One CSS transition on
   opacity handles the fade both ways (scrolling down and back up to top) —
   no scroll-linked/continuously-interpolated blur, no second observer. */
.page-template-page-info-php .v2-info::before {
	content: '';
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	height: var(--info-header-backdrop-height);
	z-index: 10;
	background: var(--info-header-backdrop-bg);
	backdrop-filter: blur(var(--info-header-backdrop-blur));
	-webkit-backdrop-filter: blur(var(--info-header-backdrop-blur));
	-webkit-mask-image: linear-gradient(
		to bottom,
		black 0%,
		black 65%,
		rgba(0, 0, 0, 0.85) 76%,
		rgba(0, 0, 0, 0.5) 87%,
		rgba(0, 0, 0, 0.18) 96%,
		transparent 100%
	);
	        mask-image: linear-gradient(
		to bottom,
		black 0%,
		black 65%,
		rgba(0, 0, 0, 0.85) 76%,
		rgba(0, 0, 0, 0.5) 87%,
		rgba(0, 0, 0, 0.18) 96%,
		transparent 100%
	);
	pointer-events: none;
	opacity: 0;
	transition: opacity 220ms ease;
}

.page-template-page-info-php.is-scrolled .v2-info::before {
	opacity: 1;
}

/* ─── About Us ───────────────────────────────────────────────────────────── */

/* Thin divider above each major section's content — one shared, reusable
   class used by About/Workflow/Services/Clients/Commitment alike
   (page-info.php). Background reads --info-divider-gradient, which every
   section wrapper sets (below, right after each section's own rules);
   this rule just falls back to the original plain off-white line if a
   section somehow doesn't set one. Dimensions/spacing are untouched —
   only the color source ever changes here.
   Unification pass: all five sections now set this to the same
   --info-divider-gradient-clients token (brief: "one unified divider
   visual language" — was a distinct gradient per section). This is
   intentionally NOT the selector the Services accordion's internal row
   dividers use (.v2-info-service's own border-image, further down) — that
   stays on its own dedicated --info-divider-gradient-accordion token,
   completely unaffected by this rule or by this change. */
.v2-info__divider {
	height: 1px;
	background: var(--info-divider-gradient, rgba(240, 242, 228, 0.12));
	/* Modest gap to the pill/text row below (brief round 2 §6: "compact",
	   18-28px) — tighter than a typical --info-section-gap-scale gap on
	   purpose, this line sits INSIDE the About section, immediately above
	   its own content, not between two sections. */
	margin-bottom: clamp(18px, 2vw, 28px);
}

.v2-info__about {
	--info-divider-gradient: var(--info-divider-gradient-clients);
}

/* Desktop: pill and text sit in the same grid row, side by side (pill spans
   columns 1-2, text starts at column 8, see .v2-info__about-text below) —
   not stacked, so the shared --info-pill-content-gap pass (Workflow/
   Services/Clients/Commitment, all pill-above-content) doesn't apply here;
   the existing large horizontal offset already is About's breathing room.
   Mobile stacks pill above text instead (breakpoint below) and does read
   the shared token there. */
.v2-info__about-grid {
	display: grid;
	grid-template-columns: repeat(var(--grid-columns-desktop), 1fr);
	gap: var(--grid-gutter);
}

/* align-self: start — without it this grid item defaults to `stretch` and
   is forced to the full height of the grid row (set by the much taller
   two-column text next to it), which in turn stretches the pill span inside
   (.v2-info__label-col is itself `display:flex`, also stretch-by-default)
   into a tall vertical block instead of a compact horizontal pill. This is
   the actual cause of the "large vertical rectangle" bug — the pill's own
   class never had a height/padding problem. Scoped to this compound
   selector (an existing item-level rule), not `.v2-info__about-grid`
   itself, so the grid container's own rule is untouched. */
.v2-info__about-grid .v2-info__label-col {
	grid-column: span var(--info-label-col-span);
	align-self: start;
}

/* Body copy: ~14px flat (not the fluid clamp used elsewhere on this page) —
   explicit visual-correction target (brief §5), deliberately smaller/denser
   than Workflow/Commitment's own body text so this fix stays scoped to
   About only. Split into two CSS columns (brief §6 Option B) rather than
   two separate backend fields — the single rich-text field/admin UI is
   unchanged, this is presentation-only. */
.v2-info__about-text {
	/* Starts well past the label column (line 6 of 12, not immediately after
	   it at line 3) — the "large empty space" is intentional (brief round 2
	   §1/§10), not a layout bug. Spans the rest of the row (to column 12)
	   but max-width below stops it from actually stretching that far. */
	grid-column: var(--info-about-text-start) / span var(--info-about-text-span);
	max-width: var(--info-about-text-max-width);
	font-family: var(--font-base);
	/* 15px, intentionally NOT --info-body-size (14px, which still only
	   feeds .v2-info-hero__scroll). Typography pass, round 4: this is now
	   the confirmed Info body-copy size, matched explicitly on Workflow/
	   Services/Commitment body text too (each keeps its own declaration,
	   not this token, so the Hero scroll UI prompt stays untouched). */
	font-size: 15px;
	font-weight: var(--weight-body);
	line-height: 1.55;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
}

/* Desktop refinement: text block starts at grid line 7 (~50% of the content
   width, was line 8 / ~59%) and still ends at line 13, the existing right
   edge. max-width cap lifted here so it can't shorten the wider track;
   tablet/mobile keep the tokens above. */
@media (min-width: 1025px) {
	.v2-info__about-text {
		grid-column: 7 / span 6;
		max-width: none;
	}
}

/* Direct-child spacing generalized from a p-only pair (p + p) to any block
   type so lists sit correctly next to paragraphs too — same computed result
   for existing p+p content, no visual change there.
   Spacing is deliberately carried on margin-BOTTOM (not margin-top): every
   child's margin-top is unconditionally 0, so whatever the browser's
   column-balancing algorithm decides starts column 2 — a fresh paragraph,
   or (see below) the browser splitting one mid-sentence — nothing pushes it
   down; the inter-paragraph gap instead comes from the PRECEDING
   paragraph's margin-bottom, which only ever takes effect at the bottom of
   whichever column/fragment that paragraph ends up in, never at another
   column's top.
   Deliberately NO break-inside: avoid here (removed — it used to keep each
   paragraph whole in one column, which is exactly what was fighting the
   browser's own height-balancing: with widely different paragraph lengths,
   "keep every paragraph intact" and "make both columns the same height" are
   often mutually exclusive, and the intact-paragraph rule was winning,
   producing a visibly short/empty second column). Paragraphs are now free
   to fragment mid-paragraph across the column break, which is what actually
   lets column-count:2's default balance algorithm equalize both columns'
   height with real, unevenly-sized editorial content. */
.v2-info__about-text > * {
	margin: 0 0 1em;
}

.v2-info__about-text > *:last-child {
	margin-bottom: 0;
}

.v2-info__about-text strong {
	font-weight: 600;
	opacity: 1;
	color: var(--color-fg);
}

.v2-info__about-text em {
	font-style: italic;
}

.v2-info__about-text a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 2px;
	transition: opacity var(--transition-ui);
}

.v2-info__about-text a:hover {
	opacity: 1;
}

.v2-info__about-text ul,
.v2-info__about-text ol {
	padding-left: 1.2em;
}

.v2-info__about-text li + li {
	margin-top: 0.3em;
}

/* ─── Workflow ───────────────────────────────────────────────────────────── */

.v2-info__workflow {
	--info-divider-gradient: var(--info-divider-gradient-clients);
}

.v2-info__workflow-grid {
	margin-top: var(--info-pill-content-gap);
	display: grid;
	grid-template-columns: repeat(var(--grid-columns-desktop), 1fr);
	/* Column gap only widened here (row-gap keeps the shared --grid-gutter) —
	   at desktop this is a single row of 3 items, so column-gap is the only
	   one that visually reads as "space between cards"; tablet's 2-column
	   wrap reverts this below so row-gap between the wrapped rows doesn't
	   inherit the wider desktop value either. */
	column-gap: var(--info-workflow-column-gap);
	row-gap: var(--grid-gutter);
}

.v2-info-workflow-item {
	grid-column: span 4;
	min-width: 0;
}

.v2-info-workflow-item__media {
	width: 100%;
	aspect-ratio: var(--info-workflow-ratio);
	overflow: hidden;
	border-radius: var(--info-workflow-radius);
	background: rgba(240, 242, 228, 0.04);
}

.v2-info-workflow-item__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* font-family/font-size/font-weight/line-height/color come from the shared
   "Info subtitle" rule above. */
.v2-info-workflow-item__title {
	margin: var(--info-workflow-title-gap) 0 0;
	letter-spacing: -0.02em;
}

.v2-info-workflow-item__text {
	margin-top: 8px;
	font-family: var(--font-base);
	/* Typography pass, round 4: matched to About's 15px body copy
	   (--info-body-size stays 14px — it also feeds .v2-info-hero__scroll,
	   a UI prompt this pass must not touch). */
	font-size: 15px;
	font-weight: var(--weight-body);
	line-height: 1.5;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
}

.v2-info-workflow-item__text > * {
	margin: 0;
}

.v2-info-workflow-item__text > * + * {
	margin-top: 0.8em;
}

.v2-info-workflow-item__text strong {
	font-weight: 600;
	opacity: 1;
}

.v2-info-workflow-item__text em {
	font-style: italic;
}

.v2-info-workflow-item__text a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 2px;
	transition: opacity var(--transition-ui);
}

.v2-info-workflow-item__text a:hover {
	opacity: 1;
}

.v2-info-workflow-item__text ul,
.v2-info-workflow-item__text ol {
	padding-left: 1.2em;
}

.v2-info-workflow-item__text li + li {
	margin-top: 0.3em;
}

/* ─── Services ───────────────────────────────────────────────────────────── */

.v2-info__services {
	/* The section's OWN intro divider — unified to the shared Clients
	   gradient like every other major section (see .v2-info__divider's own
	   comment above). Distinct from the accordion row dividers below,
	   which keep their own dedicated --info-divider-gradient-accordion
	   token, untouched by this. */
	--info-divider-gradient: var(--info-divider-gradient-clients);
}

.v2-info__services-list {
	margin-top: var(--info-pill-content-gap);
	/* No max-width (was 900px — the actual cause of the accordion reading as
	   a narrow content column instead of a full-width table, layout-
	   correction pass): this now stretches to the same full content width as
	   the rest of the page (.v2-info__section's own page-margin padding is
	   what bounds it), so each row's border-bottom below spans edge to edge
	   automatically, with no separate width rule needed on the rows
	   themselves. */
}

/* Every accordion row divider uses the same Gradient 4 (brief §3 Divider 4 /
   §6) — border-image rather than border-color (which can't render a
   gradient) is the smallest change that preserves the exact existing
   border-bottom box model: same 1px width, same full-row span, same
   edge-to-edge alignment as before. border-bottom-width stays the layout-
   affecting property (unchanged, so no layout shift); border-image only
   replaces what paints into it. The `transparent` fallback keeps the row
   from ever showing a solid line in the (essentially nonexistent today)
   case border-image isn't supported. */
.v2-info-service {
	border-bottom: 1px solid transparent;
	border-image: var(--info-divider-gradient-accordion) 1;
}

/* CSS Grid, not flex (layout-correction pass): a flex row with a fixed
   `gap` can't produce "number far left, title far right with a large
   intentional empty space between them" — it only ever reads as "number,
   small gap, title". Grid lets the title start at a fixed column
   regardless of the number's own width, and — critically — lets
   .v2-info-service__panel below (a separate element, not nested inside this
   button) share the exact same column placement via the same tokens, so
   title and description align by construction, not by a hand-tuned
   padding-left. */
.v2-info-service__toggle {
	display: grid;
	grid-template-columns: repeat(var(--grid-columns-desktop), 1fr);
	gap: var(--grid-gutter);
	align-items: baseline;
	width: 100%;
	padding: var(--info-service-padding-y) 0;
	margin: 0;
	background: none;
	border: none;
	cursor: pointer;
	text-align: left;
	color: var(--color-fg);
}

/* font-family/font-size/font-weight/line-height/color come from the shared
   "Info subtitle" rule above (typography pass, round 3 — was
   --type-utility-size/--weight-body, a much smaller/lighter utility style). */
.v2-info-service__number {
	grid-column: span var(--info-service-number-span);
	min-width: 34px;
	opacity: var(--opacity-muted);
}

/* font-family/font-size/font-weight/line-height/color come from the shared
   "Info subtitle" rule above (typography pass, round 3 — was a smaller
   clamp() at the lighter --weight-body). */
.v2-info-service__title {
	grid-column: var(--info-service-title-start) / span var(--info-service-title-span);
	letter-spacing: -0.015em;
	opacity: var(--opacity-secondary);
	transition: opacity var(--transition-ui);
}

.v2-info-service__toggle:hover .v2-info-service__title {
	opacity: 1;
}

.v2-info-service.is-open .v2-info-service__title,
.v2-info-service.is-open .v2-info-service__number {
	opacity: 1;
}

/* Smooth expand/collapse via grid-template-rows — no JS height measurement
   (design-guidelines.md §53 "smooth accordion expansion", brief §18 "CSS +
   measured height is also acceptable" — this needs no measurement at all). */
.v2-info-service__panel-wrap {
	display: grid;
	grid-template-rows: 0fr;
	transition: grid-template-rows 420ms var(--ease-premium);
}

.v2-info-service.is-open .v2-info-service__panel-wrap {
	grid-template-rows: 1fr;
}

/* Same grid as .v2-info-service__toggle, same placement tokens applied to
   .v2-info-service__panel below — this is what guarantees the open
   description starts exactly under its title (layout-correction pass):
   both read their column position from the same --info-service-title-
   start/-span custom properties, rather than the previous
   `padding-left: 54px` (an arbitrary number-width + gap sum that only
   happened to line up while the title sat directly next to the number). */
.v2-info-service__panel-inner {
	overflow: hidden;
	min-height: 0;
	display: grid;
	grid-template-columns: repeat(var(--grid-columns-desktop), 1fr);
	gap: var(--grid-gutter);
}

.v2-info-service__panel {
	grid-column: var(--info-service-title-start) / span var(--info-service-title-span);
	padding-bottom: var(--info-service-padding-y);
	font-family: var(--font-base);
	/* Typography pass, round 4: matched to About's 15px body copy
	   (--info-body-size stays 14px — it also feeds .v2-info-hero__scroll,
	   a UI prompt this pass must not touch). Service TITLE sizing
	   (.v2-info-service__title, above) is untouched. */
	font-size: 15px;
	font-weight: var(--weight-body);
	line-height: 1.55;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
	max-width: 640px;
}

.v2-info-service__panel > * {
	margin: 0;
}

.v2-info-service__panel > * + * {
	margin-top: 1em;
}

.v2-info-service__panel strong {
	font-weight: 600;
	opacity: 1;
}

.v2-info-service__panel em {
	font-style: italic;
}

.v2-info-service__panel a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 2px;
	transition: opacity var(--transition-ui);
}

.v2-info-service__panel a:hover {
	opacity: 1;
}

.v2-info-service__panel ul,
.v2-info-service__panel ol {
	padding-left: 1.2em;
}

.v2-info-service__panel li + li {
	margin-top: 0.3em;
}

/* ─── Clients ────────────────────────────────────────────────────────────── */

.v2-info__clients {
	--info-divider-gradient: var(--info-divider-gradient-clients);
	/* Breathing room below the third row (brief §5) — deliberately internal
	   padding on THIS section only, not a change to --info-section-gap (the
	   shared Workflow/Services/Clients/Commitment chapter-break margin,
	   .v2-info__section + .v2-info__section below), so the global Info
	   section-spacing system stays untouched (brief §11-12). Horizontal
	   padding still comes from the base .v2-info__section rule. */
	padding-bottom: var(--info-clients-marquee-space);
}

/* Clients → Commitment only: the empty area under the logos is this section's
   padding-bottom PLUS the shared --info-section-gap margin-top on the next
   section (which carries the visible separator). Trim just that one margin
   (~-34% of the total) on desktop; token, other transitions and ≤1024px
   untouched. */
@media (min-width: 1025px) {
	.v2-info__clients + .v2-info__section {
		margin-top: calc(var(--info-section-gap) * 0.55);
	}
}

/* Structural fix: this used to also wrap the Clients pill (which forced it
   to the same centered alignment as the intro title, wrong per the mockup —
   the pill belongs at the left page edge like every other Info section's
   pill). The pill now lives in its own preceding .v2-info__label-col
   (markup, page-info.php), matching Workflow/Services; this element wraps
   only the centered editorial intro line. margin-top reuses the same
   pill→content gap token Workflow's grid already uses below its own
   label-col, rather than a new one-off value. */
.v2-info__clients-top {
	margin-top: var(--info-pill-content-gap);
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 16px;
	text-align: center;
}

/* font-family/font-size/font-weight/line-height/color come from the shared
   "Info subtitle" rule above (typography pass, round 3 — was a fluid
   clamp() at the lighter --weight-body). */
.v2-info__clients-intro {
	margin: 0;
	letter-spacing: -0.02em;
}

/* Full-bleed strip — same negative-margin technique V1 uses for its About
   page marquee, so rows can run edge-to-edge without ever forcing a
   horizontal page scrollbar. */
.v2-info-clients__marquee {
	margin-top: var(--info-clients-marquee-space);
	width: calc(100% + var(--page-margin) * 2);
	margin-left: calc(var(--page-margin) * -1);
	overflow: hidden;
}

.v2-info-clients__row {
	overflow: hidden;
	margin-bottom: var(--info-clients-row-gap);
}

.v2-info-clients__row:last-child {
	margin-bottom: 0;
}

/* The animated element: N identical .v2-info-clients__track copies placed
   back to back (N = --info-clients-copies, set per-row inline by PHP —
   page-info.php). Animating translateX(0 → -100%/N) shifts exactly one
   copy's width — since every copy is identical, that end frame is
   pixel-identical to the start frame, so the loop point is invisible
   regardless of N. Reused technique from V1's About page marquee
   (newkid/assets/css/pages.css .about-refs__row-inner). */
.v2-info-clients__row-inner {
	display: flex;
	width: max-content;
	animation-name: v2-info-clients-scroll;
	animation-timing-function: linear;
	animation-iteration-count: infinite;
	will-change: transform;
}

.v2-info-clients__track {
	display: flex;
	align-items: center;
	flex-shrink: 0;
	gap: var(--info-clients-gap);
	padding-right: var(--info-clients-gap);
}

@keyframes v2-info-clients-scroll {
	from { transform: translate3d(0, 0, 0); }
	to   { transform: translate3d(calc(-100% / var(--info-clients-copies, 2)), 0, 0); }
}

/* Row 1 and row 3 share a direction, row 2 runs opposite, each at a
   slightly different duration so the three rows never feel synchronized —
   same pattern as V1's About page marquee.
   Durations scaled ×1.25 (was 30s/36s/33s) alongside the --info-clients-gap
   doubling above (brief §7/§17: perceived px/sec speed must not change).
   The animation moves each copy by -100%/copies, i.e. by that copy's own
   rendered width — so widening the gap widens the copy and, at a fixed
   duration, would speed the animation up. Copy width = logo widths (brief:
   untouched) + (logo count × gap), and the gap only ever accounts for part
   of that width — a real logo occupies real px too — so doubling the gap
   does not double the copy width. ×1.25 is that estimate, worked out from
   the actual track markup (page-info.php: max-width:150px logos, --info-
   clients-logo-height ~24-38px, gap contributing roughly a fifth to a
   quarter of each unit's width). Client logo count/width is admin-editable
   at runtime (inc/info.php), so this can only be an estimate, not an exact
   px/sec match — re-check by eye once real client logos are in place. */
.v2-info-clients__row--1 .v2-info-clients__row-inner { animation-duration: 37.5s; }
.v2-info-clients__row--2 .v2-info-clients__row-inner { animation-duration: 45s; animation-direction: reverse; }
.v2-info-clients__row--3 .v2-info-clients__row-inner { animation-duration: 41.25s; }

.v2-info-clients__static {
	margin-top: clamp(32px, 4vw, 56px);
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: 32px 48px;
}

.v2-info-clients__logo {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	height: var(--info-clients-logo-height);
}

.v2-info-clients__logo img {
	display: block;
	height: 100%;
	width: auto;
	max-width: 150px;
	object-fit: contain;
	/* Monochrome/off-white treatment so arbitrary client-supplied logos
	   integrate into the black interface (design-guidelines.md §44) — same
	   filter V1's About page marquee uses (brightness(0) flattens to solid
	   black+alpha, invert(1) turns that into solid white; works on any
	   source image's rendered pixels regardless of its original colors). */
	filter: brightness(0) invert(1);
	opacity: 0.82;
	transition: opacity var(--transition-ui);
}

@media (hover: hover) {
	a.v2-info-clients__logo:hover img {
		opacity: 1;
	}
}

/* ─── Commitment ─────────────────────────────────────────────────────────── */

.v2-info__commitment-grid {
	display: grid;
	grid-template-columns: repeat(var(--grid-columns-desktop), 1fr);
	gap: var(--grid-gutter);
	align-items: start;
}

.v2-info__commitment-left {
	grid-column: span 5;
	display: flex;
	flex-direction: column;
	/* Same shared pill→content gap every other Info section uses (--info-
	   pill-content-gap, above) — .v2-info__commitment-right's own alignment
	   offset (below) reads this exact value too, so title and body copy
	   move down together as one row when this changes. */
	gap: var(--info-pill-content-gap);
}

/* font-family/font-size/font-weight/line-height/color come from the shared
   "Info subtitle" rule above (typography pass, round 3 — was a fluid
   clamp() at weight 500). */
.v2-info__commitment-title {
	margin: 0;
	letter-spacing: -0.025em;
}

.v2-info__commitment-right {
	grid-column: span 7;
	display: flex;
	flex-direction: column;
	gap: clamp(24px, 3vw, 40px);
	/* The grid row (align-items: start, above) starts both columns at the
	   pill's top, not the title's — .v2-info__commitment-left stacks pill
	   above title itself, so there's no separate grid row to align to.
	   Push the right column down by exactly the left column's own pill
	   height (line-height 1.2 * --type-nav-size + its 4px top/bottom
	   padding, .v2-section-pill above) plus the left column's own pill→
	   title gap (--info-pill-content-gap, same token .v2-info__commitment-
	   left's gap reads) so body copy starts level with the title regardless
	   of pill font-size or how that shared gap changes. Desktop/tablet
	   only — reset to 0 once the columns stack (breakpoints below). */
	margin-top: calc(1.2 * var(--type-nav-size) + 8px + var(--info-pill-content-gap));
}

.v2-info__commitment-text {
	font-family: var(--font-base);
	/* Typography pass, round 4: matched to About's 15px body copy
	   (--info-body-size stays 14px — it also feeds .v2-info-hero__scroll,
	   a UI prompt this pass must not touch). Commitment TITLE sizing
	   (.v2-info__commitment-title, above) is untouched. */
	font-size: 15px;
	font-weight: var(--weight-body);
	line-height: 1.55;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
	max-width: 520px;
}

.v2-info__commitment-text > * {
	margin: 0;
}

.v2-info__commitment-text > * + * {
	margin-top: 1em;
}

.v2-info__commitment-text strong {
	font-weight: 600;
	opacity: 1;
}

.v2-info__commitment-text em {
	font-style: italic;
}

.v2-info__commitment-text a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 2px;
	transition: opacity var(--transition-ui);
}

.v2-info__commitment-text a:hover {
	opacity: 1;
}

.v2-info__commitment-text ul,
.v2-info__commitment-text ol {
	padding-left: 1.2em;
}

.v2-info__commitment-text li + li {
	margin-top: 0.3em;
}

.v2-info__commitment-logos {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 32px;
}

.v2-info__commitment-logo {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	height: var(--info-commitment-logo-height);
}

.v2-info__commitment-logo img {
	display: block;
	height: 100%;
	width: auto;
	max-width: 160px;
	object-fit: contain;
	filter: brightness(0) invert(1);
	opacity: 0.82;
}

/* ─── Tablet / intermediate ──────────────────────────────────────────────── */

@media (max-width: 1024px) {
	:root {
		/* Generous, but scaled down from the desktop chapter gap (brief §8) —
		   --info-hero-section-gap (Hero → About) is left at its desktop value
		   on purpose, same "already works, don't touch it" reasoning as
		   desktop. Halved along with the desktop value in the correction
		   pass (was clamp(150px, 18vw, 220px)).
		   Final polish pass (+~22.5%, was clamp(75px, 9vw, 110px)) — same
		   restrained nudge as the desktop value above. */
		--info-section-gap: clamp(92px, 11vw, 135px);
	}

	.v2-info__workflow-grid {
		/* Desktop's widened column-gap (brief §2) is a desktop-only
		   refinement (brief §10: "do not blindly apply the larger desktop
		   column gap to mobile") — reverts to the shared --grid-gutter here,
		   same as every other Info/Work grid at this breakpoint. */
		column-gap: var(--grid-gutter);
	}

	.v2-info-workflow-item {
		grid-column: span 6;
	}

	.v2-info__commitment-left {
		grid-column: span 12;
	}

	.v2-info__commitment-right {
		grid-column: span 12;
		/* Columns stack here (both span the full 12), so the desktop-only
		   title-row offset above no longer applies — natural stacked flow
		   (brief §9: don't force alignment between columns once stacked). */
		margin-top: 0;
	}
}

/* ─── Mobile ─────────────────────────────────────────────────────────────── */

@media (max-width: 767px) {
	:root {
		--info-hero-height: 100dvh;
		/* Unchanged value (was the single --info-section-gap used everywhere
		   pre-refinement) — Hero → About keeps its already-working mobile
		   spacing exactly as it was. */
		--info-hero-section-gap: clamp(56px, 14vw, 80px);
		/* Chapter-to-chapter gap on mobile: clearly separated but restrained
		   (brief §8 — "do not make mobile unnecessarily endless"), scaled
		   down well below the tablet/desktop values above.
		   Second mobile-rhythm correction pass: clamp(80px, 18vw, 120px)
		   (the first correction) still read as too tight against real
		   desktop pacing — bumped a further ~37.5% (within the requested
		   +35-50%, deliberately not another doubling) so sections separate
		   clearly without the page feeling endless. Tablet (clamp(92px, 11vw,
		   135px), above) is untouched — still its own distinct value.
		   Final polish pass (+~22.7%, was clamp(110px, 25vw, 165px)) — same
		   restrained nudge as desktop/tablet, not another large jump. */
		--info-section-gap: clamp(135px, 30.5vw, 202px);
		--info-clients-logo-height: clamp(20px, 6vw, 26px);
		/* Same +~17.5% bump as desktop, applied to the mobile override (was
		   clamp(24px, 8vw, 36px)).
		   Second pass (+15% again, matching the desktop bump above): was
		   clamp(28px, 9.4vw, 42px) — still comfortably within mobile width. */
		--info-commitment-logo-height: clamp(32px, 10.8vw, 48px);

		/* Services: the desktop's large number→title gap is a desktop-only
		   composition (brief §15) — title/description start right after the
		   number's own 2-column track instead of 4 columns further right, so
		   there's no oversized empty gap on a narrow screen. Still the same
		   grid mechanism/tokens as desktop, just a smaller offset — nothing
		   to override on .v2-info-service__toggle/__panel-inner themselves. */
		--info-service-title-start: 3;
		--info-service-title-span: 10;
	}

	.v2-info-hero__content {
		max-width: none;
	}

	.v2-info-hero__title {
		font-size: clamp(24px, 7vw, 30px);
	}

	/* The Header backdrop's top-clean/scroll-fade behavior (opacity 0 → 1 on
	   body.is-scrolled) is no longer mobile-only — it's declared once,
	   unconditionally, on the base rule above — so nothing needs overriding
	   here. Kept as a placeholder comment rather than silently removing the
	   subsection so a future mobile-only tweak has an obvious home. */

	.v2-info__about-grid {
		display: flex;
		flex-direction: column;
		/* Stacked here (unlike desktop's side-by-side row, see the base rule
		   above) — this is genuinely a pill→content gap on mobile, so it
		   reads the same shared token every other section's does (was a
		   one-off 16px). */
		gap: var(--info-pill-content-gap);
	}

	.v2-info__workflow-grid {
		grid-template-columns: 1fr;
		/* Stacked single column here, so row-gap is the only thing reading as
		   "space between poles" — was inheriting the shared --grid-gutter
		   (16px, used by many other grids sitewide), which read as the three
		   poles stuck together rather than distinct editorial blocks. This
		   override is scoped to the mobile stacked layout only (desktop's
		   3-column row-gap, base rule above, is untouched) and only changes
		   row-gap — column-gap is irrelevant with 1 column. ~2x the previous
		   16px, still clearly smaller than the major inter-section gap above
		   so the two rhythms stay visually distinct. Each Workflow item's own
		   internal image/title/description spacing is untouched — this only
		   adds space BETWEEN items. */
		row-gap: 32px;
	}

	.v2-info-workflow-item {
		grid-column: span 1;
	}

	/* Mobile only: desktop's centered editorial intro line (.v2-info__clients-top,
	   "─── Clients ───" section above) reads as off-brand on a narrow
	   screen — left-aligned here instead, flush with the same --page-margin
	   every other Info section pill already starts from (no new margin
	   value introduced; .v2-info__clients-intro itself has no max-width to
	   remove — its width was only ever a flex/text-align centering effect).
	   Without align-items:center, the title flex item now sizes/wraps
	   naturally within the section's own available width instead of
	   shrink-wrapping to a centered block. Desktop's own rule
	   (align-items:center; text-align:center) is untouched. */
	.v2-info__clients-top {
		align-items: flex-start;
		text-align: left;
	}

	.v2-info-clients__marquee {
		width: calc(100% + var(--page-margin) * 2);
	}

	.v2-info-clients__track {
		/* Same exact-2x relationship as the desktop --info-clients-gap token
		   (was 20px), kept as a flat mobile override rather than letting the
		   fluid token's vw component shrink it further at narrow widths —
		   the brief asks for spacing that's "clearly MORE spacious" at
		   375/390/430px, not just proportionally scaled down. gap and
		   padding-right must stay equal (loop-seam spacing, see the shared
		   comment on the desktop rule above). */
		gap: 40px;
		padding-right: 40px;
	}

	.v2-info-clients__static {
		gap: 20px 28px;
	}

	.v2-info__commitment-logos {
		gap: 20px;
	}
}

/* ─── Reduced motion ─────────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
	.v2-info-service__panel-wrap {
		transition: none;
	}

	/* Stop the loop, drop the aria-hidden duplicate tracks, and let the
	   single real track wrap into a clean static grid instead — same
	   fallback V1's About page marquee uses. */
	.v2-info-clients__row-inner {
		animation: none !important;
		flex-wrap: wrap;
		width: auto;
	}

	.v2-info-clients__row {
		overflow: visible;
	}

	.v2-info-clients__track[aria-hidden='true'] {
		display: none;
	}
}

/* ─── Legal ──────────────────────────────────────────────────────────────── */
/*
 * Shared by page-legal.php (Legal Notice) and page-privacy.php (Privacy
 * Policy) — both are the same kind of simple long-form editorial content
 * page, so this one component is reused rather than duplicated per
 * template. Styles the normal WordPress content pipeline (the_content()),
 * not a custom rich-text sanitizer allow-list like Info's fields.
 */

:root {
	--legal-content-top: clamp(96px, 10vw, 136px); /* clears the fixed .v2-header, same as Work/Single Work */
	--legal-content-max-width: 720px; /* comfortable line length for long-form legal copy */
	--legal-title-gap: clamp(28px, 4vw, 48px); /* title → body */

	/* Header sticky-backdrop — same mechanism as Work/Info (this file's own
	   ".v2-info::before" rule, "─── Info ───" section, has the full
	   rationale). Legal/Privacy has no sticky toolbar either, so this reuses
	   Info's exact "header-only" shape: same offset/fade-extra terms
	   (--work-toolbar-content-offset / --work-toolbar-backdrop-extra) for the
	   covered height, same shared --sticky-veil-* tokens (base.css) for blur/
	   background — kept as Legal's own named tokens purely so this section
	   stays self-contained, not because the values differ from Info's. */
	--legal-header-backdrop-height: calc(var(--work-toolbar-content-offset) + var(--work-toolbar-backdrop-extra));
	--legal-header-backdrop-blur: var(--sticky-veil-blur);
	--legal-header-backdrop-bg: var(--sticky-veil-bg);
}

.v2-legal-page {
	flex: 1;
	padding: var(--legal-content-top) var(--page-margin) clamp(64px, 7vw, 104px);
}

/* Fades in only once body.is-scrolled is set (assets/js/header-scroll.js —
   the same generic scroll-state script Info uses, enqueued for these two
   templates in inc/enqueue.php) — clean/transparent header at the top of
   the page, Work/Info's validated blurred veil after an actual scroll.
   Identical mask-image curve, z-index, pointer-events and transition to
   Work's/Info's own rule — copied, not reinvented, so the fade quality
   matches exactly. `position: fixed`, so it never occupies flow space. */
.page-template-page-legal-php .v2-legal-page::before,
.page-template-page-privacy-php .v2-legal-page::before {
	content: '';
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	height: var(--legal-header-backdrop-height);
	z-index: 10;
	background: var(--legal-header-backdrop-bg);
	backdrop-filter: blur(var(--legal-header-backdrop-blur));
	-webkit-backdrop-filter: blur(var(--legal-header-backdrop-blur));
	-webkit-mask-image: linear-gradient(
		to bottom,
		black 0%,
		black 65%,
		rgba(0, 0, 0, 0.85) 76%,
		rgba(0, 0, 0, 0.5) 87%,
		rgba(0, 0, 0, 0.18) 96%,
		transparent 100%
	);
	        mask-image: linear-gradient(
		to bottom,
		black 0%,
		black 65%,
		rgba(0, 0, 0, 0.85) 76%,
		rgba(0, 0, 0, 0.5) 87%,
		rgba(0, 0, 0, 0.18) 96%,
		transparent 100%
	);
	pointer-events: none;
	opacity: 0;
	transition: opacity 220ms ease;
}

.page-template-page-legal-php.is-scrolled .v2-legal-page::before,
.page-template-page-privacy-php.is-scrolled .v2-legal-page::before {
	opacity: 1;
}

.v2-legal-page__title {
	max-width: var(--legal-content-max-width);
	margin: 0 0 var(--legal-title-gap);
	font-family: var(--font-base);
	font-size: clamp(28px, 2.4vw, 40px);
	font-weight: 400;
	line-height: 1.15;
	letter-spacing: -0.025em;
	color: var(--color-fg);
}

.v2-legal-page__content {
	max-width: var(--legal-content-max-width);
	font-family: var(--font-base);
	font-size: var(--type-body-size);
	font-weight: var(--weight-body);
	line-height: 1.6;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
}

/* Uniform vertical rhythm between paragraphs/lists/headings — same
   "> * + *" pattern as Info's About text (this file, above), generalized to
   any block-level child rather than a p-only pair. Heading rules below win
   over this on specificity (class+type beats class alone) without needing
   !important. */
.v2-legal-page__content > * {
	margin: 0;
}

.v2-legal-page__content > * + * {
	margin-top: 1.2em;
}

.v2-legal-page__content h2 {
	margin-top: 2em;
	font-size: clamp(20px, 1.6vw, 26px);
	font-weight: 500;
	line-height: 1.2;
	letter-spacing: -0.02em;
	color: var(--color-fg);
	opacity: 1;
}

.v2-legal-page__content h3 {
	margin-top: 1.6em;
	font-size: clamp(16px, 1.2vw, 19px);
	font-weight: 500;
	line-height: 1.25;
	letter-spacing: -0.02em;
	color: var(--color-fg);
	opacity: 1;
}

.v2-legal-page__content h2:first-child,
.v2-legal-page__content h3:first-child {
	margin-top: 0;
}

.v2-legal-page__content strong {
	font-weight: 600;
	opacity: 1;
	color: var(--color-fg);
}

.v2-legal-page__content em {
	font-style: italic;
}

.v2-legal-page__content a {
	color: inherit;
	opacity: 1;
	text-decoration: underline;
	text-underline-offset: 2px;
	transition: opacity var(--transition-ui);
}

.v2-legal-page__content a:hover,
.v2-legal-page__content a:focus-visible {
	opacity: var(--opacity-secondary);
}

.v2-legal-page__content ul,
.v2-legal-page__content ol {
	padding-left: 1.2em;
}

.v2-legal-page__content li + li {
	margin-top: 0.4em;
}

/* ─── Mobile ─────────────────────────────────────────────────────────────── */

@media (max-width: 767px) {
	.v2-legal-page {
		padding-top: clamp(84px, 20vw, 104px);
	}
}

/* ─── Reduced motion ─────────────────────────────────────────────────────── */
/* No motion/entrance system on this template (brief: "intentionally
   SIMPLE" — readability over animation), so nothing to disable here. */

/* ─── 404 ────────────────────────────────────────────────────────────────── */
/*
 * 404.php — deliberately the same "intentionally SIMPLE" tier as Legal
 * above: no motion/entrance system, static rendering only.
 *
 * `.v2-404` is `flex: 1` inside `body`'s own flex column (base.css), the
 * same mechanism Legal uses to let the header stay fixed/out-of-flow while
 * this element naturally grows to fill the remaining space above the
 * in-flow footer — no fixed pixel height anywhere.
 *
 * No header-clearance padding-top (unlike `.v2-legal-page`, which top-
 * anchors long-form content right below the header): centering here is
 * true vertical centering within the space between Header and Footer,
 * same reasoning as `.v2-contact__main`'s own comment on this — an
 * asymmetric top offset would just push the block below true center. The
 * fixed Header (~90-110px) stays clear of the centered block at any
 * realistic viewport height on its own; the short-viewport query below
 * tightens the numeral further if it doesn't.
 */

.v2-404 {
	flex: 1;
	min-height: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;
	padding: clamp(24px, 6vh, 64px) var(--page-margin);
}

.v2-404__inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	max-width: 480px;
}

/* The dominant visual element of the page (brief §2/§3) — substantially
   larger than any other V2 heading on purpose, but a fluid clamp() rather
   than a desktop-only fixed size (brief §3/§9) so it scales down cleanly
   on narrow/short viewports instead of forcing a scrollbar. Same weight/
   tracking family as the "Display" scale (design-guidelines.md §6) so it
   reads as editorial rather than a giant bitmap-style error glyph. */
.v2-404__code {
	margin: 0;
	font-family: var(--font-base);
	font-size: clamp(110px, 22vw, 300px);
	font-weight: 500;
	line-height: 1;
	letter-spacing: -0.03em;
	color: var(--color-fg);
}

/* "Section title" scale (design-guidelines.md §6), restrained relative to
   the numeral above it, per brief §3. */
.v2-404__message {
	margin: clamp(16px, 2.5vw, 28px) 0 0;
	font-family: var(--font-base);
	font-size: clamp(18px, 1.6vw, 26px);
	font-weight: 400;
	line-height: 1.3;
	letter-spacing: -0.02em;
	color: var(--color-fg);
	opacity: var(--opacity-secondary);
}

/* Plain text link, no button chrome (brief §2/§48: "Do not invent a large
   conventional rounded CTA button... `Back to home ->` is enough") — same
   underline + opacity-on-hover interaction language as
   `.v2-legal-page__content a`, reused rather than inventing a new link
   treatment. */
.v2-404__home-link {
	display: inline-block;
	margin-top: clamp(20px, 3vw, 32px);
	font-family: var(--font-base);
	font-size: var(--type-body-size);
	font-weight: 400;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	text-decoration: underline;
	text-underline-offset: 3px;
	opacity: 1;
	transition: opacity var(--transition-ui);
}

.v2-404__home-link:hover,
.v2-404__home-link:focus-visible {
	opacity: var(--opacity-secondary);
}

.v2-404__home-arrow {
	text-decoration: none;
	display: inline-block;
}

@media (max-width: 767px) {
	.v2-404__code {
		font-size: clamp(84px, 26vw, 160px);
	}
}

@media (max-height: 700px) {
	.v2-404__code {
		font-size: clamp(72px, 18vw, 160px);
	}

	.v2-404 {
		padding: 16px var(--page-margin);
	}
}
