/* ==========================================================================
   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;
  --shadow:     0 -1px 0 var(--rule), 0 -14px 40px -12px rgba(23,21,15,.18);
  --card-hover: #F3EFE6;
  --selected:   #F6E4DC;
  --ok:         #23694A;
  --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;

  /* 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;
  --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;
    --shadow:     0 -1px 0 var(--rule), 0 -14px 40px -12px rgba(0,0,0,.6);
    --card-hover: #232019;
    --selected:   #40281F;
    --ok:         #7FD3A2;
    --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;
  }
}

/* --- 2. Reset & mobile hardening ---------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html {
  background: var(--paper);          /* stops white flash on overscroll */
  -webkit-text-size-adjust: 100%;
}
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; }
input { font-size: 16px; }                       /* below this, iOS zooms on focus */
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; }

/* 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); }
.topbar-spacer { flex: 1; }
.icon-btn {
  width: 38px; height: 38px; display: grid; place-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);
}
.icon-btn svg { width: 18px; height: 18px; }

/* --- 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; }
/* 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);
}
.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);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* filter chips — the primary control, kept in the thumb zone at peek */
.chips {
  display: flex; gap: 7px; overflow-x: auto; scrollbar-width: none;
  margin: 0 -16px; padding: 2px 16px 4px;
  scroll-padding: 16px;
}
.chips::-webkit-scrollbar { display: none; }
.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;
}
.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); }

/* 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);
}

/* --- 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;
}
.card:last-child { border-bottom: 0; }
.card:active { background: var(--card-hover); }
.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;
}
.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); }
.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); }

.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;
}

/* --- 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);
}
.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. */
.pin-label {
  position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%);
  font-family: var(--font-display); font-size: 12.5px; font-weight: 500;
  white-space: nowrap; padding: 5px 10px; border-radius: 999px;
  background: var(--ink); color: var(--paper);
  box-shadow: 0 2px 10px -2px rgba(0,0,0,.45);
  opacity: 0; transition: opacity .18s;
  pointer-events: none;            /* only the selected pin's label is live */
  cursor: pointer;
}
.pin.is-selected .pin-label { opacity: 1; pointer-events: auto; }
.pin.is-selected .pin-label:active { transform: translateX(-50%) scale(.96); }

/* Hovering a list row lights its pin, so the list can be read against the map. */
.pin.is-hovered::before { transform: scale(1.35); }
.pin.is-hovered { z-index: 2; }

/* --- 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; }
  .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; }
}

/* --- 10. Landing --------------------------------------------------------- */
.hero { max-width: 640px; margin: 0 auto; padding: 14vh 24px 60px; }
.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); }
.cats { list-style: none; padding: 0; margin: 36px 0 0; border-top: 1px solid var(--rule); }
.cats a {
  display: flex; align-items: baseline; gap: 12px; justify-content: space-between;
  padding: 18px 2px; border-bottom: 1px solid var(--rule); text-decoration: none;
}
.cats .n { font-family: var(--font-display); font-size: 24px; letter-spacing: -.02em; }
.cats .s { font-size: 13px; color: var(--muted); }
.cats a[aria-disabled="true"] { opacity: .45; pointer-events: none; }

.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; }
table.hours tr.today { background: var(--card-hover); }
table.hours tr.today th { color: var(--ink); }
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. */
.site-foot {
  max-width: 640px; margin: 0 auto;
  padding: 22px 24px calc(env(safe-area-inset-bottom) + 40px);
  border-top: 1px solid var(--rule);
}
.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; }
.detail-head {
  flex: none; padding: 0 10px 8px;
  border-bottom: 1px solid var(--rule);
}
.detail-back {
  display: inline-flex; align-items: center; gap: 4px;
  min-height: 40px; padding: 0 10px; border-radius: 999px;
  font-size: 14px; color: var(--ink-2);
}
.detail-back svg { width: 16px; height: 16px; }
.detail-back:active { background: var(--card-hover); }
@media (hover: hover) { .detail-back:hover { background: var(--card-hover); } }

#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; }
.detail-h {
  font-family: var(--font-display); font-weight: 500; font-size: 17px;
  margin: 22px 0 8px;
}
#detail table.hours { font-size: 14px; }
#detail table.hours th, #detail table.hours td { padding: 7px 12px; }

.detail-near { list-style: none; padding: 0; margin: 0; }
.detail-near a {
  display: flex; justify-content: space-between; gap: 12px; align-items: baseline;
  padding: 11px 2px; border-bottom: 1px solid var(--rule); text-decoration: none;
}
.detail-near-name { font-family: var(--font-display); font-size: 16px; }
.detail-near-walk { font-size: 12.5px; color: var(--muted); }

.detail-source {
  font-size: 12px; line-height: 1.55; color: var(--muted);
  margin-top: 22px; padding-top: 12px; border-top: 1px solid var(--rule);
}
.detail-source a { color: var(--muted); }

/* 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); }
}

/* --- 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; } }

/* 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; }
}

