/* Azulejo: cold white ground, cobalt drawing, one madder red accent for the
   path being followed. Every colour is a token here; nothing else in the
   codebase names a hex value. */

/* The two typefaces, served from this repository and from nowhere else: no
   CDN, no Google Fonts, nobody else told which reader opened which page.
   src/fonts/README.md says what they are, where each file came from and
   which licence it carries.

   EB Garamond for anything the atlas says — titles, summaries, the argument
   under a link — because it is a revival of a roman cut in the 1540s, inside
   the period this project exists to describe. Public Sans for anything the
   interface says — labels, axes, counts, controls — because a humanist
   old-style at eleven pixels on a timeline axis is unreadable and a
   grotesque is not.

   `swap`: the text is readable in the fallback while 425 KB of Garamond
   arrives, and the fallbacks are metrically close enough that the reflow is
   not a lurch. */
@font-face {
  font-family: "EB Garamond";
  src: url("fonts/EBGaramond-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "EB Garamond";
  src: url("fonts/EBGaramond-Italic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("fonts/PublicSans-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("fonts/PublicSans-Italic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("fonts/PublicSans-SemiBold.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}

:root {
  --ground: #f4f7fb;
  --paper: #ffffff;
  --land: #e6ecf5;
  --cobalt: #1f3f8f;
  --cobalt-soft: #798fbf;
  --cobalt-faint: #d5deef;
  --madder: #a8323e;
  --madder-faint: #f3dfe1;
  --ink: #1c2230;
  --ink-soft: #535b6b;
  --line: #c6d1e4;

  /* Two of these moved in M19, for contrast and not for taste. --ink-soft
     was #5c6577, which is 4.33:1 on --cobalt-faint — under AA — and
     cobalt-faint is what a search result and a queue row are highlighted
     with; it is now 5.05:1 there and 6.84:1 on paper. --cobalt-soft was
     #7f97c9, which is 2.93:1 on paper, under the 3:1 that WCAG asks of a
     line somebody has to see: it is now 3.22:1, and it draws the graph's
     edges. tests/contrast.test.mjs holds the whole table and fails if any
     of it slips. */

  /* The eight territory hues. Which actor gets which is data —
     data/geo/palette.json, from tools/build-palette.mjs — and hue means
     "not the same as the one next to it", never "this kind of state": there
     is no legend by colour and about.html says why.

     Chosen in OKLCH rather than by eye, so the family holds together and the
     difference between any two is a measured quantity rather than an
     opinion. Eight hue angles — 40°, 80°, 122°, 160°, 202°, 241°, 296°,
     347° — spread to maximise the smallest difference between any two once
     each fill is composited at the opacity it is actually drawn with over
     the land token, and kept 22° clear of cobalt (264°) and madder (19°),
     which are the two colours the map must never confuse a territory with.
     Chroma is one number for all eight (0.088 for a fill, 0.055 for a
     dependency tint, 0.105 for an outline), and lightness ALTERNATES around
     the circle — 0.825 and 0.745 — because two neighbouring hues at one
     lightness are two pale washes a small country cannot carry the
     difference between. Alternating it doubles the smallest separation
     without leaving the family: Portugal and Spain share the longest border
     on this map, and at one lightness they were 0.036 apart and read as one
     colour at the size Portugal is drawn.

     What that buys, in OKLab distance between the washes as drawn: 0.060
     between the two closest fills and 0.049 between the two closest tints,
     against the ~0.02 at which a large flat field stops being tellable from
     its neighbour; 0.43 from cobalt and 0.34 from madder, so the selected
     actor and the walked chain still own the loudest marks on the screen;
     0.087 from the land token, so a territory is visible on empty ground.
     A mark keeps 5.4:1 over the worst wash, its label 8.9:1 and the walked
     chain 3.7:1; the outlines are dark enough to be borders on their own, at
     4.6:1 against paper at worst. tests/palette.test.mjs and
     tests/contrast.test.mjs check all of it and fail when a token slips.

     The tokens are not listed in order round the circle. The colouring hands
     out the least-used hue among those free, so it works through them
     roughly in order and two territories next to each other in this list are
     often next to each other on the map; the order below is the one whose
     smallest step from each token to the next is largest — 0.107, nearly
     twice the worst pair anywhere in the palette. */
  --terr-1: #f8b39b;  --terr-1-tint: #fed1c1;  --terr-1-line: #a3593f;
  --terr-2: #7cd7de;  --terr-2-tint: #b2e8ec;  --terr-2-line: #00818a;
  --terr-3: #caa66b;  --terr-3-tint: #d5bf9b;  --terr-3-line: #785100;
  --terr-4: #cabbf9;  --terr-4-tint: #dfd6fe;  --terr-4-line: #7662a6;
  --terr-5: #79be99;  --terr-5-tint: #a4ceb6;  --terr-5-line: #016b44;
  --terr-6: #d696b7;  --terr-6-tint: #deb5c9;  --terr-6-line: #823e65;
  --terr-7: #bccf8f;  --terr-7-tint: #d6e3bb;  --terr-7-line: #66792d;
  --terr-8: #78b4df;  --terr-8-tint: #a3c8e3;  --terr-8-line: #095f8e;

  --font: "EB Garamond", Georgia, "Times New Roman", serif;
  --font-ui: "Public Sans", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* The type scale. Seven sizes and no others, on a ratio near 1.2, in rem
     so that a reader who has set their browser to 20px gets a bigger atlas
     rather than the same one. Nothing outside this block names a font-size
     in pixels except the SVG text the views draw, which is measured in the
     same user units as the geometry beside it and cannot be in rem. */
  --text-xs: 0.6875rem;    /* 11px: badges, axis labels, an aside on a row */
  --text-sm: 0.8125rem;    /* 13px: meta, hints, counts */
  --text-base: 0.9375rem;  /* 15px: the interface */
  --text-md: 1.0625rem;    /* 17px: prose, set in the serif */
  --text-lg: 1.25rem;      /* 20px: a section head */
  --text-xl: 1.5rem;       /* 24px: a card's title */
  --text-2xl: 1.9375rem;   /* 31px: the masthead */
  --leading-tight: 1.25;
  --leading: 1.5;
  --leading-prose: 1.62;
  /* A caps-and-tracking label; used often enough to be one decision. */
  --label: var(--text-xs);
  --tracking-label: 0.08em;

  /* Spacing, on the same footing as the type: hierarchy in this panel is
     made of space and rules rather than of boxes inside boxes. */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2.25rem;
  /* Where a paragraph is allowed to stop. Around 68 characters of Garamond,
     which is the width a line stops being followable at. */
  --measure: 34rem;

  --radius: 3px;
  --focus-width: 2px;
}

* { box-sizing: border-box; }

html {
  /* Not a pixel size: 100% is whatever the reader has told their browser
     they can read, and every size above is relative to it. */
  font-size: 100%;
  height: 100%;
}

body {
  margin: 0;
  height: 100%;
  /* The atlas fills the viewport: masthead, then the three views. A column
     rather than the old calc() against a header of a known height, which the
     masthead stopped being the moment it could wrap. */
  display: flex;
  flex-direction: column;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: var(--leading);
  /* Dates, counts and years line up in a column everywhere, which is most of
     what the interface is made of; the serif prose turns it off again. */
  font-variant-numeric: tabular-nums;
}

button { font: inherit; }

/* Checkboxes and the like are the browser's, and this is the one line that
   makes them the atlas's: the tick is cobalt like every other "this is on". */
input, select, textarea { accent-color: var(--cobalt); }

/* One focus ring, everywhere, and it is visible. :focus-visible so that a
   mouse click does not leave a ring behind but the keyboard always does. */
:focus-visible {
  outline: var(--focus-width) solid var(--cobalt);
  outline-offset: 1px;
}

/* Nothing moves for a reader who has asked for less motion. The map's zoom
   already checks this in JavaScript, because it animates a transform frame by
   frame; this covers everything CSS does. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --- the masthead ----------------------------------------------------- */

/* Every page wears the same bar, and it carries the one azulejo touch in the
   whole interface: a tile band along its lower edge instead of a hairline.
   Drawn as a mask rather than as an image or as markup, so the shape is SVG,
   the colour is a token like every other colour, and the five pages share one
   copy of it instead of pasting the same twenty lines into each. It is a
   band, not a wallpaper: eight pixels, once, at the join. */
.bar {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4) calc(var(--space-3) + 8px);
  background: var(--paper);
  position: relative;
}
.bar::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 8px;
  background-color: var(--cobalt-soft);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='8' viewBox='0 0 18 8'%3E%3Cpath d='M9 0.8 13.4 4 9 7.2 4.6 4Z'/%3E%3Crect y='7.4' width='18' height='0.6'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='8' viewBox='0 0 18 8'%3E%3Cpath d='M9 0.8 13.4 4 9 7.2 4.6 4Z'/%3E%3Crect y='7.4' width='18' height='0.6'/%3E%3C/svg%3E");
  -webkit-mask-size: 18px 8px;
  mask-size: 18px 8px;
  -webkit-mask-repeat: repeat-x;
  mask-repeat: repeat-x;
  opacity: 0.55;
  pointer-events: none;
}
.bar h1 {
  margin: 0; font-family: var(--font); font-size: var(--text-2xl); color: var(--cobalt);
  font-weight: normal; line-height: var(--leading-tight); letter-spacing: 0.005em;
}
.bar .tagline { margin: 0; color: var(--ink-soft); flex: 1; font-size: var(--text-sm); }
/* The graph's two filters sit exactly where the layer switches do and are
   drawn exactly like them: they are the same kind of control — what is drawn
   at all — for the other view, and one of the two is always hidden
   (src/graph-filters.js, main.js). No colour, no size and no token of their
   own; the row is the row. */
/* And the category switches, which since M68 are a masthead control of their
   own and not a group inside the legend: the same kind of control again — what
   is drawn at all — for all three views at once, so it is the same row with
   nothing of its own (src/category-control.js). */
.bar .layers, .bar .graph-filters, .bar .categories-control { display: flex; gap: var(--space-3); color: var(--ink-soft); font-size: var(--text-sm); }
.bar .layers label, .bar .graph-filters label, .bar .categories-control label { display: inline-flex; align-items: center; gap: 0.3em; }
.bar .graph-filters select { font: inherit; color: inherit; background: var(--paper); border: 1px solid var(--line); }
/* `display: flex` above beats the UA stylesheet's rule for [hidden], exactly as
   it does for the window control's own count below. Both groups are hidden by
   `main.js` when another view is on — the layer
   switches are the map's legend and the degree floor is the graph's — and
   without this neither of them ever went away: the layer switches have been on
   the graph since the graph had them, which is what taking M48's screenshot
   showed (deviation 734). */
.bar .layers[hidden], .bar .graph-filters[hidden] { display: none; }
/* The degree floor while an event is open (M86 §8). It is off by rule inside a
   lens, so it is drawn as off rather than left looking live: the same ink the
   interface uses for what it is not asserting. */
.bar .graph-filters select:disabled { color: var(--ink-soft); border-color: var(--line); opacity: 0.6; cursor: not-allowed; }
/* The two collapsed groups — the base map, in the legend, and the categories,
   which are a control of their own since M68. Collapsed, so the phone drawer
   keeps four targets instead of nineteen, and every row carries its own symbol
   or its own swatch at the row's text size — the reader learns the shapes and
   the colours here and nowhere else (glyphs-brief §4; M37 brief §3). Drawn
   exactly alike because they are the same thing twice: a name, a list under
   it, and nothing of its own. */
.bar .layers details, .bar .categories-control details { position: relative; }
.bar .layers summary, .bar .categories-control summary { cursor: pointer; list-style: none; }
.bar .layers summary::-webkit-details-marker,
.bar .categories-control summary::-webkit-details-marker { display: none; }
.bar .layers summary::after, .bar .categories-control summary::after { content: ' \25BE'; }
.bar .layers details[open] summary::after,
.bar .categories-control details[open] summary::after { content: ' \25B4'; }
.bar .categories-control .categories, .bar .layers .base-layers {
  display: flex; flex-direction: column; gap: var(--space-1);
  padding: var(--space-2); margin-top: var(--space-1);
  background: var(--paper); border: 1px solid var(--line);
  position: absolute; z-index: 5; min-width: 14ch;
}
.bar .categories-control .glyph { width: 1em; height: 1em; flex: 0 0 auto; color: var(--cobalt); }
/* A swatch is what a base layer looks like on the map, at the row's own text
   size: a line for the rivers, a filled ring for the lakes, a dashed line for
   the physical regions, and a dot the size the layer draws for the peaks and
   the cities. Every colour is the one the map itself uses, from the same
   token — the swatch and the layer cannot come apart, because there is one
   value and it is up there at `:root`. The coastlines have no row and so no
   swatch: they are always drawn (plan decision 14). */
.bar .layers .swatch { width: 1em; height: 1em; flex: 0 0 auto; align-self: center; }
/* The bands are a ramp and not a colour, so their swatch is the ramp: the same
   token the map draws them in, lightest at the foot and darkest at the head,
   which is the order the five heights are in. */
.bar .layers .swatch-relief { background: linear-gradient(to top, var(--land), var(--ink-soft)); opacity: 0.55; }
.bar .layers .swatch-rivers { height: 0.5em; border-bottom: 2px solid var(--cobalt-soft); }
.bar .layers .swatch-lakes { background: var(--cobalt-faint); border: 1px solid var(--cobalt-soft); }
.bar .layers .swatch-physical { height: 0.5em; border-bottom: 1px dashed var(--line); }
.bar .layers .swatch-mountains {
  width: 0.5em; height: 0.5em; margin: 0 0.25em; border-radius: 50%; background: var(--ink-soft);
}
.bar .layers .swatch-cities {
  width: 0.7em; height: 0.7em; margin: 0 0.15em; border-radius: 50%;
  background: var(--ink-soft); box-shadow: 0 0 0 1.5px var(--paper);
}

/* The atlas's own masthead: name, then the controls that change what is
   drawn, then the ways out. On a narrow screen the three zones stack in that
   order, which is also their order of importance. */
.masthead { flex-wrap: wrap; align-items: flex-end; row-gap: var(--space-2); }
/* The name and what the atlas is for, stacked: the tagline belongs under the
   title the way a subtitle does, not beside it competing for the same line. */
.masthead-name { flex: 0 0 auto; max-width: 30ch; }
.masthead-name .tagline { margin-top: 0.1em; line-height: var(--leading-tight); }
.masthead-tools { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; flex: 1; }
.masthead-links { display: flex; align-items: baseline; gap: var(--space-3); margin-left: auto; }
/* Map | Graph. Two ways of drawing the same records, so the control is a
   pair of buttons and not a menu of unrelated things. */
.bar .views { display: flex; }
.bar .views button {
  border: 1px solid var(--cobalt); background: var(--paper); color: var(--cobalt);
  padding: 0.2rem 0.7rem; cursor: pointer; font: inherit; font-size: var(--text-sm);
  letter-spacing: 0.01em;
}
.bar .views button:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.bar .views button:last-child { border-radius: 0 var(--radius) var(--radius) 0; border-left: 0; }
.bar .views button:hover { background: var(--cobalt-faint); }
.bar .views button[aria-pressed="true"] { background: var(--cobalt); color: var(--paper); font-weight: 600; }
.bar .views button[aria-pressed="true"]:hover { background: var(--cobalt); }

/* What the map is looking at, and how much of it anybody has read. The two
   number fields and the density hint that stood here went with M76 — the
   owner, 21 September: *"Picking up the dates exactly is unnecessary"* — so
   what is left is two lines of text and a pin. It sits in the row with the
   layer switches and the graph filters and is drawn exactly like them: the
   same text size, the same soft ink. Nothing here is a new token, a new hex
   value or a new size. */
/* A row of its own in the masthead (M83, B3). The count is said from first
   paint now, not only once the reader has moved the map, and the note beside it
   grows and shrinks with the window — so as one more item in a wrapping row it
   decided where everything after it wrapped, and the masthead changed height
   whenever the window changed. The masthead's height is the pane's height, and
   the pane is what the graph's camera is fitted to: the window was moving the
   graph's picture by the back door, which is the one thing M76 promised it
   would never do. A row of its own cannot re-wrap what is not on it. */
.bar .window-control {
  display: flex; align-items: center; gap: var(--space-2);
  color: var(--ink-soft); font-size: var(--text-sm);
  flex: 1 0 100%;
}
.bar .window-control[hidden] { display: none; }
/* "N of N events in view", and the pin that gives the world back: the line
   that used to sit above the lanes, on every view now. */
.bar .window-view { display: flex; gap: var(--space-2); align-items: baseline; margin: 0; }
.bar .window-view[hidden] { display: none; }
.bar .window-view .pin {
  background: none; border: 0; padding: 0; font: inherit;
  color: var(--cobalt); text-decoration: underline; text-underline-offset: 0.18em; cursor: pointer;
}
.bar .window-view .pin:hover { color: var(--ink); }
/* "N of N read": how much of what is on screen a person has actually read
   (M70). The soft ink the interface uses for what it says about itself, and
   no colour of its own — a picture that coloured the unread would be a filter
   in everything but name, and nothing about standing changes what is drawn. */
/* One line, whether or not there is anything on it (M83, B3): the unplaced
   count is nothing when every event of the window has a place, and a row that
   collapsed when its text did would change the height of the row above, for the
   same reason the row itself is full-width. `1lh` is one line of this row's own
   text and not a size of its own. */
.bar .window-standing {
  display: flex; align-items: baseline; gap: var(--space-2); margin: 0; min-height: 1lh;
}
.bar .window-read { color: var(--ink-soft); font-size: var(--text-xs); font-family: var(--font-ui); }
/* And beside it, how many of the events in the window the map has no point for
   (M82, A5). It was a full sentence in a box on the map, on every visit; it is
   a fact about the window, so it is said where the window's other fact is, in
   the same soft ink and with no colour of its own — the events it counts are
   drawn exactly as the rest are, on the two pictures that can draw them. */
.bar .window-unplaced { color: var(--ink-soft); font-size: var(--text-xs); font-family: var(--font-ui); }
.bar .window-unplaced:empty { display: none; }

/* The lens chips in the header. The grouping picker stood here until M77,
   with a panel of lanes to tick and reorder; it and its rules are gone. */
.bar .lens-chips-bar { position: relative; display: flex; align-items: baseline; gap: 0.5rem; }
/* The lens, said in the header: what is on and the way out of it. Cobalt,
   never madder — madder is the path being walked and nothing else. */
.lens-badge {
  display: inline-flex; align-items: baseline; gap: 0.35rem;
  padding: 0.1rem 0.5rem; border: 1px solid var(--cobalt); border-radius: var(--radius);
  background: var(--cobalt-faint); color: var(--cobalt);
}
.lens-badge .lens-kind { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-label); }
.lens-badge .lens-name { max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* And what the lens is showing beside what it is a lens on (M89 §10): the same
   size as the kind, set quietly, because it is the chip's second sentence and
   not its name. No colour of its own — the chip is cobalt and this is inside
   it. */
.lens-badge .lens-count { font-size: var(--text-xs); opacity: 0.75; white-space: nowrap; }
.lens-control.on { font-weight: 600; }
/* One chip per focus since H7, each with its own ×, then the two controls
   that belong to the whole list: "all of these" and "show everything". A
   reader holding four foci has to be able to see which four and drop one. */
.lens-chips { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem; }
.lens-chips .lens-badge { gap: 0.3rem; }
.lens-chips .lens-drop {
  border: 0; background: none; padding: 0 0.1rem; cursor: pointer;
  color: var(--cobalt); font-size: var(--text-sm); line-height: 1;
}
.lens-chips .lens-drop:hover { color: var(--madder); }
.lens-chips .lens-all[aria-pressed="true"] { font-weight: 600; color: var(--cobalt); }
/* The events one hop out from the focus set: in the picture, so that a
   neighbourhood does not read as an atlas in which nothing else happened.

   **A colour and no longer a fade** (M84). They were drawn at 0.22 and the
   owner, 22 September: *"Regarding direct connections to events that are shown
   dimmed out, they are easily confused, I suggest showing them in a different
   colour."* Which is right: opacity is the atlas's word for "outside the band"
   (`faded`) and for "further from the horizon year" (`in-horizon`), and a
   third thing said in the same word said nothing — a direct consequence of the
   event on screen looked like an event the window had taken away.

   So the ring is **`--terr-5-line`, and no new token**. It is an ink: a colour
   the palette already trusts a one-pixel line to, at 6.6:1 on paper and 5.5:1
   on land. It is the furthest of the eight from both of the colours this
   picture already spends — 105° from cobalt and 141° from madder in OKLCH, the
   largest smallest-separation of any token here — so it is neither the atlas's
   ordinary drawing nor the walked chain at a glance. Nothing in the atlas reads
   as "connected, not chosen" on its own and `STATUS.md` says so; this is the
   closest, and the key on each view names it rather than leaving it to be
   guessed. The eight hues mean only "not the one beside it" (about.html), so
   borrowing one for a mark takes no meaning away from a territory. */
   **The ink itself is in each picture's own block below and not here**, where
   the fade was. A fade is one declaration that beats nothing; an ink has to
   stand in the order the other inks stand in — after "this is placed no better
   than a region", after "this is one of the open actor's", after "this is a
   branch that converged", and *before* the selection and the walked chain,
   which are the reader's own and say so last. So each view states it where it
   spends its emphasis, and what is said once here is the thing the three have
   in common: the ring is not faded any more. */
.map .mark.lens-near, .graph .node.lens-near, .timeline .bar.lens-near { opacity: 1; }
/* And the lines between them (M77): with a lens on, the lens's own links are
   the ones in ink and everything else is there to be found. The arrowhead
   goes with its line, as it does for every other class they share. */
.graph .edge.lens-near, .graph .edge-head.lens-near { opacity: 0.16; }
.graph .edge.lens-near.chain, .graph .edge-head.lens-near.chain,
.graph .edge.lens-near.converging, .graph .edge-head.lens-near.converging { opacity: 0.6; }
/* And where the reader's own selection or walk falls inside the ring — which
   an explicit `?focus=` allows — the selection wins the ink, as it wins it over
   every other class: madder is what the reader is holding and says so last.
   There is nothing left to undim, so the pair of opacities that used to stand
   here went with the fade. */
/* A search result the lens does not keep: still listed, plainly marked, and
   still choosable — the lens narrows what is drawn, never what is found. */
.search-option.outside-lens .search-label { color: var(--ink-soft); }
.search-option .badge.outside {
  font-size: var(--text-xs); padding: 0 0.3rem; border: 1px solid var(--line);
  border-radius: 2px; color: var(--ink-soft);
}
/* Where an event is drawn, and why: the rule stated on the card. */

/* Search: an input in the header and its results under it. The list is
   absolutely positioned so the header does not grow as you type. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.search { position: relative; }
.search-input {
  font: inherit; font-size: var(--text-sm); width: 24ch; padding: 0.25rem 0.55rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper); color: var(--ink);
}
.search-input:hover { border-color: var(--cobalt-soft); }
.search-input:focus { outline: var(--focus-width) solid var(--cobalt); outline-offset: 1px; }
.search-results {
  position: absolute; z-index: 5; top: calc(100% + 4px); left: 0;
  min-width: 30ch; max-width: 48ch; max-height: 60vh; overflow-y: auto;
  margin: 0; padding: 0.25rem 0; list-style: none;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 2px 8px var(--cobalt-faint);
}
.search-group {
  padding: 0.25rem 0.6rem; font-size: var(--text-xs); text-transform: uppercase;
  letter-spacing: var(--tracking-label); color: var(--ink-soft);
}
.search-option { padding: 0.25rem 0.6rem; cursor: pointer; display: flex; gap: 0.4rem; align-items: baseline; flex-wrap: wrap; }
.search-option.active { background: var(--cobalt-faint); }
.search-option .search-label { color: var(--ink); }
.search-option .when { color: var(--ink-soft); font-size: var(--text-sm); }
.search-empty { padding: 0.35rem 0.6rem; color: var(--ink-soft); }
kbd { font-family: var(--font-ui); font-size: 0.9em; padding: 0 0.3em; border: 1px solid var(--line); border-radius: var(--radius); background: var(--ground); }
.bar-link { color: var(--cobalt); font-size: var(--text-sm); text-underline-offset: 0.18em; }
button.bar-link { background: none; border: 0; padding: 0; font: inherit; font-size: var(--text-sm); text-decoration: underline; cursor: pointer; }
.bar-link:hover { color: var(--ink); }
.fixtures-badge {
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--madder);
  color: var(--madder);
  border-radius: var(--radius);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

/* The one size a reader can drag is a custom property with the layout's own
   value as its fallback, so panes.js sets one number and removes it again for
   the default — and nothing here has to know a pixel it did not choose.

   One row, since M60: the timeline used to have a row of its own along the
   bottom, 30vh of every screen whether the reader was reading it or not. It
   is a view now and takes the same slot the map and the graph take, so the
   picture that is up is as tall as the layout. */
.layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--handle) var(--panel-width, minmax(300px, 33%));
  grid-template-rows: minmax(0, 1fr);
  grid-template-areas: "map split-panel panel";
  flex: 1 1 auto;
  min-height: 0;
  --handle: 6px;
}
.map-area { grid-area: map; position: relative; min-height: 0; background: var(--ground); }
/* The graph view takes the map's slot; one of the three is hidden at a time. */
.graph-area { grid-area: map; position: relative; min-height: 0; background: var(--ground); }
.panel { grid-area: panel; overflow: auto; padding: 1rem; border-left: 1px solid var(--line); background: var(--paper); }
/* The pane is the height the grid row gives it, and the drawing is laid out
   into that height (timeline.js). Past the point where a lane cannot be
   squeezed any further the drawing is taller than the pane and this scrolls,
   which is why `min-height: 0` is here: without it a grid item refuses to be
   smaller than its content and the scroll would land on the page instead.

   The same slot as the map and the graph since M60, and the whole of it: the
   lanes have the height the strip never had, which is what the drawing was
   squeezing them into. */
