{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "AccordionGallery-JS-CSS",
	"title": "AccordionGallery",
	"description": "Panels expand on hover or focus, revealing parallax imagery and captions.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "AccordionGallery.css",
			"target": "@components/AccordionGallery.css",
			"content": ".accordion-gallery {\n  --ag-accent: #ffffff;\n  --ag-overlay: #060010;\n  --ag-text: #ffffff;\n  --ag-gap: 10px;\n  --ag-radius: 16px;\n  --ag-media-size: 320px;\n\n  display: flex;\n  flex-direction: row;\n  gap: var(--ag-gap);\n  width: 100%;\n  max-width: 100%;\n  perspective: 1400px;\n  perspective-origin: 50% 50%;\n}\n\n.accordion-gallery--vertical {\n  flex-direction: column;\n}\n\n.ag-panel {\n  position: relative;\n  flex: 1 1 0;\n  min-width: 0;\n  min-height: 0;\n  overflow: hidden;\n  border-radius: var(--ag-radius);\n  cursor: pointer;\n  display: block;\n  text-decoration: none;\n  outline: none;\n  transform-style: preserve-3d;\n  transform-origin: center center;\n  background: #0a0713;\n  box-shadow: 0 10px 30px -18px rgba(0, 0, 0, 0.8);\n  will-change: flex-grow, transform;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.ag-panel:focus-visible {\n  box-shadow:\n    0 0 0 2px var(--ag-accent),\n    0 10px 30px -18px rgba(0, 0, 0, 0.8);\n}\n\n.ag-panel__frame {\n  position: absolute;\n  inset: 0;\n  overflow: hidden;\n  border-radius: inherit;\n}\n\n.ag-panel__media {\n  --ag-gray: 1;\n  --ag-dim: 0.35;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: var(--ag-media-size);\n  height: 100%;\n  filter: grayscale(var(--ag-gray));\n  will-change: transform, filter;\n}\n\n.accordion-gallery--vertical .ag-panel__media {\n  width: 100%;\n  height: var(--ag-media-size);\n}\n\n.ag-panel__media img {\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n  display: block;\n  user-select: none;\n  -webkit-user-drag: none;\n}\n\n.ag-panel__overlay {\n  position: absolute;\n  inset: 0;\n  pointer-events: none;\n  background:\n    linear-gradient(180deg, transparent 45%, color-mix(in srgb, var(--ag-overlay) 78%, transparent) 100%),\n    color-mix(in srgb, var(--ag-overlay) calc(var(--ag-dim, 0.35) * 100%), transparent);\n}\n\n.ag-panel__label {\n  position: absolute;\n  left: 20px;\n  bottom: 20px;\n  right: 20px;\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  pointer-events: none;\n  z-index: 2;\n}\n\n.ag-panel__bar {\n  flex: 0 0 auto;\n  width: 3px;\n  height: 26px;\n  border-radius: 3px;\n  background: var(--ag-accent);\n  opacity: 0;\n  box-shadow: 0 0 12px color-mix(in srgb, var(--ag-accent) 60%, transparent);\n}\n\n.ag-panel__text {\n  color: var(--ag-text);\n  font-family: inherit;\n  font-weight: 600;\n  font-size: clamp(1rem, 1.4vw, 1.4rem);\n  letter-spacing: 0.01em;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  opacity: 0;\n  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);\n}\n\n@media (max-width: 520px) {\n  .accordion-gallery {\n    flex-direction: column;\n    perspective: none;\n    height: auto !important;\n  }\n  .ag-panel {\n    min-height: 84px;\n    transform: none !important;\n  }\n  .accordion-gallery .ag-panel__media {\n    width: 100%;\n    height: var(--ag-media-size);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ag-panel,\n  .ag-panel__media {\n    will-change: auto;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "AccordionGallery.jsx",
			"content": "import { useRef, useEffect, useState, useCallback } from 'react';\nimport { gsap } from 'gsap';\n\nimport './AccordionGallery.css';\n\nconst DEFAULT_ITEMS = [\n  { image: 'https://picsum.photos/id/1015/900/1200', label: 'Canyon', link: '#' },\n  { image: 'https://picsum.photos/id/1018/900/1200', label: 'Ridgeline', link: '#' },\n  { image: 'https://picsum.photos/id/1039/900/1200', label: 'Falls', link: '#' },\n  { image: 'https://picsum.photos/id/1043/900/1200', label: 'Harbour', link: '#' },\n  { image: 'https://picsum.photos/id/1044/900/1200', label: 'Skyline', link: '#' }\n];\n\nconst AccordionGallery = ({\n  items = DEFAULT_ITEMS,\n  defaultIndex = 2,\n  accentColor = '#ffffff',\n  overlayColor = '#060010',\n  textColor = '#ffffff',\n  height = 460,\n  gap = 10,\n  radius = 16,\n  expandRatio = 0.52,\n  orientation = 'horizontal',\n  duration = 0.6,\n  ease = 'power3.out',\n  parallax = 0.5,\n  tilt = 8,\n  stagger = 0.06,\n  trigger = 'hover',\n  showLabels = true,\n  grayscale = true,\n  className = ''\n}) => {\n  const rootRef = useRef(null);\n  const panelRefs = useRef([]);\n  const mediaRefs = useRef([]);\n  const barRefs = useRef([]);\n  const textRefs = useRef([]);\n  const tlRef = useRef(null);\n  const firstRunRef = useRef(true);\n  const mediaSizeRef = useRef(320);\n\n  const vertical = orientation === 'vertical';\n  const count = items.length;\n  const [active, setActive] = useState(Math.min(Math.max(defaultIndex, 0), count - 1));\n\n  const prefersReduced =\n    typeof window !== 'undefined' && window.matchMedia\n      ? window.matchMedia('(prefers-reduced-motion: reduce)').matches\n      : false;\n\n  const applyLayout = useCallback(\n    animate => {\n      const panels = panelRefs.current;\n      if (!panels.length) return;\n\n      const r = Math.min(Math.max(expandRatio, 0.2), 0.9);\n      const grow = count > 1 ? (r * (count - 1)) / (1 - r) : 1;\n      const mediaSize = mediaSizeRef.current;\n\n      tlRef.current?.kill();\n      const dur = animate && !prefersReduced ? duration : 0;\n      const tl = gsap.timeline();\n\n      panels.forEach((panel, i) => {\n        if (!panel) return;\n        const isActive = i === active;\n        const media = mediaRefs.current[i];\n        const bar = barRefs.current[i];\n        const text = textRefs.current[i];\n\n        const rot = isActive ? 0 : i < active ? tilt : -tilt;\n        const rotProp = vertical ? { rotateX: -rot } : { rotateY: rot };\n\n        tl.to(panel, { flexGrow: isActive ? grow : 1, ...rotProp, duration: dur, ease }, 0);\n\n        if (media) {\n          const drift = Math.max(-1.5, Math.min(1.5, active - i));\n          const shift = drift * parallax * mediaSize * 0.06;\n          const gray = grayscale ? (isActive ? 0 : 1) : 0;\n          tl.to(\n            media,\n            {\n              xPercent: -50,\n              yPercent: -50,\n              x: vertical ? 0 : isActive ? 0 : shift,\n              y: vertical ? (isActive ? 0 : shift) : 0,\n              '--ag-gray': gray,\n              '--ag-dim': isActive ? 0 : 0.35,\n              duration: dur,\n              ease\n            },\n            0\n          );\n        }\n\n        if (showLabels && bar && text) {\n          if (isActive) {\n            tl.to([bar, text], { opacity: 1, x: 0, duration: dur, ease, stagger: prefersReduced ? 0 : stagger }, 0);\n          } else {\n            tl.to([bar, text], { opacity: 0, x: -14, duration: dur * 0.6, ease }, 0);\n          }\n        }\n      });\n\n      tlRef.current = tl;\n    },\n    [\n      active,\n      count,\n      expandRatio,\n      duration,\n      ease,\n      vertical,\n      tilt,\n      parallax,\n      grayscale,\n      showLabels,\n      stagger,\n      prefersReduced\n    ]\n  );\n\n  useEffect(() => {\n    const el = rootRef.current;\n    if (!el) return;\n\n    const measure = () => {\n      const rect = el.getBoundingClientRect();\n      const total = vertical ? rect.height : rect.width;\n      const usable = Math.max(total - gap * (count - 1), 120);\n      const size = Math.max(140, usable * Math.min(Math.max(expandRatio, 0.2), 0.9) * 1.22);\n      mediaSizeRef.current = size;\n      el.style.setProperty('--ag-media-size', `${size}px`);\n      applyLayout(!firstRunRef.current);\n    };\n\n    measure();\n    const ro = new ResizeObserver(measure);\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [applyLayout, gap, count, expandRatio, vertical]);\n\n  useEffect(() => {\n    applyLayout(!firstRunRef.current);\n    firstRunRef.current = false;\n  }, [applyLayout]);\n\n  useEffect(\n    () => () => {\n      tlRef.current?.kill();\n    },\n    []\n  );\n\n  const handleEnter = i => {\n    if (trigger === 'hover') setActive(i);\n  };\n\n  const handleClick = (i, e) => {\n    if (i !== active) {\n      e.preventDefault();\n      setActive(i);\n    }\n  };\n\n  const handleKeyDown = (i, e) => {\n    if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n      e.preventDefault();\n      setActive((i + 1) % count);\n    } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n      e.preventDefault();\n      setActive((i - 1 + count) % count);\n    }\n  };\n\n  return (\n    <div\n      ref={rootRef}\n      className={`accordion-gallery${vertical ? ' accordion-gallery--vertical' : ''}${className ? ` ${className}` : ''}`}\n      style={{\n        '--ag-accent': accentColor,\n        '--ag-overlay': overlayColor,\n        '--ag-text': textColor,\n        '--ag-gap': `${gap}px`,\n        '--ag-radius': `${radius}px`,\n        height: vertical ? `${Math.round(height * 1.6)}px` : `${height}px`\n      }}\n      role=\"list\"\n      aria-label=\"Image accordion gallery\"\n    >\n      {items.map((item, i) => {\n        const isActive = i === active;\n        const Tag = item.link ? 'a' : 'div';\n        return (\n          <Tag\n            key={i}\n            ref={el => (panelRefs.current[i] = el)}\n            className={`ag-panel${isActive ? ' ag-panel--active' : ''}`}\n            style={{ borderRadius: `${radius}px` }}\n            href={item.link || undefined}\n            onClick={e => handleClick(i, e)}\n            onMouseEnter={() => handleEnter(i)}\n            onFocus={() => setActive(i)}\n            onKeyDown={e => handleKeyDown(i, e)}\n            role=\"listitem\"\n            tabIndex={0}\n            aria-current={isActive ? 'true' : undefined}\n            aria-label={item.label}\n          >\n            <span className=\"ag-panel__frame\">\n              <span className=\"ag-panel__media\" ref={el => (mediaRefs.current[i] = el)}>\n                <img src={item.image} alt={item.alt || item.label || ''} draggable=\"false\" />\n              </span>\n              <span className=\"ag-panel__overlay\" aria-hidden=\"true\" />\n            </span>\n            {showLabels && (\n              <span className=\"ag-panel__label\" aria-hidden=\"true\">\n                <span className=\"ag-panel__bar\" ref={el => (barRefs.current[i] = el)} />\n                <span className=\"ag-panel__text\" ref={el => (textRefs.current[i] = el)}>\n                  {item.label}\n                </span>\n              </span>\n            )}\n          </Tag>\n        );\n      })}\n    </div>\n  );\n};\n\nexport default AccordionGallery;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"gsap@^3.13.0"
	]
}