/* =========================================================================
   Visage Beauty — Vancouver
   One stylesheet. Every colour, size, spacing, radius, easing and duration
   comes from theme.json; nothing is hardcoded.
   ========================================================================= */

/* -------------------------------------------------------------- 1. Ground */

:root {
	--v-gutter: var(--wp--preset--spacing--md);
	--v-rule: var(--wp--custom--rule--hairline) solid color-mix(in srgb, var(--wp--preset--color--taupe) 42%, transparent);
	--v-rule-dark: var(--wp--custom--rule--hairline) solid color-mix(in srgb, var(--wp--preset--color--cream) 16%, transparent);
	--v-focus: color-mix(in srgb, var(--wp--preset--color--clay) 100%, transparent);
}

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

html {
	-webkit-text-size-adjust: 100%;
	scroll-behavior: smooth;
}

body {
	margin: 0;
	overflow-x: clip;
	font-synthesis-weight: none;
	text-rendering: optimizeLegibility;
	-webkit-font-smoothing: antialiased;
}

/* Core inserts blockGap between top-level siblings, which shows up as a cream
   sliver between two full-bleed planes. Reset at the top level only; blockGap
   still applies inside content groups. */
.wp-site-blocks > * {
	margin-block-start: 0;
	margin-block-end: 0;
}

.wp-site-blocks > main > *,
.visage-sections > * {
	margin-block-start: 0;
	margin-block-end: 0;
}

img,
svg,
video {
	max-width: 100%;
	height: auto;
}

figure {
	margin: 0;
}

:where(a):focus-visible,
:where(button):focus-visible,
:where(summary):focus-visible,
:where(input, textarea, select):focus-visible {
	outline: 2px solid var(--v-focus);
	outline-offset: 3px;
	border-radius: var(--wp--custom--radius--image);
}

::selection {
	background: var(--wp--preset--color--blush);
	color: var(--wp--preset--color--charcoal);
}

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

.visage-skip {
	position: absolute;
	left: var(--wp--preset--spacing--sm);
	top: calc(var(--wp--preset--spacing--sm) * -8);
	z-index: var(--wp--custom--layer--panel);
	padding: var(--wp--preset--spacing--xs) var(--wp--preset--spacing--sm);
	background: var(--wp--preset--color--blush);
	color: var(--wp--preset--color--charcoal);
	border-radius: var(--wp--custom--radius--button);
	font-size: var(--wp--preset--font-size--sm);
	text-decoration: none;
	transition: top var(--wp--custom--duration--panel) var(--wp--custom--ease--out);
}

.visage-skip:focus {
	top: var(--wp--preset--spacing--sm);
}

/* Sections are their own full-bleed planes, so they sit outside any
   constrained-layout parent and never receive root padding. The wide container
   is defined here instead, from the same tokens theme.json publishes. */
.visage-header__inner,
.visage-hero__inner,
.wp-site-blocks section > .wp-block-group.alignwide,
.wp-site-blocks section > .wp-block-group > .wp-block-group.alignwide,
.visage-footer > .wp-block-group.alignwide {
	box-sizing: border-box;
	width: 100%;
	max-width: calc(var(--wp--style--global--wide-size) + (var(--v-gutter) * 2));
	margin-inline: auto;
	padding-inline: var(--v-gutter);
}

/* Constrained content inside a plane (page and post bodies). */
.wp-site-blocks section.is-layout-constrained > *,
.visage-treatment__body > .wp-block-post-content > *,
.visage-treatment__body > * {
	box-sizing: border-box;
	width: 100%;
	max-width: calc(var(--wp--style--global--content-size) + (var(--v-gutter) * 2));
	margin-inline: auto;
	padding-inline: var(--v-gutter);
}

/* The row's own image is data for the floating preview, never layout. */
.visage-index__src {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	border: 0;
}

/* -------------------------------------------------- 2. Typography helpers */

h1, h2, h3, h4 {
	text-wrap: balance;
}

p {
	text-wrap: pretty;
}

.is-style-visage-lede {
	font-size: var(--wp--preset--font-size--md);
	line-height: var(--wp--custom--leading--body);
	max-width: var(--wp--custom--measure--prose);
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 88%, var(--wp--preset--color--cream));
}

.is-style-visage-wayfinding {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	line-height: var(--wp--custom--leading--tight);
}

/* The one deliberate label form. Used on the four chapter openings and nowhere
   else, so it stays voice rather than turning into scaffolding. */
.is-style-visage-eyebrow {
	font-family: var(--wp--preset--font-family--body) !important;
	font-size: var(--wp--preset--font-size--xs) !important;
	font-style: normal;
	font-weight: 600 !important;
	letter-spacing: var(--wp--custom--tracking--wayfinding) !important;
	text-transform: uppercase;
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 58%, var(--wp--preset--color--cream));
}

.is-style-visage-fact {
	color: var(--wp--preset--color--sage);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--label);
}

.is-style-visage-facts {
	list-style: none;
	padding-left: 0 !important;
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--xs) var(--wp--preset--spacing--sm);
	margin: 0;
}

.is-style-visage-facts li {
	display: inline-flex;
	align-items: center;
	gap: var(--wp--preset--spacing--xs);
	padding: var(--wp--preset--spacing--xxs) 0;
	color: var(--wp--preset--color--sage);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
}

/* No connector glyph: a rule or a dot between items dangles at the start of a
   wrapped line. Space does the separating. */
.is-style-visage-facts {
	gap: var(--wp--preset--spacing--xs) var(--wp--preset--spacing--lg);
}

.is-style-visage-hairline {
	border: 0;
	border-top: var(--v-rule);
	opacity: 1;
	width: 100%;
	max-width: none;
}

/* ------------------------------------------------------------- 3. Buttons */

.wp-block-button__link {
	display: inline-flex;
	align-items: center;
	gap: var(--wp--preset--spacing--xs);
	text-decoration: none;
	text-transform: uppercase;
	transition:
		background-color var(--wp--custom--duration--hover) var(--wp--custom--ease--out),
		color var(--wp--custom--duration--hover) var(--wp--custom--ease--out),
		transform var(--wp--custom--duration--press) var(--wp--custom--ease--out);
}

.wp-block-button__link:active {
	transform: scale(0.97);
}

.is-style-visage-secondary .wp-block-button__link {
	background-color: var(--wp--preset--color--sand);
	color: var(--wp--preset--color--charcoal);
}

.is-style-visage-secondary .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--taupe);
}

.is-style-visage-on-dark .wp-block-button__link {
	background-color: var(--wp--preset--color--cream);
	color: var(--wp--preset--color--black);
}

.is-style-visage-on-dark .wp-block-button__link:hover {
	background-color: var(--wp--preset--color--blush);
}

.is-style-visage-quiet .wp-block-button__link {
	background: none;
	color: inherit;
	padding-inline: 0;
	border-radius: 0;
	border-bottom: var(--wp--custom--rule--hairline) solid currentColor;
	padding-block: var(--wp--preset--spacing--xxs);
}

.is-style-visage-quiet .wp-block-button__link:hover {
	background: none;
	color: var(--wp--preset--color--clay);
}

/* Raw `wp-block-buttons` markup inside a PHP pattern never receives core's
   `is-layout-flex` class, so the buttons stacked. Own the layout here. */
.wp-block-buttons.is-style-visage-actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--sm) var(--wp--preset--spacing--md);
	align-items: center;
}

.wp-block-buttons.is-style-visage-actions > .wp-block-button {
	margin: 0;
}

/* --------------------------------------------------------------- 4. Planes */

.is-style-visage-plane-cream,
.is-style-visage-plane-sand,
.is-style-visage-plane-blush,
.is-style-visage-plane-black {
	padding-block: var(--wp--preset--spacing--xxl);
	position: relative;
}

.is-style-visage-plane-cream { background-color: var(--wp--preset--color--cream); }
.is-style-visage-plane-sand { background-color: var(--wp--preset--color--sand); }
.is-style-visage-plane-blush { background-color: var(--wp--preset--color--blush); }

.is-style-visage-plane-black {
	background-color: var(--wp--preset--color--black);
	color: var(--wp--preset--color--cream);
}

.is-style-visage-plane-black :is(h1, h2, h3, h4, h5, h6) {
	color: var(--wp--preset--color--cream);
}

.is-style-visage-plane-black .is-style-visage-eyebrow {
	color: var(--wp--preset--color--blush);
}

.is-style-visage-plane-black a:not(.wp-block-button__link) {
	color: var(--wp--preset--color--blush);
}

.is-style-visage-card {
	border-top: var(--v-rule);
	padding-top: var(--wp--preset--spacing--sm);
}

.is-style-visage-card :is(h1, h2, h3, h4, h5, h6) {
	color: var(--wp--preset--color--black);
}

/* --------------------------------------------------------------- 5. Header */

/* Position and stacking only. The material — ground, ink and the scroll edge —
   is set in section 54, which reads the plane the bar is standing on. Keeping
   a background here would outrank that, because `[data-scrolled]` is one
   attribute heavier than the element rule the custom properties resolve on. */
.visage-header {
	position: sticky;
	top: 0;
	z-index: var(--wp--custom--layer--header);
	border-bottom: var(--wp--custom--rule--hairline) solid transparent;
}

.visage-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--md);
	padding-block: var(--wp--preset--spacing--sm);
	transition: padding-block var(--wp--custom--duration--panel) var(--wp--custom--ease--out);
}

.visage-header__brand {
	display: inline-flex;
	align-items: center;
	text-decoration: none;
	flex: 0 0 auto;
	color: var(--v-header-ink, var(--wp--preset--color--charcoal));
}

/* The wordmark is delivered as a matte — alpha only, no ground — and painted
   with the bar's own ink, so it changes colour with the bar instead of
   carrying a blush rectangle across a dark hero. `background-color` is what
   the mask reveals; the PNG supplies nothing but coverage. */
.visage-header__mark {
	display: block;
	height: 44px;
	aspect-ratio: 1073 / 577;
	background-color: currentColor;
	-webkit-mask: url('../img/logo-ink.png') center / contain no-repeat;
	mask: url('../img/logo-ink.png') center / contain no-repeat;
}

.visage-nav {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--md);
}

.visage-nav a {
	position: relative;
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	text-decoration: none;
	color: var(--wp--preset--color--charcoal);
	padding-block: var(--wp--preset--spacing--xxs);
	transition: color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

/* Hover paints; it never moves anything. */
.visage-nav a::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: var(--wp--custom--rule--hairline);
	background: currentColor;
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

@media (hover: hover) and (pointer: fine) {
	.visage-nav a:hover::after,
	.visage-nav a:focus-visible::after {
		transform: scaleX(1);
	}

	.visage-nav a:hover {
		color: var(--wp--preset--color--clay);
	}
}

.visage-nav a[aria-current='page']::after {
	transform: scaleX(1);
	background: var(--wp--preset--color--blush);
}

.visage-header__toggle {
	display: none;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: none;
	color: var(--wp--preset--color--charcoal);
	cursor: pointer;
	transition: transform var(--wp--custom--duration--press) var(--wp--custom--ease--out);
}

.visage-header__toggle:active {
	transform: scale(0.94);
}

/* The two glyphs and the two labels are one button; `aria-expanded` picks. */
.visage-header__toggle-glyph--close,
.visage-header__toggle-label--close {
	display: none;
}

.visage-header__toggle[aria-expanded='true'] .visage-header__toggle-glyph--open,
.visage-header__toggle[aria-expanded='true'] .visage-header__toggle-label--open {
	display: none;
}

.visage-header__toggle[aria-expanded='true'] .visage-header__toggle-glyph--close {
	display: block;
}

.visage-header__toggle[aria-expanded='true'] .visage-header__toggle-label--close {
	display: inline;
}

@media (max-width: 60rem) {
	.visage-header__toggle {
		display: inline-flex;
	}

	/* The panel is positioned against the viewport by hand — `top`/`left` plus
	   `width`/`height` — rather than with `inset: 0`, and that is deliberate.
	   The bar carries `backdrop-filter` and a `transform`, and either one makes
	   it the containing block for its own fixed children: `inset: 0` resolved
	   against the 85px bar instead of the screen, so the panel opened *inside*
	   the header, the wordmark and the control vanished under it, and the links
	   spilled out over the hero. The bar is full-width and sits at the top of
	   the viewport, so its padding box origin is the viewport origin either
	   way; sizing in viewport units makes the panel correct whether the bar is
	   a containing block or not. */
	.visage-nav {
		position: fixed;
		top: 0;
		left: 0;
		width: 100%;
		height: 100dvh;
		flex-direction: column;
		align-items: stretch;
		justify-content: flex-start;
		gap: 0;
		padding-block: calc(var(--v-header-height, 5rem) + var(--wp--preset--spacing--lg))
			calc(var(--wp--preset--spacing--xl) + env(safe-area-inset-bottom, 0px));
		padding-inline: var(--wp--preset--spacing--md);
		background: var(--wp--preset--color--cream);
		z-index: var(--wp--custom--layer--overlay);

		/* A menu longer than the screen scrolls itself, and the page under it
		   does not take over when it reaches the end. */
		overflow-y: auto;
		overscroll-behavior: contain;
		-webkit-overflow-scrolling: touch;

		opacity: 0;
		visibility: hidden;
		transform: translateY(calc(var(--wp--preset--spacing--sm) * -1));
		transition:
			opacity var(--wp--custom--duration--panel) var(--wp--custom--ease--out),
			transform var(--wp--custom--duration--panel) var(--wp--custom--ease--out),
			visibility var(--wp--custom--duration--panel);
	}

	.visage-nav[data-open='true'] {
		opacity: 1;
		visibility: visible;
		transform: none;
	}

	/* The wordmark and the control stay on top of the panel they opened: same
	   stacking context as the panel, one step above it. The reader can always
	   see where they are and how to get out. */
	.visage-header__brand,
	.visage-header__toggle {
		position: relative;
		z-index: calc(var(--wp--custom--layer--overlay) + 1);
	}

	.visage-nav a {
		display: block;
		padding-block: var(--wp--preset--spacing--sm);
		border-bottom: var(--wp--custom--rule--hairline) solid
			color-mix(in srgb, var(--wp--preset--color--taupe) 55%, transparent);
		font-family: var(--wp--preset--font-family--display);
		font-size: var(--wp--preset--font-size--xl);
		font-weight: 300;
		letter-spacing: var(--wp--custom--tracking--display);
		text-transform: none;
	}

	/* The rule under each row is the divider; the hover underline would draw a
	   second one on top of it. */
	.visage-nav a::after {
		display: none;
	}

	.visage-nav a.visage-header__book {
		margin-inline-start: 0;
		margin-block-start: var(--wp--preset--spacing--lg);
		border-bottom: 0;
		padding: 1.125rem 2.25rem;
		text-align: center;
		font-family: var(--wp--preset--font-family--body);
		font-size: var(--wp--preset--font-size--xs);
		font-weight: 600;
		letter-spacing: var(--wp--custom--tracking--wayfinding);
		text-transform: uppercase;
	}
}

/* The page behind the panel holds still. */
html.is-menu-open,
html.is-menu-open body {
	overflow: hidden;
}

/* ----------------------------------------------------------------- 6. Hero */

.visage-hero {
	position: relative;
	min-height: min(88svh, 60rem);
	display: grid;
	align-items: end;
	overflow: clip;
	background-color: var(--wp--preset--color--sand);
}

.visage-hero__media {
	position: absolute;
	inset: 0;
	overflow: clip;
}

.visage-hero__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 70% 50%;
	will-change: transform;
}

/* Warm scrim, weighted left, so the headline sits on the negative space the
   image was generated to leave. */
.visage-hero__media::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(
		100deg,
		color-mix(in srgb, var(--wp--preset--color--cream) 92%, transparent) 0%,
		color-mix(in srgb, var(--wp--preset--color--cream) 74%, transparent) 34%,
		color-mix(in srgb, var(--wp--preset--color--cream) 12%, transparent) 66%,
		transparent 100%
	);
}

.visage-hero__inner {
	position: relative;
	z-index: 1;
	padding-block: var(--wp--preset--spacing--xxl);
	display: grid;
	gap: var(--wp--preset--spacing--md);
	max-width: 32ch;
}

.visage-hero__title {
	margin: 0;
	font-size: var(--wp--preset--font-size--display);
	line-height: var(--wp--custom--leading--display);
	letter-spacing: var(--wp--custom--tracking--display);
	font-weight: 300;
}

.visage-hero__title em {
	font-style: italic;
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-hero__sub {
	margin: 0;
	max-width: var(--wp--custom--measure--narrow);
	font-size: var(--wp--preset--font-size--md);
	line-height: var(--wp--custom--leading--body);
}

.visage-hero__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--sm) var(--wp--preset--spacing--md);
	margin-top: var(--wp--preset--spacing--xs);
}

.visage-hero__scroll {
	position: absolute;
	right: var(--v-gutter);
	bottom: var(--wp--preset--spacing--md);
	display: inline-flex;
	align-items: center;
	gap: var(--wp--preset--spacing--xs);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	color: var(--wp--preset--color--cocoa-deep);
	text-decoration: none;
}

.visage-hero__scroll span {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.5rem;
	height: 2.75rem;
	color: var(--wp--preset--color--cocoa-deep);
}

@media (max-width: 48rem) {
	.visage-hero__scroll {
		display: none;
	}

	.visage-hero__media img {
		object-position: 62% 50%;
	}
}

/* -------------------------------------------------------------- 7. Marquee */

.visage-marquee {
	--v-marquee-shift: -50%;
	--v-marquee-ground: var(--wp--preset--color--white);
	position: relative;
	overflow: clip;
	padding-block: var(--wp--preset--spacing--md);
	background: var(--v-marquee-ground);
	border-block: var(--v-rule);
	user-select: none;
}

.visage-marquee__track {
	display: flex;
	width: max-content;
	gap: var(--wp--preset--spacing--lg);
	align-items: baseline;
	animation: visage-marquee var(--wp--custom--duration--marquee) linear infinite;
}

.visage-marquee__item {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xl);
	font-weight: 300;
	line-height: 1;
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 70%, var(--wp--preset--color--cream));
	white-space: nowrap;
	display: inline-flex;
	align-items: center;
	gap: var(--wp--preset--spacing--lg);
}

.visage-marquee__item::after {
	content: '';
	width: 7px;
	height: 7px;
	border-radius: var(--wp--custom--radius--pill);
	background: var(--wp--preset--color--blush);
	flex: 0 0 auto;
}

@keyframes visage-marquee {
	to {
		transform: translate3d(var(--v-marquee-shift), 0, 0);
	}
}

/* ---------------------------------------------------------- 8. Editorial */

.visage-editorial {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--wp--preset--spacing--lg);
	align-items: start;
}

@media (min-width: 56rem) {
	.visage-editorial {
		grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
		gap: var(--wp--preset--spacing--xl);
	}

	.visage-editorial--reverse > :first-child {
		order: 2;
	}
}

.visage-editorial__title {
	margin: 0 0 var(--wp--preset--spacing--sm);
	font-size: var(--wp--preset--font-size--xxl);
}

.visage-editorial__body > * + * {
	margin-top: var(--wp--preset--spacing--sm);
}

.visage-editorial__body p {
	max-width: var(--wp--custom--measure--prose);
	line-height: var(--wp--custom--leading--body);
}

.visage-figure {
	position: relative;
	overflow: clip;
	border-radius: var(--wp--custom--radius--image);
	background: var(--wp--preset--color--sand);
}

.visage-figure img {
	display: block;
	width: 100%;
	height: auto;
}

.visage-figure figcaption {
	margin-top: var(--wp--preset--spacing--xs);
	font-size: var(--wp--preset--font-size--xs);
	color: var(--wp--preset--color--cocoa-deep);
}

/* ------------------------------------------------- 9. Signature narrative */

.visage-signature__grid {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	align-items: start;
}

@media (min-width: 56rem) {
	.visage-signature__grid {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
		gap: var(--wp--preset--spacing--xl);
	}

	.visage-signature__media {
		position: sticky;
		top: calc(var(--wp--preset--spacing--xl));
	}
}

.visage-signature__media img {
	border-radius: var(--wp--custom--radius--image);
}

.visage-signature__steps {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
}

.visage-step {
	display: grid;
	gap: var(--wp--preset--spacing--xs);
	padding-bottom: var(--wp--preset--spacing--lg);
	border-bottom: var(--v-rule-dark);
}

.visage-step:last-child {
	border-bottom: 0;
	padding-bottom: 0;
}

.visage-step .visage-step__n {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--lg);
	color: var(--wp--preset--color--blush);
	line-height: 1;
}

.visage-step__t {
	margin: 0;
	font-size: var(--wp--preset--font-size--xl);
}

.visage-step p {
	margin: 0;
	max-width: var(--wp--custom--measure--prose);
	line-height: var(--wp--custom--leading--body);
	color: color-mix(in srgb, var(--wp--preset--color--cream) 82%, var(--wp--preset--color--black));
}

/* ---------------------------------------------- 10. Treatment index + hover */

.visage-index {
	position: relative;
}

.visage-index-head + .visage-index {
	padding-top: 0;
}

.visage-index-head {
	padding-bottom: 0;
}

.visage-index__list {
	list-style: none;
	margin: 0;
	padding: 0;
	border-top: var(--v-rule);
	display: block;
}

.visage-index__list > li {
	position: relative;
	border-bottom: var(--v-rule);
	margin: 0;
}

