/* The Japanese Empire — map practice
   Layout is a fixed app shell: a control bar on top, the map filling the rest.
   Nothing scrolls except the dialogs, so the map never runs away on a phone. */

:root {
  --bg: #f4f1ea;
  --ink: #23201b;
  --muted: #6b6459;
  --line: #cec7b8;
  --panel: #fffdf8;
  --air-halo: #ffffff;
  --air-time: #4d7086;
  --accent: #9a1813;
  --accent-soft: #f6e2df;
  /* Checkboxes and radios take the map's own red rather than the browser's
     blue-or-green default: a green radio beside a red checkbox is two colours
     doing one job. */
  --on-fill: #e9e5da;
  --on-ink: #23201b;
  --hover-fill: #efece3;

  --ocean: #cadfeb;
  --inactive: #ded8cb;
  --inactive-line: #b9b1a1;

  /* the map stripped to one colour, for drawing over: see `#jmap.mono` */
  /* Land at one colour has to read as *land* against `--ocean` (#cadfeb),
     which is a light blue. #e4e0d6 was the first try and sits at almost the
     same lightness as the sea: the coastline was the only thing separating
     them, and at a glance the map was one field of pale. This is warmer and a
     step darker — still light enough to draw over in any colour, and no longer
     mistakable for water. */
  --mono-land: #ded7c4;
  --mono-line: #a9a08b;
  --mono-sub: #c7bfa9;   /* the sub-unit rings: softer, and there are many */

  /* The pieces of the map that are not the argument but still have to hold on
     both grounds: the station squares, the graticule, the province and feature
     names, and the 1942 perimeter. They were seven scattered
     `prefers-color-scheme` blocks patching rules one at a time — the pattern
     the note under `dark mode` says is the wrong one, and the reason the
     annotation buttons once came out black on black. They are tokens now, so
     the whole of the difference between the two schemes is one list. */
  --sta-fill: #fffdf8;      /* a station square */
  --sta-line: #4a3b2a;
  --sta-ink: #4a453c;       /* and its name */
  --grat-line: rgba(40, 60, 80, .20);
  --grat-halo: #fffdf8;
  --grat-ink: #6a7a88;
  --sub-ink: #4a453c;       /* a province name */
  /* A feature name has to hold over two very different grounds — the sea and
     the pale yellow of China — so the halo goes the opposite way from the
     fill, and in dark it has to be heavier as well as darker. Reported
     against the Taklamakan, which is written across the palest ground on the
     map. */
  --f-sea-ink: #4a6b83;
  --f-sea-halo: rgba(255, 255, 255, .55);
  --f-land-ink: #6b5a45;
  --f-land-halo: rgba(255, 255, 255, .5);
  --f-halo-w: 2.4px;
  --extent-ink: #8c3a44;    /* Gordon's 1942 perimeter */
  --extent-op: .55;
  --warn-ink: #8c2f39;      /* a stale version, a bad field */

  --c-metropole: #9a1813;
  --c-colony: #c2463d;
  --c-occupied: #fb8072;
  --c-neighbor: #bfcedd;
  --c-city: #1f3d5c;
  --c-battle: #8e1f57;

  --bar-h: auto;
  --shadow: 0 2px 10px rgba(35, 32, 27, .16);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
}

/* ------------------------------------------------------------ dark mode --

   The map's own colours do not change. They are the argument — Japan's red,
   the Republic's yellow, the occupied salmon — and a palette that inverted
   them would be inventing history to suit a display preference. The *ocean* is
   darkened, because a bright blue field behind a dark page is a lamp, and the
   neutral land tokens with it so that a coastline still reads.

   Everything else is chrome: the bar, the panels, the dialogs, the buttons.
   Those follow the reader's setting, and they follow it by moving the tokens
   rather than by patching rules one at a time — which is what the scattered
   dark blocks in this file were doing, and why the annotation buttons ended up
   black on black. */
/* Three settings, not two. The reader's system is asked first and followed by
   default; the toggle at the foot of the Layers pane overrules it either way,
   and writes `data-theme` on the root element to say so. Absent, the media
   query decides — which is also what a reader with no JavaScript gets.

   The same declarations are written twice because CSS cannot say "this media
   query *or* this attribute" in one rule. The automatic case has to exclude a
   reader who asked for light, hence the `:not`. `tools/test/theme.js` compares
   the two lists character for character and fails if they drift. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #11161c;
    --ink: #e6e9ec;
    --muted: #9aa4ae;
    --line: #333c46;
    --panel: #1b232b;
  --air-halo: #f2f4f7;
  --air-time: #8fb6cd;
    --accent: #d9564e;
    --accent-soft: #3a2320;

    --ocean: #1d3d4f;
    --inactive: #2c3540;
    --inactive-line: #47525f;

    --mono-land: #2b333c;
    --mono-line: #55606c;
    --mono-sub: #3d4954;

    --shadow: 0 2px 12px rgba(0, 0, 0, .55);

    /* The pressed and hovered states. On a light page a button turns pale grey
       when it is on and the dark text still reads; inverted, a pale grey fill
       under near-white text is the "too little contrast" that was reported.
       These two are what the toggles and the hover use, and in dark they go
       the other way — a lighter fill with *dark* text. */
    --on-fill: #e6e9ec;
    --on-ink: #12181e;
    --hover-fill: #2a343f;

    /* and the map's own furniture, which the light list above sets out */
    --sta-fill: #20242a;
    --sta-line: #c6ccd3;
    --sta-ink: #c6ccd3;
    --grat-line: rgba(210, 225, 240, .22);
    --grat-halo: #10161c;
    --grat-ink: #93a6b6;
    --sub-ink: #b9bfc6;
    --f-sea-ink: #a8c6dc;
    --f-sea-halo: rgba(6, 12, 18, .78);
    --f-land-ink: #d8c49f;
    --f-land-halo: rgba(6, 12, 18, .78);
    --f-halo-w: 3.4px;
    --extent-ink: #e0808a;
    --extent-op: .85;
    --warn-ink: #e08b95;
  }
}
:root[data-theme="dark"] {
  --bg: #11161c;
  --ink: #e6e9ec;
  --muted: #9aa4ae;
  --line: #333c46;
  --panel: #1b232b;
  --air-halo: #f2f4f7;
  --air-time: #8fb6cd;
  --accent: #d9564e;
  --accent-soft: #3a2320;

  --ocean: #1d3d4f;
  --inactive: #2c3540;
  --inactive-line: #47525f;

  --mono-land: #2b333c;
  --mono-line: #55606c;
  --mono-sub: #3d4954;

  --shadow: 0 2px 12px rgba(0, 0, 0, .55);

  /* The pressed and hovered states. On a light page a button turns pale grey
     when it is on and the dark text still reads; inverted, a pale grey fill
     under near-white text is the "too little contrast" that was reported.
     These two are what the toggles and the hover use, and in dark they go
     the other way — a lighter fill with *dark* text. */
  --on-fill: #e6e9ec;
  --on-ink: #12181e;
  --hover-fill: #2a343f;

  /* and the map's own furniture, which the light list above sets out */
  --sta-fill: #20242a;
  --sta-line: #c6ccd3;
  --sta-ink: #c6ccd3;
  --grat-line: rgba(210, 225, 240, .22);
  --grat-halo: #10161c;
  --grat-ink: #93a6b6;
  --sub-ink: #b9bfc6;
  --f-sea-ink: #a8c6dc;
  --f-sea-halo: rgba(6, 12, 18, .78);
  --f-land-ink: #d8c49f;
  --f-land-halo: rgba(6, 12, 18, .78);
  --f-halo-w: 3.4px;
  --extent-ink: #e0808a;
  --extent-op: .85;
  --warn-ink: #e08b95;
}

/* The page behind the map, and the bands above and below a letterboxed one.
   `body` and `#stage` already take `--bg`; the root element did not, and on a
   dark page that showed as a white margin. Unconditional now — in the light
   scheme it paints the colour body paints anyway. */
html.app { background: var(--bg); }

* { box-sizing: border-box; }

html, body { margin: 0; }

/* The map is an app shell and must never scroll: a drag on it would otherwise
   pull the page about under the pointer. That is a fact about the map, though,
   and not about this stylesheet — `sources.html` uses the same file and is an
   ordinary document that has to scroll like one. It was inheriting the lock,
   and undoing it from the far end is fragile: the override it had used
   `overflow: auto`, which made `body` a scroll container with nothing in it to
   scroll, and `overscroll-behavior: none` then stopped the wheel chaining up
   to the viewport, so the wheel did nothing at all.

   Scoped to the page that wants it instead. `index.html` carries
   `<html class="app">`; anything else that links this stylesheet scrolls. */
html.app, html.app body {
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  -webkit-text-size-adjust: 100%;
}

/* ---------------------------------------------------------------- bar --- */

#bar {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  padding: 8px 12px;
  padding-top: max(8px, env(safe-area-inset-top, 0px));
  padding-left: max(12px, var(--safe-l));
  padding-right: max(12px, var(--safe-r));
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow);
  z-index: 20;
}

#bar h1 {
  margin: 0 6px 0 0;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: .01em;
  white-space: nowrap;
}

/* The title is the way back to the map as it opens. A reader who has followed
   a link into one corner, or who has zoomed somewhere and lost the thread, has
   otherwise to edit the address bar: the bbox and the layer code stay in it
   through everything else. Clicking here drops them. */
#bar h1 a {
  color: inherit;
  text-decoration: none;
  border-radius: 6px;
  padding: 2px 4px;
  margin: -2px -4px;
}

#bar h1 a:hover { background: var(--hover-fill); }
#bar h1 a:focus-visible { outline: 2px solid var(--line); outline-offset: 1px; }

#bar .spacer { flex: 1 1 auto; }

.seg {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  background: var(--panel);
}

/* `display` here beats the browser's own rule for the attribute, so a hidden
   segmented control would otherwise stay on screen. The level control in the
   Layers panel is hidden that way. */
.seg[hidden] { display: none; }

.seg button {
  appearance: none;
  border: 0;
  border-left: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  padding: 7px 12px;
  min-height: 38px;
  cursor: pointer;
  white-space: nowrap;
}

.seg button:first-child { border-left: 0; }
.seg button:hover { background: var(--hover-fill); color: var(--ink); }

.seg button.on {
  background: var(--accent);
  color: #fff;
  font-weight: 600;
}

button.plain {
  appearance: none;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  padding: 7px 12px;
  min-height: 38px;
  cursor: pointer;
}

/* Every chrome button's hover comes from one token. `#f2ede2` is a pale
   cream, which under a dark palette is a white slab — Layers, About, and the
   two annotation buttons all went white on hover, which is how it was
   reported. There is no hard-coded hover colour left in this file. */
button.plain:hover { background: var(--hover-fill); }

/* THE HEADER'S OWN BUTTONS, A SIZE DOWN.
   Asked for: smaller type, less padding inside each button, less space
   between them. Scoped to `#bar` rather than changed on `.seg`/`.plain`
   themselves, because those two carry the Layers panel's controls as well and
   the panel was not what was crowded.

   The height goes with them, or the padding would have nothing to do: a
   `min-height` of 38 was setting the height on its own and the padding never
   reached it. On a touch screen it stops at 40 rather than following the rest
   down — the finger rule elsewhere in this file is 44, and this is the one
   place it is bent, for a bar of seven controls across a phone. 40 css px is
   still over a centimetre of glass. */
#bar .seg button,
#bar button.plain {
  font-size: 13px;
  padding: 4px 9px;
  min-height: 31px;
}
#bar { gap: 6px 7px; }

@media (pointer: coarse) {
  #bar .seg button, #bar button.plain { min-height: 40px; }
}

/* Two labels per button: the words on a screen with room for them, a single
   character when the header would otherwise wrap into four rows and take a
   quarter of a phone. */
.seg button .narrow { display: none; }

/* A finger is not a mouse pointer. Anything below about 44px is a miss
   waiting to happen, so the controls grow on a touch screen. */
@media (pointer: coarse) {
  .seg button, button.plain { min-height: 44px; }
  /* the layer switches carry a mark rather than a word, so they need the
     width stated: 13px of glyph and 10px of padding is 33, and a fingertip
     is 44 */
  #layer-seg button { min-width: 44px; }
  dialog label.row { min-height: 44px; align-items: center; }
  #quiz button { min-height: 44px; }
  #legend .legend-head { min-height: 44px; }
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* -------------------------------------------------------------- stage --- */

/* The stage is what shows beyond the edge of the drawing now that the map can
   be pushed past it. It used to be painted the ocean colour, which made the
   sea look as though it simply ran out; it is the page instead, so the frame
   round the drawing reads as the map's edge. */
#stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: var(--bg);
}

/* Below the sidebar breakpoint the panels float over the map; the wrapper
   gets out of the way so their absolute positioning still resolves to
   #stage. */
#side { display: contents; }

#map-container {
  position: absolute;
  inset: 0;
  touch-action: none;          /* we drive pan and zoom ourselves */
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

#map-container.dragging { cursor: grabbing; }
#map-container.quizzing { cursor: crosshair; }

/* Shift and drag to draw the ground you want on the screen. The cursor says so
   while the key is down, which is the only announcement the gesture gets. */
#map-container.marking { cursor: crosshair; }

#marquee {
  position: absolute;
  pointer-events: none;
  border: 1.5px dashed #7a1020;
  background: rgba(122, 16, 32, .08);
  border-radius: 2px;
  z-index: 3;
}

#map-svg {
  position: absolute;
  inset: 0;
}

#map-container svg {
  display: block;
  width: 100%;
  height: 100%;
}

#loading, .load-error {
  margin: 0;
  padding: 24px;
  color: var(--muted);
}

.load-error code {
  display: inline-block;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 2px 6px;
  font-size: 13px;
}

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

#ocean { fill: var(--ocean); }

/* Atoms are painted fill *and* stroke in the same colour. Two atoms of one
   territory then show no boundary at all — which is the point: there was no
   line between North and South Korea in 1942, none between India and Pakistan
   under the Raj, and none along the Mekong in French Indochina. The stroke
   also closes the anti-aliasing hairline where two polygons abut. */
.atom {
  /* `--lit` is how the hover lift arrives — see the .hot rules. A variable
     rather than a fill on `.atom.hot`, because a fill rule replaces every
     special fill outranked by it: the first cut of the fill-based lift
     painted the South Seas Mandate's 8% wash solid colony-red. Shapes that
     declare their own fill never read this and are untouched, which is the
     behaviour the old filter gave them for free. */
  fill: var(--lit, var(--c, var(--inactive)));
  /* The stroke is switched on by `#jmap.hairline`, from the Layers panel; see
     the block below the backings for why it is off to begin with. */
  stroke: none;
  vector-effect: non-scaling-stroke;
  /* wide enough to close the hairline cracks that open between provinces
     drawn from adjacent source polygons that no longer share an edge once
     they have been simplified */
  stroke-width: 1.3;
  /* A round join builds an arc — four to eight segments — at every vertex, and
     the three rules that carry this one cover 161,408 stroked vertices between
     them: a coastline is nothing but joins. Mitred, each is one segment. At
     1.3px the join is sub-pixel either way, and the miterlimit sends anything
     sharp enough to throw a spike back to a bevel, which is sub-pixel too.
     Measured at 62% and 74% of the frame it replaces. The dashes, the selection
     outlines and the text halos keep their round joins: they are wide enough,
     or spiky enough, for the difference to show. */
  stroke-linejoin: miter;
  stroke-miterlimit: 2;
  pointer-events: auto;
  cursor: pointer;
}

/* Two neighbours that share a fill, told apart by a hairline rather than by a
   change of colour: Tannu Tuva inside Mongolia, Burma inside British India. */
/* A hairline that separates two territories sharing a fill. It is a whisper,
   not a border: 1.4 is enough to read at any zoom, and 2.2 was drawing
   attention to the Burma frontier as though it were the subject. */
#sub-outlines .edge-line {
  fill: none;
  stroke: var(--edge);
  stroke-width: var(--edge-w, 1.4);
  vector-effect: non-scaling-stroke;
}

/* Province boundaries lifted clear of a layer drawn over them — the princely
   states over British India. Copies of the same paths, stroked with the same
   whisper and filled with nothing, in a group above all of #land: what the
   reader gets is the division they were shown before the states covered it,
   with the states themselves still on top of the fill where they belong. */
#subs-lift { pointer-events: none; }
#jmap.admin-on .atom.subs.lifted > path[data-prov]:not(.fine) { stroke: none; }
#subs-lift .lift-line {
  fill: none;
  stroke: rgba(18, 15, 10, .62);
  stroke-width: .8;
  vector-effect: non-scaling-stroke;
}

/* The whole-territory shape laid under its sub-units must not take the
   pointer, or a tap in a crack would name no sub-unit at all — but while the
   divisions are still in their own file and unfetched, it is the only thing
   there, so it has to. */
/* The fillers, in a layer under every atom. They take the colour of the
   territory they belong to and never the pointer — a tap in a crack would name
   no sub-unit at all — except where their atom's sub-units are still in the
   other file, when they are the only thing there. */
#backings path {
  fill: var(--c, var(--inactive));
  stroke: none;
  stroke-width: 1.3;
  /* see .atom above */
  stroke-linejoin: miter;
  stroke-miterlimit: 2;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}
/* The seams: strips that fill the crack between two countries drawn from
   different files, under the backings and under every atom. Fill only — a
   stroke on one of these is a line along a frontier that no source drew, and
   they take no pointer, because the country on either side is the answer to
   any question asked here. */
#seams path {
  fill: var(--c, var(--inactive));
  stroke: none;
  pointer-events: none;
}

/* ---- the hairline stroke, on and off -------------------------------------
   Every filled shape used to be painted stroke as well as fill, in its own
   colour, at 1.3px. It is not there to be seen: it closes the anti-aliasing
   hairline that opens between two polygons that abutted in the source and
   stopped sharing an edge when they were simplified — between provinces of one
   country, and along every frontier where the two sides come from different
   files.

   It is also about three quarters of everything the browser rasters. Measured
   with it off: a pan of the 1930 China view costs 24% of what it costs with it
   on, India 17%, the whole empire 27%; on a phone 18%, 14% and 22%. That is the
   difference between a map that slides and a map that stutters, and it is why
   the stroke is off unless the reader asks for it.

   What they get for asking: no hairline gaps, and coasts a touch heavier —
   the stroke straddles the edge, so it also fattened every shape outward by
   about two thirds of a pixel.

   `.whole-edge` and `.coast` — the yellow and salmon halves of China's outline,
   which are `fill: none` and so draw nothing at all without their stroke —
   follow the switch too. They were held back at first on the grounds that their
   stroke is the drawing rather than a repair to it, which was the wrong way to
   see it: China's fill already ends at the coastline, and all these two add is
   the same fattened edge every other country has just given up. Keeping them
   cost 15 to 86 per cent more than dropping them, partly because `.whole-edge`
   carries China's western land frontier as well as its coast, so a view of
   India was paying for it. */
#jmap.hairline .atom,
#jmap.hairline .atom > path,
#jmap.hairline #backings path:not(.nostroke),
#jmap.hairline #land path.coast {
  stroke: var(--c, var(--inactive));
}

/* China's filler is not stroked: its outline is stroked by two paths of its
   own, one yellow and one salmon, so that the occupied shore does not carry a
   thread of Free China's colour into the sea. The total number of stroked
   points is unchanged — the line is divided, not duplicated. */
#backings path.nostroke { stroke: none; }
#backings path.whole-edge { fill: none; }

/* The salmon half of the same line, along the occupied coast. It sits beside
   the occupied zone rather than inside it, so it needs the stroke stating. */
#land path.coast {
  fill: none;
  stroke: none;
  stroke-width: 1.3;
  /* see .atom above */
  stroke-linejoin: miter;
  stroke-miterlimit: 2;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* The filler under a country, switched off. It is the country's own outline
   laid beneath its divisions so that a crack between two of them shows the
   country and not the sea; where the divisions are drawn it is a second copy
   of the same ground, and the map now draws one polygon per country with the
   Administrative layer off and the divisions with it on. `redundant` is set
   from JS, per atom and per state of the administrative file, because whether
   a backing is a second copy is not a fact about the backing: Siam's is the
   whole country until its changwat load and underneath all of them after. */
/* A filler is "redundant" once its atom has divisions of its own — the
   provinces cover the same ground, so drawing both is drawing twice. That is
   true while the divisions are *being shown*, and not otherwise.

   With Administrative off the divisions are still there, still filled, and
   still abutting one another: seventy polygons edge to edge, each antialiased,
   leaving a pale hairline along every shared boundary. Siam, China, the
   Philippines, Japan and Korea all showed their provinces as faint cracks with
   the layer switched off — and the whole-country shape that would have covered
   them was hidden at exactly the moment it was needed, because the arrival of
   the divisions is what marked it redundant.

   So the filler stands down only when the divisions are on show, and the
   divisions stand down when they are not. The reader sees one polygon per
   country either way — which is what the map looked like before the
   administrative file was ever fetched. */
