/* Self-hosted, latin-subset woff2. Both faces are SIL Open Font License 1.1;
   the licence text ships beside the files in fonts/OFL-*.txt, which is the OFL's
   only obligation. No third-party request leaves the page, so there is nothing
   to disclose in a privacy notice and no consent gate to build.
   --------------------------------------------------------------------------- */

@font-face {
	font-family: 'Jost';
	src: url('../fonts/jost-v20-latin-200.woff2') format('woff2');
	font-weight: 200;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: 'Jost';
	src: url('../fonts/jost-v20-latin-300.woff2') format('woff2');
	font-weight: 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: 'Inter';
	src: url('../fonts/inter-v20-latin-300.woff2') format('woff2');
	font-weight: 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: 'Inter';
	src: url('../fonts/inter-v20-latin-regular.woff2') format('woff2');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Lithuanian and Latvian diacritics, none of which live in the latin subset
   above. Both families are variable, so one file per family covers every weight
   the page uses; the unicode-range keeps the English and Russian pages from
   downloading it at all.

   MAINTENANCE: these two files are hand-subset (pyftsubset) to the 32 codepoints
   listed below - the complete Lithuanian and Latvian alphabets, upper and lower.
   Google's stock latin-ext subsets were 17 kB and 85 kB; the 85 kB one is
   preloaded on two pages to render text the .no-js fallback paints before it is
   hidden again, which is a lot of critical path for a handful of accents. Subset
   they are 6 kB and 7 kB.
   The range below matches the files exactly, so a diacritic outside Lithuanian
   or Latvian - a Polish ł, a Czech ř - falls through to the fallback stack
   rather than showing a blank box. If the copy ever needs one, add it to BOTH
   the range here and the pyftsubset call that built these files. */

@font-face {
	font-family: 'Jost';
	src: url('../fonts/jost-v20-latin-ext.woff2') format('woff2');
	font-weight: 200 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0100-0101, U+0104-0105, U+010C-010D, U+0112-0113, U+0116-0119, U+0122-0123, U+012A-012B, U+012E-012F, U+0136-0137, U+013B-013C, U+0145-0146, U+0160-0161, U+016A-016B, U+0172-0173, U+017D-017E;
}
@font-face {
	font-family: 'Inter';
	src: url('../fonts/inter-v20-latin-ext.woff2') format('woff2');
	font-weight: 300 400;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0100-0101, U+0104-0105, U+010C-010D, U+0112-0113, U+0116-0119, U+0122-0123, U+012A-012B, U+012E-012F, U+0136-0137, U+013B-013C, U+0145-0146, U+0160-0161, U+016A-016B, U+0172-0173, U+017D-017E;
}

/* Russian. Both families carry a Cyrillic subset, so the display voice survives
   the alphabet change - no substitute face on the Russian page. Same variable
   files, same weight ranges; the unicode-range keeps the other three pages from
   fetching either one. */

@font-face {
	font-family: 'Jost';
	src: url('../fonts/jost-v20-cyrillic.woff2') format('woff2');
	font-weight: 200 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
	font-family: 'Inter';
	src: url('../fonts/inter-v20-cyrillic.woff2') format('woff2');
	font-weight: 300 400;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Ukrainian. Jost cannot set it: the typeface has no Ґ Є І Ї ґ є і ї, so the
   nav's own words - філософія, жінки - would break mid-word into the fallback
   stack. Google's cyrillic subset for Jost is 82 glyphs and contains none of the
   eight; this is the typeface, not our subsetting.
   Raleway stands in, geometric-humanist and close to Jost at weight 200. It is
   hand-subset (pyftsubset, Raleway 4.026) to the complete Ukrainian alphabet,
   upper and lower - not only the words on the page - so future copy works.

   Two things keep this from touching the other four languages. The range below
   is Cyrillic only, so Latin never resolves here and the Ukrainian page still
   sets www.firstlookmodel.com and the language plate in Jost. And the family is
   named in --font-display only under html[lang="uk"] (see below), so the Russian
   page never references it and never fetches the file.

   Why substitute the whole Cyrillic run rather than only the eight missing
   letters: a face is chosen per character, so leaving Jost ahead of Raleway
   would set ф in Jost and і in Raleway inside one word. One face per script is
   the only way to avoid that. */
@font-face {
	font-family: 'RalewayUK';
	src: url('../fonts/raleway-cyrillic-uk.woff2') format('woff2');
	font-weight: 200 300;
	font-style: normal;
	font-display: swap;
	unicode-range: U+0404, U+0406-0407, U+0410-044F, U+0454, U+0456-0457, U+0490-0491;
}

/* Hallmark · macrostructure: Manifesto · nav: N9 edge-aligned · footer: Ft5 statement
 * genre: editorial · theme: custom — vibe: "black stage, gold monogram, quiet couture"
 * paper: oklch(8% 0 0) · accent: oklch(88% 0.19 105) gold-green
 * display: Jost (roman, weight 200) · body: Inter
 * tone: austere / luxury · audience: scouts, clients, applicant models · use: route to one of four pages
 * enrichment: none — the supplied photograph is the artifact
 * extension 2026-08-24: all four destinations open reading dialogs (native <dialog>)
 * pre-emit critique: P5 H5 E5 S5 R5 V4  (panel pass, 2026-08-24)
 */

/* Ukrainian swaps the display voice - see the RalewayUK note above. Scoped to
   the document element, so it reaches every display-face rule at once and no
   other language is affected. Jost stays in the stack behind it for Latin. */
html[lang="uk"] {
	--font-display: 'RalewayUK', 'Jost', 'Helvetica Neue', Arial, 'Nimbus Sans L', sans-serif;
}

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

html, body {
	margin: 0;
	padding: 0;
	overflow-x: clip;
	background: var(--color-paper);
}

body {
	min-height: 100svh;
	color: var(--color-ink);
	font-family: var(--font-body);
	font-weight: 400;
	-webkit-font-smoothing: antialiased;
}

.skip-link {
	position: absolute;
	left: var(--space-md);
	top: var(--space-md);
	z-index: 10;
	transform: translateY(-200%);
	padding: var(--space-xs) var(--space-sm);
	background: var(--color-paper-2);
	color: var(--color-ink);
	font-family: var(--font-display);
	font-size: var(--text-meta);
	letter-spacing: var(--tracking-wider);
	text-decoration: none;
}
.skip-link:focus-visible {
	transform: translateY(0);
	outline: 2px solid var(--color-focus);
	outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Stage — the photograph is the page. 1069x591 source proportions preserved
   on wide viewports; the crop shifts toward the model on narrow ones.
   -------------------------------------------------------------------------- */

.stage {
	position: relative;
	width: 100%;
	min-height: 100svh;
	overflow: hidden;
	container-type: inline-size;
	isolation: isolate;
}

.stage__photo {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 50% 50%;
	z-index: -2;
}

/* Contrast insurance: the nav sits over the photograph's top-left, so that band
   is scrimmed — a future photo swap with a lighter crop cannot kill the type.
   Nothing is set over the lower frame any more, so it stays clean. */
.stage::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: 0;
	height: 26%;
	z-index: -1;
	pointer-events: none;
	background: linear-gradient(to bottom, var(--color-scrim), transparent);
}

/* --------------------------------------------------------------------------
   Link voice — the nav's four destinations: white, wide-tracked, ultralight
   roman. Hover/focus draws a hairline and shifts to monogram gold.
   -------------------------------------------------------------------------- */

.link {
	position: relative;
	display: inline-block;
	color: var(--color-ink);
	font-family: var(--font-display);
	font-weight: 200;
	font-style: normal;
	letter-spacing: var(--tracking-wide);
	text-decoration: none;
	white-space: nowrap;
	transition: color var(--dur-base) var(--ease-out);
}

.link::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: -0.3em;
	height: var(--rule-hair);
	background: var(--color-accent);
	transform: scaleX(0);
	transform-origin: left center;
	transition: transform var(--dur-base) var(--ease-out);
}

.link:hover,
.link:focus-visible { color: var(--color-accent); }

.link:hover::after,
.link:focus-visible::after { transform: scaleX(1); }

.link:focus-visible {
	outline: 2px solid var(--color-focus);
	outline-offset: 6px;
}

.link:active { color: var(--color-accent-deep); }

/* A nav word for the page you are already on. The language plate overrides this
   with the accent below - it lights its current cell rather than dimming it. */
.link[aria-current="page"]:not(.lang .link) { color: var(--color-ink-muted); }

/* --------------------------------------------------------------------------
   Nav — edge-aligned, four destinations spread across the top of the stage
   -------------------------------------------------------------------------- */

.nav {
	position: absolute;
	/* The floor keeps the first nav word clear of the language plate on short
	   viewports, where 8% alone would ride up under it. */
	top: max(8%, 5.25rem);
	left: 0;
	right: 0;
	padding-inline: 7%;
}

.nav__list {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: var(--space-md);
	margin: 0;
	padding: 0;
	list-style: none;
}

.nav .link { font-size: var(--text-nav); }

/* WCAG 2.2 AA 2.5.8 Target Size (Minimum). The words set at --text-nav come out
   23px tall - one pixel short of the 24px floor. The target is grown BEHIND the
   ink rather than around it: padding-block would drag .link::after, which hangs
   off the box at bottom: -0.3em, away from the baseline.
   --tap-min-aa, not the 44px --tap-min: on narrow viewports the nav stacks into
   a column at 23px words plus a 1rem gap, a 39px pitch. A 44px target would
   overlap its neighbours, which makes mis-taps worse than a small target does.
   The pseudo is invisible, and a pseudo is not an element - event.target stays
   the anchor, so the handlers in panels.js are unaffected. */
.nav .link::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	block-size: var(--tap-min-aa);
	transform: translateY(-50%);
}

