/* map.css — /map and /feed, section 1c of the design.
 *
 * Linked AFTER maplibre-gl.css and after app.css, which is the whole
 * mechanism by which a foundation token beats a vendored default and a
 * page rule beats a foundation one. Nothing here is !important.
 *
 * NOT ONE LITERAL COLOUR. Every color below resolves through a token in
 * app.css, because a pack sets its own accent at runtime and a hex
 * written here could not follow it. The hexagon fill is one of pack
 * color's three permitted jobs; ink, lines, status and focus are ours.
 *
 * The rule this whole sheet exists to serve: a sighting's coordinates
 * never reach the browser, so nothing here may draw a point. There is no
 * dot, no pin, no ring, no crosshair and no marker shape in this file.
 * The only round thing is the nine-pixel connection dot, which is about
 * a socket and not about a place.
 */

/* The basemap's three surfaces, named here so map.js can read them
 * instead of carrying literals of its own. They are declared on :root
 * because that is what a script reads a custom property off, and this
 * sheet is served to two routes so nothing else can see them.
 *
 * Water is the pooled-status pair rather than a blue somebody chose: it
 * is already resolved in all four theme states, which a color invented
 * here would not be. */
:root {
  --basemap-void: var(--paper);
  --basemap-land: var(--surface);
  --basemap-water: var(--status-pooled-bg);
  --basemap-road: var(--ink-3);
  --basemap-boundary: var(--ink-4, var(--ink-3));
  /* Label ink for the place and road names MapLibre draws from the
   * vector basemap. --ink so the text tracks the theme's own body color
   * in all four states; the halo is --basemap-void (the paper under the
   * map), so a name stays legible over land and water alike. map.js reads
   * this and its `styled` guard refuses to draw if it is empty. */
  --basemap-label: var(--ink);
  --cell-fill: var(--pack-accent);
  --cell-edge: var(--pack-accent-firm);
  --cell-selected: var(--ink);
}

/* .bleed is 100vw, and a vertical scrollbar makes 100vw wider than the
 * viewport. Clipped here rather than in app.css: /map is the only page
 * that bleeds today. */
.map-page {
  overflow-x: clip;
}

/* -------------------------------------------------------- masthead */

/* Wrapping, because the site name is a host name and a host name is as
 * long as somebody's domain. Unwrapped it pushed the view switcher off
 * the right of a phone and took the document's scroll width with it. */
.masthead {
  flex-wrap: wrap;
  row-gap: var(--sp-2);
}

#site {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  color: var(--ink);
  font-size: var(--fs-2);
  font-weight: var(--weight-bold);
  overflow-wrap: anywhere;
}

/* The wordmark mark. Pack color's first job, and the brand shape is the
 * privacy promise, so the mark and the cells on the map are one figure. */
.masthead-mark {
  flex: none;
  width: 18px;
  height: 21px;
}

.views {
  display: flex;
  gap: var(--sp-1);
  margin-left: auto;
}

.views a {
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid transparent;
  border-radius: var(--r-1);
  color: var(--ink-2);
  font-size: var(--fs-2);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

.views a[aria-current="page"] {
  border-color: var(--line);
  background: var(--surface);
  color: var(--ink);
  font-weight: var(--weight-bold);
}

.map-page h1 {
  margin-bottom: var(--sp-4);
  font-size: var(--fs-4);
  line-height: var(--lh-4);
  letter-spacing: var(--tracking-snug);
}

/* ---------------------------------------------------------- layout */

.map-layout {
  display: grid;
  gap: var(--sp-5);
  margin-bottom: var(--sp-6);
}

.map-layout.has-map > .cell-panel,
.map-layout.has-map #map-region-head {
  padding-inline: var(--sp-4);
}

/* A grid track is at least its own min-content, so a single item that
 * cannot shrink widens the whole column past the viewport. Nothing on
 * this page needs to be wider than the column it is in. */
.map-layout > * {
  min-width: 0;
}

@media (min-width: 62rem) {
  .map-layout.has-map {
    grid-template-columns: minmax(0, 1fr) 24rem;
    align-items: start;
    padding-inline: var(--sp-5);
  }

  .map-layout.has-map #map-region-head {
    padding-inline: 0;
  }

  .map-layout.has-map > .cell-panel {
    padding-inline: 0;
    max-height: clamp(420px, 68vh, 680px);
    padding: var(--sp-4);
    overflow-y: auto;
    border: 1px solid var(--line);
    border-radius: var(--r-2);
    background: var(--surface);
  }
}

/* ------------------------------------------------------------- map */