#jmap.backs-off.admin-on #backings path.redundant { display: none; }

#backings path[data-deferred] { pointer-events: auto; }

#backings path.hot { filter: brightness(1.12); }

/* vector-effect does not inherit, so an atom drawn as a group of provinces
   needs it stated on the children too. Without it their stroke grows with the
   zoom and the fill spreads a dozen pixels past the real coastline. */
.atom > path, .atom > circle { vector-effect: non-scaling-stroke; }

/* Highlighting is applied to every atom of a territory at once, from JS. A
   plain :hover would light up only the polygon under the pointer and make the
   seams between atoms of one territory look like real boundaries — China
   would appear to be two countries. */
/* Fills, not filters. Every lift here was `filter: brightness(...)`, and
   Safari does not rasterise CSS filters on SVG elements at all — measured
   27-08-2026 in a driven Safari 18.6: `getComputedStyle` reported the filter
   and not one pixel of a 157x150 province changed. The colour already flows
   through `--c` and SVG fill inherits, so the lift is said as a colour:
   `color-mix` toward white, which every browser this map supports renders,
   and which the compositor also repaints more cheaply than a filter pass.
   The hit overlays are excluded — they are `.atom`s too, and a fill on a
   transparent hit shape would paint a blob over the country. */
.atom.hot:not(.atom-hit) {
  --lit: color-mix(in srgb, var(--c, var(--inactive)) 89%, #fff);
}
/* and the seam strips of a lit atom with it: they carry its colour, so leaving
   them out of the lighting left a band of the unlit colour along its frontier
   — widest on the ceded provinces of Cambodia, where it read as a thick inner
   band inside the outline. They are still outlined by nothing and named by
   nothing; only the brightness follows the atom. */
#seams path.hot { fill: color-mix(in srgb, var(--c, var(--inactive)) 89%, #fff); }
#sub-outlines .edge-line.hot { stroke: color-mix(in srgb, var(--c, var(--inactive)) 89%, #fff); }

/* the province under the pointer, picked out inside the lit country */
/* A child's own fill replaces the inherited one, so these carry the atom's
   lift *and* their own on top — the atom is always hot when they are. */
.atom .prov-hot {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 83%, #fff);
}

/* And the one that was *clicked* stays picked out after the pointer has gone.
   Selecting a province used to leave only the country lit, so a reader with a
   card open about Bagelen could not see which of Java's thirty-eight it was.

   A step short of the hover's lift, so that moving the pointer over a
   neighbour still reads as the nearer thing: the chosen unit holds a quieter
   tint and the hovered one brightens above it. The outline underneath is what
   says *this one* most plainly — the fill only has to keep saying it after
   the pointer leaves.

   It is on the atom's own lit colour, `--c`, for the same reason `prov-hot`
   is: a child's fill replaces the inherited one, and the country is still
   selected whenever one of its units is. */
.atom .prov-sel {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 90%, #fff);
}
/* Both at once — the reader is hovering the unit they already chose — is the
   hover's lift, not a third colour. */
.atom .prov-sel.prov-hot {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 83%, #fff);
}

/* The five protectorates of French Indochina, told apart while the federation
   is under the pointer.
 *
 * Everywhere else on this map a country's divisions are one colour and the one
 * being pointed at lifts. Indochina is not one country: it is a colony and four
 * protectorates federated under a Governor-General, and which of the five a
 * province belonged to is the fact a reader most often wants and the one its
 * name never gives. So with Administrative on, hovering the federation shades
 * all ninety-four divisions by the protectorate each was in, and the division
 * under the pointer still lifts above its own shade.
 *
 * Five steps off the atom's own colour and no further: they have to read as
 * one country in five parts, not as five countries. The three Vietnamese
 * pieces go lighter from north to south — Tonkin, Annam, Cochinchina — and the
 * two inland protectorates go darker, so the grouping the federation actually
 * had is the one the eye picks up first.
 *
 * On the 1930 sheet this covers the 1941 cession too, without naming it here:
 * `siamgain` is one of French Indochina's two atoms on that date, so it is
 * `.hot` with the other, and its six divisions carry the same `data-parent`
 * the ground they were cut from carries. On the 1942 sheet it is Thailand's
 * and is not lit at all, which is the right answer there.
 *
 * The two `:not()`s keep the rules under `prov-hot` and `parent-hot`, which
 * are what say *this one* and *this protectorate*. Without them an id selector
 * and three classes would outrank both and nothing would ever appear to
 * highlight. */
#jmap.admin-on .atom.hot [data-parent="Tonkin"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 88%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Annam"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 92%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Cochinchina"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 97%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Cambodia"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 93%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Laos"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 87%, #2a2118);
}

/* The gouvernements of the Netherlands Indies, told apart the same way.
 *
 * The same argument as Indochina's above, and the same mechanism. This colony
 * had two levels where it had any: fifty of its sixty-five units sat in a
 * gouvernement — Java's three, the two princely lands beside them, Borneo's
 * two afdeelingen, and the Moluccas — and the other fifteen were residencies
 * answering to Batavia with nothing in between. Those fifteen carry no
 * `data-parent` at all, so nothing here reaches them and pointing at
 * Palembang lights Palembang: a grouping is drawn where there was one and not
 * where there was not.
 *
 * Java is where this earns its keep. Thirty-eight of the units are on that one
 * island and at any useful zoom they are a mosaic of same-coloured cells;
 * which of the three gouvernements a residency belonged to is the fact its
 * name never gives. West to east goes lighter — West-Java, Midden-Java,
 * Oost-Java — so the order on the ground is the order in the tone, and the two
 * princely lands go darker instead of lighter because they were not
 * gouvernements of the same kind: Jogjakarta and Soerakarta were ruled by
 * their own houses under Dutch residents.
 *
 * Borneo's two afdeelingen take the same two directions, west light and
 * south-east dark, which is the division the author asked for and the only one
 * that island had.
 *
 * The two `:not()`s keep these under `prov-hot` and `parent-hot` — the classes
 * that say *this one* and *this group* — exactly as Indochina's do.
 *
 * **The steps are spaced off the lit atom's own colour, not off its resting
 * one.** A hovered atom is already lifted by about eleven per cent toward
 * white, so a shade mixed at 88 or 89 per cent comes out to the same three
 * decimal places as the lift itself: measured, `Westerafdeeling van Borneo`
 * at 89% and the hot atom both resolved to srgb 0.993 0.738 0.452, which
 * would have told a reader that west Borneo belonged to no gouvernement at
 * all — the one thing this is here to say. Every white-side step is at least
 * five points clear of it now, and the check is in tools/test/dei.js. */
#jmap.admin-on .atom.hot [data-parent="West-Java"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 78%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Midden-Java"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 84%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Oost-Java"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 95%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Soerakarta"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 92%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Jogjakarta"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 86%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Westerafdeeling van Borneo"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 72%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Zuider- en Oosterafdeeling van Borneo"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 96%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Gouvernement der Molukken"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 89%, #2a2118);
}

/* And on the 1942 sheet the Indies group by the command that held them.
 *
 * The gouvernements above are the Dutch administration and they are the 1930
 * sheet's grouping. By December 1942 the thing a reader wants to see is the
 * occupation's own division of the archipelago, and it is a cleaner one: three
 * commands cover all forty-seven units where eight gouvernements covered fifty
 * of sixty-five. The 17th Army held Java, the 25th held Sumatra, and the Navy
 * held Borneo, Celebes and everything east — which is why the boundary that
 * matters here runs between services and not between residencies.
 *
 * Three steps, and the Navy's is the pale one because it is the largest and a
 * dark wash over half the map would read as a different country rather than a
 * different command. Spaced clear of the lit atom's own colour for the reason
 * written above. */
#jmap.admin-on .atom.hot [data-parent="Java 17th Army"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 82%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Japanese 25th Army"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 94%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Japanese Navy"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 76%, #fff);
}

/* **THE CONTESTED FRONTIER MUST NOT SWALLOW A CLICK MEANT FOR A DISTRICT.**
 *
 * The frontiers nobody agreed on are in `ON_TOP`, so they are drawn over
 * every country they cross — which is right for a statement about a border
 * and wrong for the pointer: the block in northern Burma lies over Myitkyina
 * and The Triangle, and it answered for all three. Reported as the districts
 * being unclickable, which is exactly what it was.
 *
 * Only while the Administrative layer is on, because that is when there is
 * anything underneath worth reaching. With the layer off the block answers
 * for itself, as it should: the fact that the frontier was never settled is
 * the only thing the map has to say about that ground.
 *
 * `pointer-events` rather than a change to the hit testing, because
 * `pick()` walks `document.elementsFromPoint`, which leaves out anything the
 * pointer cannot reach — so the districts come to the front of the stack on
 * their own. The hit circle goes with it, or the overlay would hand the click
 * straight back. */
#jmap.admin-on #a-contested_burma,
#jmap.admin-on #a-contested,
#jmap.admin-on #atom-hits [data-atom="contested_burma"],
#jmap.admin-on #atom-hits [data-atom="contested"] {
  pointer-events: none;
}

/* The theme's answer in the hover, set apart from the place's own names by
   the same weight the `rule` line uses: it is a statement about the ground
   rather than another word for it. */
/* The caveat a reader has to have before the figures mean anything. Weighted
   so it is not skimmed past: it is the difference between "Burma's
   population" and "the population of the part of Burma that was counted". */
.pop-note-top { font-weight: 600; }

#tooltip .theme-cat { font-style: italic; }
/* The same weight for the frontier nobody agreed: both are statements about
   the ground rather than another word for it. */
#tooltip .contested-note { font-style: italic; }

/* **A THEMATIC LAYER: A QUESTION ASKED OF A PLACE, DRAWN OVER IT.**
 *
 * The Administrative layer says what a district was called; a theme says
 * something about it — here, how far the administration actually reached — and
 * the two are meant to be read together. So the categories are laid over the
 * districts at an opacity that leaves the boundaries and the names legible
 * underneath, and they take **no pointer events at all**: the reader is still
 * clicking the district, and the card tells them which category the ground is
 * in. A layer that answered the pointer itself would have taken the map away
 * to tell them about it.
 *
 * 0.55 is the figure. Below about 0.4 the four categories stop being
 * distinguishable from each other over the country's own colour; above 0.7 the
 * district boundaries under them go. */
#thematic {
  pointer-events: none;
}
/* **SOLID, NOT A WASH.** It was 0.55 so the districts underneath stayed
   readable — and the country's own amber then tinted every category, so the
   reader was matching a mixture against a key of pure colours. The fill is
   the category's own now and the boundaries come up over it in `#subs-lift`
   instead, which is the arrangement the princely states already use. */
#thematic path {
  fill-opacity: 1;
  stroke: rgba(20, 16, 10, .55);
  stroke-width: .6;
  vector-effect: non-scaling-stroke;
}

/* Burma's divisions, the Shan federation and the Karenni states.
 *
 * Eighty-five districts on one province of India, and at any useful zoom they
 * are a mosaic of same-coloured cells: which division a district belonged to
 * is the fact its name never gives. Seven Divisions, the Federated Shan
 * States gathered from the five spellings the Gazetteer uses for them, and
 * Karenni — which was never annexed and is kept apart for that reason.
 *
 * The dry zone and the delta go lighter, the hills and the plateau darker, so
 * the tone carries something of the country rather than being an arbitrary
 * ladder: Irrawaddy and Pegu palest, Sagaing and the Shan plateau deepest.
 * Every step is clear of the lit atom's own colour — the Raj's dusty rose sits
 * at 0.264 luminance, so the darker half has room — and clear of each other.
 *
 * The two `:not()`s keep these under `prov-hot` and `parent-hot`, as
 * Indochina's and the Indies' do. */
#jmap.admin-on .atom.hot [data-parent="Irrawaddy Division"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 74%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Pegu Division"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 82%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Tenasserim Division"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 90%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Arakan Division"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 97%, #fff);
}
#jmap.admin-on .atom.hot [data-parent="Magwe Division"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 95%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Mandalay Division"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 89%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Sagaing Division"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 83%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Federated Shan States"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 77%, #2a2118);
}
#jmap.admin-on .atom.hot [data-parent="Karenni States"]:not(.prov-hot):not(.parent-hot) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 70%, #2a2118);
}

/* A unit inside a larger territory that shares its colour — the land ceded to
   Thailand in 1941, the four northern Malay states — outlined so it can be
   seen and clicked without being coloured as if it were something else. The
   line is drawn in a layer of its own and masked to the silhouette, because a
   dashed stroke on the group is inherited by every province inside it and
   draws boundaries where the territory had none. */
#sub-outlines { pointer-events: none; }

#sub-outlines .sub-outline path,
#sub-outlines .sub-outline circle {
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke: var(--sub, rgba(60, 50, 40, .55));
  stroke-width: 2.8;
  stroke-dasharray: 5 3.5;
  stroke-linejoin: round;
}

/* Outlines live above the map. The mask keeps only the half of each stroke
   that falls outside the shape, so the widths here are doubled. */
#highlight { pointer-events: none; }

#highlight g path,
#highlight g circle {
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}

/* The outline is a stroke on the shape with everything inside the shape masked
   away, so what shows is the outer half of it. That makes the width a limit on
   how fine a line it can trace: an inlet narrower than the visible half is
   bridged by the stroke on either side of it and comes out as a straight line
   across the bay. At six pixels the outline of Manchukuo cut every small bay
   off its Soviet frontier and read as a visibly coarser line than the fill it
   was tracing. Three, and it follows the coast.

   The widths carry 1.3 more than the line they draw, because the mask is now
   stroked at 1.3 as well as filled — that is what closes the cracks between
   two atoms of one country, so that hovering China does not rule Manchuria and
   Jehol off inside it — and a wider mask eats half its own width off the
   visible outer half. 3.3 and 3.7 leave 2 and 2.4 showing, which is what these
   were before. Measured: the change moves 4,213 pixels inside China and 3,133
   on its outer edge, and the compensation is for the second of those. */
#highlight .hi-territory path,
#highlight .hi-territory circle {
  stroke: rgba(40, 33, 24, .8);
  stroke-width: 3.3;
}

/* Safari rasterises a mask into a coarser buffer than Chrome does, and the
   outline's design leaves only one pixel of stroke outside the mask's solid
   — measured in a driven Safari 18.6: at 3.3 not one pixel of the outline
   survives, at 5 it draws. `map.js` stamps `.saf` on the root for Apple's
   engine, and the masked strokes take enough extra width there for about
   two pixels of band to clear the buffer. The guard stroke inside the mask
   is unchanged, so the band starts where it always started. */
#jmap.saf #highlight .hi-territory path,
#jmap.saf #highlight .hi-territory circle,
#jmap.saf #highlight .hi-province path,
#jmap.saf #highlight .hi-province circle { stroke-width: 5.4; }
#jmap.saf #highlight .hi-parent path,
#jmap.saf #highlight .hi-parent circle { stroke-width: 6.6; }
#jmap.saf #highlight .hi-selected path,
#jmap.saf #highlight .hi-selected circle { stroke-width: 5.8; }

/* A pinned outline: the one line on this map that is not an answer to where
   the pointer is, so it has to look unlike all of them. Bright yellow where
   every other outline is a dark brown, and 7 against the selection's 3.7 —
   which by the arithmetic above leaves 5.7 showing against 2.4, near enough
   two and a half times the band. The blur is a filter set in `map.js`, whose
   deviation is in user units and is rewritten on every zoom. */
/* The pin. A neon yellow rather than the golden one it was: the pin is the
   map's loudest mark and it should look switched on. The dark casing that
   makes it survive the Republic's pale yellow is in the filter, not here —
   see ensurePinFilter. */
#highlight .hi-pinned path,
#highlight .hi-pinned circle {
  stroke: #eaff00;
  stroke-width: 7;
}
#jmap.saf #highlight .hi-pinned path,
#jmap.saf #highlight .hi-pinned circle { stroke-width: 9.1; }
#jmap.saf #sub-outlines .sub-outline path,
#jmap.saf #sub-outlines .sub-outline circle,
#jmap.saf #sub-outlines .sub-merged path,
#jmap.saf #sub-outlines .sub-merged circle { stroke-width: 4.6; }
/* hi-inner carries hi-province too, so the widening above caught it and a
   deliberate hairline became a band. Last, so it wins the tie. */
#jmap.saf #highlight .hi-inner path,
#jmap.saf #highlight .hi-inner circle { stroke-width: 1.6; }

#highlight .hi-province path,
#highlight .hi-province circle {
  stroke: rgba(30, 24, 16, .7);
  stroke-width: 3.3;
}

/* The division that was *clicked*, traced and left traced.
 *
 * `hi-province` above belongs to the pointer and goes when it leaves; this is
 * the one that stays while the card is open. A little stronger than the hover
 * line and a little lighter than the country's selection line at 3.7, so the
 * three can be told apart where all three are drawn at once: the country
 * round the outside, the chosen division inside it, and the hovered one
 * lighter still. */
#highlight .hi-selprov path,
#highlight .hi-selprov circle {
  stroke: rgba(24, 19, 12, .85);
  stroke-width: 3.5;
}
#jmap.saf #highlight .hi-selprov path,
#jmap.saf #highlight .hi-selprov circle { stroke-width: 5.6; }

/* A prefecture's own shape. It exists to be traced round when one of its
   districts is pointed at, and for nothing else: no fill, no stroke, and out
   of the pointer's way so it cannot swallow a click meant for a district. */
#land .shu {
  fill: none;
  stroke: none;
  pointer-events: none;
}

/* Two levels at once — Taiwan's districts inside their prefectures.
 *
 * A 郡 inside a 州 needs both drawn, and two lines of the same weight read as
 * one confused edge. The prefecture gets the emphatic line; the district's
 * drops to a hairline, because `prov-hot` already lifts its fill and that is
 * what says which district the pointer is on. Everywhere else on the map the
 * ordinary weights above still apply. */
#highlight .hi-parent path,
#highlight .hi-parent circle {
  stroke: rgba(40, 33, 24, .92);
  stroke-width: 4.2;
}
#highlight .hi-inner path,
#highlight .hi-inner circle {
  stroke: rgba(30, 24, 16, .3);
  stroke-width: 1;
}

/* and the prefecture lifts a little, so its reach is visible without reading
   the line. Well under the district's own lift, which has to stay the louder
   of the two: 1.03 against 1.06. */
.atom .parent-hot {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 86%, #fff);
}
.atom .parent-hot.prov-hot {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 80%, #fff);
}

#highlight .hi-selected path,
#highlight .hi-selected circle {
  stroke: #14110c;
  stroke-width: 3.7;
}

.site.sel .dot {
  stroke: #14110c;
  stroke-width: 2.5;
  paint-order: stroke;
}

/* The modern outline of China, drawn beneath the Republican provinces. The two
   sources disagree about the land border by a few kilometres here and there,
   and without this those disagreements open as slivers of ocean at deep zoom. */
/* A neutral land colour, not a country's: where the two sources disagree the
   sliver then reads as a seam rather than as one country leaking into the
   next. */
.chinabase {
  fill: var(--inactive);
  stroke: none;
  pointer-events: none;
}

/* Railways: a tightly dotted line, inked against the ground it crosses —
   white over a dark country, near-black over a pale one. That is the standard
   for every railway this map draws, and it is why no colour is written here:
   `--rail-ink` is set per path by map.js from the *computed* fill of the
   territory named in `data-over`, so it is right in colour, right in mono
   (where the land is one grey, and the reader may have chosen it), and right
   for a country added later without this file having to know about it.

   Non-scaling, so the dots stay the same size and the same distance apart at
   every zoom rather than stretching into dashes — the map-units versus
   screen-pixels rule in CLAUDE.md, checked at three zooms.

   Pointer-none, because the network is context: a reader pointing at Taiwan
   wants the district under it, not the line over it. */
#tw-rail, #kr-rail { pointer-events: none; }

/* A station name: the smallest lettering on the map, and there may be two
   hundred of them at once, so it is lighter than a province's and carries the
   same halo so it survives crossing the line it belongs to. */
/* A station: a small square, drawn at a constant size on screen. Filled pale
   so it reads on the dark red of the colony and on the sea alike, with the
   same brown line the map draws everything else in. The hit target over it is
   twice its size and invisible: five pixels is not a thing anybody can put a
   finger on. */
.sta-layer .sta-sq {
  fill: var(--sta-fill);
  stroke: var(--sta-line);
  stroke-width: 1.1;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}
.sta-layer .sta-hit { fill: transparent; cursor: pointer; }
.sta-layer .sta-mark:hover .sta-sq { fill: #ffd200; }
/* The one the card is open on. A touch screen has no hover to say which square
   was tapped, and a card headed Gāoxióng over a hundred and ninety identical
   squares does not say it either. */
.sta-layer .sta-mark.sel .sta-sq {
  fill: #ffd200;
  /* The brown outline both schemes give it: the selected square is yellow in
     each, and the pale stroke the other squares take in dark would be lost on
     it. Written out rather than inherited, because in dark `--sta-line` is
     the pale one. */
  stroke: #4a3b2a;
  stroke-width: 2;
}

#labels .sta {
  font-weight: 600;
  fill: var(--sta-ink);
  letter-spacing: 0;
  stroke-width: 3px;
}

