/* ============================================================
   ARCHITECTURE FIRM WEBSITES — "DAYLIGHT STUDY"
   Adam's CodePen, verbatim. This page IS the theme, exactly like
   the demos under /portfolio/themes/: it loads none of the site's
   style.css / main.js, so the pen's world never collides with the
   site's. Only /css/vt-leaf.css comes in, and that touches nothing
   but view-transition pseudos.

   Concept: the page is a set of drawing sheets pinned to a wall.
   Scroll position is time of day. One sun angle drives the hero
   section-cut, the brise-soleil blades, and every sheet's shadow.
   The only accent colour on this page is daylight.

   WHAT WAS ADDED TO THE PEN, and nothing else:
     1. .masthead nav carries the site's seven real pages and wraps
        instead of hiding links (crawl parity — see CLAUDE.md).
     2. Blade stagger extended from 4 items to 7.
     3. Prose link styling (the pen had no inline links in copy).
     4. Honeypot + form status, to talk to /api/contact.
     5. html.no-runway twins, so a visitor who picked Reduced Motion
        anywhere on the site gets a still document here too.
     6. A-102, one new section UNDER the banner (2026-07-28, Adam) —
        /web-development/ #wd_fix's diagram ported: same structure,
        same geometry, same phases, this page's colours and copy.
        The pen's own sections are untouched, banner included. See
        A-102 · THE REQUIREMENTS DIAGRAM below.
   The palette, the type, the sheets, the daylight and every keyframe
   are the pen's. Do not "improve" them.
   ============================================================ */

/* --- Registered custom properties: these are what scroll animates --- */
@property --sun {
	syntax: "<angle>";
	inherits: true;
	initial-value: 34deg;
}

@property --wall {
	syntax: "<color>";
	inherits: true;
	initial-value: #b9bfbd;
}

@property --shadow-x {
	syntax: "<length>";
	inherits: true;
	initial-value: 14px;
}

@property --shadow-y {
	syntax: "<length>";
	inherits: true;
	initial-value: 22px;
}

@property --glow {
	syntax: "<number>";
	inherits: true;
	initial-value: 0.55;
}

:root {
	/* Palette — 6 values, no brand accent. Light is the accent. */
	--sheet: #e9eae7;
	/* drawing sheet */
	--sheet-2: #dfe1dd;
	/* sheet, shaded */
	--ink: #15181a;
	--graphite: #676c6e;
	--rule: #15181a1f;
	--daylight: #f0d7a4;
	--night: #0e1113;

	--f-display: "Archivo", system-ui, sans-serif;
	--f-body: "Spectral", Georgia, serif;
	--f-mono: "DM Mono", ui-monospace, monospace;

	--gutter: clamp(1.25rem, 4vw, 5rem);
	--sheet-max: 1240px;

	color-scheme: light;
}

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

/* The wall is also the canvas a cross-document view transition exposes
   between its two halves (see css/vt-leaf.css) — so html wears it too,
   and tracks it as the sun moves. */
html {
	background: var(--wall);
	scroll-behavior: smooth;
	/* the masthead is fixed; an in-page jump must clear it */
	scroll-padding-top: 5rem;
}

@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}
}

body {
	background: var(--wall);
	color: var(--ink);
	font-family: var(--f-body);
	font-weight: 300;
	font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
	line-height: 1.65;
	overflow-x: clip;
	/* NOT hidden — hidden can break position:sticky */
	-webkit-font-smoothing: antialiased;
}

/* --- Type ------------------------------------------------- */
h1,
h2,
h3, .h1 {
	font-family: var(--f-display);
	font-weight: 500;
	font-variation-settings: "wdth" 116;
	letter-spacing: -0.02em;
	line-height: 1.02;
	text-wrap: balance;
}

h1, .h1 {
	font-size: clamp(2.4rem, 6.4vw, 5.4rem);
	margin-bottom:2rem;
}

h2 {
	font-size: clamp(1.9rem, 3.6vw, 3.1rem);
}

h3 {
	font-size: clamp(1.1rem, 1.5vw, 1.35rem);
	font-variation-settings: "wdth" 108;
	letter-spacing: -0.01em;
}

p {
	max-width: 62ch;
}

.lede {
	font-size: clamp(1.1rem, 1.4vw, 1.4rem);
	line-height: 1.5;
	color: #2b2f31;
}

