/* ===================================================================
   TeploMaps: one stylesheet.

   HOW IT SHOULD LOOK, as rules rather than preferences:

   Borders, not elevation. Every surface is a one-pixel hairline in the
     same near-black as the polygon outlines. No drop shadows, no soft
     cards, no rounded corners. The interface is made of the same line
     the map is made of.
   One accent, and it is the ramp. Highlights, selected states and the
     subscribe row use colours taken from the orange-to-red scale.
     Nothing introduces a colour the map does not already contain.
   Colour is reserved for data. Chrome stands out through size, weight
     or position, never through the ramp.
   Two typefaces, each with a job: a grotesque for the interface, the
     map's own label face for the map. Tabular numerals everywhere, so a
     column lines up and a changing number does not shift its neighbours.
   Panels share one geometry. Every panel that opens over the map uses
     the same width, position and height, defined once below. The reader
     learns one place where answers appear.
   The bottom is the control zone. Everything touched more than once a
     session sits within thumb reach. The top corners are navigation.
   Motion only in response to a tap, and every transition honours a
     reduced-motion preference.

   THE BREAKPOINT is 900px and appears here once, in the media query at
   the foot of this file. build.py holds the authoritative value and
   map.js reads it from the generated config; checks.py refuses the build
   if this file and that value disagree. Do not add a second query at a
   different width.
   =================================================================== */

:root{
  /* Opt out of every automatic dark treatment. `only light` tells the
     browser this page has no dark variant, so Safari and Chrome stop
     recolouring it and native controls render light. */
  color-scheme: only light;

  /* ColorBrewer OrRd, six class, the same array build.py classifies
     with. Swatches in the markup are written from that array, so these
     exist for the chrome that borrows from it, not as a second copy. */
  --c1:#fef0d9; --c2:#fdd49e; --c3:#fdbb84; --c4:#fc8d59; --c5:#e34a33; --c6:#b30000;

  --stroke:#232323;              /* polygon outline colour, from the .qgs */
  --nodata:#e4e1dc;
  --ink:#1b1b1b;
  --muted:#6e6a63;
  --hair:#232323;
  --hairsoft:rgba(35,35,35,.13);
  --surface:#ffffff;
  --paper:#f6f4f1;
  --frame-bg:#d8d4cd;

  /* Panel geometry, defined once. Anything positioned above the control
     bar references --barh, so when the bar changes height the
     neighbourhood box, the hint and the map attribution move with it. */
  --barh:126px;
  --panelh:208px;

  /* A grotesque for the interface, the map's label face for the map.
     Neither is fetched from a third party: the performance budget puts
     fonts on our own origin, and until the files are here the stack
     resolves to the system UI face. Drop @font-face blocks in above and
     nothing else changes. */
  --font-ui:'Archivo',system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --font-map:'Open Sans',system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
}

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html{color-scheme:only light}
input,button,select,textarea{color-scheme:only light;font-family:inherit}
html,body{margin:0;padding:0}
body{
  font-family:var(--font-ui);
  color:var(--ink);
  background:var(--paper);
  font-variant-numeric:tabular-nums;
  -webkit-text-size-adjust:100%;
}
a{color:var(--ink)}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap}

/* A placeholder figure is marked wherever it appears, in one style. */
.syn{display:inline-block;width:5px;height:5px;background:#b30000;
  border-radius:50%;margin-left:5px;vertical-align:2px}

/* ===================================================================
   The provenance banner. Not dismissible: a preview build that can be
   made to look like a release is the failure the record exists to stop.
   =================================================================== */
#provenance{
  background:var(--c2);border-bottom:1px solid var(--hair);
  padding:9px 14px;font-size:12px;line-height:1.45;color:#3b3833}
#provenance strong{display:block;font-size:12.5px;color:#7a1a00}
#provenance span{display:block;max-width:78ch;margin-top:2px}

/* ===================================================================
   The site bar, on every page that is not a map.
   =================================================================== */
#sitebar{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  flex-wrap:wrap;
  background:var(--surface);border-bottom:1px solid var(--hair);
  padding:11px 16px}
#sitename{font-size:14.5px;font-weight:700;letter-spacing:-.02em;
  text-decoration:none}
#sitenav{display:flex;gap:0;flex-wrap:wrap}
#sitenav a{
  font-size:11.5px;color:var(--muted);text-decoration:none;
  padding:5px 10px;border:1px solid transparent}
#sitenav a:hover{color:var(--ink);text-decoration:underline;text-underline-offset:2px}
#sitenav a[aria-current="page"]{color:var(--ink);font-weight:600;
  border-color:var(--hairsoft);background:var(--paper)}
#sitenav a.hl{color:#7a1a00;font-weight:600}

/* ===================================================================
   Page-level type. One measure, so a paragraph is readable at any width.
   =================================================================== */
