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

body {
  font-family: var(--ui-font-body);
  background-color: var(--ui-bg);
  color: var(--ui-on-surface);
}

/* Dark mode (UIX-01, plan 22-04): REAL colour, not the legacy global colour-invert
   hack. The body.dark-mode class no longer applies a colour-inverting CSS filter —
   instead it is the trigger that activates the Nocturnal Verdance token override
   declared in tokens.css (body.dark-mode { --ui-bg: …; --ui-surface: …; … }). Every
   chrome surface below reads its colour from those --ui-* tokens, so flipping the
   class re-points the whole chrome to real dark colours with NO filter.

   The old GL-basemap-pane counter-invert rule is RETIRED in the same change: with
   the global colour-invert gone there is no invert-of-invert to cancel. The map's
   dark appearance now comes ENTIRELY from the GL native flavor (script.js calls
   window.dionSetBasemapFlavor('dark'), which setStyle's the inner MapLibre map to
   DION_THEME.dark — untouched here). */

.fullscreen-app {
  display: flex;
  flex-direction: column;
  height: 100dvh;
}

/* ── Header layout REMOVED (Phase 24.1, UIX-12) ──────────────────────────────
   The fixed 60px Phase-24 <header>/burger chrome is GONE from index.html — the
   bottom action bar (.dion-actionbar, styled in components.css) is the new primary
   chrome and the díon mark floats over the map (.dion-wordmark). The old
   header{position:fixed;height:60px}, .logo header treatment, the burger toggle +
   its slide-in dropdown, and the .panel-toggle trigger are deleted here — they
   targeted DOM that no longer exists.

   The .lang-toggle and .mode-toggle buttons are KEPT in a hidden delegation shell
   (index.html .dion-chrome-hidden[hidden]) because panel.js/script.js query them at
   boot; they are the bar's Settings-menu delegation targets, never visible. Hide them
   globally (no longer header-scoped, since the header layout is gone). 24.1 */
.lang-toggle,
.mode-toggle {
  display: none;
}

/* ── Hamburger menu rows (UIX-02, plan 22-04) ────────────────────────────────
   The four rows (Mód · Teanga · Eochair · Faoi) restyled with --ui-* tokens.
   44px touch targets on every interactive control (a11y floor UIX-06). Active
   mode / hover use the warm-green PRIMARY token — the amber score accent is
   NEVER spent here (THEME-03: amber is the 0–10 score value's signature only). */
.ui-menu {
  list-style: none;
  min-width: 220px;
  padding: var(--ui-space-sm) 0;
  font-family: var(--ui-font-body);
}

.ui-menu__row {
  display: flex;
  flex-direction: column;
  gap: var(--ui-space-sm);
  padding: var(--ui-space-md) var(--ui-space-md);
  border-bottom: 1px solid var(--ui-surface-container-high);
}

.ui-menu__label {
  font-family: var(--ui-font-body);
  font-size: var(--ui-font-size-label);
  font-weight: var(--ui-font-weight-label);
  line-height: var(--ui-line-height-label);
  color: var(--ui-on-surface-muted);
}

.ui-menu__modes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ui-space-sm);
}

.ui-menu__mode,
.ui-menu__action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 var(--ui-space-md);
  border: 1px solid var(--ui-surface-container-high);
  border-radius: var(--ui-radius-pill);
  background-color: var(--ui-surface);
  color: var(--ui-on-surface);
  font-family: var(--ui-font-body);
  font-size: var(--ui-font-size-label);
  font-weight: var(--ui-font-weight-label);
  line-height: var(--ui-line-height-label);
  text-decoration: none;
  cursor: pointer;
}

.ui-menu__mode:hover,
.ui-menu__action:hover,
.ui-menu__mode:focus-visible,
.ui-menu__action:focus-visible {
  border-color: var(--ui-primary);
  background-color: var(--ui-primary-container);
}

/* Active feature-depth mode: warm-green primary fill (never amber). The
   subscriber in script.js marks the active row with aria-pressed="true". */
.ui-menu__mode[aria-pressed='true'] {
  border-color: var(--ui-primary);
  background-color: var(--ui-primary);
  color: var(--ui-surface);
}

/* (Phase 23, UIX-07) The menu rows no longer carry .a-presser — the retro press
   affordance is GONE. The .ui-menu__mode / .ui-menu__action token pill rules above
   now apply cleanly with no retro override needed. */

