# MapView

> A map of pins and routes without a map library, with layer toggles, a legend, zoom and pan by drag, wheel, buttons or keyboard, a location card and pin clustering; tiles come from a URL template you provide, and without one it draws a coordinate grid.

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

## Install

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

Where things are. `MapView` draws pins and route lines over a base that is either your tile server or a quiet grid of
latitude and longitude lines. It has a legend with layer toggles, zoom and pan, a scale bar and a card for the
selected pin. There is no map library and no key: Nasaq does not ship a tile service, so set `tileUrl` to one you
are allowed to use, and show its credit with `attribution`.

## When to use

- A fleet, delivery, branch or field-team view where place matters more than the numbers.
- Next to a list: select a row and show its pin ([GeoList](https://docs.nasaqui.com/components/geo-list) is the ranked-table cousin).

## When not to use

- Turn-by-turn navigation, geocoding or address search: these need a routing service.
- Tens of thousands of pins: clustering is done in the browser on every zoom, and it is built for hundreds. Filter with layers or cluster on the server first.

## Import

```tsx
import { MapView, type MapPin, type MapLayer, type MapRoute } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { MapView } from "@fadymondy/nasaq/web";
import { Truck } from "lucide-react";

export function Fleet() {
  return (
    <MapView
      label="Fleet"
      layers={[{ id: "vans", label: "Vans", labelAr: "الشاحنات", tone: "info" }]}
      pins={[{ id: "v1", lat: 24.7136, lng: 46.6753, label: "Van 12", labelAr: "شاحنة 12", layer: "vans", tone: "info", icon: Truck, status: "Moving", statusAr: "تتحرك" }]}
      routes={[{ id: "r1", layer: "vans", label: "Route A", points: [{ lat: 24.71, lng: 46.67 }, { lat: 24.75, lng: 46.72 }] }]}
      tileUrl="https://tile.example.com/{z}/{x}/{y}.png"
      attribution="© Example maps"
    />
  );
}
```

## Anatomy

```
MapView                       data-slot="map-view"  dir="ltr"  role="group" (map)
├─ tiles (img) or grid (svg)  base
├─ svg                        areas (data-slot="map-areas"): one polygon each, data-area
├─ svg                        routes: casing + line + start and end dots
├─ button[data-pin]           one per visible pin, in a ContextMenuActions when pinActions is set
├─ button[data-cluster]       a count bubble for pins that are close together (see Clustering)
├─ legend                     layers panel (collapsed by default): toggles, zones, routes with their length
├─ [data-picked-point]        draggable pin while `pickedPoint` is set
├─ svg + [data-vertex]        dashed shape and draggable vertices while `editing` (data-slot="map-edit-shape")
├─ toolbar                    Remove last point, Finish shape (data-slot="map-edit-toolbar")
├─ zoom in, zoom out, fit     buttons at the inline end
├─ scale bar, attribution
├─ section                    location card of the selected pin
└─ section[data-slot=map-cluster-card]   list of the pins in a cluster at the max cluster zoom
```

## API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `pins` | `MapPin[]` | `[]` | `id`, `lat`, `lng`, `label`, `labelAr`, `detail`, `layer`, `tone`, `icon`, `live`, `status`, `meta`. `live` adds a soft pulse and a glide to each new position (a moving driver); the glide is off while dragging and under reduced motion. |
| `routes` | `MapRoute[]` | `[]` | `id`, `points`, `label`, `layer`, `tone`, `dashed`. |
| `areas` | `MapArea[]` | `[]` | Filled polygons for zones: `id`, `points` (a ring of 3 or more, closed for you), `label`, `labelAr`, `layer`, `tone`, `dashed`. Drawn under routes and pins, listed in the legend, included when the map fits. |
| `layers` | `MapLayer[]` | `[]` | `id`, `label`, `labelAr`, `tone`, `icon`, `defaultHidden`. |
| `visibleLayers` / `onVisibleLayersChange` | `string[]` | all shown | Controlled layer visibility. |
| `tileUrl` | `string` | none | Template with `{z}`, `{x}`, `{y}`. Only http, https and site paths are accepted. |
| `attribution` | `ReactNode` | none | Tile provider credit. |
| `selectedId` / `defaultSelectedId` / `onSelect` | `string \| null` | none | Selected pin. |
| `pinActions` | `(pin) => ContextMenuAction[]` | none | Buttons in the card and a context menu on the pin. |
| `renderCard` | `(pin) => ReactNode` | facts from `meta` | Body of the card. |
| `view` / `defaultView` / `onViewChange` | `{ center: {lat, lng}, zoom }` | fits the data | Without one, the map fits every visible pin and route until someone moves it. |
| `minZoom` / `maxZoom` | `number` | `1` / `19` | |
| `cluster` | `boolean` | on above 30 visible pins | Group close pins into count bubbles. Set `true` or `false` to force it. |
| `clusterRadius` | `number` | `60` | Distance in px under which pins share a bubble. |
| `renderCluster` | `(cluster: MapClusterInfo) => ReactNode` | the count | Contents of a bubble: `{ count, pins, lat, lng, size }`. The button, size, focus and click stay. |
| `legend` | `boolean` | `true` | Legacy switch; `false` is the same as `layersPanel="hidden"`. |
| `layersPanel` | `"collapsed" \| "expanded" \| "hidden"` | `"collapsed"` | The layers panel starts as a small button over the map; `expanded` opens it, `hidden` removes it. Behaviour change: it used to start open. |
| `onMapClick` | `(point: {lat, lng}) => void` | none | A tap on empty map (not a drag, pin, area or control). |
| `onAreaClick` | `(area: MapArea) => void` | none | A tap inside a zone, topmost first. Legend zone rows become buttons. |
| `onPinClick` | `(pin: MapPin) => void` | none | Fires on pin activation, next to `onSelect`. |
| `pickedPoint` / `defaultPickedPoint` / `onPickedPointChange` | `{lat, lng} \| null` | none | A draggable pin for choosing a location. Drag it, or focus it and use the arrow keys (Shift for larger steps). |
| `editing` | `boolean` | `false` | Area editing mode: a tap adds a vertex, vertices are draggable, a toolbar offers Remove last point and Finish shape. |
| `editPoints` / `defaultEditPoints` / `onAreaChange` / `onAreaDone` | `MapLatLng[]` | `[]` | The ring being edited. `onAreaChange` fires after every add, move or removal; `onAreaDone` from Finish shape (three points or more). |
| `editTone` | `MapTone` | `"brand"` | Tone of the shape being edited. |
| `label`, `locale`, `labels`, `className` | | | Height comes from `className` (default 28rem). |

### Clustering

Pins are grouped by their distance in px on the same Web Mercator projection the map draws with, so a bubble depends on
the zoom and never on where you have panned to. Zooming in pulls the pins apart and the bubbles split; zooming out
merges them again.

- A bubble shows the number of pins, grows with it (36 to 64 px) and uses the brand action colour.
- Click, or Enter or Space, zooms and centres so the pins of the bubble fit, at least one step in.
- From zoom 16 (`MAP_CLUSTER_MAX_ZOOM`, or `maxZoom` if lower) only pins on the same spot stay together, so pins with
  equal coordinates cannot cluster forever. Activating such a bubble lists its pins in the card; choose one to select it.
- The selected pin is never inside a bubble: it stays a pin, and the bubble count leaves it out.
- Pins that are hidden by a layer are not counted. Routes are not clustered.

```tsx
<MapView pins={manyPins} clusterRadius={80} renderCluster={(c) => <span className="text-caption">{c.count}</span>} />
```

### Geometry helpers (no React)

`mapProject`, `mapUnproject`, `mapToScreen`, `mapFromScreen`, `mapFit`, `mapPanBy`, `mapZoomAt`, `mapDistance`,
`mapPointInPolygon(point, ring)` (even-odd hit test), `mapRouteLength`, `mapFormatCoordinates`, `mapBounds`, and for clustering `mapCluster(pins, zoom, { radius, maxZoom,
selectedId })` and `mapClusterExpand(pins, view, size)` (the view to move to and whether to list the pins).

## Accessibility

| Key | Action |
| --- | --- |
| Arrow keys | Pan (map focused). |
| `+` / `-` | Zoom. |
| Home or `0` | Show everything. |
| Tab | Moves through the pins and cluster bubbles. Enter or Space selects a pin, or zooms into a bubble. |
| Escape | Clears the selection. |
| Arrow keys, Shift+arrows (pin or vertex focused) | Move the picked pin or a vertex by 8 px, or 32 px with Shift. |
| Delete, Backspace (vertex focused) | Removes the vertex. |
| Enter (map focused, `editing`) | Adds a vertex at the centre of the view. |
| Shift+F10, Menu key | Opens the pin's menu. |

- Every pin is a button named by its label; the selected one says so and has `aria-pressed`.
- Every bubble is a button named like "12 pins, zoom in" ("12 pins, show the list" at the max cluster zoom).
- Everything the map shows is also in the legend (counts, routes with length) and the card, as text.
- Zoom buttons and the fit button work without a pointer; wheel zoom only adds a shortcut.

## RTL & i18n

- The canvas is always `dir="ltr"`: a map does not mirror. The legend, the card and the buttons follow the page direction, and the buttons swap sides.
- Coordinates and lengths are Latin digits, isolated with `bdi`.
- Pins carry `labelAr`, `detailAr` and `statusAr`.

## Styling & tokens

Bubbles use the brand action colour with a card-colour border and a soft ring. Status tones use `nq-info`, `nq-success`, `nq-warning`, `nq-danger` and the brand action colour; route casing uses the card colour so lines stay readable on dark tiles.

## Do / Don't

- Do show the tile provider's credit.
- Do put the same information in a list next to the map for people who cannot use a pointer.
- Do keep a list next to a clustered map: a bubble is a shortcut to the pins, not the only way to them.
- Do not use colour alone: pins carry an icon and the card carries a status word.
- Do not point `tileUrl` at a service whose terms forbid your traffic.

## Related

- [GeoList](https://docs.nasaqui.com/components/geo-list)
- [EntityList](https://docs.nasaqui.com/components/entity-list)
- [ContextMenu](https://docs.nasaqui.com/components/context-menu)

## Lab

https://docs.nasaqui.com/?path=/docs/components-charts-maps-map-view--docs

## Code

### React

```tsx
import { MapView } from "@fadymondy/nasaq/web";
import { Truck } from "lucide-react";

export function Fleet() {
  return (
    <MapView
      label="Fleet"
      layers={[{ id: "vans", label: "Vans", labelAr: "الشاحنات", tone: "info" }]}
      pins={[{ id: "v1", lat: 24.7136, lng: 46.6753, label: "Van 12", labelAr: "شاحنة 12", layer: "vans", tone: "info", icon: Truck, status: "Moving", statusAr: "تتحرك" }]}
      routes={[{ id: "r1", layer: "vans", label: "Route A", points: [{ lat: 24.71, lng: 46.67 }, { lat: 24.75, lng: 46.72 }] }]}
      tileUrl="https://tile.example.com/{z}/{x}/{y}.png"
      attribution="© Example maps"
    />
  );
}
```

### shadcn

```tsx
import { MapView } from "@/components/ui/map-view";
import { Truck } from "lucide-react";

export function Fleet() {
  return (
    <MapView
      label="Fleet"
      layers={[{ id: "vans", label: "Vans", labelAr: "الشاحنات", tone: "info" }]}
      pins={[{ id: "v1", lat: 24.7136, lng: 46.6753, label: "Van 12", labelAr: "شاحنة 12", layer: "vans", tone: "info", icon: Truck, status: "Moving", statusAr: "تتحرك" }]}
      routes={[{ id: "r1", layer: "vans", label: "Route A", points: [{ lat: 24.71, lng: 46.67 }, { lat: 24.75, lng: 46.72 }] }]}
      tileUrl="https://tile.example.com/{z}/{x}/{y}.png"
      attribution="© Example maps"
    />
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqMapView } from "@fadymondy/nasaq/vue";
import { Truck } from "lucide-vue-next";

const layers = [{ id: "vans", label: "Vans", labelAr: "الشاحنات", tone: "info" as const }];
const pins = [{ id: "v1", lat: 24.7136, lng: 46.6753, label: "Van 12", labelAr: "شاحنة 12", layer: "vans", tone: "info" as const, icon: Truck, status: "Moving", statusAr: "تتحرك" }];
const routes = [{ id: "r1", layer: "vans", label: "Route A", points: [{ lat: 24.71, lng: 46.67 }, { lat: 24.75, lng: 46.72 }] }];
</script>

<template>
  <NqMapView label="Fleet" :layers="layers" :pins="pins" :routes="routes" tile-url="https://tile.example.com/{z}/{x}/{y}.png" attribution="© Example maps" />
</template>
```

### Blade

```blade
<x-nq::map-view
    label="Fleet"
    :layers="[['id' => 'vans', 'label' => 'Vans', 'labelAr' => 'الشاحنات', 'tone' => 'info']]"
    :pins="[['id' => 'v1', 'lat' => 24.7136, 'lng' => 46.6753, 'label' => 'Van 12', 'labelAr' => 'شاحنة 12', 'layer' => 'vans', 'tone' => 'info', 'icon' => 'truck', 'status' => 'Moving', 'statusAr' => 'تتحرك']]"
    :routes="[['id' => 'r1', 'layer' => 'vans', 'label' => 'Route A', 'points' => [['lat' => 24.71, 'lng' => 46.67], ['lat' => 24.75, 'lng' => 46.72]]]]"
    tile-url="https://tile.example.com/{z}/{x}/{y}.png"
    attribution="© Example maps" />
```

### HTML + Alpine

```html
<div x-data="nqMapView(JSON.parse('{\u0022pins\u0022:[{\u0022id\u0022:\u0022v1\u0022,\u0022lat\u0022:24.7136,\u0022lng\u0022:46.6753,\u0022label\u0022:\u0022Van 12\u0022,\u0022labelAr\u0022:\u0022\\u0634\\u0627\\u062d\\u0646\\u0629 12\u0022,\u0022layer\u0022:\u0022vans\u0022,\u0022tone\u0022:\u0022info\u0022,\u0022icon\u0022:\u0022truck\u0022,\u0022status\u0022:\u0022Moving\u0022,\u0022statusAr\u0022:\u0022\\u062a\\u062a\\u062d\\u0631\\u0643\u0022}],\u0022routes\u0022:[{\u0022id\u0022:\u0022r1\u0022,\u0022layer\u0022:\u0022vans\u0022,\u0022label\u0022:\u0022Route A\u0022,\u0022points\u0022:[{\u0022lat\u0022:24.71,\u0022lng\u0022:46.67},{\u0022lat\u0022:24.75,\u0022lng\u0022:46.72}]}],\u0022areas\u0022:[],\u0022layers\u0022:[{\u0022id\u0022:\u0022vans\u0022,\u0022label\u0022:\u0022Vans\u0022,\u0022labelAr\u0022:\u0022\\u0627\\u0644\\u0634\\u0627\\u062d\\u0646\\u0627\\u062a\u0022,\u0022tone\u0022:\u0022info\u0022}],\u0022tileUrl\u0022:\u0022https:\\\/\\\/tile.example.com\\\/{z}\\\/{x}\\\/{y}.png\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,\u0022truck\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=\\u0022M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M15 18H9\\u0022\\\/\\u003E\\n  \\u003Cpath d=\\u0022M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14\\u0022\\\/\\u003E\\n  \\u003Ccircle cx=\\u002217\\u0022 cy=\\u002218\\u0022 r=\\u00222\\u0022\\\/\\u003E\\n  \\u003Ccircle cx=\\u00227\\u0022 cy=\\u002218\\u0022 r=\\u00222\\u0022\\\/\\u003E\\n\\u003C\\\/svg\\u003E\u0022}}'))" x-ref="canvas" dir="ltr" role="group" aria-roledescription="map" aria-label="Fleet" 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 h-[28rem] min-h-64 w-full touch-none select-none overflow-hidden rounded-card border border-border bg-secondary outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus">
    <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>
                    <span class="pointer-events-auto max-w-[70%] rounded-[4px] bg-card/80 px-1.5 text-end">© Example maps</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>
```
