:root {
	--canvas: #fdfdfd;
	--white: #ffffff;
	--ink: #1f2c32;
	--muted: #a4acb0;
	--muted-2: #d3d8da;
	--line: rgba(36, 54, 63, 0.24); /* drone.riotters.com: border-ink/24, its separator colour throughout */
	--ph: #eef1f2;
	--gutter: 24px;
	/* 76px, the reference's header bar measured at a wide window. The phone
	   layout at the end of this file already used 76 — measured off the same
	   reference at 390px — so the two now agree instead of the desktop bar
	   being 8px shallower than the thing it was copied from. */
	--nav-h: 76px;
	/* The page stops widening at --max-w and centres itself in anything wider. --pad is the side padding the
	   page's sections are set in; past that width it grows to take up the slack. Backgrounds still run to the
	   window edge, because this is padding on full-width sections rather than a width on a wrapper around them.
	   Two things deliberately keep the plain --gutter and stay on the window's own edges: the header bar, and
	   the hero with its gradient panel — both frame the page rather than sit inside it.
	   --gutter is otherwise the gap BETWEEN columns inside the page, and margins growing on a wide screen is
	   no reason for the space between two cards to grow with them. */
	--max-w: 1800px;
	/* About: the process grid's own measure, and the gap between its two columns. The tools row below it is
	   laid out on the same two numbers so its button starts exactly where the grid's right column does — they
	   have to be shared, or lining the two up once would come apart the next time either is touched. */
	--grid-w: 1730px;
	--grid-gap: clamp(24px, 3vw, 48px);
	--pad: max(var(--gutter), (100vw - var(--max-w)) / 2);
	/* Vertical rhythm. Every section takes its spacing from these four, so a gap is changed in one place and
	   the whole page follows. They are fluid (clamp), which is why no section repeats a literal value.
	   NOTE: the @media (max-width: 767px) block at the end of this file is a different thing — a layout
	   measured off the reference at 390px. Its numbers are measurements, not this scale, and stay literal. */
	/* The display title on a detail page.
	   Home opens at the full .big-title size; About, Service and FT Viewer all
	   open a step down from it, so the three read as one family. They each used
	   to restate the clamp, which is how FT Viewer came to be the odd one out at
	   216px while the other two sat at 160. One value now, three references. */
	--title-detail: clamp(44px, 10vw, 160px);
	--section-top: clamp(80px, 12vw, 200px);    /* a section's own top padding, above its rule */
	--section-bottom: clamp(60px, 8vw, 120px);  /* a section's bottom padding */
	--rule-w: 1px;                              /* thickness of every hairline rule on the page */
	--head-gap: clamp(40px, 6vw, 120px);        /* the rule above a title → the title */
	--head-rule-gap: clamp(20px, 2vw, 32px);    /* a title → a rule drawn under it (Capabilities) */
	--split-gap: clamp(40px, 6vw, 90px);        /* the heading block → the label / lead pair below it */
	--badge-gap: 24px;                          /* ≤767px only: the badge → the title it sits above */
	--sans: "Inter Tight", "Pretendard Variable", Pretendard, system-ui, -apple-system, sans-serif;
	--mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
	/* Squared-off digits for figures meant to read as an instrument readout. Used for numerals only —
	   it is far too mannered for words. */
	--digit: "Orbitron", var(--mono);
}

/* Thai. Inter Tight and JetBrains Mono carry no Thai, so the Thai face (Noto Sans Thai, already in the Google
   Fonts link) is named next in both stacks — Latin words in a Thai line keep the site's own letters. Thai is not
   set with tracking: its vowel and tone marks sit on the consonant before them, and spacing pulls them apart. */
html:lang(th) {
	--sans: "Inter Tight", "Noto Sans Thai", "Pretendard Variable", Pretendard, system-ui, -apple-system, sans-serif;
	--mono: "JetBrains Mono", "Noto Sans Thai", ui-monospace, SFMono-Regular, Menlo, monospace;
}
html:lang(th) .mono { letter-spacing: 0; }

@media (max-width: 1023px) {
	/* Stacked, the caption is simply the first thing on the page again — a
	   sticky element in a one-column flow follows the reader down the screen
	   and sits on top of what they scrolled to. */
 :root { --gutter: 16px; } }
@media (max-width: 767px) { :root { --gutter: 8px; --nav-h: 56px; } }

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
	margin: 0;
	background: var(--canvas);
	color: var(--ink);
	font-family: var(--sans);
	font-size: 16px;
	line-height: 1.5;
	-webkit-font-smoothing: antialiased;
	word-break: keep-all;
}
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
p, h1, h2, h3, ul { margin: 0; }
ul { list-style: none; padding: 0; }
[hidden] { display: none !important; }

.mono { font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.08em; }
.small { font-size: 11px; line-height: 1.6; color: var(--muted); }
.muted { color: var(--muted); }

/* ---------- Header ---------- */
.site-nav {
	position: fixed;
	inset: 0 0 auto 0;
	z-index: 130;
	height: var(--nav-h);
	display: grid;
	grid-template-columns: repeat(12, minmax(0, 1fr));
	align-items: center;
	padding: 0 var(--gutter);
	background: var(--canvas);
	font-size: 13px;
}
.admin-bar .site-nav { top: 32px; }
/* Below 782px the admin bar is position:absolute and scrolls away, so the header follows it down only while
   it is still on screen (--admin-bar-top, tracked in main.js) instead of holding a permanent 46px gap. */
@media (max-width: 782px) { .admin-bar .site-nav { top: var(--admin-bar-top, 46px); } }

.nav-brand { grid-column: 1 / span 5; display: flex; align-items: center; gap: 14px; min-width: 0; }
/* The brand is the wordmark itself, which carries the tagline with it — so the mark, the dot and the
   separate tagline the nav used to set in type are all inside the one image now. Height, not width, is what
   has to fit the bar, so the height is what is set. */