main{max-width:820px;margin:0 auto;padding:26px 16px 40px}
main.home{max-width:960px}
h1{font-size:27px;font-weight:700;letter-spacing:-.03em;line-height:1.1;margin:0 0 10px}
h2{font-size:17px;font-weight:700;letter-spacing:-.02em;margin:32px 0 8px}
h3{font-size:13.5px;font-weight:700;margin:20px 0 5px}
p{font-size:14px;line-height:1.62;color:#33302b;margin:0 0 13px;max-width:70ch}
ul,ol{font-size:14px;line-height:1.62;color:#33302b;max-width:70ch}
.lede{color:var(--muted);font-size:12.5px;max-width:78ch}
.fine{font-size:11.5px;line-height:1.55;color:var(--muted);max-width:78ch}
.fine.warn{border-left:3px solid var(--c5);padding-left:10px}
.caveat{font-size:12.5px;color:var(--muted);border-top:1px solid var(--hairsoft);
  padding-top:11px;margin-top:16px}
.more{display:inline-block;font-size:13px;font-weight:600;
  border:1px solid var(--hair);padding:9px 13px;text-decoration:none;
  background:var(--surface)}
.more:hover{background:var(--stroke);color:#fff}
.prose h2{border-top:1px solid var(--hairsoft);padding-top:20px}
.prose h2:first-of-type{border-top:0;padding-top:0}

/* ===================================================================
   Home, the atlas
   =================================================================== */
.hero{border-bottom:1px solid var(--hair);padding-bottom:20px}
.hero h1{max-width:16ch}
.hero p{font-size:15px;max-width:62ch}
.atlas-full h2{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.atlas-full h2 span{font-size:11px;font-weight:400;color:var(--muted)}
.atcity{font-size:10px;font-weight:700;color:var(--muted);letter-spacing:.05em;
  text-transform:uppercase;margin:20px 0 7px}
.atgrid{list-style:none;margin:0;padding:0;display:grid;gap:-1px;
  grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
.atcard{margin:0 -1px -1px 0}
.atcard > a,.atcard > .dead{
  display:flex;align-items:center;gap:12px;position:relative;
  border:1px solid var(--hair);padding:12px 14px;background:var(--surface);
  text-decoration:none;height:100%}
.atcard > a:hover{background:var(--c1)}
.atcard .thumb{flex:0 0 auto;width:46px;height:46px;border:1px solid var(--hair);
  background:#f7f5f2;display:block}
.atcard .thumb svg{display:block;width:100%;height:100%}
.atcard .tx{min-width:0}
.atcard strong{display:block;font-size:13.5px;font-weight:600;letter-spacing:-.01em;
  line-height:1.2}
.atcard .tx span{display:block;font-size:10.5px;color:var(--muted);margin-top:3px}
.atcard.soon strong{color:#a4a09a}
.atcard.soon .thumb{opacity:.45}
.atcard em{position:absolute;right:12px;top:12px;font-style:normal;font-size:9.5px;
  color:var(--muted);border:1px solid rgba(35,35,35,.28);padding:1px 5px}
.cols{display:grid;gap:-1px;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));
  margin-top:34px}
.pcard{display:block;border:1px solid var(--hair);padding:13px 14px;margin:0 -1px -1px 0;
  background:var(--surface);text-decoration:none}
.pcard:hover{background:var(--c1)}
.pcard strong{display:block;font-size:13.5px;font-weight:600}
.pcard span{display:block;font-size:11px;color:var(--muted);margin-top:4px;line-height:1.45}
.pcard.hl{background:var(--c2);box-shadow:inset 3px 0 0 var(--c6)}
.pcard.hl span{color:#6b5a3e}

/* ===================================================================
   Neighbourhood page, the search layer
   =================================================================== */
.crumbs{font-size:11px;color:var(--muted);margin-bottom:14px}
.crumbs a{color:var(--muted)}
.crumbs span{margin:0 2px}
.nhead{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;
  border-bottom:1px solid var(--hair);padding-bottom:16px}
.nhead h1{margin:0}
.nhead .sub{font-size:12.5px;color:var(--muted);margin:5px 0 0}
.nloc{flex:0 0 auto;width:96px;height:96px;border:1px solid var(--hair);
  background:var(--surface);padding:5px}
.nloc svg{display:block;width:100%;height:100%}
.answer{margin-top:20px}
.answer .lead{font-size:16px;line-height:1.6;max-width:64ch}
.answer .fig{font-size:23px;font-weight:700;letter-spacing:-.03em}
.bandstrip{display:flex;height:12px;width:100%;max-width:420px;
  margin:18px 0 8px;position:relative}
.bandstrip i{flex:1;display:block}
/* Same mark as the legend on the map, so a reader who has seen one
   recognises the other. */
.bandstrip .tick{position:absolute;top:-4px;bottom:-4px;width:2px;
  background:var(--ink);box-shadow:0 0 0 1px rgba(255,255,255,.9);
  /* centred on its position, so the highest-valued neighbourhood, which
     lands at exactly 100%, still has its mark on the strip. */
  transform:translateX(-50%)}
.bandnote{font-size:11.5px;color:var(--muted);margin-top:10px}
.kvtable{width:100%;border-collapse:collapse;font-size:13px;margin-top:6px}
.kvtable th,.kvtable td{border-bottom:1px solid var(--hairsoft);
  padding:9px 8px;text-align:left;vertical-align:baseline}
.kvtable th{font-weight:600;width:34%}
.kvtable .unit{display:block;font-size:10.5px;font-weight:400;color:var(--muted)}
.kvtable .num{font-weight:600;text-align:right;white-space:nowrap;width:16%}
.kvtable .ctx{font-size:11px;color:var(--muted);white-space:nowrap}
.nbrlist{list-style:none;margin:8px 0 0;padding:0;display:grid;gap:-1px;
  grid-template-columns:repeat(auto-fill,minmax(190px,1fr))}
.nbrlist li{margin:0 -1px -1px 0}
.nbrlist a{display:block;border:1px solid var(--hair);padding:10px 12px;
  background:var(--surface);text-decoration:none;height:100%}
.nbrlist a:hover{background:var(--c1)}
.nbrlist strong{display:block;font-size:13px;font-weight:600}
.nbrlist span{display:block;font-size:11px;color:var(--muted);margin-top:3px}
.onmap{margin-top:26px}

/* ===================================================================
   Below the map
   =================================================================== */
#below{max-width:980px}
#below .commentary p{max-width:70ch}
#below .commentary ul{max-width:70ch;margin:0 0 14px;padding-left:20px}
#below .commentary li{margin:3px 0;line-height:1.55}
.srclink{margin-top:14px}
/* ===================================================================
   The figures list. A table at every width, on every map.

   It was once a seven-column table in a box capped at 70vh with
   overflow:auto. On a phone that is three nested scroll contexts, the
   page, the box, and sideways for the columns, and the middle one traps a
   thumb travelling down the page. Scroll trapping is the single worst
   thing you can do to a touch reader. The answer then was to drop the
   table below the breakpoint and stack each row into a labelled block.

   That went too far. A table is what a reader came for, and only the
   MIDDLE of those three contexts was ever the trap. So the table stays a
   table at every width and scrolls SIDEWAYS ONLY when the columns do not
   fit. The page keeps its own vertical scroll and a thumb travelling down
   is never caught.

   overflow-y is `hidden`, not `visible`, and that is load-bearing rather
   than tidy: setting one axis to auto while the other stays visible makes
   the used value of the visible one `auto` too, which would quietly
   recreate the vertical inner scroller this whole comment is about. The
   box has no height limit below the breakpoint, so nothing is clipped.
   =================================================================== */
.tablewrap{margin:0;overflow-x:auto;overflow-y:hidden;
  -webkit-overflow-scrolling:touch}

.figtable{width:100%;border-collapse:collapse;font-size:12.5px}
.figtable th,.figtable td{padding:7px 10px;text-align:left;
  white-space:nowrap;border-bottom:1px solid var(--hairsoft)}
.figtable thead th{background:var(--surface);
  border-bottom:1px solid var(--hair);font-size:10.5px;font-weight:700;
  letter-spacing:.02em}
.figtable tbody th{font-size:12.5px;font-weight:600;letter-spacing:-.01em}
.figtable tbody td{font-variant-numeric:tabular-nums}
.figtable tbody tr:hover{background:var(--c1)}
.figtable .num{text-align:right}

.rellist{list-style:none;margin:10px 0 16px;padding:0;display:grid;gap:-1px;
  grid-template-columns:repeat(auto-fill,minmax(230px,1fr))}
.rellist li{margin:0 -1px -1px 0}
.rellist a,.rellist .dead{display:block;position:relative;
  border:1px solid var(--hair);padding:11px 13px;background:var(--surface);
  text-decoration:none;height:100%}
.rellist a:hover{background:var(--c1)}
.rellist strong{display:block;font-size:13px;font-weight:600}
/* The note under a card's title. Scoped to a direct child: an unscoped
   `.rellist span` also matched the card wrapper itself, which is a span on
   an unbuilt map and an anchor on a built one, so every unbuilt card
   carried a 3px top margin the built one did not and sat lower than its
   neighbour by exactly that much. */
.rellist a > span,.rellist .dead > span{display:block;font-size:10.5px;
  color:var(--muted);margin-top:3px}
.rellist .dead strong{color:#a4a09a}
.rellist em{position:absolute;right:11px;top:11px;font-style:normal;font-size:9.5px;
  color:var(--muted);border:1px solid rgba(35,35,35,.28);padding:1px 5px}

/* ===================================================================
   Email capture. One route only, and it never appears uninvited.
   =================================================================== */
.ecform{display:flex;gap:7px;margin:14px 0 0;max-width:440px}
.ecform input{flex:1;min-width:0;font-size:13.5px;border:1px solid var(--hair);
  padding:10px;background:var(--surface);color:var(--ink);border-radius:0;
  -webkit-appearance:none}
.ecform input::placeholder{color:#a4a09a}
.ecform button{background:var(--stroke);color:#fff;border:1px solid var(--stroke);
  font-size:12.5px;font-weight:600;padding:10px 15px;cursor:pointer}
.ecform button:focus-visible,.ecform input:focus-visible{
  outline:2px solid #b30000;outline-offset:1px}
.ecpot{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}

/* The contact form. Two boxes stacked rather than one on a line, so the
   labels stay visible: a pair of unlabelled boxes is a guessing game in a
   way a single email box is not. */
.contactform{margin:18px 0 0;max-width:440px}
.ctfield{margin:0 0 12px}
.ctfield label{display:block;font-size:12px;font-weight:600;
  color:var(--ink);margin-bottom:4px}
.ctfield input,.ctfield textarea{
  display:block;width:100%;box-sizing:border-box;font-family:inherit;
  font-size:13.5px;line-height:1.45;color:var(--ink);
  border:1px solid var(--hair);background:var(--surface);padding:8px 10px}
.ctfield textarea{resize:vertical;min-height:88px}
.ctfield input::placeholder,.ctfield textarea::placeholder{color:#a4a09a}
.contactform button{background:var(--stroke);color:#fff;border:1px solid var(--stroke);
  font-family:inherit;font-size:13.5px;font-weight:600;padding:9px 16px;
  cursor:pointer}
.contactform button:hover{background:#000}
.contactform button:focus-visible,.ctfield input:focus-visible,
.ctfield textarea:focus-visible{outline:2px solid #b30000;outline-offset:2px}
.ecdone{font-size:13.5px;line-height:1.55;margin:14px 0 0;
  border-left:3px solid var(--c5);padding-left:11px;max-width:440px}
.ecdone b{font-weight:600}

/* ===================================================================
   Footer
   =================================================================== */
#sitefoot{border-top:1px solid var(--hair);background:var(--surface);
  padding:24px 16px 30px}
/* Sections only, on one line. The atlas is not repeated here. */
.fsections{max-width:820px;margin:0 auto;display:flex;flex-wrap:wrap;
  gap:6px 20px}
.fsections a{font-size:12.5px;color:var(--ink);text-decoration:none}
.fsections a:hover{text-decoration:underline;text-underline-offset:2px}
.fnote{max-width:820px;margin:20px auto 0;font-size:11px;
  color:var(--muted);line-height:1.55}
/* The mark. Name and year, set a shade firmer than the note above it. */
.fmark{max-width:820px;margin:10px auto 0;font-size:12px;font-weight:600;
  letter-spacing:-.01em;color:var(--ink)}

/* ===================================================================
   THE MAP SHELL
   Ported from the frozen v3.4 prototype. The one structural change is
   that the page now scrolls: the frame is most of the first screen
   rather than all of it, so something below the fold signals there is
   more. Everything inside it positions against the frame exactly as
   before.
   =================================================================== */
body.mappage{background:var(--frame-bg)}
#frame{
  position:relative;width:100%;
  /* The whole screen. The map is the product, so it gets all of it, and
     nothing peeks below the fold.

     dvh, not vh or svh. `vh` is the viewport with the browser chrome at
     its smallest, so on a phone with the address bar showing the frame
     overflows and the page scrolls when it should not. `svh` is the
     opposite, sized against the largest chrome, so once the address bar
     collapses a band of the next section appears underneath. `dvh`
     tracks the viewport as the bar moves, which is the only one of the
     three that fills the screen in both states.

     The cost, taken knowingly: nothing on screen says the page
     continues. "Read the explanation" in the top bar is the whole
     signal, and a viewport-filling first screen is the classic false
     bottom. That is the trade for a map that fills the phone. */
  height:100dvh;min-height:460px;
  background:var(--surface);overflow:hidden;
  border-bottom:1px solid var(--hair)}
@supports not (height:1dvh){ #frame{height:100vh} }
/* Full screen means the frame is the screen, so the height that keeps
   the page scrollable has to come off. Two rules, not one selector list:
   a pseudo-class the browser does not know invalidates the whole list,
   and Safari knows only the prefixed one. */
#frame:fullscreen{height:100%;border-bottom:0}
#frame:-webkit-full-screen{height:100%;border-bottom:0}

#map{position:absolute;inset:0;background:#f4f4f2}

/* Shown until the geometry has parsed and drawn. Without it a visitor on
   a slow connection sees an empty basemap and leaves, and that is
   traffic already paid for. */
#loading{
  position:absolute;inset:0;z-index:610;display:flex;
  align-items:center;justify-content:center;
  background:rgba(244,244,242,.92);transition:opacity .3s ease}
#loading.off{opacity:0;pointer-events:none}
#loading span{background:var(--surface);border:1px solid var(--hair);
  padding:10px 14px;font-size:12.5px;font-weight:600;color:var(--ink)}
#noscript{position:absolute;inset:0;z-index:611;display:flex;
  flex-direction:column;align-items:center;justify-content:center;gap:4px;
  background:var(--paper);padding:20px;text-align:center}

/* OSM Standard is full-colour cartography, greens, pinks, tans, and it
   fights a sequential ramp. Muting the tiles keeps the choropleth the
   loudest thing on screen. Set --basemap-filter:none to see raw OSM. */
:root{--basemap-filter:saturate(.28) brightness(1.1) contrast(.92)}
.leaflet-tile-pane{filter:var(--basemap-filter)}
.leaflet-container{font-family:var(--font-ui);background:#f4f4f2}

/* Attribution anchors to the bottom of the map, which the control bar can
   cover. Lifting it above --barh is a licence requirement, not a layout
   preference. */
.leaflet-bottom{bottom:calc(var(--barh) + 4px)}
.leaflet-control-attribution{
  font-size:9px;line-height:1.35;background:rgba(255,255,255,.86);
  padding:2px 6px;color:var(--muted);max-width:88vw;transition:opacity .3s ease}
.leaflet-control-attribution a{color:var(--muted);text-decoration:underline;
  text-underline-offset:1px}
.leaflet-control-attribution.tucked{max-width:26px;overflow:hidden;
  white-space:nowrap;cursor:pointer}
.leaflet-control-attribution.tucked span,
.leaflet-control-attribution.tucked a{visibility:hidden}
.leaflet-control-attribution.tucked::before{
  content:'\00a9';visibility:visible;position:absolute;left:6px;top:2px;font-size:10px}

/* Map labels, the text block from the .qgs: Open Sans Bold, #323232,
   4px #fafafa buffer. Value carries the map, name confirms it. */
.labelpane{pointer-events:none}
.nlabel{
  font-family:var(--font-map);color:#323232;text-align:center;
  white-space:nowrap;line-height:1.05;
  text-shadow:0 0 4px #fafafa,0 0 4px #fafafa,0 0 4px #fafafa,0 0 2px #fafafa,0 0 1px #fafafa;
  transform:translate(-50%,-50%)}
.nlabel .v{display:block;font-size:11.5px;font-weight:700;font-variant-numeric:tabular-nums}
/* The name used to be 8.5px at opacity .9, under a value that is 11.5px
   bold at full strength, so three things stacked against it: a quarter
   smaller, a tenth fainter, and a lighter weight. It also shared the
   value's halo, and a 4px blur around a 10px glyph with thin strokes
   eats the letterforms rather than separating them from the map. Bigger,
   at full strength, with a tighter halo of its own. */
.nlabel .n{display:block;font-size:10px;font-weight:600;letter-spacing:.005em;
  text-shadow:0 0 3px #fafafa,0 0 3px #fafafa,0 0 2px #fafafa,0 0 1px #fafafa}
/* Zoomed out the constraint is how many fit, not how large each is.
   Measured on a 412px phone at city zoom: 10px with 7px padding places
   97 of 278; 8px with 3px places 163, and nine candidate slots place 256. */
.nlabel.sm .v{font-size:8px}

/* ---------- top bar ----------
   Navigation left, title centre, menu right, divided by hairlines rather
   than by gaps. The two button cells are deliberately unequal: the atlas
   is the primary action and the menu is furniture, so the width, the icon
   size and the label weight all say so. */
#topbar{
  position:absolute;top:0;left:0;right:0;z-index:620;
  background:var(--surface);border-bottom:1px solid var(--hair);
  display:flex;align-items:stretch;padding-top:env(safe-area-inset-top)}
#atlasbtn,#menubtn{
  flex:0 0 auto;background:none;border:0;cursor:pointer;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px}
#atlasbtn{border-right:1px solid var(--hair);padding:9px 15px 8px;min-width:74px}
#menubtn{border-left:1px solid var(--hair);padding:9px 8px 8px;min-width:46px}
#atlasbtn .ico{width:22px;height:22px;display:block;fill:none;stroke:var(--ink);
  stroke-width:1.4;stroke-linejoin:round;stroke-linecap:round}
#menubtn .bars{display:flex;flex-direction:column;gap:3.5px;padding:3px 0 2px}
#menubtn i{display:block;width:14px;height:1.5px;background:var(--muted)}
#atlasbtn .lbl,#menubtn .lbl{font-size:8.5px;font-weight:700;color:var(--ink);
  letter-spacing:.01em;line-height:1.15;text-align:center}
#menubtn .lbl{font-size:8px;color:var(--muted)}
#atlasbtn[aria-expanded="true"],#menubtn[aria-expanded="true"]{background:var(--stroke)}
#atlasbtn[aria-expanded="true"] .ico{stroke:#fff}
#menubtn[aria-expanded="true"] i{background:#fff}
#atlasbtn[aria-expanded="true"] .lbl,#menubtn[aria-expanded="true"] .lbl{color:#fff}
#atlasbtn:focus-visible,#menubtn:focus-visible{outline:2px solid #b30000;outline-offset:-2px}

/* A title plate running the full width, with More inside the sentence. */
#plate{flex:1 1 auto;min-width:0;padding:9px 12px 8px;
  display:flex;flex-direction:column;justify-content:center}
#plate h1{margin:0;font-size:15.5px;font-weight:700;letter-spacing:-.02em;line-height:1.15}
#plate p{margin:3px 0 0;font-size:11px;color:var(--muted);line-height:1.35;max-width:none}
#morebtn{background:none;border:0;padding:0;font-family:inherit;font-size:11px;
  font-weight:600;color:var(--ink);text-decoration:underline;
  text-underline-offset:2px;cursor:pointer;white-space:nowrap}
#morebtn:focus-visible{outline:2px solid #b30000;outline-offset:2px}

/* ---------- the atlas popup ---------- */
#atlasscrim{position:absolute;inset:0;z-index:960;background:rgba(27,27,27,.45);
  opacity:0;pointer-events:none;transition:opacity .2s ease}
#atlasscrim.on{opacity:1;pointer-events:auto}
/* The head stays, the list scrolls, and the list hides the browser's own
   scrollbar and draws the same 3px thumb the explanation panel draws.
   Two panels that open over the same map should not indicate scroll two
   different ways. */
#atlas{
  position:absolute;z-index:970;left:10px;right:10px;
  top:calc(66px + env(safe-area-inset-top));
  max-height:calc(100% - 90px);
  background:var(--surface);border:1px solid var(--hair);display:none}
#atlas.on{display:flex;flex-direction:column}
/* Flex all the way down, never a percentage height. The panel is capped
   by max-height rather than given one, so it has no definite height for a
   percentage to resolve against: `height:100%` on the scroller silently
   became its content height, and the list grew past the panel instead of
   scrolling inside it. */
#atlasbody{position:relative;display:flex;flex:1 1 auto;min-height:0}
#atlasscroll{flex:1 1 auto;min-height:0;width:100%;
  overflow-y:auto;-webkit-overflow-scrolling:touch;
  scrollbar-width:none;-ms-overflow-style:none}
#atlasscroll::-webkit-scrollbar{width:0;height:0;display:none}
#atlastrack{position:absolute;top:10px;bottom:10px;right:6px;width:3px;
  background:#e8e5e0}
#atlasthumb{position:absolute;right:6px;width:3px;background:#8a857e;
  transition:opacity .2s ease}
