{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "noise",
  "type": "registry:ui",
  "title": "Noise Background",
  "description": "High-performance procedural grain noise background with CRT static, film dust, vignette, and scanlines.",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/noise.tsx",
      "content": "\"use client\";\n\nimport React, {\n  forwardRef,\n  useCallback,\n  useEffect,\n  useImperativeHandle,\n  useRef,\n  useState,\n  type CSSProperties,\n  type HTMLAttributes,\n  type ReactNode,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type NoiseMode = \"grain\" | \"static\" | \"dust\";\n\nexport interface NoiseProps extends HTMLAttributes<HTMLDivElement> {\n  patternSize?: number;\n  patternScaleX?: number;\n  patternScaleY?: number;\n  patternRefreshInterval?: number;\n  patternAlpha?: number;\n  mode?: NoiseMode;\n  animated?: boolean;\n  fps?: number;\n  vignette?: boolean;\n  vignetteDarkness?: number;\n  scanlines?: boolean;\n  scanlineDensity?: number;\n  scanlineOpacity?: number;\n  blendMode?: CSSProperties[\"mixBlendMode\"];\n  fullScreen?: boolean;\n  canvasClassName?: string;\n  canvasRef?: React.Ref<HTMLCanvasElement>;\n  children?: ReactNode;\n}\n\nconst createPatternFrame = (\n  size: number,\n  alpha: number,\n  mode: NoiseMode,\n  frameIdx: number,\n): HTMLCanvasElement => {\n  const pCanvas = document.createElement(\"canvas\");\n  pCanvas.width = size;\n  pCanvas.height = size;\n  const pCtx = pCanvas.getContext(\"2d\", { alpha: true });\n  if (!pCtx) return pCanvas;\n\n  const imgData = pCtx.createImageData(size, size);\n  const buf32 = new Uint32Array(imgData.data.buffer);\n  const total = size * size;\n\n  for (let i = 0; i < total; i++) {\n    const y = (i / size) | 0;\n\n    let v = 0;\n    let a = alpha;\n\n    if (mode === \"static\") {\n      const scan = (y + frameIdx * 3) % 8 === 0 ? 55 : 0;\n      v = Math.min(255, ((Math.random() * 256) | 0) + scan);\n    } else if (mode === \"dust\") {\n      const isDust = Math.random() > 0.994;\n      const isFleck = Math.random() > 0.999;\n      if (isDust) {\n        v = isFleck ? 255 : (Math.random() * 200 + 55) | 0;\n        a = Math.min(255, alpha * 5);\n      } else {\n        v = (Math.random() * 90) | 0;\n        a = (alpha * 0.35) | 0;\n      }\n    } else {\n      v = (Math.random() * 256) | 0;\n    }\n\n    buf32[i] = (a << 24) | (v << 16) | (v << 8) | v;\n  }\n\n  pCtx.putImageData(imgData, 0, 0);\n  return pCanvas;\n};\n\nexport const Noise = forwardRef<HTMLDivElement, NoiseProps>(\n  (\n    {\n      patternSize = 250,\n      patternScaleX = 1,\n      patternScaleY = 1,\n      patternRefreshInterval = 1,\n      patternAlpha = 15,\n      mode = \"grain\",\n      animated = true,\n      fps = 30,\n      vignette = false,\n      vignetteDarkness = 0.6,\n      scanlines = false,\n      scanlineDensity = 3,\n      scanlineOpacity = 0.1,\n      blendMode = \"normal\",\n      fullScreen = false,\n      canvasClassName,\n      canvasRef: externalCanvasRef,\n      className,\n      style,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const containerRef = useRef<HTMLDivElement | null>(null);\n    const internalCanvasRef = useRef<HTMLCanvasElement | null>(null);\n    const patternsRef = useRef<HTMLCanvasElement[]>([]);\n\n    const [isVisible, setIsVisible] = useState(true);\n    const [isPageActive, setIsPageActive] = useState(true);\n    const [reducedMotion, setReducedMotion] = useState(false);\n\n    useImperativeHandle(ref, () => containerRef.current as HTMLDivElement);\n\n    const setCanvasRefs = useCallback(\n      (node: HTMLCanvasElement | null) => {\n        internalCanvasRef.current = node;\n        if (typeof externalCanvasRef === \"function\") {\n          externalCanvasRef(node);\n        } else if (externalCanvasRef && \"current\" in externalCanvasRef) {\n          (\n            externalCanvasRef as React.MutableRefObject<HTMLCanvasElement | null>\n          ).current = node;\n        }\n      },\n      [externalCanvasRef],\n    );\n\n    useEffect(() => {\n      const media = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n      setReducedMotion(media.matches);\n      const listener = (e: MediaQueryListEvent) => setReducedMotion(e.matches);\n      media.addEventListener(\"change\", listener);\n      return () => media.removeEventListener(\"change\", listener);\n    }, []);\n\n    useEffect(() => {\n      const container = containerRef.current;\n      if (!container) return;\n\n      const observer = new IntersectionObserver(\n        (entries) => {\n          const [entry] = entries;\n          setIsVisible(entry.isIntersecting);\n        },\n        { threshold: 0 },\n      );\n\n      observer.observe(container);\n\n      const handleVisibility = () => {\n        setIsPageActive(!document.hidden);\n      };\n      document.addEventListener(\"visibilitychange\", handleVisibility);\n\n      return () => {\n        observer.disconnect();\n        document.removeEventListener(\"visibilitychange\", handleVisibility);\n      };\n    }, []);\n\n    useEffect(() => {\n      const container = containerRef.current;\n      const canvas = internalCanvasRef.current;\n      if (!container || !canvas) return;\n\n      const updateSize = () => {\n        const rect = container.getBoundingClientRect();\n        const dpr = Math.min(window.devicePixelRatio || 1, 2);\n        const displayWidth = Math.max(1, Math.round(rect.width));\n        const displayHeight = Math.max(1, Math.round(rect.height));\n\n        const targetW = displayWidth * dpr;\n        const targetH = displayHeight * dpr;\n\n        if (canvas.width !== targetW || canvas.height !== targetH) {\n          canvas.width = targetW;\n          canvas.height = targetH;\n          canvas.style.width = `${displayWidth}px`;\n          canvas.style.height = `${displayHeight}px`;\n        }\n      };\n\n      updateSize();\n      const observer = new ResizeObserver(updateSize);\n      observer.observe(container);\n\n      return () => {\n        observer.disconnect();\n      };\n    }, [fullScreen]);\n\n    useEffect(() => {\n      const frameCount = 8;\n      const frames: HTMLCanvasElement[] = [];\n\n      for (let i = 0; i < frameCount; i++) {\n        frames.push(createPatternFrame(patternSize, patternAlpha, mode, i));\n      }\n\n      patternsRef.current = frames;\n    }, [patternSize, patternAlpha, mode]);\n\n    useEffect(() => {\n      let animationId: number;\n      let frameCount = 0;\n      let cacheIdx = 0;\n      let lastTime = performance.now();\n      const targetInterval = fps > 0 ? 1000 / fps : 1000 / 60;\n\n      const render = (now: number) => {\n        animationId = window.requestAnimationFrame(render);\n\n        if (!isVisible || !isPageActive) return;\n\n        if (now - lastTime < targetInterval) return;\n        lastTime = now;\n\n        frameCount++;\n        if (\n          patternRefreshInterval > 1 &&\n          frameCount % patternRefreshInterval !== 0\n        ) {\n          return;\n        }\n\n        const canvas = internalCanvasRef.current;\n        if (!canvas) return;\n\n        const ctx = canvas.getContext(\"2d\", { alpha: true });\n        if (!ctx) return;\n\n        const w = canvas.width;\n        const h = canvas.height;\n        if (w === 0 || h === 0) return;\n\n        ctx.clearRect(0, 0, w, h);\n\n        const frames = patternsRef.current;\n        if (frames.length > 0) {\n          if (animated && !reducedMotion) {\n            cacheIdx = (cacheIdx + 1) % frames.length;\n          }\n          const currentFrame = frames[cacheIdx];\n          const pattern = ctx.createPattern(currentFrame, \"repeat\");\n          if (pattern) {\n            const matrix = new DOMMatrix();\n            if (animated && !reducedMotion) {\n              const jx = (Math.random() * patternSize) | 0;\n              const jy = (Math.random() * patternSize) | 0;\n              matrix.translateSelf(jx, jy);\n            }\n            matrix.scaleSelf(patternScaleX, patternScaleY);\n            pattern.setTransform(matrix);\n            ctx.fillStyle = pattern;\n            ctx.fillRect(0, 0, w, h);\n          }\n        }\n      };\n\n      animationId = window.requestAnimationFrame(render);\n\n      return () => {\n        window.cancelAnimationFrame(animationId);\n      };\n    }, [\n      patternSize,\n      patternScaleX,\n      patternScaleY,\n      patternRefreshInterval,\n      animated,\n      fps,\n      isVisible,\n      isPageActive,\n      reducedMotion,\n    ]);\n\n    return (\n      <div\n        ref={containerRef}\n        data-slot=\"noise\"\n        className={cn(\n          \"relative overflow-hidden\",\n          fullScreen ? \"fixed inset-0 h-screen w-screen z-0\" : \"w-full h-full\",\n          className,\n        )}\n        style={style}\n        {...props}\n      >\n        <canvas\n          ref={setCanvasRefs}\n          data-slot=\"noise-canvas\"\n          className={cn(\n            \"pointer-events-none absolute inset-0 h-full w-full\",\n            canvasClassName,\n          )}\n          style={{\n            imageRendering: \"pixelated\",\n            mixBlendMode: blendMode,\n          }}\n        />\n\n        {vignette && (\n          <div\n            data-slot=\"noise-vignette\"\n            className=\"pointer-events-none absolute inset-0 z-2\"\n            style={{\n              background: `radial-gradient(ellipse at center, transparent 35%, rgba(0, 0, 0, ${vignetteDarkness}) 100%)`,\n            }}\n          />\n        )}\n\n        {scanlines && (\n          <div\n            data-slot=\"noise-scanlines\"\n            className=\"pointer-events-none absolute inset-0 z-3\"\n            style={{\n              background: `repeating-linear-gradient(to bottom, transparent 0px, transparent ${scanlineDensity}px, rgba(0, 0, 0, ${scanlineOpacity}) ${scanlineDensity}px, rgba(0, 0, 0, ${scanlineOpacity}) ${scanlineDensity * 2}px)`,\n            }}\n          />\n        )}\n\n        {children && <div className=\"relative z-10\">{children}</div>}\n      </div>\n    );\n  },\n);\n\nNoise.displayName = \"Noise\";\n\nexport default Noise;\n",
      "type": "registry:ui",
      "target": "@ui/noise.tsx"
    }
  ]
}
