/* Jakob Nazarenus, final variant. Adapts to the browser's colour scheme; the
   ASCII portrait fills the page behind the content and is the only moving part. */

:root {
	--bg: #ffffff;
	--surface: #f4f4f4;
	--ink: #000000;
	--muted: #333333;
	--sub: #4d4d4d;
	--faint: #6e6e6e;
	--line: #e6e6e6;
	--rule: #b3b3b3;
	--accent: #000000;
	--accent-quiet: #6e6e6e;
	--ghost: #b4b4b4;
	--art-fade: 0.42;

	--sans: ui-sans-serif, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	--mono: ui-monospace, "DejaVu Sans Mono", "SFMono-Regular", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg: #000000;
		--surface: #141414;
		--ink: #ffffff;
		--muted: #cccccc;
		--sub: #b3b3b3;
		--faint: #8c8c8c;
		--line: #262626;
		--rule: #4d4d4d;
		--accent: #ffffff;
		--accent-quiet: #8c8c8c;
		--ghost: #5c5c5c;
	}
}

* { box-sizing: border-box; }

html {
	-webkit-text-size-adjust: 100%;
	scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
	margin: 0 auto;
	max-width: 1080px;
	padding: 0 clamp(1.15rem, 4vw, 2.5rem) 4rem;
	background: var(--bg);
	color: var(--ink);
	font-family: var(--sans);
	font-size: 15.5px;
	line-height: 1.58;
}

a { color: var(--accent); text-underline-offset: 2px; text-decoration-thickness: 1px; }
a:hover { text-decoration-thickness: 2px; }
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }

/* ── Backdrop ─────────────────────────────────────────────── */

.backdrop {
	position: fixed;
	/* Not inset: 0. On iOS the URL bar collapses as you scroll, which changes
	   the viewport height, moves this box's centre, and drags the portrait down
	   with it. lvh is the height with the bar collapsed and never changes, so
	   the box stays put whatever the bar is doing. */
	top: 0;
	left: 0;
	right: 0;
	height: 100vh;
	height: 100lvh;
	z-index: 0;
	display: grid;
	place-items: center;
	overflow: hidden;
	/* Never swallow a click; the renderer tracks the pointer on the window. */
	pointer-events: none;
	user-select: none;
	opacity: var(--art-fade);
}

#backdrop-art {
	/* Placed rather than grid-centred: once the grid is larger than the
	   viewport, a scroll container start-aligns it instead of centring. The
	   renderer's transform carries the matching translate(-50%, -50%). */
	position: absolute;
	left: 50%;
	top: 50%;
	margin: 0;
	font-family: var(--mono);
	/* Placeholders. The renderer lays the type out at a fixed size, sets the
	   line height from the font it measures, and scales the result to fit. */
	font-size: 16px;
	line-height: 1;
	letter-spacing: 0;
	white-space: pre;
	color: var(--ghost);
	/* Dissolve the shoulders and crown rather than cutting them off. */
	-webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 9%, #000 74%, transparent 97%);
	mask-image: linear-gradient(to bottom, transparent 0%, #000 9%, #000 74%, transparent 97%);
}

/* Content rides above the backdrop. */
header, main, footer { position: relative; z-index: 1; }

/* ── Hero ─────────────────────────────────────────────────── */

/* The first screen is the portrait plus the identity block, with the scroll
   cue holding the floor. svh rather than vh so mobile browser chrome does not
   push the cue out of sight. */
.hero {
	min-height: 100vh;
	min-height: 100svh;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	padding: clamp(3rem, 9vw, 6rem) 0 clamp(1.75rem, 5vh, 3rem);
	/* The wash below fades out past the text block, so its soft edge reaches
	   beyond the column and, on a narrow screen, past the viewport. Clip it
	   here rather than letting it open a horizontal scrollbar. Clipping on the
	   hero works where clipping on the root does not, because the root's
	   overflow propagates to the viewport instead of applying to the box. */
	overflow-x: clip;
}

.hero-foot {
	/* Positioned so the scrim resolves against this box. The nudge below sets a
	   transform, which would otherwise make this the scrim's containing block
	   only while the animation runs, moving the halo off the words mid-hop. */
	position: relative;
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: clamp(1rem, 4vw, 3rem);
	/* A hop a few seconds in, to say the page carries on below. The pause lives
	   inside the keyframes, so repeats are spaced out rather than running three
	   hops back to back. */
	animation: hero-nudge 8s 4s 3 both;
}