/* And `scrollbar-gutter: stable`, since M83 (B11). The rows are as many as the
   titles need, and the titles need more rows the narrower the drawing is — so a
   drawing one row too tall for its pane raised a scrollbar, the scrollbar took
   a dozen pixels of width, the narrower drawing needed another row, and the
   count depended on whether the pane happened to be scrolling. It only showed
   once the 120 px left gutter went back to the drawing and the packing came
   near that boundary, but the loop was always there. Room kept for the bar
   whether or not it is shown is room the packing can count on. */
.timeline-area {
  grid-area: map; background: var(--paper);
  overflow: auto; min-height: 0; scrollbar-gutter: stable;
}
/* An `<svg>` is inline by default, so the line box under it added a
   descender's worth of height the drawing knew nothing about — enough to make
   the pane scroll by a few pixels when the lanes had just been laid out to
   fit it exactly. */
.timeline-area .timeline { display: block; }

/* The edge itself. Six pixels of ground with a grab line down the
   middle: enough to hit with a pointer, quiet enough not to read as a
   border. */
.split {
  background: var(--ground); border: 0; padding: 0;
  position: relative; z-index: 2;
}
.split::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  background: var(--line);
}
.split-panel { grid-area: split-panel; cursor: col-resize; }
.split-panel::after { width: 1px; height: 2rem; }
.split:hover::after, .split:focus-visible::after, .split.dragging::after { background: var(--cobalt); }
.split:focus-visible { outline: 2px solid var(--cobalt); outline-offset: -2px; }
.split.dragging { background: var(--cobalt-faint); }

/* With nothing open there is no panel. A third of the width spent on "Pick an
   event" beside the picture is a third of the width saying nothing, and the
   picture is what a reader who has opened nothing came for. The column and
   its edge collapse to nothing and the view takes the room; opening anything
   brings them back, at whatever width the reader had dragged the panel to,
   because that preference applies only while the panel is shown (owner,
   5 September). main.js sets the class from what the panel has to show.

   Not on a phone: there the panel is a sheet over the view and it is already
   down to its grip when nothing is open (phone.js).

   The change is not animated. A transition on `grid-template-columns` runs
   on the first paint too — the panel would flash open and shut on every
   load — and every frame of it resizes the map pane, which the map's own
   ResizeObserver answers with a full redraw. The panel arrives at once. */
body:not(.phone) .layout.panel-empty {
  grid-template-columns: minmax(0, 1fr) 0 0;
}
/* The panel's parent is the sheet's wrapper, which is `display: contents`
   here, so it is reached by descent and not as a child. */
body:not(.phone) .layout.panel-empty .panel,
body:not(.phone) .layout.panel-empty > .split-panel { display: none; }

/* --- the composer (M71) ----------------------------------------------- */

/* A fourth column, opened by the button in the masthead and closed by the one
   in its own head. A column and not an overlay, for one reason: while it is
   open the reader goes on clicking events on the picture, and a panel that
   covered the picture would be asking them to click what it is hiding. The
   view gives up the width, the card keeps its own, and closing gives it all
   back. Nothing below the button exists until the composer is opened, so the
   default state of this rule is the one that costs nothing. */
.layout.composing {
  grid-template-columns: minmax(0, 1fr) var(--handle) var(--panel-width, minmax(300px, 33%)) minmax(320px, 30%);
  grid-template-areas: "map split-panel panel composer";
}
/* With nothing open there is still no panel: the two rules compose, and the
   composer takes the column the panel would have had beside it. */
body:not(.phone) .layout.panel-empty.composing {
  grid-template-columns: minmax(0, 1fr) 0 0 minmax(320px, 34%);
}
.composer {
  grid-area: composer; overflow: auto; min-height: 0;
  padding: var(--space-3) var(--space-4) var(--space-6);
  border-left: 1px solid var(--line); background: var(--paper);
}
.composer[hidden] { display: none; }
.composer.contrib { margin-top: 0; }
.composer .composer-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.composer h2 { font-family: var(--font); font-weight: normal; font-size: var(--text-lg); color: var(--cobalt); margin: 0; }
.composer h3 { font-family: var(--font); font-weight: normal; font-size: var(--text-md); color: var(--cobalt); margin: 0; }
/* The window, beside the heading of the walk it is the span of: a number the
   reader reads and never types. */
