{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pixel-card",
  "type": "registry:ui",
  "title": "Pixel Card",
  "description": "Interactive card with synchronized harmonic pixel matrix, cursor proximity excitation, and 3D perspective tilt.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "gsap",
    "@gsap/react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/pixel-card.tsx",
      "content": "\"use client\";\n\nimport React, { useRef, useCallback, useEffect } from \"react\";\nimport gsap from \"gsap\";\nimport { useGSAP } from \"@gsap/react\";\nimport { cn } from \"@/lib/utils\";\n\nif (typeof window !== \"undefined\") {\n  gsap.registerPlugin(useGSAP);\n}\n\nexport type PixelCardVariant =\n  | \"default\"\n  | \"blue\"\n  | \"yellow\"\n  | \"pink\"\n  | \"purple\"\n  | \"emerald\";\n\nexport type PixelCardPattern = \"wave\" | \"matrix\" | \"scan\" | \"cross\";\n\nexport interface PixelCardProps extends React.HTMLAttributes<HTMLDivElement> {\n  variant?: PixelCardVariant;\n  pattern?: PixelCardPattern;\n  noise?: number;\n  gap?: number;\n  speed?: number;\n  colors?: string;\n  noFocus?: boolean;\n  enableTilt?: boolean;\n  maxTilt?: number;\n  enableSpotlight?: boolean;\n  spotlightColor?: string;\n  enableGlare?: boolean;\n  children?: React.ReactNode;\n  className?: string;\n}\n\ninterface PixelData {\n  x: number;\n  y: number;\n  color: string;\n  size: number;\n  alpha: number;\n  maxSize: number;\n  phase: number;\n}\n\ninterface VariantConfig {\n  activeColor: string;\n  spotlight: string;\n  borderColor: string;\n  gap: number;\n  speed: number;\n  colors: string;\n  noFocus: boolean;\n}\n\nconst VARIANTS: Record<PixelCardVariant, VariantConfig> = {\n  default: {\n    activeColor: \"rgba(56, 189, 248, 0.08)\",\n    spotlight: \"rgba(56, 189, 248, 0.07)\",\n    borderColor: \"rgba(56, 189, 248, 0.25)\",\n    gap: 6,\n    speed: 25,\n    colors: \"#f8fafc,#cbd5e1,#94a3b8,#38bdf8\",\n    noFocus: false,\n  },\n  blue: {\n    activeColor: \"rgba(14, 165, 233, 0.1)\",\n    spotlight: \"rgba(56, 189, 248, 0.09)\",\n    borderColor: \"rgba(56, 189, 248, 0.3)\",\n    gap: 7,\n    speed: 22,\n    colors: \"#e0f2fe,#7dd3fc,#38bdf8,#0ea5e9\",\n    noFocus: false,\n  },\n  yellow: {\n    activeColor: \"rgba(234, 179, 8, 0.08)\",\n    spotlight: \"rgba(250, 204, 21, 0.07)\",\n    borderColor: \"rgba(250, 204, 21, 0.28)\",\n    gap: 6,\n    speed: 20,\n    colors: \"#fef08a,#fde047,#facc15,#eab308\",\n    noFocus: false,\n  },\n  pink: {\n    activeColor: \"rgba(244, 63, 94, 0.1)\",\n    spotlight: \"rgba(244, 63, 94, 0.08)\",\n    borderColor: \"rgba(251, 113, 133, 0.28)\",\n    gap: 6,\n    speed: 30,\n    colors: \"#fecdd3,#fda4af,#fb7185,#f43f5e\",\n    noFocus: true,\n  },\n  purple: {\n    activeColor: \"rgba(168, 85, 247, 0.1)\",\n    spotlight: \"rgba(192, 132, 252, 0.08)\",\n    borderColor: \"rgba(192, 132, 252, 0.28)\",\n    gap: 6,\n    speed: 24,\n    colors: \"#f3e8ff,#d8b4fe,#c084fc,#a855f7\",\n    noFocus: false,\n  },\n  emerald: {\n    activeColor: \"rgba(16, 185, 129, 0.1)\",\n    spotlight: \"rgba(52, 211, 153, 0.08)\",\n    borderColor: \"rgba(52, 211, 153, 0.28)\",\n    gap: 6,\n    speed: 22,\n    colors: \"#d1fae5,#6ee7b7,#34d399,#10b981\",\n    noFocus: false,\n  },\n};\n\nfunction getPointerPosition(\n  card: HTMLDivElement,\n  event: React.MouseEvent<HTMLDivElement>,\n) {\n  const rect = card.getBoundingClientRect();\n  return {\n    x: rect.width > 0\n      ? (event.clientX - rect.left) * card.offsetWidth / rect.width\n      : 0,\n    y: rect.height > 0\n      ? (event.clientY - rect.top) * card.offsetHeight / rect.height\n      : 0,\n  };\n}\n\nexport const PixelCard = React.forwardRef<HTMLDivElement, PixelCardProps>(\n  (\n    {\n      variant = \"default\",\n      pattern = \"wave\",\n      noise = 0,\n      gap,\n      speed,\n      colors,\n      noFocus,\n      enableTilt = true,\n      maxTilt = 6,\n      enableSpotlight = true,\n      spotlightColor,\n      enableGlare = true,\n      className,\n      children,\n      onMouseMove,\n      onMouseEnter,\n      onMouseLeave,\n      onFocus,\n      onBlur,\n      ...props\n    },\n    ref,\n  ) => {\n    const containerRef = useRef<HTMLDivElement | null>(null);\n    const canvasRef = useRef<HTMLCanvasElement | null>(null);\n    const pixelsRef = useRef<PixelData[]>([]);\n    const isHoveredRef = useRef(false);\n    const animatingRef = useRef(false);\n\n    const pointerRef = useRef({\n      x: 0,\n      y: 0,\n      entryTime: 0,\n    });\n\n    const targetPointerRef = useRef({\n      x: 0,\n      y: 0,\n    });\n\n    const rotXRef = useRef<gsap.QuickToFunc | null>(null);\n    const rotYRef = useRef<gsap.QuickToFunc | null>(null);\n    const scaleRef = useRef<gsap.QuickToFunc | null>(null);\n\n    const isReducedMotion = useRef(\n      typeof window !== \"undefined\" &&\n        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    const finalSpotlightColor = spotlightColor ?? variantCfg.spotlight;\n\n    const initPixels = useCallback(() => {\n      const container = containerRef.current;\n      const canvas = canvasRef.current;\n      if (!container || !canvas) return;\n\n      const width = container.offsetWidth;\n      const height = container.offsetHeight;\n      if (width <= 0 || height <= 0) return;\n\n      const dpr = Math.min(\n        typeof window !== \"undefined\" ? window.devicePixelRatio || 1 : 1,\n        2,\n      );\n      canvas.width = Math.floor(width * dpr);\n      canvas.height = Math.floor(height * dpr);\n      canvas.style.width = `${width}px`;\n      canvas.style.height = `${height}px`;\n\n      const ctx = canvas.getContext(\"2d\");\n      if (!ctx) return;\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n      const step = Math.max(4, parseInt(finalGap.toString(), 10) || 6);\n      const cols = Math.floor(width / step);\n      const rows = Math.floor(height / step);\n      const xOffset = Math.floor((width - (cols - 1) * step) / 2);\n      const yOffset = Math.floor((height - (rows - 1) * step) / 2);\n\n      const colorList = finalColors\n        .split(\",\")\n        .map((c) => c.trim())\n        .filter(Boolean);\n      const pixelGrid: PixelData[] = [];\n\n      for (let col = 0; col < cols; col++) {\n        for (let row = 0; row < rows; row++) {\n          const x = xOffset + col * step;\n          const y = yOffset + row * step;\n          const color =\n            colorList[Math.floor(Math.random() * colorList.length)] ||\n            \"#38bdf8\";\n          const maxSize = 1.1 + Math.random() * 1.3;\n          pixelGrid.push({\n            x,\n            y,\n            color,\n            size: 0,\n            alpha: 0,\n            maxSize,\n            phase: Math.random() * Math.PI * 2,\n          });\n        }\n      }\n      pixelsRef.current = pixelGrid;\n    }, [finalColors, finalGap]);\n\n    const tick = useCallback(\n      (time: number, deltaTime: number) => {\n        const canvas = canvasRef.current;\n        if (!canvas) return;\n        const ctx = canvas.getContext(\"2d\");\n        if (!ctx) return;\n\n        const width = parseFloat(canvas.style.width) || canvas.width;\n        const height = parseFloat(canvas.style.height) || canvas.height;\n\n        const isHovered = isHoveredRef.current;\n        const pointer = pointerRef.current;\n        const targetPointer = targetPointerRef.current;\n\n        pointer.x += (targetPointer.x - pointer.x) * 0.15;\n        pointer.y += (targetPointer.y - pointer.y) * 0.15;\n\n        const pixels = pixelsRef.current;\n        const dt = Math.min(deltaTime, 33.33);\n        const factor = Math.min(1, 0.12 * (dt / 16.67));\n\n        ctx.clearRect(0, 0, width, height);\n\n        let hasVisiblePixels = false;\n        const elapsed = time - pointer.entryTime;\n        const waveRadius = isReducedMotion ? 99999 : elapsed * 320;\n        const effectiveSpeed = finalSpeed * 0.05;\n\n        for (let i = 0; i < pixels.length; i++) {\n          const p = pixels[i];\n          let targetSize = 0;\n          let targetAlpha = 0;\n\n          if (isHovered) {\n            const dx = p.x - pointer.x;\n            const dy = p.y - pointer.y;\n            const dist = Math.sqrt(dx * dx + dy * dy);\n\n            if (pattern === \"wave\") {\n              if (dist <= waveRadius) {\n                const waveIntensity = Math.min(\n                  1,\n                  Math.max(0, (waveRadius - dist) / 45),\n                );\n                const proximity = Math.max(0, 1 - dist / 130);\n                const proximityBoost = proximity * proximity;\n                const harmonicWave =\n                  Math.sin(time * effectiveSpeed + p.phase) * 0.5 + 0.5;\n                const pulse =\n                  Math.sin(time * (effectiveSpeed * 1.2) - dist * 0.03) * 0.5 +\n                  0.5;\n\n                targetSize =\n                  p.maxSize *\n                  waveIntensity *\n                  (0.3 + 0.55 * harmonicWave + proximityBoost * 0.65);\n                targetAlpha = Math.min(\n                  1,\n                  0.2 + 0.65 * pulse + proximityBoost * 0.4,\n                );\n              }\n            } else if (pattern === \"matrix\") {\n              const colOffset = Math.sin(p.x * 0.08) * 120;\n              const streamY =\n                ((time * (effectiveSpeed * 160) + colOffset) % (height + 120)) -\n                60;\n              const streamDist = Math.abs(p.y - streamY);\n              const streamIntensity = Math.max(0, 1 - streamDist / 60);\n              const proximity = Math.max(0, 1 - dist / 120);\n              const pulse =\n                Math.sin(time * (effectiveSpeed * 2) + p.phase) * 0.5 + 0.5;\n\n              targetSize =\n                p.maxSize * (0.2 + 0.8 * streamIntensity + proximity * 0.5);\n              targetAlpha = Math.min(\n                1,\n                0.2 + 0.8 * streamIntensity * pulse + proximity * 0.4,\n              );\n            } else if (pattern === \"scan\") {\n              const scanAngle = time * (effectiveSpeed * 0.9);\n              const cx = width / 2;\n              const cy = height / 2;\n              const relX = p.x - cx;\n              const relY = p.y - cy;\n              const beamDist = Math.abs(\n                relX * Math.cos(scanAngle) + relY * Math.sin(scanAngle),\n              );\n              const beamIntensity = Math.max(0, 1 - beamDist / 45);\n              const proximity = Math.max(0, 1 - dist / 120);\n\n              targetSize =\n                p.maxSize * (0.25 + 0.75 * beamIntensity + proximity * 0.5);\n              targetAlpha = Math.min(\n                1,\n                0.2 + 0.8 * beamIntensity + proximity * 0.4,\n              );\n            } else if (pattern === \"cross\") {\n              const lineDist = Math.min(Math.abs(dx), Math.abs(dy));\n              const lineIntensity = Math.max(0, 1 - lineDist / 32);\n              const proximity = Math.max(0, 1 - dist / 130);\n              const pulse =\n                Math.sin(time * (effectiveSpeed * 1.5) - dist * 0.035) * 0.5 +\n                0.5;\n\n              targetSize =\n                p.maxSize * (0.25 + 0.75 * lineIntensity + proximity * 0.5);\n              targetAlpha = Math.min(\n                1,\n                0.2 + 0.8 * lineIntensity * pulse + proximity * 0.4,\n              );\n            }\n\n            if (noise > 0) {\n              const noiseSeed =\n                Math.sin(p.x * 12.9898 + p.y * 78.233 + time * 14) * 43758.5453;\n              const pseudoRand = noiseSeed - Math.floor(noiseSeed);\n              const noiseDelta = (pseudoRand - 0.5) * noise * 1.1;\n              targetSize = Math.max(0, targetSize * (1 + noiseDelta));\n              targetAlpha = Math.min(\n                1,\n                Math.max(0, targetAlpha * (1 + noiseDelta * 0.7)),\n              );\n            }\n          }\n\n          p.size += (targetSize - p.size) * factor;\n          p.alpha += (targetAlpha - p.alpha) * factor;\n\n          if (!isHovered && p.size < 0.05) {\n            p.size = 0;\n            p.alpha = 0;\n          }\n\n          if (p.size > 0.04 && p.alpha > 0.01) {\n            hasVisiblePixels = true;\n            const offset = (p.maxSize - p.size) * 0.5;\n            ctx.globalAlpha = Math.min(1, Math.max(0, p.alpha));\n            ctx.fillStyle = p.color;\n            ctx.fillRect(p.x + offset, p.y + offset, p.size, p.size);\n          }\n        }\n\n        if (!isHovered && !hasVisiblePixels) {\n          animatingRef.current = false;\n          gsap.ticker.remove(tick);\n          ctx.clearRect(0, 0, width, height);\n        }\n      },\n      [finalSpeed, isReducedMotion, noise, pattern],\n    );\n\n    const startAnimation = useCallback(() => {\n      if (!animatingRef.current) {\n        animatingRef.current = true;\n        gsap.ticker.add(tick);\n      }\n    }, [tick]);\n\n    useGSAP(\n      () => {\n        const card = containerRef.current;\n        if (!card) return;\n\n        if (isReducedMotion || !enableTilt) return;\n\n        rotXRef.current = gsap.quickTo(card, \"rotationX\", {\n          duration: 0.45,\n          ease: \"power2.out\",\n        });\n        rotYRef.current = gsap.quickTo(card, \"rotationY\", {\n          duration: 0.45,\n          ease: \"power2.out\",\n        });\n        scaleRef.current = gsap.quickTo(card, \"scale\", {\n          duration: 0.45,\n          ease: \"power2.out\",\n        });\n\n        return () => {\n          rotXRef.current = null;\n          rotYRef.current = null;\n          scaleRef.current = null;\n          animatingRef.current = false;\n          gsap.ticker.remove(tick);\n        };\n      },\n      {\n        scope: containerRef,\n        dependencies: [enableTilt, isReducedMotion, tick],\n      },\n    );\n\n    useEffect(() => {\n      initPixels();\n\n      const container = containerRef.current;\n      if (!container) return;\n\n      const observer = new ResizeObserver(() => {\n        initPixels();\n      });\n      observer.observe(container);\n\n      return () => {\n        observer.disconnect();\n        animatingRef.current = false;\n        gsap.ticker.remove(tick);\n      };\n    }, [initPixels, tick]);\n\n    const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {\n      const card = containerRef.current;\n      if (!card) return;\n\n      const { x, y } = getPointerPosition(card, e);\n\n      targetPointerRef.current.x = x;\n      targetPointerRef.current.y = y;\n\n      card.style.setProperty(\"--pointer-x\", `${x}px`);\n      card.style.setProperty(\"--pointer-y\", `${y}px`);\n\n      if (\n        enableTilt &&\n        !isReducedMotion &&\n        rotXRef.current &&\n        rotYRef.current &&\n        scaleRef.current\n      ) {\n        const nx = x / (card.offsetWidth || 1) * 2 - 1;\n        const ny = y / (card.offsetHeight || 1) * 2 - 1;\n        const clampedX = Math.max(-1, Math.min(1, nx));\n        const clampedY = Math.max(-1, Math.min(1, ny));\n\n        rotXRef.current(-clampedY * maxTilt);\n        rotYRef.current(clampedX * maxTilt);\n        scaleRef.current(1.008);\n      }\n\n      onMouseMove?.(e);\n    };\n\n    const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {\n      const card = containerRef.current;\n      if (!card) return;\n\n      const { x, y } = getPointerPosition(card, e);\n\n      pointerRef.current.x = x;\n      pointerRef.current.y = y;\n      targetPointerRef.current.x = x;\n      targetPointerRef.current.y = y;\n      pointerRef.current.entryTime = gsap.ticker.time;\n      isHoveredRef.current = true;\n\n      card.style.setProperty(\"--pointer-x\", `${x}px`);\n      card.style.setProperty(\"--pointer-y\", `${y}px`);\n      card.style.setProperty(\"--spotlight-opacity\", \"1\");\n\n      startAnimation();\n      onMouseEnter?.(e);\n    };\n\n    const handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {\n      isHoveredRef.current = false;\n      const card = containerRef.current;\n      if (card) {\n        card.style.setProperty(\"--spotlight-opacity\", \"0\");\n      }\n\n      if (\n        enableTilt &&\n        !isReducedMotion &&\n        rotXRef.current &&\n        rotYRef.current &&\n        scaleRef.current\n      ) {\n        rotXRef.current(0);\n        rotYRef.current(0);\n        scaleRef.current(1);\n      }\n\n      onMouseLeave?.(e);\n    };\n\n    const handleFocus = (e: React.FocusEvent<HTMLDivElement>) => {\n      if (\n        finalNoFocus ||\n        (e.relatedTarget && e.currentTarget.contains(e.relatedTarget as Node))\n      ) {\n        return;\n      }\n\n      const card = containerRef.current;\n      if (card) {\n        const cx = card.offsetWidth / 2;\n        const cy = card.offsetHeight / 2;\n        pointerRef.current.x = cx;\n        pointerRef.current.y = cy;\n        targetPointerRef.current.x = cx;\n        targetPointerRef.current.y = cy;\n        pointerRef.current.entryTime = gsap.ticker.time;\n\n        card.style.setProperty(\"--pointer-x\", `${cx}px`);\n        card.style.setProperty(\"--pointer-y\", `${cy}px`);\n        card.style.setProperty(\"--spotlight-opacity\", \"1\");\n      }\n\n      isHoveredRef.current = true;\n      startAnimation();\n      onFocus?.(e);\n    };\n\n    const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => {\n      if (\n        finalNoFocus ||\n        (e.relatedTarget && e.currentTarget.contains(e.relatedTarget as Node))\n      ) {\n        return;\n      }\n\n      const card = containerRef.current;\n      if (card) {\n        card.style.setProperty(\"--spotlight-opacity\", \"0\");\n      }\n\n      isHoveredRef.current = false;\n      onBlur?.(e);\n    };\n\n    return (\n      <div\n        ref={(node) => {\n          containerRef.current = node;\n          if (typeof ref === \"function\") {\n            ref(node);\n          } else if (ref) {\n            ref.current = node;\n          }\n        }}\n        tabIndex={finalNoFocus ? -1 : 0}\n        role={\n          !finalNoFocus && (props[\"aria-label\"] || props[\"aria-labelledby\"])\n            ? \"region\"\n            : undefined\n        }\n        onMouseMove={handleMouseMove}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        onFocus={finalNoFocus ? undefined : handleFocus}\n        onBlur={finalNoFocus ? undefined : handleBlur}\n        className={cn(\n          \"group relative isolate flex flex-col items-center justify-center overflow-hidden rounded-2xl border border-white/8 bg-black/80 text-zinc-100 select-none\",\n          \"transition-[border-color,box-shadow] duration-500 hover:border-white/20 hover:shadow-2xl hover:shadow-black/60\",\n          \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-sky-500/60 focus-visible:ring-offset-1 focus-visible:ring-offset-black\",\n          \"transform-3d will-change-transform\",\n          className,\n        )}\n        {...props}\n      >\n        {enableSpotlight && (\n          <div\n            className=\"pointer-events-none absolute -inset-px rounded-[inherit] transition-opacity duration-500 ease-out\"\n            style={{\n              opacity: \"var(--spotlight-opacity, 0)\",\n              background: `radial-gradient(320px circle at var(--pointer-x, 50%) var(--pointer-y, 50%), ${finalSpotlightColor}, transparent 70%)`,\n            }}\n          />\n        )}\n        <div\n          className=\"pointer-events-none absolute -inset-px rounded-[inherit] border border-transparent transition-opacity duration-500 ease-out\"\n          style={{\n            opacity: \"var(--spotlight-opacity, 0)\",\n            background: `radial-gradient(280px circle at var(--pointer-x, 50%) var(--pointer-y, 50%), ${variantCfg.borderColor}, transparent 65%) border-box`,\n            WebkitMask:\n              \"linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0)\",\n            WebkitMaskComposite: \"xor\",\n            maskComposite: \"exclude\",\n          }}\n        />\n        {enableGlare && (\n          <div\n            className=\"pointer-events-none absolute inset-0 rounded-[inherit] transition-opacity duration-500 ease-out\"\n            style={{\n              opacity: \"var(--spotlight-opacity, 0)\",\n              background:\n                \"linear-gradient(135deg, rgba(255, 255, 255, 0.04) 0%, transparent 45%, rgba(255, 255, 255, 0.01) 100%)\",\n            }}\n          />\n        )}\n        <canvas\n          ref={canvasRef}\n          className=\"pointer-events-none absolute inset-0 block h-full w-full rounded-[inherit]\"\n        />\n        {children && (\n          <div className=\"relative z-10 flex h-full w-full flex-col items-center justify-center transform-3d transform-[translateZ(18px)] pointer-events-auto\">\n            {children}\n          </div>\n        )}\n      </div>\n    );\n  },\n);\n\nPixelCard.displayName = \"PixelCard\";\n\nexport default PixelCard;\n",
      "type": "registry:ui",
      "target": "@ui/pixel-card.tsx"
    }
  ]
}
