{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "leaflet-isochrone-reachability-map",
  "title": "Leaflet Isochrone Reachability Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/leaflet-isochrone-reachability-map/LeafletIsochroneReachabilityMap.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { ref } from 'vue'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'\nimport { LeafletMap, LeafletMarker, LeafletPolygon, type LeafletMapRef } from '@/components/ui/leaflet-map'\n\nexport interface LeafletIsochroneReachabilityMapProps {\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<LeafletIsochroneReachabilityMapProps>()\n\nconst mapRef = ref<LeafletMapRef | null>(null)\nconst selectedTime = ref<10 | 20 | 30>(20)\n\nconst hubLocation: [number, number] = [-122.419, 37.775] // Downtown San Francisco\n\n// Key-free drive-time approximation: a perturbed ring whose radius varies by\n// angle through fixed harmonic offsets, so each band reads as an irregular\n// blob rather than a perfect circle (a real isochrone needs a routing API).\nfunction createIsochroneCoords(radiusMiles: number, phase: number) {\n  const points: [number, number][] = []\n  const steps = 48\n  const latDelta = radiusMiles / 69\n  const lngDelta = radiusMiles / (69 * Math.cos((hubLocation[1] * Math.PI) / 180))\n  for (let i = 0; i <= steps; i++) {\n    const angle = (i / steps) * 2 * Math.PI\n    const jitter = 1 + 0.16 * Math.sin(angle * 3 + phase) + 0.07 * Math.sin(angle * 7 + phase * 2.3)\n    const lng = hubLocation[0] + lngDelta * Math.cos(angle) * jitter\n    const lat = hubLocation[1] + latDelta * Math.sin(angle) * jitter\n    points.push([lng, lat])\n  }\n  return points\n}\n\nconst isochrones = {\n  10: { coords: createIsochroneCoords(2.8, 0.4), pop: '340K', jobs: '520K', color: '#10b981', label: '10 Min Commute' },\n  20: {\n    coords: createIsochroneCoords(6.2, 1.7),\n    pop: '1.24M',\n    jobs: '890K',\n    color: '#3b82f6',\n    label: '20 Min Commute',\n  },\n  30: {\n    coords: createIsochroneCoords(11.5, 2.9),\n    pop: '2.85M',\n    jobs: '1.45M',\n    color: '#f59e0b',\n    label: '30 Min Commute',\n  },\n}\n\nconst destinations = [\n  { name: 'Mission Depot', lngLat: [-122.414, 37.762] as [number, number] },\n  { name: 'Daly City Hub', lngLat: [-122.47, 37.705] as [number, number] },\n  { name: 'Oakland Annex', lngLat: [-122.272, 37.804] as [number, number] },\n]\n</script>\n\n<template>\n  <div data-slot=\"leaflet-isochrone-reachability-map\" :class=\"cn('grid gap-4 lg:grid-cols-[1fr_320px]', props.class)\">\n    <div class=\"border-border bg-card relative h-[480px] overflow-hidden rounded-xl border\">\n      <LeafletMap ref=\"mapRef\" variant=\"light\" :center=\"hubLocation\" :zoom=\"10.5\" class=\"absolute inset-0 size-full\">\n        <!-- 30m Isochrone (outermost, drawn first) -->\n        <LeafletPolygon\n          :lng-lat-path=\"isochrones[30].coords\"\n          :color=\"isochrones[30].color\"\n          :weight=\"1.5\"\n          dash-array=\"4 4\"\n          fill\n          :fill-color=\"isochrones[30].color\"\n          :fill-opacity=\"selectedTime >= 30 ? 0.15 : 0.04\"\n        />\n\n        <!-- 20m Isochrone -->\n        <LeafletPolygon\n          :lng-lat-path=\"isochrones[20].coords\"\n          :color=\"isochrones[20].color\"\n          :weight=\"2\"\n          fill\n          :fill-color=\"isochrones[20].color\"\n          :fill-opacity=\"selectedTime >= 20 ? 0.22 : 0.06\"\n        />\n\n        <!-- 10m Isochrone (innermost, most opaque) -->\n        <LeafletPolygon\n          :lng-lat-path=\"isochrones[10].coords\"\n          :color=\"isochrones[10].color\"\n          :weight=\"2.5\"\n          fill\n          :fill-color=\"isochrones[10].color\"\n          :fill-opacity=\"0.3\"\n        />\n\n        <!-- Destinations -->\n        <LeafletMarker v-for=\"dest in destinations\" :key=\"dest.name\" :lng-lat=\"dest.lngLat\" anchor=\"center\">\n          <div class=\"flex flex-col items-center gap-0.5\">\n            <span class=\"border-background bg-foreground size-2 rounded-full border-2 shadow-xs\" />\n            <span\n              class=\"bg-card/90 text-foreground rounded px-1 font-mono text-xs font-semibold whitespace-nowrap shadow-xs\"\n            >\n              {{ dest.name }}\n            </span>\n          </div>\n        </LeafletMarker>\n\n        <!-- Central Hub Marker -->\n        <LeafletMarker :lng-lat=\"hubLocation\" anchor=\"center\">\n          <div class=\"relative flex items-center justify-center\">\n            <span class=\"bg-primary/30 absolute size-6 animate-ping rounded-full\" />\n            <div\n              class=\"bg-primary text-primary-foreground ring-background flex size-6 items-center justify-center rounded-full font-mono text-xs font-bold shadow-md ring-2\"\n            >\n              ★\n            </div>\n          </div>\n        </LeafletMarker>\n      </LeafletMap>\n    </div>\n\n    <!-- Isochrone Analytics Card -->\n    <Card class=\"flex flex-col justify-between\">\n      <CardHeader>\n        <div class=\"flex items-center justify-between\">\n          <Badge variant=\"outline\" class=\"font-mono text-xs\">COMMUTE ANALYSIS</Badge>\n          <Badge class=\"border-info/20 bg-info/10 text-info\">Drive-Time</Badge>\n        </div>\n        <CardTitle class=\"mt-2 text-lg\">Reachability Zones</CardTitle>\n        <CardDescription>Transit reachability from Downtown Distribution Hub.</CardDescription>\n      </CardHeader>\n      <CardContent class=\"space-y-4\">\n        <!-- Range Selector Tabs -->\n        <div class=\"border-border bg-muted grid grid-cols-3 gap-1 rounded-lg border p-1 text-xs\">\n          <button\n            v-for=\"t in [10, 20, 30] as const\"\n            :key=\"t\"\n            type=\"button\"\n            class=\"rounded-md py-1.5 font-medium transition-colors\"\n            :class=\"\n              selectedTime === t\n                ? 'bg-background text-foreground shadow-xs'\n                : 'text-muted-foreground hover:text-foreground'\n            \"\n            @click=\"selectedTime = t\"\n          >\n            {{ t }} mins\n          </button>\n        </div>\n\n        <div class=\"border-border space-y-3 border-t pt-3\">\n          <div class=\"flex items-center justify-between\">\n            <span class=\"text-muted-foreground text-xs\">Population Reach</span>\n            <span class=\"text-foreground font-mono text-base font-bold\">{{ isochrones[selectedTime].pop }}</span>\n          </div>\n          <div class=\"flex items-center justify-between\">\n            <span class=\"text-muted-foreground text-xs\">Workforce / Labor Pool</span>\n            <span class=\"text-foreground font-mono text-base font-bold\">{{ isochrones[selectedTime].jobs }}</span>\n          </div>\n          <div class=\"flex items-center justify-between\">\n            <span class=\"text-muted-foreground text-xs\">Service Band</span>\n            <span class=\"font-mono text-xs font-semibold\" :style=\"{ color: isochrones[selectedTime].color }\">\n              {{ isochrones[selectedTime].label }}\n            </span>\n          </div>\n        </div>\n\n        <div class=\"border-border text-muted-foreground space-y-2 border-t pt-3 text-xs\">\n          <div class=\"flex items-center gap-2\">\n            <span class=\"bg-success size-2 rounded-full\" />\n            <span>Green ring: 10-minute urban core</span>\n          </div>\n          <div class=\"flex items-center gap-2\">\n            <span class=\"bg-info size-2 rounded-full\" />\n            <span>Blue ring: 20-minute metropolitan belt</span>\n          </div>\n          <div class=\"flex items-center gap-2\">\n            <span class=\"bg-warning size-2 rounded-full\" />\n            <span>Amber ring: 30-minute outer ring</span>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/LeafletIsochroneReachabilityMap.vue"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/leaflet-map.json"
  ],
  "description": "Drive-time reachability zones showing 10m, 20m, and 30m commute polygons with workforce population metrics — on free OpenStreetMap/Esri tiles, no API key.",
  "categories": [
    "logistics",
    "geo",
    "map"
  ]
}