/* ============================================================================
   Block Before - neumorphic design system
   Version 0.3.4 (2026-09-13 EST)

   USER REQUIREMENTS
     Purpose : Neumorphic ("soft UI") design system for the Block Before entity
               templates. Owner directive 2026-08-01: neumorphic, modern,
               minimal, no redundancy, every element must earn its place.
     Inputs  : None. Consumed by the entity HTML templates.
     Outputs : Visual presentation, light and dark, 320px to 2560px.
     Usage   : <link rel="stylesheet" href="styles.css">

   DEPENDENCY MAP
     External libraries : none (zero runtime dependencies)
     Internal modules   : none
     System / runtime   : any browser with CSS custom properties
     Platform specific  : backdrop-filter is optional and has an @supports
                          fallback; no platform-specific assets are required.

   CHANGELOG
     0.3.4  2026-09-13  Expand the illustration-help control to the required
                        44px mobile target without changing its visual icon.
     0.3.3  2026-09-13  Raise the county "Near me" and progressive record
                        disclosure controls to the 44px mobile touch target.
     0.3.2  2026-09-13  Make the compact map control and footer preference,
                        correction, and consent controls meet the 44px touch
                        target requirement without page-level overflow.
     0.3.1  2026-09-10  Add a compact, visible source caption for each
                        extracted historical passage on settlement and
                        property pages.
     0.3.0  2026-09-10  Add audit-led first-visit orientation, research tools,
                        source-guide disclosure, saved-page controls, and
                        progressive disclosure for long browse lists.
     0.2.1  2026-08-20  Moved the translation selector from the footer to the
                        far right of the sticky header, with a compact mobile
                        layout that preserves the 44px touch target.
     0.2.2  2026-08-20  Extended the compact wordmark breakpoint through
                        360px and raised the adjacent Map target to 44px.

   ------------------------------------------------------------------------
   DOCUMENTED DEVIATION FROM Claude.md SECTION 27
   ------------------------------------------------------------------------
   Neumorphism renders depth using a light shadow on one side and a dark
   shadow on the other. That is geometrically impossible against pure white
   (no room for a lighter highlight) or pure black (no room for a darker
   shadow). Section 27.3 mandates --color-bg-primary #FFFFFF in light and
   #000000 in dark. Those two values, and only those two, are overridden with
   mid-tone surfaces. Section 27.7's shadow scale is likewise replaced, since
   the whole mechanism of the style is its shadows.

   Everything else in section 27 is retained unchanged: the type scale (27.2),
   the 8pt spacing grid (27.4), the radius scale (27.5), motion curves and
   reduced-motion (27.8), and all of accessibility (27.12).

   WHERE NEUMORPHISM IS DELIBERATELY NOT APPLIED, AND WHY
   Soft low-contrast shadow is the defining trait of the style and also its
   documented accessibility failure. So depth is applied to SURFACES only.
   It is never the sole carrier of meaning:
     - Body and label text keeps full WCAG 2.2 AA contrast against its surface
     - Interactive elements are never signalled by shadow alone; each also
       carries a text label or an accent colour
     - Focus rings stay high-contrast and solid. They are not softened
     - Archival images sit in inset wells. The recess reads as a mat around a
       print, which suits the content, and it keeps all chrome darker than the
       image so the photograph is the brightest thing on screen
   Contrast ratios are verified by check_contrast.py, not asserted here.
   ========================================================================= */

/* ---------- 1. Tokens : light ---------- */
:root {
  /* Off-white, matching bee2.io (--bg: #f7f7f5 there), so the two
     properties read as one estate. Owner directive 2026-08-02.
     The neumorphic shadows are retuned for it: on a near-white surface a
     pure-white highlight is invisible, so the light side steps up to a true
     white only slightly above the surface and the dark side warms to sit in
     the same family rather than the cool blue-grey the old mid-tone needed. */
  --surface:        #F7F7F5;
  --surface-sunken: #F1F1EE;
  --nm-light:       #FFFFFF;
  --nm-dark:        #D8D8D2;

  --ink:      #14181F;   /* primary text */
  --ink-2:    #4B535F;   /* secondary text */
  --ink-3:    #5E6672;   /* tertiary. Darkened from #6A727E: check_contrast.py */
  --accent:   #0B5FCC;   /* darkened from #007AFF to clear AA on --surface */
  --accent-ink: #FFFFFF;
  --positive: #1B6E3C;
  --caution:  #8A5200;
  --hairline: rgba(20, 24, 31, 0.10);
  --surface-texture: repeating-linear-gradient(0deg, transparent,
    transparent 2px, rgba(0,0,0,0.01) 2px, rgba(0,0,0,0.01) 4px);
  /* Locator maps: unhighlighted land, the border between, the subject. */
  --loc-fill: #C9CFDA;
  --loc-line: #E6E9EF;
  --loc-hot:  #0B5FCC;
  --loc-ring: #E6E9EF;

  /* Depth. Two shadows, one light one dark, opposite corners. */
  --lift-1: -3px -3px 7px var(--nm-light),  3px  3px 7px var(--nm-dark);
  --lift-2: -6px -6px 14px var(--nm-light),  6px  6px 14px var(--nm-dark);
  --press:  inset -3px -3px 7px var(--nm-light), inset 3px 3px 7px var(--nm-dark);
  --well:   inset -2px -2px 5px var(--nm-light), inset 4px 4px 9px var(--nm-dark);

  /* Spacing, 8pt grid (27.4, unchanged) */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 20px;
  --s6: 24px; --s7: 32px; --s8: 40px; --s9: 48px; --s10: 64px;

  /* Radii (27.5, unchanged). Neumorphism needs generous curves. */
  --r-sm: 8px; --r-md: 10px; --r-lg: 14px; --r-xl: 20px; --r-full: 9999px;

  /* Motion (27.8, unchanged) */
  --ease-standard: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --ease-spring:   cubic-bezier(0.34, 1.56, 0.64, 1);

  --content-max: 1040px;
  --nav-h: 56px;
  color-scheme: light;
}

/* ---------- 2. Tokens : dark ---------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --loc-fill: #3A3F49;
    --loc-line: #22262E;
    --loc-hot:  #6FB2FF;
    --loc-ring: #23262C;
    --surface:        #23262C;
    --surface-sunken: #202329;
    --nm-light:       #2E323A;
    --nm-dark:        #171A1E;

    --ink:      #F0F2F5;
    --ink-2:    #AEB6C2;
    --ink-3:    #8A93A0;
    --accent:   #6FB2FF;
    --accent-ink: #0A1420;
    --positive: #5FD98A;
    --caution:  #FFC061;
    --hairline: rgba(240, 242, 245, 0.10);

    /* 27.7 : dark mode carries less shadow weight */
    --lift-1: -3px -3px 7px var(--nm-light),  3px  3px 7px var(--nm-dark);
    --lift-2: -5px -5px 12px var(--nm-light),  5px  5px 12px var(--nm-dark);
    --press:  inset -3px -3px 7px var(--nm-light), inset 3px 3px 7px var(--nm-dark);
    --well:   inset -2px -2px 5px var(--nm-light), inset 4px 4px 9px var(--nm-dark);

    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --loc-fill: #3A3F49;
  --loc-line: #22262E;
  --loc-hot:  #6FB2FF;
  --loc-ring: #23262C;
  --surface:        #23262C;
  --surface-sunken: #202329;
  --nm-light:       #2E323A;
  --nm-dark:        #171A1E;
  --ink:      #F0F2F5;
  --ink-2:    #AEB6C2;
  --ink-3:    #8A93A0;
  --accent:   #6FB2FF;
  --accent-ink: #0A1420;
  --positive: #5FD98A;
  --caution:  #FFC061;
  --hairline: rgba(240, 242, 245, 0.10);
  --lift-1: -3px -3px 7px var(--nm-light),  3px  3px 7px var(--nm-dark);
  --lift-2: -5px -5px 12px var(--nm-light),  5px  5px 12px var(--nm-dark);
  --press:  inset -3px -3px 7px var(--nm-light), inset 3px 3px 7px var(--nm-dark);
  --well:   inset -2px -2px 5px var(--nm-light), inset 4px 4px 9px var(--nm-dark);
  color-scheme: dark;
}

/* ---------- 3. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               Helvetica, Arial, sans-serif, "Apple Color Emoji";
  font-size: 1.0625rem; line-height: 1.375rem; letter-spacing: -0.41px;
  color: var(--ink);
  background-color: var(--surface);
  /* The bee2.io texture: a 2px scanline at one per cent black. Barely
     visible on its own and that is the point - it stops a large flat field
     reading as dead white without becoming a pattern anyone notices. */
  background-image: var(--surface-texture);
  -webkit-font-smoothing: antialiased;
  transition: background-color .2s var(--ease-standard), color .2s var(--ease-standard);
}

img { max-width: 100%; height: auto; display: block; }

/* Any anchor target clears the sticky bar. This was set on one component
   only, so a deep link from the homepage survey strip landed with the
   chronology card half-hidden behind the nav. Scroll offset is a property of
   having a fixed header, not of any single component, so it belongs here. */
[id] { scroll-margin-top: calc(var(--nav-h) + var(--s4)); }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Focus stays hard-edged. Softening this would break 27.12. */
a:focus-visible, button:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 700;
  background: var(--accent); color: var(--accent-ink);
  padding: var(--s3) var(--s5); font-weight: 600;
  border-radius: 0 0 var(--r-md) 0;
}
.skip-link:focus { left: 0; }

.wrap { width: min(100% - 32px, var(--content-max)); margin-inline: auto; }
@media (min-width: 768px) { .wrap { width: min(100% - 64px, var(--content-max)); } }

/* ---------- 4. Type scale (27.2, unchanged) ---------- */
.t-large  { font-size: 2.125rem; line-height: 2.5625rem; letter-spacing:  .37px; font-weight: 700; }
.t-title2 { font-size: 1.375rem; line-height: 1.75rem;   letter-spacing:  .35px; font-weight: 700; }
.t-title3 { font-size: 1.25rem;  line-height: 1.5625rem; letter-spacing:  .38px; font-weight: 600; }
.t-sub    { font-size: .9375rem; line-height: 1.25rem;   letter-spacing: -.24px; }
.t-foot   { font-size: .8125rem; line-height: 1.125rem;  letter-spacing: -.08px; }
/* Caption 1 from the Apple scale (27.2). Added 2026-08-01: several components
   were already asking for .t-cap and, with no such rule, silently rendered at
   body size - a photograph credit set as large as the page text. A class name
   used in markup but absent from the stylesheet fails silently and upward,
   which is the worst direction for a caption to fail. */
.t-cap    { font-size: .75rem;   line-height: 1rem;      letter-spacing:  0;    }
.dim { color: var(--ink-2); }
h1, h2, h3 { margin: 0; }

@media (min-width: 735px) { .t-large { font-size: 2.75rem; line-height: 3.125rem; } }

/* ---------- 5. Navigation ---------- */
.topbar {
  position: sticky; top: 0; z-index: 300;
  background: var(--surface);
  min-height: var(--nav-h);
  display: flex; align-items: center;
  box-shadow: 0 4px 14px -8px var(--nm-dark);
}
.topbar .wrap { display: flex; align-items: center; gap: var(--s3); }

.wordmark {
  font-size: 1.0625rem; font-weight: 700; letter-spacing: -.41px;
  color: var(--ink); white-space: nowrap;
  display: inline-flex; align-items: center; min-height: 44px;
}
.wordmark:hover { text-decoration: none; }
.wordmark .m2 { color: var(--ink-2); font-weight: 400; }

/* The mark, beside the wordmark. currentColor so it follows the theme and
   the hover state without a second rule, and shrink-proofed so it can never
   be squeezed by the flex row (Claude.md 43.1). */
.wordmark .wmark {
  width: 22px; height: 22px; flex: 0 0 22px; margin-right: var(--s2);
  display: block; pointer-events: none;
}
@media (max-width: 600px) {
  .wordmark .wmark { width: 20px; height: 20px; flex-basis: 20px; }
}

