/* Where to Stay — the page that answers "which neighbourhood?" before you book.
 *
 * Register: product, not brand. The user is in a task, deciding where to sleep,
 * probably with a booking site open in the next tab. The interface should
 * disappear into that task. So: fixed rem scale rather than fluid clamps (a
 * heading that shrinks inside a 360px pane looks worse, not better), restrained
 * colour, and responsive behaviour that is structural — the panes reflow, the
 * type does not.
 *
 * Every colour here comes from site.css. Nothing new is invented: the navy, the
 * gold, the two planes and the alpha washes already carry measured contrast
 * ratios, and re-deriving them for one page is how a design system rots.
 */

.where-shell {
  /* Page-local structure only. Colour, type and elevation come from site.css. */
  --list-w: 360px;          /* wide enough for a place name and a day range */
  --bar-h: 60px;
  --row-h: 44px;            /* the touch floor; a tablet is a first-class target */
  --pane-gap: 1px;          /* the panes meet on a hairline, not a gutter */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);   /* ease-out-quint: fast, no bounce */

  /* One semantic scale, so nothing is guessed at a call site. */
  --z-map: 1;
  --z-pane: 2;
  --z-bar: 3;
  --z-results: 40;

  display: flex;
  flex-direction: column;
  /* Fills whatever the nav leaves, measured rather than assumed.
     `calc(100dvh - var(--nav-h))` was a standing bug waiting to happen: the
     token is a hand-counted number, and the moment the nav changed — adding the
     tagline made it 107px against a declared 102 — the page overflowed by
     exactly the difference. body is the flex column (below), so this pane takes
     the remainder and no arithmetic can drift. */
  flex: 1 1 auto;
  min-height: 0;            /* lets the map pane actually shrink inside flex */
  background: var(--surface);
}

/* The column that makes the above work. Scoped to this page, so no other
   page's layout is touched.
   height, NOT min-height. min-height lets the body GROW to fit its content,
   which is exactly what it did: the list pane stretched to 1779px inside a
   900px viewport, so .w-panel never overflowed, never scrolled, and the last
   hotel sat a thousand pixels below the fold with no way to reach it. The
   whole point of this layout is that the PANES scroll and the page does not,
   which requires the page to be pinned to the viewport. */
body.where-page {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
}

/* ── The bar ──────────────────────────────────────────────────────────────
   Two knobs and an export. Everything else on this page is the map and the
   list, so the bar stays quiet: one plane above the ground, a hairline under
   it, and no shadow competing with the panels below. */

.where-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  height: var(--bar-h);
  padding: 0 var(--space-gutter);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  position: relative;
  z-index: var(--z-bar);
}

.where-bar .trip-name {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 26ch;
  font: 600 1rem/1.2 var(--display);
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: 8px;
  padding: 7px 9px;
  margin-left: -9px;        /* the text sits flush until the field is engaged */
}
.where-bar .trip-name::placeholder { color: var(--muted); font-weight: 500; }
.where-bar .trip-name:hover { background: var(--wash); }
.where-bar .trip-name:focus-visible {
  background: var(--surface-2);
  outline: 2px solid var(--accent-text);
  outline-offset: 0;
}

/* The knobs. Native number inputs render three different ways across browsers
   and their spinners are unusable at touch size, so this is an explicit
   stepper — standard vocabulary, not an invented affordance. */
.w-knob {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--wash);
  border-radius: 9px;
}
.w-knob > .w-knob-label {
  font: 500 0.75rem/1 var(--display);
  color: var(--muted);
  padding: 0 8px 0 6px;
  white-space: nowrap;
}
.w-knob button {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ink);
  font: 600 1rem/1 var(--display);
  cursor: pointer;
  transition: background 160ms var(--ease);
}
.w-knob button:hover:not(:disabled) { background: var(--surface-2); }
.w-knob button:active:not(:disabled) { background: var(--wash-strong); }
.w-knob button:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }
.w-knob button:disabled { color: var(--muted); opacity: 0.45; cursor: not-allowed; }
.w-knob output {
  min-width: 1.6ch;
  text-align: center;
  font: 600 0.9375rem/1 var(--mono);   /* mono for the number: it changes in place */
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ── Drop a confirmation ─────────────────────────────────────────────
   Full-page veil during drag; a centred confirm card after the drop. */
.w-dropveil {
  position: fixed; inset: 0; z-index: 80;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  pointer-events: none;
}
.w-dropveil span {
  font: 700 1.2rem/1.3 var(--display);
  color: var(--accent-ink);
  background: var(--accent);
  border-radius: 14px;
  padding: 18px 28px;
  border: 2px dashed var(--accent-ink);
}
.w-dropcard {
  position: fixed; z-index: 81;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(420px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow-lg);
  padding: 18px;
  display: grid; gap: 9px;
}
.w-dropcard h2 { margin: 0; font: 700 1.05rem/1.2 var(--display); color: var(--ink); }
.w-dropcard-sub { margin: 0; font-size: 0.82rem; color: var(--muted); }
.w-dropfield { display: grid; gap: 3px; font-size: 0.75rem; color: var(--muted); }
.w-dropfield input {
  border: 1px solid var(--border-strong); border-radius: 8px;
  padding: 7px 9px; font: inherit; font-size: 0.9rem;
  background: var(--surface-2); color: var(--ink);
}
.w-dropcard-file { margin: 0; font-size: 0.75rem; color: var(--muted); }
.w-dropcard-err { margin: 0; font-size: 0.8rem; color: var(--danger); }
.w-dropcard-actions { display: flex; gap: 8px; }
#w-dropsave {
  background: var(--accent); color: var(--accent-ink);
  border: 0; border-radius: 8px; padding: 8px 14px; font: inherit; cursor: pointer;
}
#w-dropsave:disabled { opacity: 0.5; cursor: wait; }
#w-dropcancel {
  background: none; border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 14px; font: inherit; color: var(--muted); cursor: pointer;
}

/* The queue: which files landed, which is being read. Bottom-right, under
   the card's layer so an open card reads first. */
.w-dropqueue {
  position: fixed; right: 16px; bottom: 56px; z-index: 79;
  width: min(300px, calc(100vw - 32px));
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
  padding: 10px 12px;
  display: grid; gap: 5px;
}
.w-dropqueue-title { margin: 0 0 2px; font: 700 0.8rem/1.2 var(--display); color: var(--ink); }
.w-dropqueue-row {
  margin: 0;
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 0.78rem; color: var(--muted);
  /* The row is a GRID item of .w-dropqueue, and grid items default to
     min-width: auto just as flex items do. Without this the row itself
     refuses to shrink below its content, so the name never truncates however
     the name is styled, and the status is pushed outside the panel. */
  min-width: 0;
}
/* min-width: 0 is what makes the ellipsis work. A flex child defaults to
   min-width: auto, which refuses to shrink below its content — so a long
   filename ("HiltonGardenInnKyoto29Sep-2Oct26.png") never truncated and
   pushed the status column off the right edge of the panel instead. */
.w-dropqueue-name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.w-dropqueue-status { flex: none; white-space: nowrap; }
.w-dropqueue-row.is-reading .w-dropqueue-status { color: var(--accent-text); font-weight: 600; }
.w-dropqueue-row.is-review .w-dropqueue-status { color: var(--accent-text); font-weight: 600; }
.w-dropqueue-row.is-booked .w-dropqueue-status { color: var(--w-good); font-weight: 600; }

/* ── Booked hotels ───────────────────────────────────────────────────
   The user's real bookings: a badge on the row, a gold check on the map,
   and a mini-form that keeps everything on this device. */
