:root {
  --bg: #0f1114;
  --surface: #1a1d22;
  --surface2: #232830;
  --text: #e6e8eb;
  --muted: #9aa0a6;
  --primary: #4fc3f7;
  --green: #4caf50;
  --yellow: #ffc107;
  --red: #ff5252;
  --border: #2f343c;
}
@media (prefers-color-scheme: light) {
  :root {
    --bg: #f5f7fa;
    --surface: #ffffff;
    --surface2: #eef1f5;
    --text: #202124;
    --muted: #5f6368;
    --border: #d5d9df;
  }
}
* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  font-size: 14px;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

#app {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* The map takes all remaining vertical space. */
#map-area {
  flex: 1 1 0;
  position: relative;
  min-height: 0;
  overflow: hidden;
}
/*
  The map is drawn by OpenLayers into a <canvas>. Touch handling is
  delegated to OpenLayers' viewport, which needs touch-action: none
  so the browser does not interpret a pinch as page zoom. The rule
  is applied to .ol-viewport below.
*/
#map {
  position: absolute;
  inset: 0;
  z-index: 0;
}
/*
  OpenLayers' default CSS sets touch-action: pan-x pan-y on the
  viewport, which lets the browser treat a drag as a page scroll.
  Overriding to none hands every touch to OpenLayers.
*/
.ol-viewport {
  touch-action: none;
}
/*
  Theme the OpenLayers controls so they match the app's surface
  colours. OpenLayers ships with white buttons and a white
  attribution background, both of which look wrong on a dark theme.
*/
.ol-control button {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
}
.ol-control button:hover {
  background: var(--surface2);
}
.ol-attribution {
  background: var(--surface);
  color: var(--muted);
  font-size: 11px;
  border-radius: 4px 0 0 0;
}
.ol-attribution a {
  color: var(--primary);
}

#menu-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 20;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  opacity: 0.9;
  font-size: 20px;
  cursor: pointer;
}

/*
  Locate-me button. Icon-only, bottom-right, above the attribution
  line. Three states:
    - [disabled]:      no self location available
    - default:         self location exists, camera is not following it
                       (crosshair with hollow centre)
    - .following:      camera is following self
                       (crosshair with filled centre, accent colour)
  The icon is a crosshair: an outer ring, a centre dot, and four
  short ticks. It uses currentColor so it follows the button's text
  colour in both themes.
*/
#locate-btn {
  position: absolute;
  bottom: 40px;
  right: 12px;
  z-index: 20;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  opacity: 0.9;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
#locate-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
#locate-btn svg {
  display: block;
  width: 24px;
  height: 24px;
}
#locate-btn .locate-ring {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}
/* Default (not following): hollow centre */
#locate-btn .locate-dot {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}
#locate-btn .locate-tick {
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}
/* Following: accent colour, filled centre */
#locate-btn.following {
  color: var(--primary);
}
#locate-btn.following .locate-dot {
  fill: var(--primary);
  stroke: none;
}

#live-indicator {
  position: absolute;
  top: 12px;
  right: 64px;
  z-index: 20;
  height: 44px;
  line-height: 44px;
  padding: 0 14px;
  border-radius: 22px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  display: none;
  max-width: calc(100% - 128px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
#live-indicator.live {
  display: block;
  background: var(--red);
  color: #fff;
}
#live-indicator.ended {
  display: block;
  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
}
#live-indicator.hidden { display: none; }

/*
  Share button. Absolute-positioned over the map, directly beneath
  the menu button. It must be positioned here: #map-area contains
  only absolute-positioned children (#map is inset: 0, #menu-btn and
  #locate-btn are absolute), so an in-flow element would land at the
  top-left of the map.
*/
#share-dot {
    position: absolute;
    top: 64px;      /* menu-btn is 44px tall at top:12, plus 8px gap */
    right: 12px;
    z-index: 20;

    width: 44px;
    height: 44px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    box-sizing: border-box;

    display: inline-flex;
    align-items: center;
    justify-content: center;

    color: var(--outline, #888);
    opacity: 0.5;

    cursor: pointer;
    transition: color 120ms ease, opacity 120ms ease, transform 120ms ease;
    -webkit-tap-highlight-color: transparent;
}