/* src/nudge.js sets this once the reader scrolls: the prompt has been answered. */
.hero-foot.is-settled { animation: none; }

@keyframes hero-nudge {
	0%     { transform: translateY(0);     animation-timing-function: cubic-bezier(0.15, 0.85, 0.25, 1); }
	3.6%   { transform: translateY(-38px); animation-timing-function: cubic-bezier(0.6, 0, 0.9, 0.35); }
	9.5%   { transform: translateY(0);     animation-timing-function: cubic-bezier(0.15, 0.85, 0.25, 1); }
	12.2%  { transform: translateY(-15px); animation-timing-function: cubic-bezier(0.6, 0, 0.9, 0.35); }
	15%    { transform: translateY(0);     animation-timing-function: cubic-bezier(0.15, 0.85, 0.25, 1); }
	16.8%  { transform: translateY(-5px);  animation-timing-function: cubic-bezier(0.6, 0, 0.9, 0.35); }
	18.5%  { transform: translateY(0); }
	100%   { transform: translateY(0); }
}

.hero-text { position: relative; }

/* The wash that keeps the type readable over the portrait. It is built from
   the text's real line boxes, measured at runtime, rather than from one
   rectangle around the whole block: a paragraph's ragged right edge and the
   short eyebrow line leave very different shapes, and a bounding box covers
   large areas that have nothing in them. One blur over the union of the line
   boxes turns them into a single soft halo that follows the words.
   src/scrim.js fills this in; with no JavaScript there is no wash, and the
   portrait stays at its quiet strength instead. */
.hero-scrim {
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	filter: blur(16px);
}

.hero-scrim > i {
	position: absolute;
	display: block;
	background: var(--bg);
	border-radius: 9px;
}

.scroll-cue {
	flex: none;
	display: inline-flex;
	align-items: center;
	padding: 0.5rem 0.65rem;
	margin-right: -0.65rem; /* optically align the glyph with the column edge */
	color: var(--faint);
	border-radius: 3px;
}
.scroll-cue:hover { color: var(--ink); }
.scroll-cue:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.scroll-cue svg {
	display: block;
	width: 26px;
	height: auto;
	animation: cue-bob 2.4s ease-in-out infinite;
}

@keyframes cue-bob {
	0%, 100% { transform: translateY(0); }
	50% { transform: translateY(5px); }
}

@media (prefers-reduced-motion: reduce) {
	.scroll-cue svg,
	.hero-foot { animation: none; }
}

/* "collaboration" is dropped where the status line would otherwise wrap to two
   lines. 560px rather than the 620px used elsewhere: the phrase still fits
   above that, and there is no reason to drop a word from a line that fits. */
@media (max-width: 560px) {
	.wide-only { display: none; }
}

/* Visible to screen readers, absent from the layout. */
.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.eyebrow, .status {
	margin: 0;
	font-family: var(--mono);
	font-size: 11.5px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--faint);
}

.hero h1 {
	margin: 0.55rem 0 0;
	font-size: clamp(2.1rem, 5.2vw, 3.1rem);
	font-weight: 660;
	letter-spacing: -0.028em;
	line-height: 1.02;
}

.lede {
	margin: 0.9rem 0 1.1rem;
	max-width: 48ch;
	color: var(--muted);
	font-size: 16px;
	text-wrap: pretty;
}

.status { color: var(--accent-quiet); }

.links {
	list-style: none;
	display: flex;
	flex-wrap: wrap;
	gap: 0 0.6rem;
	margin: 1.1rem 0 0;
	padding: 0;
	font-family: var(--mono);
	font-size: 12.5px;
}
.links li + li::before { content: "·"; color: var(--rule); margin-right: 0.6rem; }

/* ── Sections ─────────────────────────────────────────────── */

section { padding-top: clamp(2rem, 4vw, 3rem); }

.sec-head {
	display: flex;
	align-items: baseline;
	gap: 0.75rem;
	padding-bottom: 0.55rem;
	border-bottom: 1px solid var(--rule);
}

.sec-head h2 {
	margin: 0;
	font-size: 1.05rem;
	font-weight: 620;
	letter-spacing: -0.01em;
}

.num {
	font-family: var(--mono);
	font-size: 11.5px;
	letter-spacing: 0.1em;
	color: var(--faint);
}

.sec-note {
	margin-left: auto;
	font-family: var(--mono);
	font-size: 11.5px;
	color: var(--faint);
	text-align: right;
}

