{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cover-flow",
  "type": "registry:ui",
  "title": "Cover Flow",
  "description": "A 3D coverflow carousel with spring physics. Navigate by drag, scroll wheel, or keyboard, with optional reflection and audio feedback.",
  "categories": [
    "carousels"
  ],
  "dependencies": [
    "motion"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://useplanes.com/r/cover-flow-audio.json"
  ],
  "files": [
    {
      "path": "components/interactions/CoverFlow.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { memo, useCallback, useEffect, useId, useRef, useState, useSyncExternalStore, type ReactNode } from \"react\";\nimport {\n  AnimatePresence,\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  useTransform,\n  type PanInfo,\n  type MotionValue,\n} from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\nimport { useTickAudio } from \"@/lib/cover-flow-audio\";\n\ntype Direction = \"left\" | \"right\";\n\nexport interface CoverFlowItem {\n  id: string | number;\n  image: string;\n  title: string;\n  subtitle?: string;\n}\n\nexport interface RenderImageProps {\n  src: string;\n  alt: string;\n  width: number;\n  height: number;\n  className: string;\n  draggable: boolean;\n  sizes: string;\n  priority?: boolean;\n  loading?: \"eager\" | \"lazy\";\n}\n\nexport type CoverFlowScrollMode = \"scrub\" | \"step\";\n\nexport interface UseCoverFlowOptions {\n  items: CoverFlowItem[];\n  itemWidth?: number;\n  centerGap?: number;\n  initialIndex?: number;\n  scrollMode?: CoverFlowScrollMode;\n  enableScroll?: boolean;\n  enableAudio?: boolean;\n  enableClickToSnap?: boolean;\n  scrollThreshold?: number;\n  reduceMotion?: boolean;\n  onItemClick?: (item: CoverFlowItem, index: number) => void;\n  onIndexChange?: (index: number) => void;\n}\n\nexport interface CoverFlowContainerProps {\n  role: \"region\";\n  \"aria-roledescription\"?: string;\n  \"aria-label\": string;\n  tabIndex: number;\n  onKeyDown: (e: React.KeyboardEvent) => void;\n  drag: \"x\";\n  dragConstraints: { left: number; right: number };\n  dragElastic: number;\n  dragMomentum: boolean;\n  onDragStart: () => void;\n  onDrag: (event: unknown, info: PanInfo) => void;\n  onDragEnd: (event: unknown, info: PanInfo) => void;\n}\n\nexport interface UseCoverFlowResult {\n  activeIndex: number;\n  scrollX: MotionValue<number>;\n  effectiveScrollX: MotionValue<number>;\n  isDragging: boolean;\n  scale: number;\n  prefersReducedMotion: boolean;\n  goTo: (index: number, velocity?: number, direction?: Direction) => void;\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  containerProps: CoverFlowContainerProps;\n  handleCardClick: (item: CoverFlowItem, index: number) => void;\n}\n\nexport interface CoverFlowProps {\n  items: CoverFlowItem[];\n  itemWidth?: number;\n  itemHeight?: number;\n  stackSpacing?: number;\n  centerGap?: number;\n  rotation?: number;\n  initialIndex?: number;\n  enableReflection?: boolean;\n  enableClickToSnap?: boolean;\n  enableScroll?: boolean;\n  enableAudio?: boolean;\n  scrollMode?: CoverFlowScrollMode;\n  scrollThreshold?: number;\n  reduceMotion?: boolean;\n  className?: string;\n  onItemClick?: (item: CoverFlowItem, index: number) => void;\n  onIndexChange?: (index: number) => void;\n  renderImage?: (props: RenderImageProps) => ReactNode;\n}\n\nconst defaultRenderImage = (props: RenderImageProps) => (\n  <img\n    src={props.src}\n    alt={props.alt}\n    width={props.width}\n    height={props.height}\n    className={props.className}\n    draggable={props.draggable}\n    sizes={props.sizes}\n    loading={props.loading}\n  />\n);\n\nfunction clampIndex(index: number, length: number) {\n  return Math.min(Math.max(index, 0), Math.max(length - 1, 0));\n}\n\nconst subscribeNever = () => () => {};\n\nconst SPRING_SCROLL = { stiffness: 150, damping: 24, mass: 1 };\nconst DRAG_DIVISOR_RATIO = 0.8;\nconst BOUNDS_DAMPING = 0.35;\nconst VELOCITY_PROJECTION = 0.002;\nconst SCRUB_SETTLE_MS = 120;\nconst STEP_DELTA_THRESHOLD = 40;\nconst MAX_SCRUB_VELOCITY = 4000;\n\nexport function useCoverFlow({\n  items,\n  itemWidth = 400,\n  centerGap = 250,\n  initialIndex = 0,\n  scrollMode = \"scrub\",\n  enableScroll = true,\n  enableAudio = false,\n  enableClickToSnap = true,\n  scrollThreshold = 100,\n  reduceMotion,\n  onItemClick,\n  onIndexChange,\n}: UseCoverFlowOptions): UseCoverFlowResult {\n  const safeInitial = clampIndex(initialIndex, items.length);\n  const [activeIndex, setActiveIndex] = useState(safeInitial);\n  const [isDragging, setIsDragging] = useState(false);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const [containerWidth, setContainerWidth] = useState(0);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n    const ro = new ResizeObserver(([entry]) => {\n      setContainerWidth(entry.contentRect.width);\n    });\n    ro.observe(container);\n    return () => ro.disconnect();\n  }, []);\n\n  const scale = containerWidth > 0 && itemWidth > 0 ? Math.min(1, (containerWidth * 0.78) / itemWidth) : 1;\n  const effectiveCenterGap = Math.round(centerGap * scale);\n\n  const activeIndexRef = useRef(activeIndex);\n  const enableScrollRef = useRef(enableScroll);\n  const scrollThresholdRef = useRef(scrollThreshold);\n  const onItemClickRef = useRef(onItemClick);\n  const enableClickToSnapRef = useRef(enableClickToSnap);\n  const onIndexChangeRef = useRef(onIndexChange);\n  const isMountedForCallbackRef = useRef(false);\n  const isRtlRef = useRef(false);\n  const scrollModeRef = useRef(scrollMode);\n  const centerGapRef = useRef(effectiveCenterGap);\n\n  useEffect(() => {\n    activeIndexRef.current = activeIndex;\n    enableScrollRef.current = enableScroll;\n    scrollThresholdRef.current = scrollThreshold;\n    onItemClickRef.current = onItemClick;\n    enableClickToSnapRef.current = enableClickToSnap;\n    onIndexChangeRef.current = onIndexChange;\n    scrollModeRef.current = scrollMode;\n    centerGapRef.current = effectiveCenterGap;\n  });\n\n  const systemReducedMotion = useReducedMotion();\n  const prefersReducedMotion = reduceMotion ?? systemReducedMotion ?? false;\n  const scrollX = useMotionValue(safeInitial);\n  const springX = useSpring(scrollX, SPRING_SCROLL);\n  const effectiveScrollX = prefersReducedMotion ? scrollX : springX;\n  const tick = useTickAudio(enableAudio);\n\n  const clampedInitial = clampIndex(initialIndex, items.length);\n  const [prevClampedInitial, setPrevClampedInitial] = useState(clampedInitial);\n  if (prevClampedInitial !== clampedInitial) {\n    setPrevClampedInitial(clampedInitial);\n    if (clampedInitial !== activeIndex) {\n      setActiveIndex(clampedInitial);\n      scrollX.set(clampedInitial);\n    }\n  }\n\n  useEffect(() => {\n    if (!isMountedForCallbackRef.current) {\n      isMountedForCallbackRef.current = true;\n      return;\n    }\n    onIndexChangeRef.current?.(activeIndex);\n  }, [activeIndex]);\n\n  const jumpToIndex = useCallback(\n    (index: number, velocity = 0, direction?: Direction) => {\n      const clamped = clampIndex(index, items.length);\n      const prev = activeIndexRef.current;\n      if (clamped === prev) return;\n      const dir: Direction = direction ?? (clamped > prev ? \"right\" : \"left\");\n      setActiveIndex(clamped);\n      scrollX.set(clamped);\n      tick(dir, velocity);\n    },\n    [items.length, scrollX, tick],\n  );\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    isRtlRef.current = getComputedStyle(container).direction === \"rtl\";\n\n    let accumulator = 0;\n    let lastTime = 0;\n    let lastJump = 0;\n    let scrubbing = false;\n    let rawScrub = 0;\n    let scrubVelocity = 0;\n    let lastScrubTime = 0;\n    let lastTickedIndex = 0;\n    let settleTimer: ReturnType<typeof setTimeout> | undefined;\n\n    const handleWheel = (e: WheelEvent) => {\n      if (!enableScrollRef.current) return;\n      if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) return;\n      e.preventDefault();\n\n      const now = e.timeStamp;\n      const notched = e.deltaMode !== 0 || Math.abs(e.deltaX) >= STEP_DELTA_THRESHOLD;\n\n      if (scrollModeRef.current === \"scrub\" && !notched) {\n        const deltaX = isRtlRef.current ? -e.deltaX : e.deltaX;\n\n        if (!scrubbing) {\n          scrubbing = true;\n          rawScrub = scrollX.get();\n          scrubVelocity = 0;\n          lastScrubTime = 0;\n          lastTickedIndex = Math.round(rawScrub);\n        }\n\n        const dt = lastScrubTime === 0 ? 0 : now - lastScrubTime;\n        lastScrubTime = now;\n        if (dt > 0) {\n          scrubVelocity = Math.max(\n            -MAX_SCRUB_VELOCITY,\n            Math.min(MAX_SCRUB_VELOCITY, (-deltaX / dt) * 1000),\n          );\n        }\n\n        const before = Math.round(scrollX.get());\n        rawScrub += deltaX / (centerGapRef.current * DRAG_DIVISOR_RATIO);\n        const max = items.length - 1;\n        const damped =\n          rawScrub < 0\n            ? rawScrub * BOUNDS_DAMPING\n            : rawScrub > max\n              ? max + (rawScrub - max) * BOUNDS_DAMPING\n              : rawScrub;\n        scrollX.set(damped);\n\n        const after = Math.round(damped);\n        if (after !== before) {\n          lastTickedIndex = after;\n          tick(after > before ? \"right\" : \"left\", Math.abs(scrubVelocity));\n        }\n\n        if (settleTimer) clearTimeout(settleTimer);\n        settleTimer = setTimeout(() => {\n          scrubbing = false;\n          const target = clampIndex(\n            Math.round(scrollX.get() - scrubVelocity * VELOCITY_PROJECTION),\n            items.length,\n          );\n          const prev = activeIndexRef.current;\n          setActiveIndex(target);\n          scrollX.set(target);\n          if (target !== lastTickedIndex) {\n            tick(target >= prev ? \"right\" : \"left\", Math.abs(scrubVelocity));\n          }\n        }, SCRUB_SETTLE_MS);\n        return;\n      }\n\n      if (now - lastTime > 200) accumulator = 0;\n      lastTime = now;\n      accumulator += isRtlRef.current ? -e.deltaX : e.deltaX;\n\n      const threshold = scrollThresholdRef.current;\n      const shouldJump =\n        (accumulator > threshold || accumulator < -threshold) &&\n        now - lastJump > 150;\n\n      if (shouldJump) {\n        const dir = accumulator > 0 ? \"right\" : \"left\";\n        jumpToIndex(\n          Math.round(scrollX.get()) + (dir === \"right\" ? 1 : -1),\n          Math.abs(e.deltaX),\n          dir,\n        );\n        accumulator = 0;\n        lastJump = now;\n      }\n    };\n\n    container.addEventListener(\"wheel\", handleWheel, { passive: false });\n    return () => {\n      container.removeEventListener(\"wheel\", handleWheel);\n      if (settleTimer) clearTimeout(settleTimer);\n    };\n  }, [jumpToIndex, scrollX, tick, items.length]);\n\n  const handleCardClick = useCallback(\n    (item: CoverFlowItem, index: number) => {\n      if (index === activeIndexRef.current) {\n        onItemClickRef.current?.(item, index);\n      } else if (enableClickToSnapRef.current) {\n        jumpToIndex(index);\n      }\n    },\n    [jumpToIndex],\n  );\n\n  const rawDragX = useRef(0);\n\n  const onDragStart = useCallback(() => {\n    rawDragX.current = scrollX.get();\n    setIsDragging(true);\n  }, [scrollX]);\n\n  const onDrag = useCallback(\n    (_: unknown, info: PanInfo) => {\n      const deltaX = isRtlRef.current ? -info.delta.x : info.delta.x;\n      rawDragX.current -= deltaX / (effectiveCenterGap * DRAG_DIVISOR_RATIO);\n      const raw = rawDragX.current;\n      const max = items.length - 1;\n      const damped =\n        raw < 0\n          ? raw * BOUNDS_DAMPING\n          : raw > max\n            ? max + (raw - max) * BOUNDS_DAMPING\n            : raw;\n      scrollX.set(damped);\n    },\n    [effectiveCenterGap, items.length, scrollX],\n  );\n\n  const onDragEnd = useCallback(\n    (_: unknown, info: PanInfo) => {\n      setIsDragging(false);\n      const velocityX = isRtlRef.current ? -info.velocity.x : info.velocity.x;\n      const projected = scrollX.get() - velocityX * VELOCITY_PROJECTION;\n      const clamped = clampIndex(Math.round(projected), items.length);\n      const prev = activeIndexRef.current;\n      const dir: Direction = clamped >= prev ? \"right\" : \"left\";\n      setActiveIndex(clamped);\n      scrollX.set(clamped);\n      if (clamped !== prev) tick(dir, Math.abs(info.velocity.x));\n    },\n    [items.length, scrollX, tick],\n  );\n\n  const onKeyDown = useCallback(\n    (e: React.KeyboardEvent) => {\n      const back = isRtlRef.current ? \"ArrowRight\" : \"ArrowLeft\";\n      const forward = isRtlRef.current ? \"ArrowLeft\" : \"ArrowRight\";\n      if (e.key === back) {\n        e.preventDefault();\n        jumpToIndex(activeIndexRef.current - 1, 120, \"left\");\n      }\n      if (e.key === forward) {\n        e.preventDefault();\n        jumpToIndex(activeIndexRef.current + 1, 120, \"right\");\n      }\n      if (e.key === \"Enter\" || e.key === \" \") {\n        e.preventDefault();\n        const idx = clampIndex(activeIndexRef.current, items.length);\n        const item = items[idx];\n        if (item) onItemClickRef.current?.(item, idx);\n      }\n    },\n    [jumpToIndex, items],\n  );\n\n  return {\n    activeIndex,\n    scrollX,\n    effectiveScrollX,\n    isDragging,\n    scale,\n    prefersReducedMotion,\n    goTo: jumpToIndex,\n    containerRef,\n    containerProps: {\n      role: \"region\",\n      \"aria-roledescription\": \"carousel\",\n      \"aria-label\": \"Cover Flow\",\n      tabIndex: 0,\n      onKeyDown,\n      drag: \"x\",\n      dragConstraints: { left: 0, right: 0 },\n      dragElastic: 0,\n      dragMomentum: false,\n      onDragStart,\n      onDrag,\n      onDragEnd,\n    },\n    handleCardClick,\n  };\n}\n\nexport function CoverFlow({\n  items,\n  itemWidth = 400,\n  itemHeight = 400,\n  stackSpacing = 100,\n  centerGap = 250,\n  rotation = 50,\n  initialIndex = 0,\n  enableReflection = false,\n  enableClickToSnap = true,\n  enableScroll = true,\n  enableAudio = false,\n  scrollMode = \"scrub\",\n  scrollThreshold = 100,\n  reduceMotion,\n  className,\n  onItemClick,\n  onIndexChange,\n  renderImage,\n}: CoverFlowProps) {\n  const {\n    activeIndex,\n    effectiveScrollX,\n    isDragging,\n    scale,\n    prefersReducedMotion,\n    containerRef,\n    containerProps,\n    handleCardClick,\n  } = useCoverFlow({\n    items,\n    itemWidth,\n    centerGap,\n    initialIndex,\n    scrollMode,\n    enableScroll,\n    enableAudio,\n    enableClickToSnap,\n    scrollThreshold,\n    reduceMotion,\n    onItemClick,\n    onIndexChange,\n  });\n\n  const instanceId = useId().replace(/:/g, \"x\");\n  const isMounted = useSyncExternalStore(\n    subscribeNever,\n    () => true,\n    () => false,\n  );\n  const [isMobile, setIsMobile] = useState(false);\n  const [isSafari] = useState(\n    () =>\n      typeof window !== \"undefined\" &&\n      /^((?!chrome|android).)*safari/i.test(window.navigator.userAgent),\n  );\n\n  useEffect(() => {\n    if (typeof window === \"undefined\" || !window.matchMedia) return;\n    const mql = window.matchMedia(\"(max-width: 768px), (pointer: coarse)\");\n    const apply = () => setIsMobile(mql.matches);\n    apply();\n    mql.addEventListener?.(\"change\", apply);\n    return () => mql.removeEventListener?.(\"change\", apply);\n  }, []);\n\n  const effectiveWidth = Math.round(itemWidth * scale);\n  const effectiveHeight = Math.round(itemHeight * scale);\n  const effectiveStackSpacing = Math.round(stackSpacing * scale);\n  const effectiveCenterGap = Math.round(centerGap * scale);\n\n  const reflectionFilterId =\n    isMounted && enableReflection && !isMobile && !isSafari\n      ? `${instanceId}-rf`\n      : undefined;\n  const showReflection = isMounted && enableReflection;\n\n  if (items.length === 0) return null;\n\n  return (\n    <>\n      {reflectionFilterId && (\n        <svg\n          aria-hidden=\"true\"\n          focusable=\"false\"\n          style={{\n            position: \"absolute\",\n            width: 0,\n            height: 0,\n            overflow: \"hidden\",\n          }}\n        >\n          <defs>\n            <filter\n              id={reflectionFilterId}\n              x=\"-3%\"\n              y=\"-3%\"\n              width=\"106%\"\n              height=\"106%\"\n              colorInterpolationFilters=\"sRGB\"\n            >\n              <feTurbulence\n                type=\"fractalNoise\"\n                baseFrequency=\"0.018 0.065\"\n                numOctaves={3}\n                seed={8}\n                result=\"noise\"\n              />\n              <feDisplacementMap\n                in=\"SourceGraphic\"\n                in2=\"noise\"\n                scale={5}\n                xChannelSelector=\"R\"\n                yChannelSelector=\"G\"\n                result=\"displaced\"\n              />\n              <feGaussianBlur in=\"displaced\" stdDeviation=\"0.4 1.8\" />\n            </filter>\n          </defs>\n        </svg>\n      )}\n      <motion.div\n        ref={containerRef}\n        className={cn(\n          \"group/cf relative flex h-full w-full flex-col items-center justify-center overflow-hidden bg-transparent focus:outline-none touch-pan-y\",\n          isDragging ? \"is-dragging cursor-grabbing\" : \"cursor-grab\",\n          className,\n        )}\n        {...containerProps}\n      >\n        <div\n          className=\"relative flex h-full w-full items-center justify-center pointer-events-none\"\n          style={{ transformStyle: \"preserve-3d\", perspective: 1000 }}\n        >\n          {items.map((item, index) =>\n            Math.abs(index - activeIndex) > 6 ? null : (\n            <CoverFlowItemCard\n              key={item.id}\n              item={item}\n              index={index}\n              scrollX={effectiveScrollX}\n              width={effectiveWidth}\n              height={effectiveHeight}\n              stackSpacing={effectiveStackSpacing}\n              centerGap={effectiveCenterGap}\n              rotation={rotation}\n              isActive={index === activeIndex}\n              showReflection={showReflection}\n              reflectionFilterId={reflectionFilterId}\n              enableClickToSnap={enableClickToSnap}\n              reduceMotion={prefersReducedMotion}\n              renderImage={renderImage}\n              onCardClick={handleCardClick}\n            />\n            ),\n          )}\n        </div>\n\n        <div className=\"absolute bottom-8 left-0 right-0 z-40 flex flex-col items-center justify-center pointer-events-none\">\n          <AnimatePresence>\n            <motion.div\n              key={activeIndex}\n              initial={{ opacity: 0, y: prefersReducedMotion ? 0 : 8 }}\n              animate={{ opacity: 1, y: 0 }}\n              exit={{ opacity: 0, y: prefersReducedMotion ? 0 : -6 }}\n              transition={{\n                duration: prefersReducedMotion ? 0 : 0.18,\n                ease: [0.23, 1, 0.32, 1],\n              }}\n              className=\"text-center absolute inset-x-0\"\n            >\n              <h3 className=\"text-2xl font-semibold tracking-tight drop-shadow-md text-zinc-900 dark:text-zinc-100\">\n                {items[activeIndex]?.title}\n              </h3>\n              {items[activeIndex]?.subtitle && (\n                <p className=\"mt-1 text-sm font-medium tracking-wide text-zinc-900/60 dark:text-zinc-100/60\">\n                  {items[activeIndex]?.subtitle}\n                </p>\n              )}\n            </motion.div>\n          </AnimatePresence>\n        </div>\n      </motion.div>\n    </>\n  );\n}\n\nCoverFlow.displayName = \"CoverFlow\";\n\ninterface CardProps {\n  item: CoverFlowItem;\n  index: number;\n  scrollX: MotionValue<number>;\n  width: number;\n  height: number;\n  stackSpacing: number;\n  centerGap: number;\n  rotation: number;\n  isActive: boolean;\n  showReflection: boolean;\n  reflectionFilterId?: string;\n  enableClickToSnap: boolean;\n  reduceMotion: boolean;\n  renderImage?: (props: RenderImageProps) => ReactNode;\n  onCardClick: (item: CoverFlowItem, index: number) => void;\n}\n\nconst CoverFlowItemCard = memo(function CoverFlowItemCard({\n  item,\n  index,\n  scrollX,\n  width,\n  height,\n  stackSpacing,\n  centerGap,\n  rotation,\n  isActive,\n  showReflection,\n  reflectionFilterId,\n  enableClickToSnap,\n  reduceMotion,\n  renderImage,\n  onCardClick,\n}: CardProps) {\n  const rotateY = useTransform(scrollX, (value) => {\n    if (reduceMotion) return 0;\n    const pos = index - value;\n    const absPos = Math.abs(pos);\n    return absPos < 0.5\n      ? -pos * (rotation * 2)\n      : pos < 0\n        ? rotation\n        : -rotation;\n  });\n\n  const x = useTransform(scrollX, (value) => {\n    const pos = index - value;\n    const absPos = Math.abs(pos);\n    if (absPos < 1) return pos * centerGap;\n    return pos < 0\n      ? -centerGap - (absPos - 1) * stackSpacing\n      : centerGap + (absPos - 1) * stackSpacing;\n  });\n\n  const z = useTransform(scrollX, (value) => {\n    if (reduceMotion) return 0;\n    const absPos = Math.abs(index - value);\n    return absPos > 0.5 ? -200 : absPos * -400;\n  });\n\n  const zIndex = useTransform(\n    scrollX,\n    (value) => 1000 - Math.abs(index - value) * 10,\n  );\n\n  const dimOpacity = useTransform(scrollX, (value) =>\n    Math.abs(index - value) < 0.5 ? 0 : 0.5,\n  );\n\n  const imageRenderer = renderImage ?? defaultRenderImage;\n  const cursorClass =\n    isActive || enableClickToSnap ? \"cursor-pointer\" : \"cursor-grab\";\n\n  return (\n    <motion.div\n      className={cn(\n        \"absolute top-1/2 left-1/2 preserve-3d will-change-transform group-[.is-dragging]/cf:!cursor-grabbing\",\n        cursorClass,\n      )}\n      style={{\n        width,\n        height,\n        marginTop: -height / 2,\n        marginLeft: -width / 2,\n        x,\n        z,\n        rotateY,\n        zIndex,\n        pointerEvents: \"auto\",\n      }}\n      onClick={() => onCardClick(item, index)}\n    >\n      <div className=\"relative h-full w-full rounded-xl bg-black shadow-2xl\">\n        <div className=\"absolute inset-0 z-20 rounded-xl border border-white/10 pointer-events-none\" />\n        <div className=\"relative h-full w-full overflow-hidden rounded-xl\">\n          {imageRenderer({\n            src: item.image,\n            alt: item.title,\n            width,\n            height,\n            className:\n              \"object-cover select-none pointer-events-none w-full h-full\",\n            draggable: false,\n            sizes: `${width}px`,\n            priority: isActive,\n            loading: isActive ? \"eager\" : \"lazy\",\n          })}\n          <div className=\"absolute inset-0 z-10 bg-linear-to-tr from-white/10 to-transparent opacity-0 dark:opacity-20 pointer-events-none\" />\n        </div>\n        <motion.div\n          className=\"absolute inset-0 z-10 rounded-xl bg-black pointer-events-none\"\n          style={{ opacity: dimOpacity }}\n        />\n      </div>\n\n      {showReflection && (\n        <div\n          aria-hidden=\"true\"\n          className=\"absolute left-0 overflow-hidden pointer-events-none\"\n          style={{\n            top: \"100%\",\n            width,\n            height: height * 0.42,\n            marginTop: 1,\n            transformOrigin: \"top center\",\n            transform: \"rotateX(12deg) translateZ(0)\",\n            willChange: \"transform\",\n          }}\n        >\n          <div\n            style={{\n              width: \"100%\",\n              height: \"100%\",\n              transform: \"scaleY(-1)\",\n              filter: reflectionFilterId\n                ? `url(#${reflectionFilterId})`\n                : undefined,\n              mixBlendMode: reflectionFilterId ? \"screen\" : undefined,\n              opacity: reflectionFilterId ? 0.55 : 0.4,\n            }}\n          >\n            <div\n              className={cn(\n                \"relative h-full w-full rounded-xl bg-black\",\n                reflectionFilterId && \"shadow-2xl\",\n              )}\n            >\n              <div className=\"absolute inset-0 z-20 rounded-xl border border-white/10 pointer-events-none\" />\n              <div className=\"relative h-full w-full overflow-hidden rounded-xl\">\n                {imageRenderer({\n                  src: item.image,\n                  alt: \"\",\n                  width,\n                  height,\n                  className: \"object-cover w-full h-full\",\n                  draggable: false,\n                  sizes: `${width}px`,\n                  loading: \"lazy\",\n                })}\n              </div>\n            </div>\n          </div>\n          <div\n            className=\"absolute inset-0 pointer-events-none\"\n            style={{\n              background: \"linear-gradient(to top, var(--background) 0%, color-mix(in oklab, var(--background) 70%, transparent) 40%, transparent 100%)\",\n            }}\n          />\n        </div>\n      )}\n    </motion.div>\n  );\n});\n"
    }
  ],
  "docs": "Usage, props and examples: https://useplanes.com/components/cover-flow — full catalog for agents: https://useplanes.com/llms.txt"
}