main {
  flex: 1;
  height: auto; /* flex: 1 fills the space the retired footer freed (D-15) */
  overflow-y: auto; /* For scrollable content if it exceeds the height */
}

/* (Phase 23, UIX-07) The retro 3D .button-presser / .a-presser treatment — the
   stacked box-shadow, uppercase, 2px letter-spacing, 3px border "pressed" look —
   is REMOVED. The Phase-22 UAT rejected it as a "frankenstein" mix. No visible
   chrome carries those classes anymore; the warm-clean header treatment now lives
   in styles/components.css (pill/bare-icon buttons reading --ui-* — so every
   surface re-points under body.dark-mode, no UA-default grey strand, Pitfall 4).
   The .menu-toggle / .panel-toggle / .lang-toggle / .mode-toggle styling and the
   .lang-toggle dark-rescue it once needed are all subsumed by components.css. */

.leaflet-container {
  cursor: crosshair;
  /* Base map background = the z5 overview SEA_BLUE (portolan.js SEA_BLUE #9fc0d4),
     NOT Leaflet's default #ddd. The overview is sea-dominant, so painting the
     container blue means the pre-first-paint gap (and any moment the GL `background`
     land layer — recoloured to PARCHMENT_LAND at the overview — would flash through)
     reads as water, killing the initial yellow BG flash on load. */
  background: #9fc0d4;
}

/* Honest-failure banners (D-08): unmistakable when something fails. The fixed
   wrapper sits below the 60px header and above the map so the Leaflet container
   can't clip it; individual banners are static blocks inside it, so multiple
   simultaneous failures stack vertically instead of overlapping. Deep red +
   light text via the --ui-destructive / --ui-surface tokens stays high-contrast
   in BOTH light and dark mode (the tokens re-point on body.dark-mode — no filter). */
.error-banner-stack {
  position: fixed;
  top: 60px;
  left: 0;
  right: 0;
  z-index: 1100;
}

.data-error-banner {
  background-color: var(--ui-destructive);
  color: var(--ui-surface);
  font-weight: bold;
  text-align: center;
  padding: 12px 16px;
  box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.3);
}

/* Result-popup typography (03-UI-SPEC): the 0–10 sub-score is the single most
   prominent element — accent #0b6e4f is reserved for the score VALUE only
   (constant colour, no traffic-light scale). 8px vertical rhythm between popup
   rows. The score green is a fixed legible tone that holds in BOTH light and dark
   (no invert now; Phase 23 re-renders the popup card). */
.popup-name {
  font-size: 16px;
  font-weight: 700;
  line-height: 1.2;
  margin: 8px 0;
}

.popup-score-line {
  margin: 8px 0;
}

.popup-score {
  /* THEME-03 (23-04): the popup 0–10 score VALUE is the amber honesty signature,
     re-pointed from the old fixed #0b6e4f green to the token so body.dark-mode
     re-points it (the brighter dark amber) — consistent with the headline +
     breakdown score values. Amber lives ONLY on score values. */
  color: var(--ui-score-accent);
  font-size: 24px;
  font-weight: 700;
  line-height: 1.2;
}

.popup-score-outof {
  font-size: 14px;
  font-weight: 400;
}

.popup-travel {
  font-size: 14px;
  line-height: 1.5;
  margin: 8px 0;
}

.popup-meta {
  font-size: 12px;
  line-height: 1.4;
  margin: 8px 0;
}

/* Place card (16-UI-SPEC): the click=place-info popup reuses the result-popup
   type scale — address row mirrors .popup-travel (14/400), category row mirrors
   .popup-meta (12/400). Accent #0b6e4f is deliberately NOT spent here (THEME-03
   salience: green stays the score VALUE's signal). The "Score from here" CTA is
   the card's focal action — full-width so the 44px touch target holds on BOTH
   axes (the markup sets min-height: 44px). Dark mode is a token re-point now (no filter); the card adds no filter. */
