/* Bivouac spots viewer — chrome over a full-bleed MapLibre map.
 *
 * Layout rule of thumb: the map owns the whole viewport and every control floats
 * above it, so the only thing keeping surfaces apart is the --z-* scale below.
 * Anything added here needs a slot in it.
 */

:root {
  --surface: #fff;
  --surface-2: #f1f3f5;
  --surface-scrim: rgba(255, 255, 255, .96);
  --text: #1c1c1e;
  --text-dim: #5c5f66;          /* 4.9:1 on --surface; #777 was 4.48:1 and failed AA */
  --line: #d8dade;
  --accent: #1a6ebd;
  --danger: #b00;
  --info-scrim: rgba(0, 0, 0, .45);
  --shadow: 0 2px 10px rgba(0, 0, 0, .25);
  --shadow-lg: 0 -4px 24px rgba(0, 0, 0, .28);

  --r-sm: 3px; --r-md: 6px; --r-lg: 10px; --r-xl: 16px;
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;

  --tap: 44px;                  /* WCAG 2.5.5 / Apple HIG minimum target */
  --panel-w: 320px;
  --search-h: 40px;             /* set explicitly so #panel can derive its offset */

  /* Sheet detents, compact viewports only. */
  /* bottom-left chrome stacks in one column; each row is its own height + gap */
  --attrib-row: 32px;
  --scale-row: 30px;

  --peek: 104px;
  --sheet-gap: 56px;            /* map left visible above a fully raised sheet */

  /* #search itself takes no slot: it stays out of the stacking order so a popup
     can cover the search box, while the open result list still tops everything. */
  /* the info page covers everything, including the sheet it can be opened from */
  --z-panel: 10; --z-popup: 20; --z-sheet: 30; --z-search-list: 40; --z-info: 50;

  /* Legend/overlay colours. CSS is the single source: app.js reads these back
     through cssColor(), so a swatch can no longer drift from its map layer. */
  --c-wrz: #4a90d9;
  --c-sturz: #e07b39;
  --c-hangmure: #8b5a2b;
  --c-lawinen: #9b59b6;
  --c-rules: #888888;
  --c-slope30: #dd6644;
  --c-wanderwege: #d9534f;
  --c-sperrungen: #f0ad4e;
  /* a blue-leaning emerald: the heatmap bands underneath are yellow-greens, so a
     warmer green would sink into them */
  --c-spots: #00875a;
  --c-banned: #c0392b;
  --c-restricted: #e67e22;
  --c-tolerated-above-treeline: #27ae60;
  --c-unknown: #888888;

  font-family: -apple-system, system-ui, sans-serif;
  color: var(--text);
}

html, body { margin: 0; height: 100%; }
/* deliberately no z-index: any value here would make #map a stacking context and
   trap the popups inside it, back underneath #panel */
#map { position: absolute; inset: 0; }

/* ---------- shared primitives ---------- */

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--tap); min-height: var(--tap);
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--text-dim); font: inherit; font-size: 20px; line-height: 1;
  border-radius: var(--r-md);
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); }

/* The app defined no focus style at all; this is the first one. */
:where(a, button, summary, input, select):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.swatch { width: 12px; height: 12px; border-radius: var(--r-sm); flex: none; display: inline-block; }
.swatch-wrz { background: var(--c-wrz); }
.swatch-sturz { background: var(--c-sturz); }
.swatch-hangmure { background: var(--c-hangmure); }
.swatch-lawinen { background: var(--c-lawinen); }
.swatch-rules { background: var(--c-rules); }
.swatch-slope30 { background: var(--c-slope30); opacity: .6; }
.swatch-wanderwege { background: var(--c-wanderwege); }
.swatch-sperrungen { background: var(--c-sperrungen); }

/* ---------- search ---------- */

/* No z-index here on purpose: it would make this a stacking context and trap
   #search-results inside it, forcing the box and the list to share one slot.
   Unpositioned in the stack, the input paints above the map by DOM order and
   below a popup, and the list can claim its own slot above one. */
