/*
 * senzait-kit - the component layer.
 *
 * Design TOKENS are NOT here. They come from theme.json as CSS custom properties that WordPress
 * generates, so they stay correct across core upgrades instead of being duplicated and drifting.
 * Everything below consumes them: --wp--preset--color--*, --wp--preset--font-size--*,
 * --wp--preset--spacing--*, --wp--custom--motion--*.
 *
 * The identity has no colour. Hierarchy is carried by weight, size and space - which is why this
 * file has so few declarations and so much attention to scale.
 */

/* ============================================================ base */

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

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

/*
 * A flex column so the footer reaches the bottom of a short page. Measured before this rule:
 * the footer ended 109px above the fold on an empty page, leaving a band of page-white under the
 * grey footer - and with no content yet, EVERY page is short.
 *
 * 100svh, not 100vh: on mobile, vh is the height WITHOUT the collapsing address bar, so 100vh
 * overflows the visible viewport and puts the gap back lower down where it is harder to spot.
 *
 * The skip link and the brand <defs> block are position:absolute, so they stay out of this flow.
 */
body {
	margin: 0;
	min-height: 100svh;
	display: flex;
	flex-direction: column;
	text-rendering: optimizeLegibility;
	-webkit-font-smoothing: antialiased;
}

/* main takes the slack; the footer is pushed down rather than pulled up. */
.site-main { flex: 1 0 auto; }
.site-footer { margin-block-start: auto; }

img,
svg,
video { max-width: 100%; height: auto; }

/*
 * A visible focus ring on everything focusable, always. His brief requires visible focus states,
 * and :focus-visible gives keyboard users the ring without flashing it at mouse users.
 */
:focus-visible {
	outline: 2px solid var(--wp--preset--color--ink);
	outline-offset: 3px;
}

/*
 * Headings must never push the page sideways (audit T5, 2026-09-21).
 *
 * Measured with the language switcher set to German on a 390 px phone: /consulting/ became 398 px
 * wide because one heading word could not wrap; in English "Technology commercialisation" already
 * stood 11 px outside its box on the homepage. Two rules, both at zero specificity (:where) so any
 * component that sets its own wrapping still wins:
 *   1. a TRANSLATED page (the switcher sets <html lang> to the chosen language; English stays
 *      en-US) may hyphenate, and may break an unbreakable word as a last resort. German compounds
 *      are why this exists; measured after: six pages in German at 390 px, all 375 px wide.
 *   2. English, only below 340 px. THE FIRST VERSION APPLIED TO ENGLISH EVERYWHERE AND WAS WRONG:
 *      it passed every measurement and then a screenshot showed the Contact title on a phone as
 *      "Start with a / conversation / ." - the full stop alone on a line, where before the word had
 *      simply sat 10 px into the side margin. Measured with the rule switched off: at 390 and 360 px
 *      no English heading passes the screen edge and no page scrolls sideways; only at 320 px does
 *      one word ("commercialisation") run 20 px past the edge. So English is left alone where it
 *      was never broken, and gets proper hyphenation (never a bare break first) where it was.
 */
html:not([lang|="en"]) :where(h1, h2, h3, h4) {
	overflow-wrap: break-word;
	hyphens: auto;
}
@media (max-width: 21.25rem) {
	:where(h1, h2, h3, h4) {
		overflow-wrap: break-word;
		hyphens: auto;
		hyphenate-limit-chars: 12 6 6;
	}
}

/* Honour the visitor's motion preference at the framework level, so a component cannot forget. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

/* ============================================================ a11y utilities */

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

/*
 * The skip link. It MUST be hidden until focused - it was visible in the top-left of every page
 * until this rule existed, which is both an eyesore and a sign the pattern was half-implemented.
 * Hidden by moving it off-screen rather than display:none, because a display:none element is not
 * focusable and the link would stop working entirely.
 */
.skip-link {
	position: absolute;
	top: 0;
	/* logical, not `left`: in a right-to-left language (the switcher offers Arabic) the link must
	   appear at the START of the line, which is the right-hand side. Audit T7, 2026-09-21. */
	inset-inline-start: 0;
	transform: translateY(-120%);
	padding: var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);
	background: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--page);
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	text-decoration: none;
	z-index: 100;
	transition: transform var(--wp--custom--motion--duration--fast) var(--wp--custom--motion--ease--out);
}
.skip-link:focus { transform: translateY(0); }

/* ============================================================ layout */

.container {
	width: 100%;
	max-width: var(--wp--style--global--wide-size, 84rem);
	margin-inline: auto;
	padding-inline: var(--wp--preset--spacing--30);
}

/* ============================================================ the brand mark */

/*
 * Sized by WIDTH and never restyled - it is his artwork, not a component. currentColor means it
 * inverts with its surroundings, which is why the reversed SVG files were never needed.
 * min_height_mm 8 from the brand pack is comfortably exceeded at every size used here.
 */
.brand-mark {
	display: block;
	height: auto;
	color: currentColor;
}
.brand-mark--wordmark { width: clamp(4.9rem, 8.08vw, 6.35rem); }  /* = old clamp(8.5rem, 14vw, 11rem) x 613/1062; see template.php */
.brand-mark--tagline  { width: clamp(11rem, 22vw, 17rem); }
.brand-mark--lockup   { width: clamp(12rem, 26vw, 20rem); }

/* ============================================================ site header */

/*
 * Navigation height is capped. taste-skill: 80px maximum on desktop, 64-72 the default - no
 * agency nav bar eating 15% of the viewport. Here the mark's own height sets it, within that cap.
 */
.site-header {
	/* Paints its own ground (review finding 2026-09-04, item 5.5): the fixed bloom
	   (.senzait-canvas__glow) sits behind everything and its safety margin under the header shrinks
	   at shorter (16:9) viewports - 1440x810, 1536x864, 1920x1080 - where the earlier proof (taken
	   only at 1440x900) never looked. The header is on the band colour anyway, so this is not a
	   visual change; it makes that true regardless of viewport instead of by accident of margin.
	   Never remove without re-running the header pixel proof (tools/header-pixels.js) at BOTH
	   1440x900 and a 16:9 height. */
	background: var(--wp--preset--color--band);
	border-bottom: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
}
/*
 * Inline padding MUST equal .site-main's - that equality plus core's constrained layout on
 * .site-header__col is what puts the wordmark on the page column at every width.
 */
.site-header__inner {
	width: 100%;
	max-width: var(--wp--style--global--wide-size, 84rem);
	margin-inline: auto;
	padding-block: var(--wp--preset--spacing--20);
	padding-inline: var(--wp--preset--spacing--30);
}
/* No max-width or margin here - core's `.is-layout-constrained > :where(...)` rule on
   .site-header__col already supplies both, and its :where() has zero specificity, so any width or
   margin declared here would win the cascade and break the alignment it depends on. */
.site-header__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--40);
}
/* ============================================================ BRAND TEXT - header tagline
 *
 * The brand block holds the mark and, beneath it, the tagline as LIVE TEXT - his instruction
 * 2026-09-01. line-height: 0 has moved from the container onto the link: on the container it would
 * collapse the tagline it now has as a sibling. It is there to kill the inline-block descender gap
 * under the SVG, which is a property of the link, not of the block.
 */
.site-header__brand {
	display: grid;
	justify-items: start;
	gap: 0.25rem;
	margin: 0;
}

.site-header__home {
	display: inline-block;
	line-height: 0;
	color: var(--wp--preset--color--ink);
	text-decoration: none;
}

/*
 * Nimbus Sans Bold with the pack's own tracking. NOT the strict 0.333 lockup ratio, which lands at
 * 8.9px on mobile - fine for a vector nobody reads closely, too small for live text. The xs step
 * gives a 0.450 cap ratio, which is precisely what the tagline VECTOR was already rendering at on
 * this site, so nothing about the page's proportions changes.
 *
 * Uppercased here rather than in the string: some screen readers spell out all-caps text, and the
 * SVG variant's own aria-label was already sentence case.
 */
.site-header__tagline {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: var(--wp--preset--font-size--xs);
	line-height: 1.2;
	letter-spacing: var(--wp--custom--tagline-tracking);
	text-transform: uppercase;
	color: var(--wp--preset--color--ink);
}

.site-nav__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--30);
	margin: 0;
	padding: 0;
	list-style: none;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	letter-spacing: 0.1em;
	text-transform: uppercase;
}
.site-nav__list a {
	color: var(--wp--preset--color--ink);
	text-decoration: none;
	padding-block: 0.25em;
	border-bottom: var(--wp--custom--rule--strong) solid transparent;
	transition: border-color var(--wp--custom--motion--duration--fast) var(--wp--custom--motion--ease--out);
}
.site-nav__list a:hover,
.site-nav__list a:focus-visible { border-bottom-color: var(--wp--preset--color--ink); }

/* ============================================================ main */

.site-main {
	width: 100%;
	max-width: var(--wp--style--global--wide-size, 84rem);
	margin-inline: auto;
	padding: var(--wp--preset--spacing--60) var(--wp--preset--spacing--30);
	min-height: 40vh;
}

.entry__title {
	font-size: var(--wp--preset--font-size--xxxl);
	line-height: 1.02;
	margin: 0 0 var(--wp--preset--spacing--30);
	text-wrap: balance;
	/* A title with one long word must break rather than push the page sideways (2026-09-04).
	   MEASURED on /policies/vendor-acknowledgement/ at 390x844: the title is 45.91px there and the
	   single word "Acknowledgement" is 418px wide in a 340px column, so it overflowed by 78px and
	   took the whole document to 436px against a 390px viewport - the body scrolled sideways on
	   every phone. Nothing else on the site had a word long enough to show it, which is why it had
	   never appeared; it was waiting for a title like this one, not caused by it.
	   hyphens first, so the break is a proper hyphenated one where the browser has the dictionary;
	   overflow-wrap as the guarantee, because a rule that only works when a dictionary is loaded
	   is not a guarantee. Both are inert on every title that already fits. */
	hyphens: auto;
	overflow-wrap: break-word;
}
.entry__content { max-width: var(--wp--custom--measure); }
.entry__content > * + * { margin-block-start: var(--wp--preset--spacing--30); }

.entry__meta {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-2);
}

/* ============================================================ archive */

.archive__list {
	display: grid;
	gap: var(--wp--preset--spacing--40);
	margin: var(--wp--preset--spacing--50) 0 0;
	padding: 0;
	list-style: none;
}
.archive__item { border-top: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline); padding-top: var(--wp--preset--spacing--30); }
/* The empty-listing sentence (home.php/archive.php/search.php with no results) had no rule at
   all - default black-on-white against every other secondary line on the site. Matches
   .card__excerpt's tone (ink-2), added 2026-09-05. */
.archive__empty { margin: var(--wp--preset--spacing--50) 0 0; color: var(--wp--preset--color--ink-2); }
.card__title { font-size: var(--wp--preset--font-size--lg); margin: 0 0 var(--wp--preset--spacing--10); }
.card__title a { color: var(--wp--preset--color--ink); text-decoration: none; }
.card__title a:hover { text-decoration: underline; text-underline-offset: 0.18em; }

