/*
 * 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: 660px; /* wide enough that the heading doesn't wrap at desktop widths */
	--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;
	}

	.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 20px (×1.25);
	   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: 20px;
		column-gap: var(--grid-gutter);
	}

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

	.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);
}

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

.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;
}

/* ─── 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);
	font-size: var(--type-body-size);
	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: var(--type-body-size);
	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);
	font-size: var(--type-meta-size);
	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;
	}

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

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

/* ─── Contact ────────────────────────────────────────────────────────────── */
/*
 * Black-background, typography-led editorial page — the client-requested
 * removal of the former full-viewport photographic background (this pass)
 * turned Contact from a media-led composition into one built entirely from
 * type/spacing, same visual family as the rest of V2 (design-guidelines.md
 * §3/§52 "pure black environment"). Strict no-scroll, same `height`/
 * `overflow:hidden` mechanic as Home's `body.home` (pages.css, above), not
 * `min-height` (a previous pass corrected this: `min-height` let the page
 * grow taller than the viewport whenever content ran long, producing an
 * accidental scrollbar/bounce instead of a fixed composition). Short
 * viewports are handled by shrinking spacing via the `(max-height: ...)`
 * query below, not by allowing scroll — that compression, together with the
 * compact row spacing below, comfortably fits any realistic row count.
 * `.v2-contact__main` still carries `overflow-y: auto` as a safety fallback
 * only, for the flexible-rows repeater (below) growing past even a tall,
 * fully-compressed viewport — information must never clip.
 *
 * Contact content is intentionally excluded from Lenis (inc/enqueue.php,
 * same exclusion Home already has) — there is nothing to smooth-scroll on a
 * page that never scrolls.
 */

:root {
	/* Spacing hierarchy for Contact's information composition
	   (design-guidelines.md §47), applied to a variable-length list of
	   flexible Label/Value rows:
	     --contact-col-gap    — label ↔ value, WITHIN one row (horizontal —
	                             label and value sit side by side, not
	                             stacked, so this is a column-gap, not a
	                             vertical spacing token).
	     --contact-row-gap    — row → next row (vertical, moderate).
	     --contact-group-gap  — greeting → the information rows block
	                             (vertical, large).
	   Alignment fix (2026-09, against an approved Figma mockup): the
	   previous implementation emitted a FLAT sequence of label, value,
	   label, value, ... as direct children of one 2-column grid, using
	   `margin-top` on every label-after-the-first to fake the row→row gap.
	   That margin pushed each label DOWN inside its own grid cell — but only
	   the label, never its paired value (which had no margin) — so a
	   label's top edge no longer lined up with its own value's first line;
	   visually, every label appeared associated with the row above it. Root
	   cause: faking row spacing with an item margin instead of real
	   between-row spacing on a real per-row container.
	   Fix: each row is now its own `.v2-contact__row` wrapper (real DOM
	   grouping, §1/§3/§12 of the brief) using CSS Subgrid — the wrapper
	   spans both of `.v2-contact__rows`' column tracks and its own
	   `grid-template-columns: subgrid` inherits their exact sizing, so every
	   row's label/value still share one column position across the whole
	   list (§5/§6), while `align-items: start` on the wrapper (a plain
	   single-row, 2-item grid) aligns the label's top with the value's first
	   line directly, with no margin hack involved. The parent's own
	   `row-gap` is then real, uniform vertical spacing BETWEEN wrapper
	   elements — exactly the "shared grid controls value position, parent
	   list controls row-to-row spacing" structure requested. Subgrid has
	   full evergreen-browser support (Chrome/Edge 117+, Firefox 71+, Safari
	   16+, all older than this project's baseline) — no fallback layer
	   added, consistent with this theme's existing use of other modern-only
	   CSS (100svh/100dvh, backdrop-filter) elsewhere in this file. */
	--contact-group-gap: clamp(40px, 5.5vw, 88px);
	/* +20-30% over the previous clean intended value (clamp(24px,2.6vw,34px))
	   per this pass's brief §7 — a moderate bump, not the 2x jump from the
	   pass before it. */
	--contact-row-gap: clamp(30px, 3.2vw, 42px);
	/* Increased back up slightly from the previous pass's clamp(12px,1vw,18px):
	   that read as too tight against the mockup once label/value pairing
	   was fixed — still well short of the very first pass's
	   clamp(20px,3vw,40px), per brief §4's "moderately more, not huge". */
	--contact-col-gap: clamp(18px, 1.6vw, 26px);
	/* Caps how wide a value can grow before wrapping (brief §9) — without
	   this, "Studio Paris" 's full address sits on one line inside the wide
	   `1fr` value column at desktop widths, rather than wrapping onto two
	   lines the way the mockup's address does. Phone/Email stay one line
	   regardless, since they're shorter than this width. Not a hardcoded
	   `<br>` in the content — any row's value wraps at this same width. */
	--contact-value-max-width: clamp(240px, 24vw, 340px);
	/* Same "Display / Large editorial title" scale as Work's intro heading
	   (design-guidelines.md §6) — the greeting is the largest text on the
	   page but stays restrained, not an oversized hero numeral. */
	--contact-greeting-size: clamp(28px, 2.3vw, 38px);
	--contact-value-size: clamp(18px, 1.7vw, 24px);
}

