{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "lava-lamp",
  "type": "registry:component",
  "title": "Lava Lamp",
  "description": "Warm wax blobs that wander, melt into one another and drift apart behind your content, with one that lazily trails your cursor.",
  "categories": [
    "motion",
    "Liquid"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/lava-lamp.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { motion, useInView, useMotionValue, useReducedMotion, useSpring, useTransform } from \"motion/react\";\nimport { type ComponentProps, type PointerEvent, useEffect, useId, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface LavaLampBlob {\n  size: number;\n  color: string;\n  x: number[];\n  y: number[];\n  duration: number;\n}\n\nconst palette = [\"--color-violet-500\", \"--color-fuchsia-500\", \"--color-rose-500\", \"--color-orange-500\", \"--color-amber-400\"];\nconst c = (n: number) => `var(${palette[n - 1]})`;\n\nconst defaultBlobs: LavaLampBlob[] = [\n  { size: 34, color: c(2), x: [16, 40, 28, 10], y: [30, 58, 80, 52], duration: 19 },\n  { size: 28, color: c(1), x: [82, 60, 72, 92], y: [24, 50, 78, 50], duration: 16 },\n  { size: 20, color: c(3), x: [50, 66, 38, 30], y: [12, 38, 44, 18], duration: 13 },\n  { size: 26, color: c(4), x: [36, 20, 54, 62], y: [88, 62, 52, 80], duration: 21 },\n  { size: 17, color: c(5), x: [68, 86, 56, 46], y: [84, 64, 68, 92], duration: 11 },\n  { size: 14, color: c(1), x: [90, 72, 94, 78], y: [10, 30, 40, 16], duration: 9 },\n  { size: 13, color: c(3), x: [6, 24, 14, 30], y: [10, 20, 36, 12], duration: 12 },\n];\n\nexport interface LavaLampProps extends ComponentProps<\"div\"> {\n  blobs?: LavaLampBlob[];\n  followPointer?: boolean;\n  pointerColor?: string;\n  goo?: number;\n  intensity?: number;\n}\n\nconst LEG = \"cubic-bezier(0.37, 0, 0.63, 1)\";\n\nfunction Blob({ blob, running }: { blob: LavaLampBlob; running: boolean }) {\n  const ref = useRef<HTMLDivElement>(null);\n  const anim = useRef<Animation | null>(null);\n  const reduce = useReducedMotion();\n  const { x, y, duration } = blob;\n  const path = x.map((v, i) => `${v},${y[i] ?? y[0]}`).join(\" \");\n\n  useEffect(() => {\n    const el = ref.current;\n    if (!el || reduce) return;\n    const points = path.split(\" \").map((p) => p.split(\",\").map(Number));\n    const frames = [...points, points[0]].map(([px, py]) => ({ transform: `translate(${px}%, ${py}%)`, easing: LEG }));\n    anim.current = el.animate(frames, { duration: duration * 1000, iterations: Number.POSITIVE_INFINITY });\n    return () => {\n      anim.current?.cancel();\n      anim.current = null;\n    };\n  }, [path, duration, reduce]);\n\n  useEffect(() => {\n    if (running) anim.current?.play();\n    else anim.current?.pause();\n  }, [running]);\n\n  return (\n    <div className=\"absolute inset-0\" ref={ref} style={{ transform: `translate(${x[0]}%, ${y[0]}%)` }}>\n      <div\n        className=\"absolute top-0 left-0 aspect-square -translate-x-1/2 -translate-y-1/2 rounded-full\"\n        style={{ width: `${blob.size}%`, background: blob.color }}\n      />\n    </div>\n  );\n}\n\nexport function LavaLamp({\n  blobs = defaultBlobs,\n  followPointer = true,\n  pointerColor = c(3),\n  goo = 10,\n  intensity = 0.85,\n  className,\n  children,\n  onPointerMove,\n  onPointerLeave,\n  ...props\n}: LavaLampProps) {\n  const filterId = `lava-lamp-${useId().replace(/:/g, \"\")}`;\n  const ref = useRef<HTMLDivElement>(null);\n  const inView = useInView(ref);\n  const reduce = useReducedMotion();\n\n  const px = useMotionValue(0);\n  const py = useMotionValue(0);\n  const present = useMotionValue(0);\n  const sx = useSpring(px, { stiffness: 38, damping: 14, mass: 1.2 });\n  const sy = useSpring(py, { stiffness: 38, damping: 14, mass: 1.2 });\n  const swell = useSpring(present, { stiffness: 120, damping: 20 });\n  const scale = useTransform(swell, [0, 1], [0.3, 1]);\n  const opacity = useTransform(swell, [0, 0.25], [0, 1]);\n\n  const handleMove = (e: PointerEvent<HTMLDivElement>) => {\n    onPointerMove?.(e);\n    if (!followPointer || reduce || e.pointerType === \"touch\") return;\n    const r = e.currentTarget.getBoundingClientRect();\n    if (present.get() === 0) {\n      sx.jump(e.clientX - r.left);\n      sy.jump(e.clientY - r.top);\n    }\n    px.set(e.clientX - r.left);\n    py.set(e.clientY - r.top);\n    present.set(1);\n  };\n\n  const handleLeave = (e: PointerEvent<HTMLDivElement>) => {\n    onPointerLeave?.(e);\n    present.set(0);\n  };\n\n  return (\n    <div\n      className={cn(\"relative isolate overflow-hidden\", className)}\n      onPointerLeave={handleLeave}\n      onPointerMove={handleMove}\n      ref={ref}\n      {...props}\n    >\n      <svg aria-hidden className=\"absolute size-0\">\n        <filter colorInterpolationFilters=\"sRGB\" height=\"140%\" id={filterId} width=\"140%\" x=\"-20%\" y=\"-20%\">\n          <feGaussianBlur in=\"SourceGraphic\" result=\"blur\" stdDeviation={goo} />\n          <feColorMatrix in=\"blur\" result=\"goo\" values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 22 -9\" />\n          <feComposite in=\"SourceGraphic\" in2=\"goo\" operator=\"atop\" />\n        </filter>\n      </svg>\n      <div\n        aria-hidden\n        className=\"pointer-events-none absolute inset-0 -z-10\"\n        style={{ filter: `url(#${filterId})`, opacity: intensity }}\n      >\n        {blobs.map((b, i) => (\n          <Blob blob={b} key={i} running={inView} />\n        ))}\n        {followPointer && (\n          <motion.div\n            className=\"absolute top-0 left-0 -mt-[8%] -ml-[8%] aspect-square w-[16%] rounded-full\"\n            style={{ x: sx, y: sy, scale, opacity, background: pointerColor }}\n          />\n        )}\n      </div>\n      {children}\n    </div>\n  );\n}\n"
    }
  ]
}