# Map Monitor

> A map for watching live events. Region presets jump between places, a time-range filter narrows to recent events, and an alerts panel lists them by severity in step with the pins.

Source: https://docs.nasaqui.com/components/map-monitor

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/map-monitor.json
```

An operations map: incidents, outages or warnings on a `MapView`. People jump between regions you define, narrow to
the last day or week, and read the alerts as a list ordered by severity. Choosing an alert in the list selects its pin
and centres the map on it.

## When to use

- Dispatch, field operations, security or service monitoring, where place and recency both matter.

## When not to use

- Just pins, routes or areas: use `MapView`.
- Alerts with no location: use a list or `ActivityFeed`.

## Import

```tsx
import { MapMonitor, type MapAlert, type MapRegionPreset } from "@fadymondy/nasaq";
```

## Quick start

```tsx
const regions: MapRegionPreset[] = [
  { id: "ksa", label: "Saudi Arabia", labelAr: "السعودية", center: { lat: 23.9, lng: 45.1 }, zoom: 5 },
  { id: "riyadh", label: "Riyadh", labelAr: "الرياض", center: { lat: 24.71, lng: 46.67 }, zoom: 10 },
];

<MapMonitor
  regions={regions}
  alerts={alerts} // { id, lat, lng, title, severity, at, detail? }
  defaultRange="24h"
  onVisibleAlertsChange={(list) => setBadge(list.length)}
  tileUrl={tiles}
  attribution="© OpenStreetMap"
/>;
```

## Anatomy

- `data-slot="map-monitor"`: a `section` named by its heading.
- Header: heading, the region toggle group, the time-range toggle group, the Alerts button (count badge, controls the
  panel), your `actions`.
- The `MapView`, with alerts drawn as pins (tone and icon by severity, the time in the card).
- `map-monitor-alerts`: an `aside` with counts per severity and the list (`map-monitor-alert`, `data-severity`).
  It sits beside the map on large screens and below it on small ones.

## API

Every `MapView` prop passes through (pins, routes, areas, layers, tiles, actions, clustering and so on), except
`view`, which the monitor drives.

| Prop | Type | Notes |
| --- | --- | --- |
| `alerts` | `MapAlert[]` | `{ id, lat, lng, title, titleAr?, severity?, at, detail?, detailAr?, layer? }`. `severity` defaults to `medium`; `at` is a Date, epoch ms or ISO string. Ids must not clash with `pins`. |
| `regions` | `MapRegionPreset[]` | `{ id, label, labelAr?, center, zoom }`. |
| `defaultRegion`, `onRegionChange` | `string` | A region stays highlighted while the map sits on it. |
| `timeRanges` | `MapTimeRange[]` | `{ id, ms }`, `ms: null` for all. Default 24h, 7d, 30d, all. |
| `range`, `defaultRange`, `onRangeChange` | `string` | Default the "all" range. |
| `onVisibleAlertsChange` | `(alerts) => void` | The alerts left after the time filter. |
| `alertsOpen`, `defaultAlertsOpen`, `onAlertsOpenChange` | `boolean` | The panel. Default open. |
| `selectedId`, `defaultSelectedId`, `onSelect` | `string \| null` | Shared by pins and the list. |
| `now` | `number` | "Now" for the filter and relative times. |
| `title`, `headingAs`, `actions` | | `title={null}` hides the heading. Default `h2`. |
| `mapClassName` | `string` | The map's size. Default `h-[30rem]`. |
| `labels` | `Partial<MapMonitorLabels>` | `ranges` names custom range ids; `severity` renames levels. |

### Helpers

From `map-monitor-logic.ts`, pure: `filterMapAlerts(alerts, ms, now)` keeps the alerts in a range, most severe then
newest first (an unreadable time only shows under "all"); `countMapAlerts` counts per severity; `mapAlertTime`,
`mapAlertSeverity`, `isMapRegionView`; `MAP_ALERT_SEVERITIES`, `DEFAULT_MAP_TIME_RANGES`. Types `MapAlert`,
`MapAlertSeverity`, `MapRegionPreset`, `MapTimeRange`.

## Accessibility

- Regions and time ranges are toggle groups with names ("Region", "Time range").
- The Alerts button says whether it shows or hides the panel and how many alerts there are, and has `aria-expanded`
  and `aria-controls`.
- Severity is never colour alone: each alert has an icon and the severity in words, and the count badges carry the
  level as screen-reader text. The chosen alert has `aria-current`.
- Everything the map offers (keyboard pan and zoom, pin cards) comes from `MapView`.

## RTL & i18n

English and Arabic strings are built in, and `titleAr`, `detailAr` and `labelAr` are used in Arabic. Relative times
and counts use the locale. The severity rule sits on the inline start and the panel moves to the inline end, so both
mirror.

## Styling & tokens

`bg-card`, `border-border`; severity uses `nq-danger`, `nq-warning`, `nq-info` and neutral, the same tones as the
pins. The chosen alert uses `bg-nq-selected`.

## Do / Don't

- Do set `now` from your server clock when it matters, so the filter and the times agree with the data.
- Do keep the region list short (up to six); add a search for more.
- Don't reuse an alert id as a pin id.

## Related

`map-view`, `heatmap`, `activity-feed`, `alert`.

## Lab

Charts & Maps › Map Monitor: Default, Closed panel, Empty, Arabic.

## Code

### React

```tsx
const regions: MapRegionPreset[] = [
  { id: "ksa", label: "Saudi Arabia", labelAr: "السعودية", center: { lat: 23.9, lng: 45.1 }, zoom: 5 },
  { id: "riyadh", label: "Riyadh", labelAr: "الرياض", center: { lat: 24.71, lng: 46.67 }, zoom: 10 },
];