em {
	font-style: italic;
}

/* Mono annotation — used for real drawing-set metadata only */
.anno {
	font-family: var(--f-mono);
	font-weight: 300;
	font-size: 0.68rem;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--graphite);
}

a {
	color: inherit;
}

/* Inline links inside prose. The pen had none — this page is a landing
   page and links down to the posts that hold each argument, so they need
   to look like links without becoming a second accent colour. */
main p a,
.qa a,
.note a {
	text-decoration: underline;
	text-underline-offset: 4px;
	text-decoration-thickness: 1px;
	text-decoration-color: #15181a66;
	transition: text-decoration-color .2s ease;
}

main p a:hover {
	text-decoration-color: currentColor;
}

.night p a {
	text-decoration-color: #f0d7a480;
}

/* Accessibility floor */
:focus-visible {
	outline: 2px solid var(--ink);
	outline-offset: 3px;
}

.skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 200;
	background: var(--ink);
	color: var(--sheet);
	padding: .75rem 1.25rem;
	font-family: var(--f-mono);
	font-size: .8rem;
}

.skip:focus {
	left: 0;
}

/* --- Masthead --------------------------------------------- */
.masthead {
	position: fixed;
	inset: 0 0 auto 0;
	z-index: 100;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.9rem var(--gutter);
	background: color-mix(in oklab, var(--wall) 78%, transparent);
	backdrop-filter: blur(6px);
	border-bottom: 1px solid var(--rule);
}

.wordmark {
	font-family: var(--f-display);
	font-variation-settings: "wdth" 124;
	font-weight: 700;
	font-size: .9rem;
	letter-spacing: .22em;
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;
}

/* THE SITE'S SEVEN REAL PAGES, in chain order. This page is deliberately
   not in DECK_CHAIN and nothing in the nav points at it, so there is no
   aria-current link here — but every link is a real crawlable <a href>,
   which is the site's standing rule. The pen hid the first two links
   below 640px; a landing page cannot drop nav links, so the row WRAPS
   instead. Nothing is ever clipped or hidden. */
.masthead nav {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: .35rem clamp(.75rem, 1.8vw, 1.75rem);
}

.masthead nav a {
	text-decoration: none;
	white-space: nowrap;
}

@media (hover: hover) and (pointer: fine) {
	.masthead nav a:hover {
		color: var(--ink);
		text-decoration: underline;
		text-underline-offset: 4px;
	}
}

/* Scroll progress rendered as a drafting scale bar. Hidden unless the
   motion layer below is actually running — a progress bar frozen at zero
   (no scroll-timeline support, or reduced motion) is furniture. */
.scalebar {
	display: none;
	position: fixed;
	left: 0;
	bottom: 0;
	height: 3px;
	width: 100%;
	z-index: 100;
	background: var(--rule);
}

.scalebar span {
	display: block;
	height: 100%;
	width: 100%;
	transform-origin: 0 50%;
	transform: scaleX(0);
	background: var(--ink);
}

/* --- Sheets (the recurring layout unit) -------------------- */
.sheet {
	position: relative;
	width: min(100% - (var(--gutter) * 2), var(--sheet-max));
	margin-inline: auto;
	background: var(--sheet);
	border: 1px solid var(--rule);
	padding: clamp(2rem, 5vw, 5rem);
	box-shadow: var(--shadow-x) var(--shadow-y) 60px rgb(0 0 0 / 0.22);
}

.stack>*+* {
	margin-top: clamp(3.5rem, 9vh, 8rem);
}

.stack {
	padding-block: clamp(4rem, 12vh, 10rem);
}

/* Sheet title block, bottom-right, like a real drawing */
.titleblock {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 1rem;
	flex-wrap: wrap;
	border-top: 1px solid var(--rule);
	margin-top: clamp(2rem, 4vw, 3.5rem);
	padding-top: .75rem;
}

/* --- A-101 Hero: pinned section-cut ------------------------ */
.cover {
	height: 220vh;
}

/* the pin duration */
.cover-stage {
	position: sticky;
	top: 0;
	min-height: 100vh;
	display: grid;
	align-content: center;
	padding: clamp(5rem, 10vh, 7rem) var(--gutter) 3rem;
}

.cover-inner {
	width: min(100%, var(--sheet-max));
	margin-inline: auto;
}

