/**
 * Base styles: reset, element defaults, the two allowed utilities,
 * focus visibility, reduced-motion stub. No component styles here.
 */

/* --------------------------------------------------------------------
 * Reset (modern minimal)
 * -------------------------------------------------------------------- */

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

* {
	margin: 0;
}

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

img,
picture,
video,
canvas,
svg {
	display: block;
	max-width: 100%;
}

input,
button,
textarea,
select {
	font: inherit;
}

/* --------------------------------------------------------------------
 * Element defaults
 * -------------------------------------------------------------------- */

body {
	font-family: var(--font-body);
	font-size: var(--type-body-size);
	line-height: var(--type-body-line);
	font-weight: var(--type-body-weight);
	color: var(--color-body-text);
	background-color: var(--color-page-bg);
	-webkit-font-smoothing: antialiased;
}

/* The display token is intentionally not bound to an element; it is applied
   per section (hero only, once per page). */

h1,
h2,
h3,
h4 {
	font-family: var(--font-heading);
	color: var(--color-heading-text);
	overflow-wrap: break-word;
}

h1 {
	font-size: var(--type-h1-size);
	line-height: var(--type-h1-line);
	letter-spacing: var(--type-h1-spacing);
	font-weight: var(--type-h1-weight);
}

h2 {
	font-size: var(--type-h2-size);
	line-height: var(--type-h2-line);
	letter-spacing: var(--type-h2-spacing);
	font-weight: var(--type-h2-weight);
}

h3 {
	font-size: var(--type-h3-size);
	line-height: var(--type-h3-line);
	letter-spacing: var(--type-h3-spacing);
	font-weight: var(--type-h3-weight);
}

h4 {
	font-size: var(--type-h4-size);
	line-height: var(--type-h4-line);
	letter-spacing: var(--type-h4-spacing);
	font-weight: var(--type-h4-weight);
}

a {
	color: var(--color-link-text);
}

a:hover {
	color: var(--color-link-text-hover);
}

/* --------------------------------------------------------------------
 * Layout primitive
 * -------------------------------------------------------------------- */

/* Centered content column: 1312px inside the 1440 design, 64px side pads. */
.container {
	width: min(var(--container-max), 100% - 2 * var(--container-pad));
	margin-inline: auto;
}

/* --------------------------------------------------------------------
 * Band: the boxed full-bleed frame shared by the hero and Vision
 * -------------------------------------------------------------------- */

/* Inset --page-inset from the viewport sides, capped at --band-max and
   centered on ultrawide. The dark fallback sits under the media layer. */
.band {
	position: relative;
	width: min(var(--band-max), 100% - 2 * var(--page-inset));
	margin-inline: auto;
	border-radius: 6px;
	overflow: hidden;
	background-color: var(--color-surface-darkest);
}