<MapMonitor
  regions={regions}
  alerts={alerts} // { id, lat, lng, title, severity, at, detail? }
  defaultRange="24h"
  onVisibleAlertsChange={(list) => setBadge(list.length)}
  tileUrl={tiles}
  attribution="© OpenStreetMap"
/>;
```

### shadcn

```tsx
const regions: MapRegionPreset[] = [
  { id: "ksa", label: "Saudi Arabia", labelAr: "السعودية", center: { lat: 23.9, lng: 45.1 }, zoom: 5 },
  { id: "riyadh", label: "Riyadh", labelAr: "الرياض", center: { lat: 24.71, lng: 46.67 }, zoom: 10 },
];

<MapMonitor
  regions={regions}
  alerts={alerts} // { id, lat, lng, title, severity, at, detail? }
  defaultRange="24h"
  onVisibleAlertsChange={(list) => setBadge(list.length)}
  tileUrl={tiles}
  attribution="© OpenStreetMap"
/>;
```

### Vue

```vue
<script setup lang="ts">
import { NqMapMonitor, type MapAlert, type MapRegionPreset } from "@fadymondy/nasaq/vue";

const now = Date.now();
const HOUR = 3_600_000;
const regions: MapRegionPreset[] = [
  { id: "ksa", label: "Saudi Arabia", labelAr: "السعودية", center: { lat: 23.9, lng: 45.1 }, zoom: 5 },
  { id: "riyadh", label: "Riyadh", labelAr: "الرياض", center: { lat: 24.71, lng: 46.67 }, zoom: 10 },
];
const alerts: MapAlert[] = [
  { id: "a1", lat: 24.71, lng: 46.68, title: "Road closed", titleAr: "طريق مغلق", severity: "critical", at: now - HOUR, detail: "King Fahd Rd" },
  { id: "a2", lat: 24.77, lng: 46.74, title: "Power outage", titleAr: "انقطاع الكهرباء", severity: "high", at: now - 5 * HOUR },
  { id: "a3", lat: 21.54, lng: 39.17, title: "Delayed shipment", titleAr: "شحنة متأخرة", severity: "low", at: now - 72 * HOUR },
];
</script>

<template>
  <NqMapMonitor :regions="regions" :alerts="alerts" default-range="7d" :now="now" class="max-w-5xl" />
</template>
```

### Blade

```blade
<x-nq::map-monitor
    :regions="[
        ['id' => 'ksa', 'label' => 'Saudi Arabia', 'labelAr' => 'السعودية', 'center' => ['lat' => 23.9, 'lng' => 45.1], 'zoom' => 5],
        ['id' => 'riyadh', 'label' => 'Riyadh', 'labelAr' => 'الرياض', 'center' => ['lat' => 24.71, 'lng' => 46.67], 'zoom' => 10],
    ]"
    :alerts="[
        ['id' => 'a1', 'lat' => 24.71, 'lng' => 46.68, 'title' => 'Road closed', 'titleAr' => 'طريق مغلق', 'severity' => 'critical', 'at' => now()->subHour(), 'detail' => 'King Fahd Rd', 'detailAr' => 'طريق الملك فهد'],
        ['id' => 'a2', 'lat' => 24.77, 'lng' => 46.74, 'title' => 'Power outage', 'titleAr' => 'انقطاع الكهرباء', 'severity' => 'high', 'at' => now()->subHours(5)],
        ['id' => 'a3', 'lat' => 21.54, 'lng' => 39.17, 'title' => 'Delayed shipment', 'titleAr' => 'شحنة متأخرة', 'severity' => 'low', 'at' => now()->subHours(72)],
    ]"
    default-range="7d"
    class="max-w-5xl" />