.visage-index__link {
	display: grid;
	/* number | name | leader | duration, with the tagline on a second row
	   under the name. The name track is `auto` rather than `1fr` on purpose:
	   sized to its own text, it lets the leader start where the name actually
	   ends, which is what makes the row read as a line in an index rather than
	   as a table with a hole in the middle. The leader takes the slack, so the
	   durations still align to a single right-hand edge.

	   `--v-go` is the arrow, and `--v-shift` is how far the name moves right
	   to let it in. The second is deliberately larger than the first: an arrow
	   that lands flush against the N of a name is a collision, not a mark. */
	--v-go: clamp(1.75rem, 2.6vw, 2.25rem);
	--v-shift: calc(var(--v-go) + 0.85rem);
	grid-template-columns: auto auto minmax(1.5rem, 1fr) auto;
	align-items: baseline;
	gap: var(--wp--preset--spacing--xxs) var(--wp--preset--spacing--md);
	padding-block: var(--wp--preset--spacing--md);
	/* Hover paints only. Padding, width and font-size never change, or long
	   names re-wrap under the cursor. */
	transition: color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

.visage-index__n {
	margin: 0;
	grid-column: 1;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--md);
	color: var(--wp--preset--color--cocoa);
	line-height: 1.5;
	font-variant-numeric: tabular-nums;
}

.visage-index__name {
	grid-column: 2;
	grid-row: 1;
	min-width: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xxl);
	font-weight: 300;
	line-height: var(--wp--custom--leading--heading);
	letter-spacing: var(--wp--custom--tracking--display);
	margin: 0;
}

.visage-index__name a {
	color: inherit;
	text-decoration: none;
}

/* One link, one row-sized target. */
.visage-index__name a::after {
	content: '';
	position: absolute;
	inset: 0;
}

.visage-index__meta {
	grid-column: 4;
	grid-row: 1;
	margin: 0;
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--cocoa-deep);
	white-space: nowrap;
}

.visage-index__tagline {
	/* Spanning to the end rather than sitting in the name's own track: the
	   name track is sized to the name, and a tagline is longer than a name. */
	grid-column: 2 / -1;
	grid-row: 2;
	margin: 0;
	font-size: var(--wp--preset--font-size--sm);
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 78%, var(--wp--preset--color--cream));
	max-width: var(--wp--custom--measure--prose);
}

/* A treatment with no sourced value leaves the bound paragraph empty. */
.visage-index__meta:empty,
.visage-index__tagline:empty,
.is-style-visage-lede:empty {
	display: none;
}

@media (hover: hover) and (pointer: fine) {
	.visage-index__list > li:hover .visage-index__name,
	.visage-index__list > li:focus-within .visage-index__name {
		color: var(--wp--preset--color--clay);
	}

	/* Dim the siblings so the hovered row reads as chosen. Colour only. */
	.visage-index__list[data-hovering='true'] > li:not([data-active='true']) .visage-index__link {
		color: color-mix(in srgb, var(--wp--preset--color--charcoal) 45%, var(--wp--preset--color--cream));
	}

	.visage-index__list[data-hovering='true'] > li:not([data-active='true']) .visage-index__n,
	.visage-index__list[data-hovering='true'] > li:not([data-active='true']) .visage-index__meta,
	.visage-index__list[data-hovering='true'] > li:not([data-active='true']) .visage-index__tagline {
		color: inherit;
	}
}

@media (max-width: 48rem) {
	.visage-index__link {
		--v-go: 1.25rem;
		--v-shift: 0px;
		grid-template-columns: auto minmax(0, 1fr) auto;
	}

	.visage-index__meta {
		grid-column: 2;
		grid-row: 3;
		white-space: normal;
	}

	/* A leader rule needs a gap to carry the eye across, and at this width
	   there is no gap. */
	.visage-index__lead {
		display: none;
	}

}

/* One shared preview element follows the cursor; the rows themselves never
   animate. */
.visage-preview {
	position: fixed;
	top: 0;
	left: 0;
	z-index: var(--wp--custom--layer--preview);
	width: clamp(13rem, 17vw, 19rem);
	aspect-ratio: 4 / 5;
	border-radius: var(--wp--custom--radius--image);
	overflow: clip;
	pointer-events: none;
	opacity: 0;
	transform: translate3d(-50%, -50%, 0) scale(0.94);
	transition: opacity var(--wp--custom--duration--panel) var(--wp--custom--ease--out);
	will-change: transform, opacity;
	background: var(--wp--preset--color--sand);
}

.visage-preview[data-visible='true'] {
	opacity: 1;
}

.visage-preview img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	transition: opacity var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

.visage-preview img[data-swapping='true'] {
	opacity: 0;
}

/* ---------------------------------------------------------- 11. Proof row */

/* Three generations of this section used to live here, in sections 11, 31 and
   37, each overriding the last. That is how the wash in section 67 came to be
   sized against a grid area nobody had declared on purpose. The proof row is
   now defined once, in section 67, and nowhere else. */

/* ------------------------------------------------------------ 12. Voices */

.visage-voices {
	display: grid;
	gap: var(--wp--preset--spacing--md);
	grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}

.visage-voice {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--sm);
	padding: var(--wp--preset--spacing--md);
	background: var(--wp--preset--color--cream);
	border-radius: var(--wp--custom--radius--card);
	border: var(--v-rule);
}

.visage-voice__quote {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--lg);
	font-weight: 300;
	line-height: 1.42;
	letter-spacing: var(--wp--custom--tracking--display);
}

.visage-voice__quote strong {
	font-weight: 500;
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-voice__quote[lang='zh-Hant'] {
	font-family: 'Noto Sans TC', var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.9;
	letter-spacing: 0;
}

.visage-voice__by {
	margin-top: auto;
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--wp--preset--spacing--xxs) var(--wp--preset--spacing--xs);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-voice__by cite {
	font-style: normal;
	font-weight: 600;
	color: var(--wp--preset--color--charcoal);
}

.visage-voice__by a {
	color: inherit;
}

.visage-stars {
	color: var(--wp--preset--color--cocoa-deep);
	font-variant-numeric: tabular-nums;
	letter-spacing: var(--wp--custom--tracking--label);
}

/* ------------------------------------------------------------ 13. Cities */

.visage-cities {
	display: grid;
	gap: var(--wp--preset--spacing--md);
	grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

.visage-city {
	display: grid;
	gap: var(--wp--preset--spacing--xs);
	align-content: start;
	padding-top: var(--wp--preset--spacing--sm);
	border-top: var(--wp--custom--rule--hairline) solid color-mix(in srgb, var(--wp--preset--color--charcoal) 30%, transparent);
}

.visage-city--here {
	border-top-color: var(--wp--preset--color--clay);
}

.visage-city__name {
	margin: 0;
	font-size: var(--wp--preset--font-size--xl);
	transition: color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

.visage-city__here {
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--clay);
	font-weight: 600;
}

.visage-city address {
	font-style: normal;
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--body);
}

/* ---------------------------------------------------------------- 14. FAQ */

.visage-faq {
	border-top: var(--v-rule);
}

.visage-faq__item {
	border-bottom: var(--v-rule);
}

.visage-faq__q {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--sm);
	padding-block: var(--wp--preset--spacing--sm);
	cursor: pointer;
	list-style: none;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--lg);
	font-weight: 300;
	line-height: var(--wp--custom--leading--heading);
	transition: color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

.visage-faq__q::-webkit-details-marker {
	display: none;
}

@media (hover: hover) and (pointer: fine) {
	.visage-faq__q:hover {
		color: var(--wp--preset--color--clay);
	}
}

.visage-faq__mark {
	flex: 0 0 auto;
	color: var(--wp--preset--color--cocoa);
	transition: transform var(--wp--custom--duration--panel) var(--wp--custom--ease--out);
}

.visage-faq__item[open] .visage-faq__mark {
	transform: rotate(45deg);
}

.visage-faq__a {
	overflow: hidden;
}

.visage-faq__a p {
	margin: 0;
	padding-bottom: var(--wp--preset--spacing--md);
	max-width: var(--wp--custom--measure--prose);
	line-height: var(--wp--custom--leading--body);
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 88%, var(--wp--preset--color--cream));
}

/* ---------------------------------------------------- 15. Location + hours */

.visage-location {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
}

@media (min-width: 56rem) {
	.visage-location {
		grid-template-columns: minmax(0, 4fr) minmax(0, 5fr);
		gap: var(--wp--preset--spacing--xl);
	}
}

.visage-nap {
	display: grid;
	gap: var(--wp--preset--spacing--sm);
	align-content: start;
	font-style: normal;
}

.visage-nap a {
	color: inherit;
}

.visage-hours {
	margin: 0;
	display: grid;
	gap: var(--wp--preset--spacing--xxs);
}

.visage-hours__row {
	display: flex;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--sm);
	padding-block: var(--wp--preset--spacing--xxs);
	border-bottom: var(--v-rule);
	font-size: var(--wp--preset--font-size--sm);
}

.visage-hours__row dt,
.visage-hours__row dd {
	margin: 0;
}

.visage-hours__row dd {
	font-variant-numeric: tabular-nums;
}

.visage-hours__row--closed {
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 62%, var(--wp--preset--color--cream));
}

.visage-map {
	position: relative;
	aspect-ratio: 4 / 3;
	border-radius: var(--wp--custom--radius--card);
	overflow: clip;
	background: var(--wp--preset--color--sand);
}

.visage-map iframe,
.visage-map img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
	object-fit: cover;
}

/* The frame is last in the markup and paints over the photograph; the
   photograph is the ground it arrives on, and what is left standing if the
   embed is blocked. */
.visage-map__frame {
	z-index: 1;
}

/* ------------------------------------------------------------ 16. Specs */

.visage-specs {
	margin: 0;
	display: grid;
	gap: 0;
	border-top: var(--v-rule);
}

.visage-specs__row {
	display: grid;
	grid-template-columns: minmax(7rem, 12ch) 1fr;
	gap: var(--wp--preset--spacing--sm);
	padding-block: var(--wp--preset--spacing--sm);
	border-bottom: var(--v-rule);
}

.visage-specs__row dt {
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--cocoa-deep);
	padding-top: 0.2em;
}

.visage-specs__row dd {
	margin: 0;
	font-size: var(--wp--preset--font-size--md);
	line-height: var(--wp--custom--leading--tight);
}

/* ------------------------------------------------------------ 17. Footer */

.visage-footer {
	background: var(--wp--preset--color--black);
	color: color-mix(in srgb, var(--wp--preset--color--cream) 78%, var(--wp--preset--color--black));
	padding-block: var(--wp--preset--spacing--xl) var(--wp--preset--spacing--lg);
}

.visage-footer a {
	color: var(--wp--preset--color--cream);
	text-decoration: none;
}

@media (hover: hover) and (pointer: fine) {
	.visage-footer a:hover {
		color: var(--wp--preset--color--blush);
	}
}

.visage-footer__grid {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}

.visage-footer__head {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xxl);
	font-weight: 300;
	color: var(--wp--preset--color--cream);
	margin: 0 0 var(--wp--preset--spacing--sm);
	line-height: var(--wp--custom--leading--heading);
}

.visage-footer h2,
.visage-footer h3 {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	color: color-mix(in srgb, var(--wp--preset--color--cream) 62%, var(--wp--preset--color--black));
	margin: 0 0 var(--wp--preset--spacing--xs);
}

.visage-footer ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--wp--preset--spacing--xxs);
	font-size: var(--wp--preset--font-size--sm);
}

.visage-footer__bottom {
	margin-top: var(--wp--preset--spacing--lg);
	padding-top: var(--wp--preset--spacing--sm);
	border-top: var(--v-rule-dark);
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--xs) var(--wp--preset--spacing--md);
	justify-content: space-between;
	font-size: var(--wp--preset--font-size--xs);
}

.visage-icon-circle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.5rem;
	height: 1.5rem;
	color: currentColor;
	transition: background-color var(--wp--custom--duration--hover) var(--wp--custom--ease--out),
		transform var(--wp--custom--duration--press) var(--wp--custom--ease--out);
}

.visage-icon-circle:active {
	transform: scale(0.94);
}

@media (hover: hover) and (pointer: fine) {
	.visage-icon-circle:hover {
		color: var(--wp--preset--color--clay);
	}
}

.visage-socials {
	display: flex;
	gap: var(--wp--preset--spacing--md);
}

/* ------------------------------------------------------ 18. Brand shapes */

.visage-shape {
	position: absolute;
	pointer-events: none;
	color: var(--wp--preset--color--cocoa);
	opacity: 0.5;
}

.visage-shape--wave {
	left: 0;
	right: 0;
	bottom: 0;
	width: 100%;
	height: auto;
	opacity: 1;
	color: var(--wp--preset--color--cocoa);
}

.visage-tagline-plate {
	position: relative;
	background: none;
	padding: var(--wp--preset--spacing--lg) 0 0;
	margin-top: var(--wp--preset--spacing--lg);
	border-top: var(--wp--custom--rule--hairline) solid color-mix(in srgb, var(--wp--preset--color--charcoal) 24%, transparent);
	display: grid;
	gap: var(--wp--preset--spacing--xs);
	justify-items: start;
}

.visage-tagline-plate__text {
	position: relative;
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xl);
	font-style: italic;
	font-weight: 300;
	line-height: var(--wp--custom--leading--heading);
	letter-spacing: var(--wp--custom--tracking--display);
	color: var(--wp--preset--color--charcoal);
	max-width: 22ch;
}

.visage-tagline-plate__mark {
	position: relative;
	z-index: 1;
	color: var(--wp--preset--color--cocoa);
}

/* -------------------------------------------------------------- 19. Motion */

/* Everything below is enhancement. Content is visible by default; a reveal
   that never runs leaves a finished page, not a blank one. */

@media (prefers-reduced-motion: no-preference) {
	body:not(.is-noanim) [data-reveal] {
		opacity: 0;
		transform: translate3d(0, var(--wp--preset--spacing--sm), 0);
		transition:
			opacity var(--wp--custom--duration--reveal) var(--wp--custom--ease--out),
			transform var(--wp--custom--duration--reveal) var(--wp--custom--ease--out);
		transition-delay: var(--v-delay, 0ms);
	}

	body:not(.is-noanim) [data-reveal][data-shown='true'] {
		opacity: 1;
		transform: none;
	}

	/* Scroll-driven, off the main thread, where the browser supports it. */
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-hero__media img {
			animation: visage-hero-drift linear both;
			animation-timeline: view();
			animation-range: entry 100% exit 100%;
		}

		body:not(.is-noanim) .visage-figure img {
			animation: visage-figure-rise linear both;
			animation-timeline: view();
			animation-range: entry 0% cover 42%;
		}

		body:not(.is-noanim) .visage-shape--wave {
			animation: visage-wave-drift linear both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
		}
	}
}

@keyframes visage-hero-drift {
	from {
		transform: scale(1.06) translate3d(0, 0, 0);
	}
	to {
		transform: scale(1.06) translate3d(0, 5%, 0);
	}
}

@keyframes visage-figure-rise {
	from {
		transform: scale(1.12);
		clip-path: inset(14% 0 0 0);
	}
	to {
		transform: scale(1);
		clip-path: inset(0 0 0 0);
	}
}

@keyframes visage-wave-drift {
	from {
		transform: translate3d(-3%, 0, 0);
	}
	to {
		transform: translate3d(3%, 0, 0);
	}
}

@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}

	.visage-marquee__track {
		animation: none;
		transform: none;
	}

	*,
	*::before,
	*::after {
		animation-duration: 1ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 1ms !important;
	}
}

/* `?noanim=1` renders the settled state instantly. */
.is-noanim .visage-marquee__track {
	animation: none;
}

.is-noanim *,
.is-noanim *::before,
.is-noanim *::after {
	animation-duration: 0s !important;
	transition-duration: 0s !important;
}

/* -------------------------------------------------- 20. Overflow debug UI */

.visage-overflow-panel {
	position: fixed;
	right: var(--wp--preset--spacing--sm);
	bottom: var(--wp--preset--spacing--sm);
	z-index: var(--wp--custom--layer--panel);
	max-width: min(30rem, 90vw);
	max-height: 60vh;
	overflow: auto;
	padding: var(--wp--preset--spacing--sm);
	background: var(--wp--preset--color--black);
	color: var(--wp--preset--color--cream);
	border-radius: var(--wp--custom--radius--card);
	font-family: ui-monospace, monospace;
	font-size: var(--wp--preset--font-size--xs);
	line-height: var(--wp--custom--leading--tight);
}

.visage-overflow-panel ol {
	margin: var(--wp--preset--spacing--xs) 0 0;
	padding-left: var(--wp--preset--spacing--sm);
	display: grid;
	gap: var(--wp--preset--spacing--xxs);
}

.visage-overflow-outline {
	outline: 2px solid var(--wp--preset--color--clay) !important;
	outline-offset: -2px;
}

/* Nav list, when a menu is assigned. */
.visage-nav__list {
	display: contents;
	list-style: none;
	margin: 0;
	padding: 0;
}

.visage-nav__list li {
	margin: 0;
}

.visage-header__book {
	background: var(--wp--preset--color--charcoal);
	color: var(--wp--preset--color--cream);
	border-radius: 0;
	padding: 0.85rem 1.5rem;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	text-decoration: none;
	line-height: 1;
	transition:
		background-color var(--wp--custom--duration--hover) var(--wp--custom--ease--out),
		transform var(--wp--custom--duration--press) var(--wp--custom--ease--out);
}

.visage-header__book:active {
	transform: scale(0.97);
}

.visage-header__book::after {
	display: none;
}

.visage-footer__address {
	font-style: normal;
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--body);
}

/* ------------------------------------------------- 21. Section furniture */

.visage-index__head {
	max-width: var(--wp--custom--measure--prose);
	margin-bottom: var(--wp--preset--spacing--lg);
}

.visage-index__head .is-style-visage-eyebrow {
	margin: 0 0 var(--wp--preset--spacing--xxs);
}

.visage-index__head .visage-editorial__title {
	margin: 0;
}

.visage-tagline-plate--after {
	margin-top: var(--wp--preset--spacing--md);
}

.visage-hero__link .wp-block-button__link,
.visage-cta__link .wp-block-button__link {
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	color: var(--wp--preset--color--charcoal);
	text-decoration: none;
	border-bottom: var(--wp--custom--rule--hairline) solid currentColor;
	padding-bottom: var(--wp--preset--spacing--xxs);
	transition: color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

@media (hover: hover) and (pointer: fine) {
	.visage-hero__link .wp-block-button__link:hover,
	.visage-cta__link .wp-block-button__link:hover {
		color: var(--wp--preset--color--clay);
	}
}

.visage-signature__actions {
	margin-top: var(--wp--preset--spacing--lg);
}

.visage-slot-empty {
	display: grid;
	place-items: center;
	aspect-ratio: 4 / 5;
	background: var(--wp--preset--color--sand);
	color: var(--wp--preset--color--taupe);
	border-radius: var(--wp--custom--radius--image);
}

.visage-slot-empty .visage-shape {
	position: static;
	width: 40%;
	height: auto;
	opacity: 1;
}

/* ------------------------------------------------------------- 22. CTA */

.visage-cta__inner {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	align-items: end;
}

@media (min-width: 56rem) {
	.visage-cta__inner {
		grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
		gap: var(--wp--preset--spacing--xl);
	}
}

.visage-cta__title {
	margin: 0 0 var(--wp--preset--spacing--sm);
	font-size: var(--wp--preset--font-size--xxxl);
	max-width: 16ch;
}

.visage-cta__sub {
	margin: 0;
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 92%, var(--wp--preset--color--blush));
}

.visage-cta__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--sm) var(--wp--preset--spacing--md);
}

.visage-cta__primary .wp-block-button__link {
	background: var(--wp--preset--color--charcoal);
	color: var(--wp--preset--color--cream);
	border-radius: var(--wp--custom--radius--button);
	padding: 1.0625rem 2rem;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--sm);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	text-decoration: none;
	line-height: 1;
}

.visage-cta__primary .wp-block-button__link:hover {
	background: var(--wp--preset--color--black);
	color: var(--wp--preset--color--cream);
}

/* ------------------------------------------- 23. Archive, single, journal */

.visage-archive-head__title,
.visage-page__title,
.visage-treatment__title,
.visage-404__title {
	font-size: var(--wp--preset--font-size--xxxl);
	line-height: var(--wp--custom--leading--display);
	margin: 0 0 var(--wp--preset--spacing--sm);
	max-width: 20ch;
}

.visage-archive-head .is-style-visage-eyebrow,
.visage-treatment__head .is-style-visage-eyebrow {
	margin: 0 0 var(--wp--preset--spacing--xxs);
}

.visage-filters {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--xs);
	margin-top: var(--wp--preset--spacing--md);
}

.visage-filter {
	display: inline-flex;
	align-items: center;
	padding: var(--wp--preset--spacing--xxs) 0;
	margin-right: var(--wp--preset--spacing--md);
	border-bottom: var(--wp--custom--rule--hairline) solid transparent;
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 62%, var(--wp--preset--color--cream));
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	text-decoration: none;
	transition:
		background-color var(--wp--custom--duration--hover) var(--wp--custom--ease--out),
		transform var(--wp--custom--duration--press) var(--wp--custom--ease--out);
}

.visage-filter:active {
	transform: scale(0.97);
}

