/* ==========================================================================
   find.london — prototype stylesheet
   Direction: "The Quiet Guide". Warm paper, editorial serif, desaturated map,
   colour reserved almost entirely for the pins.
   ========================================================================== */

/* --- 1. Tokens ----------------------------------------------------------- */
:root {
  --paper:      #FBF9F5;
  --paper-2:    #F4F0E8;
  --ink:        #17150F;
  --ink-2:      #46422F;
  --muted:      #67624F;
  --rule:       #E4DED1;
  --rule-2:     #D6CEBC;
  --accent:     #AE3F26;
  --accent-ink: #FFFFFF;
  /* The second homepage group. Deliberately not --ok, which means "open now"
     wherever else it appears: this is an olive, not the signal green. */
  --accent-2:   #55693A;
  /* The third homepage group, Themes. A plum: the palette had spent its
     terracotta, its olive and - on Done - its blue, and this is the hue
     left that is neither a signal nor a warning. */
  --accent-3:   #6B4B7E;
  --shadow:     0 -1px 0 var(--rule), 0 -14px 40px -12px rgba(23,21,15,.18);
  --card-hover: #F3EFE6;
  --selected:   #F6E4DC;
  --seg-on:     var(--raised);
  --seg-on-lift: 0 1px 3px rgba(0,0,0,.14);
  /* Done. It was --ink, which on a map is a black dot: the colour of a
     closure, not of something you went to and enjoyed. Blue is the one hue
     this warm palette had not spent - green is Want to go and terracotta is
     Loved, and those two are the ones it must never be mistaken for - and it
     is the oldest convention on the web for somewhere you have already been.
     The tick inside the pin carries the meaning now, so the colour only has
     to be unmistakable and not sad. */
  --done:       #2B5C9B;
  --ok:         #23694A;
  --ok-tint:    #E7F2EB;                 /* today's row when the place is open */
  --event:      #1F6473;                 /* pins and chip for Events */
  --perform:    #8A6A16;                 /* pins and dot for the performing Shows */
  /* The third of the three-way split. A plum, because the other three hues in
     use are all warm or green-blue - terracotta, olive, the signal green - and
     the teal beside it; a mustard would have gone muddy next to the
     terracotta at pin size. */
  --exhibit:    #6B4C72;                 /* pins and chip for Exhibitions */
  --selected-line: var(--accent);

  /* map style palette (consumed by mapstyle.js) */
  --map-land:   #F4F0E7;
  --map-water:  #DCE4E3;
  --map-green:  #E7EADC;
  --map-road:   #FFFFFF;
  --map-road-2: #EDE7DA;
  --map-build:  #EBE5D8;
  --map-label:  #8C8676;
  --map-halo:   #FBF9F5;
  /* An area on /areas that has not been marked. A warm stone rather
     than the accent, which on that map now means Loved and nothing
     else - drawn in the accent, an unmarked area and a loved one were
     the same wash at two opacities. */
  --map-area:   #6F6857;

  /* sheet geometry — see finder.js for the matching arithmetic */
  /* --vh is the sheet scroller's real height in px, written by finder.js.
     svh is only the pre-JS fallback: on iOS the fixed shell tracks the visual
     viewport as the URL bar shows and hides, so svh and the actual height
     disagree and the detents stop lining up with the scroll range. */
  --vh: 100svh;
  --peek:   calc(var(--vh) * .27);   /* peek = vh - (a+b+c) */
  --snap-a: calc(var(--vh) * .28);   /* full detent == max scroll */
  --snap-b: calc(var(--vh) * .37);
  --snap-c: calc(var(--vh) * .08);
  --sheet-h: calc(var(--vh) * .92);

  --sheet-progress: 0;      /* 0 at peek → 1 at full, written by JS */

  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  /* raised surfaces: a dialog sits ON the page, so it must not be the page's
     own colour. It was --paper over a --paper body with no border and no
     shadow, which in dark mode is literally invisible. */
  --raised:     #FFFFFF;
  --scrim:      rgba(12, 10, 8, .55);
  /* The reading measure for document pages. The masthead above the text uses
     it too: they were 640px and 34rem respectively, so the pill sat 48px left
     of the heading it was supposed to line up with. */
  --measure: 34rem;
  --r: 14px;
  --list-pad: 16px;      /* list gutter; the selected row's negative margins
                            mirror this, so keep the two in step */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:      #151412;
    --paper-2:    #1F1D19;
    --ink:        #DBD5C8;   /* 12.6:1, not 16:1 — near-white on near-black
                                haloes, which reads as harder to focus on */
    --ink-2:      #B9B3A4;
    --muted:      #9C9686;   /* lifted so it clears 4.5:1 on the selected row */
    --rule:       #37332C;
    --rule-2:     #5E584D;
    --accent:     #E88A6C;
    --accent-ink: #1A0E09;
    --accent-2:   #A9BE7E;
    --accent-3:   #C4A9DE;
    --shadow:     0 -1px 0 var(--rule), 0 -14px 40px -12px rgba(0,0,0,.6);
    --card-hover: #232019;
    --selected:   #40281F;
    --seg-on:     #3A362D;
    --seg-on-lift: inset 0 0 0 1px #524C40;
    --done:       #8FB3E0;
    --ok:         #7FD3A2;
    --ok-tint:    #1B2620;
    --event:      #6BBBD1;
    --perform:    #DDB74B;
    --exhibit:    #C2A0CC;
    --selected-line: var(--accent);

    --map-land:   #16150F;
    --map-water:  #0F1E26;
    --map-green:  #17211A;
    --map-road:   #3B372E;
    --map-road-2: #262319;
    --map-build:  #1F1D17;
    --map-label:  #98917F;
    --map-halo:   #16150F;
    --map-area:   #8E8878;

    --raised:     #22201B;   /* lifted off the #151412 ground */
    --scrim:      rgba(0, 0, 0, .62);
  }
}

/* The same palette again, for a reader who has chosen dark explicitly.
   The block above only fires when the SYSTEM is dark, so without this one
   the Dark setting set the attribute and changed nothing on a light machine.
   Kept adjacent so the two cannot drift apart. */
:root[data-theme="dark"] {
    --paper:      #151412;
    --paper-2:    #1F1D19;
    --ink:        #DBD5C8;   /* 12.6:1, not 16:1 — near-white on near-black
                                haloes, which reads as harder to focus on */
    --ink-2:      #B9B3A4;
    --muted:      #9C9686;   /* lifted so it clears 4.5:1 on the selected row */
    --rule:       #37332C;
    --rule-2:     #5E584D;
    --accent:     #E88A6C;
    --accent-ink: #1A0E09;
    --accent-2:   #A9BE7E;
    --accent-3:   #C4A9DE;
    --shadow:     0 -1px 0 var(--rule), 0 -14px 40px -12px rgba(0,0,0,.6);
    --card-hover: #232019;
    --selected:   #40281F;
    --seg-on:     #3A362D;
    --seg-on-lift: inset 0 0 0 1px #524C40;
    --ok:         #7FD3A2;
    --ok-tint:    #1B2620;
    --event:      #6BBBD1;
    --perform:    #DDB74B;
    --exhibit:    #C2A0CC;
    --selected-line: var(--accent);

    --map-land:   #16150F;
    --map-water:  #0F1E26;
    --map-green:  #17211A;
    --map-road:   #3B372E;
    --map-road-2: #262319;
    --map-build:  #1F1D17;
    --map-label:  #98917F;
    --map-halo:   #16150F;
    --map-area:   #8E8878;

    --raised:     #22201B;   /* lifted off the #151412 ground */
    --scrim:      rgba(0, 0, 0, .62);
}


/* --- 2. Reset & mobile hardening ---------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
/* `hidden` means hidden, whatever a class says.
   The attribute is only a UA rule - `[hidden] { display: none }` - and ANY
   author rule beats it, so an element with a class that sets `display`
   ignores it entirely. This file had patched that one class at a time, eight
   times, each after somebody noticed: the ninth was .detail-head, which is
   display:flex, which is why /nearby carried a back button with no word in it
   whenever there was nowhere to go back to (the user, 11 September 2026, on
   iOS). One rule instead, so the tenth cannot happen.
   Safe as !important because nothing on this site sets display inline; the
   code hides things with el.hidden, which is what this is for. */
[hidden] { display: none !important; }
html {
  background: var(--paper);          /* stops white flash on overscroll */
  -webkit-text-size-adjust: 100%;
  /* No pull-to-refresh, anywhere. Drag down at the top of a page that scrolls
     - the homepage, About, Privacy, Contact, a guide, a long read - and the
     browser reloads it, which is a page you were reading thrown away for a
     gesture you did not mean (the user, 10 September 2026: stop it under all
     circumstances).

     The map pages never had it, because their body is pinned to the viewport
     and has nothing to overscroll; this is the same answer for the nine that
     do scroll.

     `none` rather than `contain` because the ask was that it stop, and
     `contain` is only reliably enough on Chrome. It costs the rubber-band at
     the top and bottom of a document, which is the price of the gesture going
     away; `contain` here is the one word back if that is missed.

     The y axis only, so a sideways swipe still goes back a page and the
     strips that scroll across are untouched. Nothing replaces the gesture in
     the installed app - there is no address bar there to reload from - and
     nothing needs to: a navigation goes straight to the network and only
     falls back to the cache when the fetch fails, so any page you land on is
     the current one. Force quit and reopen is the way out of a page held open
     across a deploy. */
  overscroll-behavior-y: none;
}
body {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}
button, input, select { font: inherit; color: inherit; }
button {
  background: none; border: 0; padding: 0; cursor: pointer;
  -webkit-tap-highlight-color: transparent;      /* no grey flash */
  touch-action: manipulation;                    /* no 300ms zoom delay */
  user-select: none; -webkit-user-select: none;
}
a { color: inherit; -webkit-tap-highlight-color: transparent; }
/* 16px or iOS zooms the page when the field is focused - and on this site it
   cannot be zoomed back, because the app shell is position:fixed and there is
   nothing to pinch out of. The feedback box set its own 14.5px and did
   exactly that: focus it, and the page came back bigger and shifted, with no
   way to undo it (the user, 11 September 2026, on /nearby).
   `textarea` as well as `input`, which is what this rule had always meant. */
input, textarea { font-size: 16px; }
img { max-width: 100%; display: block; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

.u-visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* --- 3. App shell (the /coffee finder) ---------------------------------- */
body.is-app {
  position: fixed; inset: 0;         /* no document scroll → map owns the gesture */
  overscroll-behavior: none;
}
.shell { position: fixed; inset: 0; }

.map { position: absolute; inset: 0; background: var(--map-land); }
.maplibregl-ctrl-attrib.maplibregl-compact {
  font-size: 10px; background: color-mix(in srgb, var(--paper) 80%, transparent);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border-radius: 999px; color: var(--muted);
}
.maplibregl-ctrl-attrib a { color: var(--muted); }
.maplibregl-ctrl-attrib-button { background-color: var(--rule-2) !important; }
.maplibregl-ctrl-bottom-right,
.maplibregl-ctrl-bottom-left { margin-bottom: calc(var(--peek) - 6px); opacity: .85; }

/* ---- the first-visit question ------------------------------------------
   A card between the strapline and the tiles, on the homepage only, asking
   who the reader is (the user, 10 September 2026). It is a question, not a
   gate: it never covers the page, the tiles are reachable past it, and
   Dismiss puts it away. Server-rendered hidden, so revealing it costs no
   layout shift on a first visit and nothing at all on a later one. */
.welcome {
  position: relative;
  margin: 22px 0 4px; padding: 16px 16px 14px;
  border: 1px solid var(--rule); border-radius: 14px;
  background: var(--paper-2);
}
/* The cross, which is how a box like this is usually shut (the user, 10
   September 2026). It does what Dismiss does, so it carries the same
   attribute and the same handler; Dismiss stays, because a word is clearer
   than a symbol to anybody who has not met the convention. */
.welcome-x {
  position: absolute; top: 8px; right: 8px;
  display: grid; place-items: center; width: 34px; height: 34px;
  border: 0; border-radius: 999px; background: none; color: var(--muted);
  cursor: pointer;
}
.welcome-x svg { width: 16px; height: 16px; }
.welcome-x:hover { background: var(--card-hover); color: var(--ink); }
.welcome-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Room for the cross at the end of the heading, so a long line never runs
   under it. */
.welcome-h {
  margin: 0 0 12px; padding-right: 34px;
  font-size: 15px; font-weight: 600; line-height: 1.35;
  color: var(--ink); text-wrap: balance;
}
.welcome-q { margin: 16px 0 8px; font-size: 13.5px; color: var(--muted); }
.welcome-opts { display: flex; flex-wrap: wrap; gap: 8px; }
/* The follow-up answers are shorter words than the three that open the card,
   so they take a tighter chip and the rows stay even. */
.welcome-opts.is-tight { gap: 6px; }
.welcome-opt {
  flex: none; min-height: 38px; padding: 0 14px;
  border: 1px solid var(--rule-2); border-radius: 999px;
  background: var(--paper); color: var(--ink-2);
  font: inherit; font-size: 13.5px; line-height: 1.2; text-align: left;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.welcome-opts.is-tight .welcome-opt { min-height: 34px; padding: 0 12px; font-size: 13px; }
@media (hover: hover) {
  .welcome-opt:hover { border-color: var(--ink-2); color: var(--ink); }
}
/* Chosen: the same fill a chosen chip takes everywhere else on the site. */
.welcome-opt[aria-pressed="true"] {
  background: var(--selected); border-color: var(--accent); color: var(--ink);
  font-weight: 500;
}
.welcome-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.welcome-foot { display: flex; align-items: center; margin-top: 18px; }
/* Save alone: the cross at the top right is the way past, and a word for it
   as well was clutter (the user, 10 September 2026). Shaped like the contact
   form's Send - the same height and the same corner - so it reads as a
   button rather than as one more chip among the answers. */
.welcome-foot .btn-pri {
  flex: 0 0 auto; min-height: 44px; padding: 0 22px; border-radius: 10px;
  font: inherit; font-size: 14.5px; font-weight: 500; cursor: pointer;
}
.welcome-foot .btn-pri:disabled { opacity: .45; cursor: default; }
/* The word after Save, in the space the card occupied, so the answer lands
   somewhere rather than the card vanishing under the reader's finger. */
.welcome.is-done {
  margin: 22px 0 4px; padding: 16px; border: 1px solid var(--rule);
  border-radius: 14px; background: var(--paper-2);
  font-size: 15px; font-weight: 600; color: var(--ink);
}

/* dim veil over the map as the sheet rises */
.map-dim {
  position: absolute; inset: 0; pointer-events: none;
  background: var(--paper);
  opacity: calc(max(var(--sheet-progress) - 0.35, 0) * 0.72);
}

/* --- 3a. Top bar (display only — never put controls out of thumb reach) -- */
.topbar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  display: flex; align-items: center; gap: 10px;
  padding: calc(env(safe-area-inset-top) + 10px) 16px 10px;
  pointer-events: none;
}
.topbar > * { pointer-events: auto; }
.wordmark {
  font-family: var(--font-display);
  font-size: 17px; font-weight: 500; letter-spacing: -.01em;
  padding: 7px 12px; border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  border: 1px solid var(--rule);
  text-decoration: none;
}
.wordmark b { font-weight: 500; color: var(--accent); }

/* The menu shares the wordmark's bubble rather than sitting in its own. Two
   pills side by side read as two unrelated controls; one pill with a rule down
   the middle reads as the site's own furniture, which is what it is. */
.brand {
  display: flex; align-items: center;
  border-radius: 999px; border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
}
.brand .wordmark {
  background: none; border: 0; backdrop-filter: none; -webkit-backdrop-filter: none;
  padding: 7px 13px 7px 10px;
}
.nav-btn {
  width: 40px; height: 36px; display: grid; place-items: center;
  border: 0; background: none; border-radius: 999px 0 0 999px;
  border-right: 1px solid var(--rule);
  color: var(--ink); cursor: pointer; padding: 0;
}
.nav-btn svg { width: 19px; height: 19px; }
.nav-btn:active { background: var(--card-hover); }
.nav-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }


/* --- 4. The bottom sheet ------------------------------------------------- */
/* The sheet is a native scroll container with snap points. The drag is real
   scrolling, so it runs on the compositor and stays smooth while JS is busy.
   pointer-events:none on the scroller lets touches fall through to the map;
   only the sheet panel itself catches them. */
.sheet-scroll {
  position: absolute; inset: 0; z-index: 4;
  overflow-y: scroll; overflow-x: hidden;
  /* Snapping is done in JS on gesture end. CSS scroll-snap fought the explicit
     drag below, and mandatory snapping cannot be suspended mid-gesture. */
  scroll-snap-type: none;
  overscroll-behavior: contain;
  scrollbar-width: none;
  pointer-events: none;
}
.sheet-scroll::-webkit-scrollbar { display: none; }
.snap { pointer-events: none; }
.snap-a { height: var(--snap-a); }
.snap-b { height: var(--snap-b); }
.snap-c { height: var(--snap-c); }

.sheet {
  pointer-events: auto;
  position: relative;
  height: var(--sheet-h);
  display: flex; flex-direction: column;
  background: var(--paper);
  border-radius: var(--r) var(--r) 0 0;
  box-shadow: var(--shadow);
}

.sheet-head { padding: 0 16px 10px; flex: none; }
/* A category with hide_blurb prints no line under the heading - and no gap:
   the peek detent's line-clamp rule is more specific than a bare :empty. */
.sheet-sub:empty, #sheetScroll[data-detent] .sheet-sub:empty { display: none; margin: 0; }
/* The header is the sheet's handle: it takes the gesture rather than leaving it
   to scroll-chaining, which is the part that differs between engines. The chips
   keep their own horizontal scrolling. */
.grabber, .sheet-title, .sheet-sub, .detail-head { touch-action: none; }
.grabber {
  width: 100%; display: grid; place-items: center;
  padding: 8px 0 10px;
}
.grabber::before {
  content: ""; width: 38px; height: 4px; border-radius: 999px;
  background: var(--rule-2);
}
/* The line over a Category page's heading - WHAT'S ON over Music - that
   names the whole and is the way back to it, set like the kicker over an
   essay. */
.sheet-eyebrow {
  margin: 0 0 3px; font-family: var(--font-ui); font-size: 11.5px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}
.sheet-eyebrow a { color: inherit; text-decoration: none; }
.sheet-eyebrow a:hover { color: var(--ink); text-decoration: underline; }
.sheet-title {
  display: flex; align-items: baseline; gap: 9px;
  margin: 0 0 2px;
}
.sheet-title h1 {
  font-family: var(--font-display);
  font-size: 26px; font-weight: 500; letter-spacing: -.02em;
  margin: 0; line-height: 1.1;
}
.count {
  font-size: 13px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.sheet-sub {
  margin: 0 0 12px; font-size: 13px; line-height: 1.45; color: var(--muted);
  overflow: hidden;
}
/* The optional line under a category blurb that points elsewhere. */
.see-also { margin: 4px 0 0; font-size: 13px; color: var(--muted); }
.see-also a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
/* Clamped only while the sheet is down. The peek has to keep the filter chips
   in the thumb zone, and on a small phone a third line here costs a chip row -
   but clamping at every height meant the end of a long intro was simply cut
   off and never shown to anyone on a phone. Lift the sheet and there is room
   for it. */
#sheetScroll[data-detent="peek"] .sheet-sub {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* filter chips — the primary control, kept in the thumb zone at peek */
/* Wraps, never scrolls. A chip past the right edge has no affordance saying
   so, and bakeries hid three of its six that way - Patisserie included. */
.chips.is-kinds { margin-top: 8px; }
.chips {
  display: flex; flex-wrap: wrap; gap: 7px; row-gap: 7px;
  margin: 0 -16px; padding: 2px 16px 4px;
}
.chip {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  min-height: 36px; padding: 0 13px;
  border: 1px solid var(--rule-2); border-radius: 999px;
  font-size: 13.5px; white-space: nowrap; color: var(--ink-2);
  background: var(--paper);
  transition: background .15s, color .15s, border-color .15s,
              transform .15s, box-shadow .15s;
}
.chip:active { background: var(--card-hover); }
.chip[aria-pressed="true"] {
  background: var(--ink); color: var(--paper); border-color: var(--ink);
}
.chip svg { width: 14px; height: 14px; }
.chip.is-locate[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
/* Events and Experiences split the map by pin colour - teal for a run with an
   end date, the category's terracotta for the rest - so the chips carry a dot
   in the same colour and turn that colour when pressed. The chip is the
   legend; there is no other place the two colours are explained. */
.chip.is-event, .chip.is-experience, .chip.is-exhibition { --chip-hue: var(--accent); }
.chip.is-event { --chip-hue: var(--event); }
.chip.is-perform { --chip-hue: var(--perform); }
.chip.is-exhibition { --chip-hue: var(--exhibit); }
.chip.is-event::before, .chip.is-experience::before, .chip.is-exhibition::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--chip-hue);
}
.chip.is-event[aria-pressed="true"], .chip.is-experience[aria-pressed="true"],
.chip.is-exhibition[aria-pressed="true"] {
  background: var(--chip-hue); border-color: var(--chip-hue); color: var(--accent-ink);
}
.chip.is-event[aria-pressed="true"]::before,
.chip.is-experience[aria-pressed="true"]::before,
.chip.is-exhibition[aria-pressed="true"]::before {
  background: currentColor; opacity: .75;
}

/* list — the inner scroller. Default overscroll chaining is deliberate:
   scrolled to the top and still dragging down lowers the sheet. */
.sheet-body {
  flex: 1; min-height: 0; position: relative;   /* offsetTop origin for revealCard */
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 0 var(--list-pad) calc(env(safe-area-inset-bottom) + 20px);
  border-top: 1px solid var(--rule);
}

/* The wait before a category is drawn. Card-shaped, because that is what is
   coming and a skeleton the shape of the answer is the one that does not
   move anything when the answer arrives. Held back a quarter of a second so
   a category the worker already has never shows it at all; see build.py's
   LOADING_ROWS for why it exists. */
.load-rows { opacity: 0; animation: load-in .2s ease .26s forwards; }
.load-row {
  display: flex; flex-direction: column; gap: 9px;
  padding: 18px 0 19px; border-bottom: 1px solid var(--rule);
}
.load-bar {
  display: block; height: 11px; border-radius: 3px; background: var(--rule);
  animation: load-pulse 1.5s ease-in-out infinite;
}
.load-a { width: 62%; }
.load-b { width: 34%; height: 9px; opacity: .7; animation-delay: .2s; }
/* Down the list, so it reads as one thing breathing rather than seven. */
.load-row:nth-child(2) .load-a { width: 78%; }
.load-row:nth-child(3) .load-a { width: 54%; }
.load-row:nth-child(4) .load-a { width: 70%; }
.load-row:nth-child(5) .load-a { width: 46%; }
.load-row:nth-child(6) .load-a { width: 66%; }
.load-row:nth-child(7) .load-a { width: 58%; }
.load-row:nth-child(n+2) .load-bar { animation-delay: .1s; }
.load-row:nth-child(n+4) .load-bar { animation-delay: .2s; }
@keyframes load-in { to { opacity: 1; } }
@keyframes load-pulse { 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) {
  .load-rows { animation: none; opacity: 1; }
  .load-bar { animation: none; }
}

/* --- 5. Place cards ------------------------------------------------------ */
.card {
  position: relative;                      /* anchor for the stretched link */
  display: grid; grid-template-columns: 1fr auto; gap: 4px 12px;
  width: 100%; text-align: left;
  padding: 14px 0; border-bottom: 1px solid var(--rule);
  scroll-margin: 12px;
  transition: background .18s, opacity .18s;
}
.card:last-child { border-bottom: 0; }
/* A shopping district's card: its zone's colour down the left edge and a
   wash of it behind, so it does not read as one more shop. */
.card.is-district {
  --zc: var(--accent);
  padding-left: 14px; border-left: 3px solid var(--zc);
  background: color-mix(in srgb, var(--zc) 9%, transparent);
}
.card.is-district:hover { background: color-mix(in srgb, var(--zc) 14%, transparent); }
.card-clue-district { color: var(--zc); font-weight: 500; }
/* The area list's picture: full width of the card, cropped to 3:2, on the
   area pages only (areas.js adds it, nothing else does). */
.card-pic {
  grid-column: 1 / -1; display: block; width: 100%; height: auto;
  aspect-ratio: 3 / 2; object-fit: cover; border-radius: 6px;
  margin-bottom: 4px; background: var(--card-hover);
}
/* ...and that rule stopped firing the day .list-foot was appended after the
   cards: the last card is no longer the last child, so it kept its rule and
   the footer drew a second one six pixels below. Where .list-more sits between
   them the card's rule is still wanted, which is why this matches the footer
   specifically rather than "not last". */
.card:has(+ .list-foot) { border-bottom: 0; }
/* And the tap itself, which is the same light for a moment and was the same
   shape wrong. Scoped to the sheet so the station picker's buttons are
   untouched, and the area chooser's grid keeps colour without geometry, for
   the reason the hover rule gives: widening a cell in a two-column grid moves
   the cell, the column and the page under the finger. */