/* Media layer slot: cover photo plus a per-section gradient overlay. */
.band__media {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.band__overlay {
	position: absolute;
	inset: 0;
}

/* Ruling: band content sits on the GLOBAL 1312 grid. Inside the inset band a
   plain .container computes against the band width and lands 6px off-grid;
   compensating the pads by the page inset puts it back on the global grid at
   every viewport width. */
.band .container {
	width: min(var(--container-max), 100% - 2 * (var(--container-pad) - var(--page-inset)));
}

/* Below the layout switch there is no global grid to line up with: band
   padding is specified directly, so the compensation is switched off and a
   band container behaves like any other. Sections that want a different
   inset (hero and vision use 32px) set it themselves. */
@media (max-width: 1024px) {
	.band .container {
		width: min(var(--container-max), 100% - 2 * var(--container-pad));
	}
}

/* Page body content: the section that holds an editor-written body, and the
   page title when there is no hero to carry it. The body's own styling is
   .entry-content in components/entry-content.css; this is only the section
   rhythm around it. */
.page-content {
	padding-block: var(--section-pad-y);
}

/* The document column: the title and the body sharing one measure and one left
   edge, centred in the container.

   75ch HERE, WHERE .prose IS 65ch, AND THE DIFFERENCE IS DELIBERATE. A measure
   is chosen against the column it sits in, not in the abstract. .prose's 65ch
   was set on the job detail page, where the text runs beside a sticky facts
   panel and has 928px of a 1312px container to live in; at 75ch it would crowd
   that panel. This page has the container to itself and nothing beside the
   text, so the line can run longer before the return sweep gets hard to track.
   Widening .prose itself would silently re-measure the job page, which is a
   decision nobody has made - so this overrides, and only here.

   THE MEASURE IS ON THIS WRAPPER RATHER THAN ON ITS CHILDREN because ch is
   font-relative. 75ch on the h1 would be measured in 56px Archivo, resolve to
   about 2250px, and so cap nothing and centre nothing; here it is measured in
   the 16px body font and resolves to 720px.

   The TEXT is not centred and must not be. A legal document is read
   left-aligned; centred prose gives the eye a different starting point on
   every line, which is worse the longer the document gets. Only the column
   moves. */
.page-content__column {
	max-width: 75ch;
	margin-inline: auto;
}

/* The column owns the measure inside it. .entry-content carries its own 65ch
   for the templates that render editor content WITHOUT this wrapper, and as
   the inner box that cap would win here - the column would widen to 720px
   while every line stayed at 624px, which looks like nothing happened. */
.page-content__column .entry-content {
	max-width: none;
}

/* The page title, standing in for the hero's h1 on a page that has no hero.
   Full h1 token: it is the same thing the hero renders, so it is the same
   size, and a legal page reads as part of the site rather than as an
   afterthought.

   THE GAP BELOW IS THIS RULE'S JOB and nothing else's, which is why it is
   declared rather than left to the content: the title sits OUTSIDE
   .entry-content, so the editor rhythm never reaches it, and the first child
   inside correctly resets its own top margin. With neither side providing the
   space the two would touch. 64px is the header-to-content step, and it is the
   same whether the body opens on a heading, a paragraph or a list. */
.page-content__title {
	margin: 0 0 64px;
	font-size: var(--type-h1-size);
	line-height: var(--type-h1-line);
	letter-spacing: var(--type-h1-spacing);
	font-weight: var(--type-h1-weight);
	color: var(--color-heading-text);
}

@media (max-width: 1024px) {
	.page-content__title {
		margin-bottom: 48px; /* the stacking step, as everywhere else at this width */
	}
}

/* --------------------------------------------------------------------
 * Shared components
 * -------------------------------------------------------------------- */

/* Eyebrow: accent slash + mono label (composed with .type-label in markup).
   Default is the light-background variant; use --on-dark on dark sections. */
.eyebrow {
	display: flex;
	align-items: center;
	gap: 12px;
	color: var(--color-text-secondary);
}

.eyebrow--on-dark {
	color: var(--color-body-text-on-dark);
}

/* 25px accent line rotated -60deg, pure CSS. The negative inline margins
   shrink its layout footprint to the rotated width (25px * cos 60 = 12.5px)
   so the 12px gap measures from the visible slash, as in the nodes. */
.eyebrow::before {
	content: "";
	flex-shrink: 0;
	width: 25px;
	height: 1px;
	margin-inline: -6.25px;
	background-color: var(--color-accent);
	transform: rotate(-60deg);
}

/* Primary button: 14px/600 per designer ruling, markup via
   template-parts/components/button-primary.php. Renders as <a> or <button>;
   the border/appearance resets are for the native button case. */
.btn-primary {
	display: inline-flex;
	align-items: center;
	gap: 16px;
	padding: 12px 22px;
	border: 0;
	appearance: none;
	-webkit-appearance: none;
	cursor: pointer;
	background-color: var(--color-button-primary-bg);
	color: var(--color-button-primary-text);
	text-decoration: none;
	font-family: var(--font-body);
	font-size: var(--type-btn-primary-size);
	line-height: var(--type-btn-primary-line);
	letter-spacing: var(--type-btn-primary-spacing);
	font-weight: var(--type-btn-primary-weight);
	/* --on is the activation switch (hover or keyboard focus), set only in the
	   gated blocks below. Every piece of the choreography reads it, so the
	   whole button has one source of truth and reduced-motion / touch simply
	   never flip it. The box itself stays 1:1 on hover: only the letters, the
	   arrow and the background move. Press is the one exception below. */
	--on: 0;
	/* The button's surface IS the accent, so its ring is the label colour. */
	--focus-ring: var(--color-button-primary-text);
	transition:
		background-color 600ms var(--motion-ease-interaction),
		transform 600ms var(--motion-ease-interaction);
}

.btn-primary:hover {
	background-color: var(--color-button-primary-bg-hover);
	color: var(--color-button-primary-text);
}

.btn-primary svg {
	flex-shrink: 0;
	display: block;
}

/* Label: an unsplit copy holds the box (kerned, exactly as before splitting)
   and the letters overlay it, so the button can never change size. */
.btn-primary__label {
	position: relative;
	display: block;
	white-space: nowrap;
}

.btn-primary__label-box {
	visibility: hidden;
}

.btn-primary__letters {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: flex-start;
}

/* One clip mask per letter. Its height is the natural line box, which at
   14px/normal leaves 14.25px above the baseline against the tallest Slovak
   ink (Z-caron at 13.43px) and 4.25px below against the deepest descender
   (g at 2.97px), so nothing is clipped in either copy. The second copy sits
   exactly one mask below and both travel one mask up. */
.btn-letter {
	position: relative;
	display: block;
	overflow: hidden;
}

.btn-letter__copy {
	display: block;
	transform: translateY(calc(var(--on) * -100%));
	transition: transform 600ms var(--motion-ease-interaction);
	transition-delay: calc(var(--i, 0) * 15ms);
}

.btn-letter__copy--b {
	position: absolute;
	top: 100%;
	left: 0;
}

/* --------------------------------------------------------------------
 * Responsive: measure cap on running text
 * -------------------------------------------------------------------- */

/* Section intros, descriptions and body copy hold a readable measure once
   the columns collapse. Headlines set their own widths (several are
   copy-tuned), card body copy is already narrow, and media stays full
   width, so none of them are listed here. */
@media (max-width: 1024px) {
	.hero__intro,
	.social-proof__description,
	.what-we-do__text,
	.vision__text,
	.services__text,
	.deliver__text,
	.contact__text,
	.site-footer__tagline-text {
		max-width: 65ch;
	}
}

/* --------------------------------------------------------------------
 * Group fade-in (shared: stat descriptions, chips, deliver shorts, pillars)
 * -------------------------------------------------------------------- */

/* Marked with [data-fade] and grouped by [data-fade-group]; fade-in.js adds
   the hidden class, so without JS the content is simply visible. Transform
   and opacity only, so nothing reflows. */
.is-fade {
	opacity: 0;
	transform: translateY(12px);
	transition:
		opacity 500ms var(--motion-ease),
		transform 500ms var(--motion-ease);
	transition-delay: calc(var(--fade-index, 0) * 60ms);
	will-change: opacity, transform;
}

.is-fade.is-faded-in {
	opacity: 1;
	transform: translateY(0);
}

.is-fade.is-fade-done {
	will-change: auto;
}

/* --------------------------------------------------------------------
 * Arrow loop (shared: primary button, vision band, subhead text links)
 * -------------------------------------------------------------------- */

/* One square mask, two identical copies: the resting one leaves up-right
   while its twin arrives from bottom-left. Markup via
   template-parts/components/arrow-loop.php.

   The loop holds no state. It reads --on from its host (0 at rest, 1 when
   that host is hovered or focused), so every host drives it the same way,
   and --i shifts its delay (the primary button lands it on the last letter).

   Travel is in mask units and has to be far more than the 1 unit that merely
   parks each copy outside: at 1 unit the two sit edge to edge on the diagonal
   and hand off with no gap, which reads as one shape sliding past rather than
   a swap. 4.8 units empties the mask for ~15% of the duration (outgoing gone
   at ~29%, incoming reaching the edge at ~46%). Those percentages are of
   TIME, not of the value range: the interaction curve is very fast through
   its middle, so a gap that looks generous in progress terms flashes past. */
.arrow-loop {
	--arrow-travel: 4.8;
	/* Direction, as multipliers rather than a second set of transforms. The
	   default is the original diagonal - out to the top right, in from the
	   bottom left - and --horizontal only zeroes the Y. */
	--arrow-travel-x: 1;
	--arrow-travel-y: -1;
	position: relative;
	display: block;
	width: var(--arrow-loop-size, 10px);
	height: var(--arrow-loop-size, 10px);
	overflow: hidden;
	flex-shrink: 0;
}

.arrow-loop svg {
	display: block;
}

.arrow-loop__copy {
	position: absolute;
	inset: 0;
	display: block;
	transition: transform 600ms var(--motion-ease-interaction);
	transition-delay: calc(var(--i, 0) * 15ms);
}

.arrow-loop__copy--a {
	transform: translate(
		calc(var(--on, 0) * var(--arrow-travel) * var(--arrow-travel-x) * 100%),
		calc(var(--on, 0) * var(--arrow-travel) * var(--arrow-travel-y) * 100%)
	);
}

.arrow-loop__copy--b {
	transform: translate(
		calc((1 - var(--on, 0)) * var(--arrow-travel) * var(--arrow-travel-x) * -100%),
		calc((1 - var(--on, 0)) * var(--arrow-travel) * var(--arrow-travel-y) * -100%)
	);
}

/* Travels straight out to the right and back in from the left. Nothing else
   changes: same mask, same two copies, same duration, same easing, same --on
   switch. */
.arrow-loop--horizontal {
	--arrow-travel-y: 0;
}

/* --------------------------------------------------------------------
 * Pending state (a submitting button)
 *
 * The spinner is rendered beside the arrow and revealed here, rather than
 * being swapped in by script. .btn-primary is not a plain button: its label is
 * a set of per-letter clip masks and its arrow is a two-copy loop driven by
 * --on, so inserting or removing nodes mid-animation is how you tear the split
 * or strand the loop halfway through its travel.
 *
 * --on: 0 is the important half. A button that was hovered when it began
 * submitting has its loop mid-flight, and a disabled button stops matching
 * :hover, so without this the arrow would freeze wherever it happened to be
 * and still be there when the button came back. Forcing it to rest parks both
 * copies before the arrow is hidden, and the loop returns at rest.
 * -------------------------------------------------------------------- */

.btn-primary.is-pending {
	--on: 0;
	/* Genuinely disabled in the markup as well; this is only the look of it. */
	opacity: 0.65;
	cursor: default;
}

.btn-primary.is-pending .arrow-loop {
	display: none;
}

.btn-primary__spinner {
	display: none;
	flex-shrink: 0;
	/* The arrow's own box, so swapping one for the other cannot change the
	   button's width and make the row jump. */
	width: 10px;
	height: 10px;
	align-items: center;
	justify-content: center;
}

.btn-primary.is-pending .btn-primary__spinner {
	display: inline-flex;
}

.btn-primary.is-pending .btn-primary__spinner svg {
	animation: dicit-spin 800ms linear infinite;
}

@keyframes dicit-spin {
	to {
		transform: rotate(360deg);
	}
}

/* No spin. The spinner still appears and the button is still visibly disabled,
   so the pending state is conveyed without the motion. */
@media (prefers-reduced-motion: reduce) {
	.btn-primary.is-pending .btn-primary__spinner svg {
		animation: none;
	}
}

/* Keyboard focus runs the same choreography as hover. */
@media (prefers-reduced-motion: no-preference) {
	.btn-primary:focus-visible {
		--on: 1;
	}

	/* Press is the only thing that scales the box, as click feedback. */
	.btn-primary:active {
		transform: scale(0.96);
		transition-duration: 100ms;
	}

	.btn-primary:hover .btn-letter__copy,
	.btn-primary:hover .arrow-loop__copy,
	.btn-primary:focus-visible .btn-letter__copy,
	.btn-primary:focus-visible .arrow-loop__copy {
		will-change: transform;
	}
}

/* Hover only where hovering is real, so a tap never leaves it stuck on. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
	.btn-primary:hover {
		--on: 1;
	}
}

/* Icon chip: 35px square, radius 6. Glass by default (sits on photos: vision
   link, service card CTA); --light variant for light backgrounds (carousel
   nav: border-subtle fill, body-text arrow, per the node assets). Arrows run
   on currentColor. */
/* Both sizes are published at :root, the way header.css publishes
   --header-bar-space, so a layout that has to reserve room for chips can
   derive the number instead of copying it. The documents table's actions
   column is two chips and a gap, and it has to know which two. */
:root {
	--icon-chip-size: 35px;
	/* The touch size, for a chip that is a primary pointer target rather than
	   decoration. One number, read by every consumer. */
	--icon-chip-size-touch: 44px;
}

.icon-chip {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--icon-chip-size);
	height: var(--icon-chip-size);
	padding: 0;
	border: 0;
	border-radius: 6px;
	background-color: rgba(247, 248, 250, 0.2); /* page-bg at .2 per the asset, no token */
	-webkit-backdrop-filter: blur(8px);
	backdrop-filter: blur(8px);
	color: var(--color-button-primary-text);
	transition: background-color 150ms ease, color 150ms ease;
	flex-shrink: 0;

	/* Colour of a masked glyph inside the chip; see .icon-chip__glyph.
	   Overridden per placement, which is what makes recolouring a whole
	   set a one-line change. */
	--icon-chip-glyph: currentColor;
}

