/**
 * Airline Projects Map — black outlines on white.
 *
 * The whole component is drawn from one ink colour, one paper colour and hairline
 * rules. Nothing here has a shadow, a radius or a gradient: the map arrives as
 * line art because the style document draws it that way, and everything laid over
 * it is built from the same 1px stroke so the map and the furniture read as one
 * drawing.
 */

.airline-map {
	--airline-map-height: 100svh;
	--airline-map-ink: #000;
	--airline-map-paper: #fff;
	--airline-map-accent: #000;
	--airline-map-rule: 1px;
	--airline-map-gutter: clamp(1.25rem, 3vw, 3rem);
	--airline-map-mono: ui-monospace, SFMono-Regular, "SF Mono", "JetBrains Mono", "IBM Plex Mono", Menlo, Consolas, monospace;
	--airline-map-tick: 0.6875rem;
	/* How firmly the drawing is inked. Held back from full so the markers, which
	 * are not part of it, are the strongest black on the map half. */
	--airline-map-alpha: 0.61;
	/* How far past its own edge a marker still answers a click. Enough to clear
	 * the 44px a finger needs without the pin being drawn any heavier. */
	--airline-map-marker-reach: 11px;

	position: relative;
	box-sizing: border-box;
	height: var(--airline-map-height);
	overflow: hidden;
	color: var(--airline-map-ink);
	background: var(--airline-map-paper);
	border-block: var(--airline-map-rule) solid var(--airline-map-ink);
}

.airline-map *,
.airline-map *::before,
.airline-map *::after {
	box-sizing: border-box;
}

.airline-map__grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	height: 100%;
}

.airline-map__half {
	position: relative;
	min-width: 0;
	min-height: 0;
}

/*
 * MapLibre numbers its own canvas, markers and controls from 0 up to 3 within
 * the map. Isolating the map half gives those a stacking context of their own,
 * so the connector can sit above them on a z-index of 3 rather than having to
 * outbid the library.
 */
.airline-map__half--map {
	isolation: isolate;
	z-index: 1;
	border-inline-end: var(--airline-map-rule) solid var(--airline-map-ink);
}

.airline-map--panel-left .airline-map__half--map {
	order: 2;
	border-inline: var(--airline-map-rule) solid var(--airline-map-ink);
	border-inline-end: 0;
}

.airline-map__half--panel {
	z-index: 2;
	display: flex;
	flex-direction: column;
	min-height: 0;
	background: var(--airline-map-paper);
}

/* ------------------------------------------------------------------ the map */

.airline-map__canvas {
	position: absolute;
	inset: 0;
	background: var(--airline-map-paper);
}

/*
 * Nothing here recolours the map. It arrives as ink on paper because the style
 * document says so, which is also why the sea is white and the coast is a line
 * rather than the two being one grey shape. See class-airline-map-style.php.
 */
.airline-map .maplibregl-map {
	font: inherit;
	background: var(--airline-map-paper);
	outline-offset: -2px;
}

/*
 * The drawing is held back so the markers read as the subject and the map as the
 * annotation. This lands on the canvas element rather than on the map container
 * because MapLibre hangs the markers off the canvas *container* as siblings of
 * the canvas — fading the container would fade the pins with the roads, which is
 * the opposite of the point.
 */
.airline-map .maplibregl-canvas {
	opacity: var(--airline-map-alpha);
}

.airline-map .maplibregl-canvas:focus-visible {
	outline: var(--airline-map-rule) solid var(--airline-map-ink);
	outline-offset: -2px;
}

.airline-map .maplibregl-ctrl-attrib {
	padding: 0.25em 0.6em;
	font-family: var(--airline-map-mono);
	font-size: 0.625rem;
	color: var(--airline-map-ink);
	background: var(--airline-map-paper);
	border: var(--airline-map-rule) solid var(--airline-map-ink);
	border-block-end: 0;
	border-inline-end: 0;
}

.airline-map .maplibregl-ctrl-attrib a {
	color: inherit;
}

/* The library's own button is a rounded white plate with a shadow and an icon
 * baked in as a background image; all of that is undone here so the control is
 * the same hairline box as everything else, lettered rather than pictured. */
.airline-map .maplibregl-ctrl-group {
	margin: 0 0.75rem 2.25rem 0;
	background: none;
	border: 0;
	border-radius: 0;
	box-shadow: none;
}

.airline-map .maplibregl-ctrl-group button {
	width: 2rem;
	height: 2rem;
	font-family: var(--airline-map-mono);
	font-size: 1rem;
	line-height: calc(2rem - 2px);
	color: var(--airline-map-ink);
	background: var(--airline-map-paper);
	border: var(--airline-map-rule) solid var(--airline-map-ink);
	border-radius: 0;
}

.airline-map .maplibregl-ctrl-group button + button {
	border-block-start: 0;
}

.airline-map .maplibregl-ctrl-group button .maplibregl-ctrl-icon {
	display: none;
}

.airline-map .maplibregl-ctrl-zoom-in::after {
	content: "+";
}

.airline-map .maplibregl-ctrl-zoom-out::after {
	content: "−";
}

.airline-map .maplibregl-ctrl-group button:hover,
.airline-map .maplibregl-ctrl-group button:focus-visible {
	color: var(--airline-map-paper);
	background: var(--airline-map-ink);
}

