/**
 * Everything below this shim is allonsy-studio/website's pages/assets/styles/resume.css,
 * copied verbatim (2026-09-14, from castastrophe/educational-content's reference
 * to that repo's resume system — see career-ops's README for the source commit).
 *
 * It's written against website's site-wide theme (`--theme--*`, `--accent--*`),
 * which career-ops has no equivalent of. This :root block resolves the ~20
 * tokens it actually uses to the same concrete values website's _theme.css
 * gives them, so the file below works unmodified. Do not add new `--theme--*`
 * usages below this line without adding them here too.
 */
:root {
	--theme--color--yellow: #ffd36c;
	--accent--yellow--text: light-dark(hsl(38deg 88% 32%), hsl(45deg 100% 64%));
	--on-accent--yellow: #33394e;

	--theme--FontWeight--thin: 100;
	--theme--FontWeight--light: 300;
	--theme--FontWeight: 400;
	--theme--FontWeight--semi-bold: 600;
	--theme--FontWeight--bold: 700;

	--theme--text--FontFamily--signature: "Yellowtail", cursive;
	--theme--text--FontFamily--dense: "Instrument Sans", "Instrument Sans Fallback", system-ui, sans-serif;
	--theme--heading--FontFamily: "Instrument Sans", "Instrument Sans Fallback", system-ui, "Helvetica Neue", helvetica, arial, sans-serif;
	--theme--LineHeight: 1.4;
	--theme--text--LineHeight: var(--theme--LineHeight);
	--theme--text--Color: light-dark(#33394e, #fbf9f3);
	--theme--text--Color--subtle: light-dark(#5e6273, #c2b1a6);

	--theme--BorderRadius--circle: 50%;
	--theme--BorderWidth--thin: 1px;

	--theme--container--MaxWidth: 1200px;
	--theme--FontSize--minimum: 12px;
	--theme--content--MaxWidth: calc(80 * var(--theme--FontSize--minimum));

	--theme--surface--color: light-dark(#fbf9f3, hsl(228deg 20% 12%));
	--theme--ui--color--subtle: light-dark(#c2b1a6, hsl(228deg 14% 32%));
	--theme--ui--color: var(--base);
}

:root {
	/* The full accent triple, as .accent--yellow sets it: swapping --base on
       its own leaves --base--on paired with the magenta default, which puts
       white text on the yellow fill. */
	--base: var(--theme--color--yellow);
	--base--text: var(--accent--yellow--text);
	--base--on: var(--on-accent--yellow);

	/* [stacked] row-gap doubles this → 1rem (16px). */
	--resume--space--vertical: 0.5rem;

	/* The document's measure on screen. Also what the tool's chrome aligns to
       (see resume-tool.css) so the toolbar sits over the page, not beside it.
       The site's own container width — the resume is a page like any other. */
	--resume--MaxWidth: var(--theme--container--MaxWidth);

	/* Always some breathing room at the edges, scaled to the viewport & floored
       so a narrow phone still clears the bezel. */
	--resume--Gutter: clamp(1.25rem, 5vw, 3rem);

	/* The paper grain, which print restates over the dark scheme's flat ground. */
	--resume--paper--glass: repeating-conic-gradient(color-mix(in sRGB, var(--theme--surface--color) 72%, transparent) 0 0.0001%, color-mix(in sRGB, color-mix(in sRGB, var(--theme--surface--color) 92%, var(--theme--text--Color)) 72%, transparent) 0 0.0002%) 60vw 60vw / 100px 100px;
	--resume--paper: repeating-conic-gradient(var(--theme--surface--color) 0 0.0001%, color-mix(in sRGB, var(--theme--surface--color) 95%, var(--theme--text--Color)) 0 0.0002%) 60vw 60vw / 100px 100px;
}

body {
	font-family: var(--theme--text--FontFamily--dense);
	line-height: var(--theme--text--LineHeight);

	/* Light-mode paper grain, shared by the body and the sticky header. */
	background: var(--resume--paper);
	padding: 0;

	/* Resume-only (sections use scroll-snap-align below) */
	scroll-snap-type: y mandatory;

	@media (prefers-color-scheme: dark) {
		background: var(--theme--surface--color);
	}
}

/* Defaults for links */
a {
	color: var(--theme--text--Color);
}

p strong {
	font-weight: var(--theme--FontWeight--semi-bold);
}

/* A custom element is inline until told otherwise. */
resume-document {
	display: block;
}

header {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	justify-content: space-between;
	column-gap: 3rem;
	row-gap: 1rem;
	text-align: start;
	margin-block: 2.5rem;
}

.candidate-name {
	font-family: var(--theme--heading--FontFamily);
	margin-block-start: 0.5em;
	margin-block-end: 0;
	font-size: 3.5rem;
	line-height: 1;
	position: relative;
	transform-origin: top;

	&::before {
		content: "";
		display: block;
		inline-size: 80px;
		block-size: var(--block-size, 400px);
		background: var(--theme--ui--color);
		position: absolute;
		inset-inline-start: -1rem;
		inset-block-end: -0.25rem;
		z-index: -1;
	}

	span {
		text-shadow: 1px 1px 1px light-dark(transparent, hsl(0deg 0 45% / 90%));

		&:first-child {
			font-weight: var(--theme--FontWeight--semi-bold);
			letter-spacing: 0.1rem;
		}

		&:last-child {
			font-weight: var(--theme--FontWeight--thin);
			letter-spacing: 0.25rem;
		}
	}
}

.subtitle {
	font-size: 1.3rem;
	font-weight: var(--theme--FontWeight--thin);
	margin-block-start: 0.5rem;
	text-wrap: balance;

	/* Aligns with the accent block behind the name, which starts a rem outside
       the column — but only where the gutter can afford to give it back. */
	margin-inline-start: -1rem;

	@media screen and (width <= 612px) {
		margin-inline-start: 0;
	}
}

.location {
	color: var(--theme--text--Color--subtle);
	font-size: 0.95rem;
	margin-block-start: 0.25rem;

	/* Sits flush with the tagline above it. */
	margin-inline-start: -1rem;

	@media screen and (width <= 612px) {
		margin-inline-start: 0;
	}
}

/* ============================================================
   SECTION LAYOUT — headers, titles, and column grid
   ============================================================ */

section {
	scroll-snap-align: start;
	scroll-margin-block-start: 2rem;
	scroll-snap-stop: always;
	margin-block-end: var(--section--spacing--vertical, 2rem);
}

.section-header {
	container-type: inline-size;
	container-name: section-header;
	display: flex;
	justify-content: space-between;
	align-items: start;
	flex-wrap: wrap;
	column-gap: 1rem;
	margin-block-end: 1rem;
}

.section-title {
	transform-origin: left;
	font-family: var(--theme--heading--FontFamily);
	font-size: 1.25rem;
	font-weight: var(--theme--FontWeight--semi-bold);
	text-transform: uppercase;
	position: relative;
	margin-block-end: 0;
	color: var(--theme--text--Color);
	display: inline-block;

	&::before {
		content: "";
		display: block;
		inline-size: var(--inline-size, 50vw);
		block-size: 0.1em;
		background: var(--theme--ui--color);
		position: absolute;
		z-index: -1;
		pointer-events: none;
		inset-inline-end: -0.5rem;
		inset-block-end: 0.1em;
		border-radius: 1px;

		@media (prefers-color-scheme: dark) {
			background: none;
			border-block-end: 5px solid var(--theme--ui--color);
			inset-block-end: -0.2em;
		}
	}

	+ .section-aside {
		font-family: var(--theme--heading--FontFamily);
		font-size: 0.9em;
		font-weight: var(--theme--FontWeight--light);
	}

	a {
		text-decoration: none;
		color: inherit;
	}
}

.content:is(.focused) p {
	font-size: 1.2rem;
	font-weight: var(--theme--FontWeight--thin);
	max-inline-size: var(--theme--content--MaxWidth);
}

[stacked] {
	display: flex;
	flex-direction: column;

	&:not(:has(.job-item)) {
		row-gap: calc(var(--resume--space--vertical) * 2);
	}
}

/* ============================================================
   JOB EXPERIENCE — company, role header, dates, highlights
   ============================================================ */

/* Each entry is a two-column grid: dates in a left rail against a hairline
   spine, everything else in the content column. The source order stays linear
   (company, title, dates, prose) so a parser & a screen reader still read the
   entry in the order it's spoken — only the painting moves. */
.job-item {
	--rail--Width: 7rem;
	--rail--Gap: 1.5rem;

	display: grid;
	grid-template-columns: var(--rail--Width) 1fr;
	column-gap: var(--rail--Gap);
	row-gap: 0.25em;
	position: relative;
	padding-block-end: 1.5rem;

	> * {
		grid-column: 2;
	}

	/* Row 1 pairs the company with the dates rail; two-line dates outgrow it &
       push the rest of the entry down a line. Pull it back — everywhere the
       rail exists, print included, & reset only where the rail folds & the
       dates rejoin the flow. */
	&:has(.role-history) .job-company ~ .role-history,
	&:has(.job-dates > span:nth-child(2)) .job-company + :is(.role-history, .job-header) {
		margin-block-start: -1em;

		@media screen and (width <= 612px) {
			margin-block-start: 0;
		}
	}

	/* Same correction one row down, for entries that open straight onto their
       header: with no company line above it, the header shares row 1 with the
       dates & it's the summary the wrapped dates push away. Entries that do
       carry a company are excluded — the rule above already spends the pull on
       their header, & both firing would lift the summary twice.
       Reset where the rail folds & the dates rejoin the flow. */
	&:has(.job-dates > span:nth-child(2)):not(:has(> .job-company)) .job-header + .job-dates + .job-summary {
		margin-block-start: -1em;

		@media screen and (width <= 612px) {
			margin-block-start: 0;
		}
	}

	&:first-of-type::before {
		inset-block-start: 0.5em;
	}

	/* The spine. */
	&::before {
		content: "";
		position: absolute;
		inset-block: 0;
		inset-inline-start: calc(var(--rail--Width) + (var(--rail--Gap) / 2));
		inline-size: var(--theme--BorderWidth--thin);
		background: var(--theme--ui--color--subtle);
	}

	/* The node marking where each tenure begins. */
	&::after {
		content: "";
		position: absolute;
		inset-block-start: 0.4em;
		inset-inline-start: calc(var(--rail--Width) + (var(--rail--Gap) / 2));
		translate: calc(-50% + 0.5px) 0;
		inline-size: 0.55rem;
		block-size: 0.55rem;
		border-radius: var(--theme--BorderRadius--circle, 50%);
		background: var(--theme--ui--color);
	}

	/* Below the rail's useful width, fold the dates back inline. */
	@media screen and (width <= 612px) {
		grid-template-columns: 1fr;

		> * {
			grid-column: 1;
		}

		&::before,
		&::after {
			display: none;
		}
	}
}

.job-item--compact {
	padding-block-end: 0.75rem;

	.job-header {
		margin-block-end: 0;
	}
}

/* Marks the point where the document stops elaborating & starts listing. */
.job-earlier-label {
	font-family: var(--theme--heading--FontFamily);
	font-size: 0.9rem;
	font-weight: var(--theme--FontWeight--semi-bold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--theme--text--Color--subtle);
	margin-block: 0.5rem;
}

.job-company {
	font-family: var(--theme--heading--FontFamily);
	font-size: 1.1rem;
	font-weight: var(--theme--FontWeight);
	margin-block-end: 0.2rem;
}

.job-header {
	font-family: var(--theme--heading--FontFamily);
	font-size: 1.2rem;
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.5rem 1rem;
}

.job-title {
	font-weight: var(--theme--FontWeight--bold);
}

.job-dates {
	/* Row 1 of the rail column: level with the company (or, where there isn't
       one, the role title) that opens the entry. */
	grid-column: 1;
	grid-row: 1;
	align-self: start;

	display: inline;
	text-align: end;

	font-weight: var(--theme--FontWeight--light);
	font-size: 0.95rem;
	color: var(--theme--text--Color--subtle);

	span {
		text-wrap: nowrap;

		&:last-child {
			margin-inline-end: 0.8em;
		}
	}

	span:has(+ span)::after {
		content: "–";
		margin-inline-start: 0.25rem;
	}

	@media screen and (width <= 612px) {
		flex-direction: row;
		gap: 0.35rem;
		text-align: start;
	}
}

.job-summary {
	text-wrap: balance;
	font-weight: var(--theme--FontWeight--thin);
}

/* Margins don't collapse in the entry's grid, so the summary's bottom margin and
   the list's top margin stacked into a full line plus change. The entry's own
   gap & padding do the spacing. */
.job-summary:has(+ .job-highlights) {
	margin-block: 0.5em;
}

.job-highlights {
	margin: 0;
	list-style: disc;

	li:not(:last-child) {
		margin-block-end: 0.25rem;
	}
}

/* ============================================================
   SKILLS — grid of skill tags
   ============================================================ */

.skills-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	row-gap: 1rem;
	column-gap: 2rem;

	&:where(ul),
	ul {
		list-style: none;
		padding: 0;
	}

	&:not(:has(h3)),
	ul {
		display: grid;
		grid-template-columns: 1fr;
		row-gap: 0.25rem;
	}

	li {
		font-size: 0.9rem;
		line-height: var(--theme--text--LineHeight);
		margin-block-end: 0;
	}
}

/* Groups differ in length, so pack each into the shortest column rather than
   leaving a row's tallest group to set the gap. */
@supports (display: grid-lanes) {
	.skills-grid:has(h3) {
		display: grid-lanes;
		grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
	}
}

/* Without grid-lanes, resume-document.webc rebuilds it: each group goes into the
   shortest lane on a grid of 1px rows, and passes its lane, start & span in as
   custom properties. Screen only, as paper keeps the plain grid. */
@media screen {
	.skills-grid.lanes {
		grid-auto-rows: 1px;
		row-gap: 0;
		align-items: start;

		> li {
			grid-column: var(--lane, auto);
			grid-row: var(--start, auto) / span var(--span, 1);
		}
	}
}

/* ============================================================
   EDUCATION — education list items
   ============================================================ */

.education-item {
	font-size: 1.2rem;

	strong {
		font-family: var(--theme--heading--FontFamily);
	}
}

/* ============================================================
   CONTACT — icon + label grid
   ============================================================ */

.contact-grid {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 0.35rem;

	font-style: normal; /* <address> */
	font-size: 0.95rem;

	.contact-item {
		display: flex;
		align-items: center;
		gap: 0.6rem;

		svg {
			inline-size: 1em;
			block-size: 1em;
			flex-shrink: 0;
		}
	}

	/* Side by side once the header has room for two columns. */
	@media screen and (width <= 700px) {
		inline-size: 100%;
	}
}

/* ============================================================
   COVER LETTER — addressee, body, signature
   ============================================================ */

#cover-letter {
	margin-block-start: 2rem;
	text-wrap: balance;

	/* The measure belongs to the prose, not the section: capping the section
       itself made it a narrower box that then self-centred, so the letter sat
       indented from every other section's left edge. */
	> * {
		max-inline-size: var(--theme--content--MaxWidth);
	}

	.addressee,
	.signature {
		font-family: var(--theme--heading--FontFamily);
		font-size: 1.2rem;
		font-weight: var(--theme--FontWeight--semi-bold);
		display: inline-block;
	}

	.signature {
		font-weight: var(--theme--FontWeight);

		.name {
			font-family: var(--theme--text--FontFamily--signature);
			font-size: 2rem;
			font-weight: var(--theme--FontWeight--thin);
		}
	}
}

#about {
	text-wrap: balance;
}

/* ============================================================
   ROLE HISTORY — every role in a multi-role company, stacked
   ============================================================ */

.role-history {
	display: flex;
	flex-direction: column;
	row-gap: calc(var(--resume--space--vertical) * 2);
}

.role-history .role-panel + .role-panel {
	border-block-start: var(--theme--BorderWidth--thin) solid var(--theme--ui--color--subtle);
	padding-block-start: calc(var(--resume--space--vertical) * 2);
}

.role-title {
	margin: 0 0 calc(var(--resume--space--vertical) / 2);
	font-size: 0.95em;
	font-weight: var(--theme--FontWeight--semi-bold);
}

/* ============================================================
   PRINT BUTTON — fixed FAB, revealed on scroll stop
   ============================================================ */

button:is(.print) {
	--button-size: 3.5rem;
	position: fixed;
	inset-block-end: 2rem;
	inset-inline-end: 2rem;
	z-index: 1000;
	display: flex;
	align-items: center;
	gap: 0.75rem;
	background: var(--theme--ui--color);
	max-inline-size: 150px;
	color: var(--base--on);
	border: none;
	border-radius: 2rem;
	padding: 0.875rem 1.5rem;
	font-family: var(--theme--heading--FontFamily);
	font-size: 1rem;
	font-weight: var(--theme--FontWeight--semi-bold);
	cursor: pointer;
	box-shadow:
		0 4px 6px -1px rgb(0 0 0 / 10%),
		0 10px 20px -2px rgb(0 0 0 / 15%),
		0 0 0 1px rgb(0 0 0 / 5%);

	transition:
		opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
		transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
		visibility 0.3s,
		box-shadow 0.2s ease;

	&[hidden] {
		display: flex;
		opacity: 0;
		transform: translateY(1rem);
		visibility: hidden;
		pointer-events: none;
	}

	&:hover {
		box-shadow:
			0 8px 12px -2px rgb(0 0 0 / 15%),
			0 16px 32px -4px rgb(0 0 0 / 20%),
			0 0 0 1px rgb(0 0 0 / 5%);
		transform: translateY(-2px);
	}

	&:active {
		transform: translateY(0);
		box-shadow:
			0 2px 4px -1px rgb(0 0 0 / 10%),
			0 4px 8px -2px rgb(0 0 0 / 10%);
	}

	&:focus-visible {
		outline: 3px solid var(--theme--text--Color);
		outline-offset: 2px;
	}

	svg {
		inline-size: 1.25em;
		block-size: 1.25em;
	}
}

/* ============================================================
   ANIMATIONS — scroll-triggered section title highlights
   ============================================================ */

@media not (prefers-reduced-motion: reduce) {
	@supports (animation-timeline: scroll()) {
		.scroll-accent::before {
			animation: var(--animation, roll-in) linear both;
			animation-timeline: view();
			animation-range: 0 30vh;
		}
	}

	@supports not (animation-timeline: scroll()) {
		.scroll-accent::before {
			animation: var(--animation, roll-in) 0.8s linear forwards;
		}
	}
}

/* ============================================================
   DOCKED MASTHEAD — name & tagline follow the scroll (screen only)
   ============================================================ */

/* resume-document.webc adds [data-dock] & a `.dock` clone of the masthead, and
   keeps `--dock` (0 at rest, 1 once docked) current. Without that script none of
   this applies. Everything is written against `--dock`: each property is its
   resting value plus (docked value - resting value) * --dock. */
@media screen {
	html[data-dock] {
		--dock: 0;

		/* The header's top margin, which the clone has to start below. */
		--dock--hero-offset: 2.5rem;
		--dock--bar-height: 4rem;
		--dock--inset: 1.5rem;
		--dock--gap: 2rem;

		/* The width the left margin leaves for a sidebar: from the inset to the
		   content's edge, less the gap. Where there's no margin it goes negative
		   & the bar layout is used instead. */
		--dock--rail: calc((100vw - min(100vw, var(--resume--MaxWidth))) / 2 + var(--resume--Gutter) - var(--dock--inset) - var(--dock--gap));
	}

	/* The original keeps its space in the flow; the clone is what's seen. */
	html[data-dock] resume-document > header > :first-child {
		opacity: 0;
	}

	.dock {
		position: sticky;
		inset-block-start: 0;
		z-index: 20;
		display: flow-root;
		block-size: 0;
		pointer-events: none;
	}

	/* Carries the position & width; the clone of the masthead & the quick links sit
	   inside it. */
	.dock-body {
		display: flow-root;
		margin-block-start: calc(var(--dock--hero-offset) + (var(--dock--docked-top) - var(--dock--hero-offset)) * var(--dock));
	}

	.dock-name {
		display: flow-root;

		.candidate-name {
			margin-block-start: calc(0.5em * (1 - var(--dock)));
			line-height: 1;

			/* The accent block rises from the name to the top of the page; it shrinks
	           to a bar the name's own height as the page docks. */
			&::before {
				block-size: calc(100% + (var(--dock--hero-offset) + 0.5em) * (1 - var(--dock)));
				inline-size: calc(80px + (0.3rem - 80px) * var(--dock));
				inset-inline-start: calc(-1rem + 0.2rem * var(--dock));
			}

			span:first-child {
				letter-spacing: calc(0.1rem + (0.03em - 0.1rem) * var(--dock));
			}

			span:last-child {
				letter-spacing: calc(0.25rem + (0.07em - 0.25rem) * var(--dock));
			}
		}

		/* Both lines are pulled left of the name at rest to meet the accent block. */
		.subtitle,
		.location {
			margin-inline-start: calc(-1rem * (1 - var(--dock)));
		}

		.location {
			opacity: calc(1 - var(--dock) * 3);
		}
	}

	/* Wide enough to leave the column's left margin open: the name & tagline shrink
	   into it & stay there beside the resume. */
	@media (width >= 1500px) {
		.dock-body {
			--dock--docked-top: 1rem;

			/* From the column's left edge to the inset inside the viewport's. */
			translate: calc(var(--dock) * (var(--dock--inset) - var(--resume--Gutter) - (100vw - min(100vw, var(--resume--MaxWidth))) / 2)) 0;

			/* Fills the margin: the width it leaves, and type sized to it, so a wider
			   screen gets a larger sidebar instead of a thin strip. The tagline wraps
			   inside it. */
			inline-size: calc(36rem + (var(--dock--rail) - 36rem) * var(--dock));
		}

		.dock-name {
			.candidate-name {
				font-size: calc(3.5rem + (clamp(1.4rem, var(--dock--rail) / 6.2, 2.4rem) - 3.5rem) * var(--dock));
			}

			.subtitle {
				font-size: calc(1.3rem + (clamp(0.8rem, var(--dock--rail) / 16, 1.15rem) - 1.3rem) * var(--dock));
			}
		}
	}

	/* Quick links: in the sidebar only, and only once it has docked, when the
	   screen is wide & tall enough to hold them. */
	.dock-links {
		display: none;
	}

	@media (width >= 1500px) and (height >= 38rem) {
		.dock-links {
			display: block;
			margin-block-start: 1.5rem;
			font-size: 0.85rem;
			line-height: 1.3;
			opacity: clamp(0, (var(--dock) - 0.7) * 3.3, 1);
			visibility: hidden;
			pointer-events: none;

			p {
				margin: 1rem 0 0.4rem;
				font-size: 0.7rem;
				letter-spacing: 0.08em;
				text-transform: uppercase;
				color: var(--theme--text--Color--subtle);
			}

			ul {
				margin: 0;
				padding: 0;
				list-style: none;
			}

			li + li {
				margin-block-start: 0.15rem;
			}

			a,
			button {
				display: block;
				inline-size: 100%;
				margin: 0;
				padding: 0.15rem 0 0.15rem 0.6rem;
				border: 0;
				border-inline-start: 0.2rem solid transparent;
				background: none;
				color: var(--theme--text--Color);
				font: inherit;
				text-align: start;
				text-decoration: none;
				cursor: pointer;

				&:hover {
					text-decoration: underline;
				}

				&:focus-visible {
					outline: 2px solid var(--theme--text--Color);
					outline-offset: 2px;
				}

				/* A fixed-width icon before the label keeps the labels aligned. */
				[aria-hidden] {
					display: inline-block;
					inline-size: 1.25em;
					margin-inline-end: 0.4em;
					text-align: center;
					color: var(--theme--text--Color--subtle);
				}

				/* Marks a link that leaves the page, after the label. */
				.external {
					inline-size: auto;
					margin-inline: 0.4em 0;
					font-size: 0.75em;
				}
			}

			a[aria-current] {
				border-inline-start-color: var(--theme--ui--color);
				font-weight: var(--theme--FontWeight--semi-bold);
			}
		}

		.dock[data-docked] .dock-links {
			visibility: visible;
			pointer-events: auto;
		}
	}

	/* Not enough margin: dock as a compact bar across the top instead. */
	@media (width < 1500px) {
		/* Spans the dock's own box, which carries the document's margins, so the
		   bar's edges & its text line up with the content below. */
		.dock::before {
			content: "";
			position: absolute;
			inset: 0 0 auto;
			z-index: -1;
			block-size: var(--dock--bar-height);

			/* Glass: the paper grain, translucent, over a blur of what scrolls
	           beneath. The mask feathers the lower edge in place of a rule. */
			background: var(--resume--paper--glass);
			-webkit-backdrop-filter: blur(14px) saturate(1.5);
			backdrop-filter: blur(14px) saturate(1.5);
			mask-image: linear-gradient(to bottom, #000 72%, transparent);
			opacity: var(--dock);
		}

		html[data-dock] {
			scroll-padding-block-start: var(--dock--bar-height);
		}

		.dock-body {
			--dock--docked-top: 0.5rem;
		}

		.dock-name {
			.candidate-name {
				font-size: calc(3.5rem + (1.25rem - 3.5rem) * var(--dock));
			}

			.subtitle {
				font-size: calc(1.3rem + (0.8rem - 1.3rem) * var(--dock));
				margin-block-start: calc(0.5rem - 0.4rem * var(--dock));
			}
		}

		/* Docked, the name reads on one line. It flips near the end of the run,
		   when the type is already close to its final size. */
		.dock[data-docked] .candidate-name {
			br {
				display: none;
			}

			span:first-child {
				margin-inline-end: 0.3em;
			}
		}
	}

	/* A phone's bar carries the name & its accent alone. */
	@media (width <= 612px) {
		html[data-dock] {
			--dock--bar-height: 3.25rem;
		}

		/* No pull on a phone, so nothing to animate away. */
		.dock-body {
			--dock--docked-top: 0.9rem;
		}

		.dock-name {
			.subtitle,
			.location {
				margin-inline-start: 0;
			}

			.subtitle {
				opacity: calc(1 - var(--dock) * 3);
			}
		}

		.dock[data-docked] .subtitle {
			display: none;
		}
	}
}

@media print {
	.dock {
		display: none;
	}
}

/* ============================================================
   RESPONSIVE — breakpoint overrides
   ============================================================ */

@media screen and (width > 1149px) {
	/* At larger screen sizes, make the section highlight span the container instead of flowing off-screen */
	.section-title::before {
		--inline-size: 100cqi;
		inset-inline-end: inherit;
		inset-inline-start: -0.5rem;
		transform-origin: left;
	}

	.section-title + .section-aside {
		flex-basis: 100%;
		text-align: end;
		margin-block-start: -0.5rem;
	}
}

@media screen {
	/* The page scrolls on the root, which is where snapping has to be declared:
       `scroll-snap-type` on `body` never reaches the viewport. Only the hand-off
       from the letter to the resume snaps, so the rest scrolls freely. */
	html:has(#cover-letter + #about) {
		scroll-snap-type: y proximity;
	}

	#cover-letter + #about {
		scroll-snap-align: start;
		scroll-snap-stop: always;
	}

	html:has(#cover-letter + #about) section:not(#cover-letter + #about) {
		scroll-snap-align: none;
	}
}

@media not print {
	/* Centres the column on screen; in print the page margins do this job.
       Matches the element itself, not just the tool page's class, so the
       variant pages get the same gutters. */
	resume-document > * {
		/* border-box so the gutters sit inside the 1200px, rather than making
           each block 1200px + padding wide. */
		box-sizing: border-box;

		/* A definite width is what keeps every block on the same edges: `main`
           is a flex column (site.css), and `margin-inline: auto` on a flex item
           beats `align-items: stretch` — without this each section shrinks to
           its own content & self-centres, so the left edge wanders section to
           section. */
		inline-size: 100%;
		max-inline-size: var(--resume--MaxWidth);
		padding-inline: var(--resume--Gutter);
		margin-inline: auto;
	}

	#cover-letter {
		margin-block-end: 20vh;
	}
}

