:root {
  --bg: #FAF9F5;
  --panel: #FFFFFF;
  --border: #E4E2DC;
  --text: #1A1A1A;
  --text-2: #55534E;
  --muted: #8C8981;
  --accent: #A37E3E;
  --accent-hover: #7F602B;
  --accent-bg: #F5F2EC;
  --pool: #2C6E9B;
  --ok: #1B6B47;
  --warn: #A8562C;
  --radius: 3px;

  /* Where a venue's thread stands. One set of hues for the whole app, so a purple pin on the map and
     a purple heart in the rank list mean the same thing without anyone having to learn two schemes.
     Like the four map colours below these are NOT overridden in dark mode: the map's tiles are light
     in both themes, and the pin and the row must not drift apart just because the page went dark. */
  --status-awaiting: #E68A2E;    /* emailed, nothing back yet */
  --status-responded: #2E7FD6;   /* they replied and the next move is ours */
  --status-chased: #8A5CD1;      /* they replied, we've followed up -- back in their court */

  /* Anything drawn ON the map is coloured against the TILES, not against the page, and CARTO's
     Voyager basemap is light in both themes -- there is no dark variant of it. So these four sit
     outside the theme on purpose and are NOT overridden in the dark block below. Reach for --bg or
     --panel out there instead and dark mode cuts a near-black halo, and a near-black ring, into a
     permanently light map. */
  --map-pin: #6E7B8B;      /* every venue, the same colour */
  --map-sel: #A37E3E;      /* the one you picked, and the legs and airports that belong to it */
  --map-ink: #7F602B;      /* the drive times: gold, dark enough to read over tiles */
  --map-paper: #FAF9F5;    /* what a halo and a ring are cut out of */
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #101012;
    --panel: #161619;
    --border: #29292E;
    --text: #E4E4E8;
    --text-2: #9B9B9F;
    --muted: #66666A;
    --accent: #CBB07C;
    --accent-hover: #E9D2A3;
    --accent-bg: #22211D;
    --pool: #6BB0DC;
    --ok: #5FBF8F;
    --warn: #E0A06A;
  }
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 13.5px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); text-decoration: underline; }

.layout { display: flex; align-items: flex-start; min-height: 100vh; }

/* ---------- Sidebar ---------- */