/*
 * MapLibre's cooperative-gesture notice, hidden.
 *
 * The behaviour it belongs to is kept: the wheel still scrolls the page, and
 * ctrl-wheel and two fingers still zoom the map. What is gone is the dark screen
 * that drops over the drawing to announce it. On a map that is mostly white with
 * a thin black line on it, that panel was the loudest thing on the page, and it
 * arrived precisely when the visitor was trying to look at something else.
 *
 * Kept as a rule rather than by switching cooperativeGestures off, because off
 * would hand the wheel back to the map and put the scroll trap back with it.
 */
.airline-map .maplibregl-cooperative-gesture-screen {
	display: none;
}

/* --------------------------------------------------------------- the markers */

/*
 * The marker element is ours, not the library's, so it has no size of its own
 * until it is given one here — MapLibre only ever writes a transform onto it.
 * The 34px matches the pin's viewBox so the stroke lands on whole pixels.
 */
/*
 * No position of its own, deliberately. MapLibre's own .maplibregl-marker rule
 * makes every marker absolute at the container's origin and then transforms it
 * into place; this stylesheet loads after that one, so declaring position here —
 * even position:relative — puts the marker back into normal flow and every pin
 * on the map lands at its layout position plus the transform instead of at its
 * coordinates. z-index still applies: the element is positioned, by them.
 */
.airline-map__marker {
	/* Above the aircraft, which share this container — see .airline-map__plane. */
	z-index: 1;
	display: block;
	width: 34px;
	height: 34px;
	background: none;
	border: 0;
	cursor: pointer;
	/*
	 * Opacity only, and named rather than left to `all`. MapLibre writes a
	 * transform onto this element on every frame of a pan, and a transition
	 * covering that would put every pin a few frames behind the ground it is
	 * standing on. See the arrival, below.
	 */
	transition: opacity 420ms ease;
}

/*
 * The pin is drawn at 34px but answers to a 56px target, which is the reach a
 * finger needs and the size a mouse deserves at this scale. It is a pad rather
 * than a bigger element so that the drawing does not change: growing the marker
 * itself would move where MapLibre centres it and how the pin's stroke lands on
 * the pixel grid. Round, so two pins have to be genuinely overlapping before one
 * pad reaches the other's centre.
 */
.airline-map__marker::before {
	position: absolute;
	inset: calc(-1 * var(--airline-map-marker-reach));
	content: "";
	border-radius: 50%;
}

/*
 * Stacking among the pins.
 *
 * They are siblings in MapLibre's marker container and every one of them sits at
 * 1, so wherever two pads overlap the pin that happens to be later in the DOM
 * takes everything: it paints over its neighbour's label, and — since hit
 * testing follows paint order — swallows the clicks meant for it as well. Two
 * projects a few streets apart were a coin toss to click.
 *
 * Selected sits above the rest, and whichever pin the pointer is actually
 * nearest sits above that. `is-near` is set from map.js, which measures the
 * pointer against each pin's centre; see trackNearest(). Hover and focus are
 * listed too, so the raise holds for a pointer that has arrived somewhere the
 * tracker has not measured yet, and so a keyboard reaching a pin is treated as
 * the same event as a pointer reaching it.
 */
.airline-map__marker.is-active {
	z-index: 2;
}

.airline-map__marker.is-near,
.airline-map__marker:hover,
.airline-map__marker:focus-visible {
	z-index: 3;
}

/* Folded into a group — see trackNearest() and regroup() in map.js. */
.airline-map__marker.is-clustered {
	display: none;
}

/*
 * Nothing is marked until the map has arrived.
 *
 * The opening flight starts out where the whole set is a few pixels of ocean,
 * and on the way down every pin is folded into one group and then another as
 * they come apart — a mark that changes what it means three times before the
 * reader has been shown anything. The drawing lands first and is then marked.
 *
 * All of them together, on the same fade. A staggered entrance was tried on the
 * pins once and is noted against them above: it is a thing to watch rather than
 * a set of places, and by the time it finished the map had been still for a
 * second. Not clickable while invisible, either — an unseen pin is not a target.
 */
.airline-map.is-intro .airline-map__marker,
.airline-map.is-intro .airline-map__cluster {
	opacity: 0;
	pointer-events: none;
}

/* ------------------------------------------------------------- the groups */

/*
 * The stand-in for pins that have run into each other. Same square, same
 * stroke, same paper fill as a pin, so a group reads as a pin carrying more
 * than one project rather than as a different kind of object. It sits above
 * every ungrouped pin, because it is the only one of them that is a way in to
 * something rather than a destination.
 *
 * Positioned by MapLibre, like the pins — see the note on .airline-map__marker.
 */
.airline-map__cluster {
	z-index: 4;
	display: grid;
	place-items: center;
	width: 34px;
	height: 34px;
	padding: 0;
	background: none;
	border: 0;
	cursor: pointer;
	/* Opacity only, for the same reason as the pins. */
	transition: opacity 420ms ease;
}

.airline-map__cluster.is-empty {
	display: none;
}

.airline-map__cluster-art {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	overflow: visible;
	fill: none;
	stroke: var(--airline-map-ink);
	stroke-width: 1.25;
	vector-effect: non-scaling-stroke;
}