.icon-chip--light {
	background-color: var(--color-border-subtle);
	-webkit-backdrop-filter: none;
	backdrop-filter: none;
	color: var(--color-body-text);
}

/* White plate, for a chip sitting ON a card rather than on the page. The
   --light fill is border-subtle, which against surface-card is grey on grey
   and gives the glyph nothing to sit on. */
.icon-chip--white {
	background-color: var(--color-surface-white);
	-webkit-backdrop-filter: none;
	backdrop-filter: none;
	color: var(--color-body-text);
}

/* 56px plate for the services page "What we deliver" boxes. Overrides the
   size PROPERTY rather than width and height, so every size in the component
   is expressed the same way and none of them can half-win against another. */
.icon-chip--lg {
	--icon-chip-size: 56px;
}

/* Opt-in touch size. For an icon-only control that is a real pointer target
   rather than decoration: the carousel arrows, and the documents table's view
   and download links. Both are the same case, two adjacent icon-only actions
   with no text to aim at, which is what the 44px exists for.
 *
 * A modifier rather than a rule scoped per placement, so the number lives in
 * exactly one declaration and a new consumer opts in by saying what it is. */
@media (max-width: 1024px) {
	.icon-chip--touch {
		--icon-chip-size: var(--icon-chip-size-touch);
	}
}