.w-booked-badge {
  font: 700 0.62rem/1 var(--display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-ink);
  background: var(--accent);
  border-radius: 5px;
  padding: 3px 6px;
}
.w-bookedit {
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--ink);
  border-radius: 8px;
  padding: 5px 10px;
  font: 500 0.8rem/1 var(--display);
  cursor: pointer;
}
.w-bookedit:hover { border-color: var(--accent-text); }
.w-booked-info { font-size: 0.8rem; color: var(--muted); }
.w-booked-info .w-doclink, .w-booked-info .w-unbook {
  border: 0; background: none; padding: 0;
  color: var(--accent-text); font: inherit; cursor: pointer;
  text-decoration: underline;
}
.w-bookform {
  display: grid;
  gap: 8px;
  margin: 10px 0 4px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--wash);
}
.w-bookform .w-conf {
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  padding: 7px 9px;
  font: inherit;
  background: var(--surface-2);
  color: var(--ink);
}
.w-bookform .w-attach { font-size: 0.8rem; color: var(--muted); display: grid; gap: 4px; }
.w-bookform-actions { display: flex; gap: 8px; }
.w-bookform-actions .w-booksave {
  background: var(--accent); color: var(--accent-ink);
  border: 0; border-radius: 8px; padding: 6px 12px; font: inherit; cursor: pointer;
}
.w-bookform-actions .w-bookcancel {
  background: none; border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 12px; font: inherit; color: var(--muted); cursor: pointer;
}
.w-bookform-note { margin: 0; font-size: 0.72rem; color: var(--muted); }

.w-mybooked {
  margin: 10px 0;
  padding: 10px;
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
}
.w-mybooked-label { margin: 0 0 6px; font-size: 0.8rem; color: var(--muted); }
.w-mybooked-row { display: flex; gap: 8px; }
.w-mybooked-row input {
  flex: 1; border: 1px solid var(--border-strong); border-radius: 8px;
  padding: 7px 9px; font: inherit; background: var(--surface-2); color: var(--ink);
}
.w-mybooked-row button {
  border: 0; background: var(--accent); color: var(--accent-ink);
  border-radius: 8px; padding: 0 14px; font: inherit; cursor: pointer;
}
.w-bookhit { padding: 8px 2px; border-top: 1px solid var(--border); }
.w-bookhit-name { display: block; margin-bottom: 6px; }
.w-bookhit-name span { color: var(--muted); font-size: 0.8rem; }

/* The map's gold check for a booked hotel. Positioned by mapview.js. */
.w-booked-layer { position: absolute; inset: 0; pointer-events: none; z-index: 7; overflow: hidden; }
.w-bookedpin {
  position: absolute;
  transform: translate(-50%, -100%);
  display: grid;
  justify-items: center;
  gap: 2px;
}
.w-bookedpin-icon { line-height: 0; }
.w-bookedpin-icon svg { filter: drop-shadow(0 2px 3px rgba(6, 32, 35, 0.35)); }
.w-bookedpin-label {
  font: 600 0.68rem/1.1 var(--display);
  color: var(--ink);
  background: color-mix(in srgb, var(--surface-2) 88%, transparent);
  border-radius: 5px;
  padding: 2px 5px;
  max-width: 140px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Getting Around ─────────────────────────────────────────────────
   Same pill family as the knobs; the checked segment gets the gold. */
.w-mode {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--wash);
  border-radius: 9px;
}
.w-mode button {
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--muted);
  font: 500 0.75rem/1 var(--display);
  padding: 7px 10px;
  white-space: nowrap;
  cursor: pointer;
  transition: background 160ms var(--ease), color 160ms var(--ease);
}
.w-mode button:hover { color: var(--ink); }
.w-mode button:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }
.w-mode button[aria-checked="true"] {
  background: var(--accent);
  color: var(--accent-ink);
}

.where-bar .btn-export { margin-left: auto; }

/* ── The two panes ────────────────────────────────────────────────────────
   Desktop: list beside map, map takes the remainder because the map is the
   answer. Below 1080 they stack and the map goes on top, sticky, so it stays
   in view while the list scrolls under it. That is a structural change, not a
   fluid one. */

/* The map fills the whole area and the list FLOATS ON IT.
   Side by side, the pane's transparency revealed only the page background --
   a colour so close to the pane's own that it read as no change at all. Glass
   needs something behind it worth seeing. */
.where-panes {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  background: var(--surface);
}

.where-list {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--list-w);
  z-index: var(--z-pane);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Translucent, the same as the panels on the map. The pane used to be a solid
     slab of --surface-2 against a solid map, which is two opaque halves meeting
     on a hairline; letting the page's own ground read through it is what gives
     the layout air. Safe for the text because what shows through is --surface,
     a near neighbour of --surface-2 rather than a different colour, so the
     contrast the type was measured against barely moves. */
  background: var(--map-panel);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.where-map {
  position: absolute;
  inset: 0;
  min-height: 0;
  background: var(--surface);
  z-index: var(--z-map);
  overflow: hidden;
}
#w-map { position: absolute; inset: 0; }

.w-hotel-layer {
  position: absolute;
  inset: 0;
  z-index: 3;
  overflow: hidden;
  pointer-events: none;
}

.w-hotel-layer .w-mappin {
  position: absolute;
  left: 0;
  top: 0;
  border: 0;
  padding: 0;
  background: transparent;
  font: inherit;
  color: inherit;
}

/* ── Adding an activity ──────────────────────────────────────────────────
   The primary action on the page, so it gets the top of the pane and the full
   width. Not a toolbar button and not a modal: you type, you pick, it lands. */

.w-add {
  position: relative;
  padding: 12px;
  border-bottom: 1px solid var(--border);
}
.w-add input {
  width: 100%;
  height: 40px;
  padding: 0 12px 0 34px;
  font: 400 0.9375rem/1 var(--display);
  color: var(--ink);
  background: var(--wash);
  border: 1px solid transparent;
  border-radius: 9px;
  transition: background 160ms var(--ease), border-color 160ms var(--ease);
}
/* Placeholders carry the same 4.5:1 requirement as body text. --muted is
   measured for it; a lighter grey "for elegance" is the most common way a
   search field becomes unreadable. */
.w-add input::placeholder { color: var(--muted); }
.w-add input:hover { background: var(--wash-strong); }
.w-add input:focus-visible {
  background: var(--surface-2);
  border-color: var(--accent-text);
  outline: 2px solid var(--accent-text);
  outline-offset: -1px;
}
.w-add .w-search-icon {
  position: absolute;
  left: 23px;
  top: 50%;
  translate: 0 -50%;
  width: 15px;
  height: 15px;
  color: var(--muted);
  pointer-events: none;
}

/* Results escape the pane's scroll container, so they are positioned fixed by
   script rather than absolute — an absolutely-positioned dropdown inside an
   overflow:auto parent gets clipped. */
.w-results {
  position: fixed;
  z-index: var(--z-results);
  max-height: 40vh;
  overflow-y: auto;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  padding: 4px;
}
.w-results button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 10px;
  border: 0;
  border-radius: 7px;
  background: none;
  font: 400 0.875rem/1.35 var(--display);
  color: var(--ink);
  cursor: pointer;
}
.w-results button:hover,
.w-results button[aria-selected="true"] { background: var(--wash); }
.w-results button:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }
.w-results .w-result-sub {
  display: block;
  font-size: 0.75rem;
  color: var(--muted);
  margin-top: 1px;
}