.airline-map__cluster-box {
	fill: var(--airline-map-paper);
	transition: rotate 240ms cubic-bezier(0.22, 1, 0.36, 1), transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
	transform-box: fill-box;
	transform-origin: center;
}

/* The same turn a pin makes when a pointer arrives, for the same reason. */
.airline-map__cluster:hover .airline-map__cluster-box,
.airline-map__cluster:focus-visible .airline-map__cluster-box {
	rotate: 45deg;
	transform: scale(1.12);
}

.airline-map__cluster-count {
	position: relative;
	font-family: var(--airline-map-mono);
	font-size: 0.6875rem;
	line-height: 1;
	font-variant-numeric: tabular-nums;
	color: var(--airline-map-ink);
	pointer-events: none;
}

/* Holds the selected project, so it carries the mark its pin would have. */
.airline-map__cluster.is-active .airline-map__cluster-art {
	stroke: var(--airline-map-accent);
}

.airline-map__cluster.is-active .airline-map__cluster-box {
	fill: var(--airline-map-accent);
}

.airline-map__cluster.is-active .airline-map__cluster-count {
	color: var(--airline-map-paper);
}

.airline-map__pin {
	position: relative;
	display: block;
	width: 100%;
	height: 100%;
}

.airline-map__pin-art {
	display: block;
	width: 100%;
	height: 100%;
	overflow: visible;
	fill: none;
	stroke: var(--airline-map-ink);
	stroke-width: 1.25;
	vector-effect: non-scaling-stroke;
}

/*
 * The pin sits square and turns onto its corner when a pointer arrives. Nothing
 * happens on arrival — the map settling is not an event worth animating, and a
 * staggered entrance on every pin was noise before the visitor had done anything.
 *
 * The turn is on `rotate` and the emphasis on `transform`, which are separate
 * properties that compose. That is what lets a hovered pin turn and grow at once
 * without either rule having to restate the other's value — the trap that once
 * left the selected pin a square, because a `transform: scale()` had quietly
 * replaced the rotation instead of adding to it.
 *
 * It is not the rect's transform attribute for the same reason it never should
 * have been: under SVG2 that attribute *is* the CSS transform property, and
 * fill-box resolved its rotate(45 17 17) about the wrong point, leaving every
 * pin 18px from the coordinate it marked.
 */
.airline-map__pin-box {
	fill: var(--airline-map-paper);
	transition: rotate 240ms cubic-bezier(0.22, 1, 0.36, 1), transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
	transform-box: fill-box;
	transform-origin: center;
}

.airline-map__pin-dot {
	fill: var(--airline-map-ink);
	stroke: none;
}

/* Halo and crosshair belong to the selected marker only; they start collapsed. */
.airline-map__pin-halo,
.airline-map__pin-cross {
	opacity: 0;
	transition: opacity 240ms ease;
	transform-box: fill-box;
	transform-origin: center;
}

/* The turn, on hover and on focus alike — a keyboard reaching a pin is the same
 * event as a pointer reaching it. `rotate` and `transform` compose, so this is
 * both at once without either restating the other.
 *
 * The selected pin below holds the same turn. The two rules are one idea: a pin
 * on its corner is a pin being attended to, whether the pointer is what is
 * attending or the map is. Letting go of the turn the moment the pointer left
 * put the chosen pin back to the shape of every pin around it. */
.airline-map__marker:hover .airline-map__pin-box,
.airline-map__marker:focus-visible .airline-map__pin-box {
	rotate: 45deg;
	transform: scale(1.18);
}

.airline-map__marker.is-active .airline-map__pin-art {
	stroke: var(--airline-map-accent);
}

.airline-map__marker.is-active .airline-map__pin-box {
	fill: var(--airline-map-accent);
	rotate: 45deg;
	transform: scale(1.1);
}

.airline-map__marker.is-active .airline-map__pin-dot {
	fill: var(--airline-map-paper);
}

.airline-map__marker.is-active .airline-map__pin-cross {
	opacity: 1;
}

.airline-map__marker.is-active .airline-map__pin-halo {
	opacity: 1;
	animation: airline-map-halo 2.4s ease-out infinite;
}

@keyframes airline-map-halo {
	0% {
		opacity: 0.9;
		transform: scale(0.62);
	}
	70% {
		opacity: 0;
		transform: scale(1.35);
	}
	100% {
		opacity: 0;
		transform: scale(1.35);
	}
}

.airline-map__pin-name {
	position: absolute;
	inset-inline-start: calc(100% + 0.35rem);
	inset-block-start: 50%;
	padding: 0.15em 0.45em;
	font-family: var(--airline-map-mono);
	font-size: 0.625rem;
	line-height: 1.4;
	text-transform: uppercase;
	letter-spacing: 0.08em;
	white-space: nowrap;
	color: var(--airline-map-ink);
	background: var(--airline-map-paper);
	border: var(--airline-map-rule) solid var(--airline-map-ink);
	opacity: 0;
	transform: translateY(-50%);
	transition: opacity 180ms ease;
	pointer-events: none;
}

/*
 * The other side of the pin, for a name that would otherwise run off the
 * drawing. Which pins those are is a question about where the map has projected
 * them to, so map.js sets the class; see placeLabels(). Logical properties on
 * both rules, so "the other side" means the other side in an RTL document too.
 */
