/* ============================================================================
   VANGUARD  —  /luxury-website-design/
   A theme page that is also a landing page. Loads NO css/style.css and NO
   js/main.js; this file is the whole design.

   THE MATERIAL: an architectural viewfinder. Obsidian, one bronze, Roman
   capitals. Every section is a frame that a stage moves through, and scroll
   position is the camera. Nothing on the page is decorated: the frame, the
   rule and the glowing dot are the only ornament there is.

   THE COLOUR RULE, one line so it can be checked:

       One bronze, on black, and it only ever marks the thing being
       pointed at. Alabaster carries the words; bronze carries the aim.

   Bronze on obsidian is 8.3:1 and bronze on alabaster is 2.29:1, so BRONZE
   NEVER LANDS ON A LIGHT SURFACE. If any band on this page ever inverts, its
   annotation goes obsidian, not bronze. That is the whole reason the
   collection band at the foot keeps the page's own black wall instead of
   taking the lit one the other landing pages use.

   ---------------------------------------------------------------------------
   THE PEN SHIPS. Adam's CodePen is reproduced below between THE PEN BEGINS and
   THE PEN ENDS. Its palette, its type, its keyframes and its ranges are his.
   Additions and fixes are written as ADDED n blocks AFTER the pen, never as
   edits inside it: a later same-name rule wins, so there is exactly one place
   to revert. If you are about to edit anything between the markers, stop and
   ask him.

   WHAT WAS CHANGED FROM THE PEN, and nothing else:
     1. THE FULL-SCREEN "CHOOSE YOUR EXPERIENCE" GATE IS CUT (Adam, 2026-08-20).
        A load-time interstitial that covers the content is exactly what
        Google's intrusive-interstitial penalty targets, and the site already
        has a real experience selector that this one could not drive. Its look
        is not lost: the badge and the two bronze outline buttons are the
        hero's CTA pair now. Its CSS is not reproduced below.
     2. The copy is entirely new. The pen pitched architecture firms;
        /architecture-firm-websites/ already owns that term, so this page
        pitches the tier and names architecture once, linking down.
     3. Sections added, all built from mechanisms the pen already has: the
        eight-row spec sheet reuses the louvers, the fit grid reuses the
        matrix, and the questions, contact, PS note and collection band reuse
        the fade-up.
     4. The fixes in ADDED 1..12. Read those before touching anything.
   ============================================================================ */


/* ============================================================
   FONTS — self-hosted, generated by tools/fonts.py.
   This page is a THEME PAGE: it loads no style.css, so these faces are
   declared here and no other page fetches them. NO fonts.googleapis.com
   link anywhere; the site's NO CROSS-ORIGIN REQUESTS invariant holds.

   Same latin / latin-ext split policy as the rest of the site: the ext
   halves are never fetched unless an accented character is actually
   rendered, which today none is. The ranges must match LATIN / LATIN_EXT
   in tools/fonts.py EXACTLY and must not overlap.

   Cinzel is a Roman inscriptional face: it has no true lowercase design
   worth the weight and the page never sets it below ~1.3rem, so its
   hairlines always have the size to survive.
   ============================================================ */
@font-face {
	font-family: "Cinzel";
	src: url("/assets/fonts/cinzel-latin-v1.woff2") format("woff2");
	font-weight: 400 700;
	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: "Cinzel";
	src: url("/assets/fonts/cinzel-latinext-v1.woff2") format("woff2");
	font-weight: 400 700;
	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: "Plus Jakarta Sans";
	src: url("/assets/fonts/jakarta-latin-v1.woff2") format("woff2");
	font-weight: 300 600;
	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: "Plus Jakarta Sans";
	src: url("/assets/fonts/jakarta-latinext-v1.woff2") format("woff2");
	font-weight: 300 600;
	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;
}

/* ---- Poster faces, for the collection band at the foot of the page.
   style.css's own subsets, declared verbatim: .poster_daylight asks for
   JetBrains Mono and .poster_silver asks for Cinzel Decorative, and this
   page loads no style.css. Long View's poster declares no face at all and
   letters in spaced capitals on the system sans, so it costs nothing.
   The unicode-ranges must match tools/fonts.py's LATIN / LATIN_EXT exactly
   and must not overlap.

   NEVER PRELOADED. The band is the last thing on a page that runs nine
   sections before it, and nothing above it waits on these. ---- */
@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;
}

@font-face {
	font-family: "JetBrains Mono";
	src: url("/assets/fonts/jetbrains-mono-latin-v1.woff2") format("woff2");
	font-weight: 500;
	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: "JetBrains Mono";
	src: url("/assets/fonts/jetbrains-mono-latinext-v1.woff2") format("woff2");
	font-weight: 500;
	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;
}


/* ============================================================
   ================  T H E   P E N   B E G I N S  =============
   The owner's CodePen, verbatim. Do not retime it and do not restyle
   it. Additions and fixes go after THE PEN ENDS.

   The one part not reproduced here is the experience gate's CSS
   (.experience-gate, .gate-title, .gate-options, .gate-btn and the
   #experience-toggle:checked rule); the gate is cut, see the header note.
   ============================================================ */

/* ==========================================================================
   ARCHITECTURAL LUXURY DESIGN SYSTEM
   ========================================================================== */
:root {
	--obsidian: #050505;
	--charcoal: #0e0e10;
	--bronze: #c5a059;
	--bronze-glow: rgba(197, 160, 89, 0.15);
	--alabaster: #f7f5f0;
	--muted: #85827a;
	--border-light: rgba(247, 245, 240, 0.08);
	--font-serif: "Cinzel", serif;
	--font-sans: "Plus Jakarta Sans", sans-serif;
}

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

body {
	background-color: var(--obsidian);
	color: var(--alabaster);
	font-family: var(--font-sans);
	overflow-x: hidden;
	line-height: 1.6;
}

/* Semantic Typography */
h1,
h2,
h3,
h4 {
	font-family: var(--font-serif);
	font-weight: 400;
	letter-spacing: 0.02em;
	color: var(--alabaster);
}

p {
	color: var(--muted);
}

.badge {
	display: inline-flex;
	align-items: center;
	gap: 0.75rem;
	font-family: var(--font-sans);
	font-size: 0.7rem;
	letter-spacing: 0.3em;
	text-transform: uppercase;
	padding: 0.5rem 1.2rem;
	border: 1px solid rgba(197, 160, 89, 0.3);
	background: var(--charcoal);
	color: var(--bronze);
	margin-bottom: 2rem;
}

.badge-dot {
	width: 4px;
	height: 4px;
	background: var(--bronze);
	box-shadow: 0 0 6px var(--bronze);
}

/* ==========================================================================
   SCROLL ENGINE (NO JAVASCRIPT)
   ========================================================================== */
.scroll-section {
	position: relative;
	height: 300vh;
}

.scroll-section.short {
	height: 150vh;
}

.sticky-stage {
	position: sticky;
	top: 0;
	height: 100vh;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
}

/* ==========================================================================
   SECTION 1: SPATIAL VIEWFINDER (HERO)
   ========================================================================== */
.hero-viewport {
	position: relative;
	width: 100%;
	height: 100vh;
	display: flex;
	align-items: center;
	justify-content: center;
	perspective: 1200px;
}

.viewfinder-frame {
	position: absolute;
	width: 80vw;
	height: 70vh;
	border: 1px solid var(--border-light);
	background: var(--charcoal);
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	padding: 4rem;
	box-shadow: 0 40px 100px rgba(0, 0, 0, 0.9);
	animation: frameExpand linear both;
	animation-timeline: --hero-scroll;
	animation-range: contain 0% cover 100%;
}

@keyframes frameExpand {
	0% {
		transform: translateZ(-300px) rotateX(10deg);
		opacity: 0.8;
	}

	50% {
		transform: translateZ(0) rotateX(0deg);
		opacity: 1;
	}

	100% {
		transform: scale(1.15) translateZ(200px);
		opacity: 0;
		filter: blur(10px);
	}
}

.hero-title {
	font-size: clamp(2.5rem, 4vw, 4.5rem);
	line-height: 1.1;
	max-width: 950px;
}

.hero-title span {
	color: var(--bronze);
	font-style: italic;
}

/* ==========================================================================
   SECTION 2: ASYMMETRIC PARALLAX PLANES (THE PITCH)
   ========================================================================== */
.split-plane-layout {
	display: grid;
	grid-template-columns: 1fr 1fr;
	width: 100%;
	max-width: 1400px;
	padding: 0 4rem;
	gap: 6rem;
	align-items: center;
}

.plane-fixed-content {
	animation: contentFade linear both;
	animation-timeline: --split-scroll;
	animation-range: entry 20% exit 80%;
}

.plane-stack {
	position: relative;
	height: 500px;
	perspective: 1000px;
}

.architectural-panel {
	position: absolute;
	inset: 0;
	background: rgba(14, 14, 16, 0.85);
	border: 1px solid var(--border-light);
	backdrop-filter: blur(20px);
	padding: 3rem;
	display: flex;
	flex-direction: column;
	justify-content: center;
	box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
}

.panel-1 {
	animation: panelGlide1 linear both;
	animation-timeline: --split-scroll;
	animation-range: cover 0% cover 100%;
}

.panel-2 {
	animation: panelGlide2 linear both;
	animation-timeline: --split-scroll;
	animation-range: cover 0% cover 100%;
}

@keyframes panelGlide1 {
	0% {
		transform: translateY(100%) translateZ(-200px);
		opacity: 0;
	}

	30%,
	60% {
		transform: translateY(0) translateZ(0);
		opacity: 1;
	}

	100% {
		transform: translateY(-120%) translateZ(100px);
		opacity: 0;
	}
}

@keyframes panelGlide2 {

	0%,
	30% {
		transform: translateY(120%) translateZ(-100px);
		opacity: 0;
	}

	60%,
	85% {
		transform: translateY(0) translateZ(0);
		opacity: 1;
		border-color: var(--bronze);
	}

	100% {
		transform: translateY(-100%) translateZ(100px);
		opacity: 0;
	}
}

/* ==========================================================================
   SECTION 3: AEO FAQ / SEMANTIC ANSWER ENGINE BLOCK
   ========================================================================== */
.aeo-container {
	max-width: 1000px;
	width: 100%;
	padding: 0 2rem;
	perspective: 1000px;
}

.qa-block {
	border-bottom: 1px solid var(--border-light);
	padding: 3rem 0;
	animation: qaFadeUp linear both;
	animation-timeline: --aeo-scroll;
}

