/* planewatch.nodejavascript.com
 *
 * 🔴 THIS SITE'S IDENTITY, AND WHY EACH VALUE IS WHAT IT IS. Part 5 of the house
 * standard requires the theme colour, the page background, the background IMAGE
 * and the Analytics id to each be unique across the family, because each one is a
 * place where two sites can end up looking like one site.
 *
 *   theme colour   #38bdf8   a bright sky cyan — nothing else in the family is
 *                            this hue. Measured against the register on 20 Sep
 *                            2026: #08050d, #0b0714, #03090b, #1d4ed8, #34d399,
 *                            #f472b6, #fbbf24, #23523c, #6d28d9, #0a0f1f.
 *   background     #04101a   a near-black of that same hue, so the tab and the
 *                            page agree. Not a neutral grey, which is how four
 *                            sites once ended up identical.
 *   background image         ONE radial plus TWO linear — a count nothing else
 *                            in the family has. Every stop fades to nothing:
 *                            a shape that stops at a visible edge reads as two
 *                            tones in two corners, and that is the thing George
 *                            rejected (19 Sep 2026: *"i dont like what you did
 *                            with two tone colors in background. gradients are
 *                            better"*).
 *   the abstract             BEARING TICKS — a ring of short radial marks, like
 *                            the compass rose on a chart or the bearing scale
 *                            around a radar. See .dvs-pattern below. Every other
 *                            site in the family already paints a square grid,
 *                            concentric rings, a crosshatch, vertical bars, a
 *                            sudoku board, a 45° hatch, a dot matrix, graph
 *                            paper, an arc fan or a street grid; this one is not
 *                            a tint of any of them.
 */

:root {
  --theme: #38bdf8;
  --bg: #04101a;
  --ink: #e8f4fb;
  --muted: #8fa9bd;
  --line: rgba(56, 189, 248, 0.16);
  --hairline: rgba(56, 189, 248, 0.22);
  --card: rgba(8, 26, 40, 0.72);
  --consent-height: 0px;
}

/* 🔴 A CLASS THAT DECLARES `display` BEATS THE ATTRIBUTE. The gate swaps the
 * question for the panel by setting `.hidden`, and the user-agent rule for
 * `[hidden]` is a DEFAULT, which any author rule overrides. `.consentAsk`
 * declares `display: flex` for its layout, so without this line the question
 * stays on screen underneath its own settings panel — readable in behaviour and
 * invisible in every screenshot. The React sites in the family only passed this
 * by accident, because their framework's reset happened to carry it. State it
 * plainly on every site. */
[hidden] {
  display: none !important;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  padding-bottom: var(--consent-height); /* the bar is fixed; the page must not hide behind it */
  background-color: var(--bg);
  background-image:
    radial-gradient(1200px 620px at 50% -8%, rgba(56, 189, 248, 0.14), transparent 70%),
    linear-gradient(160deg, rgba(14, 116, 144, 0.13), transparent 55%),
    linear-gradient(20deg, rgba(2, 132, 199, 0.1), transparent 50%);
  background-attachment: fixed;
  color: var(--ink);
  font: 16px/1.65 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* 🔴 THE ABSTRACT. Bearing ticks: radial marks around a ring, faded at the centre
 * and at the rim by a mask. It sits on its OWN fixed layer — never in the base
 * gradient stack — because the only thing that fades a background image is a
 * mask, and a mask on <body> would fade the page's own colour and its glows with
 * it. z-index below the content, and pointer-events off so it can never take a
 * click. */
.dvs-pattern {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: repeating-conic-gradient(
    from 0deg at 50% 42%,
    rgba(56, 189, 248, 0.17) 0deg 0.55deg,
    transparent 0.55deg 3deg
  );
  -webkit-mask-image: radial-gradient(
    circle at 50% 42%,
    transparent 0 17%,
    #000 33%,
    rgba(0, 0, 0, 0.55) 55%,
    transparent 74%
  );
  mask-image: radial-gradient(
    circle at 50% 42%,
    transparent 0 17%,
    #000 33%,
    rgba(0, 0, 0, 0.55) 55%,
    transparent 74%
  );
  opacity: 0.55;
}

main,
.top,
.site-footer {
  position: relative;
  z-index: 1;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--theme);
  color: #04101a;
  padding: 10px 16px;
  border-radius: 0 0 8px 0;
  font-weight: 600;
  text-decoration: none;
}
.skip:focus {
  left: 0;
  z-index: 30;
}

.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
}

.muted {
  color: var(--muted);
}

.small {
  font-size: 13.5px;
}

/* ---------------------------------------------------------------- the bar ---
 * THE HEADER IS THE BRAND AND NOTHING ELSE. No nav: the site is one page, and a
 * nav beside the name is clutter at the top of every page. The numbers here are
 * measured off the family reference, not chosen — see standard part 3. The bar
 * sits OUTSIDE the centred column, because a sticky bar that spans the page
 * cannot live inside a max-width column. */
.top {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 12px 20px;
  background: rgba(4, 16, 26, 0.82);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--hairline);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  color: inherit;
  text-decoration: none;
}

.brand .mark {
  display: inline-flex;
  color: var(--theme);
}

/* 15px with line-height 1.1 keeps the bar at 60px. At an inherited 1.6 the two
 * lines measure 42px instead of 35 and everything below sits 7px lower. */
.brand b {
  display: block;
  font-size: 15px;
  letter-spacing: 0.2px;
  line-height: 1.1;
}

/* Renders as NODEJAVASCRIPT.COM, so it reads as the parent rather than as a
 * second site name. */
.brand small {
  display: block;
  font-size: 11.5px;
  color: var(--muted);
  letter-spacing: 0.4px;
  text-transform: uppercase;
}

/* --------------------------------------------------------------- the page --- */
main {
  max-width: 940px;
  margin: 0 auto;
  padding: 34px 20px 64px;
}

.hero h1 {
  font-size: clamp(28px, 4.6vw, 42px);
  line-height: 1.12;
  letter-spacing: -0.02em;
  margin: 0 0 14px;
}

.lede {
  font-size: 17.5px;
  color: #cfe4f0;
  max-width: none;
  margin: 0 0 18px;
}

.facts {
  list-style: none;
  padding: 0;
  margin: 0 0 8px;
  display: grid;
  gap: 8px;
}

.facts li {
  border-left: 2px solid var(--theme);
  padding: 2px 0 2px 12px;
  color: #cfe4f0;
  font-size: 15px;
}

.card {
  margin-top: 26px;
  padding: 22px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 14px;
}

.card h2 {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 20px;
  margin: 0 0 6px;
}

.step {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--theme);
  color: #04101a;
  font-size: 14px;
  font-weight: 700;
}

/* 🔴 THE MEASURE CAPS ARE GONE, AND THEY WERE MEASURED OFF THE RENDERED PAGE
 * rather than guessed at. George, 20 Sep 2026: *"the description wrapping are not
 * taking up max width available"* — and he was right, three times over: a `.sub`
 * paragraph rendered 635px wide inside an 856px card (74ch), a list rendered
 * 666px (80ch), and the lede 72ch. The card itself is already capped at 940px, so
 * that is the measure; a second cap inside it just leaves a third of the card
 * empty beside the text. */
.sub {
  color: var(--muted);
  font-size: 15px;
  margin: 0 0 14px;
  max-width: none;
}

/* -------------------------------------------------------------- controls --- */
/* ---------------------------------------------------------------------------
 * 🔴 ONE PANEL, FOUR ALIGNED ROWS — THE FILTERS ARE THE ONE PLACE THE EYE SHOULD BE ABLE TO
 * SCAN DOWN.
 *
 * George, 22 Sep 2026, looking at it: *"the filtering section is ugly look at it, its too
 * cluttered"*. The clutter was structural: four rows of pills with a label floating at the start of
 * each, each row wrapping wherever it ran out of width (`Interesting` alone under Kind; `this year /
 * no data` alone under Last seen), so nothing lined up with anything.
 *
 * The fix is a grid, and it is three things: the labels sit in a fixed, right-aligned column so they
 * read as a list of their own; every row's chips begin at the same x, wrapped lines included; and the
 * whole block is one bordered panel rather than four stray lines.
 *
 * ⚠️ THE PADDING COMES DOWN WITH IT. The chips were sized for a page where a filter row was the only
 * thing in its card; inside a panel with four of them, at 8px of padding and 14px of type, twenty-eight
 * pills become a wall. At 4px and 12.5px they are still comfortably pressable — the whole chip is the
 * target, not the text — and the widest row now fits on one line.
 * ------------------------------------------------------------------------ */
