{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "PixelSnow-JS-CSS",
	"title": "PixelSnow",
	"description": "Falling pixelated snow effect with customizable density and speed.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "PixelSnow.css",
			"target": "@components/PixelSnow.css",
			"content": ".pixel-snow-container {\n  width: 100%;\n  height: 100%;\n  position: relative;\n  overflow: hidden;\n  contain: layout style paint;\n}\n\n.pixel-snow-container canvas {\n  display: block;\n  width: 100%;\n  height: 100%;\n  transform: translateZ(0);\n  will-change: transform;\n  backface-visibility: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "PixelSnow.jsx",
			"content": "import { useCallback, useEffect, useMemo, useRef } from 'react';\nimport {\n  Color,\n  Mesh,\n  OrthographicCamera,\n  PlaneGeometry,\n  Scene,\n  ShaderMaterial,\n  Vector2,\n  Vector3,\n  WebGLRenderer\n} from 'three';\n\nimport './PixelSnow.css';\n\nconst vertexShader = `\nvoid main() {\n  gl_Position = vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision mediump float;\n\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uFlakeSize;\nuniform float uMinFlakeSize;\nuniform float uPixelResolution;\nuniform float uSpeed;\nuniform float uDepthFade;\nuniform float uFarPlane;\nuniform vec3 uColor;\nuniform float uBrightness;\nuniform float uGamma;\nuniform float uDensity;\nuniform float uVariant;\nuniform float uDirection;\n\n// Precomputed constants\n#define PI 3.14159265\n#define PI_OVER_6 0.5235988\n#define PI_OVER_3 1.0471976\n#define INV_SQRT3 0.57735027\n#define M1 1597334677U\n#define M2 3812015801U\n#define M3 3299493293U\n#define F0 2.3283064e-10\n\n// Optimized hash - inline multiplication\n#define hash(n) (n * (n ^ (n >> 15)))\n#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)\n\n// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))\nconst vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);\nconst vec3 camI = vec3(0.70710678, 0.0, -0.70710678);\nconst vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);\n\n// Precomputed branch direction\nconst vec2 b1d = vec2(0.574, 0.819);\n\nvec3 hash3(uint n) {\n  uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);\n  return vec3(hashed) * F0;\n}\n\nfloat snowflakeDist(vec2 p) {\n  float r = length(p);\n  float a = atan(p.y, p.x);\n  a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);\n  vec2 q = r * vec2(cos(a), sin(a));\n  float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));\n  float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);\n  float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);\n  float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);\n  float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);\n  return min(dMain, min(dB1, dB2)) * 10.0;\n}\n\nvoid main() {\n  // Precompute reciprocals to avoid division\n  float invPixelRes = 1.0 / uPixelResolution;\n  float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));\n  float invPixelSize = 1.0 / pixelSize;\n  \n  vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);\n  vec2 res = uResolution * invPixelSize;\n  float invResX = 1.0 / res.x;\n\n  vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));\n  ray = ray.x * camI + ray.y * camJ + ray.z * camK;\n\n  // Precompute time-based values\n  float timeSpeed = uTime * uSpeed;\n  float windX = cos(uDirection) * 0.4;\n  float windY = sin(uDirection) * 0.4;\n  vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;\n  vec3 pos = camPos;\n\n  // Precompute ray reciprocal for strides\n  vec3 absRay = max(abs(ray), vec3(0.001));\n  vec3 strides = 1.0 / absRay;\n  vec3 raySign = step(ray, vec3(0.0));\n  vec3 phase = fract(pos) * strides;\n  phase = mix(strides - phase, phase, raySign);\n\n  // Precompute for intersection test\n  float rayDotCamK = dot(ray, camK);\n  float invRayDotCamK = 1.0 / rayDotCamK;\n  float invDepthFade = 1.0 / uDepthFade;\n  float halfInvResX = 0.5 * invResX;\n  vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);\n\n  float t = 0.0;\n  for (int i = 0; i < 128; i++) {\n    if (t >= uFarPlane) break;\n    \n    vec3 fpos = floor(pos);\n    uint cellCoord = coord3(fpos);\n    float cellHash = hash3(cellCoord).x;\n\n    if (cellHash < uDensity) {\n      vec3 h = hash3(cellCoord);\n      \n      // Optimized flake position calculation\n      vec3 sinArg1 = fpos.yzx * 0.073;\n      vec3 sinArg2 = fpos.zxy * 0.27;\n      vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);\n      flakePos = flakePos * 0.8 + 0.1 + fpos;\n\n      float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;\n      \n      if (toIntersection > 0.0) {\n        vec3 testPos = pos + ray * toIntersection - flakePos;\n        float testX = dot(testPos, camI);\n        float testY = dot(testPos, camJ);\n        vec2 testUV = abs(vec2(testX, testY));\n        \n        float depth = dot(flakePos - camPos, camK);\n        float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);\n        \n        // Avoid branching with step functions where possible\n        float dist;\n        if (uVariant < 0.5) {\n          dist = max(testUV.x, testUV.y);\n        } else if (uVariant < 1.5) {\n          dist = length(testUV);\n        } else {\n          float invFlakeSize = 1.0 / flakeSize;\n          dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;\n        }\n\n        if (dist < flakeSize) {\n          float flakeSizeRatio = uFlakeSize / flakeSize;\n          float intensity = exp2(-(t + toIntersection) * invDepthFade) *\n                           min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;\n          gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);\n          return;\n        }\n      }\n    }\n\n    float nextStep = min(min(phase.x, phase.y), phase.z);\n    vec3 sel = step(phase, vec3(nextStep));\n    phase = phase - nextStep + strides * sel;\n    t += nextStep;\n    pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);\n  }\n\n  gl_FragColor = vec4(0.0);\n}\n`;\n\nexport default function PixelSnow({\n  color = '#ffffff',\n  flakeSize = 0.01,\n  minFlakeSize = 1.25,\n  pixelResolution = 200,\n  speed = 1.25,\n  depthFade = 8,\n  farPlane = 20,\n  brightness = 1,\n  gamma = 0.4545,\n  density = 0.3,\n  variant = 'square',\n  direction = 125,\n  className = '',\n  style = {}\n}) {\n  const containerRef = useRef(null);\n  const animationRef = useRef(0);\n  const isVisibleRef = useRef(true);\n  const rendererRef = useRef(null);\n  const materialRef = useRef(null);\n  const resizeTimeoutRef = useRef(null);\n\n  // Memoize shader variant value\n  const variantValue = useMemo(() => {\n    return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;\n  }, [variant]);\n\n  // Memoize color conversion\n  const colorVector = useMemo(() => {\n    const threeColor = new Color(color);\n    return new Vector3(threeColor.r, threeColor.g, threeColor.b);\n  }, [color]);\n\n  // Debounced resize handler\n  const handleResize = useCallback(() => {\n    if (resizeTimeoutRef.current) {\n      clearTimeout(resizeTimeoutRef.current);\n    }\n    resizeTimeoutRef.current = window.setTimeout(() => {\n      const container = containerRef.current;\n      const renderer = rendererRef.current;\n      const material = materialRef.current;\n      if (!container || !renderer || !material) return;\n\n      const w = container.offsetWidth;\n      const h = container.offsetHeight;\n      renderer.setSize(w, h);\n      material.uniforms.uResolution.value.set(w, h);\n    }, 100);\n  }, []);\n\n  // Visibility observer\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const observer = new IntersectionObserver(\n      ([entry]) => {\n        isVisibleRef.current = entry.isIntersecting;\n      },\n      { threshold: 0 }\n    );\n\n    observer.observe(container);\n    return () => observer.disconnect();\n  }, []);\n\n  // Main Three.js setup - only runs once\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const scene = new Scene();\n    const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);\n    const renderer = new WebGLRenderer({\n      antialias: false,\n      alpha: true,\n      premultipliedAlpha: false,\n      powerPreference: 'high-performance',\n      stencil: false,\n      depth: false\n    });\n\n    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n    renderer.setSize(container.offsetWidth, container.offsetHeight);\n    renderer.setClearColor(0x000000, 0);\n    container.appendChild(renderer.domElement);\n    rendererRef.current = renderer;\n\n    const material = new ShaderMaterial({\n      vertexShader,\n      fragmentShader,\n      uniforms: {\n        uTime: { value: 0 },\n        uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },\n        uFlakeSize: { value: flakeSize },\n        uMinFlakeSize: { value: minFlakeSize },\n        uPixelResolution: { value: pixelResolution },\n        uSpeed: { value: speed },\n        uDepthFade: { value: depthFade },\n        uFarPlane: { value: farPlane },\n        uColor: { value: colorVector.clone() },\n        uBrightness: { value: brightness },\n        uGamma: { value: gamma },\n        uDensity: { value: density },\n        uVariant: { value: variantValue },\n        uDirection: { value: (direction * Math.PI) / 180 }\n      },\n      transparent: true\n    });\n    materialRef.current = material;\n\n    const geometry = new PlaneGeometry(2, 2);\n    scene.add(new Mesh(geometry, material));\n\n    window.addEventListener('resize', handleResize);\n\n    const startTime = performance.now();\n    const animate = () => {\n      animationRef.current = requestAnimationFrame(animate);\n\n      // Only render if visible\n      if (isVisibleRef.current) {\n        material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;\n        renderer.render(scene, camera);\n      }\n    };\n    animate();\n\n    return () => {\n      cancelAnimationFrame(animationRef.current);\n      window.removeEventListener('resize', handleResize);\n      if (resizeTimeoutRef.current) {\n        clearTimeout(resizeTimeoutRef.current);\n      }\n      if (container.contains(renderer.domElement)) {\n        container.removeChild(renderer.domElement);\n      }\n      renderer.dispose();\n      renderer.forceContextLoss();\n      geometry.dispose();\n      material.dispose();\n      rendererRef.current = null;\n      materialRef.current = null;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [handleResize]); // Only recreate scene when handleResize changes\n\n  // Update material uniforms when props change\n  useEffect(() => {\n    const material = materialRef.current;\n    if (!material) return;\n\n    material.uniforms.uFlakeSize.value = flakeSize;\n    material.uniforms.uMinFlakeSize.value = minFlakeSize;\n    material.uniforms.uPixelResolution.value = pixelResolution;\n    material.uniforms.uSpeed.value = speed;\n    material.uniforms.uDepthFade.value = depthFade;\n    material.uniforms.uFarPlane.value = farPlane;\n    material.uniforms.uBrightness.value = brightness;\n    material.uniforms.uGamma.value = gamma;\n    material.uniforms.uDensity.value = density;\n    material.uniforms.uVariant.value = variantValue;\n    material.uniforms.uDirection.value = (direction * Math.PI) / 180;\n    material.uniforms.uColor.value.copy(colorVector);\n  }, [\n    flakeSize,\n    minFlakeSize,\n    pixelResolution,\n    speed,\n    depthFade,\n    farPlane,\n    brightness,\n    gamma,\n    density,\n    variantValue,\n    direction,\n    colorVector\n  ]);\n\n  return <div ref={containerRef} className={`pixel-snow-container ${className}`} style={style} />;\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0"
	]
}