/* ============================================================================================
 * BLOG LISTING + SINGLE-POST POLISH (2026-09-04). Card grid: one column, two from 64rem (the same
 * rung `.archive__list--posts` switches to in home.php/archive.php) with a column-gap; square
 * corners throughout, matching the rest of the theme. Tokens only - every `--wp--preset--*` and
 * `--wp--custom--*` name below is grepped from elsewhere in this file (ink, ink-2, hairline, band,
 * page, accent; rule--hairline/rule--strong; spacing--10/20/30/40/50; font-family--display;
 * font-size--xs/sm/md/lg/xxxl) - `ink-soft` does not exist in theme.json, `ink-2` is the real slug.
 * ============================================================================================ */
.archive__list--posts { grid-template-columns: 1fr; }
@media (min-width: 64rem) {
	.archive__list--posts { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--wp--preset--spacing--40); }
}
.card--post { display: flex; flex-direction: column; gap: var(--wp--preset--spacing--10); }
.card__media { display: block; aspect-ratio: 16 / 9; overflow: hidden; background: var(--wp--preset--color--hairline); }
.card__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card__meta,
.entry__category,
.entry__byline {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.14em;
	text-transform: uppercase;
	margin: 0;
}
.card__category,
.entry__category a { color: var(--wp--preset--color--accent); text-decoration: none; }
.card__sep { margin-inline: 0.5em; }
.card__excerpt { margin: 0; color: var(--wp--preset--color--ink-2); }

/* Single post: thumbnail, credit caption, byline, author note, prev/next. */
/* Symmetric space around the hero. Measured on the live post 2026-09-04: the header stack ran
   title -24- date -13- category -12- byline -0- IMAGE, so the byline literally touched the top edge
   of the photograph while every other gap in the stack had air. The bottom margin was already
   spacing--40 (and the figure-to-body gap measured 40px); the top now matches it. */
.entry__media { margin: var(--wp--preset--spacing--40) 0; }
.entry__media img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; }
.entry__credit { font-size: var(--wp--preset--font-size--xs); color: var(--wp--preset--color--ink-2); margin-top: var(--wp--preset--spacing--10); }
.entry__content .wp-block-image figcaption { font-size: var(--wp--preset--font-size--xs); color: var(--wp--preset--color--ink-2); }
/* ink-2, the secondary ink `.entry__meta` already uses: the byline is the same kind of line as the
   date printed above it (who, and when), and at full `ink` it competed with the title. Task 6
   review carry-over, applied 2026-09-04. */
.entry__byline { margin-block-start: var(--wp--preset--spacing--10); color: var(--wp--preset--color--ink-2); }
.entry__category { margin-block-start: var(--wp--preset--spacing--10); }
.entry__author {
	border-top: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	margin-block-start: var(--wp--preset--spacing--40);
	padding-block: var(--wp--preset--spacing--30);
}
.entry__author-name {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--md);
	margin: 0 0 var(--wp--preset--spacing--10);
}
.entry__author-role {
	display: block;
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-2);
}
.entry__author-bio { margin: 0; color: var(--wp--preset--color--ink-2); }

.pagination .nav-links,
.post-navigation .nav-links {
	display: flex;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--30);
	margin-block: var(--wp--preset--spacing--50);
}
.post-navigation a { text-decoration: none; color: var(--wp--preset--color--ink); }
.entry__nav-label {
	display: block;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-2);
}
.entry__nav-title { color: var(--wp--preset--color--ink); }

/* ============================================================ site footer */

.site-footer {
	border-top: var(--wp--custom--rule--strong) solid var(--wp--preset--color--ink);
	margin-block-start: var(--wp--preset--spacing--60);
	background: var(--wp--preset--color--page);
}
.site-footer__inner {
	display: grid;
	gap: var(--wp--preset--spacing--40);
	width: 100%;
	max-width: var(--wp--style--global--wide-size, 84rem);
	margin-inline: auto;
	padding: var(--wp--preset--spacing--50) var(--wp--preset--spacing--30);
}
@media (min-width: 48rem) {
	.site-footer__inner {
		grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
		align-items: start;
	}
	.site-footer__legal { grid-column: 1 / -1; }
}

/*
 * The footer names the company IN FULL as text rather than showing the logo - his instruction
 * 2026-09-01. The mark stays in the header, where a logo belongs.
 */
.site-footer__brand {
	display: grid;
	justify-items: start;
	gap: var(--wp--preset--spacing--10);
}

.site-footer__name {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: var(--wp--preset--font-size--md);
	line-height: 1.15;
	letter-spacing: 0.01em;
	color: var(--wp--preset--color--ink);
}

.site-footer__tagline {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 700;
	font-size: var(--wp--preset--font-size--xs);
	line-height: 1.2;
	letter-spacing: var(--wp--custom--tagline-tracking);
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-2);
}

/*
 * The CIN sits with the registered address - his instruction 2026-09-01 - not in the copyright
 * line. Its own line, and tabular figures so a 21-character identifier reads as digits rather
 * than as a word.
 */
.site-footer__cin {
	display: block;
	margin-block-start: var(--wp--preset--spacing--20);
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.04em;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--ink-2);
}

.site-footer__address {
	font-style: normal;   /* <address> is italic by browser default; the identity is not */
	line-height: 1.75;
	color: var(--wp--preset--color--ink-2);
}
.site-footer__address a { color: var(--wp--preset--color--ink); }

.site-footer__legal {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--10) var(--wp--preset--spacing--30);
	margin: 0;
	padding-top: var(--wp--preset--spacing--30);
	border-top: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-2);
}

.site-footer__list {
	display: grid;
	gap: var(--wp--preset--spacing--10);
	margin: 0;
	padding: 0;
	list-style: none;
}
.site-footer__list a:not(.wp-block-button__link):not(.social-link):not(.skip-link) { color: var(--wp--preset--color--ink); text-decoration: none; }
.site-footer__list a:hover { text-decoration: underline; text-underline-offset: 0.18em; }

/* ============================================================ the strata motif */

/*
 * The banded pyramid, from the A of the wordmark - the identity's ONLY graphic device. The brand
 * pack says its "horizontal-strata rhythm carries table headers, section rules and slide
 * dividers", and "extend the mark; do not invent decoration beside it". So this is the section
 * divider, and nothing else decorates.
 */
.strata {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 3px;
	margin-inline: auto;
}
.strata i { display: block; height: var(--wp--custom--rule--strong); background: currentColor; }
.strata i:nth-child(1) { width: 12px; }
.strata i:nth-child(2) { width: 28px; }
.strata i:nth-child(3) { width: 44px; }
.strata i:nth-child(4) { width: 60px; }


/* ============================================================================================
 * SECTIONS - Phase 5
 *
 * Every value below is a theme.json token. No literal colour, size or spacing appears, so a brand
 * change stays a data edit that regenerates theme.json and this file follows without being touched.
 *
 * The layout families are deliberately all different, one per section. With eleven sections that
 * is the constraint that does the most design work: it is what stops the page becoming eleven
 * variations on a centred card.
 * ============================================================================================ */

/* Shared section rhythm. Sections carry their own vertical space so patterns can be reordered in
   the editor without their spacing depending on what happens to sit above them. */
.s-hero, .s-about, .s-process, .s-taxonomy, .s-rows,
.s-pair, .s-tags, .s-ecosystem, .s-why, .s-insights, .s-cta, .s-faq {
	padding-block: var(--wp--preset--spacing--60);
}

/* .s-about is deliberately ABSENT: its statement has its own larger size, and
   listing it here would win on specificity (0-1-1 over 0-1-0) and silently
   shrink it - which is exactly what it did. */
/* On the front page, THE GRID section (redesign A, 2026-09-04) overrides this xl size to xxl for
 * every cell title via the higher-specificity `.s-grid > .s-cell > h2` rule - that
 * override is intentional (larger cell titles). This xl rule still governs these headings on the
 * Sections template and any non-front use of these classes, so it stays as-is. */
.s-process h2, .s-taxonomy h2, .s-rows h2,
.s-pair h2, .s-tags h2, .s-ecosystem h2, .s-why h2, .s-insights h2, .s-faq h2 {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xl);
	margin-block: 0 var(--wp--preset--spacing--40);
}

/* --- 1. Hero -------------------------------------------------------------------------------
   A full-height opener. svh not vh, for the same reason the sticky footer uses it: on mobile vh
   is the height WITHOUT the collapsing address bar, so vh would push the buttons off screen on
   exactly the devices where the fold matters most. */
.s-hero {
	min-height: min(84svh, 52rem);
	display: flex;
	flex-direction: column;
	justify-content: center;
	/* Restoring the real spacing scale (Task 3) takes --spacing--70 from 54px to 176px at 1440.
	   taste-skill caps hero top padding at 6rem and he adopted that; the bottom keeps the scale. */
	padding-block-start: min(var(--wp--preset--spacing--70), 6rem);
	padding-block-end: var(--wp--preset--spacing--70);
}

/* Compact hero: pages whose job starts below the heading (Contact) don't get the full-screen
   opener - it pushed the first form field below the fold. Opt-in via the Styles panel. */
.s-hero.is-style-compact-hero {
	min-height: 0;
	padding-block-start: var(--wp--preset--spacing--40);
	padding-block-end: var(--wp--preset--spacing--40);
}

.s-hero__title {
	font-family: var(--wp--preset--font-family--display);
	/* Capped, not the preset alone. The display preset bottoms out at 3.25rem, and at that size
	   a single long word is wider than the whole usable width of a small phone - "Technologies"
	   measures 333px and "conversation." 337px against the 326px available at 360, so being
	   unbreakable they left the viewport and /brands/ and /contact/ scrolled sideways.
	   Smaller type is the only remedy that works: 20ch below is font-relative, so shrinking the
	   size cannot change whether a word fits the MEASURE, only whether it fits the SCREEN - and
	   the screen is what overflows. overflow-wrap is not the answer either; it breaks against
	   the measure, splitting "Engineering" into "Engineerin" + "g" on three pages that are fine
	   today. 13vw is inert above ~445px - rendering at 1440 is identical, line for line - and
	   below it keeps the longest word on screen with room for roughly a 14-character word. */
	font-size: min(var(--wp--preset--font-size--display), 13vw);
	line-height: 1.02;
	letter-spacing: -0.02em;
	text-wrap: balance;
	margin-block: 0;
	/* min(), not 20ch: at 112px the 20ch measure is 1245px against a 1088px column, so a plain
	   20ch would overflow the moment this stops being centred. */
	max-inline-size: min(20ch, var(--wp--style--global--content-size), 100%);
}

.s-hero__lede {
	font-size: var(--wp--preset--font-size--md);
	line-height: 1.55;
	color: var(--wp--preset--color--ink-2);
	max-inline-size: var(--wp--custom--measure);
	margin-block: var(--wp--preset--spacing--40) 0;
}