.composer .composer-window { font-family: var(--font-ui); font-size: var(--text-sm); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.composer .composer-steps { list-style: none; margin: var(--space-2) 0 0; padding: 0; counter-reset: step; }
.composer .composer-step { border-top: 1px solid var(--line); padding: var(--space-2) 0 var(--space-3); }
.composer .composer-step-head { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.composer .composer-step-head::before {
  counter-increment: step; content: counter(step) ".";
  font-family: var(--font-ui); font-size: var(--text-sm); color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.composer .composer-step-name { font-family: var(--font); font-size: var(--text-md); color: var(--ink); }
/* The id the step actually names, shown as an identifier and never dressed as
   a title: a slug drawn where a name goes is the atlas presenting a derived
   string as the name of the thing (attributes.js). */
.composer .composer-step-ref { font-size: var(--text-xs); color: var(--ink-soft); }
.composer .composer-step-head { grid-column: 1 / -1; }
/* A citation in a column a third the width of the form's: the work's own line
   is what tells two books apart, so it takes the row and the locator sits
   under it rather than squeezing it to a truncated author. */
.composer.contrib .citation-row { flex-wrap: wrap; }
.composer.contrib .citation-row select { flex: 1 0 100%; }

/* The sheet's wrapper is not a box on a desktop: `contents` takes it out of
   the grid and leaves the panel the grid item it has always been. */
.sheet { display: contents; }
.sheet-grip, .options-button { display: none; }

/* --- the phone ------------------------------------------------------- */

/* Under 720px the panel cannot stand beside the view: the panel at the type
   scale's measure and a map worth looking at do not both fit. So the view
   takes the screen — whichever of the three it is, since M60 — and the panel
   becomes a sheet over it, up when a record is open and down to its grip when
   it is in the way. src/phone.js says why none of it is state.

   The timeline used to be a strip of 8.5rem under the view here, which on a
   phone is a fifth of the screen; it is chosen from the masthead now, like
   the other two pictures, and gets the whole of it when it is.

   Not one size a reader dragged on a desktop is named below, which is how
   this layout ignores the resizable panel of M24: --panel-width is set on
   .layout and nothing here reads it. */
@media (max-width: 720px) {
  /* What a thumb can hit, and how much of the screen the grip keeps for
     itself at the bottom edge. */
  :root { --touch: 40px; --sheet-grip: 44px; }

  .layout {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    grid-template-areas: "map";
    /* The peeking grip sits over the bottom edge, so the layout keeps that
       much clear: nothing is permanently underneath it. */
    padding-bottom: var(--sheet-grip);
  }
  .split { display: none; }
  /* The view's own row, and its own scroll: the lanes are laid out into the
     screen the way they are laid out into a pane on a desktop. */
  .timeline-area { height: 100%; }

  .sheet {
    display: flex; flex-direction: column;
    position: fixed; z-index: 6; inset-inline: 0; bottom: 0;
    height: min(68vh, 34rem);
    background: var(--paper);
    border-top: 1px solid var(--line);
    border-radius: var(--radius) var(--radius) 0 0;
    box-shadow: 0 -2px 12px var(--cobalt-faint);
    /* Two lengths and no left or right: --sheet-base is where the state puts
       it, --sheet-drag is the thumb, and a language written right to left
       moves the sheet exactly as this one does. */
    --sheet-base: 0px;
    transform: translateY(calc(var(--sheet-base) + var(--sheet-drag, 0px)));
    transition: transform 180ms ease;
  }
  .sheet.peek { --sheet-base: calc(100% - var(--sheet-grip)); }
  .sheet.dragging { transition: none; }

  .sheet-grip {
    display: flex; align-items: center; justify-content: center; gap: var(--space-2);
    flex: none; width: 100%; height: var(--sheet-grip);
    border: 0; border-bottom: 1px solid var(--line); border-radius: inherit;
    background: var(--paper); color: var(--ink-soft);
    font-family: var(--font-ui); font-size: var(--text-sm); cursor: pointer;
    /* The grip is dragged, so the browser's own scroll must not take the
       gesture first. */
    touch-action: none;
  }
  .sheet-grip:hover { color: var(--cobalt); }
  .sheet-grip-bar { width: 2.5rem; height: 4px; border-radius: 2px; background: var(--line); }
  .sheet-grip[aria-expanded="true"] .sheet-grip-bar { background: var(--cobalt-soft); }
  .panel { border-left: 0; flex: 1 1 auto; min-height: 0; overflow: auto; }

  /* There is no fourth column on a phone, because there is no second one.
     The composer comes down from under the masthead and takes the top half,
     leaving the picture below it: a reader picks a step by clicking an event
     down there, so a composer that covered the screen would be asking them to
     click what it is hiding. `.composing` changes nothing about the grid
     here — the area name it sets on a desktop has no column to land in. */
  .layout.composing { grid-template-areas: "map"; }
  .composer {
    position: fixed; z-index: 6; inset-inline: 0; top: 0;
    max-height: 56vh; border-left: 0; border-bottom: 1px solid var(--line);
    box-shadow: 0 2px 12px var(--cobalt-faint);
  }

  /* The search is the widest thing a reader types into and gets its own
     line; the two view buttons and the Options button share the next one. */
  .masthead { padding-bottom: calc(var(--space-2) + 8px); }
  /* The name and the tagline take the first line and wrap inside it: the
     30ch column they are given on a desktop is wider than the screen. */
  .masthead-name { flex: 1 1 100%; min-width: 0; max-width: none; }
  .masthead-tools { gap: var(--space-2); }
  .masthead-tools .search { flex: 1 0 100%; }
  .masthead-tools .search-input { width: 100%; }
  .options-button {
    display: inline-block;
    border: 1px solid var(--line); background: var(--paper); color: var(--cobalt);
    border-radius: var(--radius); padding: 0 var(--space-3); cursor: pointer;
    font-family: var(--font-ui); font-size: var(--text-sm);
  }
  .options-button[aria-expanded="true"] { border-color: var(--cobalt); background: var(--cobalt-faint); }
  /* Behind the button until it is pressed. `:not([hidden])` so that the map's
     own rule — the layer switches are gone in graph view — still wins. */
  .masthead-tools .lens-chips-bar, .masthead-tools .layers,
  .masthead-tools .categories-control, .masthead-tools .window-control { display: none; }
  .masthead-tools.options-open .lens-chips-bar { display: flex; flex: 1 0 100%; }
  .masthead-tools.options-open .layers:not([hidden]) { display: flex; flex: 1 0 100%; flex-wrap: wrap; }
  /* The categories are in the drawer too, and on every view: they are hidden
     by nothing, so they need no `:not([hidden])` (M68). */
  .masthead-tools.options-open .categories-control { display: flex; flex: 1 0 100%; flex-wrap: wrap; }
  /* The window of time is in the drawer with them: the two ends, the density
     hint and the count of what the map is looking at are the same kind of
     control, and the row outside it is the search and the three views. */
  .masthead-tools.options-open .window-control { display: flex; flex: 1 0 100%; flex-wrap: wrap; }
  /* Inside the drawer the two panels flow rather than floating: an absolutely
     positioned panel over a sheet is a panel half off the screen. Each
     collapsed <details> is one 40 px target the drawer keeps, and opening it
     puts its rows in the drawer's own flow. */
  .bar .layers details, .bar .categories-control details { flex: 1 0 100%; }
  .bar .categories-control .categories, .bar .layers .base-layers {
    position: static; border: 0; padding: 0; background: none;
  }

  /* **The picture gets the screen** (M82, A9). The reviewer, on a 390 px
     phone: *"title, tagline, search, three view buttons, Options, four links
     and '?' take the top 40 %; the map is a 180 px strip."*

     The four ways off this page go behind the button that already holds the
     rest of the masthead on a phone. They are not controls of the picture and
     nobody reaches for `about` before they have looked at anything; that row
     was 55 px of the 260 the masthead took.

     **The sentence under the title stays.** It is the other half of this
     milestone (A3): the one line that says what the atlas is, on the first
     screen, before anything is clicked — and a phone is the first screen a
     funder is likeliest to see it on. Hiding it here would be fixing one
     finding by undoing another.

     The "?" stays out of the drawer too, because it is the way to everything
     the card says, and a reader who cannot find the explanation has nothing
     to press Options *for*.

     Nothing moves in the DOM, so every module still finds the control it
     wired — the same bargain the drawer has made since M31. */
  .masthead-links { flex-wrap: wrap; gap: var(--space-2) var(--space-3); }
  .masthead-links > :not(.intro-button) { display: none; }
  .masthead-tools.options-open ~ .masthead-links > :not([hidden]) { display: inline-block; }

  /* Forty pixels, everywhere a thumb has to land on a control of its own.
     Not on a link inside a sentence — a citation's title, a crumb of the
     breadcrumb — which is set in the line it belongs to and would break the
     sentence if it were given a height of its own. */
  .options-button, .bar .views button, .search-input,
  .panel button:not(.link), .panel .section-toggle, .panel .chips .chip,
  .bar .layers label, .bar .layers summary,
  .bar .categories-control label, .bar .categories-control summary {
    min-height: var(--touch);
  }
  /* The summary is the collapsed group's one target, so it is a target: a
     20 px line with thirteen rows behind it is a thumb's worth of guesswork. */
  .bar .layers summary, .bar .categories-control summary { display: flex; align-items: center; }
  /* The key on a phone: the same one button as everywhere (M83, A1-4), with
     the room a thumb needs and a box that cannot run off the side. */
  .graph-key { max-width: calc(100% - var(--space-3) * 2); }
  .graph-key .graph-key-toggle { min-height: var(--touch); }

  .bar .views button { padding-inline: var(--space-4); }
  .panel .chips .chip { display: inline-flex; align-items: center; }
  .panel .section-toggle { padding-block: var(--space-3); }
}

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

.map { width: 100%; height: 100%; display: block; cursor: grab; touch-action: none; }
.map:active { cursor: grabbing; }
.map .land { fill: var(--land); stroke: var(--cobalt); stroke-width: 0.6; vector-effect: non-scaling-stroke; }
/* O mapa de base: a costa ao perto, os rios, os lagos, as regiões físicas, os
   picos e as cidades (map/layers/base.js). A coisa mais silenciosa da página —
   tudo em fichas que já existem, nenhum valor hexadecimal novo e nenhuma ficha
   nova, e nada disto apanha o rato: um rio não é um controlo, e "um clique no
   mar pousa o que o leitor tinha na mão" tem de continuar a valer por cima de
   um rio, de um lago e de um ponto de cidade.

   A costa de longe fica com o enchimento e perde o traço quando a de perto
   cobre a caixa toda: as duas não coincidem — 0.4° contra 0.015° — e desenhar
   ambas era desenhar duas margens (M36a; emenda A2 do briefing de M37). */
.map .layer-base { pointer-events: none; }
.map .layer-land.near-coast .land { stroke: none; }
/* M45b, the elevation bands. Five heights — 0–200 m, 200–500, 500–1000,
   1000–2000, above 2000 — cut out of the committed grid as polygons
   (tools/import/elevation.mjs) and drawn **under everything else the map
   draws**: this is the one layer with a fill across open land, so the
   coastline, a river, a lake, a desert and every frontier pass over it.

   **One token at five opacities, and no new hex value.** A hypsometric ramp
   is a single quantity said five times, so it is one colour said five times:
   --ink-soft, which is neutral and therefore competes with none of the eight
   territory hues, with cobalt or with madder — a relief map in a colour would
   have been a second palette, and this atlas has a palette. The five are
   spaced to be **equal steps in OKLab over --land**: 0.030 apart, against the
   ~0.02 at which a large flat field stops being tellable from its neighbour,
   so all five can be told apart and the reader can count them.

   0.33 is the darkest the top band can be, and it is the darkest for a reason
   that is not taste: it is the largest opacity at which **every promise this
   atlas already made still holds over it** — an event mark, its label and the
   walked chain, over the band alone and over the band with the worst of the
   eight territory hues washed on top of it. Measured, and asserted in
   tests/contrast.test.mjs: bare, a mark at 5.08:1, a label at 8.34:1, the
   chain at 3.45:1; under a territory, 4.43:1, 7.26:1 and 3.00:1. A darker
   ramp would take the walked chain under the 3:1 a line has to have, and it
   would buy almost nothing — see STATUS.md on what the wash does to the ramp.

   **Fill and no stroke.** A band is cut into cell files at the grid the base
   map is sharded on, and a stroked ring cut at a cell border draws a line
   along that border — a ridge that does not exist (M36 review, F1). The edge a
   reader sees is the edge between two tints, which is the edge that matters:
   it is what a frontier can be seen to sit on. */
.map .layer-base-relief path { stroke: none; fill: var(--ink-soft); }
.map .layer-base-relief path.band-0 { fill-opacity: 0.065; }
.map .layer-base-relief path.band-1 { fill-opacity: 0.135; }
.map .layer-base-relief path.band-2 { fill-opacity: 0.2; }
.map .layer-base-relief path.band-3 { fill-opacity: 0.265; }
.map .layer-base-relief path.band-4 { fill-opacity: 0.33; }
.map .layer-base-coast path { fill: none; stroke: var(--cobalt); stroke-width: 0.6; vector-effect: non-scaling-stroke; }
.map .layer-base-rivers path { fill: none; stroke: var(--cobalt-soft); stroke-width: 0.5; vector-effect: non-scaling-stroke; }
.map .layer-base-lakes path { fill: var(--cobalt-faint); stroke: var(--cobalt-soft); stroke-width: 0.5; vector-effect: non-scaling-stroke; }
.map .layer-base-physical path { fill: none; stroke: var(--line); stroke-width: 0.5; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
/* M45a. All seventeen classes of `physical` were this same mark: a desert and
   a mountain range were worth the same dashed line, and the map said nothing
   about the ground the frontiers moved across. There are four families now,
   and the family travels in the file, written by the import
   (tools/import/features.mjs) — the stylesheet guesses nothing from a name.

   **No new hex value and no new token**, which is this map's rule (CLAUDE.md)
   and the reason all three rules below are opacities over tokens that already
   existed. And none of them competes with the territories: the owner's
   question was "what do the frontiers move around", and the answer has to sit
   **under** the frontier rather than in its place. A territory is drawn at
   0.62 of one of the eight hues; the loudest ground here is 0.45 of a pale
   token.

   The rule above is still the one for the nine classes that stayed as they
   were — plateaus, plains, capes, peninsulas, lowlands, deltas, isthmuses,
   gorges and geoareas — and it is what a feature with no family draws as. */

/* Ranges and foothills: ground that rises. The only one of the three with a
   solid stroke, because a ridge has an edge — that edge is what is seen under
   a frontier following it — and a dash is what says "approximate limit".
   Cobalt, which is the colour this map draws in, and not a new colour. */
.map .layer-base-physical path.ground-relief {
  fill: var(--cobalt-soft); fill-opacity: 0.26;
  stroke: var(--cobalt-soft); stroke-width: 0.7; stroke-dasharray: none;
}
/* Deserts, tundra and wetlands: ground of a kind, at any height. It keeps the
   dash the layer already had — a desert's limit is a convention and not an
   edge — and gains a neutral wash under it. */
.map .layer-base-physical path.ground-cover {
  fill: var(--line); fill-opacity: 0.45;
  stroke: var(--line); stroke-width: 0.5;
}
/* Basins, depressions and valleys: ground that dips. Finely dotted rather than
   dashed, and the ink is the soft text colour and not cobalt, so that a hollow
   does not read as a range at second glance. */
.map .layer-base-physical path.ground-hollow {
  fill: var(--ink-soft); fill-opacity: 0.12;
  stroke: var(--ink-soft); stroke-width: 0.5; stroke-dasharray: 1 3;
}
/* A peak is drawn at its height since M45a (layers/base.js, `peakRadius`): the
   radius comes from the attribute and not from here, being one number per
   point. */
.map .layer-base-mountains circle { fill: var(--ink-soft); stroke: none; }
/* Aureolada a papel, como as marcas são: um ponto de cidade cai muitas vezes
   sobre um território cheio, e sem a auréola desaparecia lá dentro. */
.map .layer-base-cities circle { fill: var(--ink-soft); stroke: var(--paper); stroke-width: 0.8; vector-effect: non-scaling-stroke; }
/* Territories. A state is filled with its hue and its INLAND borders are
   stroked in the same hue darkened; a dependency is filled with the LIGHTER
   TINT OF ITS OWNER and bordered dotted, so "its own" and "somebody's"
   separate inside one family rather than by a second colour. Disputed is
   dashed, which is what a disputed edge already looks like here, and
   `probable` is dashed around its whole outline for the reason below.

   Two elements and not one, since M39b: `.presence` is the closed outline,
   filled and clicked, and `.presence-border` over it is the part of that
   outline that runs between two territories. CShapes' shore is not Natural
   Earth's, so stroking the whole outline drew a second coastline beside the
   one `.land` draws; the shore is `.land`'s alone now and a territory is
   drawn with no edge where it meets the sea.

   Which actor gets which hue is data/geo/palette.json and it says only that
   two neighbours differ: there is no legend by hue, and about.html says so.
   An actor the palette does not know keeps the cobalt wash the map had
   before the hues existed, so a stale palette loses colour and never a
   border. */
.map .presence { fill-opacity: 0.62; stroke: none; cursor: pointer; }
/* M43a. A `probable` outline is dashed all the way round, shore included, and
   it is the only thing on this map that draws a line along a coast. It is the
   whole line that is uncertain: a snapshot's border was drawn for one year and
   assumed until the next, and its coastline is the dataset's and not Natural
   Earth's. `non-scaling-stroke` keeps the dash the same width at every zoom,
   as `.presence-border` does, so the territory does not gain an edge as the
   reader comes in. The stroke colour is the hue's own darkened line, set with
   the hue rules below; `.unhued` keeps cobalt. */
.map .presence.probable { stroke-width: 0.7; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.map .presence.probable.unhued { stroke: var(--cobalt); }
.map .presence.unhued { fill: var(--cobalt-faint); }
.map .presence.dependency { fill-opacity: 0.7; }
.map .presence:hover { fill-opacity: 0.88; }

.map .presence.hue-0 { fill: var(--terr-1); }
.map .presence.hue-1 { fill: var(--terr-2); }
.map .presence.hue-2 { fill: var(--terr-3); }
.map .presence.hue-3 { fill: var(--terr-4); }
.map .presence.hue-4 { fill: var(--terr-5); }
.map .presence.hue-5 { fill: var(--terr-6); }
.map .presence.hue-6 { fill: var(--terr-7); }
.map .presence.hue-7 { fill: var(--terr-8); }

/* The borders themselves: open lines over the washes, and out of the way of
   the pointer — a territory is picked up by its fill, which is the whole of
   it, and never by the hairline along one side of it. */
.map .presence-border { fill: none; stroke-width: 0.7; vector-effect: non-scaling-stroke; pointer-events: none; }
.map .presence-border.unhued { stroke: var(--cobalt); }
.map .presence-border.dependency { stroke-width: 0.9; stroke-dasharray: 1 2; }
.map .presence-border.disputed { stroke-dasharray: 4 3; }
.map .presence.probable.hue-0 { stroke: var(--terr-1-line); }
.map .presence.probable.hue-1 { stroke: var(--terr-2-line); }
.map .presence.probable.hue-2 { stroke: var(--terr-3-line); }
.map .presence.probable.hue-3 { stroke: var(--terr-4-line); }
.map .presence.probable.hue-4 { stroke: var(--terr-5-line); }
.map .presence.probable.hue-5 { stroke: var(--terr-6-line); }
.map .presence.probable.hue-6 { stroke: var(--terr-7-line); }
.map .presence.probable.hue-7 { stroke: var(--terr-8-line); }
.map .presence-border.hue-0 { stroke: var(--terr-1-line); }
.map .presence-border.hue-1 { stroke: var(--terr-2-line); }
.map .presence-border.hue-2 { stroke: var(--terr-3-line); }
.map .presence-border.hue-3 { stroke: var(--terr-4-line); }
.map .presence-border.hue-4 { stroke: var(--terr-5-line); }
.map .presence-border.hue-5 { stroke: var(--terr-6-line); }
.map .presence-border.hue-6 { stroke: var(--terr-7-line); }
.map .presence-border.hue-7 { stroke: var(--terr-8-line); }
.map .presence.dependency.hue-0 { fill: var(--terr-1-tint); }
.map .presence.dependency.hue-1 { fill: var(--terr-2-tint); }
.map .presence.dependency.hue-2 { fill: var(--terr-3-tint); }
.map .presence.dependency.hue-3 { fill: var(--terr-4-tint); }
.map .presence.dependency.hue-4 { fill: var(--terr-5-tint); }
.map .presence.dependency.hue-5 { fill: var(--terr-6-tint); }
.map .presence.dependency.hue-6 { fill: var(--terr-7-tint); }
.map .presence.dependency.hue-7 { fill: var(--terr-8-tint); }

/* The selected actor's ground, and the ground of everything it held: cobalt
   over every hue, and drawn last as well, so the emphasis is never under a
   territory that happened to sort after it. Both selectors carry the weight
   of the .dependency rules above, or a selected colony would keep its tint. */
.map .presence.of-actor, .map .presence.dependency.of-actor {
  fill: var(--cobalt-soft); fill-opacity: 0.62;
}
.map .presence-border.of-actor, .map .presence-border.dependency.of-actor {
  stroke: var(--cobalt); stroke-width: 1.2; stroke-dasharray: none;
}
.map .presence-border.dependency.of-actor { stroke-dasharray: 1 2; }
.map .presence-border.of-actor.disputed { stroke-dasharray: 4 3; }

/* A mark is white with a cobalt ring wherever it lands, which is what keeps
   it above eight coloured washes without competing with the two colours that
   carry emphasis. */
.map .mark { fill: var(--paper); stroke: var(--cobalt); stroke-width: 1.5; vector-effect: non-scaling-stroke; cursor: pointer; }
.map .mark:hover { fill: var(--cobalt-faint); }
/* An event placed no more precisely than a region or a state (M80). The width
   is the layer's — half again a city's — and this is the other half of it: a
   dashed, lighter ring over a fill that lets the ground through, so that the
   mark reads as *somewhere in here* rather than as a pin dropped on a capital.
   Nothing new is spent on it: the same cobalt, the same stroke, at a lower
   opacity, which is how the atlas has said "less sure" since M73. Before the
   emphasis rules below, so a coarse mark that is selected or walked is drawn
   in madder like any other and keeps only its width. */
.map .mark.coarse {
  stroke: var(--cobalt-soft); stroke-dasharray: 3 2; fill-opacity: 0.35;
}
/* The events of the selected actor. Cobalt and heavier, never madder:
   the accent belongs to the path being followed, and the two emphases
   must not be confused with one another. */
/* Every event an open narrative walks: a ring, not a fill, so the walk can
   be read underneath the actor's emphasis and the path's accent alike. */
.map .mark.of-narrative { stroke: var(--cobalt); stroke-width: 3; stroke-dasharray: 2 2; }
.map .mark.of-actor { fill: var(--cobalt-soft); stroke: var(--cobalt); stroke-width: 2.5; }
/* The ring around whatever the reader has open (M84): the ink, in its place in
   the order — after everything that says what a record *is*, before the two
   that say what the reader is holding. */
.map .mark.lens-near { stroke: var(--terr-5-line); }
.map .mark.lens-near:hover { fill: var(--terr-5-tint); }
.map .mark.on-path { fill: var(--madder); stroke: var(--madder); }
.map .mark.selected { fill: var(--madder); stroke: var(--paper); stroke-width: 2; }
/* The symbol of an event's category, over its mark and never instead of it.
   `currentColor` in the <symbol> resolves against this <use>, which is why
   the twelve shapes carry no colour of their own: one drawing reads cobalt on
   the paper-filled mark it usually sits on, and paper on the three that take
   a solid fill. Five states, existing tokens, no new hex value.
   It takes no pointer — the circle underneath is the control — and it takes
   its mark's emphasis and adds none of its own (glyphs.js). */
.map .glyph { color: var(--cobalt); pointer-events: none; }
.map .glyph.of-actor, .map .glyph.on-path, .map .glyph.selected { color: var(--paper); }
/* A larger invisible circle behind every mark, so a click that is merely
   close still lands on it. */
.map .hit { fill: transparent; stroke: none; cursor: pointer; }
/* A stack of marks drawn as one. Heavier ring and a count beside it: the
   reader has to be able to tell a cluster from a single event. Filled with
   paper and not with a tint of cobalt, since the hues arrived: a pale blue
   disc over a pale blue territory is a disc that disappears, and white is the
   one fill none of the eight washes can be confused with. */
.map .mark.cluster { fill: var(--paper); stroke: var(--cobalt); stroke-width: 2.5; }
.map .mark.cluster:hover { fill: var(--cobalt-faint); }
.map .cluster-count { fill: var(--cobalt); font-family: var(--font-ui); font-weight: 600; pointer-events: none; }
/* Every name the map draws, in the one group that draws them (src/map/labels.js).
   The halo's width and the type size are attributes written per label and
   divided by the zoom, and they are deliberately not here: a CSS declaration
   beats a presentation attribute, so `stroke-width: 3` in this rule was the
   halo the layer thought it was scaling — 3 user units at k = 8 is a white
   cloud twenty-four pixels across, which is what buried the geography in
   docs/screens/m37-base-lisbon.png. Nothing about a label's size is set here.
   A city is told apart from an event by the soft ink its dot is drawn in, and
   a physical feature by its letterspacing — `--tracking-label`, which is an
   em and therefore follows the type size down as the zoom goes up, so it is
   about the size of nothing. A river is not a second colour and not a second
   size: it is the same name, written wider. */
.map .layer-labels { pointer-events: none; }
.map .mark-label,
.map .city-label,
.map .feature-label { font-family: var(--font-ui); paint-order: stroke; stroke: var(--paper); stroke-linejoin: round; pointer-events: none; }
.map .mark-label { fill: var(--ink); }
.map .city-label { fill: var(--ink-soft); }
.map .feature-label { fill: var(--ink-soft); letter-spacing: var(--tracking-label); }
/* A coincident cluster opened: members on rings, each on a leg back to the
   point they all share. */
.map .spread-hub { fill: var(--cobalt); stroke: none; }
.map .spread-leg { stroke: var(--cobalt-soft); stroke-width: 0.8; vector-effect: non-scaling-stroke; }
.map .edge { stroke: var(--cobalt-soft); stroke-width: 1.2; vector-effect: non-scaling-stroke; fill: none; }
.map .edge.chain { stroke: var(--madder); stroke-width: 2; }
/* The one link the reader has open (M83, B7): the same madder the graph draws
   a chosen line in and the walked chain is drawn in everywhere — the accent
   means *this is the one you are holding*, and a link being read is one of
   those. It is drawn over the other two, so the tokens are the chain's. */
.map .edge.chosen { stroke: var(--madder); stroke-width: 2; }

/* Outside the window, but still drawn because the reader is working with it:
   the walked path, the selected event, an actor's events. Greyed, not
   hidden — a chain that vanished as the band moved would be worse. */
.map .mark.faded { opacity: 0.3; }
.map .mark.faded.selected, .map .mark.faded.on-path { opacity: 0.55; }

/* The horizon: everything the selected event had led to by the year the
   reader chose. Cobalt, like every emphasis that is not the walked path, and
   fading with distance — one step away is solid, five steps away is a
   whisper. Three bands rather than a continuous ramp, because opacity in
   twenty steps says nothing anyone can read. The bands carry two classes so
   that a reachable event outside the window is not greyed back down: it is
   drawn precisely because it answers the question that was asked. */
.map .mark.in-horizon { fill: var(--cobalt-faint); stroke: var(--cobalt); stroke-width: 2; }
.map .mark.in-horizon.near { opacity: 1; }
.map .mark.in-horizon.mid { opacity: 0.72; }
.map .mark.in-horizon.far { opacity: 0.45; }
.map .mark.in-horizon.on-path, .map .mark.in-horizon.selected { opacity: 1; }

/* An event that has parts: a second, thinner outline outside its mark, saying
   there is more inside. It carries the mark's own emphasis classes, so it
   reddens with the walked chain and dims and fades exactly as the mark does;
   what it does not carry is `mark`, since it is an outline and not a record.
   Its width is set per zoom by the events layer, so nothing is declared here.
   Not a control: it takes no click, and the mark under it takes them all. */
.map .ring { fill: none; stroke: var(--cobalt); pointer-events: none; }
/* A parent inside the ring wears the ring's ink too, so that a mark and the
   outline that says there is more inside it are never two different colours
   (M84). Before the two lines below and not after them: the selection and the
   walk outrank every other class, here as on the mark itself. */
.map .ring.lens-near { opacity: 1; stroke: var(--terr-5-line); }
.map .ring.on-path, .map .ring.selected { stroke: var(--madder); }
.map .ring.faded { opacity: 0.3; }
.map .ring.faded.selected, .map .ring.faded.on-path { opacity: 0.55; }
.map .ring.in-horizon.mid { opacity: 0.72; }
.map .ring.in-horizon.far { opacity: 0.45; }
.map .ring.in-horizon.on-path, .map .ring.in-horizon.selected { opacity: 1; }

/* --- how sure the atlas is, on the line -------------------------------- */

/* Every line that stands for an edge carries one of these three, spelled by
   `src/confidence.js` and by nothing else. Written without a view in front
   of them on purpose: the graph's edges and the map's walk lines are two
   pictures of the same records, and they must not be able to disagree about
   which link is the shaky one (M73).

   Type owns the dash pattern and the weight — that is what the tokens under
   "graph view" below are — so confidence takes the dimension that is left:
   how solidly the line is inked. `stroke-opacity`, and not the `opacity`
   the emphasis states already use, because two rules writing one property
   would have the reader's own picture erase the atlas's confidence in it, or
   the other way about. These two multiply instead, which is the reading
   wanted: a disputed link outside the window is outside the window *and*
   doubtful. `fill-opacity` beside each, for the arrowhead — a filled polygon
   and not a stroke — which carries its line's classes so that it fades,
   reddens and doubts with it.

   No colour of its own. The atlas has one accent and it belongs to the path
   being followed; how sure a link is is not an accent, it is how much ink
   the line was worth. */
.edge.confidence-consensus, .edge-head.confidence-consensus { stroke-opacity: 1; fill-opacity: 1; }
.edge.confidence-probable, .edge-head.confidence-probable { stroke-opacity: 0.7; fill-opacity: 0.7; }
.edge.confidence-disputed, .edge-head.confidence-disputed { stroke-opacity: 0.34; fill-opacity: 0.34; }

/* --- graph view ------------------------------------------------------- */

/* The five edge types are told apart by the pattern of the line and its
   weight, both from these tokens, so the key in about.html has one place to
   agree with. Not by colour: the atlas has one accent and it belongs to the
   path being followed. How sure the atlas is of a link is said above and in
   ink, so no type's dash is ever taken away from it to say something else. */
:root {
  --edge-caused: none;
  --edge-caused-width: 2;
  --edge-enabled: 7 3;
  --edge-enabled-width: 1.6;
  --edge-reacted-to: 1 3;
  --edge-reacted-to-width: 1.6;
  --edge-precondition-of: 3 3;
  --edge-precondition-of-width: 1;
  --edge-inspired: 5 2 1 2;
  --edge-inspired-width: 1;
}

.graph { width: 100%; height: 100%; display: block; cursor: grab; touch-action: none; font-family: var(--font-ui); }
.graph:active { cursor: grabbing; }
/* One band per region, alternating like the timeline's lanes, so the two
   readings of the same continent look like the same thing. */
.graph .band.even { fill: var(--paper); }
.graph .band.odd { fill: var(--ground); }
.graph .band-label { fill: var(--ink-soft); font-size: 12px; pointer-events: none; }
.graph .tick { stroke: var(--line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.graph .tick-label { fill: var(--ink-soft); font-size: 11px; pointer-events: none; }
/* There is no window on this picture since M76 — the owner, 21 September: *"I
   think the graph can always show all dates, then one can zoom in and out and
   pan to look at different times"* — so the shaded band that stood here is
   gone, and with it every `.graph .faded` rule: nothing on the graph is drawn
   faded for falling outside a band any more. The two other views still fade,
   and their rules are theirs. */

.graph .edge, .graph .edge-head { stroke: var(--cobalt-soft); fill: none; vector-effect: non-scaling-stroke; pointer-events: none; }
.graph .edge-head { fill: var(--cobalt-soft); stroke: none; }
.graph .edge.type-caused { stroke-width: var(--edge-caused-width); stroke-dasharray: var(--edge-caused); }
.graph .edge.type-enabled { stroke-width: var(--edge-enabled-width); stroke-dasharray: var(--edge-enabled); }
.graph .edge.type-reacted-to { stroke-width: var(--edge-reacted-to-width); stroke-dasharray: var(--edge-reacted-to); stroke-linecap: round; }
.graph .edge.type-precondition-of { stroke-width: var(--edge-precondition-of-width); stroke-dasharray: var(--edge-precondition-of); }
.graph .edge.type-inspired { stroke-width: var(--edge-inspired-width); stroke-dasharray: var(--edge-inspired); }
/* A line carrying several links. After the type rules and at the same
   specificity, so it is the one that decides the width; the dash pattern is
   still the type's, because what the links are is not what merging changed.
   The property is set on the element, so how thick "several" looks stays
   here and only the count comes from the script. */
.graph .edge.merged { stroke-width: var(--merged-width, 2); }
.graph .edge.consequence, .graph .edge-head.consequence { stroke: var(--cobalt); }
.graph .edge-head.consequence { fill: var(--cobalt); }
/* The branches that fed the selected event from off the walked path: the
   convergence query, drawn. Cobalt and heavier, never madder — madder is
   the path the reader took, and these are the ones they did not. */
.graph .edge.converging { stroke: var(--cobalt); stroke-width: 2; opacity: 1; }
.graph .edge-head.converging { fill: var(--cobalt); }
.graph .edge.chain { stroke: var(--madder); stroke-width: 2.4; }
.graph .edge-head.chain { fill: var(--madder); }
/* The link the reader has opened (M80). Madder and heavier than the walked
   chain, and last of the three so it wins over every one of them: a line being
   *read* is the one thing on this picture the panel is about, and the accent
   has meant "the one you are holding" since the chain first took it. It keeps
   its type's dash pattern and its confidence's ink, because what kind of claim
   it is and how sure the atlas is of it are not what choosing it changed. */
.graph .edge.chosen { stroke: var(--madder); stroke-width: 3; opacity: 1; }
.graph .edge-head.chosen { fill: var(--madder); }
/* A line that stands for one link is a control, so it says so to the pointer
   and takes the focus ring like every other. `pointer-events` stays off it:
   which line a click means is decided by distance in graph-view.js, the way
   which mark it means always has been, so nothing here has to be wide enough
   to hit. */
.graph .edge[data-edge] { cursor: pointer; }
.graph .edge[data-edge]:focus-visible { stroke: var(--madder); }

.graph .node { fill: var(--paper); stroke: var(--cobalt); stroke-width: 1.5; vector-effect: non-scaling-stroke; cursor: pointer; }
.graph .node:hover { fill: var(--cobalt-faint); }
.graph .node.of-narrative { stroke: var(--cobalt); stroke-width: 3; stroke-dasharray: 2 2; }
.graph .node.of-actor { fill: var(--cobalt-soft); stroke: var(--cobalt); stroke-width: 2.5; }
.graph .node.converging { fill: var(--cobalt); stroke: var(--cobalt); }
/* The ring (M84), after `converging` and before the selection: a branch that
   converged is still drawn filled, and in the ring it is filled in the ring's
   own hue rather than in the cobalt every other mark is drawn in. */
.graph .node.lens-near { stroke: var(--terr-5-line); }
.graph .node.converging.lens-near { fill: var(--terr-5); stroke: var(--terr-5-line); }
.graph .node.lens-near:hover { fill: var(--terr-5-tint); }
.graph .node.on-path { fill: var(--madder); stroke: var(--madder); }
.graph .node.selected { fill: var(--madder); stroke: var(--paper); stroke-width: 2; }
/* A stack of nodes drawn as one, and the count beside it: read exactly as
   the map's stacks and the timeline's, so one reading serves all three.
   Filled with paper and ringed heavier, because the reader has to be able to
   tell one event from many before reading the badge. */
.graph .node.stack { fill: var(--paper); stroke: var(--cobalt); stroke-width: 2.5; }
.graph .node.stack:hover { fill: var(--cobalt-faint); }
/* A stack no zoom can part is not a promise of "zoom in": it opens the list
   in the panel instead, and says so by not inviting the wheel. */
.graph .node.stack.coincident { stroke-dasharray: 3 2; }
/* A node with its parts folded into it was dressed here until M70: filled
   rather than hollow, because it was one record the others were inside. The
   fold is gone and so is the class; what says there is more inside an event
   is the ring below, on every view. */
.graph .cluster-count {
  fill: var(--cobalt); font-family: var(--font-ui); font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em; pointer-events: none;
}
/* The horizon, drawn the way the map draws it: see the note there. */
.graph .node.in-horizon { fill: var(--cobalt-faint); stroke: var(--cobalt); stroke-width: 2; }
.graph .node.in-horizon.near { opacity: 1; }
.graph .node.in-horizon.mid { opacity: 0.72; }
.graph .node.in-horizon.far { opacity: 0.45; }
.graph .node.in-horizon.on-path, .graph .node.in-horizon.selected { opacity: 1; }
/* An event that has parts: the same ring the map and the timeline draw, at
   every zoom — the collapse above is a behaviour and this is the look, and a
   reader zoomed past the threshold was being told nothing at all. It carries
   the node's own emphasis classes and not the word `node`; its width is set
   per zoom by the view, so nothing is declared here. */
.graph .ring { fill: none; stroke: var(--cobalt); pointer-events: none; }
.graph .ring.lens-near { opacity: 1; stroke: var(--terr-5-line); }
.graph .ring.on-path, .graph .ring.selected { stroke: var(--madder); }
.graph .ring.in-horizon.mid { opacity: 0.72; }
.graph .ring.in-horizon.far { opacity: 0.45; }
.graph .ring.in-horizon.on-path, .graph .ring.in-horizon.selected { opacity: 1; }
/* The halo behind a label: paper under the letters, so a name is readable
   over a line or a mark. `vector-effect` for the reason `.graph .node` above
   carries it — a stroke inside the group the zoom scales grows with the
   picture, and the letters do not (M61), so the band round them swelled as
   the reader went in until it began eating what it exists to hold apart. The
   3 is the width on screen now and at every zoom, which is what it was always
   meant to be. */
.graph .node-label { fill: var(--ink); font-family: var(--font-ui); paint-order: stroke; stroke: var(--paper); stroke-width: 3; vector-effect: non-scaling-stroke; pointer-events: none; }
.graph .node-label.selected { fill: var(--madder); font-weight: 600; }
/* The one name the pointer is asking for (M77): a mark whose name did not
   fit carries no label until it is hovered, and this is that label. Over
   everything, since it is the reader's finger and not part of the picture. */
.graph .node-label.hovered { fill: var(--ink); font-weight: 600; }
/* The hairline that ties a label to the mark it names when it had to be
   written on another line (M77). Thin and soft: it is a tie and not a link,
   and it must never be read as an edge. */
.graph .label-leader { stroke: var(--cobalt-soft); stroke-width: 1; stroke-opacity: 0.6; vector-effect: non-scaling-stroke; fill: none; pointer-events: none; }
/* The key — in the corner of the graph, and again in about.html — is drawn
   with the same rules as the lines it explains, so the two cannot drift
   apart. */
svg.key-line { width: 62px; height: 12px; display: inline-block; vertical-align: middle; cursor: default; }
.edge-key { display: grid; grid-template-columns: auto 1fr; gap: var(--space-1) var(--space-3); align-items: center; margin: var(--space-2) 0; }
.edge-key dt, .edge-key dd { margin: 0; }

/* The key over the graph itself: small, in the corner, and out of the way of
   the pointer. It says what the five patterns mean, which is the one thing
   the picture cannot say about itself. */
.graph-key {
  position: absolute; left: var(--space-3); bottom: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 1px 4px var(--cobalt-faint);
  font-size: var(--text-xs); color: var(--ink-soft); pointer-events: none;
}
.graph-key h2 {
  margin: 0 0 var(--space-1); font-size: var(--label); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--ink-soft);
}
.graph-key .edge-key { margin: 0; gap: 0.15rem var(--space-2); }
.graph-key svg.key-line { width: 46px; }
/* **One button, everywhere** (M83, A1-4). M82 folded the key on a phone alone,
   on the argument that a desktop has room for all of it; the owner, looking at
   World War II opened on a 1440 px screen, found it over the bottom-left corner
   of the picture there too — a legend standing on the thing it is a legend to.
   So the key is folded wherever it is drawn and the button is what opens it,
   which is one behaviour to read and one to test instead of two. The box itself
   still lets the pointer through to the picture under it; the button does not,
   because pressing it is a gesture. */
.graph-key .graph-key-toggle {
  display: flex; align-items: center; pointer-events: auto;
  border: 0; padding: 0; background: none; cursor: pointer;
  font-family: var(--font-ui); font-size: var(--label); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--ink-soft);
}
.graph-key:not(.open) .graph-key-body { display: none; }
.graph-key.open { pointer-events: auto; }
/* The same box on the map and the timeline (M82, A7). Every row is drawn with
   the classes that picture uses — `.map .mark`, `.timeline .bar` — so the key
   is the picture's own ink and not a drawing of it; what is written here is
   only how big the little drawing is. */
.view-key svg.key-shape { width: 24px; height: 12px; overflow: visible; }
.view-key .edge-key dt { display: flex; align-items: center; }
/* The ink of a row, which is the ink of the thing it stands for. The
   declarations below are the same declarations and the same tokens as the
   map's and the timeline's above; what they cannot share is the selector,
   because `.map .mark` is how a test asks whether the map has drawn a mark
   yet. A browser test holds a row and a real mark to the same computed stroke
   and fill, so the two cannot drift apart unnoticed. */
.view-key .mark { fill: var(--paper); stroke: var(--cobalt); stroke-width: 1.5; }
.view-key .mark.coarse { stroke: var(--cobalt-soft); stroke-dasharray: 3 2; fill-opacity: 0.35; }
.view-key .mark.cluster { fill: var(--paper); stroke: var(--cobalt); stroke-width: 2.5; }
.view-key .cluster-count { fill: var(--cobalt); font-family: var(--font-ui); font-weight: 600; }
.view-key .bar { fill: var(--cobalt-faint); stroke: var(--cobalt); stroke-width: 1; }
.view-key .bar.instant { fill: var(--cobalt-soft); }
.view-key .bar.ongoing { stroke-dasharray: 3 3; }
.view-key .ring { fill: none; stroke: var(--cobalt); }
/* And the ring around what the reader has open (M84), in the same ink and the
   same fill the two pictures give it. */
.view-key .mark.lens-near { stroke: var(--terr-5-line); }
.view-key .bar.lens-near { fill: var(--terr-5-tint); stroke: var(--terr-5-line); }
/* And the ends of an open link, in the ink the timeline gives them. */
.view-key .bar.chosen { stroke: var(--madder); stroke-width: 2; }

/* The picture as a file. Bottom right in both views, opposite the graph's
   key, and quiet: it is a way out, not a thing to do. */
.view-export {
  position: absolute; right: var(--space-3); bottom: var(--space-3);
  padding: var(--space-1) var(--space-2);
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft); cursor: pointer;
}
.view-export:hover { color: var(--cobalt); border-color: var(--cobalt); }
.view-export:disabled { opacity: 0.5; cursor: default; }

/* Why this view is not narrowed when the timeline is. Top right, opposite
   the key, and only there while the map is holding a viewport at all. */
.graph-note {
  position: absolute; right: var(--space-3); top: var(--space-3); max-width: 22rem;
  margin: 0; padding: var(--space-2) var(--space-3);
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft);
  pointer-events: none;
}

/* The same note on the map, and it says one thing: a shard of borders that
   would not load. Madder, because it is not an explanation of the picture
   but a warning that the picture is not the one that was asked for. */
.map-note {
  position: absolute; right: var(--space-3); top: var(--space-3); max-width: 22rem;
  margin: 0; padding: var(--space-2) var(--space-3);
  background: var(--paper); border: 1px solid var(--madder); border-radius: var(--radius);
  font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft);
  pointer-events: none;
}

/* What the map is not drawing as a mark: the events of this window that span
   the whole map, and the count of those with no place at all. The quiet
   opposite of .map-note above — ink on paper with a plain line round it,
   because nothing here is a warning, and at the other corner so the two are
   never read as one block. */
/* The bottom-left corner of the map is one column (M85): the key at the foot of
   it, where it has always been, and the corner's own lines above it. Both were
   anchored to the same point and the key sat over the first words of the
   borders line. */
.map-corner-stack {
  position: absolute; left: var(--space-3); bottom: var(--space-3);
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2);
  max-width: 24rem;
}
.map-corner-stack .map-corner,
.map-corner-stack .graph-key { position: static; left: auto; bottom: auto; }
.map-corner {
  position: absolute; left: var(--space-3); bottom: var(--space-3); max-width: 24rem;
  margin: 0; padding: var(--space-2) var(--space-3);
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft);
}
.map-corner p { margin: 0; }
.map-corner p + p { margin-top: var(--space-1); }
/* Which year's borders are under the marks (M85, A15). Italic and softer than
   the line above it: it is a note about the drawing rather than about a
   record, which is how the band said it on the timeline. No new token. */