#atlashead{display:flex;justify-content:space-between;align-items:baseline;
  padding:14px 15px 11px;border-bottom:1px solid var(--hair)}
#atlashead h2{margin:0;font-size:16px;font-weight:700;letter-spacing:-.02em}
#atlashead p{margin:2px 0 0;font-size:10.5px;color:var(--muted)}
#atlasclose{background:none;border:0;font-family:inherit;font-size:19px;line-height:1;
  color:var(--muted);padding:0 2px;cursor:pointer}
#atlas .atcity{padding:13px 15px 6px;margin:0}
.atmap{
  display:flex;align-items:center;gap:12px;width:100%;text-align:left;
  background:none;border:0;border-top:1px solid var(--hairsoft);
  font-family:inherit;padding:11px 15px;position:relative;text-decoration:none}
a.atmap:hover{background:var(--c1)}
.atmap .thumb{flex:0 0 auto;width:46px;height:46px;border:1px solid var(--hair);
  background:#f7f5f2;display:block}
.atmap .thumb svg{display:block;width:100%;height:100%}
.atmap .tx{flex:1;min-width:0}
.atmap strong{display:block;font-size:13.5px;font-weight:600;letter-spacing:-.01em;
  color:var(--ink);line-height:1.2}
.atmap .tx span{display:block;font-size:10.5px;color:var(--muted);margin-top:3px}
.atmap[aria-current="page"]{background:var(--c1);box-shadow:inset 3px 0 0 var(--c6)}
.atmap.dead strong{color:#a4a09a}
.atmap.dead .thumb{opacity:.45}
.atmap em{position:absolute;right:15px;top:14px;font-style:normal;font-size:9.5px;
  color:var(--muted);border:1px solid rgba(35,35,35,.28);padding:1px 5px}

/* ---------- the drawer ---------- */
#scrim{position:absolute;inset:0;z-index:940;background:rgba(27,27,27,.42);
  opacity:0;pointer-events:none;transition:opacity .22s ease}
#scrim.on{opacity:1;pointer-events:auto}
#drawer{
  position:absolute;top:0;bottom:0;left:0;width:86%;max-width:330px;z-index:950;
  background:var(--surface);border-right:1px solid var(--hair);
  transform:translateX(-101%);transition:transform .26s cubic-bezier(.32,.72,0,1);
  display:flex;flex-direction:column;
  padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom)}
