:root {
  /* Palette — "Quiet Luxury" (Monocle-inspired): warm bone base, soft warm-black,
     terracotta accent shared with the journal (website/index.html, blog.html) so
     the map reads as the same publication. Per CLAUDE.md the base is never pure
     white, the text never pure black. */
  --bg: #FEFDFB;
  --header-bg: #2D2A26;
  --primary: #AC5537;
  --primary-hover: #9A4A30;
  /* Header-only tint: terracotta itself is too dark on --header-bg, so the
     inverted surface gets a lightened member of the same family (6.5:1). */
  --primary-light: #D9A38B;
  --accent: #AC5537;
  --accent-soft: #F7EDE7;
  --accent-soft-border: #EBD5C8;
  --map-loading-surface: rgba(254, 253, 251, 0.92);
  --map-loading-track: rgba(45, 42, 38, 0.12);
  --map-loading-accent: #B8634F;
  --text: #2D2A26;
  --text-secondary: #5C5550;
  --text-muted: #78716C;
  --border: #ECE9E4;
  --border-light: #F3F1ED;
  /* Search-match highlight — warm parchment off the paper base, not browser yellow. */
  --search-highlight: #F0E6D6;
  --shadow: 0 4px 6px rgba(45,42,38,0.08);
  --shadow-elevated: 0 12px 16px -4px rgba(45,42,38,0.10);
  --radius-card: 16px;
  --radius-pill: 9999px;
  --radius-btn: 8px;
  /* Type roles. Same pairing as the journal pages, with Noto Sans KR for CJK. */
  --font-display: 'Host Grotesk', 'Noto Sans KR', sans-serif;
  --font-body: 'Manrope', 'Noto Sans KR', sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
body {
  font-family: var(--font-display);
  height: 100dvh; overflow: hidden;
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  touch-action: manipulation;
}

/* ── Header ── */
.header {
  min-height: 56px; display: flex; align-items: center; gap: 16px;
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: 0;
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
  background: var(--header-bg); color: #fff;
  z-index: 1000; position: relative;
}
/* The publication name leads; the destination picker is the subhead beside it,
   matching the journal header (website/index.html `.logo`). */
.wordmark {
  font-family: var(--font-display); font-size: 19px; font-weight: 800;
  white-space: nowrap; letter-spacing: -0.4px;
  text-decoration: none; color: #FAF9F7;
  flex-shrink: 0; min-height: 44px;
  display: inline-flex; align-items: center;
}
.wordmark:focus-visible {
  outline: 2px solid var(--primary-light); outline-offset: 4px; border-radius: 2px;
}
.map-scope {
  display: flex; align-items: center; gap: 5px;
  white-space: nowrap; min-width: 0;
  font-family: var(--font-body); font-size: 14px; font-weight: 500;
  color: rgba(250,249,247,0.62);
}
.map-scope-dash { color: rgba(250,249,247,0.32); }
.map-scope-year { color: var(--primary-light); font-weight: 600; }
.destination-select {
  font-family: var(--font-body); font-size: 14px; font-weight: 600;
  color: rgba(250,249,247,0.92); background: transparent; border: none; cursor: pointer;
  appearance: none; -webkit-appearance: none;
  padding: 4px 20px 4px 0; letter-spacing: 0; min-height: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23D9A38B' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right center;
}
.destination-select:hover { color: #FAF9F7; }
.destination-select:focus { outline: none; }
.destination-select:focus-visible { outline: 2px solid #fff; outline-offset: 3px; border-radius: 4px; }
.destination-select option {
  background: var(--header-bg); color: #fff; font-size: 15px;
}
.header-right { display: flex; align-items: center; gap: 12px; margin-left: auto; }
.map-nav { display: flex; align-items: center; gap: 2px; }
.map-nav-link {
  display: inline-flex; align-items: center;
  padding: 0 12px; min-height: 44px;
  font-family: var(--font-body); font-size: 14px; font-weight: 500;
  color: rgba(250,249,247,0.6); text-decoration: none;
  transition: color 0.2s ease;
}
.map-nav-link:hover { color: #FAF9F7; }
.map-nav-link:focus-visible { outline: 2px solid #fff; outline-offset: -2px; border-radius: 4px; }
.spot-count { font-size: 13px; color: rgba(255,255,255,0.5); white-space: nowrap; }

/* ── Main layout ── */
.main { display: flex; height: calc(100dvh - 56px - env(safe-area-inset-top, 0px)); }

/* ── Sidebar ── */
.sidebar {
  width: 380px; min-width: 380px; background: var(--bg);
  display: flex; flex-direction: column;
  border-right: 1px solid var(--border); z-index: 500;
  -webkit-overflow-scrolling: touch;
}
.filters { padding: 16px; border-bottom: 1px solid var(--border); }
.filter-label {
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 8px;
}

.category-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 12px; }
.bool-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.chip-bool.active { border: 2.5px solid var(--primary); color: var(--primary); }
.chip-bool:disabled { opacity: 0.45; cursor: default; }
.hours-status {
  display: inline-flex; align-items: center;
  min-height: 44px; color: #9F1239; font-size: 12px; font-weight: 600;
}
.hours-retry {
  min-height: 44px; padding: 8px 14px;
  border: 1.5px solid var(--primary); border-radius: var(--radius-btn);
  background: transparent; color: var(--primary);
  font: 600 12px/1 var(--font-display); cursor: pointer;
}
.hours-retry:hover { background: var(--border-light); }
.hours-retry:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.hours-status[hidden], .hours-retry[hidden] { display: none; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px; border-radius: var(--radius-pill);
  font-size: 12px; font-weight: 600; font-family: inherit;
  cursor: pointer; border: 1px solid var(--border);
  background: var(--bg); color: var(--text-secondary);
  transition: background-color 0.18s ease, border-color 0.18s ease,
    color 0.18s ease, box-shadow 0.18s ease;
  user-select: none;
  min-height: 44px;
}
.chip-count { color: var(--text-secondary); font-size: 11px; }
/* 44px is the touch-target floor, and it is the right floor on a phone. With a
   mouse it cost the filter block four wrapped rows of category chips - 194px,
   a third of everything above the list - so the reader met two cards instead of
   a list. Pointer-accurate input gets a tighter chip; coarse input never does. */
@media (pointer: fine) {
  /* Heights stay at the 44px target floor - that guarantee is tested, and a
     scrolling single row was worse: it put 7 of the 10 categories off-screen.
     The saving here is the surrounding chrome, not the chips themselves. */
  .category-chips { gap: 5px; margin-bottom: 10px; }
  .bool-chips { gap: 5px; margin-bottom: 10px; }
}
.category-filter-chip {
  padding: 7px 11px;
  border-color: var(--border);
  background: var(--bg);
  color: var(--text-secondary);
}
.category-filter-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
  color: var(--cat-ui);
}
.category-chip-label { white-space: nowrap; }
.category-filter-chip .chip-count { margin-left: 1px; }
.category-filter-chip:hover {
  border-color: var(--cat-ui);
  background: var(--cat-hover);
  color: var(--text);
}
.category-filter-chip[aria-pressed='true'],
.category-filter-chip.active {
  border-color: var(--cat-strong);
  background: var(--cat-selected);
  color: var(--cat-strong);
  box-shadow: inset 0 0 0 1px var(--cat-strong);
}
.category-filter-chip[aria-pressed='true'] .category-filter-icon,
.category-filter-chip.active .category-filter-icon,
.category-filter-chip[aria-pressed='true'] .chip-count,
.category-filter-chip.active .chip-count { color: var(--cat-strong); }
.category-filter-chip:focus-visible {
  outline: 2px solid var(--cat-strong);
  outline-offset: 2px;
}
.chip:not(.category-filter-chip):hover { border-color: var(--text-muted); background: var(--border-light); }
.chip:not(.category-filter-chip):focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.chip-open-now { text-decoration: none; }
.chip-open-now[aria-pressed='true'] {
  background: var(--primary); border-color: var(--primary); color: var(--bg);
}
.chip-open-now[aria-pressed='true']:hover {
  background: var(--primary-hover); border-color: var(--primary-hover);
}
.bool-chips-link {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: 12px; font-weight: 600; color: var(--text-muted);
  text-decoration: none; border-bottom: 1px solid transparent;
  transition: color 0.18s ease, border-color 0.18s ease;
}
.bool-chips-link:hover { color: var(--text); border-bottom-color: var(--border); }
.bool-chips-link:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.city-select {
  width: 100%; padding: 10px 12px; border: 1.5px solid var(--border);
  border-radius: var(--radius-btn); font-size: 13px;
  font-family: inherit; background: var(--bg); color: var(--text);
  cursor: pointer; min-height: 44px;
}
.city-select:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.search-wrap { position: relative; margin-bottom: 12px; }

.spot-search {
  width: 100%; padding: 10px 34px 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-btn);
  font-family: inherit; font-size: 14px;
  background: var(--bg); color: var(--text);
  min-height: 44px; transition: border-color 0.2s;
  -webkit-appearance: none; appearance: none;
}
.spot-search::placeholder { color: var(--text-muted); }
.spot-search:focus { outline: none; border-color: var(--text-secondary); }
.spot-search:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.spot-search::-webkit-search-cancel-button { display: none; }
.spot-search-clear {
  position: absolute; top: 50%; right: 8px; transform: translateY(-50%);
  width: 24px; height: 24px; padding: 0; border: none; background: transparent;
  color: var(--text-muted); font-size: 20px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center; border-radius: 50%;
}
/* Invisible hit-area extension: 24px visual, 44px touch target */
.spot-search-clear::after {
  content: ''; position: absolute; inset: -10px; border-radius: 50%;
}
.spot-search-clear:hover { color: var(--text); background: var(--border); }
.spot-search-clear:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.spot-search-clear[hidden] { display: none; }

/* ── Spot list ── */
.list-scope-bar {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 38px;
  padding: 7px 16px;
  border-bottom: 1px solid var(--border-light);
  color: var(--text-muted);
  font-size: 11.5px;
  line-height: 1.35;
}
.list-scope-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 6px;
}
.list-sort-select {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-btn);
  background: transparent;
  color: var(--text-secondary);
  font: 600 11px/1 var(--font-display);
  cursor: pointer;
}
.list-sort-select:hover { border-color: var(--text-muted); color: var(--text); }
.list-sort-select:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.list-scope-bar button {
  flex: 0 0 auto;
  min-width: 44px;
  min-height: 44px;
  padding: 6px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-btn);
  background: transparent;
  color: var(--text-secondary);
  font: 600 11px/1 var(--font-display);
  cursor: pointer;
}
.list-scope-bar button:hover { border-color: var(--text-muted); color: var(--text); }
.list-scope-bar button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.spot-list { flex: 1; overflow-y: auto; padding: 8px; }
.spot-list-sentinel { height: 1px; width: 100%; pointer-events: none; }
/* ── List lead-in ──
   The unfiltered arrival and the inside-a-city arrival each open the list with a
   module: chips that narrow the set, and a shortlist. It is a sibling of the cards
   inside the scroll owner, so it scrolls away with them rather than permanently
   taxing the list's height. The 6px inset matches the Area section headers, which
   are the other piece of list-level furniture. One hairline separates it from the
   cards; the rest of the separation is space. */
.lead-in {
  padding: 4px 6px 14px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
  animation: leadInRise 0.45s ease-out both;
}
@keyframes leadInRise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
.lead-chip-row { display: flex; flex-wrap: wrap; gap: 6px; }
.lead-chip { padding: 7px 12px; }
.lead-chip-name { font-weight: 600; color: var(--text); }
/* The two escape hatches — "All 33 cities", "+49 more" — are offers, not filters,
   so they carry the accent rather than reading as another place to tap by mistake. */
.lead-chip-more { color: var(--primary); border-color: var(--accent-soft-border); }
.lead-chip-more .lead-chip-name { color: var(--primary); font-weight: 700; }
.lead-chip:not(.lead-chip-more):hover .lead-chip-name { color: var(--text); }
.lead-chip-more:hover { background: var(--accent-soft); border-color: var(--primary); }
@media (prefers-reduced-motion: reduce) {
  .lead-in { animation: none; }
}
/* ── Area sections ──
   Each section is its own containing block, so a header sticks only while its
   own cards are in view instead of stacking with the next one at the top edge.
   #spotList is the scroll owner in both surfaces it renders in — the desktop
   sidebar and the phone List view — so one `top: 0` covers both. */