#share-dot:hover {
    opacity: 0.75;
}

#share-dot:active {
    transform: scale(0.92);
}

#share-dot:focus-visible {
    outline: 2px solid var(--primary, #4fc3f7);
    outline-offset: 2px;
}

/* Idle: not configured */
#share-dot.idle {
    color: var(--outline, #888);
    opacity: 0.5;
}

/* Ready: configured but not sharing */
#share-dot.ready {
    color: var(--green, #00c853);
    opacity: 0.75;
}

/* Active: currently sharing */
#share-dot.active {
    color: var(--red, #ff5252);
    opacity: 1;
}

/* If the button sits over the map, keep it legible against tiles */
#share-dot {
    background-color: var(--surface, #fff);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

@keyframes share-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(0, 200, 83, 0.7); }
  70%  { box-shadow: 0 0 0 12px rgba(0, 200, 83, 0); }
  100% { box-shadow: 0 0 0 0 rgba(0, 200, 83, 0); }
}

#group-prompt,
#group-name-prompt {
  position: absolute;
  top: 64px;
  left: 12px;
  right: 12px;
  z-index: 22;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.35);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
#group-prompt[hidden],
#group-name-prompt[hidden] { display: none; }
.group-prompt-title {
  font-size: 15px;
  color: var(--text);
  font-weight: 700;
  padding-bottom: 2px;
}
.group-prompt-text {
  color: var(--text);
  font-size: 13px;
  line-height: 1.4;
}
.group-prompt-count {
  color: var(--primary);
  font-size: 13px;
  line-height: 1.4;
  padding-top: 2px;
}
#group-prompt input,
#group-name-prompt input {
  padding: 10px 12px;
  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  outline: none;
}
#group-prompt input::placeholder,
#group-name-prompt input::placeholder { color: var(--muted); }
#group-prompt button,
#group-name-prompt button {
  margin-top: 4px;
  padding: 12px 14px;
  background: var(--primary);
  color: #000;
  border: none;
  border-radius: 8px;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}

/*
  Action row: primary button and an optional secondary button side by
  side, each taking half the width. Shared by the join-group prompt
  (Join / Cancel) and the group-name prompt (Join / Not now).
*/
#group-prompt .group-prompt-actions,
#group-name-prompt .group-prompt-actions {
  display: flex;
  gap: 8px;
  margin-top: 4px;
}
#group-prompt .group-prompt-actions button,
#group-name-prompt .group-prompt-actions button {
  flex: 1;
  margin-top: 0;
}
#group-prompt button.secondary,
#group-name-prompt button.secondary {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  font-weight: 600;
}

/*
  Peer marker layout. Rendered as an OpenLayers DOM overlay with
  positioning 'bottom-center', so the element's bottom-center is
  anchored to the peer's coordinate. The explicit width and height
  are required because OpenLayers does not set them the way Leaflet's
  divIcon did.
*/
.peer-marker {
  width: 56px;
  height: 64px;
  background: transparent;
  border: none;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
}
.peer-marker-body {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 2px solid #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 700;
  font-size: 16px;
  font-family: system-ui, sans-serif;
  box-shadow: 0 1px 4px rgba(0,0,0,0.4);
  text-shadow: 0 1px 2px rgba(0,0,0,0.5);
}
.peer-marker.selected .peer-marker-body {
  width: 46px;
  height: 46px;
  border-width: 3px;
}

/* A peer outside the current group. The dashed border distinguishes
   external public peers from the group's own peers at a glance. */
.peer-marker.external .peer-marker-body {
  border-style: dashed;
}

.peer-marker-tail {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 8px solid #fff;
}

/*
  Self marker disc. A filled circle in the accent colour with a white
  border, drawn as a DOM overlay so it can carry a click handler.
  The accuracy circle behind it is a geographic circle drawn by
  OpenLayers on a vector layer.
*/
.self-marker {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--primary);
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.4);
  cursor: pointer;
}

/*
  The bottom panel sizes itself to its content. The map above it takes
  all remaining space. A max-height on the content area is a safety net
  for very short viewports; the user can collapse the panel entirely.

  position: relative and z-index: 1 lift the panel into a stacking
  context above #map-area.
*/
#bottom-area {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-top: 1px solid var(--border);
  position: relative;
  z-index: 1;
}