/* Masked glyph: an uploaded SVG used as an alpha mask over a flat fill, the
   same technique as the client and partner logos. Only the file's alpha
   matters, so whatever colour happens to be baked into the download is
   irrelevant and the glyph is fully CSS-controlled; an <img> could not be
   recoloured at all. mask-size: contain fits the whole artwork inside the
   box, so nothing can be cropped whatever the chip size.

   The mask URL is per instance and set inline. The colour comes from
   --icon-chip-glyph, declared on the chip so a placement can recolour a
   whole set from one line. */
.icon-chip__glyph {
	display: block;
	background-color: var(--icon-chip-glyph);
	-webkit-mask-image: var(--mask);
	mask-image: var(--mask);
	mask-mode: alpha;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-size: contain;
	mask-size: contain;
	-webkit-mask-position: center;
	mask-position: center;
}

.icon-chip--accent {
	background-color: var(--color-accent);
	-webkit-backdrop-filter: none;
	backdrop-filter: none;
	color: var(--color-button-primary-text);
}

/* Accent fill when the parent link is hovered or focused, or when the chip
   itself is an interactive button. aria-disabled chips stay inert and dimmed. */
a:hover .icon-chip,
a:focus-visible .icon-chip,
button.icon-chip:not([aria-disabled="true"]):hover,
button.icon-chip:focus-visible {
	background-color: var(--color-accent);
	color: var(--color-button-primary-text);
}

