{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "leaflet-maritime-vessel-map",
  "title": "Leaflet Maritime Vessel Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/leaflet-maritime-vessel-map/LeafletMaritimeVesselMap.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { LeafletMap, LeafletMarker, LeafletPolyline, type LeafletMapRef } from '@/components/ui/leaflet-map'\n\nexport type VesselType = 'Container' | 'Crude Tanker' | 'Bulk Carrier' | 'Tug'\nexport type VesselStatus = 'Underway' | 'Moored' | 'At Anchor'\n\nexport interface Vessel {\n  mmsi: string\n  name: string\n  flag: string\n  type: VesselType\n  /** [lng, lat] — Mapbox order. */\n  coords: [number, number]\n  heading: number\n  speedKnots: number\n  draught: string\n  dest: string\n  eta: string\n  status: VesselStatus\n}\n\nexport interface LeafletMaritimeVesselMapProps extends React.HTMLAttributes<HTMLDivElement> {\n  vessels?: Vessel[]\n}\n\n// Module-level constant: a stable reference keeps LeafletMap's center effect\n// from re-firing setView on every render and cancelling flyTo animations.\nconst MAP_CENTER: [number, number] = [4.4, 51.9]\n\nconst DEFAULT_VESSELS: Vessel[] = [\n  {\n    mmsi: '219018442',\n    name: 'EVER GIVEN',\n    flag: 'PA',\n    type: 'Container',\n    coords: [4.04, 51.98],\n    heading: 95,\n    speedKnots: 13.8,\n    draught: '15.7m',\n    dest: 'RTM MAASVLAKTE',\n    eta: 'OCT 24 14:00',\n    status: 'Underway',\n  },\n  {\n    mmsi: '636019812',\n    name: 'NORDIC STAR',\n    flag: 'LR',\n    type: 'Crude Tanker',\n    coords: [4.18, 51.94],\n    heading: 265,\n    speedKnots: 0.3,\n    draught: '12.1m',\n    dest: 'EUROPOORT ANCH',\n    eta: 'OCT 24 08:30',\n    status: 'At Anchor',\n  },\n  {\n    mmsi: '356782000',\n    name: 'PACIFIC RUBY',\n    flag: 'SG',\n    type: 'Bulk Carrier',\n    coords: [4.33, 51.92],\n    heading: 100,\n    speedKnots: 10.6,\n    draught: '10.4m',\n    dest: 'NLRTM ECT',\n    eta: 'OCT 29 06:00',\n    status: 'Underway',\n  },\n  {\n    mmsi: '508111222',\n    name: 'HARBOR VALIANT',\n    flag: 'NL',\n    type: 'Tug',\n    coords: [4.47, 51.9],\n    heading: 180,\n    speedKnots: 6.4,\n    draught: '4.5m',\n    dest: 'WAALHAVEN',\n    eta: 'OCT 24 12:00',\n    status: 'Underway',\n  },\n]\n\n// Nieuwe Waterweg main channel (North Sea -> Waalhaven) + Maasvlakte approach lane.\nconst SHIPPING_LANES: [number, number][][] = [\n  [\n    [3.94, 52.0],\n    [4.08, 51.965],\n    [4.22, 51.935],\n    [4.36, 51.915],\n    [4.55, 51.9],\n  ],\n  [\n    [3.99, 52.03],\n    [4.14, 51.99],\n    [4.28, 51.945],\n    [4.45, 51.905],\n  ],\n]\n\nconst PORTS: { name: string; coords: [number, number] }[] = [\n  { name: 'MAASVLAKTE', coords: [3.98, 51.99] },\n  { name: 'EUROPOORT', coords: [4.09, 51.95] },\n  { name: 'WAALHAVEN', coords: [4.42, 51.885] },\n]\n\nexport function LeafletMaritimeVesselMap({\n  className,\n  vessels = DEFAULT_VESSELS,\n  ...props\n}: LeafletMaritimeVesselMapProps) {\n  const [activeMmsi, setActiveMmsi] = React.useState(vessels[0]?.mmsi ?? '')\n  const mapRef = React.useRef<LeafletMapRef>(null)\n  const activeVessel = vessels.find((v) => v.mmsi === activeMmsi) ?? vessels[0]\n\n  const selectVessel = (v: Vessel) => {\n    setActiveMmsi(v.mmsi)\n    mapRef.current?.flyTo?.({ center: v.coords, zoom: 11.5, duration: 800 })\n  }\n\n  return (\n    <div\n      data-slot=\"leaflet-maritime-vessel-map\"\n      className={cn('grid gap-4 lg:grid-cols-[1fr_320px]', className)}\n      {...props}\n    >\n      <div className=\"border-border bg-card relative h-[480px] overflow-hidden rounded-xl border\">\n        <LeafletMap ref={mapRef} variant=\"dark\" center={MAP_CENTER} zoom={11} className=\"h-full w-full\">\n          {/* Shipping lanes */}\n          {SHIPPING_LANES.map((lane, i) => (\n            <LeafletPolyline\n              key={i}\n              lngLatPath={lane}\n              color=\"#22d3ee\"\n              weight={2}\n              opacity={0.45}\n              dashArray=\"6 6\"\n              lineCap=\"round\"\n            />\n          ))}\n\n          {/* Port terminals */}\n          {PORTS.map((p) => (\n            <LeafletMarker key={p.name} lngLat={p.coords} anchor=\"center\">\n              <div className=\"flex flex-col items-center\">\n                <span className=\"size-2.5 rotate-45 rounded-[2px] border border-cyan-400/70 bg-cyan-500/40\" />\n                <span className=\"border-border bg-card/80 text-muted-foreground mt-1 rounded border px-1 py-0.5 font-mono text-xs tracking-wider whitespace-nowrap\">\n                  {p.name}\n                </span>\n              </div>\n            </LeafletMarker>\n          ))}\n\n          {/* AIS vessels */}\n          {vessels.map((v) => (\n            <LeafletMarker key={v.mmsi} lngLat={v.coords} anchor=\"center\" onClick={() => selectVessel(v)}>\n              <div className=\"group relative flex cursor-pointer flex-col items-center\">\n                <div\n                  className={cn(\n                    'flex size-7 items-center justify-center rounded-full font-bold text-cyan-400',\n                    activeMmsi === v.mmsi ? 'bg-cyan-500/20 ring-2 ring-cyan-400' : 'bg-background/80',\n                  )}\n                  style={{ transform: `rotate(${v.heading}deg)` }}\n                >\n                  ▲\n                </div>\n                <span\n                  className={cn(\n                    'border-border bg-card/90 mt-1 rounded border px-1 py-0.5 font-mono text-xs font-semibold tracking-wider whitespace-nowrap shadow-xs',\n                    activeMmsi === v.mmsi ? 'border-cyan-400/50 text-cyan-400' : 'text-foreground',\n                  )}\n                >\n                  {v.name}\n                </span>\n              </div>\n            </LeafletMarker>\n          ))}\n        </LeafletMap>\n      </div>\n\n      {/* AIS Telemetry Drawer */}\n      <Card className=\"flex flex-col justify-between\">\n        <CardHeader>\n          <div className=\"flex items-center justify-between\">\n            <Badge variant=\"outline\" className=\"font-mono text-xs\">\n              {activeVessel.type}\n            </Badge>\n            <Badge className=\"border-cyan-500/20 bg-cyan-500/10 text-cyan-600 dark:text-cyan-400\">\n              {activeVessel.status}\n            </Badge>\n          </div>\n          <CardTitle className=\"mt-2 font-mono text-lg\">{activeVessel.name}</CardTitle>\n          <CardDescription className=\"font-mono text-xs\">\n            MMSI: {activeVessel.mmsi} • Flag: {activeVessel.flag}\n          </CardDescription>\n        </CardHeader>\n        <CardContent className=\"space-y-4\">\n          <div className=\"border-border bg-muted/40 grid grid-cols-2 gap-2 rounded-lg border p-2.5 font-mono text-xs\">\n            <div>\n              <div className=\"text-muted-foreground text-xs uppercase\">Speed Over Ground</div>\n              <div className=\"text-foreground text-sm font-bold\">{activeVessel.speedKnots} kts</div>\n            </div>\n            <div>\n              <div className=\"text-muted-foreground text-xs uppercase\">Heading</div>\n              <div className=\"text-foreground text-sm font-bold\">{activeVessel.heading}°</div>\n            </div>\n            <div className=\"mt-2\">\n              <div className=\"text-muted-foreground text-xs uppercase\">Max Draught</div>\n              <div className=\"text-foreground text-sm font-bold\">{activeVessel.draught}</div>\n            </div>\n            <div className=\"mt-2\">\n              <div className=\"text-muted-foreground text-xs uppercase\">Destination</div>\n              <div className=\"text-foreground truncate text-xs font-bold\">{activeVessel.dest}</div>\n            </div>\n          </div>\n\n          <div className=\"border-border space-y-1 border-t pt-3\">\n            <div className=\"text-muted-foreground mb-1 font-mono text-xs tracking-wider uppercase\">\n              Rotterdam Approach Traffic\n            </div>\n            {vessels.map((v) => (\n              <button\n                key={v.mmsi}\n                type=\"button\"\n                className={cn(\n                  'flex w-full items-center justify-between rounded-md px-2 py-1 text-xs transition-colors',\n                  activeMmsi === v.mmsi\n                    ? 'bg-accent text-accent-foreground font-semibold'\n                    : 'hover:bg-muted text-muted-foreground',\n                )}\n                onClick={() => selectVessel(v)}\n              >\n                <span className=\"font-mono\">{v.name}</span>\n                <span className=\"font-mono text-xs opacity-75\">{v.speedKnots} kts</span>\n              </button>\n            ))}\n          </div>\n\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            className=\"w-full font-mono text-xs\"\n            onClick={() => selectVessel(activeVessel)}\n          >\n            Center on Vessel\n          </Button>\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n\nexport default LeafletMaritimeVesselMap\n",
      "type": "registry:block",
      "target": "~/components/blocks/LeafletMaritimeVesselMap.tsx"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/leaflet-map.json"
  ],
  "description": "Automatic Identification System (AIS) vessel tracking on free OpenStreetMap/Esri tiles — no API key. Container ships, tankers, shipping lanes, and anchorage status.",
  "categories": [
    "logistics",
    "maritime",
    "map"
  ]
}