{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "metallic-lava",
  "title": "Metallic Lava",
  "description": "An animated metallic lava background using WebGL shaders.",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "registry/wise-ui/components/metallic-lava.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\ninterface MetallicLavaProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Base metallic color */\n  color?: string\n  /** Highlight/specular color */\n  highlightColor?: string\n  /** Animation speed multiplier (1 = default) */\n  speed?: number\n  /** Number of lava blobs */\n  blobCount?: number\n  /** Visual mode - \"dark\" renders light blobs on dark, \"light\" inverts */\n  mode?: \"dark\" | \"light\"\n}\n\nconst MetallicLava = React.forwardRef<HTMLDivElement, MetallicLavaProps>(\n  (\n    {\n      color = \"#8a9bae\",\n      highlightColor = \"#c5d0dc\",\n      speed = 1,\n      blobCount = 28,\n      mode = \"dark\",\n      className,\n      children,\n      ...props\n    },\n    ref\n  ) => {\n    const canvasRef = React.useRef<HTMLCanvasElement>(null)\n    const containerRef = React.useRef<HTMLDivElement>(null)\n\n    React.useImperativeHandle(ref, () => containerRef.current!)\n\n    React.useEffect(() => {\n      const canvas = canvasRef.current\n      const container = containerRef.current\n      if (!canvas || !container) return\n\n      const ctx = canvas.getContext(\"2d\")\n      if (!ctx) return\n\n      // Padding around canvas for blur/contrast filter - prevents edge artifacts\n      const PAD = 80\n      let w = 0\n      let h = 0\n      let rafId = 0\n\n      interface Blob {\n        x: number\n        y: number\n        rx: number\n        ry: number\n        rotation: number\n        rotSpeed: number\n        vx: number\n        vy: number\n        phase: number\n        freq: number\n        ampX: number\n        ampY: number\n        pulsePhase: number\n        pulseSpeed: number\n        numPoints: number\n        offsets: number[]\n      }\n\n      let blobs: Blob[] = []\n\n      function initBlobs() {\n        // Distribute blobs in a jittered grid for even coverage\n        const cols = Math.max(1, Math.ceil(Math.sqrt(blobCount * w / h)))\n        const rows = Math.max(1, Math.ceil(blobCount / cols))\n        const cellW = w / cols\n        const cellH = h / rows\n\n        blobs = Array.from({ length: blobCount }, (_, i) => {\n          const col = i % cols\n          const row = Math.floor(i / cols)\n          const baseSpeed = 0.12 + Math.random() * 0.35\n          const angle = Math.random() * Math.PI * 2\n          const baseR = 50 + Math.random() * 120\n          const stretch = 0.5 + Math.random() * 1.0\n          const numPoints = 8 + Math.floor(Math.random() * 5)\n          const offsets = Array.from(\n            { length: numPoints },\n            () => 0.7 + Math.random() * 0.6\n          )\n          return {\n            x: (col + 0.5) * cellW + (Math.random() - 0.5) * cellW * 0.8,\n            y: (row + 0.5) * cellH + (Math.random() - 0.5) * cellH * 0.8,\n            rx: baseR * stretch,\n            ry: baseR / stretch,\n            rotation: Math.random() * Math.PI * 2,\n            rotSpeed: (Math.random() - 0.5) * 0.08,\n            vx: Math.cos(angle) * baseSpeed,\n            vy: Math.sin(angle) * baseSpeed,\n            phase: Math.random() * Math.PI * 2,\n            freq: 0.15 + Math.random() * 0.35,\n            ampX: 25 + Math.random() * 70,\n            ampY: 25 + Math.random() * 70,\n            pulsePhase: Math.random() * Math.PI * 2,\n            pulseSpeed: 0.12 + Math.random() * 0.3,\n            numPoints,\n            offsets,\n          }\n        })\n      }\n\n      function resize() {\n        const rect = container!.getBoundingClientRect()\n        const dpr = Math.min(window.devicePixelRatio, 2)\n        w = rect.width\n        h = rect.height\n        // Canvas is larger than container to include padding for filter\n        const cw = w + PAD * 2\n        const ch = h + PAD * 2\n        canvas!.width = cw * dpr\n        canvas!.height = ch * dpr\n        canvas!.style.width = `${cw}px`\n        canvas!.style.height = `${ch}px`\n        // Scale for DPR and translate so container (0,0) → canvas (PAD, PAD)\n        ctx!.setTransform(dpr, 0, 0, dpr, PAD * dpr, PAD * dpr)\n        if (blobs.length === 0) initBlobs()\n      }\n\n      function animate(time: number) {\n        const t = time * 0.001 * speed\n\n        ctx!.globalCompositeOperation = \"source-over\"\n        ctx!.fillStyle = \"#0a0a0f\"\n        // Clear the full canvas including padding\n        ctx!.fillRect(-PAD, -PAD, w + PAD * 2, h + PAD * 2)\n\n        ctx!.globalCompositeOperation = \"lighter\"\n\n        for (const b of blobs) {\n          b.x += b.vx * speed\n          b.y += b.vy * speed\n\n          const wobbleX =\n            Math.sin(t * b.freq + b.phase) * b.ampX +\n            Math.sin(t * b.freq * 0.37 + b.phase * 2.1) * b.ampX * 0.3\n          const wobbleY =\n            Math.cos(t * b.freq * 0.8 + b.phase) * b.ampY +\n            Math.cos(t * b.freq * 0.29 + b.phase * 1.7) * b.ampY * 0.25\n\n          const pulse = 1 + Math.sin(t * b.pulseSpeed + b.pulsePhase) * 0.15\n          const rx = b.rx * pulse\n          const ry = b.ry * pulse\n          const rot = b.rotation + t * b.rotSpeed\n\n          const drawX = b.x + wobbleX\n          const drawY = b.y + wobbleY\n\n          const pad = Math.max(b.rx, b.ry) * 2.5\n          if (b.x > w + pad) b.x = -pad\n          if (b.x < -pad) b.x = w + pad\n          if (b.y > h + pad) b.y = -pad\n          if (b.y < -pad) b.y = h + pad\n\n          // Sharper radial gradient for glassy metallic look\n          const outerR = Math.max(rx, ry)\n          const grad = ctx!.createRadialGradient(\n            drawX, drawY, 0,\n            drawX, drawY, outerR\n          )\n          grad.addColorStop(0, \"rgba(255, 255, 255, 1.0)\")\n          grad.addColorStop(0.12, \"rgba(255, 255, 255, 0.88)\")\n          grad.addColorStop(0.3, \"rgba(225, 232, 245, 0.55)\")\n          grad.addColorStop(0.5, \"rgba(180, 195, 215, 0.18)\")\n          grad.addColorStop(0.72, \"rgba(140, 155, 175, 0.04)\")\n          grad.addColorStop(1, \"rgba(0, 0, 0, 0)\")\n\n          ctx!.fillStyle = grad\n\n          // Draw blobby shape using deformed ellipse points + smooth bezier\n          const rotCos = Math.cos(rot)\n          const rotSin = Math.sin(rot)\n          const pts: { x: number; y: number }[] = []\n          for (let i = 0; i < b.numPoints; i++) {\n            const a = (i / b.numPoints) * Math.PI * 2\n            const ex = rx * Math.cos(a) * b.offsets[i]\n            const ey = ry * Math.sin(a) * b.offsets[i]\n            pts.push({\n              x: drawX + ex * rotCos - ey * rotSin,\n              y: drawY + ex * rotSin + ey * rotCos,\n            })\n          }\n\n          ctx!.beginPath()\n          const last = pts[pts.length - 1]\n          const first = pts[0]\n          ctx!.moveTo((last.x + first.x) / 2, (last.y + first.y) / 2)\n          for (let i = 0; i < b.numPoints; i++) {\n            const next = pts[(i + 1) % b.numPoints]\n            ctx!.quadraticCurveTo(\n              pts[i].x, pts[i].y,\n              (pts[i].x + next.x) / 2, (pts[i].y + next.y) / 2\n            )\n          }\n          ctx!.closePath()\n          ctx!.fill()\n        }\n\n        rafId = requestAnimationFrame(animate)\n      }\n\n      resize()\n      rafId = requestAnimationFrame(animate)\n\n      const ro = new ResizeObserver(resize)\n      ro.observe(container)\n\n      return () => {\n        cancelAnimationFrame(rafId)\n        ro.disconnect()\n      }\n    }, [blobCount, speed])\n\n    const isDark = mode === \"dark\"\n    const bgColor = isDark ? \"#0a0a0f\" : \"#f0f0f5\"\n    const canvasFilter = isDark\n      ? \"blur(12px) contrast(14)\"\n      : \"blur(12px) contrast(14) invert(1)\"\n    const overlayBlend = isDark ? \"multiply\" : \"screen\"\n\n    return (\n      <div\n        ref={containerRef}\n        className={cn(\"relative overflow-hidden\", className)}\n        style={{ background: bgColor }}\n        {...props}\n      >\n        {/* Canvas with blur + contrast = metaball merging effect */}\n        <div\n          className=\"absolute\"\n          style={{\n            inset: -80,\n            filter: canvasFilter,\n          }}\n        >\n          <canvas ref={canvasRef} className=\"absolute inset-0\" />\n        </div>\n\n        {/* Metallic color overlay */}\n        <div\n          className=\"absolute inset-0\"\n          style={{\n            backgroundImage: `linear-gradient(\n              135deg,\n              ${color} 0%,\n              ${highlightColor} 25%,\n              ${color} 50%,\n              ${isDark ? \"#6b7a8a\" : \"#a0b0c0\"} 75%,\n              ${highlightColor} 100%\n            )`,\n            backgroundSize: \"400% 400%\",\n            animation: \"metallic-lava-gradient 20s ease-in-out infinite\",\n            mixBlendMode: overlayBlend,\n          }}\n        />\n\n        {/* Moving specular sheen for chrome/glass effect */}\n        <div\n          className=\"absolute pointer-events-none\"\n          style={{\n            width: \"120%\",\n            height: \"80%\",\n            top: \"-10%\",\n            left: \"-10%\",\n            background: `radial-gradient(ellipse, rgba(255,255,255,${isDark ? 0.15 : 0.08}) 0%, transparent 55%)`,\n            mixBlendMode: isDark ? \"screen\" : \"overlay\",\n            animation: \"metallic-lava-sheen 18s ease-in-out infinite\",\n          }}\n        />\n\n        {children && <div className=\"relative z-10\">{children}</div>}\n\n        <style>{`\n          @keyframes metallic-lava-sheen {\n            0%   { transform: translate(0%, 0%); }\n            25%  { transform: translate(20%, 15%); }\n            50%  { transform: translate(5%, 30%); }\n            75%  { transform: translate(-10%, 10%); }\n            100% { transform: translate(0%, 0%); }\n          }\n          @keyframes metallic-lava-gradient {\n            0%   { background-position: 0% 0%; }\n            25%  { background-position: 100% 30%; }\n            50%  { background-position: 60% 100%; }\n            75%  { background-position: 0% 60%; }\n            100% { background-position: 0% 0%; }\n          }\n        `}</style>\n      </div>\n    )\n  }\n)\nMetallicLava.displayName = \"MetallicLava\"\n\nexport { MetallicLava }\nexport type { MetallicLavaProps }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}