/* Hallmark · component: language switcher · genre: atmospheric · theme: custom
 * states: default · hover · focus-visible · active · current
 *   (disabled / loading / error / success do not exist for a same-document link
 *   pair - there is no request to be in flight and no failure to report)
 * contrast: white 5.4:1, gold 5.0:1 against the plate over a worst-case light
 *   photograph; higher over this one
 * --------------------------------------------------------------------------
   Language — a two-cell plate at the top right corner. It sits above the nav
   rather than inside it: the four destinations are a set the reader chooses
   from, the language is a setting that governs all four, so mixing them into
   one row would flatten that difference. Each word is a real link to a real
   page (index.html and lt/index.html), not a script toggle, so the choice
   survives a reload, a share and a crawler.
   -------------------------------------------------------------------------- */

.lang {
	position: absolute;
	/* Fixed, not proportional: the plate is a fixed-height control, so a
	   percentage top would drift into the nav as the viewport shortens. */
	top: var(--space-sm);
	right: 7%;
	z-index: 7;
}

/* The field. Everything else on this page sits directly on the photograph and
   survives it, because it is either large (the nav) or scrimmed (the panels).
   Two 15px words are neither, so the pair gets a plate of its own: the stage
   black at 74%, a hairline of accent, and the page's only blur. Squared, like
   every other edge here - the theme has no radius. */
