{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "not-found",
  "type": "registry:ui",
  "title": "Not Found",
  "description": "A beautifully designed 404 page to guide users when content is missing.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": ["@scrollxui/button", "@scrollxui/particles"],
  "dependencies": [
    "@radix-ui/react-slot",
    "class-variance-authority",
    "clsx",
    "three",
    "@types/three",
    "tailwind-merge"
  ],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/not-found.tsx",
      "content": "'use client';\n\nimport { useRef, useEffect, useState, MouseEvent } from 'react';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport { Particles } from '@/components/ui/particles';\nimport { Button } from '@/components/ui/button';\n\ninterface NotFoundProps {\n  particleCount?: number;\n  particleSize?: number;\n  animate?: boolean;\n  imageLight?: string;\n  imageDark?: string;\n  buttonText?: string;\n  buttonHref?: string;\n  className?: string;\n  onButtonClick?: () => void;\n}\n\nexport default function NotFound({\n  particleCount = 12500,\n  particleSize = 5,\n  animate = false,\n  imageLight = '/images/404-lightc.png',\n  imageDark = '/images/404-darkc.png',\n  buttonText = 'Back to Home',\n  buttonHref = '/',\n  className = '',\n  onButtonClick,\n}: NotFoundProps) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const imageRef = useRef<HTMLDivElement>(null);\n  const [isDark, setIsDark] = useState(false);\n\n  useEffect(() => {\n    const observer = () => {\n      const html = document.documentElement;\n      setIsDark(html.classList.contains('dark'));\n    };\n\n    observer();\n\n    const mutationObserver = new MutationObserver(observer);\n    mutationObserver.observe(document.documentElement, {\n      attributes: true,\n      attributeFilter: ['class'],\n    });\n\n    return () => mutationObserver.disconnect();\n  }, []);\n\n  const handleMouseMove = (e: MouseEvent) => {\n    const container = containerRef.current;\n    const image = imageRef.current;\n    if (!container || !image) return;\n\n    const { left, top, width, height } = container.getBoundingClientRect();\n    const x = e.clientX - left;\n    const y = e.clientY - top;\n\n    const rotateX = ((y - height / 2) / height) * -10;\n    const rotateY = ((x - width / 2) / width) * 10;\n\n    image.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;\n  };\n\n  const handleMouseLeave = () => {\n    if (imageRef.current) {\n      imageRef.current.style.transform = 'rotateX(0deg) rotateY(0deg)';\n    }\n  };\n\n  return (\n    <div\n      ref={containerRef}\n      onMouseMove={handleMouseMove}\n      onMouseLeave={handleMouseLeave}\n      className={`relative h-96 md:h-screen w-full flex items-center justify-center overflow-hidden bg-white dark:bg-black ${className}`}\n      style={{ perspective: '1000px' }}\n    >\n      <Particles\n        color={isDark ? '#ffffff' : '#000000'}\n        particleCount={particleCount}\n        particleSize={particleSize}\n        animate={animate}\n        className='absolute inset-0 z-0'\n      />\n\n      <div\n        ref={imageRef}\n        className='absolute inset-0 w-full h-full transition-transform duration-300 ease-out will-change-transform pointer-events-none z-10'\n      >\n        <Image\n          src={imageLight}\n          alt='404 Light'\n          fill\n          className='object-contain dark:hidden'\n          priority\n        />\n        <Image\n          src={imageDark}\n          alt='404 Dark'\n          fill\n          className='object-contain hidden dark:block'\n          priority\n        />\n      </div>\n\n      <Particles\n        color={isDark ? '#ffffff' : '#000000'}\n        particleCount={particleCount}\n        particleSize={particleSize}\n        animate={animate}\n        className='absolute inset-0 z-20 pointer-events-none'\n      />\n\n      <div className='relative z-30 mt-16 md:mt-0'>\n        <Link href={buttonHref} onClick={onButtonClick}>\n          <Button variant='default'>{buttonText}</Button>\n        </Link>\n      </div>\n    </div>\n  );\n}\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/button.tsx",
      "content": "import * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst buttonVariants = cva(\n  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90',\n        destructive:\n          'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90',\n        outline:\n          'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',\n        secondary:\n          'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n        ghost: 'hover:bg-accent hover:text-accent-foreground',\n        link: 'text-primary underline-offset-4 hover:underline',\n        success: 'bg-green-600 text-white hover:bg-green-700',\n        warning: 'bg-yellow-500 text-black hover:bg-yellow-600',\n        info: 'bg-blue-500 text-white hover:bg-blue-600',\n        dark: 'bg-gray-800 text-white hover:bg-gray-700',\n        light: 'bg-gray-100 text-gray-800 hover:bg-gray-200',\n        gradient:\n          'bg-linear-to-r from-indigo-500 via-purple-500 to-pink-500 text-white hover:opacity-90',\n        glass:\n          'bg-white/10 backdrop-blur-md text-white border border-white/20 hover:bg-white/20',\n      },\n      size: {\n        default: 'h-9 px-4 py-2',\n        sm: 'h-8 rounded-md px-3 text-xs',\n        lg: 'h-10 rounded-md px-8',\n        icon: 'h-9 w-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\nexport interface ButtonProps\n  extends\n    React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : 'button';\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    );\n  },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/particles.tsx",
      "content": "\"use client\";\nimport { useEffect, useRef } from \"react\";\nimport * as THREE from \"three\";\n\ninterface ParticlesProps {\n  color?: string;\n  particleCount?: number;\n  particleSize?: number;\n  animate?: boolean;\n  className?: string;\n}\n\nexport function Particles({\n  color = \"#ff3366\",\n  particleCount = 10000,\n  particleSize = 35,\n  animate = true,\n  className = \"\",\n}: ParticlesProps) {\n  const mountRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const container = mountRef.current;\n    if (!container) return;\n\n    let camera: THREE.PerspectiveCamera;\n    let scene: THREE.Scene;\n    let material: THREE.PointsMaterial;\n    let animationFrameId: number;\n    let mouseX = 0;\n    let mouseY = 0;\n\n    const init = () => {\n      camera = new THREE.PerspectiveCamera(\n        55,\n        window.innerWidth / window.innerHeight,\n        2,\n        2000\n      );\n      camera.position.z = 1000;\n\n      scene = new THREE.Scene();\n      scene.fog = new THREE.FogExp2(0x000000, 0.001);\n\n      const geometry = new THREE.BufferGeometry();\n      const vertices: number[] = [];\n\n      for (let i = 0; i < particleCount; i++) {\n        vertices.push(\n          2000 * Math.random() - 1000,\n          2000 * Math.random() - 1000,\n          2000 * Math.random() - 1000\n        );\n      }\n\n      geometry.setAttribute(\n        \"position\",\n        new THREE.Float32BufferAttribute(vertices, 3)\n      );\n\n      const sprite = new THREE.TextureLoader().load(\"/assets/disc.png\");\n      material = new THREE.PointsMaterial({\n        size: particleSize,\n        sizeAttenuation: true,\n        map: sprite,\n        alphaTest: 0.5,\n        transparent: true,\n      });\n      material.color.setStyle(color);\n\n      const particles = new THREE.Points(geometry, material);\n      scene.add(particles);\n\n      const renderer = new THREE.WebGLRenderer({\n        antialias: true,\n        alpha: true,\n      });\n      renderer.setPixelRatio(window.devicePixelRatio);\n      renderer.setSize(window.innerWidth, window.innerHeight);\n      container.appendChild(renderer.domElement);\n\n      return renderer;\n    };\n\n    const handleResize = () => {\n      if (!camera || !renderer) return;\n      camera.aspect = window.innerWidth / window.innerHeight;\n      camera.updateProjectionMatrix();\n      renderer.setSize(window.innerWidth, window.innerHeight);\n    };\n\n    const handlePointerMove = (event: PointerEvent) => {\n      if (event.isPrimary) {\n        mouseX = event.clientX - window.innerWidth / 2;\n        mouseY = event.clientY - window.innerHeight / 2;\n      }\n    };\n\n    const animateScene = () => {\n      if (!camera || !scene || !renderer || !material) return;\n\n      if (animate) {\n        const time = Date.now() * 0.00005;\n        const h = ((360 * (1.0 + time)) % 360) / 360;\n        material.color.setHSL(h, 0.5, 0.5);\n      }\n\n      camera.position.x += (mouseX - camera.position.x) * 0.05;\n      camera.position.y += (-mouseY - camera.position.y) * 0.05;\n      camera.lookAt(scene.position);\n\n      renderer.render(scene, camera);\n      animationFrameId = requestAnimationFrame(animateScene);\n    };\n\n    const renderer = init();\n    window.addEventListener(\"resize\", handleResize);\n    window.addEventListener(\"pointermove\", handlePointerMove);\n    animateScene();\n\n    return () => {\n      window.removeEventListener(\"resize\", handleResize);\n      window.removeEventListener(\"pointermove\", handlePointerMove);\n      cancelAnimationFrame(animationFrameId);\n\n      if (renderer) {\n        renderer.dispose();\n        container.removeChild(renderer.domElement);\n      }\n\n      if (material) material.dispose();\n    };\n  }, [color, particleCount, particleSize, animate]);\n\n  return (\n    <div\n      ref={mountRef}\n      className={`absolute top-0 left-0 w-full h-full pointer-events-none ${className}`}\n    />\n  );\n}\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}"
    }
  ]
}