.card:active { background: var(--card-hover); }
.sheet-body .card:active {
  width: auto;
  margin-left: calc(-1 * var(--list-pad)); margin-right: calc(-1 * var(--list-pad));
  padding-left: var(--list-pad); padding-right: var(--list-pad);
}
.sheet-body .nearby-start .area-grid .card:active {
  width: 100%; margin: 0; padding: 12px 14px;
}
.card.is-selected {
  background: var(--selected);
  box-shadow: inset 4px 0 0 var(--selected-line);
  /* Full-bleed across the list column. `width: auto` matters: .card still
     carries width:100% for the station-picker buttons, and with that set the
     negative margins shift the card without stretching it, which left the
     highlight short of the right-hand edge. */
  width: auto;
  margin-left: calc(-1 * var(--list-pad)); margin-right: calc(-1 * var(--list-pad));
  padding-left: var(--list-pad); padding-right: var(--list-pad);
  border-bottom-color: transparent;
}
.card.is-selected .card-name { color: var(--ink); }
.card.is-selected .card-meta,
.card.is-selected .card-verdict { color: var(--ink-2); }
.card-name {
  grid-column: 1; font-family: var(--font-display);
  font-size: 18px; font-weight: 500; letter-spacing: -.01em; line-height: 1.2;
  margin: 0;
}
.card-meta {
  grid-column: 1; font-size: 12.5px; color: var(--muted);
  display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: center;
}
/* The exhibitions on now under a venue's line on the card, each with its
   closing date, dotted in the accent so the eye finds them. */
.card-shows { list-style: none; margin: 6px 0 0; padding: 0; font-size: 13px; line-height: 1.45; color: var(--ink-2); }
.card-shows li { position: relative; padding-left: 13px; }
.card-shows li::before {
  content: ""; position: absolute; left: 1px; top: .6em; width: 5px; height: 5px;
  border-radius: 50%; background: var(--accent);
}
.card-shows .card-shows-more::before { background: var(--rule-2); }
.card-show-to, .card-shows-more { color: var(--muted); }
.card-verdict {
  grid-column: 1 / -1; font-size: 14px; line-height: 1.4; color: var(--ink-2);
  margin: 2px 0 0;
}
/* Stretched link: the whole card is the target. */
.card-link { text-decoration: none; color: inherit; }
.card-link::after { content: ""; position: absolute; inset: 0; z-index: 1; }

/* Disclosure chevron — the row has to look like it opens. */
.card-go {
  grid-column: 2; grid-row: 1 / span 2; align-self: center;
  display: grid; place-items: center; color: var(--rule-2);
  transition: color .15s, transform .15s;
}
.card-go svg { width: 16px; height: 16px; }
.card.is-selected .card-go { color: var(--accent); transform: translateX(2px); }
@media (hover: hover) {
  .sheet-body .card:hover .card-go { color: var(--ink-2); transform: translateX(2px); }
}
.dot { width: 3px; height: 3px; border-radius: 50%; background: var(--rule-2); flex: none; }
.tag {
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); border: 1px solid var(--rule); border-radius: 4px;
  padding: 1px 5px;
}
.open-now { color: var(--ok); }
/* Dates on a card. "Ending soon" takes the accent and a little weight; the
   plain "Until 27 Sept" stays quiet, because it is information rather than a
   warning. */
.runs { color: var(--muted); }
.runs-ending { color: var(--accent); font-weight: 500; }
.runs-closing { color: var(--accent); }
/* Saved page: how close a saved event is to closing. Filled inside a week,
   outlined inside a month; the plain muted line beyond that. */
.runs-urgent, .runs-soon {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 12px; font-weight: 500; line-height: 1.5;
}
.runs-urgent { background: var(--accent); color: var(--accent-ink); }
.runs-soon { border: 1px solid var(--accent); color: var(--accent); }
.closed { color: var(--muted); }

.empty {
  padding: 40px 8px; text-align: center; color: var(--muted); font-size: 14px;
}
.empty button { text-decoration: underline; color: var(--ink); }
/* /nearby/ before it knows where you are: the ask, then every area as a
   card to start from instead. The button is a real button, not the
   underlined word the other empty states use, because it is the page's one
   action rather than an aside. */
.nearby-start { text-align: left; }
.nearby-start .nearby-why { margin: 0 0 12px; }
.nearby-start .btn-locate {
  display: inline-flex; align-items: center; gap: 8px; margin: 0 0 18px;
  padding: 10px 16px; border: 0; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font: inherit; font-weight: 600; text-decoration: none; cursor: pointer;
}
.nearby-start .btn-locate svg { width: 18px; height: 18px; }
.nearby-start .nearby-or { margin: 0 0 8px; color: var(--muted); }
/* The areas to start from. They are cards in a two-column grid rather than
   rows in a list, so they carry their own edge and their own corner instead of
   the hairline a stacked row draws under itself - and nothing about them may
   change size when a pointer arrives (the user, on desktop, 10 September
   2026). */
.nearby-start .area-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
}
.nearby-start .area-grid .card {
  display: block; margin: 0; padding: 12px 14px;
  border: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  border-radius: 10px; background: var(--paper-2);
  text-align: left; font: inherit; color: inherit;
  text-decoration: none; cursor: pointer;
  transition: background .15s, border-color .15s;
}
.nearby-start .area-grid .card-name { font-size: 15px; margin: 0; }
.card-meta .walk { font-weight: 600; color: var(--ink); }
/* A guide's listings: a row per listing named under a heading, every row the
   same shape - the photograph, or the category's icon where there is none,
   in a square at the left; the name, the row's link with the out-mark after
   it, across the top; the line that says what and where at the bottom, with
   the four save buttons at its right, so the reader can act where the guide
   names the thing. In a narrow column the buttons drop under the name as a
   row of their own. The list is a container, so the switch follows the
   column's width, not the screen's (the user, 10 September 2026: the rows
   looked messy). */
.guide-listings { display: grid; gap: 6px; margin: 10px 0 20px; container-type: inline-size; }
.guide-listing {
  display: grid; grid-template-columns: 52px minmax(0, 1fr) auto;
  grid-template-areas: "pic name name" "pic meta acts"; align-items: center; gap: 4px 12px;
  padding: 8px 10px 8px 8px; border: 1px solid var(--rule); border-radius: 10px;
  background: var(--paper-2);
}
.guide-listing-pic {
  grid-area: pic; display: block; width: 52px; height: 52px; border-radius: 8px;
  overflow: hidden; background: color-mix(in srgb, var(--accent) 10%, var(--paper));
}
.guide-listing-pic img { display: block; width: 100%; height: 100%; object-fit: cover; }
.guide-listing-pic.is-icon { display: grid; place-items: center; color: var(--accent); }
.guide-listing-pic.is-icon .cat-i { width: 26px; height: 26px; }
/* The name is the row's link, set like a name rather than an underlined
   phrase in the prose; the guide's own link rule is written to lose to it. */
.guide-body .guide-listing a.guide-listing-name {
  grid-area: name; align-self: end; min-width: 0;
  font-weight: 600; line-height: 1.3; color: var(--ink); text-decoration: none;
}
.guide-body .guide-listing a.guide-listing-name:hover { color: var(--accent); }
.guide-body .guide-listing a.guide-listing-pic { text-decoration: none; }
.guide-listing-last { white-space: nowrap; }
.guide-listing-name svg { width: 14px; height: 14px; margin-left: 5px; vertical-align: -2px; opacity: .75; }
.guide-listing-cat { grid-area: meta; min-width: 0; font-size: 12.5px; line-height: 1.35; color: var(--muted); }
.guide-listing-bit { white-space: nowrap; }
/* The tray without its frame: inside a row that already has a border, a
   second bordered box around bordered buttons read as boxes in boxes (the
   user, 10 September 2026). */
.guide-listing .save-row {
  grid-area: acts; justify-self: end; display: flex; flex-wrap: wrap; gap: 5px;
  margin: 0; padding: 0; border: 0; border-radius: 0; background: none;
}
.guide-listing .save-btn { flex: 0 0 auto; min-height: 32px; padding: 0 9px; font-size: 12.5px; gap: 5px; }
.guide-listing .save-btn svg { width: 14px; height: 14px; }
.guide-listing .save-btn.is-no { width: 32px; }
/* In a stack of rows the tip opens above its button, not below into the
   next row's buttons, and runs rightward from the button, which is the
   first in its row; the hovered button rises above its neighbours (the
   user, 10 September 2026). */
.guide-listing .save-btn.is-no:hover,
.guide-listing .save-btn.is-no:focus-visible { z-index: 7; }
.guide-listing .save-tip { top: auto; bottom: calc(100% + 9px); transform: translateY(3px); }
.guide-listing .save-tip::after {
  bottom: auto; top: 100%;
  border-bottom-color: transparent; border-top-color: var(--ink);
}
/* As specific as the name's own rule above, or the strike never shows. */
.guide-body .guide-listing.is-dismissed a.guide-listing-name { text-decoration: line-through; color: var(--muted); }
.guide-listing.is-dismissed .guide-listing-pic { opacity: .45; filter: grayscale(1); }
@container (max-width: 559px) {
  .guide-listing { grid-template-columns: 52px minmax(0, 1fr); grid-template-areas: "pic name" "pic meta" "acts acts"; }
  .guide-listing-cat { align-self: start; }
  .guide-listing .save-row { justify-self: start; margin-top: 4px; }
}
.sheet-sub .sub-change {
  background: none; border: 0; padding: 0; margin: 0; font: inherit;
  color: var(--accent); text-decoration: underline; cursor: pointer;
}

.list-foot {
  padding: 18px 0 6px; font-size: 12px; color: var(--muted); line-height: 1.5;
  border-top: 1px solid var(--rule); margin-top: 6px;
}
.list-foot p { margin: 0; }
.list-foot-legal { margin-top: 8px !important; font-size: 11.5px; }
.list-foot a { color: var(--muted); text-underline-offset: 2px; }
/* Offline control. Reads as part of the small print rather than as a call to
   action: most people will never need it, and the ones who do are looking. */
.offline-line { margin: 0 0 8px !important; }
.offline-line button {
  color: var(--ink); text-decoration: underline; text-underline-offset: 2px;
  font-size: inherit; font-family: inherit;
}

/* --- 6. Map markers ------------------------------------------------------ */
.pin {
  width: 34px; height: 34px;              /* hit area, deliberately > the dot */
  display: grid; place-items: center;
  cursor: pointer; touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.pin::before {
  content: ""; width: 15px; height: 15px; border-radius: 50%;
  background: var(--accent); border: 2.5px solid var(--paper);
  box-shadow: 0 1px 4px rgba(0,0,0,.35);
  transition: transform .22s cubic-bezier(.2,.9,.3,1.2);
}
/* The map is coloured by the same three-way split the chips make, and by
   nothing else. It used to colour by "has an end date", which stopped matching
   the moment Exhibitions became their own thing: filter to Exhibitions and you
   got a mix of teal and terracotta dots, because the three perpetual ones have
   no end date. What a listing IS does not change with the calendar, so that is
   what the dot says.

   Terracotta for Experiences is the category default and stays. Teal for
   Events, plum for Exhibitions. */
.pin.is-event::before { background: var(--event); }
.pin.is-exhibition::before { background: var(--exhibit); }
.pin.is-perform::before { background: var(--perform); }
/* An event with no address. Bigger and hollow: a ring reads as an area rather
   than a point, which is the whole claim being made. Same teal as the other
   dated runs - the shape does the work, so the map does not need a fourth
   colour to learn. */
/* The ring is for a festival happening across the whole city at once. Only
   dated listings get it; a vineyard pinned at its departure station keeps the
   ordinary dot of its category. */
.pin.is-area::before {
  width: 26px; height: 26px;
  background: transparent; border: 3px solid var(--event);
  box-shadow: 0 0 0 2px var(--paper), 0 1px 5px rgba(0,0,0,.3);
}
/* An undated area - the City's churches - wears its category's colour, so
   the ring and its wash stay one thing and teal keeps meaning a run. */
.pin.is-area:not(.is-event):not(.is-exhibition)::before { border-color: var(--accent); }
.pin.is-area.is-selected::before { transform: scale(1.25); }
/* A venue pin: the dot grows to take a number, the count of what is on
   there. Teal or plum by what most of its events are, like a single pin. */
/* No `position` of its own: MapLibre places a marker with position:
   absolute and a transform from the map's corner, and a venue pin set to
   relative flowed after its siblings and drifted by their height on every
   redraw. The count sits in the same grid cell as the dot. */
.pin.is-venue::before { width: 24px; height: 24px; grid-area: 1 / 1; }
.pin-count {
  grid-area: 1 / 1; display: grid; place-items: center; width: 24px; height: 24px;
  font: 700 11px/1 var(--font-body, inherit); color: var(--paper);
  font-variant-numeric: tabular-nums; pointer-events: none;
  /* Above the dot: the hover scales the dot with a transform, which lifts
     it into the positioned layer and painted it over the number. */
  position: relative; z-index: 1;
}
.pin.is-venue.is-hovered::before { transform: scale(1.15); }
.pin.is-venue.is-selected::before { transform: scale(1.3); }
.pin.is-venue.is-selected .pin-count { transform: scale(1.2); }
.pin:active::before { transform: scale(.88); }
.pin.is-selected { z-index: 3; }
.pin.is-selected::before { transform: scale(1.55); }
.pin.is-dimmed::before { background: var(--rule-2); box-shadow: none; }
/* Names the open place. No chevron: one tap on the pin opens it, so there is
   no second action left to advertise. */
/* A card, not a pill. The 999px radius was fine round a single line of text
   and wrong the moment a second line went above it: the two long curves ate
   the corners the type needed, and the block sat inside the shape looking like
   it had been dropped there. Square corners, a small radius, and a tail into
   the pin, so the label reads as belonging to that dot rather than floating
   over the map near it. */
.pin-label {
  position: absolute; bottom: 31px; left: 50%;
  /* --nudge is set by finder.js when a centred label would run off the edge
     of the map: the card slides in by that much, and the tail below slides
     the other way so it still points at the dot. */
  transform: translateX(calc(-50% + var(--nudge, 0px)));
  width: max-content; max-width: 230px;
  font-family: var(--font-display); font-size: 13px; font-weight: 500;
  line-height: 1.3; text-align: left; white-space: normal;
  padding: 7px 11px 8px; border-radius: 4px;
  background: var(--ink); color: var(--paper);
  box-shadow: 0 4px 16px -5px rgba(0,0,0,.55);
  opacity: 0; transition: opacity .18s;
  pointer-events: none;            /* only the selected pin's label is live */
  cursor: pointer;
}
/* The label a route wears while the cursor is on it. Same card as the pin's,
   because it is the same claim about the same listing - only it follows the
   pointer, since a line has no one point that means "here". Never takes the
   pointer: a tooltip that can be hovered is a tooltip that flickers. */
.map-hint {
  position: absolute; z-index: 4; pointer-events: none;
  transform: translate(-50%, -100%);
  width: max-content; max-width: 230px;
  font-family: var(--font-display); font-size: 13px; font-weight: 500;
  line-height: 1.3; text-align: left;
  padding: 7px 11px; border-radius: 4px;
  background: var(--ink); color: var(--paper);
  box-shadow: 0 4px 16px -5px rgba(0, 0, 0, .55);
}

.pin-label::after {
  content: ""; position: absolute; top: 100%; margin-left: -5px;
  left: clamp(10px, calc(50% - var(--nudge, 0px)), calc(100% - 10px));
  border: 5px solid transparent; border-top-color: var(--ink);
}
/* Too near the top of the map for a card above the dot: it hangs below
   instead, tail pointing up. */
.pin.is-below .pin-label { bottom: auto; top: 31px; }
.pin.is-below .pin-label::after {
  top: auto; bottom: 100%;
  border-top-color: transparent; border-bottom-color: var(--ink);
}
/* The glyph sits on the text baseline rather than above it, so a label with
   one does not stand taller than a label without. */
.pin-label svg { width: 13px; height: 13px; vertical-align: -2px; margin-right: 5px; }
.pin.is-selected .pin-label { opacity: 1; pointer-events: auto; }
/* On a phone the label is not a tooltip but the thing you pressed for, so it
   carries the way in: the whole card is the link, and the arrow at its end
   says so. The sub-line under a venue's name is what is on there (the user,
   10 September 2026). */
.pin-label { text-decoration: none; }
/* Only where there is no pointer. With a hover the card is already open and
   the pin itself opens the listing, so the arrow says nothing and cost the
   card a line (the user, 10 September 2026). */
/* The last word of the name and the arrow travel together, so the arrow can
   never be left alone on a line of its own. */
.pin-last { white-space: nowrap; }
.pin-go { display: none; }
@media (hover: none), (pointer: coarse) {
  .pin-go { display: inline-block; width: 12px; height: 12px; margin: 0 0 0 5px; vertical-align: -1px; opacity: .7; }
}
.pin-label .pin-sub { display: block; margin-top: 3px; font-size: 11.5px; opacity: .78; }
/* The row the pressed pin points at - lit across the whole list column, not
   just behind its words. It painted only the content box, so the shading
   stopped a couple of pixels from the first letter and read as a box drawn
   round the text rather than as the row being lit (the user, 11 September
   2026, on Badiani at Elephant and Castle).

   Same four properties as .card.is-selected and the hover rule, and `width:
   auto` for the same reason they need it: .card carries width:100% for the
   station picker's buttons, and with that set the negative margins shift the
   row instead of stretching it, leaving the highlight short at the right. */
#list .card.is-picked {
  background: var(--card-hover);
  width: auto;
  margin-left: calc(-1 * var(--list-pad)); margin-right: calc(-1 * var(--list-pad));
  padding-left: var(--list-pad); padding-right: var(--list-pad);
}
.pin.is-selected .pin-label:active { transform: translateX(calc(-50% + var(--nudge, 0px))) scale(.96); }

/* Hovering a list row lights its pin, so the list can be read against the map. */
/* A pointer over a pin names it, so the map can be read without opening
   anything - the same job the card hover does in the other direction. The
   label already existed for the selected state; this only reveals it earlier.
   Guarded to real pointers: a touch screen reports no hover, and without the
   guard a tap would flash the label on its way to opening the entry. */
@media (hover: hover) and (pointer: fine) {
  .pin.is-hovered .pin-label { opacity: 1; }
}

.pin.is-hovered::before { transform: scale(1.35); }
/* Above the open pin, not under it. The open pin is raised so its card wins
   over the crowd around it, and a hovered neighbour was raised less - so its
   photograph slid in underneath the open pin's name. The hover is the more
   recent request and the reader is looking at it; the open pin's card is back
   on top the moment the cursor leaves, and a hover card takes no pointer, so
   nothing under it becomes unclickable. */
.pin.is-hovered { z-index: 4; }

/* --- 7. Desktop: same components, different shell ------------------------ */
@media (min-width: 900px) {
  .sheet-scroll {
    inset: 0 auto 0 0; width: 420px;
    overflow: visible; scroll-snap-type: none; pointer-events: auto;
    border-right: 1px solid var(--rule);
  }
  .snap { display: none; }
  .sheet { height: 100%; border-radius: 0; box-shadow: none; }
  .grabber { display: none; }
  /* No detents here, so data-detent never leaves "peek" and the clamp above
     would apply for ever. There is no peek to protect in this layout. */
  #sheetScroll[data-detent] .sheet-sub { display: block; }
  .sheet-head { padding-top: calc(env(safe-area-inset-top) + 62px); }
  .map { left: 420px; }
  .map-dim { display: none; }
  .maplibregl-ctrl-bottom-right { margin-bottom: 0; }

  .chip:hover { border-color: var(--ink-2); }
}

/* --- 8. Document shell (landing and 404) -------------------------------- */
body.is-doc { background: var(--paper); }

/* --- 9. View transitions ------------------------------------------------- */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation-duration: .18s; }
  ::view-transition-new(root) { animation-duration: .26s; }
}

/* The pill on a document page. The finder floats it over the map; here there
   is no map, so it sits in the flow above the heading and shares the hero's
   own gutter, which keeps its left edge on the same line as the text below. */
/* It holds at the top of the window rather than scrolling away with the
   article: the way back to everything else should not depend on how far down
   a page somebody has read (the user, 10 September 2026). Sticky rather than
   fixed, so it takes its own space in the flow and nothing has to be padded
   out from underneath it.

   Two elements rather than one: the outer runs the width of the window so
   its paper covers whatever passes beneath, and the inner holds the same
   measure the prose does, so the hamburger stays on the text's left edge as
   it always has. */