/* The Layers pane is `#dlg-options` in the markup, not `#dlg-layers`. Every
   rule here was written against the name on the button rather than the id on
   the element, and none of them applied — the sub-row indent had been dead
   since it was written, and the side-by-side railway rows were shipped dead.
   Nothing looked broken because a label with no flex on it still stacks and
   still reads.

   A standing note in the Layers pane: something true of the panel rather than
   of any one switch in it. Set smaller and quieter than a row, so it reads as
   an aside and not as another thing to tick. */
#dlg-options .pane-note {
  margin: .35em 0 .8em;
  font-size: .9em;
  line-height: 1.35;
  color: var(--muted, #6a6a6a);
  max-width: 46em;
}
#dlg-options h3 + .pane-note { margin-top: .2em; }

/* The Layers pane: a railway and the stations that hang off it, side by side
   on one row. The pair reads as one thing that way — the line and the stops on
   it — where two stacked rows read as two unrelated switches, and the panel is
   long enough already. The station half is hidden until its railway is on, and
   the row keeps its height so the ones below it do not jump. */
#dlg-options .row.pair {
  display: flex;
  gap: 1.1em;
  align-items: baseline;
}
#dlg-options .row.pair .half {
  display: flex;
  align-items: baseline;
  gap: .45em;
  flex: 1 1 0;
  min-width: 0;
}
#dlg-options .row.pair .half[hidden] { display: none; }
@media (max-width: 460px) {
  /* Two of these do not fit across a phone; they stack, and the second is
     indented so it still reads as belonging to the first. */
  #dlg-options .row.pair { display: block; }
  #dlg-options .row.pair .half { padding-left: 0; }
  #dlg-options .row.pair .half + .half { padding-left: 1.6em; font-size: .95em; }
}

/* A railway, drawn the way a railway is drawn: a solid line with the ground
   showing through it at intervals. Two strokes on the same path — see the
   clone in map.js — because one stroke cannot be two colours.

   It was a row of dots, 0.01 on 2.7 under a round cap, and it read as a grey
   stipple wherever several lines ran close together, which at the opening view
   is most of the network. A line survives being small in a way a row of dots
   does not.

   Both widths are screen pixels: non-scaling-stroke, so the ties keep their
   spacing at every zoom and the symbol never has to be switched. */
.rail {
  stroke: var(--rail-ink, #2b2b2b);
  /* **A little narrower, and the corners rounded.** At 2.2 the network read as
     heavier than the coastline it runs beside, and a mitred join on a traced
     alignment spikes wherever two surveyed points meet at an angle — which on
     a hand-traced line is often, and read as a jagged edge. 1.9 with a round
     join is the same line without the barbs, and **nothing about the geometry
     changes**: the alignment is the source's and stays the source's. The sugar
     railways keep their own width, being a different kind of line. */
  stroke-width: 1.9;
  stroke-linecap: butt;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* The railway's hit target: invisible, and wide enough for a finger. The drawn
   line is 1.9 screen pixels and nobody can press that; this is 16, which is
   inside the 44 px a touch target wants but is as much as can be given to a
   line that runs beside coastlines and through cities without stealing their
   presses. `pointer-events: stroke` so only the band round the line answers,
   never the area a closed path would enclose. */
.rail-hit {
  fill: none;
  stroke: transparent;
  stroke-width: 16;
  stroke-linecap: butt;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  pointer-events: stroke;
}
/* and the two drawn lines get out of its way */
.rail { pointer-events: none; }

/* **A picked railway lights as one thing.** It is a network, not a shape with
   an inside: the reader pressed the line and what answers is the whole of it,
   so the highlight goes on the group and every path in it thickens together.
   Screen pixels, via non-scaling-stroke, so it reads the same at every zoom —
   the map-units/screen-pixels rule in CLAUDE.md. */
#tw-rail.picked .rail,
#kr-rail.picked .rail,
#kf-rail.picked .rail,
#mn-rail.picked .rail {
  stroke-width: 3.4;
  stroke: var(--pick, #b8860b);
}
#tw-rail.picked .rail-tie,
#kr-rail.picked .rail-tie,
#kf-rail.picked .rail-tie,
#mn-rail.picked .rail-tie {
  stroke-width: 3.4;
}

.rail-tie {
  fill: none;
  stroke: var(--rail-ground, var(--bg));
  /* The ties are the ground showing through the line above, so they follow its
     width exactly: any wider and they eat the rail's edge. */
  stroke-width: 1.9;
  stroke-linecap: butt;
  stroke-linejoin: round;
  /* A HAIRLINE BREAK ON A LONG RUN OF INK, not the other way round. First
     attempt was 1.5 on 3.4, which leaves more break than line once the caps
     are counted, and it read as a dashed line rather than as a railway. One
     pixel of ground every six is a tick on something continuous. */
  stroke-dasharray: 1 5.5;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* The graticule. Faint on purpose: it is a frame of reference and not a thing
   to read, so it should be legible when looked for and invisible otherwise. */
#graticule .grat-line {
  fill: none;
  stroke: var(--grat-line);
  stroke-width: 0.8;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* Over the land now, so the mesh needs to sit on the countries without
   reading as a cage: a shade stronger than it was over water alone, and
   dashed, which the eye takes as a reference line rather than as a border. */
#grat-labels .grat-label {
  pointer-events: none;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .02em;
  paint-order: stroke;
  stroke: var(--grat-halo);
  stroke-width: 3px;
  stroke-linejoin: round;
  fill: var(--grat-ink);
  dominant-baseline: middle;
}

/* Gordon's greatest-extent perimeter */
#extent-1942 {
  fill: none;
  stroke: var(--extent-ink);
  vector-effect: non-scaling-stroke;
  stroke-width: 1.8;
  stroke-dasharray: 9 6;
  stroke-linejoin: round;
  stroke-linecap: round;
  pointer-events: none;
  opacity: var(--extent-op);
}

/* The edge of the drawing. Drawn last so it sits over everything, and never
   takes the pointer: it is a boundary, not a control. */
#frame {
  fill: none;
  stroke: var(--line);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* rivers */
.river {
  fill: none;
  stroke: #4d7f9e;
  vector-effect: non-scaling-stroke;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
  opacity: .8;
}

/* Thinner and paler than the Yangzi and the Yellow River, which are the two
   rivers this map has an argument about. These are context. */
.india-river {
  /* A pale purple out of the British mauve's own family rather than a blue.
     These rivers are almost entirely inside one country, so the useful thing
     is that they read *against* that country's fill, not that they look like
     water in the abstract — and a blue over #b07f8e came out as a slightly
     cooler mauve and read as a texture in the fill. Lightness is what carries
     it: #dcc6e4 against #b07f8e is a contrast ratio of 2.11, where the blue
     managed 1.30. The two Chinese rivers keep their own darker blue, which is
     right — they cross several countries and are an argument the map makes,
     and these are context under one of them. */
  stroke: #dcc6e4;
  stroke-width: 1.1;
  opacity: .95;
}

#legend .sw.river {
  height: 0;
  border: 0;
  border-top: 2.5px solid #4d7f9e;
  border-radius: 0;
  align-self: center;
}


.blabel {
  pointer-events: none;
  text-anchor: middle;
  paint-order: stroke;
  stroke: #fffdf8;
  stroke-width: 3px;
  stroke-linejoin: round;
  fill: #5a544a;
  font-weight: 500;
}

#legend .sw.line {
  height: 0;
  border: 0;
  border-top: 2.5px dashed #7a1020;
  border-radius: 0;
  align-self: center;
}

#legend .legend-src {
  margin: 1px 0 0 21px;
  font-size: 11px;
  font-style: italic;
  color: var(--muted);
}

/* An island too small to see gets a ring around it rather than a blob over it,
   so the real coastline shows through, and the ring disappears as soon as you
   zoom in far enough for the island itself to be visible. The hit circle stays
   either way. */
.islet {
  fill: none;
  stroke: var(--c, var(--inactive));
  vector-effect: non-scaling-stroke;
  stroke-width: 1.1;
}

.islet-hit { fill: transparent; stroke: none; }

/* The Spratlys, the Paracels and Pratas: reefs and sandbanks of a few hundred
   metres, drawn to scale and therefore invisible. At the deepest zoom the map
   allows, the largest of them is still under a pixel — so the reader who
   zoomed in to find the named island finds nothing at all, and the ring that
   pointed the way has gone by then. Their shapes carry a stroke of their own
   colour, one pixel wide at every zoom, which is the smallest thing that can
   be both seen and pointed at. Only these three atoms: the same stroke on the
   Philippines or the Indies would thicken coastlines that read perfectly well
   already. */
.tiny-isles,
.tiny-isles path {
  stroke: var(--c, var(--inactive));
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}

/* A mandate over an ocean is a line on a chart, not a shape: almost everything
   inside these outlines is water, and the islands they are about are specks. So
   they are drawn as a dashed line in the mandatory power's own colour and
   filled with nothing — `transparent` rather than `none`, because the fill is
   what the pointer lands on and a mandate that cannot be pointed at says
   nothing. They sit under every island, so an island inside one answers for
   itself and only open water answers for the mandate.

   Pointed at, the whole region takes the faintest wash of its power's colour —
   8%, which is enough to read as one area at a glance and not enough to change
   the colour of anything drawn over it. */
/* The shape carries the fill that answers the pointer and washes on hover; the
   line itself is drawn by the copy in #mandate-lift, above the land, so that a
   boundary crossing an island is not buried under it. */
.mandate {
  fill: transparent;
  stroke: none;
}

#mandate-lift { pointer-events: none; }

#mandate-lift path {
  fill: none;
  stroke-dasharray: 7 5;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Something for the dash to read against, whatever it crosses. */
#mandate-lift .mandate-casing {
  stroke: rgba(255, 253, 248, .85);
  stroke-width: 3.4;
}

#mandate-lift .mandate-line {
  stroke: var(--c, var(--muted));
  stroke-width: 1.5;
}

/* The wash is set per mandate, not once, because the three colours are nowhere
   near each other in strength. Measured as CIE76 ΔE against the ocean the map
   draws (#cadfeb): Japan's #c2463d at 8% is ΔE 5.19, and the same 8% of
   Australia's #c9a6b0 is only 2.19 — 42% of it — because that colour is close to
   the sea in lightness and barely tinted. Britain's #b07f8e manages 3.19, 62%.
   Equal percentages therefore look nothing like equal.

   The figures below are set from the rendered result rather than the model —
   sampled off a screenshot of the sea inside each mandate, cold and hovered.
   Japan comes out at ΔE 5.54, Australia 4.80 and Britain 4.65, so the two are
   at 87% and 84% of the Japanese wash: clearly visible, still the softer pair,
   which is the order asked for. At 8% each they were 42% and 62% of it and read
   as nothing at all. */
.mandate.hot {
  fill: color-mix(in srgb, var(--c, var(--muted)) var(--wash, 8%), transparent);
  opacity: 1;
}

#a-mandate_au { --wash: 21%; }
#a-mandate_br { --wash: 14%; }

/* The box round Guam is not a mandate — it is American territory cut out of
   one — so it does not wash when pointed at. Washing it said the opposite of
   what it is there to say. It keeps its line, and the tooltip still answers. */
.mandate.mandate-cutout.hot { fill: transparent; }

/* No fill support for color-mix is no reason to lose the line. */
@supports not (color: color-mix(in srgb, red 8%, transparent)) {
  .mandate.hot { fill: rgba(120, 90, 80, .07); }
  .mandate.mandate-cutout.hot { fill: transparent; }
}

/* Far enough in that the islands are worth looking at rather than merely
   finding. It was 1.6x the opening view, which on a Pacific group is barely
   past the point where they are specks; the ring went while the island was
   still too small to aim at. 3.2x leaves it a few turns of the wheel longer. */
#jmap.zoomed-in .islet { display: none; }

/* The ring's target deliberately stays after the ring itself has gone. Taking
   it away with the ring was tried, so that an island big enough to draw would
   answer for itself rather than for its group: it made atolls worse. An atoll
   is a rim round a lagoon, and the lagoon is not land — with the target gone,
   pointing at the middle of Canton or Kiritimati fell through the hole in the
   ring and answered "Polynesia, beyond the edge of this map". The finger-sized
   circle over the whole speck is the more forgiving thing, and the group it
   names is a true answer. Point at the rim and the island still names itself. */

/* an invisible, finger-sized target over territories too small to hit */
.atom-hit {
  fill: transparent;
  stroke: none;
}

.atom-hit.hot { filter: none; }
.atom-hit.sel { stroke: none; }

/* shading drawn over a territory: the approximate occupied zone in China, and
   the Japanese stripes over Portuguese Timor, which in December 1942 was still
   being fought over */
.hatch-fill {
  fill: url(#hatch);
  stroke: none;
  pointer-events: none;
}

.hatch-fill.hatch-occ { fill: url(#hatch-occ); }
.hatch-fill.hatch-us { fill: url(#hatch-us); }
.hatch-fill.hatch-thai { fill: url(#hatch-thai); }
.hatch-fill.hatch-brit { fill: url(#hatch-brit); }
.hatch-fill.hatch-raid { fill: url(#hatch-raid); }
.hatch-fill.hatch-unclear { fill: url(#hatch-unclear); }

/* Guangzhou Bay, painted back out of China. See build_map.py: the box minus
   Natural Earth's coastline, by the even-odd rule. */
#gzw-bay,
/* The traced water round the Kwantung and Kwangchowwan leaseholds. Both are
   drawn from their own tracings over a country outline that is a different,
   coarser coastline, so China showed in the channels of Guangzhou Bay and as a
   rim along the Liaodong shore. */
#lease-sea {
  fill: var(--ocean);
  stroke: none;
  pointer-events: none;
}

/* The Communist base areas: an overlay, not a colour. They are drawn over the
   occupation shading and the two hatchings cross, which is the point — the
   country inside the line the Japanese army had drawn round itself and did not
   hold. The atom rule would fill them solid with their category's colour, so
   the fill is stated here and the stroke is a thin edge rather than the 1.3 of
   an ordinary border. */
#a-ccp, #a-ccp > path {
  fill: url(#hatch-ccp);
  stroke: #7a1730;
  stroke-opacity: .65;
  stroke-width: .6;
}
/* The base areas are grouped into named regions so the pointer can answer with
   one, which makes them sub-units — and a sub-unit gets a whole-shape filler
   laid under it to close the cracks between neighbours. These have no
   neighbours and no cracks; the filler would be a solid dark-red blot under
   the hatching. */
#backings path[data-for="ccp"] { display: none; }
#a-ccp.hot, #a-ccp.hot > path { fill: url(#hatch-ccp); filter: brightness(1.15); }

.site { pointer-events: auto; cursor: pointer; }
.site .hit { fill: transparent; stroke: none; }
.site .dot { fill: var(--c, var(--c-city)); stroke: #fffdf8; stroke-width: 1.6; }
.site:hover .dot { stroke: #14110c; stroke-width: 2.2; }

.tlabel, .slabel {
  pointer-events: none;
  text-anchor: middle;
  paint-order: stroke;
  stroke: #fffdf8;
  stroke-width: 3.5px;
  stroke-linejoin: round;
  font-weight: 600;
}

/* font-size stays on the presentation attribute — the labels carry a scale()
   transform sized so that one local unit is one screen pixel, and map.js needs
   the same number to estimate label widths for collision testing.
   No text-transform: uppercasing a territory name is a fine cartographic
   convention in English but shouts the romanisations in the other two
   columns, so territories are set apart by size and colour instead. */
.tlabel { fill: #4a3b2a; letter-spacing: .07em; font-weight: 700; }

/* A division of a country, named as one: lighter and a shade less black than
   the country it sits inside. Without it Suiyuan, Chahar, Jehol and Sinkiang
   were set in the same weight as China beside them and read as four more
   countries. */
.tlabel.sub { font-weight: 600; fill: var(--sub-ink); letter-spacing: 0; }

.slabel { fill: #14304a; font-weight: 600; }

@keyframes flash {
  0%, 100% { filter: none; }
  25%, 75% { filter: brightness(1.5) saturate(1.6); }
}

.reveal { animation: flash .5s ease-in-out 3; }

/* Never animate `transform` here: site markers carry a transform attribute
   that a CSS transform would override, throwing the marker off its position. */
@keyframes wrong-flash {
  0%, 100% { filter: none; }
  40% { filter: grayscale(1) brightness(.6); }
}

.wrong { animation: wrong-flash .45s ease-in-out 1; }

@media (prefers-reduced-motion: reduce) {
  .reveal, .wrong { animation: none; }
  .reveal { filter: brightness(1.45) saturate(1.5); }
}

/* ------------------------------------------------------------ overlays -- */

#zoom-controls {
  position: absolute;
  right: max(10px, var(--safe-r));
  top: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 9;          /* above the info and quiz cards, not under them */
}

#zoom-controls button {
  appearance: none;
  /* **Centre the icon here rather than leaving it to the button.**
     A `<button>` with `appearance: none` centres its content by the UA's own
     rules, and those are not the same everywhere: with a block child the
     position can come off the text baseline, which moves with the font's
     ascent and descent — so an icon sits true in one browser and a pixel or
     two high in another, and on iPad it was visible. Reported there and not on
     iPhone or on either desktop browser; measured in Chrome at five viewports
     and three root font sizes, every offset was 0.00 px, which is what
     "cannot reproduce it, but here is the thing it depends on" looks like.
     Grid centring depends on nothing. */
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: 8px;
  /* mostly opaque so the map does not read through the glyph, and taken from
     the panel token so it follows the reader's colour scheme rather than
     staying a white tile with a pale + on it */
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  color: var(--ink);
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow);
}

/* **`display: grid` beats `[hidden]`.** The rule above is a plain element
   selector, so it wins over the UA's `[hidden] { display: none }` and the two
   buttons that are meant to appear only with their layer — the plane tools and
   the train tools — were on the screen from the first load. Introduced with
   the grid centring above and reported straight away. */
#zoom-controls button[hidden] { display: none; }

#zoom-controls button:hover { background: var(--panel); }

/* **The air button shows the aeroplane that flies the sheet.** Both drawings
   are in the markup and one is shown; `map.js` writes `data-epoch` on the
   button. The 1930 Fokker is the default so that a build whose script has not
   run yet shows an aeroplane rather than an empty square. */
/* **The other corner.** The zoom stack is top right and is about the view;
   these two are about the map itself, and full screen belongs in the corner a
   reader reaches for. Same tile as the zoom buttons so the map has one kind of
   control rather than two. */
#corner-controls {
  position: absolute;
  right: max(10px, var(--safe-r));
  bottom: max(10px, var(--safe-b, 10px));
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 9;
}

#corner-controls button {
  appearance: none;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  color: var(--ink);
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow);
  display: grid;
  place-items: center;
}

#corner-controls button:hover { background: var(--panel); }
#corner-controls svg { width: 22px; height: 22px; }

/* The "i" is a letter, not a glyph from a font nobody chose: serif so it reads
   as an italic i rather than as a stray stroke at 19 px. */
#btn-layer-info {
  font-family: Georgia, 'Times New Roman', serif;
  font-style: italic;
  font-weight: 700;
}

/* **One flash, two seconds after the layer goes on.** A reader who has just
   switched something on is looking at the map, not at the corner; the button
   appearing silently is a button nobody finds. It pulses twice and stops —
   an animation that keeps going is a thing to be got rid of rather than
   read. */
/* **BIGGER, AND LONGER.** The old flash was two beats of a 12% swell and a
   6px ring, which the author kept missing altogether — and a notice nobody
   sees is a notice that did not happen. The swell is 32% at its peak, the
   ring reaches 16px and fills in twice as strongly, the colour of the button
   itself turns over to the accent at the crest so the movement is not the
   only signal, and it runs four beats rather than two. It is still a flash
   rather than a permanent mark: the button stays where it was and says the
   same thing afterwards. */
@keyframes layer-info-flash {
  0%, 100% { box-shadow: var(--shadow); transform: scale(1);
             border-color: var(--line); color: var(--ink); }
  50%      { box-shadow: 0 0 0 16px color-mix(in srgb, var(--accent, #9a1813) 0%, transparent),
                         0 0 0 8px color-mix(in srgb, var(--accent, #9a1813) 75%, transparent);
             transform: scale(1.32);
             border-color: var(--accent, #9a1813);
             color: var(--accent, #9a1813); }
}
#btn-layer-info.flash { animation: layer-info-flash 700ms ease-in-out 4; }
@media (prefers-reduced-motion: reduce) {
  #btn-layer-info.flash { animation: none; outline: 3px solid var(--accent, #9a1813); }
}

/* Which half of the corner glyph shows: arrows out to go full screen, arrows
   in to come back. */
#btn-fullscreen .fs-in { display: none; }
#btn-fullscreen[aria-pressed="true"] .fs-out { display: none; }
#btn-fullscreen[aria-pressed="true"] .fs-in { display: inline; }

/* One block per layer in the "i", newest first, with a rule between them so
   the second and third read as further entries rather than as more of the
   first. */
#layer-info-body .layer-info-item + .layer-info-item {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
#layer-info-body h3 { margin: 0 0 6px; }
#layer-info-body .layer-info-note { white-space: pre-line; margin: 0 0 8px; }
#layer-info-body .layer-info-src { font-size: 12.5px; color: var(--muted); }
#dlg-layer-info { max-width: 640px; }
#layer-info-body { max-height: 60vh; overflow-y: auto; }

#btn-air .air-icon-e1942 { display: none; }
#btn-air[data-epoch="e1942"] .air-icon-e1930 { display: none; }
#btn-air[data-epoch="e1942"] .air-icon-e1942 { display: inline; }

#legend {
  position: absolute;
  left: max(10px, var(--safe-l));
  top: 10px;
  max-width: min(46vw, 280px);
  max-height: calc(100% - 20px);
  overflow-y: auto;
  /* a panel floating over the map must not eat the gestures meant for it:
     only the rows themselves take the pointer. **Unless it has more in it
     than fits** — see `.scrolls` below, and `legendScroll` in map.js. */
  pointer-events: none;
  overscroll-behavior: contain;
  padding: 8px 10px;
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow);
  font-size: 12.5px;
  z-index: 5;
}

#legend .legend-head, #legend .legend-body { pointer-events: auto; }   /* the rows do nothing, so they let the map through */

#legend .item {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 1px 0;
}

#legend .sw {
  flex: 0 0 auto;
  width: 14px;
  height: 12px;
  border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, .25);
}

#legend .sw.round { border-radius: 50%; width: 12px; }
#legend .sw.diamond { border-radius: 2px; transform: rotate(45deg) scale(.82); }
#legend .sw.square { border-radius: 1px; transform: scale(.8); }

/* The head is the fold control. It is a real button so it takes the keyboard
   and announces its state, but it has to read as the legend's title. */
#legend .legend-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 40px;
  margin: 0 0 5px;
  /* 3px on the right, for the caret. It is a 10px square standing on one
     corner, and a square rotated 45 degrees has a bounding box 1.41 times its
     side — 14.1px, which is 2px past each edge of the 10px the layout gives
     it. `justify-content: space-between` puts that box hard against the
     content edge, so the corner stuck 2px out of the panel; and because
     `overflow-y: auto` makes the other axis `auto` too, the browser answered
     with a horizontal scrollbar across the bottom of the legend, for two
     pixels of a rotated corner. */
  padding: 3px 3px 3px 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: left;
  cursor: pointer;
}

#legend .legend-head:hover { color: var(--ink); }

#legend .legend-head .caret {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform .15s ease;
}

#legend.folded .legend-head { margin-bottom: 0; }
#legend.folded .legend-head .caret { transform: translateY(1px) rotate(-135deg); }
#legend.folded .legend-body { display: none; }


#tooltip {
  position: fixed;
  z-index: 30;
  pointer-events: none;
  max-width: 260px;
  padding: 5px 9px;
  background: #23201b;
  color: #fdfbf6;
  border-radius: 6px;
  font-size: 13.5px;
  line-height: 1.35;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .3);
}

#tooltip .sub {
  display: block;
  color: #cfc6b6;
  font-size: 12px;
}

/* The sub-unit is the headline now, so the highlighted line is its other
   scripts; the country underneath is plainer, being context. */
#tooltip .sub.alt-script { color: #f0d9a8; }
#tooltip .sub.prov { color: #d8cfbf; }
/* What kind of rule that country's name stands for, where the name alone does
   not say it plainly: an atoll in the Carolines answered "South Seas Mandate"
   and left the reader to work out whose. Quieter than the name above it. */
#tooltip .sub.rule { color: #b9b0a1; }
/* a note that belongs to the sub-unit rather than to the country */
#tooltip .sub.prov-note { color: #cfc6b6; font-style: italic; max-width: 24rem; }

/* -------------------------------------------------------- info & quiz --- */

/* Bottom-right on a wide screen: that corner of this particular map is open
   Pacific, so the card hides almost nothing. Bottom-left would sit squarely on
   Singapore and Java. */
#info, #quiz {
  position: absolute;
  right: max(10px, var(--safe-r));
  bottom: calc(10px + var(--safe-b));
  width: min(400px, calc(100% - 20px));
  max-height: calc(100% - 20px);
  overflow-y: auto;
  /* **Nine pixels back from each side.** The card carries tables now — the
     population figures, an air service's circuit — and 14px of padding either
     side of a 400px box left 372 for them. A journey column is about 118px
     wide, so three of them and their gaps did not fit and the third was cut;
     at 10px there are 380, which is the difference between two columns and
     three on the widest cards. It is a margin, not a rule: the text inside
     still stands clear of the border. */
  padding: 12px 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: var(--shadow);
  z-index: 8;
}

#info-close {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 44px;
  height: 44px;
  appearance: none;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  padding: 4px 8px;
  cursor: pointer;
}

#info .chip {
  margin: 0 0 4px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

#info .chip::before {
  content: "";
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  margin-right: 6px;
  vertical-align: -1px;
  background: var(--chip, var(--muted));
}

#info h2 {
  margin: 0;
  font-size: 19px;
  line-height: 1.25;
  padding-right: 22px;
}

#info .alt {
  margin: 3px 0 0;
  color: var(--muted);
  font-size: 13.5px;
}

#info .when {
  margin: 6px 0 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent);
}

