{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "video-carousel",
  "type": "registry:component",
  "title": "Video Carousel",
  "description": "An interactive 3D real time audio visualizer.",
  "dependencies": [
    "framer-motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/chatcn/misc/video-carousel.tsx",
      "content": "\"use client\"\n\nimport React, { useEffect, useRef, useState, useCallback } from \"react\"\nimport { useInView, animate } from \"framer-motion\"\nimport { HugeiconsIcon } from \"@hugeicons/react\"\nimport { PauseIcon, PlayIcon } from \"@hugeicons/core-free-icons\"\n\nexport interface VideoItem {\n    url: string\n    duration: number\n}\n\nexport interface VideoCarouselProps {\n    videos: VideoItem[]\n    className?: string\n}\n\ninterface VideoState {\n    currentVideoId: number\n    isPlaying: boolean\n    hasStarted: boolean\n}\n\nexport function VideoCarousel({ videos, className = \"\" }: VideoCarouselProps) {\n    const containerRef = useRef<HTMLDivElement>(null)\n    const carouselRef = useRef<HTMLDivElement>(null)\n    const sliderRef = useRef<HTMLDivElement>(null)\n    const videoRef = useRef<(HTMLVideoElement | null)[]>([])\n    const videoSpanRef = useRef<(HTMLSpanElement | null)[]>([])\n    const videoDivRef = useRef<(HTMLSpanElement | null)[]>([])\n\n    const [containerWidth, setContainerWidth] = useState(0)\n    const [videoState, setVideoState] = useState<VideoState>({\n        currentVideoId: 0,\n        isPlaying: false,\n        hasStarted: false,\n    })\n\n    const { currentVideoId, isPlaying, hasStarted } = videoState\n    const isInView = useInView(carouselRef, { once: true, amount: 0.5 })\n\n    const getVideoWidth = useCallback((): number => {\n        if (!containerWidth) return 0\n        return containerWidth < 640 ? containerWidth * 0.88 : containerWidth * 0.7\n    }, [containerWidth])\n\n    const getGap = useCallback((): number => {\n        if (!containerWidth) return 80\n        return containerWidth < 640 ? 40 : 80\n    }, [containerWidth])\n\n    const getTranslateValue = useCallback((): number => {\n        if (!containerWidth) return 0\n        const videoWidth = getVideoWidth()\n        const gap = getGap()\n        const offset = (containerWidth - videoWidth) / 2\n        const slideOffset = currentVideoId * (videoWidth + gap)\n        return offset - slideOffset\n    }, [containerWidth, currentVideoId, getVideoWidth, getGap])\n\n    const getResponsiveWidth = useCallback((): string => {\n        if (!containerWidth) return \"60px\"\n        if (containerWidth < 480) return `${containerWidth * 0.15}px`\n        if (containerWidth < 760) return `${containerWidth * 0.12}px`\n        if (containerWidth < 1200) return `${containerWidth * 0.1}px`\n        return `${containerWidth * 0.08}px`\n    }, [containerWidth])\n\n    useEffect(() => {\n        const updateWidth = () => {\n            if (containerRef.current) {\n                setContainerWidth(containerRef.current.offsetWidth)\n            }\n        }\n        updateWidth()\n\n        const resizeObserver = new ResizeObserver(updateWidth)\n        if (containerRef.current) {\n            resizeObserver.observe(containerRef.current)\n        }\n        return () => resizeObserver.disconnect()\n    }, [])\n\n    useEffect(() => {\n        if (sliderRef.current && containerWidth) {\n            animate(sliderRef.current, { x: getTranslateValue() }, { duration: 2, ease: \"easeInOut\" })\n        }\n        if (isInView && !hasStarted) {\n            setVideoState((prev) => ({ ...prev, hasStarted: true, isPlaying: true }))\n        }\n    }, [currentVideoId, isInView, hasStarted, containerWidth, getTranslateValue])\n\n    useEffect(() => {\n        const currentVideo = videoRef.current[currentVideoId]\n        if (currentVideo && hasStarted) {\n            if (isPlaying) {\n                currentVideo.play().catch((err) => console.error(\"Video play failed:\", err))\n            } else {\n                currentVideo.pause()\n            }\n        }\n    }, [currentVideoId, isPlaying, hasStarted])\n\n    useEffect(() => {\n        const span = videoSpanRef.current\n        let animationFrameId: number\n\n        if (span[currentVideoId]) {\n            const updateProgress = (): void => {\n                const vid = videoRef.current[currentVideoId]\n                if (!vid) return\n\n                const duration = videos[currentVideoId].duration\n                const progress = Math.ceil((vid.currentTime / duration) * 100)\n\n                if (videoDivRef.current[currentVideoId]) {\n                    animate(videoDivRef.current[currentVideoId]!, { width: getResponsiveWidth() }, { duration: 0.2 })\n                }\n\n                if (span[currentVideoId]) {\n                    animate(span[currentVideoId]!, { width: `${progress}%`, backgroundColor: \"#afafaf\" }, { duration: 0.1 })\n                }\n\n                if (vid.currentTime >= duration - 0.1) {\n                    if (videoDivRef.current[currentVideoId]) {\n                        animate(videoDivRef.current[currentVideoId]!, { width: \"12px\" }, { duration: 0.3 })\n                    }\n                    if (span[currentVideoId]) {\n                        animate(span[currentVideoId]!, { backgroundColor: \"#afafaf\" }, { duration: 0.3 })\n                    }\n                } else if (isPlaying) {\n                    animationFrameId = requestAnimationFrame(updateProgress)\n                }\n            }\n\n            if (isPlaying) {\n                animationFrameId = requestAnimationFrame(updateProgress)\n            }\n        }\n\n        return () => {\n            if (animationFrameId) cancelAnimationFrame(animationFrameId)\n        }\n    }, [currentVideoId, isPlaying, videos, containerWidth, getResponsiveWidth])\n\n    const handleVideoEnd = (currentIndex: number): void => {\n        const span = videoSpanRef.current\n        const div = videoDivRef.current\n\n        if (span[currentIndex] && div[currentIndex]) {\n            animate(div[currentIndex]!, { width: \"12px\" }, { duration: 0.1 })\n            animate(span[currentIndex]!, { width: \"0%\", backgroundColor: \"#afafaf\", opacity: 0 }, { duration: 0.1 })\n        }\n\n        setTimeout(() => {\n            if (span[currentIndex]) {\n                animate(span[currentIndex]!, { opacity: 1 }, { duration: 0 })\n            }\n            const nextId = currentIndex === videos.length - 1 ? 0 : currentIndex + 1\n            setVideoState((prev) => ({ ...prev, currentVideoId: nextId, isPlaying: true }))\n        }, 300)\n    }\n\n    const togglePlayPause = (): void => {\n        setVideoState((prev) => ({ ...prev, isPlaying: !prev.isPlaying }))\n    }\n\n    const videoWidth = getVideoWidth()\n    const gap = getGap()\n    const videoHeight = videoWidth ? videoWidth * (9 / 16) : 400\n\n    return (\n        <div ref={containerRef} className={`w-full overflow-hidden h-full py-10 sm:py-16 ${className}`}>\n            <div ref={carouselRef} className=\"flex items-center\">\n                <div ref={sliderRef} className=\"flex items-center\">\n                    {videos.map((video, idx) => (\n                        <div\n                            key={idx}\n                            className=\"shrink-0\"\n                            style={{ width: videoWidth ? `${videoWidth}px` : \"70vw\", marginRight: `${gap}px` }}\n                        >\n                            <div className=\"relative w-full\" style={{ height: videoWidth ? `${videoHeight}px` : \"400px\" }}>\n                                <div className=\"w-full h-full flex items-center justify-center rounded-3xl overflow-hidden bg-black\">\n                                    <video\n                                        playsInline\n                                        preload=\"auto\"\n                                        muted\n                                        className=\"w-full h-full object-cover\"\n                                        ref={(el) => { videoRef.current[idx] = el }}\n                                        onPlay={() => setVideoState((prev) => ({ ...prev, isPlaying: true }))}\n                                        onEnded={() => handleVideoEnd(idx)}\n                                    >\n                                        <source src={video.url} type=\"video/mp4\" />\n                                    </video>\n                                </div>\n                            </div>\n                        </div>\n                    ))}\n                </div>\n            </div>\n\n            <div className=\"relative flex items-center justify-center mt-6 sm:mt-10 px-5\">\n                <div className=\"flex items-center justify-center py-3 px-5 sm:py-5 sm:px-7 bg-zinc-800 backdrop-blur rounded-full\">\n                    {videos.map((_, i) => (\n                        <span\n                            key={i}\n                            ref={(el) => { videoDivRef.current[i] = el }}\n                            className=\"mx-1.5 sm:mx-2 w-2.5 h-2.5 sm:w-3 sm:h-3 bg-gray-200 rounded-full relative cursor-pointer overflow-hidden\"\n                        >\n                            <span ref={(el) => { videoSpanRef.current[i] = el }} className=\"absolute h-full w-full rounded-full\" />\n                        </span>\n                    ))}\n                </div>\n\n                <button\n                    onClick={togglePlayPause}\n                    aria-label={isPlaying ? \"Pause video\" : \"Play video\"}\n                    className=\"ml-3 sm:ml-4 p-3 sm:p-4 rounded-full bg-zinc-800 backdrop-blur-md flex items-center justify-center hover:bg-zinc-700 transition-colors\"\n                >\n                    <HugeiconsIcon icon={isPlaying ? PauseIcon : PlayIcon} className=\"w-4 h-4 sm:w-5 sm:h-5 text-white\" />\n                </button>\n            </div>\n        </div>\n    )\n}\n\nexport default VideoCarousel",
      "type": "registry:component"
    }
  ]
}