.s-hero__actions {
	margin-block-start: var(--wp--preset--spacing--50);
}

/* --- 2. About snapshot ---------------------------------------------------------------------
   A large statement with its supporting sentence set beside it, not beneath it. */
.s-about__statement {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xxl);
	line-height: 1.12;
	letter-spacing: -0.015em;
	text-wrap: balance;
	margin-block: 0;
	max-inline-size: 16ch;
}

.s-about__body {
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.7;
	color: var(--wp--preset--color--ink-2);
	max-inline-size: var(--wp--custom--measure);
	margin-block: var(--wp--preset--spacing--30) 0;
}

@media (min-width: 56rem) {
	/*
	 * Here the section's own padding IS the column inset. The generic inset rule cannot reach
	 * these two, because .s-about is the grid and a grid item's containing block is its CELL -
	 * far narrower than the column, so max() clamps the inset to 0 and it silently does nothing.
	 * Setting it as PADDING moves the lever up one level: the percentage then resolves against
	 * the page, the grid's content box lands on the column, and every cell lands with it.
	 */
	.s-about {
		display: grid;
		grid-template-columns: 1.1fr 1fr;
		column-gap: var(--wp--preset--spacing--50);
		align-items: start;
		padding-inline: max(
			var(--wp--preset--spacing--40),
			calc((100% - var(--wp--style--global--content-size)) / 2)
		) !important;
	}
	.s-about__body { margin-block-start: 0.6rem; }
}

/* --- 3. Innovation overview ----------------------------------------------------------------
   A horizontal sequence. The rule runs behind the steps and each step sits on the page ground, so
   the line reads as continuous without any step needing its own box. */
.s-process__steps {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--20) var(--wp--preset--spacing--40);
	position: relative;
}

/* The sequence line is drawn per STEP, not once across the container, so on a phone every wrapped
   row carries it (before: only the first of three rows did, and the fragment dangled past the last). */
.s-process__steps li::before {
	content: "";
	position: absolute;
	inset-inline: 0;
	inset-block-start: 0.62em;
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	z-index: 0;
}
.s-process__steps li:last-child::before { inset-inline-end: 50%; }

.s-process__steps li {
	position: relative;
	z-index: 1;
	background: var(--wp--preset--color--band);
	padding-inline-end: var(--wp--preset--spacing--20);
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	text-transform: uppercase;
	letter-spacing: 0.06em;
}

/* --- 4. Manufacturing capabilities ---------------------------------------------------------
   A taxonomy: a heading with its members underneath. Each item is opened by a rule, which is the
   only decoration the brand pack allows itself. */
/*
 * The lede under a taxonomy section heading. Tokens copied from .s-ecosystem__lede, not invented -
 * the class had no rule at all and was rendering at pure rgb(0,0,0) with no spacing.
 *
 * Takes the --senzait-col-inset treatment, and .s-taxonomy was added to that list for it.
 * CORRECTED 2026-09-02: the first version used `margin-inline-start: 0`, copying .s-about without
 * noticing WHY .s-about does that - its items sit in grid CELLS that already begin on the column.
 * A .s-taxonomy child is a direct child of the constrained section. Measured result of the wrong
 * version: heading 169px, cards 169px, lede 62px. The 107px gap the ALIGNMENT block above already
 * documents, reproduced by reading past the note that describes it.
 */
.s-taxonomy__lede {
	font-size: var(--wp--preset--font-size--md);
	line-height: 1.55;
	max-inline-size: var(--wp--custom--measure);
	margin-block: 0 var(--wp--preset--spacing--50);
	margin-inline-start: var(--senzait-col-inset) !important;
	margin-inline-end: auto !important;
}

.s-taxonomy__grid {
	display: grid;
	gap: var(--wp--preset--spacing--40);
}

@media (min-width: 52rem) {
	.s-taxonomy__grid { grid-template-columns: repeat(3, 1fr); }
}

.s-taxonomy__item {
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--ink);
	padding-block-start: var(--wp--preset--spacing--20);
}

.s-taxonomy__item h3 {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--base);
	margin-block: 0 var(--wp--preset--spacing--10);
}

.s-taxonomy__item p {
	font-size: var(--wp--preset--font-size--sm);
	line-height: 1.65;
	color: var(--wp--preset--color--ink-2);
	margin-block: 0;
}

/* --- 5. R&D, and 9. Why Senzait ------------------------------------------------------------
   Hairline rows. Two sections share the row MECHANIC but not the layout: R&D is a single column of
   full-width rows, Why Senzait is two columns. Sharing the mechanic is what keeps the page
   coherent; the differing column count is what keeps them from reading as the same section twice. */
.s-rows__list, .s-why__list {
	list-style: none;
	margin: 0;
	padding: 0;
}

.s-rows__list li, .s-why__list li {
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	padding-block: var(--wp--preset--spacing--30);
	display: grid;
	gap: 0.35rem;
}

.s-rows__list li strong, .s-why__list li strong {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--base);
	font-weight: 700;
}

.s-rows__list li span, .s-why__list li span {
	font-size: var(--wp--preset--font-size--sm);
	line-height: 1.65;
	color: var(--wp--preset--color--ink-2);
	max-inline-size: var(--wp--custom--measure);
}

@media (min-width: 56rem) {
	.s-rows__list li {
		grid-template-columns: 18rem 1fr;
		gap: var(--wp--preset--spacing--40);
		align-items: baseline;
	}
	.s-why__list {
		display: grid;
		grid-template-columns: repeat(2, 1fr);
		column-gap: var(--wp--preset--spacing--50);
	}
}

/* --- 6. Featured technologies --------------------------------------------------------------
   Two entries at a larger size than the taxonomy, so the page has one moment of scale between the
   opener and the closing band. */
.s-pair__cols { gap: var(--wp--preset--spacing--50); }

.s-pair__cols h3 {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--lg);
	line-height: 1.15;
	margin-block: 0 var(--wp--preset--spacing--20);
}

.s-pair__cols p {
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.65;
	color: var(--wp--preset--color--ink-2);
	margin-block: 0;
	max-inline-size: var(--wp--custom--measure);
}

/* --- 7. Industries served ------------------------------------------------------------------
   Twelve short entries. A card grid would spend the whole section on twelve two-word items, so
   they are set as a dense inline list on the band ground. */
/* Inverted when the page ground became the band: a band section on a band page is invisible.
   The contrast is kept by moving this one to page white rather than by leaving it to vanish. */
.s-tags { background: var(--wp--preset--color--page); }

.s-tags__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--10) var(--wp--preset--spacing--30);
}

.s-tags__list li {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	padding-block: 0.35rem;
}

/* A trailing rule on every item but the last, so a wrapped row never starts with an orphan tick
   (measured before: 1 orphan at 1440, 5 of 6 rows at 390). */
.s-tags__list li {
	padding-inline-end: var(--wp--preset--spacing--30);
	border-inline-end: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
}
.s-tags__list li:last-child {
	padding-inline-end: 0;
	border-inline-end: 0;
}

/* --- 8. Innovation ecosystem ---------------------------------------------------------------
   A lede at reading size, then the two technologies. The lede carries the ownership wording and is
   set wider than body copy because it is the paragraph that matters most on the page. */
.s-ecosystem__lede {
	font-size: var(--wp--preset--font-size--md);
	line-height: 1.55;
	max-inline-size: var(--wp--custom--measure);
	margin-block: 0 var(--wp--preset--spacing--50);
}

.s-ecosystem__items {
	display: grid;
	gap: var(--wp--preset--spacing--40);
}

@media (min-width: 52rem) {
	.s-ecosystem__items { grid-template-columns: repeat(2, 1fr); }
}

.s-ecosystem__item {
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--ink);
	padding-block-start: var(--wp--preset--spacing--20);
}

.s-ecosystem__item h3 {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--base);
	letter-spacing: 0.04em;
	margin-block: 0 var(--wp--preset--spacing--10);
}

.s-ecosystem__item p {
	font-size: var(--wp--preset--font-size--sm);
	line-height: 1.65;
	color: var(--wp--preset--color--ink-2);
	margin-block: 0;
}

.s-ecosystem__link { margin-block-start: var(--wp--preset--spacing--20) !important; }

/* --- 10. Latest insights -------------------------------------------------------------------
   Posts as hairline rows. The empty state is the one that renders today - there are no posts - so
   it is styled rather than left to fall back to a bare paragraph. */
.s-insights__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 0;
}

.s-insights__list > li {
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	padding-block: var(--wp--preset--spacing--30);
}

.s-insights__date {
	font-size: var(--wp--preset--font-size--xs);
	text-transform: uppercase;
	letter-spacing: 0.08em;
	color: var(--wp--preset--color--ink-2);
	margin-block: 0 0.4rem;
}

.s-insights__title {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--base);
	margin-block: 0 0.4rem;
}

.s-insights__excerpt {
	font-size: var(--wp--preset--font-size--sm);
	line-height: 1.65;
	color: var(--wp--preset--color--ink-2);
	max-inline-size: var(--wp--custom--measure);
	margin-block: 0;
}

.s-insights__empty {
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	padding-block-start: var(--wp--preset--spacing--30);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--ink-2);
	margin-block: 0;
}

/* --- 11. Contact CTA -----------------------------------------------------------------------
   The single place the brand pack's reversed ground is used, which is what makes it read as
   emphasis rather than decoration.

   NOT full-bleed, and that is a deliberate trade. Bleeding it edge to edge needs
   margin-inline: calc(50% - 50vw), and 50vw INCLUDES the scrollbar - which reintroduces the
   horizontal scroll that was measured away in Phase 4. A contained band cannot regress that, and
   the dark ground carries the emphasis on its own. */
.s-cta {
	background: var(--wp--preset--color--reversed);
	color: var(--wp--preset--color--page);
	/* padding-BLOCK only. Setting inline padding here overrode the section global padding and put
	   the band text 6px right of every other section on mobile - aligned on desktop, where the
	   column inset dominated, and out by six on a phone. Leaving the inline padding to the global
	   rule puts it on the same column at every width. */
	padding-block: var(--wp--preset--spacing--60);
	margin-block-start: var(--wp--preset--spacing--50);
}

.s-cta__title {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xxl);
	line-height: 1.12;
	letter-spacing: -0.015em;
	text-wrap: balance;
	color: var(--wp--preset--color--page);
	margin-block: 0;
	max-inline-size: 18ch;
}

.s-cta__body {
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.65;
	color: var(--wp--preset--color--hairline);
	max-inline-size: var(--wp--custom--measure);
	margin-block: var(--wp--preset--spacing--30) 0;
}

.s-cta__actions { margin-block-start: var(--wp--preset--spacing--40); }

