/* POI Map — screen styles.
   Every colour, font and radius here comes from the Organic tokens in
   ds/styles.css. The raw numbers left are geometry: the breakpoint, and the
   marker sizes. How long a metre is on screen arrives as `--m`, which
   basemap.js writes on `.map`.

   The map is the page — the finder, the results and the map's own controls
   float on top of it. It fills the window above the results on a phone, and
   the whole window once there is room for the results beside it:

     under 900px          900px and up
     ┌─────────────┐      ┌─────────────────────────┐
     │  [Find ▾ ]  │      │ [Find ▾ ]               │
     │             │      │ ┌────────┐    map       │
     │   map ● you │      │ │ 1 ...  │  ● you       │
     ├─────────────┤      │ │ 2 ...  │              │
     │ ▁ Nearest   │      │ │ scroll │          [◎] │
     └─────────────┘      │ └────────┘              │
                          └─────────────────────────┘ */

/* ─────────────────────────────────────────────────────────────
   Motion
   ───────────────────────────────────────────────────────────── */
@keyframes poiPop {
  0%   { transform: translate(-50%, -100%) scale(0); }
  70%  { transform: translate(-50%, -100%) scale(1.12); }
  100% { transform: translate(-50%, -100%) scale(1); }
}
@keyframes sheetUp {
  from { transform: translateY(48px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}
@keyframes dropIn {
  from { transform: translateY(-8px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}

/* ─────────────────────────────────────────────────────────────
   Page
   ───────────────────────────────────────────────────────────── */
html, body {
  margin: 0;
  height: 100%;
  /* the palette is a light one and stays light: without this, Chrome's
     automatic dark mode re-tints the whole map to mud */
  color-scheme: light;
}

/* ─────────────────────────────────────────────────────────────
   Screen — the layout grid
   ───────────────────────────────────────────────────────────── */
.screen {
  display: grid;
  /* minmax(0, …) throughout: long POI names are min-content-wide, and a plain
     `auto` track would let them push the layout past the window */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 1fr auto;
  min-height: 100dvh;
  background: var(--color-surface);
  font-family: var(--font-body);
}

/* The map takes the first row, the row everything but the results is placed in
   — so where you are is where the map opens. It still keeps clear of the
   sheet: what the sheet covers can now be dragged back into view, but a corner
   of the map you have to pan to reach is a corner nobody finds.

   How long a metre is here arrives as `--m` from basemap.js: the 1500 m search
   radius spans 42% of the pane's shorter side until the map is zoomed.
   Tiles and markers read the same number, so they cannot drift apart. Wherever
   a tile fails to load, `.screen`'s surface shows through — a blocked tile
   server degrades to sand rather than to void.

   The pane is dragged and pinched, so the browser must not claim either gesture
   for scrolling or for zooming the page first: `touch-action: none` is what
   makes a touch reach the pointer handlers at all, and basemap.js gives back
   the pinch it takes away. A mouse drag, meanwhile, would otherwise sweep a
   text selection across the results as it went — the pane holds no text of its
   own, so the browser anchors one in the nearest thing that does. */
/* A tile's own position is a percentage of .map's box (`50% - north * --m`),
   so anything that resizes that box moves every tile with it. Sharing a grid
   row with the results card or the "search this area" pill — both sized by
   their own content, which changes with the catalogue — would otherwise
   drag .map's height along; pinning it to the viewport keeps it constant
   regardless of what its neighbours are doing. */
.map {
  grid-area: 1 / 1;
  position: sticky;
  top: 0;
  height: 100dvh;
  min-width: 0;
  overflow: hidden;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  cursor: grab;
}
.map:active { cursor: grabbing; }

/* Everything standing on the ground — tiles, ring, dot and markers — rides in
   here, and the pan is this one translate of the view offset basemap.js writes.
   Positions inside are still metres from the search centre, which is why the
   offset is subtracted going east and added going north: moving the view east
   walks the ground left. One transform on one element, so nothing in it can
   drift against anything else. */
.map-view {
  position: absolute;
  inset: 0;
  /* the fallbacks hold for the one frame between the pane existing and
     basemap.js writing to it: no pan yet, so no shift */
  transform: translate(
    calc(var(--view-east, 0) * var(--m) * -1),
    calc(var(--view-north, 0) * var(--m))
  );
}

/* OpenStreetMap's tiles. A tile is a square of ground, so it is placed the way
   a marker is — metres east and north of the centre — and sized in metres too. */
.basemap {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.basemap__tile {
  position: absolute;
  /* --east / --north (metres from the centre) come from JS, as a marker's do */
  left: calc(50% + var(--east) * var(--m));
  top: calc(50% - var(--north) * var(--m));
  /* the extra pixel closes the hairline seams fractional positions leave
     between neighbouring tiles */
  width: calc(var(--tile-m) * var(--m) + 1px);
  height: calc(var(--tile-m) * var(--m) + 1px);
}

/* Loading, or nothing to show — the map waits until it has real POIs on it */
.screen--status {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
}
.status {
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: center;
}
.status__title {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 22px;
  color: var(--color-text);
}
.status__detail {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--color-neutral-700);
}

/* ─────────────────────────────────────────────────────────────
   The search centre
   ───────────────────────────────────────────────────────────── */
/* Where the current search is centred. Fixed at the middle of the pane —
   unlike a `.poi` marker, it does not live in the panned `.map-view`, so it
   never moves off the point "search this area" will actually search. */
.center-pin {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -100%);
  width: 40px;
  height: 48px;
  pointer-events: none;
}
.center-pin__pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin: 0 2px;
  border-radius: 999px 999px 999px 6px;
  transform: rotate(-45deg);
  background: var(--color-pin);
  border: 2.5px solid var(--color-neutral-100);
  box-shadow: var(--shadow-md);
}
.center-pin__pin svg {
  transform: rotate(45deg);
  stroke: #fff;
}

/* ─────────────────────────────────────────────────────────────
   Map markers
   ───────────────────────────────────────────────────────────── */
/* the layer spans the screen so markers can be positioned in %, but only the
   markers themselves take pointer events */
.poi-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.poi {
  pointer-events: auto;
  position: absolute;
  /* --east / --north (metres from the centre) and animation-delay come from JS */
  left: calc(50% + var(--east) * var(--m));
  top: calc(50% - var(--north) * var(--m));
  transform: translate(-50%, -100%);
  width: 44px;
  height: 52px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  animation: poiPop .35s ease both;
}
.poi__pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin: 0 2px;
  border-radius: 999px 999px 999px 6px;
  transform: rotate(-45deg);
  background: var(--color-neutral-100);
  border: 2.5px solid var(--color-neutral-100);
  box-shadow: var(--shadow-md);
}
.poi__pin svg {
  transform: rotate(45deg);
  stroke: var(--color-neutral-700);
}
/* the closest POI in the category reads in the sage second voice… */
.poi[data-variant="nearest"] .poi__pin { background: var(--color-accent-2-500); }
.poi[data-variant="nearest"] .poi__pin svg { stroke: #fff; }
/* …and an explicit selection takes over in terracotta */
.poi[data-variant="selected"] .poi__pin { background: var(--color-accent-500); }
.poi[data-variant="selected"] .poi__pin svg { stroke: #fff; }

/* ─────────────────────────────────────────────────────────────
   Category finder (top dropdown)
   ───────────────────────────────────────────────────────────── */
/* the search pill, top-left over the map */
.finder {
  grid-area: 1 / 1;
  align-self: start;
  z-index: 3;
  margin: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.finder__trigger {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 54px;
  padding: 8px 16px;
  border: none;
  border-radius: 999px;
  background: var(--color-neutral-100);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  font-family: var(--font-body);
}
.finder__trigger:hover { background: var(--color-neutral-200); }
.finder__trigger:active { background: var(--color-neutral-300); }
.finder__chip {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: var(--color-accent-200);
  flex: none;
}
.finder__chip svg { stroke: var(--color-accent-800); }
.finder__labels {
  flex: 1;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.finder__kicker {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
}
.finder__value {
  font-size: 17px;
  font-weight: 700;
  color: var(--color-text);
}
.finder__chevron {
  stroke: var(--color-neutral-700);
  transition: transform .2s;
  flex: none;
}
.finder__trigger[aria-expanded="true"] .finder__chevron { transform: rotate(180deg); }

/* `display: flex` below would otherwise out-specify the UA's [hidden] rule */
.finder__menu[hidden] { display: none; }
.finder__menu {
  border-radius: var(--radius-lg);
  background: var(--color-neutral-100);
  box-shadow: var(--shadow-lg);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  animation: dropIn .18s ease both;
}
.finder__option {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  padding: 6px 10px;
  border: none;
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  font-family: var(--font-body);
  text-align: left;
}
.finder__option:hover { background: var(--color-accent-100); }
.finder__option[aria-current="true"] { background: var(--color-accent-200); }
.finder__option-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 999px;
  background: var(--color-accent-2-200);
  flex: none;
}
.finder__option-chip svg { stroke: var(--color-accent-2-800); }
.finder__option[aria-current="true"] .finder__option-chip { background: var(--color-accent-500); }
.finder__option[aria-current="true"] .finder__option-chip svg { stroke: #fff; }
.finder__option-label {
  flex: 1;
  font-size: 16px;
  font-weight: 600;
  color: var(--color-text);
}
.finder__option-count {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-neutral-600);
}

/* ─────────────────────────────────────────────────────────────
   Map controls — zoom, and locate
   ───────────────────────────────────────────────────────────── */
/* the zoom buttons and the locate button, stacked bottom-right of the map */
.map-controls {
  grid-area: 1 / 1;
  align-self: end;
  justify-self: end;
  z-index: 3;
  margin: var(--space-3);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
/* one pill holding both steps, the way every map has it */
.map-zoom {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  background: var(--color-neutral-100);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}
.map-zoom__button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 44px;
  border: none;
  background: none;
  cursor: pointer;
}
.map-zoom__button + .map-zoom__button { border-top: 1px solid var(--color-divider); }
.map-zoom__button:hover { background: var(--color-neutral-200); }
.map-zoom__button:active { background: var(--color-neutral-300); }
/* at zoom 5, the whole of France, there is no zooming out; at 19 no zooming in */
.map-zoom__button:disabled { opacity: .45; cursor: default; }
.map-zoom__button:disabled:hover { background: none; }
.map-zoom__button svg { stroke: var(--color-accent-700); }

.locate {
  width: 48px;
  height: 48px;
  border: none;
  border-radius: 999px;
  background: var(--color-neutral-100);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.locate:hover { background: var(--color-neutral-200); }
.locate:active { background: var(--color-neutral-300); }
.locate svg { stroke: var(--color-accent-700); }

/* ─────────────────────────────────────────────────────────────
   Search this area — a floating pill in the same language as the
   zoom/locate controls, offered once a pan or a zoom has carried the view
   past what the catalogue on screen was fetched for.
   ───────────────────────────────────────────────────────────── */
.research {
  grid-area: 1 / 1;
  align-self: start;
  justify-self: center;
  z-index: 3;
  /* clears the search pill's own height (54px) plus its margin, so the two
     floating bars never touch on the narrow layout the finder spans fully */
  margin: calc(54px + var(--space-3) * 2) var(--space-3) 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.research[hidden] { display: none; }
.research__button {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 8px 18px;
  border: none;
  border-radius: 999px;
  background: var(--color-neutral-100);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 700;
  color: var(--color-text);
}
.research__button:hover:not(:disabled) { background: var(--color-neutral-200); }
.research__button:active:not(:disabled) { background: var(--color-neutral-300); }
.research__button:disabled { opacity: .6; cursor: default; }
.research__button svg { stroke: var(--color-accent-700); flex: none; }
/* unlike `.research` itself, nothing here sets `display`, so the UA's own
   `[hidden]` rule is never outranked and needs no override to hold */
.research__problem {
  margin: 0;
  padding: 8px 16px;
  border-radius: var(--radius-lg);
  background: var(--color-neutral-100);
  box-shadow: var(--shadow-md);
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text);
  text-align: center;
}

/* ─────────────────────────────────────────────────────────────
   Attribution
   ───────────────────────────────────────────────────────────── */
/* Not decoration: OpenStreetMap's licence asks for it, so it stays visible at
   every size, on the opposite corner from the controls, and it is a real link. */
.attribution {
  grid-area: 1 / 1;
  align-self: end;
  justify-self: start;
  z-index: 3;
  margin: var(--space-3);
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--color-neutral-100);
  box-shadow: var(--shadow-sm);
  font-size: 11px;
  color: var(--color-neutral-700);
  text-decoration: none;
}
.attribution:hover { text-decoration: underline; }

/* ─────────────────────────────────────────────────────────────
   Bottom sheet — nearest results
   ───────────────────────────────────────────────────────────── */
.sheet {
  grid-area: 2 / 1;
  min-width: 0;
  min-height: 0;
  z-index: 2;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--color-neutral-100);
  box-shadow: 0 -8px 30px rgba(32, 30, 29, .14);
  padding: 10px 16px calc(18px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* carried by a class so picking a category can replay it, as the markers do */
.sheet--in { animation: sheetUp .3s ease both; }
.sheet__grabber {
  width: 40px;
  height: 5px;
  border-radius: 999px;
  background: var(--color-neutral-300);
  margin: 0 auto;
}
.sheet__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
}
.sheet__title {
  flex: 1;
  margin: 0;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 22px;
  color: var(--color-text);
}
/* the API's labels run long ("Bicycle repair station"), so the title takes the
   wrap and the hint keeps its one line */
.sheet__hint {
  flex: none;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  color: var(--color-neutral-600);
}
/* every result in the category is listed, so the list is what scrolls */
.sheet__list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  max-height: 40dvh;
  /* room for the scrollbar and the rows' focus rings */
  padding-right: 2px;
}

/* The row is a box holding two controls — the one that selects the place here
   and the one that leaves for it — so it is the box that is tinted and the
   controls that are transparent. Hovering anywhere on the row lights it,
   because the row still reads as one thing even though it answers to two. */
.result {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--radius-lg);
  background: var(--color-neutral-200);
}
.result:hover { background: var(--color-accent-100); }
.result--selected { background: var(--color-accent-200); }

/* it fills the row apart from the Go link, so the whole of the name and the
   distance is a target, as it was when the row itself was the button */
.result__select {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--font-body);
  text-align: left;
}
.result__rank {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 999px;
  background: var(--color-neutral-300);
  color: var(--color-neutral-800);
  flex: none;
  font-size: 15px;
  font-weight: 700;
}
/* rank 1 carries the sage voice, matching its map marker */
.result:first-child .result__rank {
  background: var(--color-accent-2-500);
  color: #fff;
}
.result__text {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.result__name {
  font-size: 16px;
  font-weight: 700;
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.result__meta {
  font-size: 13px;
  color: var(--color-neutral-700);
}
/* A real link now — it leaves for Google Maps — so it is drawn as the pill it
   always looked like, and told not to underline itself like body prose. */
.result__go {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 16px;
  border-radius: 999px;
  background: var(--color-accent-500);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  flex: none;
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  font-family: var(--font-body);
  white-space: nowrap;
}
.result__go:hover { background: var(--color-accent-600); }
.result__go:active { background: var(--color-accent-700); }
/* SVG's initial stroke is `none`, so icons must always be given one */
.result__go svg { stroke: currentColor; }

/* The last row once the list is capped — plain text weight, so it reads as
   "more of the same list" rather than another call to action like Go. */
.sheet__more {
  padding: 10px 12px;
  border: none;
  border-radius: var(--radius-lg);
  background: none;
  color: var(--color-accent-700);
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 700;
  text-align: center;
  cursor: pointer;
}
.sheet__more:hover { background: var(--color-accent-100); }

/* Two focusable things sit inside one tinted row, so the ring has to say which
   of them Tab has landed on — the row's own background cannot. */
.result__select:focus-visible,
.result__go:focus-visible,
.sheet__more:focus-visible {
  outline: 2px solid var(--color-accent-700);
  outline-offset: 2px;
}

/* ─────────────────────────────────────────────────────────────
   Wide screens — the map still fills the window; the results move out of
   the way, from a sheet across the bottom to a card down the left.
   ───────────────────────────────────────────────────────────── */
@media (min-width: 900px) {
  .screen {
    grid-template-columns: 25rem minmax(0, 1fr);
    grid-template-rows: auto 1fr;
  }

  /* the results are a card down one side here, and leave the middle of the
     window free, so the map can have all of it */
  .map { grid-area: 1 / 1 / -1 / -1; }

  .finder { margin: var(--space-4) var(--space-4) var(--space-2); }

  /* a card down the left, not a sheet across the bottom — it hangs from the
     search pill and stops where its contents do */
  .sheet {
    grid-area: 2 / 1;
    align-self: start;
    max-height: 100%;
    margin: 0 var(--space-4) var(--space-4);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: var(--space-4) var(--space-4) var(--space-2);
  }
  /* both corners move into the map column, clear of the results card */
  .map-controls,
  .attribution {
    grid-area: 2 / 2;
    margin: var(--space-4);
  }
  /* the finder no longer spans the map here, so the pill needs none of the
     mobile layout's clearance under it — it sits over the map itself, in
     the map's own column */
  .research {
    grid-area: 1 / 2;
    margin: var(--space-4);
  }
  .sheet__grabber { display: none; }
  .sheet__list {
    max-height: none;
    flex: 1;
  }
  /* the column is narrower than a phone's sheet, so names wrap instead of
     being cut — there is height to spare here, not width */
  .result__name { white-space: normal; }
}

/* ─────────────────────────────────────────────────────────────
   Reduced motion — keep the state changes, drop the movement
   ───────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .poi, .sheet--in, .finder__menu {
    animation: none;
  }
  .finder__chevron { transition: none; }
}