#info .when[hidden] { display: none; }

/* the country a sub-unit belongs to, under its name */
#info .prov {
  margin: 2px 0 0;
  font-size: 13.5px;
  color: var(--muted);
}

#info .note {
  margin: 8px 0 0;
  font-size: 13.5px;
  max-height: 30vh;
  overflow-y: auto;
}

#info .note[hidden] { display: none; }

/* The source line under a note. Set apart from the prose and quiet enough that
   it does not compete with it, but a real link and a real target. */
/* A link inside the run of a note, as against `.note-src` below, which is the
   source line standing on its own at the foot of a card. Inline, and it takes
   the surrounding size and leading so a sentence with one in it still sets as
   a sentence. */
#info .note-link {
  display: inline;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

#info .note-src {
  display: block;
  margin-top: 7px;
  font-size: 12px;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px solid var(--line, rgba(0, 0, 0, .18));
  padding-bottom: 1px;
  width: fit-content;
}

#info .note-src:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* What this place is, and under it what the group it belongs to is. The reader
   asked about Singapore and the Straits Settlements is the context; the second
   is set smaller, lighter and behind a rule so that which half is new can be
   seen without reading either. */
#info .note-own {
  color: var(--ink);
  /* **A blank line in a note is a paragraph break.** The long route notes run
     to two thoughts — what the service was, and then what is inferred rather
     than printed — and with the default `normal` the two ran together into one
     block the reader had to unpick. `pre-line` honours the newline and still
     collapses runs of spaces, so nothing else about the wrapping changes. */
  white-space: pre-line;
}

#info .note-group {
  margin-top: 10px;
  padding: 8px 0 0 10px;
  border-top: 1px solid var(--line, rgba(0, 0, 0, .12));
  border-left: 2px solid var(--line, rgba(0, 0, 0, .12));
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}

/* Which group the second block is about. Two paragraphs of prose with nothing
   between them but a rule left the reader to work out that the first answers
   what they clicked and the second answers what it sits inside; this says so.
   Drawn from the attribute rather than from an element of its own, so nothing
   in index.html has to know about it. */
#info .note-group::before {
  content: attr(data-group);
  display: block;
  margin-bottom: 3px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: .85;
}

#info .note-group[data-group=""]::before { display: none; }

/* Only ever drawn on a phone; see the media query at the foot of this file. */
#info .more {
  display: none;
  margin: 10px 0 0;
  padding: 9px 12px;
  width: 100%;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line, rgba(0, 0, 0, .16));
  border-radius: 8px;
  cursor: pointer;
}

#info .more:hover { color: var(--ink); }

#quiz .quiz-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}

#quiz .score {
  margin: 0;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

#quiz .prompt { margin: 0; font-size: 19px; line-height: 1.3; }
#quiz .prompt .lead { color: var(--muted); font-size: 13px; text-transform: uppercase; letter-spacing: .07em; }
#quiz.done .prompt .lead { display: none; }
#quiz.done .summary ul { max-height: none; overflow: visible; }
#quiz .prompt strong { display: block; }

#quiz .feedback { margin: 6px 0 0; font-size: 13.5px; min-height: 1.2em; }
#quiz .feedback.good { color: #17693f; }
#quiz .feedback.bad  { color: var(--accent); }

#quiz .quiz-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

#quiz button {
  appearance: none;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  font: inherit;
  font-size: 14px;
  padding: 8px 14px;
  min-height: 40px;
  cursor: pointer;
}

#quiz #q-reveal { background: var(--accent); border-color: var(--accent); color: #fff; }
#quiz .summary { margin: 6px 0 0; font-size: 13.5px; }
/* the card itself scrolls; a second scroller nested inside it cannot be
   reached by a finger, because the outer one takes the gesture */
#quiz .summary ul { margin: 6px 0 0; padding-left: 18px; }

/* ------------------------------------------------------------ dialogs --- */

dialog {
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 22px 24px;
  max-width: 640px;
  width: calc(100% - 32px);
  max-height: 85vh;
  overflow-y: auto;
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 12px 40px rgba(0, 0, 0, .3);
}

dialog::backdrop { background: rgba(35, 32, 27, .45); }
dialog h2 { margin: 0 0 10px; font-size: 20px; }
dialog h3 { margin: 18px 0 6px; font-size: 15px; }
dialog p, dialog li { font-size: 14px; }
/* **No link falls back to the browser's own blue.** `dialog a` covered the
   About and Layers panes and nothing else, so a source link in the info card —
   the air routes' JACAR citations, the population tables' sources — came out
   at rgb(0,0,238) on a dark panel, which is the one colour on the page that
   belongs to no palette here and is unreadable against it. Stated once, for
   every link the app draws; `.note-src` keeps its muted grey, being a class
   and so more specific. */
a { color: var(--accent); }
dialog a { color: var(--accent); }

/* The dialogs scroll, and an absolutely positioned × scrolls away with the
   text — on a phone, with no Escape key, that leaves no way out but to scroll
   all the way back up. Sticky keeps it in the corner. */
dialog .x {
  position: sticky;
  float: right;
  top: 0;
  z-index: 2;
  appearance: none;
  border: 0;
  background: var(--panel);
  border-radius: 8px;
  width: 44px;
  height: 44px;
  font-size: 24px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
}

dialog .hint { color: var(--muted); font-size: 13px; }
dialog hr { border: 0; border-top: 1px solid var(--line); margin: 16px 0; }

/* **The Layers pane is a list of switches, and it was set like prose.**
   Seven pixels above and below every row, a paragraph's default margin on
   every hint and sixteen either side of every rule: the pane ran to two and a
   half screens on a laptop and a reader looking for one switch scrolled past
   the rest twice. Tightened here and nowhere else — the other dialogs are
   reading matter and want the air.

   **The touch target is not touched.** `@media (pointer: coarse)` gives every
   row a 44px minimum height and that still holds; this takes the padding off
   the space between rows on a pointer that does not need it. */
#dlg-options label.row { padding: 1px 0; gap: 7px; font-size: 14px; }
#dlg-options .hint { margin: .15em 0 .45em; }
#dlg-options .row + .hint { margin-top: .05em; }
#dlg-options .pane-note { margin: .2em 0 .45em; }
#dlg-options hr { margin: 7px 0; }
/* Smaller ticks in this panel and nowhere else. Fifty rows of them at 18px
   made a column of boxes with the words as the second thing the eye found; at
   15 the word leads and the panel is a fifth shorter. The 44px minimum a
   coarse pointer gets is untouched — the box is smaller, the target is not. */
#dlg-options input[type="checkbox"],
#dlg-options input[type="radio"] { width: 15px; height: 15px; }
/* **BETA, bottom left of the map.** Only the GitHub Pages copy carries it —
   `betaHost` in map.js decides, and on froginawell.net the element keeps its
   `hidden` attribute and nothing is drawn. It sits under the tools' strips
   rather than over them: it is a standing note about which copy this is, not
   something to read while a timetable is running. */
#beta-badge {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 6;
  padding: 3px 8px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .12em;
  line-height: 1.4;
  cursor: help;
  box-shadow: var(--shadow);
}
#beta-badge[hidden] { display: none; }
.beta-tag[hidden] { display: none; }

/* **The Layers filter.** `!important` because a row can carry `display: flex`
   from its own rule, and this has to beat it whatever the row is; the class is
   only ever set by the filter, so nothing else is being overridden. The rows'
   own `hidden` attribute is untouched — see `layFilter` in map.js. */
.lay-hide { display: none !important; }

/* **Clear of the ×.** The close button floats right and is sticky, so a block
   that follows it flows *under* it — the search box ran beneath the × and the
   two were on top of each other. Reported. The room is reserved rather than
   cleared, because clearing it would put the box a whole 44px lower for the
   sake of a corner. */
.lay-find { position: relative; margin: 2px 50px 6px 0; }
.lay-find input {
  width: 100%;
  box-sizing: border-box;
  padding: 6px 30px 6px 9px;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.lay-find input::placeholder { color: var(--ink); opacity: .55; }
/* The browser's own clear button is a different mark in every browser and
   absent in some, so the panel draws its own and hides that one. */
.lay-find input::-webkit-search-cancel-button,
.lay-find input::-webkit-search-decoration { appearance: none; display: none; }
.lay-clear {
  position: absolute;
  right: 3px;
  top: 50%;
  transform: translateY(-50%);
  appearance: none;
  border: 0;
  background: none;
  color: var(--ink);
  opacity: .6;
  font-size: 17px;
  line-height: 1;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 6px;
  cursor: pointer;
}
.lay-clear:hover { opacity: 1; background: color-mix(in srgb, var(--ink) 10%, transparent); }
.lay-clear[hidden] { display: none; }

#dlg-options h3 { margin: 10px 0 3px; }
#dlg-options h3:first-of-type { margin-top: 4px; }

/* The jump to the other date, under the occupation settings that only apply to
   one of them. Set apart from the switches above it so it reads as an action
   rather than another thing to turn on. */
/* The button that offers the other reading of the occupation, in the Layers
   pane and again at the foot of the card. It sat almost against the paragraph
   above it and read as part of the same block; a button is a different kind of
   thing from a sentence and wants a gap saying so. */
.epoch-jump { margin-top: 18px; }
dialog .epoch-jump { font-size: 13.5px; }

dialog label.row {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 0;
  cursor: pointer;
  font-size: 14.5px;
}

dialog input[type="checkbox"],
dialog input[type="radio"] {
  width: 18px;
  height: 18px;
  /* the map's own red, not the browser's blue for one and green for the other:
     two colours doing one job in the same dialog */
  accent-color: var(--accent);
  flex: 0 0 auto;
}

dialog h3 {
  font-size: 14px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 18px 0 8px;
}

/* The layer switches are three independent toggles sharing one shell, so
   several can be lit at once; the level control below is still one-of-three. */
/* A pressed layer button. On a light page this is dark ink under pale text;
   in dark it has to go the other way, a light fill under dark text, or it is
   near-white on near-white — which is what "too little contrast between the
   selected button and the text inside it" was. Both come from one pair of
   tokens so the two can never drift apart again. */
.seg.toggles button.on { background: var(--on-fill); color: var(--on-ink); }
.seg.toggles button.on:hover { filter: brightness(.92); }

/* On a phone the three words would take a whole row of the header to
   themselves. They shrink to the marks the map already uses -- the city dot,
   the battle diamond, a patch of territory -- with the words kept for screen
   readers. */
#layer-seg .g {
  display: block;
  width: 13px;
  height: 13px;
  background: currentColor;
  border-radius: 3px;
}
#layer-seg .g.dot { border-radius: 50%; }
#layer-seg .g.diamond { border-radius: 2px; transform: rotate(45deg) scale(.86); }
#layer-seg .g.block { width: 15px; height: 11px; border-radius: 2px; }
/* a name plate, for the switch that puts country and region names on the map */
#layer-seg .g.name { width: 15px; height: 5px; border-radius: 1px; }

/* --------------------------------------------------------- responsive --- */

/* Between the phone sheet and the side column the key and the card float over
   the same corner of the map, and a card is as tall as it needs to be: its top
   ran up over the key's own heading, which is the one line of it that is
   always showing. The card gives that strip up while one is open. */
@media (min-width: 621px) and (max-width: 999px) {
  body.panel-open #info { max-height: calc(100% - 72px); }
}

/* On a wide screen the legend and the detail card move into a column of their
   own. Floating them over the map meant part of the map could never be
   clicked, which matters in the quiz, where the answer may be underneath. */
@media (min-width: 1000px) {
  #stage { display: flex; }

  #map-container {
    position: relative;
    inset: auto;
    flex: 1 1 auto;
    min-width: 0;
  }

  #side {
    display: flex;
    flex-direction: column;
    gap: 10px;
    flex: 0 0 340px;
    padding: 12px;
    overflow-y: auto;
    background: var(--panel);
    border-left: 1px solid var(--line);
  }

  /* relative, not static: the info card's close button is positioned against
     it, and static would send it off to the stage corner */
  #legend, #info, #quiz {
    position: relative;
    inset: auto;
    width: auto;
    max-width: none;
    max-height: none;
    box-shadow: none;
  }

  #legend { background: transparent; border: 0; padding: 0; font-size: 13px; }
  #legend .item { padding: 2px 0; }
  #info .note { max-height: none; }
  #quiz .prompt strong { font-size: 21px; }
}

/* The header is charged against the map, so it loses parts as the screen
   narrows: the title first, then the words on the layer switches, which
   become the marks the map itself uses. At 360px wide that is the difference
   between four rows and two. */
@media (max-width: 950px) {
  #bar h1 { display: none; }
}

@media (max-width: 900px) {
  #bar { gap: 5px 6px; padding: 5px 8px; }
  .seg button { padding: 7px 10px; font-size: 13.5px; }
  #zoom-controls button { width: 44px; height: 44px; }
  #legend { font-size: 11.5px; padding: 6px 8px; }
}

/* A short screen has no room for a column of zoom buttons on the same side
   as the cards: in landscape the quiz card is full height and the buttons
   land on top of "End quiz". They move to the free corner. */
@media (max-height: 520px) {
  #zoom-controls {
    left: max(8px, var(--safe-l));
    right: auto;
    flex-direction: row;
  }
}

@media (max-width: 620px), (max-height: 520px) {
  #bar .spacer { display: none; }
  #layer-seg button .wide { display: none; }
  #layer-seg button .narrow { display: flex; }
  #layer-seg button { padding: 8px 10px; }
  .seg button { padding: 8px 9px; font-size: 13px; }
  button.plain { padding: 8px 10px; font-size: 13px; }

  #legend {
    top: auto;
    bottom: calc(10px + var(--safe-b));
    left: max(10px, var(--safe-l));
    right: max(10px, var(--safe-r));
    max-width: none;
    max-height: 45%;
    overflow-y: auto;
  }

  /* Folded, the legend is one word and a caret, and there is no reason for it
     to span the screen: full width it was a bar across the bottom of the map
     that swallowed every tap along it — on a phone that is Surabaya and Port
     Moresby, on a tablet in landscape a sixth of the map. It now shrinks to
     its own label and lets the rest of the strip through to the map. */
  #legend.folded {
    right: auto;
    max-width: none;
    width: max-content;
  }

  /* The legend is anchored to the bottom of the screen, so it has to grow
     upwards — which means the fold control belongs at the bottom of it, where
     it stays put under your finger instead of jumping up the screen. */
  #legend { display: flex; flex-direction: column-reverse; }
  #legend .legend-head { margin: 5px 0 0; }
  #legend.folded .legend-head { margin: 0; }

  /* folded down to one line, the legend is a strip; opened, the swatches run
     across in as many columns as fit rather than down a long list */
  #legend .legend-body {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 12px;
  }

  /* The density key is a ramp and reads as one only if it starts on its own
     line: flowed in with the political colours, its caption ended up beside
     the first class and "people per km²" read as the name of a swatch. The
     head and the source line take the full width; the five classes still flow
     across as columns. */
  #legend .pop-key-head,
  #legend .legend-src { flex: 0 0 100%; }

  /* the legend and the detail sheet both live at the bottom on a phone;
     the sheet wins whenever one is open */
  body.panel-open #legend { display: none; }

  /* **The legend lives in the badge's corner on a phone.** With the map wide
     the two are nowhere near each other; at this width the legend is anchored
     to the bottom-left and the BETA mark was underneath it. Reported.
     The badge takes the corner and the legend starts above it — and only on
     the copy that carries one, which is what the `is-beta` class on the root
     is for: the stable site keeps every pixel it had. */
  #beta-badge {
    left: max(10px, var(--safe-l));
    bottom: calc(10px + var(--safe-b));
  }
  html.is-beta #legend { bottom: calc(10px + var(--safe-b) + 28px); }

  /* The quiz card is a working surface, not a reading one: on a small screen
     it has to leave the map most of the room, or the answer is under it. */
  #quiz {
    padding: 8px 12px calc(8px + var(--safe-b));
    max-height: 42%;
  }
  #quiz .quiz-head { margin-bottom: 2px; }
  #quiz .prompt { font-size: 17px; }
  #quiz .prompt .lead { font-size: 11px; }
  #quiz .quiz-actions { margin-top: 6px; }
  #quiz .feedback { margin-top: 3px; }
  #quiz.done { max-height: 62%; }

  /* the info card and quiz card become bottom sheets */
  #info, #quiz {
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    border-radius: 14px 14px 0 0;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    padding-bottom: calc(12px + var(--safe-b));
    max-height: 62%;
    overflow-y: auto;
  }

  #info .note { max-height: none; }

  /* The sheet opens as the name and nothing else. A full description is
     several paragraphs, and on a phone it took most of the screen the moment
     you touched anything: you tapped a place to see where it was, and the map
     went behind the answer. `More` brings the rest back, and the sheet is
     scrollable when it is open. Every new selection starts closed. */
  #info .more { display: block; }

  #info:not(.open) .prov,
  #info:not(.open) .when,
  #info:not(.open) #info-pop,
  #info:not(.open) .note { display: none; }

  #info:not(.open) { max-height: none; overflow: visible; }
  #info.open { max-height: 76%; overflow-y: auto; }

  #zoom-controls { top: 8px; right: 8px; }
  #zoom-controls button { width: 44px; height: 44px; }

  dialog {
    width: 100%;
    max-width: none;
    max-height: 88vh;
    margin: auto auto 0;
    border-radius: 14px 14px 0 0;
    padding: 20px 18px calc(20px + var(--safe-b));
  }
}

