/* ============================================================
   MINIMALIST WEBSITE DESIGN — the landing page for the term,
   built out of the PAPER theme (portfolio/themes/paper/).

   Paper's pen is neumorphism executed with discipline: one flat
   mid-grey field, no borders and no colour anywhere, every shape
   defined only by a dual shadow — dark below-right, white
   above-left — so things read as raised out of, or pressed into,
   one continuous sheet. That material is copied here verbatim.
   What is new is everything a one-screen theme demo never
   needed: a nav, seven sections, two pinned scroll runways and
   the collection band.

   THREE THINGS THIS FILE DOES THAT PAPER'S DOES NOT, and why:

   1. TOKENS. Paper's stylesheet has no custom properties at all;
      every value is a literal. Seven screens of shadow idiom
      needs names, so the literals are lifted into :root
      unchanged — same colours, same shadows, just addressable.

   2. DARKER INK. Paper's body colour #808080 on its #E8E8E8
      sheet measures 3.22:1, and its ghost h1 measures 1.00:1.
      That is fine for a mark on a one-screen demo and not fine
      for a page of copy on a site that sells accessibility. So
      the ink darkens (see THE INK SCALE) and the emboss moves
      OFF the heading onto a decorative watermark — the h1 is
      letterpressed instead, which is the same illusion inverted
      and is the other half of what the theme is about.

   3. SCROLL MOTION. The pen has none — every effect in it is a
      hover transition. All of it lives in ONE guarded block near
      the foot of this file, with its html.no-runway twin
      directly after. Read the note above that twin before
      touching either.
   ============================================================ */

/* ---- Poster faces, for the collection band at the foot of the page.
   style.css's own subsets, declared verbatim: .poster_daylight,
   .poster_vitrea and .poster_silver ask for them by name and this page
   loads no style.css. The unicode-ranges must match tools/fonts.py's
   LATIN / LATIN_EXT exactly and must not overlap.

   NOT PRELOADED, deliberately — three small faces for three previews at
   the very bottom of the page. Nothing above them waits on a font: this
   page's own type is the system stack, exactly as Paper's is. */
@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;
}

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


/* ============================================================
   TOKENS — Paper's literals, named.

   THE INK SCALE is the one place values changed, and the reason
   is arithmetic. On the #E8E8E8 sheet:
     #808080  3.22:1   Paper's body colour — FAILS AA
     #6b6b6b  4.35:1   Paper's badge colour — FAILS AA
     #666666  4.68:1   passes, and is the floor used here
     #5f5f5f  5.21:1   secondary copy
     #4a4a4a  7.23:1   body copy (Paper's own card colour)
     #3a3a3a  9.29:1   headings
   Paper's #808080 survives only as --ink-ghost, for decoration
   that carries no meaning. Do not use it for text.

   THE THREE SHADOW IDIOMS are byte-for-byte the pen's: a raised
   pill (.mybutton), a pressed pill (.theme_tags li) and a raised
   panel (.theme_card). Everything on this page is one of those
   three at some size.
   ============================================================ */