/* THE MARK MUST INVERT IN DARK MODE.
   It ships as dark charcoal on transparent, which is correct on a light
   header and invisible on a dark one - the owner saw it disappear. Inverting
   is exact here because the artwork is one flat colour over alpha: charcoal
   becomes near-white and the alpha edge is untouched.

   Both switches are covered, in the same order the rest of the sheet uses:
   the system preference unless the visitor has forced light, and the
   explicit data-theme attribute, which must win in both directions. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .wordmark .wmark { filter: invert(1); }
}
:root[data-theme="dark"] .wordmark .wmark { filter: invert(1); }
:root[data-theme="light"] .wordmark .wmark { filter: none; }

.topbar nav { margin-left: auto; display: flex; align-items: center; gap: var(--s2); }

.theme-btn {
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; cursor: pointer;
  background: var(--surface); color: var(--ink-2);
  border-radius: var(--r-full); box-shadow: var(--lift-1);
  transition: box-shadow .18s var(--ease-standard), color .18s var(--ease-standard);
}
.theme-btn:hover { color: var(--ink); }
.theme-btn:active { box-shadow: var(--press); }
.theme-btn svg { width: 19px; height: 19px; stroke-width: 1.75; }
.theme-btn .i-dark { display: none; }
:root[data-theme="dark"] .theme-btn .i-dark { display: block; }
:root[data-theme="dark"] .theme-btn .i-light { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-btn .i-dark { display: block; }
  :root:not([data-theme="light"]) .theme-btn .i-light { display: none; }
}

/* ---------- 6. Breadcrumb ---------- */
.crumbs ol {
  list-style: none; margin: 0; padding: var(--s5) 0 0;
  display: flex; flex-wrap: wrap; gap: var(--s2);
  font-size: .8125rem; letter-spacing: -.08px; color: var(--ink-2);
}
.crumbs li { display: flex; gap: var(--s2); }
.crumbs li + li::before { content: "/"; color: var(--ink-3); }

/* ---------- 7. Page head. One heading, one line of data. ---------- */
.pagehead { padding: var(--s6) 0 var(--s7); }
.pagehead h1 { margin-bottom: var(--s3); }
.summary { color: var(--ink-2); font-size: 1.0625rem; line-height: 1.5rem; }
.summary b { color: var(--ink); font-weight: 600; }

/* ---------- 7A. Search ----------
   The primary interaction on the site. Pressed-in field, raised buttons: the
   neumorphic grammar reads as "type into the recess, press the raised key". */
.searchbar { position: relative; max-width: 620px; margin-top: var(--s6); }
.searchbar .row { display: flex; gap: var(--s3); align-items: stretch; }

.searchbar input[type="search"] {
  flex: 1; min-width: 0;
  height: 52px; padding: 0 var(--s5);
  font-size: 1.0625rem; letter-spacing: -.41px;
  color: var(--ink);
  background: var(--surface-sunken);
  border: 0; border-radius: var(--r-full);
  box-shadow: var(--well);
  -webkit-appearance: none; appearance: none;
}
.searchbar input[type="search"]::placeholder { color: var(--ink-3); }
.searchbar input[type="search"]:focus {
  outline: 3px solid var(--accent); outline-offset: 3px;
}
.searchbar input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }

.searchbar button {
  height: 52px; min-width: 52px; padding: 0 var(--s5);
  border: 0; cursor: pointer;
  border-radius: var(--r-full);
  background: var(--surface); color: var(--ink);
  box-shadow: var(--lift-1);
  font-size: 1rem; font-weight: 600; letter-spacing: -.32px;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
}
.searchbar button:active { box-shadow: var(--press); }
.searchbar button.primary { background: var(--accent); color: var(--accent-ink); }
.searchbar button svg { width: 19px; height: 19px; stroke-width: 1.9; }

.searchbar .hint {
  margin: var(--s3) 0 0; font-size: .8125rem; color: var(--ink-2);
}
.searchbar .hint[role="status"]:empty { display: none; }

#bb-suggest {
  position: absolute; z-index: 200; left: 0; right: 0; top: 60px;
  list-style: none; margin: 0; padding: var(--s2);
  background: var(--surface);
  border-radius: var(--r-lg); box-shadow: var(--lift-2);
  max-height: 320px; overflow-y: auto;
}
#bb-suggest li {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4); border-radius: var(--r-md);
  cursor: pointer; font-size: .9375rem; letter-spacing: -.24px;
}
#bb-suggest li .nm { font-weight: 600; }
#bb-suggest li .st { color: var(--ink-2); }
#bb-suggest li .tag {
  margin-left: auto; font-size: .75rem; font-weight: 600;
  padding: 2px var(--s2); border-radius: var(--r-full);
  box-shadow: var(--press); color: var(--positive);
}
#bb-suggest li.on, #bb-suggest li:hover { box-shadow: var(--press); }

@media (max-width: 559px) {
  .searchbar .row { flex-wrap: wrap; }
  .searchbar input[type="search"] { flex: 1 1 100%; }
  .searchbar button { flex: 1; }
}

/* ---------- 8. Section ---------- */
section { padding-block: var(--s7); }
.sec-head {
  display: flex; align-items: baseline; gap: var(--s3);
  flex-wrap: wrap; margin-bottom: var(--s5);
}
.sec-head h2 { letter-spacing: .35px; }
.sec-head .n {
  font-size: .8125rem; font-weight: 600; letter-spacing: -.08px;
  color: var(--ink-2); padding: 3px var(--s3);
  border-radius: var(--r-full); box-shadow: var(--press);
}

/* ---------- 9. Plate : the archival image card ----------
   The image sits in a recessed well so the scan is the brightest thing on
   screen and the chrome recedes. This is the whole reason neumorphism suits
   an archive: a mat around a print.                                        */
.plates { display: grid; gap: var(--s5); }
.plates.maps  { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.plates.shots { grid-template-columns: repeat(auto-fill, minmax(228px, 1fr)); }

.plate { display: flex; flex-direction: column; }
.plate a {
  display: flex; flex-direction: column; height: 100%;
  color: inherit; border-radius: var(--r-lg);
  padding: var(--s3);
  background: var(--surface); box-shadow: var(--lift-1);
  transition: box-shadow .2s var(--ease-standard), transform .2s var(--ease-standard);
}
/* No translateY here: the jelly animation in 16A owns the transform on hover,
   and a competing translate would pop when the animation settles. The raised
   shadow carries the lift instead. */
.plate a:hover { box-shadow: var(--lift-2); text-decoration: none; }
.plate a:active { box-shadow: var(--press); }

.plate figure {
  margin: 0 0 var(--s3); border-radius: var(--r-md);
  background: var(--surface-sunken); box-shadow: var(--well);
  padding: var(--s2); overflow: hidden;
}
.plate img { width: 100%; border-radius: 3px; }
.plate .yr { font-size: 1.0625rem; font-weight: 600; letter-spacing: -.41px; }
.plate .meta {
  font-size: .8125rem; letter-spacing: -.08px; color: var(--ink-2);
  margin-top: 2px; margin-bottom: 0;
}
.plate .cap {
  font-size: .9375rem; line-height: 1.25rem; letter-spacing: -.24px;
  margin: 0 0 var(--s2);
}
.plate .foot { margin-top: auto; padding-top: var(--s2); }

/* Derivative not yet generated. Communicated by texture and text, never colour alone. */
.plate.pending figure {
  display: grid; place-items: center; min-height: 132px;
  background:
    repeating-linear-gradient(45deg, var(--hairline) 0 1px, transparent 1px 9px),
    var(--surface-sunken);
}
.plate.pending .ph { font-size: .8125rem; color: var(--ink-3); text-align: center; }

/* ---------- 9A. Chronology : the join, expressed on the time axis ----------
   Four lists under four headings is co-location. Interleaving every corpus in
   the order the records were made is what no single archive publishes.

   Two columns of bounded cards (owner directive 2026-08-01). A single tall
   column of 29 rows reads as a dump; a card gives each record an edge, and
   two columns halve the scroll. */
.chron {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--s5);
  grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
}
.cev {
  display: grid;
  grid-template-columns: 150px 1fr;
  grid-template-areas: "thumb year" "thumb body";
  gap: var(--s1) var(--s4);
  padding: var(--s4);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  align-content: center;
  transition: box-shadow .2s var(--ease-standard);
}
.cev:hover { box-shadow: var(--lift-2); }
.cev .cthumb { grid-area: thumb; align-self: center; }
.cev .y { grid-area: year; align-self: end; }
.cev .b { grid-area: body; align-self: start; }

.cthumb {
  display: grid; place-items: center;
  width: 150px; height: 112px;
  border-radius: var(--r-sm); overflow: hidden;
  background: var(--surface-sunken); box-shadow: var(--well);
}
.cthumb img {
  width: 100%; height: 100%; object-fit: cover; object-position: center top;
  border-radius: 3px;
}

/* No digitised image: drop the plate column entirely rather than showing an
   empty well. The Library holds catalogue records for volumes it never
   digitised. Owner directive 2026-08-01. */
.cev.noplate {
  grid-template-columns: 1fr;
  grid-template-areas: "year" "body";
}

