/* MeshWars: front page (/), also reachable at /map2. Loads coverage.css
 * (page layout, the loading overlay, the no-JS fallback, the site nav
 * bar, Leaflet chrome overrides that simply match nothing here) and
 * mc.css (the territory panel/modals/winner banner, ported into
 * map2.js -- see that file's "Territory panel" section) ahead of this
 * file, same as frontend/index.html -- reused rather than duplicated,
 * so the panel is pixel-identical to its Leaflet original. This file
 * only carries what is unique to the MapLibre map itself: the #map
 * sizing MapLibre needs, the layer switcher, and the Places Worth
 * Going markers + in-panel expand section (.mc-places-section, below
 * the territory panel rule -- built into the territory panel itself by
 * map2.js's buildScoreboardControl, not a floating panel of its own).
 * theme.css is loaded first (see map2.html) so every panel below reads
 * its colours from --mw-* tokens rather than hardcoding them -- chrome
 * follows the theme, team colours never do (those live in
 * frontend/map2.js and are gameplay, not branding).
 * See frontend/map2.js for the map itself.
 */

#map {
  position: absolute;
  top: var(--mw-nav-h);
  left: 0;
  right: 0;
  bottom: 0;
}

/* Territory panel (mc.css's .mc-scoreboard, built by map2.js's
 * buildScoreboardControl -- see that file). mc.css carries none of its
 * own positioning: on the Leaflet page it sits inside Leaflet's own
 * "topright" control corner, which does not exist here, so this file
 * places it explicitly in the same visual corner instead -- same 10px
 * gutter Leaflet's control corners use.
 *
 * max-height/overflow-y are what make expanding Places (.mc-places-
 * section, below) safe on a short viewport: without a cap the card
 * would just keep growing downward and run off the bottom of the
 * screen with no way to reach whatever scrolled past. mc.css itself
 * carries no such cap -- on the Leaflet page (frontend/index.html) this
 * same class sits inside Leaflet's own control corner with no Places
 * section to expand, so it has never needed one; scoped here rather
 * than added to the shared file so that page is untouched. */
.mc-scoreboard#mc-scoreboard-position {
  position: absolute;
  top: calc(var(--mw-nav-h) + 10px);
  right: 10px;
  z-index: 20;
  max-height: calc(100vh - var(--mw-nav-h) - 20px);
  overflow-y: auto;
}

/* The layer switcher and the tab that slides it away (map2.html's
 * #mw-layers, driven by map2.js's setupLayersCollapse). This element is
 * only the positioner now -- the card itself is .mw-layers-panel below,
 * and .mw-layers-toggle is a full-height tab welded to its right edge,
 * so the two read as one card with a grab bar. Collapsing translates
 * the pair leftwards by exactly enough to park the tab flush against
 * the viewport edge and carry the panel off screen behind it; because
 * the movement is a transform on a bottom-left anchored box it can
 * never produce a horizontal scrollbar. */
.mw-layers {
  /* Also the width the collapsed tab is left showing -- the translate
     below is written in terms of it, so the two can never disagree. */
  --mw-layers-tab-w: 24px;
  position: absolute;
  bottom: 12px;
  left: 12px;
  z-index: 5;
  display: flex;
  align-items: stretch;
  transition: transform 0.25s ease;
}

/* Park the tab's left edge on x=0: the box starts at left:12px and the
   tab occupies its rightmost --mw-layers-tab-w, so shifting by
   (tab width - own width - 12px) lands it exactly on the edge whatever
   the panel's own width happens to be. */
.mw-layers.mw-layers-collapsed {
  transform: translateX(calc(var(--mw-layers-tab-w) - 100% - 12px));
}

.mw-layers-panel {
  background: var(--mw-panel);
  color: var(--mw-text-1);
  border: 1px solid var(--mw-line);
  border-right: none;
  border-radius: 8px 0 0 8px;
  padding: 10px 14px;
  min-width: 150px;
  font-size: 13px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
  /* Delayed only on the way out (the collapsed rule below), so the card
     stays painted for the whole slide and then goes properly hidden --
     which is also what takes its checkboxes out of the tab order, so a
     collapsed panel cannot be reached by keyboard or screen reader. */
  visibility: visible;
  transition: visibility 0s;
}

.mw-layers.mw-layers-collapsed .mw-layers-panel {
  visibility: hidden;
  transition: visibility 0s linear 0.25s;
}

/* The one control: it collapses the panel when open and restores it
   when closed, and never moves out of reach because it travels with the
   panel rather than being part of it. Stretched to the panel's height
   so it is a comfortable target at either width, and so the collapsed
   state still leaves an obvious bar rather than a dot. */
.mw-layers-toggle {
  flex: 0 0 auto;
  width: var(--mw-layers-tab-w);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--mw-raised);
  /* Accent rather than a text grey: collapsed, this caret is the only
     thing left on screen, and it has to read as a control against a
     dark satellite/hillshade basemap rather than disappear into it. */
  color: var(--mw-accent);
  border: 1px solid var(--mw-line-strong);
  border-radius: 0 8px 8px 0;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
  font-family: inherit;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}

.mw-layers-toggle:hover,
.mw-layers-toggle:focus-visible {
  background: var(--mw-accent-active);
  color: var(--mw-gold-light);
}

.mw-layers-toggle-caret {
  display: block;
  transition: transform 0.25s ease;
}

.mw-layers.mw-layers-collapsed .mw-layers-toggle-caret {
  transform: rotate(180deg);
}

/* Applied by setupLayersCollapse only while it writes the state it read
   back from localStorage, so a visitor who left the panel collapsed
   gets it collapsed rather than watching it slide shut on every load. */
.mw-layers.mw-layers-no-anim,
.mw-layers.mw-layers-no-anim .mw-layers-panel,
.mw-layers.mw-layers-no-anim .mw-layers-toggle-caret {
  transition: none;
}

/* The slide is the whole point of the affordance, so with motion
   reduced it still collapses -- it just arrives there immediately. */
@media (prefers-reduced-motion: reduce) {
  .mw-layers,
  .mw-layers .mw-layers-panel,
  .mw-layers .mw-layers-toggle-caret {
    transition: none;
  }
}

/* On a phone the map is narrow enough that MapLibre's attribution strip
 * spans most of the bottom edge, and this panel -- bottom-left, and
 * painted over it -- covered the "(c) OpenStreetMap" half of it, leaving
 * only "contributors (c) CARTO" legible. CARTO's free basemap tier is
 * conditional on that attribution staying visible, and OpenStreetMap's
 * ODbL requires the credit independently of CARTO, so this is a licence
 * term rather than a cosmetic detail. Lift the panel clear of the strip
 * (~24px tall) instead of restyling the control, so both read in full.
 * The lift is on the positioner, so the collapsed tab is carried clear
 * of the strip too -- neither state may sit on top of that credit.
 * Desktop is unaffected -- there the strip only reaches the right-hand
 * third and the two never meet. */
@media (max-width: 600px) {
  .mw-layers {
    /* A wider tab for a fingertip; the collapse translate is written in
       terms of this, so nothing else needs adjusting alongside it. */
    --mw-layers-tab-w: 30px;
    bottom: 44px;
  }
}

.mw-layers-title {
  font-weight: 600;
  margin-bottom: 6px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  font-size: 11px;
  color: var(--mw-text-6);
}

.mw-layers-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.mw-layers-list li {
  padding: 2px 0;
}

.mw-layers-list label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

/* Applied to a switcher row (see map2.js setupLayerSwitcher) when the
 * current zoom is below that layer's data -- the checkbox is disabled
 * at the same time, so this only needs to read as "off" rather than
 * clickable. Colour comes from the theme's own least-emphasis token,
 * same one used for captions and disabled controls elsewhere. */
.mw-layers-list li.unavailable {
  opacity: 0.45;
  color: var(--mw-text-6);
}

.mw-layers-list li.unavailable label {
  cursor: default;
}

/* Places Worth Going (docs/features/places.md): NOT a floating panel of
 * its own -- two separate floating-panel attempts both ended up either
 * clipped behind the territory panel or (once moved to the bottom-right
 * corner) with its own header hidden behind the fixed nav bar. Built
 * directly into the territory panel instead (map2.js's
 * buildScoreboardControl): a fourth .mc-actions button, styled exactly
 * like Refresh map/Top Operators next to it (mc.css's shared
 * #mc-refresh-btn/#mc-top-btn/#mc-places-btn rule -- see that file),
 * that expands #mc-places-section in place below it. Nested inside a
 * card that already has nothing else fighting it for the corner, and
 * already scrolls as a whole if it overflows the viewport (see
 * .mc-scoreboard#mc-scoreboard-position's max-height above), so there
 * is nothing left for this to hide behind.
 *
 * #mw-places-list itself is never removed from the DOM regardless of
 * whether the section is open (map2.js keeps writing to it on every
 * moveend), so the list is current the instant a reader expands it. */
.mc-places-section {
  display: none;
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--mw-raised);
}
.mc-places-section.open {
  display: block;
}

.mc-places-section-title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--mw-text-6);
  margin-bottom: 4px;
}

.mw-places-list {
  list-style: none;
  margin: 0;
  padding: 0;
  /* A capped, independently-scrolling region rather than letting a
   * long list alone dictate the whole card's height -- the outer
   * scoreboard card still has its own max-height/overflow as a second
   * safety net (see above), this just keeps a short viewport from
   * needing to scroll the entire card just to see two more places. */
  max-height: 240px;
  overflow-y: auto;
}

.mw-places-list li {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--mw-raised);
  cursor: pointer;
  font-size: 12px;
}
.mw-places-list li:hover {
  background: rgba(255, 255, 255, 0.04);
}

.mw-places-list .mw-place-icon {
  flex: none;
  width: 9px;
  height: 9px;
}

.mw-places-list .mw-place-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mw-places-list .mw-place-meta {
  flex: none;
  font-variant-numeric: tabular-nums;
  color: var(--mw-text-6);
  font-size: 11px;
}

.mw-places-empty {
  padding: 8px 0;
  color: var(--mw-text-6);
  font-size: 12px;
}

/* MapLibre popup chrome (dark theme). MapLibre's own .maplibregl-popup-
 * content does NOT theme itself -- it ships a hardcoded white background
 * with default (black-ish) text -- unlike Leaflet, where coverage.css
 * already overrides .leaflet-popup-content-wrapper/.leaflet-popup-tip
 * for the /map-legacy page. Both popups built by map2.js (the cell
 * popup's .mc-tile-popup and the place popup's .mw-place-popup) reuse
 * mc.css/this file's own light-on-dark text colors, so without this
 * block they were a white box with unreadable grey/light text and a
 * white tip. Mirrors coverage.css's Leaflet block: same tokens, same
 * shape, just MapLibre's class names. Applies to both popups (no
 * className scoping) since they share one map and should look alike. */
.maplibregl-popup-content {
  background: var(--mw-panel);
  color: var(--mw-text-1);
  border: 1px solid var(--mw-line);
}
.maplibregl-popup-tip {
  border-top-color: var(--mw-panel);
  border-bottom-color: var(--mw-panel);
  border-left-color: var(--mw-panel);
  border-right-color: var(--mw-panel);
}
.maplibregl-popup-close-button {
  color: var(--mw-text-4);
  font-size: 16px;
}
.maplibregl-popup-close-button:hover {
  background-color: rgba(255, 255, 255, 0.08);
  color: var(--mw-text-1);
}

.mw-place-popup .mw-place-popup-name {
  font-weight: 600;
  margin-bottom: 2px;
}
.mw-place-popup .mw-place-popup-meta {
  font-size: 12px;
  color: var(--mw-text-6);
}

/* ===== Update notice (operator-authored, admin panel) =====
   Same dimmed-backdrop/dark-card shape as mc.css's .mc-modal (see
   map2.js's ensureModal), kept as its own small block rather than
   reusing that modal: this one is built and torn down by map2.js's
   loadNotice() on its own timer (once, at boot, only if unread), not
   opened by a nav button, and .mc-modal's own display:none/flex toggle
   would fight a second consumer appending into the same element. Only
   theme tokens below -- no literal colors -- so it reads correctly on
   both the gold and neon skins without any theme-specific rule here. */
.mw-notice-modal {
  position: fixed;
  inset: 0;
  z-index: 2100; /* above .mc-modal's 2000 -- a release notice outranks a stats popup */
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.7);
  padding: 16px;
}

.mw-notice-modal-inner {
  background: var(--mw-panel);
  color: var(--mw-text-1);
  border: 1px solid var(--mw-line-strong);
  border-radius: 8px;
  width: min(520px, 100%);
  max-height: 80vh;
  overflow: auto;
}

.mw-notice-modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--mw-line);
  font-weight: 700;
  font-size: 15px;
  color: var(--mw-accent);
}

.mw-notice-modal-close {
  flex: none;
  cursor: pointer;
  color: var(--mw-text-4);
  font-size: 22px;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
}
.mw-notice-modal-close:hover { color: var(--mw-text-1); }

.mw-notice-modal-body {
  padding: 16px 18px 20px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--mw-text-2);
  /* Plain text is written in with .textContent (map2.js) -- this is
     what turns the operator's line breaks back into visible ones
     without ever parsing the body as markup. */
  white-space: pre-wrap;
}

/* The body is plain text on purpose and can never carry its own link,
   so this fixed footer is the one way out to more detail, present on
   every notice. A rule above it separates it from the body the same
   way .mw-notice-modal-header's rule separates the title -- and keeps
   it visually apart from .mw-notice-modal-close up in the header, so
   "read on" and "close" never read as the same control. */
.mw-notice-modal-footer {
  display: flex;
  justify-content: flex-end;
  padding: 12px 18px 16px;
  border-top: 1px solid var(--mw-line);
}

.mw-notice-modal-rules-link {
  color: var(--mw-accent);
  text-decoration: none;
  font-size: 13px;
  font-weight: 600;
}
.mw-notice-modal-rules-link:hover,
.mw-notice-modal-rules-link:focus-visible {
  text-decoration: underline;
}

/* ===== First-visit board choice (map2.js's showBoardChoiceModal) =====
   Same dimmed-backdrop/dark-card shape as .mw-notice-modal above, one
   z-index tier higher (2200 vs 2100): this gates which board even
   paints, so on the rare first-ever visit that also carries an active
   operator notice, the choice is answered first and the notice is still
   there, unobscured, the moment this modal is gone. The two options
   reuse .mc-switch-btn's own surface/border/hover language (mc.css) --
   enlarged and stacked to fit a title plus a description line, rather
   than the toggle's compact side-by-side pair -- so a first-time
   visitor's very first choice already looks like the control they will
   use to change their mind later. Only theme tokens below, same as the
   notice modal, so both skins read correctly with no theme-specific
   rule here. */
.mw-board-choice-modal {
  position: fixed;
  inset: 0;
  z-index: 2200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.7);
  padding: 16px;
}

.mw-board-choice-modal-inner {
  background: var(--mw-panel);
  color: var(--mw-text-1);
  border: 1px solid var(--mw-line-strong);
  border-radius: 8px;
  width: min(480px, 100%);
  max-height: 80vh;
  overflow: auto;
  padding: 18px;
  animation: mw-board-choice-in 0.15s ease;
}

/* The modal's only motion: a small settle-in on open, nothing on close
   (closing only ever removes the element outright, once a choice is
   made). Reduced to nothing under prefers-reduced-motion below. */
@keyframes mw-board-choice-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .mw-board-choice-modal-inner {
    animation: none;
  }
}

.mw-board-choice-modal-header {
  font-weight: 700;
  font-size: 15px;
  color: var(--mw-accent);
  margin: 0 0 8px;
}

.mw-board-choice-modal-intro {
  margin: 0 0 16px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--mw-text-2);
}

.mw-board-choice-options {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.mw-board-choice-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  width: 100%;
  background: var(--mw-raised);
  color: var(--mw-text-1);
  border: 1px solid var(--mw-line-strong);
  border-radius: 6px;
  padding: 12px 14px;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}

.mw-board-choice-option:hover {
  background: var(--mw-line);
}

/* Explicit ring rather than the browser default: both themes' surfaces
   here are dark enough that a default outline can wash out against
   them. Inset (negative offset) so it never gets clipped by the card's
   own overflow:auto. No .active/.selected state to also style -- unlike
   .mc-switch-btn, neither option has been picked yet by definition,
   since this modal only shows when nothing is stored. */
.mw-board-choice-option:focus-visible {
  outline: 2px solid var(--mw-accent);
  outline-offset: -2px;
}

.mw-board-choice-option-title {
  font-weight: 700;
  font-size: 14px;
  color: var(--mw-accent);
}

.mw-board-choice-option-desc {
  font-size: 12px;
  line-height: 1.4;
  color: var(--mw-text-2);
}

/* -------------------------------------------------------------------------
   Map failure banner (map2.js's showMapErrorBanner) -- replaces
   #loading-overlay the first time the map construction throws,
   map.on('error') fires, the load timeout elapses, or the WebGL context
   is lost. Same full-viewport z-index tier as #loading-overlay
   (coverage.css) since it always takes that overlay's place, never
   layers on top of it. Uses --mw-hostile (already themed red/pink for
   both gold and neon -- see theme.css) as the one error accent this
   page has, and --mw-panel/--mw-text-1 for the surface so it stays
   readable in both themes and at phone width.
   ------------------------------------------------------------------------- */
.mw-map-error-banner {
  position: fixed;
  inset: 0;
  z-index: 5000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  box-sizing: border-box;
  background: var(--mw-page);
}
.mw-map-error-banner[hidden] { display: none; }
.mw-map-error-inner {
  max-width: 360px;
  width: 100%;
  text-align: center;
  background: var(--mw-panel);
  color: var(--mw-text-1);
  border: 1px solid var(--mw-hostile);
  border-radius: 6px;
  padding: 20px;
  box-sizing: border-box;
}
.mw-map-error-text {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.5;
}
.mw-map-error-reload {
  background: var(--mw-hostile);
  color: var(--mw-text-1);
  border: none;
  border-radius: 4px;
  padding: 10px 20px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.mw-map-error-reload:hover,
.mw-map-error-reload:focus-visible {
  filter: brightness(1.15);
}

/* Award highlight banner -- shown only when /results linked here with
   award params (map2.js showAwardFromUrl). Bottom-centre, clear of the
   scoreboard panel top-right and the layers box bottom-left. */
.award-banner {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  align-items: baseline;
  gap: 10px;
  max-width: min(92vw, 560px);
  padding: 10px 14px;
  border: 1px solid var(--mw-accent-wash-line, rgba(255, 255, 255, 0.18));
  border-radius: 8px;
  background: var(--mw-surface-1, rgba(18, 18, 20, 0.94));
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
  font-size: 0.9rem;
}
.award-banner-label { font-weight: 600; color: var(--mw-gold, #d9b45b); }
/* The winner's own name, in their team's colour -- the colour is the
   team, so it is not spelled out again beside it. */
.award-banner-who { font-weight: 600; }
.award-banner-detail { color: var(--mw-text-4, #9a9a9a); }
.award-banner-close {
  margin-left: auto;
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--mw-text-4, #9a9a9a);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.award-banner-close:hover,
.award-banner-close:focus-visible { color: var(--mw-gold, #d9b45b); }

@media (max-width: 560px) {
  .award-banner { bottom: 12px; font-size: 0.82rem; padding: 8px 10px; }
}
