{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bolt-strike",
  "type": "registry:ui",
  "title": "Bolt Strike",
  "description": "lightning bolts burst randomly with sparks and particles, creating a high-energy, reactive background effect.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/bolt-strike.tsx",
      "content": "'use client';\nimport React, { useEffect, useRef } from 'react';\nimport { cn } from '@/lib/utils';\n\ntype BoltStrikeProps = {\n  color?: string;\n  speed?: number;\n  intensity?: number;\n  className?: string;\n  children?: React.ReactNode;\n};\n\nclass Particle {\n  x: number;\n  y: number;\n  vx: number;\n  vy: number;\n  life: number;\n  maxLife: number;\n  size: number;\n\n  constructor(x: number, y: number) {\n    this.x = x;\n    this.y = y;\n    const angle = Math.random() * Math.PI * 2;\n    const speed = 2 + Math.random() * 4;\n    this.vx = Math.cos(angle) * speed;\n    this.vy = Math.sin(angle) * speed;\n    this.life = 0;\n    this.maxLife = 30 + Math.random() * 30;\n    this.size = 2 + Math.random() * 2;\n  }\n\n  update() {\n    this.x += this.vx;\n    this.y += this.vy;\n    this.vx *= 0.96;\n    this.vy *= 0.96;\n    this.life++;\n  }\n\n  draw(ctx: CanvasRenderingContext2D, color: string) {\n    const alpha = Math.max(0, 1 - this.life / this.maxLife);\n    const radius = Math.max(0.1, this.size * alpha);\n    ctx.shadowBlur = 10;\n    ctx.shadowColor = color;\n    ctx.fillStyle =\n      color +\n      Math.floor(alpha * 255)\n        .toString(16)\n        .padStart(2, '0');\n    ctx.beginPath();\n    ctx.arc(this.x, this.y, radius, 0, Math.PI * 2);\n    ctx.fill();\n    ctx.shadowBlur = 0;\n  }\n\n  isDead() {\n    return this.life >= this.maxLife;\n  }\n}\n\nclass Lightning {\n  segments: Array<{ x: number; y: number }>;\n  life: number;\n  maxLife: number;\n  thickness: number;\n\n  constructor(startX: number, startY: number, endX: number, endY: number) {\n    this.segments = [];\n    this.life = 0;\n    this.maxLife = 12;\n    this.thickness = 1.5 + Math.random() * 2;\n    this.generate(startX, startY, endX, endY);\n  }\n\n  generate(startX: number, startY: number, endX: number, endY: number) {\n    const distance = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2);\n    const steps = Math.max(6, Math.floor(distance / 25));\n    const roughness = distance / 12;\n\n    this.segments.push({ x: startX, y: startY });\n\n    for (let i = 1; i < steps; i++) {\n      const t = i / steps;\n      const x =\n        startX + (endX - startX) * t + (Math.random() - 0.5) * roughness;\n      const y =\n        startY + (endY - startY) * t + (Math.random() - 0.5) * roughness;\n      this.segments.push({ x, y });\n    }\n\n    this.segments.push({ x: endX, y: endY });\n  }\n\n  update() {\n    this.life++;\n  }\n\n  draw(ctx: CanvasRenderingContext2D, color: string) {\n    const alpha = Math.max(0, 1 - this.life / this.maxLife);\n\n    ctx.shadowBlur = 25;\n    ctx.shadowColor = color;\n    ctx.strokeStyle =\n      color +\n      Math.floor(alpha * 255)\n        .toString(16)\n        .padStart(2, '0');\n    ctx.lineWidth = this.thickness * 2;\n    ctx.lineCap = 'round';\n    ctx.lineJoin = 'round';\n\n    ctx.beginPath();\n    this.segments.forEach((seg, i) => {\n      if (i === 0) ctx.moveTo(seg.x, seg.y);\n      else ctx.lineTo(seg.x, seg.y);\n    });\n    ctx.stroke();\n\n    ctx.lineWidth = this.thickness * 0.7;\n    ctx.strokeStyle =\n      '#ffffff' +\n      Math.floor(alpha * 220)\n        .toString(16)\n        .padStart(2, '0');\n    ctx.beginPath();\n    this.segments.forEach((seg, i) => {\n      if (i === 0) ctx.moveTo(seg.x, seg.y);\n      else ctx.lineTo(seg.x, seg.y);\n    });\n    ctx.stroke();\n\n    ctx.shadowBlur = 0;\n  }\n\n  isDead() {\n    return this.life >= this.maxLife;\n  }\n}\n\nclass Spark {\n  x: number;\n  y: number;\n  life: number;\n  maxLife: number;\n  size: number;\n\n  constructor(x: number, y: number) {\n    this.x = x;\n    this.y = y;\n    this.life = 0;\n    this.maxLife = 20;\n    this.size = 8 + Math.random() * 12;\n  }\n\n  update() {\n    this.life++;\n  }\n\n  draw(ctx: CanvasRenderingContext2D, color: string) {\n    const alpha = Math.max(0, 1 - this.life / this.maxLife);\n    const currentSize = Math.max(\n      0.1,\n      this.size * (1 - this.life / this.maxLife),\n    );\n\n    const gradient = ctx.createRadialGradient(\n      this.x,\n      this.y,\n      0,\n      this.x,\n      this.y,\n      currentSize,\n    );\n    gradient.addColorStop(\n      0,\n      '#ffffff' +\n        Math.floor(alpha * 255)\n          .toString(16)\n          .padStart(2, '0'),\n    );\n    gradient.addColorStop(\n      0.3,\n      color +\n        Math.floor(alpha * 255)\n          .toString(16)\n          .padStart(2, '0'),\n    );\n    gradient.addColorStop(1, color + '00');\n\n    ctx.fillStyle = gradient;\n    ctx.beginPath();\n    ctx.arc(this.x, this.y, currentSize, 0, Math.PI * 2);\n    ctx.fill();\n  }\n\n  isDead() {\n    return this.life >= this.maxLife;\n  }\n}\n\nfunction BoltStrike({\n  color = '#7c3aed',\n  speed = 1,\n  intensity = 1,\n  className = '',\n  children,\n}: BoltStrikeProps) {\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n  const animFrameRef = useRef<number | undefined>(undefined);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    const ctx = canvas.getContext('2d');\n    if (!ctx) return;\n\n    const resize = () => {\n      canvas.width = canvas.offsetWidth;\n      canvas.height = canvas.offsetHeight;\n    };\n    resize();\n    window.addEventListener('resize', resize);\n\n    const particles: Particle[] = [];\n    const lightnings: Lightning[] = [];\n    const sparks: Spark[] = [];\n\n    const animate = () => {\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n      if (Math.random() < 0.08 * intensity) {\n        const x = Math.random() * canvas.width;\n        const y = Math.random() * canvas.height;\n\n        sparks.push(new Spark(x, y));\n\n        const branches = 3 + Math.floor(Math.random() * 4);\n        for (let i = 0; i < branches; i++) {\n          const angle = (Math.PI * 2 * i) / branches + Math.random() * 0.5;\n          const dist = 60 + Math.random() * 100;\n          const endX = x + Math.cos(angle) * dist;\n          const endY = y + Math.sin(angle) * dist;\n          lightnings.push(new Lightning(x, y, endX, endY));\n        }\n\n        for (let i = 0; i < 15; i++) {\n          particles.push(new Particle(x, y));\n        }\n      }\n\n      for (let i = sparks.length - 1; i >= 0; i--) {\n        sparks[i].update();\n        if (sparks[i].isDead()) {\n          sparks.splice(i, 1);\n        } else {\n          sparks[i].draw(ctx, color);\n        }\n      }\n\n      for (let i = lightnings.length - 1; i >= 0; i--) {\n        lightnings[i].update();\n        if (lightnings[i].isDead()) {\n          lightnings.splice(i, 1);\n        } else {\n          lightnings[i].draw(ctx, color);\n        }\n      }\n\n      for (let i = particles.length - 1; i >= 0; i--) {\n        particles[i].update();\n        if (particles[i].isDead()) {\n          particles.splice(i, 1);\n        } else {\n          particles[i].draw(ctx, color);\n        }\n      }\n\n      animFrameRef.current = requestAnimationFrame(animate);\n    };\n\n    animate();\n\n    return () => {\n      window.removeEventListener('resize', resize);\n      if (animFrameRef.current) {\n        cancelAnimationFrame(animFrameRef.current);\n      }\n    };\n  }, [color, speed, intensity]);\n\n  return (\n    <div className={cn('relative overflow-hidden', className)}>\n      <canvas\n        ref={canvasRef}\n        className='absolute inset-0 w-full h-full'\n        style={{ pointerEvents: 'none' }}\n      />\n      {children && <div className='relative z-10'>{children}</div>}\n    </div>\n  );\n}\n\nexport { BoltStrike };\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
