/* ==========================================================================
   CV site — Firewatch-inspired parallax landing page
   Palette: dark maroon background, orange accents (Firewatch keyart colors)
   ========================================================================== */

:root {
	--bg: #180b0c;
	--accent: #ffaf00;
	--accent-bright: #ffaf1b;
	--accent-dark: #b95c1f;
	--ink: #f7ead1;
	--ink-muted: #d8bd97;
	--hairline: rgba(255, 175, 0, 0.25);
	--font: "Avenir Next", Avenir, "Segoe UI", "Trebuchet MS", Verdana, sans-serif;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html {
	background-color: var(--bg);
	scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
}

body {
	background-color: var(--bg);
	color: var(--ink);
	font-family: var(--font);
	font-size: 16px;
	line-height: 1.6;
	-webkit-font-smoothing: antialiased;
}

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

/* --------------------------------------------------------------------------
   Top bar (over the hero sky)
   -------------------------------------------------------------------------- */

.topbar {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 30;
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 18px 28px;
	color: var(--bg);
	font-weight: 700;
	font-size: 13px;
	letter-spacing: 2px;
	text-transform: uppercase;
	transition: color 0.25s ease;
}

/* Dark backdrop behind the bar. Its opacity is scroll-linked: JS raises the
   --bar variable (0..1) as the profile section approaches, so the backdrop is
   fully in place before the dark content arrives and the name is never
   unreadable against it. */
.topbar::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background-color: rgba(24, 11, 12, 0.85);
	-webkit-backdrop-filter: blur(6px);
	backdrop-filter: blur(6px);
	box-shadow: 0 1px 0 var(--hairline);
	opacity: var(--bar, 0);
	transition: opacity 0.15s linear;
	pointer-events: none;
}

.topbar.scrolled { color: var(--ink); }

.topbar .brand { letter-spacing: 3px; }
.topbar .brand:hover { opacity: 0.7; }

.brand-group {
	display: flex;
	align-items: center;
	gap: 12px;
}

.langs a {
	display: inline-block;
	margin-left: 10px;
	padding: 2px 5px;
	opacity: 0.7;
}
.langs a:hover { opacity: 1; }
.langs a.current {
	opacity: 1;
	border-bottom: 2px solid currentColor;
}

/* Section menu: a compact toggle button between the brand and the
   language switcher; clicking it expands a dropdown with the CV
   sections. Links use /{lang}/#section paths so they also work from the
   subpages. The dropdown carries its own dark backdrop because the bar
   itself is transparent over the hero.
   Visibility: over the hero nothing shows at any size. Wide screens get
   the inline section links once scrolled (see the media query below);
   phones get the button once scrolled, opening the dropdown instead. */
.topbar .menu {
	position: relative;
}
.menu-toggle {
	display: none;
	background: none;
	border: 1px solid currentColor;
	color: inherit;
	font-family: var(--font);
	font-size: 11.5px;
	font-weight: 700;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	padding: 6px 14px;
	cursor: pointer;
	opacity: 0.85;
	transition: opacity 0.2s ease, background-color 0.2s ease;
}
.menu-toggle:hover { opacity: 1; }
.menu-toggle:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}
.menu-list {
	position: absolute;
	top: calc(100% + 12px);
	/* Anchored to the button's left edge: on phones the button sits at
	   the far left of the bar, so the dropdown must grow rightward —
	   right-anchoring pushed it out of the viewport. */
	left: 0;
	z-index: 40;
	display: none;
	flex-direction: column;
	min-width: 220px;
	padding: 8px 0;
	background-color: rgba(24, 11, 12, 0.92);
	-webkit-backdrop-filter: blur(6px);
	backdrop-filter: blur(6px);
	border: 1px solid var(--hairline);
	box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
}
.menu.is-open .menu-list { display: flex; }
.menu-list a {
	padding: 10px 20px;
	font-size: 11.5px;
	font-weight: 700;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	opacity: 0.8;
	transition: opacity 0.2s ease, color 0.2s ease, background-color 0.2s ease;
}
.menu-list a:hover {
	opacity: 1;
	color: var(--accent-bright);
	background-color: rgba(255, 255, 255, 0.04);
}
.menu-list a:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}

/* Wide screens: once the visitor scrolls into the content (parallax.js
   adds .scrolled when the About section approaches) the section links
   sit directly in the bar; the toggle never shows here. */
@media (min-width: 941px) {
	.topbar.scrolled .menu-list {
		display: flex;
		flex-direction: row;
		gap: 20px;
		position: static;
		min-width: 0;
		padding: 0;
		background: none;
		border: 0;
		box-shadow: none;
		-webkit-backdrop-filter: none;
		backdrop-filter: none;
	}
	.topbar.scrolled .menu-list a {
		padding: 2px 0;
		background: none;
	}
}

/* Narrow screens: the toggle appears only once scrolled into the
   content; over the hero the bar stays clean. The name drops out of the
   bar on phones — contact lives in the footer section. */