```

### HTML + Alpine

```html
<section data-slot="map-monitor" x-data="nqMapMonitor(JSON.parse('{\u0022alerts\u0022:[{\u0022id\u0022:\u0022a1\u0022,\u0022lat\u0022:24.71,\u0022lng\u0022:46.68,\u0022title\u0022:\u0022Road closed\u0022,\u0022titleAr\u0022:\u0022\\u0637\\u0631\\u064a\\u0642 \\u0645\\u063a\\u0644\\u0642\u0022,\u0022severity\u0022:\u0022critical\u0022,\u0022at\u0022:1790668800000,\u0022detail\u0022:\u0022King Fahd Rd\u0022,\u0022detailAr\u0022:\u0022\\u0637\\u0631\\u064a\\u0642 \\u0627\\u0644\\u0645\\u0644\\u0643 \\u0641\\u0647\\u062f\u0022},{\u0022id\u0022:\u0022a2\u0022,\u0022lat\u0022:24.77,\u0022lng\u0022:46.74,\u0022title\u0022:\u0022Power outage\u0022,\u0022titleAr\u0022:\u0022\\u0627\\u0646\\u0642\\u0637\\u0627\\u0639 \\u0627\\u0644\\u0643\\u0647\\u0631\\u0628\\u0627\\u0621\u0022,\u0022severity\u0022:\u0022high\u0022,\u0022at\u0022:1790654400000},{\u0022id\u0022:\u0022a3\u0022,\u0022lat\u0022:21.54,\u0022lng\u0022:39.17,\u0022title\u0022:\u0022Delayed shipment\u0022,\u0022titleAr\u0022:\u0022\\u0634\\u062d\\u0646\\u0629 \\u0645\\u062a\\u0623\\u062e\\u0631\\u0629\u0022,\u0022severity\u0022:\u0022low\u0022,\u0022at\u0022:1790413200000}],\u0022pins\u0022:[],\u0022regions\u0022:[{\u0022id\u0022:\u0022ksa\u0022,\u0022label\u0022:\u0022Saudi Arabia\u0022,\u0022labelAr\u0022:\u0022\\u0627\\u0644\\u0633\\u0639\\u0648\\u062f\\u064a\\u0629\u0022,\u0022center\u0022:{\u0022lat\u0022:23.9,\u0022lng\u0022:45.1},\u0022zoom\u0022:5},{\u0022id\u0022:\u0022riyadh\u0022,\u0022label\u0022:\u0022Riyadh\u0022,\u0022labelAr\u0022:\u0022\\u0627\\u0644\\u0631\\u064a\\u0627\\u0636\u0022,\u0022center\u0022:{\u0022lat\u0022:24.71,\u0022lng\u0022:46.67},\u0022zoom\u0022:10}],\u0022defaultRange\u0022:\u00227d\u0022,\u0022defaultAlertsOpen\u0022:true,\u0022now\u0022:1790672400000,\u0022locale\u0022:\u0022en\u0022,\u0022labels\u0022:{\u0022title\u0022:\u0022Live map\u0022,\u0022regions\u0022:\u0022Region\u0022,\u0022timeRange\u0022:\u0022Time range\u0022,\u0022ranges\u0022:{\u002224h\u0022:\u002224h\u0022,\u00227d\u0022:\u00227 days\u0022,\u002230d\u0022:\u002230 days\u0022,\u0022all\u0022:\u0022All\u0022},\u0022alerts\u0022:\u0022Alerts\u0022,\u0022alertsList\u0022:\u0022Current alerts\u0022,\u0022showAlerts\u0022:\u0022Show alerts\u0022,\u0022hideAlerts\u0022:\u0022Hide alerts\u0022,\u0022noAlerts\u0022:\u0022No alerts in this time range.\u0022,\u0022when\u0022:\u0022When\u0022,\u0022severity\u0022:{\u0022critical\u0022:\u0022Critical\u0022,\u0022high\u0022:\u0022High\u0022,\u0022medium\u0022:\u0022Medium\u0022,\u0022low\u0022:\u0022Low\u0022},\u0022summary\u0022:\u0022{n} alerts\u0022},\u0022icons\u0022:{\u0022critical\u0022:\u0022\\u003Csvg data-slot=\\u0022icon\\u0022 aria-hidden=\\u0022true\\u0022 xmlns=\\u0022http:\\\/\\\/www.w3.org\\\/2000\\\/svg\\u0022 viewBox=\\u00220 0 24 24\\u0022 fill=\\u0022none\\u0022 stroke=\\u0022currentColor\\u0022 stroke-width=\\u00222\\u0022 stroke-linecap=\\u0022round\\u0022 stroke-linejoin=\\u0022round\\u0022\\u003E\\n  \\u003Cpath d=\\u0022M12 16h.01\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M12 8v4\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z\\u0022\\\/\\u003E\\n\\u003C\\\/svg\\u003E\u0022,\u0022high\u0022:\u0022\\u003Csvg data-slot=\\u0022icon\\u0022 aria-hidden=\\u0022true\\u0022 xmlns=\\u0022http:\\\/\\\/www.w3.org\\\/2000\\\/svg\\u0022 viewBox=\\u00220 0 24 24\\u0022 fill=\\u0022none\\u0022 stroke=\\u0022currentColor\\u0022 stroke-width=\\u00222\\u0022 stroke-linecap=\\u0022round\\u0022 stroke-linejoin=\\u0022round\\u0022\\u003E\\n  \\u003Cpath d=\\u0022m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M12 9v4\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M12 17h.01\\u0022\\\/\\u003E\\n\\u003C\\\/svg\\u003E\u0022,\u0022medium\u0022:\u0022\\u003Csvg data-slot=\\u0022icon\\u0022 aria-hidden=\\u0022true\\u0022 xmlns=\\u0022http:\\\/\\\/www.w3.org\\\/2000\\\/svg\\u0022 viewBox=\\u00220 0 24 24\\u0022 fill=\\u0022none\\u0022 stroke=\\u0022currentColor\\u0022 stroke-width=\\u00222\\u0022 stroke-linecap=\\u0022round\\u0022 stroke-linejoin=\\u0022round\\u0022\\u003E\\n  \\u003Ccircle cx=\\u002212\\u0022 cy=\\u002212\\u0022 r=\\u002210\\u0022\\\/\\u003E\\n  \\u003Cline x1=\\u002212\\u0022 x2=\\u002212\\u0022 y1=\\u00228\\u0022 y2=\\u002212\\u0022\\\/\\u003E\\n  \\u003Cline x1=\\u002212\\u0022 x2=\\u002212.01\\u0022 y1=\\u002216\\u0022 y2=\\u002216\\u0022\\\/\\u003E\\n\\u003C\\\/svg\\u003E\u0022,\u0022low\u0022:\u0022\\u003Csvg data-slot=\\u0022icon\\u0022 aria-hidden=\\u0022true\\u0022 xmlns=\\u0022http:\\\/\\\/www.w3.org\\\/2000\\\/svg\\u0022 viewBox=\\u00220 0 24 24\\u0022 fill=\\u0022none\\u0022 stroke=\\u0022currentColor\\u0022 stroke-width=\\u00222\\u0022 stroke-linecap=\\u0022round\\u0022 stroke-linejoin=\\u0022round\\u0022\\u003E\\n  \\u003Ccircle cx=\\u002212\\u0022 cy=\\u002212\\u0022 r=\\u002210\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M12 16v-4\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M12 8h.01\\u0022\\\/\\u003E\\n\\u003C\\\/svg\\u003E\u0022}}'))" x-id="['nq-map-monitor']"
    x-on:nq-map-view="onMapView($event)" x-on:nq-map-select="onMapSelect($event)"  x-bind:aria-labelledby="uid(`title`)"     class="flex min-w-0 flex-col overflow-hidden rounded-card border border-border bg-card max-w-5xl">
    <header class="flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-border px-4 py-3">
                    <h2 x-bind:id="uid(`title`)" class="me-auto text-h4 text-foreground">Live map</h2>
                            <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup([], false)" x-modelable="value" x-bind="root"
            aria-label="Region" x-model="regionValue" class="flex w-fit max-w-full gap-0.5 rounded-control bg-secondary p-0.5">
    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('ksa')"             data-region="ksa" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Saudi Arabia</button>
                                    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('riyadh')"             data-region="riyadh" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Riyadh</button>