/*
  The tab bar doubles as the drag handle for the panel.
  touch-action is disabled on it so a vertical drag is delivered to the
  pointer event handlers instead of being treated by the browser as a
  page scroll or a pull-to-refresh gesture. Buttons inside still
  receive taps.

  position: relative is required by the grabber pill defined below.
  padding-top reserves the strip of space the grabber occupies, so it
  never overlaps the tab buttons.
*/
#tab-bar {
  position: relative;
  display: flex;
  flex: 0 0 auto;
  align-items: stretch;
  padding-top: 12px;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
#tab-bar[hidden] { display: none; }
#tab-bar.dragging {
  cursor: grabbing;
}

/*
  Grabber pill. The universal bottom-sheet affordance: "drag me".
  It carries no click handler and has pointer-events: none, so a
  gesture that starts on it reaches the tab bar's pointer handlers
  unchanged, and a click passes through to whatever is beneath.
*/
#tab-bar::before {
  content: '';
  position: absolute;
  top: 4px;
  left: 50%;
  transform: translateX(-50%);
  width: 40px;
  height: 4px;
  border-radius: 2px;
  background: var(--muted);
  opacity: 0.55;
  pointer-events: none;
}

.tab-btn {
  flex: 1;
  padding: 10px 0;
  background: var(--surface2);
  color: var(--text);
  border: none;
  border-bottom: 2px solid transparent;
  font-size: 14px;
  cursor: pointer;
}
.tab-btn.active {
  color: var(--text);
  border-bottom-color: var(--primary);
  background: var(--surface);
}

/*
  Collapse / expand toggle. Visually distinct from the tab buttons:
  larger glyph, accent colour, left rule separating it from the tabs.
  This is the tap target the user is most likely to reach for when
  they want to free up map space.
*/
#tab-collapse-btn {
  flex: 0 0 48px;
  padding: 0;
  background: var(--surface2);
  color: var(--primary);
  border: none;
  border-left: 1px solid var(--border);
  border-bottom: 2px solid transparent;
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
#tab-collapse-btn:active {
  background: var(--surface);
}

#tab-content {
  max-height: 55vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
#tab-content[hidden] { display: none; }

/* Each panel sizes itself. Only one is visible at a time. */
.tab-panel {
  display: none;
}
.tab-panel.active { display: block; }

#chart-container {
  padding: 0;
  height: 240px;
  position: relative;
}
#chart-canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}

#live-panel { padding: 16px; }
.live-hr { font-size: 20px; font-weight: 700; margin-bottom: 6px; }
.live-row { padding: 2px 0; color: var(--text); }
.live-updated { color: var(--text); font-size: 13px; margin-top: 8px; }

#presence-panel { padding: 16px; }
#presence-panel .presence-name {
  font-weight: 700;
  font-size: 15px;
  margin-bottom: 4px;
  color: var(--text);
}
#presence-panel .presence-status {
  color: var(--text);
  font-size: 13px;
  margin-bottom: 12px;
}
#presence-panel .presence-row {
  padding: 3px 0;
  color: var(--text);
}
#presence-panel .presence-note {
  margin-top: 12px;
  color: var(--text);
  font-size: 13px;
  line-height: 1.4;
}

/*
  Grid. Rows are kept compact (align-content: start) so a container
  that happens to be taller than the rows does not stretch them apart.
  Gap is uniform to keep the two columns aligned to the same baseline.
*/
#summary {
  padding: 10px 14px;
  display: none;
  grid-template-columns: 1fr 1fr;
  gap: 4px 24px;
  align-content: start;
}
#summary.active { display: grid; }
#summary .metric {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 2px 0;
  min-width: 0;
}
#summary .metric .label {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#summary .metric .value {
  font-weight: 600;
  white-space: nowrap;
}
#summary .low  .value { color: var(--green); }
#summary .mod  .value { color: var(--yellow); }
#summary .high .value { color: var(--red); }

#menu-backdrop {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 99;
}
#menu-backdrop[hidden] { display: none; }