.sidebar {
  width: 290px;
  flex: none;
  background: var(--panel);
  border-right: 1px solid var(--border);
  padding: 0.85rem 1.15rem 1.5rem;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.brand { margin-bottom: 1rem; }
/* Reset rides on the "N properties" line rather than beside the title: the sidebar is only 280px
   and sharing that row with the h1 wrapped it onto two lines. */
.brand-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}
.brand h1 {
  font-size: 1.05rem;
  font-weight: 650;
  letter-spacing: -0.01em;
}
.brand p {
  color: var(--muted);
  font-size: 0.78rem;
  margin-top: 0.15rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.field { margin-bottom: 1.05rem; }

.field > label.legend {
  display: block;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.4rem;
}

input[type="search"] {
  width: 100%;
  padding: 0.5rem 0.6rem;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
input[type="search"]:focus {
  outline: none;
  border-color: var(--accent);
}

.check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.28rem 0;
  cursor: pointer;
  color: var(--text-2);
}
.check:hover { color: var(--text); }
.check input { accent-color: var(--accent); cursor: pointer; }
.check .count {
  margin-left: auto;
  font-size: 0.72rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.region-list .check { padding: 0.3rem 0; }

/* ---------- Range filters ---------- */

/* The value lives in the legend rather than under the thumb: it is the thing you read while you
   drag, and a readout that tracks the thumb along the track is a moving target for the eye. */
.field > .legend .value {
  float: right;
  font-weight: 600;
  font-size: 0.82rem;
  color: var(--accent-hover);
  font-variant-numeric: tabular-nums;
}
.slider {
  width: 100%;
  margin: 0.45rem 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
/* Both sliders hide every venue that never gave us the number they filter on, so each says so --
   but only while it is actually filtering, since at "Any" nothing is hidden and the warning would
   be a lie. */
.hint {
  margin: 0.35rem 0 0;
  font-size: 0.72rem;
  line-height: 1.35;
  color: var(--muted);
}

/* Region hue, carried as --dot and shared by the sidebar swatch and the card tag. */
.dot {
  width: 9px;
  height: 9px;
  flex: none;
  border-radius: 50%;
  background: var(--dot, var(--muted));
}

/* Up in the brand row, not at the foot of the sidebar: with eleven regions and two sliders below
   it, "Reset" sat under a scroll and you had to go looking for the way out of your own filters. */
.reset {
  padding: 0.2rem 0.5rem;
  font: inherit;
  font-size: 0.72rem;
  color: var(--text-2);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  white-space: nowrap;
}
.reset:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- Main ---------- */

/* Carries no padding on any edge: the bar sits flush at the top of the pane, and #map (which
   carries none of its own either) fills the pane flush to its right/bottom/left edges. List
   view's spacing lives entirely on .grid. */
.main {
  flex: 1;
  min-width: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Height is deliberately driven by the controls inside, not by padding: the padding is symmetric
   and small, so the count and toggle stay vertically centered in the bar's own box as it shrinks.
   No margin-bottom -- the border is the divider, and whatever sits under it (grid or map) owns the
   gap below, so map view can sit flush against the divider while list view still gets air. */
.result-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex: none;
  padding: 0.2rem 2rem;
  border-bottom: 1px solid var(--border);
}
.result-bar { flex-wrap: wrap; }
.result-bar .count { font-size: 0.85rem; color: var(--text-2); }
.result-bar .count strong { color: var(--text); font-weight: 600; }
.result-bar select {
  font: inherit;
  font-size: 0.8rem;
  padding: 0.2rem 0.4rem;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.result-bar .right { display: flex; align-items: center; gap: 0.9rem; margin-left: auto; }

.view-toggle {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.view-toggle button {
  font: inherit;
  font-size: 0.78rem;
  padding: 0.22rem 0.75rem;
  color: var(--text-2);
  background: var(--panel);
  border: none;
  cursor: pointer;
}
.view-toggle button + button { border-left: 1px solid var(--border); }
.view-toggle button.active { color: var(--accent-hover); background: var(--accent-bg); font-weight: 600; }
.view-toggle button:hover:not(.active) { color: var(--text); }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 1rem;
  padding: 0.6rem 2rem 4rem;
}
/* `.grid`'s own `display: grid` is an author rule, and author rules always beat the browser's
   default `[hidden] { display: none }` -- so setting the `hidden` property from JS silently did
   nothing here. This explicit override wins on specificity and restores what `hidden` should do. */
.grid[hidden] { display: none; }

/* ---- rank view ---------------------------------------------------------------------------
   The favourites in preference order. One column, not the card grid: ranking is a comparison down a
   list, and a two-up grid would put #3 beside #4 with no visual sense of which came first. Rows are
   compact so as much of the shortlist as possible is on screen at once, which is the whole point. */
/* List on the left, map on the right. The two panes scroll independently and the whole thing fills
   the height under the toolbar, so the map stays put while you work down the list -- a map that
   scrolled away with the rows would be useless as the thing you glance at.

   That needs the page itself to stop scrolling, which is what `body.ranking` does: `.main` is
   `min-height: 100vh` everywhere else so the card grid can run as long as it likes, and a min-height
   bounds nothing -- the rank pane's 27 rows simply stretched the row taller and took the map with
   them, to 2248px of map. A definite height is what lets the children's overflow:auto bite. The
   sidebar already works this way (sticky, height: 100vh, its own scrollbar). */
body.ranking .main { height: 100vh; min-height: 0; overflow: hidden; }
.rank-wrap { display: flex; flex: 1; min-height: 0; }
.rank-wrap[hidden] { display: none; }
.rank-pane { flex: 1; min-width: 0; overflow-y: auto; padding: 0.6rem 1.2rem 3rem 2rem; }
/* The map pane is the box the shared #map-wrap gets moved into (see placeMap). It carries the size;
   #map-wrap inside it is flex:1 and fills. */
.rank-map {
  flex: 0 0 42%;
  max-width: 620px;
  min-width: 320px;
  display: flex;
  border-left: 1px solid var(--border);
}

.rank-bar { display: flex; align-items: center; gap: 0.7rem; padding: 0.15rem 0 0.5rem; }
.rank-compare {
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.32rem 0.8rem;
  color: #fff;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  cursor: pointer;
}
.rank-compare:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
.rank-compare:disabled { opacity: 0.4; cursor: default; }

.rank-hint {
  font-size: 0.78rem;
  color: var(--muted);
  padding: 0 0 0.7rem;
}
.rank-saved { color: var(--ok); font-weight: 600; margin-left: 0.5rem; }
.rank-saved.bad { color: var(--warn); }

.rank-list { list-style: none; display: flex; flex-direction: column; gap: 0.4rem; }

.rank-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.45rem 0.6rem;
  cursor: pointer;   /* the whole row is the door to the full card */
}
.rank-row:hover { border-color: var(--accent); }
.rank-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Half-faded and left in place while it's dragged, so the row you're holding stays legible and the
   gap it will land in is the one you can see opening up ahead of the pointer. */
.rank-row.dragging { opacity: 0.4; }

.rank-grip { display: flex; color: var(--muted); cursor: grab; flex: none; }
.rank-grip svg { width: 16px; height: 16px; }
.rank-row.dragging .rank-grip { cursor: grabbing; }

.rank-num {
  flex: none;
  min-width: 1.6rem;
  text-align: right;
  font-variant-numeric: tabular-nums;   /* 9 -> 10 must not shift the whole row left */
  font-weight: 600;
  color: var(--accent-hover);
}

.rank-thumb {
  flex: none;
  width: 76px;
  height: 52px;
  object-fit: cover;
  border-radius: var(--radius);
  background: var(--accent-bg);
}
/* `blank`, NOT `empty`: the global `.empty` (the "no venues match" paragraph) carries `padding: 4rem`,
   and with border-box that padding outgrows this box's 52px height and stretches the whole row. */
.rank-thumb.blank { display: block; }

/* min-width:0 lets the long dates string ellipse instead of forcing the row wider than the pane. */
.rank-body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1; }
.rank-name { font-weight: 600; font-size: 0.9rem; }
.rank-facts, .rank-note {
  font-size: 0.76rem;
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rank-note { color: var(--muted); font-style: italic; }

.rank-actions { display: flex; align-items: center; gap: 0.2rem; flex: none; }
.rank-move {
  font: inherit;
  font-size: 0.7rem;
  line-height: 1;
  padding: 0.28rem 0.4rem;
  color: var(--text-2);
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}
.rank-move:hover:not(:disabled) { color: var(--accent-hover); border-color: var(--accent); }
.rank-move:disabled { opacity: 0.3; cursor: default; }
/* The note and heart buttons need sizing of their own here. The card's versions are sized by
   `.triage-actions button` / `... button svg`, which is scoped to that corner overlay -- outside it
   the inline SVGs carry only a viewBox, so they collapse to 0x0 and the buttons render as 4px slivers. */
.rank-actions .note-btn,
.rank-actions .triage {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.7rem;
  height: 1.7rem;
  padding: 0;
  color: var(--text-2);
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}
.rank-actions .note-btn svg,
.rank-actions .triage svg { width: 15px; height: 15px; }
.rank-actions .note-btn:hover { color: var(--accent-hover); border-color: var(--accent); }
.rank-actions .note-btn.on { color: #C79A2E; }
.rank-actions .note-btn.on svg { fill: currentColor; }

/* The heart here only ever un-favourites -- everything in this list is already a favourite -- so it
   is drawn solid rather than outlined. Its COLOUR is spent on something the row can't otherwise
   say: where the thread stands, the same orange/blue/purple the map pins use. Ranking is mostly a
   question of who you're still waiting on, so that belongs on the row you're dragging.
   Hover goes to the warning colour in every state, to say what the click will actually do. */
.rank-actions .rank-unfav { color: var(--muted); }        /* never emailed */
.rank-actions .rank-unfav.st-awaiting  { color: var(--status-awaiting); }
.rank-actions .rank-unfav.st-responded { color: var(--status-responded); }
.rank-actions .rank-unfav.st-chased    { color: var(--status-chased); }
.rank-actions .rank-unfav svg { fill: currentColor; }
.rank-actions .rank-unfav:hover { color: var(--warn); border-color: var(--warn); }

/* Phones: the thumbnail and the facts stay, the drag handle goes. HTML5 drag doesn't fire on touch,
   so a grip here would be a control that silently does nothing -- the arrows are the way to move a
   row, and they get bigger targets to match.

   The buttons also move to a line of their own. Sharing one line with them left the text about
   150px wide, which wrapped every venue name to three lines and ellipsed the facts down to
   "EUR 2,133/ni..." -- the row still worked, but it had stopped saying anything you could rank on. */
/* Below this the two panes stop being two panes: the list would be squeezed into a column too narrow
   to compare venues in, which is the one thing this view exists to do. The list wins, the map goes;
   the full-width Map view is still a button away. */
@media (max-width: 900px) {
  .rank-map { display: none; }
}

@media (max-width: 640px) {
  .rank-pane { padding: 0.6rem 0.9rem 4rem; }
  .rank-grip { display: none; }
  .rank-row { flex-wrap: wrap; row-gap: 0.45rem; }
  .rank-thumb { width: 64px; height: 46px; }
  .rank-body { flex-basis: 0; }
  .rank-actions { flex: 1 0 100%; justify-content: flex-end; }
  .rank-move { padding: 0.45rem 0.6rem; }
}

/* No padding/margin on any edge, and neither .main nor .result-bar contributes any either -- so the
   map bleeds flush to the divider above it and to the pane's right, bottom, and left edges. flex:1
   in .main's column fills exactly the space left under the toolbar, no calc() height guessing. */
.map-wrap {
  position: relative;
  flex: 1;
  min-height: 420px;
}
.map-wrap[hidden] { display: none; }
#map { position: absolute; inset: 0; }

/* Leaflet's own divIcon class paints a white box with a border; ours is the SVG and nothing else.
   The shadow is what keeps the pin legible where it crosses a pale patch of the basemap.

   One colour for every venue. Tinting each pin by region only restated what the sidebar swatches
   and the card tags already say, and it spent the map's one strong signal -- colour -- on a fact
   the map was the worst place to read. Colour now means "this is the one you picked", which is
   the thing only the map can express. The SVG fills with currentColor, so the hue lives here in
   CSS: selection and dark mode are each a class away, with no second icon baked at another
   colour. */
.pin {
  background: none;
  border: none;
  color: var(--map-pin);
  filter: drop-shadow(0 1px 1.5px rgba(0, 0, 0, 0.35));
  cursor: pointer;
  transition: opacity 0.15s ease;
}
.pin:hover { filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.45)); }

/* Pin colour by status, and only by status: shape says whether it's a favourite (heart) or not
   (teardrop), colour says where the conversation stands. Hearts used to be pink regardless, which
   meant the one set of venues we care about most was the one set whose thread state we couldn't
   see. Grey (--map-pin, set on .pin above) is a venue we haven't emailed; currentColor in the SVG
   picks these up. */
.pin.awaiting  { color: var(--status-awaiting); }
.pin.responded { color: var(--status-responded); }
.pin.chased    { color: var(--status-chased); }

/* A note you've left keeps the pin's centre "eye" (the white disc baked into the teardrop); a pin
   with no note is drawn solid. Only the teardrops carry an eye, so this leaves the hearts untouched. */
.pin:not(.noted) svg circle { display: none; }

/* Picking a venue fades every other pin rather than removing them: the neighbours are the context
   that makes a distance line worth reading -- "an hour from Florence, and so are the six around
   it" -- and clearing 871 markers and rebuilding them on every click would be both slow and
   visibly jumpy. They stay hoverable, so a faded pin is still a pin you can go to next. */
#map.picked .pin { opacity: 0.22; }
#map.picked .pin:hover { opacity: 0.7; }
#map.picked .pin.sel { opacity: 1; }
/* Selection keeps the pin's own status colour -- it already stands out because #map.picked fades
   every OTHER pin to 0.22 while this one stays at 1 with a heavier shadow. */
.pin.sel {
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.5));
}