@media (max-width: 940px) {
	.topbar .brand { display: none; }
	.topbar.scrolled .menu-toggle { display: inline-block; }
}

/* Anchored sections stop below the fixed topbar instead of under it.
   About is the exception: it is the first section after the hero, and the
   76px offset would leave a strip of hero artwork on screen — landing
   exactly on its top matches where the hero scroll cue arrives. */
main > section[id] { scroll-margin-top: 76px; }
#about { scroll-margin-top: 0; }

/* Arriving with a hash (subpage backlinks): the hero is hidden until the
   fragment jump happened, so the pre-jump paint cannot flash it. */
html.cv-arriving #keyart { visibility: hidden; }

/* --------------------------------------------------------------------------
   Boot loader — full-screen veil with a progress bar, shown (JS only) until
   every hero layer image is downloaded, so the parallax scene never paints
   half-loaded over its orange base. The overlay element is rendered only on
   pages that have the hero; without JavaScript cv-loading is never added, so
   the site simply renders as before. preload.js swaps cv-loading for
   cv-ready, fading the veil out over the fully composed scene.
   -------------------------------------------------------------------------- */
.boot {
	position: fixed;
	inset: 0;
	z-index: 100; /* above the fixed topbar (30) and the keyart (10) */
	display: flex;
	align-items: center;
	justify-content: center;
	background-color: var(--bg);
	opacity: 0;
	visibility: hidden;
	transition: opacity 0.6s ease, visibility 0s linear 0.6s;
}
html.cv-loading .boot {
	opacity: 1;
	visibility: visible;
	transition: opacity 0.2s ease;
}
html.cv-ready .boot {
	opacity: 0;
	visibility: hidden;
}

.boot-box {
	width: min(320px, 72vw);
	text-align: center;
}

.boot-title {
	color: var(--ink-muted);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 3px;
	text-transform: uppercase;
	margin-bottom: 14px;
}

.boot-track {
	position: relative;
	height: 3px;
	border-radius: 2px;
	background: rgba(247, 234, 209, 0.14);
	overflow: hidden;
}

/* Width is set by preload.js (one step per downloaded layer image). */
.boot-fill {
	position: absolute;
	top: 0;
	left: 0;
	bottom: 0;
	width: 0;
	border-radius: 2px;
	background-color: var(--accent);
	box-shadow: 0 0 12px rgba(255, 175, 0, 0.55);
	transition: width 0.25s ease;
}
@media (prefers-reduced-motion: reduce) {
	.boot, .boot-fill { transition: none; }
}

/* --------------------------------------------------------------------------
   Key art hero — layered parallax
   Eight layers, back to front: sky/sea base, far mountain range, right-hand
   spur, left-hand ridge, city headland, city hill, figure on the cliff,
   foreground rocks. Layers are position:fixed; JS translates each by
   -scrollY * speed/100. The front layer (keyart-7, speed 100) stays in
   normal flow so it scrolls naturally with the page. The dusk scrim sits
   above the city layers but below the figure and rocks, so the foreground
   silhouette stays crisp as the light fades. Portrait screens swap in the
   recomposed portrait artwork. Parallax runs on all screen sizes;
   reduced-motion users get the layers as a static stack (JS never applies
   transforms).
   -------------------------------------------------------------------------- */

.keyart {
	position: relative;
	z-index: 10;
	/* 1000px is the design height for laptop-sized windows; on taller
	   displays the hero grows to fill the viewport so the CV content only
	   starts below the fold. */
	height: max(1000px, 100vh);
	background-color: var(--bg);
}

.keyart_layer {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	/* Matches the hero (not 100%: the JS-translated layers are position
	   fixed, and 100% would resolve against the viewport there, leaving a
	   gap under the hero on windows shorter than 1000px). */
	height: max(1000px, 100vh);
	/* 55% from the top balances sky against foreground rocks whenever the
	   upscaled artwork is taller than the hero: the sun and mountain line
	   stay in frame while the rocks still anchor the bottom of the scene
	   (bottom-anchoring would crop the sky down to a thin strip, top-
	   anchoring would push the rocks out of the hero). */
	background-position: center 55%;
	background-repeat: no-repeat;
	background-size: cover;
}

#keyart-0 { background-image: url("../img/parallax/parallax0.png"); background-color: #eb8e48; }
#keyart-1 { background-image: url("../img/parallax/parallax1.png"); }
#keyart-2 { background-image: url("../img/parallax/parallax2.png"); }
#keyart-3 { background-image: url("../img/parallax/parallax3.png"); }
#keyart-4 { background-image: url("../img/parallax/parallax4.png"); }
#keyart-5 { background-image: url("../img/parallax/parallax5.png"); }
#keyart-6 { background-image: url("../img/parallax/parallax6.png"); }
#keyart-7 { background-image: url("../img/parallax/parallax7.png"); }

