/* ============================================================
   ARCHITECTURE FIRM WEBSITES — "DAYLIGHT STUDY"
   The owner'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, at
   one fixed hour. Every sheet, poster and pin-up throws its shadow the
   same way, the brise-soleil blades sit at the one angle that hour
   gives them, and the wall carries the day as a static top-to-bottom
   gradient (THE WALL). 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, the owner) —
        /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.
     7. The three faces are SELF-HOSTED (2026-07-29) — see FONTS below.
     8. A-101.1, a second new section BETWEEN the banner and A-102
        (2026-08-06, the owner) — four frames of one view dissolving into
        each other on a pinned stage: the bare site, the drawing, the
        coloured drawing, the house as built. It is the only
        photography on the page. See A-101.1 · THE SEQUENCE below.
     9. THE SUN NO LONGER MOVES (2026-08-24, the owner). The pen drove
        --sun, --wall, --shadow-x, --shadow-y and --glow off a scroll
        timeline. The sun angle was too small to read as motion and the
        wall went dark fast over the last third of the page. The hour
        is fixed and the wall is a static gradient instead. See THE
        HOUR IS FIXED and THE WALL below.
   The palette, the type, the sheets, the daylight and every other
   keyframe are the pen's. Do not "improve" them.
   ============================================================ */

/* ============================================================
   FONTS — self-hosted, closing the last cross-origin request

   This page used to be the site's ONE exception to NO CROSS-ORIGIN
   REQUESTS ON ANY PAGE LOAD: two preconnects and a render-blocking
   fonts.googleapis.com stylesheet, which then CHAINED to a second
   round trip to fonts.gstatic.com for the actual woff2 — the font
   files could not begin downloading until the CSS came back. The owner
   accepted that "to start. We'll fix that", and this is that fix.

   The faces are identical. tools/fonts.py already carried all ten
   targets, written against exactly what this page renders (Archivo
   wght 500-700 with the wdth axis KEPT, Spectral Light 300 for body
   plus Regular 400 Italic for <em>, DM Mono at both 300 and 400) —
   they had simply never been built, so `--check` reported STALE and
   the page fell back to the CDN. Read the notes on each target in
   fonts.py before changing a weight here: several are load-bearing.

   THE RANGES MUST MATCH fonts.py's LATIN / LATIN_EXT EXACTLY AND MUST
   NOT OVERLAP — where two faces of one family claim a codepoint, the
   last declaration silently wins. This is the same split style.css
   uses, and it means the accent halves are never fetched unless an
   accented character is actually rendered. fonts.py prints which
   character triggers a latin-ext fetch on every build; today the
   answer is "not triggered by any page", and it should stay that way.
   ============================================================ */

@font-face {
	font-family: "Archivo";
	src: url(/assets/fonts/archivo-latin-v1.woff2) format("woff2");
	font-weight: 500 700;
	font-stretch: 100% 125%;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7, U+2010-2027, U+2030, U+2039-203A, U+2044, U+20AC, U+2122, U+2190-2193, U+2212;
}

@font-face {
	font-family: "Archivo";
	src: url(/assets/fonts/archivo-latinext-v1.woff2) format("woff2");
	font-weight: 500 700;
	font-stretch: 100% 125%;
	font-style: normal;
	font-display: swap;
	unicode-range: U+00A1-00A8, U+00AA-00AD, U+00AF, U+00B1-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC;
}

@font-face {
	font-family: "Spectral";
	src: url(/assets/fonts/spectral-light-latin-v1.woff2) format("woff2");
	font-weight: 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7, U+2010-2027, U+2030, U+2039-203A, U+2044, U+20AC, U+2122, U+2190-2193, U+2212;
}

@font-face {
	font-family: "Spectral";
	src: url(/assets/fonts/spectral-light-latinext-v1.woff2) format("woff2");
	font-weight: 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+00A1-00A8, U+00AA-00AD, U+00AF, U+00B1-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC;
}

/* 400 italic, not 300 italic — body copy is 300 but the pen asked
   Google for `ital,wght@1,400`, so 400 is the italic this page has
   always rendered. fonts.py says the same thing at the target. */
@font-face {
	font-family: "Spectral";
	src: url(/assets/fonts/spectral-italic-latin-v1.woff2) format("woff2");
	font-weight: 400;
	font-style: italic;
	font-display: swap;
	unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7, U+2010-2027, U+2030, U+2039-203A, U+2044, U+20AC, U+2122, U+2190-2193, U+2212;
}

@font-face {
	font-family: "Spectral";
	src: url(/assets/fonts/spectral-italic-latinext-v1.woff2) format("woff2");
	font-weight: 400;
	font-style: italic;
	font-display: swap;
	unicode-range: U+00A1-00A8, U+00AA-00AD, U+00AF, U+00B1-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC;
}

@font-face {
	font-family: "DM Mono";
	src: url(/assets/fonts/dm-mono-light-latin-v1.woff2) format("woff2");
	font-weight: 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7, U+2010-2027, U+2030, U+2039-203A, U+2044, U+20AC, U+2122, U+2190-2193, U+2212;
}

@font-face {
	font-family: "DM Mono";
	src: url(/assets/fonts/dm-mono-light-latinext-v1.woff2) format("woff2");
	font-weight: 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+00A1-00A8, U+00AA-00AD, U+00AF, U+00B1-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC;
}

@font-face {
	font-family: "DM Mono";
	src: url(/assets/fonts/dm-mono-latin-v1.woff2) format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0020-007E, U+00A0, U+00A9, U+00AE, U+00B0, U+00B7, U+2010-2027, U+2030, U+2039-203A, U+2044, U+20AC, U+2122, U+2190-2193, U+2212;
}

@font-face {
	font-family: "DM Mono";
	src: url(/assets/fonts/dm-mono-latinext-v1.woff2) format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
	unicode-range: U+00A1-00A8, U+00AA-00AD, U+00AF, U+00B1-00B6, U+00B8-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC;
}

/* ---- Two POSTER faces, for the collection band at the foot of the page.
   style.css's own title-only subsets, declared verbatim — .poster_vitrea
   and .poster_silver ask for them by name and this page loads no
   style.css. fonts.py derives their character sets from index.html's
   .poster_title strings, so they cannot drift. Paper's poster wants plain
   arial and needs nothing.
   NOT PRELOADED, deliberately: 2.9 + 2.7 KB, swap, a page below the fold —
   preloading puts them in front of the two faces that paint the cover. */
@font-face {
	font-family: "Inter";
	src: url(/assets/fonts/inter-poster-v1.woff2) format("woff2");
	font-weight: 600;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Cinzel Decorative";
	src: url(/assets/fonts/cinzel-decorative-poster-v1.woff2) format("woff2");
	font-weight: 700;
	font-style: normal;
	font-display: swap;
}

/* THE HOUR IS FIXED (2026-08-24, the owner: "the sun does not move").

   These five were @property registrations driven by a
   `scroll(root block)` keyframe called `daylight`, so the wall, the
   sun angle, the shadow throw and the clerestory glow all tracked
   scroll position. The sun never read as moving — a section cut and a
   row of blade angles are too small to show it — and what DID read
   was the wall crashing from #a9aeaa to #6f7573 over the last third
   of the document: a page going dark under the reader for no stated
   reason, fastest exactly where the copy needs to be legible.

   So the hour is pinned at mid-morning and the day is painted INTO
   the page instead, as one static gradient on the canvas (see THE
   WALL below). Nothing here interpolates any more, which is why the
   @property registrations went with it: these are ordinary tokens
   now, and --wall is the value that gradient is anchored to. */
:root {
	/* THE HOUR. --shadow-x/--shadow-y are the throw every sheet, poster
	   and pin-up on the page casts. --sun and --glow are the angle and
	   the strength of the light that throws it: their only consumers
	   were the pen's clerestory section-cut, which A-102 replaced, so
	   they are kept as the record of what the shadow numbers MEAN and
	   for anything drawn at this hour later. */
	--sun: 34deg;
	--wall: #b9bfbd;
	--shadow-x: 14px;
	--shadow-y: 22px;
	--glow: 0.55;

	/* 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;

	/* The one easing this page's DISCRETE motion uses — the index sheet
	   and its leader lines. Everything else here is scroll-scrubbed and
	   linear on purpose. Fast out of the gate, long tail: a drawing
	   instrument being drawn across paper, not a UI easing in. */
	--ease-draw: cubic-bezier(.22, 1, .36, 1);

	color-scheme: light;
}

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

/* ============================================================
   THE WALL — one gradient, painted once, and the whole of this
   page's "time of day".

   It is on the ROOT, not on body: a root background propagates to the
   canvas, so it fills overscroll too and it is what a cross-document
   view transition exposes between its two halves (see
   css/vt-leaf.css). body is left transparent so it cannot paint over
   it.

   THE STOPS ARE DERIVED FROM --wall, NOT PICKED. The top is the wall
   with --daylight mixed into it (morning light landing on it, warm),
   the bottom is the wall with --night mixed in (the light going out
   of the room, cool). In oklab that is #c1c2ba at L .81 down to
   #969c9b at L .69, and the collection band covers the last stretch,
   so the darkest wall a reader actually sees is nearer L .71.

   The mixes are the DIAL. Daylight is a warm yellow, so the top stop
   shifts hue faster than it shifts lightness: past about 82% it goes
   yellow-green rather than sunlit. The night mix is the opposite and
   is bounded by contrast — --ink measures 6.4:1 on the bottom stop,
   and .anno's --graphite has never been legible on bare wall at any
   hour (see THE COLOPHON).

   ELEVEN SCREENS OF DOCUMENT SPREAD THAT ARC OUT TO ABOUT ONE PART IN
   A HUNDRED PER SCREEN, which is the point: the wall is different at
   the bottom of the page from the top, and it is never caught
   changing. That is the whole difference from the scroll-driven
   version this replaces.

   22% IS NOT A ROUND NUMBER, IT IS #sequence. .seq_stage repaints the
   wall FLAT behind its lazy-loaded frames and spans roughly 11%..33%
   of the document, so the middle stop is pinned to plain var(--wall)
   there and the flat stage matches the gradient behind it to within a
   couple of RGB steps at either hairline. Move the sequence up or
   down the page and this stop moves with it.
   ============================================================ */