.cover h1 {
	margin: 1.25rem 0 1.5rem;
	max-width: 16ch;
}

.cover .lede {
	max-width: 46ch;
}

.cover-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 1rem 1.75rem;
	margin-top: 2rem;
}

.section-cut {
	display: block;
	width: 100%;
	height: auto;
	margin-top: clamp(1.5rem, 4vh, 3rem);
	border: 1px solid var(--rule);
	background: var(--sheet);
}

/* The light entering the clerestory pivots with --sun */
.beam {
	transform: rotate(var(--sun));
	transform-origin: 372px 96px;
	opacity: var(--glow);
}

.sun-patch {
	opacity: var(--glow);
}

.poche {
	fill: var(--ink);
}

.line {
	fill: none;
	stroke: var(--ink);
	stroke-width: 1.25;
}

.line-thin {
	fill: none;
	stroke: var(--graphite);
	stroke-width: .75;
}

.dim {
	font-family: var(--f-mono);
	font-size: 9px;
	fill: var(--graphite);
	letter-spacing: .08em;
}

/* ============================================================
   A-102 · THE REQUIREMENTS DIAGRAM  (.req_track / .req_stage)

   /web-development/ #wd_fix's runway, PORTED (2026-07-28, Adam:
   "take the graphic from the web development page, change the
   content and the colours, and put it under the banner"). Every
   element, the geometry, the four phases and every percentage are
   that section's — .req_* maps one-to-one onto .fix_*. KEEP THE
   TWO IN STEP: a retune there is a retune here.

     .fix_track    -> .req_track     400svh, the named timeline
     .fix_stage    -> .req_stage     sticky, FULL-BLEED 100dvh
     .fix_stage_bg -> .req_stage_bg  the panel that sweeps the stage
     .fix_art      -> .req_art       head / diagram / thesis fit
     .fix_node     -> .req_node      the four cards
     .fix_callout  -> .req_callout   the leader lines out to the data
     .fix_thesis   -> .req_thesis    the closing line

   THE PANEL COVERS THE WHOLE 100% x 100dvh STAGE, not the diagram
   box (Adam, same day — the first build had it bounded to the
   drawing and that was the tell that it was not the same graphic).
   `inset: 0` on a full-bleed stage, and the stage's overflow:
   hidden is what it slides up from underneath.

   WHAT CHANGES IS THE CLOTHES AND THE COPY, and nothing else. Navy
   becomes paper: the stage is the shaded wall and the panel is a
   drawing SHEET rising over it, lit along its top edge in daylight
   where #wd_fix runs a --c1/--c2 beam. Archivo and DM Mono for
   Orbitron and JetBrains, drafting square corners for the pen's
   rx="12" and 16px radius. The palette is the page's six values;
   no new accent enters.

   Enhanced-only, like everything else in this file: without
   animation-timeline there is no track and no pin, and the section
   lays out as one settled screen with the diagram fully drawn. The
   html.no-runway twins at the foot of the file do the same for a
   visitor who chose Reduced Motion anywhere on the site.
   ============================================================ */
.req_track {
	position: relative;
}

/* min-height, not height: this is the settled geometry every state
   that is NOT scrubbing falls back to. The motion layer turns it into
   the sticky pin. The stage is the WALL, a step darker than the sheet
   that rises over it, so the sweep has something to read against. */
.req_stage {
	min-height: 100dvh;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: clamp(4.5rem, 9vh, 6.5rem) var(--gutter) clamp(2rem, 5vh, 3rem);
	background: color-mix(in oklab, var(--wall) 88%, #000);
	border-block: 1px solid var(--rule);
}

/* #wd_fix's .fix_stage_bg. There it is a navy panel; here it is a
   drawing sheet being pinned up — same event, this page's material. */
.req_stage_bg {
	position: absolute;
	inset: 0;
	z-index: 0;
	background: linear-gradient(180deg, var(--sheet) 0%, var(--sheet-2) 100%);
}

/* The sheet's lit top edge. #wd_fix runs a --c1/--c2 beam with a --c1
   bloom; daylight is this page's only accent, so it is the one colour
   that lands here. */
.req_stage_bg::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 2px;
	background: linear-gradient(90deg, transparent, var(--daylight), var(--ink), transparent);
	box-shadow: 0 0 18px var(--daylight);
}