@media (hover: none) {
  .site:hover .dot { stroke: #fffdf8; stroke-width: 1.6; }
}

/* The sources page folded into the About panel. There is no second file in the
   standalone build, so `tools/bundle.py` drops sources.html in behind a
   disclosure triangle rather than leaving a link that goes nowhere. On the web
   the link resolves and these rules match nothing. */
.sources-inline > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--accent);
  margin: 10px 0 4px;
}
.sources-inline h2 { font-size: 15px; margin: 18px 0 6px; }
.sources-inline ul { padding-left: 1.1rem; }
.sources-inline li { margin: 0 0 10px; }
.sources-inline code {
  font-size: .92em;
  background: #ece7db;
  padding: 1px 4px;
  border-radius: 3px;
}

/* The Administrative switch while its 750 KB of divisions are on the way. The
   button worked all along; without this it simply sat there saying nothing for
   the length of the fetch, which read as a switch that does not work. */
#layer-seg button.busy .g,
#layer-seg button.failed .g { opacity: .35; }
#layer-seg button.busy::after {
  content: "";
  display: inline-block;
  vertical-align: -1px;
  width: 11px;
  height: 11px;
  margin-left: 7px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  opacity: .8;
  animation: jmap-spin .7s linear infinite;
}
#layer-seg button.failed::after {
  content: "!";
  margin-left: 7px;
  font-weight: 700;
  opacity: .85;
}
@keyframes jmap-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  #layer-seg button.busy::after {
    animation: none;
    border-top-color: currentColor;
    opacity: .45;
  }
}

/* A coarse shape that a finer one has taken over. Not removed: it is put back
   the moment the fine layer is not the thing being looked at, and removing it
   would take its place in the drawing order with it. */
#land .superseded { display: none; }

/* The unoccupied south of New Guinea, cut out of the occupied colouring on
   the 1942 map. The free territories drawn on top say what the ground was;
   this says what it was not, so no sliver of the occupation can show between
   the two whatever the overlay's fit. The clip is a frame with the traced
   region punched out (see clip-ng-unoccupied in the build); 1930 is left
   whole — the same shapes are simply Dutch and Australian ground there. */
#jmap.e1942 #a-newguinea_au,
#jmap.e1942 #a-dei,
#jmap.e1942 #backings path[data-for="newguinea_au"],
#jmap.e1942 #backings path[data-for="dei"],
#jmap.e1942 #seams path[data-for="newguinea_au"],
#jmap.e1942 #seams path[data-for="dei"] {
  clip-path: url(#clip-ng-unoccupied);
}

/* The fine coastlines carry no colour of their own — they are their atom's
   shapes, drawn better. */
.atom > path.fine { vector-effect: non-scaling-stroke; }

/* Sub-units lit as a group: the Straits Settlements when the pointer is on one
   of them. The rule above matches the atom itself, which is what lights a whole
   country; these are paths inside an atom and need saying separately. */
.atom > path.hot:not([data-lit-for]) {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 89%, #fff);
}

/* Labuan is a Straits Settlement drawn inside the North Borneo atom, because
   that is where the island is, and it must not brighten when North Borneo
   does. `.atom.hot` is a filter on the whole group and nothing a child says
   exempts it from a filter above it — but a filter of the child's own composes
   with it, so this undoes exactly what the group did: 1 / 1.12. It stands down
   when the sub-unit is itself what is lit. */
/* With the lift a fill, the exemption is simply the plain colour back —
   no reciprocal arithmetic. */
.atom.hot > path.foreign-sub:not(.hot) { fill: var(--c, var(--inactive)); }

/* Administrative on: draw the divisions. Without this the switch changed
   nothing visible — sub-units inherit their atom's fill and stroke, so every
   seam between two provinces was the same colour as the provinces. The line is
   a whisper, and it falls on the coast as well as inland, which reads as a
   soft edge to the land rather than as a boundary of its own.

   Only the country under the pointer draws them. Every division of every
   country at once is some fifteen hundred lines, and a map covered edge to
   edge in whispering grey answers no question the reader is asking: they are
   asking about one place, and that is the place whose divisions they get. */
/* The princely state under the pointer, told from its six hundred neighbours.
   The whole layer lightens when any of them is hovered — that is the atom's
   own highlight, and it says "these are the states" — so the one being pointed
   at has to go the other way to be seen against them. */
/* Hovering a princely state: the whole layer lightens, which says "these are
   the states, there are six hundred of them"; the one under the pointer goes
   to a flat near-white and takes the only outline on the map. */
#a-princely.hot { fill: color-mix(in srgb, var(--c, var(--inactive)) 82%, #fff); }
#a-princely .prov-hot {
  /* a very light British, not white: the state is still part of that layer */
  fill: #C79AAB;
  filter: none;
}

/* Hovering a province of British India proper: that province darkens and is
   outlined, its neighbours get the thin line the Administrative layer draws
   for whichever country is under the pointer, and the princely states are left
   entirely alone — they are not divisions of the Raj and have nothing to do
   with the question being asked. */
#a-india .prov-hot { fill: color-mix(in srgb, var(--c, var(--inactive)) 86%, #14110c); }

#jmap.admin-on .atom.subs > path[data-prov]:not(.fine) {
  /* `--sub-line` is set per atom only while Ctrl is held and every boundary
     is showing at once: a near-black line vanishes on the dark blue of the
     Philippines and the deep red of Japan, so each atom gets the ink that
     reads on its own colour. Under the pointer nothing sets it and the
     fallback is the line this layer has always drawn. See revealAllSubs.

     **Not `--sub-ink`**, which is taken: that is the colour a province *name*
     is written in, declared in `:root` at the top of this file. Using it here
     meant the fallback never applied — every hovered country's boundaries
     came out in the solid label colour instead of the soft near-black — and
     setting it per atom for the reveal would have recoloured the labels
     inside that atom as well. A custom property is a global name and this one
     already had a meaning. */
  stroke: var(--sub-line, rgba(18, 15, 10, .62));
  stroke-width: .8;
}
/* Except where a province is drawn in more than one block on this date. The
   blocks share an edge that is not a boundary, and this line drew it: Suiyuan
   is cut at Paotow and along the Yellow River so that the 1942 sheet can
   colour the corridor Mengchiang held apart from the country Fu Zuoyi kept,
   and on the 1930 sheet, where it is one province, the cut showed as a
   sideways T through the middle of it. They stop stroking themselves and one
   line is drawn round the group instead — see markSplitProvinces in map.js.

   The first attempt at this was to drop the stroke from any block that is its
   whole atom, on the grounds that it only redrew the atom's own edge. That is
   the same shape but not the same line: where the neighbour is another piece
   of the same country in the same colour, this stroke is the only thing
   drawing the boundary at all, and Chahar and Jehol disappeared out of the
   Republic entirely. */
#jmap.admin-on .atom.subs > path[data-prov].merged-sub { stroke: none; }

/* The line round a province drawn in several blocks, matching the one above.
   The visible part of a masked outline is its outer half less the 1.3 the
   mask itself is stroked at, so 2.9 shows about the 0.8 the others draw. */
#jmap.admin-on #sub-outlines .sub-merged path {
  fill: none;
  stroke: rgba(18, 15, 10, .62);
  stroke-width: 2.9;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* Nothing is outlined except the country under the pointer. The princely
   states were exempt once, and six hundred outlines at once is a mesh rather
   than a map; the Straits Settlements were exempt after them, and Malacca's
   landward border stood there as a line with nothing to say until the pointer
   arrived. A boundary on this map is an answer to a question, and the question
   is where the pointer is. */
#jmap.admin-on #a-occupiedzone > path[data-prov] { stroke: none; }

/* The reset button at the opening view: there is nothing for it to reset, and
   pressing it did nothing, which reads as broken. It says so now. */
#zoom-controls button.idle { opacity: .38; cursor: default; }

/* the third label is for phones only; everywhere else the full word fits */
#layer-seg button .mid { display: none; }

/* ------------------------------------------------- the phone layout ------ */
/* One row of buttons and nothing else: 1930, Dec 1942, Cities, Events, Admin,
   Layers. The quiz goes entirely — its card, its two mode buttons and its
   feedback line filled a phone screen and left the map a strip — and About
   folds into the bottom of Layers rather than spending a button on itself. */
@media (max-width: 620px), (max-height: 520px) {
  #bar h1,
  #bar .seg[aria-label="Mode"],
  #bar #btn-about { display: none; }

  /* words, not glyphs: "Cities" says what a dot does not */
  #layer-seg button .narrow { display: none; }
  #layer-seg button .mid { display: inline; }
  #layer-seg button { padding: 7px 6px; min-width: 0; }
  /* **TWO ROWS RATHER THAN ONE THAT RUNS OFF THE EDGE.**
     This was one row that scrolled sideways, and on a 390px screen Layers
     and the ? sat past the right-hand edge — a control a reader cannot see
     is a control they will not look for, and nothing said the row could be
     dragged. It wraps now, which costs a second line of the map and hands
     back every button. The line is bought back by the collapse tab below. */
  #bar {
    gap: 5px;
    padding: 5px 6px;
    flex-wrap: wrap;
    overflow-x: visible;
  }
  #bar > * { flex: 0 0 auto; }
  .seg button { padding: 7px 6px; font-size: 12.5px; }
  button.plain { padding: 7px 8px; font-size: 12.5px; }

  #about-slot[hidden] { display: none; }
  #about-slot { display: block; margin-top: 6px; }
  #about-slot h3 { font-size: 15px; margin: 18px 0 6px; }
}


/* The gazetteer: cities from data/cities-*.csv, drawn plainly. A filled dot for
   a town, sized by how big it was; a dot in a ring for a provincial capital; a
   dot in a square for the capital of a country or a territory. Black, because
   the colours on this map already mean sovereignty and a city is not a claim
   about who held it — the territory underneath is already saying that. */
#gaz .dot { fill: #14110c; stroke: #fffdf8; stroke-width: 0.9; }
#gaz .ring, #gaz .box { fill: none; stroke: #14110c; stroke-width: 1.15; }
#gaz .hit { fill: transparent; stroke: none; }
#gaz .gaz:hover .dot { fill: #7a1730; }
#gaz .gaz:hover .ring, #gaz .gaz:hover .box { stroke: #7a1730; stroke-width: 1.6; }
#gaz .gaz.sel .dot { fill: #7a1730; stroke: #14110c; stroke-width: 1.8; paint-order: stroke; }
#gaz .gaz.sel .ring, #gaz .gaz.sel .box { stroke: #7a1730; stroke-width: 2; }
/* the largest places carry a heavier ring, so a capital still reads as one
   when its dot has grown enough to crowd the mark around it */
#gaz .t3 .ring, #gaz .t3 .box { stroke-width: 1.5; }

/* the four legend marks, drawn with borders rather than glyphs so they match
   what is on the map exactly */
#legend .sw.gaz-sm, #legend .sw.gaz-lg {
  background: #14110c; border-radius: 50%;
}
#legend .sw.gaz-sm { width: 5px; height: 5px; margin: 0 3px; }
#legend .sw.gaz-lg { width: 10px; height: 10px; }
#legend .sw.gaz-cap1, #legend .sw.gaz-cap2 {
  width: 12px; height: 12px;
  background: radial-gradient(circle at 50% 50%, #14110c 0 2.6px, transparent 2.6px);
  border: 1.2px solid #14110c;
}
#legend .sw.gaz-cap1 { border-radius: 50%; }
#legend .sw.gaz-cap2 { border-radius: 1px; }

/* The version and build time, at the foot of the About dialog. Quiet: it is
   there to be found when somebody asks how old this is, not to be read. */
#dlg-about .version {
  margin-top: 18px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 12px;
}

/* Mengchiang as it was drawn on its own maps, minus what it actually held.
   The fill beside it is the ground under Japanese control; this is the line
   round the third of the claim that never was, and it encloses nothing —
   Free China shows through it. Thick and dotted so it reads as an assertion
   rather than as a boundary somebody surveyed. */
#mengjiang-claim {
  fill: none;
  stroke: #7a2d1f;
  stroke-width: 2.6;
  stroke-dasharray: 1 5;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
  opacity: .85;
}

/* Provinces and islands, named once the reader is close in. A step smaller
   than a country's name and a shade lighter, so that zooming into Kwangtung
   does not make the province shout down the Republic it is part of. */
.tlabel.sublabel {
  font-size: 10.5px;
  opacity: .78;
  font-style: italic;
}

/* And the division above them, upright and letter-spaced. It is the same
   family and very nearly the same size — a point and a half — because the
   distinction being drawn is between a container and its contents, not
   between an important name and an unimportant one. The size itself is set
   in map.js, which has to box the label to place it; what is here is only
   what the reader reads as a difference of voice. */
.tlabel.sublabel.grouplabel {
  font-style: normal;
  letter-spacing: .05em;
  opacity: .72;
}

/* The figure a shaded unit has earned, written on the ground it is about.

   Upright and unitalicised beside the province's own name, which is the
   difference between a name and a number; and its ink is set inline by map.js
   rather than here, because it depends on the band the unit fell in — dark
   type on the three pale ones, white on the two dark, with the halo the other
   way round each time. Only the things that are the same at every band are
   here. */
.tlabel.popval {
  font-size: 10.5px;
  font-weight: 700;
  font-style: normal;
  letter-spacing: 0;
  stroke-width: 3px;
}

/* A unit the table has no figure for, drawn a second time above the shaded
   relief so the hillshade does not come through the one hole in the data —
   see `drawPopVoid`. Flat, white and untouchable: the copy in the land is
   what answers the pointer. */
#pop-void { pointer-events: none; }
#pop-void path.pop-void {
  fill: #fff;
  stroke: rgba(18, 15, 10, .5);
  stroke-width: .8;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* And in the key, where white on a white panel is a gap in the list. */
#legend .sw.pop-blank-sw { box-shadow: inset 0 0 0 1px rgba(18, 15, 10, .45); }

/* The whole of Mengchiang's claim, drawn as nothing: no fill, no stroke, no
   pointer. It exists so that hovering the state can trace a line round all of
   it — held ground and claimed ground together — instead of round the fill,
   which stops where Japanese control stopped. */
#mengjiang-whole {
  fill: none;
  stroke: none;
  pointer-events: none;
}

/* The physical map, lettered the way an atlas letters it: spaced out, in
   italic, and quieter than anything a country or a city is given, because a
   sea is context and not a claim. Two colours only — water and land — so the
   reader can tell at a glance which kind of thing a name belongs to without
   any legend saying so. No pointer: these answer nothing, being the ground the
   rest of the map sits on. */
.flabel {
  font-style: italic;
  letter-spacing: .14em;
  text-transform: none;
  paint-order: stroke fill;
  stroke-linejoin: round;
  pointer-events: none;
  text-anchor: middle;
}

.flabel.f-sea {
  fill: var(--f-sea-ink);
  stroke: var(--f-sea-halo);
  stroke-width: var(--f-halo-w);
  opacity: .82;
}

.flabel.f-land {
  fill: var(--f-land-ink);
  stroke: var(--f-land-halo);
  stroke-width: var(--f-halo-w);
  opacity: .72;
}


/* The two buttons in the Layers pane. Everything else about annotations —
   the panel, the marks, the fields — is styled by `annotate.js`, which injects
   its own sheet when it loads, so a reader who never draws pays for neither
   the code nor the rules. */
/* The annotation buttons' look, shared with the colour editor's. They are the
   same kind of thing — a control in the Layers pane that opens a piece of
   apparatus rather than switching a layer — and there is no reason for the map
   to have two ideas about what that looks like. */
.ann-row { display: flex; gap: 6px; flex-wrap: wrap; }
.ann-row button,
#opt-colours-open,
.colour-actions button {
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  font: inherit;
  font-size: 12.5px;
  color: var(--ink);
  cursor: pointer;
}
.ann-row button:hover,
#opt-colours-open:hover,
.colour-actions button:hover { border-color: var(--muted); }

/* The rule that used to be here set `.ann-row button` to `#1b232b` with
   `color: var(--ink)` — and `--ink` was never redefined for dark, so it stayed
   `#23201b`. Near-black on near-black: the two buttons were invisible until
   the pointer crossed them, which is exactly how it was reported. It is gone,
   because the palette does the job for every control at once. */

/* When the page and the script disagree about the version, the reader is told
   rather than left with a number that is quietly wrong. */
.version-stale { color: var(--warn-ink); font-weight: 600; }

/* The update number in the bar. Small and quiet: it is a label on the build,
   not a control, and it sits next to two buttons it must not be mistaken for.
   Tabular figures so it does not jiggle as the number grows. */
.bar-version {
  align-self: center;
  color: var(--muted);
  font-size: 11px;
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  padding: 0 2px;
}
.bar-version[hidden] { display: none; }


/* ------------------------------------------------- the map at one colour --

   Every state and every province painted one grey, for a reader who wants the
   coastline and the frontiers as a base and means to lay their own colours
   over it. It is a class on the SVG rather than a repaint, so it costs nothing
   to turn on and nothing to turn off, and the colours it hides are still in
   the elements' own `--c` waiting to come back.

   The outlines are *added* rather than left off. Without them a single grey is
   a single grey blob: the fills are what separate one country from the next on
   this map, so taking the colour away takes the frontiers with it unless
   something else draws them. A hairline of a darker grey is that something.

   The hatchings go too. Stripes over Timor and diagonals over occupied China
   are assertions in exactly the way a fill is, and a map stripped to one
   colour that kept them would be stripped of everything except an argument. */
#jmap.mono .atom:not(.atom-hit),
#jmap.mono .atom > path,
#jmap.mono #backings path,
#jmap.mono #seams path {
  fill: var(--lit, var(--mono-land));
}
/* The pointer lift under mono, said the same way as in colour. Mono's rule
   above outranks the plain lifts, so each one is restated at mono's own
   specificity with the mono land colour as its base. */
#jmap.mono .atom.hot:not(.atom-hit) {
  --lit: color-mix(in srgb, var(--mono-land) 89%, #fff);
}
#jmap.mono .atom > path.hot:not([data-lit-for]),
#jmap.mono #seams path.hot {
  fill: color-mix(in srgb, var(--mono-land) 89%, #fff);
}
#jmap.mono .atom .prov-hot {
  fill: color-mix(in srgb, var(--mono-land) 83%, #fff);
}
#jmap.mono .atom .parent-hot {
  fill: color-mix(in srgb, var(--mono-land) 86%, #fff);
}
#jmap.mono .atom .parent-hot.prov-hot {
  fill: color-mix(in srgb, var(--mono-land) 80%, #fff);
}
#jmap.mono .atom.hot > path.foreign-sub:not(.hot) { fill: var(--mono-land); }
/* The stroke goes on the group and inherits, which is how every path of an
   atom gets it. That is also its one cost: a territory drawn as forty
   overlapping polygons — French Indochina is the worked example — becomes a
   scribble of hairlines rather than an outline, because each piece is stroked
   and the pieces are small. Thin and pale is what makes that tolerable while
   still separating one country from the next; at 1.1 and the full line colour
   Laos and Cambodia read as a darker grey than the rest of the map, which is
   the one thing a single-colour map must not do. */