@keyframes qaFadeUp {
	0% {
		opacity: 0;
		transform: translateY(50px) rotateX(10deg);
	}

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

.qa-question {
	font-size: 1.8rem;
	margin-bottom: 1rem;
	color: var(--bronze);
}

/* ==========================================================================
   SECTION 4: 3D TECHNICAL LOUVERS
   ========================================================================== */
.louver-container {
	display: flex;
	flex-direction: column;
	gap: 1.5rem;
	width: 100%;
	max-width: 1100px;
	padding: 0 2rem;
	perspective: 1500px;
}

.louver-slat {
	background: var(--charcoal);
	border: 1px solid var(--border-light);
	padding: 2.5rem 3rem;
	display: grid;
	grid-template-columns: 80px 1fr 2fr;
	align-items: center;
	transform-origin: center top;
	animation: louverFlip linear both;
	animation-timeline: --louver-scroll;
}

@keyframes louverFlip {
	0% {
		transform: rotateX(-75deg) translateZ(-100px);
		opacity: 0;
	}

	40%,
	80% {
		transform: rotateX(0deg) translateZ(0);
		opacity: 1;
	}

	100% {
		transform: rotateX(75deg) translateZ(-100px);
		opacity: 0;
	}
}

.louver-num {
	font-family: var(--font-serif);
	font-size: 1.5rem;
	color: var(--bronze);
}

/* ==========================================================================
   SECTION 5: BLUEPRINT MATRIX (SERVICES)
   ========================================================================== */
.blueprint-matrix {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	width: 100%;
	max-width: 1200px;
	gap: 1px;
	background: var(--border-light);
	position: relative;
}

.matrix-cell {
	background: var(--obsidian);
	padding: 3.5rem 2.5rem;
	position: relative;
	overflow: hidden;
	animation: matrixCellIn linear both;
	animation-timeline: --matrix-scroll;
	animation-range: cover 10% cover 70%;
}

@keyframes matrixCellIn {
	0% {
		opacity: 0;
		transform: translateY(40px);
	}

	50%,
	80% {
		opacity: 1;
		transform: translateY(0);
	}

	100% {
		opacity: 0;
		transform: translateY(-40px);
	}
}

.matrix-cell::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 2px;
	background: var(--bronze);
	transform: scaleX(0);
	transform-origin: left;
	animation: lineDraw linear both;
	animation-timeline: --matrix-scroll;
	animation-range: cover 20% cover 60%;
}

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

/* ==========================================================================
   SECTION 6: MANIFESTO & CTA
   ========================================================================== */
.manifesto-view {
	text-align: center;
	max-width: 1000px;
	padding: 0 2rem;
}

.manifesto-text {
	font-family: var(--font-serif);
	font-size: clamp(2.2rem, 4vw, 3.5rem);
	line-height: 1.25;
	margin-bottom: 3rem;
	background: linear-gradient(180deg, #fff 0%, var(--muted) 100%);
	-webkit-background-clip: text;
	-webkit-text-fill-color: transparent;
}

.cta-button {
	display: inline-block;
	background: transparent;
	color: var(--alabaster);
	border: 1px solid var(--bronze);
	padding: 1.25rem 3.5rem;
	font-family: var(--font-sans);
	font-size: 0.8rem;
	letter-spacing: 0.25em;
	text-transform: uppercase;
	text-decoration: none;
	transition: all 0.4s ease;
	box-shadow: 0 0 20px var(--bronze-glow);
}

.cta-button:hover {
	background: var(--bronze);
	color: var(--obsidian);
	box-shadow: 0 0 40px rgba(197, 160, 89, 0.4);
}

/* Responsive */
@media (max-width: 900px) {
	.split-plane-layout {
		grid-template-columns: 1fr;
		gap: 3rem;
	}

	.blueprint-matrix {
		grid-template-columns: 1fr;
	}

	.louver-slat {
		grid-template-columns: 1fr;
		gap: 1rem;
	}

	.viewfinder-frame {
		width: 92vw;
		padding: 2rem;
	}
}

/* ============================================================
   ==================  T H E   P E N   E N D S  ===============
   Everything below is this page's own, and none of it reaches back
   up into the pen. Read the header note before editing.
   ============================================================ */


/* ============================================================
   ADDED 1 — VIEWPORT UNITS

   The pen is written in vh. On a phone that is the LARGE viewport, so a
   300vh track and a 100vh stage are both measured against a window that
   only exists once the browser chrome has retracted: the stage overflows
   its own frame on load and the last screen of every track is dead.

   The site's rule, from THE LONG VIEW: the TRACK is svh and the STAGE is
   dvh. Mixed units, deliberately. The track must be the small viewport so
   it can never be shorter than the space it has to cover, and the stage
   must be the dynamic one so the pinned frame is always exactly the window
   in front of the reader.
   ============================================================ */
.scroll-section {
	height: 300svh;
}

.scroll-section.short {
	height: 150svh;
}

.sticky-stage {
	height: 100dvh;
}

.hero-viewport {
	height: 100dvh;
}


/* ============================================================
   ADDED 2 — THE PAGE BOUND

   `overflow-x: hidden` on <body> is the one declaration of the pen's that
   had to go. `hidden` on one axis computes `auto` on the other, which
   makes <body> a SCROLL CONTAINER — and every mechanism on this page is a
   `position: sticky` stage inside it. A sticky element inside a scroll
   container that does not itself scroll never releases, and the view
   timelines resolve against the wrong box.

   `clip` bounds the same overflow without creating a scroll container, and
   `overflow-y: visible` beside it is the one legal mixed pair. This is the
   sitewide rule (see main[data-flow] in css/style.css); it is stated here
   because this page loads none of that file.
   ============================================================ */
body {
	overflow-x: clip;
	overflow-y: visible;
}


/* ============================================================
   ADDED 3 — @keyframes contentFade

   `.plane-fixed-content` asks for an animation the pen never defines. An
   undefined name is dropped, so the copy column simply sat still while the
   panels beside it moved. Defined here rather than removed, because the
   column holding still through a 300svh track is the one thing that makes
   the panels read as moving PAST it rather than the page scrolling.

   It is an arrival and a departure with a long hold between them, so the
   copy is legible for the whole middle of the track. Opacity and translate
   only: this element is the section's reading matter.
   ============================================================ */
@keyframes contentFade {
	0% {
		opacity: 0;
		transform: translateY(2.5rem);
	}

	18%,
	82% {
		opacity: 1;
		transform: translateY(0);
	}

	100% {
		opacity: 0;
		transform: translateY(-2.5rem);
	}
}

/* the pen ranges this on `entry 20% exit 80%`, which on a 300svh track
   resolves almost entirely OUTSIDE the pinned stretch — the copy finished
   arriving before the frame locked and started leaving before the panels
   had. Re-anchored to the same `cover` span the panels ride, so all three
   elements in the section share one clock. */
.plane-fixed-content {
	animation-range: cover 0% cover 100%;
}


/* ============================================================
   ADDED 4 — THE SPENT FRAME MUST STOP PAINTING

   frameExpand ends at opacity 0 with blur(10px), held there by
   `fill-mode: both` for the whole rest of the document. At opacity 0 the
   frame is still rasterised every frame: 80vw x 70vh, projected 1.2x by
   its own translateZ, through a 10px blur, behind eight more sections.
   Invisible and expensive.

   `visibility` is discrete, so it flips at the last keyframe and nothing
   is seen to change. This is the tunnel-close-out lesson from css/style.css
   applied to the pen's own frame: a spent frame must stop PAINTING, not
   just fade. Paint cost, not appearance.

   THE PERSPECTIVE POLE IS SAFE HERE and this note is so nobody re-checks
   it: translateZ(200px) against the .hero-viewport's perspective: 1200px
   is 0.167 of the plane, well inside the 0.75 ceiling, and the projection
   is 1200/(1200-200) = 1.2x. It is only worth watching because the same
   keyframe also carries a filter, which is the combination that turns an
   over-poled Z into a GPU crash rather than jank.
   ============================================================ */
@keyframes frameExpand {
	0% {
		transform: translateZ(-300px) rotateX(10deg);
		opacity: 0.8;
		visibility: visible;
	}

	50% {
		transform: translateZ(0) rotateX(0deg);
		opacity: 1;
	}

	100% {
		transform: scale(1.15) translateZ(200px);
		opacity: 0;
		filter: blur(10px);
		visibility: hidden;
	}
}


/* ============================================================
   ADDED 5 — LINKS IN RUNNING COPY

   The pen styles .cta-button and nothing else, so an <a> inside a
   sentence rendered as the browser default: link blue on near-black,
   which is both ugly and under the contrast floor. This page carries a
   lot of links in running copy, because the altitude rule means it links
   DOWN to the pages that own each term instead of re-arguing them.

   Bronze on obsidian is 8.30:1. The underline is the affordance and it
   is always on: on a page this dark, colour alone is not enough of a
   signal, and colour alone is not a WCAG-legal one either.
   ============================================================ */
a {
	color: var(--bronze);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
	text-decoration-color: rgba(197, 160, 89, 0.45);
	transition: text-decoration-color .25s ease, color .25s ease;
}

@media (hover: hover) and (pointer: fine) {
	a:hover {
		text-decoration-color: currentColor;
	}
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible {
	outline: 2px solid var(--bronze);
	outline-offset: 3px;
	border-radius: 2px;
}

/* the two link classes that are NOT running copy: the pen's own button,
   and the masthead. Both undo the underline the rule above just set. */
.cta-button,
.masthead a,
.tt_card,
.slide_explore {
	text-decoration: none;
}

.skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 100;
	background: var(--bronze);
	color: var(--obsidian);
	padding: .75rem 1.25rem;
	font-size: .8rem;
	letter-spacing: .2em;
	text-transform: uppercase;
	text-decoration: none;
}

.skip:focus {
	left: 0;
}

.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}


/* ============================================================
   ADDED 6 — PAGE FURNITURE

   The pen is six stages and no page. These are the parts a page needs:
   a measure, a heading scale, an annotation, a lede and the definition
   block. All of them are built from the pen's own two faces and its one
   bronze, so nothing here is a new look.

   THE MEASURE IS 68ch AND IT IS THE POINT. This page argues that the tier
   is a standard rather than a style, and the cheapest visible proof of a
   standard is that the copy is set to a measure somebody chose.
   ============================================================ */
