/* Shared tokens and the full-screen image viewer.
   Both map.html and item.html load this. Page-specific layout stays in the page. */

/* ------------------------------------------------------------- fonts ---
   Self-hosted, straight from the files Google Fonts was serving (same
   subsetting, same unicode-range, so a page still downloads only the scripts
   it actually contains - the Cyrillic subsets exist for the visible
   inscriptions and load nowhere else). The fonts.googleapis.com round trip
   was a third-party dependency on the critical path: one of three measured
   cold loads stalled first paint to 2.4s waiting for it. font-display:swap,
   so text never blocks on a font. Weights cover what the site uses: Cinzel
   is the 400-600 variable file (400 titles, 600 top-level), Spectral 400
   roman and italic. */
/* Cinzel latin-ext */
@font-face{
  font-family:'Cinzel';
  font-style:normal;
  font-weight:400 600;
  font-display:swap;
  src:url(/fonts/cinzel-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Cinzel latin */
@font-face{
  font-family:'Cinzel';
  font-style:normal;
  font-weight:400 600;
  font-display:swap;
  src:url(/fonts/cinzel-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Spectral cyrillic-ext italic */
@font-face{
  font-family:'Spectral';
  font-style:italic;
  font-weight:400;
  font-display:swap;
  src:url(/fonts/spectral-italic-cyrillic-ext.woff2) format('woff2');
  unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* Spectral cyrillic italic */
@font-face{
  font-family:'Spectral';
  font-style:italic;
  font-weight:400;
  font-display:swap;
  src:url(/fonts/spectral-italic-cyrillic.woff2) format('woff2');
  unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Spectral latin-ext italic */
@font-face{
  font-family:'Spectral';
  font-style:italic;
  font-weight:400;
  font-display:swap;
  src:url(/fonts/spectral-italic-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Spectral latin italic */
@font-face{
  font-family:'Spectral';
  font-style:italic;
  font-weight:400;
  font-display:swap;
  src:url(/fonts/spectral-italic-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Spectral cyrillic-ext */
@font-face{
  font-family:'Spectral';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url(/fonts/spectral-cyrillic-ext.woff2) format('woff2');
  unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* Spectral cyrillic */
@font-face{
  font-family:'Spectral';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url(/fonts/spectral-cyrillic.woff2) format('woff2');
  unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* Spectral latin-ext */
@font-face{
  font-family:'Spectral';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url(/fonts/spectral-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Spectral latin */
@font-face{
  font-family:'Spectral';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url(/fonts/spectral-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root{
  --ground:#1C1C1C; --deep:#0C0C0C;
  --ink:#F9F9F9;                 /* 16.2:1 on --ground */
  --muted:#A8A49F;               /*  6.9:1 */
  --faint:#8A8784;               /*  4.8:1, the faintest text permitted here */
  --accent:#B8A88F;              /*  7.3:1 */
  --rule:rgba(249,249,249,.14);        /* 1.5:1 on --ground: decorative only */
  /* WCAG 1.4.11 wants 3:1 for a boundary that identifies a control. --rule
     composites to 1.52:1 on --ground and 1.42:1 on --deep, which is fine for a
     separator and not fine as the only thing marking the search field or a
     filter button. Use this on anything you have to find in order to use it. */
  --rule-control:rgba(249,249,249,.42);  /* 3.1:1 on --ground */
  --display:'Cinzel',Georgia,serif;
  --text:'Spectral',Georgia,serif;
  /* Tracking. Small uppercase labels are the workhorse of this interface, and
     they had drifted to eleven different values across the five pages, which
     reads as several slightly different design systems sharing a screen. Four
     tokens, every one of them a value already in use. */
  /* Spacing scale. Before this, vertical gaps were set ad hoc per element and
     the sticky header ended up with 3px between the navigation underline and
     the page title, and 0px between the title and the search field. Use these
     rather than inventing a value. */
  --sp-1:.25rem;   /*  4px */
  --sp-2:.5rem;    /*  8px */
  --sp-3:.75rem;   /* 12px */
  --sp-4:1rem;     /* 16px */
  --sp-5:1.5rem;   /* 24px */

  --ls-display:.02em;   /* headings that want a little air, nothing more */
  --ls-tight:.1em;      /* dense labels: chips, counts inside a control */
  --ls-label:.14em;     /* the default for small uppercase */
  --ls-wide:.2em;       /* section heads and navigation */

  /* The smallest type in the interface. Nothing goes below this: 9px labels
     were being set on a dark ground, which is where legibility gives out. */
  --label-min:.625rem;  /* 10px */

  /* ONE height for every control in the header's filter row, declared rather
     than inherited. The connected design system's Button gives each size
     variant an explicit height (h-9/h-8/h-10) and makes an icon button a SQUARE
     of that same height (size-9), never a padding-derived box. We keep 44px
     rather than its 36px, because 44px is the touch-target floor this site
     holds to and was already the min-height on .sort, .ms-btn, .clear, .back
     and .share.
     Before this, .facetRow was align-items:stretch and only .ms-btn, .sort and
     .clear were pinned to the row with height:100%. .back and .share sized
     themselves from their own padding and then stretched, so one row ran two
     different sizing models and the two odd ones out measured 60px against the
     pills' 44px and sat 16px higher. */
  --control-h:2.75rem;  /* 44px */
  /* The site footer's height, published so a FIXED surface can keep clear of
     it. MapLibre's attribution is a tile-licence requirement and the footer was
     covering it, and the scale bar, at every desktop width. */
  --footH:2.25rem;
  /* The navigation bar's height when it sits at the bottom of a phone. */
  --navH:3.25rem;
  /* BOTTOM CHROME, the one token everything above the bottom edge clears:
     Mickey split the bottom edge by device on 2026-08-31 - "bottom of the
     screen on mobile, remove the footer, top of the screen on desktop, bring
     back the footer." Below 900px the bottom edge belongs to the navigation
     bar and there is no footer; at 900px and up the navigation returns to the
     header and the footer returns to the bottom. Safe-area included, so
     consumers add only their own breathing room. */
  --chrome-b:calc(var(--navH) + var(--safe-b));
  --safe-b:env(safe-area-inset-bottom,0px);
  --safe-t:env(safe-area-inset-top,0px);
  /* What a SCROLLING page's content pads its bottom by. On desktop that is
     the fixed footer's clearance (--chrome-b). Below 900px it is 0: the
     footer is IN FLOW at the end of the page there (Mickey, 2026-09-01:
     "add footer to the bottom of the pages on mobile ... behind the bottom
     navigation") and carries the nav-bar clearance itself, so content that
     also padded by --chrome-b was the dead space he screenshotted. Fixed
     surfaces (the Map) keep using --chrome-b directly: they have no page
     end for a footer to sit at. */
  --page-b:var(--chrome-b);
}
@media (max-width:899px){
  :root{--page-b:0px}
}

/* ------------------------------------------------------------- viewer */
.viewer{
  position:fixed;inset:0;z-index:40;background:rgba(6,6,6,.97);
  display:none;opacity:0;transition:opacity .2s ease;
  backdrop-filter:blur(6px);
}
.viewer.open{display:block;opacity:1}
body.viewer-open{overflow:hidden}

.viewer-map{
  position:absolute;inset:0;opacity:0;transition:opacity .5s ease;
  filter:brightness(.22) saturate(.65);pointer-events:none;
}
.viewer.hasMap .viewer-map{opacity:1}
.viewer.hasMap::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(ellipse at center, transparent 30%, rgba(6,6,6,.72) 100%);
}
.viewer-stage{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  overflow:hidden;touch-action:none;cursor:zoom-in;
}
.viewer.zoomed .viewer-stage{cursor:grab}
.viewer.zoomed .viewer-stage:active{cursor:grabbing}
.viewer-stage img{
  max-width:100vw;max-height:100dvh;
  /* No transition on transform: it would fight the pinch gesture. */
  transform-origin:center center;user-select:none;-webkit-user-drag:none;
  display:block;
}
@supports not (max-height:100dvh){ .viewer-stage img{max-height:100vh} }

.viewer-close{
  position:absolute;top:calc(var(--safe-t) + .75rem);right:.75rem;z-index:2;
  width:2.75rem;height:2.75rem;                 /* 44px, the minimum tap target */
  background:rgba(12,12,12,.8);border:1px solid var(--rule);
  color:var(--ink);cursor:pointer;font-size:1rem;font-family:var(--text);
}
.viewer-close:hover{background:rgba(28,28,28,.95)}
.viewer-close:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* slideshow controls, hidden unless the viewer was opened with a series */
.viewer-nav,.viewer-bar{display:none}
.viewer.hasSeries .viewer-nav{display:grid}
.viewer.hasSeries .viewer-bar{display:flex}

.viewer-nav{
  position:absolute;top:50%;transform:translateY(-50%);z-index:2;
  width:2.75rem;height:4.5rem;place-items:center;
  background:rgba(12,12,12,.55);border:1px solid var(--rule);
  color:var(--ink);cursor:pointer;font-size:1.5rem;line-height:1;font-family:var(--text);
}
.viewer-nav:hover{background:rgba(28,28,28,.9)}
.viewer-nav.prev{left:.5rem}
.viewer-nav.next{right:.5rem}

.viewer-bar{
  position:absolute;left:50%;transform:translateX(-50%);
  bottom:calc(var(--safe-b) + .75rem);z-index:3;
  align-items:center;gap:.85rem;max-width:calc(100vw - 1.5rem);
  background:rgba(12,12,12,.82);border:1px solid var(--rule);
  padding:.4rem .5rem .4rem .4rem;
}
.viewer-play{
  /* 44px, the floor every control on the site holds; this was the one
     interactive control still at 36px. */
  width:var(--control-h);height:var(--control-h);flex:0 0 auto;
  background:none;border:1px solid var(--rule);color:var(--ink);
  cursor:pointer;font-size:.75rem;line-height:1;font-family:var(--text);
}
.viewer-play:hover{background:rgba(249,249,249,.1)}
.viewer-count{
  flex:0 0 auto;font-family:var(--text);font-size:.6875rem;letter-spacing:var(--ls-label);
  color:var(--faint);white-space:nowrap;
}
.viewer-caption{
  font-family:var(--text);font-size:.75rem;color:var(--muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:.35rem;
}
/* While playing, the side arrows recede so the photographs carry the screen. */
.viewer.playing .viewer-nav{opacity:0;transition:opacity .4s}
.viewer.playing:hover .viewer-nav{opacity:1}

@media (max-width:600px){
  .viewer-nav{width:2.5rem;height:3.5rem;font-size:1.25rem}
  .viewer-caption{display:none}
}

.viewer-hint{
  position:absolute;left:50%;transform:translateX(-50%);
  bottom:calc(var(--safe-b) + 1rem);z-index:2;
  font-family:var(--text);font-size:.6875rem;letter-spacing:var(--ls-label);
  text-transform:uppercase;color:var(--faint);
  background:rgba(12,12,12,.7);padding:.5rem .85rem;pointer-events:none;
  transition:opacity .3s;
}
.viewer.zoomed .viewer-hint{opacity:0}
/* the series bar occupies the same spot, so the hint stands down */
.viewer.hasSeries .viewer-hint{display:none}

/* ------------------------------------------------------------- shared bits */
.u-hidden{display:none!important}

::selection{background:var(--accent);color:var(--deep)}

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

/* Scrollbars, so a dark panel does not sprout a light gutter */
*{scrollbar-width:thin;scrollbar-color:rgba(249,249,249,.2) transparent}
*::-webkit-scrollbar{width:8px;height:8px}
*::-webkit-scrollbar-thumb{background:rgba(249,249,249,.2)}
*::-webkit-scrollbar-track{background:transparent}

/* ---------------------------------------------------------- multiselect
   Filter dropdowns. One control per facet, holding several selections. */
.ms{position:relative;display:inline-block}
.ms-btn{
  /* Borderless, like the header icons: "lets remove these gigentic boxws
     arund the filters, they are taking too much vertical space" (1 Sep). The
     44px hit area stays; the BOX is what left the row reading as a wall of
     crates. Hover wash and the focus ring carry the affordance. */
  display:inline-flex;align-items:center;gap:.5rem;
  min-height:var(--control-h);padding:.5rem .55rem;
  background:none;border:0;color:var(--muted);cursor:pointer;
  font-family:var(--text);font-size:.6875rem;letter-spacing:var(--ls-label);
  text-transform:uppercase;
}
.ms-btn:hover{background:rgba(249,249,249,.08);color:var(--ink)}
.ms-btn:hover{color:var(--ink);border-color:var(--faint)}
.ms-btn.on{color:var(--ink);border-color:var(--accent)}
.ms-count{
  display:inline-grid;place-items:center;min-width:1.25rem;height:1.25rem;
  padding:0 .25rem;background:var(--accent);color:var(--deep);
  font-size:.625rem;letter-spacing:0;
}
.ms-chev{font-size:var(--label-min);opacity:.7;transition:transform .2s}
.ms.open .ms-chev{transform:rotate(180deg)}

.ms-panel{
  position:absolute;top:calc(100% + .35rem);left:0;z-index:50;
  min-width:14rem;max-width:min(22rem, 92vw);
  max-height:min(60vh, 22rem);overflow-y:auto;overscroll-behavior:contain;
  background:rgba(12,12,12,.98);border:1px solid var(--rule);
  backdrop-filter:blur(12px);box-shadow:0 10px 32px rgba(0,0,0,.6);
  padding:.35rem;
}
/* Near the right edge the panel would otherwise run off screen. */
.ms:last-child:not(:first-child) .ms-panel{left:auto;right:0}
/* Right-aligning keeps the LAST of several filters from overflowing the page
   edge. A lone filter is both first and last, so that rule threw its panel off
   the left of the screen instead. The Locations page has exactly one filter,
   which is why the fault only appeared there. */

.ms-row{
  display:flex;align-items:center;gap:.6rem;
  padding:.5rem .55rem;min-height:2.75rem;cursor:pointer;
  font-size:.8125rem;color:var(--muted);line-height:1.3;
}
.ms-row:hover{background:rgba(249,249,249,.06);color:var(--ink)}
.ms-row input{
  appearance:none;-webkit-appearance:none;flex:0 0 auto;
  width:1rem;height:1rem;margin:0;border:1px solid var(--faint);
  background:transparent;cursor:pointer;display:grid;place-items:center;
}
.ms-row input:checked{background:var(--accent);border-color:var(--accent)}
.ms-row input:checked::after{content:'✓';color:var(--deep);font-size:.7rem;line-height:1}
.ms-row input:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.ms-text{flex:1 1 auto;min-width:0}
.ms-n{flex:0 0 auto;font-size:.625rem;color:var(--faint);letter-spacing:var(--ls-tight)}

.ms-clear{
  display:block;width:100%;margin:.35rem 0 0;padding:.55rem;
  background:none;border:0;border-top:1px solid var(--rule);
  color:var(--faint);font-family:var(--text);font-size:.625rem;
  letter-spacing:var(--ls-label);text-transform:uppercase;cursor:pointer;
}
.ms-clear:hover{color:var(--ink)}


/* ---------------------------------------------------------------- motion ---
   One easing curve and three durations, so everything that moves in this
   archive moves the same way. Before this each page invented its own.        */
:root{
  --ease:cubic-bezier(.22,.61,.36,1);
  --t-fast:.18s;
  --t-mid:.3s;
  --t-slow:.5s;
}

/* Anything that responds to a pointer settles rather than snapping. Kept to
   colour and border so no layout is animated: transitioning geometry on a
   masonry grid causes reflow on every hover. .card is handled separately
   below, with no transform, for exactly that reason. */
a, button, .chip, .ms-btn, .sort, .clear, .back, .share, .views a, .place{
  transition:color var(--t-fast) var(--ease),
             background-color var(--t-fast) var(--ease),
             border-color var(--t-fast) var(--ease),
             opacity var(--t-fast) var(--ease),
             box-shadow var(--t-mid) var(--ease),
             transform var(--t-mid) var(--ease);
}

/* .card lives inside a column-count masonry on both the Gallery and a place
   page. Promoting one to its own layer makes the browser re-fragment the
   column flow, and the thumbnail blinks as the pointer crosses it. So a
   masonry card gets no transform at all: its hover is border and shadow,
   which cannot refragment anything.
   .place is in a real CSS grid, where a lift is safe. */
.place:hover{ transform:translateY(-2px) }
.card:hover, .place:hover{ box-shadow:0 12px 28px -18px rgba(0,0,0,.95) }
.card:hover{ border-color:rgba(249,249,249,.32) }
/* No entrance animation and no opacity change on hover. Both were here and
   they fought: an animation with fill-mode `both` holds the property it
   animates, which outranks the hover rule, and the pair made every thumbnail
   blink as the pointer crossed it. The lift below is enough, and it is a
   composited property that cannot reflow a column layout.
   Scale lives on the image, so the card's own edge stays still. */
.place{ overflow:hidden }
.place img{ transition:transform var(--t-slow) var(--ease) }
.place:hover img{ transform:scale(1.03) }
/* Deliberately nothing on .card img. Any transform inside a multi-column
   container reintroduces the blink. */

/* Everything above is decoration. Someone who has asked their system to stop
   animating gets none of it. */
@media (prefers-reduced-motion:reduce){
  *, *::before, *::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
  .place:hover{ transform:none }
  .place:hover img{ transform:none }
}

/* Masonry cards: colour only, no geometry, no layer promotion. */
.card{ transition:border-color var(--t-fast) var(--ease), box-shadow var(--t-mid) var(--ease) }

/* Available to a screen reader, never on screen. Not display:none, which would
   hide it from assistive technology too, and not a negative text-indent, which
   breaks in right-to-left. The clip-path rectangle is the pattern that keeps
   the text in the accessibility tree while taking it out of the layout. */
.u-sr-only{
  position:absolute !important;width:1px;height:1px;
  margin:-1px;padding:0;overflow:hidden;white-space:nowrap;
  clip:rect(0 0 0 0);clip-path:inset(50%);border:0;
}

/* ------------------------------------------------------------------ footer ---
   A single line, bottom right, pinned. Deliberately quiet: the archive is the
   thing on screen, this is a credit. Injected once from app.js (see makeFooter)
   so all five pages carry the same one.

   pointer-events are off on the bar and back on for the link alone, so the
   strip never swallows a click meant for a photograph underneath it. */
.siteFoot{
  /* A rule along the whole bottom edge, not a tab in the corner. FIXED on
     desktop, where it accompanies the top navigation (the owner's split, both
     restated 2026-09-01: "on desktop, main navigation should remain on top" /
     "main navigations should be at the botom on the mobile"); below 900px the
     tab bar owns the bottom edge and the footer becomes page content (see the
     phone block). The record panel pads its bottom by --chrome-b so nothing
     of the record hides behind this. */
  position:fixed;left:0;right:0;bottom:0;z-index:16;
  display:flex;justify-content:flex-end;align-items:center;gap:var(--sp-3);
  padding:var(--sp-2) var(--sp-4) calc(var(--sp-2) + var(--safe-b));
  pointer-events:none;
  border-top:1px solid var(--rule);
  /* Vertical, not horizontal. The sideways fade left the text end of the strip
     fully transparent, so card titles and dates printed straight through it. */
  background:linear-gradient(to top,rgba(28,28,28,.97) 0%,rgba(28,28,28,.9) 62%,rgba(28,28,28,.72) 100%);
  backdrop-filter:blur(6px);
}
.siteFoot a{
  pointer-events:auto;
  display:inline-flex;align-items:center;gap:.5em;
  font-family:var(--text);font-size:var(--label-min);line-height:1.35;
  letter-spacing:.03em;color:var(--faint);text-decoration:none;
  text-align:right;max-width:min(30rem, 78vw);
  transition:color var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
}
.siteFoot a:hover{ color:var(--accent) }
/* The mark is a white glyph on transparency, so opacity is all it needs to sit
   at the same weight as the text beside it. */
.siteFoot img{
  width:.95em;height:.95em;flex:0 0 auto;
  opacity:.6;transition:opacity var(--t-fast) var(--ease);
}
.siteFoot a:hover img{ opacity:1 }
.siteFoot .foot-by{ color:var(--muted) }
.siteFoot .foot-year{
  font-family:var(--text);font-size:var(--label-min);letter-spacing:.03em;
  color:var(--faint);line-height:1.35;
}

@media (max-width:599px){
  /* Tighter, but still clearing the fixed tab bar: the footer is in flow and
     its own bottom padding is the bar clearance (--chrome-b, never bare
     safe-b, which put the credit line UNDER the bar). */
  .siteFoot{ padding:var(--sp-1) var(--sp-3) calc(var(--sp-1) + var(--chrome-b)) }
  .siteFoot a{ font-size:var(--label-min);letter-spacing:.02em;max-width:82vw }
}
@media (prefers-reduced-motion:reduce){
  .siteFoot a, .siteFoot img{ transition:none }
}

/* ------------------------------------------------------------- page title ---
   The page name and its count, one line under the navigation. Defined here and
   nowhere else: it was written into gallery.html and map.html separately and
   omitted from index.html, so the Locations heading fell back to the browser
   default and rendered Spectral 32px bold beside two pages showing Cinzel 18px.
   One component, one rule.

   The top margin is generous on purpose. The navigation link above is a 44px
   touch target with its label centred, so the active underline is drawn at the
   bottom of that box rather than under the text; measured from the underline,
   a small gap reads as touching. */
.titleRow{
  display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--sp-4);margin:var(--sp-5) 0 var(--sp-2);
}
/* Weight 600 and a size step up, not the body's 400 at 22px: top-level titles
   carry the page (Mickey, 2026-08-31: "Locations should be bolder since it is
   highest in the hierarchy", then 2026-09-01: "bolder and a bit bigger, all
   top level Titles, trought the app"). On a root page this h1 IS the section
   name. A detail page's own title, one rank down, takes back 400 at the old
   size via .titleBlock h1 below. The title still holds ONE size per width in
   both scroll states - only the header's padding compacts. Cinzel 600 is
   loaded by every page's font link; do not use a weight the link does not
   carry, or the browser fakes it. */
.titleRow h1{
  font-family:var(--display);font-weight:600;font-size:1.5rem;
  letter-spacing:var(--ls-display);margin:0;line-height:1.1;
  transition:font-size var(--t-mid) var(--ease);
}
@media (max-width:599px){
  .titleRow{margin:var(--sp-4) 0 var(--sp-2)}
  .titleRow h1{font-size:1.25rem}
}
@media (prefers-reduced-motion:reduce){ .titleRow h1{transition:none} }

/* ------------------------------------------------- shared components ---
   Hoisted out of the page files. Each of these was defined identically in
   two to five pages at once, which is how they drifted: the page-title row
   was written into three files and omitted from a fourth, so Locations fell
   back to the browser default. One definition per component, here.
   43 selectors were duplicated across pages; these are the ones that were
   byte-identical everywhere they appeared, so hoisting cannot change them. */
/* Chevron only, at EVERY width (Mickey, 2026-08-31: "chevron only"). A 44px
   square; the word "Back" stays in the accessibility tree as clipped text, so
   the control keeps its name without showing it. */
/* NO box on the header icon buttons. Mickey, 2026-09-01: "remove lines around
   these icons" - Back, the search icon, Share and the panel toggle are bare
   44px icon targets now: icon only, full hit area, the global focus ring, and
   a soft wash on hover. This supersedes the darker-border treatment for the
   ICON buttons specifically; the labelled controls (filter pills, sort,
   Clear) keep their boxes at the 3:1 floor, and the Slideshow button keeps
   its accent border (his standing exception). An icon button without a box is
   its own affordance, so 1.4.11's boundary contrast does not apply to a
   border it no longer has. */
.back{align-items:center; background:none; border:0; color:var(--ink); display:inline-flex; font-family:var(--text); font-size:.8125rem; justify-content:center; letter-spacing:var(--ls-label); padding:0; text-decoration:none; text-transform:uppercase; width:var(--control-h)}   /* was in: item, subject */
.back .btnLabel{
  position:absolute;width:1px;height:1px;
  margin:-1px;padding:0;overflow:hidden;white-space:nowrap;
  clip:rect(0 0 0 0);clip-path:inset(50%);border:0;
}
.back .chev{font-family:var(--display); font-size:1.7rem; line-height:1; margin-top:-.1em}   /* was in: item, subject */
/* .batch is gone. Per-batch multicol containers are what tore holes in the
   grid: each batch balanced its own columns and ended ragged, measured up to
   338px of void between two cards in one visual column. The script lays cards
   into .mcol stacks now (createStacks in app.js); only the no-JS pre-render
   still uses the .grid columns. */
/* The tag pills on a record page (and the map peek), where every one is a
   LINK into the Gallery with that filter preset. --rule-control, not --rule:
   an interactive control's boundary holds the 3:1 floor (1.4.11), and these
   sat at 1.5:1 - the one interactive pill on the site below the line the
   rest of the header holds. The old toggle-chip form (aria-pressed) is gone:
   nothing renders it since the filters became dropdowns. */
/* inline-flex with both axes centred: the chips sit in a display:flex .tags
   row whose default stretch pulled every chip to the tallest sibling's height
   while the text stayed at padding-top - measured on desktop as the label
   riding high in its box (Mickey, 2026-09-01: "the texts are not centered
   properly"). Centring is the control's own property now, so it holds in any
   container. */
.chip{background:none; border:1px solid var(--rule-control); color:var(--muted); cursor:pointer; display:inline-flex; align-items:center; justify-content:center; font-family:var(--text); font-size:.6875rem; letter-spacing:var(--ls-tight); min-height:2.75rem; padding:.55rem .75rem; text-transform:uppercase}
.dot{background:var(--accent); border-radius:50%; border:2px solid var(--deep); box-shadow:0 0 0 4px rgba(184,168,143,.25); height:14px; width:14px}   /* was in: item, subject */
.empty h2{font-family:var(--display); font-size:1.375rem; font-weight:400; margin:0 0 .7rem}   /* was in: gallery, index, map */
/* .bar, .barTop, .barBottom and .filters are gone. The header was renamed to
   .masthead and the count and sort moved into the title and filter rows; no
   element on any of the five pages carries those four classes, so none of
   them could apply. .grid.is-batched went with .batch (see above). */
.search::placeholder{color:var(--faint)}   /* was in: gallery, index, map */
.search:focus{border-bottom-color:var(--accent)}   /* was in: gallery, index, map */
.share{align-items:center; background:none; border:0; color:var(--ink); cursor:pointer; display:inline-flex; font-family:var(--text); font-size:.8125rem; gap:.45rem; letter-spacing:var(--ls-label); padding:0 .9rem; text-transform:uppercase}   /* was in: item, subject */
.share:hover{background:rgba(249,249,249,.08); color:#fff}   /* was in: item, subject */
/* The search toggle, one control on every page's top row, at EVERY width
   (Mickey, 2026-08-31: "remmove search field and only add the search icon to
   the top row, next to the Share"; then 2026-09-01, page by page: "should be
   consolidated to the search, than click than open, same as on mobile" — the
   record pages, then Locations, the Map and the Gallery, so the earlier
   desktop-keeps-the-field assumption is fully overruled). Dressed as the
   .back/.share family: the same bare 44px icon square, no box. Pressing it
   reveals the .search field in the header (searchReveal() in app.js); the
   icon goes accent while open, since there is no border to carry it. */
.searchBtn{
  flex:0 0 auto;width:var(--control-h);height:var(--control-h);
  display:inline-flex;align-items:center;justify-content:center;padding:0;
  background:none;border:0;
  color:var(--ink);cursor:pointer;
}
.searchBtn:hover{background:rgba(249,249,249,.08);color:#fff}
/* Open state: the icon goes accent, since there is no border to carry it. */
.searchBtn[aria-expanded="true"]{color:var(--accent)}
.searchBtn svg{flex:0 0 auto}
/* The field itself hides behind the icon everywhere. One rule, no width gate:
   this used to live in the phone block, and every desktop page then kept a
   permanently visible field the owner asked consolidated away. */
.masthead:not(.searchOpen) .search{display:none}

/* The split-square icon's fill half: ONE icon for every right-panel toggle
   ("instead of X for all opening and closing of the right panel, trought the
   app"). The filled half reads "the panel is out"; each page's own closed
   state fades it (body.recClosed on a record, body.peekClosed on the map). */
.panelFill{transition:opacity var(--t-fast) var(--ease)}
@media (prefers-reduced-motion:reduce){ .panelFill{transition:none} }
.views a{align-items:center; border-bottom:1px solid transparent; color:var(--faint); display:inline-flex; font-size:.6875rem; letter-spacing:var(--ls-wide); min-height:2.75rem; padding:.4rem 0; text-decoration:none; text-transform:uppercase}   /* was in: gallery, index, item, map, subject */
.views a:hover{color:var(--muted)}   /* was in: item, map, subject */
.views a[aria-current="page"]{border-bottom-color:var(--accent); color:var(--ink)}   /* was in: gallery, index, item, map, subject */

/* Compact state for the sticky masthead, shared by every page that has one. */
/* ---------------------------------------------------------------- page base
   The document shell, shared. Each of the five pages carried its own copy of
   these and they had already drifted: one omitted line-height, one used a
   different background, and reading any of them told you nothing about which
   differences were deliberate. A page now overrides only what it genuinely
   needs, and says why. */
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0}
body{
  background:var(--ground);color:var(--ink);
  font-family:var(--text);font-size:16px;line-height:1.6;
  -webkit-font-smoothing:antialiased;
}

/* A page that is a fixed surface rather than a scrolling document: the Map,
   and a single record. Both were reinventing this in their own stylesheet with
   slightly different values, which is why the checker could not tell a
   deliberate difference from a drifted one. Set `class="is-fixed"` on <html>.

   height:100% with min-height:100dvh covers the webviews that report a
   zero-height document, where a vh chain collapses and leaves a map at its
   400x300 default. overscroll-behavior stops the page rubber-banding under a
   gesture meant for the surface. */
html.is-fixed, html.is-fixed body{
  height:100%;min-height:100dvh;overflow:hidden;overscroll-behavior:none;
}
/* Sticky would collapse to static with nothing to scroll, so the header floats
   over the surface instead. Where it stops on the right is the page's own
   business: full width on the Map, short of the record panel on a record. */
html.is-fixed .masthead{position:fixed;left:0}

/* The masonry grid, and its whole column ladder. `--cols` is read back by the
   batching script rather than recomputed from the viewport, so the two cannot
   disagree about how many columns there are.

   The identical ladder was written out in both gallery.html and subject.html.
   Each page still sets its own padding and max-width, because the Gallery grid
   is the page and a place page's grid sits inside a column beside the panel. */
.grid{
  --cols:2;--gutter:.75rem;
  column-count:var(--cols);column-gap:var(--gutter);
}
@media (min-width:600px){  .grid{--cols:3;--gutter:1rem} }
@media (min-width:900px){  .grid{--cols:4} }
@media (min-width:1400px){ .grid{--cols:5} }
@media (min-width:1800px){ .grid{--cols:6} }
@media (min-width:2300px){ .grid{--cols:7} }
/* The script's masonry: per-column stacks, filled shortest-first by
   createStacks() in app.js. CSS columns BALANCE, and balancing per batch is
   what tore the measured 338px holes into the Gallery; stacks cannot hold a
   gap. The plain .grid columns above remain for the pre-rendered no-JS view,
   which is one container and therefore never had the holes. */
.grid.is-stacked{column-count:auto;display:flex;align-items:flex-start;gap:var(--gutter)}
.mcol{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:.75rem}
@media (min-width:600px){ .mcol{gap:1rem} }
/* The stack's own gap is the vertical rhythm now; a margin on top of it would
   double the spacing the estimator accounts for. */
.grid.is-stacked .card{margin:0}

/* Nothing-to-show panel. */
.empty{
  max-width:34rem;margin:4rem auto;padding:2rem 1.5rem;
  border:1px solid var(--rule);background:var(--deep);
}
/* The panel's actions sit together in one row under the words: the offer
   first (accent, iconed, chevroned like the map panel's CTA), the reset
   second. showEmpty() places its Clear button into .emptyActions when the
   panel has one. */
.empty .emptyActions{display:flex;flex-wrap:wrap;gap:var(--sp-2);align-items:center;margin-top:var(--sp-3)}
.empty .emptyGo{
  display:inline-flex;align-items:center;gap:.5rem;
  min-height:var(--control-h);padding:.5rem .9rem;
  border:1px solid var(--accent);color:var(--accent);text-decoration:none;
  font-size:.6875rem;letter-spacing:var(--ls-label);text-transform:uppercase;
}
.empty .emptyGo:hover{background:var(--accent);color:var(--deep)}
.empty .emptyGo .chev{font-size:1rem;line-height:0;margin-top:-.05em}
.empty .emptyClear{
  display:inline-flex;align-items:center;min-height:var(--control-h);
  margin:0;
}

/* The quiet catalogue-failure note (catalogFailNote in app.js). It sits under
   the header, above content that is deliberately KEPT, so it must read as a
   status line rather than an error page. */
.loadNote{
  max-width:88rem;
  margin:var(--sp-4) auto 0;
  padding:.7rem 1rem;
  border:1px solid var(--rule-control);
  background:var(--deep);color:var(--muted);
  font-size:.8125rem;line-height:1.5;
}
/* The same gutters the page content uses, so the note joins the column. */
@media (max-width:899px){ .loadNote{margin-inline:var(--sp-4)} }
@media (min-width:900px){
  .loadNote{margin-inline:1.75rem;max-width:none}
}

/* Card captions may break inside a long token.

   "Petrovaradin/Pétervárad/Peterwardein" is a single 325px word as far as the
   line breaker is concerned: slashes are not a break opportunity. Whenever that
   card landed in the rightmost column it ran 85px past the viewport and gave the
   whole page a horizontal scrollbar. Multilingual place names in three
   languages joined by slashes are normal in this archive, so this is a property
   of the content rather than one bad row. */
.cap{overflow-wrap:break-word;overflow-wrap:anywhere}

/* A researched paragraph of prose, on a record or a place. */
.about{font-size:.9375rem;line-height:1.7;color:var(--muted);margin:0 0 1.35rem}

/* Sticky page header — navigation, page title, count, search, filters.
   ONE definition for every page that has one. It lived in three separate
   stylesheets with three different sets of values, which is exactly why
   Gallery, Locations and Map looked like three different headers. Anything a
   page genuinely needs to differ on it overrides by itself, with a comment
   saying why. */
.masthead{
  position:sticky;top:0;z-index:40;
  padding-block:calc(var(--safe-t) + var(--sp-3)) var(--sp-3);
  /* Opaque. Content scrolls under the whole block, and a translucent header
     lets thumbnails show through the search field and the filter pills. */
  background:var(--ground);
  box-shadow:0 8px 18px -14px rgba(0,0,0,.9);
  border-bottom:1px solid var(--rule);
}
/* One content column, via the header's own padding rather than auto margins on
   each child.

   It used to be `.masthead > *{max-width:88rem;margin-inline:auto}`, which
   centres any child NARROWER than the column. The search field is capped at
   34rem, so it sat centred in the middle of the header while the navigation,
   the title and the filters were all hard left. It read as a stray element.
   Padding the header to the column means every child starts at the same left
   edge and nothing needs a margin. */
/* The header's text must start where the CONTENT below it starts, which is the
   88rem column's edge PLUS that column's own gutter. `max(gutter, (100% -
   88rem)/2)` is not that: it gives the greater of the two rather than the sum,
   so above 1408px the header's column and the grid's cards drifted apart -
   measured 16px at 1440, 28px at 1920 and 2560 once the shorthand bug below was
   fixed, and 16/256/576px before that. */
/* Hard left on desktop ("headers on desktop, left aligned, please" — his
   ultra-wide screenshots showed the header floating at a centred column's
   edge while the Map used the whole screen). One fixed gutter, every width. */
.masthead{padding-inline:var(--sp-4)}
@media (min-width:900px){ .masthead{padding-inline:1.75rem} }
.masthead > *{width:100%}

/* Navigation, split by device — the owner's own words, 2026-09-01, second
   ruling of the day after the removal interpretation was flagged to him:
   "on desktop, main navigation should remain on top" and "main navigations
   should be at the botom on the mobile". ONE breakpoint, TWO layouts, as the
   earlier device-split ruling already said: the header's top row from 900px
   (left-aligned, active underline), the fixed bottom tab bar below it. */
.views{display:flex;justify-content:center;gap:1.25rem;margin:0}
@media (min-width:900px){
  :root{--chrome-b:calc(var(--footH) + var(--safe-b))}
  .views{justify-content:flex-start}
}
@media (max-width:899px){
  .views{
    position:fixed;left:0;right:0;bottom:0;z-index:30;
    justify-content:space-evenly;align-items:stretch;gap:0;margin:0;
    height:calc(var(--navH) + var(--safe-b));
    padding:0 var(--sp-2) var(--safe-b);
    /* Opaque enough to hold its own over map tiles and photographs; the same
       plate the footer used, rising instead of a hard edge. */
    background:linear-gradient(to top,rgba(28,28,28,.97) 0%,rgba(28,28,28,.94) 100%);
    backdrop-filter:blur(6px);
    border-top:1px solid var(--rule);
    box-shadow:0 -8px 18px -14px rgba(0,0,0,.9);
  }
  /* Each link is a full-height, evenly sized target: the bar is 52px tall so
     every one clears the 44px minimum with room to spare. */
  .views a{flex:1 1 0;justify-content:center;height:100%;min-height:0;padding:0}
  /* The footer is PAGE CONTENT on a phone (in flow at the document's end,
     revealed on scroll, clearing the fixed bar itself); pages with no flow
     to end (the Map) carry none. */
  .siteFoot{
    position:static;
    padding:var(--sp-2) var(--sp-3) calc(var(--sp-2) + var(--chrome-b));
    background:none;backdrop-filter:none;
  }
  html.is-fixed .siteFoot{display:none}
}
/* HARD LEFT on desktop, full-bleed content ("not sure why only map is
   responsive and using whole screen, and the otheres are squized in the
   center, also headers on desktop, left aligned, please" - 1 Sep, ultra-wide
   screenshots). The 88rem centred column is GONE everywhere: grids and
   headers all pad by the same fixed 1.75rem gutter, so they still align with
   each other, at the left edge, at every width. On the record page the
   header's percentage padding also resolved against the viewport rather than
   its own width, indenting its content 324px at 2000px wide - a fixed gutter
   ends that class of bug too. */
@media (min-width:900px){
  .masthead{
    padding-block:1.5rem 1rem;
    padding-inline:1.75rem;
  }
}
@media (max-width:599px){
  .masthead{padding-block:calc(var(--safe-t) + var(--sp-2)) var(--sp-3)}
}

/* Shrinking to the compact header is animated, so it is the same movement on
   every page rather than an instant jump on some of them. */
.masthead{ transition:padding var(--t-mid) var(--ease) }
.masthead .titleRow, .masthead .ctxRow, .masthead .search, .masthead .facetRow{
  transition:margin var(--t-mid) var(--ease), padding var(--t-mid) var(--ease);
}
@media (prefers-reduced-motion:reduce){
  .masthead, .masthead .titleRow, .masthead .ctxRow, .masthead .search,
  .masthead .facetRow{ transition:none }
}

/* The header's control row: filter pills, Clear, and the sort control.

   This lived only in gallery.html, so Gallery got a single scrolling row
   while the Map, using the same markup, fell back to a plain wrapping flex
   box and stacked its four pills onto three lines: a third of a phone screen
   spent on filters. Same component, same rules, wherever it appears.

   DESKTOP: the pills scroll and the sort control does not, so sort is always
   in view at the row's right end.
   MOBILE (reversed by Mickey, 2026-09-01: "should be part of the filter row,
   scrolable left and right on mobile"): below 900px the whole row is ONE
   sideways scroller - pills, Clear and sort together - with the right-edge
   fade cue on the row itself saying there is more. The earlier fear (sort
   pushed off screen with nothing to suggest it was there) is answered by the
   fade; the bottom tab bar and the icon search have also freed the height
   that once made a permanently visible sort the safer call. See the mobile
   chrome block at the end of this file. */
/* ------------------------------------------------ detail-page context row
   The structure imitates an iOS app (Mickey: "we are imitating iOS apps") -
   structure and conventions only, never the skin; the archive keeps its own
   face. A detail page (a record, a place) behaves like a pushed view
   controller under a navigation bar:

     [chevron] SECTION-NAME ............ [Share] [panel toggle]

   with the page's OWN name on the row below and its date or count underneath,
   the owning section marked in the bottom tab bar, and the big lower title
   condensing on scroll (the compact header). A section's root page needs no
   extra row: its existing title row IS the context.

   Always in flow, never fixed over the header - a fixed corner control under
   the opaque masthead is how the record's toggle was once unclickable on a
   phone. On the place page this row is also deliberately NOT a .titleRow:
   body.hasHero collapses .titleRow at rest (the hero carries the name), and
   the context row must be visible at rest or Back and Share vanish with it. */
.ctxRow{display:flex;justify-content:flex-start;gap:var(--sp-3);align-items:center;
  margin:var(--sp-5) 0 0;
  /* The page's own .titleRow nests INSIDE this row now (one markup, two
     layouts): below 900px it wraps to its own full-width line - the settled
     [chevron] SECTION ... tools / Title form - and from 900px it joins the
     row beside the chevron. row-gap 0, or the wrap adds the column gap on
     top of the title's own margins. */
  flex-wrap:wrap;row-gap:0}
.masthead.is-tight .ctxRow{margin-top:var(--sp-3)}
.ctxRow .back, .ctxRow .share, .ctxRow .tabR{flex:0 0 auto}
.ctxRow .titleRow{order:10;flex:0 0 100%;width:100%}
/* From 900px the top nav row is gone (the tab bar is the navigation) and the
   header reclaims its height (Mickey, 2026-09-01: "Remove Gallery Location
   Map top header so we can ge more vertical space, chevron for back should
   remain, and than it will appear next to the image title"). The section
   name stands down - the tab bar's active state carries the section - and
   the row reads [chevron] Title ... tools, the iOS pushed-view bar. */
@media (min-width:900px){
  .ctxRow .ctxTitle{display:none}
  .ctxRow .back{order:-2}
  .ctxRow .titleRow,
  .masthead.is-tight .ctxRow .titleRow,
  body.hasHero .masthead .ctxRow .titleRow,
  body.hasHero .masthead.is-tight .ctxRow .titleRow{
    order:-1;flex:1 1 auto;width:auto;min-width:0;margin:0;
  }
}
/* The section name, wearing exactly the treatment a ROOT page's h1 gets -
   same face, same two sizes, same 600 weight - but as a <p>: a detail page's
   one h1 is its own title (the one-h1-per-page ruling). The weight and size
   are the hierarchy ("Locations should be bolder since it is highest in the
   hierarchy" + "bolder and a bit bigger, all top level Titles"): section name
   600 at 24px, the page's own title below it 400 at 22px. Change these with
   .titleRow h1 or the root pages and this row drift apart. */
.ctxTitle{
  font-family:var(--display);font-weight:600;font-size:1.5rem;
  letter-spacing:var(--ls-display);margin:0;line-height:1.1;
}
@media (max-width:599px){ .ctxTitle{font-size:1.25rem} }
/* The page's own name under the context row: the h1 (styled by .titleRow h1)
   with the date or count as a line under it, never beside it. */
.titleBlock{flex:1 1 auto;min-width:0}
.titleBlock .count{display:block;margin-top:var(--sp-1)}
/* Back to 400 at the pre-bump size: this h1 is the DETAIL page's own name,
   one rank below the section name above it. Equal specificity with
   .titleRow h1, so these rules must stay after it. */
.titleBlock h1{font-weight:400;font-size:1.375rem}
@media (max-width:599px){ .titleBlock h1{font-size:1.125rem} }

.facetRow{
  display:flex;flex-wrap:nowrap;align-items:center;
  gap:var(--sp-1);margin:var(--sp-2) 0 0;
}
/* Every control in the row declares the same height. Not `height:100%` against
   a stretch row: that made a control's size depend on whichever sibling
   happened to be tallest, which is how .back and .share ended up 60px next to
   44px pills. align-items:center above, so nothing stretches. */
/* Every direct child of the row is the same height and holds its width. .facets
   takes the flexibility back in its own rule below, which follows this one at
   equal specificity.

   `height`, not `min-height`: a min-height lets content push one control past
   its siblings, which is exactly how .back and .share came out 60px beside 44px
   pills. Two sizing models in one row is the bug; one declared height is the
   fix. */
.facetRow > *{box-sizing:border-box;height:var(--control-h);flex:0 0 auto}
.ms-btn, .sort, .clear, .back, .share{
  box-sizing:border-box;height:var(--control-h);
  display:inline-flex;align-items:center;justify-content:center;
}
/* Icons hold their size and never absorb the row's shrinkage, matching the
   design system's [&_svg]:shrink-0. */
.back .chev, .share svg{flex:0 0 auto}
/* No `margin-left:auto` on the sort. In a nowrap flex row an auto margin
   absorbs the free space BEFORE any flex-grow is applied, so it competed with
   the pill scroller for the same slack. .facets{flex:0 1 auto} already takes
   every spare pixel, which puts sort hard against the right end anyway:
   measured at 1440 on the Gallery, scroller 1186.9px and sort at x=1222.9. */

/* The pills are the part that scrolls. NO mask, filter or transform here: any
   of the three makes this a containing block and clips the dropdown panels,
   which are position:fixed. That mistake made every filter panel invisible
   while still reporting aria-expanded=true. */
.facets{
  display:flex;flex-wrap:nowrap;gap:var(--sp-2);
  overflow-x:auto;overflow-y:visible;
  -webkit-overflow-scrolling:touch;scrollbar-width:none;
  min-width:0;flex:1 1 auto;
  /* NO top margin. .facetRow already spaces the row from the search field, and
     a second 16px here applied to the pill scroller ALONE: the row stretched to
     60px, the pills sat at its bottom edge and Back and Share, which have no
     height of their own, stretched the full 60px and so began 16px ABOVE the
     pills. Four controls on one row, two of them taller than the other two and
     offset upwards - which is what read as Back and Share sitting on top of the
     Category pill. All four now share one 44px band. */
  margin:0;
  /* A pill left half-clipped at the scroller's edge is the thing that reads as
     damage rather than as "there is more this way". Snapping means a gesture
     always leaves whole pills against the edge. No mask, filter or transform
     here to fade the edge instead: any of the three makes this a containing
     block and clips the position:fixed dropdown panels. */
  scroll-snap-type:x proximity;
  /* The right-edge fade cue, a BACKGROUND gradient and never mask-image (a
     mask creates a containing block and clips the fixed dropdown panels).
     The gradient ends at --ground, the header's own colour, so it is
     invisible over empty space and only shows where a pill overflows. On the
     phone the whole facetRow scrolls instead and carries this cue itself
     (that block sets this back to none). */
  background-image:linear-gradient(to right,
    rgba(28,28,28,0) 0%, rgba(28,28,28,.55) 45%, var(--ground) 100%);
  background-repeat:no-repeat;
  background-size:2rem 100%;
  background-position:right center;
  /* Declared, like every other control in the row, so it cannot be sized by
     whatever happens to be inside it. */
  height:var(--control-h);
}
.facets::-webkit-scrollbar{display:none}
.facets .ms{flex:0 0 auto;scroll-snap-align:start}

/* Toast: the one-line confirmation after Share. Was defined only in item.html,
   while subject.html renders the same <div class="toast"> and set its
   appearance from JavaScript with style.cssText instead, so one component had
   two definitions that had already drifted apart in position and colour.

   It also sat UNDER the header. It was pinned at `top:calc(var(--safe-t) +
   3.75rem)` with z-index 30, and the masthead is opaque, z-index 40 and 184px
   tall on a record page, so "Link copied" was painted behind it and the Share
   button appeared to do nothing. It now hangs off the header's measured height
   and sits above it. --barH is set by both pages that raise a toast; the
   fallback only matters if that measurement has not run yet. */
.toast{
  position:fixed;left:50%;transform:translate(-50%,1rem);
  top:calc(var(--barH, 8rem) + var(--sp-2));z-index:45;
  background:rgba(12,12,12,.97);border:1px solid var(--accent);
  color:var(--ink);padding:.65rem 1rem;font-size:.75rem;
  font-family:var(--text);
  letter-spacing:var(--ls-tight);text-transform:uppercase;
  opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;
}
.toast.show{opacity:1;transform:translate(-50%,0)}
@media (prefers-reduced-motion:reduce){ .toast{transition:none} }

/* Clear. Lived ONLY in gallery.html's own <style> while map.html and
   subject.html both put `<button class="clear">` in their markup, so on those
   two pages it fell all the way back to the browser default: measured on the
   Map, a 47.9x21.5px box, background #EFEFEF, 2px outset black border, Arial
   13.33px black text, sitting 11.3px above a row of 44px dark pills. A pale
   default button jammed against the next control is what reads as two things
   overlapping. Same component, one definition, every page. */
.clear{
  background:none;border:0;color:var(--muted);
  font-family:var(--text);font-size:.6875rem;letter-spacing:var(--ls-tight);
  text-transform:uppercase;padding:.5rem .6rem;cursor:pointer;
}
.clear:hover{color:var(--ink);border-color:var(--faint)}

.sort{
  appearance:none;-webkit-appearance:none;
  display:inline-flex;align-items:center;
  min-height:var(--control-h);
  padding:.5rem 1.9rem .5rem .7rem;
  background:none;border:0;color:var(--muted);
  font-family:var(--text);font-size:.6875rem;letter-spacing:var(--ls-label);
  text-transform:uppercase;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%239a9a9a'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right .65rem center;
}
.sort:hover{color:var(--ink);border-color:var(--faint)}
/* No weaker ring of its own: the global :focus-visible is a 2px accent
   outline, and a 1px --faint one made the sort the least visible focus stop in
   a header where every other control is uniform. */
.sort option{background:#0c0c0c;color:#f9f9f9}

/* The header gives back vertical space as you scroll, which is what was asked
   for: it goes from 256px to about 200px.

   The TITLE itself no longer shrinks with it. It used to drop from 22px to
   16px, and because two of the five pages cannot scroll and therefore never
   compact, the same page title was 22px on the Map and 16px on a scrolled
   Gallery at the same moment. The title is the one piece of the header a reader
   uses to know where they are, so it holds one size everywhere. The h1 was
   worth about 6px of the 57px saving; the padding is the rest of it. */
/* A page with a hero already shows its title, large, over the photograph. The
   header's copy of it therefore waits until the hero has scrolled away, then
   fades up into place. Showing both at once was two titles for one page.

   The row used to keep its full height while invisible, on the reasoning that
   the header would then not jump when the title arrived. Measured on a 390px
   place page that reasoning was backwards: the row is 24px tall with 32px of
   margin, so 56px of the header was a blank band between the navigation and the
   search field, and the header still moved 52px on compaction (256 to 204).
   Collapsed at rest the header is 200px, and the title's 40px arrives at the
   same instant the padding closes up, so compaction now moves it 4px. Less
   blank space AND a smaller step than before.

   Height is not transitioned: 0 to auto does not interpolate, and a magic pixel
   value would be wrong at the 600px breakpoint where the h1 grows. The 4px is
   below the threshold where a step reads as movement, and what the eye actually
   follows - the title fading and sliding up - is still animated.

   DECIDED 2026-08-31 (recorded in DECISIONS.md): the band stays COLLAPSED. On
   this one page the header therefore GROWS slightly on scroll (measured 164 to
   185 at 390px) while every other page shrinks (207 to 183). The alternative -
   reserving the row's height while it is invisible - is the 56px empty band on
   a phone that Mickey photographed and reported personally; the small animated
   grow is the lesser harm. Do not "fix" the direction back into sameness. */
body.hasHero .masthead .titleRow{
  opacity:0;transform:translateY(.4rem);
  height:0;margin-top:0;margin-bottom:0;overflow:hidden;
  transition:opacity var(--t-mid) var(--ease), transform var(--t-mid) var(--ease),
             margin var(--t-mid) var(--ease);
}
/* The margins are restated rather than left to `.masthead.is-tight .titleRow`.
   That rule is three classes; the collapsed rule above is three classes plus an
   element, so it outranks it, and without this line the title arrived flush
   against both the navigation and the search field. */
body.hasHero .masthead.is-tight .titleRow{
  opacity:1;transform:none;height:auto;overflow:visible;
  margin-top:var(--sp-3);margin-bottom:var(--sp-1);
}
@media (prefers-reduced-motion:reduce){
  body.hasHero .masthead .titleRow{ transition:none;transform:none }
}

.masthead.is-tight{ padding-top:calc(var(--safe-t) + var(--sp-2)); padding-bottom:var(--sp-2) }
.masthead.is-tight .titleRow{ margin:var(--sp-3) 0 var(--sp-1) }
/* The search field holds one height in both states now; see .search above. */
.masthead.is-tight .facetRow{ margin-top:var(--sp-2) }

/* ------------------------------------------- unified page components ---
   Each of these was defined in two to five templates at once, and 25 of the
   43 duplicated selectors disagreed between them. That is where the visible
   inconsistency came from. One definition each, here; consistency.py fails
   the build if a template starts redefining one. */
/* The universal box-sizing rule lives ONCE, in the page-base block above;
   this section carried a verbatim second copy. */
a{color:inherit}   /* from gallery; was also in index, item, subject */
.count{font-size:.6875rem;letter-spacing:var(--ls-label);text-transform:uppercase;color:var(--faint)}   /* from gallery; was also in index */
.card{break-inside:avoid;margin:0 0 .75rem;background:var(--deep);
  border:1px solid var(--rule);position:relative}   /* from gallery; was also in subject */
.card img{width:100%;height:auto;display:block;background:#000}   /* from gallery; was also in subject */
.card .cap{padding:.65rem .7rem .75rem}   /* from gallery; was also in subject */
/* The thumbnail link. pointer, not zoom-in: it opens the record, and zooming
   happens there on the large image. Was byte-identical in gallery and
   subject. */
.card a.thumb{display:block;width:100%;padding:0;border:0;background:#000;cursor:pointer;line-height:0}
/* .chips is gone: the chip-row filters became the .ms dropdowns and no page
   has rendered class="chips" since. */
.empty p{font-size:.9375rem;color:var(--muted);margin:0 0 .7rem}   /* from gallery; was also in index, map */
.search{/* Capped. At full width this ran 1352px on a desktop, which reads as an
     unfinished rule across the page rather than a field you type into. */
  width:100%;max-width:34rem;display:block;
  background:transparent;border:0;
  /* --rule-control, not --rule: this is a boundary you have to find in order to
     use the field, which is the case the token comment at the top describes. */
  border-bottom:1px solid var(--rule-control);
  color:var(--ink);font-family:var(--text);font-size:1.0625rem;
  /* ONE height, every page and both scroll states. It measured 43px on the
     three scrolling pages, 35px on the Map and a record because those wear the
     compact header permanently, and it shrank 43 to 35 under the reader as the
     Gallery scrolled - the same defect as the page title that was fixed by
     holding one size. It was also the only control in the header below the
     target size the rest of the interface holds to. */
  height:var(--control-h);padding:0;
  /* No `outline:none`. It removed the focus ring from the control a keyboard
     visitor reaches first, on a page where every other control has one. */
  border-radius:0}   /* from gallery; was also in index, map */
/* The native cancel glyph is drawn by the platform in system blue, the only
   blue pixel on the site. */
.search::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
/* No second .views rule here: the navigation's one definition (display, gap,
   centring, the fixed bottom bar below 900px) is in the shared block above,
   and the copy this line replaced restated a subset of it to no effect. */
.aboutHead{font-size:.6875rem;letter-spacing:var(--ls-wide);text-transform:uppercase;color:var(--faint);font-weight:400;margin:0 0 .6rem;font-family:var(--text)}   /* from subject; was also in item */
#loc{width:100%;height:12rem;border:1px solid var(--rule);background:var(--ground);margin:0 0 2rem}   /* from subject; was also in item */
.back:hover{background:rgba(249,249,249,.08);color:#fff}   /* from subject; was also in item; bare-icon wash since 2026-09-01 */

/* Back and Share on a phone: the glyph alone.
   The filter row carries the pill scroller plus Back plus Share and must stay on
   ONE row. Measured on a 390px place page: the pills want 275px, Back and Share
   take 169px of the 358px available, so the scroller got 173px and 102px of
   pills were hidden with nothing to say so. Dropping the two labels returns 82px
   of that to the pills.
   The label is CLIPPED, not display:none. .back carries no aria-label, so its
   accessible name IS the word "Back"; taking that word out of the accessibility
   tree would leave an unnamed link. Same geometry as .u-sr-only. */
@media (max-width:599px){
  /* A square of the row's own control height, the design system's `icon:
     size-9` translated to our 44px. width AND height, not padding plus a
     min-width, so the button cannot come out any other shape. Back is a
     square at every width now, so only Share collapses here. */
  .share{width:var(--control-h);padding:0}

  /* The right-edge fade cue lives on .facets in the base rule now (the
     desktop pill scroller needed it too); below 900px the facetRow block
     moves it up to the row and stands .facets down. */
  .share .btnLabel{
    position:absolute;width:1px;height:1px;
    margin:-1px;padding:0;overflow:hidden;white-space:nowrap;
    clip:rect(0 0 0 0);clip-path:inset(50%);border:0;
  }
  /* Hoisted out of gallery.html, the only page that had it, so the place and
     record pages kept the wider pills on the row with the least room of the
     five. 19px more for the scroller. */
  .ms-btn{padding:.5rem .6rem}
}
/* Large display title used INSIDE a page: the hero on a place, the heading in
   a record panel. Not an <h1>: the header already carries the page's one h1,
   and two h1 elements with the same text at two sizes is what Mickey was
   seeing. */
.displayTitle{font-family:var(--display);font-size:2rem;font-weight:400;line-height:1.12;margin:0}

/* Count badge — how many pictures sit behind one thing. Used on a Locations
   card and on a nested map pin, so a place reads the same way on both. */
/* The chip itself, and nothing about where it sits. It used to carry
   position:absolute, which is right over the corner of a Locations card and
   wrong everywhere else: inside the place page's section heading it left
   "7 images" sitting on top of the Slideshow button. Placement belongs to the
   caller. */
.n{
  font-size:var(--label-min);letter-spacing:var(--ls-label);text-transform:uppercase;
  background:rgba(12,12,12,.75);border:1px solid var(--rule);
  color:var(--ink);padding:.3rem .5rem;line-height:1;
}

/* --------------------------------------- unified chrome, was mobile-only ---
   The SEARCH ruling of 2026-08-31 started below 900px and was unified to
   every width on 2026-09-01 ("consolidated to the search, than click than
   open, same as on mobile", page by page). Navigation stays split by device —
   his redirect later the same day: "on desktop, main navigation should remain
   on top" / "main navigations should be at the botom on the mobile".

   1. THE SEARCH FIELD IS AN ICON, everywhere. His words: "remmove search
      field and only add the search icon to the top row, next to the Share,
      when user presses on it, the search filed should appear onderneth the
      Gallery title, pushing content down, and opening the keyboard." The
      field stays where it always was in the header's flow, so revealing it
      PUSHES the filter row and the content down rather than overlaying
      anything; searchReveal() in app.js owns the state. The field-hiding rule
      lives with .searchBtn above.

   2. THE ACTIVE TAB GETS A FAT LINE. His words: "highlight the location in
      hiearchy by making the higlighted text lighter, and add a fat line to the
      bottom of the page belove the selected expressing it even more." The
      active tab's text is already the brightest thing in the bar (--ink over
      --faint); the 4px accent bar sits at the very bottom edge of the tab, as
      wide as the tab. The bar's own safe-area padding ends exactly there, so
      on an inset phone the line sits directly above the home indicator and on
      everything else it IS the bottom edge of the screen. */
/* The title row gains a 44px control, so it centres instead of sitting on
   a baseline; the count keeps to the right end, against the icon. Direct
   child only: a detail page's count lives inside .titleBlock and stays a
   line under its title. */
.titleRow{align-items:center}
.titleRow > .count{margin-left:auto}

/* On a detail page the icon leads the right-hand group, so it takes the
   auto margin from Share. */
.ctxRow .searchBtn{margin-left:auto}
.ctxRow .share{margin-left:0}

@media (max-width:899px){
  /* 2b. The fat active line belongs to the BOTTOM tab bar alone: on desktop
     the navigation is back at the top (his redirect, later the same day) and
     keeps its 1px underline. */
  .views a{position:relative}
  .views a[aria-current="page"]::after{
    content:'';position:absolute;left:0;right:0;bottom:0;height:4px;
    background:var(--accent);
  }

  /* 3. ONE scrolling filter row (Mickey, 2026-09-01: sort "should be part of
     the filter row, scrolable left and right on mobile"). The ROW is the
     scroller now: pills, Clear and the sort control ride together, and the
     right-edge fade cue moves up from the pill scroller to the row itself so
     a control resting against the edge still reads as "there is more this
     way". .facets stands down as a scroller and lays its pills out at full
     content width inside the row. Desktop is untouched: sort stays pinned at
     the row's right end, always in view.
     The filter dropdown panels are position:fixed and placed against the
     viewport, so a scrolling ancestor cannot clip them - that is the same
     reasoning the pill scroller relied on. */
  .facetRow{
    overflow-x:auto;overflow-y:visible;
    -webkit-overflow-scrolling:touch;scrollbar-width:none;
    scroll-snap-type:x proximity;
    background-image:linear-gradient(to right,
      rgba(28,28,28,0) 0%, rgba(28,28,28,.55) 45%, var(--ground) 100%);
    background-repeat:no-repeat;
    background-size:2rem 100%;
    background-position:right center;
  }
  .facetRow::-webkit-scrollbar{display:none}
  .facetRow > *{scroll-snap-align:start}
  .facets{
    overflow-x:visible;flex:0 0 auto;
    background-image:none;scroll-snap-type:none;
  }
}
@media (max-width:599px){
  /* Four 44px controls plus the section name must hold one row at 320px:
     since the title bump to Cinzel 600/20px, that needs 4px gaps ("Gallery"
     alone is ~86px, and 44x4 controls + gaps must leave it that much). The
     icons are bare boxes now, so their own internal whitespace carries the
     separation; the row reads airier than the number suggests. */
  .ctxRow{gap:var(--sp-1)}
  /* And the section name can never push a scrollbar: it gives way before the
     controls do. */
  .ctxTitle{min-width:0;flex:0 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
}