.doc-top {
  position: sticky; top: 0; z-index: 6;
  width: 100%; background: var(--paper);
  padding: calc(env(safe-area-inset-top) + 14px) 0 10px;
  display: flex; justify-content: center;
}
.doc-top-in {
  /* width:100% matters: is-doc is a flex column, and on a flex item an auto
     margin beats stretch, so without it the bar shrank to the pill and centred
     itself instead of sitting at the same left edge as the prose. */
  width: 100%; max-width: var(--measure); padding: 0 24px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
/* A document page has no map, so the capsule has nothing to float over. The
   border, tint and backdrop blur exist to lift the pill off moving tiles; on
   plain paper they make it read as a stray widget - most obviously at desktop
   width, where the centred column leaves it alone in a great deal of space.
   Same two controls here, no capsule: a masthead over an article, which is
   what it actually is. */
.doc-top-in .brand {
  border: 0; background: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  /* The icon box overhangs the text edge, so pull it back to put the glyph on
     the same line as the heading below rather than its bounding box. */
  margin-left: -9px;
}
.doc-top-in .nav-btn { border-right: 0; width: 34px; }
.doc-top-in .wordmark { padding: 7px 0 7px 3px; }

/* --- 10. Landing --------------------------------------------------------- */
/* The top. 6vh of air above the hamburger put the wordmark a third of the way
   down a phone before anything had been read, and the reader's own scroll -
   about thirty pixels - was where the page looked right. The safe-area term is
   for the installed app, where the status bar sits over the page; in a
   browser it is zero.

   It is for the pages whose column IS the top of the page. The homepage is not
   one of them any more - its bar lives inside the column - and `.hero-app`
   below takes the term back off it. */
.hero { max-width: 640px; margin: 0 auto;
        padding: calc(env(safe-area-inset-top) + clamp(14px, 2.4vh, 24px)) 24px 60px;
        /* The body is a flex column, so this is a cross-axis item centred by its
           auto margins - and such an item is sized to fit its content, which on
           a phone means its widest unbreakable content: a guide's table with a
           no-wrap first column is 540px of it, and the whole column, deck and
           picture and caption, was laid out at that width and ran off the
           screen. Full width, capped by max-width as before, and the table's
           own scrolling wrapper does what it was there for. */
        width: 100%; box-sizing: border-box; }
.doc-top + .hero { padding-top: clamp(18px, 4vh, 34px); }
.hero .wordmark-lg {
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(38px, 12vw, 62px); letter-spacing: -.035em; line-height: 1;
  margin: 0 0 18px;
}
.hero .wordmark-lg b { font-weight: 500; color: var(--accent); }
/* No per-element measure here: the container is the measure. Capping the
   paragraphs at 44ch/60ch while the rules ran the full column gave three
   different text widths stacked down one page, which read as a mistake. */
.hero p { font-size: 17px; line-height: 1.6; color: var(--ink-2); }
/* The standfirst under the wordmark. It is the one line that says what the
   site is, so it is set in the display face rather than the UI one and sized
   between the wordmark and the body - a headline introduction, not the first
   paragraph of something. Only the homepage has one; the privacy page and the
   404 keep the plain 17px body. */
.hero .lede {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(20px, 4.4vw, 24px); line-height: 1.4;
  letter-spacing: -.015em; color: var(--ink-2);
  margin: 0 0 4px;
  /* Balanced rather than measured: at 375px this line has to wrap somewhere,
     and a hard measure left "London." alone on the second line. Browsers
     without support just wrap where they always would. */
  text-wrap: balance;
}
/* Squares, not rows. Six identical lines of text said nothing about what each
   category held; a drawing does that work before the label is read. */
.cats {
  list-style: none; padding: 0; margin: 30px 0 0;
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;
}
/* The tiles are square until one of them needs more room - a two-line name
   over two counts. Then the whole row grows with it rather than leaving one
   tile hanging lower than its neighbour. */
.cats li { display: flex; min-width: 0; }
.cats a {
  display: flex; flex-direction: column; justify-content: space-between;
  /* min-width:0 matters: a flex item defaults to min-width:auto, so
     "Experiences" refused to shrink and pushed the whole grid wider than the
     screen. */
  flex: 1; min-width: 0; aspect-ratio: 1 / 1; padding: 15px;
  border: 1px solid var(--rule); border-radius: 14px;
  background: var(--paper); text-decoration: none; color: var(--ink);
  transition: border-color .15s, background .15s;
}
.cats .cat-i { width: 60px; height: 60px; color: var(--accent); flex: none; }
.cats .cat-t { display: flex; flex-direction: column; gap: 1px; }
.cats .n { font-family: var(--font-display); font-weight: 500; font-size: 20px;
           letter-spacing: -.02em; line-height: 1.15; }
.cats .s { font-size: 12.5px; color: var(--muted); }
.cats a:hover { border-color: var(--ink-2); background: var(--card-hover); }
.cats a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cats a[aria-disabled="true"] { opacity: .45; pointer-events: none; }
@media (min-width: 620px) { .cats { grid-template-columns: repeat(3, 1fr); gap: 12px; } }

/* Group headings, in the same voice as the section headings on a listing and
   on the saved page. They were small caps in --muted on the theory that they
   label a grid rather than title it - but that reasoning made them the palest
   thing on the page, and the wordmark is large enough that nothing at 19px is
   competing with it.

   Each takes its own group's colour rather than one heading colour for both.
   The tiles already separate on colour, terracotta above and olive below, and
   the heading was the one part of that split still painted grey. */
.cats-h {
  /* No rule above. The heading, its colour and fifty pixels of air are
     already three ways of saying a new section starts, and the line was a
     fourth drawn across a page whose whole layout is unlined cards. Kept as
     margin so the spacing is exactly what the rule and its padding made. */
  margin: 50px 0 0;
  font-family: var(--font-display); font-weight: 500;
  font-size: 19px; letter-spacing: -.01em; line-height: 1.2;
  color: var(--accent);
}
/* The first heading used to sit under the tagline and needed less; the four
   ways in sit there now, unheaded, and every heading follows a grid. */
.cats-h + .cats { margin-top: 12px; }

/* ---- the homepage, folded ----------------------------------------------
   The groups open and shut, as the menu's do. Folded, the page is the five
   ways in and four headings with counts on them; the twenty-six categories
   are one press each rather than a column you scroll past.

   The spacing moves from the heading to the group, because the heading is now
   a control inside it: 50px of air above a shut row made four rows read as
   four sections of nothing. */
.cats-group { margin-top: 26px; }
.cats-sum.cats-h { margin: 0; }
.cats-group .cats { margin-top: 14px; }
.cats-group .cats-note + .cats { margin-top: 10px; }
/* Built as a row rather than as a label, for the reason the menu gives about
   its own: a control that does not look like one is a control people do not
   find. It keeps the heading's face, size and the group's own colour - the
   split between terracotta, olive, purple and teal is how this page says one
   section has ended - and adds the chevron and the count that mark it as the
   kind of heading that opens. */
.cats-sum {
  display: flex; align-items: center; gap: 10px;
  min-height: 44px; padding: 4px 0;
  list-style: none; cursor: pointer;
}
.cats-sum::-webkit-details-marker { display: none; }
.cats-sum:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.cats-sum-i { display: inline-flex; flex: none; }
.cats-sum-i svg { width: 21px; height: 21px; }
/* The count and the chevron out of the group's colour and into the page's
   quiet one. In the heading's colour they read as part of the name. */
.cats-sum-n {
  font-family: var(--font-ui); font-size: 12.5px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.cats-chev {
  margin-left: auto; display: inline-flex; color: var(--rule-2);
  transition: transform .18s, color .18s;
}
.cats-chev svg { width: 18px; height: 18px; }
.cats-group[open] > .cats-sum .cats-chev { transform: rotate(180deg); color: var(--ink-2); }
@media (hover: hover) {
  .cats-sum:hover .cats-chev { color: var(--ink-2); }
}
@media (prefers-reduced-motion: reduce) { .cats-chev { transition: none; } }
/* The promise that belongs to one group rather than the whole site. */
.cats-note { margin: 4px 0 0; font-size: 13.5px; color: var(--muted); }
/* Centred on the note's first line whatever its size: half the line's spare
   height above the mark (lh is the line-height; the 1px is for a browser
   without it). Centred on the whole note it would float mid-paragraph once
   the note wraps (the user, 10 September 2026). */
.cats-note + .cats { margin-top: 10px; }
/* Food & drink takes the olive, so the two halves of the page separate on
   colour before the headings are read. */
.cats-h-food-drink { color: var(--accent-2); }
.cats-food-drink .cat-i { color: var(--accent-2); }
.cats-food-drink a:hover { border-color: var(--accent-2); }
.cats-food-drink a:focus-visible { outline-color: var(--accent-2); }
/* And Themes the plum. */
.cats-h-themes { color: var(--accent-3); }
.cats-themes .cat-i { color: var(--accent-3); }
.cats-themes a:hover { border-color: var(--accent-3); }
.cats-themes a:focus-visible { outline-color: var(--accent-3); }
/* And What's On the event colour, the one the map already gives to
   anything with a date. */
.cats-h-what-s-on { color: var(--event); }
.cats-what-s-on .cat-i { color: var(--event); }
.cats-what-s-on a:hover { border-color: var(--event); }
.cats-what-s-on a:focus-visible { outline-color: var(--event); }

.note-bar {
  font-size: 12px; line-height: 1.45; color: var(--muted);
  background: var(--paper-2); border: 1px solid var(--rule);
  border-radius: 10px; padding: 10px 12px; margin: 30px 0 0;
}

/* --- 11. Listed-but-not-written entries, hours, attribution -------------- */
.card-plain { color: var(--muted); font-size: 13.5px; }
.unknown { color: var(--muted); }

.doc-h2 {
  font-family: var(--font-display); font-weight: 500; font-size: 20px;
  margin: 34px 0 10px;
}
.unwritten {
  border-left: 2px solid var(--rule-2); padding: 2px 0 2px 14px;
  color: var(--muted); font-style: italic;
}
.hours-note { font-size: 14px; color: var(--muted); }
.hours-note code {
  font-size: 12.5px; background: var(--paper-2); border: 1px solid var(--rule);
  border-radius: 4px; padding: 1px 5px;
}
table.hours {
  width: 100%; border-collapse: collapse; font-size: 15px;
  border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden;
}
table.hours th, table.hours td {
  text-align: left; padding: 9px 14px; border-bottom: 1px solid var(--rule);
  font-weight: 400;
}
table.hours th { color: var(--muted); font-size: 13px; }
table.hours tr:last-child th, table.hours tr:last-child td { border-bottom: 0; }
/* Today's row. It was a two-percent background tint and easy to miss. Now it
   carries an accent bar and full-strength text, and borrows the green the list
   view uses for "Open until 17:00" — but only when the place is actually open,
   because green means open everywhere else on this site. A closed Sunday gets
   the emphasis without the claim. */
table.hours tr.today { background: var(--selected); }
table.hours tr.today th,
table.hours tr.today td { color: var(--ink); font-weight: 500; }
table.hours tr.today th { box-shadow: inset 3px 0 0 var(--selected-line); }
table.hours tr.today.is-open { background: var(--ok-tint); }
table.hours tr.today.is-open th { box-shadow: inset 3px 0 0 var(--ok); }
table.hours tr.today.is-open td { color: var(--ok); }
table.hours td { font-variant-numeric: tabular-nums; }

/* Site footer. The ODbL requires the attribution to be present and legible,
   not prominent — so it sits below the main content, small but readable. */
/* The document pages are a column the height of the viewport, so the footer
   sits on the bottom edge rather than floating under the content on a tall
   desktop window. No rule above it - the tiles already end the page. */
.is-doc { min-height: 100vh; min-height: 100svh; display: flex; flex-direction: column; }
/* And the strip the status bar sits over is always paper, whatever the bar
   under it is doing. Both of these pages hold their masthead with `position:
   sticky`, and iOS repositions a sticky element on the main thread: during a
   flick it is drawn where it was a frame ago, and the content flying past it
   shows through the inset. A fixed band cannot lag, because it is not being
   repositioned. Fixed, so it is out of the flex column; zero-height in a
   browser, where the inset is zero; and above the mastheads' z-index 6.

   Not on a finder page: the map runs to the edge of the screen there, under
   the status bar, and a paper band over it would be a bar across the top of
   every map on the site. */
.is-doc::before {
  content: ""; position: fixed; top: 0; left: 0; right: 0; z-index: 7;
  height: env(safe-area-inset-top); background: var(--paper);
  pointer-events: none;
}
.is-doc .hero { flex: 1 0 auto; }
.is-doc .site-foot { flex: none; }
.site-foot {
  max-width: 640px; margin: 0 auto; width: 100%; box-sizing: border-box;
  padding: 22px 24px calc(env(safe-area-inset-bottom) + 28px);
}
.hero { padding-bottom: 40px; }
.site-foot p {
  margin: 0; font-size: 11.5px; line-height: 1.55; color: var(--muted);
}
.site-foot a { color: var(--muted); text-underline-offset: 2px; }

/* Odd fact counts would otherwise leave a rule-coloured block in the grid. */
.fact:last-child:nth-child(odd) { grid-column: 1 / -1; }

/* --- 12. Detail rendered inside the sheet -------------------------------- */
/* Deliberately opens at the half detent: the reason for doing this in place
   rather than navigating is that the map stays on screen. Drag up to read on. */
.sheet[data-view="list"]   #detailHead { display: none; }
.sheet[data-view="detail"] #listHead   { display: none; }
/* The row holds the way back and, when the way back has become a step to the
   listing you came from, the way out to the category as well. */
/* No rule under it. The back control is drawn as a button now and reads as one
   on its own; a line beneath it as well put a second edge a few pixels from
   the button's own, which is two ways of saying the same thing and neither of
   them quietly. */
.detail-head {
  flex: none; padding: 2px 10px 10px;
  display: flex; align-items: center; gap: 10px;
}
.detail-up {
  margin-left: auto; font-size: 12.5px; color: var(--muted);
  text-decoration: none; white-space: nowrap; padding: 6px 4px;
}
.detail-up:hover { color: var(--accent); }

/* A button, drawn as one. It was text with an arrow in front of it - and in
   an installed app, with no browser chrome around it, this is the only way out
   of a listing. The only way out should not be the one thing on the page that
   has to be recognised as a control rather than seen as one. */
.detail-back {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 38px; padding: 0 14px 0 10px; border-radius: 999px;
  border: 1px solid var(--rule-2); background: var(--paper);
  font-size: 13.5px; color: var(--ink-2);
  transition: border-color .15s, color .15s, background .15s;
}
.detail-back svg { width: 15px; height: 15px; flex: none; }
/* As an anchor on /nearby, where the way back is a real address and not a
   step in this page's own history; it should not therefore look like body
   copy with a line under it. */
a.detail-back { text-decoration: none; }
/* A listing's name can be far longer than a category's - "San Francisco
   Ballet: Mere Mortals" - and the chip is in a header that cannot grow. */
.detail-back span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 62vw;
}
.detail-back:active { background: var(--card-hover); border-color: var(--ink-2); }
.detail-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* While the sheet is being pulled by this button, it is a handle and not a
   button: the pressed state would say a press is happening when a drag is. */
.sheet.is-dragging .detail-back:active {
  background: var(--paper); border-color: var(--rule-2);
}
@media (hover: hover) {
  .detail-back:hover {
    background: var(--card-hover); border-color: var(--ink-2); color: var(--ink);
  }
}

#detail { padding-top: 16px; animation: detail-in .22s cubic-bezier(.2,.7,.3,1); }
@keyframes detail-in { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { #detail { animation: none; } }

.detail-name {
  font-family: var(--font-display); font-weight: 500;
  font-size: 25px; line-height: 1.1; letter-spacing: -.02em; margin: 0 0 8px;
}
.detail-verdict {
  font-family: var(--font-display); font-style: italic;
  font-size: 17px; line-height: 1.4; color: var(--ink-2); margin: 0 0 14px;
}
.detail-note { font-size: 15px; line-height: 1.6; color: var(--ink-2); margin: 0 0 14px; }
.detail-unwritten {
  border-left: 2px solid var(--rule-2); padding: 2px 0 2px 12px;
  color: var(--muted); font-style: italic; font-size: 14.5px; margin: 0 0 16px;
}
/* Facts grid. Lived in the old place-page section; it belongs here now that the
   panel is the only thing that renders it. */
.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1px; background: var(--rule); border: 1px solid var(--rule);
  border-radius: var(--r); overflow: hidden; margin: 24px 0;
}
.fact { background: var(--paper); padding: 12px 14px; }
.fact dt { font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); margin: 0 0 3px; }
.fact dd { margin: 0; font-size: 15px; }
.fact a { color: var(--ink-2); }

.detail-facts { margin: 18px 0; }
.detail-facts .fact { padding: 10px 12px; }
.detail-facts dd { font-size: 14px; }
/* A section heading, not a thing. At 17px serif it sat one point above the
   serif titles underneath it, so "On here now" read as another exhibition.
   The same small caps the fact labels use: this is the page saying what the
   next block is, in the voice it already has for that. */
/* Section headings on a listing: Coming up, Regular events, Hours, Nearby.
   They were 11px uppercase in --muted, which is the styling of a field label
   on a form - the smallest, palest thing on the page - and they were being
   asked to break a page into sections. Headings should be the darkest text
   there, not the lightest.

   So: the display face the listing's own name is set in, two thirds its size,
   at full ink strength, and sentence case rather than shouted. The rule above
   does the separating that 26px of air was not managing on a long page. */
.detail-h {
  font-family: var(--font-display); font-weight: 500;
  font-size: 19px; letter-spacing: -.01em; line-height: 1.2;
  /* And in the site's own colour. Ink alone was not enough: what sits under
     these headings is a list of names in the same face, so at full strength
     the heading and its first entry read as two entries. Terracotta separates
     them without another size step, and there is nothing else on a listing it
     could be mistaken for - the links inside the lists are ink, and nothing
     here is underlined. */
  color: var(--accent);
  margin: 28px 0 10px; padding-top: 15px;
  border-top: 1px solid var(--rule);
}
/* The first one follows the actions or the facts, which already draw an edge
   of their own. */
.detail-facts + .detail-h, .detail-attrs + .detail-h { padding-top: 0; border-top: 0; }
#detail table.hours { font-size: 14px; }
#detail table.hours th, #detail table.hours td { padding: 7px 12px; }

/* "Nearby · Coffee" and "Nearby · other categories": one heading in one
   voice, the two halves joined by the middle dot the site uses everywhere
   else it joins two facts. A smaller, greyer second half was tried and read
   as a caption to the first rather than as its other half. */
.detail-h-in { font: inherit; color: inherit; }
.detail-h-in::before { content: "\00b7"; margin: 0 .4em; }
/* On a row from another category, the category before the walk. */
/* Three neighbours, and the way to all of them: /nearby, opened where this
   listing is. Right-aligned and in the small voice - it is an offer under the
   section, not the point of it. */
/* All but flush with the last row (the user, 11 September 2026, twice: 12px
   read as a stray line floating under the section, and 4px was still too
   much). Negative, because the gap is not this element's margin - it is the
   last row's own 11px of padding, which belongs to the list and cannot come
   off it without moving every row. So it is pulled back up through it, to
   about four pixels under the last line of text. */
.detail-more { margin: -7px 0 0; text-align: right; }
.detail-more a {
  font-size: 12.5px; color: var(--muted); text-decoration: none;
  border-bottom: 1px solid var(--rule-2); padding-bottom: 1px;
}
.detail-more a:hover { color: var(--ink); border-bottom-color: currentColor; }
.detail-near-cat { margin-right: 8px; }
.detail-near-cat::after { content: "\00b7"; margin-left: 8px; }
.detail-near { list-style: none; padding: 0; margin: 0; }
.detail-near a {
  display: flex; justify-content: space-between; align-items: baseline;
  /* Both axes, separately: 12px between the title and the walk beside it, and
     almost nothing above the clue that wraps under them, which is a second
     line of the same row rather than a second row. */
  column-gap: 12px; row-gap: 1px;
  padding: 11px 2px; border-bottom: 1px solid var(--rule); text-decoration: none;
}
/* The last row's rule and .detail-source's border-top stacked into a double
   line at the foot of every entry. The source block draws the separator. */
.detail-near li:last-child a { border-bottom: 0; }
.detail-near-name {
  font-family: var(--font-display); font-size: 16px;
  /* Basis 0, or the flex algorithm reads the title's full content width
     before it shrinks anything and puts the walk on a line of its own. */
  flex: 1 1 0; min-width: 0;
}
/* Never breaks. "British Optical Association Museum" pushed the column
   narrow enough to split "3 min walk" across two lines. */
.detail-near-walk {
  font-size: 12.5px; color: var(--muted);
  flex: none; white-space: nowrap;
}
/* What a What's On neighbour IS, under both: "Gig - Jazz". Not beside the
   walk, which is where the other-categories rows put their category - that
   column is held at its content's width and never wraps, so a clue in it
   fights the title for the row, and "Lecture / talk - Science & nature - 6
   min walk" won, leaving the title ten lines tall at 375px. A third child of
   a wrapping flex row takes a line of its own and no width from anything.

   The two Nearby lists therefore label in two places, which is the right
   answer rather than an inconsistency: a category is a navigational fact and
   pairs with the distance, and what a thing IS belongs under its title. */
.detail-near a { flex-wrap: wrap; }
.detail-near-clue {
  flex: 0 0 100%; margin-top: 2px;
  font-size: 12.5px; color: var(--muted);
}

/* What is on at a venue. Not .detail-near: that puts the name and the number
   at opposite ends of a flex row, which is right for "Museum of Comedy — 3 min
   walk" and falls apart for "Yinka Shonibare and Thomas Gainsborough: A
   Conversation — From 15 October 2026", where both halves wrap and interleave.
   Stacked instead, the date under the title in the same small voice the hours
   and the fact labels use. */
.detail-on { list-style: none; padding: 0; margin: 0; }
.detail-on a {
  display: block; padding: 10px 2px;
  border-bottom: 1px solid var(--rule); text-decoration: none;
}
.detail-on li:last-child a { border-bottom: 0; }
.detail-on-name {
  display: block; font-family: var(--font-display); font-size: 16px;
  line-height: 1.25; color: var(--ink);
}
.detail-on-when {
  display: block; margin-top: 3px;
  font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
/* An event's dates on an area row, in the event colour, above its one line. */
.area-when {
  display: block; margin-top: 2px;
  font-family: var(--font-ui); font-size: 12.5px; font-weight: 600;
  color: var(--event); font-variant-numeric: tabular-nums;
}
/* What the section is not showing: the events opening more than two months
   out, counted rather than listed, with the way to them. */
.area-later { padding: 9px 2px 4px; font-size: 12.5px; color: var(--muted); }
.area-later a { color: var(--ink-2); }

/* Things that repeat, on the venue rather than as listings of their own.
   Built on the same rows as the dated shows above them - one line naming the
   thing, one of quiet metadata - with a third for the caveat that stops a
   tour of the building being read as a tour of the museum. */
.detail-regular { list-style: none; padding: 0; margin: 0; }
.detail-regular a,
.detail-regular .reg-row {
  display: block; padding: 10px 2px;
  border-bottom: 1px solid var(--rule); text-decoration: none;
}
.detail-regular li:last-child a,
.detail-regular li:last-child .reg-row { border-bottom: 0; }
.reg-what {
  display: block; font-family: var(--font-display); font-size: 16px;
  line-height: 1.25; color: var(--ink);
}
.reg-when {
  display: block; margin-top: 3px;
  font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.reg-note {
  display: block; margin-top: 4px; font-size: 12.5px; line-height: 1.45;
  color: var(--ink-2);
}

.detail-source {
  font-size: 12px; line-height: 1.55; color: var(--muted);
  margin-top: 22px; padding-top: 12px; border-top: 1px solid var(--rule);
  /* Zeroed, and it matters: this is a <p>, so it kept the browser's 1em
     bottom margin - 12px - on top of the footer's own 10px of padding. Two
     lines of the same 12px small print sat 22px apart, more than a line of
     text, and the rule below that tightens the footer against this paragraph
     never touched the paragraph's own margin. */
  margin-bottom: 0;
}
.detail-source a { color: var(--muted); }

/* The correction line sits above the credit and draws the rule under Nearby
   itself, so whatever follows it - the credit, or the footer straight away
   where there is no OSM record - draws none. */
.detail-correct {
  font-size: 12.5px; line-height: 1.55; color: var(--muted);
  margin: 22px 0 0; padding-top: 12px; border-top: 1px solid var(--rule);
}
.detail-correct a { color: var(--ink-2); text-underline-offset: 2px; }
.detail-correct + .detail-source { border-top: 0; margin-top: 4px; padding-top: 0; }
.detail-correct + .detail-foot { border-top: 0; margin-top: 0; padding-top: 10px; }

/* Same footer as the list view. It draws the rule under Nearby, except where
   .detail-source is present (an OSM credit) and has already drawn one. */
.detail-foot { margin-top: 22px; padding: 12px 0 6px; }
.detail-source + .detail-foot { border-top: 0; margin-top: 0; padding-top: 10px; }

/* Must come after section 12, whose `padding` shorthand would otherwise win on
   source order and drop the detail header behind the floating wordmark. */
@media (min-width: 900px) {
  .detail-head { padding-top: calc(env(safe-area-inset-top) + 62px); }
}

/* --- 13. Row hover ------------------------------------------------------
   Hover gets the same full-bleed geometry as selection, for the same reason:
   .card keeps width:100% for the station-picker buttons, so without width:auto
   the negative margins shift the row instead of stretching it.

   Scoped to .sheet-body so the picker's .card buttons are untouched, and gated
   on hover-capable pointers so a tap does not leave a row stuck highlighted. */
@media (hover: hover) {
  .sheet-body .card:hover {
    width: auto;
    margin-left: calc(-1 * var(--list-pad));
    margin-right: calc(-1 * var(--list-pad));
    padding-left: var(--list-pad);
    padding-right: var(--list-pad);
    background: var(--card-hover);
  }
  /* selection outranks hover, which the old rule got backwards */
  .sheet-body .card.is-selected:hover { background: var(--selected); }
  /* The full-bleed above belongs to a stacked row, which has the whole column
     to widen into. An area card sits in a two-column grid, where widening it
     and pulling it out by the gutter moved the cell, the column and the page
     under the pointer. Colour only, no geometry (the user, 10 September
     2026). */
  .sheet-body .nearby-start .area-grid .card:hover {
    width: 100%; margin: 0; padding: 12px 14px;
    background: var(--card-hover); border-color: var(--accent);
  }
  /* The same light from the other direction: the area under the pointer on
     the map marks its card in the list (areas.js, lightCard). */
  .sheet-body .card.is-hovered {
    width: auto;
    margin-left: calc(-1 * var(--list-pad));
    margin-right: calc(-1 * var(--list-pad));
    padding-left: var(--list-pad);
    padding-right: var(--list-pad);
    background: var(--card-hover);
  }
  .sheet-body .card.is-selected.is-hovered { background: var(--selected); }
  /* ...and while the pointer is on the map, every other card steps well
     back: far enough that the lit one is the only one being read, which .38
     was not (the user, 10 September 2026). */
  #list.is-pointing .card:not(.is-hovered) { opacity: .16; }
}
/* Room above a card the map scrolls to the top of the list, so it does not
   sit flush against the edge. */
.sheet-body .card { scroll-margin-block: 12px; }

/* --- 14. Locate control --------------------------------------------------
   Sits bottom-left of the map, clear of the sheet at rest. Geolocation is a
   map action, so it belongs on the map rather than among the filters. */
.locate-btn {
  /* Rides the top edge of the sheet, the way map controls do: --sheet-offset is
     the sheet's current scroll offset, so the button rises with it and is never
     buried underneath. Hidden at the full detent, where there is no map left to
     locate anything on. */
  position: absolute; z-index: 5;
  left: calc(env(safe-area-inset-left) + 16px);
  /* Rides the sheet, but never climbs past the top bar. */
  bottom: min(calc(var(--peek) + var(--sheet-offset, 0px) + 12px),
              calc(var(--vh) - 124px));
  /* Fade as the sheet passes half, so it is gone before it reaches the wordmark
     rather than sliding underneath it. */
  opacity: clamp(0, calc(1 - (var(--sheet-progress) - 0.45) * 3), 1);
  width: 44px; height: 44px;            /* full touch target */
  display: grid; place-items: center;
  border-radius: 999px;
  border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  box-shadow: 0 2px 12px -4px rgba(0,0,0,.35);
  color: var(--ink-2);
  transition: color .15s, background .15s, transform .12s;
}
.locate-btn:active { transform: scale(.94); }
@media (hover: hover) { .locate-btn:hover { color: var(--ink); } }
.locate-ico { width: 21px; height: 21px; }
.locate-btn.is-active { color: var(--accent); border-color: var(--accent); }

/* Locating: hide the crosshair, show a spinner in its place. */
.locate-spin { display: none; width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid var(--rule-2); border-top-color: var(--accent);
  animation: locate-spin .7s linear infinite; }
.locate-btn.is-busy .locate-ico { display: none; }
.locate-btn.is-busy .locate-spin { display: block; }
@keyframes locate-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .locate-spin { animation-duration: 2s; } }

/* A link that has been pressed and is waiting for its page. See ui.js,
   pressedLink: in an installed app nothing else says the press landed, and
   the view transition holds this page on the screen until the next one is
   ready to paint. The spinner rides the trailing edge so it never moves the
   words, and the words dim so the whole row reads as busy rather than as
   having grown a decoration. */
a.is-going { opacity: .6; transition: opacity .12s; }
/* And a spinner, but only where the link is a row of its own with room at
   its trailing edge: the menu, the homepage's category lists and its group
   headings' links. On an inline link in a sentence there is nowhere to put
   one that does not either sit on the words or hang outside the element and
   push a narrow page sideways, so those dim and no more. */
.nav-body a.is-going, .cats a.is-going, .cats-h a.is-going { position: relative; }
.nav-body a.is-going::after, .cats a.is-going::after, .cats-h a.is-going::after {
  content: ""; position: absolute; top: 50%; right: 6px;
  width: 13px; height: 13px; margin-top: -7px; border-radius: 50%;
  border: 2px solid var(--rule-2); border-top-color: var(--accent);
  animation: locate-spin .7s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .nav-body a.is-going::after, .cats a.is-going::after,
  .cats-h a.is-going::after { animation-duration: 2s; }
}

/* At full the sheet owns the screen; fade the control out rather than pinning
   it into the sliver of map left under the wordmark. */
#sheetScroll[data-detent="full"] ~ .locate-btn { visibility: hidden; }

@media (min-width: 900px) {
  /* The list is a sibling column here, not an overlay, so nothing to ride. */
  .locate-btn { left: calc(420px + 16px); bottom: 16px; opacity: 1; }
  #sheetScroll[data-detent="full"] ~ .locate-btn { visibility: visible; }
}

/* The one line an area map says about where you are - "You're in Soho", or
   why it did not move for a reader in Leeds. Sits just above the control it
   answers, rides the sheet with it, and goes on its own after a few seconds
   or the moment the reader takes the map back. Same card as a pin's label,
   because it is the same kind of remark about the same map. */
.map-note {
  position: absolute; z-index: 5; margin: 0;
  left: calc(env(safe-area-inset-left) + 16px);
  bottom: calc(min(calc(var(--peek) + var(--sheet-offset, 0px) + 12px),
                   calc(var(--vh) - 124px)) + 54px);
  max-width: min(330px, calc(100vw - 32px));
  padding: 8px 12px; border-radius: 4px;
  background: var(--ink); color: var(--paper);
  font-family: var(--font-display); font-size: 13.5px; font-weight: 500;
  line-height: 1.35;
  box-shadow: 0 4px 16px -5px rgba(0,0,0,.55);
  opacity: clamp(0, calc(1 - (var(--sheet-progress) - 0.45) * 3), 1);
}

/* The edge chevron: a place off the framed map, pointed at from the edge with
   its name and distance. Same card as a pin's label, because it is the same
   kind of remark about the same map; the chevron turns to point at the place. */
.edge-pin {
  position: absolute; z-index: 6; transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: 5px;
  padding: 6px 11px 7px 7px; border-radius: 4px;
  font-family: var(--font-display); font-size: 13px; font-weight: 500; line-height: 1.3;
  background: var(--ink); color: var(--paper); white-space: nowrap; max-width: 280px;
  box-shadow: 0 4px 16px -5px rgba(0,0,0,.55);
  cursor: pointer;
}
.pin.is-off .pin-label { display: none; }

/* A zone's name, at the cursor while it is over the shading, in the same
   card as a pin's label. Never takes the pointer, so the map underneath
   still pans and the pins still take their clicks. */
.zone-note {
  position: absolute; z-index: 4; pointer-events: none; margin: 0;
  transform: translate(14px, 14px);
  padding: 6px 10px 7px; border-radius: 4px; white-space: nowrap;
  font-family: var(--font-display); font-size: 13px; font-weight: 500; line-height: 1.3;
  background: var(--ink); color: var(--paper);
  box-shadow: 0 4px 16px -5px rgba(0,0,0,.55);
}
.zone-note { display: flex; flex-direction: column; gap: 1px; }
.zone-note-street { font-family: var(--font-body, inherit); font-size: 11.5px; font-weight: 400; opacity: .78; }
.zone-note-street:empty { display: none; }
.edge-pin-i { width: 16px; height: 16px; flex: 0 0 auto; transform: rotate(var(--edge-angle, 0deg)); }
.edge-pin-name { overflow: hidden; text-overflow: ellipsis; }
@media (hover: hover) { .edge-pin:hover { background: var(--ink-2, var(--ink)); } }
.map-note a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
#sheetScroll[data-detent="full"] ~ .map-note { visibility: hidden; }
@media (min-width: 900px) {
  .map-note { left: calc(420px + 16px); bottom: 70px; opacity: 1; }
  #sheetScroll[data-detent="full"] ~ .map-note { visibility: visible; }
}


/* --- place card: actions and access ---------------------------------------
   Directions / Instagram / website sit under the facts as tap targets rather
   than as another row of the table. Access is demoted to a quiet marker line:
   most places have nothing recorded, so it earns a symbol, not a column. */
.actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 0; }
.action {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 13px; border: 1px solid var(--rule); border-radius: 999px;
  background: var(--paper); color: var(--ink-2);
  font-size: 13.5px; line-height: 1; text-decoration: none;
  min-height: 40px; box-sizing: border-box;
}
.action .i { width: 16px; height: 16px; flex: none; opacity: .75; }
.action span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 22ch; }
.action:hover { border-color: var(--ink-2); color: var(--ink); }
.action:hover .i { opacity: 1; }
.action:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.attrs {
  display: flex; flex-wrap: wrap; gap: 6px 16px;
  margin: 12px 0 0; font-size: 12.5px; color: var(--muted);
}
.attr { display: inline-flex; align-items: center; gap: 5px; }
.attr .i { width: 15px; height: 15px; flex: none; }

