{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "ChromaGrid-TS-CSS",
	"title": "ChromaGrid",
	"description": "A responsive grid of grayscale tiles. Hovering the grid reaveals their colors.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "ChromaGrid.css",
			"target": "@components/ChromaGrid.css",
			"content": ".chroma-grid {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  display: grid;\n  grid-template-columns: repeat(var(--cols, 3), 320px);\n  grid-auto-rows: auto;\n  justify-content: center;\n  gap: 0.75rem;\n  max-width: 1200px;\n  margin: 0 auto;\n  padding: 1rem;\n  box-sizing: border-box;\n\n  --x: 50%;\n  --y: 50%;\n  --r: 220px;\n}\n\n@media (max-width: 1124px) {\n  .chroma-grid {\n    grid-template-columns: repeat(auto-fit, minmax(320px, 320px));\n    gap: 0.5rem;\n    padding: 0.5rem;\n  }\n}\n\n@media (max-width: 480px) {\n  .chroma-grid {\n    grid-template-columns: 320px;\n    gap: 0.75rem;\n    padding: 1rem;\n  }\n}\n\n.chroma-card {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 320px;\n  height: auto;\n  border-radius: 20px;\n  overflow: hidden;\n  border: 1px solid #333;\n  transition: border-color 0.3s ease;\n  background: var(--card-gradient);\n\n  --mouse-x: 50%;\n  --mouse-y: 50%;\n  --spotlight-color: rgba(255, 255, 255, 0.3);\n}\n\n.chroma-card:hover {\n  border-color: var(--card-border);\n}\n\n.chroma-card::before {\n  content: '';\n  position: absolute;\n  inset: 0;\n  background: radial-gradient(circle at var(--mouse-x) var(--mouse-y), var(--spotlight-color), transparent 70%);\n  pointer-events: none;\n  opacity: 0;\n  transition: opacity 0.5s ease;\n  z-index: 2;\n}\n\n.chroma-card:hover::before {\n  opacity: 1;\n}\n\n.chroma-img-wrapper {\n  position: relative;\n  z-index: 1;\n  flex: 1;\n  padding: 10px;\n  box-sizing: border-box;\n  background: transparent;\n  transition: background 0.3s ease;\n}\n\n.chroma-img-wrapper img {\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n  border-radius: 10px;\n  display: block;\n}\n\n.chroma-info {\n  position: relative;\n  z-index: 1;\n  padding: 0.75rem 1rem;\n  color: #fff;\n  font-family: system-ui, sans-serif;\n  display: grid;\n  grid-template-columns: 1fr auto;\n  row-gap: 0.25rem;\n  column-gap: 0.75rem;\n}\n\n.chroma-info .role,\n.chroma-info .handle {\n  color: #aaa;\n}\n\n.chroma-overlay {\n  position: absolute;\n  inset: 0;\n  pointer-events: none;\n  z-index: 3;\n  backdrop-filter: grayscale(1) brightness(0.78);\n  -webkit-backdrop-filter: grayscale(1) brightness(0.78);\n  background: rgba(0, 0, 0, 0.001);\n\n  mask-image: radial-gradient(\n    circle var(--r) at var(--x) var(--y),\n    transparent 0%,\n    transparent 15%,\n    rgba(0, 0, 0, 0.1) 30%,\n    rgba(0, 0, 0, 0.22) 45%,\n    rgba(0, 0, 0, 0.35) 60%,\n    rgba(0, 0, 0, 0.5) 75%,\n    rgba(0, 0, 0, 0.68) 88%,\n    white 100%\n  );\n  -webkit-mask-image: radial-gradient(\n    circle var(--r) at var(--x) var(--y),\n    transparent 0%,\n    transparent 15%,\n    rgba(0, 0, 0, 0.1) 30%,\n    rgba(0, 0, 0, 0.22) 45%,\n    rgba(0, 0, 0, 0.35) 60%,\n    rgba(0, 0, 0, 0.5) 75%,\n    rgba(0, 0, 0, 0.68) 88%,\n    white 100%\n  );\n}\n\n.chroma-fade {\n  position: absolute;\n  inset: 0;\n  pointer-events: none;\n  z-index: 4;\n  backdrop-filter: grayscale(1) brightness(0.78);\n  -webkit-backdrop-filter: grayscale(1) brightness(0.78);\n  background: rgba(0, 0, 0, 0.001);\n\n  mask-image: radial-gradient(\n    circle var(--r) at var(--x) var(--y),\n    white 0%,\n    white 15%,\n    rgba(255, 255, 255, 0.9) 30%,\n    rgba(255, 255, 255, 0.78) 45%,\n    rgba(255, 255, 255, 0.65) 60%,\n    rgba(255, 255, 255, 0.5) 75%,\n    rgba(255, 255, 255, 0.32) 88%,\n    transparent 100%\n  );\n  -webkit-mask-image: radial-gradient(\n    circle var(--r) at var(--x) var(--y),\n    white 0%,\n    white 15%,\n    rgba(255, 255, 255, 0.9) 30%,\n    rgba(255, 255, 255, 0.78) 45%,\n    rgba(255, 255, 255, 0.65) 60%,\n    rgba(255, 255, 255, 0.5) 75%,\n    rgba(255, 255, 255, 0.32) 88%,\n    transparent 100%\n  );\n\n  opacity: 1;\n  transition: opacity 0.25s ease;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "ChromaGrid.tsx",
			"content": "import React, { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport './ChromaGrid.css';\n\nexport interface ChromaItem {\n  image: string;\n  title: string;\n  subtitle: string;\n  handle?: string;\n  location?: string;\n  borderColor?: string;\n  gradient?: string;\n  url?: string;\n}\n\nexport interface ChromaGridProps {\n  items?: ChromaItem[];\n  className?: string;\n  radius?: number;\n  columns?: number;\n  rows?: number;\n  damping?: number;\n  fadeOut?: number;\n  ease?: string;\n}\n\ntype SetterFn = (v: number | string) => void;\n\nexport const ChromaGrid: React.FC<ChromaGridProps> = ({\n  items,\n  className = '',\n  radius = 300,\n  columns = 3,\n  rows = 2,\n  damping = 0.45,\n  fadeOut = 0.6,\n  ease = 'power3.out'\n}) => {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const fadeRef = useRef<HTMLDivElement>(null);\n  const setX = useRef<SetterFn | null>(null);\n  const setY = useRef<SetterFn | null>(null);\n  const pos = useRef({ x: 0, y: 0 });\n\n  const demo: ChromaItem[] = [\n    {\n      image: 'https://i.pravatar.cc/300?img=8',\n      title: 'Alex Rivera',\n      subtitle: 'Full Stack Developer',\n      handle: '@alexrivera',\n      borderColor: '#4F46E5',\n      gradient: 'linear-gradient(145deg, #4F46E5, #000)',\n      url: 'https://github.com/'\n    },\n    {\n      image: 'https://i.pravatar.cc/300?img=11',\n      title: 'Jordan Chen',\n      subtitle: 'DevOps Engineer',\n      handle: '@jordanchen',\n      borderColor: '#10B981',\n      gradient: 'linear-gradient(210deg, #10B981, #000)',\n      url: 'https://linkedin.com/in/'\n    },\n    {\n      image: 'https://i.pravatar.cc/300?img=3',\n      title: 'Morgan Blake',\n      subtitle: 'UI/UX Designer',\n      handle: '@morganblake',\n      borderColor: '#F59E0B',\n      gradient: 'linear-gradient(165deg, #F59E0B, #000)',\n      url: 'https://dribbble.com/'\n    },\n    {\n      image: 'https://i.pravatar.cc/300?img=16',\n      title: 'Casey Park',\n      subtitle: 'Data Scientist',\n      handle: '@caseypark',\n      borderColor: '#EF4444',\n      gradient: 'linear-gradient(195deg, #EF4444, #000)',\n      url: 'https://kaggle.com/'\n    },\n    {\n      image: 'https://i.pravatar.cc/300?img=25',\n      title: 'Sam Kim',\n      subtitle: 'Mobile Developer',\n      handle: '@thesamkim',\n      borderColor: '#8B5CF6',\n      gradient: 'linear-gradient(225deg, #8B5CF6, #000)',\n      url: 'https://github.com/'\n    },\n    {\n      image: 'https://i.pravatar.cc/300?img=60',\n      title: 'Tyler Rodriguez',\n      subtitle: 'Cloud Architect',\n      handle: '@tylerrod',\n      borderColor: '#06B6D4',\n      gradient: 'linear-gradient(135deg, #06B6D4, #000)',\n      url: 'https://aws.amazon.com/'\n    }\n  ];\n  const data = items?.length ? items : demo;\n\n  useEffect(() => {\n    const el = rootRef.current;\n    if (!el) return;\n    setX.current = gsap.quickSetter(el, '--x', 'px') as SetterFn;\n    setY.current = gsap.quickSetter(el, '--y', 'px') as SetterFn;\n    const { width, height } = el.getBoundingClientRect();\n    pos.current = { x: width / 2, y: height / 2 };\n    setX.current(pos.current.x);\n    setY.current(pos.current.y);\n  }, []);\n\n  const moveTo = (x: number, y: number) => {\n    gsap.to(pos.current, {\n      x,\n      y,\n      duration: damping,\n      ease,\n      onUpdate: () => {\n        setX.current?.(pos.current.x);\n        setY.current?.(pos.current.y);\n      },\n      overwrite: true\n    });\n  };\n\n  const handleMove = (e: React.PointerEvent) => {\n    const r = rootRef.current!.getBoundingClientRect();\n    moveTo(e.clientX - r.left, e.clientY - r.top);\n    gsap.to(fadeRef.current, { opacity: 0, duration: 0.25, overwrite: true });\n  };\n\n  const handleLeave = () => {\n    gsap.to(fadeRef.current, {\n      opacity: 1,\n      duration: fadeOut,\n      overwrite: true\n    });\n  };\n\n  const handleCardClick = (url?: string) => {\n    if (url) {\n      window.open(url, '_blank', 'noopener,noreferrer');\n    }\n  };\n\n  const handleCardMove: React.MouseEventHandler<HTMLElement> = e => {\n    const card = e.currentTarget as HTMLElement;\n    const rect = card.getBoundingClientRect();\n    const x = e.clientX - rect.left;\n    const y = e.clientY - rect.top;\n    card.style.setProperty('--mouse-x', `${x}px`);\n    card.style.setProperty('--mouse-y', `${y}px`);\n  };\n\n  return (\n    <div\n      ref={rootRef}\n      className={`chroma-grid ${className}`}\n      style={\n        {\n          '--r': `${radius}px`,\n          '--cols': columns,\n          '--rows': rows\n        } as React.CSSProperties\n      }\n      onPointerMove={handleMove}\n      onPointerLeave={handleLeave}\n    >\n      {data.map((c, i) => (\n        <article\n          key={i}\n          className=\"chroma-card\"\n          onMouseMove={handleCardMove}\n          onClick={() => handleCardClick(c.url)}\n          style={\n            {\n              '--card-border': c.borderColor || 'transparent',\n              '--card-gradient': c.gradient,\n              cursor: c.url ? 'pointer' : 'default'\n            } as React.CSSProperties\n          }\n        >\n          <div className=\"chroma-img-wrapper\">\n            <img src={c.image} alt={c.title} loading=\"lazy\" />\n          </div>\n          <footer className=\"chroma-info\">\n            <h3 className=\"name\">{c.title}</h3>\n            {c.handle && <span className=\"handle\">{c.handle}</span>}\n            <p className=\"role\">{c.subtitle}</p>\n            {c.location && <span className=\"location\">{c.location}</span>}\n          </footer>\n        </article>\n      ))}\n      <div className=\"chroma-overlay\" />\n      <div ref={fadeRef} className=\"chroma-fade\" />\n    </div>\n  );\n};\n\nexport default ChromaGrid;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"gsap@^3.13.0"
	]
}