/* --- 12. Questions before you engage us ----------------------------------------------------
   The FAQ section on the three service pages. It sits last, immediately above the CTA band.

   The hooks are the RANK MATH PLUGIN'S OWN CLASSES, not ours: the block is dynamic, so the markup
   is printed by includes/modules/schema/blocks/faq/class-block-faq.php at render time and there is
   no chance to add a class of our own to it. `#rank-math-faq`, `.rank-math-list`,
   `.rank-math-list-item`, `.rank-math-question` and `.rank-math-answer` were read from that file
   (1.0.277.2, 2026-09-04), not guessed. Everything is scoped under `.s-faq` so nothing here
   escapes into any other use of the block.

   NO ACCORDION AND NO JAVASCRIPT, deliberately. A <details> or a click-to-open list hides the
   answers from a reader who is skimming and, worse, is exactly the pattern that makes answer
   engines and screen readers work for the text. Six questions read plainly down one column is
   both the accessible form and the one the schema describes.

   One column, taking the section's own constrained column: the item hairline runs the full column
   like .s-taxonomy__item's does, and only the TEXT inside is held to the measure. Nothing here
   sets a max-inline-size on a direct child of the section, because core's constrained layout puts
   margin-inline: auto !important on those and would centre it - the trap the ALIGNMENT block
   below documents at length. `#rank-math-faq` is the only direct child, and it is left to fill
   the column. */
.s-faq__lede {
	/* Tokens shared with .s-taxonomy__lede, including the column inset - a direct child of the
	   section with its own measure needs it, or core's auto margins centre it. */
	font-size: var(--wp--preset--font-size--md);
	line-height: 1.55;
	max-inline-size: var(--wp--custom--measure);
	margin-block: 0 var(--wp--preset--spacing--50);
	margin-inline-start: var(--senzait-col-inset) !important;
	margin-inline-end: auto !important;
}

.s-faq .rank-math-list-item {
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--ink);
	padding-block-start: var(--wp--preset--spacing--20);
	margin-block-start: var(--wp--preset--spacing--40);
}

.s-faq .rank-math-list-item:first-child { margin-block-start: 0; }

.s-faq .rank-math-question {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.3;
	max-inline-size: var(--wp--custom--measure);
	margin-block: 0 var(--wp--preset--spacing--10);
}

/* The plugin wraps the answer in wpautop(), so the text arrives as a <p> inside .rank-math-answer.
   Styling the paragraph rather than the wrapper is what keeps the hairline full-column while the
   prose stays on the measure. */
.s-faq .rank-math-answer p {
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.65;
	color: var(--wp--preset--color--ink-2);
	max-inline-size: var(--wp--custom--measure);
	margin-block: 0;
}

/* --- Button styles -------------------------------------------------------------------------
   Registered with register_block_style() in inc/patterns.php, so they appear in the editor's style
   picker without a line of JavaScript. */
.wp-block-button.is-style-senzait-solid .wp-block-button__link {
	background: var(--wp--preset--color--ink);
	color: var(--wp--preset--color--page);
	border: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--ink);
	border-radius: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	letter-spacing: 0.04em;
	padding: 0.85em 1.6em;
}

.wp-block-button.is-style-senzait-outline .wp-block-button__link {
	background: transparent;
	color: var(--wp--preset--color--ink);
	border: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--ink);
	border-radius: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	letter-spacing: 0.04em;
	padding: 0.85em 1.6em;
}

.s-cta .wp-block-button.is-style-senzait-solid .wp-block-button__link {
	background: var(--wp--preset--color--page);
	color: var(--wp--preset--color--ink);
	border-color: var(--wp--preset--color--page);
}

.wp-block-button__link:hover { opacity: 0.82; }

@media (prefers-reduced-motion: no-preference) {
	.wp-block-button__link { transition: opacity var(--wp--custom--motion--duration--fast) ease; }
}


/* ============================================================================================
 * ALIGNMENT - neutralising the constrained layout's auto margins
 *
 * WordPress's constrained layout sets margin-left/right: auto !important on EVERY direct child.
 * While a child fills the column that is invisible. Give one our own max-inline-size and it
 * becomes narrower than the column, and those auto margins centre it.
 *
 * MEASURED on the live page, against a column starting at 176px:
 *     hero lede 365   hero buttons 532   cta title 635 (170px wide)   ecosystem lede 365
 *
 * Every number about the page was correct and the page still looked wrong. It took a screenshot.
 *
 * !important is required because core's own declaration is !important. The alternative - an inner
 * wrapper in every pattern so our text is not a direct child - is tidier CSS and worse for him,
 * because it adds a nesting level to navigate in the editor for every section. His editability
 * requirement outranks our tidiness, so the override is narrow, named and explained instead.
 * ============================================================================================ */

/*
 * The destination is the COLUMN's left edge, not zero. A first attempt used 0 and aligned these to
 * the section's padding edge instead - measured 107px left of every other section's content, which
 * traded a ragged-centred page for a ragged-left one.
 *
 * The inset is computed, not typed, so it stays correct at every width: on desktop it resolves to
 * (container - column) / 2; once the container is narrower than the column, max() clamps it to 0
 * and the text aligns to the padding edge, which is what it should do there.
 */
.s-hero,
.s-about,
.s-ecosystem,
.s-cta,
.s-taxonomy,
.s-rows,
.s-pair,
.s-tags,
.s-process,
.s-why,
.s-insights,
.s-faq {
	/*
	 * EVERY section with a constrained container, not just the ones that happened to need it when
	 * this was written. Extending it one section at a time produced the same 107px misalignment
	 * three separate times: the taxonomy lede, and then the diptych and two bands in .s-rows,
	 * .s-pair and .s-tags. An undefined custom property makes the declaration invalid and the
	 * element falls back to the padding edge.
	 */
	--senzait-col-inset: max(0px, (100% - var(--wp--style--global--content-size)) / 2);
}

.s-hero__title,
.s-hero__lede,
.s-hero__actions,
.s-ecosystem__lede,
.s-cta__title,
.s-cta__body,
.s-cta__actions {
	margin-inline-start: var(--senzait-col-inset) !important;
	margin-inline-end: auto !important;
}



/* Core's FLOW layout adds margin-block-start to `> * + *`. Inside a container we turned into a
   grid that pushed items 2 and 3 down while item 1 stayed put - measured tops 1352 / 1368 / 1368.
   The grid's own gap is what should space these. */
.s-taxonomy__grid > *,
.s-ecosystem__items > * {
	margin-block-start: 0 !important;
}


/*
 * The About pair is placed by the grid, so they start at their cell edge - and the cell already
 * begins on the column, because the section pads itself to it. What they still need is a zero,
 * not an inset: without it core auto-margins centre each one INSIDE its own cell, which measured
 * as the statement sitting 118px in from the column while its body lined up perfectly.
 *
 * Unconditional rather than inside the grid media query: below 56rem the section is not a grid and
 * the container is narrower than the column, so the inset those elements would otherwise take is
 * zero anyway. One rule that is correct at every width beats two that agree only by arithmetic.
 */
.s-about__statement,
.s-about__body {
	margin-inline-start: 0 !important;
	margin-inline-end: auto !important;
}


/* ============================================================================================
 * SOCIAL - the footer profile row
 *
 * Monochrome, like everything else. The platforms' own brand colours would be the only colour on
 * the entire site, which is precisely the thing the brand pack's monochrome-forward house style
 * exists to prevent.
 * ============================================================================================ */

.site-footer__social {
	list-style: none;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 0.15rem;
	margin-block: var(--wp--preset--spacing--30) 0;
	/*
	 * The hit area is wider than the glyph inside it, so without pulling the row back by half
	 * that difference the first icon reads as indented against the company name and tagline
	 * directly above it. Optical alignment, not measured alignment.
	 */
	margin-inline: -0.625rem 0;
}

.social-link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/*
	 * 2.5rem = 40px. WCAG 2.2 added Target Size (Minimum) at AA, which is 24x24 CSS pixels; this
	 * clears that comfortably and sits close to the 44px commonly recommended for touch. The
	 * GLYPH is 1.25rem - the rest is hit area, which is the point.
	 */
	inline-size: 2.5rem;
	block-size: 2.5rem;
	color: var(--wp--preset--color--ink-2);
	text-decoration: none;
}

.social-icon {
	display: block;
	inline-size: 1.25rem;
	block-size: 1.25rem;
	fill: currentColor;
}

.social-link:hover,
.social-link:focus-visible {
	color: var(--wp--preset--color--ink);
}

@media (prefers-reduced-motion: no-preference) {
	.social-link {
		transition: color var(--wp--custom--motion--duration--fast) var(--wp--custom--motion--ease--out);
	}
}


/* ============================================================================================
 * ENQUIRY FORM - colour-aware.
 *
 * Measured 2026-09-03: this block was written for the homepage's reversed band and used the
 * `page` colour for every label, every typed character and every outline. On /contact/ the same
 * form sits on the light `band` ground, so all of it rendered white-on-#EFEFEF at 1.15:1 - the only
 * readable label on the page was the hidden honeypot's. Every colour below now comes from three
 * local tokens whose DEFAULT is dark-on-light; `.s-cta .enquiry` flips them for the black band.
 *
 * NOT `.has-reversed-background-color`: that class is never emitted on this site (verified by three
 * independent reviewers), so hooking it would leave the homepage form black-on-black. `.s-cta` is
 * the class that actually paints the reversed ground (see the .s-cta rule above).
 *
 * "(required)" is spelled out in each label rather than implied by an asterisk. An asterisk means
 * nothing to someone who has not been told what it means.
 * ============================================================================================ */

.enquiry {
	--enquiry-ink: var(--wp--preset--color--ink);
	--enquiry-rule: var(--wp--preset--color--ink-2);
	--enquiry-soft: var(--wp--preset--color--ink-2);
	margin-block-start: var(--wp--preset--spacing--40);
}

.s-cta .enquiry {
	--enquiry-ink: var(--wp--preset--color--page);
	--enquiry-rule: var(--wp--preset--color--hairline);
	--enquiry-soft: var(--wp--preset--color--hairline);
}

.enquiry__form {
	display: grid;
	gap: var(--wp--preset--spacing--30);
	max-inline-size: 46rem;
}

@media (min-width: 44rem) {
	.enquiry__form { grid-template-columns: 1fr 1fr; }
	/* Address and enquiry get the full width - a postal address wraps badly in half a column,
	   and a message box that narrow discourages the very thing we are asking for. */
	.enquiry__field--wide,
	.enquiry__actions,
	.enquiry__errors { grid-column: 1 / -1; }
}

.enquiry__field {
	display: grid;
	gap: 0.4rem;
	margin: 0;
}

.enquiry__field label {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--enquiry-ink);
}

.enquiry__required {
	text-transform: none;
	letter-spacing: 0;
	font-weight: 400;
	color: var(--enquiry-soft);
}

.enquiry__field input,
.enquiry__field textarea {
	inline-size: 100%;
	box-sizing: border-box;
	padding: 0.7rem 0.8rem;
	font-family: inherit;
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.45;
	color: var(--enquiry-ink);
	background: transparent;
	border: var(--wp--custom--rule--hairline) solid var(--enquiry-rule);
	border-radius: 0;
}