/* The far end of a leg. A filled disc, so it reads as a destination rather than as another venue
   you could click -- and it is non-interactive, so it isn't. */
.airport {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--map-paper);
  background: var(--map-ink);
  border-radius: 50%;
  box-shadow: 0 0 0 1.5px var(--map-paper), 0 1px 3px rgba(0, 0, 0, 0.4);
}

.leg { stroke: var(--map-ink); }

/* The drive sits on the line itself, in the middle, which is where the eye already is. Leaflet
   ships a tooltip as a white box with a pointer arrow; strip it back to bare text with a halo, so
   the line reads through the label instead of being interrupted by a card. */
.leg-label {
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  color: var(--map-ink);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  text-shadow: 0 0 3px var(--map-paper), 0 0 3px var(--map-paper),
               0 0 3px var(--map-paper), 0 0 6px var(--map-paper);
}
.leg-label::before { display: none; }  /* the tooltip's arrow */

/* A fixed panel in the corner, not a Leaflet popup anchored to the marker: it shows exactly one
   venue's full .card and stays put while you click around the map, rather than jumping to sit
   beside whatever you clicked. */
.map-detail {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 360px;
  max-width: calc(100% - 2rem);
  max-height: calc(100% - 2rem);
  overflow-y: auto;
  z-index: 1000;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
  border-radius: var(--radius);
}
.map-detail[hidden] { display: none; }
.map-detail .card { margin: 0; }
.map-detail-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 1;
  width: 1.6rem;
  height: 1.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--text-2);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 50%;
  cursor: pointer;
}
.map-detail-close:hover { color: var(--text); border-color: var(--accent); }

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem 1.15rem;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