#drawer.on{transform:translateX(0)}
@media (prefers-reduced-motion:reduce){#drawer,#scrim{transition:none}}
#dhead{display:flex;justify-content:space-between;align-items:flex-start;
  padding:16px 16px 13px;border-bottom:1px solid var(--hair)}
#dhead h2{margin:0;font-size:17px;font-weight:700;letter-spacing:-.02em}
#dclose{background:none;border:0;font-family:inherit;font-size:20px;line-height:1;
  color:var(--muted);cursor:pointer;padding:0 2px}
#dbody{overflow-y:auto;flex:1;padding:0 0 18px}
.dsection{border-top:1px solid var(--hair);margin-top:16px}
.dlink{display:block;width:100%;text-align:left;
  border-bottom:1px solid var(--hairsoft);
  font-size:13.5px;color:var(--ink);padding:12px 16px;text-decoration:none}
.dlink:hover{background:var(--c1)}
/* The one highlighted row, filled with the second stop of the ramp. */
#ectop{
  display:block;width:100%;text-align:left;background:var(--c2);
  border-bottom:1px solid var(--hair);box-shadow:inset 3px 0 0 var(--c6);
  padding:13px 16px;text-decoration:none}
#ectop strong{display:block;font-size:14px;font-weight:700;letter-spacing:-.01em;
  color:var(--ink);line-height:1.2}