/* ── The list ─────────────────────────────────────────────────────────────
   Rows, not cards. A card per activity would be eight nested boxes inside a
   panel inside a page, and the divider does the same job for nothing. */

/* The scroll lives on .w-panel now — the hotel tab has its own scrolling
   content, and two nested scrollers on one pane means the inner one traps the
   wheel while the outer one never moves. */
#w-items { flex: 1 1 auto; min-height: 0; }

.w-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--row-h);
  padding: 6px 12px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: none;
  text-align: left;
  cursor: pointer;
  transition: background 140ms var(--ease);
}
.w-item:hover { background: var(--wash); }
.w-item:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }

/* The marker is the whole reason the list and the map are legible together:
   the same glyph appears on the pin, so you can look at either and find the
   other. It is the page's most valuable affordance and it costs one span. */
.w-marker {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--map-pin);
  color: var(--map-pin-ink);
  font: 600 0.6875rem/1 var(--mono);
  font-variant-numeric: tabular-nums;
}
.w-item[data-current="true"] .w-marker,
.w-item:hover .w-marker { background: var(--accent); color: var(--accent-ink); }

.w-title {
  flex: 1 1 auto;
  min-width: 0;
  font: 400 0.9375rem/1.3 var(--display);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.w-remove {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 140ms var(--ease), background 140ms var(--ease), color 140ms var(--ease);
}
.w-item:hover .w-remove,
.w-remove:focus-visible { opacity: 1; }
.w-remove:hover { background: var(--danger-soft); color: var(--danger); }
.w-remove:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }
/* A pointer that cannot hover has no way to reveal a hover-only control. */
@media (hover: none) { .w-remove { opacity: 1; } }

/* Grouped state: once there is enough to cluster, the same list gains headings.
   The list does not move and nothing is navigated to — it reorganises in place. */
.w-group-head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 10px 12px 6px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.w-group-name {
  font: 600 0.8125rem/1.2 var(--display);
  color: var(--ink);
}
.w-group-days {
  margin-left: auto;
  font: 500 0.75rem/1.2 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
}

/* Collapsible area groups on the activities list (Hotels tab) */
.w-area-group { border-bottom: 1px solid var(--border); }
.w-area-toggle {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: 8px 10px; border: 0; background: var(--surface-2);
  color: var(--ink); cursor: pointer; font: inherit; text-align: left;
}
.w-area-toggle:hover { background: var(--wash); }
.w-area-chevron {
  flex-shrink: 0; transition: transform 160ms var(--ease);
}
.w-area-group.is-open .w-area-chevron { transform: rotate(90deg); }
.w-area-label {
  flex: 1 1 auto; min-width: 0;
  font: 600 0.78rem/1.3 var(--display); color: var(--accent);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.w-area-count {
  flex-shrink: 0;
  font: 700 0.62rem/1 var(--display);
  background: var(--wash); color: var(--muted);
  border-radius: 8px; padding: 2px 6px;
}

/* ── Empty state ─────────────────────────────────────────────────────────
   It teaches the interface rather than announcing that nothing is here. The
   map is deliberately not shown a world view behind it: an unplaced map is
   noise, so the pane holds the instruction until there is something to place. */

.w-empty {
  margin: auto;
  padding: 28px 24px 36px;
  max-width: 34ch;
  text-align: center;
}
.w-empty h2 {
  font: 600 1.0625rem/1.3 var(--display);
  color: var(--ink);
  margin: 0 0 6px;
  text-wrap: balance;
}
.w-empty p {
  font: 400 0.875rem/1.5 var(--display);
  color: var(--muted);
  margin: 0;
  text-wrap: pretty;
}

/* ── The map's own furniture ─────────────────────────────────────────────
   Attribution is a licence obligation, not decoration: the rail graph is
   OSM-derived under ODbL and must be credited wherever it is drawn, including
   over Apple's tiles. */

/* Attribution and scale, one row, bottom-left. The scale used to be a second
   absolutely-positioned box with a hand-picked left offset, which is the same
   trick that made the three top-left controls drift apart -- the attribution
   text wraps at narrow widths and any fixed offset would land on top of it. */
.w-mapfoot {
  position: absolute;
  left: calc(var(--list-w) + 12px);
  bottom: 10px;
  z-index: 2;
  display: flex;
  align-items: stretch;
  gap: 8px;
  max-width: calc(100% - var(--list-w) - 24px);
  pointer-events: none;
}
.w-mapfoot > * { pointer-events: auto; }

.w-credit {
  position: static;
  margin: 0;
  padding: 4px 9px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--surface-2) 88%, transparent);
  border: 1px solid var(--border);
  font: 400 0.6875rem/1.35 var(--mono);
  color: var(--muted);
  pointer-events: auto;
}
.w-credit a { color: var(--accent-text); }

/* The scale bar. Label above a ruled line, the line drawn to the exact width
   the label claims -- so it can be laid against anything on the map and read
   off directly. The end ticks are what make it a ruler rather than an
   underline: they mark where the measurement starts and stops. */
.w-scale {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  padding: 4px 9px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--surface-2) 88%, transparent);
  border: 1px solid var(--border);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.w-scale-label {
  font: 500 0.6875rem/1 var(--mono);
  font-variant-numeric: tabular-nums;   /* the number changes in place */
  color: var(--ink);
  white-space: nowrap;
  text-align: center;
}
.w-scale-bar {
  display: block;
  height: 5px;
  min-width: 20px;
  border: 1px solid var(--ink);
  border-top: 0;          /* three sides: a ruler's end ticks and its baseline */
  align-self: center;
  transition: width 120ms var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .w-scale-bar { transition: none; }
  .w-mappin { transition: none; }
}

/* A legend only when there is something to explain. */
/* Top-left, NOT bottom-right: MapKit puts its own zoom buttons in that corner
   and the legend was sitting on top of them, which is how the zoom controls
   came to look missing when they were there the whole time. The map's own
   furniture claims bottom-right and top-right; this takes what is left. */
.w-legend {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
  padding: 4px 6px;
  border-radius: 6px;
  background: var(--map-panel);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid var(--border);
  font: 400 0.6rem/1 var(--display);
  color: var(--muted);
}
/* The label reads on one line; the ramp is a swatch under it, not a thing the
   line has to make room for. */
.w-legend-text { white-space: nowrap; }

.w-legend .w-ramp {
  width: 100%;
  height: 5px;
  border-radius: 999px;
  /* A sample of what the map actually does now: the left half is a clear
     area, the right half is the dim laid over it, at the overlay's own 32%.
     There is no gradient left to show — the bands were replaced by a single
     mask with the good areas cut out of it. */
  background: linear-gradient(90deg,
    transparent 0 46%,
    color-mix(in srgb, var(--map-dim) 32%, transparent) 54% 100%);
  border: 1px solid var(--border);
}

/* ── Hotel pins ──────────────────────────────────────────────────────────
   A dot and a name. Drawn by us rather than by MapKit because MapKit will not
   render a marker whose forced label cannot be placed, which on a dense list
   means it renders none of them. Deliberately a different shape from the
   numbered activity teardrops: a hotel is a suggestion, an activity is a
   decision, and they should not look like the same kind of thing. */