/* Mirrors Home's `body.home` mechanic exactly (same file, above) — WP's
   native body_class() already adds this template-slug class, so no extra
   PHP is needed to scope it. */
body.page-template-page-contact-php {
	overflow: hidden;
	height: 100%;
}

.v2-contact {
	height: 100svh;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	/* Pure black, same as every other V2 page (design-guidelines.md §3/§52)
	   — inherited from body { background: var(--color-bg) } in base.css, no
	   Contact-specific background rule needed. The former full-viewport
	   photographic background (`.v2-contact__bg` image + protective
	   gradient overlay) was removed at the client's request; this is a
	   typography/spacing-led composition now, not a media-led one. */
}

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

.v2-contact__main {
	/* flex:1 — not the full viewport — is what's being centered within: main
	   is only the space between the (overlaid, not-in-flow) Header and the
	   Footer's own real flex space below, so centering here already lands
	   slightly above true viewport-center without any extra offset hack
	   (no `top:50%`/`translateY(-50%)` trick, no absolute positioning).
	   `padding-bottom` below nudges the centered content a bit further up
	   still — with the background photo gone, a dead-center block read as
	   generic/"centered contact us page" rather than the intended
	   upper-middle editorial placement (design-guidelines.md §1's "content
	   positioned close to the viewport edges" / asymmetric compact
	   direction). No fixed/tallied number: a proportional viewport unit, so
	   it scales instead of being tuned to one specific screen height. */
	flex: 1;
	/* min-height: 0 overrides flex's default min-height: auto, which would
	   otherwise refuse to let this item shrink below its content size —
	   required for overflow-y: auto below to ever actually engage instead of
	   silently pushing the parent `.v2-contact` (height: 100svh; overflow:
	   hidden) taller than the viewport. */
	min-height: 0;
	display: flex;
	flex-direction: column;
	justify-content: center;
	padding: 0 var(--page-margin) clamp(24px, 6vh, 64px);
	/* A typical row count (3-6) always fits without scrolling — this is a
	   safety fallback, not the intended experience, for whatever a client
	   adds later that genuinely exceeds even a tall viewport (brief §16:
	   never clip information just to preserve a strict no-scroll layout). */
	overflow-y: auto;
}

.v2-contact__block {
	display: flex;
	flex-direction: column;
	gap: var(--contact-group-gap);
	/* Controlled readable measure now that there's no photo constraining
	   the block's width — kept close to the previous value rather than
	   stretched to fill the freed-up space, per the "editorial, not empty"
	   brief: negative space around the block is intentional, not a gap to
	   fill with wider text. */
	max-width: 640px;
}

.v2-contact__greeting {
	margin: 0;
	/* Fixed line-height + min-height (not a fixed width — brief §11
	   explicitly forbids constraining long greetings) keeps this box the
	   same height regardless of which greeting word is showing, so the
	   information rows block below never jumps as the rotation runs. */
	min-height: calc(var(--contact-greeting-size) * 1.2);
	font-family: var(--font-base);
	font-size: var(--contact-greeting-size);
	/* Kept at its existing weight (not --weight-body): same "Display" scale
	   as Work's intro heading (see this rule's own comment above), a major
	   editorial statement, not supporting copy — typography-lightening pass
	   leaves it untouched. */
	font-weight: 500;
	line-height: 1.2;
	letter-spacing: -0.025em;
	color: var(--color-fg);
	will-change: transform, opacity;
}

/* ─── Information rows ───────────────────────────────────────────────────── */
/*
 * One shared grid (§3/§22 of the brief): `.v2-contact__rows` defines the
 * column tracks ONCE (`max-content 1fr`) — column 1 sized to the widest
 * label actually present (e.g. "Studio Marseille") — and every
 * `.v2-contact__row` wrapper (one per repeater item, page-contact.php)
 * inherits those exact same tracks via `grid-template-columns: subgrid`
 * rather than sizing its own local pair of columns. That's what keeps every
 * label and every value sharing one exact alignment column across the whole
 * list (brief §5/§6) while still giving each repeater item its own real row
 * container (brief §1/§3) — the two requirements that a single flat 2-column
 * grid could not satisfy together (see the `:root` block above for why).
 * Works unmodified for any row count (brief §14: 3, 4, 5, 6+ rows) since
 * nothing here indexes or counts rows.
 */

.v2-contact__rows {
	display: grid;
	grid-template-columns: max-content 1fr;
	row-gap: var(--contact-row-gap);
}