#ectop span{display:block;font-size:10.5px;color:#6b5a3e;margin-top:2px}
#ectop:focus-visible{outline:2px solid #b30000;outline-offset:-2px}

/* ---------- panels over the map: one geometry, defined once ---------- */
#panel{position:absolute;inset:0;pointer-events:none}
#panel > *{pointer-events:auto}
/* Zoom and full screen are desktop controls and this is the one thing the
   breakpoint decides beyond width. It is decided here rather than in
   map.js on purpose: a hidden button cannot be clicked, so the script
   still has no `if (wide)` in any interaction path. A touch screen
   already pinches to zoom, and a phone browser's idea of full screen is
   its own chrome rather than the page's, so neither control earns its
   space there. */
#mapctl{display:none}
#panel > #mscrim{pointer-events:none}
#panel > #mscrim.on{pointer-events:auto}

#infobox,#hint{
  position:absolute;left:10px;right:10px;
  bottom:calc(var(--barh) + 6px + env(safe-area-inset-bottom));
  background:var(--surface);border:1px solid var(--hair);display:none}
#infobox{min-height:var(--panelh);z-index:700;padding:11px 12px}
#infobox.on{display:block}
#infobox.empty{min-height:0}
/* One line tall, in the slot the neighbourhood box will occupy, so it
   teaches the action and the place the answer lands at the same time. */