#map {
  height: clamp(280px, 52vh, 560px);
  background: var(--pack-accent-quiet);
}

/* Three states, and the middle one exists for a measured reason.
 *
 *   waiting   no script ran, or it threw. #map is not in the document
 *             at all and the sentence below is what is on the page.
 *             Script off, script blocked and script broken otherwise
 *             all render as the same empty grey box.
 *   drawing   map.js is about to construct the map. #map has to be laid
 *             out BEFORE that call, because MapLibre measures its
 *             container once: constructed inside a display:none box it
 *             takes 400x300 and keeps it.
 *   live      the style loaded.
 */
#map-region[data-map="waiting"] #map,
#map-region:not([data-map="live"]) .map-legend {
  display: none;
}

#map-region:not([data-map="waiting"]) .map-fallback {
  display: none;
}

.map-fallback {
  margin-inline: var(--sp-4);
  padding: var(--sp-4);
  border: 1px dashed var(--ink-3);
  border-radius: var(--r-2);
  background: var(--surface);
}

.map-fallback p {
  margin: 0;
  color: var(--ink-2);
}

@media (min-width: 62rem) {
  #map {
    height: clamp(420px, 68vh, 680px);
    border: 1px solid var(--line);
    border-radius: var(--r-2);
    overflow: hidden;
  }

  .map-fallback {
    margin-inline: 0;
  }
}

/* ---------------------------------------------------------- legend */

.map-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  margin-top: var(--sp-3);
  padding-inline: var(--sp-4);
}

.legend-title {
  margin: 0;
}

.legend-ramp {
  display: flex;
  gap: var(--sp-1);
  margin: 0;
}

/* The five fill weights, drawn as the shape they weight. Deliberately
 * beside a printed count everywhere it appears: color alone never
 * states density. */
.hexchip {
  width: 14px;
  height: 16px;
  clip-path: var(--r-hex);
  background: var(--pack-accent);
}

.legend-note {
  flex-basis: 100%;
  margin: 0;
  color: var(--ink-3);
  font-size: var(--fs-1);
  line-height: var(--lh-1);
}

@media (min-width: 62rem) {
  .map-legend {
    padding-inline: 0;
  }
}

/* ------------------------------------------- the five fill weights */

.hexchip[data-step="1"],
.cell[data-step="1"] .cell-mark {
  opacity: 0.18;
}

.hexchip[data-step="2"],
.cell[data-step="2"] .cell-mark {
  opacity: 0.33;
}

.hexchip[data-step="3"],
.cell[data-step="3"] .cell-mark {
  opacity: 0.45;
}

.hexchip[data-step="4"],
.cell[data-step="4"] .cell-mark {
  opacity: 0.66;
}

.hexchip[data-step="5"],
.cell[data-step="5"] .cell-mark {
  opacity: 0.9;
}

/* ------------------------------------------------- stream and feed */

.cell-panel > h2 {
  margin-top: 0;
  font-size: var(--fs-3);
  line-height: var(--lh-3);
}

.feed-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--surface);
  font-size: var(--fs-1);
  line-height: var(--lh-1);
}

.feed-dot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--line);
}

.feed-word {
  font-weight: var(--weight-bold);
}

/* The manual action a reader has when nothing is arriving on its own.
 * Offered in exactly the two states where it is the only thing that
 * works, and withdrawn in the two where it is noise. */
.feed-action {
  margin-left: auto;
  font-weight: var(--weight-bold);
}

.feed-state[data-state="live"] .feed-action,
.feed-state[data-state="reconnecting"] .feed-action {
  display: none;
}

/* Every state is worded as well as drawn, so the dot is never the only
 * signal and none of this depends on hue. */
.feed-state[data-state="live"] {
  border-color: var(--status-verified-line);
  background: var(--status-verified-bg);
  color: var(--status-verified-ink);
}

.feed-state[data-state="live"] .feed-dot {
  background: currentcolor;
}

.feed-state[data-state="reconnecting"] {
  border-color: var(--status-withheld-line);
  background: var(--status-withheld-bg);
  color: var(--status-withheld-ink);
}

.feed-state[data-state="reconnecting"] .feed-dot {
  border: 2px solid currentcolor;
  background: none;
}

/* Stale and unscripted are one drawing on purpose: a page whose script
 * never ran and a page whose stream died are in the same position, and
 * both are offered the same manual action. */
.feed-state[data-state="stale"],
.feed-state[data-state="unscripted"] {
  border-style: dashed;
  border-color: var(--ink-3);
  background: var(--paper);
  color: var(--ink-2);
}

/* ------------------------------------------------- the cell list */