.visage-filter--on {
	color: var(--wp--preset--color--charcoal);
	border-bottom-color: var(--wp--preset--color--charcoal);
}

.visage-crumbs {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--xs);
	margin-bottom: var(--wp--preset--spacing--md);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-crumbs a {
	color: inherit;
	text-decoration: none;
	border-bottom: var(--wp--custom--rule--hairline) solid transparent;
}

.visage-crumbs a:hover {
	border-bottom-color: currentColor;
}

.visage-treatment__grid {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	align-items: start;
}

@media (min-width: 56rem) {
	.visage-treatment__grid {
		grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
		gap: var(--wp--preset--spacing--xl);
	}
}

.visage-treatment__actions {
	margin-top: var(--wp--preset--spacing--lg);
}

.visage-treatment__body {
	padding-block: 0 var(--wp--preset--spacing--xxl);
}

.visage-treatment__body p {
	max-width: var(--wp--custom--measure--prose);
	line-height: var(--wp--custom--leading--body);
}

.visage-treatment__head .visage-specs {
	margin-top: var(--wp--preset--spacing--md);
}

.visage-journal__item {
	padding-block: var(--wp--preset--spacing--md);
	border-bottom: var(--v-rule);
	display: grid;
	gap: var(--wp--preset--spacing--xxs);
}

.visage-journal__date {
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-journal__item .wp-block-post-title a {
	text-decoration: none;
}

.visage-404__inner {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	align-items: center;
	min-height: 60svh;
	align-content: center;
}

@media (min-width: 56rem) {
	.visage-404__inner {
		grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
	}
}

.visage-404__mark {
	position: relative;
	display: grid;
	place-items: center;
	min-height: 16rem;
	color: var(--wp--preset--color--cocoa);
}

.visage-404__mark .visage-shape {
	position: static;
	width: min(100%, 22rem);
	height: auto;
	opacity: 0.75;
}

/* ---------------------------------------------------------- 24. Booking */

.visage-booking {
	display: grid;
	gap: var(--wp--preset--spacing--sm);
	margin-top: var(--wp--preset--spacing--md);
}

.visage-booking__frame {
	position: relative;
	min-height: 42rem;
	border-radius: var(--wp--custom--radius--card);
	overflow: clip;
	background: var(--wp--preset--color--sand);
}

.visage-booking__frame iframe {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
}

.visage-booking__fallback {
	margin: 0;
	font-size: var(--wp--preset--font-size--sm);
	color: var(--wp--preset--color--cocoa-deep);
}

/* Core's flow layout adds margin between children; the row is a grid. */
.visage-index__link > * {
	margin-block: 0;
}

/* Sizing. The plate used to be set to 60px, of which the type itself was only
   about 46 — this is that same optical size, now that the ground is gone. */
.visage-header__mark {
	height: 46px;
}

@media (max-width: 40rem) {
	.visage-header__mark {
		height: 38px;
	}
}

/* Marquee: fade the ends instead of letting a letter get sliced by the edge.
   This was a mask over the whole band, which faded the band's own ground away
   with the type — and the hero is pinned underneath this line, so the two ends
   turned into windows onto a moving photograph. The fade is now two overlays in
   the band's own colour: the type still dissolves, the ground never does. */
.visage-marquee::before,
.visage-marquee::after {
	content: '';
	position: absolute;
	inset-block: 0;
	width: 6%;
	z-index: 1;
	pointer-events: none;
}

.visage-marquee::before {
	inset-inline-start: 0;
	background: linear-gradient(to right, var(--v-marquee-ground), transparent);
}

.visage-marquee::after {
	inset-inline-end: 0;
	background: linear-gradient(to left, var(--v-marquee-ground), transparent);
}

/* ------------------------------------------- 25. On-plane corrections */

/* Clay on blush measures 2.7:1. The label is small, so it takes charcoal. */
.is-style-visage-plane-blush .visage-city__here {
	color: var(--wp--preset--color--charcoal);
}

/* A blush button on a blush plane has nothing to sit on, and the guideline
   forbids dark buttons with light text. Cream fill keeps the rule. */
.visage-cta__primary .wp-block-button__link {
	background: var(--wp--preset--color--cream);
	color: var(--wp--preset--color--charcoal);
	transition:
		background-color var(--wp--custom--duration--hover) var(--wp--custom--ease--out),
		transform var(--wp--custom--duration--press) var(--wp--custom--ease--out);
}

.visage-cta__primary .wp-block-button__link:hover {
	background: var(--wp--preset--color--white);
	color: var(--wp--preset--color--charcoal);
}

.visage-cta__primary .wp-block-button__link:active {
	transform: scale(0.97);
}

/* A charcoal disc disappears on the black footer. */
.visage-footer .visage-icon-circle {
	color: color-mix(in srgb, var(--wp--preset--color--cream) 72%, var(--wp--preset--color--black));
}

@media (hover: hover) and (pointer: fine) {
	.visage-footer .visage-icon-circle:hover {
		color: var(--wp--preset--color--blush);
	}
}

/* Core sizes the featured-image figure and its img from the block attributes;
   both have to be pulled out of layout, not just the wrapper. */
.visage-index__src,
.visage-index__src img {
	position: absolute !important;
	width: 1px !important;
	height: 1px !important;
	max-width: 1px !important;
	margin: -1px !important;
	padding: 0 !important;
	overflow: hidden;
	clip-path: inset(50%);
	border: 0;
}

/* ------------------------------------------- 26. Single treatment media */

.visage-treatment__head .wp-block-post-featured-image {
	margin: 0;
	border-radius: var(--wp--custom--radius--image);
	overflow: clip;
	background: var(--wp--preset--color--sand);
}

.visage-treatment__head .wp-block-post-featured-image,
.visage-treatment__head .wp-block-post-featured-image img {
	width: 100%;
	max-width: 100%;
	height: auto;
	aspect-ratio: 4 / 5;
	object-fit: cover;
	display: block;
}

/* Terms render as links; the eyebrow is a label, not a rule. */
.visage-treatment__head .wp-block-post-terms a {
	text-decoration: none;
	border-bottom: var(--wp--custom--rule--hairline) solid transparent;
}

.visage-treatment__head .wp-block-post-terms a:hover {
	border-bottom-color: currentColor;
}

.visage-treatment__head .visage-crumbs {
	margin-bottom: var(--wp--preset--spacing--lg);
}

/* Grid items default to min-width:auto, so one image with a width attribute
   props the whole track open and the column stops shrinking. */
.visage-editorial > *,
.visage-signature__grid > *,
.visage-treatment__grid > *,
.visage-location > *,
.visage-cta__inner > *,
.visage-proof > *,
.visage-voices > *,
.visage-cities > *,
.visage-footer__grid > *,
.visage-404__inner > * {
	min-width: 0;
}

/* =========================================================================
   27. Motion system
   Entrances ease out, hovers only paint, anything per-frame is transform or
   opacity. Everything below is enhancement: with JavaScript blocked the page
   renders in its settled state.
   ========================================================================= */

.visage-sentinel {
	position: absolute;
	top: 0;
	left: 0;
	width: 1px;
	height: 1px;
	pointer-events: none;
}

/* -- reading progress ---------------------------------------------------- */

.visage-progress {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 2px;
	overflow: hidden;
	pointer-events: none;
}

.visage-progress__bar {
	display: block;
	height: 100%;
	background: var(--wp--preset--color--blush);
	transform: scaleX(0);
	transform-origin: 0 50%;
}

@media (prefers-reduced-motion: no-preference) {
	@supports (animation-timeline: scroll()) {
		body:not(.is-noanim) .visage-progress__bar[data-native='true'] {
			animation: visage-progress linear both;
			animation-timeline: scroll(root block);
		}
	}
}

@keyframes visage-progress {
	from { transform: scaleX(0); }
	to { transform: scaleX(1); }
}

/* -- line reveal --------------------------------------------------------- */

/* Present only once JavaScript has wrapped the lines; unsplit headings are
   ordinary visible text. */
[data-split='true'] .v-line {
	display: block;
	overflow: hidden;
}

[data-split='true'] .v-line__in {
	display: block;
	will-change: transform;
}

@media (prefers-reduced-motion: no-preference) {
	body:not(.is-noanim) [data-split='true']:not([data-shown='true']) .v-line__in,
	body:not(.is-noanim) .visage-hero:not([data-intro='done']) [data-split='true'] .v-line__in {
		transform: translateY(104%);
		opacity: 0;
	}

	body:not(.is-noanim) [data-split='true'] .v-line__in {
		transition:
			transform 900ms var(--wp--custom--ease--out),
			opacity 600ms var(--wp--custom--ease--out);
	}
}

/* -- hero choreography --------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
	body:not(.is-noanim) .visage-hero__media {
		clip-path: inset(0 0 0 0);
		transition: clip-path 1200ms var(--wp--custom--ease--inout);
	}

	body:not(.is-noanim) .visage-hero:not([data-intro='done']) .visage-hero__media {
		clip-path: inset(14% 6% 14% 6%);
	}

	body:not(.is-noanim) .visage-hero__sub,
	body:not(.is-noanim) .visage-hero__actions,
	body:not(.is-noanim) .visage-hero__scroll {
		opacity: 1;
		transform: none;
		transition:
			opacity 700ms var(--wp--custom--ease--out) 420ms,
			transform 700ms var(--wp--custom--ease--out) 420ms;
	}

	body:not(.is-noanim) .visage-hero:not([data-intro='done']) .visage-hero__sub,
	body:not(.is-noanim) .visage-hero:not([data-intro='done']) .visage-hero__actions,
	body:not(.is-noanim) .visage-hero:not([data-intro='done']) .visage-hero__scroll {
		opacity: 0;
		transform: translateY(var(--wp--preset--spacing--xs));
	}

	body:not(.is-noanim) .visage-hero__actions {
		transition-delay: 520ms;
	}

	body:not(.is-noanim) .visage-hero__scroll {
		transition-delay: 640ms;
	}
}

/* -- marquee rate -------------------------------------------------------- */

.visage-marquee__track {
	animation-duration: var(--v-marquee-base, 48s);
	animation-timing-function: linear;
	will-change: transform;
	backface-visibility: hidden;
}

/* -- figure zoom on hover ------------------------------------------------- */

[data-zoom] {
	overflow: clip;
}

[data-zoom] img {
	transition: transform 620ms var(--wp--custom--ease--out);
	will-change: transform;
}

@media (hover: hover) and (pointer: fine) {
	a:hover [data-zoom] img,
	[data-zoom]:hover img {
		transform: scale(1.04);
	}
}

/* =========================================================================
   28. Stacked editorial pair
   ========================================================================= */

.visage-stack {
	position: relative;
	display: grid;
	gap: var(--wp--preset--spacing--sm);
}

.visage-stack__a,
.visage-stack__b {
	border-radius: var(--wp--custom--radius--image);
	overflow: clip;
	background: var(--wp--preset--color--sand);
}

.visage-stack__a img,
.visage-stack__b img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

@media (min-width: 48rem) {
	.visage-stack {
		grid-template-columns: repeat(12, 1fr);
		gap: var(--wp--preset--spacing--sm);
	}

	.visage-stack__a {
		grid-column: 1 / span 9;
		aspect-ratio: 4 / 5;
	}

	.visage-stack__b {
		grid-column: 7 / span 6;
		grid-row: 1;
		align-self: end;
		aspect-ratio: 1 / 1;
		margin-bottom: calc(var(--wp--preset--spacing--xxl) * -1);
		z-index: 1;
		border: var(--wp--preset--spacing--xxs) solid var(--wp--preset--color--cream);
	}
}

/* The two layers drift at different rates while the pair crosses the screen. */
@media (prefers-reduced-motion: no-preference) and (min-width: 48rem) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-stack__a img {
			animation: visage-drift-slow linear both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
		}

		body:not(.is-noanim) .visage-stack__b {
			animation: visage-drift-fast linear both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
		}
	}
}

@keyframes visage-drift-slow {
	from { transform: scale(1.08) translate3d(0, -2%, 0); }
	to { transform: scale(1.08) translate3d(0, 2%, 0); }
}

@keyframes visage-drift-fast {
	from { transform: translate3d(0, 8%, 0); }
	to { transform: translate3d(0, -6%, 0); }
}

/* =========================================================================
   29. The strip — a horizontal gallery driven by vertical scroll
   The page keeps scrolling normally throughout; nothing is hijacked.
   ========================================================================= */

.visage-strip {
	position: relative;
	background: var(--wp--preset--color--cream);
}

.visage-strip__sticky {
	padding-block: var(--wp--preset--spacing--xl);
	overflow: clip;
}

.visage-strip__head {
	width: 100%;
	max-width: calc(var(--wp--style--global--wide-size) + (var(--v-gutter) * 2));
	margin: 0 auto var(--wp--preset--spacing--lg);
	padding-inline: var(--v-gutter);
	box-sizing: border-box;
}

.visage-strip__rail {
	display: flex;
	gap: var(--wp--preset--spacing--sm);
	padding-inline: var(--v-gutter);
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
	cursor: grab;
	will-change: transform;
}

.visage-strip__rail::-webkit-scrollbar {
	display: none;
}

.visage-strip__rail[data-dragging='true'] {
	cursor: grabbing;
	scroll-snap-type: none;
}

.visage-strip__item {
	position: relative;
	flex: 0 0 auto;
	width: clamp(15rem, 26vw, 24rem);
	scroll-snap-align: center;
	background: none;
}

.visage-strip__item img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	background: var(--wp--preset--color--sand);
	transition: transform 700ms var(--wp--custom--ease--out);
}

.visage-strip__item--wide {
	width: clamp(20rem, 38vw, 34rem);
}

.visage-strip__item--tall {
	align-self: flex-start;
}

.visage-strip__item--low {
	align-self: flex-end;
}

.visage-strip__cap {
	display: block;
	margin-top: var(--wp--preset--spacing--xs);
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 62%, var(--wp--preset--color--cream));
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	transition: color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

@media (hover: hover) and (pointer: fine) {
	.visage-strip__item:hover .visage-strip__cap {
		color: var(--wp--preset--color--charcoal);
	}

	.visage-strip__item:hover img {
		transform: scale(1.05);
	}
}



.visage-strip__hint {
	margin: var(--wp--preset--spacing--sm) 0 0;
	padding-inline: var(--v-gutter);
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--cocoa-deep);
}

/* Pinned mode: the rail translates while the tall section passes the viewport. */
@media (prefers-reduced-motion: no-preference) and (min-width: 56rem) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-strip[data-pinned='true'] {
			height: var(--v-track);
			view-timeline-name: --visage-strip;
			view-timeline-axis: block;
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__sticky {
			position: sticky;
			top: 0;
			height: 100svh;
			display: flex;
			flex-direction: column;
			justify-content: center;
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__rail {
			overflow: visible;
			scroll-snap-type: none;
			cursor: default;
			animation: visage-strip-travel linear both;
			animation-timeline: --visage-strip;
			animation-range: contain 0% contain 100%;
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__hint {
			display: none;
		}
	}
}

@keyframes visage-strip-travel {
	from { transform: translate3d(0, 0, 0); }
	to { transform: translate3d(calc(var(--v-travel, 0px) * -1), 0, 0); }
}

/* =========================================================================
   30. Client voices — one quote carries the section, the rest ride a rail
   ========================================================================= */

.visage-voices {
	display: block;
}

.visage-voices__lead {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	align-items: center;
	padding: 0;
	background: none;
}

@media (min-width: 56rem) {
	.visage-voices__lead {
		grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
		gap: var(--wp--preset--spacing--xl);
	}
}

.visage-voices__portrait {
	position: relative;
	aspect-ratio: 4 / 5;
	overflow: clip;
	background: var(--wp--preset--color--sand);
}

.visage-voices__portrait img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.visage-voices__mark {
	color: var(--wp--preset--color--cocoa);
	display: block;
	margin-bottom: var(--wp--preset--spacing--xs);
}

.visage-voices__quote {
	margin: 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xl);
	font-weight: 300;
	line-height: 1.34;
	letter-spacing: var(--wp--custom--tracking--display);
	color: var(--wp--preset--color--charcoal);
}

.visage-voices__quote strong {
	font-weight: 500;
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-voices__rail {
	display: flex;
	gap: var(--wp--preset--spacing--sm);
	margin-top: var(--wp--preset--spacing--md);
	overflow-x: auto;
	scroll-snap-type: x proximity;
	scrollbar-width: none;
	padding-bottom: var(--wp--preset--spacing--xxs);
}

.visage-voices__rail::-webkit-scrollbar {
	display: none;
}

.visage-voice {
	flex: 0 0 auto;
	width: min(24rem, 78vw);
	scroll-snap-align: start;
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--sm);
	padding: var(--wp--preset--spacing--sm) 0 0;
	background: none;
	border: 0;
	border-top: var(--wp--custom--rule--hairline) solid color-mix(in srgb, var(--wp--preset--color--charcoal) 24%, transparent);
	transition: border-color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

@media (hover: hover) and (pointer: fine) {
	.visage-voice:hover {
		border-top-color: var(--wp--preset--color--charcoal);
	}
}

.visage-voice__quote {
	font-size: var(--wp--preset--font-size--md);
	line-height: 1.5;
}

/* Section 31 was the proof row's second generation. See section 67. */

/* The guideline's shapes are print collateral. As corner ornaments on cards
   they were the loudest template tell on the page, so they are gone. */


/* =========================================================================
   32. Cities — Vancouver gets the photograph, the others get the line work
   ========================================================================= */

.visage-city {
	position: relative;
	display: grid;
	gap: var(--wp--preset--spacing--sm);
	align-content: start;
	padding: var(--wp--preset--spacing--sm) 0 0;
	border-top: var(--wp--custom--rule--hairline) solid color-mix(in srgb, var(--wp--preset--color--charcoal) 24%, transparent);
	background: none;
}

@media (hover: hover) and (pointer: fine) {
	.visage-city:hover .visage-city__name {
		color: var(--wp--preset--color--cocoa-deep);
	}
}

.visage-city--here {
	border-top-color: var(--wp--preset--color--charcoal);
}

.visage-city__media {
	aspect-ratio: 4 / 5;
	overflow: clip;
	background: none;
	display: grid;
	place-items: center;
}

.visage-city__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}



/* =========================================================================
   33. Map — the studio photograph is the ground the frame arrives on
   ========================================================================= */

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


/* =========================================================================
   34. Signature steps — the one being read is the one lit
   ========================================================================= */

.visage-step {
	transition: opacity var(--wp--custom--duration--panel) var(--wp--custom--ease--out);
}

@media (prefers-reduced-motion: no-preference) and (min-width: 56rem) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-step {
			animation: visage-step-focus linear both;
			animation-timeline: view();
			animation-range: entry 20% exit 80%;
		}

		body:not(.is-noanim) .visage-signature__media img {
			animation: visage-drift-slow linear both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
		}
	}
}

@keyframes visage-step-focus {
	0% { opacity: 0.4; }
	28%, 72% { opacity: 1; }
	100% { opacity: 0.4; }
}

/* =========================================================================
   36. Corrections found by looking
   ========================================================================= */

/* A grid container with place-items:center sizes its track to max-content, so
   an 800px photograph made the card 800px wide and spilled over its neighbour.
   Pin the track to the container instead. */
.visage-city__media,
.visage-slot-empty {
	width: 100%;
	min-width: 0;
	grid-template-columns: minmax(0, 1fr);
}

.visage-city__media img {
	grid-area: 1 / 1;
}

/* The proof row was four small labels on a large empty plane. Give it presence:
   taller cards, the line-art reading as a real layer. */


/* The black chapter's image is the anchor of that fold; let it fill its column. */
.visage-signature__media .visage-figure {
	aspect-ratio: 4 / 5;
}

.visage-signature__media img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* =========================================================================
   37. Quiet luxury pass
   The page was reading as a slide deck: content in rounded boxes, every
   section its own tinted band, decorative shapes in the corners. Content now
   sits on one ground, separated by whitespace and hairlines. Colour appears
   twice on the page and means something both times.
   ========================================================================= */

/* -- an asymmetric editorial grid, not two centred halves ------------------ */

@media (min-width: 56rem) {
	.visage-editorial {
		grid-template-columns: repeat(12, minmax(0, 1fr));
		column-gap: var(--wp--preset--spacing--md);
		row-gap: var(--wp--preset--spacing--xl);
	}

	.visage-editorial > *:first-child {
		grid-column: 1 / span 5;
	}

	.visage-editorial > *:last-child {
		grid-column: 7 / span 6;
	}

	.visage-editorial--reverse > *:first-child {
		grid-column: 8 / span 5;
		order: 2;
	}

	.visage-editorial--reverse > *:last-child {
		grid-column: 1 / span 6;
	}

	/* The FAQ hangs its heading in the first third and the answers in the last
	   two, so the fold is not two equal columns. */
	#visage-faq .visage-editorial > *:first-child {
		grid-column: 1 / span 4;
	}

	#visage-faq .visage-editorial > *:last-child {
		grid-column: 6 / span 7;
	}

	.visage-location {
		grid-template-columns: repeat(12, minmax(0, 1fr));
		column-gap: var(--wp--preset--spacing--md);
	}

	.visage-location > *:first-child {
		grid-column: 1 / span 5;
	}

	.visage-location > *:last-child {
		grid-column: 7 / span 6;
	}

	/* Constrain the heading, not the block: a narrow auto-margined container
	   centres itself and reads as an accident. */
	.visage-index__head > *,
	.visage-strip__head > * {
		max-width: 34ch;
	}
}