:root {
	--sheet: #E8E8E8;

	--ink-strong: #3a3a3a;
	--ink: #4a4a4a;
	--ink-soft: #5f5f5f;
	--ink-faint: #666666;
	--ink-ghost: #808080;

	--hi: #ffffff;
	--lo: #b8b8bc;

	/* the pen's own gradients — a panel is lit from the top, a pill from
	   just below its centre */
	--face-panel: radial-gradient(circle at 50% 0%, #fff, #f2f2f2);
	--face-pill: radial-gradient(circle at 50% 60%, #fff, #f0f0f0);
	--face-chip: radial-gradient(circle at 50% 60%, #fff, #f6f6f6);

	--raise-sm: 0 6px 8px rgba(0, 0, 0, .15), 0 -4px 8px rgba(255, 255, 255, .6);
	--raise: 0 10px 10px rgba(0, 0, 0, .2), 0 -6px 10px rgba(255, 255, 255, .6);
	--raise-lg: 0 20px 30px rgba(0, 0, 0, .12), 0 -8px 16px rgba(255, 255, 255, .7);
	/* the shape a raised thing collapses TO. Outset both times, because an
	   outset shadow and an inset one are different shadow types and do not
	   interpolate — anything that has to travel between the two states
	   cross-fades two layers instead (see .dep_card). */
	--raise-none: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(255, 255, 255, 0);

	--press: inset 3px 3px 6px rgba(0, 0, 0, .12), inset -3px -3px 6px rgba(255, 255, 255, .7);
	--press-deep: inset 7px 7px 14px rgba(0, 0, 0, .14), inset -7px -7px 14px rgba(255, 255, 255, .75);

	--ease: cubic-bezier(.22, 1, .36, 1);
	--gutter: clamp(1.25rem, 4vw, 5rem);
	--measure: 1180px;

	color-scheme: light;
}

* {
	box-sizing: border-box;
}

html {
	background: var(--sheet);
	/* clears the fixed masthead for in-page hash jumps */
	scroll-padding-top: 5rem;
}

body {
	margin: 0;
	background: var(--sheet);
	color: var(--ink);
	font-family: arial, helvetica, sans-serif;
	font-size: clamp(1rem, .96rem + .2vw, 1.08rem);
	line-height: 1.7;
	/* the page-level bound. `clip`, never `hidden`: hidden would make the
	   element a scroll container and break every position:sticky pin in it. */
	overflow-x: clip;
}

h1,
h2,
h3 {
	color: var(--ink-strong);
	line-height: 1.15;
	text-wrap: balance;
	margin: 0;
}

h1 {
	font-size: clamp(2.4rem, 8vw, 4.6rem);
	letter-spacing: -.02em;
	/* LETTERPRESS, not emboss. Paper's signature h1 is filled with the page
	   colour and readable only by its shadow pair — 1:1 contrast, which a
	   heading cannot have. Dark ink plus the white up-left highlight alone
	   reads as type pressed INTO the sheet: the same illusion, inverted, at
	   9.29:1. The embossed version survives on .cover_ghost, where it is
	   decorative and aria-hidden. */
	text-shadow: -0.012em -0.012em 0.02em var(--hi);
}

h2 {
	font-size: clamp(1.6rem, 3.4vw, 2.4rem);
	letter-spacing: -.015em;
	text-shadow: -0.01em -0.01em 0.02em var(--hi);
}

h3 {
	font-size: clamp(1.05rem, 1.6vw, 1.2rem);
	letter-spacing: -.005em;
}

p {
	margin: 0 0 1em;
	max-width: 68ch;
}

p:last-child {
	margin-bottom: 0;
}

a {
	color: var(--ink-strong);
	text-decoration-color: var(--lo);
	text-underline-offset: 3px;
}

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

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

em {
	font-style: italic;
}

.skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 200;
	padding: .8rem 1.4rem;
	background: var(--face-pill);
	box-shadow: var(--raise);
	border-radius: 100px;
	text-decoration: none;
	font-weight: bold;
}

.skip:focus {
	left: 1rem;
	top: 1rem;
}


/* ============================================================
   THE SHARED FURNITURE — three shapes and nothing else.

   .anno       annotation type: small, uppercase, tracked
   .plate      the raised panel  (Paper's .theme_card)
   .mybutton   the raised pill   (Paper's .mybutton, verbatim)
   .chip       the pressed pill  (Paper's .theme_tags li)
   ============================================================ */
.anno {
	margin: 0;
	font-size: .72rem;
	font-weight: bold;
	letter-spacing: .16em;
	text-transform: uppercase;
	color: var(--ink-faint);
	max-width: none;
}

.anno a {
	color: inherit;
}

.anno--note {
	margin-top: 2rem;
	margin-bottom: .9rem;
}

/* the titleblock — a page's closing annotation strip, split left and
   right. Named for the band and the PS note, which both expect it. */
.titleblock {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: .35rem 1.5rem;
	margin-top: clamp(1.75rem, 4vh, 2.75rem);
	padding-top: .8rem;
	/* the hairline is a shadow, not a border — there are no borders on this
	   page, and a 1px inset highlight is how a fold reads in this material */
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, .85), inset 0 2px 1px -1px rgba(0, 0, 0, .07);
}

.plate {
	background: var(--face-panel);
	border-radius: 28px;
	box-shadow: var(--raise-lg);
	padding: clamp(1.4rem, 2.6vw, 2.1rem);
}

.plate h3 {
	margin-bottom: .6rem;
}

.plate .anno {
	margin-bottom: .5rem;
}

.plate p {
	font-size: .96rem;
	max-width: 46ch;
}

/* the quiet variant: pressed instead of raised. Same material, other
   direction — used where a block should read as part of the sheet rather
   than as something sitting on it. */
.plate--quiet {
	background: var(--sheet);
	box-shadow: var(--press);
}

.mybutton {
	display: inline-block;
	padding: .95rem 1.9rem;
	background: var(--face-pill);
	box-shadow: var(--raise);
	border-radius: 100px;
	border: 0;
	color: var(--ink);
	font-family: inherit;
	font-size: .85rem;
	font-weight: bold;
	letter-spacing: .06em;
	text-transform: uppercase;
	text-decoration: none;
	cursor: pointer;
	/* keeps the button permanently on its own compositor layer so the hover
	   scale doesn't promote/demote it mid-transition — that boundary is what
	   made the text flash thin then thick in the pen */
	will-change: transform;
	transition: all .2s ease;
}

.mybutton--primary {
	color: var(--ink-strong);
	box-shadow: var(--raise), inset 0 0 0 1px rgba(255, 255, 255, .6);
}

@media (hover: hover) and (pointer: fine) {

	.mybutton:hover {
		filter: brightness(.98);
		transform: scale(.99);
		box-shadow: 0 5px 5px rgba(0, 0, 0, .2), 0 -6px 10px rgba(255, 255, 255, .6);
	}
}

.mybutton:focus-visible {
	filter: brightness(.98);
	transform: scale(.99);
}

.mybutton[disabled] {
	opacity: .6;
	cursor: wait;
	transform: none;
}

.chip {
	display: inline-block;
	padding: .5em 1.1em;
	border-radius: 100px;
	background: var(--sheet);
	box-shadow: var(--press);
	font-size: .72rem;
	font-weight: bold;
	letter-spacing: .07em;
	text-transform: uppercase;
	color: var(--ink-faint);
}


/* ============================================================
   THE MASTHEAD and THE INDEX PANEL

   Above 900px: a plain row of the site's seven real pages.
   Below it: the same seven in a raised panel behind a pressed
   toggle, because seven links wrapping to three lines makes a
   fixed bar a third of a phone screen tall.

   900px IS MEASURED — the wordmark, two gutters, a 44px button
   and the seven labels at this page's body size. Change a label
   and re-measure.

   CRAWL PARITY: never display:none, never fewer than seven. The
   closed panel is transformed and visibility:hidden, holding the
   same real <a href>, and the script marks it `inert`.
   ============================================================ */
.masthead {
	position: fixed;
	inset: 0 0 auto 0;
	z-index: 100;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: .85rem var(--gutter);
	background: color-mix(in oklab, var(--sheet) 82%, transparent);
	backdrop-filter: blur(8px);
	box-shadow: 0 1px 0 rgba(255, 255, 255, .7), 0 6px 14px -10px rgba(0, 0, 0, .35);
}

.wordmark {
	font-size: .8rem;
	font-weight: bold;
	letter-spacing: .2em;
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;
	color: var(--ink);
}

.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: .3rem clamp(.7rem, 1.7vw, 1.6rem);
	margin: 0;
	padding: 0;
	list-style: none;
}

.masthead nav a {
	font-size: .72rem;
	font-weight: bold;
	letter-spacing: .1em;
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;
	color: var(--ink-faint);
}

@media (hover: hover) and (pointer: fine) {
	.masthead nav a:hover {
		color: var(--ink-strong);
	}
}

/* panel-only furniture. `.masthead .index_block` is scoped to two classes
   ON PURPOSE — the element also wears `.titleblock`, declared earlier, and
   a single-class rule would lose the tie and leave a stray fold line
   across the desktop bar. */
.index_eyebrow,
.masthead .index_block,
.nav_toggle {
	display: none;
}

/* ---- the toggle: three rules in a pressed well ---- */
.nav_toggle {
	position: relative;
	z-index: 3;
	/* `flex: 0 0 auto` is not decoration — .wordmark is nowrap, so on a
	   320px screen the only thing flex CAN shrink is this button, and it
	   collapses below the 44px tap-target floor. */
	flex: 0 0 auto;
	place-items: center;
	width: 2.75rem;
	height: 2.75rem;
	padding: 0;
	border: 0;
	border-radius: 100px;
	background: var(--sheet);
	box-shadow: var(--press);
	cursor: pointer;
	transition: box-shadow .2s ease;
}

.nav_rules {
	display: grid;
	gap: 5px;
	width: 20px;
}

.nav_rule {
	display: block;
	height: 2px;
	border-radius: 2px;
	background: var(--ink-soft);
	box-shadow: 0 1px 0 rgba(255, 255, 255, .8);
	transform-origin: center;
	transition: transform .4s var(--ease), opacity .2s ease, width .3s var(--ease);
}

/* the middle rule is drawn short — one of three lines behaving differently
   is the whole character of the mark, and it is the same line hierarchy
   the page is drawn in */
.nav_rule--2 {
	width: 13px;
	transform-origin: 0 50%;
}

@media (hover: hover) and (pointer: fine) {
	.nav_toggle:hover .nav_rule--2 {
		width: 20px;
	}
}

/* pressed → raised on open: the button pops out of the sheet, which is the
   material's own way of saying "this is active" */
html.nav-open .nav_toggle {
	background: var(--face-pill);
	box-shadow: var(--raise-sm);
}

html.nav-open .nav_rule--1 {
	transform: translateY(7px) rotate(45deg);
}

html.nav-open .nav_rule--3 {
	transform: translateY(-7px) rotate(-45deg);
}

html.nav-open .nav_rule--2 {
	transform: scaleX(0);
	opacity: 0;
}

/* Scroll lock. `overflow: hidden` on the root, NOT a fixed body: hidden
   keeps the root a scroll container with its scrollTop intact, so every
   scroll(root block) timeline on the page stays live and holds exactly
   where it was. A fixed body zeroes the offset and the whole page would
   snap back to the top the moment the menu opened. */
html.nav-open {
	overflow: hidden;
}