#jmap.mono #backings path:not(.nostroke) {
  stroke: var(--mono-line);
  stroke-width: 0.9;
}
/* An atom that has no filler under it *is* the shape, so it carries the line
   itself. One that has a filler does not: the filler is the whole country and
   the atom is the pieces it was built from, and stroking the pieces draws
   every seam inside it. That is what turned the Thai cession in Laos into a
   patch visibly darker than the rest of the map — the one thing a map stripped
   to a single colour must not have. */
#jmap.mono .atom:not(.atom-hit):not(.has-fill) {
  stroke: var(--mono-line);
  stroke-width: 0.9;
}
/* The finger-sized targets over the smallest territories carry `.atom` too,
   and they are meant to be invisible. Painted with the rest they became
   eighty-three grey discs across the Pacific, each one the size of a thumb —
   which is the size they are, and the whole reason they are transparent. */
#jmap.mono .atom-hit,
#jmap.mono .islet-hit { fill: transparent; stroke: none; }
#jmap.mono .islet,
#jmap.mono .tiny-isles,
#jmap.mono .tiny-isles path {
  stroke: var(--mono-line);
}
#jmap.mono .hatch-fill,
#jmap.mono .chinabase,
#jmap.mono .ccp-fill {
  display: none;
}
/* The mandates keep their line and lose their wash. The wash is a claim about
   who administered the sea between the islands; the line is where the boundary
   was drawn, which is geography of a kind and worth keeping. */
#jmap.mono .mandate,
#jmap.mono .mandate.hot { fill: none; }
/* And every shape gets a line, not only those with a filler under them. The
   rule above draws the outline on `#backings` — the whole-country shapes — and
   on atoms that have no filler, which was meant to avoid stroking every seam
   inside a country built out of pieces. It also left the pieces of a country
   whose filler exists but is not drawn with nothing at all: at one colour
   those territories had no edge and ran into their neighbours. A hairline on
   the atom's own paths, thin enough that a dense one does not go grey. */
#jmap.mono .atom > path:not(.superseded),
#jmap.mono .atom > circle {
  stroke: var(--mono-line);
  stroke-width: 0.55;
}
/* The edge lines go the same way as the fills. Thailand's teal, China's two
   halves and the mandate's dashes are territorial claims drawn as lines rather
   than as areas, and a map stripped of its colours that kept them would have
   kept the argument and thrown away the evidence. They stay as lines — the
   frontier is still where it was — in the same grey as everything else. */
/* every line in the outline layer, not only those with `.edge-line` on them:
   the rings that trace a whole country — Thailand's teal, China's two halves —
   are unclassed paths in there, and naming the class alone left them coloured
   over grey land */
#jmap.mono #sub-outlines path,
#jmap.mono #sub-outlines .sub-outline circle,
#jmap.mono #backings path.whole-edge,
#jmap.mono #land path.coast,
#jmap.mono #mandate-lift .mandate-line { stroke: var(--mono-line); }
#jmap.mono #mengjiang-claim { stroke: var(--mono-line); }
/* The dashed rings round sub-units are 2.8px wide, which is right for a
   coloured ring seen against a coloured country and wrong once everything is
   one grey: where a territory has many of them close together — the Thai
   cessions across Laos and Cambodia are the case — the dashes touch and the
   region reads as a *darker fill*, which is precisely what this view exists to
   remove. Measured: rgb(189,182,166) over Laos against rgb(228,224,214)
   everywhere else. Halved, they read as dashes again. */
#jmap.mono #sub-outlines .sub-outline path,
#jmap.mono #sub-outlines .sub-outline circle {
  stroke: var(--mono-sub);
  stroke-width: 1.4;
  stroke-dasharray: 4 5;
}
/* And the edge lines are capped. `--edge-w` is 6 pixels on the Thai cessions,
   which is right in colour — they are small enough that a thick teal ring is
   the only way to see them — and wrong here, because a 6px ring round a small
   shape *is* the shape, and Laos and Cambodia came out as a block of
   rgb(189,182,166) against rgb(228,224,214) land. Measured before and after. */
#jmap.mono #sub-outlines .edge-line {
  stroke: var(--mono-sub);
  stroke-width: 1.6;
}

/* The ? beside About: a single glyph, so it keeps its shape rather than being
   a very short word in a button built for words. Shown at every width — the
   reader most likely to need it is on the smallest screen. */
#btn-help.icon {
  min-width: 34px;
  padding-left: 0;
  padding-right: 0;
  font-weight: 700;
  text-align: center;
}

/* Create and Load in the bar. They are actions rather than a set of choices,
   so they do not take the `on` state the other segments use. */
#ann-seg button { font-weight: 600; }

/* The single-colour map's own colour. A row rather than a label wrapping an
   input, because it holds three things and the swatch should not be dragged
   about by a click anywhere on the line. */
#options .mono-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
#options .mono-row[hidden] { display: none; }
#options .mono-row input[type="color"] {
  width: 30px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: none;
  cursor: pointer;
}
#options .mono-row button {
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: var(--panel);
  font: inherit;
  font-size: 11.5px;
  color: var(--ink);
  cursor: pointer;
}
#options .mono-row button[hidden] { display: none; }
#options .mono-row button:hover { border-color: var(--muted); }

/* A word beside a switch while its file is on the way. The bar's own button
   takes the spinner `#layer-seg button.busy` draws, but on a phone the bar
   does not carry Topography at all and the dialog's tick is the only switch
   there is — so the dialog says it too. */
#options .row-note {
  margin-left: 6px;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
#options .row-note[hidden] { display: none; }
#options .row-note.bad { color: var(--warn-ink); }

/* The space bar held down: the pointer is a hand until it is let go, whatever
   tool is out. It beats `.ann-drawing`'s crosshair, which is the point. */
#map-container.space-pan,
#map-container.space-pan.ann-drawing { cursor: grab; }
#map-container.space-pan.dragging { cursor: grabbing; }


/* Natural Earth's own coastline, laid over the map from the admin pane. A thin
   magenta line over everything: it is a transparency for checking a drawn
   shape against its source, so it has to be legible over land, over sea and
   over the highlight, and it must never be mistaken for something the map is
   saying. Pointer-none, because it is an overlay and not a thing to click. */
#ne-outline { pointer-events: none; opacity: .85; }
/* On the paths and not on the group: `vector-effect` is a property of the
   geometry element and a group does not hand it down, so the line was a
   kilometre wide in map units and looked like a pink river. */
#ne-outline path {
  fill: none;
  stroke: #ff2bd6;
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}


/* The colour editor, under its button in the Layers pane. One row per colour
   the map carries, written by map.js from the tables themselves. It is a long
   list — thirty-odd — so it scrolls inside its own box rather than pushing the
   projection controls off the bottom of the panel. */
#colour-editor[hidden] { display: none; }
#colour-editor {
  margin: .4em 0 .9em;
  padding: .6em .7em;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
}
#colour-rows {
  max-height: 15em;
  overflow-y: auto;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: .3em .6em;
  align-items: center;
}
#colour-rows input[type="color"] {
  inline-size: 2.1em;
  block-size: 1.5em;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: none;
}
/* A group heading inside the list, set like the pane's own section headings —
   `TRANSPORT`, `PROJECTION` — because that is what it is. Without this the
   dates read at the same weight as the colour names under them, and the two
   Republics of China still looked adjacent. */
#colour-rows .colour-group {
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
  margin: .5em 0 .1em;
}
#colour-rows .colour-group:first-child { margin-top: 0; }
#colour-rows .colour-name { font-size: .92em; min-width: 0; }
/* Only offered once that colour has been moved, so the row is quiet until
   there is something to undo. */
#colour-rows .colour-back {
  font-size: .8em;
  padding: .05em .45em;
  visibility: hidden;
}
#colour-rows .colour-back.on { visibility: visible; }
.colour-actions {
  display: flex;
  gap: .5em;
  align-items: center;
  margin-top: .7em;
  flex-wrap: wrap;
}
.colour-say { font-size: .88em; color: var(--muted); }

/* ---------------------------------------------------------- train tools --

   The timetable interface: a strip of controls over the foot of the map, and
   the departure table inside a station's card. Both are built by trains.js and
   map.js and exist only while the reader is zoomed in to a railway that has a
   timetable, so nothing here is on screen the rest of the time.

   THE COLOURED TRACK AND THE TRAINS TAKE NO POINTER EVENTS, AND ARE STILL
   CLICKABLE. The two are not in tension. Nothing on these layers answers the
   browser's hit test, because the whole map answers the pointer by naming what
   is under it and a transparent ribbon along every railway would stop the
   country being named whenever the mouse crossed one. Instead `hitAt` in
   trains.js measures the tap against the trains and the track and hands back
   the nearer, and `handleTap` in map.js puts that ahead of the ground. A hover
   is untouched: it never reaches this code at all. The `<title>` on each train
   is for the accessibility tree rather than a tooltip.

   Two layers, not one, with the station squares between them: the track below
   the squares, because a station is a stop on a line and not a dot under a
   five-pixel stroke, and the trains above them, because a train arriving at a
   station is the thing being watched. */
#train-layer, #train-marks { pointer-events: none; }

#train-bar {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: max(10px, var(--safe-b));
  z-index: 9;
  display: flex;
  flex-direction: column;
  gap: 5px;
  max-width: min(94vw, 720px);
  padding: 7px 10px 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  box-shadow: var(--shadow);
  font-size: 13px;
  color: var(--ink);
}

.train-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.train-row2 {
  flex-wrap: wrap;
  gap: 4px 10px;
  font-size: 11.5px;
  color: var(--muted);
}

/* Forty pixels, the same as the zoom buttons: this is the control the reader
   reaches for most and it has to take a thumb. */
.train-play {
  appearance: none;
  flex: 0 0 auto;
  width: 40px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  color: var(--ink);
  font-size: 14px;
  cursor: pointer;
}

.train-play:hover { background: var(--hover-fill); }

/* Tabular figures and a fixed width, so the clock does not shove the slider
   sideways every time the minute changes from 09:59 to 10:00. */
.train-clock {
  flex: 0 0 auto;
  min-width: 4.2em;
  font-variant-numeric: tabular-nums;
  font-size: 16px;
}

#train-time {
  flex: 1 1 120px;
  min-width: 90px;
  accent-color: var(--accent);
}

#train-speed {
  flex: 0 0 auto;
  font: inherit;
  font-size: 12px;
  padding: 3px 4px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  color: var(--ink);
}

.train-count {
  flex: 0 0 auto;
  min-width: 6.5em;
  font-size: 11.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.train-close {
  appearance: none;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}

.train-close:hover { border-color: var(--line); color: var(--ink); }

.train-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 9px;
  /* Korea and its connections list seventy-odd lines; capped at about six
     rows and scrolled, so the strip stays a strip and the map stays a map. */
  /* The chips are boxed when one is picked, and a box needs room: at
     `padding: 0` the outline of the top row was cut off by the scroller's own
     edge, under the slider. Reported with a picture. */
  padding: 3px 0 1px;
  max-height: 7.8em;
  overflow-y: auto;
}

.train-chip { white-space: nowrap; }
/* **A chip is a switch: press it and its line is the only one lit.** Forty-two
   of them in a strip against forty-two lines on the map is a matching exercise
   by colour alone, and several of these inks are a shade apart. The picked one
   is boxed rather than merely darkened, so it reads as chosen at a glance and
   in a single colour scheme. */
/* The source phrase under every train card, and the list of a line's stations
   above it. Both are prose at the foot of a table, so they take the note's
   register rather than the table's. */
.trains-src {
  margin: .5em 0 0;
  font-size: 12px;
  opacity: .7;
  line-height: 1.35;
}
.trains-list {
  margin: .15em 0 .2em;
  font-size: 12.5px;
  line-height: 1.5;
}
.trains-list-head { margin-top: .7em; }

.train-chip { cursor: pointer; padding: 1px 4px; border-radius: 5px; }
.train-chip:hover { background: color-mix(in srgb, var(--ink) 10%, transparent); }
.train-chip.on {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  outline: 1px solid var(--accent);
}
/* The connections drawn straight between cities: hidden, track and chip, until
   the switch in the bar says otherwise — see `connOn` in trains.js. */
#train-layer.conn-off .train-line-approx,
#train-layer.conn-off .train-case-approx { display: none; }
#train-bar.conn-off .train-chip-conn { display: none; }
/* And the button that lists them goes with them. With the connections
   switched off there is nothing beyond the network to add to the list, so an
   offer to add it is an offer that does nothing — and the reader presses it
   once, sees the list unchanged, and learns not to trust the strip. */
#train-bar.conn-off .train-more { display: none; }
.train-conn { white-space: nowrap; font-size: 12px; color: var(--muted); cursor: pointer; }
.train-conn input { vertical-align: middle; margin: 0 3px 0 0; }

.train-chip .sw,
.trains-table .sw {
  display: inline-block;
  width: 11px;
  height: 4px;
  border-radius: 2px;
  margin-right: 4px;
  vertical-align: middle;
}

.train-note { font-style: italic; }
.train-full { color: var(--accent); }

/* ON A PHONE THE BAR GOES TO THE TOP, and that is not a taste.

   The detail sheet is anchored to the bottom of a phone screen and so is the
   legend, and a station's card is exactly what a reader opens while the trains
   are running — so a bar at the foot of the map would spend most of its life
   underneath the thing it was being used with. Moving it only when a card
   opens would be worse: a control that jumps across the screen when you touch
   something else cannot be aimed at. So it sits at the top for the whole of a
   small screen's visit, clear of the zoom buttons at the right.

   The legend of seven lines is most of what the bar would show and there is no
   room for it, so the lines fold away and the controls stay. Which colour is
   which is still in every row of a station's card. */
@media (max-width: 620px), (max-height: 520px) {
  #train-bar {
    left: max(8px, var(--safe-l));
    right: calc(60px + max(8px, var(--safe-r)));
    bottom: auto;
    top: 8px;
    transform: none;
    max-width: none;
  }
  /* **AND THE SECOND COLUMN OF BUTTONS GETS OUT OF ITS WAY.**
     The bar reserves 60px on the right, which is the zoom column — but
     `#map-extras` sits *46px further left again*, so the book and the sugar
     switch were under the bar on a narrow screen, unreachable for as long as
     the train tools were up. Reported as the tools blocking the sugar
     button.
     Reserving for both columns instead would take another 46px off a bar
     that already struggles to fit its row on a 414px screen, so the column
     moves down rather than the bar getting narrower: below the bar, where
     there is nothing else. Only while the tools are up — with them away the
     column belongs at the top beside the zoom buttons. */
  body.trains-up #map-extras { top: 92px; }
  .train-legend { display: none; }
  /* THE ROW MUST FIT, and on a 414 px screen it did not: play, clock, slider,
     speed, "38 running" and the close button came to more than the strip is
     wide, and a flex row overflows *visibly* rather than clipping, so the
     close button was drawn outside the bar and landed on top of the zoom
     controls. The running count is the one thing here a reader can do without
     — the trains are on the map to be counted — so it goes first. */
  .train-count { display: none; }
  /* Measured at 375 px, which is the narrowest screen worth designing for: the
     row came to 5 px more than the strip and the close button was drawn
     outside it. Play 40, clock 48, slider 48 at its narrowest, speed 75, close
     28 and four gaps of 6 is 263 against 279 of room. */
  .train-row { gap: 6px; }
  #train-time { min-width: 48px; }
  /* and the play button keeps its 40x34 while the rest gives way, because it
     is the one thing on the strip that has to take a thumb */
  .train-clock { min-width: 3.2em; font-size: 15px; }
  #train-speed { font-size: 11px; }
}

/* IN LANDSCAPE THE ZOOM BUTTONS ARE A ROW ALONG THE TOP LEFT — a short screen
   has no room for a column of them beside the cards — so the strip starts
   after them rather than underneath them. And a screen 375 px tall cannot hold
   both an open card and the strip: the card is 76% of it. The legend already
   stands down in that case and this does the same, and comes back the moment
   the card is closed. The clock does not stop; the trains go on running behind
   the card, which is the point of reading one while the day plays. */
@media (max-height: 520px) {
  #train-bar {
    left: calc(152px + max(8px, var(--safe-l)));
    right: max(8px, var(--safe-r));
  }
  body.panel-open #train-bar { display: none; }
}

/* A finger has nothing to aim at on a 16 px slider. The thumb is the browser's
   and cannot be resized without taking over the whole control, but the hit
   area is the element, and a drag begun anywhere on it moves the thumb. */
@media (pointer: coarse) {
  #train-time { height: 30px; }
}

/* The departures, in the card. Held to a third of the screen and scrolled:
   127 trains called at Taihoku, and a card that long would bury the note about
   the place under a timetable. */
#info-trains { margin-top: 10px; }

.trains-head {
  margin: 0 0 4px;
  font-size: 11.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The timetable is a second file now and it is not here yet. Lower case and
   not tracked out, because this is a sentence rather than a column heading —
   the heading it shares a class with is naming a table that exists. */
.trains-waiting {
  text-transform: none;
  letter-spacing: 0;
  font-style: italic;
}

/* And the strip's counter while it waits, for the same reason: it is standing
   in for "31 running", which is a figure, with a sentence. */
/* The connection lines' names, folded away until the button beside the switch
   is pressed: Korea's 42 lines plus 32 connections is a legend nobody reads.
   The button is not in this box — the box scrolls, and a control that opens a
   list must not be inside the part of it that can be scrolled out of sight. */
.train-legend.conn-folded .train-chip-conn { display: none; }
.train-more {
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.78rem;
  text-decoration: underline;
  cursor: pointer;
  padding: 0 0.3rem;
  white-space: nowrap;
}
.train-more:hover, .train-more:focus-visible { color: var(--ink); }

#train-bar.times-waiting .train-count { font-style: italic; opacity: .75; }

.trains-scroll {
  max-height: min(34vh, 260px);
  /* Both ways. Vertical because 127 trains called at Taihoku; horizontal
     because the last column is where the train was going, and a card narrow
     enough for a phone cut it off — the reader could see that a train left at
     05:17 and not where it went. */
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: 6px;
}

.trains-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}

/* Tight, because the calling list is five columns now — the characters, the
   Mandarin, the kana and the two times — and in the sidebar layout the card is
   283 px of usable width. At 12px and 6px of padding that table was 328 wide
   and the departure column, which is the one a reader came for, was the one
   off the right-hand edge. Measured after: 274. */
.trains-table th,
.trains-table td {
  padding: 2px 4px;
  text-align: left;
  border-bottom: 1px solid var(--line);
}

/* A time is never broken across two lines and a name may be. That is the whole
   of how the five columns fit: with everything held on one line the table was
   299 px wide in a 283 px card and the departure column — the one a reader
   opened the card for — was the one over the edge. */
.trains-table th,
.trains-table td.num { white-space: nowrap; }

.trains-table th {
  position: sticky;
  top: 0;
  background: var(--panel);
  font-size: 11px;
  color: var(--muted);
}

.trains-table tr:last-child td { border-bottom: 0; }

/* The transcription marks the readings it could not be sure of, and so does
   this: a time the original print left ambiguous is not a time to plan by. */
.trains-table .unc { color: var(--accent); }
.trains-table .unc::after { content: '?'; }

/* The two map-side switches beside the zoom buttons: stations, and the train
   tools. They are icons rather than words because they sit in a column 40 px
   wide, and each carries its name in `title` and `aria-label`. Pressed reads
   as pressed — the same fill the header toggles use, which in dark mode goes
   the other way round, a light fill under dark ink. */
#zoom-controls button svg {
  width: 21px;
  height: 21px;
  display: block;
}

#zoom-controls button[aria-pressed="true"] {
  background: var(--on-fill);
  color: var(--on-ink);
  border-color: var(--on-fill);
}

#zoom-controls button[aria-pressed="true"]:hover { background: var(--on-fill); }

/* WHERE THE FLOATING PANELS DO NOT FIT TOGETHER.

   Between 621 and 999 pixels the legend, the detail card and the zoom controls
   all float over the map rather than living in a sidebar, and the train strip
   joined them. Two of the three collisions that made were real:

   * The two layer buttons under the zoom reset grew the column downwards into
     the top-right corner of the detail card, which is where its close button
     is — so the one control a reader needs to get the card out of the way was
     the one the new buttons covered. The card now clears the whole column,
     always: no motion, nothing hidden, and it keeps working if the column ever
     gains another button.
   * The strip and the card are both anchored to the bottom, and at these
     widths they overlapped by a couple of hundred pixels. They stack instead —
     the card sits above the strip — and the strip's list of line colours
     stands down while a card is open, so its height is one row plus its note
     and the lift below can be a number rather than a measurement.

   Neither applies at 1000 px and up, where the card is in a sidebar of its
   own, nor below 621, where the card is a bottom sheet and the strip is at the
   top of the map. */