/* -- headings and labels: smaller, calmer --------------------------------- */

.visage-editorial__title {
	font-size: var(--wp--preset--font-size--xl);
	max-width: 20ch;
}

.visage-index__head,
.visage-strip__head {
	margin-bottom: var(--wp--preset--spacing--xl);
}

.visage-index__name {
	font-size: var(--wp--preset--font-size--xl);
}

.visage-index__link {
	padding-block: var(--wp--preset--spacing--lg);
}

.visage-hero__title {
	font-size: var(--wp--preset--font-size--xxxl);
}

.visage-cta__title {
	font-size: var(--wp--preset--font-size--xxl);
}

.visage-archive-head__title,
.visage-page__title,
.visage-treatment__title,
.visage-404__title {
	font-size: var(--wp--preset--font-size--xxl);
}

/* -- flat, square, quiet ---------------------------------------------------- */

.visage-figure,
.visage-stack__a,
.visage-stack__b,
.visage-map,
.visage-voices__portrait,
.visage-booking__frame,
.visage-treatment__head .wp-block-post-featured-image,
.wp-block-image img,
.wp-block-post-featured-image img {
	border-radius: 0;
}

@media (min-width: 48rem) {
	.visage-stack__b {
		border: 0;
		margin-bottom: calc(var(--wp--preset--spacing--xl) * -1);
	}
}

.visage-marquee {
	border-block: var(--v-rule);
	padding-block: var(--wp--preset--spacing--sm);
}

.visage-marquee__item {
	font-size: var(--wp--preset--font-size--lg);
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 60%, var(--wp--preset--color--cream));
}

.visage-marquee__item::after {
	width: 3px;
	height: 3px;
	background: var(--wp--preset--color--cocoa);
}

/* -- the two colour moments ------------------------------------------------ */

/* The booking plane is the only large field of brand colour on the page, so it
   is allowed to be a full plane. */
.is-style-visage-plane-blush {
	padding-block: var(--wp--preset--spacing--xxxl);
}

.visage-cta__primary .wp-block-button__link {
	background: var(--wp--preset--color--charcoal);
	color: var(--wp--preset--color--cream);
	border-radius: 0;
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	padding: 1.125rem 2.25rem;
}

.visage-cta__primary .wp-block-button__link:hover {
	background: var(--wp--preset--color--black);
	color: var(--wp--preset--color--cream);
}

/* The black chapter carries more air now that it is the only one. */
.is-style-visage-plane-black {
	padding-block: var(--wp--preset--spacing--xxxl);
}

/* -- captions and small text ----------------------------------------------- */

.visage-voice__by,
.visage-index__meta,
.visage-crumbs,
.visage-strip__hint {
	text-transform: none;
	letter-spacing: var(--wp--custom--tracking--label);
}

.visage-voice__by cite {
	font-style: normal;
	font-weight: 400;
	letter-spacing: 0;
}

.visage-voices__quote {
	font-size: var(--wp--preset--font-size--lg);
	line-height: 1.42;
}

.visage-voices__mark {
	display: none;
}

/* `.visage-nav a` sets the link colour and outranks the button class, which
   left the header CTA as charcoal text on a charcoal fill. */
.visage-nav a.visage-header__book,
.visage-nav a.visage-header__book:hover {
	color: var(--wp--preset--color--cream);
}

.visage-nav a.visage-header__book:hover {
	background: var(--wp--preset--color--black);
}

/* The stacked pair reads as one composition only if the two frames do not
   collide. Offset, no overlap, no sticker border. */
@media (min-width: 48rem) {
	.visage-stack {
		grid-template-columns: repeat(12, minmax(0, 1fr));
		row-gap: var(--wp--preset--spacing--md);
	}

	.visage-stack__a {
		grid-column: 3 / span 10;
		grid-row: 1;
		aspect-ratio: 4 / 5;
	}

	.visage-stack__b {
		grid-column: 1 / span 5;
		grid-row: 2;
		align-self: start;
		aspect-ratio: 1 / 1;
		margin-bottom: 0;
		margin-top: calc(var(--wp--preset--spacing--xl) * -1);
		z-index: 1;
	}
}

/* =========================================================================
   38. Contrast correction
   Shrinking the labels raised the bar: 11px uppercase needs 4.5:1, and the
   muted greys introduced in the quiet pass measured 2.84:1 and 3.08:1 on
   cream. Small text moves to cocoa-deep (6.75:1); large display grey moves up
   to charcoal 72% (3.87:1, and only used at 22px and above).
   ========================================================================= */

.is-style-visage-eyebrow,
.visage-filter,
.visage-strip__cap,
.visage-strip__hint,
.visage-hours__row--closed {
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-marquee__item {
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 72%, var(--wp--preset--color--cream));
}

/* `.visage-proof__v` used to be in this list. It sits on sand now, not cream,
   so a mix toward cream was both the wrong ground and under AA; section 67
   sets it to plain charcoal. */
.visage-index__tagline {
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 88%, var(--wp--preset--color--cream));
}

.is-style-visage-lede {
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 92%, var(--wp--preset--color--cream));
}

@media (hover: hover) and (pointer: fine) {
	.visage-strip__item:hover .visage-strip__cap {
		color: var(--wp--preset--color--charcoal);
	}
}

/* The three studios read as a row only if all three columns start on the same
   rule, so Vancouver's photograph sits below its address rather than above it. */
.visage-city__media {
	margin-top: var(--wp--preset--spacing--xs);
	aspect-ratio: 3 / 2;
}

/* =========================================================================
   39. Hero — light type on a dark photograph
   The new hero is a warm shadowed wall, and charcoal on it measured 2.04:1.
   The scrim now darkens rather than lightens, and the type inverts.
   ========================================================================= */

.visage-hero {
	background-color: var(--wp--preset--color--cocoa-deep);
	color: var(--wp--preset--color--cream);
}

/* The photography changed and this section's premise went with it.
   It was written for a hero shot on a dark ground: 62% black across the left
   of the frame, cream type on top of it. The studio's own photography is
   high-key — porcelain skin on a warm off-white cyclorama — and a black wash
   over that does not make it dramatic, it makes it dirty, and it throws away
   the one quality the whole set is built on.

   The scrim now lifts rather than darkens, and the type is the page's own ink.
   Measured on the settled frame: charcoal on the lifted left of the hero is
   7.6:1, cocoa-deep 6.5:1. */
.visage-hero__media::after {
	background:
		linear-gradient(
			96deg,
			color-mix(in srgb, var(--wp--preset--color--cream) 90%, transparent) 0%,
			color-mix(in srgb, var(--wp--preset--color--cream) 78%, transparent) 34%,
			color-mix(in srgb, var(--wp--preset--color--cream) 24%, transparent) 62%,
			transparent 88%
		);
}

.visage-hero__title,
.visage-hero__sub {
	color: var(--wp--preset--color--charcoal);
}

.visage-hero__title em {
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-hero__link .wp-block-button__link {
	color: var(--wp--preset--color--charcoal);
	border-bottom-color: color-mix(in srgb, var(--wp--preset--color--charcoal) 45%, transparent);
}

.visage-hero__scroll {
	color: var(--wp--preset--color--cocoa-deep);
}

.visage-hero__scroll span {
	color: inherit;
}

@media (hover: hover) and (pointer: fine) {
	.visage-hero__link .wp-block-button__link:hover {
		color: var(--wp--preset--color--clay);
		border-bottom-color: var(--wp--preset--color--clay);
	}
}

/* The header sits over the photograph until the reader scrolls, so it starts
   transparent with light wayfinding and materialises on scroll.

   Only where there is a photograph. Unqualified, this painted cream wayfinding
   onto the cream page on every route except the front page: `/treatments/`,
   `/about/`, `/journal/`, every treatment and every post carried a navigation
   measuring 1:1 against its own background until the reader scrolled. `:has()`
   asks the real question — is a hero behind me — and if a browser does not
   understand it the whole block drops and the navigation is merely dark on
   cream, which is legible everywhere. */
/* The bar used to start fully transparent over the hero and materialise on
   scroll. That worked when the hero was dark and the wayfinding was cream: the
   nav had a dark photograph to sit on wherever it fell. On the studio's own
   high-key photography the wayfinding is charcoal, and the last two nav items
   land on the model's dark hair, where charcoal measures 3.2:1 against it.

   So the bar keeps its material over the hero rather than dropping it. Cream
   at 82% over the darkest thing the photograph contains puts charcoal at
   5.4:1, and a translucent layer with the picture moving under it is the same
   material the bar uses everywhere else on the page. */

/* The bar still starts transparent over the photograph, but the wayfinding on
   it follows the same rule the headline does: the ground under it is light, so
   the ink is dark. */
body:has(.visage-hero) .visage-header:not([data-scrolled='true']) .visage-nav a {
	color: var(--wp--preset--color--charcoal);
}

body:has(.visage-hero) .visage-header:not([data-scrolled='true']) .visage-header__toggle,
body:has(.visage-hero) .visage-header:not([data-scrolled='true']) .visage-header__brand {
	color: var(--wp--preset--color--charcoal);
}

body:has(.visage-hero) .visage-header:not([data-scrolled='true']) .visage-nav a.visage-header__book {
	background: var(--wp--preset--color--charcoal);
	color: var(--wp--preset--color--cream);
}

.visage-header,
.visage-nav a,
.visage-header__book {
	transition:
		background-color var(--wp--custom--duration--panel) var(--wp--custom--ease--out),
		color var(--wp--custom--duration--panel) var(--wp--custom--ease--out),
		border-color var(--wp--custom--duration--panel) var(--wp--custom--ease--out);
}

/* =========================================================================
   40. Scroll-driven composition
   Three moves, each seen once per visit, each with a job:
     - the hero recedes and the page closes over it   (spatial continuity)
     - the black chapter opens from inset to full     (chapter arrival)
     - photographs settle from overscan               (bringing media in)
   All of it runs on native scroll timelines, off the main thread, on
   transform / opacity / clip-path only.
   ========================================================================= */

.visage-hero {
	z-index: var(--wp--custom--layer--base);
}

/* Everything after the hero rides over it.
   `.visage-sections` is the front page's own `post-content`: the sections live
   in the page now rather than in the template, so they are one wrapper deeper
   than they used to be and both depths have to be named. */
.wp-site-blocks main > *:not(.visage-hero),
.visage-sections > *:not(.visage-hero) {
	position: relative;
	z-index: 1;
}

@media (prefers-reduced-motion: no-preference) and (min-width: 48rem) {
	body:not(.is-noanim) .visage-hero {
		position: sticky;
		top: 0;
	}

	@supports (animation-timeline: view()) {
		/* The photograph settles back into its own overscan as the page covers
		   it, so it reads as receding rather than sliding away. */
		body:not(.is-noanim) .visage-hero__media img {
			animation: visage-hero-recede linear both;
			animation-timeline: view();
			animation-range: exit 0% exit 100%;
		}

		body:not(.is-noanim) .visage-hero__inner,
		body:not(.is-noanim) .visage-hero__scroll {
			animation: visage-hero-cover linear both;
			animation-timeline: view();
			animation-range: exit 0% exit 62%;
		}

		/* The black chapter used to open from the sides on entry. It no longer
		   needs to: the threshold above it now darkens the whole plane to black
		   before the chapter arrives, and a clip that shows cream slivers either
		   side of a section the reader has just watched turn black undoes that.
		   The arrival moved up one section; the clip came out. */

		/* Photographs arrive from a little overscan rather than snapping in. */
		body:not(.is-noanim) .visage-stack__a img,
		body:not(.is-noanim) .visage-voices__portrait img,
		body:not(.is-noanim) .visage-city__media img,
		body:not(.is-noanim) .visage-treatment__head .wp-block-post-featured-image img {
			animation: visage-media-settle linear both;
			animation-timeline: view();
			animation-range: entry 0% cover 38%;
		}

		/* Each frame on the rail lifts as it crosses the middle of the screen. */
		body:not(.is-noanim) .visage-strip__item img {
			animation: visage-strip-lift linear both;
			animation-timeline: view(inline);
			animation-range: entry 0% exit 100%;
		}
	}
}

@keyframes visage-hero-recede {
	from {
		transform: scale(1.06);
	}
	to {
		transform: scale(1);
	}
}

@keyframes visage-hero-cover {
	from {
		opacity: 1;
		transform: translate3d(0, 0, 0);
	}
	to {
		opacity: 0;
		transform: translate3d(0, calc(var(--wp--preset--spacing--md) * -1), 0);
	}
}

@keyframes visage-chapter-open {
	from {
		clip-path: inset(0 7% 0 7%);
	}
	to {
		clip-path: inset(0 0 0 0);
	}
}

@keyframes visage-media-settle {
	from {
		transform: scale(1.09);
	}
	to {
		transform: scale(1);
	}
}

@keyframes visage-strip-lift {
	0% {
		transform: scale(0.94);
	}
	45%,
	55% {
		transform: scale(1);
	}
	100% {
		transform: scale(0.94);
	}
}

/* The old parallax pair is superseded by the settle above. */
@keyframes visage-drift-slow {
	from { transform: scale(1.06); }
	to { transform: scale(1); }
}

/* Both rails are grabbable, and neither fights the drag with snapping. */
.visage-voices__rail,
.visage-strip__rail {
	cursor: grab;
	scroll-behavior: auto;
	overscroll-behavior-x: contain;
}

.visage-voices__rail[data-dragging='true'],
.visage-strip__rail[data-dragging='true'] {
	cursor: grabbing;
	scroll-snap-type: none;
	user-select: none;
}

.visage-voices__rail[data-rail='true'],
.visage-strip__rail[data-rail='true'] {
	scroll-snap-type: none;
}

/* The header belongs over the photograph, not on a cream band above it. The
   hero is pulled up under it by exactly the header's height.

   `headerHeight()` measures the bar and overwrites this with the real number;
   what is left here only has to hold for the first paint, so it errs a hair
   tall. Being short shows a cream sliver above the photograph — visible. Being
   long hides a few more pixels of hero under a bar that is already over it —
   not visible, and the hero's own top padding reads the same variable. */
:root {
	--v-header-h: 5.5rem;
}

.wp-site-blocks main > .visage-hero,
.visage-sections > .visage-hero {
	margin-block-start: calc(var(--v-header-h) * -1);
}

.visage-hero__inner {
	padding-block-start: calc(var(--v-header-h) + var(--wp--preset--spacing--xxl));
}

@media (max-width: 40rem) {
	:root {
		--v-header-h: 5rem;
	}
}

/* The CTA needs air from the last nav item. */
.visage-nav a.visage-header__book {
	margin-inline-start: var(--wp--preset--spacing--sm);
}

/* A 42% black wash across the top of the hero used to sit here, so that cream
   wayfinding would clear the photograph behind it. The photography and the
   wayfinding have both changed: the ground under the bar is a high-key cream
   backdrop and the nav on it is charcoal, so the wash has nothing left to do
   except lay a band of grey over the brightest part of the page. Removed
   rather than lightened — there is no scrim that improves this frame. */

/* =========================================================================
   41. The chapter field
   The black plane is painted by a child layer, not by the section. A clipped
   section would let the sticky hero show through at the edges while it opens;
   a clipped field reveals the cream page instead.
   ========================================================================= */

.is-style-visage-plane-black {
	background-color: var(--wp--preset--color--cream);
	isolation: isolate;
}

.is-style-visage-plane-black::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 0;
	background-color: var(--wp--preset--color--black);
	clip-path: inset(0 0 0 0);
}

.is-style-visage-plane-black > * {
	position: relative;
	z-index: 1;
}

/* =========================================================================
   42. The header, actually stuck
   `position: sticky` resolves against the nearest scrolling ancestor's
   containing block, and core wraps a template part in its own `div`. That
   wrapper is exactly as tall as the header, so the bar unstuck itself 101px
   into the page and looked, correctly, like a bug. `display: contents` drops
   the wrapper from layout and hands the header the whole document to stick
   against.
   ========================================================================= */

.wp-site-blocks > .wp-block-template-part:first-child {
	display: contents;
}

/* Sticky needs a hard height to translate against, and the bar is a material
   the page passes under, not a strip cut out of it. */
.visage-header {
	transform: translate3d(0, 0, 0);
	will-change: transform;
	transition:
		transform 420ms var(--wp--custom--ease--out),
		background-color var(--wp--custom--duration--panel) var(--wp--custom--ease--out),
		border-color var(--wp--custom--duration--panel) var(--wp--custom--ease--out);
}

/* Going down, past the first fold, the bar leaves. Any upward intent brings it
   back at once — the reader never hunts for it. */
.visage-header[data-hidden='true'] {
	transform: translate3d(0, -100%, 0);
}

.visage-header[data-condensed='true'] .visage-header__inner {
	padding-block: var(--wp--preset--spacing--xs);
}

/* Condensed. The lockup carries a third line set very small; below about 38px
   it stops being type and starts being texture, so the bar condenses less far
   than it used to when the mark was a plate. */
.visage-header[data-condensed='true'] .visage-header__mark {
	height: 38px;
}

.visage-header__mark {
	transition:
		height 320ms var(--wp--custom--ease--out),
		background-color 420ms var(--wp--custom--ease--out);
}

/* Apple's scroll edge: the chrome is a material with content passing beneath
   it, so it thickens on scroll rather than drawing a divider. */
.visage-header[data-scrolled='true'] {
	backdrop-filter: blur(16px) saturate(140%);
	-webkit-backdrop-filter: blur(16px) saturate(140%);
}