#search {
  position: absolute; top: var(--sp-3); left: var(--sp-3);
  width: var(--panel-w);
}
#search input {
  width: 100%; height: var(--search-h); box-sizing: border-box;
  padding: 0 var(--sp-3); font: inherit; font-size: 13px;
  color: var(--text); border: none; border-radius: var(--r-lg);
  background: var(--surface-scrim); box-shadow: var(--shadow);
}
#search-results {
  position: absolute; z-index: var(--z-search-list);
  top: calc(100% + var(--sp-1)); left: 0; right: 0;
  margin: 0; padding: var(--sp-1) 0; list-style: none;
  max-height: min(300px, 50dvh); overflow-y: auto;   /* was a flat 300px */
  background: var(--surface); border-radius: var(--r-lg); font-size: 13px;
  box-shadow: var(--shadow);
}
#search-results li { padding: var(--sp-2) var(--sp-3); cursor: pointer; }
#search-results li[aria-selected="true"], #search-results li:hover { background: var(--surface-2); }
#search-results .name { font-weight: 600; display: flex; align-items: center; gap: 7px; }
/* emoji for these place types is missing from the default font on some systems,
   so the icons are drawn instead */
#search-results .ico { width: 15px; height: 15px; flex: none; }
#search-results .meta { color: var(--text-dim); font-size: 11.5px; padding-left: 22px; }
/* a lake can touch dozens of communes — keep every row one line tall */
#search-results .name span, #search-results .meta {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#search-results .oob { color: var(--danger); }
#search-results .quiet { color: var(--text-dim); cursor: default; }
#search-results .quiet:hover { background: none; }

/* ---------- desktop panel ---------- */

#panel {
  position: absolute; z-index: var(--z-panel);
  top: calc(var(--sp-3) + var(--search-h) + var(--sp-2)); left: var(--sp-3);
  width: var(--panel-w); box-sizing: border-box;
  background: var(--surface-scrim); border-radius: var(--r-lg);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow); font-size: 13px;
  max-height: calc(100dvh - var(--search-h) - 64px);
  overflow-y: auto;
}
#panel h1 { font-size: 15px; margin: 0; flex: 1; }
#lang-select {
  font: inherit; font-size: 11.5px; padding: var(--sp-1) var(--sp-2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); cursor: pointer; min-height: 28px;
}
/* the whole panel collapses to just its title bar — on a phone it otherwise
   covers most of the map */
#panel summary {
  list-style: none; cursor: pointer; display: flex;
  align-items: center; justify-content: space-between; gap: var(--sp-2);
  min-height: 32px;
}
#panel summary::-webkit-details-marker { display: none; }
/* Drawn from borders rather than set as \25BE: the glyph rendered at barely
   6px of ink and its size varies with whatever font the system falls back to. */
#panel .caret { min-width: 34px; min-height: 34px; }
#panel .caret::after {
  content: ""; width: 9px; height: 9px; margin-top: -4px;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  rotate: 45deg;
}
#panel details:not([open]) .caret::after { rotate: -45deg; margin: 0 0 0 -4px; }
#panel details[open] summary { margin-bottom: var(--sp-2); }

.group { margin: var(--sp-3) 0 var(--sp-1); font-weight: 600; color: var(--text); }
.group:first-child { margin-top: 0; }
label.check {
  display: flex; align-items: center; gap: var(--sp-2);
  margin: 2px 0; padding: var(--sp-1) 0; cursor: pointer; min-height: 28px;
}
label.check input[type=checkbox] { width: 17px; height: 17px; flex: none; accent-color: var(--accent); }

/* Label, slider and read-out on one flex row made every slider a different width
   and wrapped long translations onto three lines. A two-row grid fixes both. */