button.icon-chip {
	cursor: pointer;
}

button.icon-chip[aria-disabled="true"] {
	opacity: 0.4;
	cursor: default;
}

/* --------------------------------------------------------------------
 * Utilities (only these two for now)
 * -------------------------------------------------------------------- */

/* Mono eyebrow above section headlines ("OUR SERVICES") */
.type-label {
	font-family: var(--font-mono);
	font-size: var(--type-label-size);
	line-height: var(--type-label-line);
	letter-spacing: var(--type-label-spacing);
	font-weight: var(--type-label-weight);
	text-transform: uppercase;
}

/* Screen-reader-only content (a11y utility, exempt from the type-utility
   pairing above). */
.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* Spec values only (8,867 MW / Tier III / PUE 1.3), never prose */
.type-mono-spec {
	font-family: var(--font-mono);
	font-size: var(--type-mono-spec-size);
	line-height: var(--type-mono-spec-line);
	letter-spacing: var(--type-mono-spec-spacing);
	font-weight: var(--type-mono-spec-weight);
}

/* --------------------------------------------------------------------
 * Word reveal (Motion recipe: per-word mask + rise)
 * -------------------------------------------------------------------- */

/* Spans are built client-side by text-reveal.js on [data-text-reveal]
   headings; without JS the markup is untouched and fully visible. The
   mask is exactly one line-height tall and top-aligned, so the split
   never changes line boxes (an overflow-hidden inline-block would
   otherwise baseline-align on its bottom edge and grow the line). */
