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

   WHAT THIS PAGE IS FOR, because every decision below answers
   to it (the owner, 2026-07-31): a minimalist theme is not a theme
   with things taken away. It is one where everything left has
   value, and where the restraint reads as CONFIDENCE — the
   business that wants this is saying "I don't need my website
   to impress you, I'm impressive enough." So the page is not
   quiet for its own sake. The minimalism is the CANVAS; the
   type, the colour and the two locked runways are the proof
   that the studio can do the hard thing and chose not to do it
   everywhere.

   SIX 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 — 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.

   4. A VOICE — FRAUNCES (2026-07-31, the owner: "we will want a more
      unique font"). Paper is set in the system stack, which is
      correct for a one-screen demo of a MATERIAL and reads as
      default on a page selling design judgement. Headings, the
      two diagram statements and the survivor pill are Fraunces;
      body copy and every annotation stay on the system stack,
      so the face is the VOICE and the sans is the INFORMATION.
      SOFT is 50 on this cut and that is a material match, not a
      taste one — Paper is built out of soft dual shadows and
      the axis rounds the terminals to suit. See tools/fonts.py.

   5. COLOUR, AS IMPACT AND NEVER AS A CONSTANT (2026-07-31,
      The owner). Paper has none at all; this page has exactly one
      accent and spends it under a rule that can be stated in a
      line — see THE COLOUR RULE at the tokens. Three of its
      five appearances are scroll-driven, which is the point:
      colour is an EVENT here, not a palette.
      REPLACES the "not a single colour anywhere in it" claim
      that used to run in four places in the copy. If you are
      reading this because you are about to write "no colour"
      again — don't; grep the page for `colour` first.

   6. SECTIONS ARE CARDS (2026-07-31, the owner). Every ordinary
      section is one raised plate with its own action on it, and
      the two runways are deliberately NOT — see THE SECTION
      CARD. The rhythm that buys is the whole layout argument.
   ============================================================ */

/* ---- THE PAGE'S OWN FACE. Unlike the poster faces below, this one is
   critical: it renders the cover h1, which is the LCP element, so it is
   PRELOADED in the document head. Both halves are declared, but the
   latin-ext file is fetched only if an accented character is actually
   rendered — no page renders one today (tools/fonts.py reports this on
   every build). The ranges must match LATIN / LATIN_EXT in that script
   exactly and must not overlap: where two faces of one family claim a
   codepoint, the last declaration silently wins.

   ONE FILE, TWO WEIGHTS. 400 and 700 both render, and keeping the wght
   axis live is what serves them from a single request. Do not "optimise"
   this into two static files. */
@font-face {
	font-family: "Fraunces";
	src: url(/assets/fonts/fraunces-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: "Fraunces";
	src: url(/assets/fonts/fraunces-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;
}

/* ---- 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 (2.6-3.4K each) for
   three previews at the very bottom of the page, below several screens of
   content. The page preloads exactly ONE face, Fraunces, declared above:
   it is the only one anything above the fold waits on. */
@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 SHEET IS #EDEDED, five levels lighter than Paper's
   #E8E8E8 (2026-07-31, the owner: "I'd like the background to be a
   bit lighter"). Two knock-on effects, both handled here:
     · Every contrast ratio IMPROVED, so nothing had to move to
       stay compliant. The scale below is recomputed against the
       new sheet regardless — those numbers are the reason the
       ink is where it is and a stale table is worse than none.
     · The face gradients had to move WITH it. A raised face is
       read as raised because it sits ~10 levels above the sheet;
       leaving them on Paper's absolute values would have shrunk
       that gap to 5 and flattened every panel on the page. Each
       one below keeps its ORIGINAL DISTANCE from the sheet, not
       its original value. Move --sheet again and move all three.

   THE INK SCALE, on the #EDEDED sheet:
     #808080  3.37:1   Paper's body colour — FAILS AA
     #6b6b6b  4.55:1   Paper's badge colour — passes, unused
     #666666  4.90:1   the floor used here
     #5f5f5f  5.46:1   secondary copy
     #4a4a4a  7.57:1   body copy (Paper's own card colour)
     #3a3a3a  9.72:1   headings
   Paper's #808080 survives only as --ink-ghost, for decoration
   that carries no meaning. Do not use it for text.

   THE COLOUR RULE — and it is one line, so that it can be
   checked (2026-07-31, the owner: "colour is used to impact rather
   than be a constant"):

       COLOUR MARKS WHAT SURVIVES, WHAT YOU CAN ACT ON, AND
       WHERE YOU ARE. NOTHING ELSE ON THIS PAGE IS COLOURED.

   Which is exactly five places, and three of the five are
   scroll-driven so the colour is an EVENT rather than a palette:
     survives  the diagram-01 pill ignites as the clutter drains
               (scrubbed) · the diagram-02 mark at the motion
               phase (scrubbed)
     act on    .mybutton--primary's label
     where     the scalebar warms grey -> accent across the page
               (scrubbed) · the collection band's datum hairline
   Adding a sixth costs the rule its meaning. If something needs
   emphasis, this page has type, space and two directions of
   shadow before it has colour.

   --accent is 5.03:1 on the sheet and 5.89:1 under white, so it
   is legal as TEXT and as a text ground. --accent-lit is NOT —
   it is 3.6:1 and exists only for shadow tints and the pill's
   halo, where nothing is read. Never set copy in it.

   THE THREE SHADOW IDIOMS are 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 — the section cards are the third one, big.
   ============================================================ */
:root {
	--sheet: #EDEDED;

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

	/* THE ONE ACCENT. A Swiss red, and the lineage is the argument: red on
	   grey is the International Style's own way of making restraint read as
	   deliberate rather than empty, which is this page's entire pitch. */
	--accent: #3789a4;
	--accent-lit: #3789a4;
	--accent-ink: #ffffff;

	--hi: #ffffff;
	/* the sheet minus ~(48,48,44) per channel — Paper's own derivation for
	   its shadow tone, recomputed against #EDEDED (it read #b8b8bc against
	   #E8E8E8). It is the ghost's dark stop, every link underline and the
	   scroll hint's marker, so it has to track the sheet or all three drift
	   dark together. */
	--lo: #bdbdc1;

	/* the pen's own gradients — a panel is lit from the top, a pill from
	   just below its centre. Each bottom stop holds its ORIGINAL DISTANCE
	   above --sheet (+10 / +8 / +14); see the note above. */
	--face-panel: radial-gradient(circle at 50% 0%, #fff, #f7f7f7);
	--face-pill: radial-gradient(circle at 50% 60%, #fff, #f5f5f5);
	--face-chip: radial-gradient(circle at 50% 60%, #fff, #fbfbfb);

	--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);
	/* section scale. Same two-throw idiom, longer and softer, because a
	   plate the size of a whole section needs its shadow to fall further to
	   read as one object rather than as a lit rectangle. */
	--raise-xl: 0 34px 54px -18px rgba(0, 0, 0, .17), 0 -10px 22px rgba(255, 255, 255, .75);
	/* 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);

	/* THE TWO FAMILIES, and the split is the point: --display is the VOICE
	   (headings, the two diagram statements, the survivor pill) and --text
	   is the INFORMATION. A page arguing that what is left has to earn its
	   place cannot then set every word in the same decorative face.
	   --text is a stack, not a webfont: this page ships ONE font request. */
	--display: "Fraunces", Georgia, "Times New Roman", serif;
	--text: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

	--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: var(--text);
	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);
	font-family: var(--display);
	font-weight: 700;
	line-height: 1.15;
	text-wrap: balance;
	margin: 0;
}
.colorspan span{
	color:var(--accent)
}
h1 {
	font-size: clamp(2.4rem, 8vw, 4.6rem);
	/* Fraunces is already close-fitting at display size, so this is a
	   FRACTION of the tracking the system stack wanted here — the old -.02em
	   collided the ascenders at 74px. */
	letter-spacing: -.008em;
	/* 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: -.005em;
	text-shadow: -0.01em -0.01em 0.02em var(--hi);
}

/* h3 sits at ~17-19px, the bottom of this face's range on the page, so it
   takes the 600 end of the axis rather than 700 and no negative tracking —
   a high-contrast serif tightened at text size closes its own counters. */
h3 {
	font-size: clamp(1.05rem, 1.6vw, 1.2rem);
	font-weight: 600;
	letter-spacing: 0;
}

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

/* A WELL, not a raised panel, and the inversion is deliberate — see THE
   HIERARCHY IS THEN FIXED AT THREE LEVELS under THE BANDS. Every .plate on
   this page lives inside a raised section card, and raised-on-raised is
   the one thing this material cannot express. Paper's own .theme_card
   idiom survives at section scale instead, as .band_inner. */
.plate {
	background: var(--sheet);
	border-radius: 22px;
	box-shadow: var(--press);
	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: NO well at all — just the fold hairline, so the block
   reads as a note written on the card rather than as something set into
   it. Used where a panel would over-announce supporting commentary.
   (It used to be the pressed form, back when .plate was raised; now that
   .plate is itself a well, "quieter than a well" is no well.) */
.plate--quiet {
	background: none;
	border-radius: 0;
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, .85), inset 0 2px 1px -1px rgba(0, 0, 0, .07);
	padding: clamp(1.1rem, 2vw, 1.5rem) 0 0;
}

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

/* COLOUR APPEARANCE 3 of 5 — "what you can act on" (THE COLOUR RULE).
   The pill keeps the page's material; only the LABEL is accent, which is
   the smallest amount of colour that still reads as a signal. 5.89:1 over
   the pill's white face, so it is legal as text with room to spare. */
.mybutton--primary {
	color: var(--accent);
	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;
}

/* COLOUR APPEARANCE 4 of 5 — "where you are" (THE COLOUR RULE), and it is
   scrubbed: the fill warms from grey to accent as the page is read, so the
   one persistent piece of colour on the screen is also the one that is
   literally reporting your position.

   THE SHEEN AND THE COLOUR ARE SEPARATE LAYERS ON PURPOSE. `background`
   as one gradient could not do this — two different gradients do not
   interpolate, so the whole declaration would snap rather than travel.
   background-COLOR carries the animation and a fixed translucent white
   background-IMAGE sits over it, which keeps the raised look at every
   point in the fade. */
.scalebar span {
	display: block;
	height: 100%;
	background-color: #cfcfd4;
	background-image: linear-gradient(180deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, 0));
	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 — it did
   move to #EDEDED, and these two stops moved with it.

   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 nothing
   else on the page depends on it.

   THE SIZE RAMP IS BOUNDED BY THAT EMPTY COLUMN, so it is bounded by the
   LENGTH OF THE WORD. "bold" is four characters, as "less" was before it;
   a longer word overruns the measure-bound copy at 1100-1300px and puts a
   1:1 mark under live text. Change the word, re-derive the ramp. */
.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-family: var(--display);
		font-weight: 700;
		/* was -.05em under the system stack. Fraunces' serifs already bridge
		   the gaps at this size and negative tracking laps them over. */
		letter-spacing: -.01em;
		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);
}

/* THE DISPLAY VOICE, everywhere it speaks outside a heading: the cover's
   standfirst, the AEO definition, the two diagram statements and the type
   specimen in diagram 02. All at 400 — the weight is what separates a
   STATEMENT from a HEADING, so that a page with this many pull lines does
   not read as six competing titles. */
.cover_line,
.define,
.sub_say--survive,
.dep_thesis,
.dep_mark {
	font-family: var(--display);
	font-weight: 400;
}

.cover_line {
	margin: clamp(.6rem, 1.7vh, 1.1rem) 0 clamp(.75rem, 2vh, 1.6rem);
	max-width: 24ch;
	font-size: min(clamp(1.15rem, 2.5vw, 1.7rem), 3.7vh);
	line-height: 1.3;
	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;
	padding: 8%;
	transform: translate(13%, -8%) scale(.58);
}

/* THE SURVIVOR, and it is now the message itself rather than a blank
   placeholder (2026-07-31, the owner: change "Your Work" to "Your message" and
   put it INSIDE the pill; the "One sentence about it" line is gone). One
   object, so what the diagram removes and what it leaves are the same
   KIND of thing — the old head/line/pill trio left three survivors, which
   undercut the point at the exact moment the point was being made.

   COLOUR APPEARANCE 1 of 5 — "what survives" (THE COLOUR RULE), and the
   most important one on the page: this pill is grey while it is one
   element among six, and it is the accent by the time it is alone. The
   colour is EARNED on screen, in front of the reader, which is the whole
   argument the section is making.

   background-COLOR + a fixed white background-IMAGE, not --face-pill:
   two gradients do not interpolate, so the ignite below has to have a
   plain colour to travel. Same construction as .scalebar span. */
.mk_pill {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	max-width: 100%;
	padding: .62em 1.5em;
	border-radius: 100px;
	font-family: var(--display);
	font-weight: 700;
	font-size: clamp(1.15rem, 3.4vw, 2.1rem);
	line-height: 1.15;
	white-space: nowrap;
	color: var(--ink-strong);
	background-color: #f5f5f5;
	background-image: linear-gradient(180deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, 0));
	box-shadow: var(--raise-sm);
}

/* THE TWO STATEMENTS (2026-07-31, the owner — REPLACES .sub_caps, four
   paragraphs cross-fading one per phase, and .sub_thesis under them).
   Do not rebuild the rotating list: it asked the reader to read four
   paragraphs while a diagram was moving beside them, which is the one
   thing a scrubbed section cannot ask for. The argument it carried was
   not thrown away — it moved into #what, where it is ordinary prose a
   reader can go at their own speed.

   Both lines STAY once they arrive. They are in normal flow and only
   opacity and transform animate, so nothing reflows and the block
   reserves its own height from the first frame. */
/* `flex: 0 0 auto` matters only in the PINNED case, where .sub_stage is a
   100dvh flex column and .sub_mock beside it is flex:1 — without it the
   statements are a shrinkable sibling and the browser is entitled to
   compress them to make room for the diagram. Harmless unpinned. */
.sub_says {
	display: grid;
	flex: 0 0 auto;
	gap: clamp(.4rem, 1.2vh, .85rem);
	justify-items: center;
	width: min(100%, 54ch);
}

.sub_say {
	margin: 0;
	max-width: none;
}

/* the statement — the page's voice */
.sub_say--survive {
	font-size: clamp(1.3rem, 3.4vw, 2.1rem);
	line-height: 1.15;
	color: var(--ink-strong);
}

/* and the explanation — the page's information. The weight change is
   what stops two consecutive lines reading as two competing titles. */
.sub_say--canvas {
	font-family: var(--text);
	font-size: clamp(.98rem, 1.9vw, 1.15rem);
	line-height: 1.5;
	color: var(--ink-soft);
}


/* ============================================================
   THE BANDS — the ordinary sections between the two runways.
   No pin, no scrub on the section itself; the card gets an
   entrance and that is all.

   THE SECTION CARD (2026-07-31, the owner: "I'd like whole sections
   to be cards, with buttons on top of those cards"). Every
   ordinary section is ONE raised plate lying on the sheet, with
   its own action sitting on it.

   THE TWO RUNWAYS ARE DELIBERATELY NOT CARDS, and that is the
   layout argument rather than an omission: card, card, FULL
   SCREEN, card, card. The diagrams are the moments the page
   opens out — putting them in a frame the size of every other
   frame is what would make them ordinary. It is the same reason
   the collection band stays a recess. Do not "finish the job"
   by carding the two tracks.

   THE HIERARCHY IS THEN FIXED AT THREE LEVELS, and everything
   inside a card obeys it:
       sheet  ->  RAISED section card  ->  PRESSED sub-panel
   which is why .plate is a WELL here and not the raised panel
   it is in Paper. Two raised things stacked is the one thing
   this material cannot say — a panel on a panel reads as a
   render bug, not as a hierarchy. Anything new that goes inside
   a card is pressed, or it is bare.
   ============================================================ */
.band {
	/* the card carries its own inner padding, so the section keeps only
	   what separates one card from the next */
	padding: clamp(2rem, 6vh, 4.5rem) var(--gutter);
}

.band_inner {
	width: min(100%, var(--measure));
	margin-inline: auto;
	padding: clamp(1.15rem, 3.2vw, 3.25rem);
	border-radius: clamp(20px, 2.6vw, 40px);
	background: var(--face-panel);
	box-shadow: var(--raise-xl);
}

/* At phone widths the gutter and the card padding are two insets doing one
   job, and together they were eating a quarter of a 320px screen. The card
   keeps its inset; the section gives up most of its own. */
@media (max-width: 560px) {
	.band {
		padding-inline: .75rem;
	}
}

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

/* THE CARD'S ACTION — "buttons on top of those cards". One per card, at
   the foot, over the same fold hairline the titleblock uses so the card
   closes the way every closing strip on the page does.

   ONE, and each one different. A landing page that repeats "Start a
   conversation" five times is not being helpful, it is hedging — which is
   the exact behaviour the copy above accuses page builders of. Each card
   offers the single next thing a reader of THAT card would want. */
.card_actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: .9rem 1.25rem;
	margin-top: clamp(1.5rem, 3.5vh, 2.5rem);
	padding-top: clamp(1.1rem, 2.5vh, 1.6rem);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, .85), inset 0 2px 1px -1px rgba(0, 0, 0, .07);
}

.card_actions .anno {
	max-width: 40ch;
}

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

/* the type specimen on the demo card — bold, because a specimen should show
   the face doing its most characteristic thing. Tracking back to 0: the old
   -.03em was set for the system stack and laps Fraunces' serifs. */
.dep_mark {
	position: relative;
	z-index: 2;
	font-size: clamp(2rem, 5vw, 3.2rem);
	font-weight: 700;
	letter-spacing: 0;
	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;
}

/* NO PANEL. The form sits directly on the contact section's card — the
   fields are already wells, and a raised panel here would be the third
   surface in a stack the material only has two directions for. */
.contact_form {
	display: grid;
	gap: 1rem;
	align-content: start;
}

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

   COLOUR APPEARANCE 5 of 5 — "where you are" (THE COLOUR RULE). One
   pixel of accent, struck on scroll, marking the seam where the page
   stops selling this theme and starts showing the next. It fades to
   nothing before it reaches the right edge, so it reads as a drawn line
   rather than as a rule someone put there. */
.alsolike::after {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: 1px;
	background: linear-gradient(90deg, var(--accent), rgba(192, 40, 31, 0) 72%);
	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;
}

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

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

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

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

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


/* ============================================================
   THE COLOPHON — 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);
		}

		/* Grey for the first half of the page, then warming — the accent
		   arrives as the reader does rather than being there from the first
		   pixel. THE COLOUR RULE, "where you are". */
		@keyframes bar-fill {
			0% {
				transform: scaleX(0);
				background-color: #cfcfd4;
			}

			45% {
				background-color: #cfcfd4;
			}

			100% {
				transform: scaleX(1);
				background-color: var(--accent);
			}
		}

		/* ============ 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 72–82%   the second statement lands

		   THE TWO STATEMENTS RIDE THE REMOVAL, and their timing is the whole
		   brief (2026-07-31, the owner): "Minimalism is about what survives" is
		   keyed to 24%, the first frame of P2, so it appears AS the first
		   piece starts pressing back into the sheet — the reader is told what
		   is happening at the instant it starts happening, not afterwards.
		   The pill ignites across 42–70%, finishing as the survivor lands.
		   Move any of the three and move the other two.
		*/
		.sub_head,
		.mk,
		.mk_keep,
		.mk_pill,
		.sub_say {
			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 SURVIVOR IGNITES. See COLOUR APPEARANCE 1 at .mk_pill for why
		   this is the one that matters. It starts at 42%, once three of the
		   five pieces have gone and the outcome is legible, and finishes at
		   70% with the settle — so the pill is fully accent at the moment it
		   is fully alone. Starting it any earlier reads as a highlight;
		   ending it any later reads as an afterthought. */
		.mk_pill {
			animation-name: keep-ignite;
			animation-range: contain 42% contain 70%;
		}

		/* box-shadow interpolates only between shadow LISTS of the same
		   length and type, so the target keeps --raise-sm's two outset
		   layers and only moves their colour and throw. */
		@keyframes keep-ignite {
			to {
				color: var(--accent-ink);
				background-color: var(--accent);
				box-shadow: 0 14px 24px -6px rgba(0, 0, 0, 0.42), 0 -4px 8px rgba(255, 255, 255, .5);
			}
		}

		/* THE TWO STATEMENTS — both arrive, both STAY. cap-in, not cap-cycle:
		   nothing here rotates any more, which was the point of the change. */
		.sub_say--survive {
			animation-name: cap-in;
			animation-range: contain 24% contain 34%;
		}

		.sub_say--canvas {
			animation-name: cap-in;
			animation-range: contain 72% contain 82%;
		}

		/* cap-cycle is no longer used by this section — .dep_item still rides
		   it, which is why it stays here rather than going with the captions. */
		@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;
			}
		}

		/* cap-cycle's exit half on its own, for an item that was already
		   there when the pin locked */
		@keyframes cap-out {
			from {
				opacity: 1;
				transform: none;
			}

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

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

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

		/* ============ THE BANDS ============
		   ONE ENTRANCE PER SECTION, on the card itself. Plain `view()`, no
		   named timeline and no pin — a band is an ordinary section and
		   should behave like one.

		   IT IS THE CARD THAT MOVES, AND NOTHING INSIDE IT. This used to be
		   four separate entrances (.plate, .specs li, .qa, .define), which
		   was correct when those elements were the only objects in the
		   section — now they are contents of one object, and animating both
		   is the page contradicting its own argument in motion. A section
		   arrives as one thing because it IS one thing. */
		.band_inner {
			animation: paper-rise-scrub linear both;
			animation-timeline: view();
			animation-range: entry 10% cover 34%;
		}

		/* ============ 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 0–30%    ALREADY THERE, then leaves
		     02  gradient  contain 30–52%   the sheen arrives
		     03  motion    contain 52–74%   the card turns one full circle
		     04  jacking   contain 74–92%   the card presses in and holds
		*/
		/* SLIDE 01 IS NOT AN ENTRANCE. The heading, the card and its raised
		   face are settled markup: they scroll into place with the section
		   and are fully readable the moment the stage pins. Only then does
		   the hold begin, and 02, 03, 04 play out against it. Nothing in the
		   first beat holds an opacity-0 from-state any more, so the frame a
		   reader arrives on is the finished one. */
		.dep_card,
		.dep_face--raise,
		.dep_face--press,
		.dep_sheen,
		.dep_mark,
		.dep_item,
		.dep_thesis {
			animation-timeline: --dep;
			animation-fill-mode: both;
			animation-timing-function: var(--ease);
		}

		/* Tool 03 is "motion tied to your scroll", so the demo card does the
		   most legible version of it there is: one full turn, driven by the
		   reader. RANGE AND CURVE ARE THE OLD dep-drift'S, UNCHANGED — it
		   keeps the group's --ease, which front-loads the move so the turn is
		   over well before 03's copy starts leaving at contain 70%. A linear
		   scrub spends the whole 52–68% span instead and lands on top of that
		   exit; it was tried and it reads as too long. */
		.dep_card {
			animation-name: dep-spin;
			animation-range: contain 52% contain 68%;
		}

		@keyframes dep-spin {
			from {
				rotate: 0deg;
			}

			to {
				rotate: 360deg;
			}
		}

		/* raised from the start — it has a departure only, into the press */
		.dep_face--raise {
			animation-name: dep-fade-out;
			animation-range: 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%;
		}

		/* COLOUR APPEARANCE 2 of 5 — "what survives" (THE COLOUR RULE). The
		   specimen warms during tool 03, "motion tied to your scroll", and
		   holds through 04. That is deliberate placement rather than a spare
		   slot: 03 is the technique this whole site is built on, so it is the
		   one item in the list the page is entitled to mark as its own. */
		.dep_mark {
			animation-name: mark-warm;
			animation-range: contain 52% contain 68%;
		}

		@keyframes mark-warm {
			to {
				color: var(--accent);
			}
		}

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

		/* 01 is on screen before the pin locks, so it does not arrive — it
		   only leaves. The range is cap-cycle's own out-phase (82–100% of the
		   old contain 6–30%), so 02's arrival is unmoved. */
		.dep_item:nth-child(1) {
			animation-name: cap-out;
			animation-range: contain 26% 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 .dep_list undoes its
   absolute stacking — otherwise four items would sit on top of
   each other in a collapsed box. It resolves to what it is in
   the markup: a list. (.sub_says used to need the same undo and
   no longer does; two statements in flow are already the
   collapsed state.)

   COLOUR RESOLVES TO NONE. Three of the five accent appearances
   are scrubbed, and a reader here has scrolled nothing — so the
   pill, the specimen and the scalebar all come back grey. The
   two static ones (the primary button's label, the band's
   datum) stay, because they are not reporting progress.
   ============================================================ */
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 .band_inner,
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_say {
	animation: none !important;
	opacity: 1 !important;
	transform: none !important;
}

/* The pill resolves to its NEUTRAL state: grey, sitting in the mock's
   content slot with the clutter still around it, which is the state the
   markup describes. keep-ignite's from-state happens to be exactly that,
   but it is written out anyway — the rule here is that a collapsed runway
   RESETS what it was animating, so that nothing on the page depends on
   which direction a fill happens to resolve in. */
html.no-runway .mk_pill {
	animation: none !important;
	color: var(--ink-strong) !important;
	background-color: #f5f5f5 !important;
	box-shadow: var(--raise-sm) !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);
}

/* .sub_says needs no undo at all, and that is the point of having replaced
   the caption stack with it: two statements in normal flow ARE the
   collapsed state. There is no absolute stacking to unwind here any more. */

/* ---- 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;
	rotate: 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 specimen resolves to INK, not accent: with no scroll to earn it, the
   colour has not been spent on anything (THE COLOUR RULE) */
html.no-runway .dep_mark {
	animation: none !important;
	color: var(--ink-strong) !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;
}