#menu-sheet {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 100;
  display: flex;
  align-items: flex-end;
  pointer-events: none;
}
#menu-sheet[hidden] { display: none; }

/*
  max-height is deliberately less than 100% so a strip of the
  backdrop remains visible and tappable on short viewports. Combined
  with the explicit close button in #menu-header, this guarantees the
  sheet can always be dismissed, regardless of content length.
*/
#menu-content {
  width: 100%;
  max-height: 85%;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -4px 16px rgba(0,0,0,0.35);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  pointer-events: auto;
}

/* Menu header: title on the left, close button on the right. */
#menu-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

#menu-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}

/*
  Close button. Three levels of specificity are required so this
  wins against the generic `#menu-sheet button` rule later in this
  file, which would otherwise force a rectangular, full-width look
  with text-align: left.
*/
#menu-sheet #menu-close-btn {
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: 50%;
  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
  font-size: 16px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
#menu-sheet #menu-close-btn:active {
  background: var(--surface);
}

#menu-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  pointer-events: auto;
}

/* ---------- Section structure ---------- */

.menu-section {
  border-bottom: 1px solid var(--border);
}
.menu-section:last-of-type {
  border-bottom: none;
}

/*
  Section header. Full-width, uppercase, bold. Colour is the same as
  body text so the header reads as a visible label, not as a greyed-
  out hint. The disclosure control is the chevron on the right; the
  header is the hit target.

  The selector below uses three levels (id, class, element) so that it
  wins the cascade against the generic `#menu-sheet button` rule
  defined later in this file.

  min-height is 48px, the Material minimum touch target.
*/
#menu-sheet .menu-section > button.menu-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 48px;
  margin: 0;
  padding: 12px 16px;
  background: transparent;
  color: var(--text);
  border: none;
  border-radius: 0;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  cursor: pointer;
  flex-shrink: 1;
  white-space: normal;
  pointer-events: auto;
}
#menu-sheet .menu-section > button.menu-section-header:active {
  background: var(--surface2);
}
/*
  Hover feedback on devices with a real pointer. Guarded by
  `(hover: hover)` so a touch screen does not get a "sticky" hover
  state after a tap.
*/
@media (hover: hover) {
  #menu-sheet .menu-section > button.menu-section-header:hover {
    background: var(--surface2);
  }
}

.menu-section-title {
  flex: 1 1 auto;
  text-align: left;
}

/*
  Disclosure chevron. A single glyph that rotates on state change.
  Collapsed: points right (there is content to the right, or "inside").
  Expanded: rotates 90° clockwise to point down (the content is now
  below). Rotation is a stronger visual signal than a glyph swap, it
  animates, and it keeps the state in CSS rather than in JS.

  The primary colour matches every other interactive element in the
  sheet, so the chevron reads as "this is the thing you tap".
*/
.menu-section-chevron {
  flex: 0 0 auto;
  margin-left: 12px;
  font-size: 18px;
  line-height: 1;
  color: var(--primary);
  display: inline-block;
  transition: transform 0.15s ease;
  transform: rotate(90deg);
}
.menu-section.collapsed .menu-section-chevron {
  transform: rotate(0);
}
@media (prefers-reduced-motion: reduce) {
  .menu-section-chevron {
    transition: none;
  }
}

.menu-section.collapsed .menu-section-content {
  display: none;
}

.menu-section-content {
  padding: 4px 12px 12px;
}

/* ---------- Rows and controls ---------- */

#menu-sheet .row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/*
  Generic button in the menu sheet. `appearance: none` is important
  on Firefox/Linux: without it, the browser reaches for the OS theme
  (GTK) to render the button and can ignore the CSS background and
  border. Chrome does not have this problem, which is why the button
  may render correctly elsewhere but not in Firefox.
*/
#menu-sheet button {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;

  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  flex-shrink: 0;
  white-space: nowrap;
}

#menu-sheet label {
  display: flex;
  gap: 6px;
  align-items: center;
  color: var(--text);
}

#menu-sheet select {
  display: none;
}