.airline-map__marker.is-label-flip .airline-map__pin-name {
	inset-inline-start: auto;
	inset-inline-end: calc(100% + 0.35rem);
}

.airline-map__marker:hover .airline-map__pin-name,
.airline-map__marker.is-active .airline-map__pin-name {
	opacity: 1;
}

/* ------------------------------------------------------------------ the sky */

/*
 * Aircraft over the drawing. They are map markers, not furniture on the frame:
 * each one holds a position in the world, so panning and zooming carries it with
 * the ground underneath it the same way it carries a project pin. MapLibre owns
 * the placement — everything here is only what the aircraft looks like.
 *
 * They sit under the project pins and never take a pointer event: crossing a
 * marker is the whole idea, blocking a click on one is not.
 */
/* Positioned by MapLibre, like the pins — see the note on .airline-map__marker. */
.airline-map__plane {
	/* Aircraft and project pins are siblings in MapLibre's marker container, so
	 * without this the last one added would win. The pins are the subject. */
	z-index: 0;
	display: block;
	width: calc(1.625rem * var(--airline-plane-scale, 1));
	height: calc(1.625rem * var(--airline-plane-scale, 1));
	pointer-events: none;
	/* The aeroplane fades up under its own ping. Both animations play because the
	 * element has just joined the document, which is the one moment an aircraft
	 * arrives; nothing in JS starts them and nothing has to stop them. */
	animation: airline-map-plane-in 700ms ease-out both;
}

/*
 * The arrival: two rings opening out from the point the aircraft appeared at and
 * fading as they go, like a drop landing on water seen from directly above. The
 * marker is turned to the aircraft's heading and a circle does not care, so this
 * needs none of the counter-rotation the callsign does.
 *
 * The rings are the pseudo-elements rather than this box, because this box has to
 * stay still for them to grow out of: a transform on a parent multiplies into its
 * children, and two rings scaling inside a third scaling thing is not a ripple.
 */
.airline-map__plane-ping {
	position: absolute;
	inset: 0;
	pointer-events: none;
}

.airline-map__plane-ping::before,
.airline-map__plane-ping::after {
	position: absolute;
	inset: 0;
	content: "";
	border: var(--airline-map-rule) solid var(--airline-map-ink);
	border-radius: 50%;
	/* The resting state is also where the animation leaves them, so a ping that has
	 * played is gone rather than parked on the map at full stretch. */
	opacity: 0;
	animation: airline-map-ping 1100ms ease-out;
}

/* The second ring, a beat behind the first and fainter — one ripple reads as a
 * mark on the drawing, two read as something having landed. */
.airline-map__plane-ping::after {
	animation-delay: 260ms;
	animation-duration: 900ms;
}

@keyframes airline-map-plane-in {
	from {
		opacity: 0;
	}
	to {
		opacity: 1;
	}
}

@keyframes airline-map-ping {
	from {
		opacity: 0.5;
		transform: scale(0.4);
	}
	to {
		opacity: 0;
		transform: scale(3.6);
	}
}

.airline-map__plane-art {
	position: absolute;
	inset: 0;
}

.airline-map__plane-art svg {
	display: block;
	width: 100%;
	height: 100%;
	overflow: visible;
	fill: var(--airline-map-paper);
	stroke: var(--airline-map-ink);
	stroke-width: 1;
	stroke-linejoin: round;
	vector-effect: non-scaling-stroke;
	opacity: 0.7;
}

/*
 * The contrail, drawn behind the aircraft and masked so it thins out rather than
 * stopping dead. Solid rather than dashed on purpose: this drawing already spends
 * dashes on borders and on railways, and a third dashed line crossing the map
 * would be read as another one of those instead of as something in the air.
 */