label.slider {
  display: grid; grid-template-columns: 1fr auto;
  gap: 2px var(--sp-2); align-items: baseline; margin: var(--sp-2) 0;
}
label.slider > .lbl { grid-column: 1; grid-row: 1; }
/* the read-out shares the label's row rather than the slider's: parked beside
   the slider it made every track a different width, since "any" and "beliebig"
   are not the same size */
label.slider .val {
  grid-column: 2; grid-row: 1; text-align: right;
  color: var(--text-dim); font-variant-numeric: tabular-nums;
}
label.slider input[type=range] { grid-column: 1 / -1; grid-row: 2; width: 100%; margin: 0; }
input[type=range] { accent-color: var(--accent); }

#info-btn {
  margin-top: var(--sp-3); width: 100%; min-height: 34px;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--r-md);
  font: inherit; cursor: pointer;
}
#info-btn:hover { border-color: var(--text-dim); }

#disclaimer {
  margin-top: var(--sp-3); padding-top: var(--sp-2);
  border-top: 1px solid var(--line); color: var(--text-dim); font-size: 11.5px;
}

/* ---------- popups (desktop) ---------- */

/* Popups live inside .maplibregl-map at z-index auto, so #panel painted straight
   over them. This clears the panel and the search box both; only an open search
   result list still sits above. */
.maplibregl-popup { z-index: var(--z-popup); }
.maplibregl-popup-content {
  max-width: min(300px, calc(100vw - 2 * var(--sp-4)));
  padding: var(--sp-3);
  padding-right: var(--tap);            /* keep text clear of the close button */
  border-radius: var(--r-lg);
  background: var(--surface); color: var(--text);
  box-shadow: var(--shadow);
}
.maplibregl-popup-close-button {
  width: var(--tap); height: var(--tap);   /* was 19.8 x 17.5 */
  top: 0; right: 0;
  font-size: 20px; line-height: 1; color: var(--text-dim);
  border-radius: 0 var(--r-lg) 0 var(--r-md);
}
.maplibregl-popup-close-button:hover { background: var(--surface-2); color: var(--text); }
.maplibregl-popup-anchor-top .maplibregl-popup-tip { border-bottom-color: var(--surface); }
.maplibregl-popup-anchor-bottom .maplibregl-popup-tip { border-top-color: var(--surface); }
.maplibregl-popup-anchor-left .maplibregl-popup-tip { border-right-color: var(--surface); }
.maplibregl-popup-anchor-right .maplibregl-popup-tip { border-left-color: var(--surface); }

/* .pop is styled outside .maplibregl-popup-content on purpose: the same markup
   is rendered into the bottom sheet on compact viewports. */