/*
 * 🔴 ROOM BETWEEN THE ROWS, AND NOT BETWEEN THE CHIPS INSIDE ONE.
 *
 * George, 22 Sep 2026: *"put vertical space between filter labels, they are way too close
 * together"*. The panel was laid out with a single 4px gap doing both jobs, so a row's own chips
 * sat as far from each other as two different labels did — and once the maker row began wrapping to
 * a second line, five labels read as one undifferentiated block.
 *
 * So the space between ROWS is 16px, and the space inside a row stays 5px (`.filter-row .chips`).
 * That difference is the whole point: a wrapped line still reads as part of the row above it, rather
 * than as a new row that has lost its label.
 *
 * ⚠ IT WAS 10px FOR TEN MINUTES AND GEORGE ASKED FOR MORE — *"even more space"*, 22 Sep 2026 — so the
 * number that is here now is his eye's, not a measurement. It is one value, in one rule.
 */
.filters {
  display: grid;
  row-gap: 16px;
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: rgba(4, 16, 26, 0.35);
}

.filter-row {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  align-items: center;
  column-gap: 10px;
}

.filter-row .chip-label {
  margin: 0;
  font-size: 11.5px;
  font-weight: 600;
  text-align: right;
}

.filter-row .chips {
  margin: 0;
  gap: 5px;
}

.filter-row .chip {
  padding: 4px 10px;
  font-size: 12.5px;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

/* 🔴 THE ROW'S OWN NAME, SAID ONCE. George, 20 Sep 2026: *"i want a label instead of saying
 * last seen mnay times"*. It is not a chip and must not look like one — no border, no pill, no
 * pointer — because a label that can be mistaken for a control gets pressed. It is set back in
 * the muted colour and pushed slightly away from the first chip, so the eye reads
 * "Last seen · [All flights] [5 minutes] …" as one sentence. */
.chip-label {
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.3px;
  margin-right: 4px;
  white-space: nowrap;
}

.chip {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  padding: 8px 13px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(4, 16, 26, 0.6);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}

.chip b {
  color: var(--theme);
  letter-spacing: 0.3px;
}

.chip span {
  color: var(--muted);
}

.chip-small {
  padding: 6px 12px;
}

.chip:hover {
  border-color: var(--theme);
}

.chip[aria-pressed="true"] {
  border-color: var(--theme);
  background: rgba(56, 189, 248, 0.14);
}

/* The two states of a type button have to be told apart at a glance, because the
 * button is also the only way to stop watching something. */
.chip-on {
  padding: 7px 13px;
  font-size: 13px;
}

.chip-off {
  padding: 7px 13px;
  font-size: 13px;
  border-color: var(--muted);
  color: var(--muted);
}

/* --------------------------------------------------------- the type list --- */
.typelist {
  display: grid;
  gap: 8px;
  margin-bottom: 12px;
}

.typerow {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 14px;
  align-items: center;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: rgba(4, 16, 26, 0.55);
}

/* 🔴 YELLOW, AND THE SAME YELLOW AS THE STAR. George, 20 Sep 2026: *"why are some
 * aircraft have highlighted cards?"* — which is the answer to the question, not a
 * separate request. A starred row was tinted the site BLUE while the star beside it
 * was YELLOW, so the tint and the mark disagreed about what they meant, and a row
 * that was highlighted for one reason was indistinguishable from a row highlighted
 * for another.
 *
 * It means one thing: this row is yours. A starred type is highlighted here in the
 * same gold the star is drawn in. */
.typerow-on {
  border-color: #f0be5a;
  background: rgba(240, 190, 90, 0.1);
}

.typerow-main {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 15px;
}

/* The bar is not decoration: "136 sightings" and "1 sighting" are the same shape
 * to the eye until the two are drawn against each other. */
.typerow-meta {
  grid-column: 1;
  display: flex;
  align-items: center;
  gap: 10px;
}

.typerow-bar {
  display: inline-block;
  width: 90px;
  height: 5px;
  border-radius: 3px;
  background: rgba(56, 189, 248, 0.14);
  overflow: hidden;
  flex: none;
}

.typerow-bar i {
  display: block;
  height: 100%;
  background: var(--theme);
}

.typerow .type-toggle {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
}

/* ------------------------------------------------------ what you watch --- */
.watch-type {
  border: 1px solid var(--line);
  border-radius: 11px;
  padding: 11px 13px;
  background: rgba(4, 16, 26, 0.55);
  display: grid;
  gap: 8px;
}

.watch-type-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.watch-type-tails {
  font-size: 14px;
}

.tail {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  margin-right: 6px;
}

.tail-remove {
  color: var(--muted);
  text-decoration: none;
}

.tail-form {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.tail-form input {
  flex: 1 1 220px;
  padding: 7px 11px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: rgba(2, 10, 16, 0.7);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}

.ghost {
  padding: 9px 16px;
  border-radius: 9px;
  border: 1px solid var(--theme);
  background: transparent;
  color: var(--theme);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.ghost:hover {
  background: rgba(56, 189, 248, 0.14);
}

.linkish {
  border: 0;
  background: none;
  padding: 0;
  color: inherit;
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.watch-form {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
  margin-bottom: 12px;
}

/* 🔴 THE FIELD CLAIMS THE WHOLE ROW SO THE INPUT CAN BE FULL WIDTH. The form is
 * a wrapping flex row: a field asking for 100% takes the row, and the two
 * buttons fall onto the line beneath it instead of squeezing the input into
 * whatever is left over. `min-width: 0` defeats a flex item's automatic
 * minimum size, which is what otherwise refuses to let the input shrink. */
.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 1 1 100%;
  min-width: 0;
}

.label {
  font-size: 12.5px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--muted);
}

.field input {
  padding: 9px 12px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: rgba(2, 10, 16, 0.7);
  color: var(--ink);
  font: inherit;
  width: 100%;
  min-width: 0;
}

.field input:focus {
  outline: 2px solid var(--theme);
  outline-offset: 1px;
}

.watchlist {
  list-style: none;
  padding: 0;
  margin: 0 0 14px;
  display: grid;
  gap: 5px;
  font-size: 15px;
}

.notify {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.notify p {
  margin: 0;
}

/* --------------------------------------------------------- departures --- */
/* 🔴 THE DEPARTURE BOARD'S STYLES ARE GONE WITH THE BOARD. George, 20 Sep 2026,
 * pasting the list: *"remove all of this"*. `.empty` STAYS — it was written beside
 * the board's styles but it is used by every empty-state on the page, so removing
 * the block wholesale would have taken it with them. */
.empty {
  color: var(--muted);
  font-size: 15px;
  margin: 0 0 10px;
}

.tag {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  border: 1px solid var(--line);
  color: var(--muted);
}

/* 🔴 THE TAG VARIANTS ARE ALL GONE. `.tag-ground` and `.tag-unknown` were the phase tags on the table's
 * rows, `.tag-confirmed` and `.tag-inferred` came from the old departure list, and `.tag-watched` from
 * before that — measured 22 Sep 2026, not one of the five appears in `site/index.html` or `src/app.ts`.
 * The base `.tag` above is still live: it is the kind chip on a row of the filter list. */

/* -------------------------------------------------------------- table --- */
/* 🔴 THE TABLE'S OWN RULES ARE GONE, AND THE WHOLE BLOCK WENT TOGETHER. The six-column list of what
 * the feed can see was deleted on George's instruction (22 Sep 2026), so `.table-wrap`, `.aircraft`,
 * `.watched-row`, `.cell-city`, `.cell-type`, `.cell-tail`, `.dest-*`, `.leg-time`, `.row-tag` and the
 * `.aircraft tr.row-selected` family all styled elements the page no longer draws — verified by
 * counting each class in `site/index.html` and `src/app.ts`, where every one of them now reads 0.
 *
 * 🟢 WHAT STAYED, AND WHY: `.locmap-plane-pick` is the ring around
 * the aeroplane the map is on; `.refreshed-line` is the page's own clock; a
nd `.limits` styles the
 * privacy list, not the honesty card.
 *
 * 🔴 AND `.watch-type[data-hex]` / `.watch-type.row-selected` WENT TOO, 22 September 2026. They styled a
 * watchlist row that could be PRESSED — a border, a pointer and a focus ring while the named aeroplane had
 * a position. George replaced that press with a boolean: *"even though the filight may or may not be in the
 * air, i want this to be a boolean input, not a link"*. Verified by counting: `data-hex` on a watchlist row
 * and `row-selected` are both 0 in `src/app.ts` now. */
/*
 * THE FILTER COUNT AND ITS REFRESH, ON ONE LINE: the sentence takes the width, the control takes the
 * right edge — the same arrangement as `.refreshed-line` above the map, so the page has one way of
 * saying "this number, and a way to ask for it again".
 */
.filter-note-line {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.filter-note-line #filterNote {
  flex: 1 1 auto;
  margin: 0;
}

.refreshed-line {
  margin: 0 0 2px;
  text-align: right;
}

/* The one control on this line, and the gap that keeps it off the sentence it follows. It is
 * deliberately the same `.linkish` shape `change location` and `delete my data` use, so the three
 * quiet text controls on the page read as one kind of thing. */
.refresh-now {
  margin-left: 0.5rem;
}

/* 🔴 AND THE PLACE BESIDE THE CODE, THE TYPE'S NAME, AND THE TAIL UNDER IT ALL WENT WITH THE TABLE.
 * `.cell-city`, `.cell-type` and `.cell-tail` each styled a span inside a row of the deleted list, and
 * measured on 22 Sep 2026 not one of the three appears in `site/index.html` or `src/app.ts`. */

/* ===========================================================================
 * 🔴 THE PICKED FLIGHT, IN ONE GREEN HUE — ON THE WATCHLIST AND ON THE MAP.
 *
 * George, 22 Sep 2026: *"i want to be able to select one of those rows, if i do that i want the map to
 * zoom in to that flight. if slect again, it will unselect and zom back out again. the select and
 * unselected can be a simple green hue border"*.
 *
 * ⚠️ THE TABLE'S HALF OF THIS WENT WITH THE TABLE. A named tail in the watchlist can still be
 * pressed, and the rule below is the one that survived; the ring around the aeroplane on the map is
 * `.locmap-plane-pick`, further down.
 * ------------------------------------------------------------------------ */

/* The ring around the aeroplane the map is on, in that green. */
.locmap-plane-pick {
  fill: none;
  stroke: #4ade80;
  stroke-width: 2;
}
.locmap-plane-label-picked {
  fill: #4ade80;
}

/* 🔴 THE AEROPLANE ON THE MAP IS PRESSABLE, AND IT HAS TO LOOK IT. George, 22 Sep 2026: *"click on any
 * aircraft in the air is not zooming into that aircraft … the map should zoom into it"*. The shape and the
 * name beside it both carry the airframe's hex and both put the map on it, so both take the pointer and both
 * light up under it — the name is part of the aeroplane as far as a reader is concerned, and a click that
 * works on one of them and not the other is worse than neither. Hover is a slightly larger icon rather than
 * a colour change, because the colour on this map already means something else (green the picked one, cyan
 * every other, and the trail's own hues). */
.locmap-plane-mark[data-hex],
.locmap-plane-label[data-hex] {
  cursor: pointer;
}
.locmap-plane-mark[data-hex]:hover .locmap-plane-icon {
  fill: #7dd3fc;
}
.locmap-plane-label[data-hex]:hover {
  fill: #e8f4fb;
}

/* The way back sits under the map on its own line, at the map's own width. */
#flightAll {
  margin-top: 8px;
}

/* -------------------------------------------------------------- lists --- */
.limits,
.howto {
  padding-left: 20px;
  margin: 0;
  display: grid;
  gap: 9px;
  color: #cfe4f0;
  font-size: 15px;
  max-width: none;
}

.limits li::marker,
.howto li::marker {
  color: var(--theme);
}

#status {
  margin: 12px 0 0;
}

#status[data-kind="error"] {
  color: #fca5a5;
}