@media (max-width: 900px) {

	.nav_toggle {
		display: grid;
	}

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

	/* 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 panel to the
	   header box instead of to the viewport. */
	.masthead {
		background: var(--sheet);
		backdrop-filter: none;
	}

	html.nav-open .masthead {
		background: transparent;
		box-shadow: none;
	}

	.masthead nav {
		position: fixed;
		inset: 0;
		z-index: 1;
		display: flex;
		flex-direction: column;
		justify-content: center;
		gap: 1.25rem;
		overflow: hidden;
		padding: calc(4.6rem + env(safe-area-inset-top)) calc(var(--gutter) + .6rem) calc(2rem + env(safe-area-inset-bottom));
		background: var(--face-panel);
		box-shadow: var(--raise-lg);
		/* CLOSED STATE. visibility, not display — the seven links stay in the
		   DOM, stay crawlable, and the script marks the panel `inert` so they
		   are not in the tab order behind a shut menu. */
		visibility: hidden;
		opacity: 0;
		transform: translateY(-2.5%) scale(.985);
		transition: opacity .32s var(--ease), transform .38s var(--ease), visibility 0s linear .38s;
	}

	html.nav-open .masthead nav {
		visibility: visible;
		opacity: 1;
		transform: none;
		transition: opacity .3s ease, transform .38s var(--ease), visibility 0s;
	}

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

	.masthead .index_block {
		margin-top: 0;
	}

	.index_list {
		flex-direction: column;
		align-items: flex-start;
		justify-content: center;
		gap: .35rem;
	}

	.index_list li {
		width: 100%;
	}

	/* every link is a raised pill in its own right — the panel is the page's
	   material at full size, not a list on a card */
	.masthead nav a {
		display: block;
		padding: .85rem 1.4rem;
		border-radius: 100px;
		font-size: .8rem;
		letter-spacing: .12em;
		color: var(--ink);
		background: var(--face-pill);
		box-shadow: var(--raise-sm);
	}
}

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


/* ============================================================
   THE SCALEBAR — reading progress in the page's two idioms: a
   pressed track with a raised fill running along it. Purely
   decorative; only the motion layer draws it, so it is hidden
   outright wherever there is no timeline to drive it.
   ============================================================ */
.scalebar {
	display: none;
	position: fixed;
	inset: 0 0 auto 0;
	z-index: 101;
	height: 5px;
	background: var(--sheet);
	box-shadow: var(--press);
	pointer-events: none;
}

.scalebar span {
	display: block;
	height: 100%;
	background: linear-gradient(180deg, #fff, #d8d8dc);
	box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
	transform-origin: 0 50%;
	transform: scaleX(0);
}


/* ============================================================
   01 · THE COVER

   A pinned screen: the copy holds for the first third of the
   track and then settles away, so the runway below arrives into
   an empty frame rather than crossing live content.

   The entrance is a page-LOAD stagger, not a scroll scrub —
   nothing has scrolled yet when this paints, and a scrubbed
   hero would sit at its from-state (opacity 0) on arrival.
   ============================================================ */
/* THE TRACK AND THE PIN ARE NOT HERE. Every runway's height, sticky and
   timeline live inside the motion block, so THE BASE STATE IS THE READABLE
   ONE and a browser with no scroll timelines gets an ordinary section
   rather than four screens of scroll doing nothing. The only undo left to
   write is html.no-runway's, which is the case where the timelines DID
   apply and the visitor asked for them off. */
.cover {
	position: relative;
}

/* THE COVER'S WHOLE VERTICAL RHYTHM IS vh-AWARE, and it has to be: a sticky
   stage taller than the viewport does not pin — it scrolls with the page,
   and the pull the runway below is built around never happens. Fixed rem
   spacing overflowed at 1280x800, 1280x720, 390x844 and 390x667, i.e. at
   most of the sizes people actually have. Every gap here therefore carries
   a vh term, and the pin itself is guarded by a min-height query (see THE
   PINS in the motion block) for the screens too short to fit it at all. */
.cover_stage {
	position: relative;
	min-height: 100svh;
	display: grid;
	align-content: center;
	padding: clamp(4.25rem, 9.5vh, 7rem) var(--gutter) clamp(2rem, 5.5vh, 5rem);
}

.cover_inner {
	position: relative;
	z-index: 1;
	width: min(100%, var(--measure));
	margin-inline: auto;
}

/* THE GHOST — Paper's emboss, kept and moved off the heading. Filled with
   the page colour and readable only by its shadow pair, which is exactly
   why it carries no meaning and is aria-hidden in the markup. The dark
   tone is the sheet minus ~(48,48,44) per channel, the same derivation
   the theme's own ghost uses; recompute it if --sheet ever moves.

   HIDDEN BELOW 1100px, and that is a contrast decision rather than a
   layout one. Embossed type measures 1:1, so it must never lie under
   anything a reader has to read. Above 1100px the copy column is bound by
   its measure and the right-hand third of the cover is genuinely empty,
   which is where this goes. Below it there is no empty space on the
   screen, so the watermark is dropped — it carries no meaning and the
   emboss still appears on .mk_head in the first diagram. */
.cover_ghost {
	display: none;
}

@media (min-width: 1100px) and (min-height: 700px) {
	.cover_ghost {
		display: block;
		position: absolute;
		z-index: 0;
		right: var(--gutter);
		bottom: max(2rem, 7vh);
		margin: 0;
		max-width: none;
		/* bounded by the viewport HEIGHT as well as its width — on a short
		   landscape screen a 15vw mark runs off the bottom of the stage,
		   which is the one way this could end up looking like a bug */
		font-size: min(clamp(9rem, 15vw, 17rem), 26vh);
		line-height: .78;
		font-weight: bold;
		letter-spacing: -.05em;
		color: var(--sheet);
		text-shadow:
			-0.02em -0.02em 0.04em var(--hi),
			0.02em 0.03em 0.05em var(--lo);
		user-select: none;
		pointer-events: none;
	}
}

.cover_eyebrow {
	margin-bottom: clamp(.55rem, 1.6vh, 1.1rem);
}

.cover_line {
	margin: clamp(.6rem, 1.7vh, 1.1rem) 0 clamp(.75rem, 2vh, 1.6rem);
	max-width: 24ch;
	font-size: min(clamp(1.1rem, 2.4vw, 1.6rem), 3.6vh);
	line-height: 1.35;
	color: var(--ink-strong);
}

.lede {
	max-width: 58ch;
	color: var(--ink);
	/* `em`, not a unitless ratio — min() cannot compare a bare number with a
	   length, and the whole declaration is dropped as invalid if you try. */
	line-height: min(1.7em, 3.1vh);
}

.cover_actions {
	display: flex;
	flex-wrap: wrap;
	gap: .9rem;
	margin-top: clamp(1.1rem, 3vh, 2rem);
}

/* THE COPY COLUMN IS MEASURE-BOUND ON THE COVER, and that is what makes the
   ghost's corner safe. The heading and the closing strip are the only two
   things here that would otherwise run the full container width and reach
   into it — so the heading takes a measure and the strip groups left
   instead of spreading. Nothing to the right of this column is text. */
.cover_inner #h1 {
	max-width: 15ch;
	font-size: min(clamp(2.2rem, 8vw, 4.6rem), 8.5vh);
}