/* Dusk veil: scroll-linked (parallax.js) so the scene darkens into the CV
   background like the sun going down behind the mountains. Sits above the
   sky/city layers but below the figure on the cliff and the foreground
   rocks, which stay crisp shapes as the light fades. */
#keyart-scrim {
	background-color: var(--bg);
	opacity: 0;
}

/* Retina variants (all 2840x2500). Layers keep the shared 55% anchor: with
   the artwork and hero scaled to the same viewport it balances sky and
   rocks the same way the 1x set does. */
@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {
	#keyart-0 { background-image: url("../img/parallax/parallax0@2x.png"); background-color: #eb8e48; }
	#keyart-1 { background-image: url("../img/parallax/parallax1@2x.png"); }
	#keyart-2 { background-image: url("../img/parallax/parallax2@2x.png"); }
	#keyart-3 { background-image: url("../img/parallax/parallax3@2x.png"); }
	#keyart-4 { background-image: url("../img/parallax/parallax4@2x.png"); }
	#keyart-5 { background-image: url("../img/parallax/parallax5@2x.png"); }
	#keyart-6 { background-image: url("../img/parallax/parallax6@2x.png"); }
	#keyart-7 { background-image: url("../img/parallax/parallax7@2x.png"); }
}

/* Portrait screens get the recomposed portrait artwork (1080x1920), which
   is sharp at phone pixel densities without a separate @2x set. Layers
   anchor left: the figure stands at the left edge of the portrait canvas,
   and center-cropping a phone-width window would cut her out entirely.
   Vertical centering keeps both the sunset and the foreground rocks in
   frame on the tablet sizes where the canvas is taller than the hero
   (on phones the artwork fits the hero exactly, so it does not matter).
   Layer 2 has no mobile export: it is a small distant spur at the right
   horizon edge, and the landscape fallback would be cropped away on a
   narrow screen anyway, so the layer is simply omitted there. Keep this
   block after the retina block so it wins on high-dpi phones. */
@media (max-width: 1024px) and (orientation: portrait) {
	.keyart_layer { background-position: left center; }
	#keyart-0 { background-image: url("../img/parallax/parallax0_mobile.png"); background-color: #eb8e48; }
	#keyart-1 { background-image: url("../img/parallax/parallax1_mobile.png"); }
	#keyart-2 { background-image: none; }
	#keyart-3 { background-image: url("../img/parallax/parallax3_mobile.png"); }
	#keyart-4 { background-image: url("../img/parallax/parallax4_mobile.png"); }
	#keyart-5 { background-image: url("../img/parallax/parallax5_mobile.png"); }
	#keyart-6 { background-image: url("../img/parallax/parallax6_mobile.png"); }
	#keyart-7 { background-image: url("../img/parallax/parallax7_mobile.png"); }
}

/* Reduced motion: keep the layers absolutely stacked inside the hero (no
   fixed positioning, no JS transforms) so the scene just scrolls with the page. */
@media (prefers-reduced-motion: no-preference) {
	.keyart_layer.parallax { position: fixed; }
}

/* Hero text (scrolls with the page like the foreground layer) */
.hero-inner {
	position: absolute;
	top: 13%;
	left: 50%;
	transform: translateX(-50%);
	z-index: 2;
	width: min(92%, 900px);
	text-align: center;
	color: var(--bg);
}

.hero-eyebrow {
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 6px;
	text-transform: uppercase;
	opacity: 0.85;
}

.hero-name {
	margin-top: 10px;
	font-size: clamp(38px, 6vw, 64px);
	line-height: 1.05;
	letter-spacing: 3px;
	text-transform: uppercase;
	font-weight: 300;
}
.hero-name strong { font-weight: 800; }

.hero-role {
	margin-top: 14px;
	font-size: clamp(14px, 2vw, 18px);
	font-weight: 700;
	letter-spacing: 2.5px;
	text-transform: uppercase;
}
.hero-role .dot { color: var(--accent-dark); padding: 0 2px; }

/* On phones the role/tag line crowds the small hero and hides most of the
   artwork; the intro section below carries the same information. */
@media (max-width: 600px) {
	.hero-role { display: none; }
}

.scroll-cue {
	display: inline-block;
	margin-top: 34px;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 4px;
	text-transform: uppercase;
	color: var(--bg);
}
.scroll-cue:hover { opacity: 0.7; }

.chevron {
	display: block;
	margin-top: 4px;
	font-size: 30px;
	line-height: 1;
	animation: bob 1.7s ease-in-out infinite;
}
@keyframes bob {
	0%, 100% { transform: translateY(0); }
	50% { transform: translateY(9px); }
}
@media (prefers-reduced-motion: reduce) {
	.chevron { animation: none; }
}

/* --------------------------------------------------------------------------
   Content sections
   -------------------------------------------------------------------------- */

#main {
	position: relative;
	z-index: 20;
	background-color: var(--bg);
}

.section {
	max-width: 880px;
	margin: 0 auto;
	padding: 76px 30px 26px;
}

/* Full-bleed variant (the skill web spans the whole viewport; only the
   heading stays in the normal text column). */
