Files
zeekay e0ec477433 feat: full docs modernization — zero 404s, 585 pages, dep updates
- Enable all 15 finance component docs (from .disabled → .mdx)
- Create stock-market.mdx, 8 chart docs, 8 block docs, 2 framework docs
- Add direction.tsx component (RTL/LTR support from upstream shadcn)
- Add xs/icon-xs button sizes, data-variant/data-size attributes
- Fix build-registry.mts to handle default exports in namespace re-exports
- Add default props to order-entry, positions-list, orders-history, trading-panel
- Fix 18 navigation stubs → re-export from root implementations
- Add 44 orphaned MDX files to nav (installation, dark-mode, desktop, guides, etc)
- Migrate 6 framer-motion imports → motion/react
- Bump React 19.2.0→19.2.4, Next.js 16.0.1→16.1.6, motion→12.34.0
- Align lucide-react, sonner v2, tailwind-merge v3, postcss-selector-parser v7
- Remove framer-motion dep (superseded by motion)
- Fix @types/react-dom, TypeScript versions across workspaces
2026-03-03 07:34:51 -08:00

15 lines
23 KiB
JSON

{
"name": "youtube-pip-player",
"description": "YouTube embed with scroll-based Picture-in-Picture, draggable window, and TV effects",
"dependencies": [
"lucide-react",
"class-variance-authority"
],
"files": [
{
"name": "youtube-pip-player.tsx",
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport {\n Maximize,\n Maximize2,\n Minimize2,\n Play,\n Volume2,\n VolumeX,\n X,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Types\nexport interface YouTubePipPlayerProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onPlay\" | \"onPause\"> {\n /** YouTube video ID or array of IDs for rotation */\n videoId: string | string[]\n /** Auto-rotate videos every N seconds (default: 30) */\n rotationInterval?: number\n /** Enable scroll-based PIP mode */\n enablePip?: boolean\n /** Initial muted state (default: true) */\n defaultMuted?: boolean\n /** Show live indicator */\n showLiveIndicator?: boolean\n /** Enable TV-style visual effects */\n enableTvEffects?: boolean\n /** PIP position (default: bottom-right) */\n pipPosition?: \"bottom-right\" | \"bottom-left\" | \"top-right\" | \"top-left\"\n /** Callbacks */\n onPlay?: () => void\n onPause?: () => void\n onPipEnter?: () => void\n onPipExit?: () => void\n onMuteChange?: (muted: boolean) => void\n}\n\nconst pipPositionStyles = {\n \"bottom-right\": {\n defaultX: (w: number, pipW: number) => w - pipW - 24,\n defaultY: (h: number, pipH: number) => h - pipH - 24,\n },\n \"bottom-left\": {\n defaultX: () => 24,\n defaultY: (h: number, pipH: number) => h - pipH - 24,\n },\n \"top-right\": {\n defaultX: (w: number, pipW: number) => w - pipW - 24,\n defaultY: () => 24,\n },\n \"top-left\": { defaultX: () => 24, defaultY: () => 24 },\n}\n\nconst youtubePipPlayerVariants = cva(\n \"relative bg-black rounded-[30px] overflow-hidden\",\n {\n variants: {\n size: {\n sm: \"aspect-video max-w-md\",\n md: \"aspect-video max-w-2xl\",\n lg: \"aspect-video max-w-4xl\",\n full: \"w-full aspect-video\",\n hero: \"w-full max-h-[calc(100vh-160px)]\",\n },\n },\n defaultVariants: {\n size: \"hero\",\n },\n }\n)\n\nexport interface YouTubePipPlayerVariantProps\n extends VariantProps<typeof youtubePipPlayerVariants> {}\n\nconst YouTubePipPlayer = React.forwardRef<\n HTMLDivElement,\n YouTubePipPlayerProps & YouTubePipPlayerVariantProps\n>(\n (\n {\n className,\n size,\n videoId,\n rotationInterval = 30000,\n enablePip = true,\n defaultMuted = true,\n showLiveIndicator = true,\n enableTvEffects = true,\n pipPosition = \"bottom-right\",\n onPlay,\n onPause,\n onPipEnter,\n onPipExit,\n onMuteChange,\n ...props\n },\n ref\n ) => {\n // Normalize videoIds to array\n const videoIds = React.useMemo(\n () => (Array.isArray(videoId) ? videoId : [videoId]),\n [videoId]\n )\n\n // State\n const [currentVideoIndex, setCurrentVideoIndex] = React.useState(0)\n const [isMuted, setIsMuted] = React.useState(defaultMuted)\n const [isPip, setIsPip] = React.useState(false)\n const [showPip, setShowPip] = React.useState(true)\n const [pipSize, setPipSize] = React.useState<\n \"normal\" | \"double\" | \"fullscreen\"\n >(\"normal\")\n const [isMobile, setIsMobile] = React.useState(false)\n const [isHovered, setIsHovered] = React.useState(false)\n const [isPipHovered, setIsPipHovered] = React.useState(false)\n const [showStatic, setShowStatic] = React.useState(false)\n\n // Dragging state\n const [pipPos, setPipPos] = React.useState({ x: 0, y: 0 })\n const [isDragging, setIsDragging] = React.useState(false)\n const [dragStart, setDragStart] = React.useState({ x: 0, y: 0 })\n const [hasDragged, setHasDragged] = React.useState(false)\n\n // Refs\n const containerRef = React.useRef<HTMLDivElement>(null)\n const pipContainerRef = React.useRef<HTMLDivElement>(null)\n\n // Constants\n const PIP_WIDTH_BASE = 384\n const PIP_MARGIN = 24\n\n // Calculate current PIP width\n const getCurrentPipWidth = React.useCallback(() => {\n return pipSize === \"double\" ? PIP_WIDTH_BASE * 2 : PIP_WIDTH_BASE\n }, [pipSize])\n\n // Get YouTube embed URL\n const getYouTubeEmbedUrl = React.useCallback(\n (forPip = false) => {\n const currentId = videoIds[currentVideoIndex]\n const muteParam =\n forPip && isPip ? (isMuted ? \"1\" : \"0\") : isMuted ? \"1\" : \"0\"\n return `https://www.youtube.com/embed/${currentId}?autoplay=1&mute=${muteParam}&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1`\n },\n [videoIds, currentVideoIndex, isMuted, isPip]\n )\n\n // Mobile detection\n React.useEffect(() => {\n const checkMobile = () => setIsMobile(window.innerWidth < 768)\n checkMobile()\n window.addEventListener(\"resize\", checkMobile)\n return () => window.removeEventListener(\"resize\", checkMobile)\n }, [])\n\n // Video rotation\n React.useEffect(() => {\n if (videoIds.length <= 1) return\n const interval = setInterval(() => {\n setCurrentVideoIndex((prev) => (prev + 1) % videoIds.length)\n }, rotationInterval)\n return () => clearInterval(interval)\n }, [videoIds.length, rotationInterval])\n\n // TV static effect\n React.useEffect(() => {\n if (!enableTvEffects) return\n const interval = setInterval(() => {\n setShowStatic(true)\n setTimeout(() => setShowStatic(false), 200)\n }, 30000)\n return () => clearInterval(interval)\n }, [enableTvEffects])\n\n // Scroll-based PIP activation\n React.useEffect(() => {\n if (!enablePip) return\n\n const handleScroll = () => {\n if (!containerRef.current) return\n const rect = containerRef.current.getBoundingClientRect()\n const shouldShowPip = rect.bottom < 100\n const wasInPip = isPip\n\n setIsPip(shouldShowPip)\n\n if (shouldShowPip !== wasInPip) {\n if (shouldShowPip) {\n onPipEnter?.()\n } else {\n onPipExit?.()\n }\n }\n }\n\n window.addEventListener(\"scroll\", handleScroll)\n return () => window.removeEventListener(\"scroll\", handleScroll)\n }, [enablePip, isPip, onPipEnter, onPipExit])\n\n // Initialize PIP position\n React.useEffect(() => {\n if (typeof window === \"undefined\") return\n const pipWidth = getCurrentPipWidth()\n const pipHeight = (pipWidth * 9) / 16\n const pos = pipPositionStyles[pipPosition]\n setPipPos({\n x: pos.defaultX(window.innerWidth, pipWidth),\n y: pos.defaultY(window.innerHeight, pipHeight),\n })\n }, [getCurrentPipWidth, pipPosition])\n\n // Update position when size changes\n React.useEffect(() => {\n if (pipSize === \"fullscreen\" || typeof window === \"undefined\") return\n const pipWidth = getCurrentPipWidth()\n const pipHeight = (pipWidth * 9) / 16\n const pos = pipPositionStyles[pipPosition]\n setPipPos({\n x: pos.defaultX(window.innerWidth, pipWidth),\n y: pos.defaultY(window.innerHeight, pipHeight),\n })\n }, [pipSize, getCurrentPipWidth, pipPosition])\n\n // Dragging handlers\n const handleMouseDown = React.useCallback(\n (e: React.MouseEvent) => {\n if (pipSize === \"fullscreen\") return\n setIsDragging(true)\n setHasDragged(false)\n setDragStart({\n x: e.clientX - pipPos.x,\n y: e.clientY - pipPos.y,\n })\n },\n [pipSize, pipPos]\n )\n\n const handleMouseMove = React.useCallback(\n (e: MouseEvent) => {\n if (!isDragging || pipSize === \"fullscreen\") return\n\n const newX = e.clientX - dragStart.x\n const newY = e.clientY - dragStart.y\n\n const distanceMoved = Math.sqrt(\n Math.pow(newX - pipPos.x, 2) + Math.pow(newY - pipPos.y, 2)\n )\n if (distanceMoved > 5) setHasDragged(true)\n\n const pipWidth = getCurrentPipWidth()\n const maxX = window.innerWidth - pipWidth - PIP_MARGIN\n const maxY = window.innerHeight - (pipWidth * 9) / 16 - PIP_MARGIN\n\n setPipPos({\n x: Math.max(PIP_MARGIN, Math.min(newX, maxX)),\n y: Math.max(PIP_MARGIN, Math.min(newY, maxY)),\n })\n },\n [isDragging, pipSize, dragStart, pipPos, getCurrentPipWidth]\n )\n\n const handleMouseUp = React.useCallback(() => {\n if (isDragging && hasDragged) {\n // Snap to corner\n const pipWidth = getCurrentPipWidth()\n const pipHeight = (pipWidth * 9) / 16\n const pos = pipPositionStyles[pipPosition]\n setPipPos({\n x: pos.defaultX(window.innerWidth, pipWidth),\n y: pos.defaultY(window.innerHeight, pipHeight),\n })\n }\n setIsDragging(false)\n setHasDragged(false)\n }, [isDragging, hasDragged, getCurrentPipWidth, pipPosition])\n\n // Mouse event listeners for dragging\n React.useEffect(() => {\n if (!isDragging) return\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mouseup\", handleMouseUp)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mouseup\", handleMouseUp)\n }\n }, [isDragging, handleMouseMove, handleMouseUp])\n\n // Toggle mute\n const toggleMute = React.useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation()\n const newMuted = !isMuted\n setIsMuted(newMuted)\n onMuteChange?.(newMuted)\n },\n [isMuted, onMuteChange]\n )\n\n // Cycle PIP size\n const cyclePipSize = React.useCallback((e: React.MouseEvent) => {\n e.stopPropagation()\n setPipSize((prev) => {\n if (prev === \"normal\") return \"double\"\n if (prev === \"double\") return \"fullscreen\"\n return \"normal\"\n })\n }, [])\n\n // Close PIP\n const closePip = React.useCallback((e: React.MouseEvent) => {\n e.stopPropagation()\n setShowPip(false)\n }, [])\n\n // Request fullscreen for hero video\n const requestFullscreen = React.useCallback((e: React.MouseEvent) => {\n e.stopPropagation()\n if (!containerRef.current) return\n if (document.fullscreenElement) {\n document.exitFullscreen()\n } else {\n containerRef.current.requestFullscreen()\n }\n }, [])\n\n return (\n <>\n {/* Hero Video Container */}\n <div\n ref={containerRef}\n className={cn(\n youtubePipPlayerVariants({ size }),\n \"group transition-all duration-500\",\n className\n )}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n {...props}\n >\n {/* TV Effects - Glow */}\n {enableTvEffects && (\n <>\n <div className=\"absolute -inset-24 bg-gradient-radial from-white/40 via-white/20 to-transparent blur-[100px] opacity-80 pointer-events-none\" />\n <div className=\"absolute -inset-12 bg-gradient-radial from-white/30 via-white/15 to-transparent blur-[60px] opacity-70 pointer-events-none\" />\n <div className=\"absolute -inset-2 rounded-[30px] bg-gradient-to-r from-white/80 via-white/60 to-white/80 opacity-100 blur-2xl animate-pulse pointer-events-none\" />\n <div className=\"absolute inset-0 rounded-[30px] border-2 border-white/30 shadow-[0_0_60px_rgba(255,255,255,0.5)] pointer-events-none\" />\n </>\n )}\n\n {/* Inner frame glass effect */}\n <div className=\"absolute inset-0 pointer-events-none z-10\">\n <div\n className=\"absolute inset-0 rounded-[30px] border-2 border-white/10 bg-gradient-to-b from-white/5 to-transparent\"\n style={{\n boxShadow:\n \"inset 0 2px 20px rgba(255,255,255,0.1), inset 0 -2px 20px rgba(0,0,0,0.5)\",\n }}\n />\n </div>\n\n {/* Cinematic Vignette */}\n {enableTvEffects && (\n <div\n className=\"absolute inset-0 z-20 pointer-events-none\"\n style={{ boxShadow: \"inset 0 0 120px 60px rgba(0,0,0,0.8)\" }}\n />\n )}\n\n {/* Static Effect */}\n {showStatic && enableTvEffects && (\n <div className=\"absolute inset-0 z-40 pointer-events-none\">\n <div className=\"h-full w-full bg-white/3 animate-pulse\" />\n <div\n className=\"absolute inset-0 opacity-20\"\n style={{\n backgroundImage: `url(\"data:image/svg+xml,%3Csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' /%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.4'/%3E%3C/svg%3E\")`,\n }}\n />\n </div>\n )}\n\n {/* YouTube Video */}\n <iframe\n className=\"absolute inset-0 w-full h-full\"\n src={getYouTubeEmbedUrl()}\n title=\"Video\"\n frameBorder=\"0\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n />\n\n {/* Film Grain */}\n {enableTvEffects && (\n <div className=\"absolute inset-0 pointer-events-none opacity-[0.02] z-10\">\n <div\n className=\"h-full w-full\"\n style={{\n backgroundImage:\n \"repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,255,255,0.05) 2px, rgba(255,255,255,0.05) 4px)\",\n }}\n />\n </div>\n )}\n\n {/* Controls - Fade in on hover */}\n <div\n className={cn(\n \"absolute bottom-6 left-6 flex items-center gap-3 z-30 transition-opacity duration-300\",\n isHovered ? \"opacity-100\" : \"opacity-0\"\n )}\n >\n <button\n onClick={toggleMute}\n className=\"flex items-center gap-2 bg-black/80 backdrop-blur-md px-4 py-2 rounded-full hover:bg-black transition-all duration-300 border border-white/10\"\n >\n {isMuted ? (\n <VolumeX className=\"h-4 w-4 text-white\" />\n ) : (\n <Volume2 className=\"h-4 w-4 text-white\" />\n )}\n <span className=\"text-xs text-white/90 font-medium tracking-wider uppercase\">\n {isMuted ? \"Unmute\" : \"Mute\"}\n </span>\n </button>\n\n <button\n onClick={requestFullscreen}\n className=\"flex items-center gap-2 bg-black/80 backdrop-blur-md px-4 py-2 rounded-full hover:bg-black transition-all duration-300 border border-white/10\"\n >\n <Maximize className=\"h-4 w-4 text-white\" />\n <span className=\"text-xs text-white/90 font-medium tracking-wider uppercase\">\n Fullscreen\n </span>\n </button>\n </div>\n\n {/* Live Indicator */}\n {showLiveIndicator && (\n <div className=\"absolute top-6 left-6 flex items-center gap-2 bg-black/80 backdrop-blur-md px-4 py-2 rounded-full border border-white/10 z-30\">\n <div className=\"w-2 h-2 bg-red-600 rounded-full animate-pulse shadow-[0_0_10px_rgba(220,38,38,0.8)]\" />\n <span className=\"text-xs text-white/90 font-medium tracking-widest uppercase\">\n Live\n </span>\n </div>\n )}\n </div>\n\n {/* PIP Window */}\n {enablePip && isPip && showPip && !isMobile && (\n <div\n ref={pipContainerRef}\n className={cn(\n \"fixed z-50 transition-all ease-out group\",\n pipSize === \"fullscreen\"\n ? \"inset-0 bg-black/98 backdrop-blur-xl duration-500\"\n : isDragging\n ? \"duration-75\"\n : \"duration-300\",\n isDragging\n ? \"cursor-grabbing scale-105 shadow-2xl\"\n : pipSize === \"fullscreen\"\n ? \"\"\n : \"cursor-grab hover:scale-105\"\n )}\n style={\n pipSize !== \"fullscreen\"\n ? {\n left: `${pipPos.x}px`,\n top: `${pipPos.y}px`,\n filter: isDragging\n ? \"brightness(1.1) drop-shadow(0 20px 40px rgba(0,0,0,0.5))\"\n : \"brightness(1)\",\n }\n : {}\n }\n onMouseDown={handleMouseDown}\n onMouseEnter={() => setIsPipHovered(true)}\n onMouseLeave={() => setIsPipHovered(false)}\n >\n <div\n className={cn(\n \"relative bg-black overflow-hidden transition-all duration-500\",\n pipSize === \"fullscreen\"\n ? \"w-full h-full\"\n : pipSize === \"double\"\n ? \"aspect-video rounded-2xl\"\n : \"w-96 aspect-video rounded-2xl\"\n )}\n style={pipSize === \"double\" ? { width: \"768px\" } : {}}\n >\n {/* PIP TV Effects */}\n {enableTvEffects && pipSize !== \"fullscreen\" && (\n <>\n <div className=\"absolute -inset-12 rounded-full bg-gradient-radial from-white/30 via-white/10 to-transparent blur-3xl opacity-60 pointer-events-none\" />\n <div className=\"absolute -inset-1 rounded-2xl bg-gradient-to-r from-white/40 via-white/20 to-white/40 opacity-75 blur-xl animate-pulse pointer-events-none\" />\n <div className=\"absolute inset-0 pointer-events-none z-10\">\n <div\n className=\"absolute inset-0 rounded-2xl border-2 border-white/10 bg-gradient-to-b from-white/5 to-transparent\"\n style={{\n boxShadow:\n \"inset 0 2px 20px rgba(255,255,255,0.1), inset 0 -2px 20px rgba(0,0,0,0.5)\",\n }}\n />\n </div>\n <div className=\"absolute inset-0 pointer-events-none z-10 opacity-10\">\n <div\n className=\"h-full w-full animate-scan\"\n style={{\n backgroundImage:\n \"repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,255,255,0.03) 2px, rgba(255,255,255,0.03) 4px)\",\n }}\n />\n </div>\n </>\n )}\n\n {/* PIP YouTube Video */}\n <div className=\"absolute inset-0 overflow-hidden\">\n <iframe\n className={cn(\n \"absolute top-0 left-0 w-full h-full object-cover\",\n pipSize !== \"fullscreen\" && \"rounded-2xl\"\n )}\n style={{ aspectRatio: \"16 / 9\", pointerEvents: \"none\" }}\n src={getYouTubeEmbedUrl(true)}\n title=\"Video PiP\"\n frameBorder=\"0\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n />\n <div className=\"absolute inset-0 z-5\" />\n </div>\n\n {/* PIP Controls */}\n <div\n className={cn(\n \"absolute flex items-center gap-2 z-30 transition-opacity duration-300\",\n pipSize === \"fullscreen\"\n ? \"bottom-10 right-10\"\n : \"bottom-3 right-3\",\n isPipHovered ? \"opacity-100\" : \"opacity-0\"\n )}\n >\n <button\n onClick={toggleMute}\n className=\"relative p-2.5 bg-black/90 backdrop-blur-md rounded-full hover:bg-black transition-all duration-300 shadow-lg hover:scale-110 border border-white/10\"\n title={isMuted ? \"Unmute\" : \"Mute\"}\n >\n {isMuted ? (\n <VolumeX className=\"h-4 w-4 text-white\" />\n ) : (\n <Volume2 className=\"h-4 w-4 text-white\" />\n )}\n </button>\n\n <button\n onClick={cyclePipSize}\n className=\"relative p-2.5 bg-black/90 backdrop-blur-md rounded-full hover:bg-black transition-all duration-300 shadow-lg hover:scale-110 border border-white/10\"\n title={\n pipSize === \"fullscreen\"\n ? \"Normal Size\"\n : pipSize === \"double\"\n ? \"Fullscreen\"\n : \"Double Size\"\n }\n >\n {pipSize === \"fullscreen\" ? (\n <Minimize2 className=\"h-4 w-4 text-white\" />\n ) : (\n <Maximize2 className=\"h-4 w-4 text-white\" />\n )}\n </button>\n\n <button\n onClick={closePip}\n className=\"relative p-2.5 bg-black/90 backdrop-blur-md rounded-full hover:bg-red-600 transition-all duration-300 shadow-lg hover:scale-110 border border-white/10\"\n title=\"Close\"\n >\n <X className=\"h-4 w-4 text-white\" />\n </button>\n </div>\n\n {/* PIP Live Indicator */}\n {showLiveIndicator && pipSize !== \"fullscreen\" && (\n <div className=\"absolute top-3 right-3 z-20\">\n <div className=\"flex items-center gap-2 bg-black/80 backdrop-blur-md px-3 py-1.5 rounded-full border border-white/10\">\n <div className=\"w-2 h-2 bg-red-600 rounded-full animate-pulse shadow-[0_0_10px_rgba(220,38,38,0.8)]\" />\n <span className=\"text-[10px] text-white/80 font-medium tracking-widest uppercase\">\n Live\n </span>\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n </>\n )\n }\n)\n\nYouTubePipPlayer.displayName = \"YouTubePipPlayer\"\n\nexport { YouTubePipPlayer, youtubePipPlayerVariants }\n"
}
],
"type": "components:ui"
}