#hint{z-index:660;padding:10px 12px;font-size:12.5px;color:var(--ink);
  line-height:1.3;align-items:center;gap:9px;opacity:0;transition:opacity .35s ease}
#hint.on{display:flex;opacity:1}
#hint.out{opacity:0}
#hint b{font-weight:600}
#hint .pip{flex:0 0 auto;width:9px;height:9px;background:var(--c5);
  border:1px solid var(--stroke)}
body.bare #hint{display:none}

/* Scroll indicators are drawn, not inherited: mobile overlay scrollbars
   stay hidden until after the reader has already scrolled, which is
   exactly when the hint is no longer needed. The browser's own is
   suppressed so there is only ever one. */

/* The neighbourhood box. Deliberately short, so the map stays visible
   behind it, a reader comparing places needs to see the places. */
#ibhead{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
#ibhead h2{margin:0;font-size:15.5px;font-weight:700;letter-spacing:-.015em;line-height:1.15}
#ibhead p{margin:2px 0 0;font-size:10.5px;color:var(--muted)}
#ibclose{background:none;border:0;font-family:inherit;font-size:17px;line-height:1;
  color:var(--muted);padding:0 2px;cursor:pointer}
#ibmain{display:flex;align-items:baseline;gap:7px;margin:9px 0 0}
#ibmain b{font-size:29px;font-weight:700;letter-spacing:-.03em;line-height:1;
  font-variant-numeric:tabular-nums}
#ibmain span{font-size:11px;color:var(--muted)}
#ibtotal{margin:6px 0 0;font-size:11px;color:var(--muted);line-height:1.35}
#ibstats{display:flex;gap:14px;margin:10px 0 0;padding:9px 0 0;
  border-top:1px solid rgba(35,35,35,.15)}
#ibstats div{font-size:11px;color:var(--muted);line-height:1.3}
#ibstats b{display:block;font-size:13px;color:var(--ink);font-weight:600;
  font-variant-numeric:tabular-nums}
/* The placeholder marker in the box. A different class name from the
   site-wide .syn dot on purpose: that one turns the element it is on into
   a 5px square, which is right for a marker span and wrong for a stat
   column. Two meanings for one class name is how the stats row collapsed. */
#ibstats .synth b::after{content:'';display:inline-block;width:4px;height:4px;
  background:var(--c6);border-radius:50%;margin-left:4px;vertical-align:2px}
/* The link into the search layer. The box is a summary; the page is the
   artifact, and a reader who wants more should be able to reach it. */
#ibmore{display:inline-block;margin:9px 0 0;font-size:11px;font-weight:600;
  color:var(--ink);text-decoration:underline;text-underline-offset:2px}

/* ---------- the bottom bar ---------- */
#bar{
  position:absolute;left:0;right:0;bottom:0;z-index:640;
  background:var(--surface);border-top:1px solid var(--hair);
  padding:12px 12px calc(12px + env(safe-area-inset-bottom))}
#barrow{display:flex;align-items:stretch;gap:9px}
/* Full width, bordered, caret hard right, deliberately shaped like a
   native select so it reads as openable at a glance. */
#metricbtn{
  flex:1 1 auto;min-width:0;position:relative;text-align:left;
  background:var(--surface);border:1px solid var(--hair);
  font-family:inherit;color:var(--ink);padding:7px 26px 8px 10px;cursor:pointer}
#metricbtn .lead{display:block;font-size:9.5px;color:var(--muted);
  line-height:1.2;letter-spacing:.01em}
#metricname{display:block;font-size:15px;font-weight:700;letter-spacing:-.015em;
  line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#metricbtn .caret{position:absolute;right:10px;top:50%;margin-top:-2px;
  width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;
  border-top:6px solid var(--ink);transition:transform .18s ease}
#metricbtn[aria-expanded="true"]{background:var(--stroke);color:#fff}
#metricbtn[aria-expanded="true"] .lead{color:rgba(255,255,255,.7)}
#metricbtn[aria-expanded="true"] .caret{border-top-color:#fff;transform:rotate(180deg)}
#basemapbtn{
  flex:0 0 auto;background:var(--surface);border:1px solid var(--hair);
  font-family:inherit;font-size:11.5px;font-weight:600;color:var(--ink);
  line-height:1.2;padding:8px 11px;cursor:pointer;text-align:center}
#basemapbtn[aria-pressed="true"]{background:var(--stroke);color:#fff}
#metricbtn:focus-visible,#basemapbtn:focus-visible{outline:2px solid #b30000;outline-offset:2px}

#legend{margin-bottom:13px}
#legend .lbl{font-size:10.5px;color:var(--muted);line-height:1.2;margin-bottom:5px}
#swatches{display:flex;height:10px;width:100%;position:relative}
#swatches i{flex:1;display:block}
#tick{position:absolute;top:-3px;bottom:-3px;width:2px;background:#1b1b1b;
  display:none;pointer-events:none;box-shadow:0 0 0 1px rgba(255,255,255,.9)}
#tick.on{display:block}
#ticks{display:flex;justify-content:space-between;width:100%;margin-top:4px;
  font-size:9.5px;color:var(--muted);font-variant-numeric:tabular-nums}

/* Streets only is asked for when someone wants more map, so the bar gives
   its space back rather than standing empty. */
body.bare #metricbtn,body.bare #legend,body.bare #infobox{display:none}
body.bare{--barh:56px}
body.bare #basemapbtn{flex:1 1 auto}

/* ---------- the metric sheet ---------- */
#mscrim{position:absolute;inset:0;z-index:860;background:rgba(27,27,27,.35);
  opacity:0;pointer-events:none;transition:opacity .2s ease}
#mscrim.on{opacity:1;pointer-events:auto}
#msheet{
  position:absolute;left:0;right:0;bottom:0;z-index:870;
  background:var(--surface);border-top:1px solid var(--hair);
  transform:translateY(102%);transition:transform .24s cubic-bezier(.32,.72,0,1);
  padding-bottom:env(safe-area-inset-bottom)}
