/* ==========================================================================
   Werdegang — one horizontal axis carrying work and study together.

   Two things had to be true at once:

   1. It must work with no JavaScript, no scroll-timeline support, on a phone,
      and under prefers-reduced-motion. So the rail is a REAL horizontal
      scroller in the markup — drag, swipe, wheel, keyboard, all free.
   2. On a wide screen, vertical scroll should drive it, the way the reference
      site does. cv.js pins the panel and writes the same scrollLeft the user
      would otherwise write by hand.

   One mechanism, two drivers. Nothing is hidden unless a script has already
   proved it can be shown again.
   ========================================================================== */

/* The scroll budget. Height is written by cv.js from the actual track width;
   without it the value stays `auto`, the pin has no travel, and the rail is
   simply a scroller the reader pushes themselves. */
.cv {
	position: relative;
	height: var(--cv-scroll, auto);
}

/* The panel. This carries the paper/radius/shadow that stack.css puts on every
   other section, because #work itself has to give that up to act as the tall
   scroll block. See the exception at the bottom of stack.css. */
.cv__pin {
	display: flex;
	flex-direction: column;
	gap: var(--s-4);
	/* Head at the top, foot at the bottom, rail floating between them at its
	   own height. With the rail on flex:1 instead, every tile stretched to the
	   full height of a 900px panel and its content split to the two far edges
	   with a void down the middle. */
	justify-content: space-between;
	background: var(--paper);
	border-radius: var(--radius-stack);
	box-shadow: 0 -18px 40px -24px color-mix(in srgb, var(--ink) 55%, transparent);
	/* The dock is fixed to the bottom centre of the viewport and would sit on
	   top of the progress bar. Reserve its height, same as .colophon does. */
	padding-block: var(--s-7) calc(var(--s-5) + 3rem);
	padding-inline: var(--content-inset) var(--gutter);
}

.cv__head { flex: 0 0 auto; }
.cv__head .section__title { margin-block-end: var(--s-3); }
.cv__head .section__intro { margin: 0; color: var(--muted); }

/* Rail --------------------------------------------------------------------
   overflow-x on the rail rather than a transform on a track: a real scroll
   container is what makes drag, swipe, keyboard and scroll-snap work without
   reimplementing any of them. */

.cv__rail {
	flex: 0 1 auto;
	/* Never squeeze a tile past the point where its lines survive. If the
	   viewport cannot afford this, the short-viewport rule below unpins the
	   whole section instead of clipping. */
	min-height: 17rem;
	display: flex;
	align-items: stretch;
	gap: var(--s-5);
	list-style: none;
	margin: 0;
	padding: var(--s-2) 0 var(--s-4);
	max-width: none;
	overflow-x: auto;
	overflow-y: hidden;
	overscroll-behavior-x: contain;
	scroll-snap-type: x mandatory;
	/* Programmatic scrollLeft must land instantly while pinned; smooth here
	   would lag one frame behind every wheel event. */
	scroll-behavior: auto;
	scrollbar-width: none;
}

.cv__rail::-webkit-scrollbar { display: none; }

.cv__rail:focus-visible {
	outline: 2px solid var(--signal);
	outline-offset: 4px;
}

/* Snapping fights a programmatic scrollLeft — the browser keeps pulling the
   position back to the nearest snap point mid-write. Off while pinned. */
.cv[data-cv-pinned] .cv__rail { scroll-snap-type: none; }

.cv__item {
	flex: 0 0 auto;
	/* Wider than it needs to be for the text, deliberately. With education
	   moved to its own lane the rail is down to four tiles, and at 26rem three
	   of them fit on a wide screen at once — leaving barely any travel for the
	   pin to map onto. The extra width buys the scroll its distance back. */
	width: min(28rem, 78vw);
	scroll-snap-align: start;
	display: flex;
	max-width: none;
}

/* Tile --------------------------------------------------------------------- */

.cv__tile {
	display: flex;
	flex-direction: column;
	gap: var(--s-3);
	width: 100%;
	padding: var(--s-5);
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	/* The accent bar is the only thing separating work from study. Hue is not
	   doing the work — solid versus dashed is, so it survives greyscale and
	   colour-blind readers alike. */
	border-top-width: 2px;
}

.cv__item--work   .cv__tile { border-top-color: var(--signal); border-top-style: solid; }
.cv__item--school .cv__tile { border-top-color: var(--muted); border-top-style: dashed; }

.cv__meta {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--s-3);
	margin: 0;
	max-width: none;
	font-family: var(--font-data);
	font-size: var(--mono-label);
	letter-spacing: var(--track-label);
}