.place-card-addr {
  font-size: 14px;
  line-height: 1.5;
  /* WR-02: a reverse-geocoded label (place address / area locality) is a
     concatenation of up to four Photon fields — a pathological/long OSM label
     must not blow out the popup width on mobile. The text is escaped (no XSS);
     this is a purely visual clamp that keeps the full value accessible to
     assistive tech. Cap the width and force-wrap (incl. a single unbroken
     token), then ellipsis after three lines so the card stays bounded. The
     element's inline `margin: 8px 0` keeps left/right at 0, so the block sits
     within the (already narrow) popup content box; max-width is the guard. */
  max-width: 260px;
  overflow-wrap: anywhere;
  word-break: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.place-card-cat {
  font-size: 12px;
  line-height: 1.4;
}

.place-card-score-btn {
  display: block;
  width: 100%;
  box-sizing: border-box;
}

/* Criteria panel (D-01/D-05): a right slide-in DISCLOSURE — never a modal. A
   translateX slide from the right edge. z-index
   950 keeps it above the map (the Phase-24.1 header removal dropped the old 1000
   header layer; banners still ride 1100). The
   surface reads var(--ui-bg), which re-points to a deep slate under body.dark-mode
   (real colour, no filter); selection meaning lives in border weight, never hue
   (WCAG 1.4.1). */
.criteria-panel {
  position: fixed;
  top: 60px; /* Height of the header */
  right: 0;
  bottom: 0;
  width: 320px;
  background-color: var(--ui-bg);
  transform: translateX(100%);
  transition: transform 0.3s;
  z-index: 950;
  overflow-y: auto;
  padding: 16px;
}

.criteria-panel.active {
  transform: translateX(0);
}

@media (max-width: 767px) {
  .criteria-panel {
    width: 100%;
    left: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .criteria-panel {
    transition: none;
  }
}

/* Panel chrome (D-01): the .panel-toggle header trigger was REMOVED with the header
   (Phase 24.1) — the criteria panel is display:none this slice and is opened by
   delegation, not a header burger. The ✕ close button holds the 44×44 touch target. 24.1 */
.panel-close {
  min-width: 44px;
  min-height: 44px;
  background-color: var(--ui-surface);
  cursor: pointer;
}

.panel-heading {
  font-size: 16px;
  font-weight: 700;
  line-height: 1.2;
}

/* Headline block (D-05: pinned at the panel top; SCORE-01). Accent #0b6e4f is
   reserved for score NUMBERS only (D-07) — a fixed green that holds in light + dark
   (no invert now; Phase 23 re-renders this surface). 24px margin-bottom is the lg
   gap between the headline block and the breakdown/chip body. */
.panel-headline {
  padding: 16px;
  margin-bottom: 24px;
}

.headline-live {
  line-height: 1.2;
}

.headline-score {
  /* THEME-03 (23-04): the headline score VALUE re-pointed to the amber token
     (was the fixed #0b6e4f green) so dark mode re-points it. components.css
     Display-sizes it (28px); this keeps a legible fallback. Amber = score only. */
  color: var(--ui-score-accent);
  font-size: 24px;
  font-weight: 700;
  line-height: 1.2;
}

.headline-outof {
  font-size: 14px;
  font-weight: 400;
}

.headline-note {
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
}

/* Breakdown rows (D-05/D-08): one white card per selected criterion, 8px
   vertical rhythm, 44px minimum touch height (the row is a button — tapping
   pans to its marker, D-06). Error states use #b00020 text — visibly distinct
   from the neutral NO_POI row (SCORE-05/D-20). */
.breakdown-rows {
  margin-bottom: 24px;
}

.breakdown-row {
  display: block;
  width: 100%;
  text-align: left;
  background-color: var(--ui-surface);
  color: var(--ui-on-surface); /* breakdown-row text legible in dark (was unset → inherited black) */
  border: 1px solid var(--ui-surface-container-high);
  margin-bottom: 8px;
  padding: 8px;
  min-height: 44px;
  font-family: inherit;
  cursor: pointer;
}

.row-name {
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
}

.row-travel {
  font-size: 14px;
  line-height: 1.5;
}

.row-subscore {
  /* THEME-03 (23-04): the per-criterion sub-score VALUE re-pointed to the amber
     token (was the fixed #0b6e4f green) so dark mode re-points it. components.css
     restyles the breakdown row (green bar + amber value). Amber = score only. */
  color: var(--ui-score-accent);
  font-size: 16px;
  font-weight: 700;
  line-height: 1.2;
}

.row-state-note {
  font-size: 12px;
  line-height: 1.4;
}

.row-error {
  color: var(--ui-destructive);
}

/* Chip grid (D-02/D-03): emoji + Irish label per criterion, grouped by theme.
   Selection is border WEIGHT (1px hairline → 3px --ui-on-surface), never hue —
   meaning survives in both light + dark by weight, not colour (D-07/WCAG 1.4.1). 44×44 minimum touch targets;
   all gaps on the 4px scale. */
.chip-groups {
  margin-bottom: 16px;
}

.chip-group {
  margin-bottom: 16px;
}

.chip-group-heading {
  font-size: 16px;
  font-weight: 700;
  line-height: 1.2;
  margin-bottom: 8px;
}

.chip-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 44px;
  min-height: 44px;
  padding: 4px;
  background-color: var(--ui-surface);
  color: var(--ui-on-surface); /* chip label/emoji inherit a legible colour in both themes (was unset → browser-default black, illegible on dark) */
  border: 1px solid var(--ui-surface-container-high);
  font-family: inherit;
  cursor: pointer;
}

.chip.selected {
  border: 3px solid var(--ui-on-surface);
}

.chip-emoji {
  font-size: 24px;
}

.chip-label {
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  /* Custom-place names truncate by CSS — the stored name renders via
     textContent only (D-21), never markup (Plan 05-05). */
  max-width: 88px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Weight dots (D-03): three weight toggle buttons per selected chip (WR-09:
   aria-pressed toggles, not radios). Dots are text ●/○ in
   currentColor — fill carries the meaning, so it stays legible in both themes.
   Visible dots are small; the hit area never drops below 44×44. */
.weight-dots {
  display: flex;
  gap: 8px;
}

.weight-dot {
  min-width: 44px;
  min-height: 44px;
  background-color: transparent;
  border: none;
  color: currentColor;
  font-size: 16px;
  font-family: inherit;
  cursor: pointer;
}

/* Headline pill (D-05 mobile surfacing): fixed bottom-center over the map when
   the panel is closed and a score exists; tap reopens the panel. z-index 940 —
   below the panel (950). Accent stays reserved for the score number inside. */
.headline-pill {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  background-color: var(--ui-surface);
  border: 1px solid var(--ui-surface-container-high);
  min-height: 44px;
  padding: 8px 16px;
  z-index: 940;
  font-family: inherit;
  font-size: 14px;
  cursor: pointer;
}

/* Emoji criterion markers (D-06/D-12): one L.divIcon per scored criterion —
   the criterion's emoji at 24px on a transparent tile. The html: sink receives
   ONLY dev-authored emoji constants from criteria-defs.js (D-21). */
.poi-emoji-icon {
  font-size: 24px;
  background: transparent;
  border: none;
  text-align: center;
  line-height: 32px;
}

/* .storage-error-banner (research Pitfall 7) is a MARKER class for the corrupt
   storage banner's single-instance guard — its visual style comes entirely from
   the shared .data-error-banner rule above. */
.storage-error-banner {
  text-align: center;
}

/* Pin-drop + custom places (D-09/D-11/D-12, UI-SPEC P1-P4). Every new control
   keeps the 44px minimum touch target; destructive confirm states use #b00020
   text — never hue-only meaning, the swapped copy carries it too (D-07). */

/* Add-place CTA (P1): 44px minimum target; disabled at the 10-place cap with
   the 'uasmhéid 10 n-áit' hint beside it (D-11). */
.add-place {
  min-width: 44px;
  min-height: 44px;
  background-color: var(--ui-surface);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.5;
  cursor: pointer;
}

.add-place:disabled {
  cursor: not-allowed;
}

/* The action area at the end of the custom grid: hosts the CTA, the armed
   hint + cancel, or the inline name form — full width so it never competes
   with chips for a grid cell. */
.add-place-area {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  width: 100%;
}

/* Armed-mode hint strip (P2, mobile): fixed over the map, same surface family
   as the headline pill (--ui-surface, token hairline border, z-index 940), 44px minimum height. */
.pin-drop-hint {
  position: fixed;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 8px;
  background-color: var(--ui-surface);
  border: 1px solid var(--ui-surface-container-high);
  min-height: 44px;
  padding: 8px 16px;
  z-index: 940;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  max-width: calc(100vw - 32px);
}

.pin-drop-hint button {
  min-width: 44px;
  min-height: 44px;
  background-color: var(--ui-surface);
  font-family: inherit;
  font-size: 14px;
  cursor: pointer;
}

/* CR-02 out-of-bounds refusal hint: warn colour on the hint surfaces (same
   #b00020 as the delete/reset confirm affordances; via the --ui-destructive token, which re-points
   under body.dark-mode — same treatment, no special-casing). */
.hint-warn {
  color: var(--ui-destructive);
}

/* Inline name form (P3): 16px internal padding, Body 14/400 input, 44px-min
   buttons (save disabled until the trimmed name is non-empty). */
.place-name-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 16px;
  background-color: var(--ui-surface);
  border: 1px solid var(--ui-surface-container-high);
}

.place-name-form input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  padding: 8px;
  border: 1px solid var(--ui-surface-container-high);
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
}