#status[data-kind="ok"] {
  color: var(--muted);
}

/* -------------------------------------------------------------- footer --- */
.site-footer {
  border-top: 1px solid var(--hairline);
  padding: 26px 20px 34px;
  text-align: center;
  font-size: 14px;
  color: var(--muted);
}

.footer-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  color: var(--ink);
  margin-bottom: 8px;
}

.footer-brand .mark {
  display: inline-flex;
  color: var(--theme);
}

.footer-brand a {
  color: var(--theme);
}

.footer-links {
  margin: 0 0 6px;
}

.footer-links a,
.footer-links button {
  color: var(--ink);
}

.footer-copy {
  margin: 0;
  font-size: 12.5px;
  color: var(--muted);
}

/* --------------------------------------------------------- consent bar ---
 * Two views share this one fixed bar rather than a second dialog appearing over
 * it, because the bar is the element whose height is measured and reserved at the
 * bottom of the page. */
.consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  padding: 14px 20px;
  background: rgba(3, 13, 21, 0.97);
  border-top: 1px solid var(--hairline);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  font-size: 14.5px;
}

.consentAsk,
.consentPrefs {
  max-width: 900px;
  margin: 0 auto;
}

.consentAsk {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.consentTitle {
  font-size: 16px;
  margin: 0;
}

.consentText {
  margin: 0;
  color: #cfe4f0;
}

.consentText a {
  color: var(--theme);
}

.consentHead {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.consentClose {
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.consentList {
  list-style: none;
  padding: 0;
  margin: 10px 0;
  display: grid;
  gap: 10px;
}

.consentRow {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  justify-content: space-between;
}

.consentRowName {
  margin: 0;
  font-weight: 600;
}

.consentRowWhy {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: 13.5px;
  max-width: 68ch;
}

/* The visual state is drawn from `aria-checked`, so the ring and what a screen
 * reader announces cannot disagree — they are the same attribute. Nothing here
 * toggles a class. */
.consentSwitch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  flex: none;
}

.consentSwitch .consentDot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--muted);
}

.consentSwitch[aria-checked="true"] {
  border-color: var(--theme);
  color: var(--theme);
}

.consentSwitch[aria-checked="true"] .consentDot {
  background: var(--theme);
}

/* Accept and Reject are the same class, the same size, the same weight and the
 * same distance from the edge. Neither is given a focus ring when the bar opens,
 * because focus goes to the bar itself. */
.consentActions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  max-width: 900px;
  margin: 12px auto 6px;
}

/* 🔴 THE TWO ANSWERS ARE THE SAME SIZE, SO NEITHER ONE IS NUDGED.
 * "Accept all" and "Reject all" are different lengths, and buttons sized to their own text made the
 * accept button 104.3 px wide and the reject button 100.3 px — a four-pixel advantage to the answer the
 * site would rather have, on the one control where the reader's choice must not be steered. Found by the
 * live check, which measures both on the deployed page: `the two answers are the same size and weight`. */
.consentActions button {
  flex: 1 1 9rem;
  min-width: 9rem;
}

.consentSettings {
  display: block;
  margin: 0 auto;
  color: var(--muted);
  font-size: 13px;
}

@media (max-width: 560px) {
  .consentRow {
    flex-direction: column;
  }
  .typerow {
    grid-template-columns: 1fr;
  }
  .typerow .type-toggle {
    grid-column: 1;
    grid-row: auto;
    justify-self: start;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/* ====================================================== choosing, expanded ==
 * Added 20 Sep 2026 for the second round of the brief: airports around the
 * reader, tail numbers inside a type, and a second view that charts what is up
 * there now. Everything below is new; nothing above was changed.
 */

/* Screen-reader-only text. The chart's table needs a caption and a sighted
 * reader does not need to be told what the columns already say. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The status line sits outside every card now, because it is the one thing that
 * must be readable from BOTH views — a feed error hidden behind a tab is a
 * silent failure, which is the kind this site is written against. */
main > #status {
  margin: 0 0 18px;
}

/* ------------------------------------------------- airports around you --- */
.nearby {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.nearby-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}

#nearbyList {
  margin-top: 10px;
}

.near-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.near-km {
  color: var(--accent, var(--theme));
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}

/* ------------------------------------------- tail numbers inside a type --- */
.typerow .typerow-actions {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: stretch;
}

/* The button is no longer a direct grid child, so the placement rule written for
 * it earlier no longer applies and must be undone explicitly. */
.typerow .typerow-actions .type-toggle,
.typerow .typerow-actions .alert-toggle {
  grid-column: auto;
  grid-row: auto;
}

/* 🔴 THE BELL IS THE STAR'S TWIN IN SHAPE AND ITS OPPOSITE IN COLOUR. George, 20 Sep 2026:
 * *"i should be able to select from the list w3hich ones i want an alert for"*. Two marks
 * sit on every row now and they answer different questions — the star says show me this, the
 * bell says wake me for this. Sharing the gold would make them one mark in two places, which
 * is exactly the confusion the separate list is meant to end, so the bell takes the site's
 * own accent. */
.bell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(4, 16, 26, 0.5);
  color: color-mix(in srgb, var(--theme) 72%, #7fb4cc);
  cursor: pointer;
}

.bell svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linejoin: round;
}

.bell:hover {
  border-color: var(--theme);
  color: var(--theme);
}

.bell[aria-pressed='true'] {
  border-color: var(--theme);
  background: color-mix(in srgb, var(--theme) 16%, transparent);
  color: var(--theme);
}

.bell[aria-pressed='true'] svg {
  fill: currentColor;
}

/* Armed for some tail numbers rather than the whole type — the same idea as the star's dot,
 * so a narrowed bell can never be mistaken for an armed whole type. */
.bell-part {
  position: relative;
}

.bell-part::after {
  content: '';
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--theme);
  transform: translate(11px, 11px);
}