@media (min-width: 621px) and (max-width: 999px) and (min-height: 521px) {
  /* 52px: the 44px buttons at these widths, plus 8 of air. */
  #info, #quiz { right: calc(max(10px, var(--safe-r)) + 52px); }

  body.trains-up.panel-open #train-bar .train-legend { display: none; }
  /* 81px: the strip is 73 tall with its line colours stood down — measured,
     not guessed — and it sits 10 from the bottom, so the card's foot at 91
     clears its head at 83 with eight pixels of air. */
  body.trains-up.panel-open #info,
  body.trains-up.panel-open #quiz {
    bottom: calc(10px + var(--safe-b) + 81px);
    max-height: calc(100% - 101px);
  }
}

/* And at a thousand and up the map has a sidebar beside it, so the middle of
   the screen is not the middle of the map: the strip was centred on both and
   ran a hundred and fifty pixels over the card. Centred on the map instead. */
@media (min-width: 1000px) {
  #train-bar { left: calc(50% - 170px); }
}

/* ------------------------------------------- the printed table, in a box --

   A station's card, a train's and a line's all end in a link to the page the
   times were transcribed from. On a screen with room for it that page opens
   over the map instead of in another tab, so the reader does not lose the
   thing they were looking at to go and read about it. On a small screen the
   link stays a link: eighteen tables of thirty columns in a box 380 px wide is
   worse than the same tables with the whole screen to themselves, and map.js
   decides which by measuring rather than by guessing at the device. */
#dlg-table {
  width: min(1180px, 94vw);
  height: min(880px, 90vh);
  max-width: none;
  max-height: none;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
  color: var(--ink);
  overflow: hidden;
}

#dlg-table::backdrop { background: rgba(20, 17, 12, .45); }

#dlg-table .table-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px 8px 14px;
  border-bottom: 1px solid var(--line);
}

#dlg-table .table-title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#dlg-table .table-open { font-size: 12px; }

#dlg-table .table-close {
  width: 34px;
  height: 34px;
  font-size: 20px;
  line-height: 1;
  padding: 0;
}

/* The iframe fills what is left. Height by calc rather than by flex, because a
   percentage height on an iframe inside a flex column collapses to nothing in
   more browsers than it works in. */
/* The body scrolls. It was written for an iframe, which brings its own
   scrollbar, and the population tables are DOM inside it — so the box simply
   ended wherever its height ran out and the comparison table under the switch
   buttons could not be reached at all. */
#dlg-table .table-body { height: calc(100% - 51px); overflow-y: auto; }
#dlg-table iframe { display: block; width: 100%; height: 100%; border: 0; }

/* ---------------------------------------------- population by density --

   The card block first: a caption, one line per field with the number set
   right so a column of them reads down, and under it the note and the source.
   `.pop-btn` is the way from the figures to the map of them. */
#info-pop { margin-top: 10px; }
#info-pop[hidden] { display: none; }

.pop-block + .pop-block { margin-top: 10px; }

.pop-head {
  margin: 0 0 4px;
  font-size: 11.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.pop-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 12.5px;
  padding: 2px 0;
  border-bottom: 1px dotted var(--line);
}
.pop-row:last-of-type { border-bottom: 0; }
.pop-k { color: var(--muted); }
.pop-v { font-variant-numeric: tabular-nums; text-align: right; }

/* A line that is the sum of the lines under it. Manchukuo's nationalities are
   the case the block was needed for: 滿洲人 over Manchu, Han, Mongolian, Hui
   and other, and 日本人 over Japanese, Korean and other. Set at one weight the
   eight read as eight kinds of person, and a reader adding them up gets twice
   the population of Manchuria. The label takes the body ink as well as the
   weight — `--muted` on bold is a heading that has been half switched off. */
.pop-row.pop-sum .pop-k { font-weight: 700; color: var(--ink); }
.pop-row.pop-sum .pop-v { font-weight: 700; }

/* The row that takes a table away with you. Under the source, because the
   source is part of what is taken. */
.pop-actions { margin: 10px 0 0; }
.pop-csv { font-size: 12px; }

.pop-note, .pop-src {
  margin: 5px 0 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}
.pop-src { opacity: .82; }

.pop-btn {
  margin-top: 8px;
  font-size: 12px;
}
/* Pressed: the shading is on, and the button is the way to put it away. It is
   the same switch as the tick in the Layers panel, so it has to say which way
   it is set rather than only what pressing it would do. */
.pop-btn.on {
  color: var(--accent);
  border-bottom-color: currentColor;
}

/* The key. The head names the layer and its unit; the rows are the ramp. */
#legend .pop-key-head {
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--muted);
}
#legend .pop-key-row { padding-left: 2px; }

/* And on the map. The colour arrives as `--c` on the province — see applyPop —
   so the fill rule on `.atom` reads it and the hover lift still mixes with it.
   The line between two provinces is drawn here rather than left to the
   Administrative layer: a reader can ask for the shading without asking for
   every division of every country, and five classes over thirteen units with
   no boundary between them is a wash rather than a map. */
#jmap .atom > path.pop-shaded {
  /* The declaration has to be *here*, on the province, not only on the group.
     `fill` inherits as a finished colour: the `var(--c)` on `.atom` is resolved
     against the group's own variables and the children inherit the answer, so
     setting `--c` on a province changed nothing at all and thirteen of them
     stayed the colour of Korea. Declaring the same expression on the path
     makes it read its own variable.

     And it reads `--c`, never `--lit`. The lift a hovered country gets is
     `--lit` set on the *group*, already resolved to that country's colour, and
     a custom property inherits: with `--lit` first in this expression, putting
     the pointer anywhere on Korea turned all thirteen classes back to the red
     of Korea, which is the whole map going out at once. The province under the
     pointer still lifts — the rule below does it, mixed with the class colour
     rather than the country's — and the country's own lift now leaves the
     shading alone, which is right. Data on the map is not a hover state. */
  fill: var(--c, var(--inactive));
  stroke: rgba(18, 15, 10, .5);
  stroke-width: .8;
}
/* The units a pie map is about, with the pies over them: the boundary and
   nothing else, because the pie is what carries the reading and a fill would
   be a second one. */
#jmap .atom > path.pop-edged {
  stroke: rgba(18, 15, 10, .45);
  stroke-width: .8;
}
#jmap .atom > path.pop-shaded.prov-hot {
  fill: color-mix(in srgb, var(--c, var(--inactive)) 83%, #fff);
}

/* ------------------------------------------- the population table box --

   The card answers "what about this province"; this answers "and how does it
   sit against the others", so it is a column the reader can run their eye
   down. It borrows the box the printed timetable opens in — same close, same
   backdrop, same escape — and fills it with a table built here rather than
   with a page in an iframe. */
/* Room down each side. The box was built to hold a page in an iframe, which
   brings its own margins; a table built here has none, and ran to both edges
   of the box with the first and last columns touching the frame. */
.pop-table-block { padding: 4px 18px 14px; }
.pop-table-block + .pop-table-block { margin-top: 18px; }
.pop-table-block .pop-head { margin-bottom: 6px; }

/* The table scrolls inside its own box rather than making the whole dialog
   scroll sideways, and that box is bounded — so the horizontal bar sits at the
   bottom of what the reader is looking at instead of at the bottom of a
   sixty-four-row table they have to scroll past to find it. */
.pop-table-scroll {
  overflow: auto;
  max-height: min(56vh, 460px);
  overscroll-behavior: contain;
}

.pop-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.pop-table th, .pop-table td { padding: 4px 8px; text-align: left; }
.pop-table .num { text-align: right; }
.pop-table thead th {
  font-size: 11px;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  border-bottom: 1px solid var(--line);
  /* the head stays while the column is read down */
  position: sticky;
  top: 0;
  background: var(--paper, #fbf7ef);
}
.pop-table tbody th.name { font-weight: 500; }
.pop-table tbody tr + tr th, .pop-table tbody tr + tr td {
  border-top: 1px dotted var(--line);
}
/* the whole, above its parts */
.pop-table tr.whole th, .pop-table tr.whole td {
  font-weight: 600;
  border-bottom: 1px solid var(--line);
}
/* the row the card that opened this was about */
.pop-table tr.here th, .pop-table tr.here td { background: var(--hi, rgba(122, 23, 48, .07)); }

.pop-more {
  display: block;
  margin-top: 8px;
  font-size: 12px;
}

/* The head of each column is the way to sort by it: a button, so it answers a
   keyboard as well as a pointer, and `aria-sort` on the cell so a screen
   reader is told which way the table is ordered. */
.pop-table thead th { padding: 0; }
.pop-sort {
  appearance: none;
  background: none;
  border: 0;
  /* `font: inherit` resets the shorthand and takes the head's size with it, so
     the three that make a column head read as one are said again here. */
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: inherit;
  cursor: pointer;
  padding: 5px 8px;
  width: 100%;
  text-align: inherit;
  white-space: nowrap;
}
.pop-sort:hover { color: var(--ink); }
.pop-sort.on { color: var(--ink); font-weight: 700; }
.pop-sort .arrow { font-weight: 400; }

/* A group inside a card block or a table: the ages, the registers, the nine
   occupations. The source counted them separately and they answer separate
   questions, so they are set apart rather than run together into one column of
   twenty-one lines. */
.pop-group-head {
  margin: 9px 0 3px;
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}
.pop-table-block .pop-group-head { margin-top: 16px; }

/* The other dates of the same layer, at the foot of a table. */
.pop-switch {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  padding: 4px 18px 10px;
  border-top: 1px solid var(--line);
}
.pop-switch button { font-size: 12.5px; }

.pop-compare { border-top: 1px solid var(--line); padding-top: 12px; }

/* ------------------------------------------------ the sugar railways --

   A switch that belongs beside the map rather than in a dialog, and beside the
   zoom column rather than under it: that stack is three deep already, and this
   one is about the ground rather than about the view. Same size and the same
   four states as the buttons it sits next to. */
/* Left of the zoom column, and following it: same top, same tokens, the same
   width plus a gap across. Written from the zoom column's own values so the
   two cannot drift apart. */
#map-extras {
  position: absolute;
  top: 10px;
  right: calc(max(10px, var(--safe-r)) + 46px);
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 9;
}
#map-extras button[hidden] { display: none; }
#map-extras button {
  appearance: none;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  color: var(--ink);
  cursor: pointer;
}
#map-extras button svg { width: 22px; height: 22px; }
#map-extras button:hover { border-color: var(--accent); color: var(--accent); }
#map-extras button[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fffdf8;
}

/* **THE LAYER BUTTONS GO BLUE, NOT THE MAP'S ACCENT RED.**
   The accent is the map's own colour — it is what a country's border does
   when the pointer is on it, and what the header's pressed buttons take. A
   button in this column switches on something drawn *over* the map: the
   railways, their timetable tools, the sugar lines, the air routes and
   theirs, and the thematic layers. Red said "this is part of the map"; blue
   says "this is a layer you added, and here is where to turn it off".

   Named one by one rather than as `#map-extras button.on`, because the
   column also holds Topo, the stations, fullscreen and the layer note, and
   those are not layers of this kind.

   After the rule above and not before it: both are one id and one selector
   deep, so the later one wins, and the earlier draft of this lost that race
   silently and came out red. */
/* The five that live in the zoom column — the railways, the train tools, the
   sugar lines, the air routes, the plane tools — and the two in the extras
   column, the book and the sugar switch. Two containers because that is where
   the markup put them, and both ids are named so this beats
   `#zoom-controls button[aria-pressed="true"]` above, which is one id and an
   attribute where these are two ids. */
#map-extras #btn-theme.on,
#map-extras #btn-theme.on[aria-pressed="true"],
#map-extras #btn-sugar.on,
#map-extras #btn-sugar[aria-pressed="true"],
#zoom-controls #btn-rail.on,
#zoom-controls #btn-rail[aria-pressed="true"],
#zoom-controls #btn-trains.on,
#zoom-controls #btn-trains[aria-pressed="true"],
#zoom-controls #btn-air.on,
#zoom-controls #btn-air[aria-pressed="true"],
#zoom-controls #btn-planes.on,
#zoom-controls #btn-planes[aria-pressed="true"] {
  background: #1f3a68;
  border-color: #16294a;
  color: #fff;
}
/* Hover must not take it back to the panel fill: the rule above this one does
   exactly that for the zoom column, and a pressed button that turns pale
   under the pointer reads as unpressed at the moment the reader is looking
   hardest at it. */
#zoom-controls #btn-rail[aria-pressed="true"]:hover,
#zoom-controls #btn-trains[aria-pressed="true"]:hover,
#zoom-controls #btn-air[aria-pressed="true"]:hover,
#zoom-controls #btn-planes[aria-pressed="true"]:hover { background: #1f3a68; }

#map-extras #btn-theme.on svg,
#map-extras #btn-sugar.on svg,
#map-extras #btn-sugar[aria-pressed="true"] svg,
#zoom-controls #btn-rail[aria-pressed="true"] svg,
#zoom-controls #btn-trains[aria-pressed="true"] svg,
#zoom-controls #btn-air[aria-pressed="true"] svg,
#zoom-controls #btn-planes[aria-pressed="true"] svg { color: #fff; }

/* The two moves the zoom column makes, made again here. A short screen puts
   the column in the free corner as a row; a phone widens its buttons. */
@media (max-height: 520px) {
  #map-extras {
    left: max(8px, var(--safe-l));
    right: auto;
    top: 58px;
    flex-direction: row;
  }
}
@media (max-width: 620px), (max-height: 520px) {
  #map-extras button { width: 44px; height: 44px; }
}
@media (max-width: 620px) and (min-height: 521px) {
  #map-extras { top: 8px; right: calc(max(8px, var(--safe-r)) + 52px); }
}

/* On the map: under the trunk line and quieter than it. The government railway
   is the argument and these are the ground it ran through — thousands of
   kilometres of 762 mm plantation track, which drawn at the weight of the main
   line would bury it. Solid, because a dash is how this map says a line is
   approximate and these were surveyed. */
/* ON THE PATHS, NOT ON THE GROUP. `vector-effect` is not an inherited
   property: set on the group it applies to nothing, the width goes back to
   being map units, and eight wheel steps in each line is a kilometre wide.
   That is exactly the mistake this project keeps making — a stroke is
   something the reader sees the size of, so it is screen pixels — and the
   first cut of this layer made it again: at the island view the network read
   as a stain and at Tainan it was a field of brown sausages. */
#tw-sugar { pointer-events: none; }
#tw-sugar path {
  fill: none;
  /* The railway's own ink, which map.js sets on the group from the computed
     fill of the ground it crosses: these are the same kind of line and a
     colour of their own would say they were not. Thinner and fainter is the
     whole of the difference — three thousand kilometres of plantation track in
     a plain a hundred wide is dense however it is drawn, and the trunk line
     has to stay legible through it. */
  stroke: var(--rail-ink, #2b2b2b);
  stroke-width: 0.85;
  stroke-opacity: .55;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}


/* A place inside a Layers section, with the maps of it under it. */
#side .pane-sub {
  margin: 10px 0 2px;
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}
/* A switch this date cannot answer. Greyed, and its title says which date
   can — hover or long-press. */
#side .row.row-off { color: var(--muted); cursor: help; }
#side .row input:disabled { opacity: .5; }

/* The pies. Drawn once at a fixed radius and scaled by `k` on every rescale —
   see drawPies — so what the reader sees is the same size at every zoom. The
   ring is the map's own ink at low weight, enough to lift a pale slice off a
   pale province without ringing it in black. */
#pop-pies { pointer-events: none; }
#pop-pies .pie-back {
  fill: rgba(255, 253, 248, .82);
  stroke: rgba(18, 15, 10, .45);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
#pop-pies .pie-slice {
  stroke: rgba(18, 15, 10, .35);
  stroke-width: .6;
  vector-effect: non-scaling-stroke;
}

/* One table as wide as the source, so the name column has to stay put while
   the rest scrolls under it. */
.pop-table th.name, .pop-table thead th:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--panel);
  border-right: 1px solid var(--line);
}
.pop-table thead th:first-child { z-index: 2; }
.pop-table tr.whole th.name { background: var(--panel); }

/* And the group a column belongs to, over its own head, since the merged
   table has no heading above it to say so. */
.pop-sort .col-group {
  display: block;
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: .02em;
  opacity: .62;
  text-transform: none;
}

/* The pie the pointer is on, in the tooltip: big enough to read, with every
   slice named and its share beside it. It stands in for the description on
   those shapes — see showTooltip. */
#tooltip .tip-pie {
  display: block;
  margin-top: 6px;
  text-align: center;
}
#tooltip .tip-pie svg { display: block; margin: 0 auto 4px; }
#tooltip .tip-pie-key {
  display: block;
  text-align: left;
  font-variant-numeric: tabular-nums;
}
#tooltip .tip-pie-row {
  display: flex;
  align-items: center;
  gap: 6px;
  line-height: 1.5;
}
#tooltip .tip-pie-row .sw {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  border: 1px solid rgba(0, 0, 0, .3);
}
#tooltip .tip-pie-row .nm { flex: 1 1 auto; }
#tooltip .tip-pie-row .vv { flex: 0 0 auto; }
#tooltip .tip-pie-of {
  display: block;
  margin-top: 3px;
  text-align: left;
  opacity: .72;
  font-style: italic;
}

/* ------------------------------------------- the Other button's menu ---

   Five kinds of name, at the button that writes them. It is opened by a long
   press or an option-click — the plain press is still the master switch, and
   must stay that, because it is the one thing most readers ever want from it.

   Fixed rather than absolute: the bar it hangs from scrolls on a narrow
   screen, and a menu that stays behind while its button moves is worse than
   no menu. map.js positions it against the button on every open. */
/* **THE THREE POP-UP MENUS, AND THE COMMA THAT BROKE THEM.**

   These rules were written as `#rail-menu, #theme-menu[hidden]`, and so on
   down the block — which is two selectors, `#rail-menu` on its own and
   `#theme-menu[hidden]`. So `#rail-menu` picked up whatever the rule was for
   and `#theme-menu` got none of it. Three visible faults came out of that one
   punctuation mark:

     * `display: none` applied to `#rail-menu` **unconditionally**, and then
       the `label` rule below re-set it to `flex` — so the railway menu
       ignored its `hidden` attribute entirely and could not be shut. Reported
       as "I can't make the railway menu disappear".
     * the theme menu got no row layout at all, so its name and its source ran
       together as one paragraph. Reported with a picture.
     * and `#rail-menu` was styled as though it were a `label` and a
       `.menu-head` and a `.menu-reset`, all at once.

   Every id is written out in full in every list now. It is longer and it is
   the only form that says what it means. */
#label-menu,
#rail-menu,
#train-menu,
#station-menu,
#theme-menu,
#air-menu {
  position: fixed;
  z-index: 60;
  min-width: 15rem;
  max-width: min(20rem, calc(100vw - 16px));
  padding: 6px 4px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow);
  font-size: 13px;
  color: var(--ink);
}
#label-menu[hidden],
#air-menu[hidden],
#rail-menu[hidden],
#train-menu[hidden],
#station-menu[hidden],
#theme-menu[hidden] { display: none; }

#label-menu .menu-head,
#rail-menu .menu-head,
#train-menu .menu-head,
#station-menu .menu-head,
#theme-menu .menu-head,
#air-menu .menu-head {
  padding: 2px 10px 5px;
  color: var(--muted);
  font-size: 11.5px;
}
/* The date each block of sheets belongs to. Set apart from `.menu-head` above
   it — which names the whole menu — by a rule and the weight, so a reader
   scanning twenty-two rows can see where 1930 stops and 1942 starts. The first
   one has no rule above it, because the menu's own heading is already there. */
#air-menu .menu-sub {
  margin: 6px 0 2px;
  padding: 6px 10px 3px;
  border-top: 1px solid var(--line);
  color: var(--ink);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
#air-menu .menu-head + .menu-sub {
  margin-top: 0;
  border-top: 0;
}
#label-menu label,
#rail-menu label,
#train-menu label,
#station-menu label,
#theme-menu label,
#air-menu label {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  padding: 5px 10px;
  border-radius: 5px;
  cursor: pointer;
}
#label-menu label:hover,
#rail-menu label:hover,
#train-menu label:hover,
#station-menu label:hover,
#theme-menu label:hover,
#air-menu label:hover { background: var(--hover-fill); }
#label-menu input,
#rail-menu input,
#train-menu input,
#station-menu input,
#theme-menu input,
#air-menu input { margin-top: 2px; flex: none; }

/* **One design for all three.** The railway menu had a two-line row of its
   own — the name in bold, the source indented under it — which made the same
   list of layers look like a different kind of control depending on which
   button opened it. They are the same thing: a list of sheets, each with
   where it came from. The airline menu's row is the one they all use, and the
   source goes in brackets after the name, which is what that menu has always
   done. */
/* The name and its citation are one block of text in the row, so they flow
   and wrap as one sentence; the checkbox is the row's only other child. Three
   flex children made three columns — a narrow stack of the name beside a
   narrow stack of the source, which is how it was reported. */
#rail-menu .menu-text,
#train-menu .menu-text,
#station-menu .menu-text,
#theme-menu .menu-text { flex: 1 1 auto; min-width: 0; }
#rail-menu .src,
#train-menu .src,
#theme-menu .src { color: var(--muted); }