.map-borders { font-style: italic; }

/* --- the band over the map (M64) -------------------------------------- */

/* The owner, 18 September: "There should be a toggle on the map so I can
   choose the dates instead of a selector." M60 was right to take the strip
   out of the bottom of the map; what it lost is that a year could be swept.
   M64 brought the band back over the map, behind a button. The owner,
   21 September: "The dates two-handled band should not be hidden." So the
   button is gone and the band is simply there — and never again as furniture.

   An overlay and not a row of the grid, which is the whole of "it does not
   become the strip again": the map pane is the layout's own height with the
   band on it, which is M60's gain and M64's kept together. The container takes
   no clicks so that the map under it is still a map; the strip takes its own. */
.map-band {
  position: absolute; inset-inline: 0; top: 0; z-index: 3;
  pointer-events: none;
}
.map-band > * { pointer-events: auto; }

/* The strip itself: the width of the pane, against its top edge, under the
   masthead's two number fields that say the same window in words. Paper rather
   than nothing, so the coastline under it does not read as part of the
   drawing; one hairline to say where it ends. */
.window-strip {
  display: block; width: 100%;
  background: var(--paper); border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 4px var(--cobalt-faint);
}
/* Where the events are, under the band: density.js's own columns at
   density.js's own scale, drawn as the timeline draws its stubs and never a
   target — a two-pixel column is not something to aim at. */