.section-full {
	max-width: none;
	padding-left: 0;
	padding-right: 0;
}
.section-head {
	max-width: 880px;
	margin: 0 auto;
	padding: 0 30px;
}

.banner {
	margin-bottom: 46px;
	padding-bottom: 12px;
	border-bottom: 2px solid var(--accent);
}
.banner span {
	display: inline-block;
	color: var(--accent);
	font-size: 21px;
	font-weight: 800;
	letter-spacing: 4px;
	text-transform: uppercase;
}

/* About */

.about-grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 320px;
	gap: 44px;
	align-items: start;
}

#about .lead {
	color: var(--ink);
	font-size: 21px;
	line-height: 1.65;
}
/* Short accent rule between the two about paragraphs. */
.lead-divider {
	border: none;
	border-top: 2px solid var(--accent);
	width: 64px;
	margin: 24px 0;
}

.about-photo {
	position: relative;
	margin: 6px 14px 14px 0;
}
.about-photo img {
	display: block;
	width: 100%;
	height: auto;
	border: 1px solid var(--hairline);
}
/* Offset accent frame echoing the sunset palette behind the photo. */
.about-photo::after {
	content: "";
	position: absolute;
	top: 14px;
	right: -14px;
	bottom: -14px;
	left: 14px;
	border: 1px solid var(--accent-dark);
	z-index: -1;
}

@media (max-width: 720px) {
	.about-grid { grid-template-columns: 1fr; gap: 30px; }
	.about-photo { max-width: 380px; }
}

/* Skills — iconified cards: one card per skill group with an icon, the
   group name and its skills as pills. The icon hues follow the same
   sunset ramp the old skill web used (baked into the SVG files), and
   the card accents below mirror them per position. */

.skill-cards {
	list-style: none;
	margin: 34px auto 0;
	padding: 0 24px;
	max-width: 1198px;
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	align-items: start;
	gap: 22px;
}

.skill-card {
	border: 1px solid var(--hairline);
	border-radius: 14px;
	padding: 22px;
	background: rgba(255, 255, 255, 0.02);
	/* Header line: icon and title side by side; the toggle and the
	   collapsible list sit below. */
	display: grid;
	grid-template-columns: auto 1fr;
	grid-template-areas:
		"icon title"
		"toggle toggle"
		"body body";
	align-items: center;
	column-gap: 14px;
	transition: border-color 0.24s ease;
}

/* With JS the whole card toggles its list, so it gets a pointer cursor
   and a slightly brighter border while hovered. */
.cv-js .skill-card { cursor: pointer; }
.skill-card:hover { border-color: var(--accent-dark); }

.skill-icon {
	display: block;
	grid-area: icon;
	width: 46px;
	height: 46px;
	margin: 0;
	object-fit: contain;
	object-position: left center;
}

.skill-name {
	grid-area: title;
	margin: 0;
	font-size: 16px;
	font-weight: 700;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--ink);
}

