/*
 * Tristopic — base stylesheet.
 *
 * Design tokens come from theme.json as --wp--preset--* / --wp--custom--*
 * custom properties. Do not hardcode colours or spacing here; add them to
 * theme.json and reference the variable.
 */

/* ---------------------------------------------------------------- fonts --- */

/*
 * Self-hosted only. Drop the woff2 files into assets/fonts/ and these rules
 * pick them up; until then theme.json's stack falls back to Helvetica and the
 * system mono, so nothing breaks.
 */
/*
 * Google Sans Flex, one variable file serving the whole site.
 *
 * Two axes only: weight and width. The face ships six, and the full set is
 * 1.37MB for Latin — ROND and slnt account for most of it, opsz for 190KB more.
 * At wght and wdth alone it is 116KB, and those are the two the design spends.
 *
 * The same file backs both families. They are registers, not typefaces: the
 * reading one keeps the full width range and is set per element, while the
 * furniture one is pinned narrow and heavy here, in the descriptor, so every
 * label, clock, bracket and button in the theme inherits the register without
 * being named in a rule. A face declared at one font-stretch is instanced there
 * whatever the element asks for.
 */
@font-face {
	font-family: "Tristopic Grotesk";
	src: url("../fonts/gsflex-latin.woff2") format("woff2");
	font-weight: 1 1000;
	font-stretch: 25% 151%;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "Tristopic Grotesk";
	src: url("../fonts/gsflex-latin-ext.woff2") format("woff2");
	font-weight: 1 1000;
	font-stretch: 25% 151%;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/*
 * The sphere's tile lettering: 500 at 75%, condensed and with enough weight
 * to hold at 8px. Light on dark blooms — a thin stroke at this size loses its
 * shape against the ground — and condensed buys room and legibility at once.
 *
 * It needs a family of its own because the furniture face pins both values in
 * its descriptor, and a canvas asking for anything else in that family is
 * instanced at 600 / 76% regardless. Same file, so nothing extra is fetched.
 */
@font-face {
	font-family: "Tristopic Tile";
	src: url("../fonts/gsflex-latin.woff2") format("woff2");
	font-weight: 500;
	font-stretch: 75%;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* The furniture register: narrow and heavy, pinned for every element at once. */
@font-face {
	font-family: "Tristopic Mono";
	src: url("../fonts/gsflex-latin.woff2") format("woff2");
	font-weight: 600;
	font-stretch: 76%;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "Tristopic Mono";
	src: url("../fonts/gsflex-latin-ext.woff2") format("woff2");
	font-weight: 600;
	font-stretch: 76%;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ----------------------------------------------------------------- base --- */

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

html {
	-webkit-text-size-adjust: 100%;

	/*
	 * No scrollbar, but everything that scrolls still scrolls: wheel,
	 * trackpad, touch, keyboard, find-in-page and anchor jumps are untouched.
	 * What is given up is dragging the bar itself.
	 */
	scrollbar-width: none;
	-ms-overflow-style: none;
}

html::-webkit-scrollbar,
body::-webkit-scrollbar {
	width: 0;
	height: 0;
}

body {
	margin: 0;
	min-height: 100vh;
	text-rendering: optimizeLegibility;
	-webkit-font-smoothing: antialiased;
	/*
	 * clip, not hidden: hidden on one axis forces the other to auto, which
	 * makes body a scroll container and stops any descendant from sticking to
	 * the viewport. clip guards the same way without that side effect.
	 */
	overflow-x: clip;
}

img,
video,
canvas {
	max-width: 100%;
	height: auto;
	display: block;
}

/*
 * Focus reads the way hover reads: the control takes the accent. Not a ring —
 * a 2px box in that green was loud enough to be the loudest thing on the
 * screen, and it appeared on opening a card rather than only on keyboard use,
 * because the card moved focus in JavaScript and a programmatic focus counts as
 * focus-visible.
 *
 * Still a visible state, which is the part a keyboard needs; tabindex="-1" is
 * excluded because a container focused to be announced is not being pointed at.
 */
:where(a, button, [tabindex]:not([tabindex="-1"])):focus-visible {
	outline: none;
	color: var(--wp--preset--color--accent);
}

::selection {
	background: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--ink);
}

/* Screen-reader-only utility, matching core's class name. */
.screen-reader-text {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* -------------------------------------------------------------- eyebrow --- */

/* Mono, uppercase, tracked-out labels. Used for meta, nav, and captions. */
.tristopic-eyebrow,
.wp-block-post-terms {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
}

/* ------------------------------------------------------------------ bar --- */

/*
 * One hairline row across the top: wordmark left, one item centred, one right.
 * Nothing else. The rule underneath is the only division on the page, and the
 * current page is marked by an underline rather than a colour or a weight.
 */
.tristopic-bar {
	position: sticky;
	top: 0;
	z-index: 20;
	background: rgb(from var(--wp--preset--color--ink) r g b / 0.88);
	backdrop-filter: blur(14px);
	-webkit-backdrop-filter: blur(14px);
	border-bottom: var(--wp--custom--line);
}

.tristopic-bar__inner {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	align-items: baseline;
	gap: var(--wp--preset--spacing--20);
	padding-block: 0;
}

.tristopic-bar a {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	text-decoration: none;
	/*
	 * Chrome, not content. These sat at 18.23:1 — the same colour weight as a
	 * 72px title — so navigation competed with the headline it sits above.
	 */
	color: var(--wp--preset--color--paper-dim);
	text-underline-offset: 0.35em;
	text-decoration-thickness: 1px;
}

.tristopic-bar a:hover,
.tristopic-bar a[aria-current] {
	text-decoration: underline;
}

/* The page you are on is the one that gets the light. */
.tristopic-bar a[aria-current] {
	color: var(--wp--preset--color--paper);
}

.tristopic-bar__mark { justify-self: start; }
.tristopic-bar__centre { justify-self: center; }
.tristopic-bar__end { justify-self: end; }

/*
 * One row, at every width. The three items measure 236px of text at 375px, so
 * there was room for a single row all along; they were being stacked by a rule
 * written before anyone measured them.
 */
@media (max-width: 599px) {
	.tristopic-bar__inner {
		column-gap: var(--wp--preset--spacing--10);
	}
}

/* ------------------------------------------------------------- display --- */

/*
 * The one piece of oversized type on the site. Everything else is small and
 * technical, which is what gives this its weight.
 */
.tristopic-display {
	font-size: clamp(2.75rem, 11vw, 9rem);
	line-height: 0.94;
	letter-spacing: -0.035em;
	font-weight: 500;
	margin: 0;
	text-wrap: balance;
}

/* ------------------------------------------------------------- project --- */

.tristopic-project,
.tristopic-archive,
.tristopic-page {
	/* The bar is directly above; 64px of air under it was reading as a fault. */
	padding-block: var(--wp--preset--spacing--20) var(--wp--preset--spacing--50);
}

/* ----------------------------------------------------------- work index --- */

.tristopic-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(20rem, 100%), 1fr));
	gap: var(--wp--preset--spacing--30);
}

.tristopic-card {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--10);
}