.cover_inner .titleblock {
	justify-content: flex-start;
	gap: .35rem 1.75rem;
	max-width: 62ch;
	margin-top: clamp(.9rem, 2.6vh, 2.75rem);
}

.scroll_hint {
	position: absolute;
	left: 50%;
	bottom: 1.75rem;
	transform: translateX(-50%);
	display: flex;
	align-items: center;
	gap: .7rem;
	color: var(--ink-faint);
}

.scroll_track {
	display: block;
	width: 3.5rem;
	height: 4px;
	border-radius: 4px;
	background: var(--sheet);
	box-shadow: var(--press);
	overflow: hidden;
}

.scroll_line {
	display: block;
	width: 40%;
	height: 100%;
	border-radius: 4px;
	background: linear-gradient(180deg, #fff, #d8d8dc);
	transform: translateX(-110%);
}


/* ============================================================
   02 · SUBTRACTION — the first pinned runway

   The argument made by removing things: an ordinary page
   assembles out of the sheet, its chrome presses back into the
   sheet one piece at a time, and what survives recentres.

   NO INVISIBLE WALLS: nothing here leaves by sliding under an
   edge. Every element goes by flattening its own shadow to
   --raise-none and fading — it stops being raised, so it stops
   being there.

   Every range is in `contain` percentages, i.e. relative to the
   pin, so none of them needs retuning if the section moves.
   ============================================================ */
.sub_track {
	position: relative;
}

.sub_stage {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: clamp(.6rem, 1.8vh, 1.4rem);
	padding: clamp(4.5rem, 9vh, 6rem) var(--gutter) clamp(1.5rem, 4vh, 2.5rem);
	text-align: center;
}

.sub_head h2 {
	margin-top: .35rem;
	max-width: 22ch;
}

/* No aspect-ratio: the mock's children are all positioned in percentages,
   so it takes whatever box it is given. Pinned, that is the flex column's
   leftover height (set in the motion block); unpinned, it is this. */
.sub_mock {
	position: relative;
	height: 58vh;
	min-height: 18rem;
	width: min(100%, 46rem);
	border-radius: 20px;
	background: var(--sheet);
	box-shadow: var(--press);
}

.mk {
	position: absolute;
	border-radius: 12px;
	background: var(--face-pill);
	box-shadow: var(--raise-sm);
}

.mk_banner {
	top: 5%;
	left: 4%;
	right: 4%;
	height: 12%;
	display: flex;
	align-items: center;
	gap: 6%;
	padding: 0 4%;
}

.mk_banner span {
	height: 26%;
	border-radius: 100px;
	background: var(--sheet);
	box-shadow: var(--press);
}

.mk_banner span:first-child {
	width: 22%;
}

.mk_banner span:last-child {
	width: 34%;
	margin-left: auto;
}

.mk_side {
	top: 22%;
	left: 4%;
	width: 21%;
	bottom: 7%;
}

.mk_badges {
	top: 22%;
	right: 4%;
	width: 19%;
	height: 22%;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 8%;
	padding: 0 12%;
}

.mk_badges span {
	height: 16%;
	border-radius: 100px;
	background: var(--sheet);
	box-shadow: var(--press);
}

.mk_carousel {
	bottom: 7%;
	left: 29%;
	right: 4%;
	height: 26%;
	display: flex;
	align-items: center;
	gap: 3%;
	padding: 0 4%;
}

.mk_carousel span {
	flex: 1 1 0;
	height: 62%;
	border-radius: 8px;
	background: var(--sheet);
	box-shadow: var(--press);
}

.mk_popup {
	bottom: 38%;
	right: 6%;
	width: 32%;
	height: 20%;
	box-shadow: var(--raise);
}

.mk_x {
	position: absolute;
	top: 12%;
	right: 8%;
	width: .55rem;
	height: .55rem;
	opacity: .5;
	background:
		linear-gradient(45deg, transparent 44%, var(--ink-ghost) 44% 56%, transparent 56%),
		linear-gradient(-45deg, transparent 44%, var(--ink-ghost) 44% 56%, transparent 56%);
}

/* WHAT SURVIVES. Its RESTING state is the finished one — dead centre at
   full size — and phase 1 squeezes it into the mock's content slot with a
   transform. That way the settle in phase 3 is `transform: none`, a
   compositor-only animation, and the element never has its layout touched. */
.mk_keep {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: .5rem;
	padding: 8%;
	transform: translate(13%, -8%) scale(.58);
}

.mk_head {
	margin: 0;
	max-width: none;
	font-size: clamp(1.1rem, 3.4vw, 1.9rem);
	font-weight: bold;
	line-height: 1.1;
	color: var(--ink-strong);
	text-shadow: -0.012em -0.012em 0.02em var(--hi);
}

.mk_line {
	margin: 0;
	max-width: none;
	font-size: clamp(.8rem, 1.5vw, .95rem);
	color: var(--ink-soft);
}

.mk_pill {
	width: 8.5rem;
	height: 2.1rem;
	margin-top: .4rem;
	border-radius: 100px;
	background: var(--face-pill);
	box-shadow: var(--raise-sm);
}

/* THE CAPTIONS. A real ordered list, and that is deliberate: with no
   timeline it reads top to bottom as the caption to a settled diagram.
   The stacking and the cross-fade are added only inside the motion block. */
.sub_caps {
	margin: 0;
	padding: 0 0 0 1.4rem;
	max-width: 64ch;
	text-align: left;
	font-size: .95rem;
	color: var(--ink);
}

.sub_caps li {
	margin-bottom: .7rem;
}

.sub_thesis {
	margin: 0;
	max-width: 30ch;
	font-size: clamp(1.05rem, 2.1vw, 1.35rem);
	color: var(--ink-strong);
}

.sub_thesis em {
	font-style: italic;
}


/* ============================================================
   THE BANDS — the ordinary sections between the two runways.
   No pin, no scrub on the section itself; the plates inside get
   an entrance and that is all.
   ============================================================ */
.band {
	padding: clamp(4rem, 11vh, 8rem) var(--gutter);
}

.band_inner {
	width: min(100%, var(--measure));
	margin-inline: auto;
}

.band_inner>h2 {
	margin: .7rem 0 clamp(1.75rem, 4vh, 2.75rem);
	max-width: 26ch;
}

.split {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: clamp(1.5rem, 4vw, 3.5rem);
	align-items: start;
}

.split--wide {
	grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
}

.notes {
	display: grid;
	gap: clamp(1rem, 2vw, 1.5rem);
}

.cards {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: clamp(1rem, 2vw, 1.75rem);
	align-items: start;
}

@media (max-width: 860px) {

	.split,
	.split--wide,
	.cards {
		grid-template-columns: 1fr;
	}
}

/* the definition block — the answer an answer engine lifts. Set apart by
   measure and weight, not by a box, because a box would make it read as an
   aside rather than as the section's own first sentence. */
.define {
	font-size: clamp(1.05rem, 1.9vw, 1.2rem);
	line-height: 1.6;
	color: var(--ink-strong);
	margin-bottom: 1.4em;
}

.define strong {
	font-weight: bold;
}

/* ---- the spec list: pressed rows, one fact each ---- */
.specs {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: .55rem;
}

.specs li {
	display: grid;
	grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
	gap: .2rem 1.2rem;
	align-items: baseline;
	padding: .75rem 1.2rem;
	border-radius: 16px;
	background: var(--sheet);
	box-shadow: var(--press);
}

.spec_k {
	font-size: .7rem;
	font-weight: bold;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--ink-faint);
}