.skill-items {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

.skill-items li {
	border: 1px solid var(--hairline);
	border-radius: 999px;
	padding: 4px 12px;
	color: var(--ink-muted);
	font-size: 13.5px;
	letter-spacing: 0.3px;
}

.skill-logos {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 16px 10px;
}

.skill-logos li {
	display: flex;
	min-width: 0;
	flex-direction: column;
	align-items: center;
	gap: 7px;
	color: var(--ink-muted);
	font-size: 12px;
	line-height: 1.25;
	text-align: center;
	overflow-wrap: anywhere;
}

.skill-logos img {
	width: 40px;
	height: 40px;
	object-fit: contain;
}

/* Per-card toggle: collapsed cards show only the icon, title and button;
   opening slides the skills list out and flips the arrow with the labels
   swapped. Collapsing is applied from a <head> script (html.cv-js), so
   cards render collapsed from the first paint and without JavaScript
   every card shows its skills. */
.skill-toggle {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	grid-area: toggle;
	justify-self: start;
	margin: 14px 0 0;
	padding: 0;
	border: none;
	background: transparent;
	color: var(--accent);
	cursor: pointer;
	font-family: inherit;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	transition: color 0.24s ease;
}
.skill-toggle:hover,
.skill-toggle:focus-visible {
	color: var(--accent-bright);
	outline: none;
}
.skill-toggle .cv-arrow {
	font-size: 13px;
	line-height: 1;
	transition: transform 0.24s ease;
}
.skill-less-label { display: none; }
.skill-card.skill-open .skill-arrow { transform: rotate(180deg); }
.skill-card.skill-open .skill-more-label { display: none; }
.skill-card.skill-open .skill-less-label { display: inline; }
.skill-body {
	grid-area: body;
	display: grid;
	grid-template-rows: 1fr;
	transition: grid-template-rows 0.3s ease;
}
.skill-body-wrap { min-height: 0; overflow: hidden; }
.skill-body-wrap > :first-child { margin-top: 14px; }
.cv-js .skill-card:not(.skill-open) .skill-body { grid-template-rows: 0fr; }

/* Per-card accent on the group name, same sunset ramp as the icons. */
.skill-card:nth-child(1) .skill-name { color: #ffd166; }
.skill-card:nth-child(2) .skill-name { color: #ffaf00; }
.skill-card:nth-child(3) .skill-name { color: #ff8c1a; }
.skill-card:nth-child(4) .skill-name { color: #ff6b35; }
.skill-card:nth-child(5) .skill-name { color: #f04e3e; }
.skill-card:nth-child(6) .skill-name { color: #d84361; }
.skill-card:nth-child(7) .skill-name { color: #c9577f; }
.skill-card:nth-child(8) .skill-name { color: #b06fa8; }
.skill-card:nth-child(9) .skill-name { color: #9a7fd1; }

@media (prefers-reduced-motion: reduce) {
	.menu-toggle, .menu-list a { transition: none; }
}

@media (max-width: 900px) {
	.skill-cards { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
	.skill-cards { grid-template-columns: 1fr; gap: 12px; }
	.skill-card { padding: 12px 16px; column-gap: 12px; }
	.skill-icon { width: 32px; height: 32px; }
	.skill-name { font-size: 15px; }
	.skill-toggle { margin-top: 10px; }
	.skill-body-wrap > :first-child { margin-top: 12px; }
}

/* Experience and education */

.edu, .vol { margin-bottom: 46px; }

/* Experience timeline: the years sit right-aligned left of a vertical line,
   hollow dots mark each title, and the line runs per entry so it starts at
   the first dot, threads every entry and fades out below the last one.
   The entries after the first three (early career) collapse behind the
   button below the list; the first hidden entry stays visible but faded
   as a preview. Collapsing is applied from a <head> script (html.cv-js),
   so without JavaScript every entry is simply visible. */

.cv {
	list-style: none;
	margin: 0;
	padding: 0;
	/* Year column: sized for the longest localized ongoing label
	   ("2025 – presente"); narrower widths made that text overflow
	   right-aligned boxes toward the line. */
	--tl-time: 150px;
	--tl-gap: 34px;   /* gutter the line runs through */
	--tl-indent: calc(var(--tl-time) + var(--tl-gap));
}

.cv-item {
	position: relative;
	margin: 0;
	padding: 0 0 42px var(--tl-indent);
}
.cv-item:last-child { padding-bottom: 0; }

.cv-item::before {
	content: "";
	position: absolute;
	left: calc(var(--tl-indent) + var(--tl-time) + (var(--tl-gap) / 2) - 1px);
	top: 11px;
	/* Runs 11px past the box so the segments join at the next dot. */
	bottom: -11px;
	width: 2px;
	background: var(--hairline);
}
/* The final segment is short and dissolves below the last dot. */
.cv-item:last-child::before {
	bottom: auto;
	height: 30px;
	background: linear-gradient(to bottom, var(--hairline) 0%, transparent 100%);
}
/* The preview entry: a solid line runs under a fade overlay that covers
   the whole entry — line, dot and text dissolve together while collapsed,
   and on expand the overlay lifts gradually so the line emerges with the
   text (no popping). Expanded it is a plain segment joining the next dot. */
.cv-faded::before { bottom: 0; }
.cv-open .cv-faded::before { bottom: -11px; }
.cv-fade {
	position: absolute;
	inset: 0;
	z-index: 2;
	background: linear-gradient(180deg, rgba(24, 11, 12, 0) 0%, var(--bg) 82%);
	pointer-events: none;
	transition: opacity 0.48s ease;
}
.cv-open .cv-fade { opacity: 0; }

/* Hollow dot centred on the title's first line, on the line. It lives on
   the item (not inside the clipped wrapper) so it also renders on the
   collapsed-when-closed entries. */
.cv-item::after {
	content: "";
	position: absolute;
	top: 5px;
	left: calc(var(--tl-indent) + var(--tl-time) + (var(--tl-gap) / 2) - 6px);
	width: 12px;
	height: 12px;
	border: 2px solid var(--accent);
	border-radius: 50%;
	background: var(--bg);
	z-index: 1;
}

/* While collapsed the preview's dot dims its ring only — the centre keeps
   the opaque page background so the line never shows through the middle.
   The ring uses the pre-blended dim colour (35% accent over the page
   background) rather than alpha, so it stays uniform where it crosses
   the line instead of brightening in the overlap. The fade overlay above
   dims it a touch further toward the entry's end. */
.cv-js .cv-faded::after { border-color: #694408; }

.cv-exp {
	display: flex;
	align-items: flex-start;
	gap: var(--tl-gap);
}

.cv-time {
	flex: none;
	width: var(--tl-time);
	margin: 3px 0 0;
	color: var(--accent-dark);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 1.5px;
	line-height: 1.2;
	text-align: right;
	text-transform: uppercase;
	white-space: nowrap;
}

.cv-details { flex: 1; min-width: 0; }

.cv-title {
	position: relative;
	color: var(--ink);
	font-size: 18px;
	letter-spacing: 1.5px;
	line-height: 1.2;
	text-transform: uppercase;
}

.cv-details .org { margin: 5px 0 0; }

/* The description aligns with the title column, right of the line. */
.cv-item .bullets,
.cv-item .vol-body { margin-left: var(--tl-indent); }
.cv-item .vol-body { margin-top: 8px; }

/* Collapsed early-career entries. */
.cv-extra { display: grid; grid-template-rows: 1fr; transition: grid-template-rows 0.3s ease; }
.cv-js .cv-extra { grid-template-rows: 0fr; padding-bottom: 0; }
.cv-js .cv-extra .cv-wrap { overflow: hidden; min-height: 0; }
.cv-js .cv-extra::before,
.cv-js .cv-extra::after { content: none; }

/* Open state: everything expands, the preview fades in and the button's
   arrow flips with the labels swapped. */
.cv-open .cv-extra { grid-template-rows: 1fr; padding-bottom: 42px; }
.cv-open .cv-extra:last-child { padding-bottom: 0; }
.cv-open .cv-extra::before,
.cv-open .cv-extra::after { content: ""; }
.cv-open .cv-faded::after { border-color: var(--accent); }

.cv-more-row { margin: 26px 0 0; }
.cv-more {
	display: inline-flex;
	align-items: center;
	gap: 9px;
	padding: 9px 14px;
	border: 1px solid var(--hairline);
	background: transparent;
	color: var(--accent);
	cursor: pointer;
	font-family: inherit;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	transition: color 0.24s ease, border-color 0.24s ease;
}
.cv-more:hover,
.cv-more:focus-visible {
	color: var(--accent-bright);
	border-color: var(--accent-dark);
	outline: none;
}
.cv-arrow {
	display: inline-block;
	font-size: 15px;
	line-height: 1;
	transition: transform 0.24s ease;
}
.cv-less-label { display: none; }
.cv-open .cv-arrow { transform: rotate(180deg); }
.cv-open .cv-more .cv-more-label { display: none; }
.cv-open .cv-more .cv-less-label { display: inline; }

@media (max-width: 640px) {
	/* The line stays; the years stack under the title (as on the reference). */
	.cv { --tl-time: 0px; --tl-gap: 48px; }
	.cv-item { padding-left: var(--tl-gap); }
	.cv-item::before { left: calc((var(--tl-gap) / 2) - 1px); top: 10px; }
	.cv-item::after { left: calc((var(--tl-gap) / 2) - 6px); top: 4px; }
	.cv-exp { flex-direction: column; gap: 7px; }
	.cv-item .bullets,
	.cv-item .vol-body { margin-left: 0; }
	.cv-details { order: 1; }
	.cv-time {
		order: 2;
		width: auto;
		margin: 0;
		text-align: left;
	}
	.cv-title { font-size: 16px; }
}
@media (prefers-reduced-motion: reduce) {
	.cv-extra, .cv-arrow, .cv-fade, .skill-body, .skill-toggle .cv-arrow, .skill-card { transition: none; }
}

.edu header, .vol header {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 16px;
	flex-wrap: wrap;
}

.edu h3, .vol h3 {
	color: var(--ink);
	font-size: 18px;
	letter-spacing: 1.5px;
	text-transform: uppercase;
}

.dates {
	color: var(--accent-dark);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	white-space: nowrap;
}

.org {
	margin: 6px 0 14px;
	color: var(--accent);
	font-size: 14.5px;
	font-weight: 700;
	letter-spacing: 0.5px;
}
.org .meta {
	color: var(--ink-muted);
	font-weight: 400;
}

.bullets { list-style: none; }
.bullets li {
	position: relative;
	padding-left: 24px;
	margin-bottom: 9px;
	color: var(--ink-muted);
	font-size: 15.5px;
	line-height: 1.7;
}
.bullets li::before {
	content: "\2013";
	position: absolute;
	left: 0;
	color: var(--accent);
	font-weight: 700;
}

.edu .details {
	margin-top: 10px;
	padding-left: 20px;
	border-left: 2px solid var(--hairline);
	color: var(--ink-muted);
	font-size: 14.5px;
	line-height: 1.7;
}

/* Volunteering */

.vol h3 { font-size: 15.5px; }
.vol-role { color: var(--accent); text-transform: none; letter-spacing: 0.3px; }
.vol .dates { margin-top: 4px; }
.vol-body {
	margin-top: 8px;
	color: var(--ink-muted);
	font-size: 15.5px;
	line-height: 1.7;
}
.vol-body a {
	color: var(--accent);
	text-decoration: underline;
}
.vol-body a:hover { color: var(--accent-bright); }

/* Field/cruise experience — same article format as volunteering, with the
   location as the entry's heading above the period. */
.cruise .vol-body { margin-top: 4px; }

/* Publications & manuscripts — numbered citations; published articles
   link out, manuscripts carry a status note instead. */
.pub {
	position: relative;
	padding-left: 34px;
}
.pub-num {
	position: absolute;
	left: 0;
	top: 1px;
	color: var(--accent);
	font-size: 13px;
	font-weight: 800;
}
.pub .vol-body { margin-top: 0; }
.pub .vol-body a {
	color: inherit;
	text-decoration: none;
	transition: color 0.2s ease;
}
.pub .vol-body a:hover { color: var(--accent-bright); }
.pub-status { color: var(--ink-muted); font-style: italic; }

/* Subpage teasers (academic achievements, fieldwork) — on the main page
   a single banner-style link with an arrow pointing at the subpage; the
   subpages themselves keep their lists under lighter styling. */
a.achievements-link,
a.section-link { display: block; }
a.achievements-link .banner,
a.section-link .banner {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	margin-bottom: 0;
}
a.achievements-link .banner span,
a.section-link .banner span { transition: color 0.2s ease; }
.banner-arrow {
	color: var(--accent-bright);
	font-size: 28px;
	font-weight: 800;
	text-shadow: 0 0 12px rgba(255, 175, 27, 0.55);
	transition: transform 0.2s ease, text-shadow 0.2s ease;
}
a.achievements-link:hover .banner-arrow,
a.section-link:hover .banner-arrow {
	transform: translateX(8px);
	text-shadow: 0 0 18px rgba(255, 175, 27, 0.85);
}
a.achievements-link:hover .banner span:first-child,
a.section-link:hover .banner span:first-child { color: var(--accent-bright); }
a.achievements-link:focus-visible,
a.section-link:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 4px;
}

/* Fieldwork subpage: the two research vessels side by side above the
   cruise entries. */
.boat-grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 18px;
	margin-bottom: 40px;
}
.boat-grid .boat { margin: 0; }
.boat-grid img {
	display: block;
	width: 100%;
	height: auto;
	border: 1px solid var(--hairline);
}
@media (max-width: 640px) {
	.boat-grid { grid-template-columns: 1fr; }
	/* Phones keep the original compact arrow; the enlarged, glowing
	   one is a desktop-only tweak. */
	.banner-arrow {
		color: var(--accent);
		font-size: 22px;
		text-shadow: none;
	}
	a.achievements-link:hover .banner-arrow,
	a.section-link:hover .banner-arrow { text-shadow: none; }
}

.subhead {
	margin: 48px 0 26px;
	padding-bottom: 10px;
	border-bottom: 2px solid var(--accent);
}
.subhead span {
	color: var(--accent);
	font-size: 16px;
	font-weight: 800;
	letter-spacing: 3px;
	text-transform: uppercase;
}

/* "Contact me on LinkedIn" — boxed call-to-action shown under the about
   text and again at the top of the footer. */
.linkedin-cta {
	display: flex;
	align-items: center;
	gap: 16px;
	margin: 30px auto 0;
	max-width: 560px;
	padding: 16px 22px;
	border: 1px solid var(--hairline);
	background-color: rgba(24, 11, 12, 0.55);
	transition: border-color 0.2s ease, background-color 0.2s ease;
}
.linkedin-cta svg {
	flex: none;
	width: 24px;
	height: 24px;
	fill: var(--accent);
	transition: fill 0.2s ease;
}
.linkedin-cta:hover {
	border-color: var(--accent);
	background-color: rgba(24, 11, 12, 0.9);
}
.linkedin-cta:hover svg { fill: var(--accent-bright); }
.linkedin-cta:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}
.cta-text {
	display: flex;
	flex-direction: column;
	gap: 2px;
	text-align: left;
}
.cta-label {
	color: var(--ink);
	font-weight: 700;
	font-size: 15px;
	letter-spacing: 0.5px;
}
.cta-sub {
	color: var(--ink-muted);
	font-size: 12.5px;
	letter-spacing: 1px;
}
/* The email in the footer is interleaved with decoy fragments (footer.html):
   every span inside .cta-sub is junk. Hiding it here shows humans the real
   address, while scrapers reading the raw HTML without applying CSS are left
   with a mangled string. display:none also keeps the decoys out of copies
   and screen readers. */
.cta-sub span { display: none; }
/* The footer variant sits inside the contact row, centred like the
   rest of the footer. */
.footer-cta {
	margin: 0 auto 34px;
	justify-content: center;
}
/* Footer contact row: the LinkedIn and email calls-to-action side by
   side with an "or" between them; stacks vertically on phones. */
.footer-cta-row {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
	gap: 16px;
	margin: 0 auto 34px;
	max-width: 100%;
}
.footer-cta-row .footer-cta { margin: 0; }
.cta-or {
	color: var(--ink-muted);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 2px;
	text-transform: uppercase;
}
@media (max-width: 760px) {
	.footer-cta-row {
		flex-direction: column;
		gap: 12px;
	}
}

/* Beyond work — hobby cards */

.hobby-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 22px;
}
@media (max-width: 560px) {
	.hobby-grid { grid-template-columns: 1fr; }
}

.hobby-card {
	position: relative;
	display: block;
	aspect-ratio: 16 / 10;
	overflow: hidden;
	border: 1px solid var(--hairline);
	background-color: var(--bg);
}
.hobby-card:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 3px;
}
.hobby-card img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	filter: saturate(0.92);
	transition: transform 0.35s ease, filter 0.35s ease;
}
.hobby-card:hover img,
.hobby-card:focus-visible img {
	transform: scale(1.05);
	filter: none;
}

.hobby-label {
	position: absolute;
	inset: 0;
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 12px;
	background-color: rgba(24, 11, 12, 0.4);
	color: var(--ink);
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 3px;
	text-transform: uppercase;
	text-align: center;
	text-shadow: 0 1px 8px rgba(24, 11, 12, 0.8);
	transition: background-color 0.25s ease, color 0.25s ease;
}
.hobby-card:hover .hobby-label,
.hobby-card:focus-visible .hobby-label {
	color: var(--accent);
	background-color: rgba(24, 11, 12, 0.22);
}
.hobby-arrow {
	font-size: 17px;
	line-height: 1;
	transition: transform 0.25s ease;
}
.hobby-card:hover .hobby-arrow {
	transform: translateX(5px);
}
@media (prefers-reduced-motion: reduce) {
	.hobby-card img, .hobby-arrow, .hobby-label { transition: none; }
	.hobby-card:hover img { transform: none; }
}

/* Photo gallery subpages — justified rows, original aspect ratios.
   gallery.js computes each row: it wraps the .photo links in .photo-row
   divs and sets a pixel width per picture so every row is filled edge to
   edge without cropping. Without JS the photos simply stack full width. */

#main.gallery-root {
	padding-top: 96px;
	min-height: calc(100vh - 200px);
}

.backlink {
	margin-bottom: 26px;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 2px;
	text-transform: uppercase;
}
.backlink a {
	color: var(--accent-dark);
	transition: color 0.2s ease;
}
.backlink a:hover { color: var(--accent); }

.photo-grid { min-height: 1px; }

.photo-row {
	display: flex;
	justify-content: flex-start;
	gap: 14px;
	margin-bottom: 14px;
}
.photo-row.center { justify-content: center; }

.photo {
	flex: 0 1 auto;
	display: block;
	border: 1px solid var(--hairline);
	background-color: var(--bg);
	transition: opacity 0.25s ease;
}
.photo:hover { opacity: 0.85; }
.photo:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 3px;
}
.photo img {
	display: block;
	width: 100%;
	height: auto;
}
@media (prefers-reduced-motion: reduce) {
	.photo { transition: none; }
	.photo:hover { opacity: 1; }
}

/* Lightbox (gallery.js) */

.lightbox {
	position: fixed;
	inset: 0;
	z-index: 60;
	display: flex;
	flex-direction: column;
	background-color: rgba(20, 0, 1, 0.94);
}
.lightbox[hidden] { display: none; }

.lightbox-stage {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 0;
	padding: 24px;
}
.lightbox-stage img {
	max-width: 100%;
	max-height: 100%;
	width: auto;
	height: auto;
	border: 1px solid var(--hairline);
}

.lightbox-bar {
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 26px;
	padding: 10px 16px 22px;
}
.lightbox-bar button {
	background: none;
	border: 1px solid var(--hairline);
	color: var(--ink);
	font-family: var(--font);
	font-size: 20px;
	line-height: 1;
	padding: 8px 16px;
	cursor: pointer;
	transition: color 0.2s ease, border-color 0.2s ease;
}
.lightbox-bar button:hover {
	color: var(--accent);
	border-color: var(--accent);
}
.lightbox-bar button:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}
.lightbox-count {
	color: var(--ink-muted);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 3px;
	min-width: 74px;
	text-align: center;
}

body.lightbox-open { overflow: hidden; }

/* Footer */

.footer {
	max-width: 880px;
	margin: 40px auto 0;
	padding: 44px 30px 56px;
	border-top: 1px solid var(--hairline);
	text-align: center;
}
/* The "Contact" banner heading above the LinkedIn call-to-action. */
.footer .banner { margin-bottom: 30px; }
.footer-contact p {
	margin: 5px 0;
	color: var(--ink-muted);
	letter-spacing: 1px;
}
.footer-contact a {
	color: var(--accent);
	font-weight: 700;
}
.footer-contact a:hover { color: var(--accent-bright); }

.footer-langs {
	display: flex;
	justify-content: center;
	gap: 20px;
	margin: 28px 0;
}
.footer-langs a {
	color: var(--ink-muted);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 2px;
}
.footer-langs a:hover { color: var(--ink); }
.footer-langs a.current {
	color: var(--accent);
	border-bottom: 2px solid var(--accent);
}

.copyright {
	color: var(--accent-dark);
	font-size: 11.5px;
	letter-spacing: 2px;
	text-transform: uppercase;
}