.w-mappin {
  /* inline-flex + max-content, NOT block-level flex.
     MapKit drops a custom annotation into a container that spans the whole map,
     so a block-level flex box stretches to fill it: every pin measured ~110px
     wide whatever it contained, even one whose label was display:none and whose
     only visible child was a 12px dot. Every collision test then saw every pin
     overlapping every other one, so the label pass hid names that had room and
     the nudge pass could never find a clear spot. The box has to be the size of
     what is in it. */
  display: inline-flex;
  width: max-content;
  align-items: center;
  gap: 5px;
  /* MapKit positions the element's top-left on the coordinate, so shift it to
     centre the dot on the actual point. The nudge on top of that is set by the
     collision pass, which walks a pin out from under whatever is covering it. */
  transform: translate(calc(-5px + var(--nudge-x, 0px)),
                       calc(-5px + var(--nudge-y, 0px)));
  transition: transform 140ms var(--ease);
  pointer-events: auto;
  cursor: pointer;
}

.w-mappin-dot {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  /* GOLD, not green.
     The dot was --map-heat-line, the same hue as the heat bands it sits on and
     as the parkland underneath. It was invisible at rest and only became
     findable on hover, when it turned gold -- so the pins read as appearing on
     hover and vanishing after, when in truth they were there the whole time.
     Gold was the heat colour until the bands went green; now that they are, it
     is the one strong colour on this map with nothing to collide with, and it
     separates a hotel from the navy activity pins at a glance. */
  background: var(--accent);
  /* Ring first, then shadow: the ring is what holds the edge against a busy
     basemap, the shadow is what lifts it off one. */
  box-shadow: 0 0 0 2.5px #FFFFFF, 0 1px 4px rgba(0, 0, 0, 0.45);
}

/* A plate, not bare text: a place name floating on a map crosses roads, parks
   and other labels within a few pixels and becomes unreadable on all of them. */
.w-mappin-label {
  max-width: 15ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 2px 6px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.97);
  font: 600 0.6875rem/1.3 var(--display);
  /* Map ink, not --ink: this label sits on its own near-white chip over the
     basemap, which stays light in both themes. --ink would flip to near-white
     in dark and disappear against its own background. */
  color: var(--map-road);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

/* Crowded out: the dot stays so the hotel is still on the map, the name goes
   until zooming in makes room. Never both hidden -- a missing dot would mean a
   hotel the list claims exists and the map denies. */
.w-mappin.is-dot-only .w-mappin-label { display: none; }

/* Merged into a neighbour's stack. Hidden entirely, because its dot is under
   another one and adds nothing but weight; the stack's count speaks for it. */
.w-mappin.is-merged { display: none; }

/* A pin standing for several is a BUBBLE WITH THE NUMBER IN IT, the shape every
   map uses for a cluster — not a dot with a number stuck beside it.
   A 15px dot loses to the navy activity teardrops, which are twice its size, so
   a stack of eighteen hotels next to Disneyland simply vanished behind them and
   the map looked as though it had one hotel on it. A 26px bubble does not
   vanish, and it says how many at a glance instead of needing a label. */
.w-mappin.is-stack .w-mappin-dot { display: none; }
.w-mappin.is-stack .w-mappin-label { display: none; }

.w-mappin-count {
  display: grid;
  place-items: center;
  min-width: 26px;
  height: 26px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--accent);
  font: 700 0.8125rem/1 var(--mono);
  color: var(--accent-ink);
  /* Ring then shadow, same as the dot: the ring holds the edge against a busy
     basemap, the shadow lifts it off one. */
  box-shadow: 0 0 0 2.5px #FFFFFF, 0 2px 5px rgba(0, 0, 0, 0.45);
}
.w-mappin-count[hidden] { display: none; }

/* Big clusters get a bigger bubble, so "18" reads as more than "2" before you
   have read either number. */
.w-mappin.is-stack[data-big="true"] .w-mappin-count {
  min-width: 32px;
  height: 32px;
  font-size: 0.875rem;
}

/* A stack outranks a lone hotel for the reader's attention. */
.w-mappin.is-stack { z-index: 3; }

.w-mappin.is-on { z-index: 5; }
/* Selected always draws, even if the merge pass would otherwise fold it into a
   neighbour's stack — it is the one the reader is looking at. */
.w-mappin.is-on.is-merged { display: flex; }
/* The selected hotel always keeps its name, whatever else is competing. */
.w-mappin.is-on .w-mappin-label { display: block; }
.w-mappin.is-on .w-mappin-dot {
  width: 16px;
  height: 16px;
  /* Selected reads as bigger and darker, since gold is now the resting state
     and a selected pin has to differ from twenty of them. */
  background: var(--map-pin);
  box-shadow: 0 0 0 3px var(--accent), 0 2px 6px rgba(0, 0, 0, 0.5);
}
.w-mappin.is-on .w-mappin-label {
  max-width: 26ch;
  background: var(--accent);
  color: var(--accent-ink);
}
.w-mappin.is-on.is-edge-pointed .w-mappin-label { display: none; }

/* ── The map's own controls ──────────────────────────────────────────────
   Legend, layer toggles and base shortcuts, in one top-left column.
   Absolutely positioned ONCE, here, rather than three times with three
   hand-counted offsets — those only line up until one box changes height, and
   the legend hides itself and the base row grows with the trip, so they were
   guaranteed to drift.
   Left, not right: MapKit owns the bottom-right for zoom and the top-right for
   its map-type control. Bottom-left is the OpenStreetMap attribution, which the
   ODbL requires to stay visible. Top-left is the only free corner. */

:root {
  /* Shared by every panel on this page -- the boxes floating over the map AND
     the list pane beside it -- so a change lands everywhere at once and the two
     halves of the page cannot drift into different materials.
     Blurred rather than merely translucent: at the opacity where what is behind
     reads through clearly, text on top of it does not, and the blur is what
     buys back the contrast. */
  --map-panel: color-mix(in srgb, var(--surface-2) 72%, transparent);
}

.w-mapctl {
  position: absolute;
  /* Clear of the floating list, not of the map's left edge -- the map now runs
     underneath the pane, so 10px from the map would be 10px behind glass. */
  left: calc(var(--list-w) + 12px);
  top: 10px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  /* stretch, so the three boxes read as one panel rather than three boxes that
     happen to be near each other. */
  align-items: stretch;
  gap: 4px;
  width: 105px;
  /* The column must not swallow map drags in the gaps between its boxes. */
  pointer-events: none;
}
.w-mapctl > * { pointer-events: auto; }

/* Base shortcuts. A row of buttons rather than a dropdown: with two to five the
   whole set fits, and a menu would hide the count, which is the useful part. */

.w-bases {
  display: grid;
  gap: 3px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--map-panel);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: var(--shadow-sm);
}
.w-bases[hidden] { display: none; }

.w-bases-label {
  font: 600 0.6rem/1 var(--display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Wraps inside the panel rather than widening it, so five bases become two
   rows instead of a control panel that grows sideways across the map. */
/* Three per row, then wrap. A grid rather than flex-wrap, so the fourth base
   lands under the first instead of wherever the widths happen to allow, and so
   every button is the same size whether the number is 1 or 12. */
.w-bases-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
}

.w-bases button {
  min-width: 0;
  min-height: 22px;
  padding: 0 3px;
  border: 0;
  border-radius: 5px;
  background: var(--wash);
  font: 600 0.7rem/1 var(--mono);
  color: var(--ink);
  cursor: pointer;
  transition: background 140ms var(--ease);
}
.w-bases button:hover { background: var(--wash); }
.w-bases button[aria-current="true"] { background: var(--accent); color: var(--accent-ink); }
.w-bases button:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }

/* ── The off-view pointer ────────────────────────────────────────────────
   When a hovered row's pin is outside the view, a marker rides the frame and
   points at it. Hovering used to highlight something the reader could not see,
   which is indistinguishable from the hover not working at all.
   Coloured by KIND, like everything else on this map: navy for an activity,
   gold for a hotel. The reader should know what they are being pointed at
   before they read the name. */