/* HEAD AT THE TOP, THESIS AT THE BOTTOM, DIAGRAM SIZED OFF WHAT IS
   LEFT — #wd_fix's three-band fit, and it is there for a reason worth
   carrying over: the stage is a hard 100dvh with overflow: hidden, and
   a diagram sized off WIDTH alone will overflow a short window and get
   its head and thesis clipped off at the edges. Nothing here is
   positioned by a magic number, so a change to the copy, the type
   scale or the viewport redistributes instead of clipping — and
   clipping is what NO INVISIBLE WALLS forbids. */
.req_art {
	position: relative;
	z-index: 1;
	width: 100%;
	max-width: var(--sheet-max);
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	flex: 1 1 auto;
	min-height: 0;
}

.req_head {
	text-align: center;
}

.req_head h2 {
	margin-top: .5rem;
	max-width: 20ch;
}

/* `flex: 0 1 auto` + `min-height: 0` is the half of the fit that lets
   the diagram GIVE: a short window takes height off the drawing rather
   than off the copy above and below it. preserveAspectRatio defaults
   to `xMidYMid meet`, so the art scales down and stays centred —
   nothing to crop. No grow: a tall window leaves air around it. */
.req_art svg {
	display: block;
	width: 100%;
	height: auto;
	flex: 0 1 auto;
	min-height: 0;
}

/* Paint lives here, not on presentation attributes, so the diagram
   tracks the palette. Geometry stays on the elements. */
.req_node rect {
	fill: rgb(255 255 255 / .55);
	stroke: var(--ink);
	stroke-width: 1.5;
}

.req_node text {
	fill: var(--ink);
	font-family: var(--f-display);
	font-weight: 500;
	font-variation-settings: "wdth" 116;
	font-size: 15px;
	letter-spacing: 1px;
}

/* Two lines in the same 96px box need the smaller size. */
.req_node--stacked text {
	font-size: 13.5px;
}

.req_callout line {
	stroke: var(--ink);
	stroke-width: 1.5;
}

.req_callout circle {
	fill: var(--ink);
}

/* fill-opacity rather than --graphite: at 12.5px graphite lands near
   4:1 on the sheet, under the AA floor. Ink at .78 sits the notes
   below the card labels without dropping them out of contrast. */
.req_callout text {
	fill: var(--ink);
	fill-opacity: .78;
	font-family: var(--f-mono);
	font-size: 12.5px;
}

.req_thesis {
	text-align: center;
	font-family: var(--f-display);
	font-weight: 500;
	font-variation-settings: "wdth" 116;
	font-size: clamp(1rem, 1.9vw, 1.35rem);
	letter-spacing: -.01em;
	line-height: 1.35;
	max-width: 34ch;
	text-wrap: balance;
}

/* #wd_fix puts this em in --c2. Daylight is this page's --c2, but at
   #f0d7a4 on the sheet it is ~1.2:1 — unreadable as text. So the
   emphasis wears the same hue taken down to ~5.6:1. Roman, not
   italic: the colour is already carrying the emphasis. */
.req_thesis em {
	color: #8a6a25;
	font-style: normal;
}

/* Without fill-box an SVG child scales about the ROOT viewBox origin,
   not its own centre, so the pops fly across the diagram instead of
   growing in place. */
.req_node,
.req_callout circle {
	transform-box: fill-box;
	transform-origin: center;
}

/* --- Two-column editorial ---------------------------------- */
.split {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: clamp(2rem, 5vw, 5rem);
	align-items: start;
}

@media (max-width: 860px) {
	.split {
		grid-template-columns: 1fr;
	}
}

.note {
	border-left: 2px solid var(--ink);
	padding-left: 1.25rem;
}

.note+.note {
	margin-top: 2rem;
}

/* --- A-401 Brise-soleil (blades angle with the sun) -------- */
.blades {
	display: grid;
	gap: .4rem;
}

.blade {
	display: grid;
	grid-template-columns: 5.5rem 1fr 1.6fr;
	gap: clamp(.75rem, 2vw, 2rem);
	align-items: baseline;
	padding: 1.4rem 0;
	border-top: 1px solid var(--rule);
	transform-origin: 50% 0;
}

.blade:last-child {
	border-bottom: 1px solid var(--rule);
}

@media (max-width: 720px) {
	.blade {
		grid-template-columns: 1fr;
		gap: .4rem;
	}
}