/* Twenty-two sheets is a long menu, so it scrolls rather than running off the
   screen, and the way back sits at the foot of it. */
#air-menu,
#rail-menu,
#theme-menu { max-height: min(70vh, 30rem); overflow-y: auto; }
#air-menu .menu-reset,
#rail-menu .menu-reset,
#theme-menu .menu-reset {
  display: block;
  width: calc(100% - 12px);
  margin: 6px 6px 2px;
  min-height: 0;
  padding: 5px 8px;
  font-size: 12px;
}

/* ------------------------------------- the key used as a set of switches ---

   Option-click the year, or hold it, and every row that stands for something
   on the map grows a tick. The rows keep their spacing either way: the box
   sits in the same place the swatch would have been pushed to, so turning the
   boxes on does not reflow the whole key. */
#legend .item.pickable { gap: 6px; }
#legend .legend-tick {
  flex: 0 0 auto;
  width: 13px;
  height: 13px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* The caret that opens a category into the places in it. Sits at the end of
   the row rather than in front of the swatch, so that turning the boxes on
   does not move the colours the reader is reading. */
#legend .legend-open {
  flex: 0 0 auto;
  margin-left: auto;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  position: relative;
}
#legend .legend-open::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 5px;
  width: 6px;
  height: 6px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(-45deg);
  transition: transform .12s ease;
}
#legend .legend-open.open::before { transform: rotate(45deg); top: 4px; }
#legend .legend-open:hover { color: var(--ink); }

/* One place inside a category. Indented under it, and a shade quieter: the
   category is the reading and these are what it is made of. */
#legend .item.legend-sub {
  padding-left: 18px;
  font-size: 12px;
  color: var(--muted);
}
#legend .item.legend-sub .sw { width: 10px; height: 9px; opacity: .8; }

/* The way back, under the list and only there when something is off. */
#legend .legend-reset {
  display: block;
  margin: 8px 0 2px;
  padding: 4px 10px;
  font: inherit;
  font-size: 12px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
}
#legend .legend-reset:hover { background: var(--hover-fill); }

/* **THE KEY TAKES THE POINTER ONLY WHEN IT HAS SOMETHING TO SCROLL.**
   `pointer-events: none` is what lets a drag across the key pan the map, and
   it is also what stopped a finger ever scrolling it — so on a short screen
   with the thematic categories under the country rows, the foot of the key
   could not be reached. `legendScroll` measures after every rebuild and on
   every resize and puts this class on when, and only when, there is more in
   the box than fits. */
#legend.scrolls {
  pointer-events: auto;
  scrollbar-width: thin;
}
/* and a hint that there is more, so the reader knows to try */
#legend.scrolls::after {
  content: "";
  position: sticky;
  bottom: -8px;
  display: block;
  height: 14px;
  margin: 0 -10px -8px;
  background: linear-gradient(to bottom, transparent,
              color-mix(in srgb, var(--panel) 94%, transparent));
  pointer-events: none;
}

/* ------------------------------------- the bar chart over each table ---

   The outline of a set of places before the figures for them: one bar to a
   row, longest first. The name column is a fixed width so the bars share a
   baseline — ragged starts would make a chart of the *names* instead. */
.pop-bars { margin: 10px 0 14px; }
.pop-bars-cap {
  margin: 0 0 6px;
  font-size: 12px;
  color: var(--muted);
}
.pop-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
  line-height: 1.35;
  padding: 0.5px 0;
}
.pop-bar .nm {
  flex: 0 0 10.5em;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink);
}
.pop-bar .track {
  flex: 1 1 auto;
  min-width: 40px;
  height: 9px;
  background: rgba(35, 32, 27, .07);
  border-radius: 2px;
  overflow: hidden;
}
.pop-bar .track i {
  display: block;
  height: 100%;
  background: #5286b4;
  border-radius: 2px;
}
.pop-bar .vv {
  flex: 0 0 5.5em;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

@media (max-width: 640px) {
  .pop-bar .nm { flex-basis: 7em; }
  .pop-bar .vv { flex-basis: 4.6em; font-size: 11px; }
}

/* The right-click menu: what a reader can take away from a shape, and where
   the shape came from. Positioned in client coordinates on the body rather
   than inside the map, so it is never clipped by the map's own overflow and
   never scaled by the zoom — it is a piece of interface, not of the drawing. */
#jmap-menu {
  position: fixed;
  z-index: 60;
  min-width: 15rem;
  max-width: min(24rem, 92vw);
  padding: 0.3rem 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 6px 24px rgb(0 0 0 / 22%);
  font-size: 0.86rem;
}
#jmap-menu .menu-head {
  margin: 0.15rem 0.75rem 0.35rem;
  font-weight: 600;
}
#jmap-menu button {
  display: block;
  width: 100%;
  padding: 0.4rem 0.75rem;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
#jmap-menu button:hover,
#jmap-menu button:focus-visible { background: var(--hover, rgb(0 0 0 / 7%)); }
#jmap-menu .menu-src {
  margin-top: 0.3rem;
  padding: 0.4rem 0.75rem 0.15rem;
  border-top: 1px solid var(--line);
}
#jmap-menu .menu-src p { margin: 0 0 0.3rem; line-height: 1.35; }
#jmap-menu .menu-src-head {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.72rem;
  opacity: 0.75;
}
#jmap-menu .menu-src-what { opacity: 0.7; }
#jmap-menu .menu-src-more { font-size: 0.78rem; }
#jmap-menu .menu-src-note { opacity: 0.72; }

/* The little arrow beside a choropleth in the Layers pane: take this map away
   as data. It sits at the end of the row, quiet until the row is hovered,
   because the row's job is choosing a map and this is the afterthought.

   **It carried `button.plain`, and with it a 38px minimum height.** So the two
   rows in each group that offer a download stood a third taller than the rows
   that do not, and the Demography section read as four lists with gaps torn in
   them — reported with a picture. The size is stated here instead: 18px of
   glyph, no border, no minimum. The touch target is put back below. */
#pop-rows .row { display: flex; align-items: center; gap: 0.35rem; }
/* **It looks like a button again, and it is still small.** Borderless and
   45% opaque, it read as a mark rather than a control and a reader had to
   discover it by hovering. It takes the chrome `button.plain` has — the same
   border, radius, panel ground and hover fill as Save and Load lower down the
   pane — and states its own size, which is the whole reason `button.plain`
   was dropped in the first place: that class carries `min-height: 38px`, and
   the rows offering a download stood a third taller than the rows that do not.
   22px, and the Demography lists keep their spacing. */
#dlg-options .pop-dl {
  margin-left: auto;
  min-height: 0;
  height: 22px;
  min-width: 24px;
  padding: 0 5px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
  line-height: 1;
  opacity: 0.75;
  cursor: pointer;
}
#dlg-options .row:hover .pop-dl,
#dlg-options .pop-dl:focus-visible { opacity: 1; }
#dlg-options .pop-dl:hover { background: var(--hover-fill); opacity: 1; }
/* And a finger still gets something to hit. What grows is the box, not the
   glyph — the same division the rest of this panel makes. The row is already
   44px tall on a coarse pointer, so this adds no height to it. */
@media (pointer: coarse) {
  #dlg-options .pop-dl { min-height: 32px; min-width: 40px; }
}

/* The Demography rows sit closer than the rest of the panel: they are four
   short lists of alternatives rather than a column of separate switches, and
   the sub-heading is what separates one place from the next. */
#pop-rows .row { padding: 0; }
#dlg-options #pop-rows .pane-sub { margin: 7px 0 1px; }

/* A legend swatch whose colour can be changed: option-click, or hold on a
   touch screen. The cue is deliberately faint — the swatch's first job is the
   switch beside it, and this is the second. */
#legend .sw-editable { cursor: pointer; }
#legend .sw-editable:hover {
  outline: 1px solid var(--line);
  outline-offset: 1px;
}
/* The picker itself is never seen: it is opened by script and positioned under
   the swatch so the system's colour panel appears beside the colour it is
   about. It has to be in the layout to be clickable, so it is one pixel rather
   than `display: none`. */
.sw-picker {
  position: fixed;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

/* ---------------------------------------------------------------- air routes
   Five scheduled services, drawn over whatever ground they cross: Japan's red,
   Korea's, Manchukuo's, and open sea. No single stroke reads against all of
   those, so each line is drawn twice — a wide white halo under a narrow dark
   line — which is what makes it legible everywhere rather than in one place.

   Both widths are `non-scaling-stroke`, so they are SCREEN pixels and the pair
   keeps its proportions at every zoom. Left in map units the halo would swell
   into a white band on the way in and vanish on the way out. */
#air { pointer-events: none; }
#air .air-route { pointer-events: none; }
#air .air-halo,
#air .air-line,
#air .air-hit {
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round;
  stroke-linejoin: round;
}
#air .air-halo { stroke: var(--air-halo); stroke-width: 5; opacity: 0.9; }
/* The map's own air ink, unless the route says otherwise — see `r.ink`. */
#air .air-line { stroke: var(--air-ink, #23405c); stroke-width: 1.7; }

/* **A leg several companies share is drawn once, and in nobody's colour.**
   Two services of one airline over the same pair of cities are that airline's
   and keep its ink; four companies over one stretch belong to none of them, so
   that stretch takes the map's default. Which of the several it is comes from
   pressing it — see the chooser. */
#air .air-line-shared {
  stroke: var(--air-ink-shared, #23405c);
  stroke-width: 1.7;
  fill: none;
}

/* **The chooser.** One row per airline over the pressed stretch, each with its
   own ink beside it so the menu and the map agree. It borrows the right-click
   menu's box; only the rows are its own. */
#jmap-menu.air-chooser button {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  text-align: left;
}
#jmap-menu.air-chooser .air-swatch {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  margin-top: 4px;
  border: 1px solid rgb(0 0 0 / 25%);
}
#jmap-menu.air-chooser .air-pick-text { display: block; }
#jmap-menu.air-chooser .air-pick-name { display: block; }
#jmap-menu.air-chooser .air-pick-op {
  display: block;
  font-size: 11.5px;
  color: var(--muted);
}

/* **The legs nobody has the times for, while the week is running.**
   Same ink and the same width as the line it continues — it *is* the same
   line — at a third of the strength, so the network being flown reads as the
   thing in front and the rest as what the sources do not cover. Faded rather
   than removed: a line that vanishes when the reader presses play looks like a
   fault, and this has something to say. Empty and therefore invisible whenever
   the week is not running. */
#air .air-line-idle {
  stroke: var(--air-ink, #23405c);
  stroke-width: 1.7;
  fill: none;
  opacity: .3;
}

/* And the airports on them go the same way, so a ring does not sit at full
   strength on a line that has faded out from under it. */
/* **The airport's name, drawn in screen pixels.** The ring's group carries the
   inverse of the view scale, so `font-size` here is pixels on the screen and
   stays that size at every zoom — the one place a plain CSS length in this
   project means what it says. Off unless the reader asks for it, and never a
   press target: a name is for reading and the ring under it is the thing to
   aim at. */
/* **The buffer never rendered, because `--paper` does not exist.** It was used
   here and defined nowhere, so `stroke: var(--paper)` was invalid at
   computed-value time and the property fell back to `none` — the airport names
   were the one set of labels on the map with no halo under them, which over
   the red of occupied Manchuria left them hard to read. The literal is the one
   `.blabel` uses, and like `.blabel` it does not change with the theme: the
   land colours under it do not invert. */
#air .air-name {
  display: none;
  font-size: 11px;
  font-weight: 600;
  fill: var(--ink);
  paint-order: stroke;
  stroke: #fffdf8;
  stroke-width: 3px;
  stroke-linejoin: round;
  pointer-events: none;
}

/* Zoomed in on one country there is room for more of it, and the names are
   what the reader came that close to read. `applyAir` puts the class on when
   less than `AIR_NAME_CLOSE_LAT` degrees of latitude are in view. */
#air.air-close .air-name { font-size: 13px; }
#air.air-names .air-name { display: inline; }

#air [data-air-stop].air-idle { opacity: .3; }
/* The press target: invisible, wide enough for a finger, and the only part of
   the layer that takes the pointer. */
#air .air-hit {
  stroke: transparent;
  stroke-width: 16;
  pointer-events: stroke;
  cursor: pointer;
}
/* The ring sits in a counter-scaled group, so its radius is already screen
   pixels; the stroke is left scaling with it so the ring keeps its
   proportions rather than fattening as the reader zooms out. */
#air .air-stop {
  fill: #fff;
  stroke: #23405c;
  stroke-width: 1.3;
}
#air .air-route:hover .air-line {
  stroke: var(--air-ink, #0f2740);
  stroke-width: 2.4;
  filter: brightness(0.8);
}

/* The airport rings take the pointer; the lines' own hit stroke is separate.
   The ring a reader sees is 3.4 px and a finger is not, so an invisible circle
   under it carries the press. */
#air .air-stop-at { pointer-events: auto; cursor: pointer; }
#air .air-stop-hit { fill: transparent; stroke: none; }
#air .air-stop-at:hover .air-stop { stroke-width: 2.2; }

/* The air card's journey strip: one column per circuit, read downwards.
   The times are set smaller and in their own colour so the eye can follow the
   places or the clock without reading both at once. */
.air-strip {
  display: flex; gap: 0.55rem; overflow-x: auto;
  /* **Room for the bullet.** Each call is marked by a dot sitting 3.5px left
     of its own rule, and with `overflow-x` on the strip that is outside the
     scroll box: the first column's dots were sliced in half down their left
     edge. The padding is the space the marks stand in. */
  padding: 0 0.25rem 0.2rem 0.5rem;
  margin-left: -0.5rem;
  align-items: flex-start;
}
.air-jrn { min-width: 7.4rem; flex: 0 0 auto; }
/* The service, how often it ran, and which half of the circuit this is: three
   different facts, so three lines. Run together they read as one phrase and
   none of them could be found. */
.air-jrn-head {
  margin: 0; font-size: 0.74rem; font-weight: 600;
  letter-spacing: 0.02em; color: var(--muted);
}
.air-jrn-freq {
  margin: 0 0 0.3rem; font-size: 0.72rem; color: var(--muted);
}
.air-leg-head {
  margin: 0.45rem 0 0.3rem; font-size: 0.7rem; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted);
}
.air-jrn > .air-leg-head:first-child { margin-top: 0; }
.air-calls { list-style: none; margin: 0; padding: 0; }
/* The line of flight: a rule down the left of the calls, so a column reads as
   one journey rather than a stack of separate stops. */
.air-calls li {
  position: relative; padding: 0 0 0.4rem 0.65rem;
  border-left: 1px solid var(--line);
}
.air-calls li:last-child { border-left-color: transparent; padding-bottom: 0; }
.air-calls li::before {
  content: ''; position: absolute; left: -3.5px; top: 0.34em;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ink);
}
.air-place { display: block; font-size: 0.84rem; line-height: 1.25; }
.air-t {
  display: block; font-size: 0.74rem; color: var(--air-time);
  font-variant-numeric: tabular-nums;
}
/* ↓ is landing and ↑ is taking off. No key needed, and shorter than either
   word in every language this map is read in. */
.air-t .air-arrow { font-style: normal; opacity: 0.8; }
.air-t .air-next { display: block; font-size: 0.68rem; opacity: 0.85;
                   font-style: normal; }

/* The plane tools' control strip: a clock, a play button, a scrubber and how
   fast the clock runs. It sits over the map's foot the way the train tools'
   strip does, and tells the label placer it is there so no city name is
   lettered underneath it. */
.air-bar {
  position: absolute;
  left: 50%; transform: translateX(-50%);
  bottom: calc(12px + var(--safe-b));
  /* **Above the card, not under it.** `#info` is z-index 8 and bottom-right,
     and on a narrow window it reaches across the whole foot of the map: the
     strip was drawn underneath it, present and pressable by nothing. A control
     a reader can see and cannot press is worse than one that is not there. */
  z-index: 9;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px;
  /* **A fixed width, because the text inside it is not.** The clock goes from
     "Day 6 11:02" to "Day 6 night" and the count from "1 in the air" to "13",
     and with the bar sized to its contents every one of those changed its
     width — so the whole strip breathed in and out under the reader's hand
     while the day played, and the slider they were dragging moved with it.
     The width is stated and the slider takes up the slack. */
  width: min(560px, calc(100% - 24px));
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: var(--shadow);
}
.air-bar .air-play {
  width: 34px; height: 34px; flex: 0 0 auto;
  padding: 0; font-size: 13px; line-height: 1;
}
/* Both readouts are given the room their longest reading needs and no more,
   so neither can push the slider about as it changes. Tabular figures, so the
   digits do not shuffle either. */
.air-clock {
  font-variant-numeric: tabular-nums;
  font-size: 12.5px; font-weight: 600; white-space: nowrap;
  flex: 0 0 auto; width: 7.6em;
}
/* The map's own red, as the train tools' scrubber has: a browser left to
   itself paints a range control green here and blue in the Layers pane, which
   is two colours doing one job in one interface. */
.air-slider { flex: 1 1 auto; min-width: 90px; accent-color: var(--accent); }
.air-speed { font-size: 12px; padding: 3px 4px; flex: 0 0 auto; }
.air-count {
  font-size: 11.5px; color: var(--muted); white-space: nowrap;
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto; width: 6.6em; text-align: right;
}
/* The aeroplane. Filled rather than stroked, because a 4 px outline at this
   size fills in, and given a pale casing for the same reason the railway lines
   have one: it crosses land of every colour the reader can choose. */
#planes { pointer-events: none; }
.plane-body {
  fill: var(--ink);
  stroke: var(--panel);
  stroke-width: 1.1;
  paint-order: stroke;
}
/* **The drawn Fokker: one silhouette, drawn twice to get it.**
   It arrives filled white, which is invisible over half the palettes here —
   and inked dark it disappeared into Japan and Korea, the two countries its
   one route crosses. So it keeps a pale body and takes a dark rim.

   Two layers, because thirteen separate parts each carrying their own outline
   drew every seam inside the aeroplane: `.plane-case` is the same shapes
   filled *and* stroked in ink, which fuses them into one fat blob, and
   `.plane-body-fill` is the same shapes filled flat on top with no stroke, so
   neighbours meet without a line. All that shows of the blob is the rim. The
   stroke is in the artwork's own units, scaled down by about two, so 5 here is
   roughly two pixels on the screen. */
/* The rim, in the artwork's own units — which differ by type, the Fokker
   being 64 units across and the Nakajima 400, so each says its own. */
.plane-case > * {
  fill: var(--ink);
  stroke: var(--ink);
  stroke-linejoin: round;
}
.plane-e1930 .plane-case > * { stroke-width: 5; }
.plane-e1942 .plane-case > * { stroke-width: 32; }
.plane-body-fill > * { fill: var(--panel); stroke: none; }

@media (max-width: 620px) {
  .air-count { display: none; }
  .air-clock { width: 6.8em; font-size: 12px; }
}
/* And where an open card and the strip cannot both fit across the foot of the
   map, the strip stands down — the same answer the train tools' strip gives,
   and for the same reason. The clock does not stop: the aeroplanes go on
   flying behind the card, which is the point of reading one while the day
   plays. It comes back the moment the card is closed. */
@media (max-width: 820px) {
  body.panel-open .air-bar { display: none; }
}

/* The press target under an aeroplane: invisible, and inside the counter-scaled
   group so it stays a finger's width at every zoom. */
.plane-hit { fill: transparent; stroke: none; }
#planes .plane { cursor: pointer; }
/* One aeroplane's circuit, with the leg it is flying now marked and the ones
   behind it faded — a reader pressing it over the Yellow Sea is asking where it
   came from as much as where it is going. */
.air-legs li.done { opacity: 0.5; }
.air-legs li.now::before { background: var(--accent); }
.air-legs li.now .air-place { font-weight: 600; }


/* ---------------------------------------- the header, folded out of the way --
 *
 * **A map on a phone is mostly not map.** Two rows of buttons, the browser's
 * own furniture top and bottom, and what is left for the thing the reader
 * came for is a strip. So the whole bar folds: the tab stays, everything else
 * goes, and the map gets the height back.
 *
 * The tab is the fold control itself rather than a separate button — pressing
 * it either way — so there is never a moment when the bar is away and nothing
 * says how to bring it back. It is in the flow at the bar's right, so on a
 * wide screen where the bar was never a problem it is a small chevron beside
 * the ? and costs one button's width.
 */
#btn-bar-fold {
  margin-left: auto;
  min-width: 0;
  padding: 6px 9px;
  line-height: 1;
}
#btn-bar-fold .fold-mark { display: inline-block; transition: transform .15s ease; }
body.bar-folded #btn-bar-fold .fold-mark { transform: rotate(180deg); }

/* Folded: every child but the tab goes, and the bar shrinks to the tab's own
   height. `visibility` is not enough — a hidden row still takes its line. */
body.bar-folded #bar > *:not(#btn-bar-fold) { display: none; }
body.bar-folded #bar {
  padding-top: 2px;
  padding-bottom: 2px;
  gap: 0;
  justify-content: flex-end;
  min-height: 0;
}