.text-reveal__mask {
	display: inline-block;
	overflow: hidden;
	vertical-align: top;
}

.text-reveal__word {
	display: inline-block;
	opacity: 0;
	transform: translateY(110%);
	transition:
		transform 500ms var(--motion-ease),
		opacity 500ms var(--motion-ease);
	transition-delay: calc(var(--word-index, 0) * 30ms);
	will-change: transform, opacity;
}

[data-text-reveal].is-revealed .text-reveal__word {
	opacity: 1;
	transform: translateY(0);
}

[data-text-reveal].reveal-done .text-reveal__word {
	will-change: auto;
}

/* --------------------------------------------------------------------
 * Two-column section header: headline left, description right
 *
 * The site's default section opening, and until now a PATTERN rather than a
 * component: the same three declarations are written out in services.css,
 * why-dicit.css, what-we-do.css and company-data.css. Extracted here the first
 * time a section in a stylesheet that can see none of those needed it.
 *
 * align-items: start, not center. Centring makes the gap under the eyebrow
 * depend on the length of the description, so it moves with the copy and moves
 * again with the Slovak translation.
 *
 * The four existing copies are left where they are: they are identical to
 * this, so there is nothing to fix, and rewriting four signed-off sections to
 * adopt a class is a change with risk and no user-visible result. New sections
 * take this one.
 * -------------------------------------------------------------------- */

.section-header {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 124px;
	align-items: start;
}

@media (max-width: 1024px) {
	.section-header {
		grid-template-columns: 1fr;
		gap: 32px;
	}
}