html {
	/* the flat wall first: a browser without color-mix() drops the
	   gradient declaration whole, and an unpainted canvas is white. */
	background: var(--wall);
	background: linear-gradient(180deg,
			color-mix(in oklab, var(--wall) 86%, var(--daylight)) 0%,
			var(--wall) 22%,
			color-mix(in oklab, var(--wall) 82%, var(--night)) 100%);
	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 {
	/* no background on purpose — the root owns the wall, see THE 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 above 900px the
   row WRAPS and below it the same seven move into THE INDEX SHEET (see
   below). Nothing is ever removed. */
.masthead nav {
	display: flex;
	justify-content: flex-end;
	flex: 1 1 auto;
	min-width: 0;
}

.index_list {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	align-items: center;
	gap: .35rem clamp(.75rem, 1.8vw, 1.75rem);
	list-style: none;
}

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

/* The sheet's own furniture: only the panel wants these. `.masthead
   .index_block` is scoped to two classes ON PURPOSE — the element also
   wears `.titleblock`, which is declared further down the file and would
   win a (0,1,0) tie on source order and leave a stray rule across the
   desktop masthead. */
.index_leader,
.index_eyebrow,
.masthead .index_block,
.nav_toggle {
	display: none;
}

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

/* ============================================================
   THE INDEX SHEET — the navigation below 900px (2026-07-29)

   Seven links wrapping to three lines made the fixed masthead a
   third of a phone screen tall. They move into a sheet instead,
   built from what this page already draws with:

     · The panel IS a drawing sheet pinned over the wall — paper
       face, a daylight-lit top edge, a hairline drawing border
       inset from the trimmed edge.
     · Each link is A-102'S CALLOUT one screen up: a node at the
       margin, a leader drawn out from it, the label arriving as
       the leader lands. Staggered down the seven, so the index
       assembles the way the diagram does.
     · The toggle is the page's own LINE WEIGHTS — two 1.5-unit
       rules around a .75-unit graphite one.

   THE AMBIENT LAYER is one idea twice: the icon carries a shadow
   thrown by --sun, and the open sheet has daylight drifting
   across the paper. Nothing blinks or bounces.

   900px is MEASURED — wordmark + gutters + the seven labels at
   this page's body size is ~890px. Change a label, re-measure.

   CRAWL PARITY: never `display: none`, never fewer links — a
   transformed, visibility:hidden sheet holding the same seven
   <a href>, made `inert` by the script while closed.
   ============================================================ */
.nav_toggle {
	position: relative;
	z-index: 3;
	/* `flex: 0 0 auto` is not decoration. The masthead is a flex row and
	   .wordmark is `white-space: nowrap`, so on a 320px screen the only
	   thing flex CAN shrink is this button — it collapsed from 44px to 32
	   and the negative margin then carried its right edge 4px past the
	   viewport, where `overflow-x: clip` quietly ate the last rule of the
	   icon. Locked at 44 (the tap-target floor) with the wordmark tightened
	   below 420px to pay for it. */
	flex: 0 0 auto;
	width: 2.75rem;
	height: 2.75rem;
	margin-right: -.35rem;
	padding: 0;
	border: 0;
	background: none;
	color: var(--ink);
	cursor: pointer;
	place-items: center;
}

/* the one width where "THE BETTER WEBSITE" at .22em tracking, a 44px
   button and two gutters do not add up */
@media (max-width: 420px) {
	.wordmark {
		font-size: .82rem;
		letter-spacing: .16em;
	}
}

.nav_rules {
	display: block;
	width: 32px;
	height: 24px;
	overflow: visible;
	/* THE SUN, on the nav. Scaled to ~7% of the sheets' own throw, so it
	   lands as a 1px shadow leaning the way everything else on the page
	   leans — read as craft, never as decoration. */
	filter: drop-shadow(calc(var(--shadow-x) * .07) calc(var(--shadow-y) * .07) 0 rgb(21 24 26 / .22));
}

.nav_rule {
	fill: currentColor;
	transform-box: fill-box;
	transform-origin: center;
	transition: transform .5s var(--ease-draw), opacity .25s ease;
}

/* the thin graphite rule, drawn short — it extends on hover and retracts
   into the node on open, so its origin is the left end both times */
.nav_rule--2 {
	fill: var(--graphite);
	transform-origin: 0 50%;
}

@media (hover: hover) and (pointer: fine) {
	.nav_toggle:hover .nav_rule--2 {
		transform: scaleX(1.4);
	}
}

html.nav-open .nav_rule--1 {
	transform: translate(0, 7px) rotate(45deg) scaleX(.66);
}

html.nav-open .nav_rule--3 {
	transform: translate(0, -7px) rotate(-45deg) scaleX(.66);
}

/* The thin graphite rule RETRACTS into its own left origin rather than
   joining the fold — one of three lines behaving differently from the
   other two is the whole character of the icon, and it is the same line
   hierarchy every drawing on this page is set in. A node on the crossing
   was tried and cut: at 32px a hollow circle over two 1.5-unit rules
   reads as a bowtie, not as an intersection mark. */
html.nav-open .nav_rule--2 {
	transform: scaleX(0);
	opacity: 0;
}

/* Scroll lock. `overflow: hidden` and NOT a fixed body: hidden keeps the
   root a scroll container with its scrollTop intact, so `scroll(root
   block)` stays live and the daylight animation holds exactly where it
   was. A fixed body zeroes the scroll offset and the whole page would
   snap back to dawn the moment the menu opened. */


@media (max-width: 900px) {

	.nav_toggle {
		display: grid;
	}

	/* No backdrop-filter down here. It is a real cost on a phone, and —
	   the reason it MUST go — a backdrop-filter makes the masthead a
	   containing block for `position: fixed` descendants, which would
	   pin the sheet to the header box instead of the viewport. The bar
	   wears the wall itself instead.

	   THE WALL AT THE TOP OF THE PAGE, not plain --wall: this bar is
	   opaque and fixed, so unlike its desktop twin it cannot sample
	   what is behind it, and it can only match THE WALL gradient at one
	   depth. Matched at 0% it is seamless where a landing reader first
	   sees it, and further down it reads as a lit bar over a wall that
	   has gone dim, which is the right way round. */
	.masthead {
		background: color-mix(in oklab, var(--wall) 86%, var(--daylight));
		backdrop-filter: none;
	}

	/* the sheet is pinned OVER the bar; the wordmark and the toggle sit
	   on the paper and stay live */
	html.nav-open .masthead {
		background: transparent;
		border-bottom-color: transparent;
	}

	.wordmark {
		position: relative;
		z-index: 3;
	}

	.masthead nav {
		position: fixed;
		inset: 0;
		z-index: 1;
		display: flex;
		flex-direction: column;
		overflow: hidden;
		padding: calc(4.4rem + env(safe-area-inset-top)) calc(var(--gutter) + .9rem) calc(1.9rem + env(safe-area-inset-bottom));
		/* paper, with a daylight-lit top edge painted as a 2px background
		   layer rather than a border-image — it leaves both pseudos free
		   for the drawing border and the daylight wash below.
		   .req_stage_bg::before's recipe runs daylight into --ink; that
		   stop is dropped here. On a dark navy sheet it reads as the far
		   end of a lit edge, but across the top of white paper on a phone
		   it is just a smear. Light only. */
		background:
			linear-gradient(90deg, transparent, var(--daylight) 45%, transparent) top left / 100% 2px no-repeat,
			var(--sheet);
		visibility: hidden;
		opacity: 0;
		transform: perspective(1100px) translateY(-100%) rotateX(-7deg);
		transform-origin: 50% 0;
		transition:
			transform .55s var(--ease-draw),
			opacity .3s ease .15s,
			visibility 0s linear .55s;
	}

	html.nav-open .masthead nav {
		visibility: visible;
		opacity: 1;
		transform: perspective(1100px) translateY(0) rotateX(0deg);
		transition:
			transform .6s var(--ease-draw),
			opacity .25s ease,
			visibility 0s;
	}

	/* the sheet's drawing border — a real sheet has one, inset from the
	   trimmed edge; it clears the masthead so the wordmark sits above it */
	.masthead nav::before {
		content: "";
		position: absolute;
		inset: calc(3.5rem + env(safe-area-inset-top)) 1rem 1rem;
		border: 1px solid var(--rule);
		pointer-events: none;
	}

	/* DAYLIGHT ON PAPER — the ambient half. A narrow, very pale bar of
	   --daylight easing back and forth across the sheet on a 13s cycle,
	   under the ink (z-index: 0 against the content's 1). It is the
	   page's one accent doing the page's one thing: moving.

	   ALPHA AND WIDTH ARE BOTH DELIBERATELY SMALL. The first pass ran
	   .7 across a 32-point spread and the sheet came out custard —
	   subtle means you notice it only if you sit on the menu, which is
	   the one place on this site nobody should be sitting. If it is
	   legible in a screenshot it is too strong. */
	.masthead nav::after {
		content: "";
		position: absolute;
		inset: -20% -60%;
		z-index: 0;
		pointer-events: none;
		background: linear-gradient(104deg, transparent 28%, rgb(240 215 164 / .22) 50%, transparent 72%);
		animation: index-daylight 13s ease-in-out infinite;
	}

	@keyframes index-daylight {

		0%,
		100% {
			transform: translateX(-15%);
		}

		50% {
			transform: translateX(15%);
		}
	}

	.index_eyebrow,
	.index_list,
	.index_block {
		position: relative;
		z-index: 1;
	}

	.index_eyebrow,
	.masthead .index_block {
		display: flex;
	}

	.index_eyebrow {
		flex: 0 0 auto;
		margin-bottom: clamp(.75rem, 3vh, 1.75rem);
	}

	.index_list {
		flex: 1 1 auto;
		min-height: 0;
		overflow-y: auto;
		flex-direction: column;
		flex-wrap: nowrap;
		justify-content: center;
		align-items: stretch;
		gap: clamp(.3rem, 1.5vh, .95rem);
	}

	.index_block {
		flex: 0 0 auto;
		margin-top: clamp(1rem, 3vh, 2rem);
	}

	/* --- one link = one callout ------------------------------------ */
	.masthead nav a {
		display: flex;
		align-items: center;
		gap: .6rem;
		padding: .3rem 0;
		font-family: var(--f-display);
		font-weight: 500;
		font-variation-settings: "wdth" 116;
		font-size: clamp(1.2rem, 5.6vw, 2.05rem);
		letter-spacing: -.02em;
		line-height: 1.1;
		text-transform: none;
		color: var(--ink);
		/* the desktop row sets nowrap so a label never breaks mid-name in a
		   wrapping bar. In the panel that turned "Web Development" into a
		   clipped label on a 320px screen — the exact thing NO INVISIBLE
		   WALLS forbids. The 1.2rem floor is what keeps it on one line at
		   that width; this is the net under it. */
		white-space: normal;
	}

	/* the node at the margin */
	.masthead nav a::before {
		content: "";
		flex: 0 0 auto;
		width: 6px;
		height: 6px;
		border-radius: 50%;
		background: var(--ink);
		transform: scale(0);
		transition: transform .3s var(--ease-draw);
	}

	/* the leader drawn out from it */
	.index_leader {
		display: block;
		flex: 0 0 auto;
		width: clamp(1.4rem, 7vw, 2.75rem);
		height: 1px;
		background: var(--ink);
		opacity: .5;
		transform: scaleX(0);
		transform-origin: 0 50%;
		transition: transform .45s var(--ease-draw);
	}

	/* and the label arriving as the leader lands */
	.index_name {
		flex: 1 1 auto;
		opacity: 0;
		transform: translateX(-.5rem);
		transition: opacity .4s ease, transform .45s var(--ease-draw);
	}

	html.nav-open .masthead nav a::before {
		transform: scale(1);
	}

	html.nav-open .index_leader {
		transform: scaleX(1);
	}

	html.nav-open .index_name {
		opacity: 1;
		transform: none;
	}

	/* The stagger, and it only applies on the way IN. Closing, every row
	   drops its delay and the sheet leaves as one object — a menu that
	   dismantles itself for half a second before closing reads as lag. */
	html.nav-open .index_list li:nth-child(1) {
		--i: 0
	}

	html.nav-open .index_list li:nth-child(2) {
		--i: 1
	}

	html.nav-open .index_list li:nth-child(3) {
		--i: 2
	}

	html.nav-open .index_list li:nth-child(4) {
		--i: 3
	}

	html.nav-open .index_list li:nth-child(5) {
		--i: 4
	}

	html.nav-open .index_list li:nth-child(6) {
		--i: 5
	}

	html.nav-open .index_list li:nth-child(7) {
		--i: 6
	}

	html.nav-open .masthead nav a::before {
		transition-delay: calc(.26s + var(--i, 0) * .05s);
	}

	html.nav-open .index_leader {
		transition-delay: calc(.31s + var(--i, 0) * .05s);
	}

	html.nav-open .index_name {
		transition-delay: calc(.36s + var(--i, 0) * .05s);
	}

	/* the sheet's own metadata, first in and last in */
	.index_eyebrow,
	.index_block {
		opacity: 0;
		transition: opacity .35s ease;
	}

	html.nav-open .index_eyebrow {
		opacity: 1;
		transition-delay: .22s;
	}

	html.nav-open .index_block {
		opacity: 1;
		transition-delay: .68s;
	}

	@media (hover: hover) and (pointer: fine) {
		.masthead nav a:hover {
			text-decoration: none;
		}

		.masthead nav a:hover .index_leader {
			width: clamp(2.2rem, 11vw, 4rem);
			opacity: 1;
			transition: width .35s var(--ease-draw), opacity .35s ease;
		}
	}
}

/* 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-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 is struck at --sun, the same angle
   every shadow on the page is thrown at. Static — see THE HOUR IS
   FIXED; this used to pivot, and it was too small to see doing it. */
.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-101.1 · THE SEQUENCE  (.seq_track / .seq_stage)

   Four frames of one view — the bare site, the line drawing, the
   coloured drawing, the finished house — dissolving into each other
   on a pinned 100dvh stage. The images are the whole argument; the
   plates are annotation.

   THE FOUR FRAMES REGISTER, and that is why a dissolve works at all
   rather than reading as four unrelated pictures: they are the same
   photograph and two drawings traced from it, delivered at the same
   2528x1696 canvas and cropped identically. Any sizing value here is
   therefore a SHARED value — move it on one frame and the treeline
   steps sideways mid-dissolve.

   TWO GEOMETRIES, AND THE BREAKPOINT IS 861px (2026-08-19, the owner:
   "I'm only referencing desktop. When the image is full width on
   tablet and mobile it looks good").

     under 861px  the picture is FULL-BLEED, `cover`, anchored bottom,
                  with the annotation slip centred at the top of the
                  frame. This is the shipped mobile design and it is
                  not touched by anything below.
     861px and up the picture is a MOUNTED PLATE — see THE MOUNT.

   THE MOUNT. On desktop the picture used to be a bare 1000px-wide
   `cover` box floating on the wall with its caption hung above it,
   which read as an image dropped into the page rather than as part of
   it (the owner, 2026-08-19: "it's just a plain image stuck in the
   middle of everything"). It is now a drawing PLATE, which is the one
   thing this whole theme already is: a sheet of the page's own stock,
   a hairline window with drafting corner ticks around it, the picture
   whole inside it, and a title block across the foot carrying the
   sheet number, the phase and a four-step progress bar. It casts the
   same daylight-driven shadow every other .sheet on the page casts,
   so it hangs on the wall with them.

   NOTHING IS CROPPED ON DESKTOP. .seq_pic carries the source's own
   2528/1696 aspect ratio and is sized from the HEIGHT the frame has
   left over, so `cover` and `contain` resolve to the same picture and
   the mat stays even on all four sides. The second term of that
   `min()` is the width guard: it is the height at which the picture
   would exactly fill the available width, so a tall narrow window
   shrinks the plate instead of overflowing it.

   THE SLIP SITS ON THE WATER (2026-08-19, the owner: "the text boxes
   can be made longer and sit just above the bottom of the image").
   The bottom quarter of all four frames is pond, which is the emptiest
   band in every one of them, so the slip is anchored to the bottom
   inside edge of the picture and runs wider than it did in the canopy.
   Left-aligned, not centred: it is an annotation pinned to a drawing,
   and the title block below it is the thing that balances it.

   NOTHING IS REVEALED BY A MOVING EDGE. Each frame fades in over the
   one under it; no wipe, no mask, no travelling boundary. The plates
   fade too. This is NO INVISIBLE WALLS applied to a crossfade.

   THE PLATE IS PAPER, and it has to be. The sequence runs from
   near-black pond water to near-white drawing paper and back to a
   sunlit lawn, so there is no single ink colour legible over all four
   frames — type laid straight on the picture would be unreadable on
   at least two of them at any colour. A slip of the page's own sheet
   stock solves it once, in the page's own material: this theme is
   drawing sheets pinned to a wall, and an annotation slip pinned over
   a photograph is the same idea one level down.

   THE STAGE PAINTS THE WALL rather than nothing. The four images are
   lazy-loaded, so a frame can in principle begin its dissolve before
   its bytes land; against a transparent stage that shows as a flash
   of whatever is behind the page. Against the wall it shows as the
   wall.

   Enhanced-only, exactly like A-102 below: the base state IS the
   readable one. Without animation-timeline there is no track and no
   pin, and the four frames lay out as four ordinary full-screen
   pictures with their captions on them, in order. That is also what
   html.no-runway resolves to, which is why the twin at the foot of
   this file has nothing to undo for this section.
   ============================================================ */
.seq_track {
	position: relative;
}

/* In the base state this is only a wrapper; the motion layer turns it
   into the sticky pin. `background`, not a child element — see THE
   STAGE PAINTS THE WALL above. */
.seq_stage {
	background: var(--wall);
	border-block: 1px solid var(--rule);
}

/* min-height, not height: settled geometry. Each frame is its own
   screen until the motion layer stacks them.

   THE PLATE SITS IN THE TREES, ABOVE THE ROOFLINE (2026-08-06, the
   owner) — and UNDER 861px that is still where it sits, centred across
   the frame and held to the upper band so the house is never covered.
   That band is not a guess: `cover` anchored to the bottom puts the
   roof ridge low in a portrait viewport and the canopy fills
   everything above it. The slip is therefore hung from the TOP, with
   just enough padding to clear the fixed masthead, and every value
   carries a vh term so a short window tightens the gap instead of
   dropping it onto the roof. Desktop moves the slip to the foot of the
   picture instead; see THE SLIP SITS ON THE WATER above. */
.seq_frame {
	position: relative;
	min-height: 100dvh;
	overflow: hidden;
	display: flex;
	justify-content: center;
	align-items: flex-start;
	padding: clamp(5rem, 11vh, 7.5rem) var(--gutter) clamp(2.5rem, 7vh, 5rem);
}

/* Under 861px the mount is a pass-through: no paper, no mat, no title
   block. It exists so the markup is one shape at every width, and so
   the desktop plate has something to be. */
.seq_mount {
	position: static;
	width: 100%;
}

.seq_rail {
	display: none;
}

/* Base state only: the four frames are stacked down the page, so they
   want a rule between them. The motion layer stacks them in Z instead
   and takes it back off. */
.seq_frame+.seq_frame {
	border-top: 1px solid var(--rule);
}

/* Under 861px the picture fills the frame and sits BEHIND the slip —
   absolute so the frame's own padding does not inset it, which is what
   makes the section full-bleed while the slip still respects the
   gutter. `bottom` on a 1.49 source in a portrait window holds the
   pond, the balustrade and the house whole and lets the blank sky
   above the roofline go, which is the only band nothing happens in.
   The four share the value; see THE FOUR FRAMES REGISTER above. */
.seq_pic {
	position: absolute;
	inset: 0;
}

.seq_img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: bottom;
}

/* The annotation slip. Sized deliberately small — the owner: the images are
   the focal point — and centred in the canopy above the roofline; see
   the note on .seq_frame. Type stays left-aligned inside the centred
   box: a centred box reads as a title card, but centring three or four
   lines of prose inside it just makes them ragged. */
.seq_plate {
	position: relative;
	z-index: 1;
	width: min(100%, 40rem);
	margin-inline: auto;
	background: var(--sheet);
	border: 1px solid var(--rule);
	padding: clamp(1.1rem, 1.4vw + .6vh, 1.75rem);
	box-shadow: var(--shadow-x) var(--shadow-y) 46px rgb(0 0 0 / 0.28);
}

.seq_plate h2,
.seq_plate h3 {
	font-size: clamp(1.35rem, 2.2vw, 1.9rem);
	font-variation-settings: "wdth" 112;
	letter-spacing: -0.015em;
	margin: .5rem 0 .65rem;
}

.seq_plate p:last-child {
	font-size: clamp(.95rem, 1vw, 1.05rem);
	line-height: 1.55;
	color: #2b2f31;
}

/* ---- THE MOUNT, 861px and up ------------------------------------
   Read THE MOUNT and THE SLIP SITS ON THE WATER at the head of this
   section before retuning anything in here. Nothing below is animated,
   so none of it needs an html.no-runway twin: the plate is the same
   plate in every state, and only the dissolve inside it comes and
   goes. */
@media (min-width: 861px) {

	/* Both padding values become custom properties because the
	   picture's height is DERIVED from them. Change one here and the
	   plate resizes itself; change one without changing the other half
	   of the calc in .seq_pic and the plate overflows the frame. */
	.seq_frame {
		--pad-top: clamp(5.5rem, 12vh, 8rem);
		--pad-bot: clamp(2.5rem, 6vh, 4.5rem);
		align-items: center;
		/* the mount's shadow needs room to fall past the frame's edge;
		   the stage still clips at the viewport, which is the only hard
		   boundary in the section */
		overflow: visible;
		padding: var(--pad-top) var(--gutter) var(--pad-bot);
	}

	.seq_mount {
		/* the mat, the title block's line box, and the slip's inset
		   from the picture's edges. Everything else is derived. */
		--mat: clamp(.85rem, 1vw, 1.25rem);
		--rail-h: 1.9rem;
		--slip-in: clamp(1rem, 2.4vw, 2.75rem);
		/* mat above the picture, mat below it, mat under the title
		   block, and the picture's own hairline top and bottom */
		--chrome: calc(var(--rail-h) + var(--mat) * 3 + 2px);

		position: relative;
		width: auto;
		max-width: 100%;
		padding: var(--mat);
		background: var(--sheet);
		border: 1px solid var(--rule);
		box-shadow: var(--shadow-x) var(--shadow-y) 60px rgb(0 0 0 / 0.22);
	}

	/* The window. Height first, width from the source's own ratio, so
	   the whole drawing is on the sheet and the mat is even on four
	   sides. The second term of the min() is the WIDTH GUARD: it is the
	   height at which the picture would exactly fill the space between
	   the gutters, less 1rem for a scrollbar 100vw does not know about.
	   A tall narrow window therefore shrinks the plate instead of
	   overflowing it. */
	.seq_pic {
		position: relative;
		inset: auto;
		aspect-ratio: 2528 / 1696;
		height: min(calc(100dvh - var(--pad-top) - var(--pad-bot) - var(--chrome)),
				calc((100vw - var(--gutter) * 2 - var(--mat) * 2 - 1rem) / 1.4906));
		border: 1px solid var(--rule);
	}

	/* Drafting corner ticks, sitting halfway out into the mat: four
	   L-shapes, eight 1px bars, one static paint. They are what make
	   the mat read as a mount rather than as a margin, and they are why
	   the picture needs no more than a hairline of its own. */
	.seq_pic::after {
		content: "";
		position: absolute;
		inset: calc(var(--mat) * -0.5);
		pointer-events: none;
		--tick: linear-gradient(var(--graphite), var(--graphite));
		background:
			var(--tick) left top / 11px 1px no-repeat,
			var(--tick) left top / 1px 11px no-repeat,
			var(--tick) right top / 11px 1px no-repeat,
			var(--tick) right top / 1px 11px no-repeat,
			var(--tick) left bottom / 11px 1px no-repeat,
			var(--tick) left bottom / 1px 11px no-repeat,
			var(--tick) right bottom / 11px 1px no-repeat,
			var(--tick) right bottom / 1px 11px no-repeat;
	}

	/* The picture box carries the source ratio, so there is nothing
	   left to crop and nothing left to anchor. */
	.seq_img {
		object-position: center;
	}

	/* The title block, and it is the idea .titleblock states on every
	   other sheet on this page: sheet number and phase on the left,
	   position in the set on the right, hairline type, no chrome. */
	.seq_rail {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 1.5rem;
		height: var(--rail-h);
		margin-top: var(--mat);
	}

	.seq_step {
		display: flex;
		align-items: center;
		gap: .7rem;
		white-space: nowrap;
	}

	/* Four steps in one element. --n is the frame's own index, so the
	   bar ticks over WITH the dissolve and needs neither four spans nor
	   an animation of its own. */
	.seq_step_bar {
		width: clamp(3rem, 5vw, 4.5rem);
		height: 2px;
		background: linear-gradient(to right,
				var(--ink) 0 calc(var(--n) * 25%),
				var(--rule) calc(var(--n) * 25%));
	}

	.seq_frame:nth-of-type(1) .seq_mount {
		--n: 1;
	}

	.seq_frame:nth-of-type(2) .seq_mount {
		--n: 2;
	}

	.seq_frame:nth-of-type(3) .seq_mount {
		--n: 3;
	}

	.seq_frame:nth-of-type(4) .seq_mount {
		--n: 4;
	}

	/* The slip drops to the foot of the picture and runs wider. Its
	   bottom is measured off the mount's padding box, so it has to step
	   back up over the title block and two mats to find the picture's
	   inside edge: that is the whole of the calc.

	   The ink left edge is doing real work, not decoration. Frames 2
	   and 3 are drawings on near-white paper, and paper on paper needs
	   one edge that does not depend on the value underneath it. */
	.seq_plate {
		position: absolute;
		left: calc(var(--mat) + var(--slip-in));
		bottom: calc(var(--mat) * 2 + var(--rail-h) + clamp(1rem, 2.2vh, 2rem));
		width: min(54rem, calc(100% - var(--mat) * 2 - var(--slip-in) * 2));
		margin-inline: 0;
		padding: clamp(1.15rem, 1.5vw, 1.85rem) clamp(1.4rem, 1.9vw, 2.25rem);
		border-left: 3px solid var(--ink);
		/* the slip is the query container for its own type, see
		   THE HEADING IS MEASURED OFF THE SLIP below. It is already a
		   stacking context (z-index above), so the containment this
		   adds costs nothing. */
		container-type: inline-size;
	}

	/* The sheet number lives in the title block now; leaving it in the
	   slip as well would print it twice on one plate. */
	.seq_plate .anno {
		display: none;
	}

	/* THE HEADING IS MEASURED OFF THE SLIP, not off the viewport
	   (2026-08-20, the owner: "the text content covers too much of the
	   image"). The slip's width is DERIVED from the picture, which is
	   derived from the frame's leftover HEIGHT, so a viewport-width
	   clamp cannot track it: at 1366x768 and 1600x800 the vw term ran
	   the heading to 30px inside a 710px slip and the longest of the
	   four wrapped to a second line, growing the slip over the drawing.
	   In cqi it cannot: the longest heading measures 26.7em, so 3.3cqi
	   holds all four on ONE line at every desktop size with room to
	   spare, and the 1.6rem cap keeps it off the big screens. Retitle a
	   frame longer than "Brought to life with expert knowledge and
	   experience" and re-derive the coefficient. */
	.seq_plate h2,
	.seq_plate h3 {
		margin-top: 0;
		font-size: clamp(1.2rem, 3.3cqi, 1.6rem);
	}

	/* No measure on the prose. The sitewide 62ch cap held it to
	   two-thirds of the slip and paid for it in LINES, which is the one
	   dimension that costs the picture anything; on a slip this wide the
	   full width is a line or two shorter. */
	.seq_plate p:last-child {
		max-width: none;
		font-size: clamp(.9rem, .95vw, 1rem);
	}
}

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

   /web-development/ #wd_fix's runway, PORTED (2026-07-28, the owner:
   "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.

   ONE DELIBERATE DIVERGENCE, in the markup rather than in here
   (2026-07-28, the owner: "the boxes in the graphic are not centered"):
   this page's <svg> takes viewBox="20 0 860 440" where #wd_fix
   takes "0 0 860 440". The node row runs 75..825 in BOTH, so its
   centre is 450 and a window centred on 430 hangs every card 20
   units right of the stage — an original quirk of the pen's
   diagram, invisible there only because #wd_fix's notes are short
   enough that nothing else draws the eye to the edge. Every
   coordinate still maps one-to-one; only the window moved.
   /web-development/ is NOT corrected yet and still carries the
   offset — fix it there and this comment goes away.

     .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 (the owner, 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;
}

/* ============================================================
   A-102 ON A PHONE — THE FOUR CARDS FOLD INTO A 2x2
   (2026-07-29, the owner: "make the graphic in the .req_stage mobile
   responsive… the 4 sections 2x2. The two top cards will have the
   scroll-revealed line and text come out of the top, and the
   bottom will come out of the bottom. This should still work on
   mobile to keep it 100dvh.")

   THE PROBLEM IS THE SCALE FACTOR, NOT THE ARRANGEMENT. An
   `width: 100%` SVG with an 860-unit-wide viewBox renders at
   k = boxWidth / 860 px per user unit. A 390px phone leaves the
   stage ~350px, so k = 0.41: the 15-unit card labels painted at
   6px and each of the four cards was 61px wide. Rearranging
   alone fixes nothing — every SIZE in the drawing has to grow in
   USER UNITS, which is why this block scales as well as moves.

   WHY THE BOX IS TALLER THAN THE viewBox. viewBox is an
   attribute, not a property, so CSS cannot narrow the window to
   raise k — the only lever left is bigger content. Cards at 2.2x
   need two rows of 211 units plus a note block above and below:
   ~792 units against the window's 440. So the svg KEEPS its
   860x440 window (desktop is untouched, and with it the
   one-to-one coordinate map onto #wd_fix that the block above
   asks for) and gains `overflow: visible` plus an `aspect-ratio`
   that RESERVES those 792 units of layout height. `xMidYMid
   meet` centres the window in that box, so user y=220 lands at
   the box's middle and the drawing spills symmetrically out of
   the window into space the flex column has already counted.
   Nothing is clipped and nothing overlaps the head or the
   thesis — the stage stays one 100dvh screen.

   THE COLUMNS ARE THE DESKTOP ROW FOLDED IN HALF: cards 1-2 are
   the left column, 3-4 the right, so it reads down and then down
   again and DOM order is still reading order. That fold is the
   one assignment needing PURE TRANSLATION — callouts 1 and 3
   already point UP and take the top row, 2 and 4 already point
   DOWN and take the bottom. Pairing 1-2 across the top instead
   would mean reversing callout 2's leader AND swapping its two
   note lines, which no transform does without mirroring the
   type. Reveal order is untouched at 1,2,3,4: down the left
   column, then down the right.

   EVERY NUMBER HERE IS DERIVED — re-derive, don't nudge. Card
   centres sit at x 235 / 665 because a 23-character note at 28
   units is 386 wide (DM Mono is a .6em advance, as the markup's
   note says) and two of those plus margins must fit 860. Row
   centres at y 85 / 365 leave 68 units between the rows. Each
   `translate` is then just (target centre - that element's own
   desktop centre), and each callout's is its DOT's, so the
   leader and note travel with it.

   ONE THING IS DELIBERATELY NOT SCALED: the leaders stay 76
   units long. At 2.2x they would cost 167 units of vertical
   each, which is what pushes a 2x2 past the height a phone has.
   Their WEIGHT is scaled, so they still read.

   612px IS THE OWNER'S BREAKPOINT AND IT IS SHARED (2026-07-29: "make
   the breakpoint for the stacking 612px for both graphics"). The
   twin of this block is #wd_fix ON A PHONE in css/style.css —
   **move one and move the other.** It REPLACES the 760px this
   shipped at, which is a real trade rather than a tidy-up: the
   four-across row now holds down to 612, where its card labels
   paint at ~10px, so the range 612-760 reads smaller than the
   2x2 would have. That is the designer's call on where the wide
   row stops looking right, not a measurement.
   ============================================================ */
@media (max-width: 612px) {

	.req_art svg {
		overflow: visible;
		aspect-ratio: 860 / 792;
	}

	/* 2.2x carries the label, the rect's stroke and the stacked pair's
	   leading with it — one property, no per-child type scale.
	   `translate` and `scale` are the INDEPENDENT transform properties
	   on purpose: the scrub owns `transform` on this element and they
	   compose ahead of it, so the assemble still plays AND the
	   no-runway twin's `transform: none` cannot flatten the layout. */
	.req_node {
		scale: 2.2;
	}

	.req_node:nth-of-type(1) {
		translate: 85px -135px;
	}

	.req_node:nth-of-type(2) {
		translate: -115px 145px;
	}

	.req_node:nth-of-type(3) {
		translate: 115px -135px;
	}

	.req_node:nth-of-type(4) {
		translate: -85px 145px;
	}

	/* The dots and the leader weight are scaled to the SAME ratio the
	   cards are, so the drawing keeps desktop's proportions: a dot is
	   1/30th of a card either way, and a leader 1.34px against a 134px
	   card instead of 2.16 against 216. */
	.req_callout circle {
		scale: 2.2;
	}

	.req_callout line {
		stroke-width: 3.3;
	}

	.req_callout:nth-of-type(1) {
		translate: 85px -193px;
	}

	.req_callout:nth-of-type(2) {
		translate: -115px 203px;
	}

	.req_callout:nth-of-type(3) {
		translate: 115px -193px;
	}

	.req_callout:nth-of-type(4) {
		translate: -85px 203px;
	}

	/* 28 units is 11.4px at k=0.41 — the smallest this note goes and
	   still be read — and it is what fixes the 386-unit line length the
	   column geometry above is derived from. */
	.req_callout text {
		font-size: 28px;
	}

	/* The pair's two baselines are 18 units apart in the markup, which
	   was leading for 12.5-unit type. These push them to 36 and keep
	   the nearest one clear of the leader's tip, growing AWAY from the
	   card in each direction: up off the top row, down off the bottom. */
	.req_callout:nth-of-type(1) text:nth-of-type(1),
	.req_callout:nth-of-type(3) text:nth-of-type(1) {
		translate: 0 -18px;
	}

	.req_callout:nth-of-type(2) text:nth-of-type(1),
	.req_callout:nth-of-type(4) text:nth-of-type(1) {
		translate: 0 8px;
	}

	.req_callout:nth-of-type(2) text:nth-of-type(2),
	.req_callout:nth-of-type(4) text:nth-of-type(2) {
		translate: 0 26px;
	}
}

/* --- 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;
	}
}

/* A-201's and A-301's headings ride their own column while the prose
   beside them scrolls.

   THE BREAKPOINT IS THE SPLIT'S, and the two must move together — this
   rule lives here rather than at the foot of the file for that reason
   (2026-08-06, the owner: the headings "are sticky before the page is
   actually wide enough to split the content"). It was written at
   min-width 768px, which left a 768-860px band where `.split` had
   already collapsed to ONE column: the heading then pinned itself over
   the paragraphs directly below it, because in one column there is
   nothing beside it to scroll past. Sticky is only meaningful once
   there are two columns, so it starts exactly where the second one
   does. */
@media screen and (min-width: 861px) {

	#h-argument,
	#h-approach {
		position: sticky;
		top: 5rem;
	}
}

.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;
}

/* --- A-701's PS close (2026-07-29, the owner's sign-off) --------------------
   Every landing page ends on "PS: this theme is for sale" — the page is
   the pitch to the vertical AND the display model for the theme, so the
   offer is the last thing read. It is written entirely out of parts the
   sheet already has: the .anno label, the sheet's own body type, and the
   titleblock's hairline rule, sitting directly above the real titleblock
   so the sheet still closes the way every other sheet closes. Measure
   width is the same 62ch the sheet's notes use, so it reads as a note
   and not as a second column. */
.ps_note {
	border-top: 1px solid #ffffff1f;
	margin-top: clamp(2rem, 4vw, 3.5rem);
	padding-top: .75rem;
}

/* the RULE runs the full sheet, like the titleblock's — only the measure
   is held to a note's width, or the hairline reads as an arbitrary
   fragment floating in the middle of a wide dark sheet */
.ps_note p {
	max-width: 62ch;
}

.ps_note p+p {
	margin-top: .6rem;
}

.ps_note em {
	font-style: italic;
	color: var(--sheet);
}

.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);
}