.lang__list {
	display: flex;
	align-items: stretch;
	margin: 0;
	padding: 0;
	list-style: none;
	background: var(--color-lang-field);
	border: var(--rule-hair) solid var(--color-lang-edge);
	backdrop-filter: blur(var(--blur-plate));
	-webkit-backdrop-filter: blur(var(--blur-plate));
}

/* The divider runs the full height of the plate, so the two words read as two
   cells of one control rather than two loose links. Drawn, not typed - nothing
   for a screen reader to announce between the two languages. */
.lang__list li + li {
	border-inline-start: var(--rule-hair) solid var(--color-lang-edge);
}

.lang .link {
	display: flex;
	align-items: center;
	justify-content: center;
	min-width: var(--tap-min);
	min-height: var(--tap-min);
	padding-inline: var(--space-md);
	font-size: var(--text-lang);
	font-weight: 300;
	letter-spacing: var(--tracking-wider);
	text-transform: lowercase;
	/* Full ink, not the muted grey. The word you can switch to is the whole
	   point of the control; it cannot be the dimmest thing on the page. */
	color: var(--color-ink);
	transition: color var(--dur-base) var(--ease-out),
	            background-color var(--dur-base) var(--ease-out);
}

/* The plate already carries the underline's job, so the shared hairline is
   retired inside this control - it would sit under a padded cell and read as a
   stray mark rather than as emphasis. */
.lang .link::after { content: none; }

/* The page you are on is lit, exactly as an open reading column is lit. */
.lang .link[aria-current="page"] {
	color: var(--color-accent);
	background: var(--color-lang-field-on);
	cursor: default;
}

.lang .link:hover,
.lang .link:focus-visible {
	color: var(--color-accent);
	background: var(--color-lang-field-on);
}