/* --------------------------------------------------------------------
 * Running text
 *
 * 65ch, the measure base.css already caps section intros and descriptions at
 * below 1024. Promoted to a class because a second page needed it at desktop:
 * it was written inline on .job__prose when the job page turned out to be the
 * first column wide enough to need a cap, and a third caller would have made
 * three copies of one number.
 *
 * Only ever on RUNNING text. Lists, datasheets, boxes and media are not this
 * and take the full column.
 * -------------------------------------------------------------------- */

.prose {
	max-width: 65ch;
}

/* --------------------------------------------------------------------
 * Centred section header
 *
 * Shared by the preventive inspection ruler and the contact page. Extracted
 * rather than repeated: the eyebrow is a flex row carrying its own slash, so
 * centring it needs justify-content as well as text-align, and that is
 * exactly the detail a second copy gets wrong. Per-section measures (the
 * copy-tuned max-widths) stay with their sections.
 *
 * Used standalone today, without .section-header above it, which is why that
 * one is a plain class and not this one's base.
 * -------------------------------------------------------------------- */

.section-header--center {
	text-align: center;
}

.section-header--center .eyebrow {
	justify-content: center;
	margin-bottom: 32px;
}

.section-header--center h1,
.section-header--center h2 {
	margin-inline: auto;
}

/* The description. Promoted here from cfd-page.css the moment a second page
   needed it, for the same reason .section-header and .prose were: the markup
   half is a shared component, so the layout half cannot live in one caller's
   stylesheet. The three original callers (the inspection ruler, the contact
   page, the documents table) have no description, which is why this pairing
   arrived later than the rest of the rule. The measure is .prose's 65ch,
   applied in the markup; these centre it and hold it off the headline. */
.section-header--center .section-header__description {
	margin: 24px auto 0; /* the site's heading-to-copy step */
	font-size: var(--type-body-lg-size);
	line-height: var(--type-body-lg-line);
	font-weight: var(--type-body-lg-weight);
	color: var(--color-body-text);
}

/* --------------------------------------------------------------------
 * The site's list marker
 *
 * The eyebrow's own 60 degree accent hairline at list scale. Written for the
 * job posting lists, and promoted here the moment a second section needed it,
 * because the alternative was a second copy in a stylesheet that cannot see
 * the first. The reasoning behind the shape is in job.css where it was
 * chosen; the short version is that it is the brand's existing mark rather
 * than a new one, and at 1px by 12px it is the lightest option that still
 * says DICIT rather than website.
 *
 * Drawn rather than left to ::marker so it can be positioned against the
 * first LINE rather than the first line box: a three-line item has to hang
 * from a marker that stays put, and list-style has no control over that.
 * Because that centring depends on the item's line height, a caller whose
 * text is not body size sets --marker-line to its own ratio.
 *
 * --marker-indent exists so a caller can pull the indent back to 0 where the
 * marker is suppressed, rather than leaving text hanging off a marker that
 * is not drawn.
 * -------------------------------------------------------------------- */

/* .entry-content ul is here rather than in entry-content.css because editor
   output carries no class worth targeting, so the shared treatment has to
   reach it by selector. Extending the list is what keeps ONE definition of the
   marker; a copy in the other file would be a second one to drift. Ordered
   lists are deliberately absent - they keep their numbers, see
   entry-content.css. */
.marker-list,
.entry-content ul {
	margin: 0;
	padding: 0;
	list-style: none;
}

.marker-list > li,
.entry-content ul > li {
	position: relative;
	padding-left: var(--marker-indent, 28px);
}

.marker-list > li::before,
.entry-content ul > li::before {
	content: "";
	position: absolute;
	left: 0;
	/* Centred on the FIRST line: half a line box down, whatever the item's
	   height. A marker aligned to the item's box drifts down the longer the
	   item gets. */
	top: calc(var(--marker-line, var(--type-body-line)) * 0.5em);
	width: 12px;
	height: 1px;
	background-color: var(--color-accent);
	transform: translateY(-50%) rotate(-60deg);
}