.spec_v {
	font-size: .95rem;
	color: var(--ink);
}

@media (max-width: 520px) {
	.specs li {
		grid-template-columns: 1fr;
	}
}


/* ============================================================
   05 · DEPTH — the second pinned runway

   Four ways to build depth out of one flat grey, revealed one at
   a time in the same slot while a single demo card walks through
   the states they describe. One idea on screen at a time is the
   argument the page is making, so the section makes it.

   Tool 04 is the page's ONE mention of scrolljacking: named as
   one approach among four, with the case itself deferred to
   /scrolljacking/. Do not let it grow.
   ============================================================ */
.dep_track {
	position: relative;
}

.dep_stage {
	display: flex;
	flex-direction: column;
	gap: clamp(.75rem, 2vh, 1.5rem);
	padding: clamp(4.5rem, 9vh, 6rem) var(--gutter) clamp(1.5rem, 4vh, 2.5rem);
}

.dep_head,
.dep_body,
.dep_thesis {
	width: min(100%, var(--measure));
	margin-inline: auto;
}

.dep_head h2 {
	margin-top: .35rem;
}

.dep_body {
	display: grid;
	grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr);
	gap: clamp(1.5rem, 4vw, 3.5rem);
	align-items: start;
}

.dep_demo {
	display: grid;
	place-items: center;
	min-height: 0;
}

.dep_card {
	position: relative;
	display: grid;
	place-items: center;
	width: min(100%, 19rem);
	aspect-ratio: 5 / 4;
	max-height: 100%;
	border-radius: 28px;
}

/* TWO FACES, CROSS-FADED, and it is not a stylistic choice: an outset
   box-shadow and an inset one are different shadow types and do not
   interpolate, so a card that has to travel from raised to pressed cannot
   do it on one element. Each face carries one state at full strength and
   opacity does the travelling. */
.dep_face {
	position: absolute;
	inset: 0;
	border-radius: inherit;
}

.dep_face--raise {
	background: var(--face-panel);
	box-shadow: var(--raise-lg);
	opacity: 1;
}

.dep_face--press {
	background: var(--sheet);
	box-shadow: var(--press-deep);
	opacity: 0;
}

.dep_sheen {
	position: absolute;
	inset: 0;
	border-radius: inherit;
	background: radial-gradient(circle at 28% 18%, rgba(255, 255, 255, .95), rgba(255, 255, 255, 0) 62%);
	opacity: 1;
}

.dep_mark {
	position: relative;
	z-index: 2;
	font-size: clamp(2rem, 5vw, 3.2rem);
	font-weight: bold;
	letter-spacing: -.03em;
	color: var(--ink-strong);
	text-shadow: -0.012em -0.012em 0.02em var(--hi);
}

.dep_list {
	position: relative;
	margin: 0;
	padding: 0 0 0 1.4rem;
	list-style: none;
	counter-reset: none;
}

.dep_item {
	margin-bottom: 1.4rem;
}

.dep_num {
	display: block;
	margin-bottom: .35rem;
	font-size: .7rem;
	font-weight: bold;
	letter-spacing: .18em;
	color: var(--ink-faint);
}

.dep_item h3 {
	margin-bottom: .5rem;
}

.dep_item p {
	font-size: .95rem;
	max-width: 48ch;
	margin: 0;
}

.dep_thesis {
	margin: 0;
	max-width: 46ch;
	font-size: clamp(1rem, 1.8vw, 1.2rem);
	color: var(--ink-strong);
}

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


/* ============================================================
   07 · QUESTIONS
   ============================================================ */
.qas {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: clamp(1.25rem, 3vw, 2.5rem) clamp(1.5rem, 4vw, 3.5rem);
}

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

.qa h3 {
	margin-bottom: .5rem;
}

.qa p {
	font-size: .96rem;
	max-width: 52ch;
	margin: 0;
}


/* ============================================================
   08 · CONTACT  (+ the PS close)
   ============================================================ */
.band--contact .band_inner>.split {
	margin-top: 1.5rem;
}

.band--contact h2 {
	max-width: 18ch;
	margin-bottom: 1.5rem;
}

.contact_form {
	display: grid;
	gap: 1rem;
	padding: clamp(1.4rem, 2.6vw, 2.1rem);
	border-radius: 28px;
	background: var(--face-panel);
	box-shadow: var(--raise-lg);
}

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

.field label {
	font-size: .7rem;
	font-weight: bold;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--ink-faint);
}

.field input,
.field textarea {
	width: 100%;
	padding: .8rem 1.1rem;
	border: 0;
	border-radius: 14px;
	background: var(--sheet);
	box-shadow: var(--press);
	font-family: inherit;
	font-size: 1rem;
	color: var(--ink-strong);
	resize: vertical;
}

/* The honeypot. Positioned off-screen and NOT display:none — some bots
   skip what is not rendered, and the field only works if they fill it. */
.hp_field {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.form_status {
	margin: 0;
	min-height: 1.5em;
	font-size: .9rem;
	color: var(--ink-soft);
}

.form_status.ok {
	color: var(--ink-strong);
	font-weight: bold;
}

.form_status.err {
	color: #8a3232;
}

/* --- THE PS CLOSE. Built only out of parts the page already has: the
   annotation label, the body type, and the titleblock's fold line, sitting
   directly above the real titleblock so the section still closes the way
   every other one does. No price and no comparison. */
.ps_note {
	margin-top: clamp(2rem, 4vw, 3.5rem);
	padding-top: .8rem;
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, .85), inset 0 2px 1px -1px rgba(0, 0, 0, .07);
}

.ps_note p {
	max-width: 62ch;
}

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

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


/* ============================================================
   THE COLLECTION BAND  (.alsolike)

   "Like this theme? You might also like:", written to TRAVEL —
   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 from
   css/style.css — .slide_poster, .poster_bg, .poster_title,
   .poster_sub and each theme's own block, copied unchanged.
   NOT ported: the 3-D hover pops and the fan poses, both of
   which need the fan's preserve-3d rig. Anything that changes in
   style.css's POSTERS block changes here.

   THE WALL IS A RECESS, not a different colour. On a page with
   exactly one background this is the only honest way to set a
   region apart: the same sheet, pressed. Every contrast ratio in
   it is therefore the page's own.
   ============================================================ */
.alsolike {
	position: relative;
	padding: clamp(3.5rem, 9vh, 7rem) var(--gutter);
	background: var(--sheet);
	box-shadow:
		inset 0 16px 24px -16px rgba(0, 0, 0, .18),
		inset 0 -16px 24px -16px rgba(0, 0, 0, .18);
}

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

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

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

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

.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, raised off the recess ------------------- */
/* NO CARD CHROME: the poster is the object. `container-type` stays — it is
   what gives the poster its em scale, exactly as the home fan's column
   does — and with no padding, 100cqw IS the poster's width. */
.tt_card {
	display: block;
	text-decoration: none;
	container-type: inline-size;
	transition: translate .3s var(--ease);
}

/* The lift is on `translate`, NOT on `transform`, and that is load-bearing:
   the arrival animation 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. */