.tristopic-card__media {
	overflow: hidden;
	background: var(--wp--preset--color--ink-raised);
	aspect-ratio: 3 / 4;
}

.tristopic-card__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transform: scale(1.001);
	transition: transform var(--wp--custom--duration--slow) var(--wp--custom--ease);
}

.tristopic-card:hover .tristopic-card__media img {
	transform: scale(1.04);
}

/* ------------------------------------------------------------- project --- */

/* ----------------------------------------------------- editorial blocks --- */

/* Unbulleted, ruled list — reads as a specification, not prose. */
.tristopic-list {
	list-style: none;
	margin: 0.75em 0 var(--wp--preset--spacing--30);
	padding: 0;
}

.tristopic-list li {
	padding-block: 0.7em;
	border-bottom: var(--wp--custom--line);
	font-size: var(--wp--preset--font-size--small);
}

/* --------------------------------------------------------------- case study --- */

/*
 * ONE layout for a project, used by both the full page and the modal card.
 * Whatever changes here changes in both, which is the point: the two were
 * drifting into different designs.
 *
 * Reading order is always the same — label, title, specification row, cover,
 * lead, body — in a single column. A sidebar wastes half the width the moment
 * you scroll past the cover, and it stops case-study images running full
 * measure.
 */
.tristopic-case {
	display: grid;
	gap: 1.5rem;
}

/*
 * Trim each box to its own letters — cap height at the top, baseline at the
 * bottom — so the grid gap above is the only space between one section and the
 * next.
 *
 * Without this every box is taller than the text in it, and taller by a
 * different amount: 17px of half-leading around the 72px title, 14 around the
 * back link, 10 around a label. The gaps were within 8px of each other and read
 * as 37, 40, 57 and 47, because that leading is unevenly distributed by
 * construction and belongs to nothing.
 *
 * Descenders hang past the baseline edge rather than reserving room for it,
 * which is the point; in the ruled bands they hang into padding that is there
 * anyway. Firefox has not shipped text-box and simply keeps its leading.
 */
.tristopic-case__back,
.tristopic-case__title,
.tristopic-case__spec dt,
.tristopic-case__spec dd,
.tristopic-case__toggle-label,
.tristopic-case__toggle-mark {
	text-box: trim-both cap alphabetic;
}

/*
 * The gap does the spacing here, and WordPress's block margins are zeroed, so
 * the two never stack. Leaning on those margins instead worked on the page and
 * failed silently in the modal, whose children are hand-written markup and get
 * none: every element in the card butted against the next and the button sat
 * flat on the artwork.
 */
.tristopic-case > * + * {
	margin-block-start: 0;
}

@media (max-width: 899px) {
	.tristopic-case {
		gap: var(--wp--preset--spacing--20);
	}
}

/* Back to the sphere. The arrow slides on hover, the same gesture as the
   submit button's arrow, so the two read as one family. */
.tristopic-case__back {
	/* The grid gap spaces this; a margin as well was a gap on top of a gap. */
	margin: 0;
}

