{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "dotted-map-chart",
  "title": "Dotted Map Chart",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/charts/dotted-map-chart/DottedMapChart.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport { Map, MapMarker, MapSource, MapLayer, type MapVariant } from '@/components/ui/map'\nimport { cn } from '@/lib/utils'\nimport { Globe } from 'lucide-vue-next'\n\nexport interface MapPin {\n  lat: number\n  lng: number\n  label?: string\n  color?: string\n  description?: string\n  value?: string | number\n  status?: string\n}\n\nexport interface MapRoute {\n  from: { lat: number; lng: number }\n  to: { lat: number; lng: number }\n  color?: string\n  width?: number\n  curvature?: number\n  animated?: boolean\n  dashed?: boolean\n  duration?: number\n  label?: string\n}\n\ninterface Props {\n  pins?: MapPin[]\n  routes?: MapRoute[]\n  map?: 'world' | 'usa'\n  grid?: 'vertical' | 'diagonal'\n  shape?: 'circle' | 'hexagon'\n  dotColor?: string\n  pulse?: boolean\n  height?: number | string\n  class?: HTMLAttributes['class']\n  ariaLabel?: string\n  interactive?: boolean\n  variant?: MapVariant\n  projection?: 'globe' | 'mercator'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  pins: () => [],\n  routes: () => [],\n  map: 'world',\n  grid: 'vertical',\n  shape: 'circle',\n  dotColor: 'rgba(255, 255, 255, 0.22)',\n  pulse: true,\n  height: 420,\n  interactive: true,\n  variant: 'dark',\n  projection: 'globe',\n})\n\nconst currentProjection = ref<'globe' | 'mercator'>(props.projection)\nconst emit = defineEmits<{\n  (e: 'pin-click', pin: MapPin): void\n  (e: 'pin-hover', pin: MapPin | null): void\n}>()\n\nconst hoveredPin = ref<MapPin | null>(null)\n\nfunction onPinEnter(pin: MapPin) {\n  hoveredPin.value = pin\n  emit('pin-hover', pin)\n}\n\nfunction onPinLeave() {\n  hoveredPin.value = null\n  emit('pin-hover', null)\n}\n\nfunction toggleProjection() {\n  currentProjection.value = currentProjection.value === 'globe' ? 'mercator' : 'globe'\n}\n\nconst mapCenter = computed<[number, number]>(() => {\n  return props.map === 'usa' ? [-98, 39] : [0, 20]\n})\n\nconst mapZoom = computed(() => {\n  return props.map === 'usa' ? 3.5 : 1.5\n})\n\n// Generate synthetic telemetry dot matrix over land\nconst dotGridGeoJson = computed(() => {\n  const features: any[] = []\n  const step = props.map === 'usa' ? 3 : 6\n  const latMin = props.map === 'usa' ? 25 : -55\n  const latMax = props.map === 'usa' ? 50 : 70\n  const lngMin = props.map === 'usa' ? -125 : -170\n  const lngMax = props.map === 'usa' ? -66 : 170\n\n  for (let lat = latMin; lat <= latMax; lat += step) {\n    for (let lng = lngMin; lng <= lngMax; lng += step) {\n      features.push({\n        type: 'Feature',\n        geometry: {\n          type: 'Point',\n          coordinates: [lng, lat],\n        },\n      })\n    }\n  }\n\n  return {\n    type: 'FeatureCollection',\n    features,\n  }\n})\n\nconst routesGeoJson = computed(() => {\n  if (!props.routes || !props.routes.length) return null\n  return {\n    type: 'FeatureCollection',\n    features: props.routes.map((r, i) => ({\n      type: 'Feature',\n      id: i,\n      properties: {\n        color: r.color || 'rgba(56, 189, 248, 0.75)',\n        dashed: r.dashed ?? true,\n      },\n      geometry: {\n        type: 'LineString',\n        coordinates: [\n          [r.from.lng, r.from.lat],\n          [(r.from.lng + r.to.lng) / 2, (r.from.lat + r.to.lat) / 2 + 5],\n          [r.to.lng, r.to.lat],\n        ],\n      },\n    })),\n  }\n})\n\nconst dotPaint = computed(() => ({\n  'circle-radius': 1.5,\n  'circle-color': props.dotColor || 'rgba(255, 255, 255, 0.22)',\n  'circle-opacity': 0.4,\n}))\n\nconst routeLinePaint = {\n  'line-color': ['get', 'color'],\n  'line-width': 1.5,\n  'line-dasharray': [2, 2],\n}\n</script>\n\n<template>\n  <div\n    :class=\"cn('border-border bg-card group relative w-full overflow-hidden rounded-xl border shadow-xs', props.class)\"\n    :style=\"{\n      height:\n        typeof props.height === 'number'\n          ? `${props.height}px`\n          : /^\\d+$/.test(String(props.height))\n            ? `${props.height}px`\n            : props.height,\n    }\"\n  >\n    <Map :variant=\"variant\" :projection=\"currentProjection\" :center=\"mapCenter\" :zoom=\"mapZoom\" class=\"size-full\">\n      <!-- Telemetry Dot Grid -->\n      <MapSource id=\"dot-grid-source\" type=\"geojson\" :data=\"dotGridGeoJson\">\n        <MapLayer id=\"dot-grid-layer\" type=\"circle\" :paint=\"dotPaint\" />\n      </MapSource>\n\n      <!-- Connection Routes -->\n      <MapSource v-if=\"routesGeoJson\" id=\"routes-source\" type=\"geojson\" :data=\"routesGeoJson\">\n        <MapLayer id=\"routes-layer\" type=\"line\" :paint=\"routeLinePaint\" />\n      </MapSource>\n\n      <!-- Pins -->\n      <MapMarker\n        v-for=\"(pin, i) in pins\"\n        :key=\"i\"\n        :lng-lat=\"[pin.lng, pin.lat]\"\n        anchor=\"center\"\n        class=\"cursor-pointer select-none\"\n      >\n        <div\n          class=\"relative flex size-6 items-center justify-center\"\n          @mouseenter=\"onPinEnter(pin)\"\n          @mouseleave=\"onPinLeave\"\n          @click=\"emit('pin-click', pin)\"\n        >\n          <span\n            v-if=\"pulse\"\n            class=\"absolute inline-flex size-full animate-ping rounded-full opacity-60\"\n            :style=\"{ backgroundColor: pin.color || 'oklch(0.65 0.20 145)' }\"\n          />\n          <span\n            class=\"ring-background relative inline-flex size-2.5 rounded-full shadow-xs ring-2\"\n            :style=\"{\n              backgroundColor: pin.color || 'oklch(0.65 0.20 145)',\n              boxShadow: `0 0 10px ${pin.color || 'oklch(0.65 0.20 145)'}`,\n            }\"\n          />\n        </div>\n      </MapMarker>\n    </Map>\n\n    <!-- Projection Switcher -->\n    <div\n      class=\"border-border/70 bg-card/85 absolute top-3 right-3 z-10 flex items-center gap-1 rounded-lg border p-1 shadow-xs backdrop-blur-md\"\n    >\n      <button\n        type=\"button\"\n        class=\"text-muted-foreground hover:bg-muted hover:text-foreground flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition\"\n        @click=\"toggleProjection\"\n      >\n        <Globe class=\"size-3.5\" />\n        <span class=\"capitalize\">{{ currentProjection }}</span>\n      </button>\n    </div>\n\n    <!-- Active Pin Card -->\n    <div\n      v-if=\"hoveredPin && interactive\"\n      class=\"border-border/80 bg-card/95 animate-in fade-in slide-in-from-bottom-2 absolute bottom-3 left-3 z-10 max-w-xs rounded-xl border p-3 shadow-lg backdrop-blur-md duration-150\"\n    >\n      <div class=\"flex items-center gap-2\">\n        <span class=\"size-2 rounded-full\" :style=\"{ backgroundColor: hoveredPin.color || 'oklch(0.65 0.20 145)' }\" />\n        <h5 class=\"text-foreground text-xs font-semibold\">\n          {{ hoveredPin.label || 'Telemetry Node' }}\n        </h5>\n      </div>\n      <p v-if=\"hoveredPin.description\" class=\"text-muted-foreground mt-1 text-xs\">\n        {{ hoveredPin.description }}\n      </p>\n      <div\n        v-if=\"hoveredPin.value !== undefined\"\n        class=\"border-border/60 mt-2 flex items-baseline justify-between border-t pt-1.5 font-mono text-xs\"\n      >\n        <span class=\"text-muted-foreground\">Value</span>\n        <span class=\"text-foreground font-semibold\">{{ hoveredPin.value }}</span>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/charts/dotted-map-chart/DottedMapChart.vue"
    },
    {
      "path": "packages/registry-vue/components/charts/dotted-map-chart/index.ts",
      "content": "export { default as DottedMapChart } from './DottedMapChart.vue'\nexport type { MapPin, MapRoute } from './DottedMapChart.vue'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/charts/dotted-map-chart/index.ts"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/map.json"
  ],
  "description": "Dotted map as dependency-free SVG. Embedded world (96×48) and USA (155×74) landmasks with cartographic AK/HI insets rasterized from Natural Earth/US outlines; lat/lng pins with pulse, vertical/diagonal grids, circle/hexagon dots. No map tokens or geodata fetches.",
  "categories": [
    "chart"
  ]
}