/* ============================================================
   THE COLLECTION BAND  (.alsolike)  — 2026-07-29, the owner

   "Like this theme? You might also like:", written to TRAVEL:
   every theme page and landing page gets one eventually, so
   nothing here is named for this page and nothing is an id. Drop
   the markup in, swap the cards, and it takes the host theme's
   palette and type.

   THE CARDS ARE HOME'S POSTERS, PORTED VERBATIM (the owner: "the exact
   same code we use for the home page slider") — .slide_poster,
   .poster_bg, .poster_title, .poster_sub and each theme's block,
   copied unchanged from css/style.css. That is what makes a
   poster reusable: pure CSS, one image in three, no dependency on
   the fan carousel. NOT ported: the 3-D hover pops and the fan
   poses, both of which need the fan's preserve-3d rig. Anything
   else that changes in style.css's POSTERS block changes here.

   THE POSTER IS THE WHOLE CARD (2026-07-29, the owner). No paper face,
   no border, no padding around it — the generated preview sits
   straight on the wall under a shadow thrown by --sun, the same
   one the sheets above it cast, and the caption and titleblock
   read as a drawing's caption rather than as card furniture.

   NOT SHEET 8: the set is numbered 1..7 and its titleblocks say
   so. This is a pin-up beside the set, on its own wall.

   THAT WALL IS LIT, NOT RECESSED, for contrast reasons. The
   obvious move is .req_stage's `var(--wall) 88%, #000`, but that
   section sits a third down the page and this band sits at the
   bottom, which is the dim end of THE WALL's gradient; darker
   again puts --ink under a lead paragraph and two links at
   roughly 4:1. Mixed toward the sheet it clears 5.5:1 outright,
   and it does not depend on where the gradient happens to be.
   ============================================================ */