.w-edge {
  position: absolute;
  /* Above the map's panels as a backstop. It also steps around them, so this
     should rarely matter -- but when a bearing leaves nowhere to go, being
     readable on top beats being hidden underneath. */
  z-index: 6;
  translate: -50% -50%;
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 210px;
  padding: 5px 10px 5px 7px;
  border-radius: 999px;
  background: var(--map-pin);
  color: #FFFFFF;
  font: 600 0.75rem/1.25 var(--display);
  box-shadow: 0 0 0 2px #FFFFFF, 0 3px 10px rgba(0, 0, 0, 0.4);
  pointer-events: none;
  white-space: nowrap;
}
.w-edge[hidden] { display: none; }
.w-edge.is-hotel { background: var(--accent); color: var(--accent-ink); }

.w-edge-label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A triangle, rotated to the true bearing rather than snapped to a compass
   point -- the direction is the information. */
.w-edge-arrow {
  flex: 0 0 auto;
  width: 0;
  height: 0;
  border: 5px solid transparent;
  border-left-color: currentColor;
  /* The triangle points right at 0deg, which is the angle atan2 returns for
     due east, so no offset is needed. */
  translate: 1px 0;
}

/* ── Map layers ──────────────────────────────────────────────────────────
   Two checkboxes over the map. Bottom-left: MapKit owns the bottom-right for
   its zoom control and the top-left is the heat legend, so this is the corner
   that is actually free. A panel rather than bare controls, because a
   transparent checkbox over a map is unreadable the moment it crosses a road. */

/* TOP-left, under the heat legend — not bottom-left, which is where the
   OpenStreetMap attribution lives. Covering an attribution is not a cosmetic
   slip: the ODbL requires it to be visible, so a panel sitting on top of it is
   a licence problem as much as a layout one. Every other corner is taken —
   MapKit owns bottom-right for zoom and top-right for the map-type control. */
.w-layers {
  display: grid;
  gap: 1px;
  margin: 0;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--map-panel);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: var(--shadow-sm);
}
.w-layers label {
  display: grid;
  grid-template-columns: 12px 1fr;
  align-items: center;
  gap: 4px;
  font: 500 0.62rem/1.2 var(--display);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}
.w-layers label > span { justify-self: start; }
.w-layers input { width: 12px; height: 12px; accent-color: var(--accent-text); cursor: pointer; }
.w-layers input:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 1px; }

/* ── Tabs ─────────────────────────────────────────────────────────────────
   Two tabs over one pane. Stacking the hotel list under the activity list
   would push the hotel filters below a scroll, and a filter you have to hunt
   for is a filter nobody uses. Tabs also keep the map uncluttered: hotel pins
   only exist while the hotel tab does.

   Underline rather than a pill or a segmented control. The panel below is the
   surface; a filled tab would read as a button that opens something else. */

/* Step 2's panel heading. Replaced the two-tab strip when the duplicate
   Activities list came out; it keeps the strip's height and rule so the pane
   below it starts exactly where it always did. */
.w-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
.w-panel-title {
  font: 700 0.8125rem/1 var(--display);
  letter-spacing: 0.02em;
  color: var(--ink);
}

.w-tabs {
  display: flex;
  flex: 0 0 auto;
  border-bottom: 1px solid var(--border);
  /* No background of its own: stacking a second translucent layer on the
     translucent pane would print as a darker band across the top. */
  background: none;
}

.w-tab {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--row-h);
  padding: 0 10px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  font: 600 0.8125rem/1 var(--display);
  letter-spacing: 0.01em;
  color: var(--muted);
  cursor: pointer;
  transition: color 140ms var(--ease), border-color 140ms var(--ease);
}
.w-tab:hover { color: var(--ink); }
.w-tab[aria-selected="true"] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}
.w-tab:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }

/* The count is how the Hotels tab says "results are ready" without stealing
   the pane out from under someone who is still adding places. */
.w-tab-n {
  min-width: 1.6em;
  padding: 2px 5px;
  border-radius: 999px;
  background: var(--wash-strong);
  font: 600 0.6875rem/1.2 var(--mono);
  color: var(--muted);
}
.w-tab[aria-selected="true"] .w-tab-n { background: var(--accent-soft); color: var(--accent-text); }

/* The panel is the scroll container now, not #w-items, because the hotel tab
   has its own scrolling content. */
.w-panel {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.w-panel:focus-visible { outline: 2px solid var(--accent-text); outline-offset: -2px; }
.w-panel[hidden] { display: none; }

/* ── Hotel filters ───────────────────────────────────────────────────────
   Sticky, because the whole reason for tabs was to keep these in reach while
   the list under them scrolls. */

.w-filters {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 5px 10px;
  background: var(--map-panel);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}

.w-filters-toggle {
  display: flex;
  align-items: center;
  gap: 5px;
  width: 100%;
  min-height: 20px;
  padding: 0;
  border: 0;
  background: none;
  font: 600 0.62rem/1 var(--display);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
}
.w-filters-toggle:hover { color: var(--ink); }
.w-filters-toggle:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }
.w-filters-toggle .w-chev {
  width: 12px;
  height: 12px;
  margin-left: auto;
  transition: rotate 160ms var(--ease);
}
.w-filters-toggle[aria-expanded="false"] .w-chev { rotate: -90deg; }

.w-filters-active {
  padding: 0 5px;
  border-radius: 999px;
  background: var(--accent-soft);
  font: 600 0.6rem/1.4 var(--display);
  letter-spacing: 0;
  text-transform: none;
  color: var(--accent-text);
}

.w-filters-body { margin-top: 4px; }
.w-filters-body[hidden] { display: none; }

.w-filter-row { display: flex; flex-wrap: wrap; gap: 4px 6px; }

.w-filter {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 400 0.68rem/1 var(--display);
  color: var(--muted);
}

.w-select {
  height: 24px;
  padding: 0 6px;
  border: 1px solid var(--border-strong);
  border-radius: 5px;
  background: var(--surface);
  font: 400 0.68rem/1 var(--display);
  color: var(--ink);
  cursor: pointer;
}
.w-select:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 1px; }
.w-select:disabled { cursor: not-allowed; color: var(--muted); background: var(--wash); }

.w-filter-off { opacity: 0.55; }

.w-chips { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 4px; }

.w-chip {
  min-height: 20px;
  padding: 0 7px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--surface);
  font: 500 0.62rem/1 var(--display);
  color: var(--ink);
  cursor: pointer;
  transition: background 140ms var(--ease), border-color 140ms var(--ease);
}
.w-chip:hover { background: var(--wash); }
.w-chip[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-text);
}
.w-chip:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 1px; }

.w-brand-pills { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 4px; }
.w-brand-pill {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 6px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent-soft);
  font: 500 0.6rem/1 var(--display);
  color: var(--accent-text);
  cursor: pointer;
  transition: background 140ms var(--ease);
}
.w-brand-pill:hover { background: var(--accent); color: var(--surface); }

.w-count {
  margin: 4px 0 0;
  font: 400 0.65rem/1.3 var(--display);
  color: var(--muted);
}

/* ── Area cards ──────────────────────────────────────────────────────────
   One card per trip area, showing city name, activity count, paired stays,
   and an expandable hotel picker. The primary structure of the Hotels tab. */