.cv__span {
	color: var(--ink);
	font-size: 1.05rem;
	letter-spacing: 0;
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

/* Catalogue numbering, borrowed from the reference site. It tells you how far
   through the run you are without needing to count tiles. */
.cv__index {
	color: var(--muted);
	white-space: nowrap;
}

.cv__role {
	margin: 0;
	font-size: 1.1875rem;
	line-height: 1.2;
	color: var(--ink);
	max-width: none;
	text-wrap: balance;
}

.cv__org {
	margin: 0;
	max-width: none;
	color: var(--ink);
	font-size: var(--body-sm);
}

.cv__where {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.6ch;
	margin: 0;
	max-width: none;
	font-family: var(--font-data);
	font-size: var(--mono-label);
	letter-spacing: var(--track-label);
	text-transform: lowercase;
	color: var(--muted);
}

.cv__item--work   .cv__kind { color: var(--signal); }
.cv__item--school .cv__kind { color: var(--muted); }

.cv__kind::before { content: "#"; opacity: 0.7; }
/* --rule is a border colour; at text size on --surface it disappears
   entirely and the separators read as plain double spaces. */
.cv__place::before,
.cv__note::before { content: "· "; opacity: 0.55; }

.cv__lines {
	list-style: none;
	margin: auto 0 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--s-2);
	padding-block-start: var(--s-3);
	border-top: 1px solid var(--rule);
}

.cv__lines li {
	position: relative;
	padding-inline-start: var(--s-4);
	color: var(--muted);
	font-size: var(--body-sm);
	max-width: none;
}

.cv__lines li::before {
	content: "";
	position: absolute;
	inset-inline-start: 0;
	top: 0.7em;
	width: var(--s-2);
	height: 1px;
	background: currentColor;
	opacity: 0.55;
}

/* Foot — progress and affordance ------------------------------------------- */

.cv__foot {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: var(--s-4);
}

.cv__track {
	flex: 1 1 auto;
	height: 1px;
	background: var(--rule);
	position: relative;
}

.cv__bar {
	position: absolute;
	inset-block: -1px;
	inset-inline-start: 0;
	width: 0;
	background: var(--signal);
	transition: width 90ms linear;
}

/* Once the pin is driving the rail, "scroll sideways" is wrong — the reader
   does nothing but keep scrolling down. */
.cv[data-cv-pinned] .cv__hint { display: none; }

.cv__hint {
	flex: 0 0 auto;
	margin: 0;
	max-width: none;
	font-family: var(--font-data);
	font-size: var(--mono-label);
	letter-spacing: var(--track-label);
	text-transform: uppercase;
	color: var(--muted);
	white-space: nowrap;
}

/* ==========================================================================
   Education lane.

   The second lane under the rail. Where the rail is ordered but not scaled,
   this one is scaled: bar position and width come straight from the years, so
   a three-year degree is three years wide and the gaps between them are real.

   Label column and bar column share one track definition through --edu-cols,
   so the axis underneath lines up with the bars above it instead of being
   nudged into place with a magic margin.
   ========================================================================== */

.edu {
	--edu-cols: minmax(0, 21rem) minmax(0, 1fr);
	flex: 0 0 auto;
	padding-block-start: var(--s-4);
	border-top: 1px solid var(--rule);
}

.edu__title {
	margin: 0 0 var(--s-3);
	color: var(--muted);
}

.edu__rows {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--s-2);
}

.edu__row,
.edu__axis {
	display: grid;
	grid-template-columns: var(--edu-cols);
	gap: var(--s-4);
	align-items: center;
	max-width: none;
}

.edu__label {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.2ch 0.8ch;
	margin: 0;
	max-width: none;
	font-size: var(--body-sm);
	min-width: 0;
}

.edu__years {
	font-family: var(--font-data);
	font-size: var(--mono-label);
	letter-spacing: var(--track-label);
	font-variant-numeric: tabular-nums;
	color: var(--muted);
	white-space: nowrap;
}

.edu__degree { color: var(--ink); }

.edu__org,
.edu__note {
	font-family: var(--font-data);
	font-size: var(--mono-label);
	letter-spacing: var(--track-label);
	text-transform: lowercase;
	color: var(--muted);
}

.edu__org::before,
.edu__note::before { content: "· "; opacity: 0.55; }

/* Bar ---------------------------------------------------------------------- */

.edu__track {
	position: relative;
	height: 0.5rem;
	min-width: 0;
	border-radius: 999px;
	background: color-mix(in srgb, var(--rule) 60%, transparent);
}

/* Dashed above, solid here would contradict the rail's own coding — the tile
   accents already say dashed means study. The fill keeps that reading by
   staying in --muted rather than borrowing --signal from the work lane. */