/* ── Rows ─────────────────────────────────────────────────── */

.rows { list-style: none; margin: 0; padding: 0; }

.rows > li {
	display: grid;
	grid-template-columns: 5rem minmax(0, 1fr);
	gap: 0 1.25rem;
	padding: 1rem 0;
	border-bottom: 1px solid var(--line);
}
.rows > li:last-child { border-bottom: 0; }
.rows.compact > li { padding: 0.7rem 0; }

.yr {
	font-family: var(--mono);
	font-size: 12px;
	color: var(--faint);
	padding-top: 0.25em;
	font-variant-numeric: tabular-nums;
	letter-spacing: 0.02em;
}

.body { min-width: 0; }

.title {
	display: block;
	color: var(--ink);
	font-weight: 570;
	font-size: 15.5px;
	text-decoration: none;
	text-wrap: pretty;
}
a.title:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }

.sector {
	font-family: var(--mono);
	font-size: 11px;
	font-weight: 400;
	letter-spacing: 0.03em;
	color: var(--faint);
	margin-left: 0.6rem;
	text-transform: lowercase;
}

.by {
	margin: 0.2rem 0 0;
	color: var(--sub);
	font-size: 13.5px;
	text-wrap: pretty;
}
.by b { color: var(--muted); font-weight: 620; }

.where {
	margin: 0.15rem 0 0;
	font-family: var(--mono);
	font-size: 11.5px;
	color: var(--faint);
	letter-spacing: 0.02em;
}

.award {
	color: var(--accent);
	border: 1px solid currentColor;
	border-radius: 2px;
	padding: 0 0.35em;
	margin-left: 0.4rem;
	font-size: 10.5px;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	white-space: nowrap;
}

.chips {
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem;
	margin: 0.55rem 0 0;
}
.chips > * {
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.02em;
	color: var(--sub);
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: 3px;
	padding: 0.1rem 0.42rem;
	text-decoration: none;
}
/* Link chips share the text colour now that the palette is monochrome, so the
   underline is what separates them from the plain stack chips. */
.chips > a {
	color: var(--ink);
	text-decoration: underline;
	text-underline-offset: 2px;
}
.chips > a:hover { border-color: var(--rule); }

/* ── Detail cards ─────────────────────────────────────────── */

.detail { margin-top: 0.55rem; }

.detail > summary {
	display: inline-flex;
	align-items: baseline;
	gap: 0.4rem;
	width: fit-content;
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.03em;
	color: var(--sub);
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: 3px;
	padding: 0.1rem 0.42rem;
	cursor: pointer;
	list-style: none;
}
.detail > summary::-webkit-details-marker { display: none; }
.detail > summary::marker { content: ""; }

/* The sign carries the open/closed state on its own, so the control does not
   rely on colour alone to say what it will do. */
.detail > summary::before {
	content: "+";
	color: var(--faint);
	font-weight: 600;
}
.detail[open] > summary::before { content: "\2212"; }

.detail > summary:hover { border-color: var(--rule); color: var(--ink); }
.detail > summary:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}

.card {
	margin-top: 0.55rem;
	padding: 0.85rem 1rem;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: 3px;
}

.card .fig { margin: 0; max-width: 34rem; }

/* A plate matches the figure it holds, not the page theme. These figures were
   exported with their background baked in, most of them onto the production
   site's near-black surface, so keying the plate to the theme would frame half
   of them in the wrong colour on every visit. The generator measures each
   figure's own corners and picks the class. */