.tail-panel {
  grid-column: 1 / -1;
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px dashed var(--line);
}

.tail-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin-top: 8px;
}

.tail-box {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 13px;
  cursor: pointer;
}

.tail-box:hover {
  border-color: var(--theme);
}

.tail-box input {
  accent-color: var(--theme);
}

/* REMOVED 20 Sep 2026: `.typerow-curated` and `.tag-curated` styled a hand-written
 * list of aircraft that no longer exists — the page lists only what the feed was
 * seen showing. Nothing had emitted either class for some time, so they were rules
 * for a thing that was not on the page. Measured before deleting: zero occurrences
 * in src/, site/*.js and site/*.html. */

/* ------------------------------------------------------ the live chart --- */
.radar-wrap {
  display: flex;
  justify-content: center;
  margin: 16px 0;
}

.radar {
  width: 100%;
  max-width: 380px;
  height: auto;
  aspect-ratio: 1;
}

.radar-edge {
  fill: none;
  stroke: rgba(56, 189, 248, 0.5);
  stroke-width: 1.4;
}

.radar-ring {
  fill: none;
  stroke: rgba(56, 189, 248, 0.2);
  stroke-width: 1;
}

.radar-label,
.radar-compass {
  fill: var(--muted, #8aa6b8);
  font-size: 10px;
  font-family: var(--mono, ui-monospace, monospace);
}

.radar-compass {
  font-size: 12px;
}

.radar-field {
  fill: var(--theme);
}

.radar-dot {
  fill: rgba(56, 189, 248, 0.85);
  stroke: rgba(4, 16, 26, 0.9);
  stroke-width: 1.2;
}

/* ======================= third pass, 20 Sep 2026 ============================
 * The reader's own words drove every rule below: find the airports first, the
 * distance is from THEIR code, warplanes first, one word for watching a type,
 * tail numbers as chips rather than behind a button, descriptions that use the
 * width they have, a spinner while an airport loads, and the later steps kept
 * out of the way until the first one is answered.
 */

/* ---------------------------------------------------- where are you first --- */
.subhead {
  margin: 18px 0 6px;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}

.fallback {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.fallback summary {
  cursor: pointer;
  font-size: 14px;
  color: var(--muted, #8aa6b8);
}

.fallback summary:hover {
  color: var(--theme);
}

/* ------------------------------------------------------ the steps reveal --- */
/* 🔴 THE GLIDE DOWN. A step that appears instantly reads as a page that was
 * always there and merely redrawn; a step that settles into place reads as the
 * page responding to what was just done. Slow and small on purpose — 620ms, and
 * a move of 18px — because anything faster looks like a jolt and anything
 * larger looks like a slide. */
@keyframes step-glide {
  from {
    opacity: 0;
    transform: translateY(-18px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.step-arrive {
  animation: step-glide 620ms cubic-bezier(0.22, 0.7, 0.3, 1) both;
}

/* The steps that are waiting are simply not rendered — `[hidden]` is already in
 * force from the base sheet, and this keeps the gap out of the layout too. */
.step-gated[hidden] {
  display: none !important;
}

/* A spinner ON the button, because an airport lookup takes a moment and a button
 * that does nothing for a second gets pressed twice.
 * 🔴 SETTLED THE MOMENT THE LOOKUP ENDS — a spinner that outlives its request is
 * a lie about where the work is. */
.chip[aria-busy='true'] {
  position: relative;
  color: transparent !important;
  pointer-events: none;
}

.chip[aria-busy='true']::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 13px;
  height: 13px;
  border: 2px solid rgba(56, 189, 248, 0.28);
  border-top-color: var(--theme);
  border-radius: 50%;
  animation: chip-spin 700ms linear infinite;
}

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

/* ------------------------------------------------------------- the rows --- */
/* Three columns now: the drawing, the words, the buttons. */
.typerow {
  grid-template-columns: auto 1fr auto;
  align-items: start;
}

.typerow-thumb {
  grid-column: 1;
  grid-row: 1;
  color: var(--theme);
  opacity: 0.62;
  line-height: 0;
}

.typerow-thumb svg {
  width: 34px;
  height: 34px;
}

/* 🔴 THE DESCRIPTIONS NOW USE THE WIDTH THEY HAVE. They were in a grid cell that
 * ended before the buttons, so a full sentence wrapped early and read as a
 * narrow column of text beside a lot of empty card. */
.typerow .typerow-meta {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.typerow .typerow-actions {
  grid-column: 3;
  grid-row: 1;
  flex-direction: row;
  align-items: center;
}

/* Removed 20 Sep 2026 with the rest of the curated styling — see the note further
 * up the file. Nothing emitted `.typerow-curated`. */

/* ------------------------------------------------------ tail numbers --- */
/* Chips in the card, always visible, no disclosure button: the reader asked for
 * the tail numbers to BE there rather than hidden one press away. */
.tail-chips {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.tail-chip {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 12px;
  padding: 4px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: rgba(4, 16, 26, 0.5);
  color: var(--ink);
  cursor: pointer;
}

.tail-chip:hover {
  border-color: var(--theme);
}

/* A highlighted tail is a rule of its own, and it is drawn in the same colour as
 * a favourited type so the two read as the same kind of thing.
 *
 * 🔴 AND THE STAR INSIDE IT IS YELLOW TOO. George, 20 Sep 2026: *"the star for
 * selecting a tail should be yelow like airport"*. This rule said `var(--ink)`,
 * which is what the little star inherits through `currentColor` — so the same mark
 * came out near-white on a tail number and yellow on an airport, one card apart,
 * and two colours for one meaning is a reader having to learn the page twice. */
.tail-chip[aria-pressed='true'] {
  border-color: #f0be5a;
  background: rgba(240, 190, 90, 0.16);
  color: #f0be5a;
}

.tail-note {
  grid-column: 1 / -1;
  margin: 8px 0 0;
}

@media (prefers-reduced-motion: reduce) {
  .step-arrive {
    animation: none;
  }
  .chip[aria-busy='true']::after {
    animation-duration: 1.6s;
  }
}

/* ======================= fourth pass, 20 Sep 2026 ===========================
 * A star instead of the word, a little star on a highlighted tail number, a note
 * rather than a hidden step, and a drawing of where you are.
 */

/* ------------------------------------------------------------ the star --- */
/* 🔴 ONE CONTROL, THREE STATES, AND THE SHAPE SAYS WHICH. George, 20 Sep 2026:
 * *"instead of favority this, use a start icon"*. An outlined star means the
 * whole type is not favourited; a filled one means it is. A type narrowed to
 * particular tail numbers is NOT the whole type, so it shows outlined — which is
 * the same thing the reader was told in words before, said in a glance. */
/* 🔴 THE STAR IS YELLOW, NOT THE SITE BLUE. George, 20 Sep 2026: *"if a star is used,
 * mayeb yellow hue look letter"*. A yellow star is the mark the whole world already
 * reads as "this one is mine" — the browser's own bookmark bar, every review form,
 * every favourites list. Painting it in the site's own blue made it look like one
 * more control beside the row rather than a choice the reader had made.
 *
 * It is also already the colour this site uses for the things it marks as special:
 * the airport marker on the map and the hand-listed rows, before those went.
 * 🔴 THE OUTLINE, THE HOVER AND THE PRESSED FILL ARE ALL A YELLOW HUE, because a
 * yellow fill inside a blue border reads as two unrelated states fighting. */
.star {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(4, 16, 26, 0.5);
  color: #b08b3f;
  cursor: pointer;
}

.star svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linejoin: round;
}

.star:hover {
  border-color: #f0be5a;
  color: #f0be5a;
}

.star[aria-pressed='true'] {
  border-color: #f0be5a;
  background: rgba(240, 190, 90, 0.16);
  color: #f0be5a;
}

.star[aria-pressed='true'] svg {
  fill: currentColor;
}

/* A narrowed type: watched, but not as a whole — so it is outlined, with a dot,
 * rather than claiming to be filled. */
.star-part {
  position: relative;
}

.star-part::after {
  content: '';
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #f0be5a;
  transform: translate(11px, 11px);
}

/* -------------------------------------------------- the little star --- */
/* 🔴 ONE MARK, AND IT ALWAYS LEADS ITS TEXT. George, 20 Sep 2026: *"when a star is
 * placed inside anything it should precede the text"*. Shared by the tail numbers
 * and the airports, because it means the same thing in both: you picked this one.
 * The margin is on the RIGHT so the star sits before the word rather than after it,
 * and the tiny lift puts its point on the text's cap height instead of its baseline. */
.mark-star {
  width: 11px;
  height: 11px;
  margin-right: 5px;
  fill: currentColor;
  vertical-align: -1px;
}

/* An airport is a choice too, so it takes the same yellow. Without it the airport
 * you had picked looked exactly like the thirteen you had not. */
.near-chip[aria-pressed='true'] {
  border-color: #f0be5a;
  background: rgba(240, 190, 90, 0.16);
  color: #f0be5a;
}

.near-chip[aria-pressed='true'] .near-km {
  color: #f0be5a;
}

 * aircraft to be visible until an airport is selected. maybe make it visible just
 * put note to select airport first"*. So the step is shown with the reason it
 * cannot be used yet, and its controls are inert rather than absent — a page that
 * hides what is coming reads as broken, and a page that explains reads as
 * waiting. */
.step-why {
  margin: 0 0 14px;
  padding: 10px 13px;
  border: 1px dashed var(--line);
  border-radius: 10px;
  background: rgba(4, 16, 26, 0.4);
  color: var(--muted, #8aa6b8);
  font-size: 14px;
}

.step-gated[hidden] {
  display: none;
}

/* --------------------------------------------------------- the drawn map --- */
/* 🔴 NOT GOOGLE MAPS, AND THE REASON IS IN THE PAGE. An embedded map is an API
 * key, a billing account, and a request to Google from every visitor's browser —
 * which collides head-on with the cookie gate this site is built around, because
 * a map loads Google whether or not the reader said yes. So the map is drawn
 * here, from the two facts that matter: where you are, and how far out the page
 * is looking. */
.locmap {
  display: block;
  width: 100%;
  max-width: 340px;
  height: auto;
  margin: 14px auto 4px;
}

.locmap-ring {
  fill: rgba(56, 189, 248, 0.07);
  stroke: rgba(56, 189, 248, 0.5);
  stroke-width: 1.3;
}

.locmap-you {
  fill: var(--theme);
}

/* 🔴 THE AIM IS NOT ALWAYS THE READER, AND IT IS NOT DRAWN AS THOUGH IT WERE.
 *
 * With a place known, the fence is centred on the reader and this class is never used. With
 * no place, it is centred on the middle of the airports being watched — and a dot marked
 * "you" on a map that does not know where you are is the page inventing a fact. So the ring
 * is drawn around the aim either way, and the marker says what it is: hollow, so it reads as
 * a reference point rather than a person. */
.locmap-anchor {
  fill: none;
  stroke: var(--theme);
  stroke-width: 1.8;
}

/* 🔴 GREY IS AN AIRPORT YOU HAVE NOT PICKED. Every marker on this map used to be
 * gold, so the airport the page was watching looked exactly like the thirteen it
 * was not — and now that several can be picked at once, the picked ones have to be
 * the ones that stand out. The chosen markers and their codes are set at the end of
 * this file, after the `.locmap` block that the picked airports are drawn inside. */
.locmap-airport {
  fill: #7d97a8;
}

.locmap-line {
  stroke: rgba(56, 189, 248, 0.25);
  stroke-width: 1;
  stroke-dasharray: 3 4;
}

.locmap-label {
  fill: var(--ink, #e8f4fc);
  font-size: 10px;
  font-family: var(--mono, ui-monospace, monospace);
}

.locmap-small {
  fill: var(--muted, #8aa6b8);
  font-size: 9px;
  font-family: var(--mono, ui-monospace, monospace);
}

.locmap-note {
  margin: 0 0 14px;
}

/* 🔴 A NOTE THAT LANDS IN A CHIP CONTAINER MUST TAKE ITS OWN LINE. Measured on
 * the rendered page: the "press find airports near me" note and the notification
 * note were being laid out as if they were chips, so each sat at its own text
 * width — 601px and 352px inside an 856px card — with the rest of the line empty
 * beside it. They are paragraphs in a flex row, not chips, and this is the rule
 * that says so. */
.chips > p,
.notify > p {
  flex-basis: 100%;
  width: 100%;
  margin: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE MAP IS NOW A REAL MAP — a free one, fetched by our own server.
 *
 * George, 20 Sep 2026: *"instead of ggoogle maps, use a free service"*.
 * OpenStreetMap, measured first: 200 with no key. The tiles are 256px squares
 * absolutely placed in one box, and the fence and the airport codes are drawn in
 * an SVG laid over the SAME box in the SAME pixel space — so the circle cannot
 * drift off the map it is drawn on.
 *
 * These rules sit last on purpose: the earlier `.locmap` rule was written for a
 * drawing and capped it at 340px, which would clamp this one short.
 * ══════════════════════════════════════════════════════════════════════════ */
#locMap {
  /* 🔴 THE MAP FILLS THE CARD NOW. George, 20 Sep 2026: *"use the full available
   * width for the map"*. It was a fixed 512-pixel box with a horizontal scrollbar
   * for anything wider, which is what made a wide card look half empty. The box is
   * measured from this element, so it must be free to be as wide as the card. */
  overflow: hidden;
  margin: 14px 0 4px;
}

.locmap {
  position: relative;
  overflow: hidden;
  margin: 0 auto;
  max-width: 100%;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #0d1a24;
}

/* The second question inside step 1 — how far out — needs air above it, or the
 * airport chips read as part of the distance choice. */
#radiusHead {
  margin-top: 18px;
}

.locmap-tile {
  position: absolute;
  width: 256px;
  height: 256px;
  /* Tuned so the map reads as a dark base under the overlay rather than as a
   * bright sheet with marks on it. */
  opacity: 0.8;
  filter: grayscale(0.3) brightness(0.68) contrast(1.08);
}

.locmap-over {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.locmap-fence {
  fill: rgba(56, 189, 248, 0.1);
  stroke: var(--theme);
  stroke-width: 1.6;
}

.locmap-you-label {
  fill: var(--theme);
  font-size: 9px;
  font-family: var(--mono, ui-monospace, monospace);
}

.locmap-note a {
  color: var(--theme);
}

/* ------------------------------------------------------ where you are --- */
/* 🔴 ONE LINE THAT SAYS WHAT IT IS, WITH THE WAY TO CHANGE IT BESIDE IT. George,
 * 20 Sep 2026: *"if i have a location, selected tell me what that is, and the chage
 * location link and go beside it"*. It used to read "Around Hamilton, Ontario change
 * location" as one run-on line of small grey text — a label, a value and an action
 * with nothing between them, so the action read as part of the place name. A flex row
 * keeps them on one line while separating them, and wraps as a unit on a narrow
 * screen rather than splitting mid-phrase. */
.place-line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: 2px 0 12px;
  font-size: 14px;
}

.place-line .place-label {
  color: var(--muted);
}

.place-line .place-value {
  color: var(--ink, #e8f4fc);
}

/* 🔴 WHERE YOU ARE IS THE ONE THING ON THIS LINE WORTH MARKING. George, 20 Sep 2026:
 * *"Your location put the city in highlighted text"*. A faint wash of the theme colour
 * behind it, so it reads as the answer to a question rather than as more grey prose —
 * and the town in plain text beside it, because "Stoney Creek Hamilton" is one thought
 * and "Stoney Creek" alone would leave the province off. */
.place-line .place-area {
  color: var(--theme);
  background: color-mix(in srgb, var(--theme) 16%, transparent);
  border-radius: 5px;
  padding: 1px 7px;
}

.place-line .place-town {
  color: var(--muted);
}

/* ------------------------------------------------- the communities list --- */
/* One of these is where the reader is, and the page cannot tell which — the postal code
 * covers all of them. So they are offered rather than guessed, in the same chip family as
 * the airport choices so the page has one idea of what "pick something" looks like. */
#areaPicker {
  margin: -6px 0 12px;
}

/* ------------------------------------------------ how far out (the chips) --- */
/* ---------------------------------------------------------------------------
 * 🔴 THE SLIDER'S STYLES ARE GONE, AND SO IS THE SLIDER.
 *
 * George, 22 Sep 2026: *"i forgot the slider is actually a filter for pic an aircraf. lets remove the
 * slider and ask the distance about the pick an aircraf under kind"*. What was deleted here: the
 * `.radius-row` flex line, the `.radius-track` box the value was positioned against, `.radius-slider`
 * with its four browser-specific track and thumb rules, and `.radius-value` — the label that rode on
 * the thumb, written by `placeReadout` against the thumb's own width.
 *
 * The distance is a row of `chip chip-small` buttons in step 3 now, so it needs no styles of its own:
 * it wears the same chips as the kind, year and last-seen filters, which is the point of making it a
 * chip row rather than a track. Nothing here is left behind to rot, because a rule for an element the
 * page no longer draws is how a deleted control keeps looking alive.
 * ------------------------------------------------------------------------ */

/* 🔴 THE TYPE GROUP HEADING IN THE "WHAT THE FEED CAN SEE" TABLE. George, 20 Sep 2026:
 * *"i want to group by aircraft type, and the airport"*. A group has to look like a group
 * rather than like a row with an empty first cell, or the reader has to work out that the
 * type is a heading by reading three rows. */
/* ---------------------------------------------------------------------------
 * 🔴 THE TYPE GROUP ROWS ARE GONE, AND THESE RULES WENT WITH THEM.
 *
 * George, 22 Sep 2026: *"the line in the middle is confusing, A21N Airbus A321neo 1 aircraft …"*
 * — the type used to be named in a `<tr>` spanning every column, so the table carried a line
 * that looked like a row, answered a question the row beneath it was already answering, and said
 * nothing but "1 aircraft" when a type had one example. The type is a column on the row now, and
 * the rows are still sorted by it. `.aircraft .type-group th` and `.group-count` had no other
 * caller, so they were deleted rather than left behind: a rule for markup that does not exist is
 * how a deleted feature keeps looking alive.
 * ------------------------------------------------------------------------ */

/* "3 of 3 looks" — how often, beside how recently. */
.looks-tag {
  color: var(--theme);
  border: 1px solid color-mix(in srgb, var(--theme) 40%, transparent);
  border-radius: 999px;
  padding: 0 7px;
  font-size: 11.5px;
  white-space: nowrap;
}

.place-line .linkish {
  margin-left: 2px;
}

/* 🔴 THE WAY OUT OF EVERYTHING SITS AT THE FAR END OF THE SAME ROW. George, 22 Sep 2026:
 * *"last item, right align a link on the row ... called delete my data"*. `margin-left:
 * auto` in a flex row takes all the space nobody else claimed, so it lands on the far
 * edge — and because the row already carries `flex-wrap: wrap`, on a narrow screen it
 * drops to a line of its own and is still right-aligned rather than crowding the location
 * it sits beside. It is quieter than "change location" on purpose: it is the last resort
 * of the row, not the thing to press first. */
.place-line .linkish.place-forget {
  margin-left: auto;
  color: var(--muted);
}

.place-line .linkish.place-forget:hover,
.place-line .linkish.place-forget:focus-visible {
  color: var(--theme);
}

/* ------------------------------------------------------ the photograph --- */
/* 🔴 A REAL PHOTOGRAPH, FROM A FREE SOURCE, WITH ITS CREDIT ON THE ROW. George,
 * 20 Sep 2026: *"where are the photos. there are free opensource photos online"*.
 * Wikimedia Commons, fetched through this site's own server so the image host
 * never sees the reader. The credit sits on the row rather than only in a tooltip,
 * because every licence these files carry requires the attribution to be visible —
 * an uncredited photograph is a licence breach, not a missing nicety. */
.typerow-photo {
  width: 76px;
  height: 48px;
  object-fit: cover;
  border-radius: 7px;
  border: 1px solid var(--line);
  background: rgba(4, 16, 26, 0.6);
}

.typerow-thumb {
  grid-column: 1;
  grid-row: 1;
  line-height: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE TYPE LIST IS HEIGHT-RESTRICTED. George, 20 Sep 2026: *"Pick an aircraft
 * type needs to be hight restricted, i hate scrolling that far down"*. Sixty-two
 * rows of aeroplanes is a very long page, and everything after them — what you
 * are watching, the board, the live view — was pushed past the fold by a list the
 * reader had not finished reading.
 *
 * So the list scrolls inside its own box and the page stays a page. The filter
 * chips sit ABOVE it and never move, so the reader can always change their mind
 * without scrolling back.
 * ══════════════════════════════════════════════════════════════════════════ */
.typelist {
  max-height: 680px;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Reserved, so the rows do not shift sideways when the bar appears. */
  scrollbar-gutter: stable;
  padding-right: 8px;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 8px;
  background: rgba(4, 16, 26, 0.3);
}

@media (max-height: 700px) {
  .typelist {
    max-height: 520px;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE STEP HEADINGS ARE NOT CONTROLS, AND THEY NO LONGER LOOK LIKE ANYTHING YOU CAN PRESS.
 *
 * George, 22 Sep 2026: *"for all cards i dont want the user to collapse and uncollpase, just hide
 * the cards if they havent completed the prequisite steps."* That is already what the page does on
 * its own — `updateSteps` reveals a step only once the one before it is answered — so what was left
 * to remove was the AFFORDANCE: the pointer cursor, the rotating chevron, the 26px of padding
 * reserved for it, and the `.step-folded` rules that hid everything but the heading. A heading that
 * looks pressable and does nothing is worse than a heading that simply reads.
 *
 * The glide on arrival stays (`step-arrive`): that is the gate announcing itself, not the reader
 * tidying up after it.
 * ══════════════════════════════════════════════════════════════════════════ */

/* ------------------------------------------------- the confirmation box --- */
/* 🔴 THIS SITE'S OWN BOX, NOT THE BROWSER'S. George, 22 Sep 2026: *"the delete should not use
 * browser confirm."* A `window.confirm` is drawn by the browser — it wears the browser's name, its
 * buttons are in the browser's order rather than this page's, and it cannot be read as part of the
 * page. `<dialog>` + `showModal()` keeps every guarantee that mattered (nothing happens until an
 * answer, Escape means no, focus stays inside) and lets the box look like the page it belongs to. */
#forgetDialog {
  width: min(520px, calc(100vw - 32px));
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 20px 22px;
  background: var(--panel, #08192a);
  color: var(--ink, #e8f4fc);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

#forgetDialog::backdrop {
  background: rgba(2, 8, 14, 0.68);
  backdrop-filter: blur(2px);
}

#forgetDialog h2 {
  margin: 0 0 10px;
  font-size: 17px;
}

#forgetDialog p {
  margin: 0 0 10px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}

#forgetDialog .confirm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 16px;
}

/* The safe answer is first and it is the quiet one; the destructive answer is last and it carries
 * the only warm colour in the box, because it is the one that cannot be undone. */
#forgetDialog .confirm-actions .ghost {
  order: 1;
}

#forgetDialog .confirm-actions .danger {
  order: 2;
  border: 1px solid rgba(240, 138, 90, 0.55);
  background: rgba(240, 138, 90, 0.14);
  color: #f6b795;
  border-radius: 9px;
  padding: 8px 14px;
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
}

#forgetDialog .confirm-actions .danger:hover,
#forgetDialog .confirm-actions .danger:focus-visible {
  background: rgba(240, 138, 90, 0.24);
}

/* ------------------------------------------------------ the picked airports --- */
/* 🔴 THE ONE AIRPORT YOU PICKED, IN THE ONE COLOUR THAT MEANS YOU PICKED IT. George,
 * 20 Sep 2026: *"selecting an airport should hava star and yellow hue"* — and then
 * *"i should also be able to select multiple airport ... make sure all are viewable
 * in the maps"*. These are drawn last, over every other marker, and the map's zoom
 * is chosen to fit all of them, so no picked airport can be off the view. */
.locmap-airport-chosen {
  fill: #f0be5a;
  stroke: rgba(4, 16, 26, 0.65);
  stroke-width: 1.4;
}

.locmap-label-chosen {
  fill: #f0be5a;
  font-weight: 600;
}

.locmap-line-chosen {
  stroke: rgba(240, 190, 90, 0.5);
  stroke-dasharray: none;
}

/* ------------------------------------------------------------- the year --- */
/* 🔴 A YEAR IS A SMALL QUIET NUMBER, AND IT MUST NOT READ AS A COUNT. Every other
 * number on a type row is how MANY — sightings, airports, operators — so the year
 * gets its own mark in the same gold the page uses for marks, and the sentence
 * behind it (including that it is the TYPE's year and not the airframe's) is on its
 * tooltip rather than in the row. */
/* 🔴 NEUTRAL, NOT GOLD. George, 22 Sep 2026: *"the yellow labels are not good, make them neutral"*.
 * A year is INFORMATION — which type this is, not what the reader has done with it — so it has no
 * business spending the same colour as the star, which is the one mark on this page that gold is for.
 * The tooltip still carries the whole sentence about whose year it is. */
.year-tag {
  display: inline-block;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 11.5px;
  line-height: 1.5;
  cursor: help;
}

/* ------------------------------------------------- what you are watching --- */
/* 🔴 ONE LINE PER CHOICE, STAR FIRST. The panel used to be a row of nested boxes
 * with a "narrow to a tail number" form inside each one. George, 20 Sep 2026:
 * *"what you are watching, only list was was selected above, no more features"*. */
.watch-what {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 2px 4px;
}

.watch-what .mark-star {
  fill: #f0be5a;
  align-self: center;
}

/* ---------------------------------------------------------------------------
 * "SHOW ON MAP" — one switch on each row, which holds the map to that row.
 *
 * George, 22 Sep 2026: *"can we add a swtich called show on map, on change the map is reloaded and
 * rezoomed"*. It sits between the status and the cross, because it is a question about the status
 * beside it — the status says what the row is doing, and this says "and put the map on it".
 *
 * 🔴 THE SWITCH HAS NO WORDS BESIDE IT ANY MORE. George, 22 Sep 2026: *"remove the show on map
 * redundant text"*. Its accessible name is on the input, so a screen reader still hears it, and the
 * `title` on this label still explains it on hover.
 *
 * 🔴 AND THE ROW IS ALWAYS SWITCHABLE — THE OPPOSITE OF WHAT THIS BLOCK USED TO SAY. *Prior wording,
 * preserved and now dead:* *"A ROW WITH NOTHING IN THE AIR HAS A DISABLED SWITCH … The switch is dead
 * rather than absent"*. George retired that on 22 Sep 2026: *"even though the filight may or may not be
 * in the air, i want this to be a boolean input, not a link"* — so nothing renders `disabled` and
 * nothing here styles it. The switch answers with what the map has, whenever it is pressed.
 * ------------------------------------------------------------------------ */
.map-switch {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  white-space: nowrap;
}

/* 🔴 THIS IS A SWITCH, NOT A CHECKBOX — A PILL THAT SLIDES.
 *
 * George, 22 Sep 2026: *"instead of checkbox i want https://ant.design/components/switch"*, then, when
 * asked nothing and told plainly: *"i dont necessarliy want antd, just that type of switch"*.
 *
 * So it is THAT KIND OF CONTROL — a rounded track with a round white handle that travels, the shape
 * everybody recognises as a switch — and Ant Design's own numbers are what it is drawn to: a pill
 * 44 × 22 with a 100px radius and an 18px handle inset 2px that slides on `left 0.2s`.
 *
 * ⚠️ AND NO LIBRARY IS INVOLVED, WHICH IS THE POINT OF SAYING IT THIS WAY. This page is hand-written
 * TypeScript with no framework and no bundler; pulling in React and Ant Design for one control would
 * trade sixty lines of CSS for a build system. The input stays a REAL
 * `<input type="checkbox" role="switch">` — the accessible switch pattern, focusable and toggled by the
 * browser — and the stylesheet draws it. What a reader sees and what a reader does are then both right,
 * and the delegated `change` listener watching `input.map-show` never learns the difference.
 *
 * The OFF track is NOT Ant Design's `rgba(0, 0, 0, 0.25)`: that value is chosen for a white page and
 * disappears against this card. The muted ink at 35% is the same idea — a dim, neutral pill — on the
 * surface this page actually has, and it turns the theme colour when it is on. */
/* 🔴 THE SWITCH WEARS THE COLOUR OF THE SENTENCE BESIDE IT. George, 22 Sep 2026: *"the swtich should
 * conform with ther colors"*. `data-state` is the row's own state kind and these are the same three
 * colours `.watch-list .watch-state` is drawn in — one row, one colour, and the switch is never a
 * control in a palette of its own. */
.map-switch input {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: 44px;
  height: 22px;
  margin: 0;
  border: 0;
  border-radius: 100px;
  background: color-mix(in srgb, var(--muted) 35%, transparent);
  cursor: pointer;
  transition: background-color 0.2s;
}

.map-switch input::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.35);
  transition: left 0.2s;
}

.map-switch input:checked {
  background: var(--theme);
}

/* In the air — the loud green the status column uses. */
.map-switch[data-state='air'] input:checked {
  background: #7ee787;
}

/* Nothing of this row has ever been seen here — the amber the status column uses. */
.map-switch[data-state='never'] input:checked {
  background: #f0be5a;
}

.map-switch input:checked::after {
  left: calc(100% - 20px);
}

/* The keyboard ring, in the same shape as the rest of the page: a soft halo rather than an outline, so a
 * focused switch is visible without a box appearing round the row. */
.map-switch input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--theme) 25%, transparent);
}

/* ⚠️ THERE IS NO DISABLED STATE, AND THAT IS THE POINT. A switch that cannot be turned on is a link with a
 * hidden precondition — George asked for a boolean — so nothing renders `disabled` and nothing styles it.
 * The empty case is said in the map caption instead, where the reader is already looking. */

/* ---------------------------------------------------------------------------
 * The count that ends the filter note.
 *
 * 🔴 ON ITS OWN LINE, AND HIGHLIGHTED. George, 21 Sep 2026: *"and Showing 32 of 143 types.
 * should be on a new line and highlighted"*. It was the tail of a long grey paragraph, so
 * the one number the reader actually wants was the least visible thing on the card. It is a
 * block so it cannot run into the sentence before it, and it takes the theme colour because
 * a count is a result, not a caveat.
 * ------------------------------------------------------------------------ */
.filter-count {
  display: block;
  margin-top: 4px;
  color: var(--theme);
  font-weight: 600;
}

/* 🔴 `#liveAsk` IS DELETED WITH THE CARD THAT HELD IT. It carried the sentence "nothing is picked yet,
 * so there is nothing to list", which existed because the card printed an empty table under it. Neither
 * the card nor the table is there any more — see the note in index.html — so the sentence had nothing
 * left to explain. The alert block it used to be hidden alongside is still gated, by
 * `syncAlertVisibility`. */

/* The folded state must not be re-opened behind the reader's back by the gate,
 * and `[hidden]` still wins over it, because a step that is not yet unlocked is
 * not on the page at all. */
.step-gated[hidden] {
  display: none !important;
}

/* ---------------------------------------------------------------------------
 * "What you are watching" — one row per thing picked, each with its way out.
 *
 * 🔴 THE LIST HAD NO RULES AT ALL, because it had no markup: `#watchList` and
 * `#watchForm` were deleted from the page while `renderWatchlist()` and
 * `bindWatchForm()` stayed in app.ts reaching for them. `.watch-type`,
 * `.watch-what` and `.year-tag` survived in here from before, which is how a
 * deleted feature leaves a footprint — so the list is styled deliberately rather
 * than being left to the browser's defaults for a `<ul>`.
 * ------------------------------------------------------------------------ */
.watch-list {
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
}

.watch-list > li {
  display: flex;
  /* 🔴 ON ONE CENTRE LINE, NOT ON ONE BASELINE. George, 22 Sep 2026: *"in the air messages should be
   * vertically alinged with the switch"*. `baseline` lines text up with text, and a switch has no text
   * baseline — so the two controls beside the sentence sat at slightly different heights and the row read
   * as three things rather than one. Everything in the row shares the middle now. */
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  /* 🔴 THE LEFT AND RIGHT MARGINS INSIDE THE CELL. George, 21 Sep 2026: *"the spacing inside
   * each cell is needs fixing the left and right margins"*. The rows had vertical padding only,
   * so every line of text sat hard against the card's edge and read as one block rather than as
   * rows. The inset is what makes each row a row. */
  padding: 9px 12px;
  border-top: 1px solid var(--line);
  line-height: 1.5;
}

.watch-list > li:first-child {
  border-top: 0;
}

/* The star marks a row as watched, the same glyph the type row uses — one symbol
 * for one idea, drawn in one place. */
.watch-list .mark-star {
  fill: #f0be5a;
  flex: 0 0 auto;
}

.watch-list .watch-what {
  flex: 1 1 auto;
}

/*
 * WHAT THE WATCHED THING IS DOING, IN THE COLUMN THE WORDS "STOP WATCHING" USED TO HOLD.
 *
 * It is pushed to the right edge so every row's status lines up in one column, and it never
 * wraps: a status that broke across two lines would make the rows different heights and undo
 * the alignment it exists for.
 */
.watch-list .watch-state {
  flex: 0 0 auto;
  margin-left: auto;
  font-size: 13px;
  color: #94a3b8;
  white-space: nowrap;
}

/* In the air is the answer the reader came for, so it is the one that is allowed to be loud. */
.watch-list .watch-state[data-state='air'] {
  color: #7ee787;
  font-weight: 600;
}

.watch-list .watch-state[data-state='never'] {
  color: #f0be5a;
}

/* ---------------------------------------------------------------------------
 * THE TAIL NUMBERS OF A WATCHED TYPE, LISTED UNDER THE TYPE THEY BELONG TO.
 *
 * 🔴 THE ROW USED TO ANSWER "every one of them", WHICH NAMES NO AEROPLANE. George,
 * 22 Sep 2026: *"list available tails below them, the ones that are favourited. if it
 * every one of them, list all tails. dont say everyone of them."* So the list is the
 * answer, and the yellow is the mark that says which of them are being watched — the
 * same colour the chips in the card use, because one colour has to mean one thing.
 *
 * It takes a line of its own (`flex: 1 1 100%`) below the star, the status and the way
 * out, so every watched type reads the same way down the card.
 * ------------------------------------------------------------------------ */
.watch-list .watch-tails {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 2px;
}

/* A chip here is a statement of what is watched, not a control — pressing it is done
 * in the card in step 3, which is the one place a choice is made. */
.watch-list .watch-tails .tail-chip {
  cursor: default;
}

/* ---------------------------------------------------------------------------
 * 🔴 NEUTRAL, UNLESS IT IS FLYING.
 *
 * George, 22 Sep 2026: *"the yellow labels are not good, make them neutral, and make green hue only if
 * the tail is in the air"*. The gold had stopped saying anything: on a whole-type rule EVERY chip was
 * gold, because a whole-type rule watches every tail under it — a row of identical lights that told
 * the reader nothing, and that made the two chips which did matter, the flying ones, the hardest things
 * on the row to pick out.
 *
 * So there is no gold here at all now. A chip is a LABEL: neutral by default, dimmed when it is not
 * one of the aeroplanes being watched, and green — the same green as "in the air" elsewhere on this
 * page — when the feed is reporting that tail airborne at this moment.
 * ------------------------------------------------------------------------ */
.watch-list .watch-tails .tail-chip.tail-air {
  border-color: #22c55e;
  background: rgba(34, 197, 94, 0.16);
  color: #7ee787;
}

/* Not on the list. Still neutral — no colour is spent on it, only weight. */
.watch-list .watch-tails .tail-chip.tail-off {
  opacity: 0.5;
}

/* ---------------------------------------------------------------------------
 * THE ONES THAT CANNOT BE NAMED.
 *
 * 🔴 George, 22 Sep 2026: *"you sday in the air but shouldnt at lease on tail be highlighed in the
 * same green?"* — and the honest answer is that one of them cannot be: many transponders never send a
 * registration, so `+1 unidentified` is the aircraft that is up there and cannot be matched to a tail
 * number. It is deliberately NOT green — green means "this tail is flying" and there is no tail to
 * say it about — and it is deliberately not gold, because nothing here is a mark the reader made.
 * ------------------------------------------------------------------------ */
.watch-list .watch-tails .tail-chip.tail-quiet {
  border-style: dashed;
  color: var(--muted);
  opacity: 0.85;
}

/* The way out is a small cross ICON at the end of the row, after the status. George, 22 Sep 2026:
 * *"use an icon instead for the ✕"* — the glyph was a font's character, so its weight and its width
 * were whatever the installed font decided. Drawn here instead, in the same 24-unit box as the star
 * and the aeroplane, and stroked in `currentColor` so it still takes the colour of the row it is on. */
.watch-list .type-remove,
.watch-list .watch-remove {
  flex: 0 0 auto;
  margin-left: 0;
  padding: 0 2px;
  line-height: 1;
}

.mark-cross {
  width: 11px;
  height: 11px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  vertical-align: -1px;
}

/*
 * The aircraft the reader is watching, plotted on the map in step 4.
 *
 * 🔴 A SHAPE OF AN AEROPLANE, NOT A DOT. George, 21 Sep 2026: *"in the map, i want to see the
 * images of the plane and the aircraft type, then the tail"*. A dot says "something is here";
 * the drawing says what kind of thing it is before the label is read. Stroked rather than flat
 * so it stays legible over map tiles of any colour.
 */
/* 🔴 THE PRESS AREA OF AN AEROPLANE ON THE MAP. The shape is about 14 pixels across, so the drawing itself
 * is a hard thing to hit with a mouse and an impossible one with a thumb — and the press would silently do
 * nothing, which reads as a broken page rather than a missed click. This transparent circle is twice that
 * wide and sits under the icon inside the same group, so a press anywhere near the aeroplane counts as a
 * press on it. `fill: transparent` still receives pointer events; `fill: none` does NOT, which is the trap
 * that makes a shape look pressable and be unpressable. */
.locmap-plane-hit {
  fill: transparent;
  pointer-events: all;
}

.locmap-plane-icon {
  fill: #38bdf8;
  stroke: rgba(2, 6, 23, 0.9);
  stroke-width: 1.4;
  stroke-linejoin: round;
}

/* The icon and the label are drawn over the press area, so they can be over the pointer without stealing
 * the press — the group still answers for both. */
.locmap-plane-icon,
.locmap-plane-label {
  pointer-events: none;
}
/* …except the label, which is pressable in its own right. */
.locmap-plane-label[data-hex] {
  pointer-events: all;
}

/**
 * The path an aircraft has flown, drawn under the aeroplane and the markers.
 *
 * 🔴 GREEN, AND IT IS GREEN ON PURPOSE. George, 22 Sep 2026: *"can you make the airplane trails a
 * hue of green"*. The aeroplanes themselves are the site's blue, so a path in the same colour read as
 * part of the mark rather than as where the aircraft had been; a green trace separates the two at a
 * glance without adding a legend.
 *
 * Deliberately quieter than the aircraft, but not as quiet as it first was: at 1.5px and half opacity
 * the line vanished against the live street map — measured on the real page on 22 Sep 2026, and a
 * feature the reader cannot see is not a feature. The dark drop-shadow is what makes it read over
 * both the pale land and the pale water; without it the line disappears wherever the map is light.
 */
/* ---------------------------------------------------------------------------
 * THE FLIGHT PATH, AND WHAT ITS COLOUR MEANS.
 *
 * 🔴 IT WAS ONE COLOUR, SO IT SAID NOTHING. A climb, a cruise and a descent were drawn
 * identically, and the line answered the one question a flight path is drawn to answer with a
 * line that carried no answer. George, 22 Sep 2026: *"did you implement the gradient trail on
 * the map to indicate that it is climbing or otherwise, with a colour hue to indicate that, with
 * legend"*. It is one line per segment now, each carrying the class of what the aircraft did
 * between its two ends — so the drawing itself is the gradient.
 *
 * The three hues are not invented for the map, which is why these three were chosen: green is the
 * "in the air" colour, amber is the gold of the reader's own marks, and BLUE is the page's own theme
 * colour.
 *
 * 🔴 LEVEL WAS GREY AND GREY COULD NOT BE SEEN. George, 22 Sep 2026: *"level show be blue the color
 * your are using is hard to see on the map."* He is right, and it was a fault in the DRAWING rather
 * than in the palette: a grey line over OpenStreetMap's grey roads, grey buildings and grey land is
 * a line a reader cannot follow — and the tiles are chosen by somebody else, so the trail has to
 * carry its own contrast. Blue is that contrast, and it is also the page's own colour.
 * ------------------------------------------------------------------------ */
.locmap-trail {
  stroke-width: 2.6;
  stroke-linejoin: round;
  stroke-linecap: round;
  opacity: 0.9;
  filter: drop-shadow(0 0 1.2px rgba(2, 6, 23, 0.9));
}

.locmap-trail-climb {
  stroke: #22c55e;
}

.locmap-trail-level {
  stroke: #3b82f6;
}

.locmap-trail-descend {
  stroke: #f0be5a;
}

/* Dashed, because this is not a colour that means "level" — it means nobody reported a height,
 * and the two must not be painted the same way or the key would be lying. It is the same blue as
 * level rather than the grey it started as: a hue that cannot be seen on the map cannot say anything
 * on the map, and the dash is what carries the difference. */
.locmap-trail-unknown {
  stroke: #3b82f6;
  stroke-dasharray: 5 5;
  opacity: 0.55;
}

/* ---------------------------------------------------------------------------
 * THE KEY UNDER THE MAP. A colour with no key is a code nobody can read — George asked for the
 * hue and the legend in one sentence, and one without the other is worse than neither, because a
 * reader who cannot tell green from amber will invent a meaning for it.
 * ------------------------------------------------------------------------ */
.locmap-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 9px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}

