/**
 * OTSC Timeline
 *
 * Layout notes:
 * - Each event is a full-width row; the card inside is constrained to one
 *   column and pushed left or right. This is deliberately not a 2-column grid,
 *   because a grid lets two consecutive same-side cards share a row.
 * - Entrance animations live on inner elements (.otsc-tl-card, .otsc-tl-anim)
 *   while parallax lives on the wrappers (.otsc-tl-shift, .otsc-tl-decor), so
 *   the two never fight over `transform`.
 */

.otsc-tl {
	--otsc-rail: 2px;
	--otsc-dot: #D8212A;
	--otsc-dot-size: 15px;
	--otsc-dot-top: 26px;
	--otsc-gap: 40px;
	--otsc-pad: 16px;
	--otsc-radius: 2px;
	--otsc-dur: 700ms;
	--otsc-ease: cubic-bezier(.22, .61, .36, 1);
	--otsc-delay: 0ms;
	--otsc-py: 0px;

	max-width: var(--otsc-max, 1024px);
	margin-inline: auto;
	position: relative;
	font-family: var(--otsc-body-font, inherit);
	font-size: var(--otsc-body-size, 16px);

	/*
	 * Contain art that sits outside the timeline's own box — decorations nudged
	 * past the edge, and more importantly the side-aware entrance states, which
	 * park an element ~72px off to one side until it animates in. Un-revealed
	 * items below the fold would otherwise widen the document and give the
	 * whole page a horizontal scrollbar.
	 *
	 * `clip` rather than `hidden`: hidden would make this a scroll container
	 * and break the sticky decade nav. clip pairs with visible on the other
	 * axis, so vertical overflow still behaves normally.
	 */
	overflow-x: clip;
}

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

/* ---------------------------------------------------------------- rail --- */

.otsc-tl-track {
	position: relative;
}