/* Filters in two rows: where and when on top, what the place does below.
   A single row scrolled sideways and hid the type filters behind the edge. */
.chips-group { display: flex; flex-direction: column; gap: 2px; }
.chips-group .chips:first-child { padding-bottom: 0; }

/* Homepage: 14vh put the wordmark ~114px down a phone screen, a lot of nothing
   above the fold. Halved for narrow screens; the airier proportion is kept
   where there is room for it. This override sits at the end of the file
   because the shorthand above would otherwise win on specificity order. */
/* Was 13vh, which is 120px of nothing above the wordmark on a laptop. The
   airier proportion was a judgement about a page that had less on it. */
@media (min-width: 900px) { .hero { padding-top: clamp(16px, 2.6vh, 28px); } }

/* filters menu — museums carry ~14 subject filters, too many for a chip row */
.chip.is-more[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.filter-h {
  font-family: var(--font-display); font-weight: 500; font-size: 13px;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
  margin: 14px 0 8px;
}
.filter-row { display: flex; flex-wrap: wrap; gap: 7px; }
/* Clearing everything should not require opening the menu that set it. */
.chip.is-clear { color: var(--muted); }
.chip.is-clear:hover { color: var(--accent); border-color: var(--accent); }
.filter-row .chip { flex: none; }
.filter-n {
  font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.chip[aria-pressed="true"] .filter-n { color: inherit; opacity: .7; }
.filter-foot {
  display: flex; gap: 8px; align-items: center;
  padding: 12px 18px calc(env(safe-area-inset-bottom) + 16px);
  border-top: 1px solid var(--rule);
  flex: none;
}

/* The same news as .list-more, said quietly at the top of the page. See
   renderHeadNote(): a tail that is a kind of place rather than more of the
   same is named beside the filters, where the reader is still deciding what
   the list holds. One line, no box, and the control is three underlined
   words rather than a full-width button - it does not have to compete for
   attention at the top of a page the way it does at the foot of one. */
.head-more {
  margin: 8px 0 0;
  font-size: 12.5px; line-height: 1.45; color: var(--ink-2);
}
.head-more button {
  padding: 0; border: 0; background: none; color: var(--accent);
  font: inherit; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.head-more button:hover { text-decoration-thickness: 2px; }
.head-more button:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px;
}

/* the long tail sits behind a filter; say so rather than hiding it */
/* An underlined word in a grey paragraph is a footnote, and these are not
   footnotes - they are the way to see seventeen things the list is holding
   back. The sentence explains, the button below it is the control. */
.list-more {
  margin: 14px 0 4px; padding: 12px 14px;
  border: 1px solid var(--rule); border-radius: 12px;
  background: var(--paper-2);
  font-size: 13.5px; line-height: 1.5; color: var(--ink-2);
}
.chips-venue { padding-top: 0; }
.list-more button {
  display: block; width: 100%; margin-top: 10px;
  padding: 9px 14px; border: 1px solid var(--rule-2); border-radius: 999px;
  background: var(--raised); color: var(--ink);
  font: inherit; font-size: 13.5px; font-weight: 500; cursor: pointer;
  transition: background .15s, border-color .15s;
}
.list-more button:hover { border-color: var(--accent); color: var(--accent); }
.list-more button:active { background: var(--card-hover); }
.list-more button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* showModal() sends focus to the first focusable descendant, and browsers
   paint :focus-visible for it — so opening the filters menu ringed the first
   chip in accent, which reads as selected when it is not. Focus the heading
   instead: nothing looks pressed, and a screen reader announces the title. */
.dlg-title:focus, .dlg-title:focus-visible { outline: none; }

/* --- dialogs ------------------------------------------------------------
   Both dialogs were --paper on a --paper page with border:0 and no shadow,
   over the UA's default rgba(0,0,0,.1) backdrop. In dark mode that is three
   identical near-blacks: no edge, no lift, and the sheet's text legible
   right through the gap. The scrim does most of the work; the border and
   shadow do the rest. */
dialog.dlg {
  border: 1px solid var(--rule-2);
  border-radius: 16px;
  padding: 0;
  width: 100%;
  max-width: min(460px, 92vw);
  /* Cap the DIALOG, not the list inside it. The inner scroller used to be
     pinned at 56svh while 234px of the screen went unused, which is what cut
     Step-free in half. */
  max-height: 88svh;
  flex-direction: column;
  overflow: hidden;
  background: var(--raised);
  color: var(--ink);
  box-shadow: 0 24px 60px -16px rgba(0, 0, 0, .55), 0 2px 10px rgba(0, 0, 0, .18);
}
dialog.dlg[open] { display: flex; }
/* Nothing behind a modal scrolls while it is open: a swipe that overshoots
   the menu's list should not carry the page away under it. */
html:has(dialog.dlg[open]) { overflow: hidden; }
dialog.dlg::backdrop {
  background: var(--scrim);
  backdrop-filter: blur(6px) saturate(.9);
  -webkit-backdrop-filter: blur(6px) saturate(.9);
}

.dlg-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 16px 14px 4px 18px;
}
.dlg-head > div { flex: 1; min-width: 0; }
.dlg-x {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; margin: -4px -2px 0 0;
  border: 0; border-radius: 999px;
  background: transparent; color: var(--muted);
  transition: background .15s, color .15s;
}
.dlg-x svg { width: 17px; height: 17px; }
.dlg-x:active { background: var(--card-hover); color: var(--ink); }
.dlg-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* the scrolling middle; flexes so nothing is clipped when there is room */
.dlg-body {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  padding: 0 18px 16px;
}

/* Footer actions are buttons, not chips. They sat directly under a wall of
   filter chips using the same component, so "Clear all" read as one more
   filter rather than an action. */
.btn-sec, .btn-pri {
  min-height: 44px; padding: 0 18px;
  border-radius: 10px; font-size: 14.5px; font-family: var(--font-ui);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s, color .15s, border-color .15s;
}
/* dismissing is not the primary action in the area picker, so it is a
   secondary button - but a lone one still fills the row */
.filter-foot .btn-sec:only-child { flex: 1; }
.btn-sec {
  flex: none;
  border: 1px solid var(--rule-2); background: transparent; color: var(--ink-2);
}
.btn-sec:active { background: var(--card-hover); }
.btn-pri {
  flex: 1;
  border: 1px solid var(--ink); background: var(--ink); color: var(--paper);
  font-weight: 500;
}
.btn-pri:active { opacity: .85; }
.btn-sec:focus-visible, .btn-pri:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* badges: filter facts worth seeing on the listing, not only in the menu */
.badges { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
.badge {
  display: inline-flex; align-items: center;
  padding: 3px 10px; border-radius: 999px;
  font-size: 12px; letter-spacing: .01em;
  color: var(--accent); border: 1px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}

/* --- 15. Prose pages ---------------------------------------------------- */
/* The privacy page is the first page on the site that is mostly words. The
   hero is built for a wordmark and a list, so give running text a measure. */
.prose { max-width: var(--measure); text-align: left; }
.prose h1 { margin-bottom: 24px; }
.prose h2 {
  font-family: var(--font-display); font-size: 19px; font-weight: 500;
  margin: 30px 0 8px; color: var(--ink);
}
.prose p, .prose li { font-size: 15.5px; line-height: 1.62; color: var(--ink-2); }
.prose ul { padding-left: 20px; margin: 8px 0; }
.prose li { margin: 6px 0; }
.prose a { color: var(--ink); text-underline-offset: 2px; }
.prose strong { color: var(--ink); font-weight: 500; }
/* The three marks, named in the About page's running text and drawn beside
   their words as the buttons draw them, in their own colours. */
.mark-i { display: inline-block; width: 1.05em; height: 1.05em; vertical-align: -.16em; margin-right: .16em; }
.mark-i.is-want { color: var(--ok); }
.mark-i.is-done { color: var(--done); }
.mark-i.is-loved { color: var(--accent); }
.mark-i.is-no { color: var(--muted); }
.prose-date { margin-top: 30px !important; font-size: 13px; color: var(--muted); }

/* --- 16. Dated runs ------------------------------------------------------ */
/* Filled, where every other badge is outlined. On something about to stop
   existing, that outranks what kind of thing it is - so it is first in the row
   and it does not look like the others. Form carries as much of this as
   colour, which matters for anyone who cannot separate the two. */
.badge.is-run { border-color: var(--rule-2); color: var(--muted); }
.badge.is-ending {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.badge.is-closing { border-color: var(--accent); color: var(--accent); }
.badge.is-ended {
  background: var(--rule); border-color: var(--rule); color: var(--muted);
}


/* --- menu ---------------------------------------------------------------
   Reuses the filter dialog's shell so it arrives with the same weight and the
   same close affordance; only the body differs. */
/* Anchored to the corner it was opened from, not to the middle of the screen.
   ui.js sets left/top so that the hamburger inside the dialog lands exactly on
   the one on the page; `inset: auto` is what stops the UA centring it first
   and fighting those numbers. */
.nav-dlg {
  max-width: min(400px, 92vw);
  position: fixed; inset: auto; margin: 0;
  transform-origin: top left;
}
.nav-dlg[open] { animation: navIn .17s cubic-bezier(.2, .9, .3, 1.05); }
@keyframes navIn {
  from { opacity: 0; transform: translate(-7px, -7px) scale(.965); }
}
@media (prefers-reduced-motion: reduce) { .nav-dlg[open] { animation: none; } }
/* The head is the hamburger and nothing else. "Browse" named a thing the
   reader was already looking at, and a separate close button asked them to
   learn a second control for what the first one already does: the button that
   opened this is still under the pointer, and pressing it again shuts it. */
.nav-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 8px 0;
}
/* The account control, in the head opposite the hamburger. A pill rather than
   a row: it is the one thing in this menu that is neither a place to go nor a
   setting to change, and among twelve destinations it read as a thirteenth. */
/* The address sits above the way out rather than beside it. Side by side, a
   long one ran straight into the pill with no gap to give up - the header had
   already spent its width - and shortening it there would have meant
   truncating the only thing on the line that identifies you. Stacked, the
   column takes what it needs and the button keeps its shape. */
.nav-acct-wrap {
  display: flex; flex-direction: column; align-items: flex-end; gap: 5px;
  flex: 0 1 auto; min-width: 0; margin-right: 4px;
}
.nav-acct {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  min-height: 34px; padding: 0 13px;
  border: 1px solid var(--rule-2); border-radius: 999px;
  font-size: 13px; color: var(--ink-2); text-decoration: none;
  white-space: nowrap;
  transition: border-color .15s, color .15s;
}
/* Which account, above the way out of it. Still the thing that gives way when
   there is not enough room, but now it only has to fit the column rather than
   the header minus a button. */
.nav-who {
  max-width: 100%; font-size: 12px; color: var(--muted); text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nav-acct svg { width: 15px; height: 15px; flex: none; color: var(--muted); }
.nav-acct:hover { border-color: var(--ink-2); color: var(--ink); }
.nav-acct:hover svg { color: var(--accent); }
.nav-acct:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nav-toggle { border-right: 0; border-radius: 999px; }
.nav-body { padding-top: 4px; padding-bottom: 4px; }
/* One row shape for everything in the menu: the links, and the two group
   headings and Settings, which open instead of travelling. */
.nav-link, .nav-sum {
  display: flex; align-items: center; gap: 12px;
  /* Padded and pulled back out, so the highlight on the current row has room
     to sit around the text while every row still lines up with the heading. */
  padding: 11px 10px; margin-inline: -10px; text-decoration: none; color: var(--ink);
  border-bottom: 1px solid var(--rule);
}
.nav-link:last-child { border-bottom: 0; }
.nav-i { width: 24px; height: 24px; flex: none; color: var(--accent); }
.nav-i svg { width: 24px; height: 24px; display: block; }
.nav-t { font-family: var(--font-display); font-size: 17px; font-weight: 500;
         letter-spacing: -.01em; }
.nav-link:hover { color: var(--accent); }
/* The page you are already on. Still listed, so the menu is a stable map of
   the site rather than a list that shifts as you move through it. */
.nav-link.g-food-drink .nav-i { color: var(--accent-2); }
.nav-link.g-food-drink:hover { color: var(--accent-2); }
.nav-link.g-themes .nav-i { color: var(--accent-3); }
.nav-link.g-themes:hover { color: var(--accent-3); }
.nav-link.g-what-s-on .nav-i { color: var(--event); }
.nav-link.g-what-s-on:hover { color: var(--event); }
/* Marked by a tint on the row rather than the words "You are here", which
   said in six characters' worth of reading what the tint says at a glance;
   aria-current on the link is what carries it to a screen reader. A plain
   full-width tint, with the rules above and below it kept as on every
   other row: the rounded block that sat inside the row, with the rules
   either side of it turned off, read as messy curves and a missing line
   (the user, 10 September 2026). */
/* Edge to edge: the row's own 10px reach left a pale margin either side of
   the tint (the user, 10 September 2026). The menu body's inline padding is
   18px, so the current row takes that back and pads the same amount, and a
   row inside a group keeps its 32px indent on top. Negative margins into the
   body's padding stay inside its scroll box, so nothing scrolls sideways. */
.nav-link.is-current { background: var(--selected); margin-inline: -18px; padding-inline: 18px; }
.nav-sub .nav-link.is-current { padding-left: 40px; }

.nav-foot { border-top: 1px solid var(--rule); padding: 12px 16px calc(env(safe-area-inset-bottom) + 12px); }

/* Short screens: scroll the menu as one thing instead of pinning the settings
   to the bottom of it. Pinned, four controls take 326px of a 640px phone and
   leave the category list 182px - three and a half rows of a ten-row menu,
   with the fourth clipped mid-name. The settings are worth reaching, not worth
   two thirds of a menu called Browse. The head stays put so the close button
   does not scroll away. */
@media (max-height: 740px) {
  /* dialog.nav-dlg, not .nav-dlg: the base rule above is dialog.dlg with
     overflow hidden, and a class alone lost to it, so on a phone with the
     browser's bars showing the menu clipped at the screen's edge and could
     not scroll - the swipe moved the page underneath instead (the user, 10
     September 2026). */
  dialog.nav-dlg { overflow-y: auto; overscroll-behavior: contain; }
  .nav-dlg .nav-body { overflow: visible; flex: 0 0 auto; }
  .nav-dlg .nav-foot { flex: 0 0 auto; }
  .nav-dlg .dlg-head {
    position: sticky; top: 0; z-index: 1; background: var(--raised);
  }
}

/* --- preference switches ------------------------------------------------
   Appearance and Mode share one component so two controls sitting together
   cannot drift apart, and one layout: label on its own line, buttons full
   width beneath it. Appearance used to sit inline beside its label while Mode
   stacked, because "Londoner" is too long to fit next to one - and two
   adjacent controls built differently read as two unrelated components. The
   wider one decides for both. */
/* General sibling, not adjacent: a control with an info panel puts a
   .pref-info between itself and the next row, which broke `+` and left
   Distances sitting tight under Mode while Mode was spaced from
   Appearance. */
.pref-row ~ .pref-row { margin-top: 14px; }
.pref-row .seg { margin-top: 7px; width: 100%; }
.pref-row .seg-opt { flex: 1; padding-inline: 6px; }
.pref-head { display: flex; align-items: center; gap: 5px; }
.pref-label { font-size: 13.5px; color: var(--muted); }
.seg {
  display: inline-flex; padding: 2px; gap: 2px;
  background: var(--paper-2); border: 1px solid var(--rule); border-radius: 999px;
}
.seg-opt {
  border: 0; background: none; cursor: pointer; color: var(--muted);
  font: inherit; font-size: 13px; padding: 6px 13px; border-radius: 999px;
  white-space: nowrap; transition: background .15s, color .15s;
}
/* Its own token, because the two themes need opposite things. On paper the
   pill is white against a grey track and a soft drop shadow lifts it. In the
   dark --raised (#22201B) sits three values off --paper-2 (#1F1D19) and a
   black shadow does nothing at all, so the selected option was very nearly
   invisible: there the pill steps up in lightness and takes a hairline. */
.seg-opt[aria-checked="true"] {
  background: var(--seg-on); color: var(--ink);
  box-shadow: var(--seg-on-lift);
}
.seg-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The information circle. A button rather than a hover tooltip: a phone has no
   hover, and this explains a control that is otherwise jargon. */
/* Under the control, not in the info panel: a download that takes half a
   minute has to report itself without the reader having opened anything. */
.pref-status {
  margin: 6px 0 0; font-size: 12px; line-height: 1.4; color: var(--muted);
}
.seg-opt:disabled { opacity: .55; cursor: default; }

/* An event naming the listing it happens at, above its write-up. */
.detail-at { margin: 0 0 10px; font-size: 14px; color: var(--ink-2); }
.detail-at a { color: var(--accent); }

.info-btn {
  width: 20px; height: 20px; padding: 0; display: grid; place-items: center;
  border: 0; background: none; color: var(--muted); cursor: pointer; border-radius: 50%;
}
.info-btn svg { width: 15px; height: 15px; }
.info-btn:hover, .info-btn[aria-expanded="true"] { color: var(--accent); }
.info-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pref-info {
  margin: 8px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--muted);
  background: var(--paper-2); border: 1px solid var(--rule);
  border-radius: 10px; padding: 9px 11px;
}

/* On the homepage there is no menu to put them in - the page is the menu - so
   they sit at the end of the grid, above the footer. */
.hero .pref-row:first-of-type { margin-top: 40px; }
.hero .pref-row, .hero .pref-info { max-width: 340px; }


/* --- your location ------------------------------------------------------
   Was an inline-styled div. It needs two parts now - the dot and a heading
   cone - so it belongs here with everything else that has a light and a dark
   value. The blue is deliberately not the site accent: this is the one mark
   on the map that is you rather than a place, and it should read as the
   convention every other map uses. */
.me { position: relative; width: 16px; height: 16px; }
.me-dot {
  position: absolute; inset: 0; border-radius: 50%;
  background: #2E7DF0; border: 3px solid var(--paper);
  box-shadow: 0 0 0 6px rgba(46,125,240,.18);
}
.me-cone {
  position: absolute; left: 50%; top: 50%; width: 96px; height: 96px;
  transform: translate(-50%, -50%); transform-origin: 50% 50%;
  opacity: 0; pointer-events: none; transition: opacity .25s;
}
.me.has-heading .me-cone { opacity: 1; }
/* The cone turns with you, so it must not also animate: a transition here
   would lag the compass by its own duration and read as drift. */
@media (prefers-reduced-motion: no-preference) {
  .me-cone { transition: opacity .25s; }
}

/* ---- areas prototype ---------------------------------------------------- */
.proto-note {
  margin: 10px 0 0; padding: 8px 10px;
  border: 1px dashed var(--rule-2); border-radius: var(--r);
  font-size: 12.5px; line-height: 1.5; color: var(--muted);
}
.proto-note strong { color: var(--ink-2); font-weight: 600; }
/* The menu row for a thing that is not finished says so, quietly. */
.nav-tag {
  font-style: normal; font-size: 10px; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--rule-2); border-radius: 999px;
  padding: 1px 6px; margin-left: 7px; vertical-align: 1px;
}

/* The clue line: what a listing is, for titles that do not say. Above the name
   on a pin, in the meta row on a card. */
/* What is showing, under a venue's name on its pin: quieter than the name,
   in the interface face, one line. */
.pin-sub {
  display: block; margin-top: 3px;
  font-family: var(--font-ui); font-weight: 400; font-size: 11.5px; line-height: 1.3;
  color: var(--paper); opacity: .78;
}
.pin-kind {
  display: block; font-family: var(--font-sans, inherit);
  font-size: 9px; font-weight: 600; letter-spacing: .11em;
  text-transform: uppercase; opacity: .55; margin-bottom: 3px;
}
.card-clue { color: var(--ink-2); }

/* The switches at the top of the chip row are not narrowing filters, so they
   do not wear the filled "pressed" look. Two of the three share the teal of a
   dated pin, and filled they would sit side by side as one wide teal block in
   the default view, where all three are on and nobody has chosen anything.
   On is a ring and a solid dot in the colour of the pins it shows; off is the
   same chip gone quiet, with the dot hollowed out. */
.chip.is-toggle[aria-pressed="true"] {
  background: var(--paper); color: var(--ink);
  border-color: var(--chip-hue, var(--rule-2));
}
.chip.is-toggle[aria-pressed="true"]::before { background: var(--chip-hue); }
.chip.is-toggle[aria-pressed="false"] {
  color: var(--muted); border-color: var(--rule-2); background: var(--paper);
}
.chip.is-toggle[aria-pressed="false"]::before {
  background: transparent; box-shadow: inset 0 0 0 1.5px var(--rule-2);
}


/* --- the long read ---------------------------------------------------------
   A button under the write-up, and the full-screen reader it opens. The
   button is deliberately not an .action pill: those are ways out of the page
   (maps, the venue's site) and this is a way further in, so it takes the
   width of the column and the display face. The reader is a modal dialog
   that fills the viewport with --paper: no border, no radius, nothing on
   screen but one column of serif at the site's reading measure. */
.read-open {
  display: flex; align-items: center; gap: 10px;
  width: 100%; margin: 4px 0 18px; padding: 13px 16px;
  border: 1px solid var(--rule-2); border-radius: 12px;
  background: var(--paper); color: var(--ink);
  font-family: var(--font-display); font-size: 16.5px; text-align: left;
  cursor: pointer; transition: border-color .15s, background .15s;
}
.read-open .i { width: 20px; height: 20px; flex: none; opacity: .8; }
.read-open-t { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* The kicker over a titled essay: small, spaced, in the interface face, so
   the title below it is what the eye lands on. */
.read-open-k {
  font-family: var(--font-ui); font-size: 11px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}
.read-open-n { line-height: 1.25; }
.read-open-m { font-family: var(--font-ui); font-size: 13px; color: var(--muted); }
.read-open:hover { border-color: var(--ink-2); }
/* In the head of a list - a theme's own essay - it sits between the blurb
   and the filters, with the blurb's air above it. */
#listHead .read-open { margin: 14px 0 12px; }
.read-open:active { background: var(--card-hover); }
.read-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

dialog.reader {
  border: 0; border-radius: 0; padding: 0; margin: 0;
  width: 100vw; max-width: 100vw; height: 100svh; max-height: 100svh;
  background: var(--paper); color: var(--ink);
  overflow-y: auto; overscroll-behavior: contain;
}
dialog.reader::backdrop { background: var(--paper); }
.reader-bar {
  position: sticky; top: 0; z-index: 1;
  display: flex; align-items: center; justify-content: space-between;
  max-width: calc(var(--measure) + 40px); margin: 0 auto; box-sizing: border-box;
  padding: max(10px, env(safe-area-inset-top)) 20px 6px;
  background: var(--paper);
}
/* The wordmark leads the bar, as it does on every other page, and the kicker
   sits beside it. The wordmark rather than a logo: find.london's mark is its
   name, set the same way here as in the top bar, dot and all. */
.reader-brand { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.reader-mark {
  font-family: var(--font-display); font-size: 16px; font-weight: 500;
  letter-spacing: -.01em; color: var(--ink); text-decoration: none;
}
.reader-mark b { font-weight: 500; color: var(--accent); }
.reader-kicker {
  font-family: var(--font-ui); font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.reader-kicker::before { content: "\00b7"; margin-right: 10px; color: var(--rule-2); }
.reader-body {
  max-width: calc(var(--measure) + 40px); margin: 0 auto; box-sizing: border-box;
  padding: 8px 20px calc(56px + env(safe-area-inset-bottom));
}
.reader-title:focus, .reader-title:focus-visible { outline: none; }
.reader-title {
  font-family: var(--font-display); font-weight: 500; font-size: 34px;
  line-height: 1.12; margin: 20px 0 8px; text-wrap: balance;
}
.reader-meta { font-family: var(--font-ui); font-size: 14px; color: var(--muted); margin: 0 0 30px; }
.reader-body p:not(.reader-meta) {
  font-family: var(--font-display); font-size: 19px; line-height: 1.62;
  color: var(--ink); margin: 0 0 1.15em; text-wrap: pretty;
}

/* The lead image in the reader. Width and height come from the data so the
   column does not jump as it loads; the caption carries the credit, because a
   Commons photograph is licensed, not free, and the licence asks for a name. */
.reader-fig { margin: 4px 0 28px; }
/* One placed inside the text rather than at the head of it: air above as
   well as below, and a tall one is held to a height a reader can see past. */
.reader-fig.is-inline { margin: 28px 0 30px; }
.reader-fig.is-inline img { max-height: 78vh; width: auto; max-width: 100%; }
/* A tall lead - a portrait opening an essay - is held the same way, and
   centred, so it does not stand in the left margin of a column it does not
   fill. */
.reader-fig.is-tall img { max-height: 78vh; width: auto; max-width: 100%; margin: 0 auto; }
.reader-fig img {
  display: block; width: 100%; height: auto; border-radius: 10px;
  background: var(--card-hover);
}
.reader-fig figcaption {
  font-family: var(--font-ui); font-size: 13.5px; line-height: 1.5;
  color: var(--muted); margin: 10px 2px 0;
}
/* The credit is owed, not read: it stays on the line but steps back from the
   caption, so the eye lands on what the picture shows and not on who took it.
   About 3.7:1 on paper, which is the large-text floor and enough for a line
   of small print that nobody needs to read to use the page. */
.reader-credit { color: color-mix(in srgb, var(--muted) 62%, var(--paper)); }
/* No underline. The credit is a link because the licence asks it to be one,
   not because anybody is meant to follow it, and a rule under every caption
   pulled the eye to the photographer's name instead of the photograph. */
.reader-credit a { color: inherit; text-decoration: none; }
.reader-credit a:hover,
.reader-credit a:focus-visible { text-decoration: underline; text-underline-offset: 2px; }

/* The photograph on the listing itself, above the write-up. Same block as the
   reader's, at the panel's width, with the credit in the caption. */
/* A listing named inside another listing's write-up. The same convention as
   the long read's marks: prose-coloured text under a dotted accent rule, so
   our own listings read differently from a link out to the web, which is
   prose with a hairline. */
.note-link {
  color: inherit;
  text-decoration: underline; text-decoration-style: dotted;
  text-decoration-color: var(--accent); text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.note-link:hover { background: var(--selected); border-radius: 3px; box-shadow: 0 0 0 2px var(--selected); }
.note-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }

.detail-fig { margin: 0 0 16px; }
.detail-fig img {
  display: block; width: 100%; height: auto; border-radius: 10px;
  background: var(--card-hover);
}
/* The panel's photograph is a button, because the file behind it is bigger
   than the panel. Kept to the shape of the image it wraps - no padding, no
   border, no background - so the card looks exactly as it did before the
   picture became clickable, plus a corner mark. */
.fig-zoom {
  display: block; position: relative; width: 100%; padding: 0; border: 0;
  background: none; cursor: zoom-in; border-radius: 10px;
}
.fig-zoom:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* Always there rather than on hover: on a touch screen there is no hover, and
   an affordance nobody can discover is not one. Quiet enough to read past. */
.fig-zoom-i {
  position: absolute; right: 8px; bottom: 8px; width: 15px; height: 15px;
  padding: 5px; border-radius: 6px; color: #fff;
  background: rgba(18, 16, 12, .5); opacity: .82;
  transition: opacity .15s, background .15s;
}
.fig-zoom:hover .fig-zoom-i { opacity: 1; background: rgba(18, 16, 12, .72); }

.detail-fig figcaption {
  font-family: var(--font-ui); font-size: 12.5px; line-height: 1.5;
  color: var(--muted); margin: 8px 2px 0;
}
/* A caption is written only when it says something the picture cannot, so
   most figures have none - and every renderer still emits the figcaption,
   because the lightbox reads its credit through closest('figure'). Empty, it
   was an 8px band of nothing under the photograph; so it takes no space. */
figcaption:empty { display: none; }

/* More photographs of the same place, under the lead one. A row that scrolls
   sideways rather than a grid that grows: four pictures of a street should
   cost the panel one band of height, whatever the number, and the write-up
   below should stay where the reader expects it. */
.detail-figs {
  display: flex; gap: 8px; margin: -8px 0 16px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.detail-figs::-webkit-scrollbar { display: none; }
.fig-more { flex: 0 0 auto; margin: 0; }
.fig-more .fig-zoom { width: auto; border-radius: 8px; }
/* One height for the row and each picture's own width within it, which the
   browser knows before the bytes arrive from the width and height the markup
   carries. Every tile used to be 108 by 76 with the picture cropped to fit,
   which showed the middle third of a portrait - an engraving of Equiano was
   a collar and a chin. Clamped at both ends: a panorama would otherwise run
   the row, and a tall portrait would thin to a sliver; only at those extremes
   does the crop still bite. */
.fig-more img {
  display: block; height: 76px; width: auto; min-width: 46px; max-width: 136px;
  object-fit: cover; border-radius: 8px; background: var(--card-hover);
}
/* Smaller than the lead picture's, and out of the way of a 108px thumbnail. */
.fig-more .fig-zoom-i { right: 4px; bottom: 4px; width: 11px; height: 11px; padding: 4px; }

/* The picture on its own, over the page. Sized by the file rather than by the
   viewport: the images are 1024px on the long edge, and a lightbox that
   stretched one to fill a 27-inch screen would show a softer picture than the
   card it was opened from. So the image is drawn at its natural size and only
   ever shrunk to fit. */
dialog.lightbox {
  border: 0; padding: 0; margin: 0;
  width: 100vw; max-width: 100vw; height: 100svh; max-height: 100svh;
  background: transparent; overflow: hidden;
}
dialog.lightbox[open] {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 26px; box-sizing: border-box;
}
/* On a touch screen the whole dialog is the swipe surface, dark included, so
   the browser must not read a drag on it as a scroll and cancel the pointer
   half way through. There is nothing to scroll here in any case. */
@media (hover: none), (pointer: coarse) { dialog.lightbox[open] { touch-action: none; } }
/* Nearly opaque, and blurred: at 92% the page read through it - the save
   tray's hint is large light type and it showed as a ghost behind the
   photograph. The picture should be the only thing in the room. */
dialog.lightbox::backdrop {
  background: rgba(10, 9, 7, .96); backdrop-filter: blur(4px);
}
.lightbox img {
  display: block; max-width: 100%; max-height: calc(100svh - 190px);
  width: auto; height: auto; border-radius: 8px;
  box-shadow: 0 20px 60px -20px rgba(0, 0, 0, .8);
  /* The gestures are ours: sideways through the set, down to close. Without
     this the browser takes the drag for a pan and the picture never moves. */
  touch-action: none; user-select: none; -webkit-user-select: none;
  -webkit-user-drag: none;
}
@media (prefers-reduced-motion: reduce) { .lightbox img { transition: none !important; } }
/* The caption, under the picture, in the house face and at a size that reads
   as words about the picture rather than as small print. */
.lightbox-cap {
  margin: 0; max-width: 620px; text-align: center;
  font-family: var(--font-display); font-weight: 400; font-size: 17px; line-height: 1.4;
  color: rgba(255, 255, 255, .88);
}
/* The credit, at the foot of the screen and out of the picture's way: the
   licence's line, in the interface face and a touch smaller than it was. */
.lightbox-credit {
  position: absolute; left: 26px; right: 26px; bottom: 12px; margin: 0;
  text-align: center; font-family: var(--font-ui); font-size: 11.5px; line-height: 1.4;
  color: rgba(255, 255, 255, .55);
}
/* Why the picture is small: the full-size file could not be fetched. Sits
   above the credit, in the same quiet voice. */
.lightbox-note {
  position: absolute; left: 26px; right: 26px; bottom: 34px; margin: 0;
  text-align: center; font-family: var(--font-ui); font-size: 12px; line-height: 1.4;
  color: rgba(255, 255, 255, .7);
}
/* The Save button a pending top-up offers, inside the status line. */
.pref-save {
  font: inherit; font-weight: 600; color: var(--accent); background: none; border: 0;
  padding: 0; cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}
/* As on the panel: the credit is a link because the licence asks it to be
   one, not because anybody is meant to follow it, so no rule under it until
   the pointer says otherwise. */
.lightbox-credit a { color: inherit; text-decoration: none; }
.lightbox-credit a:hover,
.lightbox-credit a:focus-visible { text-decoration: underline; text-underline-offset: 2px; }
/* Stepping through a set. Big targets at the edges of the screen, where a
   thumb reaches on a phone and the cursor already is on a desktop; hidden
   outright when there is only one picture, because an arrow that goes nowhere
   is worse than no arrow. */
.lightbox-step {
  position: absolute; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; padding: 0; border: 0; border-radius: 999px;
  background: rgba(255, 255, 255, .12); color: rgba(255, 255, 255, .9);
  cursor: pointer; transition: background .15s;
}
/* A set of one has nowhere to step to. ui.js hides both arrows for it, and
   the display above was overriding the hidden attribute - so a listing with
   one photograph offered a way to the next one, which was itself. */
.lightbox-step:hover { background: rgba(255, 255, 255, .24); color: #fff; }
.lightbox-step:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.lightbox-step svg { width: 22px; height: 22px; }
.lightbox-step.is-prev { left: 12px; }
.lightbox-step.is-next { right: 12px; }

/* Over the picture's top corner, not in the flow: the picture is the reason
   the dialog is open and it should have the room. */
.lightbox .dlg-x {
  position: absolute; top: 12px; right: 12px; margin: 0;
  color: rgba(255, 255, 255, .8);
}
.lightbox .dlg-x:hover, .lightbox .dlg-x:active {
  background: rgba(255, 255, 255, .14); color: #fff;
}

/* A photograph on the pin's label: a landscape crop above the name, so the
   card stays one name wide and the picture reads as a thumbnail, not a
   poster. The label loses its top padding to let the image sit flush.

   Desktop only. On a phone the map IS the screen rather than a panel beside
   one, so a label with a picture on it covers the streets around the pin -
   which is the thing the reader opened the map to see. finder.js checks the
   same 900px query before it sets src, so on a phone the bytes are not
   fetched either. */
@media (min-width: 900px) {
  /* A map of photographs. The picture is how you tell one place from another
     before opening it, and at 230px it was a stamp; at up to 440px it is the
     reason to look. Beautiful Streets had this alone for a while and it earns
     its place everywhere, so the flag that switched it on is gone.

     Fluid rather than flat, because the map is the window less a fixed panel:
     440px is twice the old size on a large screen and two thirds of the map on
     a small laptop, which is the reason a phone gets no picture at all - there
     is no hover to ask for one, and the listing's own photograph is already
     the width of the panel. The ceiling is what does the work: a label holding
     a photograph always sits at it, because the image's own width is what the
     label sizes itself to.

     The ceiling is for a 3:2 picture. Anything taller narrows the label
     instead of growing the card downwards: --shape, set by flShape() in
     ui.js, is 1 for a landscape photograph and 0.5 for a 3:4 one, so a
     portrait pin is a narrow tall card of the same height as its landscape
     neighbours rather than a band cut out of the middle of the picture.

     The width is set outright, not left to the picture. Sized by its
     contents, the label was its name's width until the photograph loaded and
     then grew to the cap - and finder.js measures it for the nudge that keeps
     it inside the map at the instant it shows, which was the narrow moment.
     A card measured at 240px and nudged accordingly then arrived at 440px
     centred on the same pin, with a hundred pixels of the Albert Hall under
     the edge of the map. The cap is the box from the start, the picture's
     aspect-ratio gives it its height before a byte has arrived, and what
     gets measured is what gets shown. max-width stays, because the base
     rule's 230px would otherwise win. */
  .pin-label.has-img {
    /* No overflow: hidden here. It clipped the picture to the card's corners
       and, with it, the tail that points at the dot - the one thing that
       says which pin a photograph belongs to on a crowded map. The picture
       rounds its own top corners instead, below. */
    padding-top: 0;
    --cap: calc(clamp(280px, 32vw, 440px) * var(--shape, 1));
    width: var(--cap); max-width: var(--cap);
  }
  .pin-thumb {
    /* max-width: none, because the global img rule caps images at 100% of the
       content box and this one is meant to run out over the padding. Capped,
       it stopped 22px short and left a dark strip down the right of the
       card. */
    display: block; width: calc(100% + 22px); max-width: none; margin: 0 -11px 7px;
    border-radius: 4px 4px 0 0;     /* the card's corners, on the picture that fills them */
    /* The picture's own ratio, from flShape(); 3:2 only when nothing said
       otherwise. object-fit stays: at the clamped extremes - a panorama, or
       a photograph taller than 5:8 - the ratio is not quite the file's and
       the crop is what takes up the difference. */
    aspect-ratio: var(--ar, 3 / 2); object-fit: cover; background: var(--rule-2);
  }
}
@media (max-width: 899px) {
  /* The element is still built, because a window can be resized across the
     breakpoint; it just never shows and never loads. */
  .pin-thumb { display: none; }
}
/* Not on the open listing's own pin. Its photograph is already in the panel
   an inch away, so the same picture on the label was a duplicate rather than
   a signpost; the label goes back to being a name. Every other pin keeps the
   picture on hover, which is where it earns its place - a glimpse of what you
   would be going to see, rather than a dot and a name. */
.pin.is-selected .pin-thumb { display: none; }
.pin.is-selected .pin-label.has-img { padding-top: 7px; width: max-content; }

/* The same place listed in another category as well. Sits with the "At
   <venue>" line, which answers the neighbouring question. */
.detail-also {
  margin: -6px 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--muted);
}
.detail-also a { color: var(--ink-2); }

/* Which of the three a What's On row is, in the colour its pin and its chip
   already use. Bigger and saturated so it does not read as the 3px grey
   separator beside it. */
.card-kind {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--accent);
}
.card-kind.is-event { background: var(--event); }
.card-kind.is-exhibition { background: var(--exhibit); }

/* ---- what you have chosen ------------------------------------------------
   A row that only exists once something is on, holding one chip per filter
   set in the menu. Those were invisible out here before: twenty of them
   behind a button, and the only trace was a number on it. */
.chips-active {
  border-top: 1px solid var(--rule);
  margin-top: 2px; padding-top: 8px; align-items: center;
}
.chip.is-drop {
  background: var(--selected); border-color: transparent; color: var(--ink);
  padding-right: 9px; gap: 7px;
}
.chip.is-drop:hover { border-color: var(--accent); }
.chip-x {
  font-size: 15px; line-height: 1; color: var(--muted); margin-top: -1px;
}
.chip.is-drop:hover .chip-x { color: var(--accent); }

/* Not a chip. It is the one control here that does something rather than
   being something you can turn on, and as a pill it read as another filter. */
.btn-reset {
  margin-left: auto; padding: 6px 2px; min-height: 32px;
  font-size: 13px; color: var(--muted);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--rule-2);
}
.btn-reset:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* An off switch is excluding results, so it should look excluded rather than
   merely unselected: a dashed edge, the dot hollowed out. */
.chip.is-toggle[aria-pressed="false"] { border-style: dashed; }

/* Nearby's chips are not switches at all. They are radio buttons - one radius,
   one kind - wearing the toggle's clothes, and the toggle says the wrong thing
   twice: a dashed edge means "off, excluding results" when the others are
   merely not chosen, and the only mark of the chosen one was a solid edge in
   the same grey, which on a phone in daylight is nearly nothing (the user's
   Android screenshot, 10 September 2026). So the chosen one is filled, the way
   a chosen thing is filled everywhere else on the site. */
.chips[role="radiogroup"] .chip.is-toggle {
  border-style: solid; border-color: var(--rule-2); color: var(--ink-2);
}
.chips[role="radiogroup"] .chip.is-toggle[aria-checked="true"] {
  background: var(--selected); border-color: var(--accent);
  color: var(--ink); font-weight: 500;
}

/* In the empty state the same chips sit under the sentence that named them.
   The surrounding block styles its buttons as underlined text links, which is
   right for "Reset everything" and wrong for a chip. */
.empty-chips { border-top: 0; margin: 10px 0 4px; padding-top: 0; justify-content: center; }
.empty-chips .btn-reset { display: none; }
.empty .chip.is-drop { text-decoration: none; }
.empty .chip.is-drop .chip-x { text-decoration: none; }

/* ---- saved -------------------------------------------------------------
   Three marks on a listing, kept on the device. Each earns its own colour
   when it is on and shares one quiet outline when it is not: a row of three
   filled buttons would shout louder than anything it sits under. */
/* Deliberately unlike everything around it. The rest of a listing is text and
   outlined pills; this is a filled tray of three segments sitting above the
   name, so it reads as a thing to press rather than another link to skim
   past. It is the only control on the page that is about the reader. */
.save-row {
  display: flex; gap: 5px; margin: 0 0 18px;
  padding: 5px; border-radius: 15px;
  background: var(--paper-2); border: 1px solid var(--rule);
}
.save-btn {
  /* Grow to share the room, but never below the label: forced equal thirds
     put "Want to go" on two lines while Done and Loved sat half empty. */
  flex: 1 1 auto; white-space: nowrap;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 7px; min-height: 42px; padding: 0 10px; border-radius: 11px;
  /* Three buttons in a tray, not three words in a box. Off has to look
     pressable or nobody presses it. */
  border: 1px solid var(--rule); background: var(--paper);
  font-size: 13px; font-weight: 500; color: var(--ink-2);
  transition: background .15s, color .15s, border-color .15s;
}
.save-btn svg { width: 16px; height: 16px; transition: transform .16s; }
.save-btn:active { background: var(--card-hover); transform: translateY(0); }

/* Not interested. No word on it - it is the only button here that is a no,
   and a fourth label put "Want to go" on two lines - so it takes a square and
   sits apart from the three, separated by the gap rather than by a rule. */
.save-btn.is-no {
  flex: 0 0 auto; width: 42px; padding: 0; margin-right: 3px;
  color: var(--muted);
}
.save-btn.is-no[aria-pressed="true"] {
  background: var(--ink-2); border-color: var(--ink-2); color: var(--paper);
}

/* Hover shows what pressing would do: the button takes on its own colour at
   the strength of a tint, lifts a millimetre, and the symbol moves in the way
   that symbol should - a flag is planted, a heart swells, a cross turns. The
   tint only appears while the button is off, because a pressed button is
   already wearing the full version of the same colour and dimming it on
   hover would read as switching off. */
@media (hover: hover) {
  .save-btn:hover { transform: translateY(-1px); }
  .save-btn:not([aria-pressed="true"]):hover {
    border-color: var(--ink-2); color: var(--ink);
    box-shadow: 0 2px 7px rgba(0, 0, 0, .07);
  }
  .save-btn.is-want:not([aria-pressed="true"]):hover {
    color: var(--ok); border-color: var(--ok);
    background: color-mix(in srgb, var(--ok) 11%, var(--paper));
  }
  .save-btn.is-done:not([aria-pressed="true"]):hover {
    color: var(--done); border-color: var(--done);
    background: color-mix(in srgb, var(--done) 11%, var(--paper));
  }
  .save-btn.is-loved:not([aria-pressed="true"]):hover {
    color: var(--accent); border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 11%, var(--paper));
  }
  /* Quieter than the three yeses: a no should never be the most inviting
     thing in the tray. */
  .save-btn.is-no:not([aria-pressed="true"]):hover {
    color: var(--ink-2); border-color: var(--muted);
    background: color-mix(in srgb, var(--ink) 6%, var(--paper));
    box-shadow: none;
  }
  .save-btn.is-want:hover svg  { transform: translateY(-2px); }
  .save-btn.is-done:hover svg  { transform: scale(1.14); }
  .save-btn.is-loved:hover svg { transform: scale(1.18); }
  /* A cross laid on its side is the same cross, so this is the spin and not
     the destination: you see it turn, and it lands where it started. */
  .save-btn.is-no:hover svg    { transform: rotate(90deg); }
}
@media (prefers-reduced-motion: reduce) {
  .save-btn, .save-btn svg { transition: none; }
  .save-btn:hover, .save-btn:hover svg { transform: none; }
}

.save-btn[aria-pressed="true"] { color: var(--accent-ink); }
.save-btn.is-want[aria-pressed="true"] {
  background: var(--ok); border-color: var(--ok);
}
.save-btn.is-done[aria-pressed="true"] {
  background: var(--done); border-color: var(--done); color: var(--accent-ink);
}
.save-btn.is-loved[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent);
}
.save-btn[aria-pressed="true"] svg { fill: currentColor; fill-opacity: .18; }

/* The tooltip on Not interested. The browser's own `title` took a second to
   appear, could not be styled, and never showed on a keyboard focus - on the
   one control in the tray with no word on it, that was the only explanation
   of what pressing it does, arriving late and only for a mouse. */
.save-btn.is-no { position: relative; }
.save-tip {
  /* Below the button, not above it. The tray is the first thing in #detail,
     which is the scrolling panel: a tooltip above the top button sits 30px
     outside that box and `overflow: auto` cuts it in half. Below, it opens
     over the listing's name, which is a transient overlay doing what a
     transient overlay is for. */
  position: absolute; top: calc(100% + 9px); left: -4px;
  z-index: 5; width: max-content; max-width: 15rem;
  padding: 7px 10px; border-radius: 8px;
  background: var(--ink); color: var(--paper);
  font-size: 12.5px; line-height: 1.35; font-weight: 400; text-align: left;
  white-space: normal;
  box-shadow: 0 6px 20px -6px rgba(0, 0, 0, .5);
  opacity: 0; visibility: hidden; transform: translateY(-3px);
  transition: opacity .14s, transform .14s, visibility .14s;
}
/* A tail, so it points at the button rather than floating over it. */
.save-tip::after {
  content: ""; position: absolute; bottom: 100%; left: 22px; margin-left: -5px;
  border: 5px solid transparent; border-bottom-color: var(--ink);
}
.save-btn.is-no:focus-visible .save-tip {
  opacity: 1; visibility: visible; transform: none;
}
@media (hover: hover) {
  .save-btn.is-no:hover .save-tip {
    opacity: 1; visibility: visible; transform: none;
  }
}
@media (prefers-reduced-motion: reduce) { .save-tip { transition: none; } }

/* Pressed. The title goes through, so the state is legible before a word of
   the banner is read. */
#detail.is-dismissed .detail-name {
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  text-decoration-color: var(--muted);
  color: var(--muted);
}
/* Directly under the tray that was just pressed and above the name it struck
   through: the explanation belongs where the eye already is. */