/* The hero bleeds to the card's edges, which means cancelling .card's padding rather than
   asking .card to give it up -- every other child wants that padding.

   It is a <button>, not a <div>: it is the one thing on the card you can click, so it should be
   the one thing that takes focus, answers the Enter key and announces itself to a screen reader.
   The reset below is what stops a button looking like one.

   `aspect-ratio` and not a height: the box is the right shape before the image has loaded a
   single byte, so 870 lazy-loaded photos arriving over a slow connection settle into holes that
   were already the right size instead of shoving the grid down under the reader as they land. */
.hero {
  position: relative;
  display: block;
  width: calc(100% + 2.3rem);
  margin: -1.1rem -1.15rem 0;
  padding: 0;
  aspect-ratio: 3 / 2;
  background: var(--accent-bg);
  border: none;
  border-radius: var(--radius) var(--radius) 0 0;
  overflow: hidden;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.hero:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.hero img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.4s ease;
}
.hero:hover img { transform: scale(1.03); }
/* How many more there are, which is the affordance: it is what tells you the card is a door. */
.shots {
  position: absolute;
  right: 0.5rem;
  bottom: 0.5rem;
  padding: 0.12rem 0.4rem;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
  border-radius: 2px;
  backdrop-filter: blur(2px);
  font-variant-numeric: tabular-nums;
}

