/* ==========================================================================
   Athens listings map — layout and theme.

   Committed to a single light look: the OSM raster tiles are light, and a dark
   chrome around light tiles reads worse than a consistent light shell.

   Colour: the choropleth uses one sequential blue ramp (light -> dark) for
   magnitude. Selection uses orange, deliberately outside the ramp so "selected"
   can never be confused with "expensive".
   ========================================================================== */

:root {
  --surface-1: #fcfcfb;
  --surface-2: #f4f4f1;
  --surface-3: #eceae5;
  --border: #d9d7d0;
  --border-strong: #b9b6ad;

  --text-primary: #14140f;
  --text-secondary: #52514e;
  --text-muted: #7a7873;

  /* Sequential blue ramp — magnitude encoding (steps 200..700). */
  --seq-1: #9ec5f4;
  --seq-2: #6da7ec;
  --seq-3: #3987e5;
  --seq-4: #256abf;
  --seq-5: #184f95;
  --seq-6: #0d366b;

  --accent: #2a78d6;
  --select: #eb6834; /* orange — selection, never part of the ramp */
  --warn: #eda100;
  --danger: #e34948;
  --good: #1baf7a;

  --shadow-1: 0 1px 2px rgba(20, 20, 15, 0.08), 0 2px 8px rgba(20, 20, 15, 0.06);
  --shadow-2: 0 2px 6px rgba(20, 20, 15, 0.1), 0 8px 24px rgba(20, 20, 15, 0.1);

  --header-gap: 10px;
  --radius: 6px;
  --sidebar-w: 400px;
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  display: flex;
  flex-direction: column;
  font-family: "Inter", "Segoe UI", "Noto Sans", "DejaVu Sans", Arial, sans-serif;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-primary);
  background: var(--surface-2);
  /* Greek text must never fall back to a font that drops accents. */
  font-variant-ligatures: none;
}

/* ============================ HEADER ============================

   ONE slim toolbar row, plus a chip row that exists only while a filter is
   engaged. The 22-fieldset inline panel that used to live here has moved into an
   overlay sheet (below): map height is the scarce resource, and the old panel
   was mostly empty state.
   ================================================================ */

.app-header {
  flex: 0 0 auto;
  /* Positioned so the filter sheet can be anchored to its bottom edge, and so
     the z-index below actually applies. */
  position: relative;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-1);
  z-index: 500;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 12px;
  min-height: 42px;
  flex-wrap: wrap;
}

/* The brand mark is a rule in the magnitude ramp's darkest blue rather than a
   new colour or a logo: nothing to load, and no new hue introduced. */
.brand-title {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.01em;
  white-space: nowrap;
  border-left: 3px solid var(--seq-4);
  padding-left: 8px;
  line-height: 1.3;
  flex: 0 0 auto;
}

.brand-sub {
  font-weight: 400;
  color: var(--text-muted);
}

/* --- text search: the one filter that earns a permanent slot ------------- */
.tb-search {
  position: relative;
  flex: 0 1 250px;
  min-width: 130px;
  display: flex;
}

.tb-search-icon {
  position: absolute;
  left: 7px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 13px;
  line-height: 1;
  color: var(--text-muted);
  pointer-events: none;
}

.tb-search input[type="search"] {
  padding-left: 22px;
}

/* --- the filter disclosure, with a live count of what is engaged --------- */
.btn-filters {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  font-weight: 600;
}

.btn-filters-icon {
  font-size: 11px;
  color: var(--text-muted);
}

.btn-filters.has-active {
  border-color: var(--accent);
  background: #eef5fd;
  color: var(--accent);
}

.btn-filters.has-active .btn-filters-icon {
  color: var(--accent);
}

.fcount {
  display: inline-block;
  min-width: 17px;
  padding: 0 4px;
  border-radius: 20px;
  background: var(--accent);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.fcount[hidden] {
  display: none;
}

.icon-btn {
  font: inherit;
  font-size: 13px;
  line-height: 1;
  width: 22px;
  height: 22px;
  padding: 0;
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface-1);
  color: var(--text-muted);
  cursor: pointer;
}

.icon-btn:hover {
  background: var(--surface-3);
  color: var(--text-primary);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* --- stat strip -----------------------------------------------------------
   Five rule-separated tiles instead of five bordered boxes. Label over value
   rather than side by side: it is NARROWER (each tile is as wide as its widest
   line, not the sum of both) and no taller than the buttons next to it, so the
   five live counts cost the toolbar no height at all. */
.summary-bar {
  display: flex;
  margin-left: auto;
  flex: 0 1 auto;
  min-width: 0;
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: 0 8px;
  border-left: 1px solid var(--border);
  min-width: 0;
}

.stat:first-child {
  border-left: 0;
}

.stat:last-child {
  padding-right: 0;
}

.stat-label {
  font-size: 9px;
  line-height: 1.3;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  white-space: nowrap;
}

.stat-value {
  font-size: 12.5px;
  line-height: 1.3;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  white-space: nowrap;
}

/* Provenance now lives at the bottom of the filter sheet, where it can be read
   in full instead of being truncated into a header line. */
.data-as-of {
  margin: 0 0 7px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-secondary);
}

.token-status-row {
  margin: 0 0 9px;
}

.token-status {
  font-size: 10px;
  padding: 3px 8px;
  border-radius: 20px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-secondary);
  white-space: nowrap;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.token-status.is-bad {
  border-color: #f0d9a8;
  background: #fdf6e6;
  color: #6b5410;
}

.token-status.is-ok {
  border-color: #b6e6d3;
  background: #eaf9f3;
  color: #14614a;
}

.btn {
  font: inherit;
  font-size: 12px;
  padding: 5px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-1);
  color: var(--text-primary);
  cursor: pointer;
}

.btn:hover {
  background: var(--surface-3);
}

.btn-ghost {
  border-color: var(--border);
  color: var(--text-secondary);
}

.btn-small {
  font-size: 11px;
  padding: 3px 8px;
}

/* ==================== ACTIVE-FILTER CHIPS ====================
   Only what is engaged, each chip individually removable. The row has no height
   at all while nothing is filtered, which is what buys the map its space back.
   ============================================================= */

.active-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  padding: 0 12px 6px;
}

.active-bar[hidden] {
  display: none;
}

.afchip {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 2px 1px 9px;
  border: 1px solid var(--border-strong);
  border-radius: 20px;
  background: var(--surface-2);
  font-size: 11px;
  color: var(--text-primary);
  max-width: 100%;
}

.afchip-label {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.afchip-x {
  font: inherit;
  font-size: 11px;
  line-height: 16px;
  width: 17px;
  height: 17px;
  padding: 0;
  flex: 0 0 auto;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.afchip-x:hover {
  background: var(--border);
  color: var(--text-primary);
}

/* The trailing "clear everything" chip is a chip, not a button-shaped button:
   it belongs to the row it clears. */
.afchip-all {
  font: inherit;
  font-size: 10.5px;
  padding: 2px 10px;
  border-style: dashed;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}

.afchip-all:hover {
  background: var(--surface-3);
  color: var(--text-primary);
}

/* ==================== FILTER SHEET ====================
   The expanded state has to be small too, not just hidden by default. So:

     · it is an OVERLAY anchored to the bottom edge of the header, which means it
       costs the map no layout height in either state;
     · it is laid out in MACRO-COLUMNS, so the sheet is as tall as its tallest
       section rather than as tall as all of them stacked;
     · a range pair is ONE ROW (short label + min–max) and a multi-state filter
       is ONE ROW (short label + segment) — no bordered fieldset, no uppercase
       legend line, no wrapper padding per filter;
     · the parenthetical glosses that used to widen the legends are `title`
       tooltips now, and the provenance + the permanently-inert rented/auction
       explanation are behind one collapsed summary line at the bottom.
   ====================================================== */

/* Dims the map, not the header: the toolbar's live counts must stay readable
   while filtering, so this sits BELOW the header's z-index (500) and above the
   legend overlay (400). */
.fsheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 480;
  background: rgba(20, 20, 15, 0.24);
}

.fsheet-scrim[hidden] {
  display: none;
}

/* `top: 100%` follows the header's own height, so the sheet stays flush under
   the toolbar whether or not the active-filter chip row is present. */
.fsheet {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  max-height: 78vh;
  background: var(--surface-1);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border-strong);
  border-radius: 0 0 var(--radius) var(--radius);
  box-shadow: var(--shadow-2);
  animation: fsheet-in 0.14s ease-out;
}

.fsheet[hidden] {
  display: none;
}

@keyframes fsheet-in {
  from {
    transform: translateY(-6px);
    opacity: 0.3;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fsheet {
    animation: none;
  }
}

.fsheet-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  flex: 0 0 auto;
  padding: 3px 12px;
  border-bottom: 1px solid var(--surface-3);
  background: var(--surface-2);
}

.fsheet-title {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-secondary);
}

.fsheet-count {
  font-size: 10px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: 20px;
  background: var(--accent);
  color: #fff;
  font-variant-numeric: tabular-nums;
}

.fsheet-count[hidden] {
  display: none;
}

.fsheet-result {
  font-size: 10.5px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.fsheet-head #btn-reset-all {
  margin-left: auto;
}

.fsheet-close {
  font: inherit;
  font-size: 12px;
  line-height: 1;
  width: 20px;
  height: 20px;
  padding: 0;
  flex: 0 0 auto;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-1);
  color: var(--text-secondary);
  cursor: pointer;
}

.fsheet-close:hover {
  background: var(--surface-3);
  color: var(--text-primary);
}

/* The macro-column grid. Sections flow into as many columns as fit; the row is
   as tall as its tallest section, so the sheet's height is a MAX, not a SUM.
   `overflow-y` is a fallback for very short viewports, not the normal case. */
.fsheet-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: grid;
  /* 446px is not arbitrary: it is the narrowest macro column that still fits TWO
     sub-columns of controls (2 × 208 + 14 gap + 16 slack). Anything narrower and
     the range rows would drop to a single column and the sheet would get TALLER
     on a WIDER screen, which is exactly the trap this replaces. */
  grid-template-columns: repeat(auto-fit, minmax(446px, 1fr));
  gap: 0 20px;
  align-content: start;
  padding: 0 12px 8px;
}

/* Below ~1400px three macro columns of 446 no longer fit. Rather than let the
   control rows collapse to one per column (which would make the sheet taller on
   a smaller screen), the gutter and the sub-column minimum shrink together, so
   the sheet keeps its 3 × 2 shape all the way down to ~1130px. */
@media (max-width: 1400px) {
  .fsheet-body {
    grid-template-columns: repeat(auto-fit, minmax(336px, 1fr));
  }

  .rgrid,
  .sgrid {
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 5px 12px;
  }

  .rrow,
  .segrow {
    grid-template-columns: 72px minmax(0, 1fr);
  }
}

/* ============================ FILTERS ============================ */

.fgroup {
  margin: 0;
  padding: 6px 0 3px;
  min-width: 0;
  border-top: 1px solid var(--surface-3);
}

.fgroup:first-child {
  border-top: 0;
}

.fgroup-title {
  margin: 0 0 5px;
  font-size: 9.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-muted);
}

/* --- range pairs: one row each ------------------------------------------- */
.rgrid,
.sgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(214px, 1fr));
  gap: 5px 14px;
  align-items: start;
}

.rrow,
.segrow {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  align-items: center;
  gap: 1px 7px;
  min-width: 0;
}

/* Hierarchy: SECTION TITLES are uppercase and muted, control labels are
   sentence-case and a shade darker. Sentence case is also ~15% narrower, which
   is what lets every label sit on ONE line in an 84px gutter. */
.rrow-label,
.segrow-label,
.checkrow-label,
.chipset-label {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-secondary);
  line-height: 1.3;
}

.rrow-inputs {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.rrow-inputs input {
  width: 100%;
  min-width: 0;
}

.rrow-unit {
  font-size: 9.5px;
  color: var(--text-muted);
  white-space: nowrap;
  flex: 0 0 auto;
}

/* The three honest caveats (unknown year, unrated, approximate travel time)
   stay attached to the range they are about — they only occupy height on the
   rows that have one. */
.fset-note {
  grid-column: 1 / -1;
  margin: 1px 0 0;
  font-size: 10px;
  line-height: 1.3;
  color: var(--text-muted);
}

.fset-note:empty {
  display: none;
}

/* Magenta = the user's own rating, amber = notes, slate = hidden. Existing
   meanings from the card palette, reused rather than re-invented. */
.rrow-mine .rrow-label {
  color: var(--grp-ink);
}

.segrow-note .segrow-label {
  color: var(--note-ink);
}

.check-hidden {
  color: var(--hid-ink);
}

/* The four-way lift and the two long-labelled segments get the full column. */
.segrow-wide {
  grid-column: 1 / -1;
}

.checkrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 3px 14px;
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px dotted var(--border);
}

/* --- the two multi-select chip sets -------------------------------------- */
.chipset {
  min-width: 0;
}

.chipset + .chipset {
  margin-top: 8px;
}

.chipset-label {
  display: block;
  margin-bottom: 4px;
}

.chipset-mine .chipset-label {
  color: var(--grp-ink);
}

/* --- provenance + the permanently-inert filters --------------------------
   Folded into the head bar's existing row: while closed it adds NO height to
   the panel at all, and when opened it takes its own full-width line. */
.fsheet-meta {
  flex: 0 0 auto;
  min-width: 0;
}

.fsheet-meta[open] {
  flex: 1 1 100%;
  order: 10;
}

.fsheet-meta > summary {
  font-size: 9.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}

.fsheet-meta > summary:hover {
  color: var(--text-primary);
}

.fsheet-meta > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.fsheet-meta-body {
  padding: 6px 0 3px;
  max-width: 720px;
}

.fset {
  margin: 0;
  padding: 3px 8px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
  min-width: 0;
}

.fset > legend {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  padding: 0 3px;
  font-weight: 600;
}

.hint {
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  color: var(--text-muted);
  cursor: help;
}

.rrow-label .hint,
.segrow-label .hint,
.chipset-label .hint {
  font-variant-numeric: tabular-nums;
}

.fset .hint {
  border-bottom: 1px dotted var(--border-strong);
}

.fset-inert {
  opacity: 0.8;
  background: var(--surface-2);
}

.inert-note {
  font-size: 10.5px;
  color: var(--text-secondary);
  line-height: 1.4;
}

.inert-note code {
  font-size: 10px;
  background: var(--surface-3);
  padding: 0 3px;
  border-radius: 3px;
}

.range-row {
  display: flex;
  align-items: center;
  gap: 4px;
}

.range-row input {
  width: 100%;
  min-width: 0;
}

.dash {
  color: var(--text-muted);
}

input[type="number"],
input[type="search"],
select {
  font: inherit;
  font-size: 12px;
  padding: 4px 6px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: #fff;
  color: var(--text-primary);
  width: 100%;
  font-variant-numeric: tabular-nums;
}

input:focus-visible,
select:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Segmented three/four-way controls. "any" is always the default. */
.seg {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  overflow: hidden;
}

.seg button {
  font: inherit;
  font-size: 10.5px;
  padding: 2px 8px;
  border: 0;
  border-right: 1px solid var(--border);
  background: #fff;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
}

.seg button:last-child {
  border-right: 0;
}

.seg button:hover {
  background: var(--surface-3);
}

.seg button[aria-pressed="true"] {
  background: var(--accent);
  border-right-color: var(--accent);
  color: #fff;
  font-weight: 600;
}

.check {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
}

.check input {
  width: auto;
  margin: 0;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.chip {
  font: inherit;
  font-size: 10.5px;
  padding: 2px 8px;
  border: 1px solid var(--border-strong);
  border-radius: 20px;
  background: #fff;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
}

.chip:hover {
  background: var(--surface-3);
}

.chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}

.chip .chip-count {
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
  margin-left: 4px;
}

/* ============================ MAIN ============================ */

.app-main {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
}

.map-pane {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
}

#map {
  position: absolute;
  inset: 0;
  background: var(--surface-3);
}

/* Leaflet must sit under the header/sidebar chrome — but do NOT do that by
   flattening the panes.
   `.leaflet-pane { z-index: 1 }` used to live here, and because it has the same
   specificity as Leaflet's own `.leaflet-popup-pane { z-index: 700 }` (both are
   single class selectors, and this file loads later) it WON, collapsing every
   built-in pane to 1. Stacking then fell back to DOM order, while the metro panes
   — which set z-index as an INLINE style and so were never overridden — kept
   240–450 and painted on top of the popups.
   The correct fix is a stacking context on the container: everything Leaflet does
   internally is then contained and ordered normally, and the whole map sits below
   the header (500) and sidebar (450) regardless of its internal values. */
.map-pane {
  z-index: 0;
}

.leaflet-container {
  font: inherit;
  font-size: 12px;
  background: var(--surface-3);
}

/* ============================ LEGEND ============================ */

.map-overlay {
  position: absolute;
  z-index: 400;
  background: rgba(252, 252, 251, 0.95);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  backdrop-filter: blur(3px);
}

/* Bottom-RIGHT, not bottom-left: every area polygon sits in the western half of
   the map, so the old lower-left position covered Πειραιάς / Ταμπούρια. The
   right-hand side of the map pane is empty. `bottom` clears the attribution. */
.legend-box {
  right: 10px;
  bottom: 26px;
  padding: 7px 9px;
  width: 224px;
}

.legend-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  font-weight: 600;
  margin-bottom: 6px;
}

.metric-toggle {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  overflow: hidden;
}

.metric-toggle button {
  font: inherit;
  font-size: 9.5px;
  text-transform: none;
  letter-spacing: 0;
  padding: 2px 6px;
  border: 0;
  border-right: 1px solid var(--border);
  background: #fff;
  color: var(--text-secondary);
  cursor: pointer;
}

.metric-toggle button:last-child {
  border-right: 0;
}

.metric-toggle button[aria-pressed="true"] {
  background: var(--accent);
  color: #fff;
  font-weight: 600;
}

/* Horizontal ramp: six stacked rows used to cover a large block of the
   lower-left map, which is exactly where several polygons sit. */
.legend-scale {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  gap: 0;
}

.legend-cell {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  flex: 1 1 0;
  min-width: 26px;
}

.legend-swatch {
  height: 10px;
  border: 1px solid rgba(20, 20, 15, 0.18);
  border-right-width: 0;
  flex: 0 0 auto;
}

.legend-cell:last-child .legend-swatch {
  border-right-width: 1px;
}

.legend-tick {
  font-size: 9px;
  line-height: 1.3;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  /* The tick sits on the class boundary, i.e. this cell's left edge. */
  margin-left: -1px;
}

.legend-collapse {
  font: inherit;
  font-size: 12px;
  line-height: 1;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 3px;
  background: #fff;
  color: var(--text-secondary);
  cursor: pointer;
  flex: 0 0 auto;
}

/* The legend gained two metro blocks (three operating lines, three future
   projects, four mark rows and two caveats), so on a short window it could
   otherwise grow tall enough to cover the map it explains. 62vh leaves it
   unscrolled at any normal window height and simply caps it on a small one. */
#legend-body {
  max-height: 62vh;
  overflow-y: auto;
}

.legend-box.is-collapsed #legend-body {
  display: none;
}

.legend-box.is-collapsed .legend-head {
  margin-bottom: 0;
}

.legend-foot {
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--surface-3);
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 9.5px;
  color: var(--text-muted);
}

.legend-foot > div {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Dash patterns must match DASH_BY_SOURCE in app.js exactly, so a gradient is
   used rather than `border-style: dashed` (whose dash length is not settable). */
.swatch-line {
  width: 22px;
  height: 2px;
  background: var(--seq-4);
  flex: 0 0 auto;
}

.swatch-line.municipal {
  background: repeating-linear-gradient(
    90deg,
    var(--seq-4) 0 7px,
    transparent 7px 10px
  );
}

.swatch-line.dashed {
  background: repeating-linear-gradient(
    90deg,
    var(--seq-4) 0 2.5px,
    transparent 2.5px 5px
  );
}

.swatch-line.empty {
  background: var(--border-strong);
  opacity: 0.6;
}

/* A point in an area the latest import introduced: same dot, teal ring — exactly
   how the map draws it (see markerStyle/NEW_AREA_TEAL in app.js). */
.swatch-dot.swatch-new-area {
  border-color: #0f8ea6;
  box-shadow: 0 0 0 1px #0f8ea6;
}

/* The dataset-total count on a legend row. */
.legend-foot .lgd-count {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* A listing drawn at its OWN coordinates — a point, never a shape. */
.swatch-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--seq-4);
  border: 1.5px solid #fff;
  box-shadow: 0 0 0 1px var(--border-strong);
  flex: 0 0 auto;
}

.listing-tip {
  font-size: 10.5px;
  font-weight: 600;
  padding: 2px 6px;
}

/* ---- the metro overlay --------------------------------------------------
   INFRASTRUCTURE, not data, and the CSS says so as loudly as the JS does.

   The three official liveries (Μ1 #0a8f3c green, Μ2 #d3222a red, Μ3 #0a5fa8
   blue) sit outside this app's palette on purpose, and they are kept anyway
   because they are what makes the overlay legible to anyone who knows Athens.
   What keeps them out of the data encoding's way is the MARK, not the hue:

     data          fills   — polygon fill 0.55, listing dot fill 0.95, blue ramp
     metro         strokes — 2.2 px lines with a white casing, unfilled rings

   Red and green are not map colours in this app at all (delisted red and the
   νεόδμητο green live on the sidebar's cards and badges), so a red LINE can only
   be Μ2. Only Μ3's blue shares a hue with the ramp, and a thin cased line under
   a 55%-opaque fill cannot be read as a magnitude class.

   Every swatch below is driven by --metro-colour, set inline from the payload's
   own `colour` field, so the legend cannot drift from what Leaflet draws. */
.legend-metro {
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--surface-3);
}

.legend-metro-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  font-size: 9.5px;
  color: var(--text-muted);
}

.legend-metro-toggle {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
  cursor: pointer;
}

.legend-metro-toggle input {
  width: 12px;
  height: 12px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* "σχηματικό" — the caveat, always on screen, never only in a tooltip. The
   tooltip carries the full sentence; this word makes sure nobody has to hover to
   learn that the geometry is one point per station. */
.legend-metro-note {
  font-style: italic;
  border-bottom: 1px dotted var(--border-strong);
  cursor: help;
}

.legend-metro-rows {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 4px;
  font-size: 9.5px;
  color: var(--text-muted);
}

.legend-metro-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.legend-metro-ref {
  font-weight: 700;
  color: var(--text-secondary);
  flex: 0 0 auto;
}

.legend-metro-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The cased line, reproduced exactly: a white rounded body with the coloured
   hairline through its middle. */
.swatch-metro {
  width: 22px;
  height: 7px;
  flex: 0 0 auto;
  border-radius: 4px;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(20, 20, 15, 0.14);
  display: flex;
  align-items: center;
}

.swatch-metro::before {
  content: "";
  display: block;
  width: 100%;
  height: 2.2px;
  background: var(--metro-colour, #6b6a66);
}

/* A RING, never a solid disc — the listing points are solid discs on the blue
   ramp, and the two marks must not be confusable at a glance. */
.swatch-metro-stop {
  width: 9px;
  height: 9px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: #fff;
  border: 1.6px solid var(--metro-colour, #6b6a66);
}

/* ---- the two FUTURE tiers ------------------------------------------------
   Dash patterns match TIER in metro.js: '9,5' for under construction, '1.5,4.5'
   for planned, plus a step down in weight and a mix towards the paper colour. A
   line that is not carrying passengers must not be able to read as one that is,
   so the difference is in four channels at once rather than one. */
.swatch-metro.is-uc::before {
  height: 1.9px;
  background: repeating-linear-gradient(
    90deg,
    var(--metro-colour, #6b6a66) 0 5.5px,
    transparent 5.5px 8.5px
  );
}

.swatch-metro.is-planned::before {
  height: 1.6px;
  opacity: 0.8;
  background: repeating-linear-gradient(
    90deg,
    var(--metro-colour, #6b6a66) 0 1.5px,
    transparent 1.5px 4px
  );
}

/* A future station: HOLLOW (no fill at all) and smaller — an operating stop and a
   2032 aspiration must not look alike. Dashed for under construction, dotted for
   planned, matching the ring dashArray in metro.js. */
.swatch-metro-stop.is-future {
  width: 8px;
  height: 8px;
  background: transparent;
  border-style: dashed;
  border-width: 1.4px;
}

.swatch-metro-stop.is-future.is-planned {
  width: 7px;
  height: 7px;
  border-style: dotted;
  opacity: 0.85;
}

.legend-metro-future .legend-metro-rows {
  gap: 1px;
}

/* The «ενδεικτικές θέσεις» line: the caveat is ON SCREEN, not only in a tooltip,
   because the positions are geocodes of station names and two of the 29 had to be
   thrown away outright. */
.legend-metro-caveat {
  margin-top: 2px;
  color: var(--text-secondary);
  cursor: help;
}

.legend-metro-warn {
  font-size: 9px;
  color: var(--warn);
  flex: 0 0 auto;
  width: 22px;
  text-align: center;
}

/* An interchange: two concentric rings, one per line. Five of the 65 stations. */
.swatch-metro-stop.is-interchange {
  width: 7px;
  height: 7px;
  border-color: var(--metro-colour-2, #6b6a66);
  box-shadow:
    0 0 0 1.2px #fff,
    0 0 0 2.6px var(--metro-colour, #6b6a66);
}

/* The station tooltip. Deliberately unlike `.listing-tip`: dark, lighter weight,
   two lines of name plus the line pills, and it states outright that it is not a
   listing. */
.metro-tip {
  font-size: 10px;
  line-height: 1.35;
  padding: 3px 7px;
  background: rgba(28, 28, 24, 0.92);
  color: #f7f7f5;
  border: 0;
  box-shadow: var(--shadow-1);
}

.metro-tip::before {
  border-top-color: rgba(28, 28, 24, 0.92);
}

.metro-tip .metro-tip-name {
  display: block;
  font-weight: 700;
}

.metro-tip .metro-tip-en {
  display: block;
  opacity: 0.72;
  font-size: 9px;
}

.metro-tip .metro-tip-lines {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 2px;
}

.metro-tip .metro-tip-line {
  display: inline-block;
  padding: 0 4px;
  border-radius: 6px;
  font-size: 9px;
  font-weight: 700;
  color: #fff;
  background: var(--metro-colour, #6b6a66);
}

.metro-tip .metro-tip-hint {
  font-size: 9px;
  opacity: 0.8;
}

.metro-tip .metro-tip-kind {
  display: block;
  margin-top: 1px;
  font-size: 8.5px;
  font-style: italic;
  opacity: 0.62;
}

/* ---- the TangoFix destination -------------------------------------------
   The reference point for the "Χρόνος → TangoFix" column. Deliberately NOT a
   circle: the listing points are circles on the €/m² ramp and the areas are
   polygons, so a third circle would read as a listing. A labelled 🎯 pin cannot
   be mistaken for either. It is map furniture — it is in no filtered set and is
   counted by no statistic. */
.swatch-tango {
  font-size: 11px;
  line-height: 1;
  flex: 0 0 auto;
}

.tango-pin {
  display: flex !important;
  align-items: center;
  gap: 3px;
  width: auto !important;
  height: auto !important;
  white-space: nowrap;
  pointer-events: auto;
}

.tango-pin-dot {
  font-size: 15px;
  line-height: 1;
  filter: drop-shadow(0 1px 1px rgba(20, 20, 15, 0.5));
}

.tango-pin-label {
  font-size: 10.5px;
  font-weight: 700;
  color: #14140f;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid var(--border-strong);
  border-radius: 3px;
  padding: 0 4px;
}

/* Polygon labels (Leaflet tooltips used as direct labels). */
.area-label {
  background: rgba(252, 252, 251, 0.86);
  border: 1px solid var(--border);
  border-radius: 3px;
  box-shadow: none;
  padding: 1px 5px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  pointer-events: none;
}

.area-label::before {
  display: none;
}

.area-label .lbl-count {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.area-label.is-empty {
  opacity: 0.4;
  font-weight: 400;
}

.area-label.is-selected {
  border-color: var(--select);
  box-shadow: 0 0 0 1.5px var(--select);
}

/* An area the LATEST import introduced. Teal — the colour this map already uses
   for «νέο από 20/07» (.badge-fresh, .card.is-fresh) — extended to a second,
   related meaning rather than a tenth hue invented for it. Selection still wins:
   .is-selected comes after this rule and overrides the border. */
.area-label.is-new-area {
  border-color: #0f8ea6;
  background: rgba(228, 246, 250, 0.92);
}

.area-label .lbl-new {
  color: #0f8ea6;
}

/* ============================ SIDEBAR ============================ */

.sidebar {
  flex: 0 0 var(--sidebar-w);
  width: var(--sidebar-w);
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--surface-1);
  border-left: 1px solid var(--border);
  z-index: 450;
}

.sidebar-head {
  flex: 0 0 auto;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-1);
}

.sidebar-title-row {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: space-between;
}

.sidebar-head h2 {
  margin: 0;
  font-size: 14px;
  font-weight: 650;
}

.area-meta {
  margin-top: 3px;
  font-size: 10.5px;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  align-items: center;
}

.sidebar-stats {
  display: flex;
  gap: 12px;
  margin-top: 7px;
  font-size: 11px;
  color: var(--text-secondary);
  flex-wrap: wrap;
}

.sidebar-stats b {
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.sort-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  font-weight: 600;
}

.sort-row select {
  flex: 1 1 auto;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
}

.sidebar-list {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.card {
  border: 1px solid var(--border);
  border-left: 3px solid var(--seq-3);
  border-radius: var(--radius);
  padding: 8px 9px;
  background: var(--surface-1);
  cursor: pointer;
  transition: box-shadow 0.12s, border-color 0.12s;
}

.card:hover,
.card.is-hovered {
  box-shadow: var(--shadow-1);
  border-color: var(--border-strong);
  background: #fff;
}

.card-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.card-price {
  font-size: 15px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.card-ppsm {
  font-size: 11px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.card-loc {
  font-size: 11.5px;
  color: var(--text-secondary);
  margin-top: 1px;
}

.card-loc .micro {
  color: var(--text-primary);
  font-weight: 600;
}

.card-specs {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 10px;
  margin-top: 5px;
  font-size: 11px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.card-specs .spec b {
  color: var(--text-primary);
  font-weight: 600;
}

/* Driving minutes to TangoFix. A fact about the property's location, so it sits
   with the other facts (m² · υ/δ · όροφος · έτος) rather than in the badge row.
   Kept visually quiet — no warning text, no asterisk: the provenance note lives
   once in the header. `is-coarse` is the ONE thing it flags, and it is about
   which point was routed from, not about traffic. */
.card-specs .spec-tango {
  padding-left: 10px;
  border-left: 1px solid var(--border);
}

.card-specs .spec-tango.is-coarse b {
  /* The `~` already says it; the dotted underline makes it hoverable-looking so
     the tooltip (and the matching badge) get found. */
  border-bottom: 1px dotted var(--border-strong);
  cursor: help;
}

.card-specs .spec-tango.is-missing {
  color: var(--text-muted);
}

.badge-tango-coarse {
  border-style: dashed;
}

/* ---- who to call ---------------------------------------------------------
   The whole point of the contact block is dialling it, so the phone is the one
   thing in the card styled as an action. */
.card-contact {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
  margin-top: 6px;
  padding-top: 5px;
  border-top: 1px dotted var(--border);
  font-size: 11px;
  color: var(--text-secondary);
}

.card-contact .agency {
  font-weight: 600;
  color: var(--text-primary);
}

.card-contact .agency.is-private {
  color: #4a3aa7;
}

.card-contact .agency.is-missing {
  font-weight: 500;
  color: var(--text-muted);
  font-style: italic;
}

.card-contact .agent {
  color: var(--text-muted);
}

.card-contact .phones {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px;
  margin-left: auto;
}

.card-contact .tel {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  border-bottom: 1px solid transparent;
}

.card-contact .tel:hover {
  border-bottom-color: var(--accent);
}

.card-dates {
  margin-top: 3px;
  font-size: 10.5px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.card-dates b {
  font-weight: 600;
  color: var(--text-secondary);
}

.sep {
  color: var(--border-strong);
}

/* Off-market: visibly de-emphasised, but never hidden or struck through into
   illegibility — the user asked to be able to see what went away. */
.card.is-delisted {
  border-left-color: var(--danger);
  border-left-style: dashed;
  background: var(--surface-2);
}

.card.is-delisted .card-price,
.card.is-delisted .card-ppsm {
  color: var(--text-muted);
  text-decoration: line-through;
}

.card.is-fresh {
  border-left-color: #0f8ea6;
}

.badges {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}

.badge {
  font-size: 9.5px;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: 3px;
  border: 1px solid;
  white-space: nowrap;
  letter-spacing: 0.01em;
}

/* Distinct palettes: `unfinished` (resale shell) vs `under_construction`
   (unfinished new build) must never be mistaken for one another. */
.badge-unfinished {
  color: #7a3d02;
  background: #fdf0e2;
  border-color: #f0c99b;
}

.badge-construction {
  color: #4a3aa7;
  background: #eeecfb;
  border-color: #c3bcee;
}

.badge-new {
  color: #14614a;
  background: #eaf9f3;
  border-color: #b6e6d3;
}

.badge-parking {
  color: #1c5cab;
  background: #eaf2fd;
  border-color: #bcd7f7;
}

.badge-lift-yes {
  color: #14614a;
  background: #eaf9f3;
  border-color: #b6e6d3;
}

.badge-lift-no {
  color: #8c2726;
  background: #fdeceb;
  border-color: #f5c2c1;
}

.badge-lift-unknown {
  color: var(--text-muted);
  background: var(--surface-2);
  border-color: var(--border);
  border-style: dashed;
}

.badge-dup {
  color: #6b5410;
  background: #fdf6e6;
  border-color: #f0d9a8;
}

/* Off-market. Deliberately the strongest negative in the palette: a delisted
   listing must be unmistakable, because it cannot be bought. */
.badge-delisted {
  color: #fff;
  background: var(--danger);
  border-color: #c53c3b;
}

/* "New since the snapshot" — teal, so it collides with neither the blue
   magnitude ramp, nor the green Νεόδμητο badge, nor the amber notes marker,
   nor the orange selection colour. */
.badge-fresh {
  color: #0b5563;
  background: #e4f6fa;
  border-color: #a5dde8;
}

.badge-energy {
  color: #3f4b1f;
  background: #f2f7e6;
  border-color: #cfdfa9;
}

/* A value the source did not give, or gave as a placeholder. Dashed, exactly
   like the "elevator not stated" badge, so "unknown" always looks the same. */
.badge-unknown {
  color: var(--text-muted);
  background: var(--surface-2);
  border-color: var(--border-strong);
  border-style: dashed;
}

.badge-conflict {
  color: #7a3d02;
  background: #fdf0e2;
  border-color: #eda100;
}

.badge-precision {
  color: var(--text-secondary);
  background: var(--surface-2);
  border-color: var(--border);
  font-weight: 500;
}

.card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 7px;
}

.card-link {
  font-size: 11px;
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}

.card-link:hover {
  text-decoration: underline;
}

.card-area {
  font-size: 10px;
  color: var(--text-muted);
}

/* The agency's own Κωδικός Ακινήτου — what they ask for on the phone.
   `user-select: all` so one click grabs the whole code: it is read out loud or
   pasted into an email, and part-selecting a reference is worse than useless.
   Monospace digits keep it from being misread. Not `[hidden]`-guarded because it
   is never hidden — myCodeHtml() emits nothing at all when there is no code. */
.card-mycode {
  font-size: 10px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  user-select: all;
  -webkit-user-select: all;
  cursor: text;
}

.card-mycode b {
  color: var(--text);
  font-weight: 600;
}

.empty-state {
  padding: 26px 14px;
  text-align: center;
  color: var(--text-muted);
  font-size: 12px;
}

/* Why a code search found nothing. Left-aligned and boxed rather than following
   the centred empty-state text: it is two lines of prose that has to be read,
   not a label to be glanced at. */
.empty-hint {
  margin: 12px auto 0;
  max-width: 40ch;
  padding: 9px 11px;
  text-align: left;
  line-height: 1.45;
  border-left: 3px solid var(--accent, #eb6834);
  background: rgba(235, 104, 52, 0.07);
  border-radius: 0 4px 4px 0;
}

.group-head {
  position: sticky;
  top: -8px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-secondary);
  display: flex;
  justify-content: space-between;
  gap: 8px;
  z-index: 2;
}

.group-head.is-unlocated {
  background: #fdf6e6;
  border-color: #f0d9a8;
  color: #6b5410;
}

.group-head .gh-count {
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  font-weight: 600;
}

.group-head .gh-hint {
  font-weight: 500;
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
}

/* ============================ MISC ============================ */

.boot-error {
  position: fixed;
  inset: auto 12px 12px 12px;
  z-index: 9000;
  padding: 10px 14px;
  border-radius: var(--radius);
  background: #fdeceb;
  border: 1px solid #f5c2c1;
  color: #8c2726;
  font-size: 12px;
  box-shadow: var(--shadow-2);
}

/* Popups */
.leaflet-popup-content {
  margin: 9px 11px;
  font-size: 12px;
  min-width: 190px;
}

.leaflet-popup-content h3 {
  margin: 0 0 4px;
  font-size: 13px;
}

.popup-meta {
  font-size: 11px;
  color: var(--text-secondary);
}

.popup-meta dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1px 8px;
  margin: 5px 0 0;
}

.popup-meta dt {
  color: var(--text-muted);
}

.popup-meta dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 1100px) {
  :root {
    --sidebar-w: 330px;
  }
}

/* ---- narrower desktops / tablets ----------------------------------------
   Degradation order, cheapest information first: the brand's subtitle goes,
   then the stat strip drops to its own line (the header grows by ~19px, still a
   fraction of the block it replaced), then the sheet folds to fewer columns and
   scrolls inside itself. No control is ever removed. */
@media (max-width: 1180px) {
  .brand-sub {
    display: none;
  }
}

@media (max-width: 940px) {
  .toolbar {
    row-gap: 4px;
  }

  .summary-bar {
    order: 10;
    flex-basis: 100%;
    margin-left: 0;
    overflow-x: auto;
  }

  .stat:first-child {
    padding-left: 0;
  }
}

@media (max-width: 620px) {
  .tb-search {
    flex-basis: 100%;
  }
}

/* ============================ NOTES ============================
   User-written notes are not property data, so they get their own hue: a
   sticky-note amber. It stays out of the blue magnitude ramp and out of the
   orange selection colour, so "has a note" can never be read as "expensive"
   or "selected".
   ============================================================== */

:root {
  --note-tint: #fdf9ec;
  --note-border: #efdcae;
  --note-ink: #6b5410;
  --note-mark: #d9a406;
}

/* Marker visible without opening the editor: the card's accent bar turns amber. */
.card.has-note {
  border-left-color: var(--note-mark);
}

.note-block {
  /* Bleed to the card's edges (card padding is 8px 9px). */
  margin: 7px -9px -8px;
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--radius) var(--radius);
}

.note-block.is-set {
  background: var(--note-tint);
  border-top-color: var(--note-border);
}

.note-toggle {
  font: inherit;
  font-size: 10.5px;
  display: flex;
  align-items: center;
  gap: 5px;
  width: 100%;
  padding: 4px 9px;
  border: 0;
  background: none;
  color: var(--text-muted);
  text-align: left;
  cursor: pointer;
  border-radius: 0 0 var(--radius) var(--radius);
}

.note-toggle:hover {
  background: var(--surface-3);
  color: var(--text-secondary);
}

.note-toggle.is-set {
  color: var(--note-ink);
  font-weight: 600;
}

.note-toggle.is-set:hover {
  background: #faf1dc;
}

.note-icon {
  font-size: 11px;
  line-height: 1;
}

.note-label {
  white-space: nowrap;
}

.note-preview {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 400;
  font-style: italic;
  opacity: 0.85;
}

.note-caret {
  margin-left: auto;
  font-size: 8px;
  opacity: 0.6;
}

.note-editor {
  padding: 0 9px 8px;
}

.note-input {
  font: inherit;
  font-size: 11.5px;
  line-height: 1.4;
  width: 100%;
  min-height: 48px;
  resize: vertical;
  padding: 5px 6px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: #fff;
  color: var(--text-primary);
}

.note-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.note-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 5px;
}

.note-status {
  font-size: 10px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.note-status.is-typing,
.note-status.is-saving {
  color: var(--accent);
}

.note-status.is-saved {
  color: var(--good);
  font-weight: 600;
}

.note-status.is-error {
  color: var(--danger);
  font-weight: 600;
}

.note-delete {
  font-size: 10px;
  padding: 2px 7px;
  flex: 0 0 auto;
}

.note-delete:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ============ GROUPS · RATING · HIDDEN (local, per listing) ============
   The user's own state about a listing, so — exactly like the notes amber — it
   must not borrow a hue that already means something about the DATA:
     blue ramp   magnitude          orange  map selection
     amber       notes              teal    new since 20/07
     red         delisted           green   νεόδμητο
     violet      υπό κατασκευή / ιδιώτης

   Two deliberate decisions, because the palette is crowded:

   1. GROUPS GET A CHANNEL OF THEIR OWN — the chip pill. Group colours never
      appear on the map, never on a marker and never on the card's left bar, so
      the six group slots can be mutually distinguishable without colliding with
      any data meaning: a teal PILL cannot be read as "new since 20/07", because
      that meaning lives on the left bar and the badge row. The slot is chosen by
      the group's position (.gc-1 … .gc-6) and repeats after six — harmless,
      because a chip always carries its NAME as well as its colour.
   2. GROUP MEMBERSHIP DOES NOT TINT THE CARD. With «Είδα» / «Λίστα» /
      «Απορρίφθηκε» in daily use most cards would be tinted, which would turn the
      glance channel into noise. The left bar therefore keeps exactly the meanings
      it had (note amber, new teal, delisted red, hidden slate), and membership is
      shown where it can also be changed: the pressed chips on the card itself.

   Rating -> MAGENTA (#b8288f), the hue the retired binary `favourite` flag used
             to own. Rating and groups are one "yours" family, told apart by the
             glyph (★ versus a named pill) rather than by yet another hue; gold
             stars were rejected because gold is the notes amber.
   Hidden -> COOL SLATE, desaturated and unchanged by this pass. "Hidden" is the
             absence of a semantic, so it should read as switched-off rather than
             as a category; the existing greys are warm (#b9b6ad), so a cool slate
             stays distinguishable from "0 results" and "unknown".
   ========================================================================== */

:root {
  /* The "yours" family: the rating stars and the group chip default. */
  --grp-tint: #fdeef7;
  --grp-border: #f0bade;
  --grp-ink: #8a1a6b;
  --grp-mark: #b8288f;

  --hid-tint: #eef1f4;
  --hid-border: #c3ccd6;
  --hid-ink: #3f4a57;
  --hid-mark: #6f7b8a;
}

/* Six group slots. Each sets its own trio, and every chip rule below reads them,
   so a new slot is four values and no new rules. */
.gc-1 { --gc-ink: #8a1a6b; --gc-mark: #b8288f; --gc-tint: #fdeef7; --gc-border: #f0bade; }
.gc-2 { --gc-ink: #3b2a8a; --gc-mark: #6b4bd6; --gc-tint: #f0edfd; --gc-border: #c9bff3; }
.gc-3 { --gc-ink: #0a5257; --gc-mark: #0f7f86; --gc-tint: #e7f6f7; --gc-border: #a9dbde; }
.gc-4 { --gc-ink: #454f0f; --gc-mark: #66761c; --gc-tint: #f2f5e3; --gc-border: #cdd7a4; }
.gc-5 { --gc-ink: #77341a; --gc-mark: #b0512c; --gc-tint: #fbeee8; --gc-border: #eec4b1; }
.gc-6 { --gc-ink: #2c3b56; --gc-mark: #43597e; --gc-tint: #eef1f7; --gc-border: #bfc9db; }

/* ---- at-a-glance card state ---------------------------------------------
   Only ever on screen while "Εμφάνιση κρυμμένων" is ticked. Deliberately
   de-emphasised — it is present so it can be restored, not to compete. */
.card.is-hidden-listing {
  border-left-color: var(--hid-mark);
  border-left-style: dashed;
  background: var(--hid-tint);
  opacity: 0.82;
}

.card.is-hidden-listing:hover {
  opacity: 1;
  background: #f6f8fa;
}

.badge-hidden {
  color: #fff;
  background: var(--hid-mark);
  border-color: #5c6874;
}

/* ---- the card's "your stuff" cluster ------------------------------------
   Everything the USER decides about this listing — rating, hide, groups, note —
   in one block at the foot of the card, so the badge row above it stays a
   readable list of facts about the PROPERTY. These are buttons, not badges:
   badges describe the flat and are not clickable. */
.card-mine {
  margin: 7px -9px -8px; /* bleed to the card's edges (padding is 8px 9px) */
  padding: 0;
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--radius) var(--radius);
  background: rgba(0, 0, 0, 0.015);
}

.card-actions {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 6px 9px 5px;
}

.act-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

/* The note block already bleeds to the card edges; inside the cluster it only
   needs its separator, not a second outer margin. */
.card-mine .note-block {
  margin: 0;
  border-radius: 0 0 var(--radius) var(--radius);
}

.act {
  font: inherit;
  font-size: 10.5px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}

.act:hover {
  background: var(--surface-3);
  color: var(--text-secondary);
  border-color: var(--border-strong);
}

.act:focus-visible,
.star:focus-visible,
.gchip:focus-visible,
.rate-clear:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.act .act-icon {
  font-size: 12px;
  line-height: 1;
}

.act-hide.is-on {
  color: var(--hid-ink);
  background: var(--hid-tint);
  border-color: var(--hid-border);
}

.act-hide.is-on:hover {
  background: #e3e9ee;
}

/* ---- rating: 1–5, and "unrated" is a real state -------------------------
   Three signals, so it survives colour-blindness and a screen reader: the glyph
   (★ / ☆), the colour, and aria-pressed on each star plus an aria-label on the
   group. The value is ALSO written out ("3/5" / "αβαθμολόγητη"), because a row
   of stars is easy to miscount and because "unrated" must not look like zero. */
.rating {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  margin-right: 2px;
}

.star {
  font: inherit;
  font-size: 13px;
  line-height: 1;
  padding: 1px 1px;
  border: 0;
  background: none;
  color: var(--border-strong);
  cursor: pointer;
}

.star:hover {
  color: var(--grp-mark);
}

.star.is-on {
  color: var(--grp-mark);
}

.rate-value {
  font-size: 10px;
  font-weight: 600;
  color: var(--grp-ink);
  margin-left: 4px;
  font-variant-numeric: tabular-nums;
}

.rating.is-unrated .rate-value {
  color: var(--text-muted);
  font-weight: 400;
  font-style: italic;
}

.rate-clear {
  font: inherit;
  font-size: 9.5px;
  line-height: 1;
  margin-left: 4px;
  padding: 2px 5px;
  border: 1px solid var(--grp-border);
  border-radius: 999px;
  background: var(--grp-tint);
  color: var(--grp-ink);
  cursor: pointer;
}

.rate-clear:hover {
  background: #fbe1f1;
}

/* ---- group chips on the card -------------------------------------------
   Multi-select: several may be on at once, because a listing can belong to
   several groups. Off is an outline, on is filled with the group's own slot
   colour — plus aria-pressed, so the state is never colour-only. */
.group-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.gchip {
  font: inherit;
  font-size: 10px;
  font-weight: 600;
  padding: 2px 8px;
  border: 1px solid var(--gc-border, var(--border));
  border-radius: 999px;
  background: #fff;
  color: var(--gc-ink, var(--text-muted));
  cursor: pointer;
  white-space: nowrap;
}

.gchip:hover {
  background: var(--gc-tint, var(--surface-3));
}

.gchip.is-on {
  background: var(--gc-mark, var(--grp-mark));
  border-color: var(--gc-mark, var(--grp-mark));
  color: #fff;
}

.gchip.is-on::before {
  content: "✓ ";
  font-size: 9px;
}

.gchip-new {
  border-style: dashed;
  border-color: var(--border-strong);
  color: var(--text-muted);
  font-weight: 400;
}

.gchip-new:hover {
  background: var(--surface-3);
  color: var(--text-secondary);
}

/* ---- header: the group filter chips ------------------------------------
   Same .chip base as the search_area chips, so the two facets look and behave
   alike; the only addition is the group's slot colour when it is pressed. */
.chip-group[aria-pressed="true"] {
  background: var(--gc-mark);
  border-color: var(--gc-mark);
}

.chip-ungrouped {
  border-style: dashed;
}

.chip-ungrouped[aria-pressed="true"] {
  background: var(--text-muted);
  border-color: var(--text-muted);
  border-style: solid;
}

/* ---- header: the group manager (rename / delete / create) --------------- */
.btn-manage-groups {
  margin-top: 5px;
}

/* Guarded for the same reason as .fav-notice and .fsheet: this element carries
   `hidden` in the markup, and `display: flex` would otherwise beat the UA sheet's
   `[hidden] { display: none }` and leave the panel permanently open. */
.group-admin[hidden] {
  display: none;
}

.group-admin {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.ga-row {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11.5px;
}

.ga-name {
  font-weight: 600;
  color: var(--gc-ink, var(--text-primary));
  border-left: 3px solid var(--gc-mark, var(--border-strong));
  padding-left: 6px;
  min-width: 120px;
}

.ga-count {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  min-width: 86px;
}

.ga-del {
  color: #8c2726;
  border-color: #f5c2c1;
  background: #fdeceb;
}

.ga-empty {
  font-size: 11.5px;
  color: var(--text-muted);
  font-style: italic;
}

/* ---- header + sidebar counters ------------------------------------------ */
.toolbar .btn-undo-hide {
  margin-top: 0;
  flex: 0 0 auto;
}

.btn-undo-hide {
  margin-top: 5px;
  align-self: flex-start;
  color: var(--hid-ink);
  border-color: var(--hid-border);
  background: var(--hid-tint);
}

.sidebar-stats .stat-grouped,
.sidebar-stats .stat-rated {
  color: var(--grp-ink);
}

.sidebar-stats .stat-grouped b,
.sidebar-stats .stat-rated b {
  color: var(--grp-mark);
}

.sidebar-stats .stat-hidden {
  color: var(--hid-ink);
}

/* ==========================================================================
   SHORT-TERM RENTAL (Airbnb) RESTRICTION
   ==========================================================================
   No new hue: the "απαγόρευση νέας βραχυχρόνιας" badge borrows the same red
   TINT the `Χωρίς ανελκυστήρα` badge already uses for "no / not available",
   deliberately NOT the solid `--danger` fill of ⛔ Αποσύρθηκε — a delisted
   listing must stay the single strongest negative on a card, because it cannot
   be bought at all, whereas this one can (and an existing registration keeps
   operating). The unverified case reuses `.badge-unknown` verbatim, so
   "unknown" looks the same here as it does for an unstated lift or a
   placeholder year. */
.badge-airbnb-banned {
  color: #8c2726;
  background: #fdeceb;
  border-color: #f5c2c1;
}

/* Nothing of its own: `.badge-unknown` already carries the dashed grey. This
   selector exists so the harness (and a future reader) can find it. */
.badge-airbnb-unknown {
  font-weight: 600;
}

/* Five states do not fit one line on a narrow panel, so this one wraps. */
.seg-5 {
  flex-wrap: wrap;
}

.segrow-airbnb .segrow-label {
  color: #8c2726;
}

/* «Νέες περιοχές» — the same teal the map, the ✨ badge and the fresh-card border
   use, so the control is visibly about the same thing the teal ring means. */
.segrow-new-area .segrow-label {
  color: #0b5563;
}

.segrow-new-area .fset-note {
  color: var(--text-secondary);
}

/* ==========================================================================
   THE RESTORED-FILTERS NOTICE
   ==========================================================================
   Same chip idiom as every other thing in the active-filter row, but it is a
   statement rather than a filter, so it is muted and carries no filter colour. */
.afchip-restored {
  border-style: dashed;
  border-color: var(--border-strong);
  background: var(--surface-1);
  color: var(--text-secondary);
  font-weight: 600;
}

.afchip-restored .afchip-warn {
  margin-left: 5px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--surface-3);
  color: #7a3d02;
  font-weight: 700;
  font-size: 9.5px;
}

/* ==========================================================================
   THE LISTING POPUP: summary · photos · ♥ favourites · group picker
   ========================================================================== */
.listing-popup {
  font-size: 11.5px;
  color: var(--text-secondary);
  max-width: 320px;
}

.listing-popup .pop-title {
  margin: 0;
  font-size: 13px;
  color: var(--text-primary);
}

.pop-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-top: 2px;
}

.pop-price {
  font-size: 14px;
  color: var(--text-primary);
}

.pop-ppsm {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.pop-specs {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 9px;
  margin-top: 4px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.pop-loc {
  margin-top: 3px;
  color: var(--text-muted);
  font-size: 10.5px;
}

.pop-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
}

.pop-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--surface-3);
  /* Three children now (♥ · 🚫 Απόκρυψη · spitogatos↗), which do not fit 340px on
     one line — so the row wraps rather than squeezing a label to nothing. */
  flex-wrap: wrap;
  row-gap: 5px;
}

/* The ♥. THREE signals, not just a colour: the glyph (♥ / ♡), the fill, and an
   aria-label that names every group it is in. The "on" colour is the app's
   generic interactive accent — the same blue that marks a pressed segment — so
   it reads as "this action is active" and invents no new meaning. */
.pop-fav {
  font: inherit;
  font-size: 10.5px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-muted);
  cursor: pointer;
  max-width: 190px;
}

.pop-fav:hover {
  background: var(--surface-3);
  border-color: var(--border-strong);
  color: var(--text-secondary);
}

.pop-fav.is-on {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
}

.pop-fav-icon {
  font-size: 13px;
  line-height: 1;
}

.pop-fav-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 🚫 Απόκρυψη, the popup's copy of the card's control — same wording, same
   prefs.js path, same reversibility. Deliberately quieter than the ♥: hiding is
   destructive-looking even though it is not destructive, so it does not get a
   filled accent until it is actually ON, and then it borrows the SAME slate
   palette the hidden card and the 🙈 badge already use rather than inventing a
   third way to say "hidden". */
.pop-hide {
  font: inherit;
  font-size: 10.5px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}

.pop-hide:hover {
  background: var(--surface-3);
  border-color: var(--border-strong);
  color: var(--text-secondary);
}

.pop-hide.is-on {
  color: var(--hid-ink);
  background: var(--hid-tint);
  border-color: var(--hid-border);
}

.pop-hide-icon {
  font-size: 11px;
  line-height: 1;
}

/* ---- the area's two 1–3 ratings ----------------------------------------
   Safety perception and prestige, from data/area-rankings.json. THREE signals
   per chip, for the same reason the 1–5 star rating has three: the icon, the
   colour band, and the value WRITTEN OUT — because «δεν έχει αξιολογηθεί» must
   never be able to look like a low score, and a bare "1/3" must never look like
   a colour someone chose. The full evidence is in each chip's `title`. */
.pop-ratings {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 5px;
}

.pop-rating {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 2px 7px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface-2);
  font-size: 10px;
  color: var(--text-secondary);
  cursor: help;
}

.pop-rating-icon {
  font-size: 10.5px;
}

.pop-rating-value {
  font-variant-numeric: tabular-nums;
}

/* 1 = worst, 3 = best, on both scales. Deliberately the same red/amber/green
   the lift and delisted badges already use, so the direction needs no legend. */
.pop-rating.r-1 {
  color: #8c2726;
  background: #fdeceb;
  border-color: #f5c2c1;
}

.pop-rating.r-2 {
  color: #6b5410;
  background: #fdf6e6;
  border-color: #f0d9a8;
}

.pop-rating.r-3 {
  color: #14614a;
  background: #eaf9f3;
  border-color: #b6e6d3;
}

/* UNRATED is its own look, and pointedly NOT the middle one: dashed and muted,
   the same treatment `.badge-lift-unknown` gives an unstated lift. Reading this
   as "average" would be exactly the misreading the research refuses. */
.pop-rating.is-unrated {
  color: var(--text-muted);
  background: var(--surface-2);
  border-color: var(--border);
  border-style: dashed;
  font-style: italic;
}

/* The sources contradict each other here. Visible on the chip, not only in the
   tooltip — a hedged finding must not read as a confident one. */
.pop-rating.is-flagged {
  border-style: dashed;
  border-color: var(--warn);
}

.pop-rating-flag {
  color: var(--warn);
  font-size: 10px;
}

/* ---- 🚇 the live transit route to TangoFix ------------------------------
   A plain keyless link to the Google Maps URLs API, opened in a new tab. Styled
   as a link rather than as a button because that is what it is, and set apart
   from the 🚗 figure above it so the two are not read as one number: that one is
   stored free-flow driving time, this is a live public-transport lookup. */
.pop-transit {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 5px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}

.pop-transit:hover {
  text-decoration: underline;
}

/* Routed from the area centroid because the listing has no coordinates of its
   own. Dashed underline, so "approximate" is visible and not only in the title. */
.pop-transit.is-coarse {
  color: var(--text-secondary);
  text-decoration: underline dashed;
  text-underline-offset: 2px;
}

/* `display: flex` / `inline-flex` on the UA sheet's `[hidden] { display: none }`
   wins, so every box-display container added above needs its own guard. Two real
   bugs shipped from omitting exactly this. */
.pop-ratings[hidden],
.pop-rating[hidden],
.pop-hide[hidden],
.pop-transit[hidden] {
  display: none;
}

.pop-fav:focus-visible,
.pop-hide:focus-visible,
.pop-transit:focus-visible,
.pop-group:focus-visible,
.pop-back:focus-visible,
.pop-new-group:focus-visible,
.pop-thumb:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---- the group picker (same view, swapped content) ---------------------- */
.pop-picker-head {
  display: flex;
  align-items: center;
  gap: 7px;
}

.pop-back {
  font: inherit;
  font-size: 13px;
  line-height: 1;
  padding: 1px 7px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface-2);
  color: var(--text-secondary);
  cursor: pointer;
}

.pop-back:hover {
  background: var(--surface-3);
}

.pop-picker-hint {
  margin: 4px 0 6px;
  font-size: 10px;
  color: var(--text-muted);
}

.pop-groups {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

/* Reads the same `--gc-*` trio the sidebar's own group chips do, so a group's
   colour is the same wherever it appears. */
.pop-group {
  font: inherit;
  font-size: 10.5px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border: 1px solid var(--gc-border, var(--border));
  border-radius: 999px;
  background: #fff;
  color: var(--gc-ink, var(--text-muted));
  cursor: pointer;
  white-space: nowrap;
}

.pop-group.is-on {
  background: var(--gc-mark, var(--accent));
  border-color: var(--gc-mark, var(--accent));
  color: #fff;
}

.pop-group-tick {
  font-size: 10px;
  line-height: 1;
}

.pop-groups-empty {
  margin: 0;
  font-size: 10.5px;
  color: var(--text-muted);
}

.pop-new-group {
  font: inherit;
  font-size: 10.5px;
  font-weight: 600;
  margin-top: 7px;
  padding: 3px 9px;
  border: 1px dashed var(--border-strong);
  border-radius: 999px;
  background: var(--surface-1);
  color: var(--text-secondary);
  cursor: pointer;
}

.pop-new-group:hover {
  background: var(--surface-3);
}

/* A write that did not land says so, right where the click happened. */
.pop-error {
  margin-top: 6px;
  padding: 3px 6px;
  border: 1px solid #f5c2c1;
  border-radius: 4px;
  background: #fdeceb;
  color: #8c2726;
  font-size: 10.5px;
  font-weight: 600;
}

/* ---- photos: one scrolling row of 300x220 thumbnails ------------------- */
.pop-photos {
  margin-top: 6px;
}

.pop-photos-count {
  font-size: 10px;
  color: var(--text-muted);
}

.pop-photos-load {
  margin-left: 6px;
  font-size: 10px;
  color: var(--text-muted);
  font-style: italic;
}

.pop-strip {
  display: flex;
  gap: 4px;
  margin-top: 3px;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 3px;
  scrollbar-width: thin;
}

.pop-thumb {
  flex: 0 0 auto;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--surface-2);
  cursor: pointer;
  line-height: 0;
  overflow: hidden;
}

.pop-thumb:hover {
  border-color: var(--accent);
}

.pop-thumb img {
  display: block;
  width: 120px;
  height: 88px;
  object-fit: cover;
}

.pop-photo-large {
  display: block;
  width: 100%;
  max-width: 300px;
  height: auto;
  margin-top: 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
}

.pop-photo-caption {
  margin-top: 4px;
  font-size: 10.5px;
  color: var(--text-muted);
}

/* ==========================================================================
   THE FAVOURITES PAGE (/favourites)

   Same tokens, same card, different job. The map page answers "where?"; this
   page answers "who do I call, and about which of these?" — so the agency
   header, not the listing, is the primary object, and the listing cards sit
   inside it. No new hues: the palette already carries nine meanings, so the
   section kinds are distinguished by their left rule and icon only.
   ========================================================================== */

.fav-main {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 12px 14px 28px;
  background: var(--surface-2);
}

/* ---- controls -------------------------------------------------------- */

.fav-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--header-gap);
  padding: 8px 14px;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-1);
  position: sticky;
  top: 0;
  z-index: 10;
}

.fav-ctl {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.fav-ctl-label {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  font-weight: 600;
}

.fav-ctl select {
  width: auto;
  min-width: 150px;
}

/* ---------------------------------------------------------------------------
   The multi-select group chips («Επιλεγμένες ομάδες»).

   Deliberately the SAME visual vocabulary as the map page's .gchip — pill, ✓ when
   on, brand fill — because it is the same act on the same objects, and two
   different-looking group pickers in one app would read as two different features.
   `display: flex` needs the [hidden] guard: the UA rule `[hidden] { display: none }`
   has lower specificity and loses, which is exactly the bug .fav-notice shipped
   with.
   --------------------------------------------------------------------------- */
.fav-groupset {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  max-width: 340px;
}

.fav-groupset[hidden] {
  display: none;
}

.fav-groupset-empty {
  font-size: 11px;
  color: var(--text-muted);
  font-style: italic;
}

.fav-gchip {
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-1, #fff);
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.fav-gchip:hover {
  background: var(--surface-3);
}

/* aria-pressed IS the state — no parallel class to fall out of sync with it. */
.fav-gchip[aria-pressed='true'] {
  background: var(--grp-mark);
  border-color: var(--grp-mark);
  color: #fff;
}

.fav-gchip[aria-pressed='true']::before {
  content: '✓';
  font-size: 10px;
}

.fav-gchip-n {
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  opacity: 0.75;
}

/* Direction toggle sits on the sort control's baseline, not above it. */
.fav-dir {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

.fav-dir-icon {
  font-variant-numeric: tabular-nums;
}

.fav-ctl-checks {
  flex-direction: row;
  align-items: center;
  gap: 12px;
  margin-left: auto;
}

/* ---- a section: one agency (or bucket) and its listings -------------- */

.fav-section {
  margin-bottom: 14px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius);
  overflow: hidden;
}

/* Section kind is carried by the left rule + the icon, never by a new hue. */
.fav-section.is-agency { border-left-color: var(--seq-4); }
.fav-section.is-private { border-left-color: #4a3aa7; }
.fav-section.is-none { border-left-color: var(--border-strong); }
.fav-section.is-area { border-left-color: var(--seq-2); }
.fav-section.is-ungrouped { border-left-color: var(--text-muted); }
.fav-section.is-flat { border-left-color: transparent; }

.fav-head {
  padding: 9px 12px;
  background: var(--surface-1);
  border-bottom: 1px solid var(--surface-3);
  position: sticky;
  top: 0;
}

.fav-head-top {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.fav-head-name {
  margin: 0;
  font-size: 14px;
  font-weight: 650;
  letter-spacing: -0.01em;
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.fav-head-icon {
  font-size: 13px;
  line-height: 1;
}

.fav-head-count {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Two offices sharing one romanised name but different agent_ids. */
.fav-branch {
  font-size: 9.5px;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: 3px;
  border: 1px dashed var(--border-strong);
  color: var(--text-muted);
  background: var(--surface-2);
  white-space: nowrap;
}

/* ---- aggregates ------------------------------------------------------ */

.fav-head-aggs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  margin-top: 5px;
}

.fav-agg {
  font-size: 10.5px;
  padding: 1px 7px;
  border-radius: 3px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.fav-agg-sub {
  margin-left: 5px;
  color: var(--text-muted);
}

/* Rating reuses the magenta the retired boolean favourite owned. */
.fav-agg-rating {
  color: #8d1f6d;
  background: #fdeaf6;
  border-color: #f0c2e0;
}

.fav-agg-unrated {
  color: var(--text-muted);
  border-style: dashed;
}

/* Off-market inside a group: the strongest negative already in the palette. */
.fav-agg-warn {
  color: #8c2726;
  background: #fdeceb;
  border-color: #f5c2c1;
}

/* ---- contact: shown ONCE per agency, because that is the point -------- */

.fav-head-contact {
  margin: 6px 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 3px 10px;
  font-size: 11px;
  color: var(--text-secondary);
}

.fav-head-contact.is-note {
  color: var(--text-muted);
  font-style: italic;
}

.fav-head-phones {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.fav-head-phones .tel {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  border-bottom: 1px solid transparent;
}

.fav-head-phones .tel:hover {
  border-bottom-color: var(--accent);
}

.fav-head-address,
.fav-head-agents {
  color: var(--text-muted);
}

.fav-head-profile {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}

.fav-head-profile:hover {
  text-decoration: underline;
}

/* ---- the cards ------------------------------------------------------- */

.fav-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: 8px;
  padding: 9px;
  background: var(--surface-2);
}

.fav-card {
  background: var(--surface-1);
}

/* ---- photos: collapsed by default, so a long list stays scannable ----- */

.fav-photos {
  margin-top: 6px;
}

.fav-photos-toggle {
  font: inherit;
  font-size: 10.5px;
  padding: 2px 7px;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--surface-2);
  color: var(--text-secondary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.fav-photos-toggle:hover {
  background: var(--surface-3);
  border-color: var(--border-strong);
}

.fav-photos-caret {
  font-size: 8px;
  line-height: 1;
}

.fav-photos-load {
  margin-left: 6px;
  font-size: 10px;
  color: var(--text-muted);
  font-style: italic;
}

.fav-strip {
  display: flex;
  gap: 4px;
  margin-top: 4px;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 3px;
  scrollbar-width: thin;
}

.fav-thumb {
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
  line-height: 0;
  background: var(--surface-2);
}

.fav-thumb:hover {
  border-color: var(--accent);
}

.fav-thumb img {
  display: block;
  width: 120px;
  height: 88px;
  object-fit: cover;
}

/* ---- empty state, restored-filters notice, provenance ---------------- */

.fav-empty,
.fav-empty-list {
  padding: 26px 18px;
  text-align: center;
  color: var(--text-muted);
  font-size: 12px;
  background: var(--surface-1);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
}

/* `display: flex` beats the UA sheet's `[hidden] { display: none }`, so without
   this the empty notice bar renders as a bare amber strip whenever there is
   nothing to say. Same trap applies to any hideable flex/grid container. */
.fav-notice[hidden] {
  display: none;
}

.fav-notice {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-bottom: 10px;
  padding: 6px 10px;
  font-size: 11px;
  border-radius: var(--radius);
  border: 1px solid #f0d9a8;
  background: #fdf6e6;
  color: #6b5410;
}

.fav-notice-item {
  font-variant-numeric: tabular-nums;
}

.fav-provenance {
  margin-top: 16px;
  font-size: 10px;
  color: var(--text-muted);
  line-height: 1.5;
}

@media (max-width: 900px) {
  .fav-controls { gap: 8px; }
  .fav-ctl-checks { margin-left: 0; }
  .fav-cards { grid-template-columns: 1fr; }
}

/* ==========================================================================
   ΤΗΛΕΦΩΝΟ — the phone
   ==========================================================================

   EVERYTHING BELOW IS ADDITIVE. Not one rule above this line was edited, and
   nothing here can apply on a desktop: every block is inside a media query that
   needs either a narrow viewport or a coarse pointer, and the three unconditional
   declarations (`.view-toggle`, `.btn-glyph`, `.tiptap`) are all `display: none`
   or style an element that only exists on a touch screen. At 1280px with a mouse
   this file therefore behaves exactly as it did before.

   THE PROBLEM THIS SOLVES
   ---------------------------------------------------------------------------
   `.app-main` was a two-pane flex split — a fixed-width sidebar plus whatever the
   map could have — and the only responsive step was 400px -> 330px at 1100px. On
   a 390px phone that left the map about 60px wide: BOTH views unusable at once,
   which is worse than either one alone.

   THE BREAKPOINTS, AND WHY THOSE NUMBERS
     760px  the split collapses. The sidebar is 400px and a card needs about all
            of it (price row, spec row, badge row, contact row, the "your stuff"
            cluster), so the first width at which the two panes can BOTH work is
            roughly 400 + 400. Below that one of them has to go, and the honest
            place to draw the line is where the map would start losing more than
            it gains — ~760px. A landscape phone (844 x 390) is above it and keeps
            the split, correctly: 844px really does fit a 330px sidebar and a
            514px map.
     620px  the brand mark folds away to .sr-only and the search field shares its
            row with the view toggle, so the toolbar loses a whole row.
     480px  the longest button labels fold away to .sr-only behind a glyph.
     430px  the two least useful stat tiles fold away.
   Each step removes CHROME, never a control and never a number that appears
   nowhere else — see the notes at each one.

   COARSE POINTER vs NARROW VIEWPORT
   ---------------------------------------------------------------------------
   Touch-target sizing keys off `(pointer: coarse), (max-width: 760px)` — the OR
   of the two. `pointer: coarse` is the honest signal (it says the PRIMARY input
   is a finger, so a hybrid laptop with a mouse reports `fine` and is left alone),
   and the width clause catches a narrow window driven by a mouse, which is what a
   device emulator is. LAYOUT keys off the width alone: a 1024px tablet wants big
   targets, it does not want a single-pane phone layout.
   ========================================================================== */

/* --- the single-pane switch ------------------------------------------------
   Hidden — and therefore not in the Tab order — at every width where both panes
   are on screen, because there is then nothing to switch between. The `[hidden]`
   guard is the documented trap: `display: inline-flex` would beat the UA sheet's
   `[hidden] { display: none }`. */
.view-toggle {
  display: none;
}

.view-toggle[hidden] {
  display: none;
}

/* Same idiom as `.seg` and `.metric-toggle`, and the same accent for the active
   half. NO NEW COLOUR MEANING: the accent blue already means "this control is
   the active one" on every pressed segment, pressed chip and the ♥. */
.view-toggle button {
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 11px;
  border: 0;
  border-right: 1px solid var(--border);
  background: #fff;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
}

.view-toggle button:last-child {
  border-right: 0;
}

.view-toggle button[aria-pressed="true"] {
  background: var(--accent);
  border-right-color: var(--accent);
  color: #fff;
}

/* Stands in for a label that has folded away to .sr-only. Never shown above
   480px: on a desktop the word IS the button. */
.btn-glyph {
  display: none;
}

/* --- the tap-to-read tooltip (js/tiptap.js) --------------------------------
   The bubble that makes a `title` reachable without a mouse. It is only ever
   created on a touch screen, so it needs no media query of its own — but it does
   need the `[hidden]` guard, because it is a flex container that lives its whole
   life behind the `hidden` attribute. */
.tiptap[hidden] {
  display: none;
}

.tiptap {
  position: fixed;
  z-index: 8800;
  display: flex;
  align-items: flex-start;
  gap: 6px;
  max-width: 300px;
  padding: 8px 4px 8px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-1);
  box-shadow: var(--shadow-2);
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-primary);
}

/* `pre-wrap`: several of these titles are multi-paragraph and their newlines are
   the only structure they have. */
.tiptap-text {
  white-space: pre-wrap;
  min-width: 0;
  flex: 1 1 auto;
}

.tiptap-x {
  font: inherit;
  font-size: 15px;
  line-height: 1;
  width: 40px;
  height: 40px;
  padding: 0;
  flex: 0 0 auto;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

/* ==========================================================================
   TOUCH TARGETS — `(pointer: coarse)` OR a narrow window
   ==========================================================================
   The filter panel was deliberately compacted from 418px to 234px and that
   density is NOT undone here: it survives untouched on every mouse-driven
   desktop. What follows only ever runs where a finger is the pointer, and it
   brings the interactive surface to ~44px without changing a single font size
   that carries meaning.
   ========================================================================== */

@media (pointer: coarse), (max-width: 760px) {
  .btn,
  .btn-small {
    min-height: 44px;
    padding: 8px 14px;
  }

  .btn-small {
    font-size: 11.5px;
  }

  .icon-btn {
    width: 44px;
    height: 44px;
    font-size: 16px;
  }

  .view-toggle button {
    min-height: 44px;
    padding: 8px 13px;
  }

  /* The segmented controls: 3px 9px at 11px was the single worst target in the
     app. `flex-wrap` is what lets the 4-state lift and the 5-state short-let
     control take two rows instead of overflowing their own rounded box. */
  .seg {
    flex-wrap: wrap;
  }

  .seg button {
    min-height: 44px;
    padding: 8px 13px;
    font-size: 12px;
  }

  .chip,
  .gchip,
  .act,
  .pop-group,
  .pop-new-group,
  .pop-fav {
    min-height: 44px;
    padding: 8px 15px;
    font-size: 12px;
  }

  .pop-back {
    min-height: 44px;
    min-width: 44px;
    font-size: 16px;
  }

  /* Five stars, each its own button: 13px glyphs with 1px of padding were
     unhittable next to one another. 5 x 44 = 220px, which fits the narrowest
     card this app renders. */
  .star {
    width: 44px;
    height: 44px;
    font-size: 20px;
  }

  .rate-clear {
    min-height: 44px;
    padding: 8px 12px;
    font-size: 11px;
  }

  .note-toggle {
    min-height: 44px;
  }

  .note-delete {
    min-height: 44px;
    padding: 8px 12px;
  }

  .fsheet-close {
    width: 44px;
    height: 44px;
    font-size: 16px;
  }

  .legend-collapse {
    width: 44px;
    height: 44px;
    font-size: 18px;
  }

  .metric-toggle button {
    min-height: 44px;
    padding: 8px 12px;
    font-size: 11px;
  }

  /* The provenance disclosure is a `summary`, so it needs the height itself. */
  .fsheet-meta > summary {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 2px;
  }

  /* Each removable chip on the active-filter row. The row matters MORE on a
     phone than on a desktop — it is the only thing that explains a filtered
     count without opening the sheet — so it is kept in full, and its ✕ is a
     real target rather than a 17px dot. */
  .afchip {
    min-height: 44px;
    padding: 2px 2px 2px 13px;
    font-size: 12px;
  }

  .afchip-x {
    width: 40px;
    height: 40px;
    line-height: 40px;
    font-size: 14px;
  }

  .afchip-all {
    min-height: 44px;
    padding: 8px 15px;
    font-size: 11.5px;
  }

  input[type="number"],
  input[type="search"],
  select {
    min-height: 44px;
  }

  /* A 12px checkbox is not a target, and the label has to carry the height so
     the whole «Χωρίς διπλότυπα» row is tappable, not just the box. */
  .check,
  .legend-metro-toggle {
    min-height: 44px;
  }

  .check input,
  .legend-metro-toggle input {
    width: 22px;
    height: 22px;
  }

  /* Dialling is the entire point of the contact block. */
  .card-contact .tel,
  .fav-head-phones .tel,
  .card-link,
  .fav-head-profile {
    display: inline-block;
    padding: 10px 0;
  }

  .fav-photos-toggle {
    min-height: 44px;
    padding: 8px 12px;
  }

  /* Leaflet's own zoom buttons. The second selector is not redundant: Leaflet's
     `.leaflet-touch .leaflet-bar a` is more specific than a bare `.leaflet-bar a`
     and would otherwise keep them at 30px. */
  .leaflet-bar a,
  .leaflet-touch .leaflet-bar a {
    width: 44px;
    height: 44px;
    line-height: 44px;
    font-size: 20px;
  }

  /* Leaflet sets these with the `font:` shorthand (16px/24px in a 24px box), so
     the longhands here have to be longhands to win. */
  .leaflet-container a.leaflet-popup-close-button {
    width: 40px;
    height: 40px;
    font-size: 20px;
    line-height: 40px;
  }

  /* Not interactive, but tiptap.js makes every one of these tappable, so they
     need something to tap. */
  .hint,
  .legend-metro-note,
  .legend-metro-caveat {
    padding: 3px 2px;
  }
}

/* ==========================================================================
   ≤760px — ONE PANE AT A TIME
   ========================================================================== */

@media (max-width: 760px) {
  /* ---- the split collapses ------------------------------------------------
     The DEFAULT is the map, expressed as "the sidebar is hidden unless the body
     says list". That ordering is deliberate: if app.js never boots, no
     `data-view` attribute is ever written and the phone still gets a full-width
     map rather than the 60px strip this whole block exists to end. */
  .sidebar {
    display: none;
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
    border-left: 0;
  }

  body[data-view="list"] .sidebar {
    display: flex;
  }

  body[data-view="list"] .map-pane {
    display: none;
  }

  .view-toggle {
    display: inline-flex;
    margin-left: auto;
    border: 1px solid var(--border-strong);
    border-radius: 4px;
    overflow: hidden;
    flex: 0 0 auto;
  }

  /* ---- the toolbar --------------------------------------------------------
     Wrapped rows, in DOM order, so the reading order, the Tab order and the
     visual order stay the same order — no `order` juggling except the stat
     strip's, which the 940px rule above already established. */
  .toolbar {
    padding: 6px 10px;
    gap: 6px 8px;
  }

  .brand-title {
    font-size: 12px;
    padding-left: 7px;
  }

  /* All five live counts survive, on two short rows rather than behind a
     horizontal scrollbar nobody sees. Each tile is label-over-value and only
     ~26px tall, so two rows cost ~26px — cheaper than hiding a number that
     appears nowhere else on the page. The 430px step below is where that stops
     being true. */
  .summary-bar {
    flex-wrap: wrap;
    row-gap: 2px;
    overflow-x: visible;
  }

  /* ---- the filter sheet: a full-height, single-column, scrolling sheet -----
     `position: fixed` rather than absolute-under-the-header: on a phone there is
     no room for a sheet that starts below a 3-row toolbar. It stays inside the
     header's stacking context (z-index 500), so it still paints over the scrim
     (480) and over the map, exactly as the anchored version did. */
  .fsheet {
    position: fixed;
    inset: 0;
    max-height: none;
    border-top: 0;
    border-bottom: 0;
    border-radius: 0;
    animation: none;
  }

  .fsheet-head {
    position: sticky;
    top: 0;
    z-index: 2;
    padding: 5px 10px;
    row-gap: 4px;
    border-bottom: 1px solid var(--border);
  }

  /* The result count and the provenance disclosure each take their own line, so
     the row that carries «Επαναφορά όλων» and ✕ cannot be pushed off it. */
  .fsheet-result {
    order: 5;
    flex: 1 1 100%;
  }

  .fsheet-meta {
    order: 20;
    flex: 1 1 100%;
  }

  /* ONE column — which is the whole reason the macro-column grid existed: three
     446px columns cannot exist at 390px, and `auto-fit` would otherwise leave a
     single column of 446px content in a 366px box. */
  .fsheet-body {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    padding: 0 10px 24px;
  }

  .rgrid,
  .sgrid {
    grid-template-columns: minmax(0, 1fr);
    gap: 9px 0;
  }

  /* Label ABOVE its control rather than in an 84px gutter: at 366px a gutter
     leaves too little for a min–max pair with 44px inputs, and a wrapped
     4- or 5-state segment wants the full width. */
  .rrow,
  .segrow {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px 0;
  }

  .rrow-label,
  .segrow-label,
  .checkrow-label,
  .chipset-label {
    font-size: 11px;
  }

  .segrow-wide {
    grid-column: auto;
  }

  .checkrow {
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
  }

  /* iOS zooms the whole page when a control with a font smaller than 16px takes
     focus, and it does not zoom back out. This is the only reason for the size. */
  input[type="number"],
  input[type="search"],
  select {
    font-size: 16px;
  }

  /* ---- the active-filter chips ------------------------------------------- */
  .active-bar {
    padding: 0 10px 7px;
    gap: 6px;
  }

  /* ---- the sidebar, now the whole screen -------------------------------- */
  .sidebar-head {
    padding: 8px 10px;
  }

  .sidebar-head h2 {
    font-size: 15px;
  }

  .sidebar-list {
    padding: 8px 8px 28px;
  }

  .card {
    padding: 10px;
  }

  /* ---- the legend: folded by DEFAULT, because 224px of it over a 390px map
     is a third of the map -------------------------------------------------
     The default is a property of the SCREEN, not of stored state: the markup
     ships the box with NEITHER class, `#legend-body` is visible without
     `.is-collapsed` (desktop) and hidden without `.is-expanded` (here). One tap
     on `+` opens the whole thing — the ramp, the six provenance rows and both
     metro blocks with their toggles. Nothing is removed. See the legend block in
     js/app.js for the matching state resolution. */
  .legend-box #legend-body {
    display: none;
  }

  .legend-box.is-expanded #legend-body {
    display: block;
    max-height: 44vh;
  }

  .legend-box:not(.is-expanded) .legend-head {
    margin-bottom: 0;
  }

  .legend-box {
    right: 8px;
    bottom: 22px;
    width: min(236px, calc(100vw - 16px));
    padding: 6px 8px;
  }

  .legend-head {
    flex-wrap: wrap;
    row-gap: 4px;
  }

  /* ---- the listing popup ------------------------------------------------
     The width cap comes from js/app.js (popupMaxWidth) because Leaflet applies
     it to the content node and the wrapper's margins sit outside it; the height
     cap is Leaflet's own `maxHeight`, which adds `.leaflet-popup-scrolled` and
     gives the popup a real scroll container that `autoPan` understands.

     THE PHOTO STRIP'S WHEEL HANDLER IS UNTOUCHED, and cannot interfere here:
     Leaflet's `disableScrollPropagation` binds `wheel` and nothing else, and the
     handler in app.js also listens to `wheel` only. A finger never fires one, so
     native horizontal panning of the strip, vertical scrolling of the popup and
     two-finger map gestures all reach the browser unmodified. */
  .leaflet-popup-content {
    margin: 8px 10px;
    min-width: 0;
    font-size: 12.5px;
  }

  .listing-popup {
    max-width: none;
  }

  /* So that reaching the end of the popup does not start scrolling the page. */
  .leaflet-popup-scrolled {
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  .pop-photo-large {
    max-width: 100%;
  }

  .pop-fav {
    max-width: none;
  }

  .pop-actions {
    flex-wrap: wrap;
  }

  /* ---- /favourites -----------------------------------------------------
     The page becomes a NORMAL SCROLLING DOCUMENT. As a flex column with a
     scrolling <main> the header was permanently on screen: brand + 5 stat tiles
     + 3 selects + 2 checkboxes + a reset button + the provenance line, about
     180px of a 390px-wide phone that never moves. Letting it scroll away is the
     fix, and it also disarms the sticky stack the layout otherwise builds — the
     controls row and every agency header are pinned explicitly to `static`, so
     there is exactly one thing on screen at a time. Nothing is removed: every
     control is in the same place, one flick up. */
  body.page-favourites {
    display: block;
    height: auto;
    min-height: 100%;
  }

  .page-favourites .fav-main {
    overflow: visible;
    padding: 10px 10px 32px;
  }

  .page-favourites .fav-controls {
    position: static;
    padding: 8px 10px;
    gap: 8px;
  }

  .page-favourites .fav-head {
    position: static;
    padding: 8px 10px;
  }

  .page-favourites .fav-ctl {
    flex: 1 1 100%;
  }

  .page-favourites .fav-ctl select {
    width: 100%;
    min-width: 0;
  }

  .page-favourites .fav-ctl-checks {
    flex-wrap: wrap;
    margin-left: 0;
    gap: 4px 12px;
  }

  .page-favourites .fav-cards {
    padding: 8px;
  }

  .page-favourites .fav-provenance {
    margin-top: 6px;
  }
}

/* ==========================================================================
   ≤620px — the toolbar loses a row
   ==========================================================================
   The brand mark is the cheapest thing in the header: the tab title and the
   favicon already say what the page is. It folds away to .sr-only rather than
   `display: none`, so the document keeps its only <h1> in the accessibility
   tree, and the search field then shares row one with the view toggle instead of
   claiming a row of its own (the 620px rule above gives it `flex-basis: 100%`;
   this overrides that without editing it).
   ========================================================================== */

@media (max-width: 620px) {
  .toolbar .brand-title {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  .toolbar .tb-search {
    flex: 1 1 150px;
    min-width: 120px;
  }

  .toolbar .view-toggle {
    margin-left: 0;
  }
}

/* ==========================================================================
   ≤480px — the longest labels fold to .sr-only behind a glyph
   ==========================================================================
   `Σημειωμένες` and `Επαναφορά` are ~150px of a 366px row between them. The word
   is hidden the .sr-only way, NOT removed: each control keeps exactly the
   accessible name it has on a desktop, and each keeps a glyph (🏷 / ↺) plus its
   live count, so it is still recognisable by eye.
   ========================================================================== */

@media (max-width: 480px) {
  .toolbar .btn-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  /* …except the view toggle's, which is the one place the words are the whole
     point: `Χάρτης` / `Λίστα` is what tells you which pane you are looking at. */
  .toolbar .view-toggle .btn-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip: auto;
  }

  .toolbar .btn-glyph {
    display: inline;
    font-size: 14px;
  }

  /* With their words folded away these two are a glyph and a badge, so they no
     longer need a word's worth of side padding. */
  .toolbar #btn-reset,
  .toolbar .btn-marked {
    padding-left: 12px;
    padding-right: 12px;
  }
}

/* ==========================================================================
   ≤430px — the stat strip is triaged
   ==========================================================================
   Below this the five tiles need three rows, and three rows of statistics on a
   360px phone costs more map than it returns. Exactly ONE tile goes, and it is
   the only one of the five that is still on screen somewhere else:

     Διάμ. €/m²   ->  the list view's own stats block (#sidebar-stats) carries it

   `Εύρος` and `Περιοχές` stay precisely because they are NOT repeated anywhere,
   so hiding them would delete a number from the page rather than move the user to
   the other copy of it. `Αγγελίες` and `Διάμ. τιμή` are the two the header is for.
   ========================================================================== */

@media (max-width: 430px) {
  .summary-bar .stat-tile-ppsm {
    display: none;
  }

  .summary-bar .stat-tile-count {
    padding-left: 0;
  }

  .summary-bar .stat-tile-areas {
    padding-right: 0;
  }
}