.w-area-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  margin: 8px 10px;
  background: var(--surface-2);
  overflow: hidden;
  transition: border-color 200ms var(--ease);
}
.w-area-card.is-selected {
  border-color: var(--accent);
}

/* Two rows, not a wrapping flex row. As a flex row in a ~230px column the
   title, the meta and the badge each became a flex item that wrapped, and
   then wrapped INTERNALLY too — "Universal Studios / Hollywood", "1 place ·
   Days / 1–2", and a pill reading "NEEDS / HOTEL" over two lines. A grid
   gives the title the full width and puts the meta and the badge on one
   shared row where neither can break. */
.w-area-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "city  city"
    "meta  needs";
  align-items: center;
  row-gap: 2px;
  column-gap: 6px;
  width: 100%;
  padding: 9px 4px 9px 12px;
  border: 0;
  background: transparent;
  cursor: pointer;
  font: inherit;
  color: inherit;
  text-align: left;
}
.w-area-head:hover { background: var(--wash); }

.w-area-city {
  grid-area: city;
  font: 700 0.88rem/1.25 var(--display);
  color: var(--ink);
  min-width: 0;
  /* Long names ("Universal Studios Hollywood") get two lines and then stop,
     so one area card can never grow taller than its neighbours by much. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.w-area-meta {
  grid-area: meta;
  font: 400 0.72rem/1.3 var(--display);
  color: var(--muted);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.w-area-n {
  font-weight: 600;
  color: var(--ink);
}
.w-area-needs {
  grid-area: needs;
  justify-self: end;
  font: 500 0.62rem/1 var(--display);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;          /* never "NEEDS / HOTEL" */
  color: var(--accent-text);
  padding: 3px 7px;
  border-radius: 999px;
  background: var(--accent-soft);
}

/* Paired stays under each area */
.w-area-stays {
  padding: 0 12px;
}
.w-area-stay {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 10px;
  margin-bottom: 6px;
  border-radius: 8px;
  background: var(--wash);
  border: 1px solid var(--border);
}
.w-area-stay.is-booked {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.w-area-stay-head {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
}
.w-area-stay-head strong {
  font: 600 0.78rem/1.2 var(--display);
  color: var(--ink);
}
.w-area-stamp {
  font: 700 0.56rem/1 var(--display);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent-text);
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--accent);
  color: var(--accent-ink);
}
.w-area-stay-sub {
  font: 400 0.7rem/1.3 var(--display);
  color: var(--muted);
}
.w-area-stay-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
}
.w-area-book-link {
  font: 500 0.68rem/1 var(--display);
  color: var(--accent-text);
  text-decoration: none;
  padding: 3px 8px;
  border-radius: 6px;
  background: var(--accent);
  color: var(--accent-ink);
  transition: opacity 140ms;
}
.w-area-book-link:hover { opacity: 0.85; }