body {
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

h2 {
	font-size: clamp(1.9rem, 3.4vw, 3rem);
	line-height: 1.15;
	margin-bottom: 1.5rem;
}

h3 {
	font-size: clamp(1.25rem, 1.9vw, 1.5rem);
	line-height: 1.25;
}

p {
	max-width: 68ch;
}

p + p {
	margin-top: 1.1rem;
}

strong {
	color: var(--alabaster);
	font-weight: 600;
}

em {
	font-style: italic;
}

/* the annotation: the pen's badge type without the box. Section numbers,
   titleblock lines and the PS label all wear it. --muted is 5.3:1 on
   obsidian, which is the floor this page holds everywhere. */
.anno {
	font-family: var(--font-sans);
	font-size: .7rem;
	font-weight: 500;
	letter-spacing: .3em;
	text-transform: uppercase;
	color: var(--muted);
	max-width: none;
}

.lede {
	font-size: clamp(1.02rem, 1.5vw, 1.18rem);
	font-weight: 300;
	color: var(--muted);
}

.lede strong {
	font-weight: 500;
}

/* THE DEFINITION BLOCK — the answer an answer engine lifts whole. Set
   apart by measure and weight, never by a box: a box makes it read as an
   aside rather than as the section's own first sentence. The <strong>
   carries the definition and nothing else in the paragraph does. */
.define {
	font-size: clamp(1.05rem, 1.7vw, 1.22rem);
	line-height: 1.62;
	color: var(--muted);
}

.define strong {
	color: var(--alabaster);
	font-weight: 600;
}

/* every stage holds its content in one measured column */
.stage-inner {
	width: 100%;
	max-width: 1200px;
	padding: 0 clamp(1.5rem, 5vw, 4rem);
}

.section-head {
	margin-bottom: clamp(1.75rem, 4vh, 2.75rem);
}

.section-head h2 {
	margin-bottom: 0;
}

/* the hero's action pair, which is where the cut experience gate went.
   The primary is the pen's own .cta-button; the secondary wears the
   badge's chrome so the two read as a pair rather than as a button and
   an afterthought. */
.hero-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 1rem;
	margin-top: 2rem;
}

.cta-ghost {
	display: inline-block;
	padding: 1.25rem 2.5rem;
	border: 1px solid var(--border-light);
	color: var(--alabaster);
	font-size: 0.8rem;
	letter-spacing: 0.25em;
	text-transform: uppercase;
	text-decoration: none;
	transition: border-color .4s ease, box-shadow .4s ease;
}

@media (hover: hover) and (pointer: fine) {
	.cta-ghost:hover {
		border-color: var(--bronze);
		box-shadow: 0 0 20px var(--bronze-glow);
	}
}

/* the badge is an inline-flex box with a bottom margin, which is right
   where it opens a section and wrong everywhere else */
.badge--tight {
	margin-bottom: 1rem;
}


/* ============================================================
   ADDED 7 — THE MASTHEAD AND THE INDEX

   This page is not in anybody's nav, so it carries its own way back to
   the site: a wordmark and the seven chain pages, as real links, present
   at every width. Crawl parity is the whole reason the panel is built
   this way rather than hidden below the breakpoint.

   Above 900px it is a row across the top. Below, it collapses to a
   toggle and the seven links become a full-height sheet. The panel is
   `inert` while closed AND ONLY while the query matches, or the desktop
   nav would be killed outright; the page behind it is `inert` while it
   is open, which is the cheapest correct focus trap there is. The script
   is inline in the page, because this page loads no main.js.
   ============================================================ */
.masthead {
	position: fixed;
	inset: 0 0 auto;
	z-index: 40;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1.5rem;
	padding: 1.1rem clamp(1.25rem, 4vw, 3rem);
	background: linear-gradient(180deg, rgba(5, 5, 5, .92) 0%, rgba(5, 5, 5, .72) 60%, rgba(5, 5, 5, 0) 100%);
}

.wordmark {
	font-family: var(--font-serif);
	font-size: .95rem;
	font-weight: 600;
	letter-spacing: .22em;
	text-transform: uppercase;
	color: var(--alabaster);
	white-space: nowrap;
}

.index_sheet {
	display: flex;
	align-items: center;
	gap: clamp(1rem, 2.2vw, 2rem);
}

.index_sheet a {
	font-family: var(--font-sans);
	font-size: .68rem;
	font-weight: 500;
	letter-spacing: .2em;
	text-transform: uppercase;
	color: var(--muted);
	transition: color .25s ease;
}

@media (hover: hover) and (pointer: fine) {
	.index_sheet a:hover {
		color: var(--bronze);
	}
}

.index_sheet a:focus-visible {
	color: var(--bronze);
}

.nav_toggle {
	display: none;
	width: 44px;
	height: 44px;
	background: none;
	border: 0;
	padding: 0;
	cursor: pointer;
	color: var(--alabaster);
}

.nav_toggle svg {
	width: 24px;
	height: 24px;
	display: block;
	margin: 0 auto;
}

@media (max-width: 900px) {
	.nav_toggle {
		display: block;
	}

	.index_sheet {
		position: fixed;
		inset: 0;
		flex-direction: column;
		align-items: flex-start;
		justify-content: center;
		gap: 1.5rem;
		padding: 6rem clamp(1.5rem, 8vw, 4rem) 4rem;
		background: var(--obsidian);
		border-right: 1px solid var(--border-light);
		translate: -100% 0;
		visibility: hidden;
		transition: translate .45s cubic-bezier(.54, .35, .29, .99), visibility .45s;
	}

	html.nav-open .index_sheet {
		translate: 0 0;
		visibility: visible;
	}

	.index_sheet a {
		font-family: var(--font-serif);
		font-size: 1.35rem;
		letter-spacing: .1em;
		color: var(--alabaster);
	}
}

/* the fixed masthead is ~66px of chrome, so every in-page jump has to
   clear it or the target heading lands underneath */
[id] {
	scroll-margin-top: 5.5rem;
}


/* ============================================================
   ADDED 8 — THE LADDERS

   THE PEN'S nth-child SELECTORS ARE CORRECT AS WRITTEN and this note
   exists so nobody "fixes" them: the badge is the first child of both
   .louver-container and .aeo-container, so .louver-slat:nth-child(2) IS
   the first slat. Keep the badge inside the container and the pen's
   three rules keep meaning what they meant.

   WHAT DID CHANGE IS HOW MANY THERE ARE, and it is a geometry limit
   rather than a preference. A .sticky-stage is one viewport with
   `overflow: hidden`, so whatever it holds has to fit inside a short
   laptop window or it is clipped at a hard edge with no visible cause,
   which is the NO INVISIBLE WALLS rule. Four slats and six cells fit.
   Eight answers do not, which is why #questions is not a stage at all.
   ============================================================ */

/* --- the spec sheet: four slats, the pen's +10 ladder continued ------- */
.louver-slat:nth-child(2) {
	animation-range: cover 8% cover 58%;
}

.louver-slat:nth-child(3) {
	animation-range: cover 18% cover 68%;
}

.louver-slat:nth-child(4) {
	animation-range: cover 28% cover 78%;
}

.louver-slat:nth-child(5) {
	animation-range: cover 38% cover 88%;
}

.louver-title {
	font-size: clamp(1.15rem, 1.6vw, 1.4rem);
}

.louver-slat p {
	font-size: .95rem;
}

/* --- the fit grid: six cells over two rows -------------------------- */
/* The pen states one range for every cell, so all six arrive together.
   Staggering them by DOM order is what makes the grid assemble rather
   than appear, and the second row is held back a beat behind the first
   so the eye reads across before it reads down. */
.matrix-cell:nth-child(1) {
	animation-range: cover 8% cover 68%;
}

.matrix-cell:nth-child(2) {
	animation-range: cover 12% cover 72%;
}

.matrix-cell:nth-child(3) {
	animation-range: cover 16% cover 76%;
}

.matrix-cell:nth-child(4) {
	animation-range: cover 20% cover 80%;
}

.matrix-cell:nth-child(5) {
	animation-range: cover 24% cover 84%;
}

.matrix-cell:nth-child(6) {
	animation-range: cover 28% cover 88%;
}

.matrix-cell:nth-child(1)::before {
	animation-range: cover 14% cover 54%;
}

.matrix-cell:nth-child(2)::before {
	animation-range: cover 18% cover 58%;
}

.matrix-cell:nth-child(3)::before {
	animation-range: cover 22% cover 62%;
}

.matrix-cell:nth-child(4)::before {
	animation-range: cover 26% cover 66%;
}

.matrix-cell:nth-child(5)::before {
	animation-range: cover 30% cover 70%;
}

.matrix-cell:nth-child(6)::before {
	animation-range: cover 34% cover 74%;
}

.matrix-cell h3 {
	margin-bottom: .75rem;
}

.matrix-cell p {
	font-size: .95rem;
}

/* the matrix carries its own heading above it, inside the stage */
.matrix-head {
	margin-bottom: clamp(1.5rem, 3.5vh, 2.5rem);
}


/* ============================================================
   ADDED 9 — #questions IS NOT A STAGE

   Eight self-contained answers is roughly 1,800px of type. There is no
   window it fits in, and the whole point of the section is that every
   answer is present, at full size, in the document, on load: it is the
   part of this page an answer engine is meant to lift.

   So the section flows normally and each block arrives on its OWN
   view() timeline, which is exactly what the site's own kit does for a
   card grid on a phone. The pen's fade-up keyframe is reused unchanged.

   IT IS AN INTRO, NOT A SCRUB. `forwards`, not `both`, and the range
   ends while the block is still climbing the screen: a triggered outro
   plays to a viewport that has already moved on, and an answer that
   fades back out is an answer that is not there to be quoted.

   THE RANGE IS ADDED 20's. It read `entry 15% cover 18%` until then, which
   finished the arrival with the block's top still 269px BELOW the fold on a
   390x844 phone: still climbing, but climbing off screen. See ADDED 20.

   The pen's .qa-block names --aeo-scroll. That timeline does not exist
   on this page any more, and an unresolvable timeline with `both` fill
   parks the element on its FROM keyframe, which here is opacity 0. The
   override below is therefore not a preference: without it the section
   is eight invisible paragraphs.
   ============================================================ */
.qa-block {
	animation-name: qaFadeUp;
	animation-timing-function: linear;
	animation-fill-mode: forwards;
	animation-timeline: view();
	animation-range: entry 15% cover 35%;
	transform-origin: 50% 100%;
}

.qa-block:last-child {
	border-bottom: 0;
}

.qa-question {
	font-size: clamp(1.3rem, 2.1vw, 1.8rem);
}

.qa-block p {
	font-size: 1rem;
}

/* the questions section is a plain block, so it states its own rhythm */
.flow-section {
	padding: clamp(4rem, 12vh, 8rem) 0;
}

.flow-section .stage-inner {
	margin-inline: auto;
}

.qa-more {
	margin-top: 2.5rem;
}