.enquiry__field textarea { resize: vertical; }

.enquiry__field input:focus-visible,
.enquiry__field textarea:focus-visible {
	outline: var(--wp--custom--rule--strong) solid var(--enquiry-ink);
	outline-offset: 2px;
	border-color: var(--enquiry-ink);
}

/* The error state carries NO colour - the message is the signal, the border is reinforcement. */
.enquiry__field input[aria-invalid="true"],
.enquiry__field textarea[aria-invalid="true"] {
	border-width: var(--wp--custom--rule--strong);
	border-color: var(--enquiry-ink);
}

.enquiry__error {
	font-size: var(--wp--preset--font-size--xs);
	color: var(--enquiry-ink);
	font-weight: 700;
}

.enquiry__errors {
	border: var(--wp--custom--rule--strong) solid var(--enquiry-ink);
	padding: var(--wp--preset--spacing--20);
	margin-block-end: var(--wp--preset--spacing--20);
}

.enquiry__errors p {
	margin-block: 0 0.5rem;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	color: var(--enquiry-ink);
}

.enquiry__errors ul {
	margin: 0;
	padding-inline-start: 1.1rem;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--enquiry-soft);
}

.enquiry__errors a { color: var(--enquiry-ink); }

.enquiry__actions { margin: 0; }

/* The button inverts against its ground: ink-on-page on the light page, page-on-ink on the band.
   Both pairs measure above 17:1. */
.enquiry__submit {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	letter-spacing: 0.04em;
	padding: 0.85em 1.8em;
	color: var(--wp--preset--color--page);
	background: var(--enquiry-ink);
	border: var(--wp--custom--rule--hairline) solid var(--enquiry-ink);
	border-radius: 0;
	cursor: pointer;
}

.s-cta .enquiry__submit {
	color: var(--wp--preset--color--ink);
}

.enquiry__submit:hover { opacity: 0.85; }

.enquiry__submit:focus-visible {
	outline: var(--wp--custom--rule--strong) solid var(--enquiry-ink);
	outline-offset: 3px;
}

.enquiry__done {
	border: var(--wp--custom--rule--strong) solid var(--enquiry-ink);
	padding: var(--wp--preset--spacing--30);
	margin: 0;
	max-inline-size: var(--wp--custom--measure);
	font-size: var(--wp--preset--font-size--base);
	color: var(--enquiry-ink);
}

/*
 * The honeypot. Moved OFF-SCREEN rather than display:none - some bots deliberately skip fields
 * that are display:none or type=hidden, which would defeat the whole point of it.
 */
.enquiry__trap {
	position: absolute;
	inline-size: 1px;
	block-size: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

@media (prefers-reduced-motion: no-preference) {
	.enquiry__submit,
	.enquiry__field input,
	.enquiry__field textarea {
		transition: border-color var(--wp--custom--motion--duration--fast) var(--wp--custom--motion--ease--out),
		            opacity var(--wp--custom--motion--duration--fast) var(--wp--custom--motion--ease--out);
	}
}


/* ============================================================================================
 * THE GROUND, THE GRID AND THE GLOW
 *
 * The visual language of his references, on his own colours. Fixed behind everything, decorative
 * only, and cheap: no filter: blur() anywhere - a full-viewport blurred layer is among the most
 * expensive things a browser can paint, and a radial-gradient is already soft.
 *
 * position: fixed is the trick. The content scrolls past a stationary glow, which reads as
 * parallax without a scroll handler, a JavaScript frame, or a single repaint we asked for.
 * ============================================================================================ */

.senzait-canvas {
	position: fixed;
	inset: 0;
	z-index: -1;
	pointer-events: none;   /* decoration must never intercept a click */
	overflow: hidden;
}

/*
 * The glow. accent-bright rather than accent: the deep accent is for type, and at 1.88:1 against
 * the reversed ground it is unreadable as text anyway - this is the variant that exists to bloom.
 *
 * Opacity on the ELEMENT, not alpha inside the gradient: identical result, and no support question
 * about color-mix() at all.
 */
.senzait-canvas__glow {
	position: absolute;
	inset: -20%;
	opacity: 0.22;
	/* ONE disc, the kaminaglik mechanism: a soft radial of the deep accent, top-centre, behind the
	   grid. A radial gradient is already soft, so still no filter: blur() - it is the single most
	   expensive paint a browser can do and this reads identically. Measured header sampling must
	   stay one colour across its width (audit item 5.5): the disc starts BELOW the header.
	   .senzait-canvas is position:fixed inset:0 (viewport box); this glow's inset:-20% expands that
	   box to 140% in each axis, so "at 50% 38%" resolves to 33.2% of viewport height, not 38% -
	   the brief's starting 52vmax reached ~295px up to the header at 1440x900 (measured: centre
	   sample read (219,223,233) vs edge (239,239,239), a real leak). Size cut instead of moving the
	   centre, to keep the "top-centre" placement described above: 30vmax at 1440x900 = 432px radius.
	   CORRECTED (review finding, 2026-09-04): this is an explicit two-value radial-gradient size
	   (`30vmax 30vmax`), so its percentage stops resolve against that 432px radius, not against the
	   viewport - and the "transparent 64%" stop lands at 0.64 x 432px = ~276px from centre, NOT at
	   432px as an earlier reading of this math wrongly assumed. The nearest header sample point sits
	   ~295px from centre, so the real safety margin is ~18-23px, not the ~130-137px previously
	   claimed here and in task-3-report.md/STATUS.md (both carry a correction line, not a rewrite).
	   Still enough to clear the header - five header crops read byte-identical, see below - but with
	   far less headroom than stated before. Do NOT nudge the centre (50% 38%) or the size (30vmax)
	   without re-running the header pixel proof (task-3-report.md); this margin does not forgive a
	   casual tweak.
	   Final values (redesign A task 3, measured 2026-09-04 at 1440x900): 30vmax at 50% 38% - five
	   header crops read byte-identical (239,239,239); see task-3-report.md for the pixel proof.
	   CORRECTED AGAIN (final-review finding 5.5, 2026-09-04): the ~18-23px margin above is real but
	   was only ever measured at 1440x900. At 16:9 viewports (1440x810, 1536x864, 1920x1080) the
	   viewport is shorter, "at ... 38%" resolves to a smaller absolute distance from the top, and the
	   proof failed there - the bloom showed through .site-header. Fixed at the SOURCE of the leak,
	   not by re-tuning this gradient again: .site-header now paints var(--wp--preset--color--band) as
	   its own background, so no amount of margin arithmetic here can leak through it at any viewport.
	   The pixel proof now holds at 1440x900 AND 1440x810 - see tools/header-pixels.js (reusable
	   Playwright MCP proof: five header crops + one below-header crop that must differ) and
	   task-3-report.md / STATUS.md / vps/kb/senzait/projects/website-redesign-a.md, each corrected. */
	background: radial-gradient(30vmax 30vmax at 50% 38%, var(--wp--preset--color--accent) 0%, transparent 64%);
}

/*
 * Reduced motion changes nothing here - nothing moves. But a visitor who has asked for calm should
 * not be given a saturated wash either, so it is quietened.
 */
@media (prefers-reduced-motion: reduce) {
	.senzait-canvas__glow { opacity: 0.08; }
}

/* The accent, used sparingly and only where it earns its place. */
a:not(.wp-block-button__link):not(.social-link):not(.skip-link) {
	color: var(--wp--preset--color--accent);
	text-underline-offset: 0.2em;
}

.s-ecosystem__link a { color: var(--wp--preset--color--accent); }

/*
 * On the reversed band the accent measures 1.88:1 - the theme's own comment calls it unreadable
 * as text, and the site-wide rule above was painting the only call-to-action on About, Services,
 * R&D, Consulting and Brands with it. Same exclusion list as the site-wide rule, so the two are
 * directly comparable and this one wins on specificity rather than !important. Buttons keep their
 * own colours.
 */
.s-cta a:not(.wp-block-button__link):not(.social-link):not(.skip-link) {
	color: var(--wp--preset--color--page);
}

:focus-visible {
	outline: var(--wp--custom--rule--strong) solid var(--wp--preset--color--accent);
	outline-offset: 3px;
}


/* ============================================================================================
 * THE MARK IS INK - wherever it finds itself.
 *
 * The mark is drawn with fill="currentColor", which is what lets it invert on the dark ground.
 * That also means it inherits ANY colour set on an ancestor - and the accent link rule turned the
 * wordmark #0F3BAF, because the mark lives inside the home link.
 *
 * Excluding the home link from that rule would have fixed today and broken the next time the mark
 * appears inside something coloured. THIS ELEMENT IS INK is the rule that actually holds.
 * ============================================================================================ */

.brand-mark {
	color: var(--wp--preset--color--ink);
}

/* On the reversed ground it must still flip, so the identity reads there too. */
.s-cta .brand-mark,
.has-reversed-background-color .brand-mark {
	color: var(--wp--preset--color--page);
}

/* The home link carries no accent either - it is the logo, not a link to be coloured. */
.site-header__home,
.site-header__home:hover {
	color: var(--wp--preset--color--ink);
}

/*
 * A photograph inside a section.
 *
 * Tokens copied from neighbouring rules, not invented — .s-taxonomy__lede was written once with a
 * class that had no CSS at all and rendered at pure black with no spacing, and nine hairline rules
 * before it referenced a variable name that was never emitted.
 *
 * margin-inline-start takes --senzait-col-inset for the same reason the lede does: .s-taxonomy is a
 * constrained container, so a child given a max-inline-size is centred by core's own
 * `margin-inline: auto !important`. Measured proof of getting this wrong is in the ALIGNMENT block
 * above: heading 169px, lede 62px.
 *
 * aspect-ratio is declared as well as width/height on the <img>, so the box is reserved even if a
 * stylesheet arrives before the image dimensions are parsed. CLS was 0 before this image and the
 * change is worthless if it does not stay 0.
 */
.s-figure {
	margin-block: var(--wp--preset--spacing--50) var(--wp--preset--spacing--50);
	margin-inline-start: var(--senzait-col-inset) !important;
	margin-inline-end: auto !important;
	max-inline-size: var(--wp--style--global--content-size);
}

.s-figure img {
	display: block;
	inline-size: 100%;
	block-size: auto;
	aspect-ratio: 3 / 2;
	object-fit: cover;
	border: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
}

/*
 * A photograph at the head of a taxonomy card.
 *
 * NO --senzait-col-inset here, and that is deliberate: a card is a GRID CELL that already begins on
 * the column, so this takes the .s-about treatment — start 0, end auto — rather than the .s-hero
 * one. Applying the inset inside a cell would push it in by the container's own offset, which is
 * the mirror image of the 107px error recorded in the ALIGNMENT block.
 *
 * aspect-ratio 3/2 crops the one square-ish source (cnc, 2400x2207) to match its neighbours, so the
 * three cards line up regardless of what was photographed.
 */
.s-card-figure {
	margin-block: 0 var(--wp--preset--spacing--30);
	margin-inline-start: 0 !important;
	margin-inline-end: auto !important;
}

.s-card-figure img {
	display: block;
	inline-size: 100%;
	block-size: auto;
	aspect-ratio: 3 / 2;
	object-fit: cover;
}

/*
 * The research diptych — two portrait photographs side by side.
 *
 * They are a pair because they MATCH: both sources are portrait at exactly 0.67, noted when they
 * were chosen. A single tall image in a wide column reads as a mistake; two of the same shape read
 * as a decision.
 *
 * Takes the --senzait-col-inset treatment, like .s-figure and for the same reason: a direct child
 * of a constrained section that is given a max-inline-size gets centred by core's own
 * `margin-inline: auto !important`.
 *
 * Stacks below 40rem. Two portraits side by side on a phone would be two thin columns of nothing.
 */
.s-diptych {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--wp--preset--spacing--30);
	margin-block: var(--wp--preset--spacing--40) var(--wp--preset--spacing--50);
	margin-inline-start: var(--senzait-col-inset) !important;
	margin-inline-end: auto !important;
	max-inline-size: var(--wp--style--global--content-size);
}