.brand { display: inline-flex; align-items: center; }
.brand img { display: block; height: 32px; width: auto; }
.nav-center { grid-column: 6 / span 3; text-align: center; color: #6c777c; }
/* Language switch, beside the menu button. Closed it is just the current code; open, a short list under it. */
.lang { grid-column: 11; justify-self: end; position: relative; }
/* Bare text, no frame around it: it sits in the header quietly and comes forward on hover. */
.lang-btn {
	padding: 5px 4px;
	background: none;
	border: 0;
	font-family: var(--mono);
	font-size: 12px;
	letter-spacing: 0.04em;
	color: #6c777c;
	cursor: pointer;
}
.lang-btn:hover, .lang-btn[aria-expanded="true"] { color: var(--ink); }
.lang-list {
	position: absolute;
	top: calc(100% + 6px);
	right: 0;
	z-index: 30;
	margin: 0;
	padding: 4px;
	min-width: 148px;
	list-style: none;
	background: var(--white);
	border: 1px solid var(--line);
	border-radius: 4px;
	box-shadow: 0 8px 20px rgba(31, 44, 50, 0.06);
}
.lang-list button {
	display: block;
	width: 100%;
	padding: 8px 10px;
	background: none;
	border: 0;
	border-radius: 4px;
	font: inherit;
	font-size: 13px;
	color: var(--ink);
	text-align: left;
	cursor: pointer;
}
.lang-list button:hover { background: #f1f3f4; }
.lang-list button[aria-selected="true"] { font-weight: 500; background: #f1f3f4; }
.menu-btn {
	grid-column: 12;
	justify-self: end;
	padding: 5px 12px;
	background: #ececec;
	border-radius: 4px;
	font-size: 13px;
	font-weight: 500;
}
.menu-btn[aria-expanded="true"] { background: var(--ink); color: var(--white); }
@media (max-width: 767px) {
	.nav-center { display: none; }
	/* Twelve equal columns leave the language switch and the menu button sharing too little room at this width
	   and they run into each other. Three tracks instead: the brand takes what is left, those two take what
	   they need. */
	.site-nav { grid-template-columns: minmax(0, 1fr) auto auto; gap: 14px; }
	.nav-brand { grid-column: 1; }
	.lang { grid-column: 2; }
	.menu-btn { grid-column: 3; }
}

.menu-overlay {
	position: fixed;
	inset: 0;
	z-index: 120;
	background: var(--canvas);
	padding: calc(var(--nav-h) + 40px) var(--pad) var(--gutter);
	display: flex;
	flex-direction: column;
	justify-content: space-between;
}
.menu-overlay-nav .menu a {
	display: block;
	font-size: clamp(36px, 6vw, 88px);
	line-height: 1.1;
	letter-spacing: -0.03em;
	color: var(--muted-2);
	transition: color .25s;
}
.menu-overlay-nav .menu a:hover, .menu-overlay-nav .menu a:focus-visible { color: var(--ink); }
.menu-overlay-foot { font-size: 11px; color: var(--muted); }

/* ---------- 3D drone layer ---------- */
.drone-layer {
	position: fixed;
	inset: 0;
	z-index: 100; /* above sections, below header (130) and menu (120) */
	pointer-events: none;
	opacity: 0;
}
.drone-layer canvas { display: block; width: 100%; height: 100%; }
/* V5 renderer: the area is moved / scaled by mavic-v5-mount.js; its canvas must not block clicks on the page
   (the renderer sets pointer-events:auto inline, pointer moves are forwarded to the container instead). */
.drone-layer--v5 { opacity: 1; visibility: hidden; transform-origin: 50% 50%; will-change: transform, opacity; }
.drone-layer--v5.is-ready { visibility: visible; }
.drone-layer--v5 canvas { pointer-events: none !important; }

/* ---------- Common blocks ---------- */
main { display: block; }
.block { position: relative; background: var(--canvas); padding: var(--section-top) var(--pad) var(--section-bottom); }
.block-white { background: var(--white); }
/* Every section heading is ruled off the same way, after the reference: a line above the title, then
   --head-gap down to it. The line sits on the head, inside .block's gutter padding, so it stops short of the
   screen edges. Capabilities is the single exception, and .caps says why. */
.block-head {
	display: flex;
	justify-content: space-between;
	align-items: flex-start;
	gap: 16px;
	border-top: var(--rule-w) solid var(--line);
	padding-top: var(--head-gap);
}
/* A section title that links to its own page (main.js wraps the revealed letters when the title carries
   data-href). The letters keep the colours the reveal gives them, so the link sets none of its own; the
   underline on hover is the only thing marking it as clickable, kept proportional to these very large sizes.

   The rule is drawn as a pseudo-element rather than text-decoration so it can be wiped on: it is always
   there at scaleX(0) and only its transform changes, and the two states anchor it at opposite ends — it
   grows from the left on the way in and retreats to the right on the way out, so the movement reads as one
   stroke travelling rather than a line that grows and then shrinks back. Everything is in em, so it holds
   its proportions from the 216px desktop title down to the mobile one. 0.065em is measured: it puts the
   rule where text-decoration was drawing it, 0.178em under the baseline. */
.title-link { position: relative; color: inherit; text-decoration: none; }
.title-link::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0.065em;
	height: 0.018em;
	background: currentColor;
	transform: scaleX(0);
	transform-origin: bottom right;
	transition: transform 0.3s ease-out;
}
.title-link:hover::after,
.title-link:focus-visible::after { transform: scaleX(1); transform-origin: bottom left; }
@media (prefers-reduced-motion: reduce) { .title-link::after { transition: none; } }
.big-title {
	font-size: clamp(64px, 15vw, 216px);   /* tops out at 216px — reached at a 1440px window */
	font-weight: 400;
	line-height: 0.9;
	letter-spacing: -0.045em;
	margin-left: -0.04em;
}
.badge {
	flex: none;
	display: inline-grid;
	place-items: center;
	width: 22px;
	height: 22px;
	margin-top: 8px;
	border: 1px solid var(--line);
	border-radius: 50%;
	font-size: 10px;
	color: #6c777c;
}
.split {
	display: grid;
	grid-template-columns: repeat(12, minmax(0, 1fr));
	--row-gap: 24px;
	gap: var(--row-gap) var(--gutter);
	margin-top: var(--split-gap);
}
.label { grid-column: 1 / span 4; display: flex; align-items: center; gap: 8px; font-size: 10px; color: #6c777c; padding-top: 12px; }
.label-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--muted); }
/* The lead's type lives on .split rather than on .lead itself, because the arrow standing under the lead is
   its sibling, not its child, and has to read the same two numbers to know how tall one of its lines is. */
.split { --lead-size: clamp(20px, 2vw, 28px); --lead-lh: calc(1.35 * var(--lead-size)); }
.lead { grid-column: 7 / span 6; font-size: var(--lead-size); line-height: var(--lead-lh); letter-spacing: -0.02em; }
/* The arrow at the foot of a section's copy: a second way into that section's own page, standing on the
   copy's own left edge so it reads as the end of that copy rather than a separate element. It runs forward
   under the pointer and presses in on the click — the reference's own 8px and 0.9, except that the reference
   parks its arrow 8px back to close the gap after a word, and this one stands alone at the left edge, so it
   starts flush and travels forward instead.

   --muted is the page's quiet mark: the same ink [ NUMBERS ] and the other bracketed labels are set in.
   width: max-content keeps the hit area on the arrow itself — without it the link stretches to fill whatever
   holds it (a grid column in .split, a flex column in the Service tabs) and the pointer sets it running from
   halfway across the page.

   It stands --go-step under the copy, then backs up half one of that copy's lines, so it reads as close to
   the paragraph without crowding its descenders. The two numbers it needs come from the container: --go-line
   is the copy's line height and --row-gap the gap the container has already put between them, subtracted so
   the arrow keeps its distance whatever that gap is. Both are stated wherever the copy is, desktop and the
   measured mobile block alike, so the arrow follows the type instead of being set twice. */
.go-link { --go-step: 32px; justify-self: start; align-self: start; display: block; width: max-content; margin-top: calc(var(--go-step) - var(--go-line, 0px) / 2 - var(--row-gap, 0px)); color: var(--muted); }
.split > .go-link { grid-column: 7 / span 6; --go-line: var(--lead-lh); }
.tab-copy > .go-link { --go-line: var(--copy-lh); }
.go-arrow {
	display: block;
	width: clamp(30px, 2.6vw, 40px);
	height: auto;
	fill: currentColor;
	transition: transform 0.3s ease;
}
.go-link:hover .go-arrow { transform: translateX(8px); }
.go-link:active .go-arrow { transform: scale(0.9); }
@media (prefers-reduced-motion: reduce) { .go-arrow { transition: none; } }
@media (max-width: 1023px) {
	.label, .lead, .split > .go-link { grid-column: 1 / -1; }
}

/* Placeholder images */
.ph {
	position: relative;
	display: grid;
	place-items: center;
	background: linear-gradient(160deg, #e7ebed 0%, #f5f7f8 60%, #eef1f2 100%);
	color: var(--muted);
	font-size: 11px;
	overflow: hidden;
}
/* Service media panel. The reference pins this to a flat height rather than letting an aspect ratio set it
   (dsk:h-[720px] / md:h-[512px], 16:9 only on mobile), which is what makes it read as a large panel. */
.ph-wide { height: 720px; }
@media (max-width: 1023px) { .ph-wide { height: 512px; } }
@media (max-width: 767px) { .ph-wide { height: auto; aspect-ratio: 16 / 9; } }
.ph-fill { position: absolute; inset: 0; }
/* Fills its panel and covers the placeholder label once the clip has something to show. */
.ph-video,
.ph-shot { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* Opening a tab wipes the panel across from the left while the picture eases down from slightly enlarged,
   after the reference, which animates a clip-path inset on the same box. */
@keyframes ph-wipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes ph-settle { from { transform: scale(1.09); } to { transform: scale(1); } }
.ph-wide.is-entering { animation: ph-wipe .72s cubic-bezier(.76, 0, .24, 1) both; }
/* The delay holds the picture enlarged until the wipe is most of the way across, so the shrink reads as a
   separate, later move rather than starting with it. "both" keeps it at 1.09 through the wait. */
.ph-wide.is-entering .ph-video { animation: ph-settle 1.4s cubic-bezier(.16, 1, .3, 1) .45s both; }
@media (prefers-reduced-motion: reduce) {
	.ph-wide.is-entering, .ph-wide.is-entering .ph-video { animation: none; }
}
.ph-card { aspect-ratio: 4 / 3; }

/* Scroll reveal */
[data-reveal] .w, [data-reveal-chars] .c { transition: color .2s linear; }
[data-reveal] .w { color: var(--muted-2); }
[data-reveal] .w.on { color: var(--ink); }
[data-reveal-chars] .c { color: var(--muted-2); }
[data-reveal-chars] .c.on { color: var(--ink); }

/* Per-character reveal with a flare (main.js sets each letter's colour). Letters wait at --reveal-faint;
   a soft band moving through the text lifts each one to a near-white peak, then drops it into its resting
   colour — --reveal-key for words listed in data-key, --reveal-body for the rest. */
[data-reveal-glow] {
	--reveal-faint: rgba(199, 204, 203, 1);
	--reveal-body: rgba(31, 44, 50, 1);
	--reveal-key: rgba(31, 44, 50, 1);
	--reveal-peak: rgba(244, 246, 247, 1);
	--reveal-peak-key: rgba(244, 246, 247, 1);
}
[data-reveal-glow] .c { color: var(--reveal-faint); }

/* ---------- 1. Hero ---------- */
.hero {
	position: relative;
	height: 100svh;
	min-height: 520px;
	display: flex;
	flex-direction: column;
	/*
	 * No top padding: the panel runs the full height of the section and the
	 * fixed header — opaque #fdfdfd — sits on top of it, which is how the
	 * reference gets its white top. Padding the section instead stopped the
	 * panel below the bar and gave it a top edge of its own; the difference is
	 * invisible while the bar is opaque and stops being invisible the moment
	 * anything about the bar changes.
	 *
	 * The white frame is these 24px of side padding on a --canvas ground, and
	 * the hero-foot below the panel is the fourth side.
	 */
	padding: 0 var(--gutter);
	overflow: hidden;
	background: var(--canvas);
}
.hero-panel {
	position: relative;
	flex: 1;
	display: grid;
	place-items: center;
	background: linear-gradient(180deg, #dde5e9 0%, #fdfdfd 100%); /* same as drone.riotters.com #hero */
	overflow: hidden;
}

/*
 * The wordmark, laid into the panel.
 *
 * Vector, and drawn the way it is meant to be seen: white strokes carrying a
 * top-to-bottom alpha fade, so the lettering starts nearly solid and sinks
 * into the gradient rather than stopping at an edge. That fade is in the
 * artwork, which is why nothing here has to blend or dim it — it composites
 * normally and the panel's gradient shows through the transparent part.
 *
 * (It replaced a raster version that was a white plate with the letters
 * pressed into it, opaque and needing multiply to stop it covering the hero.
 * The vector needs no such trick, and it stays sharp at any width.)
 *
 * Sized by width: the lettering runs the full width of its 2160x560 artboard,
 * so 100% puts it edge to edge as drawn. The band is then far shorter than the
 * panel and centres itself in it.
 */
.hero-panel::before {
	content: '';
	position: absolute;
	inset: 0;
	background: url('media/hero-wordmark.svg') center / 100% auto no-repeat;
	pointer-events: none;
}
/* The reference's soft white bloom stood in for its blurred wordmark SVG, and the hero wordmark it sat
   behind was text that has since gone. The bloom went with it; what is above is the real artwork. */
.hero-wordmark { position: relative; }
.hero-wordmark {
	font-size: 30vw;
	font-weight: 300;
	line-height: 1;
	letter-spacing: -0.05em;
	color: transparent;
	-webkit-text-stroke: 1.5px rgba(255, 255, 255, 0.95);
	text-shadow: 0 0 30px rgba(31, 44, 50, 0.05);
	user-select: none;
	white-space: nowrap;
	transform: translateY(-4%);
}
.hero-foot {
	display: flex;
	justify-content: space-between;
	align-items: flex-end;
	gap: 16px;
	padding: 24px 0 var(--gutter);
}
.hero-title { font-size: clamp(18px, 1.6vw, 24px); font-weight: 400; line-height: 1.3; letter-spacing: -0.01em; }
.hero-title span { display: block; }
.scroll-down { display: inline-flex; align-items: center; gap: 12px; font-size: 13px; white-space: nowrap; }
.scroll-down svg { width: 16px; height: 10px; animation: bob 1.8s ease-in-out infinite; }
@keyframes bob { 50% { transform: translateY(3px); } }

/* ---------- 2. Intro ---------- */
/* Height follows the copy, not the viewport: the reference holds a flat 400px above and below the sentence
   on desktop (its block measures 400 + 216 + 400 = 1016px). A vw-based value fell short of that on anything
   narrower than about 2080px, so the gap is fixed here and only steps down on smaller screens. */
.intro {
	position: relative;
	display: flex;
	align-items: center;
	padding: 400px var(--pad);
	background: var(--canvas);
}
/* Rules around the intro copy: on the reference the intro block carries border-y, inset by the gutter
   on both sides rather than full-bleed, so each stops short of the screen edges. */
.intro::before,
.intro::after { content: ''; position: absolute; left: var(--pad); right: var(--pad); height: 1px; background: var(--line); }
.intro::before { top: 0; }
.intro::after { bottom: 0; }
.intro-line { position: absolute; top: 0; bottom: 0; left: 50%; width: 1px; background: var(--line); }
.intro-text {
	position: relative;
	width: min(50%, 720px);
	padding-right: 40px;
	font-size: clamp(30px, 3.6vw, 56px);
	line-height: 1.12;
	letter-spacing: -0.035em;
	color: var(--muted-2);
}
.intro-text .w.key { cursor: pointer; }
/* Narrow screens: the clip lives under the sentence rather than following a cursor that isn't there. */
.intro-shot { display: none; }
@media (max-width: 767px) {
	/* stack the sentence and the clip instead of sitting them side by side in the row */
	.intro { flex-direction: column; align-items: stretch; }
	.intro-shot {
		display: block;
		width: 100%;
		margin-top: 32px;
		aspect-ratio: 16 / 9;
		overflow: hidden;
		background: #0b1114;
	}
	.intro-shot-video { width: 100%; height: 100%; object-fit: cover; display: block; }
	/* the tapped word keeps an underline so it is clear which clip is playing */
	.intro-text .w.key[data-selected="true"] { border-bottom: 2px solid var(--ink); }
}
/* Clip that rides with the cursor over a key word. Sized 16:9 like the reference (427x240), parked at
   92% and faded out until a word is hovered; the transform transition is what gives it its trailing drift. */
.key-preview {
	position: fixed;
	left: 0;
	top: 0;
	z-index: 50;
	width: min(427px, 62vw);
	aspect-ratio: 16 / 9;
	overflow: hidden;
	background: #0b1114;
	pointer-events: none;
	opacity: 0;
	/* grows from its lower edge, so it opens upward away from the word */
	transform-origin: 50% 100%;
	transform: translate3d(var(--px, 0px), var(--py, 0px), 0) scale(0.92);
	transition: opacity .28s ease, transform .3s cubic-bezier(.22, .61, .36, 1);
}
.key-preview.is-on { opacity: 1; transform: translate3d(var(--px, 0px), var(--py, 0px), 0) scale(1); }
.key-preview video { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (prefers-reduced-motion: reduce) { .key-preview { display: none; } }
@media (max-width: 1023px) { .intro { padding: 200px var(--pad); } }
/* The intro sentence rests quieter than a heading: only its key words reach full ink. */
.intro-text { --reveal-body: rgba(31, 44, 50, 0.48); }
@media (max-width: 767px) {
	.intro-line { display: none; }
	.intro-text { width: 100%; padding-right: 0; }
}

/* ---------- 3. About ---------- */
/* Rule above the title, as the reference's line above "Gear": 1px ink/24 inset by the gutter, title 24px below */
/* About opens straight off the intro, so it drops its own top padding — the shared rule on .block-head still
   draws the line and sets the gap below it. */
#about { padding-top: 0; }
#about .badge { margin-top: 0; }

/* ---------- 4. Stats (sticky) ---------- */
/* The only section that used to let the body's colour show through; it now states its own, a shade below
   --canvas so the numbers sit in a slightly deeper field than the section above them. */
.numbers { position: relative; height: 260vh; padding: 0 var(--pad); background: #fafafa; }
.numbers-sticky {
	position: sticky;
	top: 0;
	height: 100vh;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	padding-bottom: 12vh;
}
.numbers { --hub: clamp(67px, 6.68vw, 120px); } /* hub size (reference: 80px below 1024px) */
@media (max-width: 1023px) { .numbers { --hub: 80px; } }
/* The arc stands on the grid's top line, as in the reference; the label sits 8px above the hub. */
.numbers-visual { position: relative; display: flex; flex-direction: column; align-items: center; }
.arc { width: min(360px, 70vw); height: auto; overflow: visible; color: var(--ink); }
/* reference arc diameter clamp(480px, 48vw, 880px); the path spans 380 of the 400-unit viewBox */
@media (min-width: 1024px) { .arc { width: clamp(505px, 50.53vw, 926px); } }
.arc path, .arc line { vector-effect: non-scaling-stroke; } /* lines keep their 1px / 1.5px weight at any arc size */
.numbers-visual .small { position: absolute; left: 50%; bottom: calc(var(--hub) / 2 + 8px); transform: translateX(-50%); padding: 0 12px; white-space: nowrap; }
.numbers-grid {
	position: relative;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	border-top: 1px solid var(--line);
	border-bottom: 1px solid var(--line);
}
.numbers-hub {
	position: absolute;
	top: 0;
	left: 50%;
	width: var(--hub); /* same size rule as the reference's drone-lock hub */
	height: var(--hub);
	transform: translate(-50%, -50%);
	border: 1px solid var(--line);
	border-radius: 50%;
	background: var(--canvas);
}
/* Banter loader inside the hub (supplied markup/CSS; px converted to em so it scales with the hub, dots in ink) */
.banter-loader {
  font-size: calc(var(--hub) * 0.105); /* 1em = one dot; the loader is 3.6em = ~38% of the hub */
  position: absolute;
  left: 50%;
  top: 50%;
  width: 3.6em;
  height: 3.6em;
  margin-left: -1.8em;
  margin-top: -1.8em;
}

.banter-loader__box {
  float: left;
  position: relative;
  width: 1em;
  height: 1em;
  margin-right: 0.3em;
}

.banter-loader__box:before {
  content: "";
  position: absolute;
  left: 25%; /* dot at half the box size, centred in its cell (the movement grid stays the same) */
  top: 25%;
  width: 50%;
  height: 50%;
  background: var(--ink);
  border-radius: 50%;
}

.banter-loader__box:nth-child(3n) {
  margin-right: 0;
  margin-bottom: 0.3em;
}

.banter-loader__box:nth-child(1):before,
.banter-loader__box:nth-child(4):before {
  margin-left: 1.3em;
}

.banter-loader__box:nth-child(3):before {
  margin-top: 2.6em;
}

.banter-loader__box:last-child {
  margin-bottom: 0;
}

@keyframes moveBox-1 {
  9.0909090909% { transform: translate(-1.3em, 0); }
  18.1818181818% { transform: translate(0, 0); }
  27.2727272727% { transform: translate(0, 0); }
  36.3636363636% { transform: translate(1.3em, 0); }
  45.4545454545% { transform: translate(1.3em, 1.3em); }
  54.5454545455% { transform: translate(1.3em, 1.3em); }
  63.6363636364% { transform: translate(1.3em, 1.3em); }
  72.7272727273% { transform: translate(1.3em, 0); }
  81.8181818182% { transform: translate(0, 0); }
  90.9090909091% { transform: translate(-1.3em, 0); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(1) { animation: moveBox-1 12s infinite; }

@keyframes moveBox-2 {
  9.0909090909% { transform: translate(0, 0); }
  18.1818181818% { transform: translate(1.3em, 0); }
  27.2727272727% { transform: translate(0, 0); }
  36.3636363636% { transform: translate(1.3em, 0); }
  45.4545454545% { transform: translate(1.3em, 1.3em); }
  54.5454545455% { transform: translate(1.3em, 1.3em); }
  63.6363636364% { transform: translate(1.3em, 1.3em); }
  72.7272727273% { transform: translate(1.3em, 1.3em); }
  81.8181818182% { transform: translate(0, 1.3em); }
  90.9090909091% { transform: translate(0, 1.3em); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(2) { animation: moveBox-2 12s infinite; }

@keyframes moveBox-3 {
  9.0909090909% { transform: translate(-1.3em, 0); }
  18.1818181818% { transform: translate(-1.3em, 0); }
  27.2727272727% { transform: translate(0, 0); }
  36.3636363636% { transform: translate(-1.3em, 0); }
  45.4545454545% { transform: translate(-1.3em, 0); }
  54.5454545455% { transform: translate(-1.3em, 0); }
  63.6363636364% { transform: translate(-1.3em, 0); }
  72.7272727273% { transform: translate(-1.3em, 0); }
  81.8181818182% { transform: translate(-1.3em, -1.3em); }
  90.9090909091% { transform: translate(0, -1.3em); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(3) { animation: moveBox-3 12s infinite; }

@keyframes moveBox-4 {
  9.0909090909% { transform: translate(-1.3em, 0); }
  18.1818181818% { transform: translate(-1.3em, 0); }
  27.2727272727% { transform: translate(-1.3em, -1.3em); }
  36.3636363636% { transform: translate(0, -1.3em); }
  45.4545454545% { transform: translate(0, 0); }
  54.5454545455% { transform: translate(0, -1.3em); }
  63.6363636364% { transform: translate(0, -1.3em); }
  72.7272727273% { transform: translate(0, -1.3em); }
  81.8181818182% { transform: translate(-1.3em, -1.3em); }
  90.9090909091% { transform: translate(-1.3em, 0); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(4) { animation: moveBox-4 12s infinite; }

@keyframes moveBox-5 {
  9.0909090909% { transform: translate(0, 0); }
  18.1818181818% { transform: translate(0, 0); }
  27.2727272727% { transform: translate(0, 0); }
  36.3636363636% { transform: translate(1.3em, 0); }
  45.4545454545% { transform: translate(1.3em, 0); }
  54.5454545455% { transform: translate(1.3em, 0); }
  63.6363636364% { transform: translate(1.3em, 0); }
  72.7272727273% { transform: translate(1.3em, 0); }
  81.8181818182% { transform: translate(1.3em, -1.3em); }
  90.9090909091% { transform: translate(0, -1.3em); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(5) { animation: moveBox-5 12s infinite; }

@keyframes moveBox-6 {
  9.0909090909% { transform: translate(0, 0); }
  18.1818181818% { transform: translate(-1.3em, 0); }
  27.2727272727% { transform: translate(-1.3em, 0); }
  36.3636363636% { transform: translate(0, 0); }
  45.4545454545% { transform: translate(0, 0); }
  54.5454545455% { transform: translate(0, 0); }
  63.6363636364% { transform: translate(0, 0); }
  72.7272727273% { transform: translate(0, 1.3em); }
  81.8181818182% { transform: translate(-1.3em, 1.3em); }
  90.9090909091% { transform: translate(-1.3em, 0); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(6) { animation: moveBox-6 12s infinite; }

@keyframes moveBox-7 {
  9.0909090909% { transform: translate(1.3em, 0); }
  18.1818181818% { transform: translate(1.3em, 0); }
  27.2727272727% { transform: translate(1.3em, 0); }
  36.3636363636% { transform: translate(0, 0); }
  45.4545454545% { transform: translate(0, -1.3em); }
  54.5454545455% { transform: translate(1.3em, -1.3em); }
  63.6363636364% { transform: translate(0, -1.3em); }
  72.7272727273% { transform: translate(0, -1.3em); }
  81.8181818182% { transform: translate(0, 0); }
  90.9090909091% { transform: translate(1.3em, 0); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(7) { animation: moveBox-7 12s infinite; }

@keyframes moveBox-8 {
  9.0909090909% { transform: translate(0, 0); }
  18.1818181818% { transform: translate(-1.3em, 0); }
  27.2727272727% { transform: translate(-1.3em, -1.3em); }
  36.3636363636% { transform: translate(0, -1.3em); }
  45.4545454545% { transform: translate(0, -1.3em); }
  54.5454545455% { transform: translate(0, -1.3em); }
  63.6363636364% { transform: translate(0, -1.3em); }
  72.7272727273% { transform: translate(0, -1.3em); }
  81.8181818182% { transform: translate(1.3em, -1.3em); }
  90.9090909091% { transform: translate(1.3em, 0); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(8) { animation: moveBox-8 12s infinite; }

@keyframes moveBox-9 {
  9.0909090909% { transform: translate(-1.3em, 0); }
  18.1818181818% { transform: translate(-1.3em, 0); }
  27.2727272727% { transform: translate(0, 0); }
  36.3636363636% { transform: translate(-1.3em, 0); }
  45.4545454545% { transform: translate(0, 0); }
  54.5454545455% { transform: translate(0, 0); }
  63.6363636364% { transform: translate(-1.3em, 0); }
  72.7272727273% { transform: translate(-1.3em, 0); }
  81.8181818182% { transform: translate(-2.6em, 0); }
  90.9090909091% { transform: translate(-1.3em, 0); }
  100% { transform: translate(0, 0); }
}
.banter-loader__box:nth-child(9) { animation: moveBox-9 12s infinite; }
.number {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 6px;
	min-height: 220px;
	padding: 24px 12px;
	text-align: center;
}
.number + .number { border-left: 1px solid var(--line); }
.number-num { font-size: clamp(28px, 3vw, 44px); letter-spacing: -0.03em; color: var(--muted-2); transition: color .4s; }
.number.on .number-num { color: var(--ink); }
.number .small { font-size: 9px; }
@media (max-width: 767px) {
	.numbers { height: auto; padding-top: 20vh; }
	.numbers-sticky { position: static; height: auto; padding-bottom: 60px; }
	.numbers-grid { grid-template-columns: 1fr; }
	.number { min-height: 140px; }
	.number + .number { border-left: 0; border-top: 1px solid var(--line); }
	.number-num { color: var(--ink); }
}

/* ---------- 5. Service ---------- */
.tabs { margin-top: clamp(32px, 4vw, 56px); }
.tab-list {
	display: flex;
	gap: clamp(20px, 2.2vw, 40px);
	border-bottom: 1px solid var(--line);
	overflow-x: auto;
	scrollbar-width: none;
}
.tab-list::-webkit-scrollbar { display: none; }
.tab {
	position: relative;
	padding-bottom: 10px;
	/* Shrinks with the viewport like the reference. Its labels are one word, ours are up to three, so the
	   floor is lower: this keeps all four on one line down to the 768px breakpoint. */
	font-size: clamp(18px, 2.4vw, 46px);
	letter-spacing: -0.03em;
	color: var(--muted);
	white-space: nowrap;
	transition: color .25s;
}
.tab::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--ink); transform: scaleX(0); transform-origin: left; transition: transform .3s; }
.tab[aria-selected="true"] { color: var(--ink); }
.tab[aria-selected="true"]::after { transform: scaleX(1); }
.tab-panel {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--gutter);
	padding-top: 32px;
	animation: fade .4s ease;
}
.tab-copy { --copy-size: clamp(16px, 1.4vw, 20px); --copy-lh: calc(1.45 * var(--copy-size)); --row-gap: 24px; display: flex; flex-direction: column; gap: var(--row-gap); max-width: 560px; font-size: var(--copy-size); line-height: var(--copy-lh); }
@keyframes fade { from { opacity: 0; } }
@media (max-width: 767px) { .tab-panel { grid-template-columns: 1fr; } }

/* ---------- Service page: the same tabs, stood on their side ----------
   On the Service page the sectors are a list down the left rather than a row across the top, after the
   reference. Only the list changes: the panels, the clip handling and the keyboard support are the ones
   above, and Home is untouched. */
/* The page's two tracks, named once: the sector list, then everything the list opens. The features section
   below reads the same two numbers, so it starts under the picture rather than under the list. */
/* The list is narrowed to 80% of the width it started at — only the width: the type and the padding inside
   a row are unchanged. It does not go below this: at 70% "Agricultural Spraying" breaks onto a second
   line and that one row stands taller than the other three.
   --svc-w is the width the whole group is held to; .svc-inner centres it, so the leftover falls evenly on
   both sides and the copy on the right keeps a margin instead of running to the section's edge. */
/* Every section on this page is held to the same centred measure, so their headings start on one line.
   A section left out of this list runs the full width and its heading steps out of that line — which is
   what happened to Process and then to Our Standard. */
#service-select, #service-features, #service-process, #service-trust { --svc-w: 1440px; --svc-list: minmax(0, 16rem); --svc-row-gap: clamp(24px, 3vw, 56px); }
.svc-inner { max-width: var(--svc-w); margin-inline: auto; }
.tabs-side { display: grid; grid-template-columns: var(--svc-list) minmax(0, 1fr); gap: var(--svc-row-gap) var(--grid-gap); align-items: start; }
.tabs-side .tab-list { grid-column: 1; flex-direction: column; gap: 8px; border-bottom: 0; overflow: visible; }
/* Each sector is a bordered row, filled with ink once it is the open one. */
.tabs-side .tab {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	padding: 16px 20px;
	border: var(--rule-w) solid var(--line);
	font-size: clamp(15px, 1.2vw, 17px);
	font-weight: 400;
	letter-spacing: -0.01em;
	color: var(--ink);
	white-space: normal;
	text-align: left;
	transition: background .25s, color .25s, border-color .25s;
}
.tabs-side .tab[aria-selected="true"] { background: var(--ink); border-color: var(--ink); color: var(--white); }
/* The row tabs mark themselves with a rule underneath; a filled row says it already. */
.tabs-side .tab::after { content: none; }
.tab-arrow { flex: none; width: 24px; height: auto; fill: currentColor; transform: translateX(-6px); transition: transform .3s ease; }
.tabs-side .tab:hover .tab-arrow,
.tabs-side .tab[aria-selected="true"] .tab-arrow { transform: translateX(0); }
@media (prefers-reduced-motion: reduce) { .tab-arrow { transition: none; } }
/* Every panel sits in the one cell beside the list, so the list does not move as sectors are switched. */
.tabs-side .tab-panel { grid-column: 2; grid-row: 1; padding-top: 0; }
/* Fills its column; the 4:3 keeps the height in step so the clip is not squeezed. What used to be empty
   to its right is now the --svc-lead track at the far left. */
.ph-slot { aspect-ratio: 4 / 3; }
/* 600 is this stylesheet's weight for a line that has to carry — the same one .step-title and the strong half
   of .process-title use. */
.tab-title { margin: 0; font-size: clamp(22px, 2.4vw, 36px); font-weight: 600; line-height: 1.1; letter-spacing: -0.03em; }
/* Stacked, there is nothing to push across: the lead track goes and the list runs full width. */
@media (max-width: 1023px) {
	.tabs-side { grid-template-columns: 1fr; }
	.tabs-side .tab-list { grid-column: 1; }
	.tabs-side .tab-panel { grid-column: 1; grid-row: 2; }
}

/* ---------- Service page: our standard ----------
   After the reference: the pictures take one side and the three things the work is held to stand down the
   other, one under the next rather than three across. Each is marked with a small ink square — the page has
   no accent colour to spend, so the mark is the same ink as the headings. */
.standard {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	/* Twice the page's usual column gap: the pictures and the words are two separate things here, not two
	   halves of one, and the wider channel is what says so. */
	gap: clamp(32px, 4vw, 64px) clamp(48px, 6vw, 96px);
	align-items: start;
}
/* Two square pictures over one wide one, as in the reference. The wide one spans both tracks rather than
   being a third column, so the block keeps its shape when the page narrows. */
.standard-media { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gutter); }
.standard-shot { margin: 0; }
.standard-shot img { display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: cover; }
.standard-shot-wide { grid-column: 1 / -1; }
/* The wide slot carries a clip rather than a still. data-inview-play in main.js runs it only while it is
   on screen, so nothing is decoding further up or down the page. */
.standard-shot-wide img,
.standard-clip { aspect-ratio: 16 / 9; }
.standard-clip { display: block; width: 100%; height: auto; object-fit: cover; background: var(--ph); }
/* The words sit a line down from the top of the pictures, so the two columns do not start dead level. */
.standard-copy { padding-top: clamp(16px, 2vw, 40px); }
/* This one heading carries the weight; the other .about-title uses stay at 400. */
#service-trust .about-title { font-weight: 600; }
/* Set in caps in the stylesheet rather than typed that way, so the HTML and the translations keep the
   sentence as written and a screen reader is not handed a line of capitals to spell out. */
#service-process .about-title,
#service-trust .about-title { text-transform: uppercase; }
.standard-list { display: grid; gap: clamp(28px, 3vw, 44px); margin: clamp(32px, 4vw, 56px) 0 0; padding: 0; list-style: none; }
.standard-mark { display: block; width: 12px; height: 12px; background: var(--ink); }
.standard-name { margin: clamp(14px, 1.4vw, 20px) 0 0; font-size: clamp(18px, 1.6vw, 22px); font-weight: 600; letter-spacing: -0.02em; }
.standard-text { margin: 10px 0 0; max-width: 46ch; font-size: clamp(14px, 1.1vw, 15px); line-height: 1.6; color: #6c777c; }
@media (max-width: 1023px) { .standard { grid-template-columns: 1fr; } }

/* ---------- Service page: process ----------
   Six steps, first call to report. The rule over each step is the connector: a dot where the step starts and
   an arrowhead where it hands on to the next. It is drawn inside the cell rather than between cells, so it
   stays right when the grid drops from three columns to two to one — nothing has to know its neighbours. */
/* Held in from the edges of the section and given a wider column gap than the rest of the page: six short
   steps set the full width would read as one loose band, and each one has only a name and two or three
   lines to carry. --flow-w is the one number to change to let them out again. */
.flow {
	--flow-w: 1180px;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: clamp(40px, 5vw, 64px) clamp(32px, 4vw, 56px);
	max-width: var(--flow-w);
	margin: clamp(48px, 6vw, 80px) auto 0;
	padding: 0;
	list-style: none;
}
.flow-rail { position: relative; border-top: var(--rule-w) solid var(--line); padding-top: 12px; }
/* The first step's rule is ink: it marks where the sequence starts. */
.flow-step-first .flow-rail { border-top-color: var(--ink); }
.flow-dot, .flow-end {
	position: absolute;
	top: -4px;
	width: 7px;
	height: 7px;
	border-radius: 999px;
	background: var(--line);
}
.flow-dot { left: 0; }
.flow-step-first .flow-dot { background: var(--ink); }
/* A triangle made of borders — no file to load, and it takes its colour from the same token as the rule. */
.flow-arrow {
	position: absolute;
	right: 0;
	top: -4px;
	width: 0;
	height: 0;
	border-left: 7px solid var(--line);
	border-top: 4px solid transparent;
	border-bottom: 4px solid transparent;
}
/* The last step closes on a dot instead of handing on. */
.flow-end { right: 0; }
.flow-no { display: block; font-size: 12px; letter-spacing: 0.08em; color: var(--muted); }
.flow-name { margin: clamp(20px, 2.4vw, 32px) 0 0; font-size: clamp(18px, 1.6vw, 22px); font-weight: 400; letter-spacing: -0.02em; }
.flow-list { margin: 16px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
/* The em dash is drawn by the marker rather than typed into the sentence, so it never gets read aloud or
   copied with the text. */
.flow-list li { position: relative; padding-left: 22px; font-size: clamp(14px, 1.1vw, 15px); line-height: 1.6; color: #6c777c; }
.flow-list li::before { content: "\2014"; position: absolute; left: 0; color: var(--line); }
@media (max-width: 1023px) { .flow { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 767px) { .flow { grid-template-columns: 1fr; } }

/* ---------- Service page: key features ----------
   A ticked list rather than a numbered one: these are what the sector gets, not steps in an order. The tick
   is drawn with the same two borders as .check-list, so the two marks match.
   The section stands on the same two tracks as the sector list above and puts everything in the second one,
   so the label and the ticks begin under the picture, not under the list. */
/* It carries on from the sector above rather than opening a new section, so the space between the two is
   half what it was: both the foot of the sector above and the head of this one are halved, which keeps
   them in step at every width. The grid is on the inner box, not the section, so the white still runs
   the full width of the window. */
#service-select { padding-bottom: clamp(30px, 4vw, 60px); }
#service-features { padding-top: clamp(12px, 1.5vw, 24px); }
#service-features .svc-inner { display: grid; grid-template-columns: var(--svc-list) minmax(0, 1fr); column-gap: var(--grid-gap); }
#service-features .svc-inner > * { grid-column: 2; }
/* The two section headings on this page — Where We Fly and Key Features — set alike. */
.svc-title { margin: 0; font-size: clamp(22px, 2.2vw, 34px); font-weight: 300; line-height: 1.1; letter-spacing: -0.03em; }
/* Our Service is set like the About page's head title — the size from --title-detail and the
   weight, tracking and leading from .big-title itself — so the two pages open on the same note. The
   values are restated rather than the class reused, because .svc-title is declared later in this file
   and would win over .big-title on weight. .big-title's -0.04em left pull is left off on purpose: it
   optically aligns a title standing at the page edge, and this one has to line up with the list under
   it. Key Features stays at the smaller size above. */
.svc-title-lg {
	text-transform: uppercase;
	font-size: var(--title-detail);
	font-weight: 400;
	line-height: 0.9;
	letter-spacing: -0.045em;
}

@media (max-width: 1023px) { #service-features .svc-inner { grid-template-columns: 1fr; } #service-features .svc-inner > * { grid-column: 1; } }
/* Two across, like the About page's process cards: at this page's width auto-fit would open four
   thin columns and the rows would stop reading as pairs. */
.feature-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: clamp(28px, 3vw, 40px) clamp(32px, 4vw, 64px);
	max-width: var(--grid-w);
	margin: clamp(32px, 4vw, 56px) auto 0;
	padding: 0;
	list-style: none;
}
@media (max-width: 767px) { .feature-grid { grid-template-columns: 1fr; } }
.feature { display: flex; align-items: flex-start; gap: 16px; }
.feature-check { position: relative; flex: none; width: 28px; height: 28px; border: var(--rule-w) solid var(--ink); }
.feature-check::before {
	content: "";
	position: absolute;
	left: 8px;
	top: 10px;
	width: 11px;
	height: 6px;
	border-left: 1.5px solid var(--ink);
	border-bottom: 1.5px solid var(--ink);
	transform: rotate(-45deg);
}
.feature-title { margin: 0; font-size: clamp(16px, 1.3vw, 18px); font-weight: 500; letter-spacing: -0.01em; }
.feature-text { margin: 8px 0 0; max-width: 42ch; font-size: clamp(14px, 1.1vw, 15px); line-height: 1.6; color: #6c777c; }

/* ---------- 6. Capabilities ---------- */
/* The one section that draws its rule on itself rather than on .block-head: the head lives inside .caps-stage,
   which pins to the top of the window, so a rule on the head would ride down the page with the pinned heading
   instead of staying at the section's edge. It is drawn as a line inside the section rather than as a border on
   it, so that it stops at the gutter exactly like the other three — a border would run the full screen width.
   The gap below it is the same --head-gap, so only the element carrying the line differs; the result matches.
   Its head's own top rule is turned off to avoid drawing the line twice. */
.caps {
	position: relative;
	background: var(--white);
	/* the rule's own thickness is inside this padding here, where the other sections get it from their
	   border-top, so the line-to-title gap comes out identical */
	padding: calc(var(--head-gap) + var(--rule-w)) var(--pad) 0;
}
.caps::before {
	content: "";
	position: absolute;
	top: 0;
	left: var(--pad);
	right: var(--pad);
	border-top: var(--rule-w) solid var(--line);
}
.caps .block-head { border-top: 0; padding-top: 0; padding-bottom: var(--head-rule-gap); border-bottom: 1px solid var(--line); }
/* After echo-vale.aura.build: a tall portrait image holds the left column, the copy sits in a much narrower
   right column, and both start at the page gutter rather than bleeding to the screen edge.
   Measured there at a 1470px viewport: columns 889px / 428px (2.077 : 1), gap 103px, image height
   calc(viewport - 144px) with the caption laid over its bottom edge. */
.caps-grid {
	display: grid;
	/* --shot-h drives the picture. Its column is reserved at the width that height implies (3:4), rather
	   than a share of the window — a fr column would keep widening the picture on a wide screen while its
	   height stayed capped, flattening it. The picture itself lives in the stage, which spans both columns,
	   so the column cannot take its width from the content. */
	--shot-h: min(810px, 68svh);
	/* The empty 1fr tracks either side hold the picture and the steps together in the middle of a wide
	   window instead of letting the step column stretch and pull the pair to the left. */
	--caps-cols: minmax(0, 1fr) calc(var(--shot-h) * 3 / 4) minmax(0, 460px) minmax(0, 1fr);
	--caps-gap: clamp(32px, 7vw, 103px);
	grid-template-columns: var(--caps-cols);
	column-gap: var(--caps-gap);
	align-items: start;
}
/* Heading and picture pin as one block, so both let go on the same scroll position — the moment the last
   step reaches the line the first one started on. The stage spans the grid but only its own children take
   the pointer, leaving the step column beside it usable. */
.caps-stage {
	grid-column: 1 / -1;
	grid-row: 1;
	position: sticky;
	top: var(--nav-offset, 68px); /* measured in main.js — clears the fixed header (and the admin bar) */
	z-index: 2;
	pointer-events: none;
	/* same tracks as the outer grid, so the picture sits in the centred column while the heading keeps
	   the full width above it */
	display: grid;
	grid-template-columns: var(--caps-cols);
	column-gap: var(--caps-gap);
	align-content: start;
}
.caps-stage > * { pointer-events: auto; }
/* opaque, so a step passing underneath disappears behind the heading rather than showing through */
.caps .block-head { grid-column: 1 / -1; grid-row: 1; background: var(--white); }
.caps-media {
	grid-column: 2;
	grid-row: 2;
	margin-top: 32px;
	/* height follows the window, width follows the height: the portrait shape holds at any size.
	   Change --shot-h on .caps-grid to resize the picture — the 3:4 shape is kept either way. */
	height: var(--shot-h, 810px);
	aspect-ratio: 3 / 4;
	max-width: 100%;
}
/* No trailing space: the column is exactly as tall as its steps, so the pin releases at the moment the
   last step reaches the line the first one started on, and the stage then scrolls away with the page.
   The top padding drops the first step onto that line, level with the top of the picture. */
.caps-right {
	grid-column: 3;
	grid-row: 1;
	padding-top: calc(var(--caps-head, 220px) + 32px);
}
/* One step per screenful, its copy centred, so each arrives on its own as the column scrolls. */
.caps-step {
	display: flex;
	flex-direction: column;
	justify-content: center;
	min-height: min(810px, 68svh);
	transition: opacity .45s ease;
}
.caps-step[data-active="false"] { opacity: 0.28; }
.caps-step-no { color: var(--muted); margin-bottom: 14px; }
.caps-step-title {
	font-size: clamp(28px, 2.8vw, 44px);
	font-weight: 400;
	line-height: 1.15;
	letter-spacing: -0.03em;
	margin-bottom: 18px;
}
/* Anchors the .ph-fill shots inside the picture. Without it they resolve against the
   sticky stage instead and cover the whole section. */
.caps-media { position: relative; overflow: hidden; }
.caps-desc { max-width: 360px; font-size: 14px; line-height: 1.5; }
@media (max-width: 1023px) {
	/* One column, no pinning: the picture leads and the steps simply follow it down the page. The gap under the
	   rule stays on .caps itself (--head-gap), so the heading keeps step with the other sections; adding it
	   here as well would push this one heading further down than the rest. */
	.caps-grid { grid-template-columns: 1fr; }
	.caps-stage {
		grid-column: auto; grid-row: auto; position: static; pointer-events: auto;
		display: block; /* the centring tracks are pointless in one column */
	}
	.caps .block-head { grid-column: auto; grid-row: auto; }
	.caps-media { grid-column: auto; grid-row: auto; margin-top: 24px; height: auto; aspect-ratio: 4 / 3; }
	.caps-right { grid-column: auto; grid-row: auto; padding-top: 0; }
	.caps-step { min-height: 0; padding: 32px 0; }
	.caps-step[data-active="false"] { opacity: 1; }
}

/* ---------- 7. Software cards ---------- */
.cards {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--gutter);
	margin-top: clamp(60px, 8vw, 140px);
}
.card { display: flex; flex-direction: column; gap: 16px; padding-top: 16px; border-top: 1px solid var(--line); }
.card-top { display: flex; justify-content: space-between; align-items: center; }
.card-icon { width: 16px; height: 16px; color: var(--ink); }
.card-title { font-size: 22px; font-weight: 400; letter-spacing: -0.02em; }
.card-desc { font-size: 14px; color: #5d686d; }
.card-foot strong { color: var(--ink); font-weight: 500; }
@media (max-width: 1023px) {
	.cards { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(260px, 80%); overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 8px; }
	.card { scroll-snap-align: start; }
}

/* ---------- 8. Visual ---------- */
/* The closing section: a band of open space, then the film. No side padding — the film runs edge to edge, and
   the stage above it is empty anyway. The stage is the larger half of the gap under the cards above; the rest
   is that section's own bottom padding. */
.visual { position: relative; background: var(--white); padding: 0; }
.visual-stage { height: 300px; }
/* Width of the window, height from the clip's own 16:9, so nothing is cropped at any size. */
.visual-film { position: relative; width: 100%; aspect-ratio: 16 / 9; overflow: hidden; background: var(--ink); }
.visual-film-video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Laid over the film, centred. The footage is deep blue sea and shadowed rock, so white reads on it without
   dimming the picture; the soft shadow is there for the sunlit rock it passes over. */
/* Centred by position, not by flex or grid: either of those would make the <strong> a separate item and break
   the sentence onto its own line. As a plain block the line stays one line and wraps normally when narrow. */
.film-copy {
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	transform: translateY(-50%);
	margin: 0;
	padding: 0 var(--gutter);
	color: var(--white);
	font-size: clamp(32px, 5.6vw, 88px);
	font-weight: 600;
	line-height: 1.05;
	/* Opened up rather than tracked tight like the section titles: this line is all caps, which needs the air.
	   In em, so it holds at every size. */
	letter-spacing: 0.02em;
	text-align: center;
	text-shadow: 0 2px 28px rgba(0, 0, 0, 0.45);
	pointer-events: none;
}
.film-copy strong { font-weight: 900; }
@media (max-width: 767px) { .visual-stage { height: 170px; } }

/* ---------- Service page head ---------- */
/* The statement sits in the middle with a picture running off each side, over a washed-out wide backdrop.
   It reaches the window edges rather than sitting in --pad, like .hero does, because the two side pictures
   are meant to be cut by the screen. */
.service-head {
	position: relative;
	isolation: isolate;
	background: var(--canvas);
	padding: calc(var(--nav-h) + var(--section-top) / 2) 0 var(--section-bottom);
	overflow: hidden;
}
/* A wide band rather than a full-height picture, after the reference. The picture is laid in at its own
   proportions — width 100%, height auto, so nothing is enlarged — and the band's height is all that decides
   how much of it shows. --band is the one number to change to make the strip deeper or shallower.
   The band ends on straight edges: no fade top or bottom. */
.service-head-back {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	--band: clamp(216px, 27.4vw, 460px);
	height: var(--band);
	z-index: -1;
	overflow: hidden;
}
/* Desaturated and faint, so it reads as a field the type sits on rather than a picture of its own.
   cover on a band this wide scales by width alone, so the picture is still not enlarged; object-position is
   only choosing which horizontal slice of it the band shows — here the skyline, not the empty sky above it. */
.service-head-back img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 50% 40%;
	filter: grayscale(1) contrast(0.9);
	opacity: 0.22;
}
/* Three tracks on the reference's proportions: each side picture is ~30% of the width, the words take the
   rest. The sides carry no gutter — they are supposed to run out of the screen. */
.service-head-inner {
	display: grid;
	grid-template-columns: 3fr 4fr 3fr;
	align-items: start;
	gap: clamp(24px, 4vw, 64px);
}
.service-head-side { margin: 0; }
.service-head-side img { display: block; width: 100%; aspect-ratio: 24 / 25; object-fit: cover; }
/* The right-hand picture hangs lower than the left — the stagger is the whole shape of the section. */
.service-head-side-b { margin-top: clamp(60px, 12vw, 220px); }
/* Plain block flow, not a grid: .label carries a `grid-column` of its own for the 12-column sections, and
   inside a grid here that span would open extra tracks and set the lines side by side. */
/* The words start a line lower than the pictures beside them: the padding is the old gap plus one line of
   the title, written out so the reason stays readable rather than baked into a single number. */
.service-head-copy {
	text-align: center;
	padding-top: calc(clamp(24px, 4vw, 72px) + 1.1 * clamp(30px, 3.8vw, 62px));
}
.service-head-title {
	margin: clamp(24px, 3vw, 44px) 0 0;
	font-size: clamp(30px, 3.8vw, 62px);
	font-weight: 300;
	line-height: 1.1;
	letter-spacing: -0.03em;
	text-transform: uppercase;
	color: var(--muted);
	text-wrap: balance;
}
/* Same device as .process-title: the opening clause carries the weight, the rest stays light. */
.service-head-title strong { font-weight: 600; color: var(--ink); }
.service-head-lead {
	margin: clamp(24px, 3vw, 40px) auto 0;
	max-width: 52ch;
	font-size: clamp(15px, 1.2vw, 18px);
	line-height: 1.6;
	color: #6c777c;
}
/* Stacked, the pictures stop flanking anything: the words lead and the pair sits under them, side by side. */
@media (max-width: 1023px) {
	.service-head-inner { grid-template-columns: repeat(2, minmax(0, 1fr)); padding-inline: var(--pad); }
	.service-head-copy { grid-column: 1 / -1; order: -1; padding-top: 0; }
	.service-head-side-b { margin-top: clamp(24px, 6vw, 64px); }
}

/* ---------- About page ---------- */
/* --head-top is where the rule above the title sits: it is the section's own top padding, so naming it once
   lets the picture hang from exactly that line at any width, whatever the padding is set to. */
.page-head {
	position: relative;
	--head-top: calc(var(--nav-h) + var(--section-top) / 2);   /* half a section's usual top space */
	background: var(--canvas);
	padding: var(--head-top) var(--pad) var(--section-bottom);
}
/* Smaller than a home-page section title: this line carries the company name as well, so it needs the room
   to stay on one line without running the width of the window. The size is --title-detail, shared with
   Service's "Our Service" and FT Viewer's section titles. */
.page-head .big-title { font-size: var(--title-detail); }
/*
 * A detail page's section titles, same size as the head titles above.
 *
 * Inside a min-width query on purpose: the @media (max-width: 767px) block at the
 * end of this file sets every .big-title to min(80px, 21vw), which is a measured
 * phone layout rather than a point on this scale. A rule of this weight outside
 * the query would beat it and drop the phone title to 44px. Above the breakpoint
 * there is nothing else claiming the size, so one rule is enough.
 */
@media (min-width: 768px) {
	.page-detail .big-title { font-size: var(--title-detail); }
}
/* And set in caps, the way About types them into the markup and Service transforms them. Done here rather
   than typed into the HTML so the words stay in ordinary case for the reveal's aria-label and for a
   translation — the caps are how the line is set, not how it is written. Outside the query above because
   that is a size decision and this is not: the phone layout keeps its own size and takes the caps too. */
.page-detail .big-title { text-transform: uppercase; }
/* ---------- Contact ----------
 * Laid out after the Skylens contact page: a picture band with the title, then who to reach beside
 * the enquiry form, then the FAQ. The look is this site's own — white tone, ink type, hairlines. */
.contact-head {
	position: relative;
	display: grid;
	place-items: center;
	min-height: clamp(280px, 34vw, 520px);
	padding: calc(var(--nav-h) + 24px) var(--pad) 48px;
	overflow: hidden;
	text-align: center;
}
/* The band's picture, cover-cropped, with a light shade over it so the white title holds on the bright sky. */
.contact-head-media { position: absolute; inset: 0; background: var(--ph); }
.contact-head-media::after { content: ""; position: absolute; inset: 0; background: rgba(15, 29, 36, 0.3); }
.contact-head-img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 55%; }
.contact-head-copy { position: relative; }
.contact-head-title { margin: 0; color: var(--white); font-size: var(--title-detail); font-weight: 400; line-height: 0.9; letter-spacing: -0.045em; text-transform: uppercase; }

/* Two columns that stack when there is no room for both: copy on the left, the form or the questions right. */
.contact-grid {
	max-width: var(--grid-w);
	margin-inline: auto;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
	gap: clamp(40px, 5vw, 96px);
	align-items: start;
}
.contact-grid .label { padding-top: 0; }
.contact-title { margin-top: 20px; max-width: 14em; font-size: clamp(32px, 3.6vw, 56px); font-weight: 400; line-height: 1.08; letter-spacing: -0.035em; }
.contact-text { margin-top: 20px; max-width: 34em; font-size: 16px; line-height: 1.6; color: #6c777c; }
.contact-details {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));   /* always two by two, as the reference sets them */
	gap: 28px 32px;
	margin: 36px 0 0;
	padding-top: 36px;
	border-top: var(--rule-w) solid var(--line);
}
.contact-details dt { font-size: 20px; font-weight: 500; letter-spacing: -0.02em; }
.contact-details dd { margin: 8px 0 0; font-size: 15px; color: #6c777c; }
.contact-details a:hover { color: var(--ink); }

/* The form sits on a panel a half-step off white, as the reference's does, with white fields inside it. */
.contact-card { display: grid; gap: 20px; padding: clamp(20px, 2.4vw, 36px); background: var(--canvas); border: var(--rule-w) solid rgba(36, 54, 63, 0.1); border-radius: 6px; }
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 20px; }
.field { display: grid; gap: 8px; }
.field > span { font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #6c777c; }
.field input, .field textarea, .field select {
	width: 100%;
	padding: 14px 16px;
	font: inherit;
	font-size: 15px;
	color: var(--ink);
	background: var(--white);
	border: var(--rule-w) solid rgba(36, 54, 63, 0.14);
	border-radius: 4px;
	transition: border-color 0.3s ease;
}
.field textarea { resize: vertical; min-height: 160px; }
/* The select drawn like the inputs, with the same two-border chevron the FAQ uses in place of the system arrow. */
.field select {
	appearance: none;
	-webkit-appearance: none;
	padding-right: 40px;
	background-image: linear-gradient(45deg, transparent 50%, #6c777c 50%), linear-gradient(135deg, #6c777c 50%, transparent 50%);
	background-position: calc(100% - 22px) 52%, calc(100% - 17px) 52%;
	background-size: 5px 5px, 5px 5px;
	background-repeat: no-repeat;
	cursor: pointer;
}
.field select:invalid { color: var(--muted); }
/* flatpickr's calendar, brought into the site's type and ink. Its defaults are otherwise left alone. */
.flatpickr-calendar { font-family: var(--sans); box-shadow: 0 8px 24px rgba(15, 29, 36, 0.12); }
/* Scoped under the calendar so these outrank flatpickr's own rules, whose stylesheet loads after this one. */
.flatpickr-calendar .flatpickr-day.selected,
.flatpickr-calendar .flatpickr-day.selected:hover,
.flatpickr-calendar .flatpickr-day.selected:focus { background: var(--ink); border-color: var(--ink); color: var(--white); }
.flatpickr-calendar .flatpickr-day.today { border-color: var(--muted); }

/* Site location: the address with a Find button beside it, suggestions under it, then the map once pinned. */
.site-picker { position: relative; }
.site-picker > label > span { font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #6c777c; }
.site-search { display: flex; gap: 8px; }
.site-search input { flex: 1 1 auto; min-width: 0; }
.site-find { flex: none; padding: 0 20px; font-size: 14px; color: var(--ink); background: var(--white); border: var(--rule-w) solid rgba(36, 54, 63, 0.14); border-radius: 4px; transition: border-color 0.3s ease; }
.site-find:hover { border-color: var(--ink); }
.site-results {
	position: absolute;
	z-index: 5;
	top: 74px;
	left: 0;
	right: 0;
	margin: 0;
	padding: 6px 0;
	list-style: none;
	background: var(--white);
	border: var(--rule-w) solid rgba(36, 54, 63, 0.14);
	border-radius: 4px;
	box-shadow: 0 8px 24px rgba(15, 29, 36, 0.08);
}
.site-results button { display: block; width: 100%; padding: 10px 16px; font-size: 14px; text-align: left; }
.site-results button:hover, .site-results button:focus-visible { background: var(--canvas); outline: none; }
.site-map { height: 260px; border-radius: 4px; overflow: hidden; border: var(--rule-w) solid rgba(36, 54, 63, 0.14); }
.site-note { margin: 0; font-size: 13px; color: #6c777c; }
.site-note:empty { display: none; }
.field input::placeholder, .field textarea::placeholder { color: var(--muted); }
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--ink); }
.field-trap { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.contact-send { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }
.contact-submit { border: 0; cursor: pointer; }
.contact-submit:disabled { opacity: 0.35; cursor: not-allowed; }
.contact-submit:disabled:hover { opacity: 0.35; }
/* A required field's label carries a quiet asterisk. */
.field:has([required]) > span::after,
.site-picker:has([required]) > label > span::after { content: " *"; color: var(--ink); }
.contact-status { margin: 0; font-size: 14px; color: #6c777c; }
.contact-status.is-error { color: #a33a2c; }

/* FAQ: the intro on the left, the questions on the right, each one a <details> that opens in place. */
.faq-list { display: grid; gap: 10px; }
.faq-item { background: var(--white); border: var(--rule-w) solid rgba(36, 54, 63, 0.1); border-radius: 6px; transition: background-color 0.3s ease, border-color 0.3s ease; }
/* The open question takes the top of the home hero's gradient (#dde5e9) — the one tint the site already uses. */
.faq-item[open] { background: #dde5e9; border-color: #dde5e9; }
.faq-item summary {
	display: flex;
	justify-content: space-between;
	align-items: center;
	gap: 16px;
	padding: 18px 22px;
	font-size: 16px;
	font-weight: 500;
	letter-spacing: -0.01em;
	cursor: pointer;
	list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
/* A chevron drawn from two borders, turned to point up when the answer is open. */
.faq-item summary::after {
	content: "";
	flex: none;
	width: 8px;
	height: 8px;
	margin-top: -4px;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: rotate(45deg);
	transition: transform 0.3s ease;
}
.faq-item[open] summary::after { margin-top: 4px; transform: rotate(-135deg); }
.faq-item p { margin: 0; padding: 0 22px 20px; font-size: 15px; line-height: 1.65; color: #6c777c; }

/* FT Viewer · Photogrammetry band. A picture that opens out to the full screen as the page scrolls, holds while
   its title rises in, then is washed out to white from below as Capabilities arrives.
 *
 * Without script, and with reduced motion, the band is one screen tall and simply shows the picture full-size
 * with the title on it. main.js adds .is-live and from then on writes two numbers on the section every frame:
 *   --zr  0 → 1  how far the picture has opened (2/3 of the screen → all of it)
 *   --zo  0 → 1 → 0.25  its opacity: in from the white above, and back towards white at the end
 * and toggles .is-titled once the picture is full. The section is then 3 screens tall: the first screen is
 * the approach, the rest is the stage held in place while the wash comes up. */
.zoom-band { position: relative; background: var(--white); }
.zoom-stage { position: relative; height: 100vh; height: 100svh; overflow: hidden; }
/* As wide as the screen and never taller than the photograph's own 16:9 at that width, centred in the stage.
   On a landscape screen that is the whole stage, as before; as the window narrows the picture shrinks with it
   and keeps its proportions instead of being cropped to a tall sliver, with the band's white above and below. */
.zoom-media { position: absolute; inset: 0; margin: auto 0; height: min(100%, calc(100vw * 9 / 16)); background: var(--ph); }
.zoom-media img { width: 100%; height: 100%; object-fit: cover; }
/* A light shade behind the middle, where the white title sits, so it holds on the brighter parts of the photograph. */
.zoom-media::after { content: ""; position: absolute; inset: 0; background: radial-gradient(ellipse at 50% 50%, rgba(15, 29, 36, 0.45), rgba(15, 29, 36, 0) 70%); }
.zoom-title {
	position: absolute;
	top: 50%;
	left: var(--pad);
	right: var(--pad);
	margin: 0;
	text-align: center;
	color: var(--white);
	font-size: clamp(32px, 4.4vw, 72px);
	font-weight: 400;
	line-height: 1.235;   /* twice the clear space between the two lines that 1.1 left (9.7px → 19.4px at 72px) */
	letter-spacing: -0.03em;
	text-wrap: balance;
	transform: translateY(-50%);   /* dead centre of the screen */
}
.zoom-wash { display: none; }

.zoom-band.is-live { height: 300vh; height: 300svh; }
.zoom-band.is-live .zoom-stage { position: sticky; top: 0; }
.zoom-band.is-live .zoom-media {
	opacity: var(--zo, 0);
	transform: scale(calc(0.667 + 0.333 * var(--zr, 0)));
	transform-origin: 50% 50%;
	will-change: transform, opacity;
}
.zoom-band.is-live .zoom-title { opacity: 0; transform: translateY(calc(-50% + 16px)); transition: opacity 0.6s ease, transform 0.6s ease; }
.zoom-band.is-live.is-titled .zoom-title { opacity: 1; transform: translateY(-50%); }
/* The fade-out runs on the colour, not on opacity, so it tracks the scroll directly instead of trailing it
   through the 0.6s transition the entrance uses. */
.zoom-band.is-live .zoom-title { color: rgba(255, 255, 255, var(--zt, 1)); }
/* Rides at the foot of the section, so it rises over the pinned stage in the last stretch before the stage lets
   go, and ends on the same white Capabilities begins with — the seam between the two is not there to see. */
.zoom-band.is-live .zoom-wash {
	display: block;
	position: absolute;
	left: 0;
	right: 0;
	bottom: -2px;       /* a hair past the seam: the scaled picture's last row of pixels otherwise shows as a grey line */
	z-index: 1;
	height: 90vh;
	background: linear-gradient(to top, var(--canvas) 22%, rgba(253, 253, 253, 0));
	pointer-events: none;
}

/* FT Viewer · Capabilities: six rows, picture and copy side by side, the picture swapping sides every row.
   Each row is a wrapping flex line rather than a two-column grid, so it stacks on its own once the two
   bases no longer fit side by side — no breakpoint to keep in step with the rest of the file. The rows are
   ruled off with the same hairline as the section heads. */
.vcaps { margin: var(--split-gap) 0 0; padding: 0; list-style: none; }
/* No heading of its own: the Photogrammetry band above introduces it, so the rows start straight away. The
   first row loses its rule and its top padding, since there is no head above it to rule off from. */
.vcaps-block { padding-top: 0; }   /* the band's white wash above already gives the space */
.vcaps-block .vcaps { margin-top: 0; }
.vcaps-block .vcap:first-child { border-top: 0; padding-top: 0; }
.vcap {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;   /* the picture and its copy sit together as one pair in the middle of the row */
	gap: 32px clamp(32px, 4vw, 64px);
	padding: var(--split-gap) 0;
	border-top: var(--rule-w) solid var(--line);
}
.vcap:nth-child(even) { flex-direction: row-reverse; }
.vcap-media {
	flex: 0 1 auto;     /* as wide as the picture itself, so a tall one leaves no empty band beside it */
	min-width: 0;
	max-width: 640px;
}
/* Every picture is shown whole, at its own proportions — nothing is cropped. A portrait photograph at the
   full 640px would stand 960px tall, so the height is capped and a tall picture narrows instead. */
.vcap-media img { display: block; width: auto; max-width: 100%; height: auto; max-height: 560px; }
/* The frame is exactly the picture's size and clips it, so the hover zoom grows the picture inside its own
   edges. It uses the site's [data-hover-zoom] lean (main.js reports the pointer); the amount is kept smaller
   than the photographs elsewhere take, since the whole picture is on show here rather than a crop of it. */
.vcap-frame { display: block; overflow: hidden; }
.vcap-frame[data-hover-zoom] img {
	transform: translate(calc(var(--lean-x) * 4px), calc(var(--lean-y) * 4px)) scale(var(--lean-scale, 1));
	transition: transform .4s cubic-bezier(.16, 1, .3, 1);
}
.vcap-frame[data-hover-zoom]:hover img { --lean-scale: 1.05; }
@media (prefers-reduced-motion: reduce) { .vcap-frame[data-hover-zoom] img { transition: none; transform: none; } }
.vcap-copy { flex: 0 1 460px; min-width: 0; }
/* A portrait picture takes the narrow slot a text column takes in the rows around it, centred in it, and its
   copy takes the wide slot a landscape picture takes. The pair is then the same width as every other row, so
   the portrait sits on the centre line of the text above and below it instead of drifting with its own width. */
.vcap--tall .vcap-media { flex: 0 1 460px; max-width: none; display: flex; justify-content: center; }
.vcap--tall .vcap-copy { flex: 0 1 640px; }
/* Fusion: the picture is drawn a gap's width further out from its copy, as asked, without moving the copy.
   `left` shifts only how it is painted, so the row keeps its centring. Only while the row is side by side —
   stacked, the shift would push the picture off the screen's edge. */
@media (min-width: 1240px) {
	.vcap--nudge .vcap-media { position: relative; left: calc(-1 * clamp(32px, 4vw, 64px)); }
	/* On Site and Hardware: the copy moves out by the gap it already has from its portrait — the row gap plus
	   the margin either side of the 373px-wide picture inside its 460px slot ((460 - 373.33) / 2 = 43.33px). */
	.vcap--tall .vcap-copy { position: relative; left: calc(43.33px + clamp(32px, 4vw, 64px)); }
}
.vcap-title { margin-top: 0; max-width: 18ch; font-size: clamp(28px, 3vw, 40px); font-weight: 400; line-height: 1.15; letter-spacing: -0.03em; }
.vcap-text { margin-top: 16px; max-width: 46ch; font-size: 16px; line-height: 1.7; color: #6c777c; }
.page-head-body { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--split-gap) var(--gutter); margin-top: var(--split-gap); align-items: start; }
.page-head .lead { grid-column: 1 / span 5; }
/* Lifted out of the row below the title and hung from the rule above it instead, after the reference: the
   picture starts on that line and runs down past the title beside it.
   Its width is what columns 7-12 of the twelve would have been: half the content, less half a gap. */
.page-head-media {
	position: absolute;
	top: var(--head-top);
	right: var(--pad);
	width: calc((100% - 2 * var(--pad)) / 2 - var(--gutter) / 2);
}
/* The number stays on top of it rather than behind. */
.page-head .badge { position: relative; z-index: 1; }
/* The picture's own 3:2, so it fills the frame with nothing cropped away. Held back from full strength and
   faded out towards its left edge, after the reference, so it settles into the page instead of sitting on it
   as a hard rectangle — the title and lead beside it stay the loudest thing here. */
.page-head-media .ph {
	aspect-ratio: 3 / 2;
	opacity: 0.55;
	-webkit-mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 0.35) 22%, #000 62%);
	mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 0.35) 22%, #000 62%);
}
/* The comb of bars, after the reference: laid across the foot of the picture, starting and ending with it. It
   is one strip painted with the hero panel's own gradient — nothing new is introduced — light on the left to
   full colour on the right, then cut into bars by a mask.
   Decoration only: it carries no meaning and is hidden from readers. */
.bar-strip {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 39px;   /* the bars' own height, kept shorter than the band the reference draws */
	/* From the hero panel's near-white to a step past its blue-grey, so the right-hand end reads clearly
	   against the picture behind it. Same hue throughout — only the depth changes. */
	background: linear-gradient(90deg, #fdfdfd 0%, #dde5e9 55%, #c0ccd2 100%);
	-webkit-mask-image: repeating-linear-gradient(90deg, #000 0 10px, transparent 10px 24px);
	mask-image: repeating-linear-gradient(90deg, #000 0 10px, transparent 10px 24px);
	/* The bars drift to the right, as in the reference. The mask is what is moving, by exactly the 24px it
	   repeats over, so the loop has no seam — the bars simply keep coming. The gradient painted underneath
	   stays put, so a bar deepens as it travels right rather than carrying its own colour along.
	   The stylesheet's reduced-motion rule at the end switches this off with every other animation. */
	-webkit-mask-position: 0 0;
	mask-position: 0 0;
	animation: bar-drift 0.5s linear infinite;   /* 48px a second — two bar periods a second */
}
@keyframes bar-drift {
	to { -webkit-mask-position: 24px 0; mask-position: 24px 0; }
}

/* The copy is shorter than the block of pictures beside it, so it is centred against them rather than
   hung from the top — it stays on their midline at any width. Once the two stack, each is alone in its
   row and this has nothing to do. */
.about-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--split-gap) var(--gutter); align-items: center; }
/* Four pictures in a square, with the years badge sitting across the middle of them. */
.about-shots {
	grid-column: 1 / span 6;
	position: relative;
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 12px;
}
.about-shot { aspect-ratio: 3 / 4; }
/* A picture that answers the pointer: it grows a little under it and leans the way the pointer moves. The
   frame already clips, and the picture is scaled past the frame first, so leaning never uncovers a corner.
   main.js writes --lean-x / --lean-y as the pointer's position across the frame, -1 to 1. */
[data-hover-zoom] { --lean-x: 0; --lean-y: 0; }
[data-hover-zoom] .ph-shot {
	transform: translate(calc(var(--lean-x) * 6px), calc(var(--lean-y) * 6px)) scale(var(--lean-scale, 1));
	transition: transform .4s cubic-bezier(.16, 1, .3, 1);
	will-change: transform;
}
[data-hover-zoom]:hover .ph-shot { --lean-scale: 1.08; }
.years-badge {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 2;
	margin: 0;
	padding: 20px 28px;
	min-width: 190px;
	background: var(--ink);
	color: var(--white);
	text-align: center;
}
.years-badge-num { display: block; font-family: var(--digit); font-size: clamp(34px, 3.4vw, 54px); font-weight: 600; line-height: 1; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.years-badge-label { display: block; margin-top: 6px; font-size: 12px; color: var(--muted-2); }

.about-copy { grid-column: 8 / span 5; }
.about-copy .label { grid-column: auto; padding-top: 0; }
.about-title {
	margin: 18px 0 0;
	font-size: clamp(28px, 3.2vw, 48px);
	font-weight: 400;
	line-height: 1.15;
	letter-spacing: -0.03em;
}
.about-text { margin: 20px 0 0; font-size: clamp(15px, 1.2vw, 17px); line-height: 1.6; color: #56636a; }
/* The reference's ticked list. The tick is drawn, so it needs no icon file and takes the text colour. */
.check-list { margin: clamp(24px, 3vw, 40px) 0 0; padding: 0; list-style: none; }
.check-list li { position: relative; padding: 10px 0 10px 30px; border-top: var(--rule-w) solid var(--line); font-size: 15px; }
.check-list li:last-child { border-bottom: var(--rule-w) solid var(--line); }
.check-list li::before {
	content: "";
	position: absolute;
	left: 4px;
	top: 17px;
	width: 11px;
	height: 6px;
	border-left: 1.5px solid var(--ink);
	border-bottom: 1.5px solid var(--ink);
	transform: rotate(-45deg);
}

/* The four figures, side by side and ruled off from each other. */
.figure-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--gutter); margin: 0; padding: 0; list-style: none; }
.figure { padding: clamp(24px, 3vw, 40px) clamp(16px, 2vw, 28px); border: var(--rule-w) solid var(--line); }
/* tabular figures: the number changes on every frame while it counts up, and proportional digits would make
   it twitch sideways as it went */
.figure-num { margin: 0 0 10px; font-family: var(--digit); font-size: clamp(28px, 3.4vw, 54px); font-weight: 600; line-height: 1; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.figure .small { color: var(--muted); }

/* ---------- About: process ---------- */
/* The heading is one sentence in two weights — the opening in the page's quiet grey, the question itself in
   ink — so the emphasis is in the markup (<strong>) rather than in two separate elements that would break
   onto their own lines. */
/* Set in caps by the stylesheet, not by the markup, so the sentence stays readable where it is written and
   a translation is not forced into capitals that may not suit its script. Caps need less negative tracking
   than lower case at this size — every letter is now full height, and -0.03em closed them up. */
/* --head-gap is measured to the top of the text BOX, and a box holds more empty space above its letters the
   larger the type is. At Home's 216px that leaves 19px of air over the caps; at this title's size, only 10.
   The same rule therefore looks tighter here, so the difference is added back as a share of this title's own
   font size — 0.17em, which is Home's air minus this one's, given that its type runs about 4.15x larger
   across the fluid range. The gap to the letters then matches Home's at any width. */
.process-title { flex: 1; margin-top: 0.17em; text-align: center; font-size: clamp(26px, 3.6vw, 54px); font-weight: 300; line-height: 1.12; letter-spacing: -0.015em; text-transform: uppercase; color: var(--muted); }
.process-title strong { font-weight: 600; color: var(--ink); }
/* Once the reveal has rebuilt this title the <strong> is gone and its words are marked .key instead,
   so the weight is restated here. Both selectors are kept: the title is bold before the script runs. */
.process-title .w.key { font-weight: 600; }
/* Centred under the title, so the two read as one heading block; the steps below stay left, where a column
   of copy belongs. The auto margins centre the box itself — text-align only centres the lines inside it.
   The width is in em rather than px so it holds the sentence on one line at every size the fluid font takes:
   the line needs 20.1em whatever that em is worth, and 21em leaves a little room. Measured at one line from
   1440px down to 390px — a longer sentence here will want that number checked again. */
.process-lead { margin: clamp(40px, 5vw, 80px) auto 0; max-width: 21em; text-align: center; font-size: clamp(18px, 1.6vw, 24px); line-height: 1.35; letter-spacing: -0.02em; }
/* Two across, so the four steps read as a square block rather than a row of three with one hanging under it.
   Each step is ruled off at the top, all four in the same hairline.
   It stops widening at 1730px, a little short of the page's own 1800 cap, and centres in the space that
   leaves: past that width two columns of running text get too long a measure to read comfortably. */
/*
 * HOW IT WORKS — a heading that stays put beside a column of step cards.
 *
 * This replaced a 2×2 grid of four bare steps. The grid gave each step the
 * same weight and the same silence; read down a page it was four paragraphs
 * with numbers on. A column puts them in the order they happen, and leaves
 * the left half to say once what the four of them are for.
 *
 * The accent is --ink, not the lime of the reference this was taken from.
 * This site has no accent colour on purpose — the palette is ink, muted and
 * one hairline, and colour arrives in photographs. A single borrowed green
 * would be the only one on the site and would have to be explained every
 * time something else was designed.
 */

/*
 * One grid, not two columns side by side.
 *
 * The heading and the cards have to be released at the same moment, and a
 * sticky element is released when its container's bottom reaches its own
 * height — so two boxes of different heights in the same container always
 * come off at different times. The heading was 240px against the cards' 269
 * and lagged them by 29px of scroll; below about 1400px it wraps taller than
 * a card and went first instead. No single number tracks both.
 *
 * Putting the heading in the cards' own grid settles it with no number at
 * all. It takes row one, the rows are equalised, and it is stretched to its
 * row — so it is exactly as tall as a card, whichever of the two is the
 * taller at this width, and the two are let go together.
 */
.how { --how-stick: calc(var(--nav-h) + clamp(20px, 2.4vw, 36px)); display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); grid-auto-rows: 1fr; gap: clamp(12px, 1.2vw, 20px) var(--grid-gap); max-width: var(--grid-w); margin: clamp(48px, 6vw, 96px) auto 0; }
/* The left half is a caption, not a column of its own: it stays with the first
   card while the rest scroll past it. Both halves stop at the same line, so
   the heading and the card that has arrived sit level. */
.how-intro { grid-column: 1; grid-row: 1; position: sticky; top: var(--how-stick); align-self: stretch; }
.how-title { margin: clamp(16px, 2vw, 28px) 0 0; font-size: clamp(26px, 2.8vw, 44px); font-weight: 600; line-height: 1.08; letter-spacing: -0.02em; text-transform: uppercase; }
.how-lead { margin: clamp(20px, 2.4vw, 32px) 0 0; max-width: 26em; font-size: clamp(15px, 1.2vw, 18px); line-height: 1.6; color: #6c777c; }

/*
 * The cards stack: each one stops at the same line and the next slides over
 * it, so a reader going down the page deals with one step at a time instead
 * of four at once.
 *
 * It is two declarations and no script — every card is sticky at the same
 * offset, and z-index rises with the order, so a later card paints over an
 * earlier one. The rows are equalised because covering has to be complete: a
 * card shorter than the one beneath it leaves a strip of the last step
 * showing under the current one, which reads as a bug rather than a stack.
 */
/*
 * Nothing is held after the fourth card, on purpose.
 *
 * An empty row was tried below it, so that the fourth step would be held at
 * the line for as long as the other three. It works, and it leaves the fourth
 * card alone at the top of a screen of nothing while the room it is held
 * through scrolls by. The section reads better ending on the last step and
 * moving on: the four cards all come off the line together and go up with the
 * heading beside them.
 *
 * (If it is ever wanted back: an empty row, not padding. The rectangle a
 * sticky child is held inside is the container's CONTENT box, and padding
 * sits outside it — padding moved the section's bottom edge and changed
 * nothing at all about the stack.)
 */
/* The list keeps its tag and loses its box, so the cards are items of the grid
   above rather than of a grid inside it — which is what lets the heading share
   a row with the first card. role="list" is in the markup because this is the
   one thing display:contents has historically taken away. */
.how-steps { display: contents; }
/* The number and the title share a line — the number is a label on the title,
   not a thing above it. The body then spans both columns underneath. */
.how-step { grid-column: 2; position: sticky; top: var(--how-stick); display: grid; grid-template-columns: auto minmax(0, 1fr); align-content: start; align-items: center; column-gap: clamp(14px, 1.4vw, 22px); padding: clamp(18px, 1.8vw, 26px); border-radius: 18px; background: var(--ph); }
/* Order of paint. Written out past the four steps there are, so adding one
   does not quietly put it behind the rest. */
.how-step:nth-child(1) { z-index: 1; }
.how-step:nth-child(2) { z-index: 2; }
.how-step:nth-child(3) { z-index: 3; }
.how-step:nth-child(4) { z-index: 4; }
.how-step:nth-child(5) { z-index: 5; }
.how-step:nth-child(6) { z-index: 6; }
.how-step-body { grid-column: 1 / -1; }
/* The first card carries the weight, the way the reference has it — it is the
   step the reader is being started on, not a state anything toggles. */
.how-step:first-child { background: var(--ink); }

.how-step-no {
	display: grid;
	place-items: center;
	width: clamp(38px, 3.4vw, 48px);
	aspect-ratio: 1;
	margin: 0;
	border-radius: 50%;
	background: var(--ink);
	color: var(--white);
	font-family: var(--digit);
	font-size: clamp(15px, 1.3vw, 18px);
}
.how-step:first-child .how-step-no { background: var(--white); color: var(--ink); }

.how-step-title { margin: 0; font-size: clamp(16px, 1.5vw, 22px); font-weight: 600; letter-spacing: -0.01em; text-transform: uppercase; }
.how-step:first-child .how-step-title { color: var(--white); }

/* The inner panel is what makes the dark card legible, so the light cards
   take it too — one shape, two colourways, rather than two layouts. */
.how-step-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(16px, 1.6vw, 28px); margin-top: clamp(14px, 1.4vw, 20px); padding: clamp(16px, 1.6vw, 24px); border-radius: 12px; background: var(--white); }
.how-step-text { margin: 0; font-size: clamp(14px, 1.1vw, 16px); line-height: 1.6; color: #6c777c; }
.how-step-points { margin: 0; padding: 0 0 0 clamp(16px, 1.6vw, 28px); border-left: var(--rule-w) solid var(--line); list-style: none; font-size: clamp(14px, 1.1vw, 16px); line-height: 1.6; }
/* Deleting the placeholder bullets has to leave a finished card, not a rule
   with nothing beside it. */
.how-step-points:empty { display: none; }
.how-step-body:has(.how-step-points:empty) { grid-template-columns: minmax(0, 1fr); }
.how-step-points li { position: relative; padding-left: 18px; }
.how-step-points li + li { margin-top: clamp(8px, 0.8vw, 12px); }
.how-step-points li::before { content: ""; position: absolute; left: 0; top: 0.62em; width: 5px; height: 5px; border-radius: 50%; background: var(--ink); }

/*
 * The two columns stop earlier than the rest of the page does.
 *
 * Everything else here breaks at 1023px. This section cannot wait that long:
 * the heading's own column is about 400px at 1070, where HOW OUR AERIAL
 * MAPPING PROCESS WORKS sets as five lines of two words beside cards that are
 * also too narrow for their two halves. The reference stacks at 1024, but it
 * is not carrying this heading at this size.
 *
 * Stacked, nothing is held and nothing overlaps: stacking needs a column
 * beside something that holds still, and in one column a card covering the
 * one above it would simply hide the step just read.
 */
@media (max-width: 1199px) {
	.how { grid-template-columns: minmax(0, 1fr); grid-auto-rows: auto; row-gap: clamp(24px, 3vw, 40px); }
	.how-intro { position: static; align-self: start; }
	.how-step { grid-column: 1; position: static; }
}

/* ---------- About: tools and the dark close ---------- */
/* Both sections are a quiet label with a wide row under it, so they share that shape: copy on the left, the
   way onward on the right, wrapping to one column when there is no room for two. */
/* The tools section is held to the process grid's measure and split on its columns, so the copy sits over
   the grid's left column and the button starts on its right one. */
/* Two columns: everything written on the left, the logo on the right. The title sits inside the left column
   rather than spanning the row, which is what lets the logo centre against the whole of what it illustrates
   — title, sentence and link together — instead of against the sentence alone. */
.tools-inner {
	max-width: var(--grid-w);
	margin-inline: auto;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	align-items: center;
	gap: clamp(28px, 4vw, 64px) var(--grid-gap);
}
/* FT Viewer closes on this block with the film straight under it — no empty drone stage between — so the
   section's own bottom padding is the whole gap: one --section-bottom from the logo to the film. */
.tools-text { max-width: 620px; font-size: clamp(18px, 1.8vw, 26px); line-height: 1.4; letter-spacing: -0.02em; }
/* start, not stretch: the link is only as wide as its words, standing on the column's left edge */
.tools-copy { display: grid; justify-items: start; gap: clamp(28px, 3vw, 44px); }
/* FT Viewer's mark is an icon, not a photograph, so it is not put in a .ph panel — it keeps its own
   proportions and a size a logo can hold, rather than being cropped to fill a box. The file carries its
   white background as transparency, so it sits on whatever colour the section is set to. */
.tools-logo { justify-self: center; display: block; width: 100%; max-width: 340px; }
.tools-logo img { display: block; width: 100%; height: auto; transition: transform 0.3s ease; }
/* The same lean the pictures give, kept smaller: a logo is a mark, not a photograph to push into. */
.tools-logo:hover img,
.tools-logo:focus-visible img { transform: scale(1.04); }
@media (prefers-reduced-motion: reduce) { .tools-logo img { transition: none; } }
/* The text link, built from the CTA pattern the client supplied: caps, no frame, a rule wiped on under the
   words and an arrow running forward beside them. The rule is always there at scaleX(0) and only its
   transform changes — anchored bottom right at rest and bottom left on hover, so it grows from the left
   going in and leaves to the right coming out, reading as one stroke travelling rather than a line that
   grows and shrinks back. It spans the label's padding box, so it runs the 15px past the words that the
   original does, stopping just short of the arrow. */
.cta { display: inline-flex; align-items: center; color: var(--ink); cursor: pointer; }
.cta-label { position: relative; padding: 0 15px 7px 0; font-size: 14px; letter-spacing: 4px; text-transform: uppercase; }
.cta-label::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 2px;
	background: currentColor;
	transform: scaleX(0);
	transform-origin: bottom right;
	transition: transform 0.25s ease-out;
}
.cta:hover .cta-label::after,
.cta:focus-visible .cta-label::after { transform: scaleX(1); transform-origin: bottom left; }
/* parked 8px back so it sits close to the words, and running out to its true place under the pointer */
.cta-arrow { flex: none; width: 30px; height: auto; fill: currentColor; transform: translateX(-8px); transition: transform 0.3s ease; }
.cta:hover .cta-arrow { transform: translateX(0); }
.cta:active .cta-arrow { transform: scale(0.9); }
@media (prefers-reduced-motion: reduce) { .cta-label::after, .cta-arrow { transition: none; } }
.next-body { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: clamp(28px, 4vw, 64px); margin-top: clamp(28px, 3vw, 48px); }
/* The pill. Outlined on a light ground, solid white on the dark one; the arrow is the same drawing the
   section arrows use, sized to the words beside it. */
.btn-pill {
	display: inline-flex;
	align-items: center;
	gap: 14px;
	flex: none;
	padding: 18px 34px;
	border: var(--rule-w) solid var(--line);
	border-radius: 999px;
	font-size: clamp(14px, 1.1vw, 17px);
	color: var(--ink);
	transition: background .25s, color .25s, border-color .25s;
}
.btn-pill:hover { background: var(--ink); border-color: var(--ink); color: var(--white); }
.btn-pill-arrow { width: 26px; height: auto; fill: currentColor; transition: transform .3s ease; }
.btn-pill:hover .btn-pill-arrow { transform: translateX(6px); }
.next-step { background: var(--ink); color: var(--white); }
.next-step .label { color: var(--muted); }
/* Two sizes of heading close the page. This one takes the same measure as .about-title further up — the
   same clamp, so they stay level at every width, not just where they were compared — and the dark section
   below it takes the larger one. Its tracking is eased off from the -0.035em lower case wants, because caps
   put every letter at full height and read tighter at the same value. */
.tools-title { margin: 0; max-width: 16em; font-size: clamp(28px, 3.2vw, 48px); font-weight: 600; line-height: 1.12; letter-spacing: -0.015em; text-transform: uppercase; }
.next-title { flex: 1 1 460px; max-width: 700px; font-size: clamp(30px, 4.4vw, 64px); font-weight: 300; line-height: 1.12; letter-spacing: -0.035em; }
.next-action { flex: none; display: flex; flex-direction: column; align-items: stretch; gap: 16px; }
.btn-pill-solid { justify-content: center; background: var(--white); border-color: var(--white); color: var(--ink); padding-inline: clamp(48px, 8vw, 130px); }
.btn-pill-solid:hover { background: var(--muted-2); border-color: var(--muted-2); color: var(--ink); }
.next-mail { align-self: center; font-size: 13px; letter-spacing: 0.04em; color: var(--muted); text-transform: none; }
.next-mail:hover { color: var(--white); }

@media (max-width: 1023px) {
	.about-shots, .about-copy { grid-column: 1 / -1; }
	.figure-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }

	/* Too narrow to stand the picture beside the words, so it goes behind them instead: it fills the whole
	   head, edge to edge, and the title and lead sit on it. Held back further than on a wide screen, where it
	   only had to sit beside the text rather than under it. */
	/* width:auto undoes the half-width the wide layout gives it — with a width set, inset:0 alone would not
	   stretch it across. */
	.page-head-media { position: absolute; top: var(--head-top); left: 0; right: 0; bottom: 0; width: auto; z-index: 0; }
	/* Keeps the same left-hand fade as the wide layout — the picture gathers on the right and gives way to the
	   words on the left — and fades downward as well, into the section below it. The two masks are combined by
	   intersection, so a pixel has to survive both. Held back further than on a wide screen, where the words sit
	   beside the picture rather than on it. */
	.page-head-media .ph {
		height: 100%;
		aspect-ratio: auto;
		opacity: 0.45;
		-webkit-mask-image:
			linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 0.35) 22%, #000 62%),
			linear-gradient(180deg, #000 55%, transparent 100%);
		mask-image:
			linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 0.35) 22%, #000 62%),
			linear-gradient(180deg, #000 55%, transparent 100%);
		-webkit-mask-composite: source-in;
		mask-composite: intersect;
	}
	/* A tall crop of a wide photograph keeps only a narrow slice of it. Left to itself that slice is the
	   bright, empty left of this picture; this brings the drone into it. */
	.page-head-media .ph-shot { object-position: 62% 55%; }
	.page-head .block-head, .page-head .lead { position: relative; z-index: 1; }
	.page-head .lead { grid-column: 1 / -1; max-width: 32rem; }
	.page-head-body { margin-top: clamp(24px, 4vw, 40px); }
}
@media (max-width: 767px) {
	.page-head { --head-top: calc(var(--nav-h) + 24px); }
	.bar-strip { height: 25px; }
	.years-badge { padding: 16px 20px; min-width: 150px; }
	.about-title { font-size: 28px; }
	.how-step-body { grid-template-columns: minmax(0, 1fr); }
	.how-step-points { padding-left: 0; border-left: 0; }
	/* One column: the pill and the mail stop being a side column and become the row's own last line. */
	.tools-inner { grid-template-columns: 1fr; align-items: flex-start; }
	/* Stacked, the logo keeps the centring it has above and only comes down in size. */
	.tools-logo { max-width: 220px; }
	.next-body { align-items: flex-start; }
	.btn-pill { width: 100%; justify-content: center; padding-inline: 24px; }
	.next-action { width: 100%; }
}

/* ---------- Footer ---------- */
.site-footer { background: var(--white); padding: 0 var(--pad); }
.footer-top, .footer-mid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 24px var(--gutter);
}
.footer-top { padding: 40px 0 56px; }
.footer-lead, .footer-cta p { font-size: clamp(18px, 1.6vw, 24px); line-height: 1.3; letter-spacing: -0.01em; max-width: 440px; }
.footer-cta { display: flex; flex-direction: column; align-items: flex-start; gap: 24px; }
/* Written as two lines, each a sentence; 440px would break the first one again before its <br>. */
.footer-cta p { max-width: none; }
.btn-dark {
	display: inline-block;
	padding: 12px 28px;
	background: var(--ink);
	color: var(--white);
	border-radius: 3px;
	font-size: 13px;
	transition: opacity .2s;
}
.btn-dark:hover { opacity: .85; }
.footer-mid { align-items: end; padding-bottom: 40px; }
/* A page that closes with its own call to action drops the footer's, leaving the logo row first in the
   footer with nothing above it. It takes the top padding the dropped block used to provide. */
.site-footer > .footer-mid:first-child { padding-top: 56px; }
/* The wordmark is set art, not type, so the footer holds it at a fixed width and lets its own proportions
   give the height. Its white ground is carried as transparency, so the footer's colour shows through. */
.footer-logo img { display: block; width: 100%; max-width: 190px; height: auto; }
.footer-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gutter); font-size: 13px; }
.footer-col-title { color: var(--muted); margin-bottom: 16px; }
.footer-cols li { padding: 2px 0; }
.footer-cols a:hover { text-decoration: underline; }
.footer-bottom {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 12px var(--gutter);
	padding: 16px 0 24px;
	border-top: 1px solid var(--line);
	font-size: 10px;
	color: #6c777c;
}
@media (max-width: 767px) {
	.footer-top, .footer-mid, .footer-bottom { grid-template-columns: 1fr; }
	.footer-mid .footer-logo { order: 2; }
}

/* ---------- Generic pages ---------- */
.page-main { padding: calc(var(--nav-h) + 60px) var(--pad) 80px; }
.entry + .entry { margin-top: 80px; }
.entry-title { font-size: clamp(40px, 8vw, 120px); margin-bottom: 32px; }
.entry-content { max-width: 760px; font-size: 18px; line-height: 1.7; }
.entry-content > * + * { margin-top: 1em; }

/* ---------- Mobile type & text position (matched to drone.riotters.com at 390px) ----------
   Read this block as a different kind of code from everything above. Above, the page is fluid and its spacing
   comes from the tokens in :root. Here every number is a measurement taken off the reference at 390px — the
   comment on each group records what was measured — so these values are literal on purpose and do not belong
   in the token scale. Changing a token above will not (and should not) move them. */
@media (max-width: 767px) {
	:root { --nav-h: 76px; }

	/* Header: 76px bar, 24px side gutter, menu 14px (64x28) */
	.site-nav { padding: 0 24px; }
	.menu-btn { padding: 2px 16px; font-size: 14px; line-height: 24px; }

	/* Hero: copy 24px under the panel, 20/28 light; Scroll Down below it on the left */
	.hero-foot { flex-direction: column; align-items: flex-start; gap: 8px; padding: 24px 0 0; }
	.hero-title { padding-right: 32px; font-size: 20px; font-weight: 300; line-height: 28px; letter-spacing: -0.4px; }
	.hero-title span { display: inline; }
	.scroll-down { min-height: 48px; font-size: 14px; font-weight: 500; line-height: 24px; }

	/* Intro: 48/56 light, from 81px */
	.intro { min-height: 691px; align-items: flex-start; padding: 81px 16px 0; }
	.intro-text { font-size: 48px; font-weight: 300; line-height: 56px; letter-spacing: -2.4px; }

	/* Section heads: badge on top right, title below it (80/80 light) */
	/* The head stacks here, which puts the badge between the rule and the title. One gap for all four sections,
	   so the rule-to-title distance matches across the page the way it does on a wide screen. */
	.block-head { flex-direction: column; align-items: flex-end; gap: var(--badge-gap); }
	.block-head .big-title { align-self: stretch; order: 1; }
	/* 80px is the measured size, and it holds from about 381px up. Below that the longest title, "Capabilities",
	   is a single word that cannot wrap — it would push the page sideways instead — so the size follows the
	   width from there down. All four titles shrink together, so they stay the same size as each other at any
	   width. The tracking is in em for the same reason: it has to shrink with the letters. */
	.big-title { margin-left: 0; font-size: min(80px, 21vw); font-weight: 300; line-height: 1; letter-spacing: -0.05em; }
	.badge { width: 44px; height: 32px; margin-top: 0; border-radius: 999px; font-size: 16px; line-height: 24px; }
	.label { gap: 8px; padding-top: 0; font-size: 12px; line-height: 24px; letter-spacing: 0.3px; }
	.split { --lead-size: 20px; --lead-lh: 28px; margin-top: 0; }
	.lead { font-weight: 300; letter-spacing: -0.2px; }

	/* 3. About (reference "Gear"): badge 24, title 80, label 200, lead 264 */
	#about { padding-bottom: 80px; }
	#about .block-head { padding-bottom: 40px; }
	#about .split { --row-gap: 40px; }

	/* 4. Stats (reference "drone lock"): number left 40/48, label right 12/24 */
	.numbers-visual .small { font-size: 12px; line-height: 24px; }
	.number { flex-direction: row; justify-content: space-between; align-items: flex-start; min-height: 0; padding: 24px 0; text-align: left; }
	.number-num { font-size: 40px; font-weight: 300; line-height: 48px; letter-spacing: -1.8px; }
	.number .small { font-size: 12px; line-height: 24px; letter-spacing: normal; text-align: right; }

	/* 5. Service: badge 49, title 121, tabs 241 (24/32), copy 322 (16/28) */
	#service { padding-top: 48px; }
	#service .tabs { margin-top: 40px; }
	.tab-list { gap: 16px; }
	.tab { padding-bottom: 16px; font-size: 24px; font-weight: 300; line-height: 32px; letter-spacing: -0.4px; }
	.tab-panel { padding-top: 32px; }
	.tab-copy { --copy-size: 16px; --copy-lh: 28px; --row-gap: 28px; padding-right: 24px; font-weight: 300; letter-spacing: -0.2px; }

	/* 6. Capabilities: badge 17, title 61 (76/76), list 161 (32/32), stat 32/32, text 16/24
	   (the measured 16px top padding is dropped: this section now takes the shared --head-gap like the rest) */
	.caps .block-head { padding-bottom: 24px; }
	.caps-step-title { font-size: 32px; font-weight: 300; line-height: 32px; letter-spacing: -1px; margin-bottom: 12px; }
	.caps-step-no { font-size: 12px; line-height: 16px; }
	.caps-desc { font-size: 16px; font-weight: 300; line-height: 24px; letter-spacing: -0.2px; }

	/* 7. Software: badge 33, title 89, label 185, lead 233; cards 12/20 · 14/20 · 12/18 */
	#software { padding-top: 32px; }
	#software .block-head { padding-bottom: 16px; }
	#software .split { --row-gap: 24px; }
	.card { gap: 12px; }
	.card-top .small, .card-foot { font-size: 12px; line-height: 20px; }
	.card-title { font-size: 14px; line-height: 20px; letter-spacing: -0.2px; }
	.card-desc { font-size: 12px; line-height: 18px; }


	/* Footer: lead 25, second line 145, button 213 (16/24, 48 high), columns 301 (15/28 · 15/20), legal 12/16 */
	.footer-top { gap: 24px; padding: 24px 0 40px; }
	.footer-lead, .footer-cta p { font-size: 18px; font-weight: 300; line-height: 24px; letter-spacing: -0.3px; }
	.footer-cta { gap: 20px; }
	.btn-dark { width: 100%; padding: 12px 16px; font-size: 16px; font-weight: 500; line-height: 24px; text-align: center; }
	.footer-cols { font-size: 15px; line-height: 20px; letter-spacing: -0.4px; }
	.footer-col-title { margin-bottom: 24px; line-height: 28px; }
	.footer-bottom { font-size: 12px; line-height: 16px; letter-spacing: normal; }
}

@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*, *::before, *::after { animation: none !important; transition: none !important; }
}