/* --- A-501 Scope ------------------------------------------ */
.work {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 1px;
	background: var(--rule);
	border: 1px solid var(--rule);
}

@media (max-width: 860px) {
	.work {
		grid-template-columns: 1fr;
	}
}

.project {
	background: var(--sheet);
	padding: clamp(1.5rem, 3vw, 2.25rem);
}

.project svg {
	display: block;
	width: 100%;
	height: auto;
	margin-bottom: 1.25rem;
}

.project p {
	font-size: .95rem;
}

/* --- A-601 Questions -------------------------------------- */
.qa {
	border-top: 1px solid var(--rule);
	padding: clamp(1.75rem, 3vw, 2.5rem) 0;
}

.qa:last-of-type {
	border-bottom: 1px solid var(--rule);
}

.qa h3 {
	margin-bottom: .6rem;
	max-width: 44ch;
}

/* --- A-701 Contact: designed night, not an interpolation --- */
.night {
	background: var(--night);
	color: var(--sheet);
	border-color: #ffffff1f;
}

.night h2 {
	color: var(--sheet);
}

.night p {
	color: #b6bab8;
}

.night .anno {
	color: #8c9391;
}

.night .titleblock {
	border-color: #ffffff1f;
}

.night :focus-visible {
	outline-color: var(--daylight);
}

.field {
	display: grid;
	gap: .4rem;
	margin-bottom: 1.25rem;
}

.field label {
	font-family: var(--f-mono);
	font-size: .68rem;
	letter-spacing: .14em;
	text-transform: uppercase;
	color: #8c9391;
}

.field input,
.field textarea {
	font: inherit;
	font-family: var(--f-body);
	font-size: 1rem;
	background: transparent;
	color: var(--sheet);
	border: 0;
	border-bottom: 1px solid #ffffff33;
	padding: .6rem 0;
}

.field input:focus,
.field textarea:focus {
	outline: 0;
	border-bottom-color: var(--daylight);
}

/* The honeypot. Bots fill every field; humans never see this one, and
   screen readers are told to skip it. Not display:none — some bots skip
   what is not rendered. Matches functions/api/contact.js's `company`. */