.lang .link:active { color: var(--color-accent-deep); }

/* Inset, because an outline offset outward would fall on the photograph and
   lose its own contrast; inside the cell it lands on the plate. */
.lang .link:focus-visible {
	outline: 2px solid var(--color-focus);
	outline-offset: -4px;
}

/* --------------------------------------------------------------------------
   Marks — the three phrases the PSD sets across the monogram's lower edge
   -------------------------------------------------------------------------- */

/* Screen-reader-only heading, so the page has an h1 without printing one
   over the photograph. */
.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* --------------------------------------------------------------------------
   Narrow viewports — crop toward the model, stack both link groups centred.
   The monogram leaves the frame below 700px; type keeps its scrim.
   -------------------------------------------------------------------------- */

@media (max-width: 700px), (max-aspect-ratio: 13/10) {
	/* With no type over the lower frame, the portrait crop can hold all of it. */
	.stage__photo {
		height: 100%;
		object-position: 62% 15%;   /* centres the model and keeps her head in frame */
	}

	/* The lit floor runs under the stacked nav here, so the top scrim carries
	   more weight than it does on desktop. */
	/* The photograph now fills the whole frame, so the stacked nav sits over the
	   model's lit mid-tones — the scrim carries more weight here than it did
	   when the lower frame was plain black. */
	.stage::before {
		height: 54%;
		background: linear-gradient(to bottom,
			var(--color-scrim-heavy) 0%,
			var(--color-scrim-mid) 58%,
			transparent 100%);
	}
	/* Same floor as desktop, one rung lower: the stacked nav starts below the
	   language plate rather than beside it. */
	.nav { top: max(6%, 4.5rem); padding-inline: var(--space-lg); }

	.lang { top: var(--space-md); right: var(--space-lg); }

	/* The nav band's scrim already darkens this corner, so the plate can drop
	   its blur here and keep the corner cheap to paint. */
	.lang__list {
		backdrop-filter: none;
		-webkit-backdrop-filter: none;
	}

	.lang .link { padding-inline: var(--space-sm); }

	.nav__list {
		flex-direction: column;
		align-items: center;
		gap: var(--space-md);
	}

	.nav .link { font-size: var(--text-nav); }
}

/* Tablet portrait — more room at the top for the stacked nav. */
@media (min-width: 500px) and (max-aspect-ratio: 13/10) {
	.nav { top: 5%; }
}

/* --------------------------------------------------------------------------
   Reading field — no box. Clicking a destination dims the photograph and sets
   the copy directly on that darkened ground: one column, strict measure,
   generous leading. There is no frame, no shadow and no card, because a
   bordered rectangle floating over a photograph is lightbox vocabulary and the
   hero already carries its own hard split. The type is the layer.
   -------------------------------------------------------------------------- */

/* Non-modal on purpose: a modal dialog puts itself in the top layer, which
   would bury the nav under its own backdrop. Keeping it out of the top layer
   leaves the four destinations lit and clickable, so a reader can move between
   them without closing anything first. Esc, click-away and focus return are
   handled in panels.js, since a non-modal dialog gives none of them for free. */
.panel {
	position: fixed;
	inset: 0;
	z-index: 5;
	width: 100%;
	height: 100%;
	max-width: none;
	max-height: none;
	margin: 0;
	padding: 0;
	border: 0;
	background: transparent;
	color: var(--color-ink);
	overflow: clip;
	pointer-events: none;   /* the field is a layer, not a lid */
}

.panel[open] .panel__frame { pointer-events: auto; }

/* The scrim is the whole treatment: it takes the photograph down far enough
   that white type holds over it, and nothing else is drawn. It sits under the
   nav, so the destinations keep their full contrast. */
.scrim {
	position: absolute;
	inset: 0;
	z-index: 1;
	background: var(--color-scrim-read);
	opacity: 0;
	pointer-events: none;
	transition: opacity var(--dur-base) var(--ease-out);
}

.is-reading .scrim { opacity: 1; }

.nav { z-index: 6; }