@media (prefers-reduced-transparency: reduce) {
	.visage-header[data-scrolled='true'] {
		background-color: var(--wp--preset--color--cream);
		backdrop-filter: none;
		-webkit-backdrop-filter: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.visage-header {
		transition: background-color 1ms, border-color 1ms;
	}

	.visage-header[data-hidden='true'] {
		transform: none;
	}
}

/* =========================================================================
   43. Chapter rhythm
   Every plane carried `xxl` top and bottom, so two cream sections in a row
   met in 320px of empty cream with nothing in it. Measured at 1440px that was
   roughly 1,300px of the page — the reason the second half read as sparse
   rather than unhurried.

   The page is now paced in chapters instead of sections. A chapter opens with
   full air; the sections that continue it hang off the one gap above them. A
   change of ground (cream to black, cream to blush) is still a full stop and
   keeps its own air on both sides.
   ========================================================================= */

/* A cream plane following another cream plane is a continuation, not a new
   chapter: the gap above it is the previous plane's own bottom padding. */
.is-style-visage-plane-cream + .is-style-visage-plane-cream {
	padding-block-start: 0;
}

/* A heading and the list it introduces are one thing. `.visage-index__head`
   already carries the gap between them. */
.visage-index-head {
	padding-block-end: 0;
}

/* The proof row used to be the index's own footnote on the same cream ground,
   so both sides of that joint gave way and the two read as one block. It is a
   plane of its own now — sand, its own air, its own interaction — so the index
   keeps its full bottom padding and the seam is a change of ground rather than
   a squeezed gap. See section 67. */

/* =========================================================================
   44. The three studios
   Vancouver had a photograph and the other two had a void beside them, which
   read as two columns that failed to load. All three now carry the room, and
   Vancouver keeps the emphasis through its label and its live phone number,
   not through being the only one with an image.
   ========================================================================= */

.visage-city__media {
	margin-top: var(--wp--preset--spacing--sm);
	aspect-ratio: 3 / 2;
	background-color: color-mix(in srgb, var(--wp--preset--color--sand) 60%, var(--wp--preset--color--cream));
}

.visage-city__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* The one the reader is standing in is the one that gets the taller frame. */
@media (min-width: 56rem) {
	.visage-city--here .visage-city__media {
		aspect-ratio: 4 / 5;
	}
}

.visage-city address {
	margin-block-end: 0;
}

/* All three columns carry the same five rows, so a subgrid puts every name,
   every address and every photograph on one line across the fold. Without it
   the two shorter columns left a void where the picture should start. */
@media (min-width: 56rem) {
	.visage-cities {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		grid-template-rows: auto auto auto auto auto;
	}

	.visage-city {
		grid-row: span 5;
		grid-template-rows: subgrid;
		gap: var(--wp--preset--spacing--xs);
	}

	/* Tops align; Vancouver's taller frame hangs below the other two, which
	   reads as the emphasis it is rather than as a column that came up short. */
	.visage-city__media {
		align-self: start;
		margin-top: var(--wp--preset--spacing--sm);
	}
}

/* The label row is empty for the two sister studios; it still has to hold its
   line so the three names start together. */
.visage-city__here:empty::before {
	content: '\200B';
}

.visage-city__go {
	margin-block: 0;
	font-size: var(--wp--preset--font-size--sm);
}

/* =========================================================================
   45. The closing plane
   The blush field ran 900px tall with a heading, a paragraph and two buttons
   in the top third of it. It now closes the page as a composition: the type
   holds the left, and the portrait runs off the bottom edge into the footer's
   black so the two planes interlock rather than stack.
   ========================================================================= */

.visage-cta {
	overflow: clip;
}

.visage-cta__media {
	display: none;
}

@media (min-width: 56rem) {
	.visage-cta__inner {
		position: relative;
		z-index: 1;
		grid-template-columns: repeat(12, minmax(0, 1fr));
		align-items: end;
		column-gap: var(--wp--preset--spacing--md);
	}

	.visage-cta__inner > *:first-child {
		grid-column: 1 / span 6;
	}

	.visage-cta__inner > .visage-cta__actions {
		grid-column: 1 / span 6;
	}

	/* The photograph sets the plane's height; the type should not be stretched
	   across it by the grid's default row distribution. */
	.visage-cta__inner {
		align-content: center;
		row-gap: var(--wp--preset--spacing--lg);
	}

	.visage-cta__media {
		display: block;
		position: absolute;
		right: max(var(--v-gutter), (100% - var(--wp--style--global--wide-size)) / 2);
		bottom: 0;
		width: clamp(18rem, 26vw, 25rem);
		/* Taller than the plane's own padding, so it meets the footer edge. */
		height: calc(100% - var(--wp--preset--spacing--xl));
		z-index: 0;
	}

	.visage-cta__media img {
		display: block;
		width: 100%;
		height: 100%;
		object-fit: cover;
		object-position: 50% 20%;
	}
}

/* =========================================================================
   46. The pinned strip, filling its own viewport
   In pinned mode the rail sat in the middle of a 100svh sticky pane with a
   band of empty cream above and below it. The frames now take the height they
   are given, and the heading sits over them as the one centred moment on the
   page — a change of axis where the page changes from reading to looking.
   ========================================================================= */

@media (prefers-reduced-motion: no-preference) and (min-width: 56rem) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__sticky {
			justify-content: flex-start;
			padding-block: var(--wp--preset--spacing--lg) 0;
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__head {
			text-align: center;
			margin-bottom: var(--wp--preset--spacing--md);
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__head > * {
			max-width: none;
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__rail {
			flex: 1 1 auto;
			align-items: center;
			padding-block-end: var(--wp--preset--spacing--lg);
			min-height: 0;
		}

		/* Fill the pane. The three heights are the composition: the rail reads
		   as a hung wall of pictures, not a row of equal tiles. */
		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__item {
			height: 62%;
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__item--tall {
			height: 82%;
			align-self: center;
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__item--low {
			height: 48%;
			align-self: center;
		}

		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__item img {
			height: 100%;
		}

		/* The caption belongs to the picture; hanging it below a percentage
		   height would push the last item's caption out of the pane. */
		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__cap {
			position: absolute;
			inset: 100% 0 auto 0;
			margin-top: var(--wp--preset--spacing--xxs);
		}
	}
}

/* =========================================================================
   47. The black chapter holds its picture
   Three steps scroll past a 1,500px black plane while the photograph that the
   whole chapter is about scrolled away with the first one. It stays now, so
   the eye has something to come back to between steps.
   ========================================================================= */

@media (min-width: 56rem) {
	/* Sticky is only worth having if there is travel. At `aspect-ratio: 4/5` the
	   photograph was 729px inside a 976px column, so it held for 247px and read
	   as a rounding error. Capping it against the viewport gives it roughly
	   400px to hold for, which is two of the three steps. */
	.visage-signature__media {
		position: sticky;
		top: var(--wp--preset--spacing--lg);
		align-self: start;
	}

	.visage-signature__media .visage-figure {
		aspect-ratio: auto;
		height: min(62vh, 32rem);
	}

	/* The chapter openings are the two places the page is allowed to speak up. */
	.is-style-visage-plane-black .visage-editorial__title,
	.visage-cta__title {
		font-size: var(--wp--preset--font-size--xxl);
	}
}

/* =========================================================================
   48. The voices rail
   The last quote was sliced mid-sentence by the viewport edge with nothing to
   say it continued. Both rails now fade at the ends and say they can be
   dragged, in the same words and the same place.
   ========================================================================= */

.visage-voices__rail {
	-webkit-mask-image: linear-gradient(90deg, #000 0, #000 88%, transparent 100%);
	mask-image: linear-gradient(90deg, #000 0, #000 88%, transparent 100%);
	padding-block-end: var(--wp--preset--spacing--xs);
}

.visage-voices__rail[data-dragging='true'] {
	-webkit-mask-image: none;
	mask-image: none;
}

.visage-strip__hint,
.visage-voices__hint {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--xxs);
	margin: var(--wp--preset--spacing--sm) 0 0;
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	color: var(--wp--preset--color--cocoa-deep);
}

/* Touch devices scroll it; only a pointer gets told to drag. */
@media (hover: none) {
	.visage-strip__hint,
	.visage-voices__hint {
		visibility: hidden;
	}
}

/* =========================================================================
   49. The opening chapter, as a composition
   Two columns of equal weight left roughly 350px of empty cream in the bottom
   left of the fold. The same three elements now sit on the twelve column
   field as a triangle: the text top left, the portrait holding the right two
   thirds, the still life dropped low and pulled back into the left column so
   the eye travels down and across rather than stopping.
   ========================================================================= */

.visage-editorial__portrait,
.visage-editorial__still {
	margin: 0;
	overflow: clip;
	background-color: color-mix(in srgb, var(--wp--preset--color--sand) 55%, var(--wp--preset--color--cream));
}

.visage-editorial__portrait img,
.visage-editorial__still img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

@media (min-width: 56rem) {
	.visage-editorial--three {
		grid-template-columns: repeat(12, minmax(0, 1fr));
		grid-template-rows: auto auto;
		column-gap: var(--wp--preset--spacing--md);
		row-gap: var(--wp--preset--spacing--lg);
		align-items: start;
	}

	.visage-editorial--three > .visage-editorial__col {
		grid-column: 1 / span 5;
		grid-row: 1;
	}

	.visage-editorial--three > .visage-editorial__portrait {
		grid-column: 7 / span 6;
		grid-row: 1 / span 2;
		aspect-ratio: 4 / 5;
	}

	/* Sits below the text and laps one column over the portrait's lower left
	   corner, so the two frames read as one field at two depths rather than as
	   two pictures in two boxes. */
	.visage-editorial--three > .visage-editorial__still {
		grid-column: 4 / span 4;
		grid-row: 2;
		aspect-ratio: 1 / 1;
		z-index: 1;
	}
}

@media (max-width: 55.999rem) {
	.visage-editorial--three {
		gap: var(--wp--preset--spacing--md);
	}

	.visage-editorial--three > .visage-editorial__portrait {
		aspect-ratio: 4 / 5;
	}

	.visage-editorial--three > .visage-editorial__still {
		aspect-ratio: 3 / 2;
	}
}

/* =========================================================================
   50. Index rows
   A last child's bottom margin collapses straight out through a parent with
   no bottom padding, so zeroing the chapter joint also removed the gap the
   heading needed. The section owns that space now instead of the child.
   ========================================================================= */

.visage-index-head {
	padding-block-end: var(--wp--preset--spacing--lg);
}

.visage-index-head .visage-index__head {
	margin-bottom: 0;
}

/* A rule that draws across the row under the cursor. It says "this whole line
   is the target" without moving a single character. */
.visage-index__list > li::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: -1px;
	height: var(--wp--custom--rule--hairline);
	background: var(--wp--preset--color--clay);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform 420ms var(--wp--custom--ease--out);
	pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
	.visage-index__list > li:hover::after,
	.visage-index__list > li:focus-within::after {
		transform: scaleX(1);
	}
}

@media (prefers-reduced-motion: reduce) {
	.visage-index__list > li::after {
		transition: none;
	}
}

/* =========================================================================
   51. Motion, second pass
   Three things this adds, and one it fixes.

   The fix first: the reveal hides content until JavaScript marks it shown, so
   a blocked script left the page blank below the fold. The hiding rule is now
   inside `scripting: enabled`, and the motion layer reveals everything if it
   throws. Content is the default state again; motion is the enhancement.

   Then: the two colour planes arrive the same way, so the page reads as
   chapters rather than as sections; photographs arrive by uncovering rather
   than by the same rise every other element uses; and the opening chapter's
   two frames drift at different rates as the fold passes, which is what makes
   a flat page read as having depth.
   ========================================================================= */

@media (prefers-reduced-motion: no-preference) and (scripting: none) {
	body [data-reveal] {
		opacity: 1;
		transform: none;
	}

	body [data-split='true'] .v-line__in {
		transform: none;
		opacity: 1;
	}
}

/* -- photographs uncover, they do not rise ------------------------------- */

/* The clip goes on the image, never on the observed element. A `clip-path`
   that collapses a box to nothing also collapses its intersection rectangle,
   so an IntersectionObserver watching that same element never fires and the
   reveal never arrives: the picture hides itself permanently. */
@media (prefers-reduced-motion: no-preference) {
	body:not(.is-noanim) figure[data-reveal],
	body:not(.is-noanim) [data-reveal].visage-signature__media {
		opacity: 1;
		transform: none;
	}

	body:not(.is-noanim) figure[data-reveal] img,
	body:not(.is-noanim) [data-reveal].visage-signature__media img {
		clip-path: inset(0 0 0 0);
		transition:
			clip-path 900ms var(--wp--custom--ease--inout) var(--v-delay, 0ms),
			transform 620ms var(--wp--custom--ease--out);
	}

	body:not(.is-noanim) figure[data-reveal]:not([data-shown='true']) img,
	body:not(.is-noanim) [data-reveal].visage-signature__media:not([data-shown='true']) img {
		clip-path: inset(0 0 100% 0);
	}
}

/* -- the second colour plane arrives like the first ---------------------- */

.is-style-visage-plane-blush {
	position: relative;
	background-color: var(--wp--preset--color--cream);
	isolation: isolate;
}

/* `z-index: -1` rather than lifting every child: the plane's photograph is
   absolutely positioned, and a blanket `> * { position: relative }` dropped it
   back into flow at the far left. `isolation` keeps the negative layer inside
   this section. */
.is-style-visage-plane-blush::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	background-color: var(--wp--preset--color--blush);
	clip-path: inset(0 0 0 0);
}

@media (prefers-reduced-motion: no-preference) and (min-width: 48rem) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .is-style-visage-plane-blush::before {
			animation: visage-chapter-open linear both;
			animation-timeline: view();
			animation-range: entry 10% entry 90%;
		}

		/* The opening chapter's two frames pass the fold at different rates.
		   Small enough to read as depth rather than as an effect. */
		body:not(.is-noanim) .visage-editorial__still {
			animation: visage-still-drift linear both;
			animation-timeline: view();
			animation-range: entry 0% exit 100%;
		}
	}
}

@keyframes visage-still-drift {
	from {
		transform: translate3d(0, var(--wp--preset--spacing--md), 0);
	}
	to {
		transform: translate3d(0, calc(var(--wp--preset--spacing--md) * -1), 0);
	}
}

/* -- pressable things answer the press ----------------------------------- */

.visage-index__list > li:active .visage-index__name,
.visage-city:active .visage-city__name {
	color: var(--wp--preset--color--clay);
}

.visage-faq__q:active .visage-faq__mark {
	transform: scale(0.9);
}

.visage-faq__item[open] .visage-faq__q:active .visage-faq__mark {
	transform: rotate(45deg) scale(0.9);
}

/* =========================================================================
   52. Rail edges
   A frame sliced by the viewport with a hard edge reads as a layout that
   broke. Fading the cut says the row continues, which is the whole point of a
   rail.
   ========================================================================= */

/* The mask goes on the pane, not on the rail. A mask lives in its element's
   own coordinate space, so masking the rail sends the soft edge travelling
   left with it and it starts eating frames in the middle of the screen. The
   sticky pane does not move, so the fade stays at the viewport's edges. */
@media (prefers-reduced-motion: no-preference) and (min-width: 56rem) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-strip[data-pinned='true'] .visage-strip__sticky {
			-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 3%, #000 97%, transparent 100%);
			mask-image: linear-gradient(90deg, transparent 0, #000 3%, #000 97%, transparent 100%);
		}
	}
}

.visage-voices__hint {
	color: var(--wp--preset--color--cocoa-deep);
}

/* =========================================================================
   53. The last contrast failure
   Section 38 moved every 11px label onto cocoa-deep and missed one: the "you
   are here" marker stayed on clay, which measures 4.17:1 on cream and needs
   4.5:1 at that size. Darkening clay rather than replacing it keeps the marker
   distinct from the page's other labels, which is its whole job.
   ========================================================================= */

.visage-city__here {
	/* 5.95:1 on cream, measured. */
	color: color-mix(in srgb, var(--wp--preset--color--clay) 80%, var(--wp--preset--color--black));
}

/* =========================================================================
   54. The header takes the ground it is standing on
   The bar was one cream translucent plate for the whole page, so on the black
   chapter and on the footer it sat as a light slab over a dark plane, cutting
   the chapter heading in half on its way past. It now reads the plane under
   its own baseline and inverts, which is also what makes the black chapter
   feel like a room the reader walks into rather than a section they scroll by.

   The material follows Apple's rule for floating chrome: a translucent layer
   with the content moving under it, separated by a short gradient rather than
   a hard 1px divider.
   ========================================================================= */

.visage-header {
	--v-header-ground: color-mix(in srgb, var(--wp--preset--color--cream) 82%, transparent);
	--v-header-ink: var(--wp--preset--color--charcoal);
	--v-header-edge: color-mix(in srgb, var(--wp--preset--color--taupe) 55%, transparent);

	background-color: var(--v-header-ground);
	color: var(--v-header-ink);
	backdrop-filter: blur(18px) saturate(150%);
	border-bottom-color: transparent;
	transition:
		background-color 420ms var(--wp--custom--ease--out),
		color 420ms var(--wp--custom--ease--out),
		transform 460ms var(--wp--custom--ease--out);
}

.visage-header[data-ground='dark'] {
	--v-header-ground: color-mix(in srgb, var(--wp--preset--color--black) 62%, transparent);
	--v-header-ink: var(--wp--preset--color--cream);
	--v-header-edge: color-mix(in srgb, var(--wp--preset--color--cream) 22%, transparent);
}

.visage-header[data-ground='blush'] {
	--v-header-ground: color-mix(in srgb, var(--wp--preset--color--blush) 78%, transparent);
	--v-header-edge: color-mix(in srgb, var(--wp--preset--color--cocoa-deep) 30%, transparent);
}

/* The scroll edge: a short fade under the bar instead of a rule, drawn only
   once there is content passing beneath it. */
.visage-header::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	top: 100%;
	height: var(--wp--preset--spacing--sm);
	background: linear-gradient(
		to bottom,
		var(--v-header-edge),
		transparent
	);
	opacity: 0;
	pointer-events: none;
	transition: opacity 420ms var(--wp--custom--ease--out);
}

.visage-header[data-scrolled='true']::after {
	opacity: 1;
}

/* The bar keeps its own border off; the gradient above is the separation. */
.visage-header[data-scrolled='true'] {
	border-bottom-color: transparent;
}

.visage-nav a,
.visage-header__toggle {
	color: var(--v-header-ink);
}

@media (hover: hover) and (pointer: fine) {
	.visage-header[data-ground='dark'] .visage-nav a:hover {
		color: var(--wp--preset--color--blush);
	}
}

/* The book button is charcoal-on-light everywhere else; on a dark ground it
   would disappear into the bar, so it takes the brand colour instead. */
.visage-header[data-ground='dark'] .visage-header__book {
	background-color: var(--wp--preset--color--blush);
	color: var(--wp--preset--color--charcoal);
}

/* Reading progress rides the bar's ink, not a fixed blush. */
.visage-header[data-ground='dark'] .visage-progress__bar {
	background: var(--wp--preset--color--blush);
}

@media (prefers-reduced-transparency: reduce) {
	.visage-header {
		--v-header-ground: var(--wp--preset--color--cream);
		backdrop-filter: none;
	}

	.visage-header[data-ground='dark'] {
		--v-header-ground: var(--wp--preset--color--black);
	}

	.visage-header[data-ground='blush'] {
		--v-header-ground: var(--wp--preset--color--blush);
	}
}

/* =========================================================================
   55. The signature, held
   The black chapter used to be a sticky photograph beside three paragraphs:
   the ground changed, but nothing else did, so the reader passed through the
   most important claim on the page at the same speed as everything else.

   The media column now pins for the length of the narrative and the frame
   changes under each step. The steps themselves are ordinary flow content —
   the pin is `position: sticky`, so with JavaScript blocked or scroll
   timelines unsupported the section degrades to the column it always was.
   ========================================================================= */

.visage-signature__grid {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	align-items: start;
}

.visage-signature__narrative {
	min-width: 0;
}

.visage-signature__open {
	margin-bottom: var(--wp--preset--spacing--xl);
}

.visage-signature__open .is-style-visage-lede {
	max-width: 34ch;
}

.visage-signature__steps {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* -- the frames ----------------------------------------------------------- */

.visage-signature__frames {
	position: relative;
	display: grid;
	background-color: color-mix(in srgb, var(--wp--preset--color--cocoa-deep) 30%, var(--wp--preset--color--black));
	overflow: clip;
}

.visage-signature__frame {
	grid-area: 1 / 1;
	margin: 0;
	/* Nothing appears from nothing: the incoming frame is already at size and
	   only the last few percent of the scale is left to travel. */
	opacity: 0;
	transform: scale(1.04);
	transition:
		opacity 620ms var(--wp--custom--ease--out),
		transform 900ms var(--wp--custom--ease--out);
}

.visage-signature__frame[data-current='true'] {
	opacity: 1;
	transform: scale(1);
}

.visage-signature__frame img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: var(--wp--custom--radius--image);
}

/* The counter is the only wayfinding the pin needs: it says how much of the
   held moment is left, which is the question a pin always raises. */
.visage-signature__count {
	display: flex;
	align-items: baseline;
	gap: 0.35em;
	margin: var(--wp--preset--spacing--sm) 0 0;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--lg);
	line-height: 1;
	color: var(--wp--preset--color--blush);
	font-variant-numeric: tabular-nums;
}

.visage-signature__count-sep,
.visage-signature__count-total {
	color: color-mix(in srgb, var(--wp--preset--color--cream) 40%, var(--wp--preset--color--black));
}

@media (max-width: 55.999rem) {
	.visage-signature__count {
		display: none;
	}
}

/* -- the pin -------------------------------------------------------------- */

@media (min-width: 56rem) {
	.visage-signature__grid {
		grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
		gap: var(--wp--preset--spacing--xl);
	}

	.visage-signature__media {
		position: sticky;
		/* Clear of the header, and holding its own air below. */
		top: calc(var(--v-header-height, 5.5rem) + var(--wp--preset--spacing--md));
	}

	/* The frame takes most of the fold. A pinned photograph that only fills a
	   third of the height reads as a thumbnail the page forgot to scroll. */
	.visage-signature__frames {
		height: min(
			74svh,
			calc(100svh - var(--v-header-height, 5.5rem) - var(--wp--preset--spacing--xl))
		);
	}

	/* Each step gets enough run for the frame under it to be looked at, and no
	   more. At 62svh the three paragraphs sat 470px apart inside their own
	   boxes, so the fold showed two headings and a void between them; 46svh
	   still holds the frame for roughly half a screen per step and reads as a
	   rhythm rather than as gaps. */
	.visage-signature__steps {
		gap: 0;
	}

	.visage-step {
		min-height: 46svh;
		display: grid;
		align-content: center;
		gap: var(--wp--preset--spacing--xs);
		padding-block: var(--wp--preset--spacing--md);
		padding-bottom: var(--wp--preset--spacing--md);
		border-bottom: 0;
	}

	/* Out-of-focus steps stay legible — this is text, not chrome — but the one
	   the frame belongs to is unmistakable. */
	.visage-step {
		transition:
			opacity 520ms var(--wp--custom--ease--out),
			filter 520ms var(--wp--custom--ease--out);
	}

	/* 0.62 is not a taste value. The step paragraph is cream mixed 82% toward
	   the ground, so at 0.42 it measured 2.83:1 on black and failed AA
	   outright. At 0.62 it measures 5.20:1 and the heading 7.43:1: every step
	   stays readable at every point in the sequence, which is the only version
	   of this effect worth shipping. */
	.visage-signature[data-live='true'] .visage-step:not([data-current='true']) {
		opacity: 0.62;
	}
}

/* The phone gets the sequence too. Dropping the pin below 56rem meant a phone
   reader — most of this audience — saw one photograph and three paragraphs,
   and the other two photographs were never shown at all. The frame is shorter
   and sits above the narrative instead of beside it; everything else is the
   same mechanism. */
@media (max-width: 55.999rem) {
	.visage-signature__media {
		position: sticky;
		top: calc(var(--v-header-height, 4.5rem) + var(--wp--preset--spacing--xs));
		z-index: 1;
		/* The pinned frame must not have the ground show through behind it as
		   the steps pass underneath. */
		background-color: var(--wp--preset--color--black);
		padding-block-end: var(--wp--preset--spacing--xs);
	}

	.visage-signature__frames {
		height: 42svh;
	}

	.visage-step {
		min-height: 44svh;
		display: grid;
		align-content: center;
		border-bottom: 0;
		padding-bottom: 0;
		transition: opacity 520ms var(--wp--custom--ease--out);
	}

	.visage-signature[data-live='true'] .visage-step:not([data-current='true']) {
		opacity: 0.62;
	}
}

/* Below the pin's useful height there is not enough fold left to hold a
   photograph and a paragraph at once. The frame stops travelling and the
   section becomes one picture over a list. */
@media (max-height: 34rem), (max-width: 20rem) {
	.visage-signature__media {
		position: static;
		padding-block-end: 0;
	}

	.visage-signature__frames {
		aspect-ratio: 4 / 5;
		height: auto;
	}

	.visage-step {
		min-height: 0;
	}
}