.dismiss-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: -6px 0 18px; padding: 12px 14px; border-radius: 12px;
  background: var(--paper-2); border: 1px solid var(--rule-2);
}
.dismiss-text {
  flex: 1 1 12rem; margin: 0; font-size: 13px; line-height: 1.45;
  color: var(--ink-2);
}
.dismiss-head { display: block; font-weight: 600; margin-bottom: 3px; }
.dismiss-n {
  font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink);
}
/* The loud half, deliberately. The countdown is the quiet confirmation of
   something reversible; the reversal is the only thing anybody needs to find
   in a hurry, so it is the one filled, full-height control in the block. */
.dismiss-undo {
  flex: 0 0 auto; min-height: 40px; padding: 0 20px;
  border: 0; border-radius: 11px; cursor: pointer;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-display); font-size: 15px; font-weight: 500;
  letter-spacing: -.01em;
  transition: filter .15s, transform .15s;
}
.dismiss-undo:hover { filter: brightness(1.08); transform: translateY(-1px); }
.dismiss-undo:active { transform: translateY(0); }
.dismiss-undo:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .dismiss-undo { transition: none; } }

/* A mark on the row and on the dot, in the colour it wears on the listing. */
/* Green is go, blue is done, terracotta is loved. Want to go carries the
   green because it is the live one - the thing you are still going to do.
   Done was near-black, on the theory that the archive should recede; on a
   map that is a black dot, which reads as a closure rather than as somewhere
   you went. Three colours that have to stay apart at 25px, and blue was the
   hue this palette had not spent. */