.alsolike {
	position: relative;
	padding: clamp(3.5rem, 9vh, 7rem) var(--gutter);
	background: color-mix(in oklab, white 76%, rgb(148, 148, 148));
	border-block: 1px solid var(--rule);
}

/* THE DATUM — a hairline struck across the wall, left to right, that
   the whole band hangs off. Static here; the motion layer draws it. */
.alsolike::after {
	content: "";
	position: absolute;
	top: -1px;
	left: 0;
	right: 0;
	height: 1px;
	background: linear-gradient(90deg, var(--ink), var(--daylight));
	transform-origin: 0 50%;
}

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

/* --graphite, not --ink, is what .anno wears everywhere else on this page —
   but everywhere else it sits on paper. On the band's wall it measures
   1.7:1 and is simply not readable. The mono, the tracking and the caps are
   already doing the "this is annotation" job; the colour does not have to. */
.alsolike_eyebrow {
	color: var(--ink);
}

.alsolike h2 {
	margin: .6rem 0 1rem;
	max-width: 20ch;
}

.alsolike_lead {
	margin-bottom: clamp(2rem, 5vh, 3.25rem);
}

.alsolike_grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: clamp(1.25rem, 2.5vw, 2.25rem);
}

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

.alsolike_more {
	margin-top: clamp(1.75rem, 4vh, 2.75rem);
}