/* Section spacing lives on the section, never as a margin on the header: a
   sticky box is inset from the scrollport by its *margin* box, so a margin-top
   here would pin every header that far below the top edge. */
.spot-group { display: block; }
.spot-group + .spot-group { margin-top: 12px; }
.spot-group-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: baseline; gap: 6px;
  margin: 0 0 6px;
  padding: 9px 6px;
  /* Opaque: cards scroll underneath it. The paper base, not a translucent blur. */
  background: var(--bg);
  border-bottom: 1px solid var(--border-light);
  font: 700 11px/1.3 var(--font-display);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text);
}
/* A pinned header stops at the scroll container's 8px top padding, which would
   leave a sliver of card scrolling through the gap above it. This paints that
   gap in paper and travels with the header. Decorative only — no text. */
.spot-group-head::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 100%;
  height: 8px; background: var(--bg);
}
.spot-group-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spot-group-dash { flex: 0 0 auto; color: var(--border); }
.spot-group-count { flex: 0 0 auto; font-weight: 600; letter-spacing: 0.04em; color: var(--text-muted); }
.spot-card {
  padding: 14px; border-radius: var(--radius-card); cursor: pointer;
  transition: all 0.15s; border: 1px solid transparent;
  margin-bottom: 4px; width: 100%; text-align: left; font-family: inherit;
  background: none; color: inherit; text-decoration: none; display: block;
}
.spot-card:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.spot-card:hover { background: #FAF9F7; border-color: var(--border); box-shadow: var(--shadow); }
.spot-card.active { background: #F8F0EB; border-color: var(--accent); box-shadow: var(--shadow-elevated); }
.spot-card.active .card-korean,
.spot-card.active .card-city,
.spot-card.active .card-distance { color: var(--text-secondary); }
.card-top { display: flex; align-items: start; gap: 12px; }
.card-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: 10px; flex-shrink: 0;
  font-size: 14px; font-weight: 700; color: #fff;
}
.card-info { flex: 1; min-width: 0; }
/* Name and rating share a baseline: the card leads with what the place is and
   how it scores, and the rating never pushes the name into a second line. */
.card-name-row { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.card-name {
  flex: 1 1 auto; min-width: 0;
  font-size: 14px; font-weight: 600; color: var(--text);
  line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.card-korean {
  font-family: 'Noto Sans KR', var(--font-display);
  font-size: 12px; color: var(--text-muted); margin-top: 1px;
}
.spot-empty-state {
  padding: 24px 18px;
  text-align: center;
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1.5;
}
.spot-empty-reset {
  margin-top: 14px;
  min-height: 44px;
  padding: 9px 16px;
  border: 1.5px solid var(--text);
  border-radius: var(--radius-btn);
  background: var(--text);
  color: var(--bg);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.spot-empty-reset:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
.spot-empty-reset:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.spot-data-error { display: flex; flex-direction: column; align-items: center; gap: 7px; }
.spot-data-error strong { color: var(--text); font-family: var(--font-display); }
.spot-data-error .spot-empty-reset { margin-top: 7px; }
.card-hero {
  width: 56px; height: 56px; border-radius: 10px; flex-shrink: 0;
  background-size: cover; background-position: center; background-color: #F2EFEA;
  position: relative; overflow: hidden;
}
/* 890 of 905 places have no photo. The initial-letter tile gives each of those
   rows its own shape at 56px, where a repeated category glyph gave the whole
   column one. Ink comes from the category's vetted on-colour pair, so amber and
   light green get dark type instead of unreadable white. */
.card-hero-letter {
  width: 56px; height: 56px; border-radius: 10px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--cat-color, var(--text-muted));
  color: var(--cat-on-color, #FEFDFB);
  font-family: var(--font-display);
  font-size: 24px; font-weight: 700; line-height: 1;
  letter-spacing: -0.01em;
  user-select: none;
}
/* ── Saved star on list cards ──
   Hung off .card-top as its own column rather than overlaid on the tile: the
   letter tile carries 890 of 905 rows and its glyph is the only thing that
   distinguishes them, so covering a corner of it costs more than it saves.
   A full 44px target with a smaller glyph inside keeps the row height honest. */
.card-fav {
  flex: 0 0 auto;
  align-self: center;
  width: 44px; height: 44px;
  margin: -4px -6px -4px 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-pill);
  background: transparent; padding: 0; cursor: pointer;
  color: var(--border);
  transition: color 0.18s ease, background-color 0.18s ease, transform 0.18s ease;
}
.card-fav-glyph { font-size: 17px; line-height: 1; pointer-events: none; }
.card-fav:hover { background: var(--border-light); color: var(--text-muted); }
.card-fav.is-saved { color: var(--primary); }
.card-fav.is-saved:hover { color: var(--primary-hover); }
.card-fav:active { transform: scale(0.92); }
.card-fav:focus-visible { outline: 2px solid var(--primary); outline-offset: -4px; }
/* Been here reads as a quieter version of the same star, never as a different
   control — you have been, but you still chose to save it. */
.card-fav.is-saved.is-visited { color: var(--primary); opacity: 0.55; }
.card-fav.is-saved.is-visited:hover { opacity: 0.85; }

/* ── Autocomplete suggestions ── */
.search-suggest {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 50;
  background: var(--bg-card, #fff); border: 1px solid var(--border);
  border-radius: var(--radius-btn); margin-top: 4px; max-height: 320px;
  overflow-y: auto; overflow-anchor: none; box-shadow: var(--shadow-elevated);
}
.search-suggest-group { padding: 4px 0; }
.search-suggest-group + .search-suggest-group { border-top: 1px solid var(--border-light, #eee); }
.search-suggest-label {
  font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-muted); padding: 8px 12px 4px;
}
.search-suggest-item {
  width: 100%; text-align: left; padding: 8px 12px; background: none; border: none;
  font-family: inherit; font-size: 13px; color: var(--text); cursor: pointer;
  display: flex; align-items: center; gap: 10px;
  min-height: 44px;
}
.search-suggest-item:hover,
.search-suggest-item.sel { background: #F2EFEA; }
.search-suggest-item:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.search-suggest-icon {
  width: 20px; height: 20px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  text-align: center;
}
.search-suggest-category-icon { width: 18px; height: 18px; color: var(--cat-ui); }
/* Spot rows get a tinted tile in their own category tone — same token set as
   every other category affordance, no new colors. */
.search-suggest-tile {
  width: 26px; height: 26px; border-radius: 7px;
  background: var(--cat-tint);
}
.category-search-suggestion:hover,
.category-search-suggestion.sel { background: var(--cat-hover); }
.category-search-suggestion:focus-visible { outline-color: var(--cat-strong); }
.search-suggest-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.search-suggest-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.search-suggest-detail {
  font-family: 'Noto Sans KR', var(--font-display);
  font-size: 11px; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.search-suggest-meta {
  margin-left: auto; padding-left: 8px; flex: 0 0 auto;
  font-size: 11px; color: var(--text-muted);
}
.search-suggest-all { font-weight: 600; color: var(--text); }
.search-suggest-all .search-suggest-meta { color: inherit; }

/* Search highlights: warm paper tint, never the browser's yellow. Applies to
   every surface that renders a match — sidebar cards and suggestions alike. */
.spot-card mark,
.search-suggest mark {
  background: var(--search-highlight);
  color: inherit;
  padding: 0 1px;
  border-radius: 2px;
}

/* ── Category spotlight: dim non-matching when filtering ── */
.spotlight-active .mapboxgl-canvas-container::before {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: rgba(250, 249, 247, 0.35);
}

/* ── Map photo-tile markers ───────────────────────────────────────────────
   Replaces Mapbox circle layers with HTML markers showing spot thumbnails.
   Three variants: single (unclustered), grid (2–9 spots), dense (10–99). */
.map-tile {
  position: relative; cursor: pointer;
  display: inline-block; /* hug content so absolutely-positioned badge lands on the tile */
  transition: transform 0.15s ease;
}
.map-tile:hover { transform: translateY(-2px) scale(1.04); z-index: 3; }
.map-tile:focus-visible { outline: 3px solid var(--primary); outline-offset: 4px; border-radius: 12px; z-index: 4; }
.map-tile[data-category]:focus-visible { outline-color: var(--cat-strong); }

/* Single spots use a quiet pale surface, readable same-hue outline icon, and a
   thin category edge. Labels on the interactive wrapper remain the non-color cue. */
.map-tile-single {
  width: 44px; height: 44px; border-radius: 12px;
  border: 2px solid var(--bg);
  background: var(--cat-hover, var(--border-light));
  box-shadow: 0 2px 8px rgba(45,42,38,0.18), inset 0 0 0 1px var(--cat-ui, var(--primary));
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  color: var(--cat-ui, var(--primary)); line-height: 1;
  transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.map-tile-single .gfp-category-marker-icon { width: 26px; height: 26px; }
.map-tile[data-category]:hover .map-tile-single {
  background: var(--cat-selected);
  color: var(--cat-strong);
}

/* Cluster grid tile: 2x2 of small category icons — tells the brain what's
   inside the cluster at a glance ("3 coffees + 1 bar") instead of a number. */
.map-tile-grid {
  width: 54px; height: 54px; border-radius: 12px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,0.28);
  padding: 2px;
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px;
}
.map-tile-grid-cell {
  background-color: var(--cat-hover, #fafafa);
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px var(--cat-ui, var(--border));
  display: flex; align-items: center; justify-content: center;
  color: var(--cat-ui, var(--text-muted)); line-height: 1;
}
.map-tile-grid-cell .gfp-category-cluster-icon { width: 15px; height: 15px; }

/* Dense mixed clusters remain neutral; a single-category filter safely passes
   its category tokens to this aggregate. */
.map-tile-dense {
  width: 60px; height: 60px; border-radius: 50%;
  border: 2px solid var(--bg);
  background: var(--bg);
  box-shadow: 0 2px 8px rgba(45,42,38,0.20), inset 0 0 0 1px var(--text-muted);
  display: flex; align-items: center; justify-content: center;
  color: var(--text); font-size: 18px; font-weight: 800;
}
.map-tile[data-category] .map-tile-dense {
  background: var(--cat-hover);
  box-shadow: 0 2px 8px rgba(45,42,38,0.20), inset 0 0 0 1px var(--cat-ui);
  color: var(--cat-strong);
}

/* Plain count circle (100+ spots): keep the old visual for readability. */
.map-tile-big {
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--header-bg); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; font-weight: 700; box-shadow: 0 2px 6px rgba(0,0,0,0.3);
  border: 2px solid #fff;
}

/* Count badge overlay shared by grid/dense variants */
.map-tile-count {
  position: absolute; bottom: -4px; right: -4px;
  background: var(--text); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 1;
  padding: 3px 6px; border-radius: 999px;
  border: 2px solid #fff; min-width: 22px; text-align: center;
}

/* Active/selected state (when popup open). The paper gap inside the category
   ring is what separates selected from hover at a glance. */
.mapboxgl-marker.gfp-marker-front { z-index: 4; }
.map-tile.selected .map-tile-single {
  background: var(--cat-selected);
  color: var(--cat-strong);
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 5px var(--cat-strong), 0 4px 16px rgba(45,42,38,0.24);
}

/* Pointer resting on a sidebar card — a continuous soft breathe rather than the
   burst's four-cycle announcement, because this tracks a pointer rather than
   marking an event. Deliberately declared BEFORE .marker-burst: the two share
   one ::after, and while a burst is still running it must win the cascade. */
.map-tile.list-hover::after {
  content: '';
  position: absolute; inset: -2px;
  border-radius: 14px;
  pointer-events: none;
  opacity: 0;
  animation: markerHaloHover 1.4s ease-in-out infinite;
}
@keyframes markerHaloHover {
  0%, 100% { box-shadow: 0 0 0 2px var(--cat-strong, var(--primary)); opacity: 0.35; }
  50%      { box-shadow: 0 0 0 10px var(--cat-strong, var(--primary)); opacity: 0; }
}
/* The halo is the motion; this is the part that still reads with the animation
   suppressed, so hover matches the settled selected treatment exactly. */
.map-tile.list-hover .map-tile-single {
  background: var(--cat-selected);
  color: var(--cat-strong);
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 5px var(--cat-strong), 0 4px 16px rgba(45,42,38,0.24);
}

/* Off-map selection — a card, a suggestion, a ?spot= link — announces its
   marker; a marker click never sets this class. A recreated node joins the
   running timeline through --marker-burst-delay rather than restarting it. */
.map-tile.marker-burst::after {
  content: '';
  position: absolute; inset: -2px;
  border-radius: 14px;
  pointer-events: none;
  opacity: 0;
  animation: markerHaloBurst 825ms ease-out 4 both;
  animation-delay: var(--marker-burst-delay, 0ms);
}
@keyframes markerHaloBurst {
  0%   { box-shadow: 0 0 0 2px var(--cat-strong, var(--primary)); opacity: 0.4; }
  100% { box-shadow: 0 0 0 14px var(--cat-strong, var(--primary)); opacity: 0; }
}

.card-meta { display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
/* Open/closed status. AA-passing green/red text + dot; unknown renders nothing. */
.card-open { font-size: 11px; font-weight: 600; display: inline-flex; align-items: center; gap: 4px; }
.card-open::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.card-open.is-open { color: #2E7D32; }
.card-open.is-closed { color: #c62828; }
.card-rating { flex: 0 0 auto; font-size: 12px; font-weight: 600; color: #B45309; }
.card-city { font-size: 11px; color: var(--text-muted); }
/* Arrival line — station + walk, or the city when there is no named station.
   One line, clipped rather than wrapped, so every card keeps the same height and
   the list stays a scannable column. */
.card-arrival {
  margin-top: 3px;
  font-size: 11.5px; line-height: 1.35; color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.card-arrival .card-city { color: inherit; }
.card-walk { color: var(--text-muted); }
.card-arrival-dot { color: var(--border); }
/* Vibe tags are text, not a third chip family — the card already carries a
   category pill, an open-state dot and sometimes a guide badge. The .card-meta
   gap is the separator, so nothing here needs generated content that a screen
   reader would have to read out. */
.card-vibe { font-size: 11px; color: var(--text-muted); }
.spot-card.active .card-arrival,
.spot-card.active .card-vibe { color: var(--text-secondary); }
/* .card-walk and .card-rating colour themselves, so the inherited overrides
   above cannot reach them. Against the active card's warmer #F8F0EB the muted
   grey lands at 4.26:1 and the amber at 4.46:1 — both under AA for their sizes.
   Restate each here rather than lightening the card the selection depends on. */
.spot-card.active .card-walk { color: var(--text-secondary); }  /* 6.50:1 on #F8F0EB */
.spot-card.active .card-rating { color: #A34A08; }              /* 5.27:1 on #F8F0EB */
.category-affordance {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border: 1px solid color-mix(in srgb, var(--cat-color) 22%, transparent);
  background: var(--cat-tint);
  color: var(--cat-text);
}
.category-affordance-icon {
  width: 12px;
  height: 12px;
  flex: 0 0 12px;
  color: currentColor;
}
.card-cat-label {
  font-size: 10px; font-weight: 650; padding: 2px 7px;
  border-radius: var(--radius-pill); text-transform: uppercase; letter-spacing: 0.3px;
}

/* ── Map ── */
.map-container { flex: 1; position: relative; }
#map { width: 100%; height: 100%; }
.map-loading {
  position: absolute;
  inset: 0;
  z-index: 650;
  display: block;
  color: var(--text);
  pointer-events: none;
  opacity: 1;
  transition: opacity 180ms ease-out;
}
.map-loading.is-complete { opacity: 0; }
.map-loading[hidden] { display: none; }
.map-loading-copy {
  position: absolute;
  top: 16px;
  left: 50%;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 8px 13px;
  transform: translateX(-50%);
  border: 1px solid rgba(45, 42, 38, 0.12);
  border-radius: var(--radius-pill);
  background: var(--map-loading-surface);
  box-shadow: 0 3px 12px rgba(45, 42, 38, 0.10);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  white-space: nowrap;
}
.map-loading-copy strong {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 650;
  line-height: 1;
  letter-spacing: 0.01em;
}
.map-loading-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--map-loading-accent);
}
.map-loading-track {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  overflow: hidden;
  background: var(--map-loading-track);
}
.map-loading-track::after {
  content: '';
  position: absolute;
  inset: 0;
  width: 38%;
  background: var(--map-loading-accent);
  animation: map-loading-progress 1.1s ease-in-out infinite;
}
@keyframes map-loading-progress {
  from { transform: translateX(-100%); }
  to { transform: translateX(365%); }
}
#map .mapboxgl-ctrl-group button,
#map .mapboxgl-ctrl-attrib-button {
  width: 44px;
  height: 44px;
}
#map .mapboxgl-ctrl-logo {
  min-width: 88px;
  min-height: 44px;
  background-position: left center;
}

/* ── Map empty state ────────────────────────────────────────────────────
   Shown when Mapbox fails (bad token, CSP block, offline, timeout).
   Frames the failure as recoverable: the sidebar list still works, and
   geolocation-driven "near me" doesn't depend on the map either. */
.map-empty-state {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  height: 100%; padding: 32px 24px; box-sizing: border-box;
  background: var(--bg); text-align: center;
}
.map-empty-icon {
  font-size: 44px; margin-bottom: 14px; opacity: 0.55;
}
.map-empty-title {
  font-family: var(--font-display);
  font-size: 19px; font-weight: 700;
  color: var(--text); margin-bottom: 8px; letter-spacing: -0.005em;
}
.map-empty-body {
  font-family: var(--font-body);
  font-size: 17px; line-height: 1.5;
  color: var(--text-secondary);
  max-width: 360px; margin-bottom: 20px;
}
.map-empty-body strong { color: var(--text); font-weight: 500; }
.map-empty-actions {
  display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;
  width: 100%; max-width: 320px;
}
.map-empty-cta {
  font-family: var(--font-display);
  font-size: 13.5px; font-weight: 600;
  padding: 10px 16px;
  border-radius: var(--radius-btn);
  cursor: pointer;
  border: 1px solid var(--border);
  background: #fff;
  color: var(--text);
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;
}
.map-empty-cta:hover { background: var(--border-light); border-color: var(--text-muted); }
.map-empty-cta.primary {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.map-empty-cta.primary:hover {
  background: var(--primary-hover); border-color: var(--primary-hover);
}
.map-empty-cta.mobile-only { display: none; }
@media (max-width: 768px), (max-height: 600px) and (max-width: 1024px) {
  /* Mobile: smaller hero icon, tighter padding, and full-width stacked buttons
     so neither CTA gets clipped at the viewport edge on narrow phones. */
  .map-empty-state { padding: 20px 16px; }
  .map-empty-icon { font-size: 36px; margin-bottom: 8px; }
  .map-empty-title { font-size: 17px; margin-bottom: 6px; }
  .map-empty-body { font-size: 15px; margin-bottom: 14px; }
  .map-empty-actions { flex-direction: column; gap: 8px; }
  .map-empty-cta { width: 100%; }
  .map-empty-cta.mobile-only { display: inline-flex; }
}

/* ──────────────────────────────────────────────────────────────────────── */
/* Place Detail UI — mobile bottom sheet + desktop side panel               */
/* Replaces the legacy floating Mapbox popup. Same data, viewport-adaptive  */
/* chrome. Driven by showPlaceDetail / openMobileSheet / openDesktopPanel.  */
/* ──────────────────────────────────────────────────────────────────────── */
.place-sheet,
.place-panel {
  --pd-surface: #FAF9F7;
  --pd-surface-card: rgba(255,255,255,0.6);
  --pd-text: var(--text);
  --pd-text-2: var(--text-secondary);
  --pd-text-3: var(--text-muted);
  --pd-border: var(--border);
  --pd-border-soft: var(--border-light);
  --pd-radius: 16px;
  --pd-radius-inner: 12px;
  /* Ink, not brand green. The provider keeps its colour on a chip inside. */
  --pd-cta-bg: #2D2A26;
  font-family: var(--font-display);
  color: var(--pd-text);
  z-index: 900;
}
.place-sheet[hidden], .place-panel[hidden] { display: none !important; }

/* Mobile bottom sheet */
.place-sheet {
  position: fixed;
  left: 16px;
  right: 16px;
  bottom: 14px;
  background: var(--pd-surface);
  border: 1px solid rgba(232, 229, 225, 0.95);
  border-radius: 22px;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.22), 0 4px 18px rgba(15, 23, 42, 0.08);
  display: flex; flex-direction: column;
  max-height: min(430px, calc(100dvh - 92px));
  overflow: hidden;
  transform: translateY(0); opacity: 1;
  transition: max-height 0.25s ease-out, transform 0.25s ease-out, opacity 0.2s ease-out;
  padding-bottom: 0;
}
.place-sheet[data-state="expanded"] { max-height: calc(100dvh - 82px); }
.place-sheet.is-opening { transform: translateY(100%); opacity: 0; }
.place-sheet.is-closing { transform: translateY(100%); opacity: 0; }

/* Desktop side panel — anchored to the top-right; height tracks the surface's
   content. With max-height capping at viewport, long spots scroll inside the
   body and the CTA stays at the bottom of the visible panel. Short spots make
   the panel a compact card without dead space below the CTA. */
.place-panel {
  position: fixed;
  top: calc(72px + env(safe-area-inset-top, 0px));
  right: 24px;
  bottom: auto;
  max-height: calc(100dvh - 96px - env(safe-area-inset-top, 0px));
  width: 420px;
  background: var(--pd-surface);
  border: 1px solid rgba(232, 229, 225, 0.95);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.16), 0 6px 24px rgba(15, 23, 42, 0.08);
  border-radius: 18px;
  display: flex; flex-direction: column;
  overflow: hidden;
  transform: translateX(0); opacity: 1;
  transition: transform 0.25s ease-out, opacity 0.2s ease-out;
}
.place-panel.is-opening { transform: translateX(100%); opacity: 0; }
.place-panel.is-closing { transform: translateX(100%); opacity: 0; }

/* Drag handle (mobile only — hidden on desktop panel) */
.pd-handle {
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-height: 44px; padding: 8px 0 2px;
  background: none; border: none; cursor: pointer; font-family: inherit;
}
.pd-handle::before {
  content: ''; width: 44px; height: 5px;
  border-radius: 999px; background: #D6D0C9;
  transition: background 0.15s;
}
.pd-handle:hover::before, .pd-handle:focus-visible::before { background: #A8A099; }
.pd-handle:focus-visible { outline: none; }
.place-panel .pd-handle { display: none; }

/* Close button (top-right both surfaces) */
.pd-close {
  position: absolute; top: 12px; right: 14px;
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(245, 242, 238, 0.92);
  border: none; border-radius: 9999px;
  color: var(--pd-text-2);
  cursor: pointer; transition: background 0.15s;
}
.pd-close svg { width: 18px; height: 18px; }
/* Invisible hit-area extension: 34px visual, 44px touch target */
.pd-close::after {
  content: ''; position: absolute; inset: -5px; border-radius: 50%;
}
.pd-close:hover { background: rgba(0,0,0,0.08); }
.pd-close:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.place-sheet .pd-close { top: 8px; }

/* Scrollable body + sticky CTA bar (shared between sheet and panel)
   .pd-body uses flex:0 1 auto so it shrinks to its content height — when the
   spot has minimal info the surface stays compact and the CTA sits naturally
   below the content with no empty band above it. With long content the body
   hits the surface's max-height (compact preview / expanded sheet / panel) and its own
   overflow-y kicks in to scroll. CTA stays a sibling so it's always pinned
   to the bottom of the surface regardless of content length. */
.pd-body {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 4px 20px 12px;
  scrollbar-gutter: stable;
}
.place-sheet .pd-body { padding-top: 0; }
.place-panel .pd-body { padding-top: 18px; }
.pd-cta-bar {
  flex: 0 0 auto;
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 12px 20px calc(12px + env(safe-area-inset-bottom, 0px));
  background: var(--pd-surface);
  border-top: 1px solid var(--pd-border-soft);
}
.pd-cta {
  flex: 1 1 auto;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 48px;
  padding: 12px 16px;
  border-radius: var(--pd-radius-inner);
  font-family: inherit; font-size: 14px; font-weight: 700;
  color: #fff; text-decoration: none; cursor: pointer;
  background: var(--pd-cta-bg);
  border: none;
  transition: background 0.15s, transform 0.1s;
  letter-spacing: 0.01em;
}
/* Lift the ink rather than filtering the whole button — a filter would wash
   out the provider chip's brand colour along with it. */
.pd-cta:hover { background: #453F38; }
.pd-cta:active { transform: translateY(1px); }
.pd-cta svg { flex-shrink: 0; }

/* Provider chip: brand recognition at 22px instead of across a whole button. */
.pd-cta-chip {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border-radius: 6px;
}
.pd-cta-chip svg { width: 13px; height: 13px; }
.pd-cta-label { line-height: 1.2; }

/* Compact Google escape hatch beside the local primary. Present in the mobile
   preview bar too, so the fallback is reachable without expanding the sheet. */
.pd-cta-secondary {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 48px;
  padding: 12px 14px;
  border: 1px solid var(--pd-border);
  border-radius: var(--pd-radius-inner);
  background: transparent;
  font-family: inherit; font-size: 13px; font-weight: 600;
  color: var(--pd-text-2);
  text-decoration: none; cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.pd-cta-secondary:hover {
  background: var(--pd-accent-soft);
  border-color: var(--pd-text-3);
  color: var(--pd-text);
}
.pd-cta-secondary svg { width: 16px; height: 16px; flex: 0 0 auto; }

/* Header */
.pd-header { padding-top: 4px; padding-right: 40px; }
.pd-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 9px; }
.pd-chip {
  display: inline-flex; align-items: center;
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.5px;
  padding: 4px 10px;
  border-radius: 9999px;
  white-space: nowrap;
}
.pd-name {
  font-family: var(--font-display);
  font-size: 22px; font-weight: 650;
  color: var(--pd-text); line-height: 1.2;
  margin: 0;
}
.place-panel .pd-name { font-size: 22px; }
.pd-local-name {
  font-family: 'Noto Sans KR', var(--font-display);
  font-size: 14px; font-weight: 600; color: var(--pd-text-2);
  margin-top: 4px;
}
.pd-locality {
  font-family: var(--font-body);
  font-size: 13px; color: var(--pd-text-3);
  margin-top: 3px;
}
.pd-rating {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 8px; font-size: 13px;
}
.pd-rating .stars { color: #B45309; letter-spacing: 1px; }
.pd-rating .val { font-weight: 600; color: var(--pd-text-2); }
.pd-meta {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px 10px;
  margin-top: 9px;
  font-size: 12px; color: var(--pd-text-3);
}
.pd-meta-item { display: inline-flex; align-items: center; gap: 4px; }
.pd-meta-item strong { color: var(--pd-text-2); font-weight: 600; }
.pd-meta .stars { color: #B45309; letter-spacing: 0.5px; font-size: 12px; }
.pd-meta-item.is-rating strong { color: var(--pd-text); font-size: 12.5px; }
.pd-meta-dot {
  width: 3px; height: 3px; border-radius: 999px;
  background: currentColor; opacity: 0.5;
  flex-shrink: 0;
}

/* Description (clamped on mobile preview).
   Long-form prose uses the editorial serif (Quiet Luxury palette) — gives the
   description weight and character vs. the surrounding sans UI chrome. */
.pd-desc {
  margin-top: 12px;
  font-family: var(--font-body);
  font-size: 16px; color: var(--pd-text-2);
  line-height: 1.65;
}
.place-sheet[data-state="preview"] .pd-desc {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Journal banner — the written review, promoted to the first tappable element
   in the card. Terracotta because the journal is the one editorial voice on an
   otherwise operational surface; it is deliberately not category-tinted. */
.pd-journal-banner {
  display: flex; align-items: center; gap: 10px;
  margin-top: 13px;
  padding: 11px 13px;
  min-height: 48px;
  background: var(--accent-soft);
  border: 1px solid var(--accent-soft-border);
  border-radius: var(--pd-radius-inner);
  color: var(--accent);
  text-decoration: none;
  transition: background 0.18s, border-color 0.18s;
}
.pd-journal-banner:hover { background: #F2E2D8; border-color: var(--accent); }
.pd-journal-mark { flex: 0 0 auto; font-size: 15px; line-height: 1; }
.pd-journal-copy { display: grid; gap: 1px; min-width: 0; }
.pd-journal-kicker {
  font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.7px;
  opacity: 0.75;
}
.pd-journal-title {
  font-family: var(--font-display);
  font-size: 13.5px; font-weight: 700; line-height: 1.25;
  overflow-wrap: anywhere;
}
.pd-journal-arrow { flex: 0 0 auto; margin-left: auto; font-size: 15px; line-height: 1; }

/* Compact utility action row — small icon+label buttons aligned in one flex row.
   The row holds 2–4 items: Show on map and Share, plus Instagram and a website or
   reservation action when available. Directions and address copying live in
   their dedicated lower sections. */
.pd-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 13px;
}
.pd-action {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 8px 4px;
  background: #ffffff;
  border: 1px solid var(--pd-border);
  border-radius: 10px;
  font-family: inherit; font-size: 11.5px; font-weight: 500;
  color: var(--pd-text-2);
  text-decoration: none; cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.1s;
  min-height: 38px;
  white-space: nowrap;
}
.pd-action:hover { background: #fafaf9; border-color: var(--pd-text-3); color: var(--pd-text); }
.pd-action:active { transform: scale(0.98); }
.pd-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.pd-action svg { flex-shrink: 0; width: 15px; height: 15px; }
.pd-action-label { line-height: 1.2; overflow: hidden; text-overflow: ellipsis; }

.place-sheet .pd-actions {
  gap: 0;
  padding: 8px 0;
  border-top: 1px solid #F2EFEA;
  border-bottom: 1px solid #F2EFEA;
}
.place-sheet .pd-action {
  min-height: 54px;
  border: 0;
  border-radius: 0;
  border: 1px solid var(--pd-border);
  background: rgba(254, 253, 251, .9);
  flex-direction: column;
  gap: 5px;
  font-size: 11px;
}
.place-sheet .pd-action + .pd-action { border-left: 1px solid #F2EFEA; }
.place-sheet .pd-action:hover { background: #fafaf9; border-color: #F2EFEA; }
.place-sheet .pd-action svg { width: 18px; height: 18px; }

.place-panel .pd-actions {
  margin-top: 10px;
  gap: 6px;
}
.place-panel .pd-action {
  min-height: 32px;
  padding: 6px 8px;
  border-radius: 8px;
  font-size: 11px;
}

/* Section title (shared) */
.pd-section { margin-top: 16px; }
.pd-section-title {
  font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.7px;
  color: var(--pd-text-3);
  margin-bottom: 8px;
}

/* Card primitive — used by Must Try, Good For, Practical info, Address, and
   the Related-posts list. Gives sections a soft container, subtle border,
   and consistent rhythm. */
.pd-card {
  margin-top: 12px;
  padding: 13px 14px;
  background: #ffffff;
  border: 1px solid #ECE9E4;
  border-radius: 12px;
  box-shadow: 0 1px 2px rgba(26,26,26,0.03);
}
.pd-card-accent-warm {
  background: linear-gradient(135deg, #FFFBEA 0%, #FFF7D6 100%);
  border-color: #F9D86A;
}
.pd-card-accent-warm .pd-section-title { color: #92400E; }
.pd-card-accent-warm .pd-must-try-text { color: #713f12; }

/* Must-try — content style (card chrome handled by .pd-card.pd-card-accent-warm) */
.pd-must-try-content {
  display: grid;
  grid-template-columns: 46px 1fr;
  gap: 10px;
  align-items: center;
}
.pd-must-icon {
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  color: #8A4F16;
  background: rgba(255, 255, 255, 0.76);
  box-shadow: inset 0 0 0 1px rgba(146, 64, 14, 0.12);
}
.pd-must-icon svg { width: 24px; height: 24px; }
.pd-must-try-text { font-family: var(--font-body); font-size: 15.5px; line-height: 1.55; color: var(--pd-text); }

/* Good for (vibe tags) */
.pd-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.pd-tag {
  font-size: 12px; padding: 4px 10px;
  border-radius: 9999px;
  background: #F5F2EE;
  color: var(--pd-text-2);
}

/* Two-column grid for Must Try + Good For (desktop side panel only).
   Mobile sheet keeps cards stacked. Single-card grids span full width. */
.pd-grid-2 { margin-top: 12px; }
.pd-grid-2 > .pd-card { margin-top: 12px; }
.pd-grid-2 > .pd-card:first-child { margin-top: 0; }
@media (min-width: 768px) {
  .place-panel .pd-grid-2 {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: 10px;
    align-items: stretch;
  }
  .place-panel .pd-grid-2 > .pd-card { margin-top: 0; }
  .place-panel .pd-grid-2 > .pd-card:first-child { margin-top: 0; }
  .place-panel .pd-good-for {
    background: linear-gradient(180deg, #fff 0%, #FCFBFA 100%);
  }
}

/* Practical info row — 1 to 4 small cards, columns set via --cols inline. */
.pd-practical-row {
  display: grid;
  grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));
  gap: 8px;
  margin-top: 12px;
}
.pd-practical-row > .pd-card { margin-top: 0; padding: 10px 12px; }
.pd-info-card {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: 7px;
  align-items: start;
}
.pd-info-card-icon {
  color: var(--pd-text-3);
  margin-top: 1px;
}
.pd-info-card-icon svg {
  width: 16px;
  height: 16px;
}
.pd-card-label {
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--pd-text-3);
  margin-bottom: 3px;
}
.pd-card-value {
  font-size: 12.5px; line-height: 1.35;
  color: var(--pd-text);
}

.place-panel .pd-practical-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  padding: 7px 10px;
  background: #ffffff;
  border: 1px solid #ECE9E4;
  border-radius: 12px;
  box-shadow: 0 1px 2px rgba(26,26,26,0.03);
}
.place-panel .pd-practical-row > .pd-card {
  flex: 1 1 50%;
  min-width: 0;
  padding: 8px 8px;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}
.place-panel .pd-practical-row > .pd-card:nth-child(even) { border-left: 1px solid #F2EFEA; }
.place-panel .pd-practical-row > .pd-card:nth-child(n+3) { border-top: 1px solid #F2EFEA; }
.place-panel .pd-info-card {
  grid-template-columns: 20px 1fr;
  gap: 7px;
}
.place-panel .pd-info-card-icon {
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #059669;
  background: #ECFDF5;
  border-radius: 999px;
}
.place-panel .pd-info-card-icon svg {
  width: 12px;
  height: 12px;
}
.place-panel .pd-card-label {
  font-size: 9.5px;
  margin-bottom: 2px;
}
.place-panel .pd-card-value {
  font-size: 11.5px;
  line-height: 1.3;
}

/* Hours card: compact schedule + collapsible full week below. The open/closed
   state lives once, in the header availability line. */
.pd-hours-today { font-weight: 600; }
.pd-hours-week-toggle {
  display: inline-block;
  margin-top: 4px;
  font-size: 11px;
  color: var(--pd-text-3);
  cursor: pointer;
  user-select: none;
  background: none; border: none; padding: 0;
  font-family: inherit;
}
.pd-hours-week-toggle:hover { color: var(--pd-text); }
.pd-hours-week-list {
  margin: 6px 0 0;
  padding: 6px 0 0;
  border-top: 1px solid #F2EFEA;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--pd-text-2);
}
.pd-hours-week-list > .pd-hours-day { font-weight: 600; color: var(--pd-text); }
.pd-hours-week-list > .pd-hours-day-today {
  font-weight: 700;
  color: #92400E;
}
.pd-hours-week-list > .pd-hours-time-today { color: #92400E; }

/* Permanently-closed banner */
.pd-closed-banner {
  margin-top: 12px;
  padding: 10px 14px;
  background: #FEF2F2;
  border: 1px solid #FECACA;
  border-radius: 12px;
  font-size: 13px; font-weight: 600;
  color: #991B1B;
  display: flex; align-items: center; gap: 8px;
}
.pd-closed-banner svg { width: 18px; height: 18px; flex-shrink: 0; }

/* Address card — list lives inside a .pd-card so it has no own border.
   .pd-addr-list rows are full-width buttons; subtle dividers between rows. */
.pd-addr-list { margin-top: 4px; }
.pd-addr-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%;
  padding: 8px 0;
  background: none; border: none;
  font-family: inherit; text-align: left;
  cursor: pointer;
  min-height: 44px;
  transition: opacity 0.12s;
}
.pd-addr-row + .pd-addr-row { border-top: 1px solid #F2EFEA; }
.pd-addr-row:hover { opacity: 0.78; }
.pd-addr-row:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; border-radius: 4px; }
.pd-addr-text { font-size: 13px; color: var(--pd-text); line-height: 1.4; flex: 1; min-width: 0; word-break: break-word; }
.pd-addr-text.is-ko { font-family: 'Noto Sans KR', var(--font-display); color: var(--pd-text-2); }
.pd-addr-list .copy-hint {
  font-size: 10px; font-weight: 600; letter-spacing: 0.4px; text-transform: uppercase;
  color: var(--pd-text-3);
  padding: 4px 8px;
  background: #F5F2EE;
  border-radius: 6px;
}
.pd-addr-loading {
  display: block; height: 14px; width: 70%;
  background: linear-gradient(90deg, #efeae3 0%, #f8f4ee 50%, #efeae3 100%);
  background-size: 200% 100%;
  animation: pdShimmer 1.2s infinite linear;
  border-radius: 4px;
}
@keyframes pdShimmer {
  from { background-position: 0 0; }
  to { background-position: -200% 0; }
}

/* Footer feedback links */
.pd-feedback {
  display: flex; gap: 16px; flex-wrap: wrap;
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--pd-border-soft);
}
.pd-feedback-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px; color: var(--pd-text-3);
  text-decoration: none;
  transition: color 0.15s;
}
.pd-feedback-link svg { width: 12px; height: 12px; }
.pd-feedback-link:hover { color: var(--pd-text-2); text-decoration: underline; }

/* Related blog posts — list lives inside a .pd-card so items sit on the
   white surface without their own border chrome. */
.pd-related-list { margin-top: 4px; display: flex; flex-direction: column; gap: 4px; }
.pd-related-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 4px;
  text-decoration: none;
  transition: opacity 0.15s;
  border-radius: 8px;
}
.pd-related-item + .pd-related-item { border-top: 1px solid #F2EFEA; }
.pd-related-item:hover { opacity: 0.82; }
.pd-related-thumb {
  width: 44px; height: 44px;
  border-radius: 8px;
  background: #F5F2EE center/cover no-repeat;
  flex-shrink: 0;
}
.pd-related-meta { min-width: 0; flex: 1; }
.pd-related-title { font-size: 13px; font-weight: 600; color: var(--pd-text); line-height: 1.3; }
.pd-related-date { font-size: 11px; color: var(--pd-text-3); margin-top: 2px; }

/* Structured detail disclosure (mobile sheet only) */
.pd-more-toggle {
  display: inline-flex;
  flex: 0 0 44px;
  align-items: center;
  justify-content: center;
  gap: 4px;
  align-self: center;
  margin: 0;
  padding: 8px 16px;
  min-height: 44px;
  background: none; border: none;
  font-family: inherit; font-size: 13px; font-weight: 500;
  color: var(--pd-text-2);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.pd-more-toggle svg { width: 14px; height: 14px; }
.pd-more-toggle:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 4px;
}
.place-sheet[data-state="expanded"] .pd-more-toggle,
.place-panel .pd-more-toggle { display: none; }

/* Mobile preview state — surface acts like a compact card hovering over the
   map. Body shrinks to its (preview-only) content so the sheet height tracks
   what's actually visible. Expanded-only blocks are hidden AND aria-hidden so
   focus can't reach them. */
.place-sheet[data-state="preview"] { max-height: min(360px, calc(100dvh - 116px)); }
.place-sheet[data-state="preview"] .pd-body {
  max-height: 240px;
  overflow: hidden;
}
.place-sheet[data-state="preview"] .pd-expanded-only { display: none; }

@media (max-width: 360px) {
  .place-sheet { left: 10px; right: 10px; bottom: 10px; border-radius: 18px; }
  .pd-body { padding-left: 14px; padding-right: 14px; }
  .pd-cta-bar { padding-left: 14px; padding-right: 14px; }
  .pd-name { font-size: 20px; }
  .pd-action { font-size: 11px; }
  /* Header must fit 320px: tighten gaps so .header-right stays in-viewport */
  .header {
    gap: 8px;
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
  }
  .header-right { gap: 8px; }
  /* This block precedes the max-width:768px rules in source order, so the
     narrow-phone wordmark needs the extra specificity to win the cascade. */
  .header .wordmark { font-size: 16px; letter-spacing: -0.5px; }
}

@media (prefers-reduced-motion: reduce) {
  .map-loading { transition: none; }
  .map-loading-track::after { animation: none; transform: translateX(82%); }
  .category-filter-chip,
  .map-tile,
  .map-tile-single { transition: none; }
  .map-tile:hover { transform: none; }
  .map-tile.list-hover::after { animation: none; }
  .map-tile.marker-burst::after { animation: none; }
  .place-sheet, .place-panel { transition-duration: 0.01s; }
}

/* Narrow desktop: collapse the spot-list sidebar when detail panel is open */
@media (min-width: 769px) and (max-width: 1199px) {
  .sidebar.collapsed-by-detail {
    width: 0; min-width: 0;
    overflow: hidden;
    border-right: none;
    opacity: 0;
    transition: width 0.2s ease, opacity 0.15s ease;
  }
}

/* Shared utility classes still consumed by spot-list cards and the new place
   detail (e.g. copyToClipboard expects a `.copy-hint` span inside the row). */
.copy-hint { font-size: 10px; color: var(--text-muted); white-space: nowrap; flex-shrink: 0; }
.card-distance { font-size: 11px; color: var(--text-muted); }
.card-type-label {
  font-size: 10px; font-weight: 600; padding: 2px 8px;
  border-radius: var(--radius-pill); background: var(--border-light); color: var(--text-secondary);
}
.card-michelin { font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: var(--radius-pill); }

/* ── View Toggle ── */
.view-toggle {
  display: none; /* shown on mobile */
  align-items: center;
  background: rgba(255,255,255,0.12);
  border-radius: var(--radius-pill);
  overflow: hidden;
  height: 44px;
}
.view-toggle-btn {
  display: flex; align-items: center; justify-content: center;
  padding: 0 12px; height: 100%;
  font-size: 12px; font-weight: 500; color: rgba(255,255,255,0.6);
  cursor: pointer; border: none; background: none;
  transition: all 0.15s; gap: 4px; min-width: 44px;
}
.view-toggle-btn.active {
  background: rgba(255,255,255,0.2); color: #fff;
}
.view-toggle-btn svg, .view-toggle-btn span { width: 14px; height: 14px; pointer-events: none; }

/* Mobile-only map filters. The existing sidebar is promoted to a bottom sheet
   when filters are open, so desktop keeps its established two-column layout. */
.map-chip-strip,
.result-count-pill,
.mobile-filter-foot,
.mobile-filter-backdrop,
.mobile-filter-head,
.mobile-filter-summary { display: none; }
/* Author-level guard: the media queries below set display on these, which would
   otherwise beat the UA sheet's [hidden] rule. */
.result-count-pill[hidden] { display: none !important; }

/* ── Mobile ── */
@media (max-width: 768px), (max-height: 600px) and (max-width: 1024px) {
  .main { position: relative; flex-direction: column-reverse; }
  .sidebar {
    width: 100%; min-width: 100%; max-height: 45dvh;
    border-right: none; border-top: 1px solid var(--border);
    transition: max-height 0.3s ease, opacity 0.2s ease;
    overflow-y: auto; display: block;
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .spot-list { overflow-y: visible; }
  .map-container { flex: 1; min-height: 0; }
  .spot-count { display: none; }
  .chip { min-height: 44px; padding: 10px 16px; font-size: 13px; flex: 0 0 auto; white-space: nowrap; }
  /* 16px floor on form controls: below that, iOS zooms on focus, which
     hijacks the tap inside this overflow:hidden dvh layout and the select
     picker never visibly opens. Same fix as the blog search input. */
  .city-select { height: 44px; min-height: 44px; font-size: 16px; }
  .spot-search { font-size: 16px; }

  /* Compact filter stack: search and city share the first row, categories
     become one horizontally-scrollable chip row. Halves the sidebar height
     so the map keeps the screen. Desktop keeps the labeled vertical stack. */
  .filters {
    display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
    padding-left: max(16px, env(safe-area-inset-left, 0px));
    padding-right: max(16px, env(safe-area-inset-right, 0px));
  }
  /* The compact stack drops its own labels — the search field, city select and
     category chips all state themselves. Scoped to .filters on purpose: the list
     lead-in reuses this class inside #spotList, where "Start with a city" and
     "Patrick's picks" are the only thing naming what those rows are for. */
  .filters .filter-label { display: none; }
  .search-wrap { flex: 1 1 0; min-width: 0; margin-bottom: 0; order: 1; }
  .city-select { flex: 0 1 42%; min-width: 132px; width: auto; order: 2; }
  .category-chips {
    flex: 1 1 100%; order: 3; margin-bottom: 0;
    flex-wrap: nowrap; overflow-x: auto;
    padding: 4px 6px; scroll-padding-inline: 6px;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    /* Seven of ten categories start off-screen here. Same two-sided mask as the
       map chip strip: chips dissolve into the paper at whichever edge still has
       track beyond it, rather than being cut off mid-pill. Snap makes the flick
       land on a chip edge instead of halfway through one. */
    --track-fade-start: 0px;
    --track-fade-end: 0px;
    scroll-snap-type: x proximity;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--track-fade-start),
      #000 calc(100% - var(--track-fade-end)), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 var(--track-fade-start),
      #000 calc(100% - var(--track-fade-end)), transparent 100%);
  }
  .category-chips.is-scrolled { --track-fade-start: 20px; }
  .category-chips.is-overflowing { --track-fade-end: 24px; }
  .category-chips .chip { scroll-snap-align: start; }
  .category-chips::-webkit-scrollbar { display: none; }
  .bool-chips { flex: 1 1 100%; order: 3; margin-bottom: 0; }
  .view-toggle { display: flex; }
  .header-right { gap: 8px; }
  .toggle-label { display: none; }
  /* Phone header keeps identity + destination + the view toggle. Site nav is
     reachable through the wordmark, which links home. The scope prefix and year
     go with it: at 375px the wordmark, a full-width destination and the toggle
     already claim the row, and anything else spills into the toggle. */
  .map-nav { display: none; }
  .map-scope-label, .map-scope-dash, .map-scope-year { display: none; }
  .map-scope { flex: 0 0 auto; }
  .wordmark { font-size: 17px; }

  /* Collapse sidebar when popup is open */
  .sidebar.collapsed {
    max-height: 0; overflow: hidden; border-top: none;
  }

  /* View modes */
  .main.view-list .map-container { display: none; }
  .main.view-list .sidebar {
    display: flex; flex: 1; flex-direction: column;
    max-height: 100%; min-height: 0;
    border-top: none; overflow: hidden;
  }
  .main.view-list #filters { flex: 0 0 auto; max-height: 52dvh; overflow-y: auto; }
  .main.view-list .spot-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .main.view-map .sidebar { display: none; }
  .main.view-map .map-container { flex: 1; }

  /* ── Mobile map: persistent filter strip ──
     The map view stays a true full-map surface, but its filter state no longer
     hides behind a floating badge. This row owns it: what is active, how to
     drop it, and the way into the full sheet. It is a sibling row rather than
     an overlay so it never covers tiles the visitor is reading. */
  .main.view-map .map-chip-strip {
    position: relative;
    z-index: 1;
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding-top: 5px;
    padding-bottom: 5px;
    padding-left: max(12px, env(safe-area-inset-left, 0px));
    padding-right: max(12px, env(safe-area-inset-right, 0px));
    border-bottom: 1px solid var(--border);
    background: var(--bg);
  }
  /* The sheet is the strip's expanded form — showing both at once would state
     the same filters twice, and the backdrop would swallow the taps anyway. */
  .main.view-map.filters-open .map-chip-strip { display: none; }
  /* Scroll affordance as a mask on the track rather than a bar painted over it:
     chips dissolve into the paper at whichever edge still has track beyond it,
     instead of being guillotined mid-pill as they pass under Filters. Both
     insets are 0 until JS says otherwise, which leaves the gradient opaque. */
  .map-chip-track {
    --track-fade-start: 0px;
    --track-fade-end: 0px;
    display: flex;
    flex: 1 1 auto;
    gap: 6px;
    align-items: center;
    min-width: 0;
    overflow-x: auto;
    padding: 3px 2px;
    scroll-padding-inline: 2px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--track-fade-start),
      #000 calc(100% - var(--track-fade-end)), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 var(--track-fade-start),
      #000 calc(100% - var(--track-fade-end)), transparent 100%);
  }
  /* Classes live on the track itself so the same two rules serve every chip row
     that scrolls, not just the one inside .map-chip-strip. */
  .map-chip-track.is-scrolled { --track-fade-start: 22px; }
  .map-chip-track.is-overflowing { --track-fade-end: 26px; }
  .map-chip-track::-webkit-scrollbar { display: none; }
  .map-chip {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    padding: 10px 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--bg);
    color: var(--text-secondary);
    font: 650 13px/1 var(--font-display);
    white-space: nowrap;
    cursor: pointer;
  }
  .map-chip:hover { border-color: var(--text-muted); color: var(--text); }
  .map-chip:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
  /* The way into the sheet never scrolls out of reach. */
  .map-chip-filters {
    border-color: rgba(45, 42, 38, 0.22);
    color: var(--text);
  }
  .map-chip-plus { font-size: 16px; font-weight: 600; line-height: 1; }
  /* Open now is the one chip that earns the accent: it is the only filter whose
     membership changes with the clock, so its state must read at a glance. */
  .map-chip-open-now.active {
    border-color: var(--primary);
    background: var(--accent-soft);
    color: var(--primary-hover);
  }
  /* Removable chips are shared with the sheet's summary row; only the type
     scale is nudged so the strip reads as one set of controls. */
  .map-chip-track .mobile-filter-summary-chip { font: 650 13px/1 var(--font-display); }

  /* ── Result-count pill ──
     The map view's missing feedback loop, plus a labeled route to the list that
     the icon-only header toggle cannot give. Parked above the Mapbox control
     row (44px control + 10px margin) so it clears attribution, the wordmark
     logo, and geolocate at every width instead of dodging them per breakpoint. */
  .main.view-map .result-count-pill {
    position: absolute;
    bottom: calc(64px + env(safe-area-inset-bottom, 0px));
    left: 50%;
    z-index: 700;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    max-width: calc(100% - 32px);
    min-height: 44px;
    padding: 10px 18px;
    transform: translateX(-50%);
    border: 1px solid rgba(45, 42, 38, 0.14);
    border-radius: var(--radius-pill);
    background: var(--text);
    box-shadow: 0 6px 20px rgba(45, 42, 38, 0.24);
    color: var(--bg);
    font: 650 13px/1 var(--font-display);
    white-space: nowrap;
    cursor: pointer;
  }
  .main.view-map .result-count-pill:focus-visible {
    outline: 2px solid var(--bg); outline-offset: 2px;
  }
  .result-count-pill-sep { opacity: 0.45; }
  .result-count-pill-action { color: var(--primary-light); }
  /* An open place card owns the lower map; an open sheet owns the screen. */
  .main.view-map:has(> #placeSheet:not([hidden])) .result-count-pill,
  .main.view-map.filters-open .result-count-pill { display: none; }

  .main.view-map.filters-open .mobile-filter-backdrop {
    position: absolute;
    inset: 0;
    z-index: 925;
    display: block;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: rgba(28, 25, 23, 0.38);
    cursor: pointer;
  }

  .main.view-map.filters-open .sidebar {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 950;
    display: flex;
    width: 100%;
    min-width: 0;
    max-height: min(82dvh, calc(100dvh - 56px - env(safe-area-inset-top, 0px)));
    flex-direction: column;
    overflow: hidden;
    padding-bottom: 0;
    border: 0;
    border-radius: 18px 18px 0 0;
    background: var(--bg);
    box-shadow: 0 -16px 42px rgba(28, 25, 23, 0.18);
    opacity: 1;
  }
  .main.view-map.filters-open #filters {
    width: 100%;
    max-height: 100%;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* The scroll region starts flush with the sheet edge and the sticky header
       carries the inset itself. With the padding here instead, the header pins
       16px low and scrolled chips bleed through the gap above it. */
    padding-top: 0;
    padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    border-bottom: 0;
    -webkit-overflow-scrolling: touch;
  }
  .main.view-map.filters-open .category-chips {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    overflow: visible;
    padding: 3px;
    scroll-padding-inline: 0;
  }
  .main.view-map.filters-open .category-filter-chip {
    width: 100%;
    min-width: 0;
    padding: 8px 10px;
    justify-content: flex-start;
  }
  .main.view-map.filters-open .category-filter-chip .chip-count { margin-left: auto; }
  .main.view-map.filters-open .spot-list,
  .main.view-map.filters-open .list-scope-bar { display: none; }

  .main.view-map.filters-open .mobile-filter-head {
    position: sticky;
    top: 0;
    z-index: 2;
    order: -2;
    display: flex;
    flex: 1 1 100%;
    align-items: center;
    justify-content: space-between;
    min-height: 52px;
    margin-bottom: 2px;
    padding-top: 16px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border);
    background: var(--bg);
    font-size: 16px;
    font-weight: 700;
  }
  .mobile-filter-head button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    padding: 8px;
    border: 0;
    border-radius: var(--radius-btn);
    background: transparent;
    color: var(--text);
    font: inherit;
    cursor: pointer;
  }
  .mobile-filter-head button:hover { background: var(--border-light); }
  .mobile-filter-head button:focus-visible {
    outline: 2px solid var(--primary); outline-offset: 1px;
  }

  .mobile-filter-summary:not([hidden]):not(:empty) {
    order: -1;
    display: flex;
    flex: 1 1 100%;
    gap: 6px;
    overflow-x: auto;
    padding: 3px 4px 5px;
    scroll-padding-inline: 4px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .mobile-filter-summary::-webkit-scrollbar { display: none; }
  .mobile-filter-summary-chip {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    padding: 9px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--border-light);
    color: var(--text-secondary);
    font: 600 12px/1.2 var(--font-display);
    white-space: nowrap;
  }
  button.mobile-filter-summary-chip { cursor: pointer; }
  button.mobile-filter-summary-chip:hover { border-color: var(--text-muted); color: var(--text); }
  button.mobile-filter-summary-chip:focus-visible {
    outline: 2px solid var(--primary); outline-offset: 2px;
  }
  .category-summary-chip {
    border-color: var(--cat-ui);
    background: var(--cat-hover);
    color: var(--cat-strong);
  }
  .category-summary-icon { width: 15px; height: 15px; color: var(--cat-strong); }
  button.category-summary-chip:hover {
    border-color: var(--cat-strong);
    background: var(--cat-selected);
    color: var(--cat-strong);
  }
  button.category-summary-chip:focus-visible { outline-color: var(--cat-strong); }
  /* ── Sheet commit footer ──
     The sheet used to have no commit action: you closed it blind through the ✕.
     This pins the decision to the bottom of the modal, with the live count so
     the visitor knows what they are agreeing to before the map reappears.
     Pinned by flex rather than position:sticky — #filters is the scroll region,
     so the footer is simply the column's last, non-shrinking row. */
  .main.view-map.filters-open #filters { flex: 1 1 auto; min-height: 0; }
  .main.view-map.filters-open .mobile-filter-foot {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 10px;
    padding-top: 10px;
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    padding-left: max(16px, env(safe-area-inset-left, 0px));
    padding-right: max(16px, env(safe-area-inset-right, 0px));
    border-top: 1px solid var(--border);
    background: var(--bg);
  }
  .filter-reset-btn {
    flex: 0 0 auto;
    min-height: 44px;
    padding: 10px 12px;
    border: 0;
    border-radius: var(--radius-btn);
    background: transparent;
    color: var(--text-secondary);
    font: 600 13px/1 var(--font-display);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }
  .filter-reset-btn:hover { color: var(--text); }
  .filter-reset-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
  .filter-commit-btn {
    flex: 1 1 auto;
    min-height: 48px;
    padding: 12px 18px;
    border: 0;
    border-radius: var(--radius-btn);
    background: var(--text);
    color: var(--bg);
    font: 700 15px/1 var(--font-display);
    white-space: nowrap;
    cursor: pointer;
  }
  .filter-commit-btn:hover { background: #1F1D1A; }
  .filter-commit-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
  /* A dead end stays committable: closing is how the visitor reaches the chip
     strip that removes the offending filter, so this reads spent, not blocked. */
  .filter-commit-btn.is-empty { background: var(--text-muted); }
  .list-scope-bar {
    min-height: 42px;
    padding-left: max(16px, env(safe-area-inset-left, 0px));
    padding-right: max(16px, env(safe-area-inset-right, 0px));
  }
}

/* On short landscape phones, keep at least two useful list rows visible.
   The complete filter stack remains scrollable in its own compact region. */
@media (max-height: 600px) and (max-width: 1024px) and (orientation: landscape) {
  .main.view-list #filters { max-height: 38dvh; }
  /* Vertical space is the scarce resource here; the 44px chips stay, the
     surrounding padding gives the map back a few rows. */
  .main.view-map .map-chip-strip { padding-top: 2px; padding-bottom: 2px; }
}

/* ── Place detail: editorial utility card ───────────────────────────────
   One outer paper surface, with hierarchy created by typography, rules and
   alignment rather than a stack of rounded boxes. */
.place-sheet,
.place-panel {
  --pd-surface: #FEFDFB;
  --pd-text: #262421;
  --pd-text-2: #514C47;
  --pd-text-3: #77716B;
  --pd-border: #E5E1DC;
  --pd-border-soft: #EFEBE6;
  --pd-accent: var(--accent);
  --pd-accent-soft: #F3F1ED;
  --pd-accent-text: var(--accent);
  background: var(--pd-surface);
  container-name: place-detail;
  container-type: inline-size;
  font-family: var(--font-body);
  color: var(--pd-text);
}
.place-sheet::before,
.place-panel::before {
  content: '';
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: var(--pd-accent);
}
.place-sheet { border-radius: 18px; }
.place-panel { border-radius: 14px; }
.pd-body {
  padding: 8px 24px 18px;
  scrollbar-gutter: auto;
}
.place-panel .pd-body { padding: 24px 26px 24px; }
.pd-header { padding-top: 4px; padding-right: 42px; }
.pd-chips { gap: 7px; margin-bottom: 11px; }
.pd-chip {
  padding: 3px 8px;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  border-radius: 4px;
  font-family: var(--font-display);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.075em;
}
.pd-name {
  font-family: var(--font-display);
  font-size: 25px;
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.025em;
}
.place-panel .pd-name { font-size: 25px; }
.pd-local-name {
  margin-top: 5px;
  font-family: 'Noto Sans KR', var(--font-body);
  font-size: 14px;
  line-height: 1.45;
}
/* Transit line: station · exit · walk, on its own line under the local name.
   Latin-first — the Noto Sans KR stack above belongs to the Korean name only.
   Normal inline flow, not a flex row, so a long station name wraps as ordinary
   text rather than as rigid boxes. */
.pd-locality {
  display: block;
  margin-top: 3px;
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.45;
  color: var(--pd-text-3);
  overflow-wrap: anywhere;
}
/* The exit is the one number a visitor acts on at the turnstile, so it carries
   a little more weight without breaking out of the muted tone family. */
.pd-locality-exit { font-weight: 600; color: var(--pd-text-2); }
.pd-locality-walk { white-space: nowrap; }
.pd-locality-dot { color: var(--pd-text-3); }
/* Ratings are Google's, and the chip says so instead of implying they are mine. */
.pd-rating-source {
  margin-left: 4px;
  font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--pd-text-3);
}
.pd-meta {
  gap: 6px 9px;
  margin-top: 10px;
  font-size: 12.5px;
  line-height: 1.45;
}
.pd-meta-arrival { max-width: 100%; }
.pd-meta-arrival svg { width: 13px; height: 13px; flex: 0 0 auto; }
.pd-meta-arrival span { min-width: 0; }
.pd-awards {
  margin-top: 9px;
  padding-left: 10px;
  border-left: 2px solid var(--pd-accent);
  color: var(--pd-text-2);
  font-size: 12.5px;
  line-height: 1.45;
}
.pd-desc {
  margin-top: 15px;
  font-family: var(--font-body);
  font-size: 15.5px;
  line-height: 1.62;
  color: var(--pd-text-2);
}
.pd-hero {
  height: 132px;
  margin: 17px -26px 0;
  overflow: hidden;
  background: var(--pd-border-soft);
}
.place-sheet .pd-hero { margin-left: -24px; margin-right: -24px; }
.pd-hero[hidden] { display: none; }
.pd-hero img { width: 100%; height: 100%; display: block; object-fit: cover; }

.pd-close {
  top: 14px;
  right: 16px;
  z-index: 5;
  width: 44px;
  height: 44px;
  background: rgba(254, 253, 251, 0.94);
  box-shadow: 0 1px 6px rgba(38, 36, 33, 0.12);
  color: var(--pd-text-2);
}
.place-sheet .pd-close { top: 9px; }
.pd-close::after { inset: 0; }
.pd-close:hover { background: var(--pd-accent-soft); }
.pd-more-toggle { display: flex; width: max-content; }
.pd-close:focus-visible,
.pd-action:focus-visible,
.pd-cta:focus-visible,
.pd-handle:focus-visible,
.pd-more-toggle:focus-visible,
.pd-feedback-link:focus-visible,
.pd-journal-banner:focus-visible,
.pd-related-item:focus-visible,
.pd-nearby-item:focus-visible,
.pd-cta-secondary:focus-visible,
.pd-hours-week-toggle:focus-visible {
  outline: 3px solid var(--primary);
  outline-offset: 3px;
}
.pd-handle:focus-visible { outline-offset: -4px; }

/* Flat utility rail. Icons clarify destination; labels clarify intent. */
.pd-actions,
.place-panel .pd-actions,
.place-sheet .pd-actions {
  display: flex;
  flex-wrap: nowrap;
  gap: 0;
  margin-top: 17px;
  padding: 7px 0;
  border-top: 1px solid var(--pd-border-soft);
  border-bottom: 1px solid var(--pd-border-soft);
}
.pd-action,
.place-panel .pd-action,
.place-sheet .pd-action {
  flex: 1 1 0;
  min-width: 0;
  min-height: 48px;
  padding: 6px 3px;
  border: 0;
  border-radius: 0;
  background: transparent;
  flex-direction: column;
  gap: 4px;
  font-family: var(--font-display);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--pd-text-2);
}
.pd-action + .pd-action,
.place-sheet .pd-action + .pd-action { border-left: 1px solid var(--pd-border-soft); }
.pd-action:hover,
.place-sheet .pd-action:hover { background: var(--pd-accent-soft); border-color: var(--pd-border-soft); }
.pd-action svg,
.place-panel .pd-action svg,
.place-sheet .pd-action svg { width: 18px; height: 18px; }
.pd-action-label { width: 100%; overflow: hidden; text-align: center; text-overflow: ellipsis; }
/* Reserve leads the rail and takes the place's own accent tint — enough to
   read as the primary action without the contrast failure a solid category
   fill would cause behind white text. */
.pd-action-reserve,
.place-panel .pd-action-reserve,
.place-sheet .pd-action-reserve {
  background: var(--pd-accent-soft);
  color: var(--pd-accent-text);
  font-weight: 700;
}
.pd-action-reserve:hover,
.place-sheet .pd-action-reserve:hover { background: var(--pd-accent-soft); filter: brightness(0.96); }
/* Promoting Reserve makes four actions the widest rail. At phone widths that
   leaves "Show on map" a few pixels short, so tighten this case rather than
   ellipsising the label that returns the visitor to the map. */
@media (max-width: 430px) {
  .pd-actions-4 .pd-action-label { font-size: 11px; letter-spacing: -0.02em; }
}

/* "More near here" — every card ends with the next stop instead of a dead end. */
.pd-nearby { margin-top: 18px; }
.pd-nearby-list { display: grid; gap: 6px; margin-top: 9px; }
.pd-nearby-item {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 52px;
  padding: 9px 11px;
  border: 1px solid var(--pd-border-soft);
  border-radius: 10px;
  background: transparent;
  font-family: var(--font-display);
  text-align: left;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.pd-nearby-item:hover { background: var(--pd-accent-soft); border-color: var(--pd-accent); }
.pd-nearby-name {
  min-width: 0;
  font-size: 13.5px; font-weight: 700; line-height: 1.3;
  color: var(--pd-text);
  overflow-wrap: anywhere;
}
.pd-nearby-meta {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 8px;
}
.pd-nearby-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 7px;
  border-radius: 999px;
  font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.5px;
}
.pd-nearby-chip svg { width: 11px; height: 11px; }
.pd-nearby-walk {
  font-size: 11.5px; font-weight: 600;
  color: var(--pd-text-3);
  white-space: nowrap;
}
@container place-detail (max-width: 380px) {
  .pd-nearby-item { flex-wrap: wrap; }
  .pd-nearby-meta { width: 100%; }
}
/* Must Try + Good For stay paired, but read like an editorial spread. */
.pd-grid-2,
.place-panel .pd-grid-2 {
  display: grid;
  grid-template-columns: minmax(0, 1.42fr) minmax(104px, .78fr);
  align-items: start;
  gap: 25px;
  margin-top: 0;
  padding: 20px 0;
  border-bottom: 1px solid var(--pd-border-soft);
}
.pd-grid-2 > .pd-must-try,
.pd-grid-2 > .pd-good-for { margin: 0; min-width: 0; }
.pd-grid-2 > .pd-good-for { padding-left: 21px; border-left: 1px solid var(--pd-border-soft); }
.pd-must-try,
.pd-good-for {
  margin-top: 0;
  padding: 20px 0;
  border-bottom: 1px solid var(--pd-border-soft);
}
.pd-grid-2 .pd-must-try,
.pd-grid-2 .pd-good-for { padding-top: 0; padding-bottom: 0; border-bottom: 0; }
.pd-section-title,
.pd-card-label {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: .095em;
  text-transform: uppercase;
  color: var(--pd-text-3);
}
.pd-must-try-text {
  margin: 0;
  padding: 0 0 0 14px;
  border-left: 4px solid var(--pd-accent);
  font-family: var(--font-body);
  font-size: 15.5px;
  line-height: 1.55;
  color: var(--pd-text);
}
.pd-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 7px 15px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.pd-tag {
  position: relative;
  flex: 0 1 auto;
  max-width: 100%;
  min-width: 0;
  padding: 0 0 0 11px;
  border-radius: 0;
  background: transparent;
  font-size: 14px;
  line-height: 1.35;
  color: var(--pd-text-2);
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  text-wrap: pretty;
}
.pd-tag::before {
  content: '';
  position: absolute;
  top: .55em;
  left: 0;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--pd-accent);
}

/* Plan Your Visit is a ruled ledger: two concise columns, one full-width note. */
.pd-plan {
  padding: 20px 0 0;
  border-bottom: 1px solid var(--pd-border-soft);
}
.pd-plan-title {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.015em;
}
.pd-plan-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.06fr) minmax(0, .94fr);
}
.pd-plan-item {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  align-content: start;
  gap: 8px;
  min-width: 0;
  padding: 14px 12px 15px 0;
  border-top: 1px solid var(--pd-border-soft);
}
.pd-plan-item:nth-child(2) { padding-left: 16px; border-left: 1px solid var(--pd-border-soft); }
.pd-plan-wide {
  grid-column: 1 / -1;
  padding-right: 0;
  padding-left: 0;
  border-left: 0 !important;
}
.pd-plan-grid > .pd-plan-item:only-child { grid-column: 1 / -1; }
.pd-info-card-icon {
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 1px;
  border-radius: 0;
  background: transparent;
  color: var(--pd-text-3);
}
.pd-info-card-icon svg { width: 15px; height: 15px; }
.place-panel .pd-info-card-icon,
.place-sheet .pd-info-card-icon {
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border-radius: 0;
  background: transparent;
  color: var(--pd-text-3);
}
.place-panel .pd-info-card-icon svg,
.place-sheet .pd-info-card-icon svg { width: 15px; height: 15px; }
.place-panel .pd-card-label,
.place-sheet .pd-card-label { margin-bottom: 8px; font-size: 10.5px; }
.pd-card-value {
  font-family: var(--font-body);
  font-size: 14.5px;
  line-height: 1.58;
  color: var(--pd-text);
  text-wrap: pretty;
}
.place-panel .pd-card-value,
.place-sheet .pd-card-value { font-size: 14.5px; line-height: 1.58; }
.pd-hours-compact { display: grid; gap: 2px; }
.pd-hours-line {
  display: grid;
  grid-template-columns: max-content 9px max-content;
  align-items: baseline;
  column-gap: 3px;
  width: max-content;
  max-width: 100%;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.pd-hours-range { font-weight: 600; }
.pd-hours-note { color: var(--pd-text-2); font-size: 12.5px; }
.pd-hours-note {
  width: 100%;
  grid-template-columns: max-content 9px minmax(0, 1fr);
}
.pd-hours-note > span:last-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pd-hours-note-single { grid-template-columns: minmax(0, 1fr); }
.pd-hours-raw { display: grid; gap: 3px; }
.pd-hours-raw-line { display: block; }
.pd-hours-week-toggle {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: 2px 0 -8px;
  padding: 8px 0;
  border: 0;
  background: transparent;
  color: var(--pd-text-2);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.pd-hours-week-list[hidden] { display: none !important; }
.pd-hours-week-list {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 3px 10px;
  margin: 5px 0 4px;
  padding: 9px 0 0;
  border-top: 1px solid var(--pd-border-soft);
  font-size: 12.5px;
  line-height: 1.45;
}
.pd-hours-week-note-label,
.pd-hours-week-note { margin-top: 4px; }
.pd-hours-week-note { color: var(--pd-text-2); }
.pd-hours-week-value { display: grid; min-width: 0; gap: 1px; }
.pd-hours-week-day-note {
  color: var(--pd-text-3);
  font-size: 11.5px;
  line-height: 1.35;
}
.pd-freshness {
  display: block;
  margin-top: 6px;
  color: var(--pd-text-3);
  font-size: 11.5px;
  line-height: 1.35;
}

.pd-plan-copy { margin: 0; }

/* The component width, not the viewport width, determines whether editorial
   columns have enough measure for prose. The desktop panel is 420px wide, as
   are common phone sheets, so both use a calm single-column reading flow. */
@container place-detail (max-width: 560px) {
  .pd-grid-2,
  .place-panel .pd-grid-2 {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .pd-grid-2 > .pd-good-for {
    padding: 16px 0 0;
    border-top: 1px solid var(--pd-border-soft);
    border-left: 0;
  }
  .pd-plan-grid { grid-template-columns: 1fr; }
  .pd-plan-item,
  .pd-plan-item:nth-child(2) {
    grid-column: 1;
    padding: 14px 0 15px;
    border-left: 0;
  }
}

/* Address, related reading and directions remain open sections, not cards. */
.pd-address,
.pd-related-list-card,
.pd-directions {
  margin: 0;
  padding: 20px 0;
  border-bottom: 1px solid var(--pd-border-soft);
}
.pd-address .pd-section-title,
.pd-related-list-card .pd-section-title,
.pd-directions .pd-section-title { margin-bottom: 9px; }
.pd-addr-list { margin-top: 0; }
.pd-addr-row { padding: 10px 0; min-height: 48px; }
.pd-addr-row + .pd-addr-row { border-top-color: var(--pd-border-soft); }
.pd-addr-text { font-size: 13.5px; line-height: 1.5; }
.pd-addr-list .copy-hint {
  padding: 5px 7px;
  border-radius: 4px;
  background: var(--pd-accent-soft);
  color: var(--pd-accent-text);
}
.pd-related-item { min-height: 52px; border-radius: 0; }
.pd-related-thumb { border-radius: 2px; }
.pd-directions { padding-bottom: 0; border-bottom: 0; }
.pd-directions-note {
  margin: -2px 0 12px;
  color: var(--pd-text-3);
  font-size: 12.5px;
  line-height: 1.4;
}
.pd-google-icon { overflow: visible; }
.pd-feedback {
  display: flex;
  gap: 8px 18px;
  margin: 13px 0 0;
  padding: 9px 0 0;
  border-top: 1px solid var(--pd-border-soft);
}
.pd-feedback-link {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  font-size: 12.5px;
  color: var(--pd-text-3);
}
.pd-feedback-link svg { width: 14px; height: 14px; }

/* The compact mobile preview keeps one unambiguous local navigation action.
   Expanded mobile and desktop expose the complete Directions section above. */
.place-sheet[data-state="preview"] { max-height: min(430px, calc(100dvh - 96px)); }
.place-sheet[data-state="preview"] .pd-body {
  flex: 1 1 auto;
  max-height: none;
  overflow: hidden;
  padding-bottom: 8px;
}
.pd-preview-cta { padding: 11px 20px calc(11px + env(safe-area-inset-bottom, 0px)); }
.pd-preview-cta .pd-cta,
.pd-preview-cta .pd-cta-secondary { min-height: 48px; border-radius: 8px; }
.place-sheet[data-state="expanded"] .pd-preview-cta,
.place-panel .pd-preview-cta { display: none; }

/* Expanded sheets and desktop panels keep the primary local navigation action
   outside the scrolling body. Preview retains its existing dedicated CTA. */
.pd-sticky-cta {
  position: relative;
  z-index: 3;
  flex: 0 0 auto;
  display: none;
  gap: 10px;
  align-items: center;
  padding: 11px 20px calc(11px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--pd-border-soft);
  background: var(--pd-surface);
  box-shadow: 0 -8px 20px rgba(38, 36, 33, 0.06);
}
.pd-sticky-cta .pd-cta,
.pd-sticky-cta .pd-cta-secondary { min-height: 48px; border-radius: 8px; }
/* Phone widths: the ink primary and the Google ghost share one row, so trim
   their horizontal padding rather than let "Open in Naver Maps" wrap to two
   lines. Wrapping stays the fallback if a provider label ever outgrows this. */
@media (max-width: 430px) {
  .pd-cta-bar { gap: 8px; padding-left: 14px; padding-right: 14px; }
  .pd-cta-bar .pd-cta { padding-left: 12px; padding-right: 12px; }
  .pd-cta-bar .pd-cta-secondary { padding-left: 11px; padding-right: 11px; }
}
.place-sheet[data-state="expanded"] .pd-sticky-cta,
.place-panel .pd-sticky-cta { display: flex; }
.place-sheet[data-state="preview"] .pd-sticky-cta { display: none; }
.pd-sr-status {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* Availability is the one operational fact that belongs in the preview.
   It stays compact, wraps without truncation, and remains honest when only
   today's published schedule (not a live status) can be shown safely. */
.pd-availability {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 5px 9px;
  margin-top: 12px;
  padding-top: 11px;
  border-top: 1px solid var(--pd-border-soft);
  font-size: 12.5px;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}
.pd-availability-status {
  font-family: var(--font-display);
  font-weight: 700;
}
.pd-availability-status.is-open { color: #237A45; }
.pd-availability-status.is-closed { color: #A33C34; }
/* Neither open nor closed: an unresolved rule reads in muted ink so it is
   never mistaken for a confirmed state. */
.pd-availability-status.is-unknown {
  color: #7A6A55;
  font-weight: 600;
}
.pd-availability-separator {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--pd-text-3);
}
.pd-availability-today {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px 7px;
  color: var(--pd-text-2);
}
.pd-availability-today strong {
  font-family: var(--font-display);
  color: var(--pd-text);
}
.pd-availability-today.is-closed span { color: #A33C34; }
/* Qualifies a venue-level status for complexes whose sub-units keep their own
   hours. Secondary by design: it must not compete with the status itself. */
.pd-availability-caveat {
  font-size: 11.5px;
  color: var(--pd-text-3);
}

/* Mobile uses a real hidden/inert disclosure region rather than relying only
   on visual clipping. Desktop and the expanded sheet show the full prose;
   preview shows one editorial paragraph. */
.pd-more-region[hidden] { display: none !important; }
.place-panel .pd-desc-summary,
.place-sheet[data-state="expanded"] .pd-desc-summary { display: none; }
.pd-desc-full + .pd-desc-full { margin-top: 10px; }

.pd-tags-more {
  flex: 1 0 100%;
  list-style: none;
}
.pd-tags-more button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: -4px 0 -8px;
  padding: 8px 0;
  border: 0;
  background: transparent;
  color: var(--pd-accent-text);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.pd-tags-more button:focus-visible {
  outline: 3px solid var(--primary);
  outline-offset: 3px;
}

/* Long logistics prose becomes an ordered set of labeled, source-order facts.
   Consecutive facts share a label; procedural sequences are never regrouped. */
.pd-visit-facts,
.pd-visit-fact-items,
.pd-best-time-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.pd-visit-facts { display: grid; gap: 13px; }
.pd-visit-fact {
  display: grid;
  gap: 5px;
  min-width: 0;
}
.pd-visit-fact + .pd-visit-fact {
  padding-top: 12px;
  border-top: 1px solid var(--pd-border-soft);
}
.pd-visit-fact-label {
  font-family: var(--font-display);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: .075em;
  text-transform: uppercase;
  color: var(--pd-accent-text);
}
.pd-visit-fact-items,
.pd-best-time-list { display: grid; gap: 7px; }
.pd-visit-fact-items li,
.pd-best-time-list li {
  position: relative;
  padding-left: 13px;
  text-wrap: pretty;
}
.pd-visit-fact-items li::before,
.pd-best-time-list li::before {
  content: '';
  position: absolute;
  top: .68em;
  left: 0;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--pd-accent);
}
.pd-card-label { margin-top: 0; }
.pd-sr-only {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

@media (max-width: 420px) {
  .pd-body { padding-left: 20px; padding-right: 20px; }
  .place-sheet .pd-hero { margin-left: -20px; margin-right: -20px; }
  .pd-hours-line { font-size: 13px; }
}

@media (max-width: 360px) {
  .place-sheet .pd-actions,
  .place-panel .pd-actions { display: grid; }
  .pd-actions-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pd-actions-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .pd-actions-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pd-actions .pd-action { width: 100%; font-size: 10.5px; }
  .pd-actions .pd-action + .pd-action { border-left: 0; }
  .pd-actions-2 > .pd-action:nth-child(2),
  .pd-actions-3 > .pd-action:nth-child(n+2),
  .pd-actions-4 > .pd-action:nth-child(even) { border-left: 1px solid var(--pd-border-soft); }
  .pd-actions-4 > .pd-action:nth-child(n+3) { border-top: 1px solid var(--pd-border-soft); }
}

@media (prefers-reduced-motion: reduce) {
  .place-sheet *,
  .place-panel * { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; }
}
.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* ══ Personal state, share, and connectivity (WP8) ══════════════════════════
   Saved/been-here is the visitor's own layer over 905 vetted places. It borrows
   the terracotta accent that already means "active filter" and adds no new hue —
   the ten category colours remain the only palette on this page. */

/* ── Saved filter chip (desktop sidebar) ── */
.chip-saved { text-decoration: none; }
.chip-saved-count {
  color: var(--text-secondary);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.chip-saved[aria-pressed='true'] {
  background: var(--primary); border-color: var(--primary); color: var(--bg);
}
.chip-saved[aria-pressed='true'] .chip-saved-count { color: var(--bg); }
.chip-saved[aria-pressed='true']:hover {
  background: var(--primary-hover); border-color: var(--primary-hover);
}
/* Nothing saved yet: still offered, because the chip is the only thing on the
   page that says the star exists — just not competing with live filters. The
   quieting is done on the border alone; dimming the label instead cost it the
   4.5:1 floor, and "★ 0" already states the empty case. */
.chip-saved.is-empty:not([aria-pressed='true']) { border-color: var(--border-light); }

/* ── Place panel: save + been here ── */
.pd-personal {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}
.pd-save,
.pd-visit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 44px;
  padding: 9px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--bg);
  color: var(--text-secondary);
  font: 650 12.5px/1 var(--font-display);
  cursor: pointer;
  transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.pd-personal-glyph { font-size: 14px; line-height: 1; }
.pd-personal-label { line-height: 1.1; }
.pd-save:hover,
.pd-visit:hover { border-color: var(--text-muted); color: var(--text); }
.pd-save:focus-visible,
.pd-visit:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.pd-save[aria-pressed='true'] {
  border-color: var(--primary);
  background: var(--accent-soft);
  color: var(--primary-hover);
}
/* Been here is the more settled of the two states, so it fills with ink rather
   than accent — the accent is reserved for things still on the list. */
.pd-visit[aria-pressed='true'] {
  border-color: var(--text);
  background: var(--text);
  color: var(--bg);
}
.pd-visit[aria-pressed='true']:hover { background: #1F1D1A; color: var(--bg); }
/* Saved *and* visited: the star stays, dimmed, exactly as on the card. */
.pd-save[aria-pressed='true'].is-visited { opacity: 0.72; }
.pd-save[aria-pressed='true'].is-visited:hover { opacity: 1; }

/* ── Saved marker badge ──
   Ink circle, not a category tint: the tile's colour is already carrying the
   category, and ink is the one value that reads against all ten at 32px. */
.map-tile-star {
  position: absolute;
  top: -3px;
  right: -3px;
  width: 16px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--text);
  box-shadow: 0 0 0 1.5px var(--bg);
  color: #FFD27D;
  font-size: 9px;
  line-height: 1;
  pointer-events: none;
  z-index: 1; /* above the burst halo pseudo-element */
}

/* ── Share button (mobile sheet footer) ──
   Desktop's lives in .list-scope-bar and inherits that bar's button styling. */
.filter-share-btn { display: none; }

/* ── Toast ──
   Bottom-centre on both surfaces, above the sheet and the count pill. Ink on
   paper, no icon: it is a receipt, not an alert. */
.map-toast {
  position: fixed;
  left: 50%;
  bottom: max(20px, env(safe-area-inset-bottom, 0px));
  z-index: 1200;
  max-width: min(520px, calc(100vw - 32px));
  padding: 11px 16px;
  transform: translate(-50%, 10px);
  border-radius: var(--radius-btn);
  background: var(--text);
  color: var(--bg);
  font: 600 13px/1.4 var(--font-display);
  text-align: center;
  box-shadow: var(--shadow-elevated);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s ease, transform 0.22s ease;
}
.map-toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

/* ── Offline banner ──
   A status, not an error: everything the visitor needs is already cached, so it
   stays quiet and dismissible rather than blocking the list. */
.offline-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
  padding: 9px 16px;
  border-bottom: 1px solid var(--accent-soft-border);
  background: var(--accent-soft);
  color: var(--text);
  font: 600 12px/1.4 var(--font-display);
}
.offline-banner-copy { flex: 1 1 auto; min-width: 0; }
.offline-banner-dismiss {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-btn);
  background: transparent;
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.offline-banner-dismiss:hover { color: var(--text); background: rgba(172, 85, 55, 0.1); }
.offline-banner-dismiss:focus-visible { outline: 2px solid var(--primary); outline-offset: -3px; }
/* The phone copy is a second mount point for one state; desktop uses the
   sidebar copy only. Author-level guard so [hidden] still wins on both. */
.offline-banner-map { display: none; }
.offline-banner[hidden] { display: none !important; }

@media (max-width: 768px), (max-height: 600px) and (max-width: 1024px) {
  /* Saved joins Open now as the second accent-earning toggle in the strip:
     both answer "what can I act on right now". */
  .map-chip-saved.active {
    border-color: var(--primary);
    background: var(--accent-soft);
    color: var(--primary-hover);
  }
  .map-chip-saved .chip-saved-count { font-size: 12px; font-variant-numeric: tabular-nums; }
  .map-chip-saved.is-empty:not(.active) { border-color: var(--border-light); }

  /* Share sits beside Reset as the sheet's other secondary action, so the
     primary commit button keeps its weight. */
  .main.view-map.filters-open .filter-share-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    min-height: 44px;
    padding: 10px 12px;
    border: 0;
    border-radius: var(--radius-btn);
    background: transparent;
    color: var(--text-secondary);
    font: 600 13px/1 var(--font-display);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }
  .main.view-map.filters-open .filter-share-btn:hover { color: var(--text); }
  .main.view-map.filters-open .filter-share-btn:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
  }
  /* `.main` is column-reverse, so this DOM position lands the notice directly
     above the chip strip — the topmost row of the map surface. */
  .main.view-map .offline-banner-map:not([hidden]) {
    display: flex;
    position: relative;
    z-index: 2;
    padding-left: max(12px, env(safe-area-inset-left, 0px));
    padding-right: max(12px, env(safe-area-inset-right, 0px));
  }
  /* One notice per surface: the map copy owns Map view, the sidebar copy owns
     List, and the filter sheet is not the place to report connectivity. */
  .main.view-map #offlineBannerList { display: none; }
  .map-toast { bottom: max(84px, calc(env(safe-area-inset-bottom, 0px) + 76px)); }
}

@media (prefers-reduced-motion: reduce) {
  .card-fav,
  .map-toast { transition: none; }
}