.panel__frame {
	position: absolute;
	/* The column drops from the word that opened it: panels.js writes
	   --panel-left / --panel-top from the opener's own box. The fallbacks below
	   hold if the script never runs. */
	top: var(--panel-top, 22%);
	bottom: auto;              /* the column hugs its copy */
	left: var(--panel-left, auto);
	right: var(--panel-right, 6.3%);
	width: min(44ch, 42%);     /* measure first: 44ch keeps 60–70 characters */
	max-height: var(--panel-max-height, 68%);
	display: flex;
	flex-direction: column;
	gap: var(--space-lg);
	opacity: 0;
	transform: translateY(6px);
	transition:
		opacity var(--dur-base) var(--ease-out),
		transform var(--dur-base) var(--ease-out);
}

.panel[open] .panel__frame {
	opacity: 1;
	transform: translateY(0);
}

/* The title is a quiet marker, not a dialog header: the nav word above is
   already lit, so this only names the column. */
.panel__head { display: block; }

.panel__title {
	margin: 0;
	font-family: var(--font-display);
	font-weight: 300;
	font-style: normal;
	font-size: var(--text-meta);
	letter-spacing: var(--tracking-wider);
	color: var(--color-ink-muted);
}

.panel__scroll {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding-bottom: var(--space-xs);
	/* currentColor is the ink white — an opaque alpha stop, not a colour
	   decision. It fades the copy into the field's lower edge so a mid-scroll
	   position reads as "there is more". */
	mask-image: linear-gradient(to bottom,
		currentColor 0,
		currentColor calc(100% - var(--space-lg)),
		transparent 100%);
	scrollbar-width: thin;
	scrollbar-color: var(--color-rule) transparent;
}
.panel__scroll:focus-visible {
	outline: 2px solid var(--color-focus);
	outline-offset: 6px;
}
.panel__scroll::-webkit-scrollbar { width: 2px; }
.panel__scroll::-webkit-scrollbar-track { background: transparent; }
.panel__scroll::-webkit-scrollbar-thumb { background: var(--color-rule); }

/* Flow: a label and its value are one unit; a tick opens a new one. Scoped
   `.panel .panel__x` so these outrank `.panel p` (class + type). */
.panel .panel__scroll > * + * { margin-top: var(--space-md); }
.panel .panel__lead + * { margin-top: var(--space-lg); }
.panel .panel__label + p,
.panel .panel__label + address,
.panel .panel__label + ul { margin-top: var(--space-2xs); }
.panel .panel__label + .panel__note { margin-top: var(--space-md); }
.panel .panel__scroll > * + .panel__rule { margin-top: var(--space-xl); }
.panel .panel__rule + * { margin-top: var(--space-xl); }

.panel p,
.panel li {
	margin: 0;
	color: var(--color-ink-body);
	font-size: var(--text-body);
	font-weight: 400;
	line-height: var(--leading-body);
	text-wrap: pretty;
	overflow-wrap: break-word;
}

/* The lead is the column's entry point: larger, in full ink, tighter leading. */
.panel .panel__lead {
	color: var(--color-ink);
	font-size: var(--text-lead);
	font-weight: 300;
	line-height: var(--leading-tight);
}

/* A postal address is the payload of its column, not an aside — it takes the
   lead size, in the display face, with stack leading for its short lines. */
.panel address {
	font-family: var(--font-display);
	font-weight: 300;
	font-style: normal;
	font-size: var(--text-lead);
	line-height: var(--leading-stack);
	letter-spacing: var(--tracking-wide);
	color: var(--color-ink);
}

.panel .panel__label {
	margin: 0;
	font-family: var(--font-display);
	font-weight: 300;
	font-size: var(--text-body);
	letter-spacing: var(--tracking-wider);
	color: var(--color-ink);
}

.panel .panel__note {
	font-size: var(--text-meta);
	color: var(--color-ink-muted);
	line-height: var(--leading-stack);
}

.panel a {
	color: var(--color-accent);
	text-decoration: none;
	border-bottom: var(--rule-hair) solid var(--color-accent-deep);
	overflow-wrap: break-word;
	transition: color var(--dur-fast) var(--ease-out);
}
.panel a:hover,
.panel a:focus-visible { color: var(--color-ink); }
.panel a:focus-visible {
	outline: 2px solid var(--color-focus);
	outline-offset: 3px;
}

.panel__tips {
	margin: 0;
	padding: 0;
	list-style: none;
}
.panel .panel__tips li {
	position: relative;
	padding-left: var(--space-lg);
}
.panel .panel__tips li + li { margin-top: var(--space-sm); }
.panel .panel__tips li::before {
	content: "—";
	position: absolute;
	left: 0;
	color: var(--color-accent-deep);
}