/* ============================================================
   ADDED 10 — INSIDE THE FRAME, AND THE REVERSED SPLIT

   The pen's hero frame is a flex column set to space-between with three
   children: the badge, the title, and a footer row. The footer row is
   the only part it left as an inline style, so it is written out here.

   The frame is 70vh of a 100dvh stage with 4rem of padding, and the
   heading is the largest type on the page, so on a short window the three
   children have to be allowed to close up rather than push each other
   out through `overflow: hidden`.

   .reversed mirrors the split so two consecutive split sections do not
   land the art on the same side twice. That is the site's own rule for
   its feature splits and it is the same reasoning here: consecutive
   splits alternate, so adding one means checking its neighbour.
   ============================================================ */
.hero-foot {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	align-items: flex-end;
	gap: 1.5rem;
}

.hero-foot p {
	max-width: 46ch;
	margin: 0;
}

.frame-mark {
	font-family: var(--font-serif);
	font-size: .9rem;
	letter-spacing: .18em;
	color: var(--bronze);
	white-space: nowrap;
}

@media (max-height: 760px) {
	.viewfinder-frame {
		padding: 2.25rem;
		gap: 1.25rem;
	}

	.hero-title {
		font-size: clamp(1.9rem, 3.4vw, 3rem);
	}
}

.split-plane-layout.reversed .plane-fixed-content {
	order: 2;
}

.split-plane-layout.reversed .plane-stack {
	order: 1;
}

@media (max-width: 900px) {

	/* stacked, the copy always leads: the panels are the illustration of
	   the argument above them, not a column beside it. And the stack has
	   to leave the absolute-positioned panels a box to sit in. */
	.split-plane-layout.reversed .plane-fixed-content,
	.split-plane-layout.reversed .plane-stack {
		order: 0;
	}

	.plane-stack {
		height: 380px;
	}

	.architectural-panel {
		padding: 2rem;
	}
}

.panel-tag {
	color: var(--bronze);
	font-size: 0.8rem;
	font-weight: 500;
	letter-spacing: 0.2em;
	text-transform: uppercase;
}

.architectural-panel h3 {
	margin: 0.5rem 0 1rem;
}

/* THE PEN DECLARES --manifesto-scroll AND NEVER USES IT. Rather than
   delete the name, this is the animation it was plainly for: without one
   the closing section is a 150svh track holding a completely static
   screen, which is dead scroll on the page that argues nothing is
   accidental. contentFade is reused, so no new motion is invented. */
.manifesto-view {
	animation: contentFade linear both;
	animation-timeline: --manifesto-scroll;
	animation-range: cover 0% cover 100%;
}

.manifesto-note {
	margin: -1.5rem auto 2.5rem;
}

.manifesto-view p {
	margin-inline: auto;
}


/* ============================================================
   ADDED 11 — THE CONTACT SECTION, THE PS CLOSE AND THE TITLEBLOCK

   A real form posting to /api/contact, the same Cloudflare Pages function
   every other page uses, with the `company` honeypot. The handler is
   inline in the page: this page loads no main.js.

   The section flows rather than pinning. A form inside a 300svh pinned
   stage means a visitor scrolls three screens while the thing they are
   trying to fill in sits still and then leaves; and a scrubbed frame
   wrapped around form controls is the same failure as a scrubbed frame
   around a card grid, one level worse because this one is interactive.

   THE PS CLOSE is the last block inside #contact, above the titleblock,
   exactly where /architecture-firm-websites/ puts it. No price and no
   comparison in it: a theme costs about what starting from scratch costs,
   and the only saving that may be claimed is that it settles the design.
   ============================================================ */
.contact-grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: clamp(2rem, 5vw, 5rem);
	align-items: start;
}

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

.contact_form {
	display: grid;
	gap: 1.1rem;
}

.field {
	display: grid;
	gap: .45rem;
}

.field label {
	font-family: var(--font-sans);
	font-size: .68rem;
	font-weight: 500;
	letter-spacing: .22em;
	text-transform: uppercase;
	color: var(--muted);
}

.field input,
.field textarea {
	width: 100%;
	font-family: var(--font-sans);
	font-size: 1rem;
	color: var(--alabaster);
	background: var(--charcoal);
	border: 1px solid var(--border-light);
	padding: .85rem 1rem;
	transition: border-color .25s ease;
}

.field textarea {
	resize: vertical;
	min-height: 8rem;
}

.field input:focus,
.field textarea:focus {
	border-color: var(--bronze);
}

/* the honeypot. Off-screen rather than display:none, because a bot that
   reads computed style skips a hidden field and fills a visible one. */
.hp_field {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.contact_form button {
	justify-self: start;
	cursor: pointer;
	background: transparent;
	color: var(--alabaster);
	border: 1px solid var(--bronze);
	padding: 1.1rem 3rem;
	font-family: var(--font-sans);
	font-size: 0.8rem;
	letter-spacing: 0.25em;
	text-transform: uppercase;
	transition: background .4s ease, color .4s ease, box-shadow .4s ease;
	box-shadow: 0 0 20px var(--bronze-glow);
}

@media (hover: hover) and (pointer: fine) {
	.contact_form button:hover:not(:disabled) {
		background: var(--bronze);
		color: var(--obsidian);
		box-shadow: 0 0 40px rgba(197, 160, 89, 0.4);
	}
}

.contact_form button:disabled {
	opacity: .55;
	cursor: default;
}

.form_status {
	font-size: .9rem;
	min-height: 1.4em;
	margin: 0;
}

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

.form_status.err {
	color: #e8a49a;
}

.ps_note {
	position: relative;
	margin-top: clamp(2.5rem, 6vh, 4rem);
	padding-top: clamp(1.5rem, 3vh, 2.25rem);
	border-top: 1px solid var(--border-light);
}

.ps_note .anno {
	color: var(--bronze);
	margin-bottom: .75rem;
}

.titleblock {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 1rem;
	margin-top: clamp(2rem, 5vh, 3rem);
	padding-top: 1rem;
	border-top: 1px solid var(--border-light);
}


/* ============================================================
   ADDED 12 — THE COLLECTION BAND

   "Like this theme? You might also like:" — global for the themes and the
   landing pages. Built to travel, so nothing here is named for this page
   and nothing is an id.

   The three cards are HOME'S POSTERS, PORTED VERBATIM: .slide_poster,
   .poster_bg, .poster_title, .poster_sub, .poster_kicker and each theme's
   own block, copied unchanged out of css/style.css, which this page does
   not load. NOT ported: the 3-D hover pops and the fan poses, both of
   which need the carousel's preserve-3d rig. A change to style.css's
   POSTERS block is a change to this copy.

   THE BAND KEEPS THIS PAGE'S BLACK WALL, and that is the one thing it
   does differently from the other three. Everywhere else the band sits on
   a lit wall; here it cannot, because this theme's annotation and links
   are bronze, and bronze on a light surface measures 2.29:1. The colophon
   under it takes the same wall, and the band's own bottom hairline is the
   only rule between them.
   ============================================================ */
.alsolike {
	position: relative;
	padding: clamp(3.5rem, 9vh, 7rem) clamp(1.5rem, 5vw, 4rem);
	border-top: 1px solid var(--border-light);
	background:
		radial-gradient(120% 90% at 50% 0%, rgba(197, 160, 89, .07) 0%, transparent 62%),
		var(--obsidian);
}

.alsolike_inner {
	width: min(100%, 1200px);
	margin-inline: auto;
}

.alsolike_eyebrow {
	color: var(--bronze);
	margin-bottom: .9rem;
}

.alsolike h2 {
	max-width: 22ch;
}

.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. No card chrome: the poster is the object, and
   the caption below it reads as a caption rather than as furniture.
   container-type is what gives the poster its em scale, exactly as
   .theme_stack_col does for the home fan, and with no padding 100cqw IS
   the poster's width. */
.tt_card {
	display: block;
	container-type: inline-size;
	transition: translate .3s ease;
}

/* the lift is on `translate`, never on `transform`: the poster elements
   inside own transform, and a hover written as one would fight them */
@media (hover: hover) and (pointer: fine) {
	.tt_card:hover {
		translate: 0 -4px;
	}

	.tt_card:hover .tt_art {
		box-shadow: 0 22px 46px rgb(0 0 0 / .55);
	}
}

.tt_art {
	position: relative;
	overflow: hidden;
	aspect-ratio: 700 / 450;
	--u: calc(100cqw / 700);
	font-size: calc(16 * var(--u));
	border: 1px solid var(--border-light);
	box-shadow: 0 16px 36px rgb(0 0 0 / .45);
	transition: box-shadow .3s ease;
}

.tt_note {
	margin-top: .9rem;
	font-size: .92rem;
}

.tt_card .titleblock {
	margin-top: .9rem;
	padding-top: .75rem;
}

.tt_card .titleblock .anno:last-child {
	color: var(--bronze);
}

/* ---- the poster base, ported verbatim from css/style.css ---------- */
.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). Kept as its own layer because
   overflow:hidden on .slide_poster itself would flatten the translateZ
   pops of the title/button. */
.poster_bg {
	position: absolute;
	inset: 0;
	overflow: hidden;
	border-radius: inherit;
	transform: translateZ(0);
}

/* Every poster element rests 2px off the card face (translateZ(2px)) —
   never coplanar with the opaque .poster_bg at Z 0, so the preserve-3d
   depth sort never has a tie to resolve against the bg. (The mouseout
   blink this was first blamed on was really the --bounce overshoot —
   see the pops comment below — but the lift stays as cheap insurance.)
   The hover pops overwrite from this 2px base; give any NEW poster
   element the same rest lift. Raw px on purpose, matching the pops
   (45/30px). */
.poster_title {
	margin: 0;
	position: relative;
	font-size: 3.4em;
	line-height: 1.05;
	letter-spacing: 2px;
	transform: translateZ(2px);
	transition: transform .35s ease;
}

.poster_sub {
	margin: 0;
	position: relative;
	font-size: 1.3em;
	line-height: 1.2;
	transform: translateZ(2px);
	transition: transform .35s ease;
}

.poster_kicker {
	margin: 0 0 .4em;
	position: relative;
	font-size: .8em;
	letter-spacing: .28em;
	text-transform: uppercase;
	font-family: 'Courier New', monospace;
	transform: translateZ(2px);
}