.cev .y {
  font-size: 1.25rem; font-weight: 700; letter-spacing: -.41px;
  font-variant-numeric: tabular-nums;
}
.cev .b { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cev .b a { font-size: .9375rem; letter-spacing: -.24px; }
.cev .b em { font-style: normal; font-size: .8125rem; color: var(--ink-2); }
/* Source is carried by a label, never by colour alone (27.3). */
.cev.map    .b em::before { content: "Map / "; }
.cev.photo  .b em::before { content: "Image / "; }
.cev.survey .b em::before { content: "Survey / "; }

/* One column of cards on narrow screens. */
@media (max-width: 900px) {
  .chron { grid-template-columns: 1fr; }
}
@media (max-width: 460px) {
  .cev { grid-template-columns: 110px 1fr; }
  .cthumb { width: 110px; height: 82px; }
}

/* ---------- 9B. Narrative : quoted federal text, the substance ----------
   The measure lives on the wrapper, not on the card, so the heading and the
   source note align flush with the text block instead of overhanging it. */
/* The CARD is full width like every other panel on the page; the TEXT
   inside it keeps the 55-75 character reading measure (27.2).
   The constraint used to sit on .narr-block, which shrank the whole card and
   left it visibly narrower than the panels above and below it. Line length
   is a property of the text, not of the container it sits in. */
/* Prose sits in a card, like every table and every panel does.
   Owner directive 2026-08-02: a section without the boundary its neighbours
   have reads as unfinished rather than as deliberate. Audited across all 298
   pages, 209 of which had a bare Provenance while the section above it was
   carded. The inset shadow is the site's prose idiom (.narrative, .prov);
   the raised one (.panel) is for data. */
.narr-block, .prosecard {
  max-width: none;
  border-radius: var(--r-lg); padding: var(--s5) var(--s6);
  /* Same fix as .anec: surface-on-surface with only an inset shadow does
     not read as a container on a small bright screen. */
  background: var(--surface-sunken);
  border: 1px solid var(--hairline);
  box-shadow: none;
}
.narr-block + .narr-block { margin-top: var(--s4); }
.narr-block h3 { margin-bottom: var(--s3); }
.narr-block .t-foot { margin: var(--s3) 0 0; }
.narr-block > p:last-child, .prosecard > p:last-child { margin-bottom: 0; }
.prosecard .hist:last-child { margin-bottom: 0; }
@media (max-width: 480px) {
  .narr-block, .prosecard { padding: var(--s4); }
}

.narrative {
  border-radius: var(--r-lg); padding: var(--s5) var(--s6);
  box-shadow: var(--press);
}
.narrative p {
  margin: 0 0 var(--s4);
  /* Fills the card. Owner directive 2026-08-01: a full-width card holding
     68-character lines reads as unfinished, and the empty right half is more
     jarring than the long measure.
     A wide measure costs the eye its return sweep, so the line height is
     opened from 1.625 to 1.75 to compensate, which is the standard trade
     when a column is wider than the 55-75 character ideal (27.2). */
  font-size: 1.0625rem; line-height: 1.75rem; letter-spacing: -.41px;
}
.narrative p:last-child { margin-bottom: 0; }

/* ---------- 10A. Historical prose ----------
   The county-history chapters, and the excerpts from them on place and
   property pages.

   These fill the column. They were first written with max-width: 34rem for
   the 55-75 character ideal of 27.2, and that was wrong for the same reason
   it was wrong on .narrative: every other block in a section runs the full
   width, so a narrow paragraph between a full-width intro and a full-width
   trailing link leaves a ragged column with nothing aligned to it. The
   owner rejected exactly this on 2026-08-01 and rejected it again here.

   The measure is therefore long, and the line height is opened from 1.625
   to 1.75 to pay for the eye's longer return sweep, which is the standard
   trade when a column is wider than the ideal. Same compromise, same
   numbers, as .narrative directly above. */
.hist {
  margin: 0 0 var(--s4);
  font-size: 1.0625rem; line-height: 1.75rem; letter-spacing: -.41px;
  color: var(--ink-2);
  /* The recogniser occasionally returns a 36-character run where the book
     printed leader dots ("Burlingame............-++seeeeeeeeee"). At 320px
     that is wider than the column and would push the page sideways, which
     28.3.2 forbids at any width. Nothing is deleted to prevent it. */
  overflow-wrap: break-word;
}
/* No ::first-letter treatment. It was added to echo the book's drop cap and
   was removed on 2026-08-02: a chapter whose opening paragraph continues a
   sentence from the previous page starts on a lower-case letter, and the
   rule then set a bold "m" in the middle of a sentence, which reads as a
   rendering fault. Twelve chapters still open that way. A decoration that is
   wrong whenever the text is not shaped as expected is not worth its
   occasional benefit. */
.chips li.on {
  /* The pressed state is how this stylesheet says "you are here" already
     (#bb-suggest li.on, .plate a:active). Colour alone would also fail
     27.3, which forbids signalling state by colour without a second cue;
     the part label carries aria-current for the same reason. */
  box-shadow: var(--press); color: var(--ink);
}
.chips li a {
  /* Claude.md 30.4.1: transform does not apply to a non-replaced inline
     element, so a bare inline anchor here matches the jelly rule, is told
     to animate, and silently does nothing.

     Accent, not inherit. A chip that IS a link and does not look like one
     is the exact defect the owner reported twice: a reader cannot tell it
     is clickable, so it reads as a label and the page reads as filler
     (Claude.md 31.3.1). Every chip carrying an anchor now says so. */
  display: inline-block; color: var(--accent); text-decoration: none;
}
/* A contents entry fills its chip, so the whole chip is the hit area and
   every entry clears the 44px target of 27.4 without the text being that
   tall. */
.chips.toc li { padding: 0; }
.chips.toc li a { padding: 8px var(--s3); }

/* A settlement the 1881 chapter never names. Colour alone would not carry
   this (27.3), so it is a word. */
.q {
  font-size: .6875rem; font-weight: 500; color: var(--ink-3);
  margin-inline-start: var(--s1);
}

/* The book's own section headings, restored into the chapter text. Without
   them a 31,000-word chapter is a wall a reader cannot navigate or re-find
   a place in. */
/* The chapter sits in a card like every other block of content on the site.
   It was left bare on the page background on the theory that a card around
   340 paragraphs is a box with no bottom in view; the owner disagreed, and
   a section without the boundary every neighbouring section has reads as
   unfinished rather than as deliberate. */
.chapterbody {
  background: var(--surface); border-radius: var(--r-lg);
  border: 1px solid var(--hairline); box-shadow: var(--lift-1);
  padding: var(--s5) var(--s6);
  margin-top: var(--s4);
}
.chapterbody .hist:last-child { margin-bottom: 0; }
.chapterbody .histhead:first-child { margin-top: 0; }

.histhead {
  /* No scroll-margin-top here. Claude.md 31.3.2: the offset is a property
     of having a fixed header, so it is set once on [id] in section 4. A
     value repeated here would be more specific and would silently win. */
  margin: var(--s7) 0 var(--s3);
  display: flex; align-items: center; gap: var(--s3);
}
.histhead .tocup {
  /* A symbol, not the word "Contents": at one per section it was repeated
     thirty-seven times down the page and read as clutter (owner directive
     2026-08-02). The accessible name is still a phrase, on the aria-label
     and the tooltip, so nothing is lost to a screen reader.

     It carries a resting lift, like .theme-btn above. Drawn flat with only
     a hover state it was invisible until the cursor found it, which is the
     hover-only affordance 28.1 forbids: on a touch screen there is no
     hover, so the control simply did not announce itself at all.

     Size: 36px visually, raised to the 44px minimum wherever the pointer is
     coarse (27.4, 28.5). */
  color: var(--ink-2); text-decoration: none; line-height: 0;
  display: inline-flex; align-items: center; justify-content: center;
  margin-inline-start: auto;
  width: 36px; height: 36px; border-radius: var(--r-full);
  background: var(--surface); box-shadow: var(--lift-1);
  flex: none;
  transition: box-shadow .18s var(--ease-standard),
              color .18s var(--ease-standard);
}
.histhead .tocup:hover { color: var(--accent); box-shadow: var(--lift-2); }
.histhead .tocup:active { box-shadow: var(--press); }
.histhead .tocup svg { width: 18px; height: 18px; pointer-events: none; }
@media (pointer: coarse) {
  .histhead .tocup { width: 44px; height: 44px; }
}
@media (max-width: 480px) {
  .chapterbody { padding: var(--s4); }
}

/* ---------- 10. Table ---------- */
.panel {
  background: var(--surface); border-radius: var(--r-lg);
  box-shadow: var(--lift-1); padding: var(--s2);
}
.tablewrap { overflow-x: auto; border-radius: var(--r-md); }
table { border-collapse: collapse; width: 100%; font-size: .9375rem; letter-spacing: -.24px; }
th, td { text-align: left; padding: var(--s3) var(--s4); vertical-align: top; }
thead th {
  font-size: .8125rem; font-weight: 600; letter-spacing: .5px;
  text-transform: uppercase; color: var(--ink-2); white-space: nowrap;
}
tbody tr { border-top: 1px solid var(--hairline); }
td .note { font-size: .8125rem; color: var(--ink-2); }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .8125rem; font-weight: 600; letter-spacing: -.08px;
  padding: 4px var(--s3); border-radius: var(--r-full);
  box-shadow: var(--press); color: var(--ink-2); white-space: nowrap;
}
.pill.ok   { color: var(--positive); }
.pill.warn { color: var(--caution); }

/* ---------- 11. Consolidated child entity ---------- */
.child {
  border-radius: var(--r-lg); padding: var(--s5);
  box-shadow: var(--press); margin-top: var(--s5);
}
.child h3 { margin-bottom: var(--s1); }
.chips { display: flex; flex-wrap: wrap; gap: var(--s2); list-style: none; margin: var(--s4) 0 0; padding: 0; }
.chips li {
  font-size: .8125rem; font-weight: 600; letter-spacing: -.08px;
  padding: 6px var(--s3); border-radius: var(--r-sm);
  background: var(--surface); box-shadow: var(--lift-1);
}

/* ---------- 12. Item page ---------- */
.hero {
  border-radius: var(--r-lg); padding: var(--s3);
  background: var(--surface-sunken); box-shadow: var(--well);
  margin-bottom: var(--s3);
}
.hero img { width: 100%; border-radius: var(--r-sm); }

blockquote.sig {
  margin: 0; padding: var(--s5) var(--s6);
  border-radius: var(--r-lg); box-shadow: var(--press);
  font-size: 1rem; line-height: 1.4375rem; letter-spacing: -.32px;
}
blockquote.sig footer { margin-top: var(--s3); font-size: .8125rem; color: var(--ink-2); }

/* The archive-is-growing note on the front pages (owner directive
   2026-08-16): one line, no box, readers steer the build order. */
.build-note { text-align: center; margin: 0 0 var(--s6); }

/* The "From the record" story section (owner directive 2026-08-15): a
   short quotation from the record plus a derived-fact strip, near the
   top of every property, structure and county page. */
.story { margin-bottom: var(--s6); }
blockquote.story-q {
  margin: 0 0 var(--s3); padding: 0 var(--s5);
  border-inline-start: 3px solid var(--ink-2);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: 1rem; line-height: 1.4375rem; letter-spacing: -.32px;
}
blockquote.story-q p { margin: 0 0 var(--s1); }
blockquote.story-q cite {
  display: block; font-size: .8125rem; font-style: normal;
  color: var(--ink-2);
}
.story-facts { margin: 0 0 var(--s2); }
.story-src { margin: 0; }

.facts { display: grid; margin: 0; }
.facts > div {
  display: grid; grid-template-columns: 190px 1fr; gap: var(--s4);
  padding: var(--s3) 0; border-top: 1px solid var(--hairline);
}
/* Inside a panel the rows take the panel's horizontal padding so they line
   up with the table rows elsewhere on the page. The vertical rhythm and the
   separators are already set above and are not repeated here. */
.panel .facts > div { padding-left: var(--s4); padding-right: var(--s4); }
.facts > div:first-child { border-top: 0; }
.facts dt { font-size: .9375rem; color: var(--ink-2); }
.facts dd { margin: 0; font-size: .9375rem; }
@media (max-width: 599px) { .facts > div { grid-template-columns: 1fr; gap: 2px; } }

code {
  display: block; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: .8125rem; line-height: 1.25rem; letter-spacing: 0;
  border-radius: var(--r-md); box-shadow: var(--press);
  padding: var(--s4); margin-top: var(--s3);
  overflow-x: auto; white-space: pre-wrap; word-break: break-word;
  color: var(--ink-2);
}

/* ---------- 13. Provenance ---------- */
.prov {
  border-radius: var(--r-lg); padding: var(--s5) var(--s6);
  background: var(--surface-sunken); border: 1px solid var(--hairline);
}
.prov h2 { margin-bottom: var(--s3); }
.prov ul { margin: 0; padding-left: var(--s5); }
.prov li { font-size: .8125rem; line-height: 1.4375rem; color: var(--ink-2); }
.prov li b { color: var(--ink); font-weight: 600; }

/* ---------- 14. App suggestion slot ----------
   Claude.md 28.14.1 : never named ad / banner / promo / sponsor, or uBlock
   strips it and the house-ad CTR kill criterion fires on an artefact.       */
.toolbox {
  display: flex; gap: var(--s4); align-items: center; flex-wrap: wrap;
  border-radius: var(--r-lg); padding: var(--s5); box-shadow: var(--lift-1);
  margin-block: var(--s7);
}
.toolbox .body { flex: 1 1 260px; min-width: 0; }
.toolbox h3 { font-size: 1.0625rem; font-weight: 600; letter-spacing: -.41px; }
.toolbox p { margin: var(--s1) 0 0; font-size: .9375rem; line-height: 1.25rem; color: var(--ink-2); }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 var(--s6);
  border-radius: var(--r-full); border: 0; cursor: pointer;
  background: var(--accent); color: var(--accent-ink);
  font-size: 1.0625rem; font-weight: 600; letter-spacing: -.41px;
  transition: filter .15s var(--ease-standard), transform .15s var(--ease-standard);
}
.btn:hover { filter: brightness(1.1); text-decoration: none; }
.btn:active { transform: scale(.97); }

/* ---------- 15. Footer ---------- */
footer.site {
  margin-top: var(--s9); padding-block: var(--s7) var(--s9);
  color: var(--ink-2); font-size: .8125rem; line-height: 1.4375rem;
  box-shadow: 0 -4px 14px -10px var(--nm-dark);
}
footer.site ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s4); }

/* ---------- 16. Prototype banner. Removed in production. ---------- */
.proto {
  margin: 0; text-align: center; padding: var(--s2) var(--s4);
  font-size: .8125rem; font-weight: 600; color: var(--ink-2);
  box-shadow: var(--press);
}

/* ============================================================================
   16A. JELLY HOVER - every interactable element animates on mouseover
   ----------------------------------------------------------------------------
   Owner directive 2026-08-01, standing rule for this site. Ported from the
   house pattern in WebAPPS/01_pdfb2 (app/src/app/globals.css) so the estate
   feels like one product. Three tiers by weight of control:

     jelly        primary controls: buttons, links, cards, plates, rows
     jelly-subtle text inputs, on focus rather than hover
     jelly-micro  secondary controls: selects, labels, checkboxes, radios

   The animation is transform-only, so it composites on the GPU and never
   triggers layout (Claude.md 27.8: never animate layout).
   ========================================================================= */

/* Uniform zoom, not squash and stretch.
   Owner directive 2026-08-01: the original pdfb2 keyframes scale X and Y by
   different amounts (scale(0.95, 1.05)), which is squash-and-stretch. On a
   text link that reads as horizontal shaking and feels bad. These bounce the
   element in and out on BOTH axes equally, so it reads as a springy zoom. */
@keyframes jelly {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.06); }
  55%  { transform: scale(0.985); }
  78%  { transform: scale(1.018); }
  100% { transform: scale(1); }
}
@keyframes jelly-subtle {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.03); }
  58%  { transform: scale(0.994); }
  80%  { transform: scale(1.009); }
  100% { transform: scale(1); }
}
/* Wide rows need their own amplitude.
   Owner directive 2026-08-01: table rows still "moved laterally" under the
   standard jelly. They were not shaking, they were scaling correctly - but a
   uniform 6 percent scale on a row roughly 1000px wide and 47px tall grows it
   about 60px across and 3px down, so the horizontal change is all the eye
   sees and a true zoom reads as a lateral stretch. The fix is amplitude, not
   axis: keep the scale uniform and make it small enough that the horizontal
   delta stays a few pixels. */
@keyframes jelly-row {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.006); }
  65%  { transform: scale(0.998); }
  100% { transform: scale(1); }
}
@keyframes jelly-micro {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.02); }
  70%  { transform: scale(0.996); }
  100% { transform: scale(1); }
}

/* Primary interactive surfaces. Keep this list in sync when new components
   are added: the rule is that nothing interactive is left un-animated. */