.otsc-tl-rail {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 50%;
	/*
	 * Above aside art (0), below the cards (2). Without this the rail sat at
	 * auto and tied with the art, losing on source order — so icons drew over
	 * the line instead of under it.
	 */
	z-index: 1;
	width: var(--otsc-rail);
	transform: translateX(-50%);
	background: var(--otsc-line, #005CFF);
	border-radius: 2px;
	pointer-events: none;
}

.otsc-tl-rail-fill {
	position: absolute;
	inset: 0;
	display: block;
	background: var(--otsc-line-fill, #D8212A);
	transform: scaleY(var(--otsc-progress, 0));
	transform-origin: top center;
	transition: transform 120ms linear;
}

.otsc-tl:not(.otsc-tl--fill) .otsc-tl-rail-fill {
	display: none;
}

/* --------------------------------------------------------------- items --- */

.otsc-tl-items {
	display: flex;
	flex-direction: column;
	gap: var(--otsc-gap);
	margin: 0;
	padding: 0;
}

.otsc-tl-item {
	position: relative;
	width: 100%;
	margin: 0;
}

/* The card column: half the track, minus the gutter each side of the rail. */
.otsc-tl-col {
	position: relative;
	/*
	 * This element carries `perspective` (further down, for the 3D flip), and
	 * perspective creates a stacking context. With z-index auto that context
	 * sat at level 0, trapping the card's own z-index inside it — so from the
	 * outside the whole column painted at level 0 and lost to a later item's
	 * aside art on source order. The level has to live HERE, on the element
	 * that forms the context; setting it on anything inside is invisible.
	 */
	z-index: 2;
	width: calc(50% - (var(--otsc-gap) + var(--otsc-rail) / 2));
}

.otsc-tl-item--right .otsc-tl-col {
	margin-left: auto;
}

.otsc-tl-item--milestone .otsc-tl-col {
	width: 100%;
	margin-inline: auto;
}

.otsc-tl-shift {
	/*
	 * The transform here makes this a stacking context, which traps the card's
	 * own z-index inside it — from the outside the whole card subtree painted
	 * at level 0, ordered by DOM. Aside art rising into the row above comes
	 * later in the DOM, so it drew over the previous card. Giving the wrapper
	 * the level directly fixes the comparison for good:
	 *
	 *   aside 0  <  rail 1  <  card 2  <  dot 3
	 *
	 * all in one context, so ordering no longer depends on source order.
	 */
	position: relative;
	z-index: 2;
	transform: translate3d(0, var(--otsc-py, 0px), 0);
	will-change: transform;
}

/* ---------------------------------------------------------------- dot ---- */

.otsc-tl-dot {
	position: absolute;
	top: var(--otsc-dot-top);
	left: 50%;
	z-index: 3;
	width: var(--otsc-dot-size);
	height: var(--otsc-dot-size);
	transform: translate(-50%, -50%);
	border-radius: 50%;
	background: var(--otsc-dot);
	box-shadow: 0 0 0 4px rgba(216, 33, 42, .25);
	box-shadow: 0 0 0 4px color-mix(in srgb, var(--otsc-dot) 25%, transparent);
}

.otsc-tl-item--milestone .otsc-tl-dot {
	width: calc(var(--otsc-dot-size) * 1.5);
	height: calc(var(--otsc-dot-size) * 1.5);
}

/* --------------------------------------------------------------- card ---- */

.otsc-tl-card {
	position: relative;
	z-index: 2;
	padding: var(--otsc-pad);
	border-radius: var(--otsc-radius);
	background: var(--otsc-card-bg, #000000);
	color: var(--otsc-card-color, #fff);
}

.otsc-tl-arrow {
	position: absolute;
	top: calc(var(--otsc-dot-top) - 7px);
	width: 14px;
	height: 14px;
	transform: rotate(45deg);
	background: var(--otsc-card-bg, #000000);
}

.otsc-tl-item--left .otsc-tl-arrow {
	right: -7px;
}

.otsc-tl-item--right .otsc-tl-arrow {
	left: -7px;
}

.otsc-tl-item--milestone .otsc-tl-arrow {
	display: none;
}

.otsc-tl-date {
	margin: 0 0 4px;
	font-family: var(--otsc-year-font, inherit);
	font-size: calc(var(--otsc-body-size, 16px) * .9);
	font-weight: 700;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--otsc-accent, #D8212A);
}

/* Media-first cards lead with the image, so the title needs headroom. */
.otsc-tl-item--media-first .otsc-tl-media + .otsc-tl-title {
	margin-top: 8px;
}

.otsc-tl-title {
	margin: 0 0 8px;
	font-family: var(--otsc-title-font, inherit);
	font-size: var(--otsc-title-size, 22px);
	font-weight: 600;
	line-height: 1.2;
	color: var(--otsc-title-color, #fff);
}

.otsc-tl-body {
	font-size: var(--otsc-body-size, 16px);
	line-height: 1.5;
}

.otsc-tl-body > :first-child { margin-top: 0; }
.otsc-tl-body > :last-child { margin-bottom: 0; }
.otsc-tl-body p { margin: 0 0 8px; }

/* -------------------------------------------------------------- media ---- */

.otsc-tl-media {
	margin: 0 0 8px;
	overflow: hidden;
	border-radius: calc(var(--otsc-radius) * .75);
	line-height: 0;
}

.otsc-tl-media img,
.otsc-tl-media video {
	display: block;
	width: 100%;
	height: auto;
}

.otsc-tl-lightbox {
	display: block;
	cursor: zoom-in;
}

.otsc-tl-facade {
	position: relative;
	display: block;
	width: 100%;
	aspect-ratio: 16 / 9;
	padding: 0;
	border: 0;
	cursor: pointer;
	background-color: #000;
	background-size: cover;
	background-position: center;
}

.otsc-tl-play {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 64px;
	height: 64px;
	transform: translate(-50%, -50%);
	border-radius: 50%;
	background: rgba(0, 0, 0, .6);
	transition: background 200ms ease, transform 200ms ease;
}

.otsc-tl-play::before {
	content: "";
	position: absolute;
	top: 50%;
	left: 54%;
	width: 0;
	height: 0;
	transform: translate(-50%, -50%);
	border-left: 18px solid #fff;
	border-top: 11px solid transparent;
	border-bottom: 11px solid transparent;
}

.otsc-tl-facade:hover .otsc-tl-play,
.otsc-tl-facade:focus-visible .otsc-tl-play {
	background: var(--otsc-accent, #D8212A);
	transform: translate(-50%, -50%) scale(1.06);
}

/* Inline video: poster + centred play button until playback starts. */
.otsc-tl-videowrap {
	position: relative;
	line-height: 0;
}

.otsc-tl-play-btn {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	padding: 0;
	border: 0;
	background: transparent;
	cursor: pointer;
}

.otsc-tl-videowrap.is-playing .otsc-tl-play-btn {
	display: none;
}

.otsc-tl-play-btn:hover .otsc-tl-play,
.otsc-tl-play-btn:focus-visible .otsc-tl-play {
	background: var(--otsc-accent, #D8212A);
	transform: translate(-50%, -50%) scale(1.06);
}

.otsc-tl-embed {
	width: 100%;
	aspect-ratio: 16 / 9;
	border: 0;
}

/* --------------------------------------------------------- decorations --- */

.otsc-tl-decor-layer {
	position: absolute;
	inset: 0;
	pointer-events: none;
}

.otsc-tl-decor-layer--bg,
.otsc-tl-decor-layer--free {
	z-index: 1;
}

.otsc-tl-decor-layer--front {
	z-index: 3;
}

.otsc-tl-decor {
	position: absolute;
	left: calc(var(--otsc-lx, 0%) + var(--otsc-ox, 0%));
	top: calc(var(--otsc-ly, 0%) + var(--otsc-oy, 0%));
	width: var(--otsc-w, 35%);
	transform: translate(var(--otsc-tx, -50%), var(--otsc-ty, -50%)) translate3d(0, var(--otsc-py, 0px), 0);
	will-change: transform;
}

.otsc-tl-decor > .otsc-tl-anim {
	display: block;
}

.otsc-tl-decor img {
	display: block;
	width: 100%;
	height: auto;
	opacity: var(--otsc-op, 1);
	transform: rotate(var(--otsc-rot, 0deg));
	mix-blend-mode: var(--otsc-blend, normal);
}

/* ------------------------------------------------------- aside column ---- */

/*
 * Art parked in the empty half of the row, opposite the card. It hangs off the
 * <article> rather than the card column, because the card column only ever
 * spans the side the card is on.
 */
.otsc-tl-aside {
	position: absolute;
	top: 0;
	bottom: 0;
	z-index: 1;
	width: calc(50% - (var(--otsc-gap) + var(--otsc-rail) / 2));
	pointer-events: none;
}

.otsc-tl-item--right .otsc-tl-aside { left: 0; }
.otsc-tl-item--left .otsc-tl-aside { right: 0; }

/*
 * Aside art has to be capped on BOTH axes. Card-relative decorations only cap
 * width, which is fine when they are anchored to a card corner — but a tall
 * portrait icon sized to a share of the column width grows past the row and
 * lands on top of its neighbours. Filling the column and constraining the
 * image itself lets the aspect ratio decide which axis binds.
 */
.otsc-tl-aside .otsc-tl-decor {
	inset: 0;
	left: 0;
	top: 0;
	width: auto;
	display: grid;
	place-items: center;
	transform: translate3d(0, var(--otsc-py, 0px), 0);
}

/*
 * Absolute, not a grid item. As a grid item its height came from its content,
 * so the image's `max-height: 100%` had nothing definite to resolve against,
 * resolved to `none`, and a tall icon grew the wrapper straight over the
 * neighbouring rows. Pinning it to the decor box gives that percentage a real
 * number to work from.
 */
.otsc-tl-aside .otsc-tl-decor > .otsc-tl-anim {
	position: absolute;
	inset: 0;
	display: grid;
	/*
	 * minmax(0, 1fr) rather than the default auto track. An auto track grows to
	 * fit its content, so the image's percentage max-height resolved against
	 * the image's own height and never clamped. A 1fr track in a fixed-height
	 * box is definite, which is what that percentage needs.
	 */
	grid-template-rows: minmax(0, 1fr);
	grid-template-columns: minmax(0, 1fr);
	place-items: center;
}

/* Short cards would otherwise squeeze their icon down to nothing. */
.otsc-tl-item--has-aside {
	min-height: 240px;
}

.otsc-tl-aside .otsc-tl-decor img {
	width: auto;
	height: auto;
	max-width: var(--otsc-w, 52%);
	max-height: 100%;
}

/* A milestone spans the full width, so there is no empty column to sit in. */
.otsc-tl-item--milestone .otsc-tl-aside { display: none; }

/* ---------------------------------------------------------- year label --- */

.otsc-tl-year {
	position: relative;
	z-index: 2;
	display: flex;
	justify-content: center;
	scroll-margin-top: 96px;
}

.otsc-tl-year-label {
	display: inline-block;
	padding: 4px 16px;
	border-radius: var(--otsc-year-radius, 4px);
	background: var(--otsc-year-bg, #005CFF);
	color: var(--otsc-year-color, #fff);
	font-family: var(--otsc-year-font, inherit);
	font-size: var(--otsc-year-size, 21px);
	font-weight: 700;
	line-height: 1.3;
}

/* ----------------------------------------------------------- jump nav ---- */

.otsc-tl-nav {
	position: sticky;
	top: 12px;
	z-index: 5;
	margin-bottom: 20px;
}

.otsc-tl-nav ul {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	justify-content: center;
	margin: 0;
	padding: 8px;
	list-style: none;
	border-radius: var(--otsc-btn-radius, 4px);
	background: var(--otsc-card-bg, #000000);
	background: color-mix(in srgb, var(--otsc-card-bg, #000000) 88%, transparent);
	backdrop-filter: blur(6px);
}

.otsc-tl-nav-btn {
	padding: 4px 12px;
	border: 0;
	border-radius: var(--otsc-btn-radius, 4px);
	background: transparent;
	color: var(--otsc-card-color, #fff);
	font: inherit;
	font-size: 14px;
	cursor: pointer;
	transition: background 160ms ease, color 160ms ease;
}

.otsc-tl-nav-btn:hover,
.otsc-tl-nav-btn:focus-visible {
	background: var(--otsc-accent, #D8212A);
	color: #fff;
}

.otsc-tl-nav-btn.is-current {
	background: var(--otsc-line-fill, #D8212A);
	color: #fff;
}

/* -------------------------------------------------------- pagination ----- */

.otsc-tl-pagination {
	margin-top: calc(var(--otsc-gap) * .8);
	text-align: center;
}

.otsc-tl-sentinel {
	height: 1px;
}

.otsc-tl-more {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	padding: 10px 26px;
	border: 0;
	border-radius: var(--otsc-btn-radius, 4px);
	background: var(--otsc-accent, #D8212A);
	color: #fff;
	font: inherit;
	font-weight: 600;
	cursor: pointer;
	transition: filter 160ms ease, opacity 160ms ease;
}

.otsc-tl-more:hover:not([disabled]) { filter: brightness(1.1); }
.otsc-tl-more[disabled] { opacity: .5; cursor: default; }
.otsc-tl--pag-auto .otsc-tl-more { display: none; }
.otsc-tl--pag-auto.otsc-tl--no-observer .otsc-tl-more { display: inline-flex; }

.otsc-tl-spinner {
	display: none;
	width: 15px;
	height: 15px;
	border: 2px solid rgba(255, 255, 255, .35);
	border-top-color: #fff;
	border-radius: 50%;
	animation: otsc-tl-spin 700ms linear infinite;
}

.otsc-tl.is-loading .otsc-tl-spinner { display: block; }

@keyframes otsc-tl-spin { to { transform: rotate(360deg); } }

.otsc-tl-status {
	margin: 10px 0 0;
	font-size: 14px;
	opacity: .75;
}

.otsc-tl-status:empty { display: none; }

/* -------------------------------------------------------- animations ----- */

.otsc-tl-anim {
	transition-property: opacity, transform, filter, clip-path;
	transition-duration: var(--otsc-dur);
	transition-timing-function: var(--otsc-ease);
	transition-delay: var(--otsc-delay, 0ms);
}

/*
 * SPECIFICITY CONTRACT — read before editing this block.
 *
 * Every "from" state is gated on `.otsc-tl-js`, the class an inline head
 * script adds, so a visitor without working JavaScript never gets a hidden,
 * offset or blurred card. The gate is wrapped in :where() because :where()
 * contributes zero specificity — writing it as a plain descendant selector
 * would push the hide rules above the reveal rule and nothing would ever
 * become visible.
 *
 * The reveal rules are prefixed with `.otsc-tl` so they score (0,3,0) and beat
 * every "from" state, including the side-aware ones and their mobile
 * overrides, regardless of source order.
 */

/* ---- from states (armed only when JS is live) ---- */

:where(.otsc-tl-js) .otsc-tl-anim:not(.otsc-tl-anim--none) { opacity: 0; }

:where(.otsc-tl-js) .otsc-tl-anim--fade-up { transform: translate3d(0, 36px, 0); }
:where(.otsc-tl-js) .otsc-tl-anim--fade-down { transform: translate3d(0, -36px, 0); }
:where(.otsc-tl-js) .otsc-tl-anim--zoom-in { transform: scale(.88); }
:where(.otsc-tl-js) .otsc-tl-anim--zoom-out { transform: scale(1.12); }
:where(.otsc-tl-js) .otsc-tl-anim--blur-in { filter: blur(14px); transform: translate3d(0, 18px, 0); }
:where(.otsc-tl-js) .otsc-tl-anim--rise-rotate { transform: translate3d(0, 42px, 0) rotate(-3deg); }
:where(.otsc-tl-js) .otsc-tl-anim--mask-up { clip-path: inset(100% 0 0 0); }

/* Perspective has to sit on the ancestor for the 3D flip to read. */
.otsc-tl-col,
.otsc-tl-decor { perspective: 1200px; }

:where(.otsc-tl-js) .otsc-tl-anim--flip-in { transform: rotateY(28deg) translate3d(0, 20px, 0); transform-origin: center; }
:where(.otsc-tl-js) .otsc-tl-item--right .otsc-tl-anim--flip-in { transform: rotateY(-28deg) translate3d(0, 20px, 0); }

/* Side-aware: "side" enters from the outer edge, "in" from the rail. */
:where(.otsc-tl-js) .otsc-tl-item--left .otsc-tl-anim--fade-side { transform: translate3d(-52px, 0, 0); }
:where(.otsc-tl-js) .otsc-tl-item--right .otsc-tl-anim--fade-side { transform: translate3d(52px, 0, 0); }
:where(.otsc-tl-js) .otsc-tl-item--left .otsc-tl-anim--fade-in { transform: translate3d(52px, 0, 0); }
:where(.otsc-tl-js) .otsc-tl-item--right .otsc-tl-anim--fade-in { transform: translate3d(-52px, 0, 0); }
:where(.otsc-tl-js) .otsc-tl-item--left .otsc-tl-anim--mask-side { clip-path: inset(0 0 0 100%); }
:where(.otsc-tl-js) .otsc-tl-item--right .otsc-tl-anim--mask-side { clip-path: inset(0 100% 0 0); }

/*
 * Inside the aside, "its own side" is the opposite of the card's — the art
 * enters from the outer edge of the empty column it occupies.
 *
 * These tie with the card rules above on specificity (both 0,2,0), so they
 * MUST stay below them: source order is what decides. They are still under the
 * reveal rule at (0,3,0), which is what lets the entrance resolve.
 */
:where(.otsc-tl-js) :where(.otsc-tl-item--right) .otsc-tl-aside .otsc-tl-anim--fade-side { transform: translate3d(-72px, 0, 0); }
:where(.otsc-tl-js) :where(.otsc-tl-item--left) .otsc-tl-aside .otsc-tl-anim--fade-side { transform: translate3d(72px, 0, 0); }
:where(.otsc-tl-js) :where(.otsc-tl-item--right) .otsc-tl-aside .otsc-tl-anim--fade-in { transform: translate3d(72px, 0, 0); }
:where(.otsc-tl-js) :where(.otsc-tl-item--left) .otsc-tl-aside .otsc-tl-anim--fade-in { transform: translate3d(-72px, 0, 0); }
:where(.otsc-tl-js) :where(.otsc-tl-item--right) .otsc-tl-aside .otsc-tl-anim--mask-side { clip-path: inset(0 100% 0 0); }
:where(.otsc-tl-js) :where(.otsc-tl-item--left) .otsc-tl-aside .otsc-tl-anim--mask-side { clip-path: inset(0 0 0 100%); }

.otsc-tl-anim--none { opacity: 1; transition: none; }

/* ---- reveal state: must outrank every "from" state above ---- */

.otsc-tl .otsc-tl-anim.is-in {
	opacity: 1;
	transform: none;
	filter: none;
}

/*
 * Mask variants land on negative insets rather than inset(0). inset(0) still
 * clips to the border box, which would shave off anything a card deliberately
 * hangs outside itself — the arrow pointing at the rail, most of all.
 */
.otsc-tl .otsc-tl-anim--mask-up.is-in,
.otsc-tl .otsc-tl-anim--mask-side.is-in {
	clip-path: inset(-100% -100% -100% -100%);
}

/* ------------------------------------------------------------ mobile ----- */

@media (max-width: 782px) {
	.otsc-tl {
		--otsc-gap: 22px;
		--otsc-rail-offset: 26px;
	}

	.otsc-tl-rail {
		left: var(--otsc-rail-offset);
	}

	.otsc-tl-col,
	.otsc-tl-item--milestone .otsc-tl-col {
		width: calc(100% - var(--otsc-rail-offset) - var(--otsc-gap));
		margin-left: auto;
	}

	.otsc-tl-dot {
		left: var(--otsc-rail-offset);
	}

	.otsc-tl-arrow,
	.otsc-tl-item--left .otsc-tl-arrow,
	.otsc-tl-item--right .otsc-tl-arrow {
		right: auto;
		left: -7px;
	}

	.otsc-tl-year {
		justify-content: flex-start;
		padding-left: calc(var(--otsc-rail-offset) + var(--otsc-gap));
	}

	.otsc-tl-decor-layer--bg {
		overflow: hidden;
		border-radius: var(--otsc-radius);
	}

	.otsc-tl-decor--m-hide {
		display: none;
	}

	/*
	 * No empty column survives the collapse, so aside art moves in behind the
	 * card and is allowed to outgrow it — what the reader sees is the part
	 * sticking out past the card's edges, like a sticker tucked underneath.
	 * The card sits at z-index 2, so z-index 0 puts this behind it while the
	 * overflow stays visible.
	 */
	.otsc-tl-aside {
		display: block;
		left: 0;
		right: 0;
		top: 0;
		bottom: auto;
		z-index: 0;
		/* Full width: the art runs under the rail as well as the card. */
		width: auto;

		/*
		 * A fixed band straddling the card's top edge, rather than a box the
		 * full height of the row. Sized to the row, the icon simply fits behind
		 * a tall card and vanishes; a fixed band overhangs by the same amount
		 * whether the card is two lines or a full photo.
		 */
		height: var(--otsc-aside-mobile-h, 132px);
		/*
		 * A fixed rise, not a percentage of the band, so changing the icon size
		 * does not also move it. What actually shows above the card is bounded
		 * by the row gap — past that the art tucks behind the card above, which
		 * is harmless (it sits at z-index 0, the cards at 2) but invisible.
		 */
		transform: translateY(calc(-1 * var(--otsc-aside-mobile-rise, 88px)));
	}

	.otsc-tl-aside .otsc-tl-decor {
		inset: 0;
		left: 0;
		top: 0;
		width: auto;
		transform: none;
	}

	.otsc-tl-aside .otsc-tl-anim {
		position: absolute;
		inset: 0;
	}

	/*
	 * Both caps matter, and which one binds depends on the artwork: a flat
	 * icon (the 800x319 car) is stopped by the width, a square one (the
	 * 751x800 record player) by the band height. Raising only one leaves half
	 * the set looking small.
	 */
	/*
	 * Flush to the left edge and running rightward, so the art crosses the line
	 * and carries on under the tile with nothing clipped off. Centring it on
	 * the rail instead pushed half the image off-screen, since there are only
	 * ~26px to the left of the line to work with.
	 *
	 * Positioned against the anim box, which is absolute with a definite
	 * height, so the percentage max-height still resolves.
	 */
	.otsc-tl-aside .otsc-tl-decor img {
		position: absolute;
		top: 50%;
		left: var(--otsc-aside-mobile-x, 10px);
		transform: translateY(-50%) rotate(var(--otsc-rot, 0deg));
		max-width: var(--otsc-w-mobile, 88%);
		max-height: 100%;
	}

	/* Room for that overhang, so it never lands on the next card. */
	.otsc-tl-item--has-aside {
		min-height: 0;
	}

	/*
	 * The card is opaque, so the row gap is the ONLY place aside art can show.
	 * That makes this the real size control on mobile: the icon is sized to sit
	 * inside this band nearly whole, rather than being tall and mostly hidden.
	 * Raise the gap and the icon can grow with it; shrink it and it cannot.
	 */
	.otsc-tl-items {
		row-gap: calc(var(--otsc-gap) * 1.9);
	}

	/* Side-aware entrances all resolve to the single column's direction. */
	:where(.otsc-tl-js) .otsc-tl-item--left .otsc-tl-anim--fade-side,
	:where(.otsc-tl-js) .otsc-tl-item--right .otsc-tl-anim--fade-side,
	:where(.otsc-tl-js) .otsc-tl-item--left .otsc-tl-anim--fade-in,
	:where(.otsc-tl-js) .otsc-tl-item--right .otsc-tl-anim--fade-in {
		transform: translate3d(38px, 0, 0);
	}

	:where(.otsc-tl-js) .otsc-tl-item--left .otsc-tl-anim--mask-side,
	:where(.otsc-tl-js) .otsc-tl-item--right .otsc-tl-anim--mask-side {
		clip-path: inset(0 100% 0 0);
	}

	:where(.otsc-tl-js) .otsc-tl-item--right .otsc-tl-anim--flip-in,
	:where(.otsc-tl-js) .otsc-tl-anim--flip-in {
		transform: rotateY(20deg) translate3d(0, 16px, 0);
	}

	.otsc-tl-nav ul {
		flex-wrap: nowrap;
		justify-content: flex-start;
		overflow-x: auto;
		border-radius: 8px;
		scrollbar-width: none;
	}

	.otsc-tl-nav ul::-webkit-scrollbar { display: none; }
	.otsc-tl-nav li { flex: 0 0 auto; }
}

/* ----------------------------------------------------------- lightbox ---- */

.otsc-tl-lb {
	position: fixed;
	inset: 0;
	z-index: 100000;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 4vmin;
	background: rgba(0, 0, 0, .88);
	opacity: 0;
	transition: opacity 200ms ease;
}

.otsc-tl-lb.is-open { opacity: 1; }

.otsc-tl-lb img {
	max-width: 100%;
	max-height: 92vh;
	object-fit: contain;
}

.otsc-tl-lb-close {
	position: absolute;
	top: 16px;
	right: 16px;
	width: 42px;
	height: 42px;
	border: 0;
	border-radius: 50%;
	background: rgba(255, 255, 255, .12);
	color: #fff;
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
}

/* ---------------------------------------------------- reduced motion ----- */

@media (prefers-reduced-motion: reduce) {
	.otsc-tl-anim,
	.otsc-tl-anim.is-in {
		opacity: 1 !important;
		transform: none !important;
		filter: none !important;
		clip-path: none !important;
		transition: none !important;
	}

	.otsc-tl-shift {
		transform: none !important;
	}

	.otsc-tl-decor {
		transform: translate(var(--otsc-tx, -50%), var(--otsc-ty, -50%)) !important;
	}

	.otsc-tl-rail-fill { transition: none; }
	.otsc-tl-spinner { animation: none; }
}

.otsc-tl-empty {
	padding: 12px 16px;
	border-left: 4px solid #D8212A;
	background: rgba(216, 33, 42, .08);
}

/* ------------------------------------------------------ numbered pages --- */

.otsc-tl-pages {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	align-items: center;
	justify-content: center;
	margin-top: calc(var(--otsc-gap) * .8);
}

.otsc-tl-page {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 38px;
	height: 38px;
	padding: 0 10px;
	border-radius: var(--otsc-btn-radius, 4px);
	background: var(--otsc-card-bg, #000000);
	background: color-mix(in srgb, var(--otsc-card-bg, #000000) 90%, transparent);
	color: var(--otsc-card-color, #fff);
	font-weight: 600;
	text-decoration: none;
	transition: background 160ms ease;
}

.otsc-tl-page:hover,
.otsc-tl-page:focus-visible {
	background: var(--otsc-accent, #D8212A);
	color: #fff;
}

.otsc-tl-page.is-current {
	background: var(--otsc-accent, #D8212A);
	color: #fff;
	cursor: default;
}

.otsc-tl-pages .otsc-tl-status {
	flex: 1 0 100%;
	text-align: center;
}

/* --------------------------------------------------- end of timeline ----- */

.otsc-tl-end {
	margin-top: calc(var(--otsc-gap) * .9);
	text-align: center;
}

.otsc-tl-end[hidden] {
	display: none;
}

.otsc-tl-end-inner {
	display: inline-block;
	max-width: 62ch;
	padding: var(--otsc-pad);
	border-radius: var(--otsc-radius);
	background: var(--otsc-card-bg, #000000);
	color: var(--otsc-card-color, #fff);
	font-size: var(--otsc-body-size, 16px);
	line-height: 1.5;
}

.otsc-tl-end-inner > :first-child { margin-top: 0; }
.otsc-tl-end-inner > :last-child { margin-bottom: 0; }

/* A hidden button leaves the spinner as the only loading cue. */
.otsc-tl-pagination > .otsc-tl-spinner {
	margin: 0 auto;
	border-color: rgba(216, 33, 42, .35);
	border-color: color-mix(in srgb, var(--otsc-accent, #D8212A) 35%, transparent);
	border-top-color: var(--otsc-accent, #D8212A);
}