.window-strip .stub { fill: var(--cobalt); stroke: none; pointer-events: none; }

/* The one thing on the map that must stay above the band: the note saying a
   shard of borders would not load. It is a warning, and a warning behind a
   control is a warning nobody reads — the more so since M75, where the band
   is on the map's top edge on every visit rather than when it is asked for. */
.map-note { z-index: 4; }

/* A large event over the ground it covers, in place of a mark it has no
   honest point for (map/layers/regions.js). Faint enough that a coastline, a
   territory and a mark are all still readable through it, and never a target:
   the record is opened from its mark, its bar or the corner. */
.map .region-wash { fill: var(--cobalt); opacity: 0.07; stroke: none; pointer-events: none; }

/* --- timeline -------------------------------------------------------- */

/* pan-y and not none: a sideways drag is the band sliding, and a finger
   moving up the screen still scrolls the timeline, which is taller than its
   own area as soon as the rows pack. */
.timeline { display: block; font-family: var(--font-ui); font-variant-numeric: tabular-nums; touch-action: pan-y; }
/* The alternating ground behind the rows: quiet enough to be felt rather
   than seen, because it is there to keep the eye on one row and not to
   divide the drawing into stripes. */
.timeline .lane.even { fill: var(--paper); }
.timeline .lane.odd { fill: var(--ground); }
.timeline .tick { stroke: var(--line); stroke-width: 1; }
.timeline .tick-label { fill: var(--ink-soft); font-size: 11px; }
.timeline .bar { fill: var(--cobalt-faint); stroke: var(--cobalt); stroke-width: 1; cursor: pointer; }
.timeline .bar:hover { fill: var(--cobalt-soft); }
/* An event of one day is six pixels wide on a century-long axis. Hollow, it
   reads as a small empty ring; filled, it reads as a mark, which is what it
   is. The states below still say what it is a mark of. */
.timeline .bar.instant { fill: var(--cobalt-soft); }
.timeline .bar.instant:hover { fill: var(--cobalt); }
.timeline .bar.ongoing { stroke-dasharray: 3 3; }
.timeline .bar.of-narrative { stroke: var(--cobalt); stroke-width: 2; stroke-dasharray: 3 2; }
.timeline .bar.of-actor { fill: var(--cobalt-soft); stroke: var(--cobalt); stroke-width: 2; }
/* The ring (M84). A bar carries a fill as well as an outline, so it takes the
   same ink's own tint under it — and a one-day mark, which is drawn in the
   stronger of the two cobalts, takes the stronger of the two greens, so that
   "one day" goes on meaning what it means inside the ring as well as outside
   it. */
.timeline .bar.lens-near { fill: var(--terr-5-tint); stroke: var(--terr-5-line); }
.timeline .bar.instant.lens-near { fill: var(--terr-5); }
.timeline .bar.lens-near:hover { fill: var(--terr-5); }
/* The two ends of the link the reader has opened (M87 §8). The map strokes
   that link's line madder and these are the same link said on another picture,
   so they are stroked with the same token and the same width. Before the walked
   path below, which fills as well as strokes: an end that is also a step of the
   chain reads as the chain. */
.timeline .bar.chosen { stroke: var(--madder); stroke-width: 2; }
.timeline .bar.on-path { fill: var(--madder-faint); stroke: var(--madder); }
.timeline .bar.selected { fill: var(--madder); stroke: var(--madder); }
/* And at the left end of a bar, in the same rule against the bar's own fill:
   a bar is cobalt-faint by default and cobalt-soft when it is an instant or
   the open actor's, both of which cobalt reads on; `on-path` is madder-faint
   and `selected` is solid madder. A bar below GLYPH_MIN_BAR carries none at
   all (timeline.js), which is why there is no rule for a smaller one. */
.timeline .glyph { color: var(--cobalt); pointer-events: none; }
.timeline .glyph.on-path { color: var(--madder); }
.timeline .glyph.selected { color: var(--paper); }
.timeline .bar-label { fill: var(--ink); font-size: 11px; pointer-events: none; }
.timeline .bar-label.faded { opacity: 0.45; }
.timeline .bar-label.selected { fill: var(--madder); font-weight: 600; }
/* The timeline had a stack — several bars drawn as one with a `+n` beside it
   — until M77, when the owner asked for every bar to carry its title instead.
   Its rules went; the map's and the graph's counts are set above. */
/* Outside the band: still there, still clickable, plainly not in the window. */
.timeline .bar.faded { opacity: 0.28; }
/* An event past the margin: a tick on the floor of its lane saying the
   dataset carries on, not a bar. No stroke, no pointer — there is nothing to
   aim at and nothing to open (timeline.js). */
.timeline .bar.stub { fill: var(--cobalt); stroke: none; cursor: default; pointer-events: none; }
/* The horizon, drawn the way the map draws it: see the note there. */
.timeline .bar.in-horizon { fill: var(--cobalt-faint); stroke: var(--cobalt); stroke-width: 2; }
.timeline .bar.in-horizon.near { opacity: 1; }
.timeline .bar.in-horizon.mid { opacity: 0.72; }
.timeline .bar.in-horizon.far { opacity: 0.45; }
.timeline .bar.in-horizon.on-path, .timeline .bar.in-horizon.selected { opacity: 1; }

/* An event that has parts: a second, thinner outline outside its bar, saying
   there is more inside — the same ring the map and the graph draw, and since
   M77 the whole of what says so here (m30c-brief, §1). It carries the bar's
   own emphasis classes and not the word `bar`, so it reddens and fades with
   the bar and no selector that counts bars counts outlines. */
.timeline .ring { fill: none; stroke: var(--cobalt); pointer-events: none; }
.timeline .ring.lens-near { opacity: 1; stroke: var(--terr-5-line); }
.timeline .ring.on-path, .timeline .ring.selected { stroke: var(--madder); }
.timeline .ring.faded { opacity: 0.28; }
.timeline .ring.in-horizon.mid { opacity: 0.72; }
.timeline .ring.in-horizon.far { opacity: 0.45; }
.timeline .ring.in-horizon.on-path, .timeline .ring.in-horizon.selected { opacity: 1; }

/* A large event: a band the whole height of the drawing rather than a bar in
   one lane (large.js). Faint, because it is the ground the other events stand
   on and not a mark competing with them; and with no handle and no cursor of
   its own, so the only band a reader tries to grab is the window's. */
.timeline .large-band { fill: var(--cobalt-faint); opacity: 0.5; pointer-events: none; }
.timeline .large-band-label { fill: var(--ink-soft); font-size: 11px; pointer-events: none; }
/* The window of time: a shaded band under the bars, a handle at each end
   over them. The band is drawn as a control — the handles are solid cobalt
   bars with a visible grip, not a tint of the shading, and the keyboard focus
   is the atlas's own ring rather than the browser's.

   Two drawings carry it since M64 and there is one rule for both: the lanes,
   and the strip a reader opens over the map (.window-strip, below). They are
   the same elements from the same module (src/window-band.js), so a reader who
   has learnt one band has learnt the other. */
.timeline .window-band, .window-strip .window-band { fill: var(--cobalt); opacity: 0.07; cursor: grab; }
.timeline .window-band:hover, .window-strip .window-band:hover { opacity: 0.11; }
.timeline .window-band:active, .window-strip .window-band:active { cursor: grabbing; }
.timeline .window-handle, .window-strip .window-handle { fill: var(--cobalt); opacity: 0.62; cursor: ew-resize; }
.timeline .window-handle:hover, .window-strip .window-handle:hover { opacity: 0.85; }
.timeline .window-handle:focus, .window-strip .window-handle:focus {
  outline: none; opacity: 1; stroke: var(--madder); stroke-width: 2;
}
.timeline .window-year, .window-strip .window-year {
  fill: var(--cobalt); font-size: 11px; font-weight: 600; pointer-events: none;
}
/* What year the borders on the map are from. Its own line at the top right:
   a note about the whole drawing, not about either handle. */
.timeline .window-marker { fill: var(--ink-soft); font-size: 10.5px; font-style: italic; pointer-events: none; }

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

/* The panel is a stack of cards, and the hierarchy in it is made of space and
   rules rather than of boxes: a box inside a box inside a box is what the
   contribution form looks like, and this is meant to read like a page. Four
   levels and no more — the card's title, a section head in the serif, a
   small caps-and-tracking label, and the row. */

.panel { padding: var(--space-4) var(--space-4) var(--space-6); }
.panel > * { max-width: var(--measure); }
.panel h2 {
  font-family: var(--font); font-weight: normal; font-size: var(--text-lg); color: var(--cobalt);
  margin: var(--space-5) 0 var(--space-2); padding-top: var(--space-3);
  border-top: 1px solid var(--line);
  display: flex; align-items: baseline; gap: var(--space-2);
}
.panel h3 {
  font-size: var(--label); text-transform: uppercase; letter-spacing: var(--tracking-label);
  color: var(--ink-soft); margin: var(--space-4) 0 var(--space-1); font-weight: 600;
}
/* The card's own title opens the panel: no rule over it, and nothing above
   it to be separated from. The same for whatever a card happens to begin
   with, which for the empty state is a plain section. */
.panel > *:first-child > h2:first-child, .panel > h2:first-child {
  margin-top: 0; padding-top: 0; border-top: 0;
}
.panel .event-head h2, .panel .actor-head h2, .panel .place-head h2,
.panel .source-head h2, .panel .narrative-head h2, .panel .cluster-list h2,
.panel .edge-card-head h2 {
  font-size: var(--text-xl); margin-top: 0; padding-top: 0; border-top: 0; line-height: var(--leading-tight);
}
/* The link's card (M80, and its heading since M82, A11). The heading *is* the
   link — this event, the type, that event, in the order the record writes them
   — because a card headed "enabled" says nothing about which two things it is
   between, which is the one thing a link's card is about. No new size and no
   new colour: the ends are the same event control every list uses, the type is
   the interface's own soft ink, and the confidence is the same badge, on the
   line below. */