/* ---- Silver Plate, ported verbatim ------------------------------- */
.poster_silver .poster_bg {
	background:
		radial-gradient(circle 16em at 30% 20%, #38475b, transparent 70%),
		#242c38;
}

/* THE BAND IS ANCHORED BY ITS CENTRE ON THE CORNER, not by a tuned top/right
   pair (2026-07-31, Adam: on the previews the badge read "too far up and too
   far in"). translate(50%, -50%) parks the box's centre exactly on the card's
   top-right corner; the 4em diagonal then steps it back inside along the
   band's own normal. The label is centred in the BOX, and only this puts the
   box centre on the middle of the VISIBLE chord — the old offsets left it
   1.5em up-left of it here, and further off on the landing pages, where the
   ribbon inherits the host theme's face and measures differently again.
   4em is the one choice in the rule: it is half the chord's diagonal, so the
   visible band is 4em times 2 times root-2 long (~11.3em, which is what it
   already measured — this re-centres the label, it does not resize the band).
   Keep the horizontal padding generous: the box has to stay LONGER than that
   chord or the gold runs out before the corner does. The real theme does the
   same thing with translate(28%, 55%) on a fixed 260px width
   (portfolio/themes/silver-plate/theme.css .purchased-banner). */
.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
   (portfolio/themes/silver-plate/theme.css .teapot / .teapot--mirror) */
.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;
}

/* ---- Long View, ported verbatim ---------------------------------- */
.poster_longview .poster_bg {
	background:
		/* the sheet, warmed unevenly the way vellum is */
		radial-gradient(ellipse 90% 80% at 32% 24%, #f2eee5 0%, #e7e1d4 68%),
		#e7e1d4;
}

.poster_longview .poster_bg::after {
	content: "";
	position: absolute;
	inset: .65em;
	border: 1px solid rgba(31, 42, 33, .3);
}

/* THE MARK — the maturity circle. Two rings on one element: the dashed
   promise is the border, the planted thing is the small inset disc. */
.poster_longview .poster_mark {
	position: absolute;
	left: 50%;
	top: 50%;
	width: 7.4em;
	height: 7.4em;
	translate: -50% -50%;
	transform: translateZ(1px);
	border: 1px dashed rgba(31, 42, 33, .55);
	border-radius: 50%;
}

.poster_longview .poster_mark::after {
	content: "";
	position: absolute;
	left: 50%;
	top: 50%;
	width: 1.5em;
	height: 1.5em;
	translate: -50% -50%;
	border: 1px solid rgba(31, 42, 33, .75);
	border-radius: 50%;
	background: rgba(31, 42, 33, .12);
}

.poster_longview .poster_kicker {
	color: #46523f;
}

.poster_longview .poster_title {
	font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
	font-size: 2em;
	font-weight: 600;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: #1f2a21;
}

.poster_longview .poster_sub {
	color: #46523f;
}

/* ---- Daylight Study, ported verbatim ----------------------------- */
.poster_daylight .poster_bg {
	background:
		linear-gradient(118deg, rgba(240, 215, 164, .58) 0 20%, rgba(240, 215, 164, 0) 62%),
		linear-gradient(168deg, #eceded 0%, #dfe1dd 100%);
}

/* the sheet edge. ::after because .poster_bg::before is unused and this
   is the layer that clips — the border must not ride the type's Z pops */
.poster_daylight .poster_bg::after {
	content: "";
	position: absolute;
	inset: .65em;
	border: 1px solid rgba(21, 24, 26, .28);
}

.poster_daylight .poster_kicker {
	font-family: "JetBrains Mono", ui-monospace, monospace;
	color: #676c6e;
}

.poster_daylight .poster_title {
	font-family: "JetBrains Mono", ui-monospace, monospace;
	font-size: 2.3em;
	letter-spacing: .01em;
	text-transform: uppercase;
	color: #15181a;
}

.poster_daylight .poster_sub {
	color: #676c6e;
}


/* ============================================================
   ADDED 13 — THE COLOPHON, AND THE FORCED-COLOURS FLOOR
   ============================================================ */
.colophon {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 1rem;
	padding: 1.5rem clamp(1.5rem, 5vw, 4rem) 2.5rem;
	background: var(--obsidian);
}

/* the band closes itself with a hairline and 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 never matches. */
main:has(.alsolike) + .colophon {
	border-top: 0;
}

/* The manifesto quote is the single most quotable sentence on the page
   and it is painted by clipping a gradient to the glyphs. In Windows
   High Contrast the gradient is dropped and the transparent fill is
   kept, so the sentence renders as nothing at all. */
@media (forced-colors: active) {
	.manifesto-text {
		background: none;
		-webkit-text-fill-color: currentColor;
		color: CanvasText;
	}

	.badge,
	.cta-button,
	.cta-ghost,
	.tt_art {
		border-color: CanvasText;
	}
}


/* ============================================================================
   ============        A D D E D   1 4  —  T H E   S E T T L E D   P A G E
   ============================================================================

   THIS BLOCK IS THE REASON THE PAGE IS NOT BLANK, and it is the single most
   load-bearing thing in the file.

   The pen is 100% scroll-timeline driven and every one of its keyframes runs
   `fill-mode: both` off a FROM state of `opacity: 0`. Where the timeline
   cannot resolve, the animation becomes a 0s time animation parked on that
   FROM state: six full screens of nothing, with 300svh of scroll under each
   one. That is not a degraded page, it is a broken one, and it is what
   Firefox, an older Safari, a reduced-motion reader and anyone who picked
   Standard or Slide Deck on this site would all have seen.

   A COLLAPSED RUNWAY MUST HAVE ITS SCRUB KILLED IN THE SAME RULE. Killing the
   track without killing the animation leaves the content invisible; killing
   the animation without killing the track leaves screens of dead scroll.

   THREE GUARDS, ONE OUTCOME, and they are three copies on purpose:

     1. @supports not (animation-timeline: view())
        the browser cannot do this at all. Firefox today.
     2. @media (prefers-reduced-motion: reduce)
        the reader asked their operating system for stillness, and they get
        it without needing this site's own stamp or any JavaScript.
     3. html.no-runway
        the site's own stamp, set pre-paint for reduced motion AND for the
        'standard' and 'deck' experiences. A class cannot be tested inside
        @supports, which is why this one cannot be folded into the first.
        The experience selector is not on this page, but a choice stored
        anywhere else on the site still stamps the flag here.

   Editing one of these means editing all three. They must stay identical.
   ============================================================================ */

@supports not (animation-timeline: view()) {

	.scroll-section,
	.scroll-section.short {
		height: auto;
		min-height: 100svh;
	}

	.sticky-stage {
		position: static;
		height: auto;
		overflow: visible;
		padding-block: clamp(3.5rem, 10vh, 7rem);
	}

	.hero-viewport {
		position: static;
		display: block;
		height: auto;
		perspective: none;
	}

	.viewfinder-frame {
		position: static;
		width: auto;
		height: auto;
		animation: none;
		transform: none;
		filter: none;
		opacity: 1;
		visibility: visible;
	}

	.plane-fixed-content {
		animation: none;
		transform: none;
		opacity: 1;
	}

	.plane-stack {
		position: static;
		display: grid;
		gap: 1.25rem;
		height: auto;
		perspective: none;
	}

	.architectural-panel {
		position: static;
		inset: auto;
		animation: none;
		transform: none;
		opacity: 1;
		backdrop-filter: none;
		background: var(--charcoal);
		border-color: var(--border-light);
	}

	.aeo-container,
	.louver-container {
		perspective: none;
	}

	.qa-block,
	.louver-slat,
	.matrix-cell,
	.manifesto-view {
		animation: none;
		transform: none;
		opacity: 1;
	}

	.matrix-cell::before {
		animation: none;
		transform: scaleX(1);
	}
}

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

	.scroll-section,
	.scroll-section.short {
		height: auto;
		min-height: 100svh;
	}

	.sticky-stage {
		position: static;
		height: auto;
		overflow: visible;
		padding-block: clamp(3.5rem, 10vh, 7rem);
	}

	.hero-viewport {
		position: static;
		display: block;
		height: auto;
		perspective: none;
	}

	.viewfinder-frame {
		position: static;
		width: auto;
		height: auto;
		animation: none;
		transform: none;
		filter: none;
		opacity: 1;
		visibility: visible;
	}

	.plane-fixed-content {
		animation: none;
		transform: none;
		opacity: 1;
	}

	.plane-stack {
		position: static;
		display: grid;
		gap: 1.25rem;
		height: auto;
		perspective: none;
	}

	.architectural-panel {
		position: static;
		inset: auto;
		animation: none;
		transform: none;
		opacity: 1;
		backdrop-filter: none;
		background: var(--charcoal);
		border-color: var(--border-light);
	}

	.aeo-container,
	.louver-container {
		perspective: none;
	}

	.qa-block,
	.louver-slat,
	.matrix-cell,
	.manifesto-view {
		animation: none;
		transform: none;
		opacity: 1;
	}

	.matrix-cell::before {
		animation: none;
		transform: scaleX(1);
	}

	.cta-button,
	.cta-ghost,
	.tt_card,
	.tt_art,
	a,
	.field input,
	.field textarea,
	.contact_form button {
		transition: none;
	}
}

/* ---- guard 3: the site's own stamp. Same collapse, class-scoped. ---- */
html.no-runway .scroll-section,
html.no-runway .scroll-section.short {
	height: auto;
	min-height: 100svh;
}

html.no-runway .sticky-stage {
	position: static;
	height: auto;
	overflow: visible;
	padding-block: clamp(3.5rem, 10vh, 7rem);
}

html.no-runway .hero-viewport {
	position: static;
	display: block;
	height: auto;
	perspective: none;
}

html.no-runway .viewfinder-frame {
	position: static;
	width: auto;
	height: auto;
	animation: none;
	transform: none;
	filter: none;
	opacity: 1;
	visibility: visible;
}

html.no-runway .plane-fixed-content {
	animation: none;
	transform: none;
	opacity: 1;
}

html.no-runway .plane-stack {
	position: static;
	display: grid;
	gap: 1.25rem;
	height: auto;
	perspective: none;
}

html.no-runway .architectural-panel {
	position: static;
	inset: auto;
	animation: none;
	transform: none;
	opacity: 1;
	backdrop-filter: none;
	background: var(--charcoal);
	border-color: var(--border-light);
}

html.no-runway .aeo-container,
html.no-runway .louver-container {
	perspective: none;
}

html.no-runway .qa-block,
html.no-runway .louver-slat,
html.no-runway .matrix-cell,
html.no-runway .manifesto-view {
	animation: none;
	transform: none;
	opacity: 1;
}

html.no-runway .matrix-cell::before {
	animation: none;
	transform: scaleX(1);
}


/* ============================================================================
   ============   A D D E D   1 5  —  A   S T A G E   P I N S   O N L Y
   ============   W H E R E   I T S   C O N T E N T   F I T S
   ============================================================================

   A .sticky-stage is ONE viewport with `overflow: hidden`, and the pen centres
   its content inside it. So the moment the content is taller than the window,
   the overflow splits top and bottom and the frame eats both ends of it: the
   section's own badge disappears off the top behind a hard edge with no visible
   cause. That is the NO INVISIBLE WALLS rule, and it was live here at every
   viewport shorter or narrower than about 1440x900.

   MEASURED, not guessed (the settled wrapper against its stage):

       1440x900   split 500/900    louvers 814/900    matrix  761/900   fits
       1280x720   split 500/720    louvers 809/720    matrix  750/720
        900x700   split 702/700    louvers 1168/700   matrix 1367/700
        390x844   split 1113/844   louvers 1494/844   matrix 1749/844

   THE FIX IS NOT TO SHRINK THE CONTENT. Tightening the pen's padding or
   dropping rows would be a change to the pitch, and the pen ships. It is to
   stop PINNING where the pin cannot honestly hold what it is given: below the
   thresholds the section becomes an ordinary full-screen block, the content
   flows, and each piece arrives on its own view() timeline and STAYS.

   That is the site's own answer, not a new one. `.grid-section` in
   css/style.css keeps its pinned runway on DESKTOP ONLY and lets a phone bring
   the cards in individually, for exactly this reason: "a phone cannot pin six
   single-column cards".

   TWO THRESHOLDS, because the two shapes need different room, and each states
   what it actually measured:
     · the splits stack below 901px and need ~1100px stacked, so they pin above
       that width and above 620px of height (they need ~500px two-column).
     · the louvers and the matrix need ~820px even multi-column, so they also
       want 880px of height. A 1280x720 laptop therefore reads those two as
       ordinary sections. That is deliberate: 809px of slats in a 720px window
       is not a pinned frame, it is a clipped one.

   `forwards`, never `both`, on everything below: unpinned, these are intros to
   content the reader is scrolling past, and an outro would fade the copy back
   out behind them.

   EVERY RANGE IN THIS BLOCK IS ADDED 20's, NOT THIS BLOCK'S ORIGINAL. The
   substitutes shipped on `entry 8-12% cover 12-16%`, which played each arrival
   out in 80-160px of scroll and finished it with the element still in the
   bottom quarter of the screen. Read ADDED 20 before changing a number here.
   ============================================================================ */

@media (max-width: 900px), (max-height: 620px) {

	#definition,
	#default {
		height: auto;
		min-height: 100svh;
	}

	#definition .sticky-stage,
	#default .sticky-stage {
		position: static;
		height: auto;
		min-height: 100svh;
		overflow: visible;
		padding-block: clamp(3.5rem, 10vh, 6rem);
	}

	/* the copy column stops being scrubbed and simply arrives */
	#definition .plane-fixed-content,
	#default .plane-fixed-content {
		animation-name: qaFadeUp;
		animation-fill-mode: forwards;
		animation-timeline: view();
		animation-range: entry 15% cover 35%;
		transform-origin: 50% 100%;
	}

	/* THE PANELS COME OUT OF THE ABSOLUTE STACK. They are `position: absolute;
	   inset: 0` inside a fixed-height .plane-stack precisely so they can glide
	   through a pinned frame one after another; with no frame to glide through
	   they are two cards, and they stack. */
	#definition .plane-stack,
	#default .plane-stack {
		height: auto;
		display: grid;
		gap: 1.25rem;
		perspective: none;
	}

	#definition .architectural-panel,
	#default .architectural-panel {
		position: static;
		inset: auto;
		animation-name: qaFadeUp;
		animation-fill-mode: forwards;
		animation-timeline: view();
		animation-range: entry 15% cover 35%;
		transform-origin: 50% 100%;
	}

	/* panel-2 wears the bronze border at the top of its glide; unpinned there
	   is no glide, so it takes it at rest and the pair still reads as a
	   sequence rather than as two identical cards. */
	#definition .panel-2,
	#default .panel-2 {
		border-color: var(--bronze);
	}
}

