{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "video-reel",
  "type": "registry:block",
  "title": "Video Reel",
  "description": "A component for displaying short-form vertical videos similar to TikTok, YouTube Shorts, or Instagram Reels.",
  "dependencies": [
    "react-player"
  ],
  "registryDependencies": [
    "user-info",
    "post-engagement",
    "post-text"
  ],
  "files": [
    {
      "path": "src/components/deso/video-reel.tsx",
      "content": "'use client';\n\nimport React, { useState, useRef, useEffect } from 'react';\nimport { ChevronUp, ChevronDown, Play, Pause, Volume2, VolumeX } from 'lucide-react';\nimport { cn } from '@/lib/utils/deso';\nimport { Button } from '@/components/ui/button';\nimport { UserInfo } from './user-info';\nimport { PostText } from './post-text';\nimport { PostEngagement } from './post-engagement';\nimport { Profile } from '@/lib/schemas/deso';\nimport ReactPlayer from 'react-player/lazy';\nimport { Timestamp } from './timestamp';\n\nexport interface VideoReelItem {\n  id: string;\n  videoUrl: string;\n  publicKey: string;\n  profile?: Profile;\n  text?: string;\n  timestamp: Date;\n  engagement: {\n    likes: number;\n    comments: number;\n    reposts: number;\n    diamonds: number;\n    diamondValue?: string;\n    views: number;\n  };\n  isLiked?: boolean;\n  isReposted?: boolean;\n}\n\nexport interface VideoReelProps {\n  videos: VideoReelItem[];\n  variant?: 'single' | 'carousel' | 'carousel-with-arrows' | 'full-height';\n  className?: string;\n  autoPlay?: boolean;\n  showEngagement?: boolean;\n  onLike?: (videoId: string) => void;\n  onComment?: (videoId: string) => void;\n  onRepost?: (videoId: string) => void;\n  onDiamond?: (videoId: string) => void;\n  showVideoProgress?: boolean;\n}\n\nexport function VideoReel({\n  videos,\n  variant = 'single',\n  className,\n  autoPlay = true,\n  showEngagement = true,\n  onLike,\n  onComment,\n  onRepost,\n  onDiamond,\n  showVideoProgress = true,\n}: VideoReelProps) {\n  const [currentIndex, setCurrentIndex] = useState(0);\n  const [isScrolling, setIsScrolling] = useState(false);\n  const [visibleVideoIds, setVisibleVideoIds] = useState<Set<string>>(new Set());\n  const [pausedVideoIds, setPausedVideoIds] = useState<Set<string>>(new Set());\n  const [showPlayIcon, setShowPlayIcon] = useState<Set<string>>(new Set());\n  const [mutedVideoIds, setMutedVideoIds] = useState<Set<string>>(new Set(videos.map(v => v.id))); // Start all muted\n  const [videoDurations, setVideoDurations] = useState<Map<string, number>>(new Map());\n  const [videoProgress, setVideoProgress] = useState<Map<string, number>>(new Map());\n  const containerRef = useRef<HTMLDivElement>(null);\n  const videoRefs = useRef<Map<string, HTMLDivElement>>(new Map());\n  const playerRefs = useRef<Map<string, any>>(new Map());\n  const currentVideo = videos[currentIndex];\n\n  const handleScroll = (e: React.WheelEvent) => {\n    if ((variant === 'carousel' || variant === 'full-height') && videos.length > 1 && !isScrolling) {\n      // Only change video if scroll is significant enough\n      if (Math.abs(e.deltaY) > 50) {\n        setIsScrolling(true);\n        if (e.deltaY > 0 && currentIndex < videos.length - 1) {\n          setCurrentIndex(prev => prev + 1);\n        } else if (e.deltaY < 0 && currentIndex > 0) {\n          setCurrentIndex(prev => prev - 1);\n        }\n        // Reset scrolling flag after a delay\n        setTimeout(() => setIsScrolling(false), 500);\n      }\n    }\n  };\n\n  const navigateUp = () => {\n    if (currentIndex > 0) {\n      setCurrentIndex(currentIndex - 1);\n    }\n  };\n\n  const navigateDown = () => {\n    if (currentIndex < videos.length - 1) {\n      setCurrentIndex(currentIndex + 1);\n    }\n  };\n\n  useEffect(() => {\n    if (variant === 'carousel' || variant === 'carousel-with-arrows' || variant === 'full-height') {\n      const container = containerRef.current;\n      if (container) {\n        const targetScrollTop = variant === 'full-height' \n          ? currentIndex * window.innerHeight \n          : currentIndex * container.clientHeight;\n        container.scrollTo({\n          top: targetScrollTop,\n          behavior: 'smooth',\n        });\n      }\n    }\n  }, [currentIndex, variant]);\n\n  // Set up intersection observer for video visibility\n  useEffect(() => {\n    if (variant === 'single') {\n      // For single variant, always show the current video\n      setVisibleVideoIds(new Set([currentVideo?.id].filter(Boolean)));\n      return;\n    }\n\n    const observer = new IntersectionObserver(\n      (entries) => {\n        const newVisibleIds = new Set<string>();\n        entries.forEach((entry) => {\n          const videoId = entry.target.getAttribute('data-video-id');\n          if (videoId && entry.isIntersecting && entry.intersectionRatio > 0.5) {\n            newVisibleIds.add(videoId);\n          }\n        });\n        setVisibleVideoIds(newVisibleIds);\n      },\n      {\n        root: containerRef.current,\n        threshold: [0.5], // Video must be at least 50% visible\n      }\n    );\n\n    // Observe all video elements\n    videoRefs.current.forEach((element) => {\n      observer.observe(element);\n    });\n\n    return () => {\n      observer.disconnect();\n    };\n  }, [videos, variant, currentVideo?.id]);\n\n  // Helper function to set video ref\n  const setVideoRef = (videoId: string, element: HTMLDivElement | null) => {\n    if (element) {\n      videoRefs.current.set(videoId, element);\n    } else {\n      videoRefs.current.delete(videoId);\n    }\n  };\n\n  // Helper function to set player ref\n  const setPlayerRef = (videoId: string, player: any) => {\n    if (player) {\n      playerRefs.current.set(videoId, player);\n    } else {\n      playerRefs.current.delete(videoId);\n    }\n  };\n\n  // Handle video duration\n  const handleDuration = (videoId: string, duration: number) => {\n    setVideoDurations(prev => new Map(prev).set(videoId, duration));\n  };\n\n  // Handle video progress\n  const handleProgress = (videoId: string, progress: { played: number, playedSeconds: number }) => {\n    setVideoProgress(prev => new Map(prev).set(videoId, progress.playedSeconds));\n  };\n\n  // Handle seeking\n  const handleSeek = (videoId: string, seconds: number) => {\n    const player = playerRefs.current.get(videoId);\n    if (player) {\n      player.seekTo(seconds, 'seconds');\n    }\n  };\n\n  // Format time for display\n  const formatTime = (seconds: number) => {\n    const mins = Math.floor(seconds / 60);\n    const secs = Math.floor(seconds % 60);\n    return `${mins}:${secs.toString().padStart(2, '0')}`;\n  };\n\n  // Handle video click to play/pause\n  const handleVideoClick = (videoId: string) => {\n    const isPaused = pausedVideoIds.has(videoId);\n    \n    if (isPaused) {\n      // Resume playing\n      setPausedVideoIds(prev => {\n        const newSet = new Set(prev);\n        newSet.delete(videoId);\n        return newSet;\n      });\n      // Hide play icon after a delay\n      setShowPlayIcon(prev => {\n        const newSet = new Set(prev);\n        newSet.delete(videoId);\n        return newSet;\n      });\n    } else {\n      // Pause video\n      setPausedVideoIds(prev => {\n        const newSet = new Set(prev).add(videoId);\n        return newSet;\n      });\n      // Show play icon\n      setShowPlayIcon(prev => new Set(prev).add(videoId));\n    }\n  };\n\n  // Handle mute/unmute toggle\n  const handleMuteToggle = (videoId: string, e?: React.MouseEvent) => {\n    if (e) {\n      e.stopPropagation();\n      e.preventDefault();\n    }\n    setMutedVideoIds(prev => {\n      const newSet = new Set(prev);\n      if (newSet.has(videoId)) {\n        newSet.delete(videoId);\n      } else {\n        newSet.add(videoId);\n      }\n      return newSet;\n    });\n  };\n\n  if (!currentVideo && variant === 'single') {\n    return null;\n  }\n\n  const renderVideo = (video: VideoReelItem, index: number) => {\n    const isVisible = visibleVideoIds.has(video.id);\n    const isPaused = pausedVideoIds.has(video.id);\n    const isMuted = mutedVideoIds.has(video.id);\n    const shouldPlay = autoPlay && isVisible && !isPaused;\n    const showIcon = showPlayIcon.has(video.id);\n\n    return (\n      <div\n        key={video.id}\n        ref={(el) => setVideoRef(video.id, el)}\n        data-video-id={video.id}\n        className=\"relative w-full h-full bg-black cursor-pointer\"\n        onClick={(e) => {\n          // Only handle click if it's not on a UI element\n          const target = e.target as HTMLElement;\n          const isUIElement = target.closest('[data-ui-element=\"true\"]') || \n                             target.closest('button') || \n                             target.closest('a') ||\n                             target.closest('[role=\"button\"]');\n          \n          if (!isUIElement) {\n            handleVideoClick(video.id);\n          }\n        }}\n      >\n        {/* Video */}\n        <div className=\"absolute inset-0\">\n          <ReactPlayer\n            ref={(player) => setPlayerRef(video.id, player)}\n            url={video.videoUrl}\n            width=\"100%\"\n            height=\"100%\"\n            controls={false}\n            playing={shouldPlay}\n            loop\n            muted={isMuted}\n            onDuration={(duration) => handleDuration(video.id, duration)}\n            onProgress={(progress) => handleProgress(video.id, progress)}\n            progressInterval={100}\n            className={cn(\n              'object-cover',\n              variant === 'single' ? 'rounded-lg overflow-hidden' : ''\n            )}\n            style={{ \n              objectFit: 'cover',\n            }}\n          />\n        </div>\n\n        {/* Mute/Unmute Button */}\n        <div className=\"absolute top-4 right-4 z-30 pointer-events-auto\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            onClick={(e) => {\n              e.stopPropagation();\n              e.preventDefault();\n              handleMuteToggle(video.id, e);\n            }}\n            onMouseDown={(e) => {\n              e.stopPropagation();\n              e.preventDefault();\n            }}\n            className=\"bg-black/50 text-white hover:bg-black/70 rounded-full h-10 w-10 backdrop-blur-sm\"\n          >\n            {isMuted ? (\n              <VolumeX className=\"h-5 w-5\" />\n            ) : (\n              <Volume2 className=\"h-5 w-5\" />\n            )}\n          </Button>\n        </div>\n\n        {/* Video Scrubber */}\n        {showVideoProgress && (() => {\n          const duration = videoDurations.get(video.id) || 0;\n          const currentTime = videoProgress.get(video.id) || 0;\n          const progress = duration > 0 ? (currentTime / duration) * 100 : 0;\n\n          return (\n            <div className=\"absolute bottom-4 left-4 right-4 z-20 pointer-events-auto w-[70%]\">\n              <div className=\"flex items-center gap-2 text-white text-xs\">\n                <span className=\"min-w-[32px]\">{formatTime(currentTime)}</span>\n                <div className=\"flex-1 relative\">\n                  <div className=\"h-1 bg-white/30 rounded-full overflow-hidden\">\n                    <div \n                      className=\"h-full bg-white rounded-full transition-all duration-100\"\n                      style={{ width: `${progress}%` }}\n                    />\n                  </div>\n                  <input\n                    type=\"range\"\n                    min={0}\n                    max={duration}\n                    value={currentTime}\n                    onChange={(e) => {\n                      const newTime = parseFloat(e.target.value);\n                      handleSeek(video.id, newTime);\n                    }}\n                    onClick={(e) => e.stopPropagation()}\n                    onMouseDown={(e) => e.stopPropagation()}\n                    className=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\"\n                  />\n                </div>\n                <span className=\"min-w-[32px]\">{formatTime(duration)}</span>\n              </div>\n            </div>\n          );\n        })()}\n\n        {/* Bottom Gradient Fade */}\n        <div className=\"absolute bottom-0 left-0 right-0 h-48 bg-gradient-to-t from-black/60 via-black/80 to-transparent pointer-events-none\" />\n        \n        {/* Overlay Content */}\n        <div className=\"absolute inset-0 flex pointer-events-none\">\n          {/* Left side - User info and text */}\n          <div className={cn(\"flex-1 flex flex-col justify-end p-4  pointer-events-auto relative z-20\", showVideoProgress ? 'pb-14' : 'pb-4')}>\n            <div className=\"space-y-3 max-w-xs\">\n              <UserInfo\n                publicKey={video.publicKey}\n                profile={video.profile}\n                pictureSize=\"md\"\n                className=\"!text-white [&_span]:!text-white\"\n                usernameClassName=\"!text-white font-semibold\"\n              >\n                <Timestamp timestamp={video.timestamp} className=\"text-white/40 text-sm inline-block w-fit\" />\n              </UserInfo>\n              {video.text && (\n                <PostText\n                  text={video.text}\n                  className=\"!text-white text-sm [&_p]:!text-white [&_a]:!text-white\"\n                  lineClamp={3}\n                />\n              )}\n            </div>\n          </div>\n\n          {/* Right side - Engagement */}\n          {showEngagement && (\n            <div className=\"flex flex-col justify-end items-center p-4 space-y-4 pointer-events-auto relative z-20\">\n              <PostEngagement\n                variant=\"like\"\n                count={video.engagement.likes}\n                active={video.isLiked}\n                onClick={() => onLike?.(video.id)}\n                layout=\"column\"\n                size=\"md\"\n                className=\"text-white\"\n              />\n              <PostEngagement\n                variant=\"comment\"\n                count={video.engagement.comments}\n                onClick={() => onComment?.(video.id)}\n                layout=\"column\"\n                size=\"md\"\n                className=\"text-white\"\n              />\n              <PostEngagement\n                variant=\"repost\"\n                count={video.engagement.reposts}\n                active={video.isReposted}\n                onClick={() => onRepost?.(video.id)}\n                layout=\"column\"\n                size=\"md\"\n                className=\"text-white\"\n              />\n              <PostEngagement\n                variant=\"diamond\"\n                count={video.engagement.diamonds}\n                value={video.engagement.diamondValue}\n                onClick={() => onDiamond?.(video.id)}\n                layout=\"column\"\n                size=\"md\"\n                className=\"text-white\"\n              />\n              <PostEngagement\n                variant=\"view\"\n                count={video.engagement.views}\n                layout=\"column\"\n                size=\"md\"\n                className=\"text-white\"\n              />\n            </div>\n          )}\n        </div>\n\n\n\n        {/* Play/Pause Icon Overlay */}\n        {(isPaused || showIcon) && (\n          <div className=\"absolute inset-0 flex items-center justify-center pointer-events-none z-30\">\n            <div className=\"bg-black/50 rounded-full p-4 backdrop-blur-sm\">\n              {isPaused ? (\n                <Play className=\"h-12 w-12 text-white fill-white\" />\n              ) : (\n                <Pause className=\"h-12 w-12 text-white\" />\n              )}\n            </div>\n          </div>\n        )}\n    </div>\n    );\n  };\n\n  if (variant === 'single') {\n    return (\n      <div className={cn('w-full aspect-[9/16] max-w-sm mx-auto bg-black rounded-xl overflow-hidden relative', className)}>\n        {renderVideo(currentVideo, 0)}\n      </div>\n    );\n  }\n\n  // Full height variant\n  if (variant === 'full-height') {\n    return (\n      <div className={cn('relative w-full h-screen overflow-hidden bg-black', className)}>\n        <div\n          ref={containerRef}\n          className=\"h-full overflow-y-auto overflow-x-hidden snap-y snap-mandatory scrollbar-hide\"\n          onWheel={handleScroll}\n          style={{ scrollBehavior: 'smooth' }}\n        >\n          <div className=\"flex flex-col\">\n            {videos.map((video, index) => (\n              <div key={video.id} className=\"snap-start w-full h-screen\">\n                {renderVideo(video, index)}\n              </div>\n            ))}\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className={cn('flex items-center gap-4', className)}>\n      {/* Video Container */}\n      <div className=\"relative w-full aspect-[9/16] overflow-hidden rounded-xl bg-black max-w-sm mx-auto\">\n        <div\n          ref={containerRef}\n          className=\"h-full overflow-y-auto overflow-x-hidden snap-y snap-mandatory scrollbar-hide\"\n          onWheel={handleScroll}\n          style={{ scrollBehavior: 'smooth' }}\n        >\n          <div className=\"flex flex-col\">\n            {videos.map((video, index) => (\n              <div key={video.id} className=\"snap-start w-full aspect-[9/16]\">\n                {renderVideo(video, index)}\n              </div>\n            ))}\n          </div>\n        </div>\n      </div>\n\n      {/* Navigation Arrows */}\n      {variant === 'carousel-with-arrows' && videos.length > 1 && (\n        <div className=\"flex flex-col space-y-2\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            onClick={navigateUp}\n            disabled={currentIndex === 0}\n            className=\"bg-black/10 text-foreground hover:bg-black/20 disabled:opacity-30 border\"\n          >\n            <ChevronUp className=\"h-4 w-4\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            onClick={navigateDown}\n            disabled={currentIndex === videos.length - 1}\n            className=\"bg-black/10 text-foreground hover:bg-black/20 disabled:opacity-30 border\"\n          >\n            <ChevronDown className=\"h-4 w-4\" />\n          </Button>\n        </div>\n      )}\n    </div>\n  );\n} ",
      "type": "registry:component",
      "target": "src/components/deso-ui/video-reel.tsx"
    }
  ]
}