.panel .link-ends { margin: var(--space-1) 0 var(--space-2); display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.panel .link-ends .arrow { color: var(--ink-soft); font-size: var(--text-sm); font-family: var(--font-ui); }
/* A heading made of the two ends is set at the heading's size; the buttons
   inside it are the plain event control and take it from their parent. */
.panel h2.link-ends .link { font-size: inherit; line-height: inherit; }
.panel .confidence-hint { color: var(--ink-soft); }
.panel .meta { color: var(--ink-soft); margin: 0; font-size: var(--text-sm); }
/* And it wraps (M89 §11, A11). The head line is dates, place, lane, category,
   "map at Y" and the lens control, and on a phone the sheet cut it: "Africa ·
   Other  map at 1", "Asia · m". A line of six short things that will not wrap
   is a line that loses its last two on any pane narrower than it. Flex with a
   wrap rather than a plain `inline`, so the pieces break between themselves and
   never inside a date. */
@media (max-width: 720px) {
  .panel .meta {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: 0.15rem 0.35rem; overflow-wrap: anywhere;
  }
}
.panel .count { color: var(--ink-soft); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.panel .muted { color: var(--ink-soft); }
.panel .hint { color: var(--ink-soft); font-size: var(--text-sm); margin: 0 0 var(--space-2); }
.panel .when { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
/* An aside the card sets apart: what the reader is looking at and why. */
.panel .notice {
  padding: var(--space-2) var(--space-3); border-left: 3px solid var(--cobalt-soft);
  background: var(--ground); margin: 0 0 var(--space-3); font-size: var(--text-sm);
}
.panel .notice.disputed { border-color: var(--madder); background: var(--madder-faint); }
/* Who put the path on screen together. Madder, because the chain it is about
   is drawn madder: the notice and the picture it explains are the same colour,
   and it is a fact about the argument, so it is as loud as a dispute is. */
.panel .notice.generated { border-color: var(--madder); }
/* Whose material this is, where it is not the atlas's own. Quieter than a
   dispute — it is a fact about the file and not about the argument — but it
   is set apart, because the licence asks for it to be legible beside the
   text it covers. */
.panel .notice.licence, .entry .notice.licence { color: var(--ink-soft); font-family: var(--font-ui); }
/* The path walked, as a breadcrumb at the top of the panel. One madder rule
   down its left, which is the accent the map and the graph draw the same
   path in, so the three pictures agree about what the reader is holding. */
.panel .breadcrumb { border-left: 2px solid var(--madder); padding-left: var(--space-3); margin-bottom: var(--space-3); }
.panel .breadcrumb ol { list-style: none; margin: 0; padding: 0; font-size: var(--text-sm); }
.panel .breadcrumb li { display: inline; }
.panel .breadcrumb li + li::before { content: ' / '; color: var(--ink-soft); }
.panel .breadcrumb .current { color: var(--ink); font-weight: 600; }
.panel .breadcrumb .arrow { font-size: var(--text-xs); }
.panel .breadcrumb .actions { margin-top: var(--space-2); }

/* Back and Forward, above the card's title: the browser's own history, named
   so the reader knows what they are going back to before they press it. */
.panel .card-history { display: flex; gap: var(--space-3); margin: 0 0 var(--space-2); font-size: var(--text-sm); }
.panel .card-history .link.small { margin-left: 0; }
.panel .card-history .go-forward { margin-left: auto; }

/* The actors of an event, as chips in the head. The role is on the title
   attribute and comes out on hover or focus: six names have to fit on two
   lines, and six names each trailing a role would be the paragraph this card
   was rearranged to stop being. */
.panel .chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-2) 0 0; }
.panel .chips .chip {
  border: 1px solid var(--line); background: var(--ground); color: var(--ink);
  font-family: var(--font-ui); font-size: var(--text-sm); line-height: 1.6;
  padding: 0.05rem 0.5rem; border-radius: 999px; cursor: pointer;
}
.panel .chips .chip:hover, .panel .chips .chip:focus-visible { background: var(--cobalt-faint); border-color: var(--cobalt-soft); color: var(--cobalt); }
.panel .chips .chip.highlighted { border-color: var(--cobalt); color: var(--cobalt); }
.panel .chips .chip .role { display: none; font-style: italic; color: var(--ink-soft); }
.panel .chips .chip:hover .role, .panel .chips .chip:focus-visible .role { display: inline; }

/* The quiet links out of a card: the full entry, somebody else's account,
   and the way to disagree with the record. One line, at the foot of the
   head, so the card's own text is what follows the title. */
.panel .head-links { display: flex; flex-wrap: wrap; gap: var(--space-3); margin: var(--space-2) 0 0; font-size: var(--text-sm); }
.panel .head-links > p { margin: 0; }

/* A citation nobody has opened the source for says so, quietly. The atlas
   has 1,874 of them and none checked; a mark that shouted would be 1,874
   alarms, and no mark at all would be a claim it cannot support. */
.panel .citation .unchecked { color: var(--ink-soft); font-size: var(--text-xs); font-family: var(--font-ui); }
/* The standing marker (M70): one line on every card saying how far the record
   has been read. The same soft ink, size and interface face the `unchecked`
   mark above uses, because it is the same fact one level up — and `read` and
   `unread` look alike on purpose: a draft is drawn exactly as a signed record
   is, and this line says so rather than staging it. */
.panel .standing { color: var(--ink-soft); font-size: var(--text-xs); font-family: var(--font-ui); margin: var(--space-1) 0 0; }
.panel .badge.verified { border-color: var(--cobalt); color: var(--cobalt); }

/* A card's collapsible sections. The header is a button, so that Enter and
   Space work without a keydown handler anywhere, but it is set exactly like
   the section heads it replaced — the same serif, the same rule above, the
   same count beside it — because a control that announces itself as a
   control would make the card look like a form. What it does add is the
   marker: a triangle that turns, which is the one thing that says a header
   can be opened at all. */
.panel .card-section > .section-head { margin: 0; padding: 0; border: 0; display: block; }
.panel .section-toggle {
  width: 100%; text-align: left; background: none; border: 0; border-top: 1px solid var(--line);
  border-radius: 0; padding: var(--space-3) 0 var(--space-2); margin-top: var(--space-4);
  display: flex; align-items: baseline; gap: var(--space-2); cursor: pointer;
  font-family: var(--font); font-weight: normal; font-size: var(--text-lg); color: var(--cobalt);
}
.panel .section-toggle:hover { background: none; color: var(--ink); }
.panel .section-toggle:hover .section-marker { border-left-color: var(--ink); }
.panel .card-section:first-child .section-toggle { margin-top: 0; padding-top: 0; border-top: 0; }
/* A triangle drawn from a border, so it costs no glyph and no file, and
   turns a quarter when the section opens. */
.panel .section-marker {
  flex: none; width: 0; height: 0; align-self: center;
  border-left: 6px solid var(--cobalt-soft);
  border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  transition: transform 120ms ease;
}
.panel .card-section.open .section-marker { transform: rotate(90deg); }
.panel .section-label { flex: 1; min-width: 0; }
/* A count that carries a dispute is the whole reason the count is in the
   header: a closed section still says there is an argument inside it. */
.panel .section-toggle .count.disputed { color: var(--madder); }
.panel .section-body { padding-bottom: var(--space-2); }
/* Inside a section the head is already the level above, so the sub-headings
   the cards write stay where they are and nothing gains a second rule. */
.panel .section-body > h2 { margin-top: var(--space-4); }
.panel .section-body > *:first-child { margin-top: 0; }

/* Prose is the serif and stops at a readable measure; it is also the one
   place proportional figures are wanted, because they are being read as
   words rather than compared down a column. */
.panel .summary p, .panel .explanation {
  font-family: var(--font); font-size: var(--text-md); line-height: var(--leading-prose);
  font-variant-numeric: proportional-nums;
}

.panel .link { background: none; border: 0; padding: 0; color: var(--cobalt); text-decoration: underline; text-underline-offset: 0.15em; cursor: pointer; text-align: left; font: inherit; }
.panel .link:hover { color: var(--ink); }
.panel .link.small { font-size: var(--text-sm); margin-left: var(--space-2); }
.panel button:not(.link) {
  border: 1px solid var(--cobalt); background: var(--paper); color: var(--cobalt);
  padding: 0.25rem 0.7rem; border-radius: var(--radius); cursor: pointer; font-size: var(--text-sm);
}
.panel button:not(.link):hover { background: var(--cobalt-faint); }
.panel button:not(.link):disabled, .panel button:not(.link)[disabled] {
  border-color: var(--line); color: var(--ink-soft); background: var(--ground); cursor: not-allowed;
}
/* The one way out of a card, and there is no other (M84, close.js). The owner,
   22 September: *"Here you can see the 'close' button. Instead it should be a
   simple cross on the top right corner."*

   Floated rather than positioned, which is what makes it the *card's* corner
   and not the panel's: the title beside it is a flex container and shortens to
   clear the float by itself, the notices above it wrap around it line by line,
   and everything below it goes back to the full measure once it has passed —
   none of which a box taken out of the flow would do, and all of which a card
   whose head is three lines on a phone and one on a desk needs.

   No new hex value, no new type size: the cross is the multiplication sign in
   the interface's own face at the size a section head already is, inked cobalt
   as every control in the panel is and madder under the pointer as the lens
   chip's × already is. The square is 2.75rem because a touch target is 44 CSS
   pixels (WCAG 2.5.5) — a target size and not a type size, and the glyph
   inside it is `--text-lg` like everything else.

   Written `.panel button.card-close` and placed here, because `.panel
   button:not(.link)` above draws every other button in this panel as a bordered
   pill and a cross in a box is not a simple cross — and `:not(.link)` costs a
   class, so a rule naming only the two classes would lose to it wherever it
   stood. What this keeps from that rule is the corner radius and the faint
   ground under the pointer. */
.panel button.card-close {
  float: right;
  display: flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem;
  /* Pulled back into the padding the panel already keeps, so the cross sits in
     the corner of the card rather than a finger's width inside it. */
  margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) 0 var(--space-2);
  border: 0; background: none; padding: 0; cursor: pointer;
  font-family: var(--font-ui); font-size: var(--text-lg); line-height: 1;
  color: var(--cobalt);
}
/* And the ground the generic rule puts under a hovered button, kept: it is
   what makes a 44-pixel target look like one when a finger is on it. */
.panel button.card-close:hover { color: var(--madder); }
.panel .follow { text-align: left; }

/* Confidence, designed. Three states that have to be tellable apart in a
   list of forty rows: consensus is solid, probable is outlined, and disputed
   is dashed and madder and says the word — the atlas's worst possible
   failure is a contested link that looks settled. */
.panel .badge, .prose .badge {
  display: inline-block; padding: 0 0.45em; border-radius: var(--radius);
  font-size: var(--text-xs); font-family: var(--font-ui); line-height: 1.7;
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  border: 1px solid var(--cobalt-soft); color: var(--cobalt); white-space: nowrap;
}
.panel .badge.consensus, .prose .badge.consensus { background: var(--cobalt); color: var(--paper); border-color: var(--cobalt); }
.panel .badge.probable, .prose .badge.probable { background: var(--paper); }
.panel .badge.disputed, .prose .badge.disputed { border-style: dashed; border-color: var(--madder); color: var(--madder); background: var(--madder-faint); }
.panel .badge.status, .prose .badge.status { border-color: var(--madder); color: var(--madder); }

.panel .edges, .panel .steps, .panel .citations { list-style: none; padding: 0; margin: 0; }
.panel .edge-row { padding: var(--space-2) 0; border-top: 1px solid var(--line); }
.panel .edge-row:first-child { border-top: 0; }
/* A disputed link is marked twice — the dashed rule down its left and the
   ground behind it — so that scanning a list of consequences shows the
   disagreements without reading a word. */
.panel .edge-row.disputed {
  background: var(--madder-faint); border-left: 3px dashed var(--madder);
  padding-left: var(--space-2); margin-left: calc(-1 * var(--space-2));
}
.panel .edge-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.panel .arrow { color: var(--ink-soft); font-style: italic; }
.panel .via, .panel .depth { color: var(--ink-soft); font-size: var(--text-sm); }
.panel details { margin-top: var(--space-1); }
.panel summary { cursor: pointer; color: var(--ink-soft); font-size: var(--text-sm); }
.panel summary:hover { color: var(--cobalt); }
/* The other branches, in tiers by how far up each was met. The tier that fed
   the event directly is open; the rest are a line each until they are asked
   for, so a walk with a thousand ancestors is still a list a reader can see
   the shape of (health review B, finding 30). */
.panel .branch-tier { margin-top: var(--space-2); border-top: 1px solid var(--line); }
.panel .branch-tier > summary {
  padding: var(--space-2) 0; font-family: var(--font-ui); font-size: var(--text-sm);
  letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--ink-soft);
}
.panel .branch-tier[open] > summary { color: var(--cobalt); }
.panel .branch-tier > summary .count { margin-left: var(--space-2); }

.panel .dispute { border-left: 3px dashed var(--madder); padding-left: var(--space-3); margin-top: var(--space-2); }
.panel .last-step.disputed { border-left: 3px solid var(--madder); padding-left: var(--space-3); }

/* The walked path: one madder rule down the whole list, which is the same
   accent the map and the graph draw it in. */
.panel .steps .step { padding: var(--space-1) 0 var(--space-1) var(--space-4); border-left: 2px solid var(--madder); }
.panel .steps .step.start { border-left-color: var(--madder); }
.panel .steps .step.disputed .arrow { color: var(--madder); }
.panel .actions { margin: var(--space-3) 0 0; display: flex; gap: var(--space-2); }

/* A citation is set the way a footnote is: serif, hanging, and the
   identifiers underneath rather than in the middle of the sentence. */
.panel .citation {
  padding: var(--space-2) 0; border-top: 1px dotted var(--line);
  font-family: var(--font); font-size: var(--text-base); line-height: var(--leading);
  padding-left: 1.25rem; text-indent: -1.25rem;
}
.panel .citation:first-child { border-top: 0; }
/* `overflow-wrap` because a catalogue's search URL is one unbroken word a
   hundred and forty characters long, and an unbreakable word wider than the
   column does not wrap — it runs off the right of the panel and takes the
   citation's own text with it. Only ever seen on a card whose sources section
   is open, which was nothing that opened by itself until the link's card did
   (M80). Anywhere, not break-word: these are URLs and there is no word in one
   to keep whole. */
.panel .citation .identifiers {
  color: var(--ink-soft); font-size: var(--text-sm); display: block; text-indent: 0;
  font-family: var(--font-ui); overflow-wrap: anywhere;
}
.panel .citation.missing, .panel .unsafe-url { color: var(--madder); }
.panel code { font-size: 0.85em; background: var(--ground); padding: 0 0.25rem; border-radius: 2px; }

/* --- actors ---------------------------------------------------------- */

.panel .actor-type, .panel .source-type {
  text-transform: uppercase; letter-spacing: var(--tracking-label); font-size: var(--label);
  color: var(--ink-soft);
}
.panel .also-known { margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--ink-soft); }
/* The way out to somebody else's account of the same thing. Set apart from
   the card's own text: what the atlas says is the summary below it. */
.panel .wikipedia { margin: var(--space-2) 0 0; font-size: var(--text-sm); }
/* The credit under a quoted lead, and the importer's own note under that
   (M86 §1). One line in the interface's face and not the atlas's, because it
   is a statement about where the words above came from and not part of them;
   the note is behind `?review=1` and is never on the published page. */
.panel .summary-credit { margin: var(--space-1) 0 0; font-family: var(--font-ui); font-size: var(--text-xs); }
.panel .summary-credit a { color: var(--cobalt); }
.panel .provenance { margin: var(--space-2) 0 0; font-size: var(--text-sm); }
.panel .wikipedia a { color: var(--cobalt); }
.panel .wikipedia .muted { margin-left: var(--space-2); }

/* Relations and territory as tables. Two columns — what, and when — with
   anything further on a quiet second line under them, so a dozen periods
   scan down the dates instead of being read one paragraph at a time. */
.panel .actor-rows { list-style: none; padding: 0; margin: 0; }
.panel .actor-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--space-3); align-items: baseline;
  padding: var(--space-1) 0; border-top: 1px solid var(--line);
}
.panel .actor-row:first-child { border-top: 0; }
.panel .actor-row .row-what { min-width: 0; }
.panel .actor-row .when { white-space: nowrap; font-size: var(--text-sm); }
.panel .actor-row .row-go { grid-column: 2; justify-self: end; margin-left: 0; }
.panel .actor-row .row-meta {
  grid-column: 1 / -1; font-size: var(--text-sm); color: var(--ink-soft);
  display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: baseline;
}
.panel .actor-row.highlighted { border-left: 3px solid var(--cobalt); padding-left: var(--space-2); margin-left: calc(-1 * var(--space-2)); }
.panel .role { color: var(--ink-soft); font-style: italic; }
/* What this event is inside. In the head and not among the sections, because
   it is a fact about the record rather than one of the questions the card
   answers: `parent` never enters the adjacency. */
.panel .part-of-event { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--ink-soft); }
/* Two more facts about how this event is drawn rather than about what it was:
   what a lens on it would keep, and — where it is a large event — the band,
   the wash and the corner it is drawn as instead of a mark. Beside "Part of"
   and "Drawn in …", at the size those are: the head says how to read the
   pictures, and the sections below answer the questions. */
.panel .subtree-lens, .panel .large-event { margin: var(--space-2) 0 0; font-size: var(--text-sm); }
/* The note beside a role: what this actor did in *this* event, on a line of
   its own under the row so that a phrase never pushes the lane off the end. */
.panel .actor-row .row-note { grid-column: 1 / -1; font-size: var(--text-sm); }
/* What the place was called when. `names` is what search scans; this is what
   the years say (place.js). */
.panel .historical-names { list-style: none; padding: 0; margin: 0; font-size: var(--text-sm); }
.panel .historical-names li {
  display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: baseline; color: var(--ink-soft);
}
.panel .notice.actor { border-color: var(--cobalt); }
.panel .notice.narrative { border-color: var(--cobalt); }

/* Narratives: the list, and one being read. The step's own words are set in
   the serif the summaries use; everything around them stays interface. */