button, a,
input[type="button"], input[type="submit"], input[type="reset"], input[type="file"],
[role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"],
[tabindex]:not([tabindex="-1"]),
.btn, .theme-btn, .wordmark, .navlink,
.plate a, .chips li, .pill, summary {
  transition: transform .1s ease-out;
  will-change: transform;
}

button:hover, a:hover,
input[type="button"]:hover, input[type="submit"]:hover,
input[type="reset"]:hover, input[type="file"]:hover,
[role="button"]:hover, [role="link"]:hover, [role="tab"]:hover,
[role="menuitem"]:hover, [role="option"]:hover,
[tabindex]:not([tabindex="-1"]):hover,
.btn:hover, .theme-btn:hover, .wordmark:hover, .navlink:hover,
.plate a:hover, .chips li:hover, .pill:hover,
summary:hover {
  animation: jelly .5s ease;
}

/* Anything full-width and short: table rows, the toolbox strip, grouped list
   rows. Same uniform zoom, gentle enough that it does not read as sideways
   movement. Add new full-width rows here, not to the list above. */
.panel tbody tr, .toolbox, .bz, .cev, .pcard {
  transition: transform .1s ease-out;
  will-change: transform;
}
.panel tbody tr:hover, .toolbox:hover, .bz:hover, .cev:hover, .pcard:hover {
  animation: jelly-row .45s ease;
}

/* Secondary controls: lighter touch. */
select, label[for], input[type="checkbox"], input[type="radio"] {
  transition: transform .1s ease-out;
  will-change: transform;
}
select:hover, label[for]:hover,
input[type="checkbox"]:hover, input[type="radio"]:hover {
  animation: jelly-micro .3s ease;
}

/* Text entry animates on focus, not hover: hovering a field you are not using
   should not twitch. */
input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]),
textarea {
  transition: transform .1s ease-out;
  will-change: transform;
}
input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):focus,
textarea:focus {
  animation: jelly-subtle .4s ease;
}

/* Transform does not apply to non-replaced inline elements, so a link sitting
   in a paragraph would not animate at all. These contexts hold short links
   (source names, "Full record", place names), so inline-block is safe here and
   does not risk a long link refusing to wrap. Deliberately NOT applied inside
   blockquote.narrative, which is quoted text and carries no links.

   The first three lines are STRUCTURAL and were added 2026-08-01 after three
   new components shipped with links that silently refused to animate. Naming
   each new component's class here is a list that will always be one component
   behind; a heading link and a caption link are short by definition in any
   component, present and future, so covering the element and the type-scale
   class fixes the class of bug rather than an instance of it. */
h1 a, h2 a, h3 a, h4 a,
figcaption a, .t-cap a, .t-sub a, .t-foot a,
.attrib a, .prov a, .sec-intro a, .note a, .more a, .ixlist a,
.peeps li a, .anecmore a, .rthumb a,
footer.site a, .panel td a, .cev .b a, .summary a, .storebadges {
  display: inline-block;
  max-width: 100%;
}
/* .t-sub was missing and 112 links across the site silently did not animate,
   including "Read the whole chapter on ..." (owner report 2026-08-02). That
   is the third time this exact bug has shipped, so it is no longer left to
   review: b2town_gate.py now reads every class that wraps an anchor out of
   the built pages and fails the build if it is not covered here. */

/* Icons inside controls ride the parent rather than animating separately. */
button svg, a svg, [role="button"] svg {
  transition: transform .1s ease-out;
  pointer-events: none;
}

/* Press: squish, and cancel any running jelly so the two do not fight. */
button:active, a:active,
input[type="button"]:active, input[type="submit"]:active,
[role="button"]:active,
.btn:active, .theme-btn:active, .plate a:active {
  transform: scale(.95) !important;
  animation: none !important;
}

/* Nothing disabled or non-interactive should move. */
button:disabled, input:disabled, select:disabled,
[aria-disabled="true"], .disabled,
.no-jelly, .no-jelly:hover {
  animation: none !important;
  transform: none !important;
}

/* Table rows are interactive only where they carry a link. */
.panel tbody tr:not(:has(a)):hover { animation: none; }

/* ---------- 17. Motion preference (27.8) ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .plate a:hover { transform: none; }
}

/* ---------- 18. Contrast preference (27.12) ----------
   Soft shadow is the first thing to fail for a low-vision reader, so in
   high-contrast mode the style steps back and real borders take over.     */
@media (prefers-contrast: more) {
  :root { --ink-2: #333A44; --ink-3: #333A44; --hairline: rgba(20,24,31,.55); }
  :root[data-theme="dark"] { --ink-2: #DCE2EA; --ink-3: #DCE2EA; --hairline: rgba(240,242,245,.55); }
  .plate a, .panel, .toolbox, .theme-btn, .chips li { border: 1px solid var(--hairline); }
  .plate figure, .prov, .child, blockquote.sig, code, .pill { border: 1px solid var(--hairline); }
}

/* ---------- 9C. Source attribution ----------
   Owner question, 2026-08-01: "is the Written history written by you, or
   copied from the source?" It is always copied. That must be unmistakable and
   must appear BEFORE the passage, not in a footnote under it. */
.attrib {
  background: var(--surface-sunken); border: 1px solid var(--hairline);
  /* The attribution paragraph is a section's whole content wherever it
     appears, so it is carded here rather than wrapped at each of the six
     call sites, which is how the previous inconsistency happened: some
     provenance sections used prov() and got a card, six hand-rolled one and
     did not. Styling the element covers every present and future caller. */
  margin: 0;
  border-radius: var(--r-lg); padding: var(--s5) var(--s6);
  font-size: .875rem; line-height: 1.375rem; color: var(--ink-2);
}
.attrib + .attrib {
  background: var(--surface-sunken); border: 1px solid var(--hairline); margin-top: var(--s3); }
@media (max-width: 480px) {
  .attrib {
  background: var(--surface-sunken); border: 1px solid var(--hairline); padding: var(--s4); }
}
.badge-src {
  display: inline-block; margin-right: var(--s2);
  font-size: .6875rem; font-weight: 700; letter-spacing: .6px;
  text-transform: uppercase; color: var(--positive);
  padding: 3px var(--s2); border-radius: var(--r-full);
  box-shadow: var(--press);
}
/* A quotation should read as one. Rule down the left edge, and the opening
   mark set in the margin so the block is visibly not our own prose. */
blockquote.narrative {
  /* Reset the UA margin (1em 40px). Without this the quote sits 80px
     narrower than its own heading and the block reads as misaligned. */
  margin: 0;
  position: relative;
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--r-lg) var(--r-lg) 0;
}
/* No decorative quote glyph. An opening mark with no closing counterpart
   reads as a defect, and a matched pair would still be ornament: the SOURCE
   DOCUMENT badge, the words "Quoted word for word", the accent rule and the
   inset card already say this is a quotation four times over. Claude.md 27.1:
   eliminate decoration that does not communicate. */

/* Appearance. Inline with the other footer links, at the footer's own size.
   It was a block of its own below them and rendered noticeably larger than
   everything around it, which gave a control almost nobody touches more
   weight than the contact address. It is a preference, not a feature. */
.segs {
  display: inline-flex; vertical-align: middle; padding: 1px;
  border-radius: var(--r-full); box-shadow: var(--press);
}
.segs button {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  border: 0; background: none; cursor: pointer;
  padding: 1px var(--s2); border-radius: var(--r-full);
  font: inherit; color: var(--ink-2); line-height: 1.4;
}
.segs button[aria-pressed="true"] {
  background: var(--surface); color: var(--ink);
  box-shadow: var(--lift-1); font-weight: 600;
}
.segs button:focus-visible {
  outline: 2px solid rgba(11,95,204,.45); outline-offset: 1px;
}

/* Contact and the cookie control. Two short links on their own line so both
   stay findable on every page without lengthening the navigation row. */
footer.site .ftlinks {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1);
  margin-top: var(--s2);
}
footer.site .ftlinks span { margin: 0 var(--s1); color: var(--ink-3); }
footer.site .ftlinks [data-suggest],
footer.site .ftlinks [data-consent] {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
}
@media (max-width: 600px) {
  footer.site .ftlinks > span[aria-hidden="true"] { display: none; }
}
/* Header language menu (owner request 2026-08-20). A native select driven
   by assets/translate.js, which translates the page IN PLACE through our
   own Worker: the address, layout, images and controls stay put, only
   text changes. 44px tall so the touch target clears Claude.md 28.5 even
   in the compact navigation row. The visible desktop label becomes a
   screen-reader label on narrow screens while the select remains visible. */
.topbar .trwrap {
  display: inline-flex; align-items: center; gap: var(--s1);
  flex: 0 1 auto; min-width: 0; margin: 0;
}
.topbar .trlbl { color: var(--ink-2); font-size: .9375rem; }
.topbar .trwrap select {
  font: inherit; font-size: .9375rem; color: var(--ink-2);
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: var(--r-sm); padding: 6px 24px 6px 8px;
  min-height: 44px; max-width: 220px;
}
.topbar .trwrap select:focus-visible {
  outline: 3px solid rgba(11,95,204,.45); outline-offset: 2px;
}
@media (max-width: 600px) {
  .topbar > .wrap { gap: var(--s2); }
  .topbar .trwrap { flex-basis: 144px; }
  .topbar .trlbl {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap;
    border: 0;
  }
  .topbar .trwrap select { width: 100%; max-width: none; }
}
@media (max-width: 360px) {
  .topbar .wordmark .m2 { display: none; }
  .topbar .trwrap { flex-basis: 160px; }
}

/* Location help. Shown only when the browser refuses without asking, so it
   must read as instructions rather than as an error: numbered, in the
   reader's own operating system's words. */
.locHelp {
  margin-top: var(--s4); padding: var(--s4);
  border-radius: var(--r-lg); box-shadow: var(--press);
  font-size: .8125rem; line-height: 1.125rem; color: var(--ink-2);
  max-width: 560px;
}
.locHelp-t {
  margin: 0 0 var(--s2); font-weight: 600; color: var(--ink);
}
.locHelp ol { margin: 0 0 var(--s4); padding-left: var(--s5); }
.locHelp ol:last-child { margin-bottom: 0; }
.locHelp li { margin-bottom: var(--s1); }

/* House advertisement. The icon is the point: it is what a reader
   recognises, so it leads and it is large enough to read as an app icon
   rather than as a bullet. Class name kept per Claude.md 28.14.1 so an ad
   blocker's heuristics do not strip it. */
/* Generous separation. Two ads on one page sat close enough to read as a
   pair of controls rather than as two separate offers. */
.toolbox { display: flex; align-items: center; gap: var(--s4);
  margin-block: var(--s10); }