</div>
                            <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u00227d\u0022]'), false)" x-modelable="value" x-bind="root"
            aria-label="Time range" x-model="rangeValue" class="flex w-fit max-w-full gap-0.5 rounded-control bg-secondary p-0.5">
    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('24h')"             data-range="24h" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">24h</button>
                                    <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('7d')"      data-pressed         data-range="7d" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">7 days</button>
                                    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('30d')"             data-range="30d" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">30 days</button>
                                    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('all')"             data-range="all" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">All</button>
</div>
                <button data-slot="button"
     type="button"                         x-bind:aria-expanded="open ? `true` : `false`" x-bind:aria-controls="uid(`alerts`)" x-bind:aria-label="alertsLabel()" x-on:click="toggleAlerts()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M10.268 21a2 2 0 0 0 3.464 0"/>
  <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/>
</svg>            Alerts
            <span data-slot="badge"  style="display: none"     x-show="badgeIs(`danger`)" x-text="totalText()" class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-danger/40 bg-nq-danger-soft text-nq-danger-text"></span>
            <span data-slot="badge"  style="display: none"     x-show="badgeIs(`warning`)" x-text="totalText()" class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-warning/40 bg-nq-warning-soft text-nq-warning-text"></span>
            <span data-slot="badge"  style="display: none"     x-show="badgeIs(`neutral`)" x-text="totalText()" class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border bg-secondary text-foreground"></span></button>
    </header>
    <div class="flex min-w-0 flex-col lg:flex-row">
        <div x-data="nqMapView(JSON.parse('{\u0022pins\u0022:[],\u0022routes\u0022:[],\u0022areas\u0022:[],\u0022layers\u0022:[],\u0022minZoom\u0022:1,\u0022maxZoom\u0022:19,\u0022clusterRadius\u0022:60,\u0022editTone\u0022:\u0022brand\u0022,\u0022locale\u0022:\u0022en\u0022,\u0022labels\u0022:{\u0022map\u0022:\u0022Map\u0022,\u0022layers\u0022:\u0022Layers\u0022,\u0022routes\u0022:\u0022Routes\u0022,\u0022areas\u0022:\u0022Areas\u0022,\u0022zoomIn\u0022:\u0022Zoom in\u0022,\u0022zoomOut\u0022:\u0022Zoom out\u0022,\u0022fit\u0022:\u0022Show everything\u0022,\u0022close\u0022:\u0022Close\u0022,\u0022copyCoordinates\u0022:\u0022Copy coordinates\u0022,\u0022coordinates\u0022:\u0022Coordinates\u0022,\u0022hint\u0022:\u0022Drag to move. Use the arrow keys to pan, plus and minus to zoom, Home to show everything.\u0022,\u0022routeLength\u0022:\u0022{km} km\u0022,\u0022showLayer\u0022:\u0022Show {name}\u0022,\u0022noPins\u0022:\u0022Nothing to show on the map\u0022,\u0022selected\u0022:\u0022{name}, selected\u0022,\u0022clusterZoom\u0022:\u0022{n}, zoom in\u0022,\u0022clusterList\u0022:\u0022{n}, show the list\u0022,\u0022clusterHint\u0022:\u0022These pins share one spot. Choose one to see it.\u0022,\u0022pickedPoint\u0022:\u0022Chosen location\u0022,\u0022pickedHint\u0022:\u0022Drag to move. Arrow keys nudge it, Shift for bigger steps.\u0022,\u0022vertex\u0022:\u0022Point {n}\u0022,\u0022vertexHint\u0022:\u0022Drag to move. Arrow keys nudge it. Delete removes it.\u0022,\u0022shape\u0022:\u0022Shape being edited\u0022,\u0022removeLast\u0022:\u0022Remove last point\u0022,\u0022finishShape\u0022:\u0022Finish shape\u0022,\u0022editHint\u0022:\u0022Click the map, or press Enter, to add a point.\u0022},\u0022classes\u0022:{\u0022pin\u0022:{\u0022brand\u0022:\u0022border-primary bg-primary text-primary-foreground\u0022,\u0022info\u0022:\u0022border-nq-info bg-nq-info-soft text-nq-info-text\u0022,\u0022success\u0022:\u0022border-nq-success bg-nq-success-soft text-nq-success-text\u0022,\u0022warning\u0022:\u0022border-nq-warning bg-nq-warning-soft text-nq-warning-text\u0022,\u0022danger\u0022:\u0022border-nq-danger bg-nq-danger-soft text-nq-danger-text\u0022,\u0022neutral\u0022:\u0022border-nq-line-strong bg-secondary text-foreground\u0022},\u0022stroke\u0022:{\u0022brand\u0022:\u0022stroke-primary\u0022,\u0022info\u0022:\u0022stroke-nq-info\u0022,\u0022success\u0022:\u0022stroke-nq-success\u0022,\u0022warning\u0022:\u0022stroke-nq-warning\u0022,\u0022danger\u0022:\u0022stroke-nq-danger\u0022,\u0022neutral\u0022:\u0022stroke-muted-foreground\u0022},\u0022fill\u0022:{\u0022brand\u0022:\u0022fill-primary\\\/15\u0022,\u0022info\u0022:\u0022fill-nq-info\\\/15\u0022,\u0022success\u0022:\u0022fill-nq-success\\\/15\u0022,\u0022warning\u0022:\u0022fill-nq-warning\\\/15\u0022,\u0022danger\u0022:\u0022fill-nq-danger\\\/15\u0022,\u0022neutral\u0022:\u0022fill-muted-foreground\\\/15\u0022},\u0022swatch\u0022:{\u0022brand\u0022:\u0022bg-primary\u0022,\u0022info\u0022:\u0022bg-nq-info\u0022,\u0022success\u0022:\u0022bg-nq-success\u0022,\u0022warning\u0022:\u0022bg-nq-warning\u0022,\u0022danger\u0022:\u0022bg-nq-danger\u0022,\u0022neutral\u0022:\u0022bg-muted-foreground\u0022},\u0022badge\u0022:{\u0022brand\u0022:\u0022border-nq-brand\\\/40 bg-[color-mix(in_oklab,var(--nq-brand)_14%,transparent)] text-foreground\u0022,\u0022info\u0022:\u0022border-nq-info\\\/40 bg-nq-info-soft text-nq-info-text\u0022,\u0022success\u0022:\u0022border-nq-success\\\/40 bg-nq-success-soft text-nq-success-text\u0022,\u0022warning\u0022:\u0022border-nq-warning\\\/40 bg-nq-warning-soft text-nq-warning-text\u0022,\u0022danger\u0022:\u0022border-nq-danger\\\/40 bg-nq-danger-soft text-nq-danger-text\u0022,\u0022neutral\u0022:\u0022border-border bg-secondary text-foreground\u0022}},\u0022icons\u0022:{\u0022default\u0022:\u0022\\u003Csvg data-slot=\\u0022icon\\u0022 aria-hidden=\\u0022true\\u0022 xmlns=\\u0022http:\\\/\\\/www.w3.org\\\/2000\\\/svg\\u0022 viewBox=\\u00220 0 24 24\\u0022 fill=\\u0022none\\u0022 stroke=\\u0022currentColor\\u0022 stroke-width=\\u00222\\u0022 stroke-linecap=\\u0022round\\u0022 stroke-linejoin=\\u0022round\\u0022\\u003E\\n  \\u003Cpath d=\\u0022M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0\\u0022\\\/\\u003E\\n  \\u003Ccircle cx=\\u002212\\u0022 cy=\\u002210\\u0022 r=\\u00223\\u0022\\\/\\u003E\\n\\u003C\\\/svg\\u003E\u0022}}'))" x-ref="canvas" dir="ltr" role="group" aria-roledescription="map" aria-label="Live map" tabindex="0" data-slot="map-view" title="Drag to move. Use the arrow keys to pan, plus and minus to zoom, Home to show everything."
    x-bind:data-dragging="drag ? '' : null" x-bind:class="drag ? 'cursor-grabbing' : 'cursor-grab'"
    x-on:keydown="keyDown($event)" x-on:pointerdown="pointerDown($event)" x-on:pointermove="pointerMove($event)" x-on:pointerup="pointerUp($event)" x-on:pointercancel="pointerUp($event)" x-on:dblclick="dblClick($event)"
    class="relative isolate min-h-64 w-full touch-none select-none overflow-hidden border-border bg-secondary outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus h-[30rem] min-w-0 flex-1 rounded-none border-0">
    <div aria-hidden="true" class="pointer-events-none absolute inset-0 overflow-hidden" x-show="tiles.length > 0" style="display: none">
        <template x-for="tile in tiles" :key="tile.key">
            <img x-bind:src="tile.src" alt="" draggable="false" referrerpolicy="no-referrer" class="absolute max-w-none" x-bind:style="tile.style" x-on:error="$event.target.style.visibility = 'hidden'">
        </template>
    </div>
    <svg aria-hidden="true" data-slot="map-grid" class="pointer-events-none absolute inset-0 size-full stroke-border" x-html="gridSvg"></svg>
    <svg aria-hidden="true" data-slot="map-areas" class="pointer-events-none absolute inset-0 size-full" x-html="areasSvg"></svg>
    <svg aria-hidden="true" data-slot="map-routes" class="pointer-events-none absolute inset-0 size-full" x-html="routesSvg"></svg>
    <svg aria-hidden="true" data-slot="map-edit-shape" class="pointer-events-none absolute inset-0 size-full" x-html="editSvg"></svg>
    <template x-for="c in clusterItems" :key="c.key">
        <button type="button" data-map-control x-bind:data-cluster="c.id" x-bind:data-count="c.count" x-bind:aria-label="str(view.zoom >= clusterMax ? 'clusterList' : 'clusterZoom', { n: countText(c.count) })"
            x-on:click="expand(c)" x-bind:style="`left:${c.x}px;top:${c.y}px;width:${c.d}px;height:${c.d}px`"
            class="absolute z-10 flex -translate-x-1/2 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full border-2 border-card bg-primary text-label tabular-nums text-primary-foreground shadow-md ring-4 ring-primary/25 outline-none transition-transform duration-150 ease-nq hover:scale-105 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus">
            <bdi x-text="c.count"></bdi>
        </button>
    </template>
    <template x-for="item in pinItems" :key="item.key">
        <button type="button" data-map-control x-bind:data-pin="item.pin.id" x-bind:data-selected="item.pin.id === selectedId ? '' : null" x-bind:data-live="item.pin.live ? '' : null"
            x-bind:aria-label="item.pin.id === selectedId ? str('selected', { name: name(item.pin) }) : name(item.pin)" x-bind:aria-pressed="String(item.pin.id === selectedId)"
            x-on:click="pinClick(item.pin)" x-bind:style="`left:${item.x}px;top:${item.y}px`"
            x-bind:class="(item.pin.id === selectedId ? 'z-20' : 'z-10') + (item.pin.live && !drag ? ' motion-safe:transition-[left,top] motion-safe:duration-1000 motion-safe:ease-linear' : '')"
            class="absolute flex -translate-x-1/2 -translate-y-full cursor-pointer flex-col items-center outline-none focus-visible:[&>span:first-child]:outline-2 focus-visible:[&>span:first-child]:outline-offset-2 focus-visible:[&>span:first-child]:outline-nq-focus">
            <span x-bind:class="(item.pin.id === selectedId ? 'size-10 scale-110 ring-4 ring-nq-focus/30 ' : 'size-8 ') + cls('pin', item.pin.tone)" x-html="icon(item.pin)"
                class="flex items-center justify-center rounded-full border-2 shadow-sm transition-transform duration-150 ease-nq [&_svg]:size-4"></span>
            <span x-show="item.pin.live" aria-hidden="true" x-bind:class="(item.pin.id === selectedId ? 'size-10 ' : 'size-8 ') + cls('swatch', item.pin.tone)" class="pointer-events-none absolute top-0 rounded-full opacity-25 motion-safe:animate-ping" style="display: none"></span>
            <span aria-hidden="true" x-bind:class="cls('swatch', item.pin.tone)" class="-mt-0.5 h-2 w-0.5"></span>
            <bdi dir="auto" x-show="item.pin.id === selectedId" x-text="name(item.pin)" class="pointer-events-none absolute top-full mt-0.5 max-w-40 truncate rounded-[4px] border border-border bg-card px-1.5 text-caption text-foreground shadow-sm" style="display: none"></bdi>
        </button>
    </template>
    <template x-for="v in vertices" :key="v.i">
        <button type="button" data-map-control x-bind:data-vertex="v.i" x-bind:aria-label="str('vertex', { n: v.i + 1 })" title="Drag to move. Arrow keys nudge it. Delete removes it." x-bind:style="`left:${v.x}px;top:${v.y}px`"
            x-bind:class="cls('swatch', editTone)" x-on:pointerdown="handleDown($event, v.x, v.y)" x-on:pointermove="handleMove($event, 'vertex', v.i)" x-on:pointerup="handleUp($event)" x-on:pointercancel="grab = null" x-on:keydown="handleKey($event, 'vertex', v.i, v.x, v.y)"
            class="absolute z-20 flex cursor-move touch-none items-center justify-center outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-card shadow-sm"></button>
    </template>
    <button type="button" data-map-control data-picked-point aria-label="Chosen location" title="Drag to move. Arrow keys nudge it, Shift for bigger steps." x-show="picked && ready" x-bind:style="`left:${pickedAt.x}px;top:${pickedAt.y}px`"
        x-on:pointerdown="handleDown($event, pickedAt.x, pickedAt.y)" x-on:pointermove="handleMove($event, 'picked', 0)" x-on:pointerup="handleUp($event)" x-on:pointercancel="grab = null" x-on:keydown="handleKey($event, 'picked', 0, pickedAt.x, pickedAt.y)"
        class="absolute z-20 flex cursor-move touch-none items-center justify-center outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus -translate-x-1/2 -translate-y-full flex-col" style="display: none">
        <span class="flex size-10 items-center justify-center rounded-full border-2 border-primary bg-primary text-primary-foreground shadow-md ring-4 ring-nq-focus/30 [&_svg]:size-5"><svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/>
  <circle cx="12" cy="10" r="3"/>