@media (hover: hover) and (pointer: fine) {
	.tt_card:hover {
		translate: 0 -4px;
	}

	.tt_card:hover .tt_art {
		box-shadow: 0 26px 34px rgba(0, 0, 0, .16), 0 -8px 16px rgba(255, 255, 255, .7);
	}
}

.tt_art {
	position: relative;
	overflow: hidden;
	aspect-ratio: 700 / 450;
	border-radius: 18px;
	--u: calc(100cqw / 700);
	/* posters size themselves in em, off this */
	font-size: calc(16 * var(--u));
	box-shadow: var(--raise-lg);
	transition: box-shadow .3s ease;
}

/* THE GLINT — one pass of light across the poster 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 255 255 / .5) 50%, transparent 62%);
	transform: translateX(-140%);
}

.tt_note {
	margin-top: .9rem;
	font-size: .92rem;
	color: var(--ink);
	max-width: none;
}

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

/* =========================== 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 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 in the poster's own face.
   Here h1,h2,h3 carry a letterpress text-shadow, 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-shadow: none;
	text-wrap: wrap;
	transform: translateZ(2px);
}

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

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

/* ---- Daylight Study: the architecture sheet — pale paper with a warm
   band of daylight falling across it and an ink border a hair inside the
   card edge. ---- */
.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;
}

/* ---- Vitrea: ambient colour blobs under a glass chip (translucent fill
   only — NO backdrop-filter, it would flatten the 3D rest lift) ---- */