.toolbox + .toolbox, section + .toolbox { margin-top: var(--s10); }
.tbicon {
  flex: 0 0 auto; width: 72px; height: 72px;
  border-radius: var(--r-lg); box-shadow: var(--lift-1);
}
.toolbox .body { min-width: 0; }
.toolbox .tbmeta { margin-top: 2px; }
.toolbox .tbmodules { margin-top: 6px; font-size: 0.86rem; line-height: 1.5;
  color: var(--dim, #6b6b70); }
/* Vendor artwork, used as supplied. Version 0.4.0 keeps every multi-store
   group one badge wide, stacked vertically, so actions never narrow the
   adjacent house-ad copy. Height is fixed and width follows, so neither
   badge is stretched or cropped; both vendors forbid altering the artwork
   and Apple's is an SVG that would otherwise scale unpredictably. */
.storebadges { display: inline-block; flex: 0 0 auto; max-width: 100%; }
.storebadge { display: block; line-height: 0; margin-bottom: var(--s2); }
.storebadge:last-child { margin-bottom: 0; }
.storebadge img { height: 44px; width: auto; display: block; }
@media (max-width: 560px) {
  .toolbox { flex-wrap: wrap; }
  .tbicon { width: 56px; height: 56px; }
}

/* ---------- 8B. Consent ----------
   A banner, not a modal that blocks the page. The site is fully usable while
   it is open, which is the point: declining must cost nothing.

   Accept and decline are the SAME size, weight and shape. European
   regulators have held repeatedly that a prominent Accept beside a quiet
   Reject is not freely given consent, so neither is allowed to out-shout the
   other, and decline comes first in the DOM. */
.consent {
  position: fixed; z-index: 600;
  left: var(--s4); right: var(--s4);
  bottom: calc(var(--s4) + env(safe-area-inset-bottom, 0px));
  max-width: 480px; margin-inline: auto;
  padding: var(--s5);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--lift-2);
  animation: slideUpFade .35s var(--ease-spring);
}
.consent-t {
  margin: 0 0 var(--s2);
  font-size: 1.0625rem; font-weight: 600; letter-spacing: -.41px;
}
.consent-b {
  margin: 0 0 var(--s4);
  font-size: .8125rem; line-height: 1.125rem; color: var(--ink-2);
}
.consent-r { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }
.consent-r button {
  height: 44px; border: 0; border-radius: var(--r-full);
  font: inherit; font-size: .9375rem; font-weight: 600; letter-spacing: -.24px;
  cursor: pointer;
}
.consent-no { background: var(--surface); color: var(--ink); box-shadow: var(--lift-1); }
.consent-yes { background: var(--accent); color: var(--accent-ink); box-shadow: var(--lift-1); }
.consent-r button:active { box-shadow: var(--press); }
.consent-r button:focus-visible { outline: 3px solid rgba(11,95,204,.45); outline-offset: 2px; }
.consent-m { margin: var(--s3) 0 0; font-size: .75rem; text-align: center; }

@media (prefers-reduced-motion: reduce) { .consent { animation: none; } }

/* ---------- 9A-2. Browse indexes ----------
   Complete lists of everything published, as names to scan rather than
   records to compare. Multi-column so 125 settlements are a block the eye
   crosses rather than a screen and a half of scrolling. */
.ixlist {
  list-style: none; margin: var(--s5) 0 0; padding: 0;
  columns: 4 190px; column-gap: var(--s6);
}
.ixlist li {
  break-inside: avoid; margin-bottom: var(--s2);
  font-size: .9375rem; line-height: 1.25rem; letter-spacing: -.24px;
}

/* ---------- 9A-1. Home page ----------
   One column, one measure, one control height.

   The first version put a 52px search row above a row of native selects and a
   44px button: three control heights, the browser's own font inside the
   selects, and its own chevrons, next to a hint line that repeated the
   placeholder and a link that repeated the footer. Five different things
   competing on a page whose whole job is to hold one input.

   Everything here is now 52px tall, uses the page's font, and shares one
   width. Nothing is said twice. */
.home { display: grid; place-items: center; min-height: 66vh; }
.homehero { width: 100%; max-width: 620px; }
/* The footer takes the hero's measure on the home page so the two line up.
   Everywhere else the footer already sits on the same left edge as the
   content; here the hero is a centred 620px column inside a 1040px wrap, so
   without this the footer started 200px to its left and the page read as two
   unrelated blocks. */
main.home + footer.site > .wrap { max-width: 620px; margin-inline: auto; }
.homehero .t-large { margin-bottom: var(--s4); }
/* The home headline is a step smaller than every other page's title (owner
   directive 2026-08-02, "reduce the font size of title by ~1/3"). It is
   generated from 5,000 combinations and runs to three lines at the old 44px,
   which made a search page open on a wall of type instead of on its field.
   Title 2 on small screens, Title 1 above: both are scale values (27.2), and
   together they take it from 34/44 to 22/28. */
.home .homehero .t-large { font-size: 1.375rem; line-height: 1.75rem;
  letter-spacing: .35px; }
@media (min-width: 735px) {
  .home .homehero .t-large { font-size: 1.75rem; line-height: 2.125rem;
    letter-spacing: .36px; }
}
/* Quieter than the headline by a clear step. It is a subtitle explaining the
   headline, not a second headline, and at --ink-2 it competed. */
.homehero .summary { margin-bottom: var(--s7); color: var(--ink-3); }
/* Footnote size and the quietest ink the palette carries (owner directive
   2026-08-02). With the headline down to Title 1 the tagline was within one
   step of it and the pair read as two headlines; dropping it to 13px and
   --ink-3 puts a clear gap between them and leaves the search field as the
   only prominent thing on the page. 13px is the floor for UI text (27.13)
   and this clears 4.5:1 against the page in both themes. */
.home .homehero .summary { font-size: .8125rem; line-height: 1.125rem;
  letter-spacing: -.08px; }
/* No hint line on the home page. The placeholder now says "a town, an
   address, or anything on this site", which carries the same information in
   the place a visitor is already looking. A second sentence restating it
   under the field is the redundancy this page was stripped to remove. The
   hint still renders on the results page, where the field is not the focus. */
.home .homehide { display: none; }
.home .searchbar { margin: 0; }
/* A smaller field on the home page (owner directive 2026-08-02, "reduce the
   font size in the search bar by ~1/3").
   NOT a third: 17px less a third is 11px, and an input under 16px makes iOS
   Safari zoom the whole page the moment it is focused (28.10), which would
   trade a slightly large placeholder for the field jumping under the
   visitor's thumb. 16px is the floor, so the rest of the reduction is taken
   out of the control instead - 52px tall becomes the standard 44px - which
   is what actually reads as "smaller" at a glance. */
.home .searchbar input[type="search"] {
  font-size: 1rem; letter-spacing: -.32px; height: 44px;
  padding: 0 var(--s4);
}
.home .searchbar button { height: 44px; min-width: 44px; }

/* The quiet link between the two modules. Text, not a filled control: it
   offers an alternative, it is not the action on the page.

   Every property is stated and the selector is specific enough to beat both
   `.searchbar button` and `.browse button`. Without that the two switches
   inherited whichever module they sat in and came out as two different
   controls in two different places: a 52px raised pill under the search
   field, and an accent-coloured button inside the browse row.
   One component, one appearance, one position. */
.searchbar .switch,
.browse + .switch,
.switch {
  /* Right-aligned, so each sits directly under the action it is an
     alternative to: "or browse" under Search, "or search" under Go.
     display:block with width:fit-content and an auto left margin, because a
     button is inline-block by default and would otherwise ignore the auto
     margin and stay against the left edge. */
  display: block; width: fit-content;
  height: auto; min-width: 0;
  margin: var(--s3) 0 0 auto; padding: 0;
  border: 0; border-radius: 0; background: none; box-shadow: none;
  color: var(--accent); font: inherit; font-size: .9375rem; font-weight: 400;
  letter-spacing: -.24px; cursor: pointer; white-space: nowrap;
}
.searchbar .switch:active,
.browse + .switch:active,
.switch:active { box-shadow: none; }
.switch:focus-visible { outline: 3px solid rgba(11,95,204,.45); outline-offset: 3px; }

[hidden] { display: none !important; }

.browse {
  display: grid; gap: var(--s2); margin-top: var(--s4);
  /* The county name is the longest of the three and was the one truncating,
     so it gets the extra room rather than an equal share. minmax(0,...)
     because a grid track's default min-content floor would otherwise refuse
     to shrink and push the button off the row. */
  grid-template-columns: minmax(0, .85fr) minmax(0, 1.3fr) minmax(0, 1fr) auto;
}
.browse select {
  -webkit-appearance: none; appearance: none;
  height: 52px; padding: 0 var(--s6) 0 var(--s4);
  min-width: 0; border: 0; border-radius: var(--r-full);
  background-color: var(--surface); color: var(--ink);
  box-shadow: var(--lift-1);
  font: inherit; font-size: 1rem; font-weight: 600; letter-spacing: -.32px;
  /* One chevron, drawn to match the icon weight used everywhere else. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234B535F' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s3) center;
  background-size: 18px 18px;
  text-overflow: ellipsis;
}
.browse select:focus-visible,
.browse button:focus-visible {
  outline: 3px solid rgba(11,95,204,.45); outline-offset: 2px;
}
.browse button {
  height: 52px; padding: 0 var(--s5); border: 0;
  border-radius: var(--r-full);
  background: var(--accent); color: var(--accent-ink);
  box-shadow: var(--lift-1);
  font: inherit; font-size: 1rem; font-weight: 600; letter-spacing: -.32px;
  cursor: pointer;
}
.browse button:active { box-shadow: var(--press); }

@media (max-width: 560px) {
  .browse { grid-template-columns: 1fr; }
  .searchbar .row { flex-wrap: wrap; }
}

/* ---------- 9B-1. Then and now ----------
   Two surveys of the same ground, wiped between with a range input.

   The range input IS the control, not a decorative handle with a script
   behind it: it is focusable, arrow-key operable and announced by a screen
   reader for free, which a div with a drag handler is not. It is made
   invisible and stretched over the image, and the visible handle is drawn on
   its thumb. Without JavaScript it still renders both sheets, the newer one
   at half width, which is a legible fallback rather than a broken control. */
.cmp {
  position: relative; overflow: hidden;
  border-radius: var(--r-lg); box-shadow: var(--lift-1);
  aspect-ratio: 1 / var(--cmp-ar, 0.75);
  background: var(--surface-sunken);
  --cmp-x: 50%;
}
/* Both panes fill the box and share identical geometry, so the two sheets
   line up whatever transform each one carries. The overlay is CLIPPED rather
   than sized to the handle: the old version set the overlay's width to the
   handle position and stretched its image to 100vw to compensate, which
   worked for a static wipe and makes independent pan and zoom impossible,
   because the two images then live in different coordinate systems. */
.cmp-pane {
  position: absolute; inset: 0; overflow: hidden;
  touch-action: none;               /* we handle drag and pinch ourselves */
}
.cmp-pane img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  transform-origin: 50% 50%;
  /* will-change is deliberately NOT set here.
     Claude.md 30.1 asks for it on the jelly selectors, where the transform
     is a short animation. This transform is driven continuously by a drag,
     the element is a child of a clip-path parent, and promoting it to its
     own layer buys little against the risk of a compositing quirk in that
     combination. Left off unless a measured frame-rate problem appears.

     (An earlier version of this comment blamed a Safari paint bug for a
     blank comparison. That was wrong and is recorded so nobody re-derives
     it: the blank was an artefact of a test that flipped loading="lazy" to
     eager after load, which decodes the image but schedules no repaint.) */
  -webkit-user-select: none; user-select: none; -webkit-user-drag: none;
}
.cmp .cmp-b {
  clip-path: inset(0 calc(100% - var(--cmp-x)) 0 0);
  border-right: 0;
}
.cmp-div {
  position: absolute; top: 0; bottom: 0; width: 2px;
  left: var(--cmp-x); margin-left: -1px;
  background: var(--nm-light); pointer-events: none;
}
.cmp-grip {
  position: absolute; top: 50%; left: var(--cmp-x);
  width: 44px; height: 44px; margin: -22px 0 0 -22px;
  border-radius: var(--r-full); cursor: ew-resize; touch-action: none;
  background: var(--surface); box-shadow: var(--lift-1);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-2); z-index: 3;
}
.cmp-grip:hover { color: var(--accent); box-shadow: var(--lift-2); }
.cmp-grip svg { width: 20px; height: 20px; pointer-events: none; }
/* The range input stays for the keyboard and for assistive technology: the
   grip is a pointer affordance and cannot be tabbed to or arrowed. It is
   visually hidden rather than removed (27.12). */
.cmp-r {
  position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  margin: 0; padding: 0; opacity: 0; width: 100%;
  -webkit-appearance: none; appearance: none; background: transparent;
}
.cmp-r:focus-visible {
  opacity: 1; height: 6px; outline: 3px solid rgba(11,95,204,.55);
}
.cmp-r:focus-visible { outline: 3px solid rgba(11,95,204,.55); outline-offset: 2px; }
.cmp-l {
  position: absolute; bottom: var(--s3);
  padding: 2px var(--s3); border-radius: var(--r-full);
  font-size: .75rem; font-weight: 600; letter-spacing: 0;
  background: var(--mat-regular, rgba(255,255,255,.85)); color: var(--ink);
  pointer-events: none;
}
/* Controls for the comparison: a zoom group per side, then reset and save.
   Built by script, so a visitor without JavaScript sees the two sheets and
   the wipe default and never sees buttons that would not work. */
/* Each side's zoom sits under the sheet it controls, and the two actions
   that apply to BOTH sides sit between them (owner directive 2026-08-02).
   Grid rather than space-between, because with three flex children the
   middle one lands wherever the outer two leave it rather than centred. */
.cmpbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: var(--s3);
  margin-top: var(--s3);
}
/* Columns are assigned EXPLICITLY, not left to source order. The actions are
   appended last so that the narrow layout can drop them onto their own row
   underneath both zoom groups; with auto-placement that same order puts the
   second zoom group in the middle column and the actions on the right,
   which is the reverse of what was asked for. */
/* grid-row: 1 on all three is what keeps them on ONE line.
   Auto-placement walks a cursor through the grid in source order: the second
   zoom group takes column 3, which advances the cursor past column 2, so the
   actions - explicitly placed in column 2 - could only go to a SECOND ROW.
   Naming the row for every item removes the cursor from the question. */
.cmpbar > .cmpgrp:nth-of-type(1) { grid-column: 1; grid-row: 1; justify-self: start; }
.cmpbar > .cmpgrp:nth-of-type(2) { grid-column: 3; grid-row: 1; justify-self: end; }
.cmpbar > .cmpact { grid-column: 2; grid-row: 1; }
.cmpgrp {
  display: flex; align-items: center; gap: var(--s2);
  font-size: .8125rem; color: var(--ink-2);
}
.cmpz {
  min-width: 44px; min-height: 44px; border: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface); color: var(--ink-2);
  border-radius: var(--r-full); box-shadow: var(--lift-1);
  transition: box-shadow .18s var(--ease-standard),
              color .18s var(--ease-standard);
}
.cmpz:hover { color: var(--accent); box-shadow: var(--lift-2); }
.cmpz:active { box-shadow: var(--press); }
.cmpz[disabled] { opacity: .4; cursor: default; box-shadow: var(--lift-1); }
.cmpz svg { width: 18px; height: 18px; pointer-events: none; }
/* The zoom level is typed as well as stepped, so it is a real field: it
   reads as editable, and it clears the 44px target because the wrapper
   carries the height. */