.card-mark { display: inline-flex; vertical-align: -3px; margin-left: 8px; }
.card-mark svg { width: 18px; height: 18px; }
.card-mark.is-want  { color: var(--ok); }
.card-mark.is-done  { color: var(--done); }
.card-mark.is-loved { color: var(--accent); }
.card-mark svg { fill: currentColor; fill-opacity: .18; }

/* A marked pin is the mark. The dot goes entirely rather than wearing a
   coloured ring: a ring said only that this one was different from its
   neighbours and made you open it to find out how, while the flag, the tick
   and the heart say which at map scale. Bigger than the 15px dot, because a
   symbol needs room where a disc does not, and it earns the space - there are
   never many of these on a map at once, and they are the ones you are looking
   for. */
.pin.is-saved-want::before,
.pin.is-saved-done::before,
.pin.is-saved-loved::before { display: none; }
.pin-glyph {
  grid-area: 1 / 1;
  width: 25px; height: 25px; border-radius: 50%;
  display: grid; place-items: center;
  border: 2px solid var(--paper);
  box-shadow: 0 1px 4px rgba(0,0,0,.35);
  color: var(--accent-ink);
  transition: transform .22s cubic-bezier(.2,.9,.3,1.2);
}
.pin-glyph svg { width: 14px; height: 14px; display: block; }
/* On an area map every pin wears its category's drawing, on a disc in the
   colour of the category's group - the same three the menu and the homepage
   use - so the map reads as things to do, food and drink, and themes. */
.pin.is-glyph::before { display: none; }
.pin.is-glyph .pin-glyph { background: var(--accent); }
.pin.is-glyph.is-g-food-drink .pin-glyph { background: var(--accent-2); }
.pin.is-glyph.is-g-themes .pin-glyph { background: var(--accent-3); }
.pin.is-glyph.is-g-what-s-on .pin-glyph { background: var(--event); }
.pin.is-saved-want  .pin-glyph { background: var(--ok); }
.pin.is-saved-done  .pin-glyph { background: var(--done); }
.pin.is-saved-loved .pin-glyph { background: var(--accent); }
/* The dot's own states, on the shape that replaced it. Smaller multipliers:
   a 15px disc can grow by half and still be a dot, where 25px doing the same
   would cover the street it is standing on. */
.pin:active .pin-glyph      { transform: scale(.9); }
.pin.is-hovered .pin-glyph  { transform: scale(1.16); }
.pin.is-selected .pin-glyph { transform: scale(1.3); }
/* Filtered out, but still marked: the shape stays so the map does not appear
   to lose it, and the colour goes with everything else that is dimmed. */
.pin.is-dimmed .pin-glyph {
  background: var(--rule-2); color: var(--muted); box-shadow: none;
}
@media (prefers-reduced-motion: reduce) { .pin-glyph { transition: none; } }

/* The three chips on /saved/, each in the colour its mark wears everywhere
   else. Off-state comes from .chip.is-toggle: dashed, and quiet. */
.chip.is-mark-want[aria-pressed="true"]  { border-color: var(--ok); color: var(--ink); }
.chip.is-mark-done[aria-pressed="true"]  { border-color: var(--done); color: var(--ink); }
.chip.is-mark-loved[aria-pressed="true"] { border-color: var(--accent); color: var(--ink); }
.chip.is-mark-want[aria-pressed="true"] svg  { color: var(--ok); }
.chip.is-mark-done[aria-pressed="true"] svg  { color: var(--done); }
.chip.is-mark-loved[aria-pressed="true"] svg { color: var(--accent); }
.chip.is-mark-loved[aria-pressed="true"] svg,
.chip.is-mark-done[aria-pressed="true"] svg { fill: currentColor; fill-opacity: .18; }

/* ---- advanced, at the foot of the saved page ----------------------------
   The controls somebody goes looking for rather than arrives wanting: what
   they dismissed, a copy of the lot as a file, and starting again. Under the
   same heading the list's own sections use, so the page has one voice. */
.saved-adv { margin-top: 30px; }
.saved-clear { margin: 18px 0 8px; }
/* Outlined rather than filled. It is the most destructive thing on the site
   and should not be the loudest: a red button sitting there invites the press
   it is warning about. The warning arrives after the first press, when it is
   the only thing being asked. */
.saved-clear-btn {
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--paper));
  background: none; color: var(--accent);
  border-radius: 999px; padding: 8px 16px; font-size: 13px; cursor: pointer;
}
.saved-clear-btn:hover { border-color: var(--accent);
                         background: color-mix(in srgb, var(--accent) 8%, var(--paper)); }
.saved-clear-sure {
  padding: 14px; border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--paper));
  background: color-mix(in srgb, var(--accent) 9%, var(--paper));
}
.saved-clear-warn {
  margin: 0 0 12px; font-size: 13.5px; line-height: 1.5; color: var(--ink);
  max-width: 40rem;
}
.saved-clear-row { display: flex; gap: 8px; flex-wrap: wrap; }
.saved-clear-go {
  min-height: 40px; padding: 0 18px; border: 0; border-radius: 11px;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-display); font-size: 15px; font-weight: 500;
  letter-spacing: -.01em; cursor: pointer;
}
.saved-clear-go:hover { filter: brightness(1.08); }
.saved-clear-go[disabled] { opacity: .6; cursor: default; }
.saved-clear-no {
  min-height: 40px; padding: 0 16px; border: 1px solid var(--rule-2);
  border-radius: 11px; background: var(--paper); color: var(--ink-2);
  font-size: 14px; cursor: pointer;
}
.saved-clear-no:hover { border-color: var(--ink-2); color: var(--ink); }

/* ---- not interested, at the foot of the saved page ----------------------
   Quiet on purpose: a line of text with a count, shut until asked. It has to
   be findable, so it is always there once there is anything in it, and it has
   to be unremarkable, because it is a list of things somebody has already
   said no to. */
/* No rule of its own, and no 26px pushing it away from one. It carried both
   from when it sat alone at the foot with nothing above it; under the Advanced
   heading, which draws its own line, that was a second line twenty-six pixels
   below the first. */
.saved-dismissed { margin: 4px 0 8px; }
.saved-dis-sum {
  cursor: pointer; list-style: none; font-size: 13px; color: var(--muted);
  padding: 4px 0; display: flex; align-items: center; gap: 6px;
}
.saved-dis-sum::-webkit-details-marker { display: none; }
.saved-dis-sum::after { content: "\2304"; margin-top: -6px; }
.saved-dismissed[open] .saved-dis-sum::after { content: "\2303"; margin-top: 4px; }
.saved-dis-sum:hover { color: var(--ink-2); }
.saved-dis-row {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 8px 0; border-bottom: 1px solid var(--rule); font-size: 14px;
}
.saved-dis-row:last-child { border-bottom: 0; }
.saved-dis-name { color: var(--ink); text-decoration: none; font-weight: 500; }
.saved-dis-name:hover { text-decoration: underline; }
.saved-dis-cat { color: var(--muted); font-size: 12.5px; }
.saved-dis-undo {
  margin-left: auto; border: 1px solid var(--rule); background: var(--paper);
  color: var(--ink-2); border-radius: 999px; padding: 4px 11px; font-size: 12.5px;
  cursor: pointer;
}
.saved-dis-undo:hover { border-color: var(--ink-2); color: var(--ink); }

/* ---- the account, at the top of the saved page --------------------------
   A list of saves opens by saying whether anything is keeping them. Three
   states and only one of them is a warning: somebody signed in is being told
   a fact, and somebody with nothing saved has nothing to lose yet.

   Built from --accent rather than a borrowed alert red. Terracotta IS this
   site's red, and importing a foreign one to say something urgent would make
   the urgency look like it came from somewhere else. */
.saved-warn {
  margin: 0 0 14px; padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--paper));
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 9%, var(--paper));
  color: var(--ink);
}
.saved-warn strong {
  display: block; margin-bottom: 3px;
  font-family: var(--font-display); font-weight: 500; font-size: 16px;
  letter-spacing: -.01em; line-height: 1.25; color: var(--accent);
}
.saved-warn p { margin: 0; font-size: 13.5px; line-height: 1.5; }
/* The site-wide box says the fact and then explains it, in two paragraphs. */
.saved-warn p + p { margin-top: 3px; }
/* The line under "Warning!": the fact itself, weightier than the sentence
   that explains it. */
.saved-warn .warn-lead { margin: 0 0 3px; font-size: 14px; font-weight: 600; }
.saved-warn a { color: var(--ink); font-weight: 600; }
.saved-warn a:hover { color: var(--accent); }
.saved-note-link { color: var(--accent); }

/* ---- saves at risk, everywhere else -------------------------------------
   The "!" on the way to Saved, past five marked places with no account, and
   the tip that says why on hover or keyboard focus. Terracotta, for the
   reason the box above gives: it is this site's red. The tip hangs under the
   pill's right-hand edge, because the pill sits at the right-hand edge of
   the page; in the menu it hangs under the word instead. */
.warn-mark {
  display: inline-grid; place-items: center; flex: none;
  width: 16px; height: 16px; margin-left: 1px; border-radius: 50%;
  border: 1.5px solid var(--accent); color: var(--accent);
  font-family: var(--font-display); font-weight: 700; font-size: 11px; line-height: 1;
}
.saved-link, .nav-link { position: relative; }
.warn-tip {
  position: absolute; top: calc(100% + 9px); right: 0; z-index: 6;
  width: max-content; max-width: 16rem;
  padding: 7px 10px; border-radius: 8px;
  background: var(--ink); color: var(--paper);
  font-family: inherit; font-size: 12.5px; line-height: 1.35; font-weight: 400;
  text-align: left; white-space: normal;
  box-shadow: 0 6px 20px -6px rgba(0, 0, 0, .5);
  opacity: 0; visibility: hidden; transform: translateY(-3px);
  transition: opacity .14s, transform .14s, visibility .14s;
}
.warn-tip::after {
  content: ""; position: absolute; bottom: 100%; right: 14px;
  border: 5px solid transparent; border-bottom-color: var(--ink);
}
.nav-link .warn-tip { right: auto; left: 44px; }
.nav-link .warn-tip::after { right: auto; left: 22px; }
/* Shown for the mark alone, not the whole row: hovering the Saved row on the
   way down the menu raised the tip over the next row and it took the click
   meant for Nearby (the user, 10 September 2026). The tip itself never takes
   the pointer. */
.warn-tip { pointer-events: none; }
.is-warn:focus-visible .warn-tip { opacity: 1; visibility: visible; transform: none; }
@media (hover: hover) {
  .is-warn .warn-mark:hover + .warn-tip { opacity: 1; visibility: visible; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .warn-tip { transition: none; } }

/* The banner, past fifteen: the box above, on every other page. In a sheet
   head it takes the margin the saved page gives it; on a document page it
   needs its own room under the top bar. */
.is-doc .saved-warn.is-site { margin: 18px 0 8px; }
.warn-later {
  margin: 8px 0 0; padding: 4px 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--muted);
  text-decoration: underline; text-underline-offset: 3px;
}
.warn-later:hover { color: var(--ink); }
/* What the callback came back saying, when it came back unhappy. */
.saved-acct-said {
  margin: 10px 0 0; padding: 10px 12px; border-radius: 10px;
  background: var(--selected); color: var(--ink);
  font-size: 13px; line-height: 1.45;
}

/* ---- a copy of the marks, as a file ------------------------------------
   Same quiet foot treatment as the dismissals above it: a line of text you
   open, not a control competing with the list. */
.saved-data { margin: 4px 0 8px; }
.saved-data-note {
  margin: 6px 0 10px; font-size: 13px; line-height: 1.5; color: var(--ink-2);
  max-width: 34rem;
}
.saved-data-row { display: flex; gap: 8px; flex-wrap: wrap; }
.saved-data-btn {
  border: 1px solid var(--rule-2); background: var(--paper);
  color: var(--ink); border-radius: 999px; padding: 7px 15px;
  font-size: 13px; cursor: pointer;
}
.saved-data-btn:hover { border-color: var(--ink-2); }
.saved-data-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.saved-data-status {
  margin: 9px 0 0; font-size: 12.5px; color: var(--muted); min-height: 1em;
  font-variant-numeric: tabular-nums;
}

/* ---- saved: a list, not a map ------------------------------------------
   The finder's shell is built for search - a map filling the screen with a
   sheet pulled over it. Nothing is being searched for here, so the sheet
   takes the whole shell and the map waits behind the toggle. */
/* List mode is a page, not a sheet: the whole panel scrolls, heading and
   chips and all. It used to keep the finder's fixed head over a scrolling
   body, and on a phone that head was half the screen - title, note, toggle,
   chips - with the finder's touch-action:none on it and nothing behind it to
   catch a swipe. A finger that started there moved nothing, and a list that
   only scrolls when you touch its lower half reads as a list that does not
   scroll. There is no map under this mode, so there is nothing for a grabber
   to reveal, and it goes. */
body.saved-page:not(.saved-map) .snap { display: none; }
body.saved-page:not(.saved-map) .sheet-scroll { overflow: hidden; }
body.saved-page:not(.saved-map) .sheet {
  height: 100%; border-radius: 0; box-shadow: none;
  padding-top: calc(env(safe-area-inset-top) + 58px);
  overflow-y: auto; -webkit-overflow-scrolling: touch;
}
body.saved-page:not(.saved-map) .sheet-body { flex: none; min-height: auto; overflow: visible; }
body.saved-page:not(.saved-map) .grabber { display: none; }
body.saved-page:not(.saved-map) .sheet-title,
body.saved-page:not(.saved-map) .sheet-sub { touch-action: auto; }
body.saved-page #map, body.saved-page .map-dim { display: none; }
body.saved-page.saved-map #map { display: block; }
/* On a phone the map and the list cannot both have the screen, so in map
   mode the panel is the finder's sheet exactly - the snaps, the scroller and
   the gesture from ui.js - rather than the fixed 46% panel it was, which could
   not be pulled up to read or pushed down to see. */
@media (max-width: 899px) {
  body.saved-page.saved-map .sheet { padding-top: 0; }
}
/* On a wide one they can, so Map means "and the map" rather than "instead of
   the list": the list keeps a column and the map takes the rest. Without
   this the phone's bottom sheet was being applied inside the desktop's 420px
   column, which pinned the panel to the foot of it with the whole column
   empty above. */
@media (min-width: 900px) {
  body.saved-page.saved-map .sheet-scroll {
    inset: 0 auto 0 0; width: 420px; border-right: 1px solid var(--rule);
  }
  body.saved-page.saved-map .sheet {
    height: 100%; max-height: none; position: relative;
    border-radius: 0; box-shadow: none; padding-top: 0;
  }
}

/* The rhythm above the list was twelve pixels under the note plus ten above
   this and two below - twenty-two over the toggle and almost none under it, so
   it read as attached to the chips rather than as a control of its own. Even
   now: the note carries the gap above it, this carries the gap below. */
.view-toggle {
  display: inline-flex; gap: 4px; padding: 4px; margin: 0 0 10px;
  border: 1px solid var(--rule); border-radius: 12px; background: var(--paper-2);
}
.view-btn {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 34px; padding: 0 13px; border-radius: 9px;
  font-size: 13px; color: var(--ink-2);
}
.view-btn svg { width: 15px; height: 15px; }
.view-btn[aria-pressed="true"] { background: var(--paper); color: var(--ink); box-shadow: var(--seg-on-lift); }

/* A section is a heading, a count and its own control. */
.saved-sec { margin: 0 0 10px; }
.saved-sec-head {
  display: flex; align-items: center; gap: 9px;
  padding: 20px 0 8px; border-bottom: 1px solid var(--rule); margin-bottom: 2px;
}
/* Same job as .detail-h on a listing, so the same treatment: these were the
   other small grey uppercase headings on the site, and leaving them behind
   would have made two conventions out of one. */
.saved-sec-h {
  font-family: var(--font-display); font-weight: 500;
  font-size: 19px; letter-spacing: -.01em; line-height: 1.2;
  color: var(--accent); margin: 0;
}
.saved-sec-n {
  font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.saved-select {
  margin-left: auto; font: inherit; font-size: 12.5px; color: var(--ink-2);
  background: var(--paper); border: 1px solid var(--rule-2);
  border-radius: 999px; padding: 5px 9px;
}

/* The view toggle, in the title row. The title row is baseline-aligned for the
   count beside the heading, so the toggle centres itself against the pair
   rather than dropping its own baseline onto theirs. */
body.saved-page .sheet-title #viewToggle {
  margin-left: auto; align-self: center; display: flex;
}
body.saved-page .sheet-title .view-toggle { margin: 0; }
/* And the account note, now the last thing in the head rather than the first.
   It carried no top margin because nothing was ever above it. */
body.saved-page #savedNote:not(:empty) { margin-top: 11px; }
body.saved-page #savedNote .saved-warn { margin-bottom: 4px; }
body.saved-page #savedNote .sheet-sub { margin-bottom: 2px; }
/* A mark with nothing in it. Still on the row, so the counts stay readable as
   a set, and inert, so pressing it cannot empty the page. */
.chip[disabled] { opacity: .42; cursor: default; }

/* ---- saved: the time-limited strip --------------------------------------
   Everything with dates, soonest first, in one panel at the top - and nothing
   with dates anywhere else on the page. A show that closes on Sunday and a
   museum somebody has meant to visit for two years are both saved, and only one
   of them is urgent; this is the whole of the difference.

   A panel rather than a section, because the page under it is a page and this
   is the one thing on it with a clock. A wash of paper-2 does that without
   another rule or another colour. */
.saved-band {
  margin: 2px 0 6px; padding: 2px 14px 8px;
  border-radius: 14px; background: var(--paper-2);
}
/* Small, uppercase and in the site's own red: two labels inside one panel, so
   they have to read as labels rather than as two headings, which would make
   the panel two panels. */
.saved-band-h {
  margin: 0; padding: 15px 0 0;
  font-size: 11.5px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--accent);
}
.saved-band .card { border-bottom-color: var(--rule-2); }
.saved-band .card:last-child { border-bottom: 0; }
/* The press, and the hover, full-bleed to the panel rather than to the list.
   `.card` inside `.sheet-body` stretches itself to the list's own padding, and
   inside a panel with padding of its own that ran the highlight out past the
   panel's rounded edge. */
.sheet-body .saved-band .card:active {
  width: auto; margin-left: -8px; margin-right: -8px;
  padding-left: 8px; padding-right: 8px;
  border-radius: 8px; background: var(--paper);
}
@media (hover: hover) {
  .sheet-body .saved-band .card:hover {
    width: auto; margin-left: -8px; margin-right: -8px;
    padding-left: 8px; padding-right: 8px;
    border-radius: 8px; background: var(--paper);
  }
}
.saved-band-more {
  display: block; width: 100%; margin: 8px 0 0; padding: 8px;
  border: 0; border-radius: 9px; background: none;
  font: inherit; font-size: 13px; color: var(--ink-2); cursor: pointer;
}
.saved-band-more:hover { background: var(--paper); color: var(--ink); }
/* What has already closed: a line with a count, shut. Kept because it is a
   record of what somebody meant to see, and quiet because it is the one thing
   on this page that cannot be acted on. */
.saved-ended { margin: 2px 0 0; }

/* ---- saved: intent, as an index ----------------------------------------
   One collapsible row per category, in the site's own order, with a small
   label wherever the group changes. Collapsed, the whole of what a reader owns
   is a screen of headings with counts on them.

   It replaced a section called Anytime: twenty-one museums, statues,
   galleries, theatres, streets and an antique market in one list, in the order
   they happened to be saved. Nobody opens this page wanting that. They open it
   wanting to go shopping, or wanting gelato, and each of those is a category
   the site already has. */
.saved-index { margin-top: 2px; }
.saved-grp {
  display: flex; align-items: baseline; gap: 10px;
  padding: 22px 0 4px;
}
.saved-grp-h {
  margin: 0; font-size: 11.5px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
}
/* The index's one control, on the first group's line - which is the index's
   top edge, so it reads as belonging to what is under it. */