.locmap-legend .legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.locmap-legend .legend-line {
  display: inline-block;
  width: 22px;
  height: 0;
  border-top: 3px solid currentColor;
  border-radius: 2px;
}

.locmap-legend .legend-climb {
  color: #22c55e;
}

.locmap-legend .legend-level {
  color: #3b82f6;
}

.locmap-legend .legend-descend {
  color: #f0be5a;
}

.locmap-legend .legend-unknown {
  color: #3b82f6;
  border-top-style: dashed;
  opacity: 0.7;
}

/* "B38M · C-GXXX" — the type first, then the tail, both in the mono face the rest of the
 * codes on this page use. Stroked so the text survives being drawn over a busy tile. */
.locmap-plane-label {
  fill: #e2e8f0;
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  paint-order: stroke;
  stroke: rgba(2, 6, 23, 0.85);
  stroke-width: 3;
  stroke-linejoin: round;
}

/* ---------------------------------------------------------------------------
 * 🔴 THE HISTORIC PANEL'S RULES WERE DELETED WITH THE PANEL, NOT LEFT BEHIND. George, 22 Sep
 * 2026: *"remove this section, if these plans show up then they show up"*. `.historic`,
 * `.historic-head`, `.historic-days` and `.historic-gone` went with it — a stylesheet that still
 * draws a removed section is how a deleted feature keeps looking alive to the next reader.
 * ------------------------------------------------------------------------ */