.w-area-upload-label {
  font: 400 0.65rem/1 var(--display);
  color: var(--accent-text);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.w-area-upload-label:hover { color: var(--ink); }

.w-area-doc-btn {
  border: 0;
  background: transparent;
  font: 400 0.65rem/1 var(--display);
  color: var(--accent-text);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  padding: 0;
}
.w-area-doc-btn:hover { color: var(--ink); }

.w-area-remove {
  border: 0;
  background: transparent;
  font: 500 0.85rem/1 var(--display);
  color: var(--muted);
  cursor: pointer;
  padding: 0 4px;
  opacity: 0.5;
  transition: opacity 140ms;
}
.w-area-stay:hover .w-area-remove { opacity: 1; }
.w-area-remove:hover { color: var(--ink); }

/* "Add Hotel" button */
/* Header row: the area title (which selects it on the map) plus its own
   collapse control, kept as separate hit targets so neither steals the
   other's meaning. */
.w-area-bar { display: flex; align-items: stretch; }
.w-area-bar .w-area-head { flex: 1 1 auto; width: auto; }

/* NOTE: deliberately NOT `.w-area-toggle` — that class already exists
   above for list.js's collapsible activity groups and carries
   `width: 100%`, which made this button span the whole card and
   squeeze the title into a 94px column. */
.w-area-collapse {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px: a disclosure is one of the easiest controls to mis-tap. */
  min-width: 44px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 140ms, background 140ms;
}
.w-area-collapse:hover { background: var(--wash); color: var(--ink); }
.w-area-collapse-chev { width: 16px; height: 16px; transition: transform 180ms var(--ease); }
.w-area-card.is-collapsed .w-area-collapse-chev { transform: rotate(-90deg); }

/* The places in this area — what the collapse control hides. */
.w-area-acts {
  list-style: none;
  margin: 0;
  padding: 2px 12px 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.w-area-act {
  font: 400 0.74rem/1.35 var(--display);
  color: var(--muted);
  padding-left: 10px;
  position: relative;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.w-area-act::before {
  content: "";
  position: absolute;
  left: 0; top: 0.55em;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--border-strong);
}
.w-area-acts-empty {
  margin: 0;
  padding: 2px 12px 6px;
  font: 400 0.72rem/1.35 var(--display);
  color: var(--muted);
}

.w-area-foot {
  padding: 4px 10px 9px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
/* Side by side when there is room, stacked when there is not — 104px is about
   where "I already have one" stops fitting on one line. */
.w-area-foot > * { flex: 1 1 104px; }
/* "I already have one" — moved here from the old blank stay line, so the two
   ways to fill an area sit side by side under the area they belong to. */
.w-area-have {
  border: 1px dashed var(--border);
  background: transparent;
  font: 500 0.72rem/1 var(--display);
  color: var(--muted);
  cursor: pointer;
  padding: 6px 12px;
  border-radius: 8px;
  transition: color 140ms, border-color 140ms;
}
.w-area-have:hover { color: var(--accent-text); border-color: var(--accent); }

.w-area-add {
  border: 1px dashed var(--border);
  background: transparent;
  font: 500 0.72rem/1 var(--display);
  color: var(--muted);
  cursor: pointer;
  padding: 6px 12px;
  border-radius: 8px;
  width: 100%;
  transition: color 140ms, border-color 140ms;
}
.w-area-add:hover {
  color: var(--accent-text);
  border-color: var(--accent-text);
}

/* Expandable hotel picker within an area */
.w-area-hotels {
  border-top: 1px solid var(--border);
  padding: 6px 0 4px;
}
.w-area-hotels-head {
  margin: 0;
  padding: 4px 12px 6px;
  font: 400 0.62rem/1.3 var(--display);
  color: var(--muted);
}
.w-area-no-hotels {
  margin: 0;
  padding: 8px 12px;
  font: 400 0.72rem/1.3 var(--display);
  color: var(--muted);
}
.w-area-disclosure {
  margin: 4px 12px 6px;
  font: 400 0.58rem/1.3 var(--display);
  color: var(--muted);
}

.w-area-hotel-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  cursor: default;
  transition: background 100ms;
}
.w-area-hotel-row:hover { background: var(--wash); }
.w-area-hotel-row.is-booked { opacity: 0.5; }

/* The rank is the disclosure now: it opens the per-place travel breakdown.
   24px square is the WCAG 2.5.8 floor, and it was 22 before it was clickable. */
.w-area-hotel-rank {
  flex: 0 0 24px;
  min-height: 24px;
  text-align: center;
  font: 600 0.68rem/24px var(--mono);
  color: var(--muted);
  background: var(--wash);
  border: 1px solid transparent;
  border-radius: 5px;
  padding: 0;
  cursor: pointer;
}
.w-area-hotel-rank:hover { border-color: var(--border-strong); color: var(--ink); }
.w-area-hotel-rank[aria-expanded="true"] { background: var(--accent); color: var(--accent-ink); }

/* The breakdown drops onto its own line under the row it belongs to. */
.w-area-hotel-row { flex-wrap: wrap; }
.w-area-hotel-body {
  flex: 1 0 100%;
  margin: 6px 0 2px 30px;
  padding: 8px 10px;
  background: var(--wash);
  border-radius: 8px;
}
.w-area-hotel-body .w-trips { margin-bottom: 0; }
.w-area-hotel-body .w-hotel-addr { margin: 8px 0 0; }
.w-area-hotel-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.w-area-hotel-name {
  font: 500 0.72rem/1.2 var(--display);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.w-area-hotel-sub {
  font: 400 0.62rem/1 var(--display);
  color: var(--muted);
}
.w-area-hotel-badge {
  font: 600 0.72rem/1 var(--display);
  color: var(--accent-text);
}
.w-area-hotel-pick {
  flex: 0 0 auto;
  border: 1px solid var(--accent);
  background: transparent;
  font: 500 0.62rem/1 var(--display);
  color: var(--accent-text);
  cursor: pointer;
  padding: 3px 8px;
  border-radius: 6px;
  transition: background 140ms, color 140ms;
}
.w-area-hotel-pick:hover {
  background: var(--accent);
  color: var(--accent-ink);
}

/* ── Hotel rows (legacy) ─────────────────────────────────────────────────
   Rows with a disclosure, matching the activity list. Not cards: a card per
   hotel inside a panel inside a page is three nested boxes to say one thing. */

.w-group {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0;
  padding: 12px 12px 6px;
  font: 800 0.75rem/1.2 var(--display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
}
.w-group span {
  font: 400 0.75rem/1.2 var(--display);
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
}

.w-hotel { border-bottom: 1px solid var(--border); }

.w-hotel-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--row-h);
  padding: 8px 12px;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  transition: background 140ms var(--ease);
}
.w-hotel-head:hover { background: var(--wash); }

/* Rank, not a pin glyph — these are ordered by fit, and the number says so.
   Deliberately a different shape from the activity marker so the two lists are
   never confused on a shared map. */
.w-rank {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: var(--wash-strong);
  font: 600 0.75rem/1 var(--mono);
  color: var(--muted);
}
.w-hotel.is-open .w-rank,
.w-hotel-head:hover .w-rank { background: var(--accent); color: var(--accent-ink); }
/* The head is a plain container now; it must not keep button affordances it no
   longer has. */
.w-hotel-head { cursor: default; }
.w-disclose { cursor: pointer; }

.w-hotel-main { flex: 1 1 auto; min-width: 0; }

.w-hotel-name {
  display: block;
  font: 500 0.875rem/1.3 var(--display);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The name is the link to the hotel's own site. Underlined on hover only:
   twenty-five permanently underlined rows is a page of blue-book text, and the
   row already reads as interactive. */
.w-hotel-name.is-link { text-decoration: none; }
.w-hotel-name.is-link:hover,
.w-hotel-name.is-link:focus-visible { text-decoration: underline; color: var(--accent-text); }
.w-hotel-name.is-link:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; border-radius: 3px; }

/* The rank became the disclosure control when the name became a link — an <a>
   inside a <button> is invalid HTML and browsers disagree about which one a
   click belongs to. Now each does one thing: the number expands the row, the
   name leaves the site. */
.w-disclose {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.w-disclose:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; border-radius: 7px; }

.w-hotel-sub {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 2px;
  font: 400 0.75rem/1.3 var(--display);
  color: var(--muted);
}
.w-typical { color: var(--ink); }
.w-brand {
  padding: 1px 6px;
  border-radius: 4px;
  background: var(--wash-strong);
  font-size: 0.6875rem;
}

/* The score is information, not a hero metric: mono, small, and captioned so
   "84" cannot be mistaken for a price or a distance. */
.w-score {
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  gap: 1px;
}
.w-score { position: relative; }
.w-score:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 3px; border-radius: 4px; }
.w-score-n { font: 700 1rem/1 var(--mono); color: var(--ink); }

/* The tooltip. "100" means nothing on its own, and a score a reader cannot
   interrogate is a score they are right not to trust — so this explains the
   number in terms of THEIR trip, with the actual minutes in it.
   Positioned right-aligned and above: the score sits at the right edge of a
   360px pane, so a left-anchored tip would run off it. */
.w-tip {
  position: absolute;
  right: -4px;
  bottom: calc(100% + 8px);
  z-index: var(--z-results);
  width: max-content;
  max-width: 250px;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface-2);
  box-shadow: var(--shadow-lg);
  font: 400 0.75rem/1.45 var(--display);
  color: var(--muted);
  text-align: left;
  text-transform: none;
  letter-spacing: 0;
  opacity: 0;
  visibility: hidden;
  translate: 0 4px;
  transition: opacity 130ms var(--ease), translate 130ms var(--ease), visibility 130ms;
}
.w-tip b { display: block; margin-bottom: 3px; color: var(--ink); font-weight: 700; }
.w-score:hover .w-tip,
.w-score:focus-visible .w-tip {
  opacity: 1;
  visibility: visible;
  translate: 0 0;
}
@media (prefers-reduced-motion: reduce) {
  .w-tip { transition: opacity 1ms, visibility 1ms; translate: 0 0; }
}
/* On touch there is no hover, and the row's own tap is for expanding it. The
   expanded panel already lists every travel time, so the tip is desktop-only
   rather than a second tap target competing with the first. */
@media (hover: none) { .w-tip { display: none; } }
.w-score-l {
  font: 400 0.625rem/1 var(--display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.w-hotel-body { padding: 0 12px 12px 44px; }
.w-hotel-body[hidden] { display: none; }

.w-trips { margin: 0 0 10px; padding: 0; list-style: none; }
.w-trips li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 3px 0;
  font: 400 0.8125rem/1.4 var(--display);
  color: var(--muted);
}
.w-trips li span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.w-trips li span:last-child { flex: 0 0 auto; font-family: var(--mono); color: var(--ink); }

/* The slot ratings will land in. Present and honest about being empty, so the
   day a source is connected this is data arriving, not a redesign. */
.w-rating-slot {
  margin: 0 0 8px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--wash);
  font: 500 0.8125rem/1.4 var(--display);
  color: var(--ink);
}
.w-rating-slot span { font-weight: 400; color: var(--muted); }
.w-rating-slot b { font: 700 0.9375rem/1 var(--mono); }
/* A thin rating is shown, not hidden — and shown as provisional, matching what
   the ranking already does with it internally. */
.w-rating-slot.is-thin { background: var(--wash); }
.w-rating-slot.is-thin b { color: var(--muted); }

.w-hotel-addr {
  margin: 0 0 6px;
  font: 400 0.75rem/1.4 var(--display);
  color: var(--muted);
}

/* Booking is the action this whole page builds toward, so it is a button and
   the hotel's own site is a link beside it. The direct site earns nothing and
   is kept anyway: it is genuinely useful to someone deciding, and hiding it to
   protect a commission would be the kind of design this page is arguing
   against. */
.w-hotel-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.w-book {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 0 14px;
  border-radius: 9px;
  background: var(--accent);
  font: 600 0.8125rem/1 var(--display);
  color: var(--accent-ink);
  text-decoration: none;
  transition: filter 140ms var(--ease);
}
.w-book:hover { filter: brightness(0.94); }
.w-book:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }

.w-hotel-link {
  font: 500 0.8125rem/1 var(--display);
  color: var(--accent-text);
}