.plate {
	overflow: hidden;
	/* --rule, not --line: the hairline has to read against a white plate and a
	   near-black one, in both themes, so every figure gets the same edge. */
	border: 1px solid var(--rule);
	border-radius: 6px;
}
.plate--ink { background: #141619; }
.plate--paper { background: #ffffff; }

.plate img, .plate video {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: contain;
	/* Safari does not reliably clip a video to a rounded parent, so the media
	   carries the radius too. */
	border-radius: inherit;
}
/* A deliberately low-resolution source, so show crisp pixels, not blur. */
.plate--pixel img { image-rendering: pixelated; }

.card figcaption {
	margin-top: 0.4rem;
	font-family: var(--mono);
	font-size: 10.5px;
	letter-spacing: 0.03em;
	color: var(--faint);
}

.card .abstract {
	margin: 0;
	color: var(--sub);
	font-size: 13.5px;
	max-width: 72ch;
	text-wrap: pretty;
}

.card .fig + .abstract,
.card .fig + .facts {
	margin-top: 0.8rem;
	padding-top: 0.8rem;
	border-top: 1px solid var(--line);
}

.card .facts {
	margin: 0;
	display: grid;
	grid-template-columns: 6.5rem minmax(0, 1fr);
	gap: 0 1rem;
	font-size: 12.5px;
}
.card .abstract + .facts {
	margin-top: 0.7rem;
	padding-top: 0.7rem;
	border-top: 1px solid var(--line);
}
.card .facts dt {
	font-family: var(--mono);
	font-size: 10.5px;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--faint);
	padding: 0.18rem 0;
}
.card .facts dd {
	margin: 0;
	color: var(--sub);
	padding: 0.18rem 0;
}

/* Wide screens put the figure beside the text rather than above it. Stacked,
   a wide plate over a short fact list leaves a lot of empty card; side by side
   the two fill each other's space. Only where there is a figure to place, and
   only where both columns still have room to be readable. */
@media (min-width: 900px) {
	.card:has(.fig) {
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
		column-gap: 1.5rem;
		align-items: start;
	}
	.card:has(.fig) > .fig {
		grid-column: 2;
		grid-row: 1 / span 3;
		max-width: none;
	}
	.card:has(.fig) > .abstract,
	.card:has(.fig) > .facts { grid-column: 1; }
	/* The figure no longer sits above the text, so the rule that divided them
	   would now cut across the top of the left column. */
	.card:has(.fig) > .fig + .abstract,
	.card:has(.fig) > .fig + .facts {
		margin-top: 0;
		padding-top: 0;
		border-top: 0;
	}
}

@media (max-width: 620px) {
	.card .facts { grid-template-columns: minmax(0, 1fr); gap: 0; }
	.card .facts dd { padding-top: 0; padding-bottom: 0.4rem; }
}

/* ── Education + toolbox ──────────────────────────────────── */

.split {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
	gap: 0 clamp(1.5rem, 4vw, 3rem);
	align-items: start;
}
.split > section { padding-top: clamp(2rem, 4vw, 3rem); }

.tools {
	margin: 0;
	display: grid;
	grid-template-columns: 7rem minmax(0, 1fr);
	gap: 0 1rem;
	font-size: 13.5px;
}
.tools dt {
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--faint);
	padding: 0.62rem 0;
	border-bottom: 1px solid var(--line);
}
.tools dd {
	margin: 0;
	color: var(--sub);
	padding: 0.6rem 0;
	border-bottom: 1px solid var(--line);
}
.tools dt:last-of-type, .tools dd:last-of-type { border-bottom: 0; }

/* ── Footer ───────────────────────────────────────────────── */

footer {
	margin-top: clamp(2.5rem, 6vw, 4.5rem);
	padding-top: clamp(1.75rem, 4vw, 2.5rem);
	border-top: 1px solid var(--rule);
}

.closing {
	margin: 0;
	font-size: clamp(1.5rem, 4vw, 2.1rem);
	font-weight: 640;
	letter-spacing: -0.022em;
}
.mail { margin: 0.4rem 0 0; font-size: 1.05rem; }
.colophon {
	margin: 1.5rem 0 0;
	font-family: var(--mono);
	font-size: 11.5px;
	color: var(--faint);
}

/* ── Responsive ───────────────────────────────────────────── */

@media (max-width: 860px) {
	.split { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 620px) {
	.rows > li { grid-template-columns: minmax(0, 1fr); gap: 0.3rem; }
	.yr { padding-top: 0; }
	.sec-head { flex-wrap: wrap; }
	.sec-note { margin-left: 0; width: 100%; text-align: left; }
	.tools { grid-template-columns: minmax(0, 1fr); }
	.tools dt { border-bottom: 0; padding-bottom: 0; }
}

/* ── Print ────────────────────────────────────────────────── */

@media print {
	:root { --bg: #fff; --surface: #fff; --ink: #000; --muted: #333; --sub: #444; --faint: #555; --line: #ccc; --rule: #888; --accent: #000; }
	body { max-width: none; font-size: 10pt; padding: 0; }
	.backdrop { display: none; }
	.hero { padding-top: 0; min-height: 0; display: block; }
	.scroll-cue { display: none; }
	a { text-decoration: none; }
	section, .rows > li { break-inside: avoid; }
	.detail > summary { display: none; }
	.card { border-color: var(--line); }
}