.edu__fill {
	position: absolute;
	inset-block: 0;
	border-radius: 999px;
	background: var(--muted);
	min-width: 2px;
}

/* Axis --------------------------------------------------------------------- */

.edu__axis { margin-block-start: var(--s-2); }

.edu__axis-pad { display: block; }

.edu__ticks {
	position: relative;
	height: 1.4em;
	min-width: 0;
	border-top: 1px solid var(--rule);
}

.edu__tick {
	position: absolute;
	top: 0;
	transform: translateX(-50%);
	padding-block-start: 0.35em;
	font-family: var(--font-data);
	font-size: var(--mono-label);
	letter-spacing: var(--track-label);
	font-variant-numeric: tabular-nums;
	color: var(--muted);
	white-space: nowrap;
}

.edu__tick::before {
	content: "";
	position: absolute;
	top: 0;
	inset-inline-start: 50%;
	width: 1px;
	height: 0.3em;
	background: var(--rule);
}

/* The outermost ticks would otherwise hang half outside the panel. */
.edu__tick--first { transform: none; }
.edu__tick--first::before { inset-inline-start: 0; }

.edu__tick--last { transform: translateX(-100%); }
.edu__tick--last::before { inset-inline-start: 100%; }

/* Entrance -----------------------------------------------------------------
   Gated on [data-cv-anim], which cv.js only sets once its observer is live.
   If the script never runs, nothing is ever transparent. */

.cv[data-cv-anim] .cv__item {
	opacity: 0;
	transform: translateX(3.5rem) rotate(1.2deg);
	transition:
		opacity 520ms var(--ease),
		transform 620ms var(--ease);
}

.cv[data-cv-anim] .cv__item.is-in {
	opacity: 1;
	transform: none;
}

/* The panel below the rail — capability blocks and inventory. Same panel
   treatment as any other section, so the two read as one stack. */
.work__rest {
	background: var(--paper);
	border-radius: var(--radius-stack);
	box-shadow: 0 -18px 40px -24px color-mix(in srgb, var(--ink) 55%, transparent);
	padding-block: var(--s-8);
	padding-inline: var(--content-inset) var(--gutter);
}

.work__rest > :first-child { margin-block-start: 0; }
.work__rest .section__intro { color: var(--muted); }

/* The pin ------------------------------------------------------------------
   Only above the breakpoint cv.js also uses for its own decision, so the CSS
   and the script never disagree about whether this section is pinned.

   position:sticky dies inside any ancestor whose overflow is not visible, and
   Elementor sets overflow on both containers and widget wrappers in places.
   The whole chain from #work down to .cv is cleared explicitly rather than
   hoped about. */

#work,
#work > .elementor-element,
#work .elementor-widget-container { overflow: visible; }

@media (min-width: 62em) {
	.cv__pin {
		position: sticky;
		top: 0;
		height: 100vh;
	}
}

/* A viewport too short to hold the panel turns the pin into a squeeze — the
   tiles lose their lines before they lose anything decorative.

   The threshold is 52rem rather than the 40rem stack.css uses, because this
   panel now carries two lanes: heading, rail, progress and the education
   timeline. Measured need is roughly 830px, so anything shorter flows
   normally instead. cv.js applies the same number, or the two would disagree
   about whether this section is pinned. */
@media (max-height: 52rem) {
	.cv { height: auto; }
	.cv__pin { position: static; height: auto; }
}

/* Narrow -------------------------------------------------------------------
   No pin below the breakpoint cv.js uses. The rail goes back to being a
   snapping scroller the reader swipes, which is the right interaction on a
   touch device anyway. */

@media (max-width: 61.99em) {
	.cv { height: auto; }
	.cv__pin { padding-block: var(--s-7); }
	.work__rest { padding-block: var(--s-7); }
	.cv__item { width: min(22rem, 82vw); }

	.edu { --edu-cols: minmax(0, 1fr); }
	.edu__row { align-items: start; gap: var(--s-2); }
	.edu__axis-pad { display: none; }
}

@media (max-width: 47.99em) {
	.cv__pin,
	.work__rest { padding-inline: var(--gutter); }
	.cv__meta { flex-direction: column; align-items: flex-start; gap: var(--s-1); }
}

/* Reduced motion -----------------------------------------------------------
   No pin, no fly-in. The rail stays horizontal and scrollable — that is
   navigation, not decoration, so removing it would remove the content. */

@media (prefers-reduced-motion: reduce) {
	.cv { height: auto; }
	.cv__pin { position: static; height: auto; }

	.cv[data-cv-anim] .cv__item {
		opacity: 1;
		transform: none;
		transition: none;
	}

	.cv__bar { transition: none; }
}