.hp_field {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.form_status {
	font-family: var(--f-mono);
	font-size: .72rem;
	letter-spacing: .1em;
	text-transform: uppercase;
	margin-top: 1rem;
	min-height: 1.2em;
	color: #8c9391;
}

.form_status.ok {
	color: var(--daylight);
}

.form_status.err {
	color: #e9a08f;
	text-transform: none;
	letter-spacing: .02em;
	font-size: .8rem;
}

.cta {
	display: inline-block;
	text-decoration: none;
	font-family: var(--f-mono);
	font-size: .78rem;
	letter-spacing: .16em;
	text-transform: uppercase;
	background: var(--daylight);
	color: var(--night);
	border: 0;
	padding: 1.05rem 2.5rem;
	cursor: pointer;
	transition: filter .25s ease, transform .25s ease;
}

.cta:hover {
	filter: brightness(1.08);
	transform: translateY(-1px);
}

.cta[disabled] {
	opacity: .55;
	cursor: wait;
	transform: none;
}

.cta.on-light {
	background: var(--ink);
	color: var(--daylight);
}

.colophon {
	padding: 2.5rem var(--gutter) 3.5rem;
	border-top: 1px solid var(--rule);
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
}

.colophon a {
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* ============================================================
   MOTION LAYER - 3D LUXURY EDITION
   ============================================================ */
@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {

		/* 1. The Sun: Keeps the global color/shadow shifting */
		:root {
			animation: daylight linear both;
			animation-timeline: scroll(root block);
		}

		@keyframes daylight {
			0% {
				--sun: 6deg;
				--wall: #aab2b1;
				--shadow-x: 34px;
				--shadow-y: 12px;
				--glow: .30;
			}

			35% {
				--sun: 44deg;
				--wall: #c3c8c5;
				--shadow-x: 16px;
				--shadow-y: 26px;
				--glow: .62;
			}

			70% {
				--sun: 92deg;
				--wall: #a9aeaa;
				--shadow-x: -16px;
				--shadow-y: 26px;
				--glow: .55;
			}

			100% {
				--sun: 126deg;
				--wall: #6f7573;
				--shadow-x: -34px;
				--shadow-y: 10px;
				--glow: .22;
			}
		}

		.scalebar {
			display: block;
		}

		.scalebar span {
			animation: measure linear both;
			animation-timeline: scroll(root block);
		}

		@keyframes measure {
			to {
				transform: scaleX(1);
			}
		}

		/* 2. Hero Section: Dramatic 3D fly-in */
		.cover-stage {
			perspective: 1200px;
		}

		/* 1b. A-102 · THE REQUIREMENTS DIAGRAM — #wd_fix's track, pin,
		   phases and percentages, all four unchanged. 400 - 100 = 300svh
		   of scrub, and `contain 0%` is always "the pin just locked"
		   wherever the section sits in the page, so the percentages need
		   no retuning for position. See the A-102 block above. */
		.req_track {
			height: 400svh;
			view-timeline: --req-runway block;
		}

		.req_stage {
			position: sticky;
			top: 0;
			height: 100dvh;
		}

		.req_node,
		.req_stage_bg,
		.req_callout line,
		.req_callout circle,
		.req_callout text,
		.req_thesis {
			animation-timeline: --req-runway;
			animation-timing-function: cubic-bezier(.4, 0, .2, 1);
			animation-fill-mode: both;
		}

		/* The undrawn state is set HERE, not as a presentation attribute,
		   so a browser that cannot scrub renders the callouts fully drawn
		   instead of with four invisible lines. pathLength="1" on each is
		   what lets one shared dashoffset keyframe drive all four. */
		.req_callout line {
			stroke-dasharray: 1;
			stroke-dashoffset: 1;
		}

		/* P1 — the four cards assemble, staggered (0 to 23.5%) */
		.req_node:nth-of-type(1) {
			animation-name: req-fade-up-scale;
			animation-range: contain 0% contain 13%;
		}

		.req_node:nth-of-type(2) {
			animation-name: req-fade-up-scale;
			animation-range: contain 3.5% contain 16.5%;
		}

		.req_node:nth-of-type(3) {
			animation-name: req-fade-up-scale;
			animation-range: contain 7% contain 20%;
		}

		.req_node:nth-of-type(4) {
			animation-name: req-fade-up-scale;
			animation-range: contain 10.5% contain 23.5%;
		}

		/* P2 — the sheet sweeps up the whole stage behind them (20 to 38%) */
		.req_stage_bg {
			animation-name: req-slide-up-bg;
			animation-range: contain 20% contain 38%;
		}

		/* P3 — the leaders draw out to the data in sequence (38 to 85%).
		   Each is dot, then line, then note, overlapping by a few percent
		   so the note lands as the line arrives rather than after it. */
		.req_callout:nth-of-type(1) line {
			animation-name: req-draw-line;
			animation-range: contain 38% contain 49%;
		}

		.req_callout:nth-of-type(1) circle {
			animation-name: req-pop-dot;
			animation-range: contain 38% contain 43%;
		}

		.req_callout:nth-of-type(1) text {
			animation-name: req-fade-in;
			animation-range: contain 42% contain 49%;
		}

		.req_callout:nth-of-type(2) line {
			animation-name: req-draw-line;
			animation-range: contain 50% contain 61%;
		}

		.req_callout:nth-of-type(2) circle {
			animation-name: req-pop-dot;
			animation-range: contain 50% contain 55%;
		}

		.req_callout:nth-of-type(2) text {
			animation-name: req-fade-in;
			animation-range: contain 54% contain 61%;
		}

		.req_callout:nth-of-type(3) line {
			animation-name: req-draw-line;
			animation-range: contain 62% contain 73%;
		}

		.req_callout:nth-of-type(3) circle {
			animation-name: req-pop-dot;
			animation-range: contain 62% contain 67%;
		}

		.req_callout:nth-of-type(3) text {
			animation-name: req-fade-in;
			animation-range: contain 66% contain 73%;
		}

		.req_callout:nth-of-type(4) line {
			animation-name: req-draw-line;
			animation-range: contain 74% contain 85%;
		}

		.req_callout:nth-of-type(4) circle {
			animation-name: req-pop-dot;
			animation-range: contain 74% contain 79%;
		}

		.req_callout:nth-of-type(4) text {
			animation-name: req-fade-in;
			animation-range: contain 78% contain 85%;
		}

		/* P4 — the closing line, on the last of the track (86 to 100%) */
		.req_thesis {
			animation-name: req-slide-up-fade;
			animation-range: contain 86% contain 100%;
		}

		@keyframes req-fade-up-scale {
			0% {
				opacity: 0;
				transform: translateY(22px) scale(.96);
			}

			100% {
				opacity: 1;
				transform: translateY(0) scale(1);
			}
		}

		@keyframes req-slide-up-bg {
			0% {
				transform: translateY(100%);
			}

			100% {
				transform: translateY(0%);
			}
		}

		@keyframes req-draw-line {
			0% {
				stroke-dashoffset: 1;
			}

			100% {
				stroke-dashoffset: 0;
			}
		}

		@keyframes req-pop-dot {
			0% {
				transform: scale(.4);
				opacity: 0;
			}

			100% {
				transform: scale(1);
				opacity: 1;
			}
		}

		@keyframes req-fade-in {
			0% {
				opacity: 0;
			}

			100% {
				opacity: 1;
			}
		}

		@keyframes req-slide-up-fade {
			0% {
				opacity: 0;
				transform: translateY(16px);
			}

			100% {
				opacity: 1;
				transform: translateY(0);
			}
		}

		.cover-inner {
			animation: epic-hero-enter linear both;
			animation-timeline: view();
			animation-range: entry 0% cover 40%;
			transform-style: preserve-3d;
		}

		@keyframes epic-hero-enter {
			from {
				opacity: 0;
				transform: translateZ(-800px) rotateX(40deg) translateY(200px);
			}

			to {
				opacity: 1;
				transform: translateZ(0) rotateX(0deg) translateY(0);
			}
		}

		/* 3. The Stack: each sheet is LAID UP on the wall — it arrives
		   standing a few degrees open on its vertical centreline, a little
		   proud of the wall, and turns flat as it takes the screen.

		   Two things about the pen's sheet-drop did not survive the sheets
		   getting long, and both scale with HEIGHT, which is why it only
		   went wrong here:

		   1. `perspective` on `.stack`. The projection centre is the centre
		      of the perspective element, and `.stack` is the whole document
		      — several thousand px tall. A sheet near either end is
		      therefore viewed from thousands of px off-axis, so ANY z it
		      takes shears it bodily down the page instead of foreshortening
		      it in place. The perspective moves onto the sheet's own
		      transform, where the projection centre is the sheet itself.
		      `.work` already carries its own for the projects, so nothing
		      else on the page wanted the ancestor one.
		   2. `rotateX` about `top center`. Tilting the LONG axis throws the
		      bottom edge ~sin(35°) × sheet-height into z — on a 1600px
		      sheet that is 900px of depth, and the keystone that produces
		      is the stretch. Turning on the SHORT axis (rotateY, the axis
		      `.project`'s iso-pop uses) bounds the foreshortening by
		      `--sheet-max`, which is fixed no matter how long the copy gets.

		   The turn alternates down the stack, so the six read as a set being
		   laid up in order rather than six copies of one effect. `scale()`
		   is gone with the drop: paper does not grow, and it was the other
		   half of what smeared the type. */
		.stack>.sheet {
			--turn: -1;
			animation: sheet-layup linear both;
			animation-timeline: view();
			/* Ends on `entry`, not `cover`. On a subject taller than the
			   viewport `cover 45%` is still scrubbing long after the sheet
			   owns the screen — the reader is trying to read a moving page.
			   This settles as the sheet fills the screen and then holds. */
			animation-range: entry 5% entry 85%;
		}

		.stack>.sheet:nth-of-type(even) {
			--turn: 1;
		}

		@keyframes sheet-layup {
			0% {
				opacity: 0;
				transform: perspective(1800px) translate3d(0, 3rem, -90px) rotateY(calc(var(--turn) * 10deg));
			}

			45% {
				opacity: 1;
			}

			100% {
				opacity: 1;
				transform: perspective(1800px) translate3d(0, 0, 0) rotateY(0deg);
			}
		}

		/* 4. Brise-soleil: Aggressive 90-degree 3D snap.
		   The pen staggered four blades; this page specifies seven, so the
		   ladder runs to :nth-child(8) — child 1 is the .anno caption. Same
		   5% step the pen used, not a retune. */
		.blade {
			animation: snap-louvre linear both;
			animation-timeline: view();
			animation-range: entry 10% cover 55%;
		}

		@keyframes snap-louvre {
			from {
				opacity: 0;
				transform: perspective(900px) rotateX(-90deg) translateY(50px);
			}

			to {
				opacity: 1;
				transform: perspective(900px) rotateX(0deg) translateY(0);
			}
		}

		.blade:nth-child(3) {
			animation-range: entry 15% cover 60%;
		}

		.blade:nth-child(4) {
			animation-range: entry 20% cover 65%;
		}

		.blade:nth-child(5) {
			animation-range: entry 25% cover 70%;
		}

		.blade:nth-child(6) {
			animation-range: entry 30% cover 75%;
		}

		.blade:nth-child(7) {
			animation-range: entry 35% cover 80%;
		}

		.blade:nth-child(8) {
			animation-range: entry 40% cover 85%;
		}

		/* 5. Projects: Exploding staggered isometric rotations */
		.work {
			perspective: 1800px;
		}

		.project {
			animation: iso-pop linear both;
			animation-timeline: view();
			animation-range: entry 5% cover 50%;
		}

		/* Stagger the origins so they fold open like a triptych */
		.project:nth-child(1) {
			transform-origin: left center;
		}

		.project:nth-child(2) {
			transform-origin: center center;
		}

		.project:nth-child(3) {
			transform-origin: right center;
		}

		@keyframes iso-pop {
			from {
				opacity: 0;
				transform: translateY(200px) rotateY(60deg) scale(0.7);
			}

			to {
				opacity: 1;
				transform: translateY(0) rotateY(0deg) scale(1);
			}
		}

		/* 6. QA Section: Heavy opposing lateral slide-ins */
		.qa {
			overflow: hidden;
		}

		.qa:nth-child(odd) {
			animation: slide-right linear both;
			animation-timeline: view();
			animation-range: entry 10% cover 45%;
		}

		.qa:nth-child(even) {
			animation: slide-left linear both;
			animation-timeline: view();
			animation-range: entry 10% cover 45%;
		}

		@keyframes slide-right {
			from {
				opacity: 0;
				transform: translateX(-15vw);
			}

			to {
				opacity: 1;
				transform: translateX(0);
			}
		}

		@keyframes slide-left {
			from {
				opacity: 0;
				transform: translateX(15vw);
			}

			to {
				opacity: 1;
				transform: translateX(0);
			}
		}
	}
}

/* ============================================================
   html.no-runway — the site-wide "runways collapsed, scrubs off"
   stamp, set pre-paint by js/deck-prepaint.js for reduced motion
   AND for a visitor who chose Standard or Slide Deck in the
   experience selector on any other page. The pen's own
   prefers-reduced-motion guard does not see that choice, so it is
   honoured here.

   THE RULE THIS OBEYS: a collapsed runway MUST have its scrub
   killed in the same rule. Every keyframe above starts at
   opacity 0, and `both` with no resolvable timeline holds the FROM
   state — so dropping the timeline alone renders the page BLANK.
   Each selector therefore also resets what it was animating.
   Declared after the motion block so it wins the ties.
   ============================================================ */
html.no-runway {
	animation: none;
}

html.no-runway .scalebar {
	display: none;
}

html.no-runway .cover-inner,
html.no-runway .stack>.sheet,
html.no-runway .blade,
html.no-runway .project,
html.no-runway .qa:nth-child(odd),
html.no-runway .qa:nth-child(even) {
	animation: none;
	opacity: 1;
	transform: none;
}

/* A-102, the same collapse #wd_fix's twins make: undo the track, the
   pin and every scrub, and resolve to one settled screen with the
   diagram fully drawn. The dashoffset reset is load-bearing and easy
   to miss — kill the animation without it and the four leader lines
   stay at their undrawn from-state, which is the "a collapsed runway
   renders blank" trap one element at a time. The panel resolves to
   translate: none, i.e. already swept up, so the sheet is simply
   there. */
html.no-runway .req_track {
	height: auto;
	view-timeline: none;
}

html.no-runway .req_stage {
	position: static;
	height: auto;
}

html.no-runway .req_node,
html.no-runway .req_stage_bg,
html.no-runway .req_callout line,
html.no-runway .req_callout circle,
html.no-runway .req_callout text,
html.no-runway .req_thesis {
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
}

html.no-runway .req_callout line {
	stroke-dashoffset: 0 !important;
}