</svg></span>
        <span aria-hidden="true" class="-mt-0.5 h-2 w-0.5 bg-primary"></span>
    </button>
    <p x-show="empty" class="absolute inset-x-0 top-1/2 -translate-y-1/2 text-center text-body-sm text-muted-foreground" style="display: none">Nothing to show on the map</p>
            <div data-map-control data-slot="map-legend" dir="ltr" x-show="hasLegend" x-init="legendOpen = false" class="absolute start-3 top-3 z-30 flex max-w-[calc(100%-6rem)] cursor-default flex-col items-start gap-1.5" style="display: none">
            <button data-slot="button"
     type="button"                         x-bind:aria-expanded="String(legendOpen)" x-on:click="legendOpen = !legendOpen" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5 shadow-sm">
        <svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/>
  <path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"/>
  <path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"/>
</svg>Layers</button>
            <div x-show="legendOpen" class="flex w-56 max-w-full flex-col gap-2 rounded-card border border-border bg-card p-3 text-body-sm shadow-md" style="display: none">
                <ul role="list" class="flex flex-col gap-1.5" x-show="layers.length > 0" style="display: none">
                    <template x-for="layer in layers" :key="layer.id">
                        <li>
                            <label class="flex cursor-pointer items-center gap-2">
                                <input type="checkbox" x-bind:checked="isOn(layer.id)" x-on:change="toggleLayer(layer.id, $event.target.checked)" x-bind:aria-label="str('showLayer', { name: name(layer) })" class="size-4 shrink-0 cursor-pointer accent-primary">
                                <span aria-hidden="true" x-bind:class="cls('swatch', layer.tone)" class="size-2.5 shrink-0 rounded-full"></span>
                                <span class="min-w-0 flex-1 truncate text-foreground" x-text="name(layer)"></span>
                                <span class="text-caption tabular-nums text-muted-foreground" x-text="layerCount(layer.id)"></span>
                            </label>
                        </li>
                    </template>
                </ul>
                <div class="flex flex-col gap-1.5" x-show="vAreas.length > 0" x-bind:class="layers.length > 0 ? 'border-t border-border pt-2' : ''" style="display: none">
                    <span class="text-caption text-muted-foreground">Areas</span>
                    <ul role="list" class="flex flex-col gap-1.5">
                        <template x-for="area in vAreas" :key="area.id">
                            <li>
                                <button type="button" x-bind:data-area-item="area.id" x-on:click="areaClick(area)" class="flex w-full cursor-pointer items-center gap-2 rounded-control text-start outline-none hover:bg-accent focus-visible:outline-2 focus-visible:outline-nq-focus">
                                    <span aria-hidden="true" x-bind:class="cls('swatch', area.tone)" class="size-3 shrink-0 rounded-[3px] opacity-70"></span>
                                    <span class="min-w-0 flex-1 truncate text-foreground" x-text="name(area)"></span>
                                </button>
                            </li>
                        </template>
                    </ul>
                </div>
                <div class="flex flex-col gap-1.5" x-show="vRoutes.length > 0" x-bind:class="(layers.length > 0 || vAreas.length > 0) ? 'border-t border-border pt-2' : ''" style="display: none">
                    <span class="text-caption text-muted-foreground">Routes</span>
                    <ul role="list" class="flex flex-col gap-1.5">
                        <template x-for="route in vRoutes" :key="route.id">
                            <li class="flex items-center gap-2">
                                <span aria-hidden="true" x-bind:class="cls('swatch', route.tone ?? 'info') + (route.dashed ? ' opacity-60' : '')" class="h-0.5 w-4 shrink-0 rounded-full"></span>
                                <span class="min-w-0 flex-1 truncate text-foreground" x-text="name(route)"></span>
                                <bdi class="text-caption tabular-nums text-muted-foreground" x-text="str('routeLength', { km: fmtKm(route.points) })"></bdi>
                            </li>
                        </template>
                    </ul>
                </div>
            </div>
        </div>
    <div data-map-control dir="ltr" class="absolute end-3 top-3 z-30 flex cursor-default flex-col gap-1.5">
        <button data-slot="button"
     type="button"                         aria-label="Zoom in" title="Zoom in" x-bind:disabled="view.zoom &gt;= maxZoom ? &#039;&#039; : null" x-on:click="zoomBy(1)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover size-control p-0 shadow-sm">
        <svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 12h14"/>
  <path d="M12 5v14"/>