/* A short tick, not a full divider: enough to break a thought without drawing
   a line across the column. */
.panel__rule {
	width: var(--space-xl);
	height: var(--rule-hair);
	margin: 0;
	border: 0;
	background: var(--color-accent-deep);
}

/* Close reads as a word at the end of the column, in the nav's own voice. */
.panel__close {
	align-self: flex-start;
	position: relative;      /* anchors the 2.5.8 target below */
	padding: 0;
	border: 0;
	background: transparent;
	color: var(--color-ink-muted);
	font-family: var(--font-display);
	font-weight: 300;
	font-size: var(--text-meta);
	letter-spacing: var(--tracking-wider);
	line-height: 1;
	cursor: pointer;
	transition: color var(--dur-fast) var(--ease-out);
}
/* WCAG 2.2 AA 2.5.8. The close word is 18px tall; ::after already carries the
   hover rule, so ::before grows the target behind the ink. Held to 24px rather
   than 44px: the button sits directly under .panel__scroll, and a taller target
   would float over the last line of the column and swallow clicks meant for it. */
.panel__close::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	top: 50%;
	block-size: var(--tap-min-aa);
	transform: translateY(-50%);
}

.panel__close::after {
	content: "";
	display: block;
	height: var(--rule-hair);
	margin-top: 0.3em;
	background: var(--color-accent);
	transform: scaleX(0);
	transform-origin: left center;
	transition: transform var(--dur-base) var(--ease-out);
}
.panel__close:hover,
.panel__close:focus-visible { color: var(--color-accent); }
.panel__close:hover::after,
.panel__close:focus-visible::after { transform: scaleX(1); }
.panel__close:active { color: var(--color-accent-deep); }
.panel__close:focus-visible {
	outline: 2px solid var(--color-focus);
	outline-offset: 4px;
}

/* The word that opened the column stays lit while it is open. */
.link[aria-expanded="true"] { color: var(--color-accent); }
.link[aria-expanded="true"]::after { transform: scaleX(1); }

/* Narrow viewports — the stacked nav fills the top of the frame, so a column
   opened there would land on top of it. The nav steps aside while reading and
   the close word moves to the head of the column, where it reads as "back"
   without scrolling a long text to reach it. */
@media (max-width: 700px), (max-aspect-ratio: 13/10) {
	.nav {
		transition: opacity var(--dur-base) var(--ease-out);
	}

	.is-reading .nav,
	.is-reading .lang {
		opacity: 0;
		visibility: hidden;
		pointer-events: none;
	}

	.panel__frame {
		top: var(--space-lg);
		bottom: var(--space-lg);
		left: var(--space-lg);
		right: var(--space-lg);
		width: auto;
		max-width: 44ch;
		max-height: none;
		margin-inline: auto;
		gap: var(--space-md);
	}

	.panel__close {
		order: -1;              /* head of the column, not its end */
		align-self: flex-end;
	}
}

@media (prefers-reduced-motion: reduce) {
	.link,
	.lang .link,
	.link::after { transition-duration: 0ms; }
	.link::after { transition: opacity 120ms linear; }

	/* The reading field keeps a crossfade only — no spatial move. */
	.panel__frame { transition: opacity var(--dur-fast) linear; transform: none; }
	.panel[open] .panel__frame { transform: none; }
	.panel__close::after { transition: opacity var(--dur-fast) linear; }
	.panel__scroll { scroll-behavior: auto; }
}

/* --------------------------------------------------------------------------
   No-JS fallback. The faithful per-page build does not ship, so the anchors
   have nowhere to go without script — instead every panel renders as a static
   section stacked under the photograph, and the in-page anchors reach them.
   panels.js removes .no-js, so this never applies when script runs.
   -------------------------------------------------------------------------- */

.no-js .panel {
	position: static;
	display: block;
	width: auto;
	height: auto;
	max-width: none;
	max-height: none;
	background: var(--color-paper);
	color: var(--color-ink);
}

.no-js .panel__frame {
	position: static;
	width: min(44ch, 100% - var(--space-lg) * 2);
	max-width: none;
	max-height: none;
	margin: 0 auto;
	padding-block: var(--space-xl);
	opacity: 1;
	transform: none;
}

.no-js .panel__close { display: none; }
.no-js .panel__scroll { overflow: visible; mask-image: none; }