.tristopic-case__back a {
	display: inline-flex;
	align-items: baseline;
	gap: 0.6em;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
	text-decoration: none;
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-case__back a:hover {
	color: var(--wp--preset--color--paper);
}

.tristopic-case__back a span {
	transition: transform var(--wp--custom--duration--base) var(--wp--custom--ease);
}

.tristopic-case__back a:hover span {
	transform: translateX(-0.25rem);
}

/*
 * The display register: thin and open, against furniture that is dense and
 * narrow. Both registers move on both axes and in opposite directions, so a
 * title and a label are told apart by shape before size.
 *
 * The two adjustments below are the ones the optical-size axis would have made
 * on its own, made by hand because that axis was too expensive to carry: at a
 * phone's title size, thin wide letters on black go fragile, so the weight
 * comes up and the width comes back in.
 */
.tristopic-case__title {
	margin: 0;
	font-size: clamp(2rem, 6vw, 4.5rem);
	line-height: 0.96;
	letter-spacing: -0.035em;
	font-weight: 700;
	font-stretch: 124%;
	text-wrap: balance;
}

@media (max-width: 599px) {
	.tristopic-case__title {
		font-stretch: 108%;
		letter-spacing: -0.02em;
	}
}

/*
 * The facts: one hairline band, evenly spaced. Each fact is as wide as its own
 * text and the space left over is divided equally between them, because equal
 * columns cannot give equal gaps — every column holds a different length of
 * word, so the space between one value and the next is whatever the pair
 * happens to leave over. At 1440px that was 165, 121, 112 and 223px across a
 * single row.
 *
 * Nothing lowers the items' min-width, so each one can shrink to its longest
 * word and no further. Removing that floor let the card — narrower than the
 * page — split words down the middle, including a year reading 202/2.
 */
.tristopic-case__spec {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 0.6rem var(--wp--preset--spacing--20);
	margin: 0;
	padding-block: 0.7rem;
	border-top: var(--wp--custom--line);
	border-bottom: var(--wp--custom--line);
}

/*
 * The row reads outward: role on the left edge, year on the right, the two
 * middle facts centred between them. Keyed to the fact rather than to its
 * position, so a project missing one does not shuffle the others along.
 */
.tristopic-case__spec [data-fact="role"] {
	text-align: left;
}

.tristopic-case__spec [data-fact="discipline"],
.tristopic-case__spec [data-fact="client"],
.tristopic-case__spec [data-fact="industry"],
.tristopic-case__spec [data-fact="live"] {
	text-align: center;
}

.tristopic-case__spec [data-fact="year"] {
	text-align: right;
}

.tristopic-case__spec dt {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
}

.tristopic-case__spec dd {
	/*
	 * Set in its own right now that the leading which used to separate label
	 * from value has gone: near the 11.5px it read as before, so the pair still
	 * binds against a 24px section gap.
	 */
	margin: 0.5rem 0 0;
	color: var(--wp--preset--color--paper-soft);
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.3;
}

/*
 * One row on a phone, whatever the row holds. At 375px the band is 355px wide,
 * so five columns at a halved gap are 64px — wide enough for INDUSTRY, the
 * longest label at eight characters, once the label is at 10px with most of
 * its tracking removed. Values wrap inside their own column, which is what
 * makes a single row possible at all.
 *
 * Nothing here counts the facts, which matters because a project need not carry
 * every one and the card holds all five in the markup, hiding what it has not
 * got. A display:none child is not a flex item, so the row spaces whatever is
 * actually on screen.
 */
@media (max-width: 599px) {
	/*
	 * Flexbox breaks a line before it shrinks anything, so wrapping has to be
	 * off for the row to tighten at all: with it on, the card sent the year to
	 * a line of its own rather than narrowing the four beside it. The items
	 * still stop at their longest word, which is the floor restored above.
	 */
	.tristopic-case__spec {
		flex-wrap: nowrap;
		column-gap: var(--wp--preset--spacing--10);
	}

	/*
	 * Six facts is the row that cannot hold even tightened, so it keeps its
	 * licence to break. All space-between can do with the leftovers is fling
	 * them at both edges, so the second line starts where the first one does.
	 */
	.tristopic-case__spec:has([data-fact="live"]) {
		flex-wrap: wrap;
		justify-content: flex-start;
		column-gap: var(--wp--preset--spacing--20);
	}

	.tristopic-case__spec dt {
		/*
		 * A flat 10px. The clamp this replaces was sized for DISCIPLINE, which
		 * no longer exists; INDUSTRY is the longest label now.
		 */
		font-size: 0.625rem;
		letter-spacing: 0.06em;
	}

	/*
	 * One line per value, the same as the desktop row, with an ellipsis where
	 * there is not room for all of it.
	 *
	 * The alignment was never different between the two; the wrapping was. Five
	 * values come to 369px of text against 337 inside a card at this width, so
	 * flex shrank the two longest until they broke, and the row became three
	 * ragged lines where the desktop has two clean ones. No size fixes that —
	 * the values would have to reach 8.7px to fit at 320 — so a value keeps its
	 * line and gives up its tail instead.
	 *
	 * min-width: 0 is safe here. It was removed once because, with break-word
	 * active, flex could shrink a box until words split down the middle; nowrap
	 * and an ellipsis clip at a character and say so.
	 *
	 * The row itself needs it as much as its columns do. The row is a grid item,
	 * and a grid item's automatic minimum is its own content — so with the
	 * columns held to one line, a long client name pushed the whole row 93px
	 * wider than the card instead of taking the ellipsis it had been given.
	 */
	.tristopic-case__spec,
	.tristopic-case__spec > * {
		min-width: 0;
	}

	.tristopic-case__spec dd {
		font-size: 0.75rem;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	/*
	 * The year does not give anything up. Flex shrinks every column in
	 * proportion, so a long client name elsewhere in the row was enough to turn
	 * 2026 into 20… — four characters truncated to make room for a value that
	 * was going to be truncated anyway. It holds its width and the shrink falls
	 * on the columns that have something to give.
	 */
	.tristopic-case__spec [data-fact="year"] {
		flex-shrink: 0;
	}
}

/*
 * Covers both the bare <img> used in the modal and the figure the
 * post-featured-image block produces on the page.
 *
 * No height ceiling and no object-fit: the box follows the image rather than
 * the image being fitted to a box, so whatever the proportions, the whole
 * artwork is on screen at its own shape. A ceiling of 70svh was still boxing
 * tall work and letterboxing it against the panel.
 */
.tristopic-case__cover img,
.tristopic-case__cover {
	width: 100%;
	height: auto;
	border-radius: 0.75rem;
	display: block;
}

.tristopic-case__cover {
	background: var(--wp--preset--color--ink);
}

/* One step back from the display, and still clear of the body's 400 / 100%. */
.tristopic-case__lead {
	margin: 0;
	max-width: 46ch;
	font-size: var(--wp--preset--font-size--lead);
	line-height: 1.4;
	font-weight: 300;
	font-stretch: 106%;
	text-wrap: pretty;
}

.tristopic-case__lead:empty {
	display: none;
}

/*
 * Editor content: styled generically, since it comes from the block editor.
 *
 * Deliberately not a grid, and deliberately without a measure of its own.
 * WordPress builds alignment out of max-width and auto margins in an ordinary
 * block container, so a grid here left the constrained layout nothing to act on
 * and every block came out at a single width. A 62ch cap on paragraphs then
 * made it worse than that — narrower than the content measure, so the auto
 * margins centred them and the text no longer lined up with anything else.
 *
 * The measure is the layout's contentSize and the rhythm is theme.json's block
 * gap, both of which the editor knows about, so the draft matches the page.
 */

/*
 * Constrained layout centres its content — with margin-left: auto !important,
 * which is why this answers in kind. Here the text hangs off the left edge the
 * title, the facts and the cover already share; the lead paragraph above is
 * already left aligned at its own measure, so a centred body would have given
 * the page two columns of prose starting in two different places.
 *
 * A block the editor has explicitly centred keeps its own margins.
 */
.tristopic-case__body > *:not(.aligncenter) {
	margin-inline: 0 !important;
}

.tristopic-case__body img,
.tristopic-case__body video,
.tristopic-case__body iframe {
	width: 100%;
	height: auto;
	border-radius: 0.75rem;
	display: block;
}

/*
 * An image and its caption stack. Not galleries, which are figures too and are
 * built on a flex row — a grid here collapsed three across into one down.
 */
.tristopic-case__body figure:not(.wp-block-gallery) {
	margin: 0;
	display: grid;
	gap: 0.6rem;
}

/* Galleries breathe on the same step as everything else in a case. */
.tristopic-case__body .wp-block-gallery {
	--wp--style--unstable-gallery-gap: var(--wp--preset--spacing--20);

	gap: var(--wp--preset--spacing--20);
	margin: 0;
}

/* ------------------------------------------------------------- slider --- */

/*
 * The gallery's Slider style: a scroll-snap strip, and nothing more. With no
 * JavaScript it is a row of images that scrolls, which is already the whole
 * behaviour on a touch screen; the buttons below are for pointers and are built
 * at runtime.
 *
 * Slides stop short of the full width so the next one shows at the edge. A
 * carousel whose slides exactly fill it reads as a static image, and the edge of
 * what is next is the strongest signal that there is a next.
 */
.wp-block-gallery.is-style-slider {
	flex-wrap: nowrap;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
}

.wp-block-gallery.is-style-slider::-webkit-scrollbar {
	width: 0;
	height: 0;
}

.wp-block-gallery.is-style-slider.has-nested-images figure.wp-block-image {
	flex: 0 0 min(100%, 42rem);
	width: auto;
	scroll-snap-align: start;
}

.tristopic-slider__nav {
	display: flex;
	align-items: baseline;
	gap: var(--wp--preset--spacing--20);
	margin-block-start: 0.6rem;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
}

.tristopic-slider__step {
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	letter-spacing: inherit;
	color: var(--wp--preset--color--accent-deep);
	cursor: pointer;
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-slider__step[disabled] {
	color: var(--wp--preset--color--ink-line);
	cursor: default;
}

/* Pushed to the far edge, where the year sits in the facts row above. */
.tristopic-slider__count {
	margin-inline-start: auto;
	font-variant-numeric: tabular-nums;
}

/*
 * A wide or full block is its own statement and should not carry the paragraph
 * rhythm into the images either side of it.
 */
.tristopic-case__body > .alignwide,
.tristopic-case__body > .alignfull {
	margin-block: var(--wp--preset--spacing--40);
}

.tristopic-case__body figcaption {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wide);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
}

.tristopic-case__body h2,
.tristopic-case__body h3 {
	font-size: var(--wp--preset--font-size--lead);
	margin-top: var(--wp--preset--spacing--20);
}

/* A quiet shimmer while the body is in flight. */
.tristopic-case__body.is-loading::before {
	content: "";
	display: block;
	height: 1px;
	background: linear-gradient(
		90deg,
		var(--wp--preset--color--ink-line),
		var(--wp--preset--color--paper-dim),
		var(--wp--preset--color--ink-line)
	);
	background-size: 200% 100%;
	animation: tristopic-load 1.1s linear infinite;
}

@keyframes tristopic-load {
	from { background-position: 200% 0; }
	to   { background-position: -200% 0; }
}

.tristopic-case__actions {
	margin: 0;
}

/*
 * `hidden` is a user-agent `display: none`, which means any author `display`
 * on the same element silently beats it — and then the element is still in
 * the layout, invisible, intercepting clicks. Settle it once, for everything.
 */
[hidden] {
	display: none !important;
}

/* ---------------------------------------------------------------- chrome --- */

/*
 * The bar, the home page HUD and the colophon are all chrome: strips that
 * span the viewport rather than sitting in the content column. They share one
 * inset so their text starts and ends on the same line. Change this value and
 * all three move together.
 */
:root {
	--tristopic-chrome-inset: 0px;
}

.tristopic-bar__inner {
	padding-inline: var(--tristopic-chrome-inset);
}

/*
 * WordPress gives a top-level group the root padding and caps its children at
 * the content width. Right for a column of prose, wrong for a strip of chrome
 * that should reach the same edges as the bar.
 */
.tristopic-footer {
	padding-inline: var(--tristopic-chrome-inset);
}

/*
 * Only what the hardware reserves. env() is 0 on an ordinary screen, so the
 * colophon sits on the bottom edge like the rest of the chrome, and becomes a
 * value only where a home indicator would otherwise cover it.
 */
.tristopic-footer {
	padding-block-end: env(safe-area-inset-bottom, 0px);
}

.tristopic-footer > .tristopic-colophon {
	max-width: none;
	margin-inline: 0;
}

/* ---------------------------------------------------------------- frame --- */

/*
 * Pages on the fixed-frame template fill the viewport instead of scrolling:
 * bar pinned top, colophon strip pinned bottom, content in between.
 *
 * Two rules make this safe rather than merely tidy:
 *
 *   1. It only engages where the content measurably fits. Below the threshold
 *      the page reverts to ordinary scrolling. A phone after browser chrome,
 *      or anyone at 200% zoom, cannot hold this content at a legible size,
 *      and clipping it would be far worse than a scrollbar.
 *
 *   2. The content column carries overflow:auto rather than the document. If
 *      copy ever outgrows the frame the column scrolls and everything stays
 *      reachable; the page itself still never shows a scrollbar.
 */
.tristopic-frame {
	display: grid;
	gap: var(--wp--preset--spacing--40);
	padding-block: var(--wp--preset--spacing--40);
}

/*
 * WordPress's constrained layout gives every child auto inline margins, which
 * makes this shrink to its contents and centre itself. Inside the frame that
 * was corrected in the grid; outside it, the masthead was centred while the
 * copy beside it stayed left. Correct it on the element instead.
 */
.tristopic-frame__masthead {
	margin-inline: 0;
	max-width: none;
	width: 100%;
}

.tristopic-frame__title {
	font-size: clamp(2.5rem, 7vw, 5.5rem);
	line-height: 0.94;
	letter-spacing: -0.035em;
	margin: 0;

	/*
	 * The same weight as a project title, narrower because it is set larger:
	 * this clamps to 5.5rem where a project title clamps to 4.5, and one width
	 * across both would put the bigger one at the smaller one's setting.
	 */
	font-weight: 700;
	font-stretch: 116%;
}

@media (max-width: 599px) {
	.tristopic-frame__title {
		font-stretch: 108%;
		letter-spacing: -0.02em;
	}
}

/*
 * Subheads belong to the display register but not to its numbers: 250 at 132%
 * is confident at 72px and fragile at 22px. This is the adjustment the
 * optical-size axis would have made by itself, had it been worth its 190KB.
 */
.tristopic-case__body h2,
.tristopic-case__body h3,
.tristopic-frame__body h2,
.tristopic-frame__body h3 {
	font-weight: 500;
	font-stretch: 112%;
}

.tristopic-frame__meta {
	margin: var(--wp--preset--spacing--20) 0 0;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
	max-width: 24ch;
}

.tristopic-frame__body > * + * {
	margin-top: var(--wp--preset--spacing--30);
}

@media (min-width: 1100px) and (min-height: 768px) {
	.tristopic-framed {
		height: 100svh;
		overflow: hidden;
	}

	.tristopic-framed .wp-site-blocks {
		height: 100svh;
		display: grid;
		grid-template-rows: auto minmax(0, 1fr) auto;
	}

	.tristopic-framed .tristopic-frame {
		/*
		 * The whole vertical rhythm is a function of viewport height, so a
		 * short laptop screen tightens the page rather than pushing it past
		 * the frame. Every clamp keeps a floor that stays readable.
		 */
		--frame-pad: clamp(1.25rem, 3.6vh, 3.25rem);
			--frame-step: clamp(0.6rem, 1.9vh, 1.6rem);

		grid-template-columns: minmax(0, 1fr);
		align-content: start;
		min-height: 0;
		gap: var(--frame-step);
		padding-block: var(--frame-pad);
		text-align: center;
	}

	.tristopic-framed .tristopic-frame__masthead {
		align-self: start;

		/* Undo the constrained layout's auto margins so the item stretches. */
		justify-self: stretch;
		margin-inline: 0;
		max-width: none;
		width: 100%;
		text-align: center;
	}

	/*
	 * Sized against the viewport's height as much as its width: on a framed
	 * page the title is a label above the thing the visitor came for, and the
	 * height is what it has to share.
	 */
	.tristopic-framed .tristopic-frame__title {
		font-size: clamp(2.25rem, 5.5vh, 3.5rem);
	}

	/* The safety valve: this column scrolls, the document never does. */
	.tristopic-framed .tristopic-frame__body {
		/*
		 * Overriding WordPress's own block gap reaches every nested layout —
		 * columns, lists, the form — from one place.
		 */
		--wp--style--block-gap: var(--frame-step);

		min-height: 0;
		overflow: auto;
		overscroll-behavior: contain;
		scrollbar-width: none;
	}

	.tristopic-framed .tristopic-frame__body::-webkit-scrollbar {
		width: 0;
		height: 0;
	}

	.tristopic-framed .tristopic-frame__body > * + *,
	.tristopic-framed .tristopic-frame__body .entry-content > * + * {
		/*
		 * The second selector is the one that matters. WordPress writes its
		 * block gap as a literal length into a generated container rule, so
		 * setting --wp--style--block-gap alone leaves the post content on a
		 * flat 2rem and the frame's height scaling never reaches it.
		 */
		margin-top: var(--frame-step);
	}

	.tristopic-framed .tristopic-frame__body .has-lead-font-size {
		font-size: clamp(1.0625rem, 2.6vh, 1.375rem);
	}


	.tristopic-framed .tristopic-frame__meta {
		max-width: none;
		margin-top: 0.35rem;
	}

	/*
	 * WordPress caps every child of the post content at the content size and
	 * centres it with auto margins, which held the page to 970px and half a
	 * column off centre. Take the full measure back.
	 */
	.tristopic-framed .entry-content,
	.tristopic-framed .entry-content > * {
		max-width: none;
		width: auto;
		margin-inline: 0;
	}

	/* Prose keeps a readable measure. The band deliberately does not. */
	.tristopic-framed .entry-content > p:not(.tristopic-band) {
		max-width: 60ch;
		margin-inline: auto;
	}

	/*
	 * Same auto-margin trap as the masthead: released, the body takes the
	 * column, which is what lets the band run the full measure.
	 */
	.tristopic-framed .tristopic-frame__body {
		justify-self: stretch;
		width: 100%;
		max-width: none;
		margin-inline: 0;
	}

	.tristopic-framed .tristopic-footer {
		margin-top: 0;
	}
}

/*
 * A textarea's baseline is its last line, so baseline alignment drops the
 * label to the bottom of the box. Align this row to the top instead, then
 * push the label down by the 6px that baseline alignment yields on the
 * single-line rows, so every label in the sheet sits on the same offset.
 */
.tristopic-field:has(textarea) {
	align-items: start;
}

.tristopic-field:has(textarea) .tristopic-field__index,
.tristopic-field:has(textarea) > label {
	padding-block-start: 6px;
}

/* ------------------------------------------------------------ inclusions --- */

/* Fields stay left-aligned inside the centred column: a centred label above a
   centred input gives the eye no edge to run down. */
.tristopic-commission__form {
	max-width: 40rem;
	margin-inline: auto;
	text-align: left;
}

/* The title and the close control share a line in the modal. */
.tristopic-case__head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--20);
}

