/* 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;
  --accent: #9a1813;
  --accent-soft: #f6e2df;

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

  --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);
}

* { 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: #f2ede2; }
#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: #fff;
}

/* `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: #f2ede2; 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: #fff;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  padding: 7px 12px;
  min-height: 38px;
  cursor: pointer;
}

button.plain:hover { background: #f2ede2; }

/* 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: #fff;
  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 {
  fill: 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. */
#jmap.backs-off #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. */
.atom.hot { filter: brightness(1.12); }
/* 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 { filter: brightness(1.12); }
#sub-outlines .edge-line.hot { filter: brightness(1.12); }

/* the province under the pointer, picked out inside the lit country */
.atom .prov-hot { filter: brightness(1.06); }

/* 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;
}

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

#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;
}

/* 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: rgba(40, 60, 80, .20);
  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: #fffdf8;
  stroke-width: 3px;
  stroke-linejoin: round;
  fill: #6a7a88;
  dominant-baseline: middle;
}

@media (prefers-color-scheme: dark) {
  #graticule .grat-line { stroke: rgba(210, 225, 240, .22); }
  #grat-labels .grat-label { stroke: #10161c; fill: #93a6b6; }
}

/* Gordon's greatest-extent perimeter */
#extent-1942 {
  fill: none;
  stroke: #8c3a44;
  vector-effect: non-scaling-stroke;
  stroke-width: 1.8;
  stroke-dasharray: 9 6;
  stroke-linejoin: round;
  stroke-linecap: round;
  pointer-events: none;
  opacity: .55;
}

/* 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;
}

/* browse cities: context, drawn under everything examinable */
.browse { pointer-events: auto; cursor: pointer; }
.browse .hit { fill: transparent; stroke: none; }
.browse .dot { fill: #6f6a60; stroke: #f2efe7; stroke-width: 1.1; }
.browse:hover .dot { fill: #3c382f; stroke: #14110c; stroke-width: 1.6; }
.browse.sel .dot { stroke: #14110c; stroke-width: 2.2; paint-order: stroke; }

#legend .sw.browse-sw { background: #6f6a60; }

.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; }
.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;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: rgba(255, 253, 248, .94);
  color: var(--ink);
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow);
}

#zoom-controls button:hover { background: #fff; }

#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 */
  pointer-events: none;
  padding: 8px 10px;
  background: rgba(255, 253, 248, .94);
  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); }

/* 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;
  padding: 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; }

/* One line for a first-time visitor, gone as soon as they touch anything. */
#hint {
  position: absolute;
  left: 50%;
  top: 12px;
  transform: translateX(-50%);
  z-index: 12;
  margin: 0;
  max-width: min(92%, 460px);
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(35, 32, 27, .88);
  color: #fdfbf6;
  font-size: 13.5px;
  text-align: center;
  box-shadow: var(--shadow);
  /* it must not stand between a finger and the map: the first touch anywhere
     dismisses it, so it never needs to be hit itself */
  pointer-events: 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;
  padding: 12px 14px;
  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. */
#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);
}

#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: #fff;
  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; }
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 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"] {
  width: 18px;
  height: 18px;
  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. */
.seg.toggles button.on { background: var(--ink); }
.seg.toggles button.on:hover { background: #3a352c; }

/* 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 --- */

/* 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;
    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: 6px 8px; padding: 6px 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 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 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) .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 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 { filter: brightness(1.12); }

/* 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. */
.atom.hot > path.foreign-sub:not(.hot) { filter: brightness(.8929); }

/* 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 { filter: brightness(1.18) saturate(.62); }
#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 { filter: brightness(.86) saturate(1.25); }

#jmap.admin-on .atom.subs > path[data-prov]:not(.fine) {
  stroke: 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; }
  #bar {
    gap: 5px;
    padding: 5px 6px;
    flex-wrap: nowrap;          /* one row, and it must stay one row */
    overflow-x: auto;
    scrollbar-width: none;
  }
  #bar::-webkit-scrollbar { display: none; }
  #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;
}

/* 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: #4a6b83;
  stroke: rgba(255, 255, 255, .55);
  stroke-width: 2.4;
  opacity: .82;
}

.flabel.f-land {
  fill: #6b5a45;
  stroke: rgba(255, 255, 255, .5);
  stroke-width: 2.4;
  opacity: .72;
}

@media (prefers-color-scheme: dark) {
  .flabel.f-sea { fill: #93b4cc; stroke: rgba(0, 0, 0, .45); }
  .flabel.f-land { fill: #c2ac8e; stroke: rgba(0, 0, 0, .45); }
}