#msheet.on{transform:translateY(0)}
@media (prefers-reduced-motion:reduce){#msheet,#mscrim{transition:none}}
#mgrab{padding:9px 0 4px;display:flex;justify-content:center;cursor:pointer}
#mgrab span{width:38px;height:3px;background:#c9c5be;border-radius:2px}
#mtitle{margin:0;padding:2px 16px 8px;font-size:11px;color:var(--muted)}
.mopt{
  display:flex;align-items:center;gap:11px;width:100%;text-align:left;
  background:none;border:0;border-top:1px solid var(--hairsoft);
  font-family:inherit;padding:13px 16px;cursor:pointer}
.mopt .sw{flex:0 0 auto;display:flex;width:44px;height:10px}
.mopt .sw i{flex:1}
.mopt .tx{flex:1;min-width:0}
.mopt strong{display:block;font-size:14px;font-weight:600;letter-spacing:-.01em;
  color:var(--ink);line-height:1.2}
.mopt .tx span{display:block;font-size:10.5px;color:var(--muted);margin-top:2px}
.mopt[aria-current="true"]{background:var(--c1);box-shadow:inset 3px 0 0 var(--c6)}
.mopt .dot{flex:0 0 auto;width:5px;height:5px;border-radius:50%;background:var(--c6)}

/* ===================================================================
   DESKTOP · one breakpoint, at 900px.

   The same layout as the phone, not a different one. The two bars stay;
   they simply stop spanning the full width, inset from the edge with
   the map visible past them and at every corner. No side panel: the map
   is the page at both sizes and the chrome floats on it.

   Every layout defect during the desktop work came from the two modes
   diverging, so the breakpoint sets widths and nothing else. There is no
   `if (wide)` branch anywhere in map.js.
   =================================================================== */
@media (min-width:900px){
  :root{--inset:18px;--barw:452px}

  #topbar,#bar,#infobox,#hint,#msheet,#atlas{
    left:50%;right:auto;width:var(--barw);
    transform:translateX(-50%);border:1px solid var(--hair)}
  #topbar{top:var(--inset);padding-top:0}
  #bar{bottom:var(--inset);padding:12px 14px 13px}
  #infobox,#hint,#msheet{bottom:calc(var(--barh) + var(--inset) + 8px)}
  #msheet:not(.on){transform:translateX(-50%) translateY(calc(100% + var(--barh) + 40px))}
  #msheet.on{transform:translateX(-50%)}
  #atlas{top:calc(var(--inset) + 74px);max-height:calc(100% - 200px)}

  /* Top right, level with the bar at the top and clear of the legend at
     the bottom, which are both centred. Full screen first, worded rather
     than drawn, because two corner brackets are not obvious and a word
     is; the zoom pair directly under it. Both are sized to be seen from
     across the page rather than to be tidy: the whole reason they exist
     is that a map under a pointer looks like a picture until something
     on it says otherwise. */
  #mapctl{
    display:flex;flex-direction:column;align-items:flex-end;gap:10px;
    position:absolute;top:var(--inset);right:var(--inset);z-index:630}
  #zoomctl{display:flex;flex-direction:column;
    background:var(--surface);border:1px solid var(--hair)}
  #zoomctl button{
    display:block;width:44px;height:42px;padding:0;
    background:none;border:0;font-family:inherit;font-size:23px;
    font-weight:600;line-height:1;color:var(--ink);cursor:pointer}
  #zoomctl button + button{border-top:1px solid var(--hairsoft)}
  #zoomctl button:hover:not(:disabled){background:var(--c1)}
  /* At the floor or the ceiling the button does nothing, and a control
     that does nothing while looking live is the map telling a lie about
     itself. */
  #zoomctl button:disabled{color:#bdb9b2;cursor:default}
  #fsbtn{
    background:var(--surface);border:1px solid var(--hair);
    font-family:inherit;font-size:13px;font-weight:600;color:var(--ink);
    line-height:1.2;padding:11px 14px;cursor:pointer;text-align:center}
  #fsbtn:hover{background:var(--c1)}
  #fsbtn[aria-pressed="true"]{background:var(--stroke);color:#fff}
  #zoomctl button:focus-visible,#fsbtn:focus-visible{
    outline:2px solid #b30000;outline-offset:2px}

  #plate h1{font-size:16.5px}
  #plate p{font-size:11.5px}
  #atlasbtn{min-width:82px}
  #drawer{width:360px;max-width:360px}

  .leaflet-bottom{bottom:0}
  .leaflet-control-attribution{margin-right:4px}

  /* The table itself is already a table at every width. What the extra
     room buys is a height cap, so several hundred rows do not push the
     rest of the page out of reach, and a header that sticks while they
     scroll past. Both need a vertical scrolling box, which is safe here
     and only here: it is a thumb trap on a phone and harmless under a
     pointer. */
  .tablewrap{border:1px solid var(--hair);background:var(--surface);
    max-height:70vh;overflow:auto}
  .figtable thead th{position:sticky;top:0;z-index:1}
  .figtable tbody tr[id]{scroll-margin-top:0}
}

/* The static map. It is the social card, the image-search entry, and what
   a reader sees when the interactive map has not loaded, one file doing
   three jobs, so it sits in the markup rather than only in a meta tag. */
.mapshot{margin:16px 0 0;border:1px solid var(--hair);background:var(--surface)}
.mapshot img{display:block;width:100%;height:auto}
.mapshot figcaption{font-size:11px;color:var(--muted);line-height:1.5;
  padding:9px 12px;border-top:1px solid var(--hairsoft)}

/* ===================================================================
   Selling. A paid map's page is a normal page with a buy control on it,
   not a locked one. Nothing is greyed, blurred or half-shown, so there
   is no "locked" state to style, only an offer.
   =================================================================== */
#preview{background:var(--frame-bg);padding:18px 16px;
  border-bottom:1px solid var(--hair)}
#preview .mapshot{max-width:980px;margin:0 auto}
#buy{max-width:980px;margin:0 auto;padding:26px 16px 0}
.buybox{border:1px solid var(--hair);background:var(--surface);padding:18px 20px}
.buybox h2{margin:0 0 8px;font-size:19px}
.buybox p{max-width:64ch}
.includes{list-style:none;margin:12px 0 16px;padding:0}
.includes li{position:relative;padding:6px 0 6px 20px;font-size:13.5px;
  line-height:1.55;border-bottom:1px solid var(--hairsoft);max-width:64ch}
.includes li::before{content:'';position:absolute;left:0;top:12px;
  width:9px;height:9px;background:var(--c5);border:1px solid var(--stroke)}
