{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "PixelCard-JS-TW",
	"title": "PixelCard",
	"description": "Card content revealed through pixel expansion transition.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "PixelCard/PixelCard.jsx",
			"content": "import { useEffect, useRef } from 'react';\n\nclass Pixel {\n  constructor(canvas, context, x, y, color, speed, delay) {\n    this.width = canvas.width;\n    this.height = canvas.height;\n    this.ctx = context;\n    this.x = x;\n    this.y = y;\n    this.color = color;\n    this.speed = this.getRandomValue(0.1, 0.9) * speed;\n    this.size = 0;\n    this.sizeStep = Math.random() * 0.4;\n    this.minSize = 0.5;\n    this.maxSizeInteger = 2;\n    this.maxSize = this.getRandomValue(this.minSize, this.maxSizeInteger);\n    this.delay = delay;\n    this.counter = 0;\n    this.counterStep = Math.random() * 4 + (this.width + this.height) * 0.01;\n    this.isIdle = false;\n    this.isReverse = false;\n    this.isShimmer = false;\n  }\n\n  getRandomValue(min, max) {\n    return Math.random() * (max - min) + min;\n  }\n\n  draw() {\n    const centerOffset = this.maxSizeInteger * 0.5 - this.size * 0.5;\n    this.ctx.fillStyle = this.color;\n    this.ctx.fillRect(this.x + centerOffset, this.y + centerOffset, this.size, this.size);\n  }\n\n  appear() {\n    this.isIdle = false;\n    if (this.counter <= this.delay) {\n      this.counter += this.counterStep;\n      return;\n    }\n    if (this.size >= this.maxSize) {\n      this.isShimmer = true;\n    }\n    if (this.isShimmer) {\n      this.shimmer();\n    } else {\n      this.size += this.sizeStep;\n    }\n    this.draw();\n  }\n\n  disappear() {\n    this.isShimmer = false;\n    this.counter = 0;\n    if (this.size <= 0) {\n      this.isIdle = true;\n      return;\n    } else {\n      this.size -= 0.1;\n    }\n    this.draw();\n  }\n\n  shimmer() {\n    if (this.size >= this.maxSize) {\n      this.isReverse = true;\n    } else if (this.size <= this.minSize) {\n      this.isReverse = false;\n    }\n    if (this.isReverse) {\n      this.size -= this.speed;\n    } else {\n      this.size += this.speed;\n    }\n  }\n}\n\nfunction getEffectiveSpeed(value, reducedMotion) {\n  const min = 0;\n  const max = 100;\n  const throttle = 0.001;\n  const parsed = parseInt(value, 10);\n\n  if (parsed <= min || reducedMotion) {\n    return min;\n  } else if (parsed >= max) {\n    return max * throttle;\n  } else {\n    return parsed * throttle;\n  }\n}\n\nconst VARIANTS = {\n  default: {\n    activeColor: null,\n    gap: 5,\n    speed: 35,\n    colors: '#f8fafc,#f1f5f9,#cbd5e1',\n    noFocus: false\n  },\n  blue: {\n    activeColor: '#e0f2fe',\n    gap: 10,\n    speed: 25,\n    colors: '#e0f2fe,#7dd3fc,#0ea5e9',\n    noFocus: false\n  },\n  yellow: {\n    activeColor: '#fef08a',\n    gap: 3,\n    speed: 20,\n    colors: '#fef08a,#fde047,#eab308',\n    noFocus: false\n  },\n  pink: {\n    activeColor: '#fecdd3',\n    gap: 6,\n    speed: 80,\n    colors: '#fecdd3,#fda4af,#e11d48',\n    noFocus: true\n  }\n};\n\nexport default function PixelCard({ variant = 'default', gap, speed, colors, noFocus, className = '', children }) {\n  const containerRef = useRef(null);\n  const canvasRef = useRef(null);\n  const pixelsRef = useRef([]);\n  const animationRef = useRef(null);\n  const timePreviousRef = useRef(performance.now());\n  const reducedMotion = useRef(\n    typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches\n  ).current;\n\n  const variantCfg = VARIANTS[variant] || VARIANTS.default;\n  const finalGap = gap ?? variantCfg.gap;\n  const finalSpeed = speed ?? variantCfg.speed;\n  const finalColors = colors ?? variantCfg.colors;\n  const finalNoFocus = noFocus ?? variantCfg.noFocus;\n\n  const initPixels = () => {\n    if (!containerRef.current || !canvasRef.current) return;\n\n    const rect = containerRef.current.getBoundingClientRect();\n    const width = Math.floor(rect.width);\n    const height = Math.floor(rect.height);\n    const ctx = canvasRef.current.getContext('2d');\n\n    canvasRef.current.width = width;\n    canvasRef.current.height = height;\n    canvasRef.current.style.width = `${width}px`;\n    canvasRef.current.style.height = `${height}px`;\n\n    const colorsArray = finalColors.split(',');\n    const pxs = [];\n    for (let x = 0; x < width; x += parseInt(finalGap, 10)) {\n      for (let y = 0; y < height; y += parseInt(finalGap, 10)) {\n        const color = colorsArray[Math.floor(Math.random() * colorsArray.length)];\n\n        const dx = x - width / 2;\n        const dy = y - height / 2;\n        const distance = Math.sqrt(dx * dx + dy * dy);\n        const delay = reducedMotion ? 0 : distance;\n\n        pxs.push(new Pixel(canvasRef.current, ctx, x, y, color, getEffectiveSpeed(finalSpeed, reducedMotion), delay));\n      }\n    }\n    pixelsRef.current = pxs;\n  };\n\n  const doAnimate = fnName => {\n    animationRef.current = requestAnimationFrame(() => doAnimate(fnName));\n    const timeNow = performance.now();\n    const timePassed = timeNow - timePreviousRef.current;\n    const timeInterval = 1000 / 60;\n\n    if (timePassed < timeInterval) return;\n    timePreviousRef.current = timeNow - (timePassed % timeInterval);\n\n    const ctx = canvasRef.current?.getContext('2d');\n    if (!ctx || !canvasRef.current) return;\n\n    ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);\n\n    let allIdle = true;\n    for (let i = 0; i < pixelsRef.current.length; i++) {\n      const pixel = pixelsRef.current[i];\n      pixel[fnName]();\n      if (!pixel.isIdle) {\n        allIdle = false;\n      }\n    }\n    if (allIdle) {\n      cancelAnimationFrame(animationRef.current);\n    }\n  };\n\n  const handleAnimation = name => {\n    cancelAnimationFrame(animationRef.current);\n    animationRef.current = requestAnimationFrame(() => doAnimate(name));\n  };\n\n  const onMouseEnter = () => handleAnimation('appear');\n  const onMouseLeave = () => handleAnimation('disappear');\n  const onFocus = e => {\n    if (e.currentTarget.contains(e.relatedTarget)) return;\n    handleAnimation('appear');\n  };\n  const onBlur = e => {\n    if (e.currentTarget.contains(e.relatedTarget)) return;\n    handleAnimation('disappear');\n  };\n\n  useEffect(() => {\n    initPixels();\n    const observer = new ResizeObserver(() => {\n      initPixels();\n    });\n    if (containerRef.current) {\n      observer.observe(containerRef.current);\n    }\n    return () => {\n      observer.disconnect();\n      cancelAnimationFrame(animationRef.current);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [finalGap, finalSpeed, finalColors, finalNoFocus]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={`group h-[400px] w-[300px] relative overflow-hidden grid place-items-center aspect-[4/5] border border-[var(--pixel-card-border,#27272a)] bg-[var(--pixel-card-background,transparent)] rounded-[25px] isolate transition-colors duration-200 ease-[cubic-bezier(0.5,1,0.89,1)] select-none ${className}`}\n      onMouseEnter={onMouseEnter}\n      onMouseLeave={onMouseLeave}\n      onFocus={finalNoFocus ? undefined : onFocus}\n      onBlur={finalNoFocus ? undefined : onBlur}\n      tabIndex={finalNoFocus ? -1 : 0}\n    >\n      <div className=\"absolute inset-0 m-auto aspect-square bg-[radial-gradient(circle,var(--pixel-card-active-color,#09090b),transparent_85%)] opacity-0 transition-opacity duration-800 ease-[cubic-bezier(0.5,1,0.89,1)] group-hover:opacity-100 group-focus-within:opacity-100\" />\n      <canvas className=\"w-full h-full block\" ref={canvasRef} />\n      {children}\n    </div>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}