.picker-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
}
.picker-btn .picker-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 150px;
}
.picker-btn.compact .picker-value {
  max-width: 90px;
}
.picker-btn .picker-chevron {
  color: var(--text);
  opacity: 0.7;
  font-size: 11px;
}
.picker-btn[hidden] {
  display: none;
}

/*
  Sharing warning. Always visible at the top of the sheet, outside the
  collapsible sections. The warning describes a condition of the whole
  menu (insecure context), not of one section, and must not be hidden
  by a collapsed Sharing section.
*/
.share-warning {
  margin: 8px 12px 0;
  padding: 8px 10px;
  background: rgba(211, 47, 47, 0.12);
  border: 1px solid rgba(211, 47, 47, 0.40);
  color: #c62828;
  border-radius: 6px;
  font-size: 13px;
  line-height: 1.4;
}
.share-warning.info {
  background: rgba(25, 118, 210, 0.10);
  border-color: rgba(25, 118, 210, 0.40);
  color: #1565c0;
}
@media (prefers-color-scheme: dark) {
  .share-warning {
    background: rgba(255, 82, 82, 0.15);
    border-color: rgba(255, 82, 82, 0.50);
    color: #ff8a80;
  }
  .share-warning.info {
    background: rgba(79, 195, 247, 0.15);
    border-color: rgba(79, 195, 247, 0.50);
    color: #82d4f7;
  }
}

/*
  Inline help message under the API-key field. Same visual language as
  the share warning above the sheet, but scoped to a field: it explains
  a specific rejection the server just returned for the key in this
  field. It is only rendered when that field is visible.
*/
.sharing-help {
  margin-top: 6px;
  padding: 8px 10px;
  background: rgba(211, 47, 47, 0.12);
  border: 1px solid rgba(211, 47, 47, 0.40);
  color: #c62828;
  border-radius: 6px;
  font-size: 13px;
  line-height: 1.4;
}
@media (prefers-color-scheme: dark) {
  .sharing-help {
    background: rgba(255, 82, 82, 0.15);
    border-color: rgba(255, 82, 82, 0.50);
    color: #ff8a80;
  }
}
.sharing-help[hidden] { display: none; }

/*
  Title row of the Sharing section. The title text grows to fill the
  free space and the start/stop button sits at its natural size after
  it.

  `flex-wrap: nowrap` is deliberate. `.row` sets `flex-wrap: wrap` for
  the two-column field rows; on the title row, wrapping is the wrong
  behaviour: a wrapped button lands on a second line whose height is
  not accounted for in the section's height, which is one way the
  button can end up invisible in some engines. Forcing a single line
  removes that possibility.
*/
.sharing-title-row {
  padding-bottom: 2px;
  flex-wrap: nowrap;
}
.sharing-title {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 700;
  color: var(--text);
}

.sharing-group-row {
  padding: 4px 0 2px;
  gap: 8px;
}
.sharing-group-label {
  color: var(--text);
}
.sharing-group-row .group-hash {
  font-family: monospace;
  color: var(--text);
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sharing-fields {
  margin-top: 6px;
}
.sharing-fields input {
  flex: 1 1 120px;
  min-width: 0;
  padding: 8px 10px;
  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 13px;
}
.sharing-fields input::placeholder { color: var(--muted); }

/*
  Legacy rule for the old bottom-of-section action row. The button
  now lives in the title row, and the status text is on its own
  line, so this container no longer exists in the DOM. Kept in case
  a future layout re-introduces the row.
*/
.sharing-actions {
  margin-top: 8px;
  justify-content: space-between;
  flex-wrap: nowrap;
}

/*
  Start/stop sharing button. Deliberately has NO bespoke styling
  beyond the "on" state: it inherits everything from #menu-sheet
  button, exactly the way the "Change" button in the group row does.
  The two buttons then render identically, which is the point.
*/
#share-toggle.on {
  background: var(--green);
  color: #fff;
  border-color: var(--green);
}

/*
  Status text below the title row. The old layout clipped it to one
  line because it shared a flex row with the button. Now that it has
  the full section width, it is allowed to wrap: the tail of a long
  error string ("Sharing · last error: Upload failed: HTTP 403 — …")
  is the part that matters.
*/
.share-status {
  display: block;
  font-size: 13px;
  color: var(--text);
  overflow-wrap: break-word;
  margin-top: 4px;
}