@media (max-width: 900px), (max-height: 879px) {

	#standard,
	#fit {
		height: auto;
		min-height: 100svh;
	}

	#standard .sticky-stage,
	#fit .sticky-stage {
		position: static;
		height: auto;
		min-height: 100svh;
		overflow: visible;
		padding-block: clamp(3.5rem, 10vh, 6rem);
	}

	#standard .louver-container {
		perspective: none;
	}

	/* the slats stop flipping through a frame and simply open, in order. The
	   pen's own keyframe is reused at half its rotation, because a 75deg flip
	   with no frame around it reads as a glitch rather than as a louver. */
	#standard .louver-slat {
		animation-name: louver-open;
		animation-fill-mode: forwards;
		animation-timeline: view();
		animation-range: entry 15% cover 35%;
	}

	#fit .matrix-cell {
		animation-name: qaFadeUp;
		animation-fill-mode: forwards;
		animation-timeline: view();
		animation-range: entry 15% cover 35%;
		transform-origin: 50% 100%;

		/* AND THE CELL LENDS ITS OWN TIMELINE TO ITS ::before. The pen gives
		   .matrix-cell `overflow: hidden`, which makes every cell a SCROLL
		   CONTAINER, so a bare `view()` on the pseudo resolves against the
		   cell instead of the window: a box that never scrolls, a timeline
		   pinned at ~99%, and a bronze rule that was simply drawn already,
		   at every scroll position, on every phone. Named here and referenced
		   below, so the rule rides the same window the cell does. */
		view-timeline-name: --cell-view;
	}

	/* the bronze rule still draws, it just draws on arrival */
	#fit .matrix-cell::before {
		animation-fill-mode: forwards;
		animation-timeline: --cell-view;
		animation-range: cover 40% cover 55%;
	}
}

@keyframes louver-open {
	from {
		transform: rotateX(-38deg);
		opacity: 0;
	}

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


/* ============================================================================
   ADDED 16 — THE GUARDS, RESTATED AT ADDED 15's SPECIFICITY

   ADDED 15 had to write `#standard .louver-slat` to beat the pen, which is
   (1,1,0). The three collapse guards in ADDED 14 are written on classes alone,
   (0,2,0) at best — so an ID selector added later silently outranks every one
   of them, and a reader who asked for no motion would have got the mobile
   intros anyway. Specificity, not source order, is the trap here.

   THESE MUST STAY IN STEP WITH ADDED 14. Same three triggers, same outcome:
   nothing animates, nothing is left parked on a FROM keyframe.
   ============================================================================ */

@supports not (animation-timeline: view()) {

	#definition .plane-fixed-content,
	#default .plane-fixed-content,
	#definition .architectural-panel,
	#default .architectural-panel,
	#standard .louver-slat,
	#fit .matrix-cell {
		animation: none;
		transform: none;
		opacity: 1;
	}

	#fit .matrix-cell::before {
		animation: none;
		transform: scaleX(1);
	}
}

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

	#definition .plane-fixed-content,
	#default .plane-fixed-content,
	#definition .architectural-panel,
	#default .architectural-panel,
	#standard .louver-slat,
	#fit .matrix-cell {
		animation: none;
		transform: none;
		opacity: 1;
	}

	#fit .matrix-cell::before {
		animation: none;
		transform: scaleX(1);
	}
}

html.no-runway #definition .plane-fixed-content,
html.no-runway #default .plane-fixed-content,
html.no-runway #definition .architectural-panel,
html.no-runway #default .architectural-panel,
html.no-runway #standard .louver-slat,
html.no-runway #fit .matrix-cell {
	animation: none;
	transform: none;
	opacity: 1;
}

html.no-runway #fit .matrix-cell::before {
	animation: none;
	transform: scaleX(1);
}


/* ============================================================================
   ADDED 17 — THE COVER FRAME SIZES TO WHAT IS IN IT

   The pen fixes the frame at `height: 70vh` and lays it out
   `justify-content: space-between`, which is right on the wide screen it was
   drawn on and wrong the moment the copy needs more room than that. Measured
   at 390px wide the frame is 466px holding 613px of content, and at 390x667 it
   is 368px holding 562px: the badge and the hero foot simply sat outside the
   frame's own border, on top of the page. Not clipped, but plainly broken.

   The pen's own mobile rule only ever changed the frame's WIDTH and padding,
   so its height was never given an answer at this size. This is that answer,
   and it is the smallest one that works: below the breakpoint the frame stops
   being a fixed box and becomes a box that fits its contents, capped so it can
   never grow past the stage that holds it. `space-between` has nothing to
   distribute at auto height, so the gap takes over the spacing.

   Above the breakpoint NOTHING here applies and the pen's 70vh stands.
   ============================================================================ */
@media (max-width: 900px), (max-height: 760px) {
	.viewfinder-frame {
		height: auto;
		max-height: 86dvh;
		gap: clamp(1rem, 2.5vh, 1.75rem);
	}

	.viewfinder-frame .badge {
		margin-bottom: 1rem;
	}

	.hero-foot {
		gap: 1rem;
	}
}

/* the hero actions wrap to their own line before they ever squeeze the
   frame-mark off the end of the foot row */
@media (max-width: 560px) {
	.hero-actions {
		width: 100%;
	}

	.cta-button,
	.cta-ghost {
		flex: 1 1 auto;
		text-align: center;
		padding-inline: 1.5rem;
	}
}