.pop { font-size: 12.5px; }
.pop h2 { font-size: 13.5px; margin: 0 0 var(--sp-1); }
.pop table { border-collapse: collapse; width: 100%; }
.pop td { padding: 1px var(--sp-1) 1px 0; vertical-align: top; }
.bar { background: var(--surface-2); border-radius: var(--r-sm); height: 7px; width: 90px; display: inline-block; }
.bar i { display: block; height: 100%; border-radius: var(--r-sm); background: #2e8b57; }
.warn { color: var(--danger); }
.pop .links {
  margin-top: 7px; padding-top: var(--sp-2); border-top: 1px solid var(--line);
  /* one per row: the labels are of very uneven width, so a wrapping row left a
     ragged orphan on its own line anyway */
  display: flex; flex-direction: column; align-items: start;
}
.pop .links a {
  color: var(--accent); text-decoration: none; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 6px; min-height: 32px;
}
.pop .links a:hover { text-decoration: underline; }
.pop .links .ico { width: 15px; height: 15px; flex: none; }

/* ---------- MapLibre controls ---------- */

.maplibregl-ctrl-group button {
  width: var(--tap); height: var(--tap);    /* MapLibre ships 29 x 29 */
}
.maplibregl-ctrl-group { border-radius: var(--r-lg); box-shadow: var(--shadow); }
.maplibregl-ctrl-group button:first-child { border-radius: var(--r-lg) var(--r-lg) 0 0; }
.maplibregl-ctrl-group button:last-child { border-radius: 0 0 var(--r-lg) var(--r-lg); }
.maplibregl-ctrl-group button:only-child { border-radius: var(--r-lg); }
/* the icons are 20px sprites in a 29px box; keep them that size in a 44px one */
.maplibregl-ctrl-group button .maplibregl-ctrl-icon { background-size: 20px 20px; }

/* The stock compass is two detached triangles that read as a vague smudge at
   20px. A ringed needle survives the size and still says which way north is
   once MapLibre rotates it with the bearing. Both colours are chosen to hold
   on the light and the dark control face alike, so this icon opts out of the
   dark-mode inversion further down rather than needing a second copy. */
.maplibregl-ctrl-group button.maplibregl-ctrl-compass .maplibregl-ctrl-icon {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 29 29'%3E%3Ccircle cx='14.5' cy='14.5' r='10.6' fill='none' stroke='%239aa0a8' stroke-width='1.5' opacity='.45'/%3E%3Cpath d='M14.5 4.2 17.3 14.6 11.7 14.6Z' fill='%23e0483f'/%3E%3Cpath d='M14.5 24.8 11.7 14.4 17.3 14.4Z' fill='%239aa0a8'/%3E%3C/svg%3E");
}

/* MapLibre gives each control a 10px margin off a flush-to-the-edge container,
   which left the scale at 10px and the sheet's own chrome at 12px — close enough
   to read as a mistake. Everything is placed off the same --sp-3 inset instead,
   stacked upward in one column so nothing can collide at narrow widths. */
.maplibregl-ctrl-bottom-left .maplibregl-ctrl,
.maplibregl-ctrl-bottom-right .maplibregl-ctrl { margin: 0; }
.maplibregl-ctrl-bottom-right {
  right: var(--sp-3);
  bottom: calc(var(--sp-3) + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: flex-end;
  gap: var(--sp-2);
}
/* the scale sits directly above the attribution; order, not DOM position, so a
   language switch re-adding the attribution control can't reshuffle them */
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-scale { order: -1; }

/* !important for the same cross-origin reason as the dark-mode block below:
   MapLibre's .maplibregl-compact rule is more specific and cannot be edited. */
.maplibregl-ctrl-attrib { border-radius: var(--r-md) !important; }
.maplibregl-ctrl-scale {
  box-sizing: border-box;
  background: var(--surface-scrim); color: var(--text);
  border-color: var(--text-dim); border-radius: var(--r-md);
  box-shadow: var(--shadow);
  padding: 1px var(--sp-2) 2px; font-size: 11px;
}

/* ---------- info page ---------- */

/* Full-viewport reading surface over the map, on every viewport: the text is
   long enough that a column beside a live map would be worse than covering it. */
#info {
  position: fixed; inset: 0; z-index: var(--z-info);
  display: flex; align-items: center; justify-content: center;
  background: var(--info-scrim);
  padding: max(var(--sp-4), env(safe-area-inset-top)) var(--sp-3)
           calc(var(--sp-4) + env(safe-area-inset-bottom));
}
#info[hidden] { display: none; }

/* The card itself never scrolls — it is sized to the viewport and only its body
   moves, so the title and the close button stay where they were put. */
#info-panel {
  display: flex; flex-direction: column;
  width: 44rem; max-width: 100%; max-height: 100%; box-sizing: border-box;
  background: var(--surface); color: var(--text);
  border-radius: var(--r-xl); box-shadow: var(--shadow);
  padding: var(--sp-4);
}

#info-head {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  flex: none; padding-bottom: var(--sp-2);
}
#info-head h2 { font-size: 19px; margin: 0; flex: 1; align-self: center; }

