/* ==========================================================================
   Layout primitives: container, sections, grids, stacks, icons.
   ========================================================================== */

.od-container {
	width: 100%;
	max-width: var(--od-container);
	margin-inline: auto;
	padding-inline: var(--od-gutter);
}

.od-container--narrow {
	max-width: var(--od-container-narrow);
}

.od-container--wide {
	max-width: var(--od-container-wide);
}

/* --- Sections ------------------------------------------------------------ */

.od-section {
	padding-block: var(--od-section-y);
	background: var(--od-surface);
	position: relative;
}

/*
 * Anchor landing.
 *
 * html carries scroll-padding-top so a target clears the sticky header, but a
 * section is its own padding plus that offset — up to 176px of section padding
 * on top of 96px of scroll padding, so a jump landed the eyebrow around 270px
 * down and looked like the link had overshot into an empty band.
 *
 * scroll-margin-top pulls the landing point back by the section's own top
 * padding, so what stops under the header is the heading rather than the top of
 * the padding box. Negative is legal here — this is a margin, not padding — and
 * it applies only to sections that are actually anchor targets, so ordinary
 * scrolling keeps the full rhythm.
 */
.od-section[id] {
	scroll-margin-top: calc(var(--od-section-y) * -1 + var(--od-s-4));
}

.od-section--lg[id] {
	scroll-margin-top: calc(var(--od-section-y-lg) * -1 + var(--od-s-4));
}

.od-section--xl[id] {
	scroll-margin-top: calc(var(--od-section-y-xl) * -1 + var(--od-s-4));
}

.od-section--tight[id] {
	scroll-margin-top: calc(var(--od-section-y-tight) * -1 + var(--od-s-4));
}

.od-section--alt {
	background: var(--od-alt);
}

.od-section--ink {
	background: var(--od-ink);
}

.od-section--lg {
	padding-block: var(--od-section-y-lg);
}

.od-section--xl {
	padding-block: var(--od-section-y-xl);
}

/*
 * Film grain over an ink band. Ink fields this large go dead flat on a good
 * screen; a trace of noise gives them a surface. Capped at 3.5% — beyond ~4%
 * it starts eating text contrast.
 */
.od-section--grain::after {
	content: "";
	position: absolute;
	inset: 0;
	background-image: var(--od-grain);
	opacity: 0.035;
	pointer-events: none;
	mix-blend-mode: overlay;
}

/*
 * The same grain on a LIGHT band, which needs different handling entirely.
 *
 * `overlay` against white is close to a no-op — the blend has nothing darker to
 * work with, so the texture disappears. `multiply` lays the noise down as ink on
 * paper instead, and because dark specks on white read far louder than light
 * specks on ink, the opacity comes down rather than up. The effect wanted here
 * is stock, not static: you should notice the surface is not flat without ever
 * identifying why.
 */
.od-section--grain:not(.od-section--ink)::after {
	opacity: 0.1;
	mix-blend-mode: multiply;
}

/*
 * Blueprint grid over a light band. Two hairline gradients on a 32px cell —
 * cheap, no image, no request. The point is texture you feel rather than see:
 * at rgba(...,0.05) the lines read as paper stock, and anything stronger turns
 * the section into graph paper and starts competing with the cards sitting on
 * it. Masked to fade out at the edges so the grid has no hard cut-off line at
 * the section seam, which is what makes a tiled pattern look pasted on.
 */
.od-section--grid::before {
	content: "";
	position: absolute;
	inset: 0;
	background-image:
		repeating-linear-gradient(to right, var(--od-grid-line) 0 1px, transparent 1px var(--od-grid-cell)),
		repeating-linear-gradient(to bottom, var(--od-grid-line) 0 1px, transparent 1px var(--od-grid-cell));
	/* Anchored to the centre so the lines stay symmetrical about the container
	   at every viewport, instead of drifting with the left gutter. */
	background-position: 50% 0;
	/*
	 * Fades at the top and bottom seams ONLY. The first attempt used a radial
	 * mask, which killed the grid at the left and right edges — precisely the
	 * gutters where it is the one place the grid is actually visible, since the
	 * cards are opaque and cover the middle. Horizontal must stay at full
	 * strength; only the horizontal seam needs softening.
	 */
	-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 86%, transparent 100%);
	mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 86%, transparent 100%);
	pointer-events: none;
}

/*
 * On ink the grid has to invert. --od-grid-line is dark ink, which is invisible
 * against a dark band — and it needs to be far fainter than the light-surface
 * value, because light lines on a dark ground read much louder than dark lines
 * on a light one at the same alpha.
 */
.od-section--ink.od-section--grid {
	--od-grid-line: rgba(255, 255, 255, 0.05);
}

/* Keep real content above the grain, the grid and any wash. */
.od-section--grain > *,
.od-section--grid > * {
	position: relative;
	z-index: 1;
}

.od-section--tight {
	padding-block: var(--od-section-y-tight);
}

.od-section--flush-top {
	padding-top: 0;
}

/* Two adjacent sections on the same background would otherwise double their
   vertical padding into an enormous gap. Collapse the seam. */
.od-section--alt + .od-section--alt,
.od-section--ink + .od-section--ink {
	padding-top: 0;
}

/* --- Section header ------------------------------------------------------ */

.od-section-header {
	margin-bottom: var(--od-gap-header);
	max-width: var(--od-measure);
}

.od-section-header--center {
	margin-inline: auto;
	text-align: center;
}