/* --- one card = one poster, hung on the wall ------------------------- */
/* NO CARD CHROME (2026-07-29, the owner: "remove all the backgrounds… so just the
   generated theme previews show"). The <a> is a bare box: the poster is the
   object, the shadow belongs to the poster, and the caption below reads as a
   drawing's caption. `container-type` stays — it is what gives the poster its
   em scale, exactly as .theme_stack_col does for the home fan — and with the
   padding gone 100cqw IS the poster's width. */
.tt_card {
	display: block;
	text-decoration: none;
	container-type: inline-size;
	transition: translate .3s var(--ease-draw);
}

/* The lift is on `translate`, NOT on `transform`, and that is load-bearing:
   the pin-up below owns `transform` and its fill state keeps applying after
   it settles, so a hover written as a transform would be overridden by the
   animation and silently do nothing. `translate` is an independent property
   the animation never touches, and it composes ahead of the transform. */
@media (hover: hover) and (pointer: fine) {
	.tt_card:hover {
		translate: 0 -4px;
	}

	.tt_card:hover .tt_art {
		box-shadow: calc(var(--shadow-x) * .6) calc(var(--shadow-y) * .6 + 6px) 46px rgb(0 0 0 / .34);
	}
}

.tt_art {
	position: relative;
	overflow: hidden;
	aspect-ratio: 700 / 450;
	--u: calc(100cqw / 700);
	/* posters size themselves in em, off this */
	font-size: calc(16 * var(--u));
	/* half the sheets' throw. A full-size shadow under a 390px preview reads
	   as a UI drop-shadow rather than as something hung on a wall; at half it
	   is the same gesture as the sheets above, thrown at the same hour. */
	box-shadow: calc(var(--shadow-x) * .5) calc(var(--shadow-y) * .5) 36px rgb(0 0 0 / .24);
	transition: box-shadow .3s ease;
}