@media (prefers-reduced-motion: reduce) {
	.visage-signature__frame {
		transition: opacity 200ms linear;
		transform: none;
	}

	.visage-signature__frame[data-current='true'] {
		transform: none;
	}

	.visage-step {
		min-height: 0;
	}
}

body.is-noanim .visage-step {
	min-height: 0;
	opacity: 1;
}

body.is-noanim .visage-signature__frame:not([data-current='true']) {
	display: none;
}

/* =========================================================================
   56. The threshold
   One photograph, held for the length of a scroll, darkening into the black
   chapter below it. Two problems solved with one section: the page had no
   change of scale after the hero, and it cut from cream to black with nothing
   in between.

   The hold is `position: sticky`. The settle and the darkening are a view
   timeline where the browser has one; where it does not, the section is a
   full-bleed photograph with a fixed scrim over it, which is a perfectly good
   thing to be.
   ========================================================================= */

.visage-threshold {
	position: relative;
	/* The extra height is the hold. Everything inside stays one viewport tall.
	   240 rather than 210 because the hold now carries two beats of type; at
	   210 the answer arrived and left inside half a screen of scroll. */
	height: 240svh;
	background-color: var(--wp--preset--color--black);
	color: var(--wp--preset--color--cream);
}

.visage-threshold__hold {
	position: sticky;
	top: 0;
	height: 100svh;
	display: grid;
	/* The layers stretch; only the type sits at the foot. Aligning the grid
	   itself to `end` collapses the scrim to zero height and the photograph
	   loses the weight the type is standing on. */
	align-items: stretch;
	overflow: clip;
	isolation: isolate;
}

.visage-threshold__media {
	grid-area: 1 / 1;
	height: 100%;
	will-change: transform;
}

.visage-threshold__media img,
.visage-threshold__media .visage-slot-empty {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: 0;
	aspect-ratio: auto;
}

/* Two scrims in one layer: a weight at the foot so the type has something to
   sit on from the first frame, and the darkening that carries the plane into
   the next chapter. */
.visage-threshold__scrim {
	position: relative;
	grid-area: 1 / 1;
	background:
		linear-gradient(
			to top,
			color-mix(in srgb, var(--wp--preset--color--black) 92%, transparent) 0%,
			color-mix(in srgb, var(--wp--preset--color--black) 78%, transparent) 22%,
			color-mix(in srgb, var(--wp--preset--color--black) 34%, transparent) 48%,
			transparent 76%
		);
}

.visage-threshold__scrim::after {
	content: '';
	position: absolute;
	inset: 0;
	background-color: var(--wp--preset--color--black);
	opacity: 0;
}

/* The hold is a grid, and a grid item does not inherit the layout width that
   `alignwide` normally resolves against, so the measure is set here directly. */
.visage-threshold__inner {
	display: grid;
	grid-area: 1 / 1;
	align-self: end;
	justify-self: center;
	z-index: 1;
	box-sizing: border-box;
	width: 100%;
	max-width: calc(var(--wp--style--global--wide-size) + (var(--v-gutter) * 2));
	padding-inline: var(--v-gutter);
	padding-block-end: var(--wp--preset--spacing--xl);
}

.visage-threshold .is-style-visage-eyebrow {
	color: var(--wp--preset--color--blush);
	margin: 0 0 var(--wp--preset--spacing--sm);
}

.visage-threshold__title {
	margin: 0;
	max-width: 18ch;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 300;
	/* The chapter that changes the ground under the reader is allowed `xxl`.
	   The photograph is doing the shouting; this only has to be certain. */
	font-size: var(--wp--preset--font-size--xxl);
	line-height: var(--wp--custom--leading--display);
	letter-spacing: var(--wp--custom--tracking--display);
	color: var(--wp--preset--color--cream);
	text-wrap: balance;
}

.visage-threshold__body {
	margin: var(--wp--preset--spacing--sm) 0 0;
	max-width: 46ch;
	color: color-mix(in srgb, var(--wp--preset--color--cream) 84%, var(--wp--preset--color--black));
}

/* -- the hold, driven by scroll ------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-threshold {
			view-timeline-name: --visage-threshold;
			view-timeline-axis: block;
		}

		/* The frame settles rather than moves: a photograph that travels under
		   a pin reads as a slideshow, one that resolves reads as arriving. */
		body:not(.is-noanim) .visage-threshold__media {
			animation: visage-threshold-settle linear both;
			animation-timeline: --visage-threshold;
			animation-range: entry 0% exit 100%;
		}

		/* The darkening only starts once the reader is on their way out, so the
		   photograph gets the whole first half of the hold at full strength. */
		/* The darkening waits for the answer. Closing from exit 10% put the
		   plane a quarter of the way to black before the answer had finished
		   arriving, so the photograph was gone for the half of the hold it was
		   meant to be carrying. It now starts once the answer is settled and
		   finishes under it, which is what makes the hand-off into the black
		   chapter read as one movement. */
		body:not(.is-noanim) .visage-threshold__scrim::after {
			animation: visage-threshold-close linear both;
			animation-timeline: --visage-threshold;
			animation-range: exit 8% exit 92%;
		}

		/* The question leaves, the answer takes its place, and the answer
		   leaves again as the plane closes to black. The ranges overlap on
		   purpose: at any point in the hold something is on its way in or on
		   its way out, so the fold is never a still frame. */
		/* Timed against `contain`, not `exit`.
		   `exit` does not begin until the section's bottom edge reaches the
		   bottom of the viewport — which, on a section this tall, is the moment
		   the pin lets go. Everything keyed to it therefore happened after the
		   held moment was already over: the reader spent the whole hold on the
		   question and met the answer on the way out, if at all.

		   `contain` is the pin's actual lifetime. The question now leaves a
		   third of the way in and the answer owns the rest of the hold. */
		body:not(.is-noanim) .visage-threshold__beat--ask {
			animation: visage-threshold-leave linear both;
			animation-timeline: --visage-threshold;
			animation-range: contain 16% contain 36%;
		}

		body:not(.is-noanim) .visage-threshold__beat--answer {
			animation: visage-threshold-answer linear both;
			animation-timeline: --visage-threshold;
			animation-range: contain 30% exit 62%;
		}

		body:not(.is-noanim) .visage-threshold__gauge span {
			animation: visage-gauge linear both;
			animation-timeline: --visage-threshold;
			animation-range: contain 0% exit 55%;
		}
	}
}

/* A settle plus a slow travel. Scale alone resolves in the first third of the
   hold and then the photograph is a still; the drift keeps it moving for the
   whole of it, at a rate low enough to read as the frame breathing. */
@keyframes visage-threshold-settle {
	from { transform: scale(1.16) translate3d(0, -1.5%, 0); }
	to { transform: scale(1.02) translate3d(0, 1.5%, 0); }
}

/* In, hold, out — one keyframe, because the answer's whole life happens inside
   a single range. */
@keyframes visage-threshold-answer {
	0% {
		opacity: 0;
		transform: translate3d(0, var(--wp--preset--spacing--md), 0);
	}
	12%, 80% {
		opacity: 1;
		transform: none;
	}
	100% {
		opacity: 0;
		transform: translate3d(0, calc(var(--wp--preset--spacing--sm) * -1), 0);
	}
}

@keyframes visage-threshold-close {
	from { opacity: 0; }
	to { opacity: 1; }
}

@keyframes visage-threshold-leave {
	from { opacity: 1; transform: none; }
	to { opacity: 0; transform: translate3d(0, calc(var(--wp--preset--spacing--md) * -1), 0); }
}

/* Without a view timeline there is no hold to pay for: the section is one
   viewport of photograph, and the black chapter follows it directly. */
@supports not (animation-timeline: view()) {
	.visage-threshold {
		height: auto;
	}

	.visage-threshold__hold {
		position: static;
		height: min(100svh, 44rem);
	}
}

@media (prefers-reduced-motion: reduce) {
	.visage-threshold {
		height: auto;
	}

	.visage-threshold__hold {
		position: static;
		height: min(100svh, 44rem);
	}
}

body.is-noanim .visage-threshold {
	height: auto;
}

body.is-noanim .visage-threshold__hold {
	position: static;
	height: min(100svh, 44rem);
}

/* On a phone the hold costs two screens of scrolling for one photograph, and
   a 100svh section with type at its foot is already the strongest thing on
   the page. Keep the picture, drop the hold. */
@media (max-width: 47.999rem) {
	.visage-threshold {
		height: auto;
	}

	.visage-threshold__hold {
		position: static;
		height: 78svh;
	}

	.visage-threshold__title {
		font-size: var(--wp--preset--font-size--xl);
	}
}

/* =========================================================================
   57. The index, arriving
   The list was complete and flat: five rules already drawn, five names already
   set, nothing to watch. The rows now arrive one at a time as the list crosses
   the fold, and the rule under each one draws itself from the left gutter.

   Reveals here enhance content that is already visible. The rows are opaque
   and in place in the stylesheet's own settled state; the animation only runs
   where a view timeline exists to run it against, and it ends on the values
   the settled state already had.
   ========================================================================= */

.visage-index__list > li {
	/* The rule is drawn rather than bordered so it has something to animate. */
	border-bottom: 0;
}

.visage-index__list > li::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: var(--wp--custom--rule--hairline);
	background: var(--wp--preset--color--taupe);
	transform-origin: left center;
}

@media (hover: hover) and (pointer: fine) {
	/* The hovered row's own rule takes the accent, so the row the cursor is on
	   is bounded as well as coloured. */
	.visage-index__list > li:hover::after,
	.visage-index__list > li:focus-within::after {
		background: var(--wp--preset--color--clay);
	}

	.visage-index__list > li::after {
		transition: background-color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
	}
}

@media (prefers-reduced-motion: no-preference) and (min-width: 48rem) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-index__list > li {
			animation: visage-row-arrive linear both;
			animation-timeline: view();
			animation-range: entry 12% entry 78%;
		}

		body:not(.is-noanim) .visage-index__list > li::after {
			animation: visage-rule-draw linear both;
			animation-timeline: view();
			animation-range: entry 18% entry 92%;
		}
	}
}