.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, gold sold ribbon ---- */
.poster_silver .poster_bg {
	background:
		radial-gradient(circle 16em at 30% 20%, #38475b, transparent 70%),
		#242c38;
}

.poster_silver .poster_ribbon {
	position: absolute;
	top: 1.6em;
	right: -2.4em;
	transform: 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 — it continues the band's recess rather than
   starting a new surface, so the two read as one pressed
   stretch at the foot of the page. TRAVELS WITH THE BAND: a
   page that takes .alsolike takes this pairing.
   ============================================================ */
.colophon {
	padding: 2.5rem var(--gutter) 3.5rem;
	background: var(--sheet);
	box-shadow: inset 0 -16px 24px -16px rgba(0, 0, 0, .18);
	display: flex;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: 1rem;
}

/* the band already draws the top of the recess — a second one here would
   read as a seam between two surfaces that are meant to be one */
main:has(.alsolike)+.colophon {
	box-shadow: inset 0 -16px 24px -16px rgba(0, 0, 0, .18);
}


/* ============================================================
   ============  T H E   M O T I O N   L A Y E R  =============
   ============================================================

   EVERYTHING scroll-driven on this page is inside this one
   block, and nothing outside it moves except hover transitions.

   The guard is doubled on purpose: @supports so a browser
   without scroll timelines simply gets the settled page, and
   prefers-reduced-motion so a reader who asked for stillness
   gets it from the OS without needing the site's own stamp.
   The site's stamp (html.no-runway) is handled after the block,
   because a class cannot be tested inside @supports.
   ============================================================ */
@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {

		/* ---- reading progress ---- */
		.scalebar {
			display: block;
		}

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

		@keyframes bar-fill {
			to {
				transform: scaleX(1);
			}
		}

		/* ============ THE PINS ============
		   Every track height and every sticky stage on this page is declared
		   HERE and nowhere else — see the note at .cover. A browser that
		   cannot resolve these timelines never gets a 400svh section whose
		   contents do not move. */
		/* THE COVER PINS ONLY WHERE IT FITS. A sticky box taller than the
		   viewport does not stick — it scrolls, and the hold this track exists
		   to buy silently stops happening. Below 700px of viewport height the
		   copy genuinely cannot fit one screen, so there the cover is an
		   ordinary section with no track at all and the page simply starts
		   scrolling. Both the height and the settle scrub are inside the
		   guard, so nothing is left holding a from-state. */
		@media (min-height: 700px) {
			.cover {
				height: 170vh;
				view-timeline: --cover block;
			}

			.cover_stage {
				position: sticky;
				top: 0;
			}
		}

		.sub_track {
			height: 400svh;
			view-timeline: --sub block;
		}

		.dep_track {
			height: 400svh;
			view-timeline: --dep block;
		}

		.sub_stage,
		.dep_stage {
			position: sticky;
			top: 0;
			height: 100dvh;
		}

		/* pinned, the mock and the demo column take the leftover height of
		   their flex stage instead of the fixed one the unpinned base gives
		   them */
		.sub_mock {
			flex: 1 1 auto;
			height: auto;
			min-height: 0;
		}

		.dep_body {
			flex: 1 1 auto;
			min-height: 0;
			align-items: center;
		}

		.dep_demo {
			height: 100%;
		}

		/* Pinned, below 860px, the stage is one column and the demo becomes a
		   strip above the copy rather than being dropped — it is the thing the
		   copy is describing. Explicit rows, because a pinned grid has a fixed
		   height to divide and `auto` would let the copy column push the demo
		   off the bottom of the screen. */
		@media (max-width: 860px) {
			.dep_body {
				grid-template-rows: minmax(0, 7.5rem) minmax(0, 1fr);
				gap: 1.25rem;
				align-items: stretch;
			}

			.dep_card {
				width: auto;
				height: 100%;
				max-width: 100%;
			}
		}

		/* ============ 01 · THE COVER ============
		   TWO different things, and they must not be confused. The ENTRANCE
		   is a page-load stagger: nothing has scrolled when this paints, so a
		   scrubbed hero would sit at opacity 0 on arrival. The DEPARTURE is
		   scrubbed, and it is what gives the 170vh pin a job — the copy holds
		   for the first third of the track, then settles away, so the runway
		   below arrives into an empty frame instead of crossing live copy. */
		.cover_ghost,
		.cover_eyebrow,
		#h1,
		.cover_line,
		.lede,
		.cover_actions,
		.cover_inner .titleblock {
			animation: paper-rise .9s var(--ease) both;
		}

		.cover_ghost {
			animation-duration: 1.4s;
		}

		.cover_eyebrow {
			animation-delay: .1s;
		}

		#h1 {
			animation-delay: .2s;
		}

		.cover_line {
			animation-delay: .32s;
		}

		.lede {
			animation-delay: .44s;
		}

		.cover_actions {
			animation-delay: .56s;
		}

		.cover_inner .titleblock {
			animation-delay: .68s;
		}

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

			to {
				opacity: 1;
				transform: none;
			}
		}

		@media (min-height: 700px) {

			.cover_inner,
			.scroll_hint {
				animation: cover-settle linear both;
				animation-timeline: --cover;
				animation-range: contain 38% contain 100%;
			}

			/* the hint leaves first — nobody needs to be told to scroll once
			   they already are */
			.scroll_hint {
				animation-range: contain 0% contain 22%;
			}
		}

		@keyframes cover-settle {
			to {
				opacity: 0;
				transform: translateY(-2.5rem);
			}
		}

		/* the marker inside the scroll hint sweeps its own pressed track, on a
		   loop, because it is the one thing on the page that has to move
		   before the reader has done anything */
		.scroll_line {
			animation: hint-sweep 2.4s var(--ease) infinite;
		}

		@keyframes hint-sweep {

			0%,
			10% {
				transform: translateX(-110%);
			}

			90%,
			100% {
				transform: translateX(260%);
			}
		}

		/* ============ 02 · SUBTRACTION ============
		   Four phases, all in `contain` percentages — relative to the pin, so
		   none of them needs retuning if the section moves up or down the
		   page. 400svh track, 100dvh stage: 300svh of scrub.

		     P1  0–22%   the ordinary page assembles out of the sheet
		     P2 24–56%   its chrome presses back in, one piece at a time
		     P3 56–70%   what survives recentres at full size
		     P4 84–100%  the thesis
		*/
		.sub_head,
		.mk,
		.mk_keep,
		.sub_caps li,
		.sub_thesis {
			animation-timeline: --sub;
			animation-fill-mode: both;
			animation-timing-function: var(--ease);
		}

		.sub_head {
			animation-name: paper-rise-scrub;
			animation-range: contain 0% contain 8%;
		}

		/* P1 — each piece presses OUT of the sheet: no shadow and no opacity,
		   then both. Staggered, so the page builds itself rather than
		   appearing all at once. */
		@keyframes mk-emerge {
			from {
				opacity: 0;
				box-shadow: var(--raise-none);
				transform: scale(.985);
			}

			to {
				opacity: 1;
				transform: none;
			}
		}

		/* P2 — and back in. THE REMOVAL IS THE SHADOW FLATTENING, not a slide:
		   nothing on this page is allowed to disappear behind a hard edge, so
		   each piece stops being raised and therefore stops being there.

		   Each piece carries BOTH animations on one line, arrival and removal
		   — one element cannot hold two separate rules for the same property
		   without one silently winning, and `both` fill makes the second
		   animation's implicit from-state read the first one's result. Read
		   the five as a schedule: arrivals stagger 2→24%, removals run 26→58%
		   in REVERSE order, since the last thing added is the first thing
		   anyone would cut. */
		.mk_popup {
			animation-name: mk-emerge, mk-recede;
			animation-range: contain 15% contain 24%, contain 26% contain 34%;
		}

		.mk_badges {
			animation-name: mk-emerge, mk-recede;
			animation-range: contain 8% contain 18%, contain 32% contain 40%;
		}

		.mk_carousel {
			animation-name: mk-emerge, mk-recede;
			animation-range: contain 11% contain 21%, contain 38% contain 46%;
		}

		.mk_side {
			animation-name: mk-emerge, mk-recede;
			animation-range: contain 5% contain 15%, contain 44% contain 52%;
		}

		.mk_banner {
			animation-name: mk-emerge, mk-recede;
			animation-range: contain 2% contain 12%, contain 50% contain 58%;
		}

		@keyframes mk-recede {
			to {
				opacity: 0;
				box-shadow: var(--raise-none);
				transform: scale(.98);
			}
		}

		/* P1 + P3 — the survivor arrives with the rest and then, once it is
		   alone, settles to `transform: none`, which is its resting state.
		   Compositor-only both times; its layout is never touched. */
		.mk_keep {
			animation-name: mk-keep-in, mk-keep-settle;
			animation-range: contain 6% contain 18%, contain 58% contain 72%;
		}

		@keyframes mk-keep-in {
			from {
				opacity: 0;
			}

			to {
				opacity: 1;
			}
		}

		@keyframes mk-keep-settle {
			from {
				transform: translate(13%, -8%) scale(.58);
			}

			to {
				transform: none;
			}
		}

		/* THE CAPTIONS — stacked and cross-faded, one per phase. The stacking
		   lives here and nowhere else: with no timeline the list is a list. */
		.sub_caps {
			position: relative;
			flex: 0 0 auto;
			width: min(100%, 64ch);
			min-height: 8.5rem;
			padding: 0;
			list-style: none;
		}

		.sub_caps li {
			position: absolute;
			inset: 0;
			margin: 0;
			animation-name: cap-cycle;
		}

		.sub_caps li:nth-child(1) {
			animation-range: contain 3% contain 26%;
		}

		.sub_caps li:nth-child(2) {
			animation-range: contain 26% contain 48%;
		}

		.sub_caps li:nth-child(3) {
			animation-range: contain 48% contain 72%;
		}

		/* the last one STAYS — it is the conclusion, and the thesis lands
		   under it. Its own keyframe, because cap-cycle ends at opacity 0. */
		.sub_caps li:nth-child(4) {
			animation-name: cap-in;
			animation-range: contain 72% contain 84%;
		}

		@keyframes cap-cycle {
			0% {
				opacity: 0;
				transform: translateY(.8rem);
			}

			16% {
				opacity: 1;
				transform: none;
			}

			82% {
				opacity: 1;
				transform: none;
			}

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

		@keyframes cap-in {
			from {
				opacity: 0;
				transform: translateY(.8rem);
			}

			to {
				opacity: 1;
				transform: none;
			}
		}

		.sub_thesis {
			animation-name: paper-rise-scrub;
			animation-range: contain 86% contain 98%;
		}

		@keyframes paper-rise-scrub {
			from {
				opacity: 0;
				transform: translateY(1rem);
			}

			to {
				opacity: 1;
				transform: none;
			}
		}

		/* ============ THE BANDS ============
		   One entrance, on the elements that are cards. Plain `view()`, no
		   named timeline and no pin — a band is an ordinary section and
		   should behave like one. */
		.plate,
		.specs li,
		.qa,
		.define {
			animation: paper-rise-scrub linear both;
			animation-timeline: view();
			animation-range: entry 12% cover 40%;
		}

		/* ============ 05 · DEPTH ============
		   Same shape as SUBTRACTION: 400svh track, 100dvh stage, everything
		   in `contain`. Four tools revealed one at a time in the same slot,
		   while the demo card walks through the states they describe.

		     01  shadow    contain 8–30%    the raised face arrives
		     02  gradient  contain 30–52%   the sheen arrives
		     03  motion    contain 52–74%   the card drifts
		     04  jacking   contain 74–92%   the card presses in and holds
		*/
		.dep_head,
		.dep_card,
		.dep_face--raise,
		.dep_face--press,
		.dep_sheen,
		.dep_item,
		.dep_thesis {
			animation-timeline: --dep;
			animation-fill-mode: both;
			animation-timing-function: var(--ease);
		}

		.dep_head {
			animation-name: paper-rise-scrub;
			animation-range: contain 0% contain 8%;
		}

		.dep_card {
			animation-name: cap-in, dep-drift;
			animation-range: contain 2% contain 12%, contain 52% contain 68%;
		}

		@keyframes dep-drift {
			from {
				translate: 0 0;
			}

			to {
				translate: 0 -1.1rem;
			}
		}

		.dep_face--raise {
			animation-name: dep-fade-in, dep-fade-out;
			animation-range: contain 10% contain 22%, contain 76% contain 88%;
		}

		.dep_sheen {
			animation-name: dep-fade-in;
			animation-range: contain 32% contain 44%;
		}

		.dep_face--press {
			animation-name: dep-fade-in;
			animation-range: contain 76% contain 88%;
		}

		@keyframes dep-fade-in {
			from {
				opacity: 0;
			}

			to {
				opacity: 1;
			}
		}

		@keyframes dep-fade-out {
			from {
				opacity: 1;
			}

			to {
				opacity: 0;
			}
		}

		/* the four tools, stacked in one slot and cross-faded — the same
		   construction as the captions above, for the same reason */
		.dep_list {
			position: relative;
			height: 100%;
			min-height: 0;
			padding: 0;
		}

		.dep_item {
			position: absolute;
			inset: 0;
			display: flex;
			flex-direction: column;
			justify-content: center;
			margin: 0;
			animation-name: cap-cycle;
		}

		.dep_item:nth-child(1) {
			animation-range: contain 6% contain 30%;
		}

		.dep_item:nth-child(2) {
			animation-range: contain 30% contain 52%;
		}

		.dep_item:nth-child(3) {
			animation-range: contain 52% contain 74%;
		}

		.dep_item:nth-child(4) {
			animation-name: cap-in;
			animation-range: contain 74% contain 86%;
		}

		.dep_thesis {
			animation-name: paper-rise-scrub;
			animation-range: contain 88% contain 99%;
		}

		/* ============ THE COLLECTION BAND ============
		   ONE NAMED TIMELINE for the band's own furniture, because the 1px
		   datum's own view() entry phase would be a thousandth of the scroll
		   and it would strike in a single frame. The cards keep their own
		   view() in COVER percentages — a card is shorter than the viewport
		   at every width, so there is no contain phase to hang them on. */
		.alsolike {
			view-timeline: --alsolike block;
		}

		.alsolike::after,
		.alsolike_eyebrow,
		.alsolike h2,
		.alsolike_lead {
			animation-timeline: --alsolike;
			animation-fill-mode: both;
			animation-timing-function: var(--ease);
		}

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

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

			to {
				transform: scaleX(1);
			}
		}

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

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

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

		/* the cards press out of the recess, staggered left to right */
		.tt_card {
			animation: poster-lay linear both;
			animation-timeline: view();
		}

		.tt_card:nth-child(1) {
			animation-range: cover 4% cover 30%;
		}

		.tt_card:nth-child(2) {
			animation-range: cover 8% cover 34%;
		}

		.tt_card:nth-child(3) {
			animation-range: cover 12% cover 38%;
		}

		@keyframes poster-lay {
			from {
				opacity: 0;
				transform: translateY(2.5rem) scale(.96);
			}

			to {
				opacity: 1;
				transform: none;
			}
		}

		.tt_art::after {
			animation: poster-glint linear both;
			animation-timeline: view();
		}

		.tt_card:nth-child(1) .tt_art::after {
			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 poster-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 of the site. The block
   above cannot see that choice (a class is not testable inside
   @supports), so it is honoured here.

   THE RULE THIS OBEYS, and it is the one that bites: a collapsed
   runway MUST have its scrub killed in the same rule. Nearly
   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 every tie.

   The two pinned tracks additionally undo their height, their
   view-timeline and their sticky, and the two cross-faded stacks
   (.sub_caps, .dep_list) undo the absolute stacking as well —
   otherwise four captions would sit on top of each other in a
   collapsed box. Both resolve to what they are in the markup: a
   list.
   ============================================================ */
html.no-runway .scalebar {
	display: none;
}

html.no-runway .cover {
	height: auto;
	view-timeline: none;
}

html.no-runway .cover_stage {
	position: static;
}

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

html.no-runway .cover_ghost,
html.no-runway .cover_eyebrow,
html.no-runway #h1,
html.no-runway .cover_line,
html.no-runway .lede,
html.no-runway .cover_actions,
html.no-runway .cover_inner,
html.no-runway .plate,
html.no-runway .specs li,
html.no-runway .qa,
html.no-runway .define,
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;
}

/* ---- SUBTRACTION collapses to one settled diagram plus its caption list.
   The mock resolves to its ASSEMBLED state — every piece raised, the
   survivor squeezed into the content slot — because that is the state the
   markup describes and the captions still read in order underneath it. */
html.no-runway .sub_track {
	height: auto;
	view-timeline: none;
}

html.no-runway .sub_stage {
	position: static;
	height: auto;
	padding-top: clamp(6rem, 12vh, 8rem);
}

html.no-runway .sub_mock {
	flex: none;
	height: 58vh;
	min-height: 18rem;
}

html.no-runway .sub_head,
html.no-runway .mk,
html.no-runway .sub_thesis {
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
}

html.no-runway .mk_keep {
	animation: none !important;
	opacity: 1 !important;
	/* keeps its markup transform, which is deliberate — the survivor sits in
	   the mock's content slot, which is where the diagram has it before
	   anything has been removed. `transform: none` here would centre it over
	   the still-present sidebar and carousel. */
	transform: translate(13%, -8%) scale(.58);
}

html.no-runway .sub_caps {
	position: static;
	min-height: 0;
	padding-left: 1.4rem;
	list-style: decimal;
}

html.no-runway .sub_caps li {
	position: static;
	inset: auto;
	margin-bottom: .7rem;
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
}

/* ---- DEPTH collapses the same way: the card resolves to RAISED and lit
   (the state the markup rests in), and the four tools become a list. */
html.no-runway .dep_track {
	height: auto;
	view-timeline: none;
}

html.no-runway .dep_stage {
	position: static;
	height: auto;
	padding-top: clamp(6rem, 12vh, 8rem);
}

html.no-runway .dep_body {
	flex: none;
	min-height: auto;
	grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr);
	grid-template-rows: auto;
	align-items: start;
}