/* Little arrows over the photo, so you can flick through a venue's shots on the card itself without
   opening the gallery. Quiet by default, darker on hover; small enough to stay out of the way of
   the picture. */
.hero-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 1.5rem;
  height: 1.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  color: #fff;
  background: rgba(0, 0, 0, 0.42);
  cursor: pointer;
  z-index: 2;
  transition: background 0.15s ease;
}
.hero-nav svg { width: 13px; height: 13px; }
.hero-nav.prev { left: 0.4rem; }
.hero-nav.next { right: 0.4rem; }
.hero-nav:hover { background: rgba(0, 0, 0, 0.72); }

/* ---- the gallery ---- */
.lightbox { position: fixed; inset: 0; z-index: 2000; display: flex; }
.lightbox[hidden] { display: none; }
.lb-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.82); }
/* No panel chrome to speak of: the gallery is the photographs and nothing else. The venue's name,
   its price, its capacity are all on the card you clicked, six inches away and still on screen --
   printing them again under the photo was one description of a venue too many. */
.lb-panel {
  position: relative;
  margin: auto;
  width: min(1400px, 94vw);
  background: transparent;
}
/* Black behind the stage, and object-fit: contain rather than cover -- these are other people's
   photographs at whatever aspect ratio they shot them, and cropping a portrait shot of a chapel
   to a 3:2 letterbox to save a band of empty space is not a trade worth making in the one place
   the reader came to actually LOOK at the picture. The card crops; the gallery does not. */
/* With nothing under it competing for room, the photograph gets the screen. */
.lb-stage {
  position: relative;
  height: 78vh;
  background: transparent;
  overflow: hidden;
}
.lb-stage img { width: 100%; height: 100%; object-fit: contain; display: block; }
.lb-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 2.4rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  line-height: 1;
  color: #fff;
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 50%;
  cursor: pointer;
}
.lb-nav:hover { background: rgba(0, 0, 0, 0.75); }
.lb-nav.prev { left: 0.6rem; }
.lb-nav.next { right: 0.6rem; }
/* A venue with one photograph has a picture, not a gallery. */
.lightbox.solo .lb-nav,
.lightbox.solo .lb-count,
.lightbox.solo .lb-thumbs { display: none; }
.lb-count {
  position: absolute;
  left: 0.7rem;
  bottom: 0.7rem;
  padding: 0.15rem 0.45rem;
  font-size: 11px;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
  border-radius: 2px;
}
.lb-close {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 2;
  width: 1.9rem;
  height: 1.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3rem;
  line-height: 1;
  color: #fff;
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 50%;
  cursor: pointer;
}
.lb-close:hover { background: rgba(0, 0, 0, 0.8); }
.lb-thumbs {
  display: flex;
  justify-content: center;
  gap: 0.35rem;
  padding: 0.7rem 0.6rem 0;
  overflow-x: auto;
}
.lb-thumbs img {
  width: 84px;
  height: 56px;
  flex: none;
  object-fit: cover;
  border-radius: 2px;
  opacity: 0.45;
  cursor: pointer;
  transition: opacity 0.15s ease;
}
.lb-thumbs img:hover { opacity: 0.8; }
.lb-thumbs img.on { opacity: 1; outline: 2px solid var(--accent); outline-offset: -2px; }

.card h2 {
  font-size: 0.98rem;
  font-weight: 620;
  letter-spacing: -0.01em;
  line-height: 1.3;
}

.place { font-size: 0.8rem; color: var(--muted); }

.tags { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.16rem 0.45rem;
  border-radius: var(--radius);
  background: var(--accent-bg);
  color: var(--text-2);
  white-space: nowrap;
}
.tag.region { background: color-mix(in srgb, var(--dot) 11%, transparent); }
.tag.pool { color: var(--pool); background: color-mix(in srgb, var(--pool) 12%, transparent); }

/* An icon-only chip has no text to give it width, so the horizontal padding that framed a word
   now just makes a lopsided box. Square it up, and let the glyph be the content. */
.tag.icon { padding: 0.16rem 0.34rem; gap: 0.28rem; }
.tag.icon svg { width: 12px; height: 12px; flex: none; }

/* The airports: the same gold as the discs and the legs on the map, so a chip and a pin read as
   one fact seen twice. Hover for the distance and the drive -- see airportChips(). */