/* Replaces app.css's PROVISIONAL #sightings pair for this page. On /map
 * and /feed the list is one row per HEXAGON with its reports nested
 * inside, which is the screen-reader equivalent of the map and is the
 * whole map with no script. */
#sightings {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}

#sightings > .cell {
  margin: 0;
  padding: var(--sp-3) 0 var(--sp-3) var(--sp-3);
  border-bottom: 1px solid var(--line);
  border-left: 4px solid transparent;
  color: var(--ink);
}

/* Selection is a 4px ink outline and never a color change, so it
 * survives any pack accent and any fill weight. On the map it is a line
 * layer of the same width; here it is the same weight on the edge. */
#sightings > .cell[aria-current="true"] {
  border-left-color: var(--ink);
  background: var(--surface);
}

/* Wrapping, and the three labels unbreakable. In the desktop panel the
 * row is 24rem wide and an unwrapped one broke the cell's own name in
 * half — "Cell R8-" over "2E67" — which is the one string on the row a
 * person is meant to be able to read out. */
.cell-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1) var(--sp-2);
  margin: 0;
}

.cell-name,
.cell-count,
.cell-recent {
  white-space: nowrap;
}

.cell-mark {
  flex: none;
  width: 18px;
  height: 21px;
}

.cell-name {
  font-weight: var(--weight-bold);
}

.cell-count {
  color: var(--ink-2);
}

.cell-recent {
  margin-left: auto;
  color: var(--ink-3);
}

.cell-reports {
  margin: 0;
  padding: 0 0 0 calc(18px + var(--sp-2));
  list-style: none;
}

.cell-reports > li {
  margin: 0;
  padding: 0;
  border: 0;
}

.cell-reports a {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap-min);
  color: var(--ink-2);
  text-decoration: none;
}

.cell-reports a:hover .report-age {
  color: var(--ink);
  text-decoration: underline;
}

.report-age {
  color: var(--ink-3);
}

/* The status word is in the markup and is what a screen reader hears;
 * this only makes the served value read as a word. */
#sightings .status {
  text-transform: capitalize;
}

.cells-empty {
  margin: var(--sp-4) 0 0;
  color: var(--ink-2);
}

/* -------------------------------------------- counts drawn on cells */

/* The number of reports in the cell, centred in the cell it counts.
 * A DOM element rather than a MapLibre symbol layer: symbol text needs a
 * glyph endpoint and a font PBF, and this application serves neither and
 * may not fetch one. It is not a marker in the usual sense — no shape,
 * no shadow, no anchor, nothing that could be read as a position. */
.cell-count-marker {
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: var(--weight-bold);
  line-height: 1;
  pointer-events: none;
  user-select: none;
}

/* At the two heaviest fills the accent is dark enough that ink loses
 * contrast, so the numeral switches to the accent's derived partner. */
.cell-count-marker[data-step="4"],
.cell-count-marker[data-step="5"] {
  color: var(--pack-accent-ink);
}

/* ---------------------------------------- MapLibre's own furniture */

/* Tokens over vendored defaults, which is why this sheet is linked last.
 * The zoom buttons are the one control on the map and they are a tap
 * target like any other. */
.maplibregl-ctrl-group {
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--surface);
  box-shadow: none;
}

.maplibregl-ctrl-group button {
  width: var(--tap-min);
  height: var(--tap-min);
  background: var(--surface);
}

.maplibregl-ctrl-group button + button {
  border-top-color: var(--line);
}

/* MapLibre's + and − are dark glyphs baked into data: URIs, so on dark
 * paper they are a dark shape on our dark surface. There is no token to
 * point at inside somebody else's SVG; inverting the glyph is the only
 * lever, and it is not a color. */
@media (prefers-color-scheme: dark) {
  .maplibregl-ctrl-group button .maplibregl-ctrl-icon {
    filter: invert(1);
  }
}

[data-theme="dark"] .maplibregl-ctrl-group button .maplibregl-ctrl-icon {
  filter: invert(1);
}

[data-theme="light"] .maplibregl-ctrl-group button .maplibregl-ctrl-icon {
  filter: none;
}

.maplibregl-ctrl-attrib,
.maplibregl-ctrl-attrib a {
  color: var(--ink-2);
  font-family: var(--font-sans);
  font-size: var(--fs-1);
}

.maplibregl-ctrl-attrib.maplibregl-compact {
  background: var(--surface);
}

/* --------------------------------------------------------- closing */

#approximation p:last-child,
#attribution {
  color: var(--ink-3);
}

#attribution {
  font-size: var(--fs-1);
  line-height: var(--lh-1);
}