.cmplvlwrap {
  display: inline-flex; align-items: center; gap: 1px;
  height: 44px; padding: 0 var(--s2); border-radius: var(--r-sm);
  background: var(--surface); box-shadow: var(--press);
}
.cmplvl {
  width: 3.1em; border: 0; background: transparent; padding: 0;
  text-align: right; font: inherit; font-variant-numeric: tabular-nums;
  font-weight: 600; color: var(--ink);
  -moz-appearance: textfield; appearance: textfield;
}
.cmplvl::-webkit-outer-spin-button,
.cmplvl::-webkit-inner-spin-button {
  /* The steppers are the +/- buttons either side; a second pair of tiny
     arrows inside the field is a duplicate control with a 12px target. */
  -webkit-appearance: none; margin: 0;
}
.cmplvl:focus { outline: none; }
.cmplvlwrap:focus-within { box-shadow: 0 0 0 3px rgba(11,95,204,.35); }
.cmpx { font-weight: 600; color: var(--ink-2); font-size: .8125rem; }

.cmpact { display: flex; gap: var(--s2); justify-self: center; }
.cmpact button {
  min-height: 44px; padding: 0 var(--s4); border: 0; cursor: pointer;
  background: var(--surface); color: var(--ink-2);
  border-radius: var(--r-full); box-shadow: var(--lift-1);
  font-size: .8125rem; font-weight: 600;
  transition: box-shadow .18s var(--ease-standard),
              color .18s var(--ease-standard);
}
.cmpact button:hover { color: var(--accent); box-shadow: var(--lift-2); }
.cmpact button:active { box-shadow: var(--press); }
@media (max-width: 620px) {
  /* Three groups will not fit on one line at this width, so the two zoom
     groups share a row and the shared actions take the row beneath. */
  .cmpbar { grid-template-columns: 1fr 1fr; }
  .cmpbar > .cmpgrp:nth-of-type(1) { grid-column: 1; grid-row: 1; justify-self: start; }
  .cmpbar > .cmpgrp:nth-of-type(2) { grid-column: 2; grid-row: 1; justify-self: end; }
  .cmpbar > .cmpact { grid-column: 1 / -1; grid-row: 2; justify-self: stretch; }
  .cmpact button { flex: 1; }
}

.cmp-l0 { left: var(--s3); }
.cmp-l1 { right: var(--s3); }

/* ---------- 9C-1. Locator maps ----------
   Where this county is, in two glances. Inlined SVG, so these follow the
   theme through custom properties; an <img>-loaded SVG would not see them.
   The tokens live with the other tokens in section 2, not here: declared
   mid-file they sat AFTER the dark-mode block and silently won in dark
   mode. */
.locwrap {
  display: grid; gap: var(--s4); margin: 0 0 var(--s8);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  max-width: 560px;
  align-items: stretch;
}
/* Column layout with the caption pushed to the bottom. The two maps have
   different aspect ratios, so without this their captions sit at different
   heights and the pair reads as misaligned rather than as a set. */
.loc {
  margin: 0; padding: var(--s3);
  border-radius: var(--r-lg); box-shadow: var(--press);
  display: flex; flex-direction: column;
}
.loc svg { display: block; width: 100%; height: auto; margin: auto 0; }
.loc figcaption { margin-top: var(--s3); text-align: center; }
/* A locator standing on its own, outside the two-up grid. Without a width
   it stretched to the full reading column and drew a 60px marker dot on a
   map nobody needed that large. */
.loc.locsolo { max-width: 260px; margin: 0 0 var(--s8); }

/* ---------- 9D-1. Populated place grid ----------
   125 settlements. As a table this was one column of names beside two short
   numbers, mostly white space, scrolling for several screens. As cards it
   fits three across and each one carries where the place actually is. */
.pgrid {
  list-style: none; margin: var(--s5) 0 0; padding: 0;
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
/* Bird's-eye views and land ownership maps (b2town_panoramic). Wide
   panoramics read better two-up; one column below the medium break. */
.lvgrid {
  margin: var(--s5) 0 0; display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
}
.lvc { margin: 0; }
.lvc img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r-md); background: var(--surface-sunken);
  box-shadow: var(--lift-1);
}
.lvc figcaption { margin-top: var(--s2); }
/* The jelly rule (30.4.1): a class that wraps a link must be
   inline-block or the hover animation silently does nothing. */
.lvc a, .lvgrid a, .storebadges { display: inline-block; max-width: 100%; }
/* City directory pages: the recogniser's text, paragraph by paragraph.
   Left-aligned, reading measure, and each paragraph visibly a row so a
   name lookup reads like the scan it came from. */
.dirtext { max-width: 72ch; }
.dirtext p { margin: 0 0 var(--s2); }
.dirtext p + p { border-top: 0.5px solid var(--hairline); padding-top: var(--s2); }
.pcard {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3); border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  transition: box-shadow .2s var(--ease-standard);
}
.pcard:hover { box-shadow: var(--lift-2); }
.pcard .pmap { flex: 0 0 72px; }
.pcard .pmap svg { width: 72px; height: auto; display: block; }
.pcb { min-width: 0; }
.pcn { margin: 0; }
.pcw, .pcs, .pch, .pca { margin: 2px 0 0; }
.pch { color: var(--caution); }
/* Fire insurance survey. Its own section was removed for being a table of
   three links into this grid; the facts it carried moved onto the cards
   (owner directive 2026-08-02). Only three of 125 places have this, and they
   sort first, so it marks them without a badge or a colour-only cue (27.3). */
.pca { color: var(--accent); font-weight: 600; }

/* Photographs of people. Every other image on this site is a map or a
   document, so these are given room rather than shrunk into a strip. */
.phgrid {
  display: grid; gap: var(--s4); margin-top: var(--s4);
  grid-template-columns: 1fr;
}
@media (min-width: 640px) { .phgrid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .phgrid { grid-template-columns: 1fr 1fr 1fr; } }
.phc { margin: 0; }
.phc img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r-md); box-shadow: var(--lift-1);
  background: var(--surface-sunken);
}
.phc figcaption { margin-top: var(--s2); }

/* An anecdote lifted out of a chapter.
   It is an INSET, deliberately: an anecdote dropped into the running text
   needs to land in exactly the right place, and that match was only 24 per
   cent reliable. A box needs the right passage and nothing more (owner
   suggestion 2026-08-02), which is what made the feature shippable. */
.anec {
  margin: var(--s5) 0 0;
  padding: var(--s5) var(--s6);
  border-radius: var(--r-lg);
  max-width: none;

  /* A REAL boundary, not a neumorphic one.
     This was background:var(--surface) with box-shadow:var(--press), which
     is the site's sunken idiom - and on a phone it is invisible, because the
     background is then identical to the page (#F7F7F5 on #F7F7F5) and the
     only distinction is a 7px inset shadow that a small bright screen eats.
     Reported as "I didn't see ANY insets on mobile" (owner 2026-08-02); the
     boxes were rendering the whole time.

     Three cues instead of one, so it survives any screen: a different fill,
     a hairline, and an accent rule down the leading edge that reads as a
     pull-quote at a glance. The stylesheet already conceded this by adding
     borders under prefers-contrast, which almost nobody sets. */
  background: var(--surface-sunken);
  border: 1px solid var(--hairline);
  border-inline-start: 3px solid var(--accent);
  box-shadow: var(--lift-1);
}
.anectag {
  margin: 0; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: .5px; font-weight: 600;
}
.anech { margin: var(--s2) 0 var(--s3); }
.anecq { margin: 0; padding: 0; border: 0; }
.anecq p {
  margin: 0;
  font-size: 1.0625rem; line-height: 1.75rem; letter-spacing: -.41px;
  color: var(--ink-2);
}
.anecmore { margin: var(--s3) 0 0; }
@media (min-width: 900px) {
  /* Two abreast once there is room, so ten of them do not become a column
     of boxes the length of the page. */
  .anecwrap { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); }
}
@media (max-width: 480px) { .anec { padding: var(--s4); } }

/* The index of people. Two or three columns of names, each one a link into
   the passage that describes them. A name is the shortest possible row, so
   a table would be mostly whitespace; a wrapped column list is the right
   shape and reads like the index at the back of the book it comes from. */
/* People are CHIPS, exactly like the settlements list directly beneath them.
   They were a bare column of links, which put two treatments for the same
   kind of thing - a short clickable label - on one page (owner 2026-08-02).
   The layout comes from .chips; only the sub-label differs, and it sits
   inline the way "not in 1881" does on a settlement chip. */
.peeps li { padding: 0; }
.peeps li a { padding: 8px var(--s3); font-weight: 600; }
.peeps li a .q { margin-inline-start: var(--s2); font-weight: 500; }

/* Place locator inside a table row. Sized like the photo thumbnails so the
   two tables on this page share one rhythm. */
/* Same rule for the locator cells: a column that can collapse will. */
.pmap { width: 104px; min-width: 104px; padding-right: 0; }
.pmap svg { display: block; width: 104px; min-width: 104px; max-width: none;
  height: auto; }
@media (max-width: 600px) {
  .pmap, .pmap svg { width: 68px; min-width: 68px; }
}

/* Row thumbnail. Fixed cell so the column stays straight whether or not a
   given property has a photograph, and the image is cropped to a constant
   shape so 39 rows do not each set their own rhythm. */
/* A THUMBNAIL CELL MUST NOT BE SQUEEZED (owner report 2026-08-03).
   `width` on a table cell is a suggestion, and `table { width: 100% }` on a
   400px phone shrank this column to about 40px. The image kept its 90px
   height because the global `img { max-width: 100% }` clamps width only, so
   object-fit: cover cropped every photograph to a vertical sliver - a house
   became a doorway, a herbarium sheet became a barcode.

   min-width holds the column open, max-width: none stops the global clamp,
   and aspect-ratio drives the height from the width so the two can never
   disagree again. The table then overflows and .tablewrap scrolls it, which
   is what it is for (28.3.2, 34.15). */
.rthumb { width: 120px; min-width: 120px; padding-right: 0; }
.rthumb img {
  width: 120px; min-width: 120px; max-width: none;
  aspect-ratio: 4 / 3; height: auto;
  object-fit: cover; object-position: center;
  border-radius: var(--r-sm); background: var(--surface-sunken);
}
/* ONE media rule, not two. A second block for the same selector sat below
   this one setting width 76px and a hard height of 57px, so the aspect ratio
   above was overridden and every mobile thumbnail was 76x57 - the exact
   duplicate-rule collision Claude.md 30.6 and 34.6 were written about, found
   only by measuring the rendered box rather than reading the stylesheet. */
@media (max-width: 600px) {
  .rthumb, .rthumb img { width: 96px; min-width: 96px; }
}

/* Visually hidden, still read aloud. */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- 9C-2. Property photograph ----------
   The building itself. Placed directly under the page title because on a
   property page it IS the content: everything else describes it. The credit
   is a licence condition, so it is part of the figure rather than a footnote
   that could be separated from the image.

   Named .pphoto, not .hero: section 12 already defines .hero for the item
   page's lead image, and reusing the name meant the later rule won, this
   component's height cap never applied, and the item page silently inherited
   spacing it never asked for. A component gets its own class. */
.pphoto {
  margin: 0 0 var(--s7); border-radius: var(--r-lg);
  padding: var(--s3); box-shadow: var(--lift-1);
}
.pphoto img {
  width: 100%; border-radius: var(--r-md);
  background: var(--surface-sunken);
  /* Capped against the viewport, not a fixed pixel height. At full height a
     900px-wide photograph pushed the listing itself below the fold, which
     inverts the hierarchy: the picture introduces the record, it is not the
     whole page. Centre-cropped because these are buildings shot square on,
     so the subject sits in the middle and the loss is sky and foreground. */
  height: auto; max-height: min(52vh, 460px);
  object-fit: cover; object-position: center;
}
.pphoto figcaption { margin-top: var(--s3); }

/* The index line as the lead image of a business page. Wider than the card
   crop because here it is the subject, not a thumbnail. */
.bzhero {
  margin: 0 0 var(--s7); padding: var(--s3);
  border-radius: var(--r-lg); box-shadow: var(--lift-1);
}
.bzhero img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r-sm); background: var(--surface-sunken);
}
.bzhero figcaption { margin-top: var(--s3); }