/* THE GLINT — one pass of daylight across the sheet as it lands, and
   the band's one flourish. Scrubbed by the motion layer, so it happens
   once, on arrival, at the reader's own speed; parked off-frame here so
   a browser with no scroll timelines never sees it. */
.tt_art::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 3;
	pointer-events: none;
	background: linear-gradient(104deg, transparent 38%, rgb(255 249 233 / .55) 50%, transparent 62%);
	transform: translateX(-140%);
}

/* FULL INK ON BOTH, and it follows from the card losing its paper. The
   caption used to sit on --sheet, where a softened #2b2f31 / #55595b read
   as a quieter tier; it now sits on the band's wall, where those measure
   ~4.2:1 and ~2.6:1. --ink holds 5.5:1 at every hour of the page. The
   hierarchy is carried by size and case instead, which is what the mono
   was doing anyway. */
.tt_note {
	margin-top: .9rem;
	font-size: .92rem;
	color: var(--ink);
}

/* .titleblock's own margin is set for a 5rem-padded sheet */
.tt_card .titleblock {
	margin-top: .9rem;
}

.tt_card .titleblock .anno {
	color: var(--ink);
}

/* =========================== POSTERS ====================================
   Ported from css/style.css. Everything is em-based so it rides .tt_art's
   --u-derived font-size. */
.slide_poster {
	position: relative;
	width: 100%;
	height: 100%;
	border-radius: inherit;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: .4em;
	text-align: center;
	transform-style: preserve-3d;
}

/* Decor layer: carries the poster's background art and anything that
   needs corner clipping (the sold ribbon). */
.poster_bg {
	position: absolute;
	inset: 0;
	overflow: hidden;
	border-radius: inherit;
	transform: translateZ(0);
}

/* Every poster element rests 2px off the card face — never coplanar with
   the opaque .poster_bg at Z 0, so preserve-3d's depth sort never has a
   tie to resolve. Give any NEW poster element the same rest lift.

   The three resets are this page's tax, not home's: home has no element
   rules on h3/p at all, so a poster title there renders at the UA's bold
   in the poster's own face. Here h1,h2,h3 carry Archivo, a wdth axis, a
   negative tracking and text-wrap: balance, all of which would leak in
   under .poster_title and quietly restyle three other studios' work. */
.poster_title {
	margin: 0;
	position: relative;
	font-size: 3.4em;
	line-height: 1.05;
	letter-spacing: 2px;
	font-weight: 700;
	font-variation-settings: normal;
	text-wrap: wrap;
	transform: translateZ(2px);
}

.poster_sub {
	margin: 0;
	position: relative;
	max-width: none;
	font-size: 1.3em;
	line-height: 1.2;
	transform: translateZ(2px);
}

/* ---- Paper: neumorphic — embossed ghost headline + soft pill ---- */
.poster_paper .poster_bg {
	background: #e4e5e9;
}

.poster_paper .poster_title {
	font-family: arial, sans-serif;
	font-size: 4.2em;
	color: #e4e5e9;
	text-shadow:
		-0.02em -0.02em 0.04em #ffffff,
		0.02em 0.03em 0.05em #b4b5bd;
}