@keyframes visage-row-arrive {
	from {
		opacity: 0;
		transform: translate3d(0, var(--wp--preset--spacing--sm), 0);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

@keyframes visage-rule-draw {
	from { transform: scaleX(0); }
	to { transform: scaleX(1); }
}

/* On a phone the wide frame becomes a narrow vertical slice of it. Centred,
   that slice is forehead; the lashes the section is about sit lower. */
@media (max-width: 47.999rem) {
	.visage-threshold__media img {
		object-position: 58% 46%;
	}
}

/* =========================================================================
   58. The closing plane, interlocked
   Section 45 set out to run the portrait off the bottom edge into the
   footer's black. It stopped at the edge instead: `overflow: clip` on the
   plane cut it exactly at the seam, so the two planes still stacked.

   The portrait now crosses. Vertical overflow is allowed while the horizontal
   axis stays clipped, the plane is lifted above the footer in the stacking
   order, and the footer takes enough extra head room on wide screens that
   nothing in it ever sits under the crossing.
   ========================================================================= */

@media (min-width: 56rem) {
	.visage-cta {
		/* `clip` on one axis is the only value that leaves the other visible. */
		overflow-x: clip;
		overflow-y: visible;
		position: relative;
		z-index: 1;
	}

	.visage-cta__media {
		/* The crossing is deep enough to read as one plane passing behind
		   another, and short enough that it never reaches the footer's type. */
		bottom: calc(var(--v-cta-cross) * -1);
		height: calc(100% - var(--wp--preset--spacing--lg) + var(--v-cta-cross));
	}

	/* The type used to finish two hundred pixels above the seam, which put the
	   plane's whole lower half out of use. It now closes on the crossing.

	   The plane also takes a floor. Once the manifesto became the chapter's
	   opening movement, this one lost its own top padding and collapsed to the
	   height of four lines of type, which left the closing portrait at 380px —
	   a thumbnail at the end of a page that has been running full-bleed
	   photography for seventeen thousand pixels. */
	.visage-cta {
		padding-block-end: var(--wp--preset--spacing--xl);
		min-height: min(64svh, 34rem);
		display: grid;
		align-content: center;
	}

	.visage-footer {
		padding-block-start: calc(
			var(--wp--preset--spacing--xxl) + var(--v-cta-cross)
		);
	}
}

:root {
	--v-cta-cross: clamp(3rem, 5vw, 6rem);
}

/* =========================================================================
   59. The lead voice
   The quote was centred against a 4:5 portrait, so it floated in the middle of
   the column with two hundred pixels of empty cream above it and the same
   below. Setting it to the portrait's foot makes the pair read as one
   composition — a caption at scale — and gives the quote its own measure so
   it forms a block rather than four long lines.

   The size stays at `xl`. The presence comes from the measure, the leading and
   the position, which is the whole argument of the type scale.
   ========================================================================= */

@media (min-width: 56rem) {
	.visage-voices__lead {
		align-items: end;
	}
}

.visage-voices__quote {
	max-width: 30ch;
	text-wrap: pretty;
}

/* The attribution is the evidence, not a footnote: a rule sets it apart from
   the quote instead of letting it trail off the last line. */
.visage-voices__lead .visage-voice__by {
	margin-top: var(--wp--preset--spacing--sm);
	padding-top: var(--wp--preset--spacing--xs);
	border-top: var(--v-rule);
}

/* =========================================================================
   60. Between pages
   The site is multi-page: the index goes to a treatment, the treatment goes to
   booking. Without a transition each of those is a white flash and a jump to a
   new scroll position, which undoes the continuity the scroll spent eleven
   thousand pixels building.

   Cross-document view transitions cost two declarations and no JavaScript. In
   a browser that does not support them the navigation is exactly what it was.
   ========================================================================= */

@view-transition {
	navigation: auto;
}

@media (prefers-reduced-motion: no-preference) {
	::view-transition-old(root) {
		animation: visage-page-out 180ms var(--wp--custom--ease--out) both;
	}

	::view-transition-new(root) {
		animation: visage-page-in 320ms var(--wp--custom--ease--out) both;
	}
}

/* Reduced motion still gets the flash suppressed — a cross-fade is the gentle
   equivalent, not a reason to fall back to a hard cut. */
@media (prefers-reduced-motion: reduce) {
	::view-transition-old(root),
	::view-transition-new(root) {
		animation-duration: 120ms;
		animation-name: visage-page-fade;
	}
}

/* The header is the one thing that should not travel: it is in the same place
   on both documents, so it stays put while the page under it changes. */
.visage-header {
	view-transition-name: visage-header;
}

@keyframes visage-page-out {
	from { opacity: 1; }
	to { opacity: 0; }
}

@keyframes visage-page-in {
	from {
		opacity: 0;
		transform: translate3d(0, var(--wp--preset--spacing--xs), 0);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

@keyframes visage-page-fade {
	from { opacity: 0; }
	to { opacity: 1; }
}

/* =========================================================================
   61. The manifesto
   The brand runs two voices and the site had only been speaking one of them.
   The expressive voice exists for the tagline, and the tagline was set at 22px
   in the footer under the opening hours. Here it takes the whole fold at the
   largest type on the site.

   Size: the page's own rule is that section headings stay at `xl` and only a
   chapter that changes the ground under the reader may take `xxl`. This is the
   third exception and the last one — one line, one fold, nothing else on the
   plane. It is capped at 6rem, which is a deliberate ceiling: past that a
   headline stops reading as confident and starts reading as loud, which is the
   exact failure the first build was rebuilt to escape.

   The words arrive one at a time as the fold is crossed. Each carries its own
   index and offsets its own range from it, so the reveal is driven by scroll
   position rather than by a timer — the reader sets the pace of the sentence.
   ========================================================================= */

.visage-manifesto {
	--v-manifesto-step: 7%;
	/* The inner sets the height. Left to itself the plane added its own `xxl`
	   top and bottom on top of that, which put 340px of empty blush under a
	   line that is already vertically centred in its own fold. */
	padding-block: 0;
}

.visage-manifesto__inner {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
	align-content: center;
	min-height: min(76svh, 44rem);
	padding-block: var(--wp--preset--spacing--xl);
}

.visage-manifesto__line {
	margin: 0;
	max-width: 16ch;
	font-family: var(--wp--preset--font-family--display);
	font-weight: 300;
	font-size: clamp(2.75rem, 1.1rem + 7vw, 6rem);
	line-height: 1.02;
	/* Cormorant's hairlines collapse when crowded, and they are at their most
	   fragile at the top of the scale. This is the one size on the site where
	   the tracking opens rather than tightens. */
	letter-spacing: -0.005em;
	color: var(--wp--preset--color--charcoal);
	text-wrap: balance;
}

/* Each word is a clip box with the word inside it, so the reveal has an edge
   to travel out from rather than fading in place. */
.visage-manifesto__word {
	display: inline-block;
	overflow: hidden;
	vertical-align: bottom;
	/* Cormorant's descenders and the italic's overshoot both sit below the
	   baseline; a box clipped to the em would shave them. */
	padding-bottom: 0.12em;
	margin-bottom: -0.12em;
}

.visage-manifesto__word-in {
	display: inline-block;
	will-change: transform;
}

.visage-manifesto__by {
	margin: 0;
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 78%, var(--wp--preset--color--blush));
}

@media (prefers-reduced-motion: no-preference) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-manifesto {
			view-timeline-name: --visage-manifesto;
			view-timeline-axis: block;
		}

		body:not(.is-noanim) .visage-manifesto__word-in {
			animation: visage-word-rise linear both;
			animation-timeline: --visage-manifesto;
			animation-range:
				entry calc(16% + (var(--i) * var(--v-manifesto-step)))
				entry calc(62% + (var(--i) * var(--v-manifesto-step)));
		}

		body:not(.is-noanim) .visage-manifesto__by {
			animation: visage-word-fade linear both;
			animation-timeline: --visage-manifesto;
			animation-range: entry 70% entry 100%;
		}
	}
}

@keyframes visage-word-rise {
	from {
		transform: translate3d(0, 106%, 0);
	}
	to {
		transform: none;
	}
}

@keyframes visage-word-fade {
	from { opacity: 0; }
	to { opacity: 1; }
}

/* The blush plane and the booking plane are one chapter in two movements, so
   the joint between them gives way exactly as two cream planes do. */
.is-style-visage-plane-blush + .is-style-visage-plane-blush {
	padding-block-start: 0;
}

.visage-manifesto + .visage-cta .visage-cta__title {
	margin-top: 0;
}

/* The footer's first column now names the studio rather than repeating the
   tagline the manifesto carries two planes above it. */
.visage-footer__place {
	margin: var(--wp--preset--spacing--xs) 0 var(--wp--preset--spacing--sm);
	max-width: 26ch;
	font-size: var(--wp--preset--font-size--sm);
	color: color-mix(in srgb, var(--wp--preset--color--cream) 72%, var(--wp--preset--color--black));
}

/* =========================================================================
   62. A new link form: the arrow link
   The site had two link forms — a filled button and a text link with a
   hairline under it. Neither says "this goes somewhere else" without shouting,
   which is exactly what a secondary path needs.

   The rule draws from the label to the arrow on hover, and the arrow travels
   the last few pixels to meet it. Nothing moves on the label itself, so the
   text never reflows under the cursor.
   ========================================================================= */

.visage-arrow-link {
	display: inline-grid;
	grid-template-columns: auto 1fr auto;
	align-items: center;
	gap: var(--wp--preset--spacing--xs);
	width: fit-content;
	min-width: min(100%, 15rem);
	padding-block: var(--wp--preset--spacing--xxs);
	color: var(--wp--preset--color--charcoal);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	text-decoration: none;
	transition: color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

/* The rule spans the whole gap at rest and brightens on hover. An earlier
   version started it at scaleX(0.24) and grew it: at rest that is a short
   dash floating between a label and an arrow, which reads as a typo rather
   than as a link. */
.visage-arrow-link__rule {
	height: var(--wp--custom--rule--hairline);
	background: currentColor;
	opacity: 0.3;
	transform-origin: left center;
	transition: opacity 380ms var(--wp--custom--ease--out);
}

.visage-arrow-link__go {
	/* The label is set at 11px, and an arrow the same length as the cap height
	   of an 11px word is a tick rather than a direction. */
	width: 1.6em;
	height: 1.6em;
	transition: transform 380ms var(--wp--custom--ease--out);
}

@media (hover: hover) and (pointer: fine) {
	.visage-arrow-link:hover,
	.visage-arrow-link:focus-visible {
		color: var(--wp--preset--color--clay);
	}

	.visage-arrow-link:hover .visage-arrow-link__rule,
	.visage-arrow-link:focus-visible .visage-arrow-link__rule {
		opacity: 1;
	}

	.visage-arrow-link:hover .visage-arrow-link__go,
	.visage-arrow-link:focus-visible .visage-arrow-link__go {
		transform: translate3d(var(--wp--preset--spacing--xxs), 0, 0);
	}
}

.visage-arrow-link:active {
	transform: scale(0.985);
}

/* On the black chapter and the footer the arrow link inverts with everything
   else; `currentColor` on the rule and the glyph does that for free. */
.is-style-visage-plane-black .visage-arrow-link {
	color: var(--wp--preset--color--blush);
}

/* =========================================================================
   63. Academy and wholesale
   The quietest plane on the page, on purpose. Two readers the front page never
   made room for, placed after the booking question is answered so they never
   compete with it, and set as an offset split rather than as another pair of
   equal columns.
   ========================================================================= */

.visage-beyond__grid {
	display: grid;
	gap: var(--wp--preset--spacing--lg);
}

.visage-beyond__media {
	margin: 0;
	overflow: clip;
	background-color: color-mix(in srgb, var(--wp--preset--color--taupe) 45%, var(--wp--preset--color--sand));
}

.visage-beyond__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.visage-beyond__list {
	display: grid;
	gap: var(--wp--preset--spacing--md);
	margin-top: var(--wp--preset--spacing--lg);
}

.visage-beyond__item {
	display: grid;
	gap: var(--wp--preset--spacing--xs);
	justify-items: start;
	padding-top: var(--wp--preset--spacing--sm);
	border-top: var(--wp--custom--rule--hairline) solid
		color-mix(in srgb, var(--wp--preset--color--cocoa-deep) 34%, transparent);
}

.visage-beyond__k {
	margin: 0;
	font-size: var(--wp--preset--font-size--lg);
	font-weight: 400;
}

.visage-beyond__p {
	margin: 0;
	max-width: 44ch;
	color: color-mix(in srgb, var(--wp--preset--color--charcoal) 92%, var(--wp--preset--color--sand));
}

@media (min-width: 56rem) {
	/* Twelve columns, and the split is deliberately off centre: the picture
	   takes five and starts in the gutter, the text takes six and starts one
	   column short of the right edge. Two equal halves would have made this
	   read as important as the chapters either side of it, which it is not. */
	.visage-beyond__grid {
		grid-template-columns: repeat(12, minmax(0, 1fr));
		column-gap: var(--wp--preset--spacing--md);
		align-items: start;
	}

	.visage-beyond__media {
		grid-column: 1 / span 5;
		aspect-ratio: 4 / 5;
		/* Dropped below the heading's baseline so the two blocks interlock
		   rather than starting on the same line. */
		margin-top: var(--wp--preset--spacing--xl);
	}

	.visage-beyond__body {
		grid-column: 7 / span 6;
	}
}

@media (max-width: 55.999rem) {
	.visage-beyond__media {
		aspect-ratio: 3 / 2;
	}
}

/* =========================================================================
   64. The chapter rail
   Seventeen thousand pixels is a deliberate length, but past a certain point
   "how far in am I, and how do I get back to the treatments" stops being
   answerable by scrolling and guessing.

   Ticks at rest, names on hover or focus. It sits in the right margin outside
   the wide container, so it never takes surface from the page, and it stays
   away until the reader is past the first screen — on the hero it would be an
   answer to a question nobody has yet.
   ========================================================================= */

.visage-rail {
	--v-rail-tick: 1.25rem;
	--v-rail-pad: 0.5rem;

	position: fixed;
	top: 50%;
	/* The padding on each link is inside the rail's box, so the margin it is
	   anchored by is reduced by exactly that much. */
	right: calc(clamp(0.75rem, 2vw, 1.75rem) - var(--v-rail-pad));
	transform: translateY(-50%);
	z-index: var(--wp--custom--layer--sticky);
	opacity: 0;
	visibility: hidden;
	transition:
		opacity 420ms var(--wp--custom--ease--out),
		visibility 420ms;
}

.visage-rail[data-visible='true'] {
	opacity: 1;
	visibility: visible;
}

.visage-rail__list {
	display: grid;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
}

.visage-rail__link {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: flex-end;
	/* A tick alone is a small target. The inline padding buys a comfortable hit
	   area; the rail's own `right` is pulled in by the same amount so the ticks
	   still finish where they would have without it. */
	padding-block: 0.45rem;
	padding-inline: var(--v-rail-pad);
	text-decoration: none;
	color: var(--wp--preset--color--cocoa-deep);
}

/* The label is taken out of flow.
   In flow it kept its full width at `opacity: 0`, so the column of ticks was
   pushed left by however long the longest chapter name happened to be, and at
   rest the rail sat well inside the page looking unbalanced against the right
   edge. Absolutely positioned, the rail is only ever as wide as a tick, the
   ticks hug the margin, and the names still open leftward on hover — which is
   the direction there is room in. */
.visage-rail__label {
	position: absolute;
	top: 50%;
	/* Clear of the tick at its longest: the current chapter's tick scales to
	   1.8, so it reaches `--v-rail-tick * 1.8` back from the right edge. */
	right: calc(100% - var(--v-rail-pad) + (var(--v-rail-tick) * 0.8) + var(--wp--preset--spacing--xs));
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--label);
	text-transform: uppercase;
	white-space: nowrap;
	opacity: 0;
	transform: translate3d(var(--wp--preset--spacing--xxs), -50%, 0);
	transition:
		opacity 300ms var(--wp--custom--ease--out),
		transform 300ms var(--wp--custom--ease--out);
	pointer-events: none;
}

/* The tick is the whole rail at rest: a hairline that lengthens and takes the
   accent when its chapter owns the fold. */
.visage-rail__tick {
	flex: 0 0 auto;
	width: var(--v-rail-tick);
	height: var(--wp--custom--rule--hairline);
	background: currentColor;
	opacity: 0.38;
	transform-origin: right center;
	transition:
		opacity 300ms var(--wp--custom--ease--out),
		transform 300ms var(--wp--custom--ease--out),
		background-color 300ms var(--wp--custom--ease--out);
}

.visage-rail__link[aria-current='true'] .visage-rail__tick {
	opacity: 1;
	transform: scaleX(1.8);
	background: var(--wp--preset--color--clay);
}

.visage-rail:hover .visage-rail__label,
.visage-rail[data-open='true'] .visage-rail__label {
	opacity: 0.7;
	transform: translate3d(0, -50%, 0);
}

.visage-rail:hover .visage-rail__link[aria-current='true'] .visage-rail__label,
.visage-rail[data-open='true'] .visage-rail__link[aria-current='true'] .visage-rail__label {
	opacity: 1;
	color: var(--wp--preset--color--clay);
}

@media (hover: hover) and (pointer: fine) {
	.visage-rail__link:hover .visage-rail__tick {
		opacity: 1;
		transform: scaleX(1.5);
	}

	.visage-rail__link:hover .visage-rail__label {
		opacity: 1;
	}
}

.visage-rail__link:focus-visible {
	outline: 2px solid var(--wp--preset--color--clay);
	outline-offset: 3px;
}

/* Over the two dark planes the rail's warm ink disappears. It takes the
   ground's own contrast the same way the header does. */
body:has(.visage-header[data-ground='dark']) .visage-rail__link {
	color: var(--wp--preset--color--cream);
}

body:has(.visage-header[data-ground='dark']) .visage-rail__link[aria-current='true'] .visage-rail__tick {
	background: var(--wp--preset--color--blush);
}

body:has(.visage-header[data-ground='dark']) .visage-rail:hover .visage-rail__link[aria-current='true'] .visage-rail__label {
	color: var(--wp--preset--color--blush);
}

/* The rail needs a margin of its own to sit in. Because alignments are root
   padding aware, that margin is exactly `--v-gutter` (2rem) from 1200px up to
   1280px, and grows from there as the 76rem container stops widening.

   Hiding the rail below 84rem was the wrong call: 2rem is enough for it, it
   just is not enough for the roomy version. Between 75rem and 84rem it takes a
   compact form that fits inside the gutter with clearance, and only below
   75rem — where a fixed element starts covering the reading column rather than
   sitting beside it — does it stand down. */
@media (min-width: 75rem) and (max-width: 83.999rem) {
	.visage-rail {
		--v-rail-tick: 0.75rem;
		--v-rail-pad: 0.25rem;

		right: 0.375rem;
	}
}

@media (max-width: 74.999rem) {
	.visage-rail {
		display: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.visage-rail,
	.visage-rail__tick,
	.visage-rail__label {
		transition-duration: 1ms;
	}
}

/* =========================================================================
   65. The phone booking bar
   A new call-to-action form, and the only one that floats. On a phone the
   header's Book button leaves with the header and the next one is at the foot
   of a seventeen-thousand-pixel page — so for most of the document the
   distance between deciding and booking was the whole rest of the scroll.

   It is a translucent layer with the page moving under it, in Apple's terms:
   the material says "this is chrome, the page is still there", where an opaque
   bar would have said "the page ends here".
   ========================================================================= */

.visage-bookbar {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: var(--wp--custom--layer--sticky);
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: var(--wp--preset--spacing--sm);
	padding: var(--wp--preset--spacing--xs) var(--v-gutter);
	/* Home indicator and gesture bar on a modern phone. */
	padding-bottom: max(var(--wp--preset--spacing--xs), env(safe-area-inset-bottom));
	background-color: color-mix(in srgb, var(--wp--preset--color--cream) 86%, transparent);
	backdrop-filter: blur(18px) saturate(150%);
	box-shadow: 0 -1px 0 color-mix(in srgb, var(--wp--preset--color--taupe) 55%, transparent);

	opacity: 0;
	visibility: hidden;
	transform: translate3d(0, 100%, 0);
	transition:
		opacity 300ms var(--wp--custom--ease--out),
		transform 380ms var(--wp--custom--ease--out),
		visibility 380ms;
}

.visage-bookbar[data-visible='true'] {
	opacity: 1;
	visibility: visible;
	transform: none;
}

.visage-bookbar__note {
	margin: 0;
	font-size: var(--wp--preset--font-size--xs);
	letter-spacing: var(--wp--custom--tracking--label);
	color: var(--wp--preset--color--cocoa-deep);
}

/* The one dark button on a light ground the guideline allows, at the size it
   allows it: small, and sitting on cream. */
.visage-bookbar__go {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 44px;
	padding-inline: var(--wp--preset--spacing--md);
	background: var(--wp--preset--color--charcoal);
	color: var(--wp--preset--color--cream);
	border-radius: var(--wp--custom--radius--button);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	text-decoration: none;
	transition: transform var(--wp--custom--duration--press) var(--wp--custom--ease--out);
}

.visage-bookbar__go:active {
	transform: scale(0.97);
}

@media (prefers-reduced-transparency: reduce) {
	.visage-bookbar {
		background-color: var(--wp--preset--color--cream);
		backdrop-filter: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.visage-bookbar {
		transition: opacity 200ms linear, visibility 200ms;
		transform: none;
	}

	.visage-bookbar[data-visible='true'] {
		transform: none;
	}
}

/* Never on a pointer device: the header is still there, and a floating bar
   over a desktop layout is an advert, not a shortcut. */
@media (min-width: 48rem) {
	.visage-bookbar {
		display: none;
	}
}

/* =========================================================================
   66. The three studios, re-set
   The addresses sat above the photographs and the three columns hold text of
   three different lengths, so the pictures started at three different heights
   and the row finished ragged with 250px of cream hanging under it. Vancouver
   also took a taller frame than the other two, which turned the ragged edge
   into the loudest thing in the section.

   The photograph now leads in all three columns at the same ratio, so the one
   element that is identical across them is what sets the line. Vancouver keeps
   its emphasis where emphasis belongs — the marker, the live phone number and
   a rule at full strength — rather than by being the only one a different size.
   ========================================================================= */

.visage-city {
	position: relative;
	padding-top: var(--wp--preset--spacing--sm);
	border-top: var(--wp--custom--rule--hairline) solid
		color-mix(in srgb, var(--wp--preset--color--charcoal) 18%, transparent);
}

.visage-city--here {
	border-top-color: color-mix(in srgb, var(--wp--preset--color--clay) 70%, transparent);
}

.visage-city__media {
	margin-top: 0;
	margin-bottom: var(--wp--preset--spacing--sm);
	aspect-ratio: 4 / 5;
	overflow: clip;
}

/* Vancouver no longer gets a different frame; it gets a different rule. */
@media (min-width: 56rem) {
	.visage-city--here .visage-city__media {
		aspect-ratio: 4 / 5;
	}
}

.visage-city__name {
	margin: 0;
}

/* A hairline draws under the city name on hover, in the same language as the
   arrow link and the treatment index. One vocabulary for "this is a way out". */
.visage-city__go a {
	position: relative;
	text-decoration: none;
	padding-bottom: 2px;
}

.visage-city__go a::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: var(--wp--custom--rule--hairline);
	background: currentColor;
	transform: scaleX(1);
	transform-origin: left center;
	opacity: 0.4;
	transition:
		opacity var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

@media (hover: hover) and (pointer: fine) {
	.visage-city__go a:hover::after,
	.visage-city__go a:focus-visible::after {
		opacity: 1;
	}
}

/* Both beats occupy the same cell, so the answer arrives where the question
   was rather than below it. The settled state — no scroll timeline, reduced
   motion, `?noanim=1` — shows the question only; the answer is repeated in a
   visually hidden block that is always in the accessibility tree, so nothing
   is lost when the choreography does not run. */
.visage-threshold__beat {
	grid-area: 1 / 1;
	align-self: end;
}

.visage-threshold__beat--answer {
	opacity: 0;
	pointer-events: none;
}

/* The reply is cream like every other line on the page; only the word that is
   actually the answer takes the brand colour. Setting the whole line in blush
   made "Yes." and the duration read as one undifferentiated heading. */
.visage-threshold__beat--answer .visage-threshold__title {
	color: var(--wp--preset--color--cream);
}

@supports not (animation-timeline: view()) {
	.visage-threshold__beat--answer {
		display: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.visage-threshold__beat--answer {
		display: none;
	}
}

body.is-noanim .visage-threshold__beat--answer {
	display: none;
}


/* =========================================================================
   67. The index, re-cut as an index
   The section had the right content in the wrong shape. Five rows, each 76rem
   across, each holding a name at the left and a duration at the right with
   eight hundred pixels of cream between them, and a 15px chevron fading in at
   the far right-hand edge — as far from the name the reader was actually
   looking at as it was possible to put it. The chevron was not weak because it
   was small. It was weak because nothing was near it.

   The row is now built the way an index page in a book is built:

     01   Barbie Perm ·············· 6–8 weeks
          Our signature lash lift, and the reason most people find us.

   The number is the address and is the one thing that never moves. The name's
   track is sized to the name. A leader rule runs from where the name ends to
   where the duration starts, so the gap is doing a job — carrying the eye —
   instead of being a hole. The durations still align right, because the leader
   is the track that takes up the slack.

   And the hover is one mechanical movement rather than a set of states. The
   name and its tagline slide right; a beat later the arrow draws into the room
   they made, pointing at the name from a hand's width away; the leader gives
   up exactly the width the name took, so the line does not stretch; the rule
   under the row draws; and a stroke of clay crosses the whole thing left to
   right, reaching each element in the order the eye reads them.

   Two constraints held all the way through. Nothing here is layout: the slide
   is a transform, the leader shortens by a clip, and the colour is a gradient
   clipped to the glyphs, so a long treatment name still cannot re-wrap under
   the cursor. And nothing is duplicated in the DOM to get the colour, so a
   screen reader is not handed every treatment name twice.
   ========================================================================= */

.visage-index__list > li {
	/* The wash sits at z-index -1 and has to stay above the row's own ground
	   rather than falling through to the section's. */
	isolation: isolate;
}

/* The row-sized hit target is `.visage-index__name a::after`, and it resolves
   against the nearest positioned ancestor. That was the `li`; it is the link
   wrapper now, which is the same box — the padding that makes the row tall
   lives on the wrapper, so `inset: 0` still covers the whole row. */
.visage-index__link {
	position: relative;
}

/* -- the leader ----------------------------------------------------------- */

.visage-index__lead {
	grid-column: 3;
	grid-row: 1;
	align-self: center;
	height: var(--wp--custom--rule--hairline);
	margin-inline: var(--wp--preset--spacing--xs);
	background: currentColor;
	opacity: 0.28;
	/* Clipped from the left by the same distance the name slides, so the two
	   stay in contact. Clip rather than width: a width transition is layout,
	   and this row has a rule against that. */
	clip-path: inset(0 0 0 0);
	transition:
		opacity 260ms var(--wp--custom--ease--out),
		clip-path 300ms var(--wp--custom--ease--out);
	pointer-events: none;
}

/* -- the arrow ------------------------------------------------------------ */

/* It shares the name's cell and starts flush with the name's left edge, which
   is where the name is about to stop being. */
.visage-index__go {
	grid-column: 2;
	grid-row: 1;
	justify-self: start;
	align-self: center;
	display: block;
	width: var(--v-go);
	height: var(--v-go);
	color: var(--wp--preset--color--clay);
	/* Drawn open left to right: the shaft grows and the head lands last, which
	   is the order the row is read in. Clipped to nothing at rest — with the
	   name sitting on top of it, anything else would be a collision. */
	clip-path: inset(0 100% 0 0);
	transition: clip-path 260ms var(--wp--custom--ease--out);
	pointer-events: none;
}

/* The stored files carry no width or height — sizing an icon is the
   stylesheet's job — so without a base size an SVG with only a viewBox falls
   back to the replaced-element default of 300px and takes the row with it. */
.visage-icon {
	display: inline-block;
	width: 1em;
	height: 1em;
	overflow: visible;
	vertical-align: middle;
	flex: none;
}

.visage-index__go svg {
	display: block;
	width: 100%;
	height: 100%;
	/* The glyph's shaft runs the full width of its own viewBox, so the tip
	   sits exactly on the edge and the SVG viewport would otherwise shave half
	   its stroke off. */
	overflow: visible;
}

/* The rule under each row was drawn by an earlier pass as a hover effect —
   `scaleX(0)`, growing from the left — and then replaced by the scroll-driven
   draw in section 57 without the old transform being taken back. Above 48rem
   the scroll animation overrode it and nobody saw the difference; below 48rem
   there is no scroll animation, so every separator in the list was sitting at
   zero width. The rule is full width now, and the drawing on hover is the
   leader's job. */
.visage-index__list > li::after {
	transform: scaleX(1);
}

@media (max-width: 48rem) {
	/* No hover to reveal it, so at this width the arrow is simply present, at
	   the end of the name's own line and at a size that belongs to a thumb. */
	.visage-index__go {
		grid-column: 3;
		grid-row: 1;
		justify-self: end;
		align-self: start;
		margin-block-start: 0.35em;
		color: var(--wp--preset--color--cocoa);
		clip-path: none;
	}

	/* The duration moved to its own line under the tagline, which added a line
	   of height to every row. `lg` on top of that left a hundred pixels of
	   nothing beneath the last word of each one. */
	.visage-index__link {
		padding-block: var(--wp--preset--spacing--md);
	}
}

/* -- the movement --------------------------------------------------------- */

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
	.visage-index__name,
	.visage-index__tagline {
		transition: transform 300ms var(--wp--custom--ease--out);
	}

	.visage-index__list > li:hover .visage-index__name,
	.visage-index__list > li:focus-within .visage-index__name,
	.visage-index__list > li:hover .visage-index__tagline,
	.visage-index__list > li:focus-within .visage-index__tagline {
		transform: translate3d(var(--v-shift), 0, 0);
	}

	/* The tagline follows the name rather than leaving with it. Sixty
	   milliseconds is not a delay anyone reads as a delay; it is the
	   difference between two lines moving and one line dragging the other. */
	.visage-index__list > li:hover .visage-index__tagline,
	.visage-index__list > li:focus-within .visage-index__tagline {
		transition-delay: 40ms;
	}

	/* The room opens before the arrow fills it. Drawing them together reads as
	   the arrow shoving the name aside; drawing it 90ms late reads as the
	   arrow arriving in a space that was already waiting for it. */
	.visage-index__list > li:hover .visage-index__go,
	.visage-index__list > li:focus-within .visage-index__go {
		clip-path: inset(0 0 0 0);
		transition-delay: 90ms;
	}

	/* Only the clip lives here. The leader taking clay is a colour change, and
	   colour changes are still owed to a visitor who has asked for less
	   motion — that rule sits outside the motion gate, below. */
	.visage-index__list > li:hover .visage-index__lead,
	.visage-index__list > li:focus-within .visage-index__lead {
		clip-path: inset(0 0 0 var(--v-shift));
	}

	/* Leaving is one movement, not five. The stagger is the stroke arriving;
	   on the way back everything travels together, and faster. */
	.visage-index__list > li:not(:hover):not(:focus-within) .visage-index__name,
	.visage-index__list > li:not(:hover):not(:focus-within) .visage-index__tagline,
	.visage-index__list > li:not(:hover):not(:focus-within) .visage-index__go,
	.visage-index__list > li:not(:hover):not(:focus-within) .visage-index__lead {
		transition-duration: 180ms;
		transition-delay: 0ms;
	}
}

@media (hover: hover) and (pointer: fine) {
	.visage-index__list > li:hover .visage-index__lead,
	.visage-index__list > li:focus-within .visage-index__lead {
		opacity: 1;
		color: var(--wp--preset--color--clay);
	}
}

/* -- the stroke of clay --------------------------------------------------- */

/* Two stops with a soft seam between them, twice the width of the box. At rest
   the seam is parked exactly on the left edge with the clay half off screen;
   at `background-position: 0` it has crossed to the right edge. The 4% feather
   is the difference between a wipe and a guillotine. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
	.visage-index__n,
	.visage-index__name a,
	.visage-index__meta,
	.visage-index__tagline {
		background-image: linear-gradient(
			90deg,
			var(--wp--preset--color--clay) 0 48%,
			currentColor 52% 100%
		);
		background-size: 200% 100%;
		background-position: 100% 0;
		background-repeat: no-repeat;
		-webkit-background-clip: text;
		background-clip: text;
		-webkit-text-fill-color: transparent;
		transition: background-position 260ms var(--wp--custom--ease--out);
	}

	.visage-index__tagline {
		transition:
			transform 300ms var(--wp--custom--ease--out),
			background-position 260ms var(--wp--custom--ease--out);
	}

	.visage-index__name a {
		transition-duration: 300ms;
	}

	/* Reached in reading order, not all at once — the delays are the stroke's
	   travel time across the row. */
	.visage-index__list > li:hover .visage-index__n,
	.visage-index__list > li:focus-within .visage-index__n {
		background-position: 0 0;
	}

	.visage-index__list > li:hover .visage-index__name a,
	.visage-index__list > li:focus-within .visage-index__name a {
		background-position: 0 0;
		transition-delay: 60ms;
	}

	.visage-index__list > li:hover .visage-index__tagline,
	.visage-index__list > li:focus-within .visage-index__tagline {
		background-position: 0 0;
		transition-delay: 40ms, 100ms;
	}

	.visage-index__list > li:hover .visage-index__meta,
	.visage-index__list > li:focus-within .visage-index__meta {
		background-position: 0 0;
		transition-delay: 160ms;
	}

	.visage-index__list > li:not(:hover):not(:focus-within) .visage-index__n,
	.visage-index__list > li:not(:hover):not(:focus-within) .visage-index__name a,
	.visage-index__list > li:not(:hover):not(:focus-within) .visage-index__meta {
		transition-duration: 180ms;
		transition-delay: 0ms;
	}

	/* The press lands everything at once. A row being clicked has already been
	   chosen, and making the click wait out a 450ms sweep is exactly the
	   latency this section is trying not to have. */
	.visage-index__list > li:active .visage-index__n,
	.visage-index__list > li:active .visage-index__name a,
	.visage-index__list > li:active .visage-index__meta,
	.visage-index__list > li:active .visage-index__tagline,
	.visage-index__list > li:active .visage-index__go,
	.visage-index__list > li:active .visage-index__lead {
		transition-duration: 110ms;
		transition-delay: 0ms;
	}
}

/* `background-clip: text` needs a transparent fill to show, and a transparent
   fill with no background is an invisible treatment name. Neither of these
   contexts paints backgrounds, so both take the fill back. */
@media print, (forced-colors: active) {
	.visage-index__n,
	.visage-index__name a,
	.visage-index__meta,
	.visage-index__tagline {
		background-image: none;
		-webkit-text-fill-color: currentColor;
	}
}

/* -- the wash ------------------------------------------------------------- */

/* The one part of the row that answers the cursor continuously rather than on
   enter and leave: a soft warm light under the hand, tracking the pointer's x
   with its own lag. It is not a card — no edge, no corner, no rectangle. It is
   the row acknowledging that something is resting on it.

   JavaScript writes `transform` straight onto `__glow` rather than setting a
   custom property on the row: a variable on the row restyles every descendant
   on every pointer move, and there are six of them per row. */
.visage-index__wash {
	position: absolute;
	inset: 0;
	z-index: -1;
	overflow: clip;
	pointer-events: none;
	opacity: 0;
	transition: opacity 300ms var(--wp--custom--ease--out);
}

.visage-index__glow {
	position: absolute;
	top: 50%;
	left: 0;
	display: block;
	width: 32rem;
	height: 32rem;
	margin: -16rem 0 0 -16rem;
	background: radial-gradient(
		closest-side,
		color-mix(in srgb, var(--wp--preset--color--blush-soft) 58%, transparent),
		color-mix(in srgb, var(--wp--preset--color--blush-soft) 20%, transparent) 52%,
		transparent 100%
	);
	will-change: transform;
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
	.visage-index__list > li[data-active='true'] .visage-index__wash {
		opacity: 1;
	}
}

/* -- the preview materialises rather than fading -------------------------- */

/* An opacity fade on a photograph the size of a postcard reads as a tooltip
   appearing. Unrolling it from its own centre, out of a blur, reads as the
   picture being brought up out of the row — which is what it is. */
@media (prefers-reduced-motion: no-preference) {
	.visage-preview {
		clip-path: inset(14% 0 14% 0);
		filter: blur(6px);
		transition:
			opacity var(--wp--custom--duration--panel) var(--wp--custom--ease--out),
			clip-path 380ms var(--wp--custom--ease--out),
			filter 300ms var(--wp--custom--ease--out);
	}

	.visage-preview[data-visible='true'] {
		clip-path: inset(0 0 0 0);
		filter: blur(0);
	}
}

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

/* Not "no feedback" — the same feedback, without the travel. The name does not
   slide, so the space the arrow lives in never opens, so the arrow stands down
   above 48rem and the row answers in colour: the text takes clay, the leader
   brightens, the rule under the row takes the accent. Below 48rem the arrow is
   a resting mark rather than a reveal and is unaffected.

   The alternative was to fade the arrow in on top of the name, or to move it to
   the far end of the row for these visitors only. The first is a collision and
   the second is a different design shown to one group of people. */
@media (prefers-reduced-motion: reduce) and (hover: hover) and (pointer: fine) {
	.visage-index__go {
		display: none;
	}
}

@media (prefers-reduced-motion: reduce) {
	.visage-index__list > li:hover .visage-index__name,
	.visage-index__list > li:focus-within .visage-index__name,
	.visage-index__list > li:hover .visage-index__n,
	.visage-index__list > li:focus-within .visage-index__n,
	.visage-index__list > li:hover .visage-index__meta,
	.visage-index__list > li:focus-within .visage-index__meta,
	.visage-index__list > li:hover .visage-index__tagline,
	.visage-index__list > li:focus-within .visage-index__tagline {
		color: var(--wp--preset--color--clay);
	}

	.visage-index__wash {
		display: none;
	}
}

/* `?noanim=1` renders the settled state, and the settled state of a row is the
   one nobody is pointing at. */
body.is-noanim .visage-index__n,
body.is-noanim .visage-index__name a,
body.is-noanim .visage-index__meta,
body.is-noanim .visage-index__tagline {
	background-image: none;
	-webkit-text-fill-color: currentColor;
	transition: none;
}

/* Gated the way the reveal itself is gated: on a pointer device the arrow only
   exists once the name has moved, and nothing moves here. Below 48rem it is a
   plain resting mark and stays. */
@media (hover: hover) and (pointer: fine) {
	body.is-noanim .visage-index__go {
		display: none;
	}
}

body.is-noanim .visage-index__wash {
	display: none;
}

/* -- the threshold's composition ------------------------------------------ */

/* The type block used to be two paragraphs floating at the foot of a
   photograph. It now hangs off a rule, and that rule is the gauge: it fills as
   the hold advances, so the fold answers "is this section doing something, and
   how much of it is left" without a word of chrome. */
.visage-threshold__inner {
	grid-template-columns: auto minmax(0, 1fr);
	column-gap: var(--wp--preset--spacing--sm);
	align-items: end;
}

.visage-threshold__gauge {
	position: relative;
	align-self: stretch;
	width: var(--wp--custom--rule--hairline);
	margin-block: var(--wp--preset--spacing--xs);
	background: color-mix(in srgb, var(--wp--preset--color--cream) 26%, transparent);
	overflow: hidden;
}

.visage-threshold__gauge span {
	position: absolute;
	inset: 0;
	display: block;
	background: var(--wp--preset--color--blush);
	transform: scaleY(0);
	transform-origin: top center;
}

@keyframes visage-gauge {
	from { transform: scaleY(0); }
	to { transform: scaleY(1); }
}

/* Both beats share the second column, so the answer replaces the question in
   place rather than appearing under it. */
.visage-threshold__beats {
	display: grid;
	min-height: 11rem;
	align-items: end;
}

.visage-threshold__beat {
	grid-area: 1 / 1;
	align-self: end;
}

/* "Yes." is the whole answer; the duration is the evidence for it. Giving the
   two different colours is what makes the line read as a reply rather than as
   another heading. */
.visage-threshold__yes {
	color: var(--wp--preset--color--blush);
}

@media (max-width: 47.999rem) {
	.visage-threshold__gauge {
		display: none;
	}

	.visage-threshold__inner {
		grid-template-columns: minmax(0, 1fr);
	}

	.visage-threshold__beats {
		min-height: 0;
	}
}

/* No hold, no gauge: with the section collapsed to one screen there is no
   progress for it to report. */
@supports not (animation-timeline: view()) {
	.visage-threshold__gauge { display: none; }
	.visage-threshold__inner { grid-template-columns: minmax(0, 1fr); }
	.visage-threshold__beats { min-height: 0; }
}

@media (prefers-reduced-motion: reduce) {
	.visage-threshold__gauge { display: none; }
	.visage-threshold__inner { grid-template-columns: minmax(0, 1fr); }
	.visage-threshold__beats { min-height: 0; }
}

body.is-noanim .visage-threshold__gauge { display: none; }
body.is-noanim .visage-threshold__inner { grid-template-columns: minmax(0, 1fr); }
body.is-noanim .visage-threshold__beats { min-height: 0; }

/* =========================================================================
   67. What we can promise
   Two problems, one answer.

   The first was a defect: the treatment index ends on a hairline and every
   fact under it began with a hairline of its own, so the two sections met as
   a pair of parallel rules 25px apart. Stacked dividers always read as a
   mistake. The plane takes sand, a change of ground separates the two
   completely, and every rule in the joint comes out.

   The second was that four sourced promises — the answers to the questions a
   sceptical reader actually has — were set as the quietest thing on the page.
   They are now the one place on the site that responds to the cursor with a
   ground of its own: a blush wash wipes up from the foot of the fact the
   reader is on, its rule draws, and the other three step back.

   Hover paints. Nothing here moves layout, so no fact ever re-wraps under the
   cursor.
   ========================================================================= */

.visage-proof-plane {
	padding-block: var(--wp--preset--spacing--xl);
}

/* The index's own last rule now runs straight into the seam between two
   grounds, where it has nothing left to separate. */
.visage-index:has(+ .visage-proof-plane) .visage-index__list > li:last-child::after {
	display: none;
}

.visage-proof {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
	/* Space separates the facts. There are no rules in this section at all. */
	gap: var(--wp--preset--spacing--xxs);
}

.visage-proof__item {
	position: relative;
	isolation: isolate;
	display: block;
	padding: var(--wp--preset--spacing--md) var(--wp--preset--spacing--sm);
	border-top: 0;
}

.visage-proof__body {
	display: grid;
	align-content: start;
	gap: var(--wp--preset--spacing--xs);
}

/* The wash. `clip-path` rather than a scaled box, because a scale would drag
   the fact's own type with it; the clip moves only the ground. */
.visage-proof__wash {
	position: absolute;
	inset: 0;
	z-index: -1;
	background: var(--wp--preset--color--blush-soft);
	clip-path: inset(100% 0 0 0);
	/* Off the cursor is the system responding, so it is quick; onto it is the
	   reader arriving, so it has room to travel. */
	transition: clip-path 220ms var(--wp--custom--ease--out);
}

.visage-proof__k {
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xl);
	font-weight: 300;
	line-height: var(--wp--custom--leading--heading);
	letter-spacing: var(--wp--custom--tracking--display);
	color: var(--wp--preset--color--charcoal);
	transition: color 240ms var(--wp--custom--ease--out);
}

.visage-proof__rule {
	display: block;
	height: var(--wp--custom--rule--hairline);
	background: var(--wp--preset--color--clay);
	transform: scaleX(0);
	transform-origin: left center;
	transition: transform 220ms var(--wp--custom--ease--out);
}

/* Plain charcoal, not a mix toward the ground. Charcoal on sand measures
   6.36:1; the 84% mix measured 4.44:1, which is under AA before anything has
   even been hovered. */
.visage-proof__v {
	display: block;
	margin: 0;
	max-width: 30ch;
	font-size: var(--wp--preset--font-size--sm);
	line-height: var(--wp--custom--leading--body);
	color: var(--wp--preset--color--charcoal);
}

/* Deliberately no sibling dimming here.
   The treatment index recedes its unhovered rows and it works there, on cream,
   at display size. On sand at 13px it does not: opacity is a blend toward the
   ground, so 0.52 takes this paragraph to 2.2:1 and even 0.72 only reaches
   3.4:1 — under AA either way, for text the reader may well be part-way
   through when they move the cursor. The wash and the rule single out the
   hovered fact well enough on their own. */
@media (hover: hover) and (pointer: fine) {
	.visage-proof__item:hover .visage-proof__wash,
	.visage-proof__item:focus-within .visage-proof__wash {
		clip-path: inset(0 0 0 0);
		transition-duration: 420ms;
	}

	.visage-proof__item:hover .visage-proof__rule,
	.visage-proof__item:focus-within .visage-proof__rule {
		transform: scaleX(1);
		transition-duration: 420ms;
		transition-delay: 60ms;
	}

	.visage-proof__item:hover .visage-proof__k {
		color: var(--wp--preset--color--black);
	}
}

/* Each fact arrives as the row crosses the fold, one after the next. */
@media (prefers-reduced-motion: no-preference) and (min-width: 48rem) {
	@supports (animation-timeline: view()) {
		body:not(.is-noanim) .visage-proof__item {
			animation: visage-proof-arrive linear both;
			animation-timeline: view();
			animation-range:
				entry calc(14% + (var(--i) * 7%))
				entry calc(64% + (var(--i) * 7%));
		}
	}
}

@keyframes visage-proof-arrive {
	from {
		opacity: 0;
		transform: translate3d(0, var(--wp--preset--spacing--sm), 0);
	}
	to {
		opacity: 1;
		transform: none;
	}
}


/* =========================================================================
   68. The open menu decides the bar's colour
   The bar reads the plane under its own baseline (section 54) and takes that
   plane's ink. Open, the plane under it is the panel's cream and nothing else,
   so over the black chapter the links were cream on cream and the whole menu
   was a blank screen. Stated here rather than beside the panel's geometry
   because section 54 is what it has to outrank, and specificity between two
   single-attribute selectors is settled by source order.
   ========================================================================= */

@media (max-width: 60rem) {
	.visage-header[data-menu='open'] {
		--v-header-ground: var(--wp--preset--color--cream);
		--v-header-ink: var(--wp--preset--color--charcoal);
		--v-header-edge: transparent;

		background-color: var(--wp--preset--color--cream);

		/* Nothing is passing under the bar any more; the blur would only sample
		   the panel's own flat cream. */
		backdrop-filter: none;
		-webkit-backdrop-filter: none;
	}

	.visage-header[data-menu='open'] .visage-header__brand,
	.visage-header[data-menu='open'] .visage-header__toggle {
		color: var(--wp--preset--color--charcoal);
	}

	/* The id carries this past the hero's own `:has()` rule, which is heavier
	   than any class chain available here. */
	.visage-header[data-menu='open'] #visage-nav a {
		color: var(--wp--preset--color--charcoal);
	}

	.visage-header[data-menu='open'] #visage-nav a.visage-header__book {
		background: var(--wp--preset--color--charcoal);
		color: var(--wp--preset--color--cream);
	}

	/* The scroll edge belongs to a page being scrolled under the bar. */
	.visage-header[data-menu='open']::after {
		opacity: 0;
	}
}