/* ---------- 9B2. Page illustration (generated line art) ----------
   The owner's directive for image-less pages: an AI-generated line-art
   header, obviously an ILLUSTRATION, never mistakable for a photograph or
   a record. Posterised to two inks on white paper by the generator, so
   dark mode darkens the paper the same two-switch way as the wordmark:
   the system preference unless the visitor forced a theme, and the forced
   theme must win in both directions.

   BUT NOT WITH invert(1) ALONE, which is what shipped and what the owner
   rejected on sight (2026-08-29): "the illustrations in dark mode were
   simply inverted in colour and don't look normal at all."

   invert(1) is exact for the WORDMARK because that is one flat colour over
   alpha, so flipping it cannot change a hue that is not there. An
   illustration is three flat colours - a constant paper rgb(247,245,240)
   plus two inks that vary per drawing - and inverting those rotates every
   hue to its complement. Measured on real frames: gold rgb(198,166,84)
   became blue rgb(57,89,171), terracotta rgb(162,84,52) became cyan
   rgb(93,171,203), and a purple-and-olive carriage came back green and
   purple with the two inks swapped. That is a photographic negative, not a
   dark theme.

   Following invert with hue-rotate(180deg) undoes exactly the hue flip the
   inversion introduced while leaving the lightness inversion in place, so
   the paper still goes to near-black and each ink keeps its own hue family:
   gold stays gold, terracotta stays terracotta. Luminance contrast against
   the paper is unchanged to within one unit on every ink measured, so
   nothing becomes harder to see. This is the same trick used for the
   wordmark plus the one term the wordmark did not need.

   Verified by rendering the three treatments side by side and LOOKING at
   them (32.7) before shipping: prototype/review/darkmode_illustration_fix.png

   The disclaimer lives behind a '?' badge in the lower-right corner
   (owner directive 2026-08-13): a standing caption under every header
   read as noise. The badge opens the note on hover and focus. */
.illhero {
  margin: 0 0 var(--s7); padding: var(--s3);
  border-radius: var(--r-lg); box-shadow: var(--lift-1);
  background: var(--surface);
}
.illframe { position: relative; }
.illframe > img {
  display: block; max-width: 448px; width: 100%; height: auto;
  margin: 0 auto; border-radius: var(--r-sm); background: #FFFFFF;
}
.illhelp {
  position: absolute; right: var(--s2); bottom: var(--s2);
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  border: 0.5px solid color-mix(in srgb, var(--ink) 25%, transparent);
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--surface) 85%, transparent);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  color: var(--ink-2);
  font: 600 15px/1 system-ui, -apple-system, sans-serif;
  cursor: help; touch-action: manipulation;
}
/* The hit area grows to the 44px minimum without changing the badge's
   visual size: a tap just outside the glyph still opens the note. */
.illhelp::before {
  content: ""; position: absolute; inset: -10px;
}
.illhelp:hover, .illhelp:focus-visible,
.illhelp[aria-expanded="true"] { color: var(--ink); }
.illhelp:active { box-shadow: var(--press); }
.illnote {
  position: absolute; right: -4px; bottom: calc(100% + 8px);
  width: max-content; max-width: 260px; padding: var(--s2) var(--s3);
  border: 0.5px solid color-mix(in srgb, var(--ink) 25%, transparent);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  box-shadow: var(--lift-1);
  color: var(--ink);
  font-size: 13px; line-height: 1.4; font-weight: 400;
  text-align: left; cursor: default;
  opacity: 0; pointer-events: none;
  transform: translateY(4px);
  transition: opacity 150ms var(--ease-standard),
              transform 150ms var(--ease-standard);
}
.illhelp:hover .illnote, .illhelp:focus-visible .illnote,
.illhelp[aria-expanded="true"] .illnote {
  opacity: 1; pointer-events: auto;
  transform: translateY(0);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .illhelp, .illnote { background: var(--surface); }
}
/* NO FILTER IN DARK MODE. THE DRAWING IS NOT INVERTED AT ALL.
   ==========================================================================
   Owner report, twice: "the illustrations in dark theme were still wrong -
   the colors were inverted and they look simply awful."

   Both previous attempts were wrong, and wrong in the same way. The first
   used invert(1), copied from the wordmark rule; that is exact for the
   wordmark because it is ONE flat colour over alpha, and on a three-colour
   drawing it rotates every hue to its complement - gold ink came out blue.
   The second added hue-rotate(180deg), which fixed the HUE and left the
   TONE inverted, so the darkest ink became the brightest element: fine
   linework glared while the fills went muddy. It still read as a
   photographic negative, because it still was one.

   The mistake underneath both was treating the paper as a background to be
   removed. IT IS NOT. These are drawings ON PAPER: the white is the sheet,
   the lines are drawn on it, and the tonal relationship between them IS the
   picture. Inverting a print does not adapt it to a dark theme, it makes a
   negative of it, which is what the owner saw twice.

   What an archive actually does with a print on a dark wall is MOUNT it.
   The illustration keeps its own paper, the .illframe card gives it a
   border and a rounded edge, and the result reads as a framed print rather
   than a rendering fault. Rendered side by side before shipping this time
   (review/darkmode_v3.png), which is what should have settled it at the
   first attempt.

   No rule at all is the correct rule here. The declarations are removed
   rather than set to `filter: none`, because a property that is never set
   needs no resetting, and an empty override invites someone to "restore"
   the filter later. */

/* ---------- 9D. Atlas business index ----------
   One card per business, not per reading. Each carries a crop of the actual
   scanned index line, so a machine-read name arrives with its own evidence
   rather than asking to be believed. Two columns, matching the chronology,
   because the eye reads a scanned line faster beside its name than under it. */
.bzlist {
  list-style: none; margin: var(--s5) 0 0; padding: 0;
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
}
.bz {
  padding: var(--s4); border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  transition: box-shadow .2s var(--ease-standard);
}
.bz:hover { box-shadow: var(--lift-2); }
/* A business named in more than one atlas is the whole point of the join,
   so it is marked. Never by colour alone (Claude.md 27.12). */
.bz.multi { box-shadow: var(--lift-2); }
.bz.multi .bzs strong { color: var(--accent); }

/* A fixed band, centre-cropped. The recogniser sometimes returns a box
   spanning two or three printed lines, and left to its natural height that
   crop shows a neighbouring business above the one the card is about, which
   reads as the card being mislabelled. Cropping to the middle keeps the
   right line and drops the bleed. The height is set below the width-driven
   scale of the widest crop so cover never trims the left or right edge,
   where these indexes print the atlas sheet number. */
.bzc {
  display: block; margin-bottom: var(--s3); height: 38px;
  border-radius: var(--r-sm); overflow: hidden;
  box-shadow: var(--press);
}
.bzc img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  /* The scans are warm paper; a neutral backdrop stops them floating. */
  background: var(--surface-sunken);
}
.bzn { margin: 0; }
.bzk { margin: var(--s1) 0 0; text-transform: uppercase; letter-spacing: .5px; }
.bzy { margin: var(--s2) 0 0; }
.bzy a { margin-right: var(--s2); }
.bzs { margin: var(--s1) 0 0; }
.bzalt, .bzreg { margin: var(--s2) 0 0; }

@media (max-width: 480px) {
  .bzlist { grid-template-columns: 1fr; }
}

/* ===========================================================================
   17. THE ARCHIVE MAP
   Owner report 2026-08-02: clicking around the site loses your bearing. A
   drawer, opened from the top bar, showing the whole hierarchy with this page
   marked in it. Breadcrumbs answer "how did I get here"; this answers "what
   is here", which is the question that was actually being asked.
   =========================================================================== */

.topbar > .wrap { display: flex; align-items: center; gap: var(--s3); }
.topbar .wordmark { margin-right: auto; }

.mapbtn {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-width: 44px; min-height: 44px; padding: 0 var(--s3);
  font: inherit; font-size: .8125rem; font-weight: 600; letter-spacing: -.08px;
  color: var(--ink-2); background: var(--surface);
  border: 0; border-radius: var(--r-full);
  box-shadow: var(--lift-1); cursor: pointer;
}
.mapbtn svg { width: 17px; height: 17px; pointer-events: none; }
.mapbtn:active { box-shadow: var(--press); }
.mapbtn[aria-expanded="true"] { color: var(--accent); box-shadow: var(--press); }
@media (max-width: 420px) { .mapbtnl { display: none; } .mapbtn { padding: 0 10px; } }