/* --------------------------------------------------------------------
 * Focus visibility
 *
 * KEYBOARD ONLY: every rule in the theme uses :focus-visible, so no ring
 * appears on click or tap. The single exception is the form input, which
 * keeps its accent border on plain :focus because a user clicking into a
 * long form needs to see which field is live (see contact.css).
 *
 * One token carries the colour so the indicator is consistent everywhere
 * and still legible on dark surfaces. Outlines follow the element's own
 * border-radius, so nothing needs a per-component shape.
 * -------------------------------------------------------------------- */

:root {
	--focus-ring: var(--color-accent);
}

/* Dark surfaces: the accent would disappear against near-black. */
.band,
.site-footer,
.site-header--top,
.site-header--bar,
.services__slide,
.why__image-card {
	--focus-ring: var(--color-heading-text-on-dark);
}

/* ...unless the header is currently showing a light panel. */
.site-header__shell.is-open,
.site-header.is-mobile-open {
	--focus-ring: var(--color-accent);
}

:focus-visible {
	outline: 2px solid var(--focus-ring);
	outline-offset: 2px;
}

/* --------------------------------------------------------------------
 * Reduced motion
 * -------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		transition-delay: 0ms !important; /* staggered reveals must not survive as dead waits */
		scroll-behavior: auto !important;
	}
}

/* --------------------------------------------------------------------
 * Responsive: 44px touch targets
 *
 * A few controls sit just under the threshold at their design sizes
 * (the primary button is 42.5px, the logo link 42px). Padding would
 * change the button's box, so they grow with min-height instead, which
 * only affects the flex box and leaves the label metrics alone.
 * -------------------------------------------------------------------- */

@media (max-width: 1024px) {
	.btn-primary {
		min-height: 44px;
	}

	.site-header__logo {
		display: flex;
		align-items: center;
		min-height: 44px;
	}
}

/* --------------------------------------------------------------------
 * Scroll dots (shared: deliver columns, why-dicit cards)
 * -------------------------------------------------------------------- */

/* Built by scroll-dots.js under any track marked [data-scroll-dots].
   Desktop shows every item, so the indicator is not rendered there. */
.scroll-dots {
	display: none;
}

@media (max-width: 1024px) {
	/* 18px gap so the pitch between two inactive dots is exactly 24px
	   (6 + 18), which is what lets each hit target be a full WCAG 2.5.8
	   square without overlapping its neighbour. The gap is a flex gap, so it
	   stays 18px when a dot grows into its 20px pill; the pill pushes its
	   neighbours out rather than closing the space. */
	.scroll-dots {
		--dot-gap: 18px;
		display: flex;
		justify-content: center;
		align-items: center;
		gap: var(--dot-gap);
		margin-block: 24px;
	}

	/* The button box is exactly the mark, so the gap is unaffected by a dot
	   becoming a pill. The touch target is an invisible overlay instead of
	   padding, which would have widened the box and pushed the dots apart. */
	.scroll-dots__dot {
		position: relative;
		display: block;
		padding: 0;
		border: 0;
		line-height: 0;
		background: none;
		cursor: pointer;
	}

	/* 24 x 44 centred on each dot, satisfying WCAG 2.5.8 outright. With the
	   24px pitch two inactive targets touch exactly and never overlap, and a
	   pill only pushes its neighbours further away, so no dot can ever steal
	   another's centre (which is what made a tap on one activate the other). */
	.scroll-dots__dot::after {
		content: "";
		position: absolute;
		top: 50%;
		left: 50%;
		width: 24px;
		height: 44px;
		transform: translate(-50%, -50%);
	}

	/* Inactive uses border-subtle rather than text-tertiary: this should read
	   as an inert track marker on the light background, not as muted text. */
	.scroll-dots__mark {
		display: block;
		width: 6px;
		height: 6px;
		border-radius: 999px;
		background-color: var(--color-border-subtle);
		transition:
			width 150ms ease,
			background-color 150ms ease;
	}

	.scroll-dots__dot[aria-current="true"] .scroll-dots__mark {
		width: 20px;
		background-color: var(--color-accent);
	}
}