.tag.airport-tag {
  color: var(--accent-hover);
  background: color-mix(in srgb, var(--accent) 11%, transparent);
  font-variant-numeric: tabular-nums;
  cursor: help;
}
.tag.replied { color: var(--ok); background: color-mix(in srgb, var(--ok) 13%, transparent); }
.tag.ruled { color: var(--muted); background: color-mix(in srgb, var(--muted) 13%, transparent); }
.tag.open { color: var(--warn); background: color-mix(in srgb, var(--warn) 13%, transparent); }
.tag.chased { color: var(--muted); background: color-mix(in srgb, var(--muted) 10%, transparent); }
/* The headline number, so a card's cost is legible without opening anything. */
.tag.price {
  color: var(--accent-hover);
  background: var(--accent-bg);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

a.tag {
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--border);
  text-decoration: none;
}
a.tag:hover {
  color: var(--accent-hover);
  border-color: var(--accent);
  background: var(--accent-bg);
  text-decoration: none;
}
a.tag svg { width: 11px; height: 11px; flex: none; }

/* Same affordance as the mail/website tags, but it copies the drafted chaser to the clipboard
   rather than navigating -- so it is a button, and says so when it has fired. */
button.tag {
  cursor: pointer;
  font: inherit;
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--warn);
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
}
button.tag:hover { background: color-mix(in srgb, var(--warn) 12%, transparent); }
button.tag.copied { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 50%, transparent); }
button.tag svg { width: 11px; height: 11px; flex: none; }

/* The email chip is now a button (it copies the address instead of opening a mailto:), but it should
   still read as the neutral contact chip sitting next to the website link -- not the amber of the
   follow-up button -- right up until it fires, when it flashes the same green tick. */
button.tag.copyemail {
  color: var(--accent);
  border-color: var(--border);
  text-transform: none;
  letter-spacing: 0;
}
button.tag.copyemail:hover {
  color: var(--accent-hover);
  border-color: var(--accent);
  background: var(--accent-bg);
}
button.tag.copyemail.copied {
  color: var(--ok);
  border-color: color-mix(in srgb, var(--ok) 50%, transparent);
}

dl { display: grid; grid-template-columns: auto 1fr; gap: 0.3rem 0.7rem; font-size: 0.8rem; }
dt {
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 0.15rem;
  white-space: nowrap;
}
dd { color: var(--text-2); }

/* Everything below here comes straight from the venue's own reply, so it is
   visually separated from the researched fields above it. */
.verified {
  padding-top: 0.75rem;
  border-top: 2px solid color-mix(in srgb, var(--ok) 35%, transparent);
  display: grid;
  gap: 0.5rem;
}
.verified .legend {
  font-size: 0.63rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ok);
  font-weight: 600;
}
.verified dl { gap: 0.25rem 0.7rem; }
.verified dd { color: var(--text); }
.verified dd.money { color: var(--accent-hover); font-weight: 560; }

.said summary {
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
  cursor: pointer;
  list-style: none;
}
.said summary::-webkit-details-marker { display: none; }
.said summary::before { content: "▸ "; }
.said[open] summary::before { content: "▾ "; }
.said summary:hover { color: var(--accent-hover); }
.said p {
  margin-top: 0.45rem;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--text-2);
}
/* An email, so its own line breaks are the layout. */
.said p.draft { white-space: pre-wrap; }
.said ul.said-list {
  margin-top: 0.45rem;
  padding-left: 1.1rem;
  display: grid;
  gap: 0.32rem;
}
.said ul.said-list li {
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-2);
}
.said ul.said-list li::marker { color: var(--muted); }

/* Booked out, too small, or not hosting weddings: keep it, but stop it competing. */
.card.ruled-out { opacity: 0.55; }
.card.ruled-out:hover { opacity: 1; }

/* Only drawn when a note or a dead end remains; most cards end at the last dl row. */
.contact {
  font-size: 0.8rem;
  padding-top: 0.7rem;
  margin-top: auto;
  border-top: 1px solid var(--border);
}
.contact .none { color: var(--muted); font-style: italic; }
.note { font-size: 0.75rem; color: var(--muted); }

.empty {
  padding: 4rem 1rem;
  text-align: center;
  color: var(--muted);
}

@media (max-width: 820px) {
  .layout { flex-direction: column; }
  .sidebar {
    width: 100%;
    height: auto;
    position: static;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
  .main { padding: 1.25rem 1rem 3rem; width: 100%; }
}

/* ---- triage ------------------------------------------------------------------------------- */
/* The keep/discard controls sit in the top-right corner of the card, over the photo -- the Zillow
   spot, where a heart is expected. Dark translucent discs, like the "N photos" badge, so they read
   the same whether they land on a bright sky or (on a text-only card) the panel. Off, they are
   quiet white glyphs; on, the heart fills red and the discard fills solid. */
.card { position: relative; }
.triage-actions {
  position: absolute;
  top: 0.55rem;
  right: 0.55rem;
  z-index: 3;
  display: flex;
  gap: 0.3rem;
}
.triage-actions button {
  width: 1.75rem;
  height: 1.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  color: #fff;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(2px);
  transition: background 0.15s ease, color 0.15s ease;
}
.triage-actions button svg { width: 15px; height: 15px; }
.triage-actions button:hover { background: rgba(0, 0, 0, 0.72); }
.triage-actions .keep.on { color: #ff5a6a; }
.triage-actions .keep.on svg { fill: currentColor; }
.triage-actions .drop.on { background: #c0392b; }
/* A note exists: the sheet fills amber, so the corner tells you at a glance which venues you've
   annotated -- distinct from the red heart and the red discard so the three never blur together. */
.triage-actions .note-btn.on { color: #e9c46a; }
.triage-actions .note-btn.on svg { fill: currentColor; stroke: rgba(0, 0, 0, 0.35); }
.triage-actions button:disabled { opacity: 0.55; cursor: default; }
/* In the map's detail panel the card shares its top-right corner with the panel's own close
   button (1.6rem wide at right:0.5rem, so its inner edge is 2.1rem in). Tuck the triage buttons
   just left of it, making a row: heart, eye, close. List-view cards are untouched. */
.map-detail .triage-actions { top: 0.45rem; right: 2.5rem; }

/* Neither `.card.favorite` nor `.card.discarded` carries a style of its own, deliberately. The
   triage buttons in the corner already say which a card is -- a filled heart, a filled eye -- and
   in the "Favorites only" / "Discarded only" views every card on screen is the same state anyway,
   so a per-card marker just adds noise to a wall of identical noise. The classes stay on the
   markup as hooks; only `.ruled-out` (a venue the venue itself has ruled out) still dims. */

.brand-actions { display: flex; gap: 0.4rem; align-items: center; }
.logout-form { margin: 0; display: inline; }

/* ---- notes -------------------------------------------------------------------------------- */
/* The note as it reads on the card: a quiet amber-tinted strip high up (right under the chips), so
   a note you left is obvious without shouting. Clamped to a few lines -- the full text is one click
   away in the editor -- and click anywhere on it to edit. */
.card-note {
  display: flex;
  gap: 0.4rem;
  align-items: flex-start;
  padding: 0.45rem 0.6rem;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--text-2);
  background: color-mix(in srgb, #e9c46a 14%, transparent);
  border-left: 2px solid #e9c46a;
  border-radius: 0 var(--radius) var(--radius) 0;
  cursor: pointer;
}
.card-note:hover { background: color-mix(in srgb, #e9c46a 22%, transparent); }
.card-note svg { width: 13px; height: 13px; flex: none; margin-top: 0.15rem; color: #b8860b; }
.card-note span {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: pre-wrap;
  word-break: break-word;
}

/* The editor: a small modal, above everything, so re-renders of the grid behind it don't matter. */
/* ---- pairwise comparison ---------------------------------------------------------------------
   Full screen and opaque, not a panel over the list. One decision at a time is the entire point --
   the ranking behind it is the thing you are trying not to have to think about all at once. */
/* Below the lightbox (2000) and the note editor (2500): the cards in here are real cards, so their
   photo gallery and their note editor open from inside the comparison and must land on top of it. */
.compare {
  position: fixed;
  inset: 0;
  z-index: 1800;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}
.compare[hidden] { display: none; }

.cmp-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 1.4rem;
  border-bottom: 1px solid var(--border);
}
.cmp-title { display: flex; align-items: baseline; gap: 0.7rem; }
.cmp-progress { font-size: 0.8rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.cmp-head-actions { display: flex; gap: 0.5rem; }
.cmp-undo, .cmp-done, .cmp-tie {
  font: inherit;
  font-size: 0.78rem;
  padding: 0.3rem 0.75rem;
  color: var(--text-2);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}
.cmp-undo:hover:not(:disabled), .cmp-done:hover, .cmp-tie:hover {
  color: var(--accent-hover);
  border-color: var(--accent);
}
.cmp-undo:disabled { opacity: 0.35; cursor: default; }
.cmp-done { color: var(--accent-hover); font-weight: 600; }

/* The two choices, equal in every way -- same width, same treatment, no visual hint that one of
   them is the "new" one. A layout that favoured either side would quietly bias every answer. */
/* `stretch`, not `center`: the two cards must be the same height and start at the same line, or the
   facts you are comparing sit at different heights on the screen and the eye has to hunt for each
   one. Centring them independently -- which is what the first cut did -- put a venue with no photo
   and a short blurb a hundred pixels off from the one beside it. */
.cmp-stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;      /* centres the pair; the pair itself sizes to its tallest card */
  justify-content: center;
  padding: 1.1rem 1.2rem;
}
.cmp-pair {
  display: flex;
  align-items: stretch;     /* both cards take the tallest one's height, so their facts line up */
  justify-content: center;
  gap: 1.2rem;
  width: 100%;
  max-height: 100%;
}
.cmp-vs { flex: none; align-self: center; color: var(--muted); font-size: 0.8rem; }

/* One side: the card, which scrolls on its own, and the vote underneath it. The button stays put
   while the card scrolls -- on a venue with a long "what they said" the card is taller than the
   screen, and a pick button at the bottom of that would be somewhere you have to go looking for. */
.cmp-side {
  flex: 1 1 0;
  max-width: 460px;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.cmp-cardwrap { flex: 1; min-height: 0; overflow-y: auto; }
/* min-height, not height: a short card fills its side so the two panels read as a matched pair
   rather than one floating above the other, and a long one still grows and scrolls. */
.cmp-cardwrap .card { border-radius: var(--radius); min-height: 100%; }
/* The heart and the eye are hidden in here on purpose. They are live controls, and un-favouriting a
   venue halfway through a comparison run would pull it out from under the sort that is placing it.
   Prune before you start, or after you finish. The note button stays -- a note changes nothing about
   the run, and this is exactly when you want to jot down why one beat the other. */
.cmp-cardwrap .triage { display: none; }

.cmp-pick {
  flex: none;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.6rem 0.8rem;
  color: #fff;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cmp-pick:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.cmp-pick:focus-visible { outline: 2px solid var(--accent-hover); outline-offset: 2px; }
.cmp-key { opacity: 0.75; margin-right: 0.15rem; }

.cmp-foot {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 0.6rem 1.4rem 0.9rem;
  border-top: 1px solid var(--border);
}
.cmp-keys { font-size: 0.75rem; color: var(--muted); }

/* Narrow: the two choices stack, and the whole thing scrolls. Side by side below this width leaves
   each photo too small to tell one stone farmhouse from another, which is most of the decision. */
@media (max-width: 720px) {
  .cmp-stage { align-items: flex-start; overflow-y: auto; }
  .cmp-pair { flex-direction: column; align-items: stretch; max-height: none; }
  .cmp-side { max-width: none; }
  .cmp-cardwrap { overflow: visible; }   /* the page scrolls instead, one card after the other */
  .cmp-keys { display: none; }
}

/* The full venue card, popped up over the rank list. Below the lightbox (2000) on purpose: clicking
   the card's photo opens the gallery, and it has to land ON TOP of the card it came from, not
   behind it. The note editor (2500) sits above both for the same reason. */
.venue-modal { position: fixed; inset: 0; z-index: 1900; display: flex; }
.venue-modal[hidden] { display: none; }
.vm-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); }
.vm-panel {
  position: relative;
  margin: auto;
  width: min(460px, 94vw);
  max-height: 92vh;
  overflow-y: auto;
  border-radius: var(--radius);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
}
/* The card brings its own panel background, border and padding, so the wrapper adds none -- it is
   purely the positioner. Its own triage buttons sit top-right, hence the close button tucked to
   their left, the same arrangement the map's detail panel uses. */
.vm-panel .card { border-radius: var(--radius); }
.vm-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 4;
  width: 1.6rem;
  height: 1.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.15rem;
  line-height: 1;
  color: #fff;
  background: rgba(0, 0, 0, 0.45);
  border: none;
  border-radius: 50%;
  cursor: pointer;
}
.vm-close:hover { background: rgba(0, 0, 0, 0.72); }
/* Same tuck as `.map-detail .triage-actions`: heart, eye, note, then the close button. */
.vm-panel .triage-actions { right: 2.5rem; }

.note-modal { position: fixed; inset: 0; z-index: 2500; display: flex; }
.note-modal[hidden] { display: none; }
.nm-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); }
.nm-panel {
  position: relative;
  margin: auto;
  width: min(460px, 92vw);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
  padding: 1rem 1.1rem 1.1rem;
}
.nm-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.7rem;
  font-weight: 620;
}
.nm-close {
  background: none;
  border: none;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--text-2);
  cursor: pointer;
}
.nm-close:hover { color: var(--text); }
#nm-text {
  width: 100%;
  min-height: 130px;
  resize: vertical;
  padding: 0.6rem;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
#nm-text:focus { outline: none; border-color: var(--accent); }
.nm-actions { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 0.8rem; }
.nm-save, .nm-cancel {
  padding: 0.42rem 0.9rem;
  font: inherit;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  cursor: pointer;
}
.nm-save { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }
.nm-save:hover { background: var(--accent-hover); }
.nm-cancel { background: var(--panel); color: var(--text-2); }
.nm-cancel:hover { color: var(--text); }