/* ============================================================================
   ============   A D D E D   1 8  —  T H E   P E N   R E A C H E S
   ============   A   S H O R T   W I N D O W
   ============================================================================

   REPORTED BY ADAM (2026-08-20): "the animation for the .louver-slat looks
   completely different... I gave you exactly the animations I liked", and
   "section 4 doesn't do anything".

   BOTH COMPLAINTS ARE ONE CAUSE, AND THE CAUSE IS ADDED 15, NOT THE PEN.
   ADDED 15 unpins #standard and #fit below 880px of viewport HEIGHT and swaps
   the pen's motion for a substitute. Measured at 1536x760 — a 1080p screen at
   Windows' default 125% scaling, which is the single most common desktop
   viewport there is:

                        what ADDED 15 served     the pen
       keyframe         louver-open              louverFlip
       perspective      none                     1500px
       pin              position: static         sticky
       range            entry 8% cover 12%       cover 8% cover 58%
                        (retimed since, by ADDED 20)

   No 3D, no lock, and about 90px of scroll to play it in. A sweep at 90px
   steps caught each slat mid-flip exactly once. #fit got the same treatment
   with qaFadeUp, which is why the matrix "doesn't do anything" either.

   ADDED 15's TRIGGER WAS REAL AND ITS CURE WAS WORSE THAN THE DISEASE. The
   louver container measures 839px against a 760px stage, so it genuinely
   overflowed by 79px with `overflow: hidden` eating ~40px off each end — the
   NO INVISIBLE WALLS rule. But the answer to "the content is 79px too tall"
   is to reclaim 79px, not to throw away the animation. ADDED 15's own note
   ruled that out ("THE FIX IS NOT TO SHRINK THE CONTENT") and then traded the
   pen's motion away to protect the pen's padding. That is the wrong way round:
   the padding is a proportion, the animation is the pitch.

   ADAM'S CALL (2026-08-20), asked because it changes how his pen looks:
   trim the padding, keep the motion.

   THE HEIGHT IS ALMOST ENTIRELY PADDING. 2.5rem top and bottom on four slats
   is 320px of the 839. Trimming to 1.5rem and the gap to 1rem reclaims 160px:

       1536x760   container 839 -> 679   fits, 81px spare
       1366x720   container 839 -> 677   fits, 43px spare
       1280x700   container 839 -> 673   fits, 27px spare

   #fit needed far less, and at Adam's own viewport it needed NOTHING — the
   wrapper measures 756px against a 760px stage, so ADDED 15 was firing there
   for a 4px margin it already had. One notch off the cell padding (3.5rem ->
   2.5rem) carries it down to 700px too:

       1536x760   wrapper 756 -> 692   fits, 68px spare
       1280x700   wrapper 749 -> 685   fits, 15px spare

   SO ADDED 15'S FLOOR MOVES FROM 880px TO 690px. Below that it still owns the
   two sections, unchanged, and it still owns every width <= 900px, where the
   slats stack single-column and get taller rather than shorter. That width
   arm is why this is a THREE-WAY split and not a threshold edit.

   WHAT THIS BLOCK DELIBERATELY DOES NOT DO: retime anything. An earlier pass
   re-anchored the slats from `cover` to `contain` to keep the whole wave
   inside the pin, and Adam rejected it on sight — the pen's slats BLEED across
   the section edges on purpose, which is the "slight lock" he was describing.
   `contain` clamps exactly that away. THE PEN'S RANGES ARE HIS. The ladders
   restated below are ADDED 8's values, character for character; they are here
   only because ADDED 15's `#fit .matrix-cell` is (1,1,0) and outranks ADDED
   8's (0,2,0), so a range set on classes alone would not survive this branch.
   If you change a number here, change ADDED 8 too, or they will disagree.
   ============================================================================ */

@media (min-width: 901px) and (min-height: 690px) and (max-height: 879px) {

	/* --- put the pin back exactly as the pen has it ------------------- */
	#standard,
	#fit {
		height: 150svh;
	}

	#standard .sticky-stage,
	#fit .sticky-stage {
		position: sticky;
		height: 100dvh;
		min-height: 0;
		overflow: hidden;
		padding-block: 0;
	}

	/* --- the louvers: the pen's motion, on reclaimed room ------------- */
	#standard .louver-container {
		perspective: 1500px;
		gap: 1rem;
	}

	#standard .louver-slat {
		padding-block: 1.5rem;
		animation-name: louverFlip;
		animation-timing-function: linear;
		animation-fill-mode: both;
		animation-timeline: --louver-scroll;
	}

	/* ADDED 8's ladder, restated at (1,2,0) to clear ADDED 15's (1,1,0).
	   nth-child counts from the badge, so (2) is slat 01. */
	#standard .louver-slat:nth-child(2) {
		animation-range: cover 8% cover 58%;
	}

	#standard .louver-slat:nth-child(3) {
		animation-range: cover 18% cover 68%;
	}

	#standard .louver-slat:nth-child(4) {
		animation-range: cover 28% cover 78%;
	}

	#standard .louver-slat:nth-child(5) {
		animation-range: cover 38% cover 88%;
	}

	/* --- the matrix: the pen's motion, one notch off the cell padding -- */
	#fit .matrix-cell {
		padding-block: 2.5rem;
		animation-name: matrixCellIn;
		animation-timing-function: linear;
		animation-fill-mode: both;
		animation-timeline: --matrix-scroll;
		transform-origin: 50% 50%;
	}

	#fit .matrix-cell:nth-child(1) {
		animation-range: cover 8% cover 68%;
	}

	#fit .matrix-cell:nth-child(2) {
		animation-range: cover 12% cover 72%;
	}

	#fit .matrix-cell:nth-child(3) {
		animation-range: cover 16% cover 76%;
	}

	#fit .matrix-cell:nth-child(4) {
		animation-range: cover 20% cover 80%;
	}

	#fit .matrix-cell:nth-child(5) {
		animation-range: cover 24% cover 84%;
	}

	#fit .matrix-cell:nth-child(6) {
		animation-range: cover 28% cover 88%;
	}

	/* the bronze rule draws on the pen's own timeline again */
	#fit .matrix-cell::before {
		animation-name: lineDraw;
		animation-timing-function: linear;
		animation-fill-mode: both;
		animation-timeline: --matrix-scroll;
	}

	#fit .matrix-cell:nth-child(1)::before {
		animation-range: cover 14% cover 54%;
	}

	#fit .matrix-cell:nth-child(2)::before {
		animation-range: cover 18% cover 58%;
	}

	#fit .matrix-cell:nth-child(3)::before {
		animation-range: cover 22% cover 62%;
	}

	#fit .matrix-cell:nth-child(4)::before {
		animation-range: cover 26% cover 66%;
	}

	#fit .matrix-cell:nth-child(5)::before {
		animation-range: cover 30% cover 70%;
	}

	#fit .matrix-cell:nth-child(6)::before {
		animation-range: cover 34% cover 74%;
	}
}

/* ADDED 16's lesson, restated at this block's specificity. Everything above is
   (1,1,0) or (1,2,0), so the three collapse guards — written on classes in
   ADDED 14 and on `#standard`/`#fit` in ADDED 16 — must be repeated here or a
   reader who asked for no motion gets the pen anyway, parked on a FROM
   keyframe. THESE MUST STAY IN STEP WITH ADDED 14 AND ADDED 16. */
@supports not (animation-timeline: view()) {

	#standard,
	#fit {
		height: auto;
		min-height: 100svh;
	}

	#standard .sticky-stage,
	#fit .sticky-stage {
		position: static;
		height: auto;
		min-height: 100svh;
		overflow: visible;
		padding-block: clamp(3.5rem, 10vh, 7rem);
	}

	#standard .louver-container {
		perspective: none;
	}

	#standard .louver-slat,
	#fit .matrix-cell {
		animation: none;
		transform: none;
		opacity: 1;
	}

	#fit .matrix-cell::before {
		animation: none;
		transform: scaleX(1);
	}
}

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

	#standard,
	#fit {
		height: auto;
		min-height: 100svh;
	}

	#standard .sticky-stage,
	#fit .sticky-stage {
		position: static;
		height: auto;
		min-height: 100svh;
		overflow: visible;
		padding-block: clamp(3.5rem, 10vh, 7rem);
	}

	#standard .louver-container {
		perspective: none;
	}

	#standard .louver-slat,
	#fit .matrix-cell {
		animation: none;
		transform: none;
		opacity: 1;
	}

	#fit .matrix-cell::before {
		animation: none;
		transform: scaleX(1);
	}
}

html.no-runway #standard,
html.no-runway #fit {
	height: auto;
	min-height: 100svh;
}

html.no-runway #standard .sticky-stage,
html.no-runway #fit .sticky-stage {
	position: static;
	height: auto;
	min-height: 100svh;
	overflow: visible;
	padding-block: clamp(3.5rem, 10vh, 7rem);
}

html.no-runway #standard .louver-container {
	perspective: none;
}

html.no-runway #standard .louver-slat,
html.no-runway #fit .matrix-cell {
	animation: none;
	transform: none;
	opacity: 1;
}

html.no-runway #fit .matrix-cell::before {
	animation: none;
	transform: scaleX(1);
}


/* ============================================================================
   ============   A D D E D   1 9  —  T H E   H E L D   S E N T E N C E
   ============================================================================

   Adam, 2026-08-21: on Flow and on Slide Deck, hold the reader in place at
   #manifesto and scrub a shine across the quote before the page moves on.

   WHY THIS SECTION AND NOT ANOTHER. It is the page's thesis, it is one
   sentence, and it is already painted by clipping a gradient to the glyphs
   (see .manifesto-text above). A sweep is therefore not a new mechanism
   bolted on: it is the SAME background, made far wider than the box and
   dragged across it. THE SENTENCE STARTS DULL AND THE SWEEP LIGHTS IT, which
   is the pen Adam pointed at (`.masked-reveal` / `textScrub`) rather than a
   highlight crossing text that was already lit. IT IS SILVER: white through
   greys, and no bronze in it. Restraint is part of what this page argues, so
   it passes ONCE and it settles.

   THREE PARTS, AND THEY ARE ONE GEOMETRY:

     1. THE HOLD IS THE TRACK. #manifesto dropped `.short` in index.html, so it
        is the pen's ordinary 300svh stage: 100svh of entry, then 200svh of
        pin. There is no new pinning machinery here at all — .sticky-stage
        already does it, it simply had nothing worth holding for.

     2. THE SITE'S OWN cover -> contain RULE, and it is the reason the shine
        cannot be seen anywhere but the pin. `contain 0%` IS the pin lock and
        `contain 100%` IS the release, so a range stated in `contain` can only
        play while the frame is frozen. The shine runs `contain 18% -> 68%`:
        36svh of settled reading, then 100svh (exactly one screen of scroll)
        of sweep, then 64svh before release.

     3. THE ARRIVAL STILL STARTS ON `cover`. contentFade is re-ranged to
        `cover 0% contain 100%` rather than moved wholly onto `contain`,
        because a stage that entered the screen empty and only began to fill
        once it locked would be a screen of dead scroll on the page that
        argues nothing is accidental. Mixed-name ranges are legal and this is
        what they are for: the quote arrives as the section rides up, holds
        through the pin, and its exit lands exactly on the release.

   WHY THE SHINE IS ON .manifesto-text AND THE FADE IS ON .manifesto-view.
   .manifesto-view is already scrubbed on opacity and transform. An element
   mid-animation on opacity is a render surface, and stacking a second scrubbed
   paint on the same box is the failure the site names on .hover_card. They are
   two elements, one timeline, no interaction.

   THE FOUR GUARDS. The first three are ADDED 14's, restated for the one new
   animation — a collapsed runway MUST have its scrub killed in the same rule,
   or `fill: both` parks the sentence DULL forever — the guards park it LIT
   (`background-position: 0%`), which is the opposite end from the rest state.
   Getting that backwards is how a fallback reader gets a grey sentence and no
   way to light it. The fourth is ADDED
   13's forced-colours floor, restated because this block comes after it and
   would otherwise put the gradient back into a High Contrast render where the
   glyphs are painted by `color` alone.
   ============================================================================ */