@media (min-width: 40rem) {
	.s-diptych { grid-template-columns: 1fr 1fr; }
}

.s-diptych__item { margin: 0; }

.s-diptych__item img {
	display: block;
	inline-size: 100%;
	block-size: auto;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	border: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
}

/*
 * The stage label on an innovation — "Patent pending" and the like.
 *
 * Styled as a small uppercase eyebrow, matching .entry__meta's treatment, because it occupies the
 * same slot a date would and should read as metadata rather than as a heading.
 */
.s-stage {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-2);
	margin-block: 0 var(--wp--preset--spacing--20);
}

.entry__standfirst {
	font-size: var(--wp--preset--font-size--md);
	line-height: 1.55;
	color: var(--wp--preset--color--ink-2);
}

/* ============================================================================================
 * ALIGNING THE DOCUMENT TEMPLATES to the content column.
 *
 * page.php, single.php, index.php, search.php and 404.php each render straight into <main>, and
 * nothing positioned what they render. `.entry__content`'s measure controls how WIDE the text is;
 * it says nothing about where it STARTS. So the privacy policy and terms began at 51px while the
 * column edge - and every other page - is at 169px, with the canvas hairline running vertically
 * through the text and the h1 spanning the full width of <main> above much narrower paragraphs.
 *
 * Reported by Vishal, and he was right. It was missed because the decision about these templates
 * was about LINE LENGTH, which was correct, and alignment was never checked.
 *
 * WRITTEN GENERALLY ON PURPOSE. A first attempt targeted `.entry--page, .entry--single` and fixed
 * three pages while leaving /blog/ misaligned, because index.php renders `post_class()` with no
 * class to hang a rule on. That was the third time in one day a rule was written for the case in
 * front of me and missed its siblings. `> *` covers whatever a template renders - article, bare
 * h1, pagination, empty state - without each needing to be named, and keeps working when a
 * template changes.
 *
 * EXCLUDES site-main--front, where the page is built from sections that position themselves and
 * would be inset twice.
 * ============================================================================================ */
.site-main:not(.site-main--front) > * {
	--senzait-col-inset: max(0px, (100% - var(--wp--style--global--content-size)) / 2);
	margin-inline-start: var(--senzait-col-inset);
	margin-inline-end: auto;
	max-inline-size: var(--wp--style--global--content-size);
}

/* ============================================================================================
 * POLICY DOCUMENTS (2026-09-04) - the Policies hub and the eleven documents beneath it.
 *
 * These pages use the document template above; everything here is the small amount that template
 * does not already give them. Three classes, all written by tools/md-to-blocks.py, none invented
 * here: s-doc__meta (the company / version / effective / approved-by line under the title),
 * s-doc__note (the draft-for-lawyer note on the two sets of terms) and s-doc__download (the link
 * to the page's own PDF).
 *
 * Tokens only, as everywhere else in this file - no literal colour, size or spacing.
 * ============================================================================================ */

/* The masthead line of every document. The display face, tracked and small, with a hairline under
   it: the same treatment .entry__meta already gives a post's byline, because it is the same kind of
   line - who issued this, in what version, from when. Not uppercased, unlike .entry__meta: this one
   carries a CIN, a version number and a date, and uppercasing a company identifier makes it harder
   to read back, not easier. */
.s-doc__meta {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.08em;
	color: var(--wp--preset--color--ink-2);
	padding-block-end: var(--wp--preset--spacing--20);
	border-block-end: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
}

/* The honest note on documents 09 and 10: these are contracts, and the Director has not yet had
   them reviewed by a lawyer. Italic and muted - present and readable, not shouted. */
.s-doc__note {
	font-style: italic;
	color: var(--wp--preset--color--ink-2);
}

/* The PDF link, added after the masthead line. Only spacing: the link itself takes the site's own
   link styling, which is what a link on this site looks like. */
.s-doc__download {
	margin-block-end: var(--wp--preset--spacing--40);
}

/* TABLES. There is exactly one today (the Vendor Acknowledgement's document register) and it has
   three columns, but the rule is written for any table in a document.

   THE CONSTRAINT THAT DECIDES THIS: the body must never scroll sideways. A table that is wider
   than the column has to be dealt with somewhere, and the only two places are the table (squash
   the columns until the text wraps to one word per line) or the page (push the whole layout wider
   and give every page a horizontal scrollbar). Neither is acceptable, so the overflow is taken by
   the figure WordPress already wraps every table in - it scrolls inside its own box and the page
   does not move. Measured at 390 after deploying, along with documentElement.scrollWidth. */
.entry__content .wp-block-table {
	overflow-x: auto;
	/* the scroll container must not itself be squashed by the flex/grid it sits in */
	max-width: 100%;
}
.entry__content .wp-block-table table {
	width: 100%;
	border-collapse: collapse;
}
.entry__content .wp-block-table th,
.entry__content .wp-block-table td {
	border: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	padding: var(--wp--preset--spacing--20);
	text-align: start;
	vertical-align: top;
}
.entry__content .wp-block-table th {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

/* ============================================================================================
 * PRINT - what makes the policy PDFs clean.
 *
 * Each of the eleven documents is rendered to a PDF from its own live page, so the PDF is the page
 * and can never drift from it. Everything that is site rather than document is removed here:
 * the header and its nav, the footer and its nav, the skip link, the fixed canvas and its glow,
 * the Complianz cookie banner and its manage-consent button, and the GTranslate switcher. Every
 * one of those selectors was read off the served HTML on 2026-09-04, not guessed.
 *
 * NO @page MARGIN IS SET, deliberately. The PDFs are produced with explicit margins
 * (18mm top and bottom, 16mm left and right); a margin declared here as well would be applied on
 * top of those and the text would sit in the middle of a very large white frame. The page SIZE is
 * declared so a browser printing the page by hand gets A4 rather than the visitor's locale default.
 *
 * The document template's own inset (.site-main:not(.site-main--front) > *) is undone: on paper
 * there is no column to align to, and the margin is the paper's.
 * ============================================================================================ */
@media print {
	@page {
		size: A4;
	}

	.site-header,
	.site-nav,
	.site-footer,
	.site-footer__nav,
	.skip-link,
	.senzait-canvas,
	.gtranslate_wrapper,
	#cmplz-cookiebanner-container,
	#cmplz-manage-consent,
	.cmplz-cookiebanner {
		display: none !important;
	}

	/* A link telling the reader to download the document they are already holding. */
	.s-doc__download {
		display: none !important;
	}

	/* Printing a blog article (single.php): the prev/next article nav (`.post-navigation`, core's
	   own class from the_post_navigation()) and the author note (`.entry__author`) are both
	   site-furniture-for-browsing, not part of the article itself - neither belongs on paper. */
	.post-navigation,
	.entry__author {
		display: none !important;
	}

	html,
	body {
		background: #fff !important;
		color: #000 !important;
	}

	.site-main,
	.site-main:not(.site-main--front) > *,
	.entry,
	.entry__content {
		max-width: none !important;
		max-inline-size: none !important;
		margin-inline: 0 !important;
		padding-inline: 0 !important;
		width: auto !important;
	}
	.site-main {
		padding-block: 0 !important;
		min-height: 0 !important;
	}

	.entry__content,
	.entry__title,
	.entry__content * {
		color: #000 !important;
	}

	/* Underline links rather than colour them: colour is the one thing a black-and-white print
	   cannot carry, and an un-underlined blue link prints as plain text. */
	.entry__content a {
		color: #000 !important;
		text-decoration: underline;
	}

	/* Keep a heading with the text it introduces, and never split a row of the register. */
	.entry__content h2,
	.entry__content h3 {
		break-after: avoid;
		page-break-after: avoid;
	}
	.entry__content tr,
	.entry__content li {
		break-inside: avoid;
		page-break-inside: avoid;
	}

	/* On paper the table has the full width of the page and nothing to scroll into. */
	.entry__content .wp-block-table {
		overflow-x: visible;
	}
}

/* ============================================================================================
 * THE NAVIGATION, first rendered 2026-09-02.
 *
 * The site went live on 2026-09-01 with no primary menu, so this nav had never appeared and its
 * styling had never been exercised. Two faults surfaced the moment it did.
 *
 * COLOUR. `.site-nav__list a` (0,1,1) sets ink and was being beaten by the site-wide accent rule
 * `a:not(.wp-block-button__link):not(.social-link):not(.skip-link)` (0,3,1). The exclusion list is
 * repeated here so the two rules are directly comparable and the next person can see why this one
 * wins - rather than reaching for !important, which hides the collision instead of resolving it.
 * The hover state already underlines in ink, so blue text was never the intent.
 * ============================================================================================ */
.site-nav__list a:not(.wp-block-button__link):not(.social-link):not(.skip-link) {
	color: var(--wp--preset--color--ink);
}

/* The current page is marked with the underline the hover state already draws. WordPress emits
   aria-current="page" on the matching item; the attribute is the thing that is true, so it is the
   selector - not .current-menu-item, which could drift from what is announced. */
.site-nav__list a[aria-current="page"],
.site-footer__list a[aria-current="page"] {
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

/* Sub-menu: hidden until the parent is hovered, focused within, or opened by the script. Items are
   44px tall - WCAG 2.5.8 target size. Depth stays 2 in site-header.php. */
.site-nav__list li { position: relative; }
.site-nav__list .sub-menu {
	position: absolute;
	inset-inline-start: 0;
	inset-block-start: 100%;
	min-inline-size: 14rem;
	margin: 0;
	padding: var(--wp--preset--spacing--10) 0;
	list-style: none;
	background: var(--wp--preset--color--page);
	border: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	display: none;
	z-index: 20;
}
.site-nav__list li:hover > .sub-menu,
.site-nav__list li:focus-within > .sub-menu,
.site-nav__list li.is-open > .sub-menu { display: block; }
.site-nav__list .sub-menu a {
	display: block;
	min-block-size: 44px;
	padding: 0.6rem var(--wp--preset--spacing--30);
	line-height: 1.6;
}
.site-nav__list .menu-item-has-children > a::after {
	content: "";
	display: inline-block;
	inline-size: 0.45em;
	block-size: 0.45em;
	margin-inline-start: 0.4em;
	border-inline-end: var(--wp--custom--rule--hairline) solid currentColor;
	border-block-end: var(--wp--custom--rule--hairline) solid currentColor;
	transform: translateY(-0.2em) rotate(45deg);
}

/* The phone menu button. Hidden above the stacking breakpoint; the list is hidden below it until
   the button is pressed. Without JavaScript the `js` class is absent and the list stays open. */
.site-nav__toggle {
	display: none;
	min-block-size: 44px;
	min-inline-size: 44px;
	padding: 0.5rem 0.9rem;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink);
	background: transparent;
	border: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--ink);
	border-radius: 0;
	cursor: pointer;
}