/* --------------------------------------------------------- case disclosure --- */

/*
 * The toggle that folds the project text away. Brackets rather than an icon,
 * so the control is set in the same mono the rest of the furniture uses.
 */
.tristopic-case__toggle {
	/*
	 * No margin: the article is a grid with its own gap, so a margin here is
	 * added to that gap rather than replacing it, and the control drifts away
	 * from the text it opens.
	 */
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--20);
	width: 100%;
	/* The same step off the rule that the facts band uses. */
	padding: 0.7rem 0;
	border: 0;
	border-bottom: var(--wp--custom--line);
	background: none;
	cursor: pointer;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-case__toggle:hover,
.tristopic-case__toggle[aria-expanded="true"] {
	color: var(--wp--preset--color--paper);
}

.tristopic-case__toggle-mark {
	color: var(--wp--preset--color--accent-deep);
	font-variant-numeric: tabular-nums;
}

.tristopic-case__toggle-mark::before {
	content: "[ + ]";
}

.tristopic-case__toggle[aria-expanded="true"] .tristopic-case__toggle-mark::before {
	content: "[ - ]";
}

.tristopic-case__more > * + * {
	margin-top: var(--wp--preset--spacing--30);
}

/* ------------------------------------------------------ folds in a case --- */

/*
 * core/details, in the house disclosure language: mono rules and bracket marks,
 * the same furniture as the facts band above it. The only fold left on the site,
 * and the only one worth having — a real <details>, so it opens with JavaScript
 * off, placed by hand rather than by the template, holding whatever a particular
 * project decides is its long version rather than whatever the markup happened
 * to wrap.
 */