/* =========================================================================
   69. Art-direction slots are image blocks
   The hero photograph and the two editorial figures were bare markup written
   into a PHP pattern, which is exactly why nobody could swap them without
   opening the theme. They are `core/image` blocks now, so an editor replaces
   them from the media library — and the block library's own figure rules
   arrive with them. Its `margin-block-end` and its `img { height: auto }` both
   have to lose to the composition here, so these are stated element-plus-class
   to outrank `.wp-block-image` on specificity rather than on source order.
   ========================================================================= */

figure.visage-hero__media,
figure.visage-editorial__portrait,
figure.visage-editorial__still {
	margin: 0;
}

figure.visage-hero__media img,
figure.visage-editorial__portrait img,
figure.visage-editorial__still img {
	height: 100%;
	max-width: none;
}

/* The scrim is drawn on the media element itself, which is now the figure. */
figure.visage-hero__media {
	display: block;
}

/* An unfilled slot is an empty figure. Hold the frame so the grid does not
   collapse around a picture that has not been uploaded yet. */
figure.visage-editorial__portrait:not(:has(img)),
figure.visage-editorial__still:not(:has(img)) {
	display: block;
	min-height: 12rem;
}

/* =========================================================================
   70. What changed when the sections became blocks
   Core blocks emit `<p>`, `<h3>` and `<figure>` where these sections used to
   hand-write `<span>`, `<li>` and bare `<div>`. Every rule that follows exists
   because an element changed, not because a design did: the class names, the
   grids and the type are the ones that were already here.
   ========================================================================= */

/* Text that used to be a span, now a paragraph. The design spaces these from
   their own grid and their own rules; a paragraph's default margin is the one
   thing it brings that was never wanted. */
.visage-proof__k,
.visage-proof__v,
.visage-step__n,
.visage-threshold__title > p,
.visage-threshold__body,
.visage-footer__bottom p {
	margin: 0;
}

/* The answer beat's opening word is its own paragraph so it can be blush
   without an editor being handed a colour picker. The two sit on one flex line
   and have to read as one sentence. */
.visage-threshold__title > p {
	font: inherit;
	letter-spacing: inherit;
	color: inherit;
	max-width: none;
}

/* Art-direction slots that are image blocks now. Same reason as section 69:
   the block library's figure margin and `img { height: auto }` have to lose to
   the composition, and element-plus-class settles it on specificity rather
   than on which stylesheet loaded last. */
figure.visage-threshold__media,
figure.visage-beyond__media,
figure.visage-cta__media,
figure.visage-signature__frame {
	margin: 0;
}

figure.visage-threshold__media img,
figure.visage-signature__frame img {
	height: 100%;
	max-width: none;
}

/* -- The arrow link, as a button style ----------------------------------- */

/* Was an anchor wrapping a label, a rule and an inline SVG, which meant the
   label could not be edited without editing the theme. It is a button carrying
   a named style now, and the rule and the glyph are drawn by this rule instead
   of written into the markup.
   The label is an anonymous grid item and comes first at `order: 0`; the two
   pseudo-elements take the columns after it. */
/* The rule is the `1fr` column, and `1fr` of nothing is nothing. Every box
   between the link and the entry it sits in shrink-wraps — `justify-items:
   start` on the entry, then the buttons row's own flex — so the width has to
   be stated at the top of that chain rather than claimed at the bottom. */
.wp-block-buttons:has(.is-style-visage-arrow),
.is-style-visage-arrow {
	width: 15rem;
	max-width: 100%;
}

.is-style-visage-arrow .wp-block-button__link {
	display: grid;
	grid-template-columns: auto 1fr auto;
	align-items: center;
	gap: var(--wp--preset--spacing--xs);
	width: 100%;
	padding-inline: 0;
	padding-block: var(--wp--preset--spacing--xxs);
	background: none;
	border-radius: 0;
	color: var(--wp--preset--color--charcoal);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 600;
	letter-spacing: var(--wp--custom--tracking--wayfinding);
	text-transform: uppercase;
	text-decoration: none;
	transition: color var(--wp--custom--duration--hover) var(--wp--custom--ease--out);
}

/* The rule spans the whole gap at rest and brightens on hover. An earlier
   version started it at scaleX(0.24) and grew it: at rest that is a short dash
   floating between a label and an arrow, which reads as a typo rather than as
   a link. */
.is-style-visage-arrow .wp-block-button__link::before {
	content: '';
	order: 2;
	height: var(--wp--custom--rule--hairline);
	background: currentColor;
	opacity: 0.3;
	transition: opacity 380ms var(--wp--custom--ease--out);
}

/* The label is set at 11px, and an arrow the same length as the cap height of
   an 11px word is a tick rather than a direction. */
.is-style-visage-arrow .wp-block-button__link::after {
	content: '';
	order: 3;
	width: 1.6em;
	height: 1.6em;
	background: currentColor;
	mask: url('../icons/arrow-right.svg') no-repeat center / contain;
	transition: transform 380ms var(--wp--custom--ease--out);
}

@media (hover: hover) and (pointer: fine) {
	.is-style-visage-arrow .wp-block-button__link:hover,
	.is-style-visage-arrow .wp-block-button__link:focus-visible {
		background: none;
		color: var(--wp--preset--color--clay);
	}

	.is-style-visage-arrow .wp-block-button__link:hover::before,
	.is-style-visage-arrow .wp-block-button__link:focus-visible::before {
		opacity: 1;
	}

	.is-style-visage-arrow .wp-block-button__link:hover::after,
	.is-style-visage-arrow .wp-block-button__link:focus-visible::after {
		transform: translate3d(var(--wp--preset--spacing--xxs), 0, 0);
	}
}

/* On the black chapter and the footer the arrow link inverts with everything
   else; `currentColor` on the rule and the glyph does that for free. */
.is-style-visage-plane-black .is-style-visage-arrow .wp-block-button__link {
	color: var(--wp--preset--color--blush);
}

/* -- A section with nothing in it ---------------------------------------- */

/* Three of these sections are a heading over a list that comes from the
   database, and a heading over nothing is worse than no section at all. The
   list used to decide by returning early from its own pattern; now the pattern
   is the heading, so the decision moves here.
   Front end only: in the editor the list is a shortcode placeholder, and a
   section that vanishes cannot be edited. `editor.css` puts them back. */
.visage-strip:not(:has(.visage-strip__item)),
.visage-voices-plane:not(:has(.visage-voice, .visage-voices__lead)),
.visage-marquee:not(:has(.visage-marquee__item)) {
	display: none;
}