.saved-openall {
  margin-left: auto; padding: 0; border: 0; background: none;
  font: inherit; font-size: 12.5px; color: var(--ink-2);
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.saved-openall:hover { color: var(--accent); }

.saved-cat { border-bottom: 1px solid var(--rule); }
.saved-cat-sum {
  display: flex; align-items: center; gap: 10px;
  min-height: 48px; padding: 11px 0;
  list-style: none; cursor: pointer;
}
.saved-cat-sum::-webkit-details-marker { display: none; }
.saved-cat-sum:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The category's own symbol, the one it wears on the homepage and on its pins.
   In ink rather than the accent: the name beside it carries the colour, as
   every other heading on the site does, and two terracotta marks per row down
   a fifteen-row index is a page in one colour. */
.saved-cat-i { display: inline-flex; flex: none; color: var(--ink-2); }
.saved-cat-i svg { width: 19px; height: 19px; }
/* Same treatment as .detail-h and the section headings, for the reason given
   there: what sits under these is a list of names in the same face, and at
   full strength the heading and its first row read as two rows. */
.saved-cat-n {
  font-family: var(--font-display); font-weight: 500;
  font-size: 19px; letter-spacing: -.01em; line-height: 1.2;
  color: var(--accent);
}
.saved-cat-c {
  font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums;
}
/* One drawing for both states, turned a quarter when the group opens. */
.saved-cat-chev {
  margin-left: auto; display: inline-flex; color: var(--rule-2);
  transition: transform .18s, color .18s;
}
.saved-cat-chev svg { width: 16px; height: 16px; }
.saved-cat[open] > .saved-cat-sum .saved-cat-chev {
  transform: rotate(90deg); color: var(--ink-2);
}
.saved-cat[open] > .saved-cat-sum { border-bottom: 1px solid var(--rule); }
.saved-cat-sum:hover .saved-cat-chev { color: var(--ink-2); }
/* The last row in an open group draws no rule: the group's own is right under
   it, and two hairlines four pixels apart read as a mistake. */
.saved-cat > .card:last-child { border-bottom: 0; }
@media (prefers-reduced-motion: reduce) { .saved-cat-chev { transition: none; } }

/* ---- saved: nothing saved ----------------------------------------------
   A blank page, because every control on this page operates on saved things
   and there are none (the user, 11 September 2026). Left-aligned rather than
   centred like the other empty states: this is the page, not an aside in it. */
body.saved-empty .sheet-body { border-top: 0; }
.saved-none { padding: 22px 0 10px; max-width: 30rem; }
.saved-none-h {
  margin: 0 0 10px; font-family: var(--font-display); font-weight: 500;
  font-size: 22px; letter-spacing: -.015em; line-height: 1.25; color: var(--ink);
}
.saved-none-p { margin: 0; font-size: 15px; line-height: 1.55; color: var(--muted); }
.saved-none-go { margin: 18px 0 0; font-size: 15px; }
.saved-none-go a { color: var(--accent); font-weight: 500; }

/* The list has the whole window on a wide screen, not the 420px column the
   finder keeps for its map. There is no map here until it is asked for, and
   a quarter-width list beside three quarters of nothing is not a layout.

   A reading column rather than the whole of it. 1040px was chosen for two
   columns of sections side by side; with one column of rows it set a card's
   verdict on a line a foot wide with a date at the far end of it. */
@media (min-width: 900px) {
  body.saved-page:not(.saved-map) .sheet-scroll {
    inset: 0; width: auto; border-right: 0;
  }
  body.saved-page:not(.saved-map) .sheet-head,
  body.saved-page:not(.saved-map) .sheet-body {
    max-width: 760px; margin-inline: auto; width: 100%;
  }
  body.saved-page .sheet { padding-top: 0; }
  body.saved-page:not(.saved-map) .sheet-head { padding-top: calc(env(safe-area-inset-top) + 62px); }
}

/* ---- homepage: the menu, and the way to Saved ---------------------------
   The hamburger is the same one every other page has; the heart is the only
   thing on this page that is about the reader rather than about London, so it
   is a quiet link in the corner rather than another card in the grid. */
/* The homepage's bar holds at the top of the window as the text pages' does.
   It keeps its own contents - the wordmark here is the heading immediately
   below it, at the size the page opens on, and a second small one above that
   would be the same word twice. */
/* The one .hero whose bar is inside it. Every other page puts `.doc-top` above
   `<main class="hero">` as a sibling, and `.doc-top + .hero` above drops the
   column's own safe-area term because the bar is carrying it. Here the bar is
   the column's first child, so nothing was dropping it and the installed app
   counted the inset twice: 59px of paper above a bar that then held itself
   another 59px clear of a status bar it was already clear of. That left the
   bar starting 78px down the page, still sticky, still 107px tall, with the
   wordmark passing through the band it occupies - which is what showed above
   the header on an iPhone (the user, 11 September 2026).

   The bar owns the inset. The column starts at the top of the window. */
.hero-app { padding-top: 0; }
.hero-top {
  position: sticky; top: 0; z-index: 6;
  display: flex; align-items: center; justify-content: space-between;
  margin: 0 0 18px; min-height: 40px;
  /* 4px above the hamburger was the right figure when the column held 19px of
     its own over this bar. The bar is flush to the top of the window now, as
     `.doc-top` is on every other page, so the air that was above it belongs
     inside it - and at the same distance from the status bar that the text
     pages hold their masthead at. */
  padding: calc(env(safe-area-inset-top) + 10px) 0 8px;
  background: var(--paper);
}
/* The rule down its right-hand side belongs to the brand pill on a finder
   page, where it divides the hamburger from the wordmark beside it. Nothing
   sits beside it here, so it was a line drawn against nothing. */
.hero-top .nav-btn {
  margin-left: -10px; border-right: 0; border-radius: 999px;
}
.saved-link {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 36px; padding: 0 13px; border-radius: 999px;
  border: 1px solid var(--rule-2); background: var(--paper);
  font-size: 13.5px; color: var(--ink-2); text-decoration: none;
}
.saved-link svg { width: 15px; height: 15px; color: var(--accent); }
.saved-link:hover { border-color: var(--accent); color: var(--ink); }
#savedCount { font-variant-numeric: tabular-nums; }

/* ---- the menu, folded --------------------------------------------------- */
/* No rule of its own: the group's hairline is drawn by whichever row ends it -
   the heading when shut, the last category when open - so every line in the
   menu is the same width. The <details> element does not carry the rows'
   10px negative margin, and its border stopped 10px short at each end. */
.nav-group { border-bottom: 0; }
/* Takes the row shape above and adds only what a summary needs. The group's
   own rule closes it when it is shut, so the heading drops its hairline then
   and takes it back when open, where it is separating a heading from the
   rows it owns rather than doubling a line. */
.nav-sum { cursor: pointer; list-style: none; }
.nav-group:not([open]) > .nav-sum { border-bottom-color: transparent; }
.nav-sum::-webkit-details-marker { display: none; }
.nav-sum:hover { color: var(--accent); }
.nav-sum.g-food-drink .nav-i { color: var(--accent-2); }
.nav-sum.g-food-drink:hover { color: var(--accent-2); }
.nav-sum.g-themes .nav-i { color: var(--accent-3); }
.nav-sum.g-themes:hover { color: var(--accent-3); }
.nav-sum.g-what-s-on .nav-i { color: var(--event); }
.nav-sum.g-what-s-on:hover { color: var(--event); }
.nav-sum-n { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.nav-chev { margin-left: auto; display: inline-flex; transition: transform .18s; }
.nav-chev svg { width: 16px; height: 16px; color: var(--muted); }
.nav-group[open] > .nav-sum .nav-chev { transform: rotate(180deg); }
/* Stepped in so the nesting is legible without a heading in a different face
   doing the work. Half the icon's width: enough that the two levels read as
   two, not so much that the sub-rows lose the left margin they share with
   everything else on the page. */
.nav-sub { padding-bottom: 6px; }
.nav-sub .nav-link { padding-left: 32px; }
.nav-sub .nav-link:last-child { border-bottom: 1px solid var(--rule); }
/* Nothing follows these, so nothing needs separating from them. */
.nav-body > .nav-group:last-child[open] .nav-sub .nav-link:last-child,
.nav-body > .nav-group:last-child:not([open]) > .nav-sum,
.nav-foot .nav-group:not([open]) > .nav-sum,
.nav-foot .nav-sub > :last-child { border-bottom-color: transparent; }
/* Settings sits in the foot, so it needs no rule of its own above it. */
.nav-foot .nav-group { border-bottom: 0; }
.nav-foot .nav-sum { padding-top: 4px; }
/* Air between the rule under Settings and the first label: the rows of
   links carry their own padding, the controls did not (the user, 10
   September 2026). */
.nav-foot .nav-sub { padding-top: 12px; }
@media (prefers-reduced-motion: reduce) { .nav-chev { transition: none; } }

/* The tray at the foot of the long read. A rule above it, so it reads as
   the end of the essay and the start of a decision, and the heading in the
   essay's own face. */
/* An area's essay carries more than a listing's: a standfirst under the
   title, a list of sources at the end, and a button back to the map. */
.reader-body .reader-deck {
  font-family: var(--font-display); font-style: italic; font-size: 20px; line-height: 1.45;
  color: var(--ink-2); margin: 0 0 16px;
}
.reader-body .reader-h { font-family: var(--font-display); font-weight: 500; font-size: 22px; margin: 44px 0 12px; }
.reader-sources { margin: 0; padding-left: 20px; font-size: 15px; line-height: 1.9; }
/* A link out to the web. Coloured text was the loudest thing in the column -
   louder than the marks, which are the links that actually do something here -
   so the accent now belongs to our own listings and a link off the site is the
   prose's own colour with a hairline under it. It colours on hover, where the
   reader has already asked. The Sources list keeps the accent: it is a list of
   links, below the essay, and nothing there is competing to be read past. */
.reader-body p a:not(.reader-cta-btn):not(.read-pop-name) {
  color: inherit; text-decoration: underline;
  text-decoration-color: var(--muted); text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.reader-body p a:not(.reader-cta-btn):not(.read-pop-name):hover,
.reader-body p a:not(.reader-cta-btn):not(.read-pop-name):focus-visible {
  color: var(--accent); text-decoration-color: var(--accent);
}
.reader-sources a {
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
}
/* A phrase in the essay that names a listing. Dotted rather than underlined,
   and in the rule colour rather than the accent, so it is distinguishable
   from a source link and quiet enough to read past. */
.read-link { position: relative; }
.read-mark {
  font: inherit; color: inherit; background: none; border: 0; padding: 0;
  cursor: pointer;
  /* Dotted and in the accent, under text that keeps the colour of the prose
     around it. A rule in the rule colour was invisible in a page of serif:
     the dots have to be seen from reading distance to be worth having. The
     source links are the other way round - accent text, solid underline - so
     the two are never mistaken for each other. */
  text-decoration: underline; text-decoration-style: dotted;
  text-decoration-color: var(--accent); text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.read-mark:hover, .read-mark[aria-expanded="true"] {
  background: var(--selected); border-radius: 3px;
  box-shadow: 0 0 0 2px var(--selected);
}
.read-mark:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
.read-pop {
  position: absolute; z-index: 6; top: calc(100% + 8px); left: 50%;
  transform: translateX(calc(-50% + var(--nudge, 0px)));
  /* Wide enough for the three marks on one line: they measure 286px, and a
     narrower card pushed Loved out through its own edge. */
  width: 312px; box-sizing: border-box; padding: 12px 12px 10px;
  background: var(--paper); border: 1px solid var(--rule); border-radius: 14px;
  box-shadow: 0 12px 34px -12px rgba(0,0,0,.4);
  font-family: var(--font-ui); font-size: 14px; line-height: 1.45;
  text-align: left; white-space: normal;
}
.read-link.is-above .read-pop { top: auto; bottom: calc(100% + 8px); }
.read-pop-cat {
  display: block; font-size: 11px; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
}
.read-pop-name {
  display: flex; align-items: center; gap: 6px; margin: 3px 0 11px;
  font-family: var(--font-display); font-size: 17px; line-height: 1.25;
  color: var(--ink); text-decoration: none;
}
.read-pop-name svg { width: 13px; height: 13px; flex: none; color: var(--muted); }
.read-pop-name:hover { color: var(--accent); }
.read-pop-name:hover svg { color: var(--accent); }
.read-pop .save-row { margin: 0; flex-wrap: wrap; gap: 4px; }
.read-pop .save-btn { padding: 0 8px; }
/* The way back to the map ends the essay, and the sources follow it as an
   appendix. A rule above the button because the piece has finished; without
   it the button crowded the last line and then the list crowded the button. */
.reader-cta {
  margin: 44px 0 0; padding-top: 28px; border-top: 1px solid var(--rule);
}
/* An anchor when the button goes somewhere, a form's submit button when its
   only job is to close the reader, so the two have to look the same: a button
   brings its own border, font and size, and an anchor brings none of them. */
.reader-cta-btn {
  display: inline-block; padding: 12px 22px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink); font-family: var(--font-ui);
  font-weight: 500; text-decoration: none;
  border: 0; font-size: 15px; line-height: 1.3; cursor: pointer;
}
.reader-cta-btn:hover { filter: brightness(1.06); }
.reader-cta-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.reader-foot { margin: 40px 0 0; padding-top: 24px; border-top: 1px solid var(--rule); }
.reader-body .reader-foot-h { margin: 0 0 14px; font-size: 19px; }
.reader-tray { max-width: 420px; }

/* --- the long reads, as a page --------------------------------------------
   The document shell's column is a measure for prose; a grid of cards wants
   the width the review grid had. Each card is the listing's photograph, its
   name, its verdict and the two ways in, with the same four-button tray the
   listing shows, so a reader marks a place from here the way they would
   there. */
main.hero.reads { max-width: 1100px; }
/* The masthead over it sits on the same left edge. The document shell centres
   its bar at the prose measure, which on this page left the menu a third of
   the way in from the cards' left edge. Only the width: the shared rule
   already spaces the pill and the Saved link apart and centres them. */
body.is-reads .doc-top-in { max-width: 1100px; }
/* The same masthead over a guide, at the guide's own column width. Keyed on
   is-reads alone, the guide page fell back to the bare flex default - brand
   and pill bunched at the left, in a column narrower than the title below,
   so the two left edges disagreed. */
body.is-guide .doc-top-in { max-width: 680px; }
/* The shelf's sections: the same face and colour as the homepage's headings. */
.read-h {
  margin: 36px 0 0; font-family: var(--font-display); font-weight: 500;
  font-size: 19px; letter-spacing: -.01em; line-height: 1.2; color: var(--accent);
}
.read-h + .read-grid { margin-top: 14px; }
/* A place that is just over an area's line, on the area's list. */

/* ---- an area's listings, folded ---------------------------------------
   The City holds 181 listings in 19 sections and ran to seventeen thousand
   pixels of panel. Shut, the same page is about twenty rows, and the counts
   are the summary: 34 What's On, 24 Monuments, 24 Coffee is the City before
   you have opened anything.

   The rows are built to be pressed - icon, name, count, chevron - in the
   same language as the menu's two group rows, because that is the control
   this is. The group headings above them are not pressable and are set as
   labels rather than rows, so the difference is visible before it is
   discovered. */
.area-secs { margin-top: 26px; border-top: 1px solid var(--rule); }
.area-group {
  font-family: var(--font-ui); font-size: 11px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
  margin: 20px 0 2px;
}
.area-secs > .area-group:first-of-type { margin-top: 14px; }
.area-sec { border-bottom: 1px solid var(--rule); }
.area-sum {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 2px; cursor: pointer; list-style: none;
  font-family: var(--font-display); font-size: 16px; color: var(--ink);
}
.area-sum::-webkit-details-marker { display: none; }
.area-sum:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 4px; }
.area-sum-i { display: inline-flex; flex: none; color: var(--accent); }
.area-sum-i svg { width: 19px; height: 19px; }
/* The category's drawing in its group's colour, the same three the homepage
   tiles and the map pins use, so a row here and a pin there are the same
   thing said twice. */
.area-sum.g-food-drink .area-sum-i { color: var(--accent-2); }
.area-sum.g-themes .area-sum-i { color: var(--accent-3); }
.area-sum.g-what-s-on .area-sum-i { color: var(--event); }
.area-sum-t { min-width: 0; }
.area-sum-n {
  margin-left: auto; font-family: var(--font-ui); font-size: 12.5px;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.area-chev { display: inline-flex; flex: none; color: var(--muted); transition: transform .18s; }
.area-chev svg { width: 17px; height: 17px; }
.area-sec[open] > .area-sum .area-chev { transform: rotate(180deg); }
.area-sec[open] > .area-sum { color: var(--accent); }
@media (hover: hover) {
  .area-sum:hover { color: var(--accent); }
  .area-sum:hover .area-sum-i { color: inherit; }
}
/* Indented under the row it belongs to, and given air at the foot so the
   last entry does not sit on the next row's rule. */
.area-sec > .detail-on { margin: 0 0 8px 29px; }
.area-sec > .detail-on a { padding: 9px 2px; }
.area-sec > .detail-on li:last-child a { border-bottom: 0; }

/* Nineteen sections is nineteen presses for a reader who wanted the whole
   area. Quiet, right-aligned, and rendered by the build only for the version
   that has JavaScript to make it work. */
.area-all {
  display: block; margin-left: auto; padding: 4px 2px;
  border: 0; background: none; cursor: pointer;
  font: inherit; font-family: var(--font-ui); font-size: 12.5px;
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
}
.area-all:hover { text-decoration-thickness: 2px; }
.area-all:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.read-grid {
  list-style: none; margin: 26px 0 0; padding: 0;
  /* 332 is the narrowest card whose tray still fits its four buttons on one
     line - 16px of padding each side, a border each side, and the 294px the
     tray measures. Below that the grid gives up a column rather than a word,
     and the measurement is checked in the browser, not trusted. */
  display: grid; gap: 18px; grid-template-columns: repeat(auto-fill, minmax(332px, 1fr));
}
.read-card {
  border: 1px solid var(--rule); border-radius: var(--r); background: var(--paper);
  display: flex; flex-direction: column; position: relative;
}
/* The card does not clip. It did, to round the photograph's corners, and the
   Not interested tooltip - which hangs below the tray at the card's foot -
   was cut off at the border. The photograph rounds its own corners instead,
   and a card with a tooltip up is lifted above the row of cards beneath it,
   which would otherwise be painted over the tip. */
.read-card:hover, .read-card:focus-within { z-index: 2; }
.read-pic {
  display: block; aspect-ratio: 3 / 2; background: var(--card-hover);
  overflow: hidden; border-radius: var(--r) var(--r) 0 0;
}
/* Cropped to the card's 3:2 from whatever shape the photograph is, and a tall
   one is cropped from a little above centre: a column, a spire and a tower
   all keep their subject in the upper half, and a centred crop of a portrait
   photograph showed the plinth and lost the point. */
.read-pic img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 35%; display: block; }
.read-body { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.read-kind { margin: 0; font-size: 12.5px; color: var(--muted); }
.read-name {
  margin: 0; font-family: var(--font-display); font-weight: 500;
  font-size: 21px; line-height: 1.2; letter-spacing: -.01em;
}
.read-name a { color: var(--ink); text-decoration: none; }
.read-name a:hover { color: var(--accent); }
.read-verdict { margin: 0; font-size: 14.5px; line-height: 1.5; color: var(--ink-2); }
.read-links { margin: 4px 0 10px; display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 13.5px; }
.read-links a { color: var(--accent); }
.read-links .read-go { font-weight: 500; }
/* The tray sits on the card's floor whatever the height of the text above,
   and is cut a size smaller than the listing's: a card is fifty pixels
   narrower than the panel the tray was drawn for, and at the listing's size
   the fourth word fell off the edge. */
.read-card .save-row { margin: auto 0 0; gap: 4px; }
.read-card .save-btn { padding: 0 7px; gap: 4px; font-size: 12.5px; min-height: 38px; border-radius: 10px; }
.read-card .save-btn svg { width: 14px; height: 14px; }
.read-card .save-btn.is-no { width: 40px; }
@media (max-width: 619px) {
  main.hero.reads { padding-left: 12px; padding-right: 12px; }
  .read-body { padding: 12px; }
}
/* Said no to: still on the shelf, struck through, one press from back. */
.read-card.is-dismissed .read-name a { text-decoration: line-through; color: var(--muted); }
.read-card.is-dismissed .read-pic img { opacity: .45; }

/* ---- guides -------------------------------------------------------------
   Text-heavy guides that are not long reads. The chrome and the shelf are the
   long reads', and the prose is set in the same face at the reader's size, so
   the two families read as one. What a guide has that an essay does not is a
   contents block, sub-headings, tables and small source citations, which is
   the difference between a thing you read once and a thing you come back to
   for one fact. */
.hero.guide { max-width: 680px; }
.guide-kicker {
  font-family: var(--font-ui); font-size: 12.5px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 14px;
}
.guide-kicker a { color: var(--accent); text-decoration: none; }
.guide-kicker a:hover { text-decoration: underline; }
.guide-title {
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(32px, 6vw, 44px); line-height: 1.08; letter-spacing: -.02em;
  margin: 0 0 14px; text-wrap: balance;
}
.hero .guide-deck {
  font-family: var(--font-display); font-style: italic; font-size: 20px;
  line-height: 1.45; color: var(--ink-2); margin: 0 0 12px;
}
.hero .guide-meta { font-family: var(--font-ui); font-size: 14px; color: var(--muted); margin: 0 0 26px; }

/* The contents. A list with hairlines rather than a boxed panel, so it reads
   as part of the page and not as a widget dropped onto it. Two columns where
   there is room, because thirteen sections in one column is a page of its
   own before the guide has started. */
.guide-toc { margin: 0 0 40px; padding: 14px 0 6px; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.guide-toc-h {
  font-family: var(--font-ui) !important; font-size: 11px !important; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted) !important; margin: 0 0 8px;
}
.guide-toc ol { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 28px; }
.guide-toc li { break-inside: avoid; padding: 5px 0; }
.guide-toc a {
  font-family: var(--font-display); font-size: 16.5px; line-height: 1.3;
  color: var(--ink); text-decoration: none;
}
.guide-toc a:hover { color: var(--accent); }
@media (max-width: 560px) { .guide-toc ol { columns: 1; } }

.guide-body h2 {
  font-family: var(--font-display); font-weight: 500; font-size: 26px; line-height: 1.15;
  letter-spacing: -.015em; margin: 48px 0 10px; padding-top: 22px;
  border-top: 1px solid var(--rule); scroll-margin-top: 18px; text-wrap: balance;
}
.guide-body h2:first-child { margin-top: 8px; padding-top: 0; border-top: 0; }
.guide-body h3 {
  font-family: var(--font-display); font-weight: 500; font-size: 20px; line-height: 1.25;
  margin: 30px 0 6px; scroll-margin-top: 18px; text-wrap: balance;
}
.hero .guide-body p {
  font-family: var(--font-display); font-size: 18px; line-height: 1.6;
  color: var(--ink); margin: 0 0 1.05em; text-wrap: pretty;
}
.guide-body strong { font-weight: 600; color: var(--ink); }
.guide-body ul { margin: 0 0 1.2em; padding-left: 22px; }
.guide-body li {
  font-family: var(--font-display); font-size: 18px; line-height: 1.55;
  color: var(--ink); margin: 0 0 .45em; text-wrap: pretty;
}
/* Links off the site, as the essays draw them: the prose's own colour with a
   hairline, coloured only on hover. */
/* A listing named in the prose carries the essays' dotted mark (.note-link), so
   the guide's own link look leaves those alone - and it is written with
   :where(), so it loses to the name and the picture in a listing row above,
   which are links too. */
.guide-body a:where(:not(.guide-src):not(.note-link)) {
  color: inherit; text-decoration: underline;
  text-decoration-color: var(--muted); text-decoration-thickness: 1px; text-underline-offset: 2px;
}
.guide-body a:where(:not(.guide-src):not(.note-link)):hover { color: var(--accent); text-decoration-color: var(--accent); }
/* The source behind a sentence. Small and quiet, set off from the sentence it
   follows, and in the UI face so it reads as apparatus rather than prose. */
.guide-src {
  font-family: var(--font-ui); font-size: 12px; color: var(--muted);
  text-decoration: none; margin-left: .35em; white-space: nowrap;
}
.guide-src::before { content: "\2014\00a0"; }
.guide-src:hover { color: var(--accent); text-decoration: underline; }

.guide-table { overflow-x: auto; margin: 6px 0 1.4em; }
.guide-table table {
  border-collapse: collapse; width: 100%;
  font-family: var(--font-ui); font-size: 14.5px; line-height: 1.5;
}
.guide-table th {
  text-align: left; font-weight: 600; color: var(--muted); font-size: 12px;
  letter-spacing: .06em; text-transform: uppercase;
  padding: 6px 10px 8px 0; border-bottom: 1px solid var(--rule-2);
}
.guide-table td { padding: 9px 10px 9px 0; border-bottom: 1px solid var(--rule); vertical-align: top; color: var(--ink-2); }
.guide-table td:first-child { color: var(--ink); font-weight: 500; white-space: nowrap; }
.guide-table td .guide-src { display: block; margin: 2px 0 0; }
.hero .guide-checked {
  margin: 44px 0 0; padding-top: 16px; border-top: 1px solid var(--rule);
  font-family: var(--font-ui); font-size: 13px; color: var(--muted);
}
/* The guide's photograph, between the meta line and the contents, drawn as
   the reader draws its lead picture. */
.guide-fig { margin: 0 0 30px; }
.guide-fig img { display: block; width: 100%; height: auto; border-radius: 10px; background: var(--card-hover); }
.guide-fig figcaption { font-family: var(--font-ui); font-size: 12.5px; line-height: 1.5; color: var(--muted); margin: 8px 2px 0; }

/* ---- The World in London: a map of flags ---------------------------------
   A person's pin is the flag of the country they are conventionally
   described by, on a disc the size of a category glyph; the card and the
   panel carry the same flag beside the name. Square flags from flag-icons
   (MIT), one sprite, reached with <use>, clipped to a circle here. */
.flag {
  width: 1em; height: 1em; display: inline-block; vertical-align: -.15em;
  clip-path: circle(50% at 50% 50%);
}
.pin.is-flag::before { display: none; }
.pin-flag {
  grid-area: 1 / 1; width: 25px; height: 25px; border-radius: 50%;
  display: grid; place-items: center; overflow: hidden;
  border: 2px solid var(--paper); background: var(--paper);
  box-shadow: 0 1px 4px rgba(0,0,0,.35);
  transition: transform .22s cubic-bezier(.2,.9,.3,1.2), opacity .2s;
}
.pin-flag .flag { width: 100%; height: 100%; display: block; clip-path: none; vertical-align: 0; }
.pin.is-hovered .pin-flag { transform: scale(1.16); }
.pin.is-selected .pin-flag { transform: scale(1.3); }
.pin.is-dimmed .pin-flag { filter: grayscale(1); opacity: .45; }
/* A marked pin is the mark, here as everywhere. */
.pin.is-saved-want .pin-flag,
.pin.is-saved-done .pin-flag,
.pin.is-saved-loved .pin-flag { display: none; }
.card-flag { width: 18px; height: 18px; margin-right: 7px; vertical-align: -3px; }
.fact-country .flag { width: 18px; height: 18px; margin-right: 6px; vertical-align: -4px; }
.fact-also { color: var(--muted); }

/* --- Contact form ---------------------------------------------------------
   The first page on the site that asks the reader to type. Same measure as
   the prose pages, one column, labels above fields: a form on a phone is read
   top to bottom, and a label beside its box is a label that wraps. The kind
   of message is a segmented control - the one already used for preferences -
   because three choices that change the questions underneath are a mode, not
   a field. */
.form { margin-top: 12px; }
.form fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.form legend { padding: 0; }
/* Its own rule after the fieldset reset, or the reset's margin wins and the
   control sits on the first question's label. */
.form fieldset.form-kind { display: flex; width: 100%; margin: 0 0 30px; }
.form-kind .seg-opt { flex: 1; padding-inline: 8px; }
.form-kind .seg-opt input { position: absolute; opacity: 0; width: 0; height: 0; }
.form-kind .seg-opt:has(input:checked) {
  background: var(--seg-on); color: var(--ink); box-shadow: var(--seg-on-lift);
}
.form-kind .seg-opt:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }
.form-kind .seg-opt { display: inline-flex; align-items: center; justify-content: center; }

.field { display: block; margin: 0 0 24px; }
.field > span, .field-head {
  display: block; margin-bottom: 8px;
  font-size: 14px; font-weight: 500; color: var(--ink);
}
.field small, .field-hint {
  display: block; margin-top: 7px;
  font-size: 12.5px; line-height: 1.45; color: var(--muted);
}
.field .opt { font-weight: 400; color: var(--muted); }
.form input[type="text"], .form input[type="url"], .form input[type="email"],
.form input[type="date"], .form input[type="search"], .form select, .form textarea {
  display: block; width: 100%; box-sizing: border-box;
  min-height: 44px; padding: 9px 12px;
  border: 1px solid var(--rule-2); border-radius: 10px;
  background: var(--raised); color: var(--ink);
  font: inherit; font-size: 16px; line-height: 1.4;
}
.form textarea { min-height: 120px; resize: vertical; }
.form select {
  appearance: none; -webkit-appearance: none; padding-right: 36px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2367624F' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 10l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 18px;
}
.form input:focus-visible, .form select:focus-visible, .form textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);
}
.form input:user-invalid, .form textarea:user-invalid { border-color: var(--accent); }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
/* A pair of dates under one question: the pair's own labels are lighter, and
   the hint belongs to the question, not to the second date. */
