{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "heat-overlay",
  "type": "registry:component",
  "title": "Heat Overlay",
  "description": "Heatmap-style overlay drawing soft radial blobs for canvas activity samples.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/heat-overlay/heat-overlay.tsx",
      "content": "\"use client\";\n\nimport { type ComponentPropsWithoutRef, forwardRef, useId } from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/**\n * Tone of the heat gradient — drives the inner circle color.\n *\n * @public\n */\nexport type HeatOverlayTone = \"cool\" | \"danger\" | \"neutral\" | \"warn\";\n\nconst TONE_FILL: Record<HeatOverlayTone, string> = {\n  cool: \"fill-blue-500\",\n  danger: \"fill-red-500\",\n  neutral: \"fill-foreground\",\n  warn: \"fill-amber-500\",\n};\n\n/**\n * One heat sample.\n *\n * @public\n */\nexport type HeatPoint = {\n  /** Stable identifier — used as the React key. */\n  id: string;\n  /** Optional per-point tone override. */\n  tone?: HeatOverlayTone;\n  /** Sample weight `0..1` — drives the circle opacity + radius. */\n  weight: number;\n  /** X coordinate in canvas pixels. */\n  x: number;\n  /** Y coordinate in canvas pixels. */\n  y: number;\n};\n\n/**\n * Localizable strings.\n *\n * @public\n */\nexport type HeatOverlayLabels = {\n  /** Aria-label override. Defaults to `\"Heat overlay\"`. */\n  region?: string;\n};\n\nconst DEFAULT_LABELS = {\n  region: \"Heat overlay\",\n} as const satisfies Required<HeatOverlayLabels>;\n\n/**\n * Props for {@link HeatOverlay}.\n *\n * @public\n */\nexport type HeatOverlayProps = {\n  /** Optional global tone applied to every point that omits its own. Defaults to `\"warn\"`. */\n  defaultTone?: HeatOverlayTone;\n  /** Sample radius in pixels at full weight. Defaults to `48`. */\n  intensity?: number;\n  /** Localizable strings. */\n  labels?: HeatOverlayLabels;\n  /** Sample points in render order. */\n  points: HeatPoint[];\n} & ComponentPropsWithoutRef<\"svg\">;\n\nconst clamp01 = (v: number): number => {\n  if (v < 0) {\n    return 0;\n  }\n  if (v > 1) {\n    return 1;\n  }\n  return v;\n};\n\nconst HeatBlob = (props: {\n  defaultTone: HeatOverlayTone;\n  gradientId: string;\n  intensity: number;\n  point: HeatPoint;\n}): React.ReactElement => {\n  const { defaultTone, gradientId, intensity, point } = props;\n  const weight = clamp01(point.weight);\n  const tone = point.tone ?? defaultTone;\n  return (\n    <circle\n      className={cn(\"mix-blend-multiply\", TONE_FILL[tone])}\n      cx={point.x}\n      cy={point.y}\n      data-heat-point={point.id}\n      data-heat-tone={tone}\n      fill={`url(#${gradientId})`}\n      fillOpacity={weight * 0.6}\n      r={Math.max(8, intensity * weight)}\n    />\n  );\n};\n\n/**\n * Heatmap-style overlay drawn on top of a canvas. Each sample renders\n * as a soft radial blob whose radius + opacity scale with its weight.\n * Pure presentation; the host computes the point list from the\n * activity stream.\n *\n * Render inside a `position: relative` parent that shares the canvas\n * pixel coordinate space; the SVG is `pointer-events: none` so host\n * gestures pass through.\n *\n * @example\n * ```tsx\n * <div className=\"relative h-screen w-screen\">\n *   <Canvas />\n *   <HeatOverlay\n *     points={[\n *       { id: \"a\", x: 120, y: 80,  weight: 1.0, tone: \"danger\" },\n *       { id: \"b\", x: 320, y: 220, weight: 0.4 },\n *     ]}\n *   />\n * </div>\n * ```\n *\n * @public\n */\nexport const HeatOverlay = forwardRef<SVGSVGElement, HeatOverlayProps>(\n  (props, ref) => {\n    const {\n      className,\n      defaultTone = \"warn\",\n      intensity = 48,\n      labels,\n      points,\n      ...rest\n    } = props;\n    const resolvedLabels = { ...DEFAULT_LABELS, ...labels };\n    const gradientId = useId();\n    if (points.length === 0) {\n      return null;\n    }\n    return (\n      <svg\n        aria-label={resolvedLabels.region}\n        className={cn(\n          \"pointer-events-none absolute inset-0 z-10 h-full w-full\",\n          className,\n        )}\n        data-heat-overlay\n        ref={ref}\n        role=\"img\"\n        {...rest}\n      >\n        <defs>\n          <radialGradient cx=\"50%\" cy=\"50%\" id={gradientId} r=\"50%\">\n            <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity=\"1\" />\n            <stop offset=\"70%\" stopColor=\"currentColor\" stopOpacity=\"0.4\" />\n            <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity=\"0\" />\n          </radialGradient>\n        </defs>\n        {points.map((point) => (\n          <HeatBlob\n            defaultTone={defaultTone}\n            gradientId={gradientId}\n            intensity={intensity}\n            key={point.id}\n            point={point}\n          />\n        ))}\n      </svg>\n    );\n  },\n);\nHeatOverlay.displayName = \"HeatOverlay\";\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
