{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "heatmap",
  "title": "Heatmap",
  "description": "Globe-projected heatmap visualizing earthquake density with zoom-dependent styling.",
  "dependencies": [],
  "registryDependencies": [
    "@mapcn/map"
  ],
  "files": [
    {
      "path": "src/registry/blocks/heatmap/page.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId } from \"react\";\n\nimport { Map, useMap } from \"@/components/ui/map\";\n\nconst EARTHQUAKE_GEOJSON_URL =\n  \"https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson\";\n\nconst HEATMAP_GRADIENT_COLORS = [\n  \"#fff7bc\",\n  \"#fee391\",\n  \"#fec44f\",\n  \"#fe9929\",\n  \"#d7301f\",\n];\n\nconst HEATMAP_COLOR_STOPS: [number, string][] = [\n  [0.15, HEATMAP_GRADIENT_COLORS[0]],\n  [0.35, HEATMAP_GRADIENT_COLORS[1]],\n  [0.55, HEATMAP_GRADIENT_COLORS[2]],\n  [0.75, HEATMAP_GRADIENT_COLORS[3]],\n  [1, HEATMAP_GRADIENT_COLORS[4]],\n];\n\nfunction GlobeHeatmapLayers() {\n  const { map, isLoaded } = useMap();\n  const id = useId();\n  const sourceId = `heatmap-source-${id}`;\n  const heatLayerId = `heatmap-layer-${id}`;\n  const pointLayerId = `heatmap-point-layer-${id}`;\n\n  useEffect(() => {\n    if (!map || !isLoaded) return;\n\n    if (!map.getSource(sourceId)) {\n      map.addSource(sourceId, {\n        type: \"geojson\",\n        data: EARTHQUAKE_GEOJSON_URL,\n      });\n    }\n\n    if (!map.getLayer(heatLayerId)) {\n      map.addLayer({\n        id: heatLayerId,\n        type: \"heatmap\",\n        source: sourceId,\n        maxzoom: 6,\n        paint: {\n          \"heatmap-weight\": [\n            \"interpolate\",\n            [\"linear\"],\n            [\"get\", \"mag\"],\n            0,\n            0,\n            6,\n            0.8,\n          ],\n          \"heatmap-intensity\": [\n            \"interpolate\",\n            [\"linear\"],\n            [\"zoom\"],\n            0,\n            0.55,\n            6,\n            1.25,\n          ],\n          \"heatmap-color\": [\n            \"interpolate\",\n            [\"linear\"],\n            [\"heatmap-density\"],\n            0,\n            \"rgba(59, 130, 246, 0)\",\n            ...HEATMAP_COLOR_STOPS.flat(),\n          ],\n          \"heatmap-radius\": [\"interpolate\", [\"linear\"], [\"zoom\"], 0, 8, 6, 34],\n          \"heatmap-opacity\": [\n            \"interpolate\",\n            [\"linear\"],\n            [\"zoom\"],\n            4.5,\n            0.75,\n            6.5,\n            0.08,\n          ],\n        },\n      });\n    }\n\n    if (!map.getLayer(pointLayerId)) {\n      map.addLayer({\n        id: pointLayerId,\n        type: \"circle\",\n        source: sourceId,\n        minzoom: 4.5,\n        paint: {\n          \"circle-radius\": [\n            \"interpolate\",\n            [\"linear\"],\n            [\"get\", \"mag\"],\n            1,\n            3,\n            6,\n            10,\n          ],\n          \"circle-color\": [\n            \"interpolate\",\n            [\"linear\"],\n            [\"get\", \"mag\"],\n            1,\n            HEATMAP_GRADIENT_COLORS[1],\n            2.5,\n            HEATMAP_GRADIENT_COLORS[2],\n            4,\n            HEATMAP_GRADIENT_COLORS[3],\n            6,\n            HEATMAP_GRADIENT_COLORS[4],\n          ],\n          \"circle-stroke-width\": 1,\n          \"circle-stroke-color\": \"rgba(255,255,255,0.8)\",\n          \"circle-opacity\": [\n            \"interpolate\",\n            [\"linear\"],\n            [\"zoom\"],\n            4.5,\n            0,\n            6.5,\n            0.7,\n          ],\n        },\n      });\n    }\n\n    return () => {\n      try {\n        if (map.getLayer(pointLayerId)) map.removeLayer(pointLayerId);\n        if (map.getLayer(heatLayerId)) map.removeLayer(heatLayerId);\n        if (map.getSource(sourceId)) map.removeSource(sourceId);\n      } catch {\n        // ignore\n      }\n    };\n  }, [map, isLoaded, sourceId, heatLayerId, pointLayerId]);\n\n  return null;\n}\n\nconst gradient = `linear-gradient(to right, ${HEATMAP_GRADIENT_COLORS.join(\", \")})`;\n\nexport default function Page() {\n  return (\n    <div className=\"bg-card relative h-screen\">\n      <div className=\"relative h-full\">\n        <Map\n          center={[-113, 43]}\n          zoom={3.2}\n          projection={{ type: \"globe\" }}\n          pitch={24}\n          minZoom={1.2}\n          maxZoom={8}\n        >\n          <GlobeHeatmapLayers />\n        </Map>\n      </div>\n\n      <div className=\"bg-card/90 absolute top-4 left-4 z-10 rounded-lg border px-3 py-2.5 backdrop-blur-sm\">\n        <p className=\"text-foreground text-sm font-medium\">\n          Global Earthquakes Heatmap\n        </p>\n\n        <div\n          className=\"mt-3 h-2 w-full rounded-full\"\n          style={{ backgroundImage: gradient }}\n        />\n        <div className=\"text-muted-foreground flex items-center justify-between pt-1.5 text-[10px]\">\n          <span>Low</span>\n          <span>High</span>\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:page",
      "target": "app/heatmap/page.tsx"
    }
  ],
  "meta": {
    "iframeHeight": "800px"
  },
  "categories": [
    "visualization",
    "heatmap"
  ],
  "type": "registry:block"
}