/* The exit is re-anchored so it finishes ON the pin release. Same specificity
   as the declaration above; later wins. */
.manifesto-view {
	animation-range: cover 0% contain 100%;
}

/* THE SENTENCE STARTS DULL AND THE SWEEP LIGHTS IT. Adam, 2026-08-21: a thin
   band passing over already-lit text was not obvious enough, and the bronze in
   it read as yellow on a page whose accent is bronze everywhere else. This is
   the pen's own `.masked-reveal` mechanism instead — ONE wide gradient, far
   wider than the box, dragged across it, so the whole sentence changes state
   rather than a highlight crossing it. IT IS SILVER: white through greys, no
   bronze anywhere in it.

   READ THE GRADIENT RIGHT TO LEFT, because that is the order it is seen in.
   `background-size: 240%` means the box shows 41.7% of the gradient at a time,
   and `background-position` slides that window from the right end to the left:

     100% -> the window sits at 58.3-100%, which is flat #78756e. DULL.
       0% -> the window sits at 0-41.7%: flat alabaster with the gleam arriving
             at its right edge. LIT, and this is the settled state every
             fallback gets.

   THE RAMP GOES IN THE MIDDLE OF WHAT IS LEFT, AND THAT IS THE WHOLE TRICK.
   The two flat runs are forced: dull must cover 58.3-100%, lit must cover
   0-41.7%. Everything between them, 41.7-58.3%, is the only room the bright-
   to-dull edge has, and the edge is only ON the sentence while the window
   overlaps it. Put the ramp at one end of that room and most of the scroll
   buys nothing: the first version ran 28-50% of a 350% image and the sentence
   sat dead still for the first 40% of the sweep, then did everything at once.
   Centred, the edge is crossing for ~86 of the 100svh.

   THREE NUMBERS, ONE DERIVATION. Window width is 1/S, travel is (S-1)/S, and
   the ramp must fit inside [1/S, (S-1)/S]. Change `background-size` and every
   stop below moves; re-derive them, do not nudge.

   THE DULL IS DULL, NOT HIDDEN. #78756e on obsidian is 4.19:1, which clears
   the WCAG floor for large text with room to spare, and this sentence is the
   page's thesis and its most liftable line — it is readable the whole time,
   whether or not the reader ever scrolls the sweep. The pen's own version
   bottoms out at #151515 and is genuinely invisible; that is the one thing
   from it that is not copied.

   `no-repeat` is not optional: the base shorthand above reset it to `repeat`,
   and a 350%-wide gradient left to tile puts a second sweep on the sentence.
   The 100deg rake (rather than a flat `to right`) is what makes the second
   line lag the first instead of the two lighting in lockstep. */
.manifesto-text {
	background-image: linear-gradient(100deg,
			#f7f5f0 0%,
			#f7f5f0 26%,
			#ffffff 40%,
			#e6e3dc 46%,
			#a9a69e 52%,
			#78756e 58%,
			#78756e 100%);
	background-size: 240% 100%;
	background-position: 100% 0;
	background-repeat: no-repeat;
	animation: manifesto-shine linear both;
	animation-timeline: --manifesto-scroll;
	animation-range: contain 18% contain 68%;
}

@keyframes manifesto-shine {
	from {
		background-position: 100% 0;
	}

	to {
		background-position: 0% 0;
	}
}

/* ---- guard 1: the browser cannot resolve a view timeline. Firefox today. ---- */
@supports not (animation-timeline: view()) {

	.manifesto-text {
		animation: none;
		background-position: 0% 0;
	}
}

/* ---- guard 2: the reader asked their operating system for stillness. ---- */
@media (prefers-reduced-motion: reduce) {

	.manifesto-text {
		animation: none;
		background-position: 0% 0;
	}
}

/* ---- guard 3: the site's own stamp — reduced motion, Standard, Slide Deck. ---- */
html.no-runway .manifesto-text {
	animation: none;
	background-position: 0% 0;
}

/* ---- guard 4: ADDED 13's forced-colours floor, restated after the gradient. ---- */
@media (forced-colors: active) {

	.manifesto-text {
		background-image: none;
		animation: none;
		-webkit-text-fill-color: currentColor;
		color: CanvasText;
	}
}


/* ============================================================================
   THE SLIDE DECK IS NOT THE SETTLED PAGE, AND THIS IS THE ONE PLACE THEY PART

   `html.no-runway` is one stamp covering three different visitors: reduced
   motion, Standard, and Slide Deck. For the first two that is exactly right.
   For the third it is only right BY DEFAULT — a visitor who went and chose the
   slide deck asked for MORE motion, not less, and the stamp lands here because
   this page has no deck wiring for them to land in, not because they wanted
   the page still.

   Adam asked for the hold on Flow AND on Slide Deck, so this section, and ONLY
   this section, comes back out of the collapse for `data-experience="deck"`.
   Everything else on the page stays settled for them: a deck reader gets an
   ordinary page with one held sentence in it.

   THREE THINGS GATE IT, and every one of them is load-bearing:
     . @supports (animation-timeline: view()) — re-pinning a stage whose scrub
       cannot resolve is how you ship a blank screen. Firefox never enters.
     . [data-experience="deck"] — Standard is left collapsed. It is the only
       way to tell the two apart, since they share the class.
     . :not(.reduce-motion) — compliance outranks the choice. The pre-paint
       stamp sets that class for OS reduced motion as well as for the Reduced
       Motion option, so a deck reader who asked for stillness keeps it.

   The values below are the pen's own (.scroll-section, .sticky-stage and the
   two animations above), restated at (1,4,0) because that is what it takes to
   clear ADDED 14's guard 3. IF YOU CHANGE A NUMBER ABOVE, CHANGE IT HERE TOO.
   ============================================================================ */

@supports (animation-timeline: view()) {

	html.no-runway[data-experience="deck"]:not(.reduce-motion) #manifesto {
		height: 300svh;
		min-height: 0;
	}

	html.no-runway[data-experience="deck"]:not(.reduce-motion) #manifesto .sticky-stage {
		position: sticky;
		top: 0;
		height: 100dvh;
		min-height: 0;
		overflow: hidden;
		padding-block: 0;
	}

	html.no-runway[data-experience="deck"]:not(.reduce-motion) #manifesto .manifesto-view {
		animation: contentFade linear both;
		animation-timeline: --manifesto-scroll;
		animation-range: cover 0% contain 100%;
	}

	html.no-runway[data-experience="deck"]:not(.reduce-motion) #manifesto .manifesto-text {
		animation: manifesto-shine linear both;
		animation-timeline: --manifesto-scroll;
		animation-range: contain 18% contain 68%;
	}
}

/* ============================================================================
   ============   A D D E D   2 0  —  A N   A R R I V A L   H A S   T O
   ============   A R R I V E   O N   S C R E E N
   ============================================================================

   REPORTED BY ADAM (2026-08-21): on a phone "timing seems very fast and most
   animations are finishing/exiting barely on screen".

   ONE CAUSE, FIVE RANGES, AND IT IS NOT THE PEN'S. Every unpinned arrival on
   this page rides its own `view()` timeline: ADDED 15's four substitutes for
   the sections it will not pin, and ADDED 9's eight answers. All of them ended
   on a `cover` percentage in the teens, and a `cover` percentage is measured
   against `viewport + element`, so the end of the range landed a fifth of a
   screen above the fold. MEASURED at 390x844, before:

       element                  scroll span   element top when it SETTLED
       .plane-fixed-content     160px         576  (of 844 — 35% of it showing)
       .architectural-panel     120px         640-656
       .louver-slat              80-120px     676-702
       .matrix-cell              80-120px     674-704
       .qa-block                160-200px     572-625

   Everything finished in the bottom quarter of the screen, inside 80-200px of
   scroll. A phone flick is 600-1000px, so every one of these played and
   settled between two frames of a single flick: the reader's thumb leaves the
   glass, and by the time the element is somewhere it can be looked at the
   animation is over. Nothing was broken, and that is why it survived a
   desktop pass — the same ranges on a 1440x900 window are merely brisk.

   THE LAW, and it is the whole fix: AN ARRIVAL MUST END WITH THE ELEMENT IN
   VIEW. `entry X%` is measured against the element's own height, so it is the
   right unit for "it has just appeared"; `cover Y%` is measured against
   `viewport + element`, so it is the right unit for "it is far enough up the
   screen to read". Ending on `cover 35%` puts the settle point at
   `0.35 x (viewport + element)` of travel, which resolves to the middle third
   at every element height on the page:

       element h    settles with its top at   scroll span
       247          462                       ~345px
       296          445                       ~355px
       370          419                       ~370px
       443          394                       ~384px
       752          285                       ~446px

   Taller elements settle higher, which is what you want: a tall block needs
   more of itself on screen before it counts as arrived. THE KEYFRAMES ARE
   UNTOUCHED — this is a range change and only a range change.

   AND THE BRONZE RULE WAS NOT SLOW OR FAST, IT WAS NEVER PLAYING. Found while
   measuring the above: on mobile `#fit .matrix-cell::before` sat drawn at
   `scaleX(1)` at every scroll position on the page, so every cell arrived with
   its bronze rule already on it. THE PEN GIVES .matrix-cell `overflow: hidden`,
   AND THAT MAKES EVERY CELL A SCROLL CONTAINER. A bare `view()` names the
   nearest ancestor scrollport as its scroller, so for a pseudo-element inside
   the cell that scroller is THE CELL — a box whose content never scrolls,
   whose timeline therefore sits pinned near 100%, and whose animation is
   consequently finished before the reader has seen the section. It is a silent
   failure: the pen's own `--matrix-scroll` is a NAMED timeline on the section,
   so the desktop path never had it, and `fill: forwards` parks the rule at the
   end state, which looks exactly like a border somebody meant to draw.

   THE FIX IS A NAME. The cell publishes `view-timeline-name: --cell-view` and
   the pseudo rides that, which is the window the cell is measured against.
   Its range is the one range on the page stated with BOTH ends on `cover`:
   the subject is now the cell, and `cover 40% -> 55%` puts the draw just after
   the cell settles at `cover 35%`, which is the pen's own order — the cell
   arrives, then the rule draws across it.

   WHAT THIS DOES NOT TOUCH: every range that belongs to Adam's pen, i.e. every
   one stated against a named `--*-scroll` timeline. The hero frame, the
   panels' glide, the louver flip, the matrix wave and the manifesto hold all
   run on the pen's numbers at the sizes where they are pinned. THE PEN SHIPS.
   ============================================================================ */
