{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "meteor-shower-animation",
  "type": "registry:component",
  "title": "Meteor Shower Animation",
  "author": "Toby Belhome",
  "description": "Beautiful meteor shower animation background built with React and Canvas. Add dynamic falling meteors to your site with smooth, customizable visuals and minimal performance impact.",
  "dependencies": [
    "next-themes"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "examples/motion/backgrounds/meteor-shower-animation/01/meteor-shower.tsx",
      "content": "\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { useTheme } from \"next-themes\";\nimport { cn } from \"@/lib/utils\";\n\ninterface Meteor {\n  x: number;\n  y: number;\n  size: number;\n  speed: number;\n  angle: number;\n  opacity: number;\n  tail: { x: number; y: number }[];\n  tailLength: number;\n}\n\ninterface ThemeColors {\n  background: string;\n  meteorHead: string;\n  meteorTailStart: string;\n  meteorTailMiddle: string;\n  meteorTailEnd: string;\n}\n\nexport default function MeteorShower({\n  children,\n  className\n}: {\n  children: React.ReactNode;\n  className?: string;\n}) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });\n  const meteorsRef = useRef<Meteor[]>([]);\n  const animationRef = useRef<number>(0);\n  const { theme, resolvedTheme } = useTheme();\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  useEffect(() => {\n    const updateDimensions = () => {\n      if (typeof window !== \"undefined\") {\n        setDimensions({\n          width: window.innerWidth,\n          height: window.innerHeight\n        });\n      }\n    };\n\n    updateDimensions();\n    window.addEventListener(\"resize\", updateDimensions);\n\n    return () => {\n      window.removeEventListener(\"resize\", updateDimensions);\n    };\n  }, []);\n\n  useEffect(() => {\n    if (!canvasRef.current || dimensions.width === 0 || dimensions.height === 0 || !mounted) return;\n\n    const canvas = canvasRef.current;\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n\n    canvas.width = dimensions.width;\n    canvas.height = dimensions.height;\n\n    const isDark =\n      resolvedTheme === \"dark\" ||\n      document.documentElement.classList.contains(\"dark\") ||\n      (theme === \"system\" && window.matchMedia(\"(prefers-color-scheme: dark)\").matches);\n\n    const themeColors: ThemeColors = isDark\n      ? {\n          background: \"#000000\",\n          meteorHead: \"rgba(255, 255, 255, 1)\",\n          meteorTailStart: \"rgba(255, 255, 255, 1)\",\n          meteorTailMiddle: \"rgba(255, 240, 200, 0.8)\",\n          meteorTailEnd: \"rgba(255, 200, 100, 0.1)\"\n        }\n      : {\n          background: \"#ffffff\",\n          meteorHead: \"rgba(70, 90, 120, 1)\",\n          meteorTailStart: \"rgba(70, 90, 120, 1)\",\n          meteorTailMiddle: \"rgba(100, 120, 150, 0.8)\",\n          meteorTailEnd: \"rgba(130, 150, 180, 0.1)\"\n        };\n\n    const createMeteor = (): Meteor => {\n      const positionFactor = Math.random();\n      let x, y;\n\n      if (positionFactor < 0.25) {\n        x = -20;\n        y = dimensions.height * Math.random() * 0.7;\n      } else if (positionFactor < 0.5) {\n        x = dimensions.width * Math.random();\n        y = -20;\n      } else if (positionFactor < 0.75) {\n        x = dimensions.width + 20;\n        y = dimensions.height * Math.random() * 0.7;\n      } else {\n        x = dimensions.width * 0.3 + Math.random() * dimensions.width * 0.4;\n        y = -20;\n      }\n\n      const size = 1 + Math.random() * 10;\n      const speed = 3;\n      const angle = Math.PI / 4;\n      const tailLength = 15;\n\n      return {\n        x,\n        y,\n        size,\n        speed,\n        angle,\n        opacity: 0.7 + Math.random() * 0.3,\n        tail: [],\n        tailLength\n      };\n    };\n\n    meteorsRef.current = Array.from({ length: 5 }, createMeteor);\n\n    const animate = () => {\n      ctx.fillStyle = themeColors.background;\n      ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n      meteorsRef.current.forEach((meteor, index) => {\n        meteor.x += Math.cos(meteor.angle) * meteor.speed;\n        meteor.y += Math.sin(meteor.angle) * meteor.speed;\n\n        meteor.tail.unshift({ x: meteor.x, y: meteor.y });\n\n        if (meteor.tail.length > meteor.tailLength) {\n          meteor.tail.pop();\n        }\n\n        if (meteor.tail.length > 1) {\n          ctx.beginPath();\n\n          const gradient = ctx.createLinearGradient(\n            meteor.tail[0].x,\n            meteor.tail[0].y,\n            meteor.tail[meteor.tail.length - 1].x,\n            meteor.tail[meteor.tail.length - 1].y\n          );\n\n          gradient.addColorStop(0, themeColors.meteorTailStart.replace(\"1)\", `${meteor.opacity})`));\n          gradient.addColorStop(\n            0.3,\n            themeColors.meteorTailMiddle.replace(\"0.8)\", `${meteor.opacity * 0.8})`)\n          );\n          gradient.addColorStop(\n            1,\n            themeColors.meteorTailEnd.replace(\"0.1)\", `${meteor.opacity * 0.1})`)\n          );\n\n          ctx.strokeStyle = gradient;\n          ctx.lineWidth = meteor.size;\n\n          ctx.moveTo(meteor.tail[0].x, meteor.tail[0].y);\n          for (let i = 1; i < meteor.tail.length; i++) {\n            ctx.lineTo(meteor.tail[i].x, meteor.tail[i].y);\n          }\n\n          ctx.stroke();\n        }\n\n        ctx.beginPath();\n        ctx.arc(meteor.x, meteor.y, meteor.size / 2, 0, Math.PI * 2);\n        ctx.fillStyle = themeColors.meteorHead.replace(\"1)\", `${meteor.opacity})`);\n        ctx.fill();\n\n        if (meteor.y > dimensions.height || meteor.x < -50 || meteor.x > dimensions.width + 50) {\n          meteorsRef.current[index] = createMeteor();\n        }\n      });\n\n      if (Math.random() < 0.02 && meteorsRef.current.length < 12) {\n        meteorsRef.current.push(createMeteor());\n      }\n\n      animationRef.current = requestAnimationFrame(animate);\n    };\n\n    animate();\n\n    return () => {\n      cancelAnimationFrame(animationRef.current);\n    };\n  }, [dimensions, theme, resolvedTheme, mounted]);\n\n  return (\n    <div className={cn(\"relative w-full\", className)}>\n      <canvas\n        ref={canvasRef}\n        className=\"absolute h-full w-full\"\n        style={{ display: dimensions.width > 0 ? \"block\" : \"none\" }}\n      />\n      <div className=\"z-10\">{children}</div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/meteor-shower-animation/meteor-shower.tsx"
    },
    {
      "path": "examples/motion/backgrounds/meteor-shower-animation/01/page.tsx",
      "content": "import { Button } from \"@/components/ui/button\";\nimport MeteorShower from \"./meteor-shower\";\n\nexport default function MeteorShowerAnimationExample() {\n  return (\n    <MeteorShower className=\"flex aspect-4/2 items-center justify-center\">\n      <div className=\"z-10 space-y-4 text-center lg:space-y-6\">\n        <h4 className=\"text-2xl font-semibold text-black/80 lg:text-3xl dark:text-white/80\">\n          Bundui Components\n        </h4>\n        <Button>Discover Excellence</Button>\n      </div>\n    </MeteorShower>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/meteor-shower-animation/meteor-shower-animation-example.tsx"
    }
  ],
  "meta": {
    "isPro": false
  }
}