{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "heat-overlay",
  "title": "Heat Overlay",
  "description": "Heatmap-style overlay drawing soft radial blobs for canvas activity samples.",
  "dependencies": [
    "@vllnt/ui@^0.3.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/heat-overlay/heat-overlay.tsx",
      "content": "\"use client\";\n\nimport { type ComponentPropsWithoutRef, 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 = ({\n  ref,\n  ...props\n}: HeatOverlayProps & { ref?: React.Ref<SVGSVGElement> }) => {\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};\nHeatOverlay.displayName = \"HeatOverlay\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.3.0",
  "stability": "stable"
}