.v2-contact__row {
	/* Spans both of the parent's column tracks so `subgrid` below has both
	   to inherit — without this a row wrapper would only occupy column 1. */
	grid-column: 1 / -1;
	display: grid;
	grid-template-columns: subgrid;
	column-gap: var(--contact-col-gap);
	/* The actual fix: aligns the label's top edge with its value's first
	   line (brief §2) — both are now plain siblings in the SAME one-row
	   grid, so `start` has nothing else to fight (no margin, no separate
	   stack) and a multiline value's extra height only grows this one row,
	   never shifting the label. */
	align-items: start;
}

.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-muted);
}

.v2-contact__value {
	/* Grid items default to min-width: auto, which can force the `1fr` value
	   column wider than its track (and the page) if it holds a single long
	   unbroken string — min-width: 0 lets the track actually shrink to fit,
	   so overflow-wrap below can do its job instead of pushing the page
	   wider on narrow mobile viewports. */
	min-width: 0;
	/* Caps line length so a longer value (a street address) wraps onto
	   multiple lines like the mockup, instead of stretching across the
	   whole `1fr` track on wide desktop viewports — see this value's own
	   :root token comment. A short value (phone/email) simply doesn't reach
	   this width and stays on one line; grid's default `justify-self:
	   stretch` still keeps it flush against the column's start edge once
	   capped, not re-centered. */
	max-width: var(--contact-value-max-width);
	font-family: var(--font-base);
	font-size: var(--contact-value-size);
	font-weight: var(--weight-body);
	/* Tight internal line-height for a multiline value (e.g. a two-line
	   street address, brief §7's "small internal line spacing for
	   multiline values") — distinct from the row-to-row rhythm above. */
	line-height: 1.2;
	letter-spacing: -0.01em;
	color: var(--color-fg);
	text-decoration: none;
	opacity: var(--opacity-secondary);
	transition: opacity var(--transition-ui);
	/* Safety net against horizontal overflow on narrow mobile widths for an
	   unusually long unbroken value (e.g. a long email address) — normal
	   words/addresses still wrap at natural spaces first. */
	overflow-wrap: anywhere;
}

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

/* A row whose Link is empty (brief §12) renders plain, non-interactive
   text — same visual weight/size as a linked value, just no hover/focus
   affordance since there is nothing to activate. */
.v2-contact__value--static {
	cursor: default;
}

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

@media (max-width: 767px) {
	:root {
		/* Same ordering as desktop, just compacted — row/column gap bumped
		   the same moderate +20-30% this pass applies at desktop. */
		--contact-group-gap: clamp(32px, 8vw, 44px);
		--contact-row-gap: 26px;
		--contact-col-gap: clamp(14px, 3.5vw, 20px);
		/* Not overridden separately: at phone widths the grid's own value
		   column is already narrower than this token in the common case
		   (viewport width minus page margins minus the label column), so
		   the desktop value simply doesn't engage — the address still wraps
		   from the track's own width, no conflicting rule needed. */
	}

	/* No header-clearance padding-top here — .v2-contact__main centers its
	   content within the space between Header and Footer, so an asymmetric
	   top padding would just push the block below true center. The Header
	   overlay (~90-110px) stays clear of the centered block at any realistic
	   mobile viewport height on its own; the short-viewport query below
	   tightens things further if it doesn't. */

	/* The two-column label/value grid still has enough room at phone widths
	   (brief §18: keep it if it fits rather than blindly stacking) — the
	   label column auto-sizes to its own content, so it never takes more
	   space than the longest label actually needs, leaving the rest of the
	   line for the value to wrap onto (a multiline address included). */
}

/* ─── Short viewports (any width) ────────────────────────────────────────── */
/*
 * Contact is a strict no-scroll page (`.v2-contact { height: 100svh;
 * overflow: hidden }` above) — on a short viewport (a small laptop window, a
 * landscape phone) the composition must compress to keep fitting, not clip
 * or scroll. Height-based, not width-based, so it applies equally on a short
 * desktop window and a short mobile one, layered after both the base and
 * `(max-width: 767px)` rules above (later in source = wins for the
 * :root custom properties it overrides).
 */

@media (max-height: 700px) {
	:root {
		--contact-group-gap: clamp(24px, 4vw, 36px);
		--contact-row-gap: clamp(18px, 2.6vw, 24px);
		--contact-greeting-size: clamp(24px, 2.1vw, 32px);
	}

	.v2-contact__main {
		padding-bottom: 16px;
	}
}

/* ─── 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);
	column-count: 2;
	column-gap: var(--info-about-column-gap);
}

/* 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. */
.v2-info__about-text > * {
	margin: 0;
	break-inside: avoid;
}

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

.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);
}

/* 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__about-text {
		column-count: 1;
	}

	.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);
	}
}