.w-disclosure {
  margin: 6px 0 0;
  font: 400 0.6875rem/1.45 var(--display);
  color: var(--muted);
}

.w-loading {
  margin: 0;
  padding: 20px 12px;
  font: 400 0.875rem/1.4 var(--display);
  color: var(--muted);
}

/* ── Structure at smaller widths ─────────────────────────────────────────
   Below 1080 the panes stack and the map goes on top, held sticky so it stays
   in view while the list scrolls beneath it. Both stay visible, which is the
   whole point of the page — a stacked layout that scrolls the map away would
   turn it into a list with a picture at the top. */

@media (max-width: 1079px) {
  /* Stacked, the PAGE scrolls -- so the pin comes off and the body grows again.
     Without this the map and the list would be squeezed into one screen. */
  body.where-page { height: auto; min-height: 100dvh; overflow: visible; }
  .where-shell { flex: 0 0 auto; height: auto; }
  /* Flex column with the map pulled first, NOT `display: block` — block would
     simply follow DOM order and put the list on top, which is the opposite of
     what this layout is for. The list stays first in the source because that is
     the right reading and tab order; only the painting changes. */
  .where-panes {
    position: static;
    display: flex;
    flex-direction: column;
    background: var(--surface-2);
  }
  /* Back to a normal block below the map: floating glass only works where
     there is a map beside it to float over. */
  .where-list {
    position: static;
    width: auto;
    border-right: 0;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--surface-2);
  }
  .where-map { position: relative; inset: auto; }
  .w-mapctl, .w-mapfoot { left: 10px; }
  .w-mapfoot { max-width: calc(100% - 20px); }
  .where-map {
    order: -1;
    position: sticky;
    top: 0;
    height: 46vh;
    min-height: 300px;
    border-bottom: 1px solid var(--border);
  }
  .where-list { min-height: 0; }
  #w-map { position: absolute; inset: 0; }
  /* The page scrolls, not the pane. A pane-level scroller inside a stacked
     layout gives you two scrollbars and a map you cannot get past. */
  .w-panel { overflow: visible; }
  /* Sticky filters would pin to the pane, which no longer scrolls; let them
     ride with the page instead of hovering mid-list. */
  .w-filters { position: static; }
}

/* The bar wraps rather than crushing its controls; the export stays reachable. */
@media (max-width: 640px) {
  .where-bar {
    height: auto;
    flex-wrap: wrap;
    gap: 10px;
    padding: 10px var(--space-gutter);
  }
  .where-bar .trip-name { flex-basis: 100%; max-width: none; }
  .where-map { height: 40vh; }
}

/* ── Motion ───────────────────────────────────────────────────────────────
   State, not choreography. A row arriving is worth marking; the page loading
   is not. */

@keyframes w-row-in {
  from { background: var(--accent-soft); }
  to   { background: transparent; }
}
.w-item[data-just-added="true"] { animation: w-row-in 900ms var(--ease); }

@media (prefers-reduced-motion: reduce) {
  .where-shell *,
  .where-shell *::before,
  .where-shell *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* The map's failure state. It must not look like a missing image: the page
   still works without tiles, and saying so is more use than an empty box. */
.w-map-error {
  position: absolute;
  inset: 0;
  margin: 0;
  display: grid;
  place-content: center;
  padding: 24px;
  max-width: 34ch;
  margin-inline: auto;
  text-align: center;
  font: 400 0.875rem/1.5 var(--display);
  color: var(--muted);
  text-wrap: pretty;
}

/* Visually hidden, still announced. The page needs an h1 for structure but the
   bar is the real heading. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ── Trip Insights ────────────────────────────────────────────────────── */
[hidden] { display: none !important; }

/* Score colours. These carry meaning, so they are measured against the panel
   they sit on, not against the page: 6.22:1 / 5.93:1 / 6.54:1 in light and
   6.15:1 / 5.80:1 / 4.99:1 in dark.

   All THREE selectors are required, exactly as site.css does it. The dark set
   used to be defined only under [data-theme="dark"], which meant a visitor on
   system dark who never touched the toggle got the LIGHT greens on a dark
   panel — the one combination nobody sees while testing, because clicking the
   toggle is what fixes it. */
.where-page {
  --w-good: #1B6E4B;
  --w-warn: #8A5A00;
  --w-bad: var(--danger);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .where-page {
    --w-good: #69C79A;
    --w-warn: #E0A54B;
    --w-bad: var(--danger);
  }
}
:root[data-theme="dark"] .where-page {
  --w-good: #69C79A;
  --w-warn: #E0A54B;
  --w-bad: var(--danger);
}

.w-guide {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px var(--space-gutter);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  position: relative;
  flex-wrap: nowrap;          /* one row, always — the probe asserts this */
  overflow: visible;
}
.w-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 999px;
  padding: 4px 12px;
  font: inherit;
  color: var(--ink);
  cursor: pointer;
}
.w-chip:hover { border-color: var(--border-strong); }
.w-chip output { font-weight: 700; font-variant-numeric: tabular-nums; }
.w-chip .w-chip-label { color: var(--muted); }
.w-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border-strong); }
.w-chip[data-band="good"] .w-dot { background: var(--w-good); }
.w-chip[data-band="warn"] .w-dot { background: var(--w-warn); }
.w-chip[data-band="bad"]  .w-dot { background: var(--w-bad); }
.w-chip .w-trend { font-size: 11px; opacity: 0; }
.w-chip .w-trend.is-up   { color: var(--w-good); animation: w-trend-fade 1.6s ease-out; }
.w-chip .w-trend.is-down { color: var(--w-bad);  animation: w-trend-fade 1.6s ease-out; }
@keyframes w-trend-fade { 0% { opacity: 1; } 100% { opacity: 0; } }

.w-bubble, .w-chip-pop {
  position: absolute;
  top: calc(100% + 10px);
  max-width: 340px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
  padding: 12px 14px;
  z-index: 40;                 /* above the map, below any dialog */
  font-size: 14px;
  line-height: 1.45;
}
.w-bubble::before, .w-chip-pop::before {
  content: "";
  position: absolute;
  top: -6px;
  left: var(--arrow-x, 24px);
  width: 10px; height: 10px;
  background: var(--surface-2);
  border-left: 1px solid var(--border);
  border-top: 1px solid var(--border);
  transform: rotate(45deg);
}
.w-bubble-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 8px; }
.w-bubble-actions button {
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--ink);
  border-radius: 8px;
  padding: 3px 10px;
  font: inherit;
  cursor: pointer;
}
.w-bubble-actions .w-bubble-go { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
.w-chip-pop .w-pop-meaning { color: var(--muted); margin: 0 0 8px; }
.w-chip-pop .w-pop-note { margin: 0 0 8px; font-weight: 600; }
.w-chip-pop .w-pop-disclaimer { color: var(--muted); font-size: 12.5px; margin: 0 0 8px; }
.w-chip-pop ul { margin: 0; padding-left: 18px; }
.w-chip-pop li { margin: 4px 0; }
.w-chip-pop .w-pop-empty { color: var(--muted); margin: 0; }

@media (max-width: 640px) {
  .w-chip .w-chip-label { display: none; }   /* number + dot only; the popover names it */
  .w-chip { padding: 4px 9px; }
}

/* The dim mask. An SVG over the map rather than a MapKit overlay — see
   drawDim() in mapview.js for why. It must never eat a click meant for the
   map, and it sits under the controls that float on top. */
.w-dimlayer {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  z-index: 1;
}