</svg></button>
        <button data-slot="button"
     type="button"                         aria-label="Zoom out" title="Zoom out" x-bind:disabled="view.zoom &lt;= minZoom ? &#039;&#039; : null" x-on:click="zoomBy(-1)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover size-control p-0 shadow-sm">
        <svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 12h14"/>
</svg></button>
        <button data-slot="button"
     type="button"                         aria-label="Show everything" title="Show everything" x-on:click="fit()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover size-control p-0 shadow-sm">
        <svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <line x1="22" x2="18" y1="12" y2="12"/>
  <line x1="6" x2="2" y1="12" y2="12"/>
  <line x1="12" x2="12" y1="6" y2="2"/>
  <line x1="12" x2="12" y1="22" y2="18"/>
</svg></button>
    </div>
    <div data-map-control data-slot="map-edit-toolbar" role="toolbar" aria-label="Shape being edited" dir="ltr" x-show="editing"
        class="absolute inset-x-0 top-3 z-30 mx-auto flex w-fit max-w-[calc(100%-9rem)] cursor-default flex-wrap items-center justify-center gap-1.5 rounded-card border border-border bg-card p-1.5 shadow-md" style="display: none">
        <span class="px-1.5 text-caption text-muted-foreground">Click the map, or press Enter, to add a point.</span>
        <button data-slot="button"
     type="button"                         x-bind:disabled="ring.length === 0 ? &#039;&#039; : null" x-on:click="changeRing(ring.slice(0, -1))" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        <svg data-slot="icon" aria-hidden="true" class="rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M9 14 4 9l5-5"/>
  <path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/>