.buyform{margin:4px 0 12px}
.buyform button{font-family:inherit;font-size:15px;font-weight:600;
  background:var(--stroke);color:#fff;border:1px solid var(--stroke);
  padding:13px 20px;cursor:pointer;display:inline-flex;align-items:baseline;gap:10px}
.buyform button b{font-weight:700;font-variant-numeric:tabular-nums}
.buyform button:hover{background:var(--c6);border-color:var(--c6)}
.buyform button:focus-visible{outline:2px solid #b30000;outline-offset:2px}
.buybox .fine{margin:0 0 6px}
.buybox .fine.free{border-top:1px solid var(--hairsoft);padding-top:10px;
  margin-top:12px}
#fulfilment .link{display:block;border:1px solid var(--hair);padding:12px;
  background:var(--paper);font-size:13px;word-break:break-all;margin:10px 0}

/* The full table, collapsed. It stays in the served HTML, crawlers read
   inside <details> and follow the links in it, so nothing is lost for
   search or for a screen reader. What changes is that the page reads as a
   map with a note under it rather than as a spreadsheet with a map on top. */
.figdetails{margin-top:14px}
.figdetails > summary{
  cursor:pointer;font-size:13px;font-weight:600;
  border:1px solid var(--hair);background:var(--surface);
  padding:11px 14px;list-style:none;display:flex;align-items:center;gap:9px}
.figdetails > summary::-webkit-details-marker{display:none}
.figdetails > summary::before{
  content:'';width:0;height:0;flex:0 0 auto;
  border-left:5px solid transparent;border-right:5px solid transparent;
  border-top:6px solid var(--ink);transition:transform .18s ease}
.figdetails[open] > summary::before{transform:rotate(180deg)}
.figdetails > summary:hover{background:var(--c1)}
.figdetails > summary:focus-visible{outline:2px solid #b30000;outline-offset:2px}
.figdetails[open] > summary{border-bottom:0}
@media (prefers-reduced-motion:reduce){.figdetails > summary::before{transition:none}}

/* The generated source list. Named, not linked: the licence is made
   discoverable on the map itself, where the obligation applies. */
.srclist-full{list-style:none;margin:10px 0 18px;padding:0;max-width:70ch}
.srclist-full li{padding:11px 0;border-bottom:1px solid var(--hairsoft)}
.srclist-full li:first-child{border-top:1px solid var(--hairsoft)}
.srclist-full a{text-decoration:none}
.srclist-full strong{font-size:13.5px;font-weight:600}
.srclist-full a:hover strong{text-decoration:underline;text-underline-offset:2px}
.srclist-full span{display:block;font-size:12px;color:var(--muted);
  line-height:1.5;margin-top:3px}

/* The credit in a figure caption. The caption around it is muted, so this
   is set in ink and underlined: it is the one thing in that line a reader
   is meant to be able to click. */
.mapshot figcaption .madeby{color:var(--ink);font-weight:600;
  text-decoration:underline;text-underline-offset:2px;white-space:nowrap}
.mapshot figcaption .madeby:hover{color:var(--c6)}

/* The badge on a paid map in the atlas. Deliberately not the muted outline
   the "Soon" badge uses: a paid map is open and browsable, so its badge
   reads as active rather than as pending. Ink and weight do that, without
   spending the ramp on chrome. */
.atcard em.buy,.atmap em.buy,.rellist em.buy{
  color:var(--ink);font-weight:700;border-color:var(--stroke);
  background:var(--surface)}

/* The credit under a paid map's preview. A paid page has no map and so no
   attribution control; this is the one line that carries it. */
.previewcredit{max-width:980px;margin:9px auto 0;padding:0 16px;
  font-size:11px;line-height:1.6;color:var(--muted)}
.previewcredit a{color:var(--muted)}
.previewcredit a:hover{color:var(--ink)}

/* ===================================================================
   Categorical maps. Colour here means which of a fixed set of values a
   feature holds, a party, a zone, not where a number sits on a scale.
   The ramp is untouched: these colours arrive from the manifest because
   they belong to the thing being mapped, not to this design.
   =================================================================== */

/* The legend becomes a key: one row per value, with its count.

   Scoped to `.key`, which drawLegend puts on the element when it writes a
   key rather than a scale. Unscoped, this rule reached every map: a
   choropleth's three scale numbers were packed to the left edge under a
   full-width colour strip, reading as one run of digits, `66k167k304k`,
   instead of sitting under the low, middle and high end of the ramp they
   label. The categorical branch is the exception here, so the exception
   is what carries the class. */
#ticks .kk{display:inline-flex;align-items:center;gap:6px;margin-right:12px;
  white-space:nowrap;font-size:10px;color:var(--muted)}
#ticks .kk i{width:10px;height:10px;flex:0 0 auto;border:1px solid var(--stroke)}
#ticks .kk b{font-weight:700;color:var(--ink)}
#ticks.key{flex-wrap:wrap;gap:2px 0;justify-content:flex-start}

/* The mark: an extra fact about a feature, not what the feature is. */
b.mk,.mk{display:inline-block;width:8px;height:8px;border-radius:50%;
  background:#b30000;flex:0 0 auto;vertical-align:1px}
.markerpane{pointer-events:none}
.mkdot{position:absolute;width:9px;height:9px;border-radius:50%;
  transform:translate(-50%,-50%);box-shadow:0 0 0 1.5px #fff;
  display:block}

/* The detail card. A categorical feature carries a contact card rather
   than one figure, so the box grows for it. */
body.detailbox{--panelh:260px}
#ibhead .swatch{display:inline-block;width:10px;height:10px;
  border:1px solid var(--stroke);margin-right:6px;vertical-align:0}
#ibdetail{width:100%;border-collapse:collapse;margin:9px 0 0;font-size:11.5px}
#ibdetail th,#ibdetail td{text-align:left;vertical-align:baseline;
  padding:3px 0;border-bottom:1px solid var(--hairsoft)}
#ibdetail th{color:var(--muted);font-weight:400;width:38%;padding-right:8px}
#ibdetail td{color:var(--ink)}
#ibdetail a{color:var(--ink)}

/* The same key on a place page. */
.catstrip{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin:16px 0 6px;font-size:12.5px}
.catstrip i{width:14px;height:14px;border:1px solid var(--stroke);flex:0 0 auto}
.figtable .mk{margin-left:6px}