html.no-runway .dep_demo {
	height: auto;
}

/* the card stays beside the list rather than scrolling off it — the four
   tools describe the thing, so the thing should still be there on item 04 */
html.no-runway .dep_card {
	position: sticky;
	top: 6rem;
	width: min(100%, 19rem);
	height: auto;
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
	translate: none !important;
}

html.no-runway .dep_head,
html.no-runway .dep_thesis {
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
}

html.no-runway .dep_face--raise,
html.no-runway .dep_sheen {
	animation: none !important;
	opacity: 1 !important;
}

/* the pressed face is the END of a cross-fade, so it resolves to gone —
   resetting it to opacity 1 like everything else would bury the raised
   card under a flat panel */
html.no-runway .dep_face--press {
	animation: none !important;
	opacity: 0 !important;
}

html.no-runway .dep_list {
	position: static;
	height: auto;
	padding-left: 1.4rem;
}

html.no-runway .dep_item {
	position: static;
	inset: auto;
	display: block;
	margin-bottom: 1.6rem;
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
}

@media (max-width: 860px) {
	html.no-runway .dep_body {
		grid-template-columns: 1fr;
		grid-template-rows: auto auto;
		align-items: start;
	}

	html.no-runway .dep_card {
		position: static;
	}
}

/* ---- THE COLLECTION BAND's twin. Same trap, one element worse:
   .tt_art::after is the 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 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 {
	animation: none !important;
	transform: none !important;
}

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


/* ============================================================
   Reduced motion, the stricter case. The block above already
   covers it for anything scroll-driven — html.no-runway is a
   strict superset of html.reduce-motion, and the OS query is
   inside the motion guard. What is left is the hover and panel
   TRANSITIONS, which are not runways and survive no-runway on
   purpose (a visitor who chose Standard still wants a menu that
   opens like a menu). Paper's own twin pair, kept: edit both.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
	* {
		transition: none !important;
		animation: none !important;
	}

	.scalebar {
		display: none;
	}
}

html.reduce-motion * {
	transition: none !important;
}