.place-name-form button {
  min-width: 44px;
  min-height: 44px;
  background-color: var(--ui-surface);
  font-family: inherit;
  font-size: 14px;
  cursor: pointer;
}

.place-name-form button:disabled {
  cursor: not-allowed;
}

/* Address-search combobox (INPUT-02, 06-UI-SPEC Component A). A WAI-ARIA
   editable input + listbox pinned at the TOP of the panel, above the headline.
   White input/rows on the #f4f7fa panel surface; 24px lg gap below the block.
   Mirrors .place-name-form input (16px padding, token hairline border, min-height 44px).
   Active-option distinction is border/background WEIGHT, never accent hue
   (WCAG 1.4.1) — the reserved score green #0b6e4f never appears here. The surfaces read var(--ui-surface),
   which re-points under body.dark-mode like every other chrome surface (no filter). */
.combobox {
  margin-bottom: 24px;
}

.combobox-input {
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
  padding: 16px;
  border: 1px solid var(--ui-surface-container-high);
  background-color: var(--ui-surface);
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
}

.combobox-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background-color: var(--ui-surface);
  border: 1px solid var(--ui-surface-container-high);
  border-top: none;
}

.combobox-option {
  display: block;
  min-height: 44px;
  padding: 8px;
  margin: 0;
  border-bottom: 1px solid var(--ui-surface-container-high);
  cursor: pointer;
}