.airline-map__plane-art::before {
	position: absolute;
	inset-block-start: 50%;
	inset-inline-end: 100%;
	width: 7rem;
	height: 0;
	content: "";
	border-block-start: var(--airline-map-rule) solid var(--airline-map-ink);
	opacity: 0.28;
	-webkit-mask-image: linear-gradient(to right, transparent, #000);
	mask-image: linear-gradient(to right, transparent, #000);
}

/*
 * The callsign, for real aircraft only — an invented one would be a lie printed
 * on the map. The marker element is rotated to the aircraft's heading, so the tag
 * is turned back by the same angle to stay level with the page; JS writes the
 * angle it used into --airline-plane-spin rather than the CSS having to guess it.
 */
.airline-map__plane-tag {
	position: absolute;
	inset-block-start: 100%;
	inset-inline-start: 50%;
	font-family: var(--airline-map-mono);
	font-size: 0.5rem;
	letter-spacing: 0.08em;
	white-space: nowrap;
	color: var(--airline-map-ink);
	opacity: 0.5;
	transform: rotate(calc(-1 * var(--airline-plane-spin, 0deg))) translate(0.5rem, -0.2rem);
	transform-origin: 0 0;
}

/* ------------------------------------------------------------------ the HUD */

.airline-map__hud {
	position: absolute;
	inset: 0;
	z-index: 500;
	pointer-events: none;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.14em;
}

.airline-map__bracket {
	position: absolute;
	width: 1.25rem;
	height: 1.25rem;
	border: 0 solid var(--airline-map-ink);
}

.airline-map__bracket--tl {
	inset: 0.75rem auto auto 0.75rem;
	border-width: var(--airline-map-rule) 0 0 var(--airline-map-rule);
}

.airline-map__bracket--tr {
	inset: 0.75rem 0.75rem auto auto;
	border-width: var(--airline-map-rule) var(--airline-map-rule) 0 0;
}

.airline-map__bracket--bl {
	inset: auto auto 0.75rem 0.75rem;
	border-width: 0 0 var(--airline-map-rule) var(--airline-map-rule);
}

.airline-map__bracket--br {
	inset: auto 0.75rem 0.75rem auto;
	border-width: 0 var(--airline-map-rule) var(--airline-map-rule) 0;
}

.airline-map__readout {
	position: absolute;
	inset: 1.5rem auto auto 1.5rem;
	display: flex;
	gap: 0.5rem;
	align-items: baseline;
	margin: 0;
}

.airline-map__readout-value {
	font-size: 1.5rem;
	letter-spacing: 0.02em;
}

.airline-map__coords {
	position: absolute;
	inset: auto auto 1.5rem 1.5rem;
	margin: 0;
	letter-spacing: 0.1em;
}

/* ------------------------------------------------------------ the connector */

.airline-map__link {
	position: absolute;
	inset: 0;
	z-index: 3;
	width: 100%;
	height: 100%;
	overflow: visible;
	pointer-events: none;
}

.airline-map__link-path,
.airline-map__link-tick {
	fill: none;
	stroke: var(--airline-map-accent);
	stroke-width: 1;
	vector-effect: non-scaling-stroke;
}

.airline-map__link-origin {
	fill: none;
	stroke: var(--airline-map-accent);
	stroke-width: 1;
	vector-effect: non-scaling-stroke;
}

/* The marker has left the visible map; the line still points, but stops asserting. */
.airline-map__link.is-offscreen .airline-map__link-path {
	stroke-dasharray: 3 4;
	opacity: 0.45;
}

.airline-map__link.is-offscreen .airline-map__link-origin {
	opacity: 0;
}

/* Drawn only once JavaScript has taken over; otherwise there is nothing to join. */
.airline-map:not(.is-live) .airline-map__link {
	display: none;
}

/*
 * Held back while the opening flight is still coming in. The line joins a marker
 * to its panel, and during the descent the marker is a dot somewhere over a
 * continent — a line whipping across the screen to find it says nothing. It is
 * drawn on at the end of the flight instead; see endIntro() in map.js.
 */
.airline-map.is-intro .airline-map__link {
	opacity: 0;
}

/* ---------------------------------------------------------------- the panel */

.airline-map__panel {
	position: relative;
	flex: 1 1 auto;
	min-height: 0;
}

/*
 * The panel scrolls its own overflow and then gets out of the way. It used to
 * say overscroll-behavior: contain, which does not mean "keep my scrolling to
 * myself" — it means "swallow the wheel and never pass it on". A visitor whose
 * pointer happened to be over the right half found the page had stopped
 * scrolling, and on a viewport where the panel fits, with nothing of its own to
 * scroll, the wheel did nothing at all. Chaining to the page is the default and
 * is what a full-height component owes the page it sits in.
 */
.airline-map__project {
	display: none;
	flex-direction: column;
	gap: 1rem;
	height: 100%;
	padding: var(--airline-map-gutter);
	overflow-y: auto;
}

.airline-map__project.is-active {
	display: flex;
	animation: airline-map-enter 460ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

/*
 * Panels are display:none until selected, which is deliberate — it keeps the 27
 * photographs that are not on screen from being fetched. That also rules out a
 * transition between two panels, so the incoming one is animated on its own.
 */
@keyframes airline-map-enter {
	from {
		opacity: 0;
		transform: translateY(0.75rem);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

/* With no JavaScript every panel is shown, stacked, as a plain list of projects. */
.airline-map:not(.is-live) .airline-map__panel {
	overflow-y: auto;
}

.airline-map:not(.is-live) .airline-map__project {
	display: flex;
	border-block-end: var(--airline-map-rule) solid var(--airline-map-ink);
}

.airline-map__counter {
	display: flex;
	gap: 0.4rem;
	align-items: baseline;
	margin: 0;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	letter-spacing: 0.14em;
}

.airline-map__counter-now {
	font-size: 1.125rem;
}

.airline-map__counter-of::before {
	content: "/ ";
}

.airline-map__media {
	position: relative;
	flex: 0 1 auto;
	min-height: 0;
	border: var(--airline-map-rule) solid var(--airline-map-ink);
}

/*
 * The photograph is the one thing on the page that keeps its own colour. The
 * drawing around it is monochrome by construction, so the project reads as the
 * subject and the map as the annotation; draining the photograph too would flatten
 * that difference and cost the only warmth the component has.
 */
.airline-map__image {
	display: block;
	width: 100%;
	height: 100%;
	max-height: 44vh;
	object-fit: cover;
}

/*
 * The photograph opens the project. A bare wrapper: it must not introduce a line
 * box of its own between the border and the picture, and it must not take the
 * theme's link colour or underline with it.
 */
.airline-map__image-link {
	display: block;
	width: 100%;
	height: 100%;
	color: inherit;
	text-decoration: none;
}

.airline-map__image-link .airline-map__image {
	transition: opacity 180ms ease;
}

.airline-map__image-link:hover .airline-map__image {
	opacity: 0.88;
}

.airline-map__image--none {
	display: grid;
	place-items: center;
	aspect-ratio: 3 / 2;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.14em;
	background-image: repeating-linear-gradient(
		135deg,
		transparent 0 7px,
		var(--airline-map-ink) 7px 8px
	);
}

.airline-map__body {
	display: flex;
	flex: 1 1 auto;
	flex-direction: column;
	gap: 0.75rem;
	min-height: 0;
}

.airline-map__terms {
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem;
	margin: 0;
}

.airline-map__term {
	padding: 0.2em 0.6em;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.12em;
	border: var(--airline-map-rule) solid var(--airline-map-ink);
}

.airline-map__title {
	margin: 0;
	font-size: clamp(1.5rem, 2.6vw, 2.5rem);
	line-height: 1.05;
	text-transform: uppercase;
	letter-spacing: -0.01em;
	text-wrap: balance;
}

.airline-map__title a {
	color: inherit;
	text-decoration: none;
}

.airline-map__title a:hover,
.airline-map__title a:focus-visible {
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

.airline-map__excerpt {
	max-width: 46ch;
	margin: 0;
	font-size: 0.9375rem;
	line-height: 1.55;
}

.airline-map__specs {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
	gap: 0;
	margin: auto 0 0;
	border-block-start: var(--airline-map-rule) solid var(--airline-map-ink);
}

.airline-map__spec {
	padding: 0.6rem 0.75rem 0.6rem 0;
	border-block-end: var(--airline-map-rule) solid var(--airline-map-ink);
}

.airline-map__spec dt {
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.14em;
	opacity: 0.6;
}

.airline-map__spec dd {
	margin: 0.25rem 0 0;
	font-family: var(--airline-map-mono);
	font-size: 0.8125rem;
	font-variant-numeric: tabular-nums;
}

/*
 * The way out to Google Maps. It follows the reading rather than replacing it —
 * the degrees and minutes are the instrument, this is the door — so it is set
 * smaller and underlined, which is the one place in this component where a link
 * is allowed to look like a link.
 */
.airline-map__coord-link {
	display: inline-block;
	margin-block-start: 0.15rem;
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.1em;
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 0.2em;
	opacity: 0.75;
}

.airline-map__coord-link:hover,
.airline-map__coord-link:focus-visible {
	opacity: 1;
}

.airline-map__more {
	margin: 0;
}

.airline-map__link-out {
	display: inline-flex;
	gap: 0.75rem;
	align-items: center;
	padding: 0.7em 1.1em;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	text-decoration: none;
	letter-spacing: 0.16em;
	color: var(--airline-map-ink);
	background: var(--airline-map-paper);
	border: var(--airline-map-rule) solid var(--airline-map-ink);
	transition: color 180ms ease, background-color 180ms ease;
}

.airline-map__link-out:hover,
.airline-map__link-out:focus-visible {
	color: var(--airline-map-paper);
	background: var(--airline-map-ink);
}

/* ------------------------------------------------------------------ the index */

.airline-map__nav {
	display: flex;
	flex: 0 0 auto;
	gap: 0.5rem;
	align-items: stretch;
}

.airline-map__nav--bottom {
	border-block-start: var(--airline-map-rule) solid var(--airline-map-ink);
}

/*
 * The same bar, turned over. Its rule is on the other side and its ticks hang
 * down from the top instead of standing up from the bottom, so the two read as
 * a mirrored pair bracketing the panel rather than as one bar duplicated.
 */
.airline-map__nav--top {
	border-block-end: var(--airline-map-rule) solid var(--airline-map-ink);
}

.airline-map__step {
	flex: 0 0 auto;
	width: 3rem;
	padding: 0;
	font-family: var(--airline-map-mono);
	font-size: 0.875rem;
	color: var(--airline-map-ink);
	cursor: pointer;
	background: var(--airline-map-paper);
	border: 0;
	border-inline-end: var(--airline-map-rule) solid var(--airline-map-ink);
	transition: color 180ms ease, background-color 180ms ease;
}

.airline-map__nav .airline-map__step:last-child {
	border-inline: var(--airline-map-rule) solid var(--airline-map-ink);
	border-inline-end: 0;
}


.airline-map__step:hover,
.airline-map__step:focus-visible {
	color: var(--airline-map-paper);
	background: var(--airline-map-ink);
}

/*
 * Twenty-eight projects will not fit as twenty-eight names, so each one is drawn
 * as a tick and names itself on hover or on focus. Without CSS it stays an
 * ordinary ordered list of links, which is what a crawler and a screen reader get.
 */
.airline-map__index {
	display: flex;
	flex: 1 1 auto;
	gap: 0;
	align-items: stretch;
	min-width: 0;
	padding: 0;
	margin: 0;
	overflow-x: auto;
	list-style: none;
	scrollbar-width: none;
}

.airline-map__index::-webkit-scrollbar {
	display: none;
}

.airline-map__index li {
	flex: 1 1 0;
	min-width: 0.75rem;
}

.airline-map__index-link {
	position: relative;
	display: block;
	height: 100%;
	min-height: 3rem;
}

.airline-map__index-link::before {
	position: absolute;
	inset-block-end: 0.75rem;
	inset-inline-start: 50%;
	width: var(--airline-map-rule);
	height: 0.5rem;
	content: "";
	background: var(--airline-map-ink);
	opacity: 0.45;
	transition: height 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 220ms ease, background-color 220ms ease;
}

/* Hanging from the rule rather than standing on it. */
.airline-map__nav--top .airline-map__index-link::before {
	inset-block: 0.75rem auto;
}

.airline-map__index-link:hover::before,
.airline-map__index-link:focus-visible::before {
	height: 1rem;
	opacity: 1;
}

.airline-map__index-link.is-active::before {
	height: 1.5rem;
	background: var(--airline-map-accent);
	opacity: 1;
}

/*
 * The name is kept but never drawn. It used to be a tooltip above the tick, and
 * it could not work there: the index is a horizontal scroller, and overflow-x on
 * a box implies overflow-y, so every label was clipped by the strip it sat in.
 * Rather than fight that with an overlay, the tick is left to speak for itself —
 * it grows on hover and takes the accent when it is the current project, which is
 * as much as a one-character control needs to say.
 *
 * The text stays in the markup because it is the link's accessible name: a
 * screen reader would otherwise be handed twenty-eight anchors called nothing.
 * Removing it is a CSS decision, so with styles off it is a plain list of
 * project names again, which is what a crawler follows.
 */
.airline-map__index-name {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	white-space: nowrap;
	clip-path: inset(50%);
	border: 0;
}

.airline-map:not(.is-live) .airline-map__nav {
	display: none;
}

/* ------------------------------------------------------- the project's own map */

/*
 * One band of map across the page, centred on the project being read about. No
 * grid, because there is no second half: the canvas fills the band and the HUD
 * sits on top, exactly as it does in the map half of the big one.
 *
 * A column rather than a block, so the index bar underneath takes its height out
 * of the band instead of being laid over the drawing. Everything absolute inside
 * the band — canvas, HUD, the nearest button — then measures itself against the
 * map, which is the box those numbers were chosen against.
 */
.airline-map--local {
	display: flex;
	flex-direction: column;
}

.airline-map__band {
	position: relative;
	flex: 1 1 auto;
	min-height: 0;
}

/*
 * The neighbours as plain markup, for a reader with no JavaScript and for a
 * crawler. Once the viewer has turned them into markers it is redundant, so it
 * goes — but it is real content until then, not a placeholder.
 */
.airline-map__fallback {
	display: flex;
	flex: 0 0 auto;
	flex-wrap: wrap;
	gap: 0.35rem 1rem;
	padding: var(--airline-map-gutter);
	margin: 0;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.1em;
	list-style: none;
}

/*
 * Laid over the empty band rather than stacked under it. The band is the flex
 * child that takes the leftover height — it has to be, or the map would be built
 * into a box of nothing — so a list in the flow after it starts below the fold of
 * a block that hides its overflow. Out of the flow, it reads from the top of the
 * block, which is where it was before there was a bar to make room for.
 */
.airline-map--local:not(.is-live) .airline-map__fallback {
	position: absolute;
	inset: 0 0 auto;
}

.airline-map__fallback a {
	color: inherit;
}

.airline-map__fallback span::before {
	content: " ";
}

.airline-map.is-live .airline-map__fallback {
	display: none;
}

/*
 * The way on to the next site. Top right, opposite the zoom controls at the
 * bottom right, and above the HUD — which takes no pointer events, so a control
 * cannot live inside it.
 */
.airline-map__nearest {
	position: absolute;
	/* Clear of the corner bracket, which runs from 0.75rem to 2rem in from both
	 * edges — a hairline box laid across it reads as a mistake. */
	inset: 2.5rem 1.5rem auto auto;
	z-index: 600;
	display: inline-flex;
	gap: 0.6rem;
	align-items: center;
	padding: 0.6em 0.9em;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.14em;
	color: var(--airline-map-ink);
	cursor: pointer;
	background: var(--airline-map-paper);
	border: var(--airline-map-rule) solid var(--airline-map-ink);
	transition: color 180ms ease, background-color 180ms ease;
}

.airline-map__nearest:hover,
.airline-map__nearest:focus-visible {
	color: var(--airline-map-paper);
	background: var(--airline-map-ink);
}

/*
 * Nowhere left to walk to. With two projects on the site and the reader standing
 * on the other one, the tour has finished — and a control that does nothing when
 * pressed is worse than one that says it cannot. Set from map.js, which is where
 * the count of places worth going is known. Still readable, and still there, so
 * the frame does not shuffle around when it goes quiet.
 */
.airline-map__nearest:disabled {
	cursor: default;
	opacity: 0.4;
}

.airline-map__nearest:disabled:hover {
	color: var(--airline-map-ink);
	background: var(--airline-map-paper);
}

/* Nothing to walk to until the viewer has markers to walk between. */
.airline-map:not(.is-live) .airline-map__nearest {
	display: none;
}

/* The project whose page this is, told apart from its neighbours without being
 * shouted about — it is the one thing on the map the reader already knows. */
.airline-map__marker.is-current .airline-map__pin-art {
	stroke: var(--airline-map-accent);
}

.airline-map__marker.is-current .airline-map__pin-box {
	fill: var(--airline-map-accent);
}

.airline-map__marker.is-current .airline-map__pin-dot {
	fill: var(--airline-map-paper);
}

/* ------------------------------------------------------------------ the popup */

/*
 * MapLibre's popup arrives as a rounded white card with a drop shadow and a
 * pointed tip, which is a different drawing from this one. It is taken back to a
 * hairline box with a square corner, like every other box here.
 */
/*
 * Above the pins. MapLibre gives the popup no z-index of its own and the pins
 * carry 1, and neither the map container nor the canvas wrapper is a stacking
 * context, so the two numbers meet directly — which left a neighbouring pin
 * drawing across the card that had just been opened. Below the HUD (500) and the
 * nearest button (600): those are the frame, and the frame stays on top.
 */
.airline-map .maplibregl-popup {
	z-index: 5;
	font: inherit;
}

.airline-map .maplibregl-popup-content {
	padding: 0.85rem 1rem;
	color: var(--airline-map-ink);
	background: var(--airline-map-paper);
	border: var(--airline-map-rule) solid var(--airline-map-ink);
	border-radius: 0;
	box-shadow: none;
}

/* The tip is drawn from the ink rather than left as the library's grey. */
.airline-map .maplibregl-popup-tip {
	display: none;
}

/*
 * There is no close button to style: the popup is built without one. A cross in
 * the corner of a card this small was a second thing to look at in a drawing
 * that is otherwise four lines and a photograph, and there are already three
 * ways out — the map, another marker, and the escape key.
 */

/*
 * The photograph keeps its colour here for the same reason it does in the big
 * map's panel: the drawing around it is monochrome by construction, so the
 * project reads as the subject and the map as the annotation.
 */
.airline-map__popup-thumb {
	display: block;
	width: 100%;
	height: auto;
	max-height: 8rem;
	margin-block-end: 0.7rem;
	object-fit: cover;
	border: var(--airline-map-rule) solid var(--airline-map-ink);
}

/* The thumbnail opens the project, except on the post already being read. */
.airline-map__popup-thumb-link {
	display: block;
	color: inherit;
	text-decoration: none;
}

.airline-map__popup-thumb-link .airline-map__popup-thumb {
	transition: opacity 180ms ease;
}

.airline-map__popup-thumb-link:hover .airline-map__popup-thumb {
	opacity: 0.88;
}

.airline-map__popup-meta {
	margin: 0 0 0.3rem;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.14em;
	opacity: 0.65;
}

.airline-map__popup-title {
	margin: 0;
	font-size: 1rem;
	line-height: 1.15;
	text-transform: uppercase;
	letter-spacing: -0.005em;
}

.airline-map__popup-link,
.airline-map__popup-here {
	display: inline-block;
	margin-block-start: 0.6rem;
	font-family: var(--airline-map-mono);
	font-size: var(--airline-map-tick);
	text-transform: uppercase;
	letter-spacing: 0.14em;
}

.airline-map__popup-link {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 0.25em;
}

.airline-map__popup-here {
	margin-block-end: 0;
	opacity: 0.55;
}

/* --------------------------------------------------------------- narrow view */

/*
 * Below this the two halves stop being halves. The map keeps the top of the
 * screen, the project takes the rest, and the connector reroutes downwards —
 * the JavaScript reads the geometry rather than the breakpoint, so the two
 * cannot disagree about which way the line should go.
 */
@media (max-width: 900px) {
	.airline-map {
		height: auto;
		min-height: var(--airline-map-height);
	}

	.airline-map__grid {
		grid-template-columns: 1fr;
		grid-template-rows: minmax(18rem, 45svh) auto;
	}

	.airline-map--panel-left .airline-map__half--map {
		order: 0;
	}

	.airline-map__half--map {
		border-inline-end: 0;
		border-block-end: var(--airline-map-rule) solid var(--airline-map-ink);
	}

	.airline-map--panel-left .airline-map__half--map {
		border-inline: 0;
		border-block-end: var(--airline-map-rule) solid var(--airline-map-ink);
	}

	.airline-map__project {
		height: auto;
		overflow: visible;
	}

	.airline-map__image {
		max-height: 32vh;
	}

	.airline-map__readout {
		inset: 1.25rem auto auto 1.25rem;
	}

	.airline-map__coords {
		inset: auto auto 1.25rem 1.25rem;
	}
}

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

@media (prefers-reduced-motion: reduce) {
	.airline-map *,
	.airline-map *::before,
	.airline-map *::after {
		animation-duration: 0.001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.001ms !important;
	}

	/*
	 * The aircraft are motion and nothing else, so cutting the motion leaves them
	 * parked over a field rather than making a calmer map. They go entirely
	 * instead. The viewer also declines to create them at all in this case; this
	 * is the belt to that pair of braces, and covers the setting being switched off.
	 */
	.airline-map__plane {
		display: none;
	}
}

/* --------------------------------------------------------------- admin notice */

.airline-map-notice {
	padding: 1em;
	font-family: var(--airline-map-mono, monospace);
	border: 1px dashed currentColor;
}