/* ============================================================
   PRINT — PDF/print layout overrides
   ============================================================ */

@media print {
	button:is(.print) {
		display: none;
	}

	/* Every size in this sheet is authored in `rem`, which answers to the root
       rather than the body — so the root is the only lever that scales headings,
       copy & spacing together. The body inherits it. */
	html {
		font-size: 14px;
	}

	/* Pairs with the cover letter's `break-after` so the resume proper always
       opens page 2 behind a letter. Scoped to that adjacency on purpose: a
       variant carrying no letter keeps its summary under the header rather than
       spending a near-blank sheet on a header-only page. */
	#cover-letter + #about {
		break-before: page;
	}

	/* The ground prints with the page, always the light one: the dark scheme's
       light-on-dark would otherwise follow the reader's setting onto paper.
       `exact` makes it print without the browser's background-graphics option. */
	html:root {
		color-scheme: light;
		print-color-adjust: exact;
	}

	/* A page's background stops at its margin, leaving a white frame. With no
       page margin & the same space as padding cloned onto every page's slice of
       the document, the ground runs to the sheet's edge. Engines that can't
       clone it keep the margin & print without the ground, rather than framed. */
	@supports (box-decoration-break: clone) {
		@page {
			margin: 0;
		}

		resume-document {
			box-sizing: border-box;
			padding: 0.5in;
			box-decoration-break: clone;
		}
	}

	@supports not (box-decoration-break: clone) {
		@page {
			margin: 0.5in;
		}

		html:root body {
			background: none;
		}
	}

	body {
		background: var(--resume--paper);
	}

	/* Two columns on paper, always. `wrap` lines flex items up by their
       max-content size before it shrinks any of them, so the tagline's natural
       width alone was enough to push the contact column onto a second row —
       stacking the header and spending an extra page on the cover letter. */
	header {
		flex-wrap: nowrap;
		align-items: flex-start;
		column-gap: 2rem;
		margin-block: 0 1.5rem;
	}

	.candidate-name {
		font-size: 2.75rem;
		margin-block-start: 0;
	}

	/* The -1rem pull lines these up with the accent block behind the name. That
       block is hidden on paper, and print has no gutter to absorb the pull, so
       it dragged both lines off the inline edge of the sheet instead. */
	.subtitle,
	.location {
		margin-inline-start: 0;
	}

	.subtitle {
		font-size: 1.15rem;
	}

	/* Break between bullets, never inside one or under a heading. Keeping
	   whole roles together pushed any role taller than the space left onto
	   the next sheet, leaving page-high gaps (9 pages for the full resume). */
	.job-item--compact,
	.job-highlights li,
	.skills-grid > li,
	.education-item {
		break-inside: avoid;
	}

	/* The page-break-* aliases are for print engines that only honour those. */
	.job-company,
	.job-header,
	.role-title {
		break-after: avoid;
		page-break-after: avoid;
	}

	/* Firefox can't break a page inside a grid or flex container: it drops
	   `break-after: avoid`, stranding a company at the foot of a sheet, and an
	   entry split across sheets painted the next one over it. On paper the
	   entries, their roles & the section bodies flow as blocks: the dates pin
	   into the rail & the spine is a background, which slices with each page. */
	[stacked],
	.role-history {
		display: block;
	}

	.role-history .role-panel + .role-panel {
		margin-block-start: calc(var(--resume--space--vertical) * 2);
	}

	.job-item {
		display: block;
		padding-inline-start: calc(var(--rail--Width) + var(--rail--Gap));
		background: linear-gradient(var(--theme--ui--color--subtle) 0 0) no-repeat calc(var(--rail--Width) + (var(--rail--Gap) / 2)) 0 / var(--theme--BorderWidth--thin) 100%;

		> * + * {
			margin-block-start: 0.15em;
		}
	}

	.job-dates {
		position: absolute;
		inset-block-start: 0;
		inset-inline-start: 0;
		inline-size: var(--rail--Width);
		break-inside: avoid;
	}

	/* Firefox ignores `break-after: avoid` in block flow too. An unbreakable
	   spacer needs that much room to stay on the sheet, else it moves to the
	   next one & takes what follows with it; its negative margin cancels it,
	   so the layout doesn't change. Under a heading it keeps the heading with
	   its first lines. At the end of a job entry it keeps the next entry from
	   opening at the foot of a sheet: Firefox leaves an entry's box behind
	   when it pushes the content, splitting the dates pinned to its top. */
	.section-header {
		display: block;
	}

	.section-title {
		inline-size: fit-content;
	}

	.section-header,
	.role-title,
	.job-company,
	.job-header {
		break-inside: avoid;
	}

	.section-header::after,
	.role-title::after,
	.job-item::after {
		content: "";
		display: block;
		position: static;
		translate: none;
		inline-size: auto;
		block-size: 4rem;
		margin-block-end: -4rem;
		border-radius: 0;
		background: none;
		break-inside: avoid;
	}

	/* The tenure node moves to ::before, which the background spine freed. */
	.job-item::before {
		inset-block: 0.4em auto;
		translate: calc(-50% + 0.5px) 0;
		inline-size: 0.55rem;
		block-size: 0.55rem;
		border-radius: var(--theme--BorderRadius--circle, 50%);
		background: var(--theme--ui--color);
	}

	/* The rail no longer shares a row with the content, so nothing to pull back. */
	.job-item:has(.role-history) .job-company ~ .role-history,
	.job-item:has(.job-dates > span:nth-child(2)) .job-company + :is(.role-history, .job-header),
	.job-item:has(.job-dates > span:nth-child(2)):not(:has(> .job-company)) .job-header + .job-dates + .job-summary {
		margin-block-start: 0;
	}

	/* Entries run newest first, so everything after Red Hat predates it. */
	.job-item[data-company="Red Hat"] ~ * {
		display: none;
	}

	p,
	li {
		orphans: 2;
		widows: 2;
	}

	/* A section title alone at the foot of a page reads as the end of the
       document rather than the start of a section. Asked of the wrapper, the
       title & whatever follows, since engines differ on which one they honour. */
	.section-header,
	.section-title {
		break-after: avoid;
		page-break-after: avoid;
	}

	.section-header + * {
		break-before: avoid;
		page-break-before: avoid;
	}

	/* Short sections move as one block, so their heading can't be stranded
       from the content it names. Experience is far taller than a page & is
       held together by the rules above instead. */
	#about,
	#skills,
	#education {
		break-inside: avoid;
	}

	/* Tighter than screen: the same content in fewer sheets, with the gaps
       between blocks (not the text) taking the cut. */
	body {
		line-height: 1.3;
	}

	section {
		margin-block-end: 1.25rem;
	}

	.section-header {
		margin-block-end: 0.5rem;
	}

	.job-item {
		padding-block-end: 0.9rem;
		row-gap: 0.15em;
	}

	.job-item--compact {
		padding-block-end: 0.5rem;
	}

	.job-highlights li:not(:last-child) {
		margin-block-end: 0.15rem;
	}

	/* Three columns on paper; lanes or grid per the SKILLS rules above. */
	.skills-grid:has(h3) {
		grid-template-columns: repeat(3, 1fr);
		row-gap: 0.6rem;
		column-gap: 1.5rem;
	}

	.education-item {
		font-size: 1rem;
	}

	.contact-grid {
		gap: 0.3rem;

		.contact-item {
			font-size: 1rem;
		}
	}

	/* A letter is its own document; the resume starts on a fresh page. */
	#cover-letter {
		break-after: page;

		/* One sheet, letterhead included: the header's own margin is the gap above,
		   and tighter spacing below keeps the sign-off off a second page. */
		margin-block-start: 0;

		.addressee {
			margin-block: 0 0.5em;
		}

		.content p {
			margin-block: 0 0.6em;
		}

		/* Never a sign-off alone on a page: it takes the last paragraph with it. */
		.signature {
			margin-block: 0.5em 0;
			break-before: avoid;
			break-inside: avoid;

			.name {
				font-size: 1.6rem;
			}
		}
	}

	/* The wordmark's accent block is the one piece of brand the document
       carries, so it stays on paper. `print-color-adjust` is what keeps the
       fill from being dropped as a background graphic; the inline offset goes
       to 0 because print has no gutter for it to bleed into. */
	.candidate-name::before {
		print-color-adjust: exact;
		inset-inline-start: 0;
	}

	.section-title {
		&::before {
			display: none;
		}

		border-block-end: 1px solid currentColor;
		padding-block-end: 0.2em;
		display: block;
		letter-spacing: 0.08em;
	}

	/* Hyperlinks don't survive a printed page — spell the destination out.
       Contact links already show their URL as their text. */
	.content a[href^="http"]::after {
		content: " (" attr(href) ")";
		font-size: 0.85em;
		word-break: break-all;
	}
}

@keyframes roll-in {
	0% {
		opacity: 0;
		transform: translateX(calc(100% * var(--animation-direction, -1)));
	}

	20% {
		opacity: 1;
	}

	100% {
		transform: translateX(0%);
	}
}