.field .form-row .field { margin-bottom: 0; }
.field .form-row .field > span { font-weight: 400; color: var(--muted); }
.field .form-row + .field-hint { margin-top: 8px; }
/* Signed in, the form says whose account rather than asking for the address
   again, in the second half of the row where the box was (the user, 10
   September 2026). */
.field-said { align-self: end; margin: 0 0 24px; font-size: 15px; color: var(--muted); }
.field-said b { font-weight: 600; color: var(--ink); }
@media (max-width: 420px) {
  .form-row { grid-template-columns: 1fr; }
  .field .form-row { gap: 12px 0; }
}

/* Photographs: the browser's own picker, dressed as the other boxes; the
   chosen files listed under it; the licence box a row of its own. */
.form input[type="file"] {
  display: block; width: 100%; box-sizing: border-box;
  padding: 9px 12px; border: 1px dashed var(--rule-2); border-radius: 10px;
  background: var(--raised); color: var(--ink-2); font-size: 14px;
}
.form input[type="file"]::file-selector-button {
  font: inherit; font-size: 14px; margin-right: 12px; padding: 6px 12px;
  border: 1px solid var(--rule-2); border-radius: 8px;
  background: var(--paper-2); color: var(--ink); cursor: pointer;
}
.form input[type="file"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* .form before .photo-list, or the prose page's own list rules indent it. */
.form .photo-list { list-style: none; margin: 10px 0 0; padding: 0; font-size: 13.5px; color: var(--ink-2); }
.form .photo-list li {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0; padding: 5px 0; font-size: 13.5px; border-top: 1px solid var(--rule);
}
.form .photo-list li span { min-width: 0; overflow-wrap: anywhere; }
.form .photo-list button {
  flex: none; border: 0; background: none; padding: 4px 0; font: inherit; font-size: 13px;
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.photo-list li:first-child { border-top: 0; }
.photo-list li.is-bad { color: var(--accent); }
.photos .check {
  display: flex; gap: 10px; align-items: flex-start; margin: 14px 0 0;
  font-size: 14px; line-height: 1.45; color: var(--ink); cursor: pointer;
}
.photos .check input { margin: 3px 0 0; flex: none; accent-color: var(--accent); }
.photos [data-photo-credit] { margin: 14px 0 0; }

/* Two-way choices inside a section - place or event, yes or no - are radio
   chips: the same shape the filters use, which readers already press. */
.choice { display: flex; flex-wrap: wrap; gap: 10px; }
.choice label {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 36px; padding: 0 13px; border-radius: 999px;
  border: 1px solid var(--rule-2); background: var(--raised);
  font-size: 14px; color: var(--ink-2); cursor: pointer;
}
.choice input { margin: 0; accent-color: var(--accent); }
.choice label:has(input:checked) { border-color: var(--accent); color: var(--ink);
  background: color-mix(in srgb, var(--accent) 9%, var(--raised)); }
.choice label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Which listing. A search box with its answers underneath, drawn from the
   site's own index once the reader starts typing; the chosen one is shown as
   a card with a way to change it, so the slug travels and the name is what
   the reader sees. */
.pick { position: relative; }
.pick-list {
  list-style: none; margin: 4px 0 0; padding: 4px;
  border: 1px solid var(--rule-2); border-radius: 10px; background: var(--raised);
  box-shadow: 0 8px 24px -10px rgba(23,21,15,.25);
  max-height: 260px; overflow-y: auto;
}
.pick-list button {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  padding: 8px 10px; border-radius: 7px; font: inherit; font-size: 14.5px;
  color: var(--ink); cursor: pointer;
}
.pick-list button:hover, .pick-list button:focus-visible { background: var(--card-hover); outline: 0; }
.pick-list button small { display: block; font-size: 12.5px; color: var(--muted); }
.pick-list .pick-none { padding: 8px 10px; font-size: 13.5px; color: var(--muted); }
.picked {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 12px; border: 1px solid var(--rule-2); border-radius: 10px;
  background: var(--paper-2);
}
.picked b { font-weight: 500; color: var(--ink); }
.picked small { display: block; font-size: 12.5px; color: var(--muted); }
.picked button { flex: none; border: 0; background: none; font: inherit; font-size: 13.5px;
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; padding: 4px 0; }

.form-foot { margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--rule); }
.form-foot .btn-pri { margin-top: 4px; }
.form-foot .btn-pri { width: 100%; }
.form-status { margin: 12px 0 0; font-size: 14px; color: var(--muted); min-height: 1.4em; }
.form-status.is-err { color: var(--accent); }
.form-thanks { margin-top: 12px; }
.form-thanks p { margin: 8px 0; }
/* ---- the feedback box ---------------------------------------------------
   Above the footer, on every page that has one. One line at rest, because it
   is asking for something nobody came here to give: the prompt, an empty
   line, and no button at all until there is something to send. ui.js opens it
   on focus and grows it with the text (the user, 10 September 2026). */
.fb { max-width: 640px; margin: 40px auto 0; padding: 0 24px; }
/* In the card list there is no page to be centred on and the scroller carries
   its own side padding, so it is a block at the end of the cards rather than
   a section of the page. */
.fb.fb-in-list { max-width: none; margin: 14px 0 4px; padding: 0; }
.fb-in-list .fb-form { padding: 12px 14px; }
.fb-form {
  padding: 14px 16px 14px; border: 1px solid var(--rule); border-radius: 12px;
  background: var(--paper-2);
}
.fb-head { margin: 0; font-size: 14px; font-weight: 600; color: var(--ink); }
.fb-head label { cursor: pointer; }
.fb-hint { margin: 2px 0 8px; font-size: 13px; line-height: 1.4; color: var(--muted); }
.fb-text {
  display: block; width: 100%; box-sizing: border-box;
  min-height: 40px; padding: 9px 11px;
  border: 1px solid var(--rule-2); border-radius: 8px;
  background: var(--paper); color: var(--ink);
  /* 16px, like every other field on the site, and for the reason in the
     reset: anything smaller and iOS zooms on focus and this page cannot be
     zoomed back. */
  font: inherit; font-size: 16px; line-height: 1.45;
  resize: none; overflow: hidden;
  transition: min-height .16s ease, border-color .15s;
}
.fb-text:focus { outline: none; border-color: var(--accent); }
/* Open on focus, and stay open while there is anything in it. ui.js sets the
   class, because :focus alone would collapse the box under the reader's
   cursor the moment they reached for the button. */
.fb-form.is-open .fb-text { min-height: 92px; }
.fb-text:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.fb-foot { display: flex; align-items: center; gap: 12px; margin-top: 10px; }
/* The contact form's Send, at the size this box wants. */
.fb-send {
  flex: 0 0 auto; min-height: 38px; padding: 0 18px; border-radius: 10px;
  font: inherit; font-size: 14px; font-weight: 500; cursor: pointer;
}
.fb-send:disabled { opacity: .45; cursor: default; }
.fb-msg { font-size: 13px; color: var(--muted); }
.fb-msg.is-err { color: var(--accent); }
/* After it is sent, in the space it filled. */
.fb-done { margin: 0; font-size: 14.5px; font-weight: 600; color: var(--ink); }
@media (prefers-reduced-motion: reduce) { .fb-text { transition: none; } }

/* The honeypot. Off the page, not display:none - a script checking for the
   latter would skip it. */
.form .hp, .fb-form .hp { position: absolute; left: -9999px; top: 0; height: 0; overflow: hidden; }

/* ---- /search/, the desk tool ------------------------------------------ */
.search-page { max-width: 44rem; }
.search-lede { color: var(--ink-2); }
.search-box {
  width: 100%; font: inherit; font-size: 18px; padding: 10px 12px; margin-top: 6px;
  border: 1px solid var(--rule); border-radius: 8px; background: var(--card); color: var(--ink);
}
.search-box:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
.search-count { color: var(--ink-2); font-size: 14px; margin: 10px 0 4px; min-height: 1.4em; }
.search-results { list-style: none; margin: 0; padding: 0; }
.search-hit { padding: 12px 0; border-bottom: 1px solid var(--rule); }
.search-hit.is-deep .search-name a { color: var(--ink-2); }
.search-name { font-size: 18px; margin: 0 0 2px; }
.search-name a { color: var(--ink); text-decoration: none; }
.search-name a:hover { text-decoration: underline; }
.search-path { margin: 0 0 2px; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; letter-spacing: .01em; }
.search-where, .search-tools { margin: 0; font-size: 13px; color: var(--ink-2); }
.search-verdict { margin: 4px 0 0; font-size: 15px; font-weight: 600; }
.search-snippet { margin: 4px 0 0; font-size: 14px; color: var(--ink-2); cursor: pointer; }
.search-snippet:hover { color: var(--ink); }
.search-note.is-open { margin: 4px 0 0; cursor: pointer; }
.search-note.is-open p { margin: 0 0 8px; font-size: 14px; color: var(--ink); }
.search-tools { margin-top: 4px; }
.search-tools a { color: var(--ink-2); }
mark.search-m { background: var(--selected); color: inherit; padding: 0 1px; border-radius: 2px; }


/* ---- the filter bar: one bubble per group -----------------------------
   Title over selection, a chevron, a menu under it; the whole row wraps.
   The dot colours are the map's: performing Shows, exhibitions, events,
   and the category's own colour for experiences. */
.bubs { display: flex; flex-wrap: wrap; gap: 7px; align-items: flex-start; margin: 0 -16px; padding: 2px 16px 4px; }
.bub-wrap { position: relative; flex: none; max-width: 100%; }
.bub {
  display: inline-flex; align-items: center; gap: 8px; min-height: 42px; max-width: 100%;
  padding: 5px 9px 5px 12px; border: 1px solid var(--rule-2); border-radius: 14px;
  background: var(--paper); color: var(--ink-2); text-align: left; cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.bub:active { background: var(--card-hover); }
.bub.is-set { border-color: var(--ink); color: var(--ink); }
.bub[aria-expanded="true"], .bub.is-near[aria-pressed="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.bub-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.bub-t { font-size: 10.5px; line-height: 1.1; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.bub[aria-expanded="true"] .bub-t, .bub.is-near[aria-pressed="true"] .bub-t { color: inherit; opacity: .7; }
.bub-v { font-size: 13.5px; line-height: 1.2; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 190px; }
.bub-chev { width: 14px; height: 14px; flex: none; opacity: .55; }
.bub-x { font-size: 17px; line-height: 1; margin-top: -1px; opacity: .6; }
.bub.is-venue:hover .bub-x { opacity: 1; }
.bub[aria-expanded="true"] .bub-chev { transform: rotate(180deg); opacity: .8; }
.bub-i { width: 15px; height: 15px; flex: none; }
.bub-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: 1px; background: var(--accent); }
.bub-dot.is-perform { background: var(--perform); }
.bub-dot.is-exhibition { background: var(--exhibit); }
.bub-dot.is-event { background: var(--event); }
/* One box, plain rows. The rows were pills with their own fills and
   corners, which stacked into a wall of rounded boxes (the user, 9
   September 2026): now the menu is the only shape, a row is a line of text
   with its tick, and hover is the only fill. */
.bub-menu {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0;
  min-width: 232px; max-width: 320px; max-height: min(60vh, 440px); overflow: auto;
  padding: 6px 0; border: 1px solid var(--rule); border-radius: 12px;
  background: var(--raised, var(--paper)); box-shadow: 0 14px 36px -10px rgba(0,0,0,.4);
}
.bub-opt {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 16px 9px 14px;
  border: 0; border-radius: 0; background: none; font: inherit; font-size: 13.5px;
  color: var(--ink); text-align: left; cursor: pointer;
}
.bub-opt:hover { background: var(--card-hover); }
.bub-opt[aria-pressed="true"] { font-weight: 500; }
.bub-opt.is-empty { color: var(--muted); }
/* An annual event between one year and the next: quieter than a live date. */
.runs-returns { color: var(--muted); }
.badge.is-run.is-returns { background: var(--paper-2); color: var(--ink-2); border: 1px solid var(--rule-2); }
.bub-opt.is-group { font-weight: 500; }
.bub-opt.is-sub { padding-left: 38px; }
.bub-opt .tick { width: 14px; height: 14px; flex: none; visibility: hidden; }
.bub-opt[aria-pressed="true"] .tick { visibility: visible; }
.bub-opt-l { flex: 1; min-width: 0; }
.bub-opt .filter-n { margin-left: auto; padding-left: 10px; }
.bub-opt.is-any { color: var(--muted); }
.bub-opt.is-any[aria-pressed="true"] { color: var(--ink); }
.bub-sep { height: 1px; background: var(--rule); margin: 6px 0; }
.bub-foot { display: none; }
.bub-scrim { display: none; }
.bubs .btn-reset { align-self: center; }
@media (max-width: 899px) {
  .bub-scrim { display: block; position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.35); }
  .bub-menu {
    position: fixed; z-index: 61; left: 0; right: 0; bottom: 0; top: auto;
    max-width: none; max-height: 70vh; border-radius: 18px 18px 0 0;
    padding: 10px 0 calc(12px + env(safe-area-inset-bottom));
  }
  .bub-opt { padding: 11px 20px; }
  .bub-foot { display: block; padding: 10px 16px 0; }
  .bub-foot button {
    display: block; width: 100%; padding: 10px 14px; border: 1px solid var(--rule-2);
    border-radius: 999px; background: var(--ink); color: var(--paper);
    font: inherit; font-size: 14px; font-weight: 500; cursor: pointer;
  }
}
.card-kind.is-perform { background: var(--perform); }

/* A sub-heading under "At related venues": one per sibling house. */
.detail-h-sub { font-size: 15px; margin-top: 12px; }
.detail-h-sub a { color: inherit; text-decoration: none; }
.detail-h-sub a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ---- the account page ---------------------------------------------------
   Who this browser is signed in as, said in a sentence with the ways in and
   out beside it, and then the answers Swipe Mode has gathered - by category,
   in the collapsible index the saved page draws, each answer a control so
   that changing it is the same gesture as reading it. */
.acct-line { margin: 0 0 10px; }
.acct-actions { display: flex; flex-wrap: wrap; gap: 8px 14px; margin: 0 0 4px; }
.acct-link {
  display: inline-flex; align-items: center; min-height: 38px; padding: 0 16px;
  border: 1px solid var(--rule-2); border-radius: 999px;
  color: var(--ink); text-decoration: none; font-size: 14px;
}
.acct-link:hover { border-color: var(--ink-2); background: var(--card-hover); }
.acct-link.is-primary { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.acct-link.is-primary:hover { background: var(--ink); opacity: .9; }
/* The section head: the heading with its count, and the way into Swipe Mode
   at the far end - the one button on the page that goes somewhere. */
.acct-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 34px 0 4px;
}
.prose .acct-head h2 { margin: 0; flex: 1 1 auto; }
.acct-head .count { margin-left: 8px; }
.btn-swipe {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 42px; padding: 0 18px; border-radius: 11px;
  background: var(--accent); color: var(--accent-ink); text-decoration: none;
  font-family: var(--font-display); font-size: 15.5px; font-weight: 500;
  letter-spacing: -.01em;
}
.btn-swipe svg { width: 18px; height: 18px; }
.btn-swipe:hover { filter: brightness(1.08); }
.btn-swipe:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.acct-none { color: var(--muted); }
/* One row: the words, then the answer as three controls. Wraps on a narrow
   screen with the controls on their own line, right-aligned under the
   question, rather than squeezing a long question into a column. */
.acct-row {
  display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap;
  padding: 10px 0; border-bottom: 1px solid var(--rule);
}
.acct-cat > .acct-row:last-child { border-bottom: 0; padding-bottom: 14px; }
.acct-q { flex: 1 1 240px; min-width: 0; font-size: 15px; line-height: 1.4; color: var(--ink); }
.acct-ans { display: inline-flex; align-items: center; gap: 6px; flex: none; margin-left: auto; }
.acct-opt {
  min-height: 32px; padding: 0 12px;
  border: 1px solid var(--rule-2); border-radius: 999px;
  background: var(--paper); color: var(--ink-2);
  font: inherit; font-size: 13px; line-height: 1; cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.acct-opt[aria-pressed="true"] {
  background: var(--selected); border-color: var(--accent); color: var(--ink);
  font-weight: 500;
}
.acct-opt.is-x { width: 32px; padding: 0; display: inline-grid; place-items: center; color: var(--muted); }
.acct-opt.is-x svg { width: 13px; height: 13px; }
@media (hover: hover) {
  .acct-opt:hover { border-color: var(--ink-2); color: var(--ink); }
}
.acct-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.acct-skipped { font-size: 12px; color: var(--muted); margin: 0 2px; }
/* The category rows borrow the saved page's index; in prose their names
   would otherwise pick up the paragraph size. */
.prose .saved-cat-n { font-size: 18px; }
.prose .saved-grp-h { margin: 0; }

/* ---- Swipe Mode ---------------------------------------------------------
   A deck of cards filling the screen, the top one under the finger. The
   card is a photograph with the question over its foot; beneath the deck,
   Skip and Undo in the small voice, because they are the exceptions and the
   swipe is the rule. The stack is drawn with one transform per card - drag,
   tilt, depth - written by swipe.js as custom properties, so the CSS owns
   how a card moves and the script only says where it is. */
.swipe-shell {
  position: fixed; inset: 0; display: grid; grid-template-rows: auto 1fr auto;
  background: var(--paper); color: var(--ink);
}
.swipe-top {
  display: flex; align-items: center; gap: 10px;
  padding: calc(env(safe-area-inset-top) + 8px) 12px 6px 8px;
}
/* Nothing to say about the hamburger here any more: it is inside the brand
   pill, which draws its own divider and its own corners, exactly as it does on
   a finder page. The two rules that were here flattened a lone button into a
   circle and set a title beside it, and both are gone with the title. */
.swipe-count {
  margin-left: auto; font-size: 13px; color: var(--muted);
  font-variant-numeric: tabular-nums; text-decoration: none;
}
.swipe-count:hover { color: var(--ink); text-decoration: underline; }
.swipe-stage { position: relative; margin: 6px 14px 0; }
@media (min-width: 700px) {
  .swipe-stage { width: 100%; max-width: 440px; margin: 10px auto 0; }
}
.swcard {
  position: absolute; inset: 0;
  border-radius: 20px; overflow: hidden;
  background: var(--paper-2);
  box-shadow: 0 14px 34px -14px rgba(23, 21, 15, .4);
  transform: translateX(var(--dx, 0px)) rotate(var(--tilt, 0deg))
             translateY(calc(var(--depth, 0) * 10px)) scale(calc(1 - var(--depth, 0) * .04));
  opacity: var(--fade, 1);
  transition: transform .34s cubic-bezier(.2, .8, .2, 1), opacity .34s;
  touch-action: none; user-select: none; -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.swcard:not(.is-top) { pointer-events: none; }
.swcard.is-dragging { transition: none; cursor: grabbing; }
.swcard.is-top { cursor: grab; }
.swcard.is-going { pointer-events: none; }
.swcard.is-skipping { transform: translateY(48px) scale(.96); opacity: 0; }
.swcard.is-returning { transform: translateX(-70%) rotate(-5deg); opacity: 0; }
.swcard-pic { position: absolute; inset: 0; background: var(--paper-2); }
.swcard-pic img { display: block; width: 100%; height: 100%; object-fit: cover; }
.swcard-veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0) 30%, rgba(0,0,0,.5) 62%, rgba(0,0,0,.84) 100%);
}
.swcard-body {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 22px 20px 20px; color: #fff;
}
/* A card with no photograph, or a card of words: the site's own paper, and
   the body fills it so the words sit low the way they do over a picture. */
.swcard.is-bare .swcard-body {
  top: 0; color: var(--ink);
  display: flex; flex-direction: column; justify-content: center;
}
.swcard-cat {
  margin: 0 0 8px; font-size: 11.5px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; opacity: .85;
}
.swcard.is-bare .swcard-cat { color: var(--accent); opacity: 1; }
.swcard-q {
  margin: 0 0 18px; font-family: var(--font-display); font-weight: 500;
  font-size: clamp(24px, 6.6vw, 32px); letter-spacing: -.02em; line-height: 1.12;
  text-wrap: balance;
}
.swcard-text { margin: 0 0 12px; font-size: 16px; line-height: 1.5; }
.swcard-text a { color: var(--accent); }
.swcard-hint { margin: 14px 0 0; font-size: 14px; color: var(--muted); }
.swcard-btns { display: flex; gap: 12px; margin-top: 6px; }
.swcard-btn {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 50px; padding: 0 16px; border-radius: 14px;
  border: 1.5px solid rgba(255, 255, 255, .6); background: rgba(255, 255, 255, .14);
  color: #fff; font: inherit; font-size: 16px; font-weight: 500; cursor: pointer;
  text-decoration: none;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  transition: background .15s, border-color .15s, color .15s;
}
.swcard-btn svg { width: 20px; height: 20px; }
.swcard.is-bare .swcard-btn { border-color: var(--rule-2); background: var(--paper); color: var(--ink); backdrop-filter: none; -webkit-backdrop-filter: none; }
.swcard-btn.is-go { background: var(--ink); border-color: var(--ink); color: var(--paper); }
/* The buttons are the swipe made visible: as the card crosses the line one
   of them takes its colour, so a reader learns the gesture from the press
   and the press from the gesture. Yes is the green of Want to go; No is the
   site's own red. */
.swcard.will-yes .swcard-btn.is-yes, .swcard-btn.is-yes:active {
  background: var(--ok); border-color: var(--ok); color: #fff;
}
.swcard.will-no .swcard-btn.is-no, .swcard-btn.is-no:active {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.swcard-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.swcard.is-bare .swcard-btn:focus-visible { outline-color: var(--accent); }
/* The stamp in the corner, the way a passport is stamped: it appears at the
   commit line and says what letting go will do. */
.swcard-stamp {
  position: absolute; top: 24px; padding: 4px 14px;
  border: 3px solid; border-radius: 10px;
  font-family: var(--font-display); font-size: 30px; font-weight: 500;
  letter-spacing: -.01em; line-height: 1.2;
  background: rgba(251, 249, 245, .9);
  opacity: 0; transition: opacity .15s; pointer-events: none;
}
.swcard-stamp.is-yes { left: 22px; color: var(--ok); border-color: var(--ok); transform: rotate(-12deg); }
.swcard-stamp.is-no { right: 22px; color: var(--accent); border-color: var(--accent); transform: rotate(12deg); }
.swcard.will-yes .swcard-stamp.is-yes, .swcard.will-no .swcard-stamp.is-no { opacity: 1; }
.swipe-foot {
  display: flex; align-items: center; justify-content: center; gap: 22px;
  padding: 14px 16px calc(env(safe-area-inset-bottom) + 14px);
}
.swipe-skip, .swipe-undo {
  border: 0; background: none; padding: 8px 10px;
  font: inherit; font-size: 14px; color: var(--ink-2);
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.swipe-skip:hover, .swipe-undo:hover { color: var(--ink); }
/* Not skippable - a tutorial card, or the end card - means no Skip at all,
   not a greyed one that reads as a control that has stopped working (the
   user, 12 September 2026). visibility rather than display or `hidden`: the
   shell is a grid of auto / 1fr / auto, so a foot that collapses hands its
   height to the stage and the card grows 37px every time the deck reaches its
   end. An invisible button keeps the row, and leaves the tab order too. */
.swipe-skip:disabled { visibility: hidden; }
.swipe-skip:focus-visible, .swipe-undo:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
@media (prefers-reduced-motion: reduce) {
  .swcard, .swcard-stamp, .swcard-btn { transition: none; }
}