.panel .narrative-rows { list-style: none; padding: 0; margin: 0; }
.panel .narrative-row { padding: var(--space-2) 0; border-top: 1px solid var(--line); }
.panel .narrative-row .title { font-size: var(--text-base); }
.panel .narrative-row .summary { font-family: var(--font); margin: var(--space-1) 0 0; color: var(--ink-soft); }
.panel .step-text p { font-family: var(--font); font-size: var(--text-md); line-height: var(--leading-prose); font-variant-numeric: proportional-nums; }
.panel .step-record { border-left: 3px solid var(--cobalt-faint); padding-left: var(--space-3); }
.panel .narrative-walk .steps { list-style: none; padding: 0; margin: 0; }
.panel .narrative-walk .step { padding: var(--space-1) 0; border-left: 3px solid var(--cobalt-faint); padding-left: var(--space-2); }
.panel .narrative-walk .step.walked { border-color: var(--cobalt-soft); }
.panel .narrative-walk .step.current { border-color: var(--madder); }
.panel .part-of .narrative-row { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: baseline; }
.panel .cluster-list .when { min-width: 4ch; display: inline-block; }

/* --- offices --------------------------------------------------------- */

/* The line under the holders, saying why there is no Sources section: an
   office asserts that the post exists and nothing more, and the sources are
   on the tenures. Quiet, because it is a note about the model and not a
   claim about the post. */
.panel .office-cites { font-size: var(--text-sm); margin: var(--space-3) 0 0; }
.panel .tenure-row .row-meta .link { font-style: normal; }

/* One strip per office on the card of the actor the offices belong to. The
   SVG throws its aspect ratio away so that it fits whatever width the pane
   has without anything measuring the container (office.js); its height is
   fixed here, which is what keeps the bars the height they were drawn at.
   The `+n` of a merged bar is HTML over the strip for the same reason — a
   glyph stretched by an unknown factor is not a number. */
.panel .office-rows { list-style: none; padding: 0; margin: 0; }
.panel .office-row { padding: var(--space-2) 0; border-top: 1px solid var(--line); }
.panel .office-row:first-child { border-top: 0; }
.panel .office-row-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); margin: 0 0 var(--space-1);
}
.panel .tenure-strip { position: relative; }
.panel .tenure-strip svg { display: block; width: 100%; height: 24px; }
.panel .tenure-bar { fill: var(--cobalt-soft); cursor: pointer; }
.panel .tenure-bar:hover, .panel .tenure-bar:focus-visible { fill: var(--cobalt); }
.panel .tenure-bar.merged { fill: var(--cobalt); }
.panel .tenure-count {
  position: absolute; top: 0; transform: translateX(3px);
  font-size: var(--text-xs); color: var(--ink-soft); pointer-events: none;
}
.panel .strip-scale {
  display: flex; justify-content: space-between; font-size: var(--text-xs); margin: 0;
}
.panel .strip-empty { font-size: var(--text-sm); margin: 0; }

/* --- places ---------------------------------------------------------- */

.panel .place-events .when { min-width: 4ch; display: inline-block; }
/* An event outside the window is faded here for the same reason it is faded
   on the map: a place's history does not stop at the edge of the band. */
.panel .actor-row.faded { opacity: 0.45; }
/* Before and after, along `succeeded`: one heading per neighbour, with the
   years the succession happened in and how many events it holds. The events
   are that actor's and say so — an atlas that listed them under this one
   would be saying the two were the same record. */
.panel .succession h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); }
.panel .succession h3 + .actor-rows { margin-bottom: var(--space-3); }

/* First contact (src/intro.js). Over the view rather than inside the panel,
   because since H1c there is no panel when nothing is open — and nothing is
   open on a first visit. Not a modal: the map is drawn underneath and the
   card can be dismissed onto it, because what is behind it is the point. */
.intro-overlay {
  position: fixed; inset: 0; z-index: 20; overflow-y: auto;
  display: flex; align-items: flex-start; justify-content: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--ink) 28%, transparent);
}
/* `display: flex` on the class beats the browser's own `[hidden]` rule, and a
   dismissed overlay that still covered the map would swallow every click on
   it — which is exactly what it did until this line. */
.intro-overlay[hidden] { display: none; }
.intro-card {
  position: relative; max-width: 46rem; width: 100%; margin: auto;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--cobalt); border-radius: var(--radius);
  padding: var(--space-4) var(--space-4) var(--space-3);
  box-shadow: 0 2px 16px color-mix(in srgb, var(--ink) 22%, transparent);
}
.intro-card h2 { margin: 0; color: var(--cobalt); font-size: var(--text-xl); }
.intro-card h3 {
  margin: var(--space-4) 0 var(--space-2); color: var(--cobalt);
  font-family: var(--font-ui); font-size: var(--text-sm);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
}
.intro-card .intro-lead { margin: var(--space-2) 0; }
.intro-card ul, .intro-card ol { margin: 0; padding-left: var(--space-4); }
.intro-card ul { list-style: none; padding-left: 0; }
.intro-card li { padding: 0.15rem 0; }
.intro-card ol li { padding: 0.25rem 0; }
.intro-card .when { color: var(--ink-soft); font-size: var(--text-sm); }
.intro-card .intro-go {
  font-family: var(--font); font-size: var(--text-lg); color: var(--cobalt);
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left;
}
.intro-card .intro-go:hover { text-decoration: underline; }
.intro-card .intro-actions {
  display: flex; align-items: baseline; gap: var(--space-3);
  margin: var(--space-4) 0 0; padding-top: var(--space-3); border-top: 1px solid var(--line);
}
.intro-close {
  position: absolute; top: var(--space-2); right: var(--space-2);
  border: 0; background: none; color: var(--ink-soft);
  font-size: var(--text-lg); line-height: 1; cursor: pointer; padding: 0.2rem 0.4rem;
}
.intro-close:hover { color: var(--madder); }
.intro-button {
  border: 1px solid var(--line); background: var(--paper); color: var(--cobalt);
  border-radius: 50%; width: 1.6em; height: 1.6em; padding: 0; cursor: pointer;
  font-family: var(--font-ui); font-size: var(--text-sm); line-height: 1;
}
.intro-button:hover { border-color: var(--cobalt); }


/* --- the horizon: what did this lead to by year X? -------------------- */

.panel .horizon summary { font-family: var(--font); font-size: var(--text-lg); color: var(--cobalt); }
.panel .horizon summary strong { font-weight: normal; }
.panel .horizon details { margin-top: var(--space-5); padding-top: var(--space-3); border-top: 1px solid var(--line); }
.panel .horizon-control { display: flex; align-items: baseline; gap: var(--space-2); margin: var(--space-2) 0 var(--space-1); }
.panel .horizon-control label { color: var(--ink-soft); font-size: var(--text-sm); }
.panel .horizon-control input {
  width: 7ch; font: inherit; font-size: var(--text-sm); padding: 0.15rem 0.35rem;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.panel .horizon-row .when { min-width: 4ch; display: inline-block; }
.panel .horizon-row.disputed { border-left: 3px dashed var(--madder); padding-left: var(--space-2); }

/* --- sources --------------------------------------------------------- */

.panel .source-head h2 { font-style: italic; }
.panel .creators { color: var(--ink-soft); }
.panel .identifiers a { color: var(--cobalt); }
.panel .source-head .identifiers { margin: var(--space-1) 0 0; font-size: var(--text-sm); }
/* The citation as a running line, the way it would be typed in a footnote:
   what a reader copies out of the atlas and into their own notes. */
.panel .citation-full {
  font-family: var(--font); font-size: var(--text-md); line-height: var(--leading-prose); color: var(--ink-soft);
  border-left: 3px solid var(--cobalt-soft); padding-left: var(--space-3); margin: var(--space-3) 0 0;
}
.panel .citers .locator { color: var(--ink-soft); font-size: var(--text-sm); }
/* The title inside a citation is the door to the source's own card; it reads
   as part of the citation, not as a button on top of one. */
.panel .link.cite { font-style: normal; }

/* --- reading pages: contribute.html, about.html ----------------------- */

/* The same tokens, the same scale, the same masthead: about.html and
   sources.html are the atlas in prose and must not look like another site. */
.page-body { display: block; height: auto; }
.page-body .bar h1 a { color: inherit; text-decoration: none; }
/* A sheet, not a column of text on the ground: the reading pages are the
   atlas in prose and they are set the way the panel's cards are. */
.page {
  max-width: 42rem;
  margin: var(--space-5) auto var(--space-6);
  padding: var(--space-5) var(--space-5) var(--space-6);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.prose h2 {
  font-family: var(--font); font-weight: normal; font-size: var(--text-xl); color: var(--cobalt);
  margin: var(--space-6) 0 var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--line);
}
.prose h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.prose h3 { font-family: var(--font); font-weight: normal; font-size: var(--text-lg); color: var(--cobalt); margin: var(--space-5) 0 var(--space-1); }
.prose p, .prose li, .prose dd {
  font-family: var(--font); font-size: var(--text-md); line-height: var(--leading-prose);
  font-variant-numeric: proportional-nums;
}
.prose p.hint, .prose .hint { font-family: var(--font-ui); font-size: var(--text-sm); }
.prose a { color: var(--cobalt); text-underline-offset: 0.15em; }
.prose a:hover { color: var(--ink); }
.prose code { font-size: 0.85em; background: var(--ground); padding: 0 0.25rem; border: 1px solid var(--line); border-radius: 2px; }
.prose .levels dt { margin-top: var(--space-2); }
.prose .levels dd { margin: var(--space-1) 0 0 var(--space-5); }
.prose .footnote { color: var(--ink-soft); font-size: var(--text-sm); }
.prose .muted { color: var(--ink-soft); }

/* The bibliography on sources.html. A hanging indent, because that is what a
   bibliography looks like and the eye finds the author by it. */
/* Two groups since M85 (A17): the works, and the base maps under a heading of
   their own. No new token — the spacing is the page's own scale. */
.bib-group + .bib-group { margin-top: var(--space-6); }
.bib-summary { color: var(--ink-soft); font-size: var(--text-sm); }
.bib-list { list-style: none; padding: 0; margin: var(--space-4) 0 0; }
.bib-entry {
  padding: var(--space-3) 0; border-top: 1px solid var(--line);
  padding-left: 1.5rem; text-indent: -1.5rem;
}
.bib-entry.inactive { opacity: 0.6; }
.bib-citation { margin: 0; font-family: var(--font); font-size: var(--text-md); line-height: var(--leading-prose); }
.bib-citation a { text-decoration: none; }
.bib-citation a:hover { text-decoration: underline; }
.bib-meta {
  margin: var(--space-1) 0 0; text-indent: 0; color: var(--ink-soft);
  font-family: var(--font-ui); font-size: var(--text-sm);
}
.bib-type { text-transform: uppercase; letter-spacing: var(--tracking-label); font-size: var(--label); }
.bib-count { color: var(--cobalt); font-variant-numeric: tabular-nums; }
.bib-meta .unsafe-url { color: var(--madder); }

/* The narratives page. A card per account under the heading of each century
   it crosses: two accounts of the same years sit side by side, which is the
   whole reason the page arranges them rather than listing them. */
.narrative-period h2 { display: flex; align-items: baseline; gap: var(--space-2); }
.narrative-period .count { color: var(--ink-soft); font-family: var(--font-ui); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.narrative-cards { list-style: none; padding: 0; margin: var(--space-3) 0 0; }
.narrative-card {
  padding: var(--space-3) 0; border-top: 1px solid var(--line);
}
.narrative-card:first-child { border-top: 0; padding-top: 0; }
.narrative-card h3 {
  font-family: var(--font); font-weight: normal; font-size: var(--text-lg);
  margin: 0; line-height: var(--leading-tight);
}
.narrative-card h3 a { color: var(--cobalt); text-decoration: none; }
.narrative-card h3 a:hover { text-decoration: underline; }
/* Who wrote it, what years it is about, how long the walk is: one quiet line
   in the interface face, set apart from the account's own prose below it. */
.narrative-card .narrative-meta {
  margin: var(--space-1) 0 0; font-family: var(--font-ui); font-size: var(--text-sm);
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.narrative-card .narrative-summary {
  margin: var(--space-2) 0 0; font-family: var(--font); font-size: var(--text-md);
  line-height: var(--leading-prose); font-variant-numeric: proportional-nums;
}

/* --- the contribution form -------------------------------------------- */

.contrib { margin-top: 1.5rem; }
.contrib fieldset, .contrib .entry {
  border: 1px solid var(--line);
  background: var(--paper);
  padding: 0.75rem 1rem 1rem;
  margin: 0 0 1rem;
}
.contrib legend, .contrib .entry-head h3 {
  font-family: var(--font); font-weight: normal; font-size: var(--text-lg); color: var(--cobalt); margin: 0;
}
.contrib .entry-head { display: flex; align-items: baseline; justify-content: space-between; }
.contrib .field { margin-top: 0.75rem; }
.contrib label, .contrib .citations-label { display: block; font-size: var(--text-sm); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--ink-soft); }
.contrib input[type="text"], .contrib textarea, .contrib select {
  width: 100%; font: inherit; padding: 0.3rem 0.4rem;
  border: 1px solid var(--line); background: var(--ground); color: var(--ink); border-radius: 2px;
}
.contrib textarea { font-family: var(--font); font-size: var(--text-md); resize: vertical; }
.contrib input:focus, .contrib textarea:focus, .contrib select:focus { outline: var(--focus-width) solid var(--cobalt); background: var(--paper); }
.contrib .field.has-error input, .contrib .field.has-error textarea, .contrib .field.has-error select { border-color: var(--madder); }
.contrib .hint { color: var(--ink-soft); font-size: var(--text-sm); margin: 0.15rem 0 0; }
/* What the import claimed, and what a person has checked against the book.
   Neither is editable prose: one is read-only and the other is a tick. */
.contrib .identity-rows, .contrib .verify-rows { list-style: none; padding: 0; margin: 0.3rem 0 0; }
.contrib .identity-rows li, .contrib .verify-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; padding: 0.25rem 0; border-top: 1px solid var(--line); }
.contrib .identity-rows .lang { text-transform: uppercase; letter-spacing: var(--tracking-label); font-size: var(--text-xs); color: var(--ink-soft); }
.contrib .verify-row label { display: inline; text-transform: none; letter-spacing: 0; font-size: var(--text-sm); color: var(--ink); }
.contrib .verify-row.done label { color: var(--ink-soft); }
.contrib .verify-row .identifier { font-size: var(--text-sm); }
.contrib input[readonly] { background: var(--ground); color: var(--ink-soft); }
.queue-item .unverified { font-size: var(--text-xs); color: var(--ink-soft); }
.notice.citations { border-left: 3px solid var(--madder); background: var(--madder-faint); padding: 0.4rem 0.6rem; margin: 0.5rem 0; font-size: var(--text-sm); }
.contrib .field-error { color: var(--madder); font-size: var(--text-sm); margin: 0.2rem 0 0; }
.contrib .entry-errors { list-style: none; padding: 0.4rem 0.6rem; margin: 0.5rem 0 0; color: var(--madder); background: var(--madder-faint); }

.contrib .citations-head { display: flex; align-items: baseline; gap: 0.5rem; }
.contrib .citation-rows { list-style: none; padding: 0; margin: 0.25rem 0 0; }
.contrib .citation-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.25rem; }
/* The role became a `<select>` in M32b-1 and sits where its text input sat,
   so it takes the input's width and not the picker's. */
.contrib .citation-row select { flex: 1; }
.contrib .citation-row input { flex: 1; }

/* The reference picker (src/contribute/picker.js). Same list, same keys and
   the same cold white as the header's search box; the difference is what
   stands beside each hit, which is what tells two records of one name
   apart. The list is absolutely positioned so a form does not jump as it is
   typed into. */
.picker { position: relative; }
.picker > input { width: 100%; }
.picker-results {
  position: absolute; z-index: 5; top: calc(100% + 2px); left: 0; right: 0;
  max-height: 40vh; overflow-y: auto; margin: 0; padding: 0.25rem 0; list-style: none;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 2px 8px var(--cobalt-faint);
}
.picker-option { padding: 0.25rem 0.6rem; cursor: pointer; display: flex; gap: 0.4rem; align-items: baseline; flex-wrap: wrap; }
.picker-option.active { background: var(--cobalt-faint); }
.picker-option .picker-label { color: var(--ink); }
.picker-option .kind, .picker-option .when, .picker-option .muted, .picker-option .degree {
  font-size: var(--text-sm); color: var(--ink-soft);
}
.picker-option .kind { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-label); }
.picker-option .badge {
  font-size: var(--text-xs); padding: 0 0.3rem; border: 1px solid var(--madder);
  border-radius: 2px; color: var(--madder);
}
.picker-empty { padding: 0.35rem 0.6rem; color: var(--ink-soft); }
.picker-chosen { margin: 0.15rem 0 0; font-size: var(--text-sm); color: var(--ink-soft); }
.picker-chosen.unknown { color: var(--madder); }
/* What the chosen record already connects to: the other half of "is this the
   one I meant". */
.picker-links { list-style: none; margin: 0.15rem 0 0; padding: 0; font-size: var(--text-sm); color: var(--ink-soft); }
.picker-links li { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.contrib .citation-row .picker { flex: 2; }

.contrib .similar { border-left: 3px solid var(--madder); background: var(--madder-faint); padding: 0.5rem 0.75rem; margin-top: 0.5rem; }
.contrib .similar p { margin: 0 0 0.25rem; }
.contrib .similar ul { margin: 0 0 0.4rem; padding-left: 1.2rem; }
.contrib .acknowledge { display: flex; gap: 0.4rem; align-items: baseline; text-transform: none; letter-spacing: 0; font-size: var(--text-sm); color: var(--ink); }
.contrib .acknowledge input { width: auto; }

.contrib button {
  border: 1px solid var(--cobalt); background: var(--paper); color: var(--cobalt);
  padding: 0.2rem 0.6rem; border-radius: var(--radius); cursor: pointer;
}
.contrib button:hover { background: var(--cobalt-faint); }
.contrib button[disabled] { border-color: var(--line); color: var(--ink-soft); cursor: not-allowed; background: var(--ground); }
.contrib button.link { background: none; border: 0; padding: 0; color: var(--cobalt); text-decoration: underline; }
.contrib button.link.small { font-size: var(--text-sm); }
.contrib .add-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem; }
/* A step of a narrative is a paragraph, not a word: the row stacks rather
   than squeezing a textarea between two controls. */