.mapscrim {
  position: fixed; inset: 0; z-index: 400;
  background: rgba(0,0,0,.40);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.mapscrim[hidden], .mapdrawer[hidden] { display: none; }
/* The document keeps its scrollbar width so opening the drawer does not shift
   the page under it. overflow:hidden on <body> breaks scrolling on iOS
   (28.3.2), so it goes on the root instead. */
.mapopen { overflow: hidden; }

.mapdrawer {
  position: fixed; z-index: 500;
  top: 0; right: 0; bottom: 0;
  width: min(92vw, 420px);
  display: flex; flex-direction: column;
  padding: var(--s4) var(--s4) calc(var(--s4) + env(safe-area-inset-bottom));
  padding-right: calc(var(--s4) + env(safe-area-inset-right));
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  -webkit-backdrop-filter: blur(80px) saturate(300%);
  backdrop-filter: blur(80px) saturate(300%);
  border-radius: var(--r-xl) 0 0 var(--r-xl);
  box-shadow: var(--lift-2);
  animation: slideInRight .35s cubic-bezier(.34,1.56,.64,1);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .mapdrawer { background: var(--surface); }
}
@keyframes slideInRight {
  from { opacity: 0; transform: translateX(24px); }
  to   { opacity: 1; transform: translateX(0); }
}

.maphead { display: flex; align-items: center; gap: var(--s3); }
.maphead h2 { margin: 0; margin-right: auto; }
.mapclose {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0;
  color: var(--ink-2); background: var(--surface);
  border: 0; border-radius: var(--r-full);
  box-shadow: var(--lift-1); cursor: pointer;
}
.mapclose svg { width: 18px; height: 18px; pointer-events: none; }
.mapclose:active { box-shadow: var(--press); }
.mapnote { margin: var(--s2) 0 var(--s3); }
.mapbody { overflow-y: auto; overscroll-behavior: contain; flex: 1; }

.maplist { list-style: none; margin: 0; padding: 0; }
/* 14px per level, not 23. A chapter sits six levels down (site, state,
   county, group, town, chapter), so every pixel of indent is multiplied by
   six: at the first attempt the deepest label began 240px into a 420px
   drawer and eight of them were truncated mid-word. The guide line still
   reads as a tree at this width. */
.maplist .maplist { margin-left: 8px; padding-left: 6px;
  border-left: 1px solid var(--hairline); }
.mapline { display: flex; align-items: center; gap: 2px; min-height: 32px; }

/* The twisty. 32px is under the 44px minimum on its own, so the whole row is
   the target for following the link and this only toggles; both are reachable
   by keyboard and neither hides the other. */
.maptw {
  flex: 0 0 18px; width: 18px; height: 18px; padding: 0;
  border: 0; background: none; cursor: pointer; position: relative;
}
.maptw.empty { cursor: default; }
.maptw:not(.empty)::before {
  content: ""; position: absolute; left: 5px; top: 4px;
  width: 7px; height: 7px;
  border-right: 1.6px solid var(--ink-3); border-bottom: 1.6px solid var(--ink-3);
  transform: rotate(-45deg); transition: transform .18s ease;
}
.maptw[aria-expanded="true"]::before { transform: rotate(45deg); }

.maplabel {
  flex: 1; min-width: 0;
  padding: 5px 6px; border-radius: var(--r-sm);
  font-size: .8125rem; line-height: 1.125rem; letter-spacing: -.08px;
  color: var(--accent); text-decoration: none;
  display: inline-block;              /* transform needs a block box (30.4.1) */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
a.maplabel:hover { background: var(--surface-sunken); }
.maplabel[aria-current="page"] {
  color: var(--ink); font-weight: 600;
  background: var(--surface-sunken);
  box-shadow: inset 2px 0 0 var(--accent);
}
.k-group > .mapline > .maplabel,
.k-root > .mapline > .maplabel,
.k-state > .mapline > .maplabel,
.k-county > .mapline > .maplabel { font-weight: 600; }
.mapn {
  flex: 0 0 auto; font-size: .6875rem; color: var(--ink-3);
  padding: 0 6px; border-radius: var(--r-full);
  background: var(--surface-sunken);
}

/* ===========================================================================
   18. THE COUNTRY MAP
   Explore is the level above the states (owner directive 2026-08-02). The
   states with records are the only ones that can be opened, which is how the
   page tells the truth about how much of the country is done.
   =========================================================================== */

.statepick {
  display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap;
  margin-bottom: var(--s5);
}
.statepick select { min-height: 44px; }
.statepick button {
  min-height: 44px; padding: 0 var(--s5);
  font: inherit; font-weight: 600;
  color: var(--accent-ink, #fff); background: var(--accent);
  border: 0; border-radius: var(--r-full); cursor: pointer;
}
.statepick button:active { box-shadow: var(--press); }

.usfig { margin: 0; }
.usmap { width: 100%; height: auto; display: block; }
.usg path { fill: var(--loc-fill, #C9CFDA); }
/* Not clickable and not pretending to be: no pointer, no hover, no focus
   ring. A state the archive has not reached must not look like a link that
   is broken. */
.usg path:not(.on) { pointer-events: none; }
.usg path.on {
  fill: var(--loc-hot, #0B5FCC); cursor: pointer;
  transition: opacity .15s ease;
  /* AN SVG ELEMENT SCALES ABOUT THE SVG'S ORIGIN, NOT ITS OWN CENTRE.
     Ohio carries role="link" and a tabindex, so it matched the jelly rule in
     section 16A and was told to scale - about (0,0), the top-left corner of
     a 960x600 map. At 1.06 that threw the shape a long way down and to the
     right, which the owner reported as "moving sideways" (2026-08-02). It
     was scaling correctly and around entirely the wrong point.

     transform-box: fill-box makes the element's own bounding box the
     reference, so the same keyframes finally read as the zoom they are
     everywhere else on the site. Section 30 says the animation owns the
     transform on hover; this only says where the origin is. */
  transform-box: fill-box;
  transform-origin: center;
}
.usg path.on:hover { opacity: .82; }
.usg path.on:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.usfig figcaption { margin-top: var(--s3); }

/* Row thumbnail for the settlement tables: the county with one place marked.
   Same drawing as the township rows one section up, so the two read as one
   idea (owner directive 2026-08-02, "should have an icon for each row"). */
.pmap svg { border-radius: var(--r-xs); background: var(--surface-sunken); }

/* Section 30: nothing interactive is left un-animated. */
.mapbtn, .mapclose, .maplabel, .statepick button {
  transition: transform .1s ease-out; will-change: transform;
}
.mapbtn:hover, .mapclose:hover, a.maplabel:hover, .statepick button:hover {
  animation: jelly .5s ease;
}
.maptw { transition: transform .1s ease-out; will-change: transform; }
.maptw:not(.empty):hover { animation: jelly-micro .3s ease; }

/* Alaska and Hawaii insets on the country map (owner directive 2026-08-02).
   Drawn at their own scale in the lower-left corner, the way an atlas does
   it. The frame is a hairline so a reader can see it is a separate panel
   rather than a state adrift in the Pacific. */
.usframe rect.usinset {
  fill: none; stroke: var(--hairline); stroke-width: 1;
}
.usframe text.usinsetl {
  fill: var(--ink-3); font-size: 13px; font-weight: 600;
  font-family: inherit; letter-spacing: -.08px;
}

/* The join between two accounts of the same place (Claude.md 41.4). A rule
   with the source on it, not a heading: the chapter is continuous, the book
   changed. Deliberately quiet - it separates, it does not announce. */
.srcrule {
  display: flex; align-items: center; gap: var(--s3);
  margin: var(--s7) 0 var(--s5);
  font-size: .8125rem; letter-spacing: -.08px; color: var(--ink-3);
}
.srcrule::before, .srcrule::after {
  content: ""; flex: 1; height: 1px; background: var(--hairline);
}
.srcrule span { flex: 0 1 auto; text-align: center; }

/* ------------------------------------------------------------------------
   16C. On-this-page jump bar (Claude.md 36)

   Built in write() from the page's own <h2> elements, so it can never drift
   behind a newly added section - the failure 30.4 describes for hand-kept
   selector lists. Sits directly under the page head.
   ------------------------------------------------------------------------ */
.pagenav { margin: var(--s5) 0 var(--s6); }
.pagenav .chips.toc { margin: 0; }
@media (max-width: 600px) { .pagenav { margin: var(--s4) 0 var(--s5); } }

/* ── Explore map: state abbreviations + neumorphic relief ─────────────────
   Owner directive 2026-08-08: label each state so a reader can tell which
   shape is which, and give the map the neumorphic treatment the rest of the
   site uses.

   Neumorphism on an SVG is not a box-shadow: the shapes are paths, not
   boxes. The relief comes from an SVG filter - one light drop from the
   top-left and one dark from the bottom-right, the same two-light model the
   logo and the plates use - applied to the state layer as a whole so the
   coastline reads as a single raised sheet rather than fifty tiles.

   Contrast is preserved deliberately (27.3, WCAG AA): the label colour is
   chosen against the fill it sits on, not against the page. */
.usab {
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 13px; font-weight: 600; letter-spacing: .02em;
  text-anchor: middle;
  fill: var(--usab-ink, #4A5468);
  paint-order: stroke fill;
  stroke: var(--usab-halo, rgba(255,255,255,.85)); stroke-width: 2.5px;
  /* The label must never eat the click that belongs to the state under it. */
  pointer-events: none;
  user-select: none;
}
/* A state the archive covers is filled with the accent, so its label flips
   to white for contrast rather than staying dark on dark. */
.usg path.on ~ .usabs .usab { }
.usab-on { fill: #FFFFFF; stroke: rgba(0,0,0,.28); }
.usab-out {
  text-anchor: start; fill: var(--usab-ink, #4A5468);
  stroke: var(--usab-halo, rgba(255,255,255,.85));
}
.usled {
  stroke: var(--usab-led, rgba(74,84,104,.45));
  stroke-width: 1px; fill: none; pointer-events: none;
}

/* The raised sheet. Two lights, low amplitude - neumorphism reads as a
   surface catching light, not as a drop shadow (27.13 forbids heavy
   shadows). */
.usmap .usg { filter: url(#neu-relief); }
@media (prefers-reduced-motion: reduce) { .usmap .usg { filter: none; } }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .usab,
  :root:not([data-theme="light"]) .usab-out {
    fill: #C9CFDA; stroke: rgba(0,0,0,.55);
  }
  :root:not([data-theme="light"]) .usled { stroke: rgba(201,207,218,.40); }
}
:root[data-theme="dark"] .usab,
:root[data-theme="dark"] .usab-out { fill: #C9CFDA; stroke: rgba(0,0,0,.55); }
:root[data-theme="dark"] .usled { stroke: rgba(201,207,218,.40); }
/* A covered state whose label sits in the margin: marked by weight and the
   accent, not by a white fill it would be invisible against out there. */
.usab-outon { fill: var(--color-blue, #0B5FCC); font-weight: 700; }
:root[data-theme="dark"] .usab-outon,
:root:not([data-theme="light"]) .usab-outon { fill: #6FA8FF; }

/* ---------- 10A. County timeline (year filter + near me) ----------
   The dated content on a county page carries data-year; this control
   filters it. All rows render server-side so crawlers see everything. */
.timeline {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2);
  margin: 0 0 var(--s6); padding: var(--s3);
  border-radius: var(--r-lg); background: var(--surface-sunken);
  box-shadow: var(--lift-1);
}
.tl-title { font-weight: 600; }
.tl-lab { display: inline-flex; align-items: center; gap: var(--s2); }
.tl-lab input[type="range"] { width: 130px; accent-color: var(--accent); }
.tl-val { min-width: 3.5ch; font-variant-numeric: tabular-nums;
  color: var(--ink-2); font-size: .8125rem; }
.tl-near {
  min-height: 44px; padding: 0 var(--s4);
  font: inherit; font-size: .8125rem; font-weight: 600;
  color: var(--accent-ink); background: var(--accent);
  border: 0; border-radius: var(--r-full); cursor: pointer;
}
.tl-near:active { box-shadow: var(--press); }
.tl-note { flex-basis: 100%; }
.pcard.flash { outline: 3px solid var(--accent); outline-offset: 2px;
  border-radius: var(--r-lg); }
[hidden] { display: none !important; }

/* ---------- 10B. Suggestion dialog (moderated, no accounts) ---------- */
.sgdlg {
  position: fixed; inset: 0; z-index: 500;
  display: grid; place-items: center; padding: var(--s4);
  background: rgba(0,0,0,.40);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.sgdlg[hidden] { display: none; }
.sgdlg form {
  width: min(94vw, 460px); padding: var(--s6);
  border-radius: var(--r-xl);
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  box-shadow: var(--lift-2);
}
.sgf { display: block; margin: var(--s4) 0 0; font-size: .875rem; }
.sgf select, .sgf textarea, .sgf input {
  display: block; width: 100%; margin-top: var(--s1);
  min-height: 44px; padding: var(--s2) var(--s3);
  font: inherit; font-size: 1rem;
  color: var(--ink); background: var(--surface-sunken);
  border: 1px solid var(--hairline); border-radius: var(--r-md);
}
.sgf textarea { resize: vertical; }
.sgweb { display: none !important; }
.sgrow { display: flex; gap: var(--s3); margin-top: var(--s5); }
.sgstat { margin-top: var(--s3); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .sgdlg { background: rgba(0,0,0,.6); }
  .sgdlg form { background: var(--surface); }
}


/* ---------- Survey year picker (multi-decade comparison) ----------
   The chips under a then-and-now comparison, one per additional survey.
   Buttons rather than links: they change what the figure above shows, they
   do not navigate, and a control that does not navigate must not be an
   anchor (28.16.1). aria-pressed carries the state, so the active year is
   announced and not only coloured (27.3: never colour alone) - the same
   reasoning as .chips li.on directly above.

   Variables are the ones this sheet actually defines. The first draft used
   --fill-3 and --sep, which do not exist here; an undefined custom property
   makes the declaration invalid and it is dropped in silence, so the chip
   would have rendered unstyled and nothing would have said why. */
.cmpyears { margin-top: var(--s3); }
.chips.yrs { margin-top: var(--s2); }
.chips.yrs li { padding: 0; background: none; box-shadow: none; }
.chips.yrs .yrb {
  font: inherit; font-weight: 600; cursor: pointer;
  min-height: 44px; padding: 6px var(--s3);
  border: 0; border-radius: var(--r-sm);
  background: var(--surface); box-shadow: var(--lift-1);
  color: var(--accent);
  will-change: transform;
}
.chips.yrs .yrb[aria-pressed="true"] {
  background: var(--accent); color: var(--accent-ink);
  box-shadow: var(--press);
}

/* ---------- 10C. First-visit orientation and research controls ----------
   These components make scope, source interpretation, and reader-owned
   actions visible without competing with the archive material. */
.orientation, .research-tools, .coverage-status {
  margin-block: var(--s8);
  padding: var(--s6);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--lift-1);
}
.orientation > .sec-head, .coverage-status > .sec-head { margin-bottom: var(--s3); }
.start-steps {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s4); margin: var(--s5) 0 0; padding: 0; list-style: none;
}
.start-steps li { display: flex; align-items: flex-start; gap: var(--s3); }
.start-steps li > span {
  flex: 0 0 28px; height: 28px; display: grid; place-items: center;
  border-radius: var(--r-full); color: var(--accent-ink);
  background: var(--accent); font-size: .8125rem; font-weight: 700;
}
.start-steps p { margin: 2px 0 0; color: var(--ink-2); font-size: .9375rem; line-height: 1.3rem; }
.scope-note {
  margin-top: var(--s5); padding: var(--s4); border-radius: var(--r-md);
  background: var(--surface-sunken); border: 1px solid var(--hairline);
}
.scope-note h3 { margin: 0; }
.scope-note p { max-width: 72ch; margin: var(--s2) 0 0; color: var(--ink-2); font-size: .9375rem; line-height: 1.3rem; }

.research-tools {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s4) var(--s6); align-items: start;
}
.research-copy h2 { margin: 0; }
.research-copy p { max-width: 58ch; margin: var(--s2) 0 0; }
.research-actions { display: flex; flex-wrap: wrap; gap: var(--s2); }
.btn.secondary, .progressive-toggle, .saved-remove {
  color: var(--accent); background: var(--surface-sunken);
  border: 1px solid var(--hairline);
}
.research-status { min-height: 1.125rem; grid-column: 1 / -1; margin: 0; }
.source-guide, .saved-pages { grid-column: 1 / -1; }
.source-guide summary, .saved-pages summary {
  color: var(--accent); cursor: pointer; font-size: .9375rem; font-weight: 600;
}
.source-guide ul { max-width: 74ch; margin: var(--s3) 0 0; padding-left: var(--s5); }
.source-guide li + li { margin-top: var(--s2); }
.saved-pages ul { margin: var(--s3) 0 0; padding-left: var(--s5); }
.saved-pages li { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s2); }
.saved-remove, .progressive-toggle {
  min-height: 44px; padding: 0 var(--s3); border-radius: var(--r-full);
  font: inherit; font-size: .8125rem; font-weight: 600; cursor: pointer;
}
.progressive-toggle { display: block; margin: var(--s4) auto 0; }
.clipboard-proxy {
  position: fixed; left: -9999px; top: 0; width: 1px; height: 1px; opacity: 0;
}
.tb-origin { color: var(--ink-3) !important; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.passage-source {
  margin: calc(var(--s1) * -1) 0 var(--s4); padding-left: var(--s3);
  border-left: 2px solid var(--hairline); color: var(--ink-3);
}

@media (max-width: 734px) {
  .start-steps { grid-template-columns: 1fr; }
  .research-tools { grid-template-columns: 1fr; }
  .research-actions { width: 100%; }
  .research-actions .btn { flex: 1 1 180px; }
}

@media (prefers-contrast: more) {
  .orientation, .research-tools, .coverage-status, .scope-note,
  .btn.secondary, .progressive-toggle, .saved-remove { border-width: 2px; }
}