.combobox-option:last-child {
  border-bottom: none;
}

/* Active descendant (arrow-key target): border/background weight only — no
   accent fill, so meaning survives in both themes and never collides with the
   score green (WCAG 1.4.1, D-18). */
.combobox-option.active {
  background-color: var(--ui-surface-container-high);
  color: var(--ui-on-surface);
  border-left: 4px solid var(--ui-primary);
  padding-left: 4px;
}

/* The no-results in-list row is informational, not selectable. */
.combobox-option.no-results {
  cursor: default;
  font-style: italic;
}

.combobox-option-primary {
  display: block;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
}

.combobox-option-secondary {
  display: block;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
}

/* Geocoder-down banner (state 5): a marker class on the shared honest-failure
   banner — distinct visible state from the no-results in-list row (D-18). It
   reuses .data-error-banner's colour entirely; no new hue is introduced. */
.geocoder-error-banner {
  /* intentionally empty — all styling inherited from .data-error-banner */
}

/* Custom-chip delete × (D-11): 44×44 hit area; the two-tap confirm state
   swaps the text to 'scrios? / delete?' in the destructive colour. */
.chip-delete {
  min-width: 44px;
  min-height: 44px;
  background-color: transparent;
  border: none;
  font-family: inherit;
  font-size: 16px;
  line-height: 1.2;
  cursor: pointer;
}

.chip-delete.confirming {
  color: var(--ui-destructive);
  font-size: 12px;
}

/* Garda 24/7 sub-toggle (D-15): checkbox + Label 12/400 inside the selected
   garda chip cell; the row keeps the 44px minimum touch height. */
.garda-247-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 44px;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  cursor: pointer;
}

/* Reset (D-16): destructive two-tap button at the panel bottom — restores the
   starter set, never touches custom places. Confirm state shares the
   destructive colour. */
.reset-button {
  min-width: 44px;
  min-height: 44px;
  margin-top: 24px;
  background-color: var(--ui-surface);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.5;
  cursor: pointer;
}

.reset-button.confirming {
  color: var(--ui-destructive);
}

/* Isochrone controls (MAP-02, 06-UI-SPEC Component B). A toggle + walk/drive
   segmented switch + legend, sitting below .breakdown-rows with a 24px lg gap
   above. EXTENDS the panel system — no chrome restyle. The band hue/opacity are
   NOT here: they live in panel.js's L.geoJSON style options (opacity carries
   meaning so it survives the dark token re-point, D-12). Active mode =
   border WEIGHT + aria-pressed, never an accent fill (WCAG 1.4.1) — the reserved
   score green #0b6e4f never appears in this block. */