/* The masthead stacks on narrow screens. Above 56rem - a breakpoint the theme already uses - the
 * brand and the nav sit on one line as before. Below it they would fight: the brand loses width
 * until the tagline breaks into four lines, and the nav crushes against the right edge.
 *
 * Targets .site-header__row, not .site-header__inner: the flex row moved onto .site-header__row
 * when the header adopted core's constrained-layout column (see site-header.php / .site-header__col
 * above), so this is the box that actually needs to wrap. */
@media (max-width: 55.999rem) {
	.site-header__row {
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		gap: var(--wp--preset--spacing--20);
	}
	.site-nav__toggle { display: inline-block; margin-inline-start: auto; }
	/* Default (no JS): the list renders open and needs its own full-width line. With JS the closed
	   nav is taken out of flow (display:none), so the toggle - pushed right by margin-inline-start:auto
	   - sits flush against the column's right edge: brand left, MENU right. Before this the empty nav
	   was a third space-between item and stranded the toggle in the middle (owner caught it 2026-09-03).
	   The full-width list returns only in .is-open. */
	.site-nav { flex-basis: 100%; }
	.js .site-nav:not(.is-open) { display: none; }
	.js .site-nav.is-open { display: block; flex-basis: 100%; }
	.js .site-nav__list { display: none; }
	.js .site-nav.is-open .site-nav__list { display: flex; }
	.site-nav__list {
		flex-direction: column;
		gap: 0;
		padding-block: var(--wp--preset--spacing--10);
	}
	.site-nav__list > li > a {
		display: block;
		min-block-size: 44px;
		padding: 0.6rem 0;
		line-height: 1.6;
	}
	.site-nav__list .sub-menu {
		position: static;
		border: 0;
		padding-inline-start: var(--wp--preset--spacing--30);
	}
	.site-nav__list li:hover > .sub-menu { display: none; }
	.site-nav__list li.is-open > .sub-menu,
	.site-nav__list li:focus-within > .sub-menu { display: block; }

	/* Below the phone breakpoint the 253px tagline cannot share the 305px column with the 44px
	   toggle; the wordmark carries the brand here. Owner decision 2026-09-03. */
	.site-header__tagline { display: none; }
}


/* ============================================================================================
 * HERO VIDEO BACKGROUND (additive; front page only)
 *
 * inc/hero-video.php adds `s-hero--video` to the homepage hero group and prepends a background
 * <video> + a dark scrim as its first children. Nothing about the hero's TEXT changes: the title
 * and lede keep their DOM, their SEO, the Task-3b phone cap (min(display,13vw), NOT touched here)
 * and the Task-4 column alignment - --senzait-col-inset self-adjusts to the wider box, so the text
 * still lands on the same column as every other section (measured). Every rule below is scoped to
 * `--video`, so the black-ink hero on every OTHER page is left exactly as it was.
 * ============================================================================================ */

/* Full-bleed to the viewport edges. .s-cta stays contained on purpose - its comment above explains
   why (100vw includes the scrollbar and would reintroduce the horizontal scroll Phase 4 removed).
   The hero is the one place a background must reach the edges, so it DOES break out, and the
   overflow that creates is clipped on the body below - scoped by :has() to ONLY the page carrying
   the video, so no other page's scrolling changes. Measured: documentElement.scrollWidth ===
   clientWidth at 1440 and 390. */
.s-hero--video {
	position: relative;
	/* The hero stays in .site-main's content column (NOT full-bleed): its h1 / lede / buttons then
	   align with every section below at ALL widths. The full-width look comes from the media and scrim
	   breaking out behind the content (below), not from the hero box. A full-bleed hero box put the
	   content 18px (390) / 29px (768) LEFT of the sections - measured and fixed 2026-09-03. The video
	   still meets the header: this drops the hero's own top padding under --video. */
	padding-block-start: 0;
	/* A local stacking context so the media / scrim / content z-indexes cannot escape the hero. */
	isolation: isolate;
	/* The media/scrim reach the viewport edges from inside this normal-width hero, so overflow must
	   not clip them; the page-level overflow-x:clip below swallows the resulting 100vw sliver, and each
	   media layer clips its own contents. */
	overflow: visible;
}
/* The full-bleed media/scrim are a scrollbar's width wider than the content area, so they overflow the
   root by that sliver. Clip it at the ROOT (body alone does not stop the page-level scroll; the
   viewport's horizontal scroll is governed by the root element). overflow-y stays visible, so vertical
   scrolling is untouched. Scoped by :has() to ONLY the page carrying the video - no other page's
   scrolling changes. The decorative .senzait-canvas__glow is position:fixed and never contributed to
   scroll. Measured after this: documentElement.scrollWidth === clientWidth at 1440 / 768 / 390. */
html:has(.s-hero--video),
body:has(.s-hero--video) { overflow-x: clip; }

/* The video meets the header. .site-main carries a spacing--60 top padding (the gap below the
   masthead on every page); on the video-hero page that gap sits between the header and the top of
   the video, so it is removed HERE, scoped by :has() to only this page. The hero's own top padding
   is already zeroed above; both together let the video butt up to the header. */
.site-main:has(.s-hero--video) { padding-block-start: 0; }

/* The hero's own content (h1 / lede / actions) rides above the scrim. Scoped away from the two
   injected layers so it never fights their absolute positioning. */
.s-hero--video > :not(.s-hero__media):not(.s-hero__scrim) {
	position: relative;
	z-index: 2;
}

/* The two injected layers are DIRECT children of the constrained hero group, so WordPress's
   constrained layout caps them to content-size and centres them (max-width + auto margins). We beat
   that AND break them out to the full viewport width behind the content: max-width:none removes the
   cap, and `left:50%; margin-left:-50vw; width:100vw` reaches both viewport edges from inside the
   normal-width hero (top/bottom:0 keep them the hero's height). The h1 / lede / actions keep the
   constrained + column treatment untouched, so they stay on the page's content column. */
.s-hero--video .s-hero__media {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 50%;
	right: auto;
	width: 100vw;
	/* !important beats WordPress's constrained-layout `margin-left/right: auto !important`, which it
	   forces on every direct child of an is-layout-constrained group. Without it that auto silently
	   zeroed this negative margin and left the media shifted a half-viewport to the right. */
	margin-left: -50vw !important;
	z-index: 0;
	max-width: none;
	overflow: hidden;
	/* Poster paint target for phones / reduced motion; the URL arrives inline as --s-hero-poster. */
	background-position: center;
	background-size: cover;
	background-repeat: no-repeat;
}

.s-hero--video .s-hero__video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.s-hero--video .s-hero__scrim {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 50%;
	right: auto;
	width: 100vw;
	margin-left: -50vw !important; /* beats WP's constrained-layout margin:auto !important (see media above) */
	z-index: 1;
	max-width: none;
	/* Darkest at the left where the text sits, lightening across - the approved look. */
	background: linear-gradient(90deg, rgba(6, 14, 28, 0.8) 0%, rgba(6, 14, 28, 0.56) 52%, rgba(6, 14, 28, 0.32) 100%);
}

/* White ink over the dark video. Literal colours on purpose: this is reversed-on-video text, not a
   value the brand palette carries. Scoped to --video so no other hero's ink is touched. */
.s-hero--video .s-hero__title { color: #fff; }
.s-hero--video .s-hero__lede { color: #dfe4ee; }

/* The ink buttons vanish on the dark video - the outline one especially (ink border + ink text on a
   dark ground). Give them the reversed treatment the .s-cta dark band already uses: the solid button
   becomes a page-coloured (white) fill with ink text, the outline button gets a page-coloured edge and
   text. Scoped to --video, so every other page's buttons keep the ink defaults. */
.s-hero--video .wp-block-button.is-style-senzait-solid .wp-block-button__link {
	background: var(--wp--preset--color--page);
	color: var(--wp--preset--color--ink);
	border-color: var(--wp--preset--color--page);
}
.s-hero--video .wp-block-button.is-style-senzait-outline .wp-block-button__link {
	color: var(--wp--preset--color--page);
	border-color: var(--wp--preset--color--page);
}

/* Phones: never autoplay the ~3 MB file. Hide the <video> and paint the poster still instead. The
   55.999rem breakpoint is the theme's own mobile switch (the masthead collapses to a menu there). */
@media (max-width: 55.999rem) {
	.s-hero--video .s-hero__video { display: none; }
	.s-hero--video .s-hero__media { background-image: var(--s-hero-poster); }
}

/* Motion-sensitive visitors get the still at every width - the poster, never the moving video. */
@media (prefers-reduced-motion: reduce) {
	.s-hero--video .s-hero__video { display: none; }
	.s-hero--video .s-hero__media { background-image: var(--s-hero-poster); }
}

/* ============================================================================================
 * THE GRID (redesign A, 2026-09-04) - content in hairline-bounded cells, one sticky column.
 *
 * kaminaglik's mechanism, measured: 1px black rules BETWEEN real content cells, never decorative.
 * Here every rule is a cell edge by construction: each cell draws its own top and left edge, the
 * grid draws the outer right and bottom, and there is no other line anywhere.
 *
 * The grid is the theme's OWN (CSS grid on .s-grid). It is not a core layout variant,
 * so nothing WordPress emits for is-layout-* can restructure it. Cells stay constrained groups
 * inside; their content column is capped by core's content-size, which is wider than any cell,
 * so it clamps to the cell's padding edge (--senzait-col-inset resolves to 0 there).
 *
 * Columns: 1 (aside) + 2 (right sub-columns). 'wide' spans both right columns; 'half' takes one;
 * 'full' spans all three (hero, contact band). Below 64rem: one column, aside static, lines become
 * separators. No horizontal scroll: the root already clips (hero video block).
 *
 * Cell titles override the SECTIONS block's xl h2 size to xxl below (larger cell titles), by
 * specificity, on the front page only - the xl rule stays live for the Sections template.
 *
 * The hook is `.s-grid`, printed ONLY by front-page.php. It was `.site-main--front` until
 * 2026-09-04 - a name that reads as front-only but is shared by template-sections.php (every
 * content page) and archive-senzait_innovation.php, so the grid ran on all eight section pages:
 * their hero fell into the 324px first column and the 112px h1 overflowed it by 173-419px at
 * 1920x1040 (measured on every one of the eight, tools/measure-pages.js). A class name is not a
 * scope: the selector's reach was never pre-checked against the other templates. grid.php's
 * is_front_page() gate was always front-only, so the cells never existed elsewhere - only the
 * container styling leaked. verify.sh now fails if `s-grid` is printed on any page but the front.
 * ============================================================================================ */

.s-grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr) minmax(0, 1.5fr);
	grid-auto-rows: auto;
	grid-auto-flow: row dense;
	align-items: stretch;
	column-gap: 0;
	row-gap: 0;
	border-inline-end: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	border-block-end: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	padding-block-start: 0;
	/* Measured 2026-09-04 at 1440 via Playwright MCP: .site-header getBoundingClientRect().height
	   was 77.78125px; rounded up so the sticky aside never sits under the header. */
	--senzait-header-height: 78px;
}