</svg>Remove last point</button>
        <button data-slot="button"
     type="button"                         x-bind:disabled="ring.length &lt; 3 ? &#039;&#039; : null" x-on:click="done()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control-sm px-2.5">
        <svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg>Finish shape</button>
    </div>
    <div class="pointer-events-none absolute inset-x-3 bottom-2 z-10 flex items-end justify-between gap-3 text-caption text-muted-foreground">
        <span aria-hidden="true" class="flex flex-col items-start gap-0.5" x-bind:class="selected ? 'max-sm:invisible' : ''">
            <span class="h-1.5 border-x border-b border-foreground/60" x-bind:style="`width:${scale.px}px`"></span>
            <span class="tabular-nums" x-text="scaleText"></span>
        </span>
            </div>
    <template x-if="listed.length > 0 && !selected">
        <section data-map-control data-slot="map-cluster-card" x-bind:aria-label="countText(listed.length)" dir="ltr" class="absolute inset-x-3 bottom-8 z-30 flex max-h-[70%] cursor-default flex-col overflow-auto rounded-card border border-border bg-card p-4 shadow-lg sm:inset-x-auto sm:bottom-8 sm:start-3 sm:w-80 gap-2">
            <header class="flex items-start gap-2">
                <div class="flex min-w-0 flex-1 flex-col gap-1">
                    <span class="text-label text-foreground" x-text="countText(listed.length)"></span>
                    <span class="text-body-sm text-muted-foreground">These pins share one spot. Choose one to see it.</span>
                </div>
                <button type="button" aria-label="Close" x-on:click="clusterIds = null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 border-transparent text-foreground hover:bg-nq-hover size-control-sm p-0"><svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
            </header>
            <ul role="list" class="flex flex-col gap-1">
                <template x-for="pin in listed" :key="pin.id">
                    <li>
                        <button type="button" x-bind:data-cluster-pin="pin.id" x-on:click="select(pin.id)" class="flex w-full cursor-pointer items-center gap-2 rounded-control px-2 py-1.5 text-start text-body-sm text-foreground outline-none hover:bg-accent focus-visible:outline-2 focus-visible:outline-nq-focus">
                            <span aria-hidden="true" x-bind:class="cls('pin', pin.tone)" x-html="icon(pin)" class="flex size-6 shrink-0 items-center justify-center rounded-full border [&_svg]:size-3.5"></span>
                            <bdi dir="auto" class="min-w-0 flex-1 truncate" x-text="name(pin)"></bdi>
                        </button>
                    </li>
                </template>
            </ul>
        </section>
    </template>
    <template x-if="selected">
        <section data-map-control data-slot="map-card" x-bind:aria-label="name(selected)" dir="ltr" class="absolute inset-x-3 bottom-8 z-30 flex max-h-[70%] cursor-default flex-col overflow-auto rounded-card border border-border bg-card p-4 shadow-lg sm:inset-x-auto sm:bottom-8 sm:start-3 sm:w-80 gap-3">
            <header class="flex items-start gap-2">
                <div class="flex min-w-0 flex-1 flex-col gap-1">
                    <bdi dir="auto" class="truncate text-label text-foreground" x-text="name(selected)"></bdi>
                    <bdi dir="auto" x-show="selected.detail || selected.detailAr" class="text-body-sm text-muted-foreground" x-text="pick(selected.detail, selected.detailAr)" style="display: none"></bdi>
                </div>
                <span data-slot="badge" x-show="selected.status || selected.statusAr" x-text="pick(selected.status, selected.statusAr)" x-bind:class="cls('badge', selected.tone ?? 'neutral')"
                    class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium" style="display: none"></span>
                <button type="button" aria-label="Close" x-on:click="select(null)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 border-transparent text-foreground hover:bg-nq-hover size-control-sm p-0"><svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
            </header>
            <dl x-show="selected.meta && selected.meta.length > 0" class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-body-sm" style="display: none">
                <template x-for="(m, i) in (selected.meta || [])" :key="i">
                    <div class="contents">
                        <dt class="text-muted-foreground" x-text="pick(m.label, m.labelAr)"></dt>
                        <dd class="min-w-0 text-foreground"><bdi dir="auto" x-text="m.value"></bdi></dd>
                    </div>
                </template>
            </dl>
            <div class="flex items-center gap-1 text-caption text-muted-foreground">
                <span class="sr-only">Coordinates</span>
                <bdi dir="ltr" class="tabular-nums" x-text="coords()"></bdi>
                <button type="button" aria-label="Copy coordinates" title="Copy coordinates" x-on:click="copyCoords()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 border-transparent text-foreground hover:bg-nq-hover size-control-sm p-0"><svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
  <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg></button>
            </div>
            <div x-show="selected.actions && selected.actions.length > 0" class="flex flex-wrap gap-2" style="display: none">
                <template x-for="action in (selected.actions || [])" :key="action.id">
                    <button type="button" x-on:click="runAction(action)" x-text="action.label" x-bind:class="action.danger ? 'border-transparent bg-destructive text-destructive-foreground' : 'border-border bg-card text-foreground hover:bg-nq-hover'"
                        class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_svg]:shrink-0 h-control-sm px-2.5"></button>
                </template>
            </div>
        </section>
    </template>
