{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "PixelCard-TS-TW",
	"title": "PixelCard",
	"description": "Card content revealed through pixel expansion transition.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "PixelCard/PixelCard.tsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { type JSX } from 'react';\n\nclass Pixel {\n  width: number;\n  height: number;\n  ctx: CanvasRenderingContext2D;\n  x: number;\n  y: number;\n  color: string;\n  speed: number;\n  size: number;\n  sizeStep: number;\n  minSize: number;\n  maxSizeInteger: number;\n  maxSize: number;\n  delay: number;\n  counter: number;\n  counterStep: number;\n  isIdle: boolean;\n  isReverse: boolean;\n  isShimmer: boolean;\n\n  constructor(\n    canvas: HTMLCanvasElement,\n    context: CanvasRenderingContext2D,\n    x: number,\n    y: number,\n    color: string,\n    speed: number,\n    delay: number\n  ) {\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: number, max: number) {\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: number, reducedMotion: boolean) {\n  const min = 0;\n  const max = 100;\n  const throttle = 0.001;\n\n  if (value <= min || reducedMotion) {\n    return min;\n  } else if (value >= max) {\n    return max * throttle;\n  } else {\n    return value * 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\ninterface PixelCardProps {\n  variant?: 'default' | 'blue' | 'yellow' | 'pink';\n  gap?: number;\n  speed?: number;\n  colors?: string;\n  noFocus?: boolean;\n  className?: string;\n  children: React.ReactNode;\n}\n\ninterface VariantConfig {\n  activeColor: string | null;\n  gap: number;\n  speed: number;\n  colors: string;\n  noFocus: boolean;\n}\n\nexport default function PixelCard({\n  variant = 'default',\n  gap,\n  speed,\n  colors,\n  noFocus,\n  className = '',\n  children\n}: PixelCardProps): JSX.Element {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const pixelsRef = useRef<Pixel[]>([]);\n  const animationRef = useRef<ReturnType<typeof requestAnimationFrame> | null>(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: VariantConfig = 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.toString(), 10)) {\n      for (let y = 0; y < height; y += parseInt(finalGap.toString(), 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        if (!ctx) return;\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: keyof Pixel) => {\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      // @ts-ignore\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: keyof Pixel) => {\n    if (animationRef.current !== null) {\n      cancelAnimationFrame(animationRef.current);\n    }\n    animationRef.current = requestAnimationFrame(() => doAnimate(name));\n  };\n\n  const onMouseEnter = () => handleAnimation('appear');\n  const onMouseLeave = () => handleAnimation('disappear');\n  const onFocus: React.FocusEventHandler<HTMLDivElement> = e => {\n    if (e.currentTarget.contains(e.relatedTarget)) return;\n    handleAnimation('appear');\n  };\n  const onBlur: React.FocusEventHandler<HTMLDivElement> = 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      if (animationRef.current !== null) {\n        cancelAnimationFrame(animationRef.current);\n      }\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": []
}