#info-body {
  flex: 1; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  font-size: 13.5px; line-height: 1.55;
}
#info-body section { margin-top: var(--sp-4); }
#info-body h3 {
  font-size: 15px; margin: 0 0 var(--sp-2);
  padding-top: var(--sp-3); border-top: 1px solid var(--line);
}
#info-body p { margin: 0 0 var(--sp-2); }
/* the closing note after a definition list needs the gap the list doesn't give */
#info-body .info-list + p { margin-top: var(--sp-3); }
#info-body .info-intro { margin-top: var(--sp-2); color: var(--text-dim); }
#info-body .info-fresh {
  margin-top: var(--sp-3); color: var(--text-dim); font-size: 12.5px;
}

.info-list { margin: 0; }
.info-list dt { font-weight: 600; margin-top: var(--sp-2); }
.info-list dt:first-child { margin-top: 0; }
.info-list dd { margin: 0; color: var(--text-dim); }
.info-list .weight { font-weight: 400; color: var(--text-dim); }

/* ---------- bottom sheet (compact only) ---------- */

#sheet, #filters-btn { display: none; }

@media (max-width: 39.99em), (max-height: 27.5em) {
  :root { --search-h: var(--tap); }

  #search { right: var(--sp-3); width: auto; }
  /* below 16px iOS Safari zooms the whole page when the field takes focus */
  #search input { font-size: 16px; }
  #search-results { font-size: 15px; }
  #search-results li { padding: var(--sp-3); }

  /* the search box spans the full width here, so the zoom/compass/geolocate
     stack has to start below it instead of underneath it */
  .maplibregl-ctrl-top-right { top: calc(var(--search-h) + var(--sp-2)); }

  #panel { display: none; }
  .maplibregl-popup { display: none; }   /* details render in the sheet instead */

  body.sheet-open #filters-btn { display: none; }
  #filters-btn {
    position: absolute; z-index: var(--z-panel);
    left: 50%; translate: -50% 0;
    bottom: calc(var(--sp-3) + var(--attrib-row) + var(--scale-row) + env(safe-area-inset-bottom));
    display: inline-flex; align-items: center; gap: var(--sp-2);
    min-height: var(--tap); padding: 0 var(--sp-4);
    border: 0; border-radius: var(--tap); cursor: pointer;
    background: var(--surface); color: var(--text);
    font: inherit; font-size: 15px; font-weight: 600;
    box-shadow: var(--shadow);
  }

  #sheet.open {
    display: block;
    position: fixed; inset: 0; z-index: var(--z-sheet);
    overflow-y: scroll; scroll-snap-type: y mandatory; scroll-behavior: smooth;
    overscroll-behavior: contain;
    pointer-events: none;             /* the map stays draggable above the sheet */
    scrollbar-width: none;
  }
  #sheet::-webkit-scrollbar { display: none; }
  /* Two spacers, not one: their boundaries are the peek and half detents.
     A -> snap at scrollTop 0 (peek), B -> snap at 45dvh - peek (half),
     the panel's own `end` alignment gives the full detent. */
  #sheet .sheet-space { scroll-snap-align: start; }
  #sheet .sheet-space:nth-of-type(1) { height: calc(45dvh - var(--peek)); }
  #sheet .sheet-space:nth-of-type(2) { height: 55dvh; }

  /* A fixed height, not min-height: the three snap offsets are only predictable
     if the panel box doesn't grow with its content, and a panel taller than the
     viewport would strand the middle of a long filter list between two
     mandatory snap points. Overflow scrolls inside #sheet-body instead. */
  #sheet-panel {
    pointer-events: auto; scroll-snap-align: end;
    height: calc(100dvh - var(--sheet-gap));
    display: flex; flex-direction: column;
    box-sizing: border-box;
    background: var(--surface); color: var(--text);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: var(--shadow-lg);
    padding: 0 var(--sp-4);
    font-size: 15px;
  }
  #sheet-head {
    position: relative; flex: none;
    display: grid; grid-template-columns: 1fr auto auto; align-items: center;
    gap: var(--sp-2); padding: var(--sp-2) 0;
    background: var(--surface); border-bottom: 1px solid var(--line);
  }
  /* the drag handle, centred over the header row rather than taking a row of its own */
  #sheet-head::before {
    content: ""; position: absolute; top: 6px; left: 50%; translate: -50% 0;
    width: 40px; height: 4px; border-radius: 2px; background: var(--line);
  }
  #sheet-title { font-size: 16px; margin: var(--sp-3) 0 0; }
  #sheet-head #lang-select { font-size: 14px; margin-top: var(--sp-3); }
  #sheet-head .icon-btn { margin-top: var(--sp-3); }
  #sheet-body {
    flex: 1; overflow-y: auto; overscroll-behavior: contain;
    padding: var(--sp-3) 0 calc(var(--sp-4) + env(safe-area-inset-bottom));
  }
  #sheet-body .pop { font-size: 15px; }
  #sheet-body .pop h2 { display: none; }   /* the sheet header already carries it */
  #sheet-body .bar { width: 100%; height: 9px; }
  #sheet-body .pop td:first-child { width: 45%; }
  /* the row stacking comes from .pop .links; here each one also spans the sheet
     so the tap target is the full width, not just the label */
  #sheet-body .pop .links a { min-height: var(--tap); align-self: stretch; }
  #sheet-body label.check { min-height: var(--tap); margin: 0; }
  #sheet-body label.check input[type=checkbox] { width: 21px; height: 21px; }
  #sheet-body label.slider { margin: var(--sp-3) 0; }
  #sheet-body input[type=range] { min-height: 32px; }
  #sheet-body #info-btn { min-height: var(--tap); }

  #info { padding: 0; background: var(--surface); }
  #info-panel {
    height: 100%; border-radius: 0; box-shadow: none;
    padding: max(var(--sp-3), env(safe-area-inset-top)) var(--sp-3)
             calc(var(--sp-4) + env(safe-area-inset-bottom));
  }
}