.contrib .step-row { display: grid; grid-template-columns: 1fr auto auto; gap: 0.4rem; align-items: start; }
.contrib .step-row textarea { grid-column: 1 / -1; width: 100%; }
/* The order of the steps is the walk, so moving one is an edit and the
   controls sit on the row it belongs to. Arrows and no words: two of them,
   beside a remove that is already named. */
.contrib .row-move { display: inline-flex; gap: 0.2rem; }
.contrib .row-move button.link.small { font-size: var(--text-base); line-height: 1; padding: 0.15rem 0.35rem; text-decoration: none; }
.contrib .row-move button.link.small:hover { color: var(--ink); }
.contrib .row-move button.link.small[disabled] { opacity: 0.35; cursor: default; color: var(--ink-soft); }

.contrib .report { margin: 1rem 0; }
.contrib .report .summary { font-family: var(--font); font-size: var(--text-md); margin: 0.4rem 0 0; }
.contrib .report .summary.bad { color: var(--madder); }
.contrib .report .warning { color: var(--ink-soft); font-size: var(--text-sm); margin: 0.15rem 0; }
.contrib .bundle-preview summary { cursor: pointer; color: var(--ink-soft); font-size: var(--text-sm); }
.contrib .preview {
  max-height: 22rem; overflow: auto; background: var(--paper);
  border: 1px solid var(--line); padding: 0.5rem; font-size: var(--text-sm); margin: 0.4rem 0 0;
}
.contrib .submit-row { margin-top: 1rem; }
.contrib .submit { font-size: var(--text-md); padding: 0.4rem 0.9rem; }
.contrib .submit-note { color: var(--ink-soft); margin: 0.4rem 0 0; }

/* --- the review dashboard (review.html) -------------------------------- */
/* Not part of the atlas: a maintainer's page, unlinked, and the only one
   with two working columns — the queue stays put while a record is read. */

.review-page { max-width: none; }
.review-page .narrow { max-width: 46rem; }
/* Shown by src/review/main.js when the page is not on localhost: the public
   copy of a maintainer's page, which cannot write and now says so. Madder,
   because it is the one thing on this page a reader has to know before they
   start. */
.review-page .notice.deployed {
  max-width: 46rem; margin: 0 0 var(--space-3);
  border: 1px solid var(--madder); border-left-width: 3px; border-radius: var(--radius);
  background: var(--madder-faint); padding: var(--space-2) var(--space-3);
  font-family: var(--font-ui); font-size: var(--text-sm);
}
.dashboard-mount { margin-top: 1.5rem; }
.review { display: grid; grid-template-columns: minmax(16rem, 22rem) minmax(0, 1fr); gap: 1.5rem; align-items: start; }

.queue { position: sticky; top: 1rem; max-height: calc(100vh - 2rem); overflow: auto; border: 1px solid var(--line); background: var(--paper); padding: 0.75rem; }
.queue .progress { margin: 0 0 0.5rem; font-family: var(--font); font-size: var(--text-base); }
.queue .progress.good { color: var(--cobalt); }
.queue-search { width: 100%; padding: 0.3rem 0.4rem; border: 1px solid var(--line); background: var(--ground); color: var(--ink); font-family: var(--font-ui); }
.queue-search:focus { outline: var(--focus-width) solid var(--cobalt); background: var(--paper); }
.queue-filters { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.5rem; }
.chip {
  border: 1px solid var(--line); background: var(--ground); color: var(--ink-soft);
  font-family: var(--font-ui); font-size: var(--text-xs); padding: 0.1rem 0.45rem; border-radius: 999px; cursor: pointer;
}
.chip:hover { border-color: var(--cobalt-soft); color: var(--cobalt); }
.chip.on { background: var(--cobalt); border-color: var(--cobalt); color: var(--paper); }
.queue-kind { font-family: var(--font-ui); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--ink-soft); margin: 0.9rem 0 0.2rem; }
.queue-items { list-style: none; margin: 0; padding: 0; }

/* The virtual list. The scroller is the list itself and not the pane, the
   spacer inside it is as tall as every row would be, and the rows are placed
   inside that by src/review/list.js — which sets each row's top and height
   in pixels, so nothing here may give a row a height of its own. */
.queue-list { height: 26rem; overflow-y: auto; overflow-x: hidden; position: relative; margin-top: 0.4rem; border-top: 1px solid var(--line); }
.vlist-spacer { position: relative; }
.vlist-rows { position: absolute; inset: 0; }
.queue-count { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft); margin: 0.5rem 0 0; }
.queue-sort { align-items: baseline; }
.queue-sort-label { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft); }
.queue-item {
  display: block; width: 100%; text-align: left; border: 0; border-left: 3px solid transparent;
  background: none; color: var(--ink); font-family: var(--font-ui); font-size: var(--text-sm);
  padding: 0.25rem 0.4rem; cursor: pointer; overflow: hidden; box-sizing: border-box;
}
.queue-item .queue-marks { display: flex; flex-wrap: nowrap; gap: 0.3rem; align-items: baseline; overflow: hidden; }
.queue-item .queue-marks > * { flex: 0 0 auto; }
.queue-item .queue-marks .queue-id { flex: 0 1 auto; }
.queue-item .degree, .queue-item .claimed { font-size: var(--text-xs); color: var(--ink-soft); white-space: nowrap; }
.queue-item .claimed { color: var(--cobalt); }
.queue-item:hover { background: var(--cobalt-faint); }
.queue-item.current { border-left-color: var(--madder); background: var(--madder-faint); }
.queue-item .queue-label { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.queue-item .queue-id { color: var(--ink-soft); font-size: var(--text-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.queue-item .flag, .record-flags {
  display: inline-block; margin: 0.15rem 0.2rem 0 0; padding: 0 0.35rem;
  border: 1px solid var(--madder); color: var(--madder); font-size: var(--text-xs); border-radius: 2px;
}

.record { min-width: 0; }
.record-head h2 { font-family: var(--font); font-weight: normal; color: var(--cobalt); margin: 0; }
.record-id { color: var(--ink-soft); font-family: var(--font-ui); font-size: var(--text-sm); margin: 0.1rem 0 0.4rem; }
.review-note { border-left: 3px solid var(--madder); background: var(--madder-faint); margin: 0.4rem 0; padding: 0.4rem 0.6rem; font-family: var(--font); }
.record-flags { border-style: dashed; }
.record .editor { margin-top: 0.75rem; }
.record .sign-box { margin-top: 1rem; }
.record-actions { display: flex; align-items: baseline; gap: 0.75rem; margin-top: 1rem; }
.save-note { color: var(--ink-soft); margin: 0.5rem 0 0; }

@media (max-width: 900px) {
  .review { grid-template-columns: 1fr; }
  .queue { position: static; max-height: 22rem; }
}

/* The full entry (entry.html). The reading pages' sheet, with the long text
   set the way the panel's prose is and a table of contents beside nothing:
   a column, because an entry is read top to bottom and a sidebar would have
   the reader's eye leaving the text to find out where it is. */
.entry .entry-kind {
  margin: 0; font-family: var(--font-ui); font-size: var(--text-xs);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-soft);
}
.entry h1 {
  font-family: var(--font); font-weight: normal; font-size: var(--text-2xl); color: var(--cobalt);
  line-height: var(--leading-tight); margin: var(--space-1) 0 var(--space-2);
}
.entry .meta, .entry .also-known {
  font-family: var(--font-ui); font-size: var(--text-sm); color: var(--ink-soft); margin: 0 0 var(--space-2);
}
.entry .meta a { color: var(--cobalt); }
.entry .entry-back { margin: var(--space-2) 0; font-family: var(--font-ui); font-size: var(--text-sm); }
.entry .wikipedia { margin: 0 0 var(--space-2); font-family: var(--font-ui); font-size: var(--text-sm); }
.entry .entry-summary {
  margin: var(--space-4) 0; padding-left: var(--space-3); border-left: 3px solid var(--cobalt-faint);
}
.entry .entry-summary p { margin: 0; font-size: var(--text-md); }
.entry .count { color: var(--ink-soft); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.entry .notice {
  border: 1px solid var(--line); background: var(--ground); border-radius: var(--radius);
  padding: var(--space-2) var(--space-3); margin: 0 0 var(--space-3); font-family: var(--font-ui); font-size: var(--text-sm);
}
.entry .notice.status { border-color: var(--madder); background: var(--madder-faint); }

/* The table of contents. Small, in the interface face, and never mistaken
   for the entry's own first list. */
.entry-toc {
  margin: var(--space-5) 0 var(--space-4); padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--ground);
}
.entry-toc h2 {
  font-family: var(--font-ui); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-soft); margin: 0 0 var(--space-1); padding: 0; border: 0;
}
.entry-toc ul { list-style: none; padding: 0; margin: 0; }
.entry-toc li { font-family: var(--font-ui); font-size: var(--text-sm); line-height: var(--leading); }
.entry-toc li.level-3 { padding-left: var(--space-4); }

/* The entry itself. Line length is the sheet's; the marks and the record
   links are the two things the eye has to be able to tell apart from the
   prose without the prose becoming a page of blue. */
.entry-body blockquote {
  margin: var(--space-4) 0; padding-left: var(--space-4); border-left: 2px solid var(--cobalt-faint);
  color: var(--ink-soft);
}
.entry-body ul, .entry-body ol { padding-left: var(--space-5); }
.entry-body li { margin: var(--space-1) 0; }
.entry-body .record-link { text-decoration-style: dotted; }
.entry-body .cite-mark { text-decoration: none; font-family: var(--font-ui); }
.entry-body .cite-mark sup { font-size: var(--text-xs); }
.entry-body .cite-mark.unresolved {
  color: var(--madder); border-bottom: 1px dashed var(--madder); font-family: var(--font-ui); font-size: var(--text-sm);
}
.entry-body.empty p { color: var(--ink-soft); }

/* Chips: the actors of an event, read as a row of names rather than a table,
   because "who is in it" is a shorter question than "what did each do". */
.entry .chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; }
.entry .chip {
  display: inline-flex; align-items: baseline; gap: 0.35rem;
  padding: 0.1rem var(--space-2); border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper); font-family: var(--font-ui); font-size: var(--text-sm); text-decoration: none;
  color: var(--cobalt);
}
.entry .chip:hover { background: var(--cobalt-faint); }
.entry .chip .role { color: var(--ink-soft); font-size: var(--text-xs); }

.entry .entry-list { list-style: none; padding: 0; margin: 0; }
.entry .entry-list li {
  padding: var(--space-1) 0; border-top: 1px solid var(--line);
  font-family: var(--font-ui); font-size: var(--text-base);
}
.entry .entry-list li:first-child { border-top: 0; }
.entry .entry-list .when, .entry .entry-list .role, .entry .entry-list .muted {
  color: var(--ink-soft); font-size: var(--text-sm);
}
.entry .entry-relations h3 {
  font-family: var(--font-ui); font-size: var(--text-sm); color: var(--ink-soft);
  text-transform: uppercase; letter-spacing: 0.06em; margin: var(--space-4) 0 var(--space-1);
}

/* The citation list, with the number the entry's marks aim at hanging in the
   margin: a bibliography, and the other end of every mark above it. */
.entry .citations { list-style: none; padding: 0; margin: 0; }
.entry .citation {
  padding: var(--space-2) 0 var(--space-2) 2rem; text-indent: -2rem;
  border-top: 1px solid var(--line); font-family: var(--font); font-size: var(--text-base);
}
.entry .citation:first-child { border-top: 0; }
.entry .citation:target { background: var(--cobalt-faint); }
.entry .citation .cite-number { font-family: var(--font-ui); font-size: var(--text-sm); color: var(--ink-soft); }
.entry .citation .identifiers, .entry .citation .locator {
  color: var(--ink-soft); font-size: var(--text-sm); font-family: var(--font-ui);
}
.entry .citation .identifiers { display: block; text-indent: 0; }
.entry .citation.missing, .entry .unsafe-url { color: var(--madder); }

/* The way from a search result to a page of its own. Small and at the end of
   the row: the row itself still chooses the record inside the atlas, which is
   what a reader searching for something usually wants. */
.search-entry {
  margin-left: auto; color: var(--cobalt); font-size: var(--text-xs); font-family: var(--font-ui);
  text-decoration: none; white-space: nowrap;
}
.search-entry:hover { text-decoration: underline; }

/* "Read the full entry", on every card that can have one. */
.panel .entry-link { margin: var(--space-1) 0; font-family: var(--font-ui); font-size: var(--text-sm); }
.panel .entry-link a { color: var(--cobalt); }
/* Quieter than the entry link above it: disagreeing with a record is always
   available and is not what the reader came for. */
.panel .discuss { margin: var(--space-1) 0; font-family: var(--font-ui); font-size: var(--text-xs); }
.panel .discuss a { color: var(--ink-soft); }
.panel .discuss a:hover { color: var(--madder); }
/* Two ways to disagree on one line: the issue, and the record as it should
   read. Separated so neither reads as the other's continuation. */
.discuss .edit-record { margin-left: 0.6rem; }
.entry .discuss { margin: var(--space-1) 0; font-family: var(--font-ui); font-size: var(--text-xs); }
.entry .discuss a { color: var(--ink-soft); }
.entry .discuss a:hover { color: var(--madder); }

/* The live preview of a full entry, under the textarea it is typed into: the
   contribution form and the dashboard's editor draw the same block. */
.entry-preview {
  margin: var(--space-2) 0 0; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--ground);
}
.preview-label {
  margin: 0 0 var(--space-1); font-family: var(--font-ui); font-size: var(--text-xs);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-soft);
}
.preview-body { font-family: var(--font); font-size: var(--text-base); line-height: var(--leading-prose); }
.preview-body h2, .preview-body h3 {
  font-family: var(--font); font-weight: normal; color: var(--cobalt);
  font-size: var(--text-lg); margin: var(--space-3) 0 var(--space-1); padding: 0; border: 0;
}
.preview-body h3 { font-size: var(--text-base); }
.preview-body p { margin: var(--space-2) 0; }
.preview-notes { list-style: none; padding: 0; margin: var(--space-2) 0 0; }
.preview-notes li {
  font-family: var(--font-ui); font-size: var(--text-sm); color: var(--madder);
  padding: 0.1rem 0 0.1rem var(--space-3); border-left: 2px solid var(--madder);
}
.queue-item .has-entry {
  display: inline-block; margin: 0.15rem 0.2rem 0 0; padding: 0 0.35rem;
  border: 1px solid var(--cobalt); color: var(--cobalt); font-size: var(--text-xs); border-radius: 2px;
}

/* H6b: what stands beside the record on review.html — both ends of a link,
   the record's history, the claim, and the diff Sign is about to write. */
.record-context { margin: 0.4rem 0; }
.record-context .context-head { font-family: var(--font-ui); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--ink-soft); margin: 0 0 0.3rem; }
.context-ends { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
.context-end { border: 1px solid var(--line); background: var(--paper); padding: 0.4rem 0.5rem; min-width: 0; }
.context-end h4 { margin: 0; font-family: var(--font); font-weight: normal; color: var(--cobalt); font-size: var(--text-base); }
.context-end .context-side { margin: 0 0 0.1rem; font-family: var(--font-ui); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--ink-soft); }
.context-end .context-id { margin: 0.1rem 0; font-family: var(--font-ui); font-size: var(--text-xs); color: var(--ink-soft); word-break: break-all; }
.context-end .context-marks { margin: 0.2rem 0; display: flex; flex-wrap: wrap; gap: 0.3rem; font-family: var(--font-ui); font-size: var(--text-xs); }
.context-end .context-marks .status { color: var(--ink-soft); }
.context-end .context-marks .status.retracted { color: var(--madder); }
.context-end .context-summary { margin: 0.2rem 0 0; font-family: var(--font); font-size: var(--text-sm); }
.record-history, .record-diff { margin: 0.4rem 0; font-family: var(--font-ui); font-size: var(--text-sm); }
.record-history summary, .record-diff summary { cursor: pointer; color: var(--ink-soft); }
.history-list, .diff-list { list-style: none; margin: 0.3rem 0 0; padding: 0; }
.history-list li, .diff-list li { display: flex; gap: 0.5rem; padding: 0.15rem 0; border-bottom: 1px solid var(--line); font-size: var(--text-xs); }
.history-list .history-on, .diff-list .diff-field { flex: 0 0 8rem; color: var(--ink-soft); }
.history-list .history-signed { color: var(--cobalt); }
.diff-list .diff-before { flex: 1 1 0; min-width: 0; color: var(--ink-soft); text-decoration: line-through; overflow: hidden; text-overflow: ellipsis; }
.diff-list .diff-after { flex: 1 1 0; min-width: 0; color: var(--madder); overflow: hidden; text-overflow: ellipsis; }
.record-claim { display: flex; gap: 0.5rem; align-items: baseline; margin: 0.3rem 0; font-family: var(--font-ui); font-size: var(--text-sm); }
.record-claim .claim-held { margin: 0; color: var(--cobalt); }