.poster_paper .poster_pill {
	position: relative;
	width: 38%;
	height: 1.9em;
	border-radius: 2em;
	background: radial-gradient(circle at 50% 60%, #eef0f4, #e0e1e6 70%);
	box-shadow: .35em .35em .8em #c2c3ca, -.35em -.35em .8em #ffffff;
	transform: translateZ(2px);
}

.poster_paper .poster_sub {
	color: #74757d;
}

/* ---- Vitrea: ambient color blobs under a glass chip (translucent fill
   only — NO backdrop-filter, it would flatten the 3D pops) ---- */
.poster_vitrea .poster_bg {
	background:
		radial-gradient(circle 12em at 22% 30%, rgba(64, 140, 255, .55), transparent 70%),
		radial-gradient(circle 11em at 78% 72%, rgba(255, 96, 196, .5), transparent 70%),
		radial-gradient(circle 9em at 62% 15%, rgba(90, 230, 190, .45), transparent 70%),
		#eef1f6;
}

.poster_vitrea .poster_glass {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: .35em;
	padding: 1em 2em 1.1em;
	border-radius: 1.2em;
	background: linear-gradient(135deg, rgba(255, 255, 255, .55), rgba(255, 255, 255, .22));
	border: 1px solid rgba(255, 255, 255, .75);
	box-shadow: 0 .6em 1.6em rgba(30, 50, 90, .18), inset 0 1px 0 rgba(255, 255, 255, .9);
	transform-style: preserve-3d;
	transform: translateZ(2px);
}

.poster_vitrea .poster_title {
	font-family: "Inter", sans-serif;
	color: #1d2733;
}

.poster_vitrea .poster_sub {
	color: #40506a;
}

/* ---- Silver Plate: silver 3D type over the real theme's blue-steel room
   light (--steel #38475B fading to --coal #242C38), gold sold ribbon ---- */
.poster_silver .poster_bg {
	background:
		radial-gradient(circle 16em at 30% 20%, #38475b, transparent 70%),
		#242c38;
}

/* Centre-anchored on the corner, so the label lands on the middle of the
   VISIBLE chord whatever the text measures — this page's ribbon inherits
   Spectral, not the site's Orbitron, so a tuned top/right pair cannot be
   right in both places. Full note: css/style.css, same selector. */
.poster_silver .poster_ribbon {
	position: absolute;
	top: 0;
	right: 0;
	transform: translate(50%, -50%) translate(-4em, 4em) rotate(45deg);
	padding: .3em 3em;
	font-size: .8em;
	letter-spacing: .25em;
	text-transform: uppercase;
	font-weight: 700;
	background: linear-gradient(180deg, #f0c96c, #c89a34);
	color: #241a05;
}

/* Same pair as the real theme: one asset, mirrored to the opposite corner */
.poster_silver .poster_teapot {
	position: absolute;
	left: -.5em;
	bottom: -.5em;
	width: 4.6em;
	height: auto;
	opacity: .6;
	filter: drop-shadow(0 .1em .2em rgba(0, 0, 0, .5));
}

.poster_silver .poster_teapot--mirror {
	left: auto;
	right: -.5em;
	transform: scaleX(-1);
}

.poster_silver .poster_title {
	font-family: "Cinzel Decorative", Georgia, 'Times New Roman', serif;
	letter-spacing: .06em;
	background: linear-gradient(180deg, #fdfdfd 15%, #8d8d96 55%, #ececf4 90%);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
	filter: drop-shadow(0 .04em .05em rgba(0, 0, 0, .85));
}

.poster_silver .poster_sub {
	color: #9d9da6;
}

/* ============================================================
   THE COLOPHON — the set's titleblock strip, and it sits on THE
   BAND'S LIT WALL, not on the bare wall (fixed 2026-07-29, the owner:
   "the text is the same color as the footer").

   It was invisible, and the arithmetic is the whole story: the
   footer painted on nothing but the bare wall, and .anno's
   var(--graphite) #676c6e is never far from it — 2.85:1 against
   plain --wall and 1.91:1 against the dim end of THE WALL's
   gradient, which is where this element always sits. The footer
   was there, fully laid out, the same colour as itself.

   --ink alone does not fix it either: #15181a on the wall's dim
   end is 6.4:1, which clears text but leaves a strip of body
   copy sitting in the darkest thing on the page. So the strip
   takes .alsolike's lit wall — the band directly above it, mixed
   for exactly this reason (see THE COLLECTION BAND) — and --ink
   lands at ~13.9:1 there. The two read as one lit stretch of
   wall with the pin-up above and the credits below it, which is
   also why the band's own bottom hairline is the only rule
   between them.

   TRAVELS WITH THE BAND: any page that takes .alsolike takes
   this pairing. A page WITHOUT it has a colophon on bare wall
   again — give it this background, do not fall back to graphite.
   ============================================================ */
.colophon {
	padding: 2.5rem var(--gutter) 3.5rem;
	border-top: 1px solid var(--rule);
	background: color-mix(in oklab, white 76%, rgb(148, 148, 148));
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
}

/* .alsolike_eyebrow's reasoning, one element further down: the mono,
   the tracking and the caps already say "annotation", so the colour
   does not have to — and here it cannot, at any wall value. */
.colophon .anno {
	color: var(--ink);
}

/* The band already closes itself with a hairline; a second one
   immediately under it reads as a 2px double rule. Written through
   `main:has()` because the band is the LAST THING INSIDE <main> and the
   colophon is <main>'s sibling — `.alsolike + .colophon` looks right and
   silently never matches. */
main:has(.alsolike)+.colophon {
	border-top: 0;
}

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

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

		/* The sun was driven from here. Removed 2026-08-24 — see THE HOUR
		   IS FIXED at the top of the file. */

		.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;
		}

		/* 1a. A-101.1 · THE SEQUENCE — the pin, and the whole timing
		   board for it.

		   400svh over a 100dvh pin is 300svh of scrub, the same cadence
		   A-102 runs directly below; `contain 0%` is "the pin just
		   locked" and `contain 100%` is "it is about to release", so
		   every percentage here is a position within the hold and needs
		   no retuning if the section moves up or down the page.

		   THE BOARD IS ONE PLACE ON PURPOSE. Each frame and each plate
		   gets exactly ONE keyframe spanning the full range, with its
		   beats written as internal stops — so retiming a beat is
		   editing a number in the keyframe that owns it. The obvious
		   alternative, two animations per element on two ranges, does
		   NOT work here: both drive `opacity`, and the second one in the
		   list wins its backwards fill as well as its forwards one, so a
		   frame given a fade-in and a fade-out sits fully opaque from the
		   first frame of the section.

		     frame 1  holds from the start
		     frame 2  hold from 30    arrives 18-30
		     frame 3  hold from 58    arrives 46-58
		     frame 4  hold from 86    arrives 74-86
		     plate N arrives once its picture has landed and leaves early
		     into the next dissolve, so no two plates are ever up at once.

		   EACH FRAME ONLY EVER FADES IN, AND NO FRAME EVER FADES OUT.
		   That is not a shortcut, it is the only way to dissolve a stack
		   like this without washing out. Frames paint in DOM order, so
		   frame N+1 arriving on top is all the transition needs; run the
		   two against each other instead and at the midpoint both sit at
		   0.5, which composites to 0.5 of the new picture, 0.25 of the
		   old and 0.25 of the STAGE — a quarter of the wall bleeding
		   through the middle of every dissolve. The lower frames stay
		   opaque underneath, where they cost a composite and are never
		   seen.

		   TWELVE PERCENT PER DISSOLVE (2026-08-06, the owner: "the scroll
		   length is fine, but spread out the animations a bit"), i.e.
		   36svh of scroll to cross from one picture to the next, double
		   the 6% this shipped with. The holds absorbed the difference;
		   the track did not move.

		   The dissolves are opacity ONLY — no wipe, no mask, no moving
		   boundary anywhere in the section. See NO INVISIBLE WALLS. */
		.seq_track {
			height: 400svh;
			view-timeline: --seq-runway block;
		}

		.seq_stage {
			position: sticky;
			top: 0;
			height: 100dvh;
			overflow: hidden;
		}

		/* Stacked in Z now, so the base state's rule between frames has
		   nothing to separate and would draw a hairline across the
		   picture. */
		.seq_frame {
			position: absolute;
			inset: 0;
			min-height: 0;
		}

		.seq_frame+.seq_frame {
			border-top: 0;
		}

		.seq_frame,
		.seq_plate {
			animation-timeline: --seq-runway;
			animation-range: contain 0% contain 100%;
			animation-timing-function: linear;
			animation-fill-mode: both;
		}

		/* Frame 1 is the floor and takes no animation at all — it is
		   simply what is underneath until something covers it. The
		   grayscale sits on the PICTURE and not on the frame: the frame
		   carries the mount, and a filter there would desaturate the
		   sheet, the title block and the slip along with it. */
		.seq_frame:nth-of-type(1) .seq_img {
			filter: grayscale(1);
		}

		.seq_frame:nth-of-type(2) {
			animation-name: seq-frame-2;
		}

		.seq_frame:nth-of-type(3) {
			animation-name: seq-frame-3;
		}

		.seq_frame:nth-of-type(4) {
			animation-name: seq-frame-4;
		}

		@keyframes seq-frame-2 {

			0%,
			18% {
				opacity: 0;
			}

			30%,
			100% {
				opacity: 1;
			}
		}

		@keyframes seq-frame-3 {

			0%,
			46% {
				opacity: 0;
			}

			58%,
			100% {
				opacity: 1;
			}
		}

		@keyframes seq-frame-4 {

			0%,
			74% {
				opacity: 0;
			}

			86%,
			100% {
				opacity: 1;
			}
		}

		/* The plates rise as they arrive. A small translate and nothing
		   else: both properties a compositor can animate without
		   re-rastering type, which matters with a full-screen photograph
		   dissolving underneath. */
		.seq_frame:nth-of-type(1) .seq_plate {
			animation-name: seq-plate-1;
		}

		.seq_frame:nth-of-type(2) .seq_plate {
			animation-name: seq-plate-2;
		}

		.seq_frame:nth-of-type(3) .seq_plate {
			animation-name: seq-plate-3;
		}

		.seq_frame:nth-of-type(4) .seq_plate {
			animation-name: seq-plate-4;
		}

		@keyframes seq-plate-1 {
			0% {
				opacity: 0;
				translate: 0 1.25rem;
			}

			8%,
			17% {
				opacity: 1;
				translate: 0 0;
			}

			25%,
			100% {
				opacity: 0;
				translate: 0 0;
			}
		}

		@keyframes seq-plate-2 {

			0%,
			27% {
				opacity: 0;
				translate: 0 1.25rem;
			}

			35%,
			45% {
				opacity: 1;
				translate: 0 0;
			}

			53%,
			100% {
				opacity: 0;
				translate: 0 0;
			}
		}

		@keyframes seq-plate-3 {

			0%,
			55% {
				opacity: 0;
				translate: 0 1.25rem;
			}

			63%,
			73% {
				opacity: 1;
				translate: 0 0;
			}

			81%,
			100% {
				opacity: 0;
				translate: 0 0;
			}
		}

		@keyframes seq-plate-4 {

			0%,
			83% {
				opacity: 0;
				translate: 0 1.25rem;
			}

			91%,
			100% {
				opacity: 1;
				translate: 0 0;
			}
		}

		/* 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);
			}
		}

		/* 7. THE COLLECTION BAND — the pin-up review.
		   ONE NAMED TIMELINE for the head, four elements reading explicit
		   stops off it, so the datum / eyebrow / heading / lead arrive in a
		   stated order instead of four anonymous view() timelines each
		   guessing from its own box height. (The datum in particular CANNOT
		   use view(): it is a 1px-tall absolutely-positioned rule, so its own
		   entry phase is about a thousandth of the scroll and the line would
		   snap rather than strike.)

		   The cards keep their own view() — they sit a screen lower than the
		   head, and the entry stops that suit the heading would fire them
		   before they are on screen. Their ranges are stated in `cover`
		   throughout: a card is shorter than the viewport at every width, so
		   there is no `contain` phase to lean on, and cover is the one phase
		   whose percentages mean the same thing at every card size. */
		.alsolike {
			view-timeline: --alsolike block;
		}

		.alsolike::after,
		.alsolike_eyebrow,
		.alsolike h2,
		.alsolike_lead {
			animation-timeline: --alsolike;
			animation-timing-function: cubic-bezier(.4, 0, .2, 1);
			animation-fill-mode: both;
		}

		.alsolike::after {
			animation-name: datum-strike;
			animation-range: entry 5% entry 42%;
		}

		.alsolike_eyebrow {
			animation-name: alsolike-rise;
			animation-range: entry 12% entry 46%;
		}

		.alsolike h2 {
			animation-name: alsolike-rise;
			animation-range: entry 16% entry 52%;
		}

		.alsolike_lead {
			animation-name: alsolike-rise;
			animation-range: entry 20% entry 58%;
		}

		@keyframes datum-strike {
			from {
				transform: scaleX(0);
			}

			to {
				transform: scaleX(1);
			}
		}

		@keyframes alsolike-rise {
			from {
				opacity: 0;
				transform: translateY(1.75rem);
			}

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

		/* The three sheets are PINNED UP: each arrives standing well off the
		   wall and raked toward the centre like a triptych being opened, and
		   turns flat as it lands. Same gesture as .project's iso-pop and the
		   stack's sheet-layup, at the scale a small card can carry. The
		   perspective lives on the grid, not on an ancestor of the whole
		   page — the projection centre has to be this row (see the note on
		   .stack > .sheet for what happens when it is not). */
		.alsolike_grid {
			perspective: 1600px;
		}

		.tt_card {
			--rake: 0deg;
			animation: sheet-pinup linear both;
			animation-timeline: view();
			animation-range: cover 8% cover 34%;
		}

		.tt_card:nth-child(1) {
			--rake: -20deg;
			transform-origin: left center;
			animation-range: cover 4% cover 30%;
		}

		.tt_card:nth-child(3) {
			--rake: 20deg;
			transform-origin: right center;
			animation-range: cover 12% cover 38%;
		}

		@keyframes sheet-pinup {
			from {
				opacity: 0;
				transform: translate3d(0, 5rem, -520px) rotateY(var(--rake)) rotateX(9deg);
			}

			55% {
				opacity: 1;
			}

			to {
				opacity: 1;
				transform: translate3d(0, 0, 0) rotateY(0deg) rotateX(0deg);
			}
		}

		/* THE GLINT — daylight crossing each sheet once, a beat after it
		   lands. It is the band's one flourish and the page's one accent
		   doing the page's one thing, so it gets to be the flashy moment and
		   nothing else in here does. */
		.tt_art::after {
			animation: sheet-glint linear both;
			animation-timeline: view();
			animation-range: cover 24% cover 44%;
		}

		.tt_card:nth-child(2) .tt_art::after {
			animation-range: cover 30% cover 50%;
		}

		.tt_card:nth-child(3) .tt_art::after {
			animation-range: cover 36% cover 56%;
		}

		@keyframes sheet-glint {
			from {
				transform: translateX(-140%);
			}

			to {
				transform: translateX(140%);
			}
		}
	}
}

/* ============================================================
   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.

   NO :root TWIN. There was one, killing the daylight scrub. The wall
   is a static gradient now, so there is nothing on the root to undo
   and a still document gets exactly the wall a moving one does.
   ============================================================ */
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-101.1 · THE SEQUENCE. Undo the track, the pin and the Z-stack, and
   resolve to the base state: four ordinary full-screen pictures with
   their captions on them, in order, top to bottom.

   THE TWIN IS NOT OPTIONAL HERE even though the base state is already
   the readable one, and the reason is worth writing down because it
   reads like it should be. `html.no-runway` is a CLASS; the motion
   layer above is gated on @supports and prefers-reduced-motion and on
   nothing else. A browser that can scrub still scrubs with the class
   set — so without these five rules a visitor who chose Reduced Motion
   or Standard anywhere on the site gets the pin, the stack and three
   frames parked at opacity 0. Measured, not assumed: the first build of
   this section shipped without them on exactly that reasoning.

   `translate`, not `transform` — that is the property the plate
   keyframes animate, and resetting the other one does nothing. */
html.no-runway .seq_track {
	height: auto;
	view-timeline: none;
}

html.no-runway .seq_stage {
	position: static;
	height: auto;
	overflow: visible;
}

html.no-runway .seq_frame {
	position: relative;
	inset: auto;
	min-height: 100dvh;
}

html.no-runway .seq_frame+.seq_frame {
	border-top: 1px solid var(--rule);
}

html.no-runway .seq_frame,
html.no-runway .seq_plate {
	animation: none !important;
	opacity: 1 !important;
	translate: none !important;
}

/* 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;
}

/* THE COLLECTION BAND's twin. Same trap, one element worse: .tt_art::after
   is the daylight glint, parked at translateX(-140%) in its resting rule.
   Resolving it to `transform: none` the way every other line here does
   would leave a permanent white band lying across the middle of all three
   posters, so it resolves to opacity 0 instead — the glint is a moment, and
   with nothing to scrub there is no moment. */
html.no-runway .alsolike {
	view-timeline: none;
}

html.no-runway .alsolike::after,
html.no-runway .alsolike_eyebrow,
html.no-runway .alsolike h2,
html.no-runway .alsolike_lead,
html.no-runway .tt_card {
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
}

html.no-runway .tt_art::after {
	animation: none !important;
	opacity: 0 !important;
}

/* THE INDEX SHEET under the same stamp. The panel's own open/close is a
   discrete UI transition, not a runway, so it survives `no-runway` (a
   visitor who picked Standard or Slide Deck still wants a menu that
   opens like a menu) — what does not survive is the AMBIENT half: the
   daylight drifting across the paper and the sun-thrown shadow on the
   toggle, both of which run without anyone asking for them.

   Reduced motion is the stricter case and takes the transitions too,
   under both the OS query and the site's own stamp. */
html.no-runway .masthead nav::after {
	animation: none;
	opacity: 0;
}

html.no-runway .nav_rules {
	filter: none;
}

@media (prefers-reduced-motion: reduce) {

	.masthead nav,
	.masthead nav a::before,
	.index_leader,
	.index_name,
	.index_eyebrow,
	.masthead .index_block,
	.nav_rule,
	.tt_card {
		transition-duration: 1ms !important;
		transition-delay: 0s !important;
	}

	.masthead nav::after {
		animation: none;
		opacity: 0;
	}

	.nav_rules {
		filter: none;
	}
}

html.reduce-motion .masthead nav,
html.reduce-motion .masthead nav a::before,
html.reduce-motion .index_leader,
html.reduce-motion .index_name,
html.reduce-motion .index_eyebrow,
html.reduce-motion .masthead .index_block,
html.reduce-motion .nav_rule,
html.reduce-motion .tt_card {
	transition-duration: 1ms !important;
	transition-delay: 0s !important;
}

/* ============================================================
   HERO PAGE-LOAD ANIMATIONS 
   Hardware-accelerated, staggered reveal for .cover-inner children.
   ============================================================ */

/* 1. Set initial state to hidden to prevent content flashing (CLS protection) */
.cover-inner>h1.anno,
.cover-inner>p.h1,
.cover-inner>p.lede,
.cover-inner>.cover-actions,
.cover-inner>.titleblock {
	opacity: 0;
	/* Uses the cubic-bezier already defined in your :root */
	animation: elegant-reveal 3s var(--ease-draw) forwards;
}

/* 2. Stagger the delays for a deliberate, cascading luxury feel */
.cover-inner>h1.anno {
	animation-delay: 0.15s;
}

.cover-inner>p.h1 {
	animation-delay: 0.35s;
}

.cover-inner>p.lede {
	animation-delay: 0.55s;
}

.cover-inner>.cover-actions {
	animation-delay: 0.75s;
}

.cover-inner>.titleblock {
	animation-delay: 0.95s;
}

/* 3. The Keyframes (Opacity & Transform only for Lighthouse performance) */
@keyframes elegant-reveal {
	0% {
		opacity: 0;
		transform: translateY(24px);
	}

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

/* 4. Honor the existing .no-runway class and OS reduced-motion preferences */
html.no-runway .cover-inner>*,
html.reduce-motion .cover-inner>* {
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
}

@media (prefers-reduced-motion: reduce) {
	.cover-inner>* {
		animation: none !important;
		opacity: 1 !important;
		transform: none !important;
	}
}

/* ============================================================
   SCROLL INDICATOR
   A subtle, drafting-inspired vertical track and moving line.
   ============================================================ */

.scroll-indicator {
	position: absolute;
	right: 0;
	bottom: 3rem;
	display: flex;
	flex-direction: column;
	align-items: center;

	gap: 0.6rem;
	/* Hooks into the previous hero page-load animation */
	opacity: 0;
	animation: elegant-reveal 1.2s var(--ease-draw) forwards;
	animation-delay: 1.15s;
	/* Arrives a beat after the titleblock */
}

/* The faint construction line */
.scroll-track {
	width: 2px;
	height: 48px;
	background: var(--rule);
	position: relative;
	overflow: hidden;
}

/* The dark ink stroke that draws down */
.scroll-line {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 33%;
	background: var(--ink);
	/* Hardware-accelerated transform loop */
	animation: scroll-draw 2s cubic-bezier(0.7, 0, 0.3, 1) infinite;
}

/* The line scrubs down past the edge of the hidden overflow, holds, and repeats */
@keyframes scroll-draw {
	0% {
		transform: translateY(-100%);
	}

	50% {
		transform: translateY(300%);
	}

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

/* ============================================================
   REDUCED MOTION OVERRIDES
   ============================================================ */
html.no-runway .scroll-line,
html.reduce-motion .scroll-line {
	animation: none !important;
	/* Parks the line neatly at the top of the track when motion is disabled */
	transform: translateY(0) !important;
}

@media (prefers-reduced-motion: reduce) {
	.scroll-line {
		animation: none !important;
		transform: translateY(0) !important;
	}
}