.isochrone-controls {
  margin-top: 24px;
}

.iso-heading {
  font-size: 16px;
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 8px;
}

.iso-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  background-color: var(--ui-surface);
  border: 2px solid var(--ui-surface-container-high);
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
  padding: 0 16px;
  cursor: pointer;
}

/* Pressed (overlay on) = darker border, same width (no reflow), never an accent fill (WCAG 1.4.1). */
.iso-toggle[aria-pressed='true'] {
  border-color: var(--ui-on-surface);
}

/* Unavailable (no point scored, D-11): visibly disabled; the honest hint sits
   beside it via .iso-needs-point. */
.iso-toggle:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

.iso-needs-point {
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  margin-left: 8px;
}

/* Segmented walk/drive switch (D-10): two buttons, 8px gap, one mode at a time.
   Active = darker border-color via aria-pressed, same width (no reflow), NOT a fill. */
.iso-mode {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

.iso-mode-btn {
  min-width: 44px;
  min-height: 44px;
  background-color: var(--ui-surface);
  border: 2px solid var(--ui-surface-container-high);
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.5;
  padding: 0 16px;
  cursor: pointer;
}

.iso-mode-btn[aria-pressed='true'] {
  border-color: var(--ui-on-surface);
}

/* Legend caption (Label 12/400): states the "denser = closer" opacity meaning
   in words, defeating the colour-only-meaning trap (D-12). */
.iso-legend {
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  margin-top: 8px;
}

/* Isochrone-failed banner (state 6): its OWN marker class on the shared
   honest-failure banner — a distinct visible state from the per-row
   routing-failed and the geocoder-down banner (D-18). All colour inherited from
   .data-error-banner; no new hue introduced. */
.isochrone-error-banner {
  /* intentionally empty — all styling inherited from .data-error-banner */
}

/* Snap-distance disclosure (P8 SNAP-02, 08-CONTEXT §P8-D02/D03): the honest
   "scored from a point N m away" line beneath the headline, ALWAYS shown when
   the origin snapped, scaled by classifySnap. The WORDS are identical; only the
   styling escalates — quiet under ~50 m is a neutral muted note, caution from
   ~50–150 m is visibly escalated. Both read in dark mode
   (the muted note reads --ui-on-surface-muted, the caution reads --ui-destructive —
   both re-point under body.dark-mode, same treatment, no special-casing). */
.snap-line {
  margin-top: 4px;
}

/* Quiet: a muted neutral note (--ui-on-surface-muted: a low-emphasis
   neutral that re-points light/dark). Never alarming — a real dwelling snaps here. */
.snap-line.snap-quiet {
  color: var(--ui-on-surface-muted);
}

/* Caution: visibly escalated (the same #b00020 warn tone the out-of-bounds /
   confirm affordances use, plus bold weight) so a large-but-still-valid snap is
   impossible to miss. Reads --ui-destructive, which re-points in dark, like .hint-warn. */
.snap-line.snap-caution {
  color: var(--ui-destructive);
  font-weight: 700;
}

/* ORIGIN_UNREACHABLE banner (SNAP-03): the honest "can't place you here" state —
   its OWN marker class on the shared honest-failure banner, distinct from the
   per-row / isochrone / geocoder banners (D-18). All colour inherited from
   .data-error-banner; no new hue introduced. line-height eases the longer
   recovery-hint copy. */
.origin-unreachable-banner {
  line-height: 1.4;
}

/* ── "Eochair / Key" landcover legend (LAND-01, Plan 12-04) ──────────────────
   A bottom-left collapsible L.Control built by scripts/legend.js. It reuses the
   app's 8px rhythm + existing button conventions (no new design system) and the
   16px swatch / 44px touch / 24px group-break tokens from the UI-SPEC. The
   control reads --ui-* tokens like the rest of the chrome, so under body.dark-mode
   it re-points to real dark colour (no filter, panel.js precedent) — the swatch
   hexes are data colours and stay as-is. */
/* D-08 (260630-lbo): the Key collapsed reads as the +/− zoom PILL and opens
   IN PLACE — the toggle pill is the positioning anchor and stays pinned; the
   expanded body FLOATS above it (bottom-left control, so the body opens upward).
   This is the "no button-shift on open" fix: the pill never moves when the body
   appears/disappears. The container itself is now layout-transparent. */
.dion-key {
  font-family: Arial, sans-serif;
  position: relative;
  color: var(--ui-on-surface);
  max-width: 260px;
}

/* Collapsed pill = the zoom-control tokens (radius-pill, surface-container-high,
   elev-soft). inline-flex so the pill hugs its label rather than stretching. */
.dion-key__toggle {
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* WCAG touch-target exception (UI-SPEC) */
  padding: var(--ui-space-sm, 8px) var(--ui-space-md, 16px);
  border: none;
  border-radius: var(--ui-radius-pill);
  background: var(--ui-surface-container-high);
  color: var(--ui-on-surface);
  box-shadow: var(--ui-elev-soft);
  font-size: 14px;
  font-weight: 700;
  line-height: 1.2;
  text-align: left;
  cursor: pointer;
}

.dion-key__toggle:hover,
.dion-key__toggle:focus-visible {
  background-color: var(--ui-primary-container);
}

/* Collapsed: hide the expanded body; the toggle pill stays visible + pinned. */
.dion-key--collapsed .dion-key__body {
  display: none;
}

/* Expanded body = a floating panel ABOVE the pinned pill (bottom-left control), so
   opening it never shifts the pill. Height-capped + scrolls inside so it never
   overflows the viewport. */
.dion-key__body {
  position: absolute;
  bottom: calc(100% + var(--ui-space-sm, 8px));
  left: 0;
  width: 260px;
  padding: 8px 16px 16px;
  border-radius: var(--ui-radius-lg);
  background: var(--ui-surface);
  color: var(--ui-on-surface);
  box-shadow: var(--ui-elev-soft);
  /* Bound the expanded Key to the viewport so a tall list (all land-cover
     classes + the Meaning group) scrolls INSIDE the control instead of growing
     past the top edge and clipping its own header rows. */
  max-height: calc(100vh - 140px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.dion-key__heading {
  font-size: 12px;
  font-weight: 700;
  line-height: 1.2;
  margin: 8px 0 4px;
}

/* 24px section break between the landcover group and the meaning group. */
.dion-key__group--meaning {
  margin-top: 24px;
}

/* A legend row — swatch column + bilingual label. Interactive rows are buttons
   (44px touch height); read-only rows are divs at the natural 8px rhythm. */
.dion-key__row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  width: 100%;
  font-size: 14px;
  line-height: 1.4;
  text-align: left;
}

.dion-key__row--toggle {
  min-height: 44px; /* touch target for the show-hide rows */
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.dion-key__row--toggle:hover,
.dion-key__row--toggle:focus-visible {
  background-color: var(--ui-surface-container-high);
}

/* aria-pressed=false (hidden class) reads quieter so the on/off state is visible
   without relying on colour alone. */
.dion-key__row--toggle[aria-pressed='false'] {
  opacity: 0.55;
}

/* Disabled (landcover-unavailable) rows: honest, inert, never a fake toggle. */
.dion-key__row--disabled,
.dion-key__row--toggle:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.dion-key__swatch {
  position: relative;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border-radius: 2px;
  border: 1px solid rgba(0, 0, 0, 0.2);
  overflow: hidden;
}

/* Icon-over-fill (ICON-03, Workstream C / Phase 14): the class's sprite SVG is laid
   over the swatch's fill background-color so the Key reads exactly like the painted
   map (replacing the scrapped Canvas texture channel). Centered + contained inside
   the 16×16 swatch. aria-hidden so SR users hear the row label, not the glyph. */
.dion-key__swatch-icon {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}

/* Emoji POI key: no fill, the glyph carries the meaning. */
.dion-key__swatch--emoji {
  border: none;
  font-size: 14px;
  line-height: 16px;
  text-align: center;
}

/* Boundary key: a dashed line across the swatch (no fill), matching the painted
   admin border. The fine variant is thinner/fainter for townland/parish lines. */
.dion-key__swatch--line {
  border: none;
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
}
.dion-key__line {
  width: 100%;
  border-top: 2px dashed #9c8ba6;
}
.dion-key__swatch--line-fine .dion-key__line {
  border-top-width: 1px;
  opacity: 0.6;
}

.dion-key__label {
  flex: 1 1 auto;
}

/* ── Info-only UI (UAT: cut all other actions until requested) ──────────────────
   Click→place-info is the sole interaction, so the travel + scoring slots are hidden
   (reversible — no markup/engine removed, restore by deleting this rule). The big Info
   toggle itself was removed from the markup (info-only is permanent). The ⚙️ Settings
   slot is KEPT visible so language (GA/EN), dark theme, and About stay reachable. */
.dion-actionbar__slot--travel,
.dion-actionbar__slot--scoring { display: none !important; }

/* NOTE: the Settings-gear reposition (top-right, transparent) lives in components.css —
   it loads AFTER this file (cascade order is load-bearing), so the override must sit there
   to beat components.css's base .dion-actionbar shelf rule. */

/* ── Feature highlight (UAT: no doubt WHICH feature the info card describes) ─────────
   Point (a POI): the accent ring pulses (the icon "lights up"). Polygon (building/area):
   the accent border marches (stroke-dashoffset). Both calmed under reduced-motion. */
.dion-highlight-ring { animation: dion-hl-pulse 1.4s ease-in-out infinite; }
@keyframes dion-hl-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
.dion-highlight-shape { animation: dion-hl-march 0.9s linear infinite; }
@keyframes dion-hl-march { to { stroke-dashoffset: -24; } }
@media (prefers-reduced-motion: reduce) {
  .dion-highlight-ring, .dion-highlight-shape { animation: none; }
}

/* ── Live enrichment block in the info card (weather/warnings/biodiversity/recreation)
   Token-based + dark-mode-aware (the Phase-22 no-invert lesson): each source is a soft
   rounded block with an uppercase key over its value; small facts read as green pills. */
.place-info-enrich {
  margin-top: var(--ui-space-md, 12px);
  padding-top: var(--ui-space-md, 12px);
  border-top: 1px solid var(--ui-surface-container-high, rgba(0, 0, 0, 0.08));
  display: flex;
  flex-direction: column;
  gap: var(--ui-space-sm, 8px);
}
.place-info-enrich-loading {
  margin: 0;
  color: var(--ui-on-surface-muted, #888);
  font-style: italic;
}
.place-info-enrich-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: var(--ui-space-sm, 8px) var(--ui-space-md, 10px);
  border-radius: var(--ui-radius-md, 10px);
  background: var(--ui-surface-container-high, rgba(0, 0, 0, 0.04));
  font-size: 0.92em;
}
.place-info-enrich-k {
  font-size: 0.78em;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ui-on-surface-muted, #888);
}
.place-info-enrich-v {
  color: var(--ui-on-surface, inherit);
  line-height: 1.45;
}
/* Each biodiversity group on its own line (icon + bold localized name + count + species). */
.place-info-bio-line {
  display: block;
  margin-top: 2px;
}
.place-info-bio-line:first-child { margin-top: 0; }
/* Recreation suitability + other small facts → green pills (never amber: facts, not a score). */
.place-info-chip {
  display: inline-block;
  margin: 2px 4px 2px 0;
  padding: 2px 9px;
  border-radius: var(--ui-radius-pill, 999px);
  background: var(--ui-primary-container, rgba(0, 0, 0, 0.06));
  color: var(--ui-primary, inherit);
  font-size: 0.86em;
  font-weight: 600;
  white-space: nowrap;
}
/* Severe-weather warnings — an amber-tinted alert block (translucent so it reads on
   light AND dark); the Yellow/Orange/Red level pills stay distinct. */
.place-info-enrich-warn { background: rgba(210, 105, 30, 0.13); }
.place-info-warn { font-weight: 800; margin-right: 4px; }
.place-info-warn-yellow { color: #b8860b; }
.place-info-warn-orange { color: #d2691e; }
.place-info-warn-red { color: #b00020; }
/* All-tags detail (Castas) — divider-separated key/value rows. */
.place-info-tags {
  margin-top: var(--ui-space-md, 12px);
  padding-top: var(--ui-space-sm, 8px);
  border-top: 1px solid var(--ui-surface-container-high, rgba(0, 0, 0, 0.08));
}
.place-info-tags-hdr {
  margin: 0 0 6px;
  font-size: 0.78em;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ui-on-surface-muted, #888);
}
.place-info-tag {
  display: flex;
  gap: 8px;
  padding: 3px 0;
  font-size: 0.85em;
  border-top: 1px solid var(--ui-surface-container-high, rgba(0, 0, 0, 0.05));
}
.place-info-tag:first-of-type { border-top: none; }
.place-info-tag .place-info-label { flex: 0 0 42%; color: var(--ui-on-surface-muted, #888); }