.s-grid > .s-cell {
	background: transparent;                 /* the bloom shows through every cell */
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	border-inline-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	padding-block: var(--wp--preset--spacing--50);
	min-width: 0;
}

.s-cell--full  { grid-column: 1 / -1; }
.s-cell--wide  { grid-column: 2 / -1; }
.s-cell--half  { grid-column: span 1; }

/* The hero is the first full cell and keeps its own geometry (video breaks out behind it). */
.s-grid > .s-hero.s-cell {
	border-inline-start: 0;
	border-block-start: 0;
	padding-block: 0;
}

/* The contact band is the last full cell. Three things are settled here, each measured 2026-09-04
   at 1920x1040 after Vishal's morning screenshots (tools/measure-pages.js is the check):
   1. Ground: the generic `.s-grid > .s-cell { background: transparent }` above (0,2,0) beat
      `.s-cta` (0,1,0), so the band lost its reversed ground and its white title, labels and inputs
      sat on the light page - unreadable. The ground is restated here at (0,3,0).
   2. No top or left hairline - a light rule on a dark band reads as a stray mistake, not a cell
      edge (review finding); and no top margin inside the grid, so the band butts against the last
      row of cells like every other cell edge (.s-cta's own margin left a 64px gap the sticky
      column showed through).
   3. Layering: the sticky column is a grid item placed in rows 2-41, but Chromium constrains a
      sticky grid item to the grid CONTAINER, not to its grid area - measured: with the page
      scrolled to its end the column's bottom edge (256px) sat exactly on the grid's bottom edge,
      inside the band, its links printed over the form. Rather than depend on browser-specific
      containment, the band paints above the column: position + z-index make a stacking context and
      the column slides under the dark ground, out of sight, as the band arrives. The only other
      z-index in this stacking context is the fixed canvas at -1 (the header is not positioned), so
      1 is enough. */
.s-grid > .s-cta.s-cell {
	background: var(--wp--preset--color--reversed);
	border-block-start: 0;
	border-inline-start: 0;
	margin-block-start: 0;
	position: relative;
	z-index: 1;
}

/* Cells that used to paint their own ground now sit on the shared one. */
.s-grid > .s-tags.s-cell { background: transparent; }

/* The sticky column. Spans every row between the hero and the contact band. */
.s-side {
	grid-column: 1;
	grid-row: 2 / span 40;                    /* larger than the number of cells (currently 11 non-hero
	                                              sections, review finding count check 2026-09-04); the
	                                              grid ignores the excess */
	align-self: start;
	position: sticky;
	top: calc(var(--senzait-header-height, 5rem) + var(--wp--preset--spacing--30));
	border-block-start: var(--wp--custom--rule--hairline) solid var(--wp--preset--color--hairline);
	padding: var(--wp--preset--spacing--50) var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--ink);
}

.s-side__tagline {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--sm);
	letter-spacing: var(--wp--custom--tagline-tracking);
	text-transform: uppercase;
	margin-block: 0 var(--wp--preset--spacing--40);
}

.s-side__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--wp--preset--spacing--10);
}

.s-side__list a {
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--ink);
	text-decoration: none;
	border-block-end: var(--wp--custom--rule--hairline) solid transparent;
}
.s-side__list a:hover,
.s-side__list a:focus-visible { border-block-end-color: var(--wp--preset--color--ink); }

/* Cell titles: one face, large - the identity is the type. Scoped (review finding): the second
   selector used to be an unrestricted descendant match (`.s-cell h2...`), which would also catch
   any h2 a future content edit nests deeper inside a cell's body (a card, a diptych item). It is
   narrowed to one level - a direct child of a wp-block-group that is itself a direct child of the
   cell - which is the only nesting this rule needs to defend against; the first selector already
   covers every current section (pre-checked live: all 11 h2 titles on the front page are direct
   children of their <section>.s-cell, so this selector matches nothing extra today - same 11
   headings styled, before and after). */
.s-grid > .s-cell > h2,
.s-grid > .s-cell > .wp-block-group > h2:first-of-type {
	font-size: var(--wp--preset--font-size--xxl);
	line-height: 1.05;
	letter-spacing: -0.015em;
	text-wrap: balance;
}

/* Tablet and phone: one column. The aside becomes a band above the cells; rules become separators.
   Hero-before-aside (review finding, 2026-09-04): front-page.php prints <aside class="s-side"> as the
   FIRST child of <main>, before the_content() renders the sections - so the aside is before the hero
   in source order. The desktop grid never exposes this, since .s-side is explicitly placed by
   grid-row/column there. Collapsed to one column here with no order set, that source order would put
   the aside ABOVE the hero and h1 - the least important part of the page first. order: 0/1/2 puts
   the hero first, the aside second, and every
   other cell keeps its existing DOM order relative to the others (order: 2, same value = no reorder
   among them) so nothing but the hero/aside swap changes. Measured at 390 and 768: hero top < aside
   top. */
@media (max-width: 63.999rem) {
	.s-grid { grid-template-columns: minmax(0, 1fr); border-inline-end: 0; }
	.s-cell--full, .s-cell--wide, .s-cell--half { grid-column: 1; }
	.s-side { grid-column: 1; grid-row: auto; position: static; padding-block: var(--wp--preset--spacing--40); order: 1; }
	.s-grid > .s-cell { border-inline-start: 0; order: 2; }
	.s-grid > .s-hero.s-cell { order: 0; }
}

/* On hover the accent blooms behind that cell only - opacity on a pseudo-element, transform-free,
   fine pointers only, off under reduced motion. Contrast: body text in a hovered cell sits on
   accent #0F3BAF blended at 0.18 over band #EFEFEF; measured ratio in task-3-report.md stays
   >= 4.5 against ink #000. */
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
	.s-grid > .s-cell { position: relative; isolation: isolate; }
	.s-grid > .s-cell::before {
		content: "";
		position: absolute;
		inset: -10%;
		z-index: -1;
		pointer-events: none;
		opacity: 0;
		background: radial-gradient(60% 60% at 50% 50%, var(--wp--preset--color--accent) 0%, transparent 70%);
		transition: opacity var(--wp--custom--motion--duration--slow) var(--wp--custom--motion--ease--out);
	}
	.s-grid > .s-cell:hover::before { opacity: 0.18; }
	.s-grid > .s-hero.s-cell::before,
	.s-grid > .s-cta.s-cell::before { content: none; }
}

/* Hero marks - containing block (review fix, 2026-09-04): the marks SVG below is position:absolute,
   which resolves against the nearest *positioned* ancestor. Before this rule that ancestor was
   supplied only by two UNRELATED rules: .s-hero--video's own `position:relative` (added only when
   the owner has the background video ON in the Customizer - a toggle, not a constant; isolation:isolate
   is co-located but creates stacking context, not a containing block) and the hover-bloom rule above,
   `.s-grid > .s-cell { position:relative; isolation:isolate; }`, whose `position:relative`
   supplies the containing block (the `isolation` is a stacking-context detail). This rule is itself gated
   to `@media (pointer:fine) and (prefers-reduced-motion:no-preference)` and so is absent on a coarse
   (touch) pointer or when the visitor has asked for reduced motion. With the video OFF and either a coarse
   pointer or reduced motion, NEITHER positioned ancestor is present: the marks fall back to the initial
   containing block and render near the top of the page, off the hero entirely. This rule gives the hero cell
   an unconditional containing block of its own, independent of both the video toggle and the hover media
   query, so the marks always resolve inside the hero. */
.s-grid > .s-hero.s-cell { position: relative; }

/* Media inside cells (Task 2). Galleries are core Gallery blocks; brand figures sit under their h3.
   No prefers-reduced-motion rule: the cell video never autoplays (controls + preload="none"), so a
   reduced-motion visitor sees only the poster until they choose to press play. */
.s-cell .s-gallery { margin-block: 0 var(--wp--preset--spacing--40); }
.s-cell .s-gallery img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 3 / 2; }
.s-cell .s-brand-figure { margin-block: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40); }
.s-cell .s-brand-figure img { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; }
.s-cell .s-cell-video { margin-block: 0 var(--wp--preset--spacing--40); }
.s-cell .s-cell-video video { width: 100%; height: auto; aspect-ratio: 16 / 9; background: var(--wp--preset--color--reversed); }

/* The GTranslate language chip on phones (2026-09-21). At 390 px it measured 122 x 45 px, fixed
   20 px from the corner: a third of the screen's width, sitting on whatever scrolls beneath it -
   the photo credit under an article's picture, in the screenshot that raised it. The plugin writes
   its sizes in a runtime <style> (font 20px, flag 33px, padding 10px 15px) and its position INLINE
   on #gt_float_wrapper, so the rules below out-rank the first by one class and the second needs
   !important. Desktop is untouched. Theme CSS - not the plugin's files, not its Custom CSS box -
   so it lives in the kit and survives the plugin's updates. The chip stays above the 24 px
   target-size minimum (WCAG 2.2, 2.5.8). */
@media (max-width: 40rem) {
	#gt_float_wrapper { bottom: 10px !important; left: 10px !important; }
	.gtranslate_wrapper .gt_float_switcher { font-size: 13px; line-height: 16px; }
	.gtranslate_wrapper .gt_float_switcher img { width: 20px; margin-inline-end: 4px; }
	.gtranslate_wrapper .gt_float_switcher .gt-selected .gt-current-lang { padding: 6px 8px; }
	.gtranslate_wrapper .gt_float_switcher .gt_options a { padding: 8px 10px; }
	.gtranslate_wrapper .gt_float_switcher .gt-selected .gt-current-lang span.gt_float_switcher-arrow { height: 16px; width: 12px; }
}