</div>
        <aside x-bind:id="uid(`alerts`)" data-slot="map-monitor-alerts" x-bind:hidden="! open" x-bind:aria-labelledby="uid(`alerts-title`)"
            class="flex max-h-[30rem] min-w-0 flex-col border-t border-border lg:w-80 lg:border-s lg:border-t-0">
            <div class="flex items-center gap-2 border-b border-border px-4 py-2.5">
                <h3 x-bind:id="uid(`alerts-title`)" class="me-auto text-label text-foreground">Current alerts</h3>
                                    <span data-slot="badge"  style="display: none"     title="Critical" x-show="count(`critical`)" class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-danger/40 bg-nq-danger-soft text-nq-danger-text"><span class="sr-only">Critical: </span><span x-text="countText(`critical`)"></span></span>
                                    <span data-slot="badge"  style="display: none"     title="High" x-show="count(`high`)" class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-warning/40 bg-nq-warning-soft text-nq-warning-text"><span class="sr-only">High: </span><span x-text="countText(`high`)"></span></span>
                                    <span data-slot="badge"  style="display: none"     title="Medium" x-show="count(`medium`)" class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-nq-info/40 bg-nq-info-soft text-nq-info-text"><span class="sr-only">Medium: </span><span x-text="countText(`medium`)"></span></span>
                                    <span data-slot="badge"  style="display: none"     title="Low" x-show="count(`low`)" class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border bg-secondary text-foreground"><span class="sr-only">Low: </span><span x-text="countText(`low`)"></span></span>
                            </div>
            <ul x-show="visible.length" class="flex flex-col overflow-y-auto" style="display: none">
                <template x-for="a in visible" x-bind:key="a.id">
                    <li data-slot="map-monitor-alert" x-bind:data-alert="a.id" x-bind:data-severity="severity(a)">
                        <button type="button" x-bind:aria-current="isSelected(a) ? `true` : null" x-on:click="focusAlert(a)" x-bind:class="rowClass(a)"
                            class="flex w-full items-start gap-2.5 border-b border-s-2 border-b-border px-4 py-3 text-start outline-none transition-colors duration-150 ease-nq hover:bg-nq-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus">
                            <span aria-hidden="true" class="mt-0.5 flex size-4 shrink-0 [&_svg]:size-4" x-bind:class="iconClass(a)" x-html="iconOf(a)"></span>
                            <span class="flex min-w-0 flex-1 flex-col gap-0.5">
                                <span dir="auto" class="line-clamp-2 text-label text-foreground" x-text="titleOf(a)"></span>
                                <span dir="auto" x-show="hasDetail(a)" x-text="detailOf(a)" class="truncate text-caption text-muted-foreground" style="display: none"></span>
                                <span class="text-caption text-muted-foreground" x-text="sub(a)"></span>
                            </span>
                        </button>
                    </li>
                </template>
            </ul>
            <p x-show="visible.length === 0" class="px-4 py-8 text-center text-body-sm text-muted-foreground">No alerts in this time range.</p>
        </aside>
    </div>
</section>
```