/* ---------- About section ---------- */

/*
  The About section carries no controls. It explains what the viewer
  is, what a browser peer can do, and where to get the Android app.
  Text is at body colour so it reads as ordinary content, not as
  greyed-out fine print.
*/
.about-text {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
  margin: 6px 0;
}
.about-text:first-child {
  margin-top: 2px;
}
.about-text:last-child {
  margin-bottom: 4px;
}
.about-text a {
  color: var(--primary);
  word-break: break-all;
}
.about-text strong {
  color: var(--text);
}

/* ---------- Picker overlay ---------- */

#menu-picker {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
  padding: 16px 12px 12px;
}
#menu-picker[hidden] { display: none; }
#picker-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  padding: 2px 4px 6px;
}
#picker-search {
  padding: 10px 14px;
  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  width: 100%;
  outline: none;
}
#picker-search[hidden] { display: none; }
#picker-search::placeholder { color: var(--muted); }
#picker-options {
  max-height: 40vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.picker-option {
  display: block;
  width: 100%;
  text-align: left;
  padding: 12px 14px;
  background: transparent;
  color: var(--text);
  border: none;
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;
}
.picker-option:hover {
  background: var(--surface2);
}
.picker-option.selected {
  background: var(--surface2);
  color: var(--primary);
  font-weight: 600;
}
.picker-option:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.picker-more {
  padding: 12px 14px;
  font-size: 13px;
  color: var(--text);
  font-style: italic;
}
/*
  Informational note inside the picker. Not a choice, so rendered as
  static text rather than a button. Sits above the selectable entries
  and is not affected by the search field or the max-render cap.
*/
.picker-note {
  padding: 10px 14px;
  margin-bottom: 6px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--text);
  background: var(--surface2);
  border-radius: 8px;
}
#picker-back {
  margin-top: 6px;
  padding: 10px 14px;
  background: var(--surface2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;
}

/* ---------- Loading ---------- */

#loading-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.5);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  z-index: 1000;
}
#loading-overlay[hidden] { display: none; }
.spinner {
  width: 42px; height: 42px;
  border: 4px solid var(--surface2);
  border-top-color: var(--primary);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
#loading-text { margin-top: 12px; color: #fff; }

/* ---------- Overlays created at runtime ---------- */

/*
  Transient notice. Created on the fly by showNotice() and removed
  after a few seconds. Sits above the menu sheet (z-index 100) so a
  message that originates from the menu is visible, and ignores
  pointer events so it never swallows a tap on the map or a button
  underneath.
*/
.notice {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 12px;
  z-index: 101;
  padding: 10px 14px;
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  font-size: 13px;
  line-height: 1.4;
  box-shadow: 0 2px 8px rgba(0,0,0,0.25);
  pointer-events: none;
}

/*
  Full-screen error overlay for a condition the app cannot recover
  from. Sits above everything, including the menu sheet and the
  loading overlay. There is no dismiss button: the only action is to
  reload.
*/
.fatal-error {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: var(--surface);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
  white-space: pre-wrap;
  font: 14px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ---------- Misc ---------- */

.group-hash {
  font-family: monospace;
  color: var(--text);
  padding: 0 4px;
}
#group-change-btn {
  margin-left: 8px;
}

.sharing-explanation {
  margin-top: 6px;
  font-size: 13px;
  color: var(--text);
  line-height: 1.4;
}

.server-stats {
  margin-top: 8px;
  font-size: 13px;
  color: var(--text);
}
.server-stats[hidden] { display: none; }

/*
  Tab buttons in presence mode. The browser's default [hidden] rule
  hides them, but the .tab-btn rule sets flex: 1, so we make the
  intent explicit and avoid any cascade surprise.
*/
.tab-btn[hidden] {
    display: none;
}

/*
  Presence-mode label. Fills the space the three tab buttons would
  occupy, in the same flex row. Purely informational; it carries no
  click handler.
*/
.tab-presence-label {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    padding: 10px 16px;
    color: var(--text);
    font-size: 14px;
    font-weight: 600;
}
.tab-presence-label[hidden] {
    display: none;
}