.tristopic-case__body .wp-block-details {
	border-top: var(--wp--custom--line);
	border-bottom: var(--wp--custom--line);
}

.tristopic-case__body .wp-block-details > summary {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--20);
	padding: 0.7rem 0;
	cursor: pointer;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);

	/* Browsers disagree on which marker they draw; this covers both. */
	list-style: none;

	/*
	 * Its leading stays. text-box trims a block container's own line boxes and
	 * this is a flex row, so the declaration would be inert — and there is no
	 * element around the summary's text to put it on instead.
	 */
}

.tristopic-case__body .wp-block-details > summary::-webkit-details-marker {
	display: none;
}

.tristopic-case__body .wp-block-details > summary:hover {
	color: var(--wp--preset--color--paper);
}

.tristopic-case__body .wp-block-details > summary::after {
	content: "[ + ]";
	color: var(--wp--preset--color--accent-deep);
	font-variant-numeric: tabular-nums;
}

/* A literal en dash: the hex escape swallows the space that follows it. */
.tristopic-case__body .wp-block-details[open] > summary::after {
	content: "[ - ]";
}

.tristopic-case__body .wp-block-details > :not(summary) {
	margin-block-start: var(--wp--preset--spacing--20);
}

.tristopic-case__body .wp-block-details > :last-child:not(summary) {
	margin-block-end: var(--wp--preset--spacing--20);
}

/* --------------------------------------------------------------- motion --- */

/* The sphere and its chrome live in assets/css/sphere.css, front page only. */

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
	}
}

/* ------------------------------------------------------------- colophon --- */

/*
 * The wordmark is used as a graphic here, not as a heading: oversized, dim, and
 * cropped to the page width. Under it, one hairline strip carrying the local
 * time, the copyright line, and when the work last changed.
 */
/*
 * The bar is chrome, not a block above the content, so <main> should not carry
 * the flow layout's block margin on top of its own padding. That pairing put
 * exactly 64px between the bar and the first line of every page.
 */
.wp-site-blocks > main {
	margin-block-start: 0;
}

/*
 * The frame is a grid with its own gap; the flow margin is added to it rather
 * than replacing it, which doubled the space under the standfirst.
 */
.tristopic-frame > * + * {
	margin-block-start: 0;
}

/*
 * Pinned and opaque, so the page passes behind the footer instead of showing
 * through it. On a scrolling page the containing block is the site wrapper and
 * bottom: 0 holds it for the whole scroll; on a framed page the wrapper is a
 * grid and the footer is already in the last row, so only the background does
 * anything.
 */
/*
 * The sticky element has to be the wrapper WordPress puts around a template
 * part, not the footer inside it: sticky moves within its containing block,
 * and that wrapper is exactly the footer's height, so the footer had nowhere
 * to travel. The wrapper is a direct child of .wp-site-blocks, which spans the
 * document.
 */
.wp-block-template-part:has(> .tristopic-footer),
.wp-site-blocks > .tristopic-footer {
	position: sticky;
	bottom: 0;
	z-index: 30;
}

.tristopic-footer {
	margin-top: var(--wp--preset--spacing--40);
	background: var(--wp--preset--color--ink);
}

/*
 * A pinned footer has to be thin. Five rem of ghost wordmark permanently
 * across the bottom of the window is not a footer.
 */
.tristopic-colophon__wordmark {
	display: none;
}

.tristopic-colophon__wordmark {
	margin: 0;
	font-size: clamp(1.75rem, 6vw, 5rem);
	line-height: 0.9;
	font-weight: 500;
	letter-spacing: -0.045em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-line);
	white-space: nowrap;
	overflow: hidden;
	user-select: none;
}

.tristopic-colophon__strip {
	display: grid;
	grid-template-columns: 1fr auto 1fr;
	align-items: baseline;
	gap: var(--wp--preset--spacing--20);
	padding-block: 0;
	margin-top: 0.6rem;
	border-top: var(--wp--custom--line);
}

.tristopic-colophon__strip p {
	margin: 0;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wide);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
}

.tristopic-colophon__time {
	justify-self: start;
	font-variant-numeric: tabular-nums;
}

.tristopic-colophon__copy { justify-self: center; }
.tristopic-colophon__updated { justify-self: end; }