/*
 * Left-aligned header with a short violet rule above the eyebrow. Centred text
 * has no left edge for the eye to travel down, which is why nine centred
 * sections in a row read as one flat page. This is now the default treatment;
 * --center is reserved for the two sections that genuinely want it.
 */
.od-section-header--lead {
	max-width: var(--od-container-text);
}

.od-section-header--lead .od-eyebrow::before {
	content: "";
	display: block;
	width: 48px;
	height: var(--od-rule-strong);
	margin-bottom: var(--od-s-4);
	border-radius: var(--od-r-pill);
	background: var(--od-primary);
}

.od-section--ink .od-section-header--lead .od-eyebrow::before {
	background: #B49BFF;
}

/* A column that holds position while its neighbour scrolls past. */
@media (min-width: 1200px) {
	.od-sticky-col {
		position: sticky;
		top: calc(var(--od-header-h) + 32px);
	}
}

.od-section-header--center .od-lead {
	margin-inline: auto;
}

.od-section-header__title {
	margin-bottom: var(--od-s-4);
}

.od-section-header__subtitle {
	font-size: var(--od-fs-lead);
	line-height: 1.6;
	color: var(--od-body);
	max-width: var(--od-measure-lead);
}

.od-section--ink .od-section-header__subtitle {
	color: var(--od-on-ink-muted);
}

/* Title left, action right — collapses to stacked on mobile. */
.od-section-header--split {
	display: flex;
	flex-direction: column;
	gap: var(--od-s-5);
	max-width: none;
}

@media (min-width: 900px) {
	.od-section-header--split {
		flex-direction: row;
		align-items: flex-end;
		justify-content: space-between;
	}

	.od-section-header--split > :first-child {
		max-width: var(--od-measure);
	}
}

/* --- Grids --------------------------------------------------------------- */

.od-grid {
	display: grid;
	gap: var(--od-gap-grid);
	grid-template-columns: 1fr;
}

@media (min-width: 600px) {
	.od-grid--2,
	.od-grid--3,
	.od-grid--4 {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 900px) {
	.od-grid--3 {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.od-grid--4 {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

/* --- Stacks -------------------------------------------------------------- */

.od-stack {
	display: flex;
	flex-direction: column;
}

.od-stack--1 { gap: var(--od-s-1); }
.od-stack--2 { gap: var(--od-s-2); }
.od-stack--3 { gap: var(--od-s-3); }
.od-stack--4 { gap: var(--od-s-4); }
.od-stack--5 { gap: var(--od-s-5); }
.od-stack--6 { gap: var(--od-s-6); }

.od-cluster {
	display: flex;
	flex-wrap: wrap;
	gap: var(--od-s-3);
	align-items: center;
}

/* --- Icons --------------------------------------------------------------- */

.od-icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	color: inherit;
}

.od-icon svg {
	width: 1.25em;
	height: 1.25em;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* Icon on a tinted plate — used by service and quick-nav cards. */
.od-icon-plate {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 48px;
	height: 48px;
	border-radius: 50%;
	background: var(--od-primary-100);
	color: var(--od-primary);
	font-size: 1.125rem;
	transition: background-color var(--od-dur) var(--od-ease),
		color var(--od-dur) var(--od-ease);
}

/* Large circular icon for service cards. */
.od-icon-plate--lg {
	width: 68px;
	height: 68px;
	font-size: 1.5rem;
	margin-bottom: var(--od-s-2);
}

/* Fills with the brand colour when its card is hovered. */
.od-card--interactive:hover .od-icon-plate,
.od-card--quicknav:hover .od-icon-plate {
	background: var(--od-primary);
	color: #fff;
}

.od-section--ink .od-icon-plate {
	background: rgba(206, 155, 255, 0.14);
	color: #B49BFF;
}

/* --- Misc ---------------------------------------------------------------- */

.od-divider {
	height: 1px;
	background: var(--od-line);
	border: 0;
	margin: 0;
}

.od-text-center {
	text-align: center;
}

.od-arrow {
	display: inline-block;
	transition: transform var(--od-dur) var(--od-ease);
}

/* Prose column for editor content on inner pages. */
.od-prose > * + * {
	margin-top: var(--od-s-5);
}

.od-prose h2,
.od-prose h3 {
	margin-top: var(--od-s-7);
}

.od-prose ul,
.od-prose ol {
	padding-left: var(--od-s-5);
	max-width: var(--od-measure);
}

.od-prose ul li {
	list-style: disc;
	margin-bottom: var(--od-s-2);
}

.od-prose ol li {
	list-style: decimal;
	margin-bottom: var(--od-s-2);
}

.od-prose img {
	border-radius: var(--od-r);
}

/* Wide content must scroll inside itself, never widen the page. */
.od-scroll-x {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
}

/*
 * Solid icons.
 *
 * The default .od-icon svg rule paints stroke-only shapes, which is what makes
 * the outline set look drawn rather than filled. Brand marks are the exception:
 * a stroked LinkedIn or X reads as a wireframe of a logo rather than the logo.
 * These files use fill-rule="evenodd" so their counters knock through.
 *
 * The footer NAP glyphs (mail-solid, phone-solid, map-pin-solid) use it too:
 * at 16px on a dark band a 1.5px stroke has almost no mass and reads as a
 * smudge, where a filled shape stays a recognisable envelope or pin. They are
 * separate *-solid files rather than a restyle of the outline set, which is
 * still correct everywhere else it is used.
 */
.od-icon--solid svg {
	fill: currentColor;
	stroke: none;
}