@media (prefers-reduced-motion: reduce) {
  #sheet { scroll-behavior: auto; }
}

/* ---------- dark ---------- */

/* The swisstopo raster basemap stays light in dark mode, so the chrome is kept
   fully opaque with a heavier shadow — translucent dark panels over a light map
   read as muddy grey. */
@media (prefers-color-scheme: dark) {
  :root {
    --surface: #1e2024;
    --surface-2: #2b2f35;
    --surface-scrim: #1e2024;
    --text: #eceef1;
    --text-dim: #a7acb4;
    --line: #3a3e45;
    --accent: #6cb2f0;
    --danger: #ff6b6b;
    --shadow: 0 2px 12px rgba(0, 0, 0, .55);
    --shadow-lg: 0 -4px 26px rgba(0, 0, 0, .6);
    --info-scrim: rgba(0, 0, 0, .65);
    color-scheme: dark;
  }
  .maplibregl-ctrl-group { background: var(--surface); }
  .maplibregl-ctrl-group button + button { border-top-color: var(--line); }
  /* the compass carries its own theme-neutral colours; inverting would turn its
     red needle cyan */
  .maplibregl-ctrl-group button:not(.maplibregl-ctrl-compass) .maplibregl-ctrl-icon {
    filter: invert(1) hue-rotate(180deg);
  }
  /* MapLibre sets a dark colour on the attribution itself, and its rule is more
     specific than anything reasonable here. The stylesheet is served from unpkg,
     so it cannot be inspected or patched from this origin — !important is the
     only lever. Without it this is black text on a near-black surface. */
  .maplibregl-ctrl-attrib,
  .maplibregl-ctrl-attrib-inner { color: var(--text) !important; }
  .maplibregl-ctrl-attrib { background: var(--surface-scrim) !important; }
  .maplibregl-ctrl-attrib a { color: var(--accent) !important; }
  /* the (i) toggle is a dark SVG sprite, invisible against the dark surface */
  .maplibregl-ctrl-attrib-button {
    background-color: rgba(255, 255, 255, .12) !important;
    filter: invert(1) hue-rotate(180deg);
  }
  .bar i { background: #3fae74; }
}