/*
 * Ambient: a clock and a date are there to be glanced at, not read. The name
 * between them stays at the furniture level so the strip still has a middle.
 *
 * Qualified by the strip because the rule it overrides is `.strip p` — one
 * class and an element, which outranks a lone class however far down it sits.
 */
.tristopic-colophon__strip .tristopic-colophon__time,
.tristopic-colophon__strip .tristopic-colophon__updated {
	color: var(--wp--preset--color--paper-faint);
}

/*
 * The same, for the colophon: 292px of text needs no second row. The tracking
 * comes in with it — at the chrome's usual spacing the last item is 9px wider
 * than its share of the row and wraps inside its own cell.
 */
@media (max-width: 599px) {
	.tristopic-colophon__strip {
		column-gap: var(--wp--preset--spacing--10);
	}

	.tristopic-colophon__strip p {
		letter-spacing: 0.04em;
		white-space: nowrap;
	}
}

/* ----------------------------------------------------------- commissions --- */

/*
 * One line of small uppercase mono between two hairlines, running the full
 * measure rather than the column's, so it sets on one line instead of four.
 * The word-spacing lets the pipes read as separators rather than as characters
 * in the sentence.
 */
.tristopic-band,
.tristopic-commission__process {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wide);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
	max-width: none;
	margin-block: clamp(0.4rem, 1vh, 0.8rem);
	padding-block: clamp(0.4rem, 1.1vh, 0.75rem);
	border-block: var(--wp--custom--line);
	text-align: center;
	text-wrap: pretty;
	word-spacing: 0.15em;
}

.tristopic-commission__notice {
	margin: 0 0 var(--wp--preset--spacing--30);
	padding: 0.9em 1.1em;
	border-left: 2px solid var(--wp--preset--color--paper-dim);
	background: rgb(from var(--wp--preset--color--paper) r g b / 0.04);
	font-size: var(--wp--preset--font-size--small);
	max-width: 60ch;
}

/* A failed submission was announcing itself in the go-colour. */
.tristopic-commission__notice.is-bad {
	border-left-color: var(--wp--preset--color--danger);
}

/* ------------------------------------------------------------ field error --- */

/*
 * The row carries the state, not a box around the input: the rule under it
 * turns, the label and its number turn with it, and the message sits on the
 * next line in the same mono as every other piece of furniture.
 */
.tristopic-field__error {
	grid-column: 1 / -1;
	margin: 0.35rem 0 0;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--danger);
}

.tristopic-field.is-invalid {
	border-bottom-color: var(--wp--preset--color--danger);
}

/*
 * The asterisk carries the state, and the rule under the row turns with it so a
 * bad row can be found at a glance. The label and its number stay as they are:
 * with the asterisk already red beside them, recolouring three things per row
 * was saying the same thing three times.
 */
.tristopic-field.is-invalid .tristopic-field__star {
	color: var(--wp--preset--color--danger);
}

/* The attachment is not a sheet row; its message sits under the hint. */
.tristopic-commission__attach .tristopic-field__error {
	display: block;
	margin-top: 0.35rem;
}

/*
 * The sheet. Each field is one ruled row: index, label, control. The rule above
 * every row and below the last gives the block the same construction as the
 * project facts table, so the form reads as part of the system rather than as a
 * form dropped into it.
 */
.tristopic-commission__sheet {
	border-top: var(--wp--custom--line);
}

.tristopic-field {
	display: grid;
	grid-template-columns: 2.5rem minmax(6rem, 9rem) 1fr;
	align-items: baseline;
	gap: var(--wp--preset--spacing--20);
	/* No side padding: the control has to reach the rule drawn under it. */
	padding-block: 1.15rem;
	border-bottom: var(--wp--custom--line);
	transition: border-color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-field__index,
.tristopic-field__label {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-field__index {
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--ink-line);
	padding-left: 0.35rem;
}

.tristopic-field:focus-within .tristopic-field__index,
.tristopic-field:focus-within .tristopic-field__label {
	color: var(--wp--preset--color--paper);
}

.tristopic-field__star {
	color: var(--wp--preset--color--accent);
	margin-left: 0.35em;
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

/* No boxes and no rules of their own: the row is the field. */
.tristopic-field__control input,
.tristopic-field__control textarea,
.tristopic-field__control select {
	width: 100%;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	color: var(--wp--preset--color--paper);
	font-family: inherit;
	font-size: var(--wp--preset--font-size--body);
	line-height: 1.5;
}

.tristopic-field__control textarea {
	resize: vertical;
	/*
	 * No min-height: it beat the height clamp that scales the brief with the
	 * viewport, so the clamp was doing nothing and the row was always 6em.
	 */
	min-height: 0;
}

.tristopic-field__control select {
	appearance: none;
	cursor: pointer;
	/* A caret drawn in CSS, so no image request and it follows the text colour. */
	background-image:
		linear-gradient(45deg, transparent 50%, currentColor 50%),
		linear-gradient(135deg, currentColor 50%, transparent 50%);
	background-position: right 0.7rem center, right 0.35rem center;
	background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
	background-repeat: no-repeat;
	padding-right: 1.6rem;
}

.tristopic-field__control select option {
	background: var(--wp--preset--color--ink-raised);
	color: var(--wp--preset--color--paper);
}

/*
 * The selected row, in the platform's own popup. This is the only part of a
 * native picker that takes a colour, and only in some engines — the row under
 * the cursor stays the system highlight. Browsers that support a customizable
 * picker get the whole thing instead, below.
 */
.tristopic-field__control select option:checked {
	background: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--ink);
}

@supports (appearance: base-select) {
	.tristopic-field__control select,
	.tristopic-field__control select::picker(select) {
		appearance: base-select;
	}

	/* base-select brings its own button styling back; undo it. */
	.tristopic-field__control select {
		border: 0;
		border-radius: 0;
		padding: 0 1.6rem 0 0;
		background: transparent;
		color: var(--wp--preset--color--paper);
		font: inherit;
		font-size: var(--wp--preset--font-size--body);
		text-align: left;
	}

	/* Our CSS caret is replaced by the picker's own icon. */
	.tristopic-field__control select {
		background-image: none;
	}

	.tristopic-field__control select::picker-icon {
		color: var(--wp--preset--color--paper-dim);
		transition: transform var(--wp--custom--duration--fast) var(--wp--custom--ease);
	}

	.tristopic-field__control select:open::picker-icon {
		transform: rotate(180deg);
	}

	/* The panel: the same hairline-on-ink construction as the rest of the site. */
	.tristopic-field__control select::picker(select) {
		/*
		 * Below the field, and touching it. The explicit max-height is what
		 * makes that stick: the UA default is `stretch`, which sizes the panel
		 * to whatever space a direction offers, so the fit test compared two
		 * stretched heights instead of the panel's own and picked whichever
		 * side was roomier — upward, even where downward fitted.
		 */
		position-area: end span-end;
		position-try-fallbacks: flip-block;
		/*
		 * 18rem, not more: with the flip removed the panel cannot be allowed to
		 * grow past the room under the field, and 287px is what the tightest
		 * framed size leaves. Today's seven rows come to 282px and do not scroll;
		 * an eighth service would scroll inside the panel rather than hang off
		 * the bottom of the window.
		 */
		max-height: 18rem;

		margin-block-start: 0;
		padding: 0;
		border: var(--wp--custom--line);
		border-radius: 0;
		background: var(--wp--preset--color--ink-raised);
	}

	.tristopic-field__control select option {
		padding: 0.4em 1em;
		background: transparent;
		color: var(--wp--preset--color--paper);
		font-size: var(--wp--preset--font-size--body);
		cursor: pointer;
	}

	.tristopic-field__control select option + option {
		border-top: var(--wp--custom--line);
	}

	/* Hover, keyboard focus and the current value all read the same. */
	.tristopic-field__control select option:hover,
	.tristopic-field__control select option:focus,
	.tristopic-field__control select option:checked {
		background: var(--wp--preset--color--accent);
		color: var(--wp--preset--color--ink);
	}

	/*
	 * The tick belongs to the option, not the select — hung on the select it
	 * matches nothing and the mark stays. It would say a second time what the
	 * accent already says, so it goes.
	 */
	.tristopic-field__control select option::checkmark {
		display: none;
	}
}

/*
 * Downwards, and no flip — but only where the room is proven. The framed pages
 * are measured: the tightest, 1366x768, leaves 287px under the field for a
 * 282px panel. Everywhere else (phones, short windows, the unframed layout)
 * the browser keeps its fallback and may still flip, which is correct when it
 * cannot be shown to fit.
 *
 * This has to come after the block above, not inside the frame's media query
 * earlier in the file: same specificity, later wins.
 */
@supports (appearance: base-select) {
	@media (min-width: 1100px) and (min-height: 768px) {
		.tristopic-field__control select::picker(select) {
			position-area: end span-end;
			position-try-fallbacks: none;
		}
	}
}

.tristopic-field__control input:focus,
.tristopic-field__control textarea:focus,
.tristopic-field__control select:focus {
	outline: none;
}

/* File input: the browser's own button is unstylable across engines, so the
   control is reduced to plain text and the row carries the affordance. */
.tristopic-field__control input[type="file"] {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wide);
	color: var(--wp--preset--color--paper-dim);
	cursor: pointer;
}

.tristopic-field__control input[type="file"]::file-selector-button {
	margin-right: 1em;
	padding: 0.5em 1.1em;
	border: var(--wp--custom--line);
	border-radius: 999px;
	background: transparent;
	color: var(--wp--preset--color--paper);
	font: inherit;
	text-transform: uppercase;
	letter-spacing: var(--wp--custom--tracking--wider);
	cursor: pointer;
	transition: background var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-field__control input[type="file"]::file-selector-button:hover {
	background: rgb(from var(--wp--preset--color--paper) r g b / 0.1);
}

.tristopic-field__hint {
	display: block;
	margin-top: 0.5em;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wide);
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-line);
}

.tristopic-field__control input::placeholder,
.tristopic-field__control textarea::placeholder {
	color: var(--wp--preset--color--ink-line);
}

/* ----------------------------------------------------------------- sheet --- */

/*
 * One column, label above the field. Top-aligned labels are read in a single
 * fixation, where a label beside its field forces the eye to zigzag; the
 * gutter that arrangement needs was also what stopped the form fitting.
 */
.tristopic-commission__sheet .tristopic-field {
	grid-template-columns: auto minmax(0, 1fr);
	column-gap: 0.75rem;
	row-gap: 0.1rem;
	padding-block: clamp(0.45rem, 1.1vh, 1rem);
}

.tristopic-commission__sheet .tristopic-field__index {
	grid-column: 1;
	grid-row: 1;
	padding-left: 0;
}

.tristopic-commission__sheet .tristopic-field__label {
	grid-column: 2;
	grid-row: 1;
	justify-self: start;
}

/* The field takes the whole row, under its own label. */
.tristopic-commission__sheet .tristopic-field__control {
	grid-column: 1 / -1;
	grid-row: 2;
}

.tristopic-commission__sheet .tristopic-field__error {
	grid-column: 1 / -1;
	grid-row: 3;
}

.tristopic-commission__sheet textarea {
	height: clamp(3.75rem, 9.5vh, 6rem);
}

/*
 * The rule under the focused row is the only moving part: it draws in from
 * the left in the accent, so the eye is told where it is without anything
 * jumping.
 */
.tristopic-commission__sheet .tristopic-field {
	position: relative;
}

.tristopic-commission__sheet .tristopic-field::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	bottom: -1px;
	height: 1px;
	background: var(--wp--preset--color--accent);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--wp--custom--duration--base) var(--wp--custom--ease);
}

.tristopic-commission__sheet .tristopic-field:focus-within::after {
	transform: scaleX(1);
}

@media (prefers-reduced-motion: reduce) {
	.tristopic-commission__sheet .tristopic-field::after {
		transition: none;
	}
}

/* A filled, valid field marks its own number. Progress without a progress bar. */
.tristopic-commission__sheet .tristopic-field:has(:valid:not(:placeholder-shown)) .tristopic-field__index,
.tristopic-commission__sheet .tristopic-field:has(select:valid) .tristopic-field__index {
	color: var(--wp--preset--color--accent);
}

/*
 * An unchosen select still shows real text, so beside a placeholder input it
 * reads as if it were already answered. Required + empty is :invalid, which
 * is exactly the state to dim.
 */
.tristopic-field select:invalid {
	color: var(--wp--preset--color--paper-dim);
}

/* ---- actions ---- */

.tristopic-commission__actions {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--20);
	flex-wrap: wrap;
	margin-top: clamp(0.75rem, 2vh, 2rem);
}

/* The optional attachment, kept quiet beside the button. */
.tristopic-commission__attach {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	column-gap: 0.75rem;
	row-gap: 0.25rem;
	margin: 0;
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
}

/*
 * The hint rides on the same line as the control. Given a row of its own it
 * turned a one-line optional field into three.
 */
.tristopic-commission__hint {
	color: var(--wp--preset--color--ink-line);
}

/* What happens to the details: one quiet line under the actions (privacy, 11 Nov). */
.tristopic-commission__send {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--20);
	margin-left: auto;
}

.tristopic-commission__attach label {
	cursor: pointer;
	color: var(--wp--preset--color--paper);
	border-bottom: 1px solid var(--wp--preset--color--ink-line);
	padding-bottom: 0.15em;
	transition: border-color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-commission__attach:hover label,
.tristopic-commission__attach:focus-within label {
	border-bottom-color: var(--wp--preset--color--accent);
}

.tristopic-commission__attach input[type="file"] {
	width: 9rem;
	font: inherit;
	color: var(--wp--preset--color--paper-dim);
	cursor: pointer;
}

.tristopic-commission__attach input[type="file"]::file-selector-button {
	display: none;
}


/* The one solid element on the page, and the arrow moves on hover. */
.tristopic-commission__submit {
	display: inline-flex;
	align-items: center;
	gap: 0.9em;
	padding: 0.8em 1.4em;
	border: 0;
	border-radius: 999px;
	background: var(--wp--preset--color--paper);
	color: var(--wp--preset--color--ink);
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	cursor: pointer;
	transition: background var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-commission__submit:hover {
	background: var(--wp--preset--color--paper-dim);
}

.tristopic-commission__submit svg {
	width: 1rem;
	height: 1rem;
	transition: transform var(--wp--custom--duration--base) var(--wp--custom--ease);
}

.tristopic-commission__submit:hover svg {
	transform: translateX(0.25rem);
}

/* The honeypot: reachable by a bot, never seen or focused by a person. */
.tristopic-commission__trap {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

@media (max-width: 599px) {
	/* Index and label share a line, the control takes the next. */
	.tristopic-field {
		grid-template-columns: 2.25rem 1fr;
		row-gap: 0.5rem;
		padding-right: 0;
	}

	.tristopic-field__control {
		grid-column: 1 / -1;
	}

	.tristopic-commission__submit {
		width: 100%;
		justify-content: center;
	}
}

/* ------------------------------------------------------- phone proportions --- */

/*
 * Measured at 375px, the whole page was still on the desktop's rhythm: 32px of
 * side padding, 64px above and below the main column, a 64px gap between the
 * masthead and the body, and 64px above the footer. Those numbers are generous
 * because a desktop has the width to carry them; a phone does not.
 *
 * Two classes deep so these beat WordPress's own global-padding rule, which is
 * a single class and may be printed after this file.
 */
@media (max-width: 599px) {
	.tristopic-frame.has-global-padding,
	.tristopic-project.has-global-padding,
	.tristopic-footer.has-global-padding {
		padding-inline: 0.625rem;
	}

	/* The first line starts just under the bar rather than 64px below it. */
	.tristopic-frame {
		padding-block: 0.75rem var(--wp--preset--spacing--30);
		gap: var(--wp--preset--spacing--20);
	}

	.tristopic-project {
		padding-block: 0.75rem var(--wp--preset--spacing--30);
	}

	.tristopic-footer {
		margin-top: var(--wp--preset--spacing--30);
	}
}

/* -------------------------------------------- About + Let's talk polish --- */
/* Owner, 12-13 Nov: "polish as much as you can the about page and the contact page";
   bars between band items "as it was", with equal gaps; the About button in the
   fluo green with "See the work" as a plain link under it; text more compact. */

/*
 * Bands as items (inc/chrome.php tristopic_band_inner). Each item stays whole, so
 * a band wraps between items, never inside one. The bar between two items is the
 * mono pipe the band was typed with, drawn in the middle of the gap so the space
 * either side of it is the same; it is positioned rather than set in the line, so
 * it never changes where a line breaks. chrome.js hides the bar on an item that
 * starts a new line, so no line opens with one. The lead-in ("Includes:") is set a
 * step brighter than the items it introduces.
 */
.tristopic-band:has(.tristopic-band__item),
.tristopic-commission__process:has(.tristopic-band__item) {
	--band-gap: 1.15em;
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: baseline;
	column-gap: var(--band-gap);
	row-gap: 0.2em;
	word-spacing: normal;
}

.tristopic-band__label {
	color: var(--wp--preset--color--paper);
}

.tristopic-band__item {
	position: relative;
	white-space: nowrap;
}

.tristopic-band__item + .tristopic-band__item::before {
	content: "|";
	position: absolute;
	left: calc(var(--band-gap) / -2);
	transform: translateX(-50%);
	color: var(--wp--preset--color--paper-faint);
}

.tristopic-band__item.is-row-start::before {
	content: none;
}

/* The narrowest phones: a long item may break inside itself rather than overflow. */
@media (max-width: 359px) {
	.tristopic-band__item {
		white-space: normal;
	}
}

/*
 * A label run into its text (inc/chrome.php tristopic_runin_labels): "PRACTICE |"
 * opens the paragraph it names instead of taking a line of its own above it. Same
 * mono, same bar as the bands; two lines saved per section.
 */
.tristopic-runin {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	color: var(--wp--preset--color--paper-dim);
	white-space: nowrap;
}

.tristopic-runin::after {
	content: "|";
	margin-inline: 0.75em 0.35em;
	color: var(--wp--preset--color--paper-faint);
}

/*
 * The attachment, once chrome.js has run: the native control is hidden (it
 * stays focusable, and its label lights up when it has focus), and the chosen
 * file is shown by name in the same type as the label beside it.
 */
.tristopic-commission__attach {
	position: relative;
}

.tristopic-commission__form.is-enhanced .tristopic-commission__attach input[type="file"] {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.tristopic-commission__file {
	max-width: min(18rem, 60vw);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	/* File names keep their own case: report-v2.PDF is not REPORT-V2.PDF. */
	text-transform: none;
	letter-spacing: 0.02em;
	color: var(--wp--preset--color--paper);
}

.tristopic-commission__clear {
	appearance: none;
	margin: 0 0 0 -0.35rem;
	padding: 0.1rem 0.4rem;
	border: 0;
	background: none;
	color: var(--wp--preset--color--paper-dim);
	font: inherit;
	font-size: 1rem;
	line-height: 1;
	cursor: pointer;
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-commission__clear:hover,
.tristopic-commission__clear:focus-visible {
	color: var(--wp--preset--color--paper);
}

/*
 * "Send request" in the fluo green (owner, 13 Nov), the same as About's "Start a
 * project": the two ends of one trip, one colour. White under the cursor, the
 * arrow still moves. 44px tall for a thumb.
 */
.tristopic-commission__submit {
	min-height: 2.75rem;
	background: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--ink);
}

.tristopic-commission__submit:hover,
.tristopic-commission__submit:focus-visible {
	background: var(--wp--preset--color--paper);
	color: var(--wp--preset--color--ink);
}

/*
 * The page's way on (About). "Start a project" is the one solid element, in the
 * fluo green like "Send request" at the other end of the trip; "See the work"
 * sits under it as a plain link. No arrows, no underlines (owner, 13 Nov).
 */
.tristopic-cta {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.85rem;
}

.tristopic-cta a {
	font-family: var(--wp--preset--font-family--mono);
	font-size: var(--wp--preset--font-size--micro);
	letter-spacing: var(--wp--custom--tracking--wider);
	text-transform: uppercase;
	text-decoration: none;
}

.tristopic-cta a:first-child {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.9em;
	min-height: 2.75rem;
	padding: 0.8em 1.4em;
	border-radius: 999px;
	background: var(--wp--preset--color--accent);
	color: var(--wp--preset--color--ink);
	transition: background var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-cta a:first-child:hover,
.tristopic-cta a:first-child:focus-visible {
	background: var(--wp--preset--color--paper);
	color: var(--wp--preset--color--ink);
}

.tristopic-cta a + a {
	padding-block: 0.35em;
	color: var(--wp--preset--color--paper);
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-cta a + a:hover,
.tristopic-cta a + a:focus-visible {
	color: var(--wp--preset--color--accent);
}

/* "Attach a file" without its underline (owner, 13 Nov): it lights up instead. */
.tristopic-commission__attach label {
	border-bottom: 0;
	padding-bottom: 0;
	transition: color var(--wp--custom--duration--fast) var(--wp--custom--ease);
}

.tristopic-commission__attach:hover label,
.tristopic-commission__attach:focus-within label {
	color: var(--wp--preset--color--accent);
}
