@frameset/plex-player 2.0.5 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/utils/vast.ts","../src/hooks/useVast.ts","../src/utils/helpers.ts","../src/components/PlexVideoPlayer.tsx","../src/components/ProgressBar.tsx","../src/components/Icons.tsx","../src/components/ErrorDisplay.tsx","../src/hooks/usePlayer.ts","../src/hooks/useKeyboard.ts","../src/components/AdOverlay.tsx","../src/components/Loader.tsx","../src/components/SettingsMenu.tsx","../src/components/VolumeControl.tsx"],"sourcesContent":["// VAST Ads Parser for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { VastAdInfo, VastConfig } from '../types';\r\n\r\ninterface VastMediaFile {\r\n url: string;\r\n type: string;\r\n width: number;\r\n height: number;\r\n bitrate?: number;\r\n}\r\n\r\ninterface VastAd {\r\n id: string;\r\n title: string;\r\n description?: string;\r\n duration: number;\r\n skipOffset?: number;\r\n clickThrough?: string;\r\n clickTracking?: string[];\r\n impressionUrls: string[];\r\n mediaFiles: VastMediaFile[];\r\n trackingEvents: Record<string, string[]>;\r\n}\r\n\r\ninterface VastResponse {\r\n ads: VastAd[];\r\n error?: string;\r\n}\r\n\r\n/**\r\n * Parse VAST XML response\r\n */\r\nexport const parseVastXml = (xmlString: string): VastResponse => {\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(xmlString, 'text/xml');\r\n \r\n const parseError = doc.querySelector('parsererror');\r\n if (parseError) {\r\n return { ads: [], error: 'Failed to parse VAST XML' };\r\n }\r\n\r\n const ads: VastAd[] = [];\r\n const adElements = doc.querySelectorAll('Ad');\r\n\r\n adElements.forEach((adElement) => {\r\n const inLine = adElement.querySelector('InLine');\r\n if (!inLine) return;\r\n\r\n const id = adElement.getAttribute('id') || '';\r\n const title = inLine.querySelector('AdTitle')?.textContent || '';\r\n const description = inLine.querySelector('Description')?.textContent || undefined;\r\n\r\n // Parse creatives\r\n const creatives = inLine.querySelectorAll('Creative');\r\n let duration = 0;\r\n let skipOffset: number | undefined;\r\n let clickThrough: string | undefined;\r\n const clickTracking: string[] = [];\r\n const mediaFiles: VastMediaFile[] = [];\r\n const trackingEvents: Record<string, string[]> = {};\r\n\r\n creatives.forEach((creative) => {\r\n const linear = creative.querySelector('Linear');\r\n if (!linear) return;\r\n\r\n // Duration\r\n const durationStr = linear.querySelector('Duration')?.textContent;\r\n if (durationStr) {\r\n duration = parseVastDuration(durationStr);\r\n }\r\n\r\n // Skip offset\r\n const skipOffsetAttr = linear.getAttribute('skipoffset');\r\n if (skipOffsetAttr) {\r\n skipOffset = parseVastDuration(skipOffsetAttr);\r\n }\r\n\r\n // Media files\r\n const mediaFileElements = linear.querySelectorAll('MediaFile');\r\n mediaFileElements.forEach((mediaFile) => {\r\n const url = mediaFile.textContent?.trim();\r\n if (url) {\r\n mediaFiles.push({\r\n url,\r\n type: mediaFile.getAttribute('type') || 'video/mp4',\r\n width: parseInt(mediaFile.getAttribute('width') || '0', 10),\r\n height: parseInt(mediaFile.getAttribute('height') || '0', 10),\r\n bitrate: parseInt(mediaFile.getAttribute('bitrate') || '0', 10) || undefined,\r\n });\r\n }\r\n });\r\n\r\n // Video clicks\r\n const videoClicks = linear.querySelector('VideoClicks');\r\n if (videoClicks) {\r\n clickThrough = videoClicks.querySelector('ClickThrough')?.textContent?.trim();\r\n videoClicks.querySelectorAll('ClickTracking').forEach((ct) => {\r\n const url = ct.textContent?.trim();\r\n if (url) clickTracking.push(url);\r\n });\r\n }\r\n\r\n // Tracking events\r\n const trackingEventsEl = linear.querySelectorAll('TrackingEvents Tracking');\r\n trackingEventsEl.forEach((tracking) => {\r\n const event = tracking.getAttribute('event');\r\n const url = tracking.textContent?.trim();\r\n if (event && url) {\r\n if (!trackingEvents[event]) {\r\n trackingEvents[event] = [];\r\n }\r\n trackingEvents[event].push(url);\r\n }\r\n });\r\n });\r\n\r\n // Impression URLs\r\n const impressionUrls: string[] = [];\r\n inLine.querySelectorAll('Impression').forEach((impression) => {\r\n const url = impression.textContent?.trim();\r\n if (url) impressionUrls.push(url);\r\n });\r\n\r\n if (mediaFiles.length > 0) {\r\n ads.push({\r\n id,\r\n title,\r\n description,\r\n duration,\r\n skipOffset,\r\n clickThrough,\r\n clickTracking,\r\n impressionUrls,\r\n mediaFiles,\r\n trackingEvents,\r\n });\r\n }\r\n });\r\n\r\n return { ads };\r\n};\r\n\r\n/**\r\n * Parse VAST duration string (HH:MM:SS or HH:MM:SS.mmm)\r\n */\r\nconst parseVastDuration = (duration: string): number => {\r\n if (duration.includes('%')) {\r\n return -1; // Percentage-based, handle separately\r\n }\r\n\r\n const parts = duration.split(':');\r\n if (parts.length !== 3) return 0;\r\n\r\n const hours = parseInt(parts[0], 10);\r\n const minutes = parseInt(parts[1], 10);\r\n const seconds = parseFloat(parts[2]);\r\n\r\n return hours * 3600 + minutes * 60 + seconds;\r\n};\r\n\r\n/**\r\n * Fetch and parse VAST ad\r\n */\r\nexport const fetchVastAd = async (config: VastConfig): Promise<VastAd | null> => {\r\n try {\r\n const response = await fetch(config.url, {\r\n method: 'GET',\r\n headers: {\r\n 'Accept': 'application/xml',\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n console.error('VAST fetch failed:', response.status);\r\n return null;\r\n }\r\n\r\n const xmlString = await response.text();\r\n const result = parseVastXml(xmlString);\r\n\r\n if (result.error || result.ads.length === 0) {\r\n console.error('VAST parse error:', result.error || 'No ads found');\r\n return null;\r\n }\r\n\r\n const ad = result.ads[0];\r\n \r\n // Apply skip delay from config if not in VAST\r\n if (config.skipDelay !== undefined && ad.skipOffset === undefined) {\r\n ad.skipOffset = config.skipDelay;\r\n }\r\n\r\n return ad;\r\n } catch (error) {\r\n console.error('VAST fetch error:', error);\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Select best media file based on device capabilities\r\n */\r\nexport const selectBestMediaFile = (mediaFiles: VastMediaFile[]): VastMediaFile | null => {\r\n if (mediaFiles.length === 0) return null;\r\n\r\n const video = document.createElement('video');\r\n const supportedFiles = mediaFiles.filter((file) => {\r\n return video.canPlayType(file.type) !== '';\r\n });\r\n\r\n if (supportedFiles.length === 0) return null;\r\n\r\n // Sort by resolution (prefer higher) and bitrate\r\n supportedFiles.sort((a, b) => {\r\n const resA = a.width * a.height;\r\n const resB = b.width * b.height;\r\n if (resA !== resB) return resB - resA;\r\n return (b.bitrate || 0) - (a.bitrate || 0);\r\n });\r\n\r\n // On mobile, prefer lower resolution\r\n const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);\r\n if (isMobile && supportedFiles.length > 1) {\r\n return supportedFiles[supportedFiles.length - 1];\r\n }\r\n\r\n return supportedFiles[0];\r\n};\r\n\r\n/**\r\n * Fire tracking pixel\r\n */\r\nexport const fireTrackingPixel = (url: string): void => {\r\n const img = new Image();\r\n img.src = url;\r\n};\r\n\r\n/**\r\n * Fire multiple tracking pixels\r\n */\r\nexport const fireTrackingPixels = (urls: string[]): void => {\r\n urls.forEach(fireTrackingPixel);\r\n};\r\n\r\n/**\r\n * Convert VAST ad to player-friendly format\r\n */\r\nexport const convertToAdInfo = (ad: VastAd): VastAdInfo => {\r\n return {\r\n id: ad.id,\r\n title: ad.title,\r\n duration: ad.duration,\r\n skipOffset: ad.skipOffset,\r\n clickThrough: ad.clickThrough,\r\n };\r\n};\r\n\r\nexport type { VastAd, VastMediaFile, VastResponse };\r\n","// useVast Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useState, useCallback, useRef, useEffect } from 'react';\r\nimport { VastConfig, VastAdInfo } from '../types';\r\nimport {\r\n fetchVastAd,\r\n selectBestMediaFile,\r\n fireTrackingPixels,\r\n convertToAdInfo,\r\n VastAd,\r\n} from '../utils/vast';\r\n\r\ninterface UseVastOptions {\r\n vastConfig?: VastConfig | VastConfig[];\r\n videoRef: React.RefObject<HTMLVideoElement>;\r\n onAdStart?: (ad: VastAdInfo) => void;\r\n onAdEnd?: () => void;\r\n onAdSkip?: () => void;\r\n onAdError?: (error: Error) => void;\r\n}\r\n\r\ninterface UseVastReturn {\r\n isAdPlaying: boolean;\r\n currentAd: VastAdInfo | null;\r\n adTimeRemaining: number;\r\n canSkip: boolean;\r\n skipAd: () => void;\r\n handleAdClick: () => void;\r\n checkForAd: (currentTime: number, duration: number) => void;\r\n}\r\n\r\nexport const useVast = ({\r\n vastConfig,\r\n videoRef,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n}: UseVastOptions): UseVastReturn => {\r\n const [isAdPlaying, setIsAdPlaying] = useState(false);\r\n const [currentAd, setCurrentAd] = useState<VastAdInfo | null>(null);\r\n const [adTimeRemaining, setAdTimeRemaining] = useState(0);\r\n const [canSkip, setCanSkip] = useState(false);\r\n\r\n const currentVastAd = useRef<VastAd | null>(null);\r\n const originalSrc = useRef<string>('');\r\n const originalTime = useRef<number>(0);\r\n const playedPositions = useRef<Set<string>>(new Set());\r\n const adIntervalRef = useRef<number | null>(null);\r\n\r\n // Normalize config to array\r\n const vastConfigs = Array.isArray(vastConfig) ? vastConfig : vastConfig ? [vastConfig] : [];\r\n\r\n // Play ad\r\n const playAd = useCallback(\r\n async (config: VastConfig) => {\r\n const video = videoRef.current;\r\n if (!video || isAdPlaying) return;\r\n\r\n try {\r\n const ad = await fetchVastAd(config);\r\n if (!ad) {\r\n onAdError?.(new Error('Failed to fetch VAST ad'));\r\n return;\r\n }\r\n\r\n const mediaFile = selectBestMediaFile(ad.mediaFiles);\r\n if (!mediaFile) {\r\n onAdError?.(new Error('No compatible media file found'));\r\n return;\r\n }\r\n\r\n // Store original video state\r\n originalSrc.current = video.currentSrc;\r\n originalTime.current = video.currentTime;\r\n\r\n // Fire impression pixels\r\n fireTrackingPixels(ad.impressionUrls);\r\n\r\n // Set up ad\r\n currentVastAd.current = ad;\r\n setCurrentAd(convertToAdInfo(ad));\r\n setIsAdPlaying(true);\r\n setAdTimeRemaining(ad.duration);\r\n setCanSkip(ad.skipOffset === undefined || ad.skipOffset === 0);\r\n\r\n // Change video source to ad\r\n video.src = mediaFile.url;\r\n video.currentTime = 0;\r\n await video.play();\r\n\r\n // Fire start tracking\r\n if (ad.trackingEvents.start) {\r\n fireTrackingPixels(ad.trackingEvents.start);\r\n }\r\n\r\n onAdStart?.(convertToAdInfo(ad));\r\n\r\n // Start ad timer\r\n adIntervalRef.current = window.setInterval(() => {\r\n const remaining = Math.max(0, ad.duration - video.currentTime);\r\n setAdTimeRemaining(remaining);\r\n\r\n // Check if can skip\r\n if (ad.skipOffset && video.currentTime >= ad.skipOffset) {\r\n setCanSkip(true);\r\n }\r\n\r\n // Track quartiles\r\n const percent = (video.currentTime / ad.duration) * 100;\r\n if (percent >= 25 && ad.trackingEvents.firstQuartile) {\r\n fireTrackingPixels(ad.trackingEvents.firstQuartile);\r\n }\r\n if (percent >= 50 && ad.trackingEvents.midpoint) {\r\n fireTrackingPixels(ad.trackingEvents.midpoint);\r\n }\r\n if (percent >= 75 && ad.trackingEvents.thirdQuartile) {\r\n fireTrackingPixels(ad.trackingEvents.thirdQuartile);\r\n }\r\n }, 250);\r\n\r\n // Handle ad completion\r\n const handleAdEnd = () => {\r\n if (ad.trackingEvents.complete) {\r\n fireTrackingPixels(ad.trackingEvents.complete);\r\n }\r\n endAd();\r\n };\r\n\r\n video.addEventListener('ended', handleAdEnd, { once: true });\r\n } catch (error) {\r\n onAdError?.(error instanceof Error ? error : new Error('Ad playback failed'));\r\n endAd();\r\n }\r\n },\r\n [videoRef, isAdPlaying, onAdStart, onAdError]\r\n );\r\n\r\n // End ad and restore original video\r\n const endAd = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n // Clear interval\r\n if (adIntervalRef.current) {\r\n clearInterval(adIntervalRef.current);\r\n adIntervalRef.current = null;\r\n }\r\n\r\n // Restore original video\r\n if (originalSrc.current) {\r\n video.src = originalSrc.current;\r\n video.currentTime = originalTime.current;\r\n video.play().catch(() => {});\r\n }\r\n\r\n setIsAdPlaying(false);\r\n setCurrentAd(null);\r\n setAdTimeRemaining(0);\r\n setCanSkip(false);\r\n currentVastAd.current = null;\r\n\r\n onAdEnd?.();\r\n }, [videoRef, onAdEnd]);\r\n\r\n // Skip ad\r\n const skipAd = useCallback(() => {\r\n if (!canSkip || !currentVastAd.current) return;\r\n\r\n const ad = currentVastAd.current;\r\n if (ad.trackingEvents.skip) {\r\n fireTrackingPixels(ad.trackingEvents.skip);\r\n }\r\n\r\n onAdSkip?.();\r\n endAd();\r\n }, [canSkip, endAd, onAdSkip]);\r\n\r\n // Handle ad click\r\n const handleAdClick = useCallback(() => {\r\n if (!currentVastAd.current) return;\r\n\r\n const ad = currentVastAd.current;\r\n if (ad.clickThrough) {\r\n window.open(ad.clickThrough, '_blank');\r\n }\r\n if (ad.clickTracking) {\r\n fireTrackingPixels(ad.clickTracking);\r\n }\r\n }, []);\r\n\r\n // Check for ad at position\r\n const checkForAd = useCallback(\r\n (currentTime: number, duration: number) => {\r\n if (isAdPlaying || vastConfigs.length === 0) return;\r\n\r\n vastConfigs.forEach((config) => {\r\n const position = config.position || 'preroll';\r\n const key = `${position}-${config.midrollTime || 0}`;\r\n\r\n if (playedPositions.current.has(key)) return;\r\n\r\n let shouldPlay = false;\r\n\r\n switch (position) {\r\n case 'preroll':\r\n shouldPlay = currentTime === 0;\r\n break;\r\n case 'midroll':\r\n if (config.midrollTime && currentTime >= config.midrollTime) {\r\n shouldPlay = true;\r\n }\r\n break;\r\n case 'postroll':\r\n shouldPlay = currentTime >= duration - 0.5;\r\n break;\r\n }\r\n\r\n if (shouldPlay) {\r\n playedPositions.current.add(key);\r\n playAd(config);\r\n }\r\n });\r\n },\r\n [vastConfigs, isAdPlaying, playAd]\r\n );\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (adIntervalRef.current) {\r\n clearInterval(adIntervalRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n return {\r\n isAdPlaying,\r\n currentAd,\r\n adTimeRemaining,\r\n canSkip,\r\n skipAd,\r\n handleAdClick,\r\n checkForAd,\r\n };\r\n};\r\n\r\nexport default useVast;\r\n","// Utility functions for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\n/**\r\n * Format time in seconds to MM:SS or HH:MM:SS format\r\n */\r\nexport const formatTime = (seconds: number): string => {\r\n if (!isFinite(seconds) || isNaN(seconds)) return '0:00';\r\n\r\n const hrs = Math.floor(seconds / 3600);\r\n const mins = Math.floor((seconds % 3600) / 60);\r\n const secs = Math.floor(seconds % 60);\r\n\r\n if (hrs > 0) {\r\n return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\r\n }\r\n\r\n return `${mins}:${secs.toString().padStart(2, '0')}`;\r\n};\r\n\r\n/**\r\n * Parse time string to seconds\r\n */\r\nexport const parseTime = (time: string): number => {\r\n const parts = time.split(':').map(Number);\r\n if (parts.length === 3) {\r\n return parts[0] * 3600 + parts[1] * 60 + parts[2];\r\n }\r\n if (parts.length === 2) {\r\n return parts[0] * 60 + parts[1];\r\n }\r\n return parts[0] || 0;\r\n};\r\n\r\n/**\r\n * Calculate percentage\r\n */\r\nexport const percentage = (value: number, total: number): number => {\r\n if (total === 0) return 0;\r\n return Math.min(100, Math.max(0, (value / total) * 100));\r\n};\r\n\r\n/**\r\n * Clamp a value between min and max\r\n */\r\nexport const clamp = (value: number, min: number, max: number): number => {\r\n return Math.min(max, Math.max(min, value));\r\n};\r\n\r\n/**\r\n * Throttle function execution\r\n */\r\nexport const throttle = <T extends (...args: unknown[]) => unknown>(\r\n func: T,\r\n limit: number\r\n): ((...args: Parameters<T>) => void) => {\r\n let inThrottle = false;\r\n return (...args: Parameters<T>) => {\r\n if (!inThrottle) {\r\n func(...args);\r\n inThrottle = true;\r\n setTimeout(() => (inThrottle = false), limit);\r\n }\r\n };\r\n};\r\n\r\n/**\r\n * Debounce function execution\r\n */\r\nexport const debounce = <T extends (...args: unknown[]) => unknown>(\r\n func: T,\r\n wait: number\r\n): ((...args: Parameters<T>) => void) => {\r\n let timeout: ReturnType<typeof setTimeout> | null = null;\r\n return (...args: Parameters<T>) => {\r\n if (timeout) clearTimeout(timeout);\r\n timeout = setTimeout(() => func(...args), wait);\r\n };\r\n};\r\n\r\n/**\r\n * Check if fullscreen is supported\r\n */\r\nexport const isFullscreenSupported = (): boolean => {\r\n return !!(\r\n document.fullscreenEnabled ||\r\n (document as Document & { webkitFullscreenEnabled?: boolean }).webkitFullscreenEnabled ||\r\n (document as Document & { mozFullScreenEnabled?: boolean }).mozFullScreenEnabled ||\r\n (document as Document & { msFullscreenEnabled?: boolean }).msFullscreenEnabled\r\n );\r\n};\r\n\r\n/**\r\n * Check if Picture-in-Picture is supported\r\n */\r\nexport const isPipSupported = (): boolean => {\r\n return 'pictureInPictureEnabled' in document && document.pictureInPictureEnabled;\r\n};\r\n\r\n/**\r\n * Get current fullscreen element\r\n */\r\nexport const getFullscreenElement = (): Element | null => {\r\n return (\r\n document.fullscreenElement ||\r\n (document as Document & { webkitFullscreenElement?: Element }).webkitFullscreenElement ||\r\n (document as Document & { mozFullScreenElement?: Element }).mozFullScreenElement ||\r\n (document as Document & { msFullscreenElement?: Element }).msFullscreenElement ||\r\n null\r\n );\r\n};\r\n\r\n/**\r\n * Request fullscreen on element\r\n */\r\nexport const requestFullscreen = async (element: HTMLElement): Promise<void> => {\r\n if (element.requestFullscreen) {\r\n await element.requestFullscreen();\r\n } else if ((element as HTMLElement & { webkitRequestFullscreen?: () => Promise<void> }).webkitRequestFullscreen) {\r\n await (element as HTMLElement & { webkitRequestFullscreen: () => Promise<void> }).webkitRequestFullscreen();\r\n } else if ((element as HTMLElement & { mozRequestFullScreen?: () => Promise<void> }).mozRequestFullScreen) {\r\n await (element as HTMLElement & { mozRequestFullScreen: () => Promise<void> }).mozRequestFullScreen();\r\n } else if ((element as HTMLElement & { msRequestFullscreen?: () => Promise<void> }).msRequestFullscreen) {\r\n await (element as HTMLElement & { msRequestFullscreen: () => Promise<void> }).msRequestFullscreen();\r\n }\r\n};\r\n\r\n/**\r\n * Exit fullscreen\r\n */\r\nexport const exitFullscreen = async (): Promise<void> => {\r\n if (document.exitFullscreen) {\r\n await document.exitFullscreen();\r\n } else if ((document as Document & { webkitExitFullscreen?: () => Promise<void> }).webkitExitFullscreen) {\r\n await (document as Document & { webkitExitFullscreen: () => Promise<void> }).webkitExitFullscreen();\r\n } else if ((document as Document & { mozCancelFullScreen?: () => Promise<void> }).mozCancelFullScreen) {\r\n await (document as Document & { mozCancelFullScreen: () => Promise<void> }).mozCancelFullScreen();\r\n } else if ((document as Document & { msExitFullscreen?: () => Promise<void> }).msExitFullscreen) {\r\n await (document as Document & { msExitFullscreen: () => Promise<void> }).msExitFullscreen();\r\n }\r\n};\r\n\r\n/**\r\n * Detect video type from URL\r\n */\r\nexport const detectVideoType = (url: string): string => {\r\n const extension = url.split('?')[0].split('.').pop()?.toLowerCase();\r\n \r\n const mimeTypes: Record<string, string> = {\r\n mp4: 'video/mp4',\r\n webm: 'video/webm',\r\n ogg: 'video/ogg',\r\n ogv: 'video/ogg',\r\n m3u8: 'application/x-mpegURL',\r\n mpd: 'application/dash+xml',\r\n mov: 'video/quicktime',\r\n avi: 'video/x-msvideo',\r\n mkv: 'video/x-matroska',\r\n };\r\n\r\n return mimeTypes[extension || ''] || 'video/mp4';\r\n};\r\n\r\n/**\r\n * Check if HLS is natively supported\r\n */\r\nexport const isHlsNativelySupported = (): boolean => {\r\n const video = document.createElement('video');\r\n return video.canPlayType('application/vnd.apple.mpegurl') !== '';\r\n};\r\n\r\n/**\r\n * Generate unique ID\r\n */\r\nexport const generateId = (): string => {\r\n return `plex-${Math.random().toString(36).substring(2, 11)}`;\r\n};\r\n\r\n/**\r\n * Check if device is mobile\r\n */\r\nexport const isMobile = (): boolean => {\r\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\r\n navigator.userAgent\r\n );\r\n};\r\n\r\n/**\r\n * Check if device is touch-enabled\r\n */\r\nexport const isTouchDevice = (): boolean => {\r\n return 'ontouchstart' in window || navigator.maxTouchPoints > 0;\r\n};\r\n\r\n/**\r\n * Parse buffered time ranges\r\n */\r\nexport const getBufferedEnd = (video: HTMLVideoElement): number => {\r\n if (video.buffered.length === 0) return 0;\r\n \r\n const currentTime = video.currentTime;\r\n for (let i = 0; i < video.buffered.length; i++) {\r\n if (video.buffered.start(i) <= currentTime && video.buffered.end(i) >= currentTime) {\r\n return video.buffered.end(i);\r\n }\r\n }\r\n \r\n return video.buffered.end(video.buffered.length - 1);\r\n};\r\n\r\n/**\r\n * Check browser support for specific video format\r\n */\r\nexport const canPlayType = (type: string): boolean => {\r\n const video = document.createElement('video');\r\n const result = video.canPlayType(type);\r\n return result === 'probably' || result === 'maybe';\r\n};\r\n","// PlexVideo Player - Main Component with Tailwind CSS\r\n// © FRAMESET STUDIO\r\n\r\nimport React, {\r\n useRef,\r\n useState,\r\n useEffect,\r\n useCallback,\r\n forwardRef,\r\n useImperativeHandle,\r\n useMemo,\r\n} from 'react';\r\nimport { useVast } from '../hooks/useVast';\r\nimport { formatTime, throttle } from '../utils/helpers';\r\nimport type {\r\n PlexVideoPlayerProps,\r\n PlexVideoPlayerRef,\r\n VideoSource,\r\n} from '../types';\r\n\r\n// Icons as inline SVG components\r\nconst PlayIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n);\r\n\r\nconst PauseIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\r\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\r\n </svg>\r\n);\r\n\r\nconst VolumeHighIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\r\n </svg>\r\n);\r\n\r\nconst VolumeMuteIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\r\n </svg>\r\n);\r\n\r\nconst FullscreenIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\" />\r\n </svg>\r\n);\r\n\r\nconst FullscreenExitIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\" />\r\n </svg>\r\n);\r\n\r\nconst PipIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z\" />\r\n </svg>\r\n);\r\n\r\nconst SettingsIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z\" />\r\n </svg>\r\n);\r\n\r\nconst RepeatIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z\" />\r\n </svg>\r\n);\r\n\r\nconst CopyIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\" />\r\n </svg>\r\n);\r\n\r\nconst SpeedIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44zm-9.79 6.84a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z\" />\r\n </svg>\r\n);\r\n\r\nconst PlaylistIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z\" />\r\n </svg>\r\n);\r\n\r\n// Extended props with playlist support\r\nexport interface PlaylistItem {\r\n id: string;\r\n src: string;\r\n title: string;\r\n poster?: string;\r\n duration?: number;\r\n}\r\n\r\ninterface ExtendedPlayerProps extends PlexVideoPlayerProps {\r\n playlist?: PlaylistItem[];\r\n onPlaylistItemChange?: (index: number, item: PlaylistItem) => void;\r\n}\r\n\r\nexport const PlexVideoPlayer = forwardRef<PlexVideoPlayerRef, ExtendedPlayerProps>(\r\n (props, ref) => {\r\n const {\r\n src,\r\n poster,\r\n autoPlay = false,\r\n muted = false,\r\n loop = false,\r\n controls = true,\r\n width = '100%',\r\n className = '',\r\n style = {},\r\n pip = true,\r\n fullscreen = true,\r\n playbackSpeed = true,\r\n playbackSpeeds = [0.5, 0.75, 1, 1.25, 1.5, 2],\r\n volume: volumeEnabled = true,\r\n initialVolume = 1,\r\n textTracks = [],\r\n vast,\r\n accentColor = '#f5c518',\r\n controlsTimeout = 3000,\r\n doubleClickFullscreen = true,\r\n clickToPlay = true,\r\n preload = 'metadata',\r\n playlist = [],\r\n onPlay,\r\n onPause,\r\n onEnded,\r\n onTimeUpdate,\r\n onProgress,\r\n onVolumeChange,\r\n onFullscreenChange,\r\n onPipChange,\r\n onQualityChange,\r\n onError,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n onReady,\r\n onPlaylistItemChange,\r\n } = props;\r\n\r\n // Refs\r\n const videoRef = useRef<HTMLVideoElement>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const progressRef = useRef<HTMLDivElement>(null);\r\n const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null);\r\n const lastClickRef = useRef<number>(0);\r\n\r\n // State\r\n const [isPlaying, setIsPlaying] = useState(false);\r\n const [currentTime, setCurrentTime] = useState(0);\r\n const [duration, setDuration] = useState(0);\r\n const [buffered, setBuffered] = useState(0);\r\n const [volumeState, setVolumeState] = useState(muted ? 0 : initialVolume);\r\n const [isMuted, setIsMuted] = useState(muted);\r\n const [isFullscreen, setIsFullscreen] = useState(false);\r\n const [isPipActive, setIsPipActive] = useState(false);\r\n const [isLoading, setIsLoading] = useState(true);\r\n const [error, setError] = useState<MediaError | null>(null);\r\n const [showControls, setShowControls] = useState(true);\r\n const [playbackRate, setPlaybackRateState] = useState(1);\r\n const [isLoop, setIsLoop] = useState(loop);\r\n const [showSettings, setShowSettings] = useState(false);\r\n const [showPlaylist, setShowPlaylist] = useState(false);\r\n const [hoverTime, setHoverTime] = useState<{ time: number; x: number } | null>(null);\r\n \r\n // Context menu state\r\n const [contextMenu, setContextMenu] = useState({ x: 0, y: 0, visible: false });\r\n \r\n // Playlist state\r\n const [currentPlaylistIndex, setCurrentPlaylistIndex] = useState(0);\r\n\r\n // Parse sources\r\n const sources = useMemo<VideoSource[]>(() => {\r\n if (typeof src === 'string') {\r\n return [{ src, type: 'video/mp4' }];\r\n }\r\n return src as VideoSource[];\r\n }, [src]);\r\n\r\n // Get current video source\r\n const currentVideoSrc = useMemo(() => {\r\n if (playlist.length > 0 && playlist[currentPlaylistIndex]) {\r\n return playlist[currentPlaylistIndex].src;\r\n }\r\n return sources[0]?.src || '';\r\n }, [playlist, currentPlaylistIndex, sources]);\r\n\r\n const currentPoster = useMemo(() => {\r\n if (playlist.length > 0 && playlist[currentPlaylistIndex]?.poster) {\r\n return playlist[currentPlaylistIndex].poster;\r\n }\r\n return poster;\r\n }, [playlist, currentPlaylistIndex, poster]);\r\n\r\n // VAST ads hook\r\n const {\r\n currentAd,\r\n isAdPlaying,\r\n adTimeRemaining,\r\n canSkip,\r\n skipAd,\r\n handleAdClick,\r\n checkForAd,\r\n } = useVast({\r\n vastConfig: vast,\r\n videoRef,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n });\r\n\r\n // Player control functions\r\n const play = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n try {\r\n await video.play();\r\n } catch (err) {\r\n console.error('Play error:', err);\r\n }\r\n }, []);\r\n\r\n const pause = useCallback(() => {\r\n videoRef.current?.pause();\r\n }, []);\r\n\r\n const stop = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.pause();\r\n video.currentTime = 0;\r\n }, []);\r\n\r\n const togglePlay = useCallback(() => {\r\n if (videoRef.current?.paused) {\r\n play();\r\n } else {\r\n pause();\r\n }\r\n }, [play, pause]);\r\n\r\n const seek = useCallback((time: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.currentTime = Math.max(0, Math.min(time, video.duration || 0));\r\n }, []);\r\n\r\n const setVolume = useCallback((value: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n const newVolume = Math.max(0, Math.min(1, value));\r\n video.volume = newVolume;\r\n video.muted = newVolume === 0;\r\n setVolumeState(newVolume);\r\n setIsMuted(newVolume === 0);\r\n }, []);\r\n\r\n const mute = useCallback(() => {\r\n if (videoRef.current) {\r\n videoRef.current.muted = true;\r\n setIsMuted(true);\r\n }\r\n }, []);\r\n\r\n const unmute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.muted = false;\r\n if (video.volume === 0) {\r\n video.volume = 0.5;\r\n setVolumeState(0.5);\r\n }\r\n setIsMuted(false);\r\n }, []);\r\n\r\n const toggleMute = useCallback(() => {\r\n if (videoRef.current?.muted) {\r\n unmute();\r\n } else {\r\n mute();\r\n }\r\n }, [mute, unmute]);\r\n\r\n const toggleLoop = useCallback(() => {\r\n if (videoRef.current) {\r\n videoRef.current.loop = !videoRef.current.loop;\r\n setIsLoop(videoRef.current.loop);\r\n }\r\n }, []);\r\n\r\n const enterFullscreen = useCallback(async () => {\r\n try {\r\n await containerRef.current?.requestFullscreen();\r\n setIsFullscreen(true);\r\n } catch (err) {\r\n console.error('Fullscreen error:', err);\r\n }\r\n }, []);\r\n\r\n const exitFullscreen = useCallback(async () => {\r\n try {\r\n if (document.fullscreenElement) {\r\n await document.exitFullscreen();\r\n setIsFullscreen(false);\r\n }\r\n } catch (err) {\r\n console.error('Exit fullscreen error:', err);\r\n }\r\n }, []);\r\n\r\n const toggleFullscreen = useCallback(async () => {\r\n if (document.fullscreenElement) {\r\n await exitFullscreen();\r\n } else {\r\n await enterFullscreen();\r\n }\r\n }, [enterFullscreen, exitFullscreen]);\r\n\r\n const enterPip = useCallback(async () => {\r\n try {\r\n await videoRef.current?.requestPictureInPicture();\r\n setIsPipActive(true);\r\n } catch (err) {\r\n console.error('PiP error:', err);\r\n }\r\n }, []);\r\n\r\n const exitPip = useCallback(async () => {\r\n try {\r\n if (document.pictureInPictureElement) {\r\n await document.exitPictureInPicture();\r\n setIsPipActive(false);\r\n }\r\n } catch (err) {\r\n console.error('Exit PiP error:', err);\r\n }\r\n }, []);\r\n\r\n const togglePip = useCallback(async () => {\r\n if (document.pictureInPictureElement) {\r\n await exitPip();\r\n } else {\r\n await enterPip();\r\n }\r\n }, [enterPip, exitPip]);\r\n\r\n const setPlaybackRate = useCallback((rate: number) => {\r\n if (videoRef.current) {\r\n videoRef.current.playbackRate = rate;\r\n setPlaybackRateState(rate);\r\n setShowSettings(false);\r\n }\r\n }, []);\r\n\r\n const setQuality = useCallback((qualityLabel: string) => {\r\n const source = sources.find(s => s.label === qualityLabel || s.quality === qualityLabel);\r\n if (source && videoRef.current) {\r\n const video = videoRef.current;\r\n const wasPlaying = !video.paused;\r\n const time = video.currentTime;\r\n video.src = source.src;\r\n video.currentTime = time;\r\n if (wasPlaying) video.play();\r\n onQualityChange?.(qualityLabel);\r\n }\r\n }, [sources, onQualityChange]);\r\n\r\n // Playlist functions\r\n const playNextInPlaylist = useCallback(() => {\r\n if (playlist.length === 0) return;\r\n const nextIndex = (currentPlaylistIndex + 1) % playlist.length;\r\n setCurrentPlaylistIndex(nextIndex);\r\n onPlaylistItemChange?.(nextIndex, playlist[nextIndex]);\r\n setTimeout(() => play(), 100);\r\n }, [currentPlaylistIndex, playlist, onPlaylistItemChange, play]);\r\n\r\n // Context menu handlers\r\n const handleContextMenu = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n const rect = containerRef.current?.getBoundingClientRect();\r\n if (rect) {\r\n setContextMenu({ \r\n x: e.clientX - rect.left, \r\n y: e.clientY - rect.top, \r\n visible: true \r\n });\r\n }\r\n }, []);\r\n\r\n const closeContextMenu = useCallback(() => {\r\n setContextMenu(prev => ({ ...prev, visible: false }));\r\n }, []);\r\n\r\n const copyVideoUrl = useCallback(() => {\r\n navigator.clipboard.writeText(currentVideoSrc);\r\n closeContextMenu();\r\n }, [currentVideoSrc, closeContextMenu]);\r\n\r\n // Progress bar click handler\r\n const handleProgressClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const percent = (e.clientX - rect.left) / rect.width;\r\n seek(percent * duration);\r\n }\r\n }, [duration, seek]);\r\n\r\n // Progress bar hover handler for time preview\r\n const handleProgressHover = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const percent = (e.clientX - rect.left) / rect.width;\r\n const time = percent * duration;\r\n setHoverTime({ time: Math.max(0, Math.min(duration, time)), x: e.clientX - rect.left });\r\n }\r\n }, [duration]);\r\n\r\n const handleProgressLeave = useCallback(() => {\r\n setHoverTime(null);\r\n }, []);\r\n\r\n // Hide controls logic\r\n const hideControls = useCallback(() => {\r\n if (isPlaying && !showSettings) {\r\n setShowControls(false);\r\n }\r\n }, [isPlaying, showSettings]);\r\n\r\n const showControlsTemporarily = useCallback(() => {\r\n setShowControls(true);\r\n if (controlsTimeoutRef.current) {\r\n clearTimeout(controlsTimeoutRef.current);\r\n }\r\n if (isPlaying) {\r\n controlsTimeoutRef.current = setTimeout(hideControls, controlsTimeout);\r\n }\r\n }, [isPlaying, controlsTimeout, hideControls]);\r\n\r\n const handleMouseMove = useMemo(\r\n () => throttle(showControlsTemporarily, 200),\r\n [showControlsTemporarily]\r\n );\r\n\r\n // Video click handler\r\n const handleVideoClick = useCallback(() => {\r\n closeContextMenu();\r\n if (!clickToPlay) return;\r\n \r\n const now = Date.now();\r\n if (doubleClickFullscreen && now - lastClickRef.current < 300) {\r\n toggleFullscreen();\r\n } else {\r\n togglePlay();\r\n }\r\n lastClickRef.current = now;\r\n }, [clickToPlay, doubleClickFullscreen, togglePlay, toggleFullscreen, closeContextMenu]);\r\n\r\n // Video event handlers\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const handlePlay = () => { setIsPlaying(true); onPlay?.(); };\r\n const handlePause = () => { setIsPlaying(false); onPause?.(); };\r\n const handleEnded = () => {\r\n setIsPlaying(false);\r\n onEnded?.();\r\n if (playlist.length > 0) playNextInPlaylist();\r\n };\r\n const handleTimeUpdate = () => {\r\n setCurrentTime(video.currentTime);\r\n onTimeUpdate?.(video.currentTime);\r\n checkForAd(video.currentTime, video.duration);\r\n };\r\n const handleDurationChange = () => setDuration(video.duration);\r\n const handleProgressEvent = () => {\r\n if (video.buffered.length > 0) {\r\n const bufferedEnd = video.buffered.end(video.buffered.length - 1);\r\n setBuffered((bufferedEnd / video.duration) * 100);\r\n onProgress?.((bufferedEnd / video.duration) * 100);\r\n }\r\n };\r\n const handleVolumeChangeEvent = () => {\r\n setVolumeState(video.volume);\r\n setIsMuted(video.muted);\r\n onVolumeChange?.(video.volume, video.muted);\r\n };\r\n const handleLoadedMetadata = () => {\r\n setDuration(video.duration);\r\n setIsLoading(false);\r\n onReady?.();\r\n };\r\n const handleWaiting = () => setIsLoading(true);\r\n const handlePlaying = () => setIsLoading(false);\r\n const handleCanPlay = () => setIsLoading(false);\r\n const handleErrorEvent = () => {\r\n setError(video.error);\r\n setIsLoading(false);\r\n onError?.(video.error);\r\n };\r\n\r\n video.addEventListener('play', handlePlay);\r\n video.addEventListener('pause', handlePause);\r\n video.addEventListener('ended', handleEnded);\r\n video.addEventListener('timeupdate', handleTimeUpdate);\r\n video.addEventListener('durationchange', handleDurationChange);\r\n video.addEventListener('progress', handleProgressEvent);\r\n video.addEventListener('volumechange', handleVolumeChangeEvent);\r\n video.addEventListener('loadedmetadata', handleLoadedMetadata);\r\n video.addEventListener('waiting', handleWaiting);\r\n video.addEventListener('playing', handlePlaying);\r\n video.addEventListener('canplay', handleCanPlay);\r\n video.addEventListener('error', handleErrorEvent);\r\n\r\n return () => {\r\n video.removeEventListener('play', handlePlay);\r\n video.removeEventListener('pause', handlePause);\r\n video.removeEventListener('ended', handleEnded);\r\n video.removeEventListener('timeupdate', handleTimeUpdate);\r\n video.removeEventListener('durationchange', handleDurationChange);\r\n video.removeEventListener('progress', handleProgressEvent);\r\n video.removeEventListener('volumechange', handleVolumeChangeEvent);\r\n video.removeEventListener('loadedmetadata', handleLoadedMetadata);\r\n video.removeEventListener('waiting', handleWaiting);\r\n video.removeEventListener('playing', handlePlaying);\r\n video.removeEventListener('canplay', handleCanPlay);\r\n video.removeEventListener('error', handleErrorEvent);\r\n };\r\n }, [onPlay, onPause, onEnded, onTimeUpdate, onProgress, onVolumeChange, onReady, onError, checkForAd, playlist.length, playNextInPlaylist]);\r\n\r\n // Fullscreen change listener\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!document.fullscreenElement;\r\n setIsFullscreen(isFs);\r\n onFullscreenChange?.(isFs);\r\n };\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n }, [onFullscreenChange]);\r\n\r\n // PiP change listener\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n const handleEnterPip = () => { setIsPipActive(true); onPipChange?.(true); };\r\n const handleLeavePip = () => { setIsPipActive(false); onPipChange?.(false); };\r\n video.addEventListener('enterpictureinpicture', handleEnterPip);\r\n video.addEventListener('leavepictureinpicture', handleLeavePip);\r\n return () => {\r\n video.removeEventListener('enterpictureinpicture', handleEnterPip);\r\n video.removeEventListener('leavepictureinpicture', handleLeavePip);\r\n };\r\n }, [onPipChange]);\r\n\r\n // Keyboard shortcuts\r\n useEffect(() => {\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n if (!containerRef.current?.contains(document.activeElement)) return;\r\n \r\n switch (e.key) {\r\n case ' ':\r\n case 'k':\r\n e.preventDefault();\r\n togglePlay();\r\n break;\r\n case 'm':\r\n e.preventDefault();\r\n toggleMute();\r\n break;\r\n case 'f':\r\n e.preventDefault();\r\n toggleFullscreen();\r\n break;\r\n case 'ArrowLeft':\r\n e.preventDefault();\r\n seek(currentTime - 10);\r\n break;\r\n case 'ArrowRight':\r\n e.preventDefault();\r\n seek(currentTime + 10);\r\n break;\r\n case 'ArrowUp':\r\n e.preventDefault();\r\n setVolume(volumeState + 0.1);\r\n break;\r\n case 'ArrowDown':\r\n e.preventDefault();\r\n setVolume(volumeState - 0.1);\r\n break;\r\n }\r\n };\r\n \r\n document.addEventListener('keydown', handleKeyDown);\r\n return () => document.removeEventListener('keydown', handleKeyDown);\r\n }, [togglePlay, toggleMute, toggleFullscreen, seek, setVolume, currentTime, volumeState]);\r\n\r\n // Cleanup\r\n useEffect(() => {\r\n return () => {\r\n if (controlsTimeoutRef.current) clearTimeout(controlsTimeoutRef.current);\r\n };\r\n }, []);\r\n\r\n // Close context menu on click outside\r\n useEffect(() => {\r\n const handleClick = () => closeContextMenu();\r\n if (contextMenu.visible) {\r\n document.addEventListener('click', handleClick);\r\n return () => document.removeEventListener('click', handleClick);\r\n }\r\n }, [contextMenu.visible, closeContextMenu]);\r\n\r\n // Imperative handle\r\n useImperativeHandle(ref, () => ({\r\n play, pause, stop, seek, setVolume, mute, unmute, toggleMute,\r\n enterFullscreen, exitFullscreen, toggleFullscreen,\r\n enterPip, exitPip, togglePip, setPlaybackRate, setQuality,\r\n getCurrentTime: () => videoRef.current?.currentTime || 0,\r\n getDuration: () => videoRef.current?.duration || 0,\r\n getVolume: () => videoRef.current?.volume || 0,\r\n isMuted: () => videoRef.current?.muted ?? true,\r\n isPlaying: () => !videoRef.current?.paused,\r\n isFullscreen: () => isFullscreen,\r\n isPip: () => isPipActive,\r\n getVideoElement: () => videoRef.current,\r\n }), [play, pause, stop, seek, setVolume, mute, unmute, toggleMute, enterFullscreen, exitFullscreen, toggleFullscreen, enterPip, exitPip, togglePip, setPlaybackRate, setQuality, isFullscreen, isPipActive]);\r\n\r\n const progressPercent = duration ? (currentTime / duration) * 100 : 0;\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ \r\n position: isFullscreen ? 'fixed' : 'relative',\r\n inset: isFullscreen ? 0 : undefined,\r\n zIndex: isFullscreen ? 9999 : undefined,\r\n width, \r\n aspectRatio: isFullscreen ? 'unset' : '16/9',\r\n height: isFullscreen ? '100%' : undefined,\r\n backgroundColor: '#000',\r\n overflow: 'hidden',\r\n userSelect: 'none',\r\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n ...style \r\n }}\r\n onMouseMove={handleMouseMove}\r\n onMouseLeave={() => isPlaying && setShowControls(false)}\r\n onContextMenu={handleContextMenu}\r\n tabIndex={0}\r\n >\r\n {/* Video Element */}\r\n <video\r\n ref={videoRef}\r\n style={{ width: '100%', height: '100%', objectFit: 'contain' }}\r\n src={currentVideoSrc}\r\n poster={currentPoster}\r\n autoPlay={autoPlay}\r\n muted={muted}\r\n loop={isLoop}\r\n preload={preload}\r\n playsInline\r\n onClick={handleVideoClick}\r\n >\r\n {textTracks.map((track, i) => (\r\n <track key={i} kind={track.kind} src={track.src} srcLang={track.srclang} label={track.label} default={track.default} />\r\n ))}\r\n </video>\r\n\r\n {/* Loading Spinner */}\r\n {isLoading && !error && (\r\n <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>\r\n <div \r\n style={{ \r\n width: 48, \r\n height: 48, \r\n border: '4px solid transparent', \r\n borderTopColor: accentColor, \r\n borderRadius: '50%',\r\n animation: 'spin 1s linear infinite',\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Keyframes for spinner */}\r\n <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>\r\n\r\n {/* Error Display */}\r\n {error && (\r\n <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.9)', color: '#fff' }}>\r\n <svg style={{ width: 64, height: 64, marginBottom: 16 }} fill={accentColor} viewBox=\"0 0 24 24\">\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" />\r\n </svg>\r\n <p style={{ fontSize: 18, marginBottom: 8 }}>Error loading video</p>\r\n <button \r\n onClick={() => { setError(null); videoRef.current?.load(); }}\r\n style={{ \r\n padding: '8px 24px', \r\n borderRadius: 4, \r\n border: 'none',\r\n backgroundColor: accentColor, \r\n color: '#000', \r\n fontWeight: 500,\r\n cursor: 'pointer',\r\n }}\r\n >\r\n Retry\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Ad Overlay */}\r\n {isAdPlaying && currentAd && (\r\n <div style={{ position: 'absolute', inset: 0, zIndex: 20 }}>\r\n <div style={{ position: 'absolute', top: 16, left: 16, display: 'flex', alignItems: 'center', gap: 12, padding: '8px 12px', background: 'rgba(0,0,0,0.7)', borderRadius: 4, color: '#fff', fontSize: 14 }}>\r\n <span style={{ padding: '2px 8px', borderRadius: 4, fontSize: 11, fontWeight: 700, backgroundColor: accentColor, color: '#000' }}>AD</span>\r\n <span>{formatTime(adTimeRemaining)} remaining</span>\r\n </div>\r\n <button\r\n onClick={canSkip ? skipAd : undefined}\r\n disabled={!canSkip}\r\n style={{ \r\n position: 'absolute', \r\n bottom: 80, \r\n right: 16, \r\n padding: '8px 20px', \r\n border: '1px solid #fff', \r\n borderRadius: 4, \r\n background: 'transparent',\r\n color: '#fff',\r\n cursor: canSkip ? 'pointer' : 'not-allowed',\r\n opacity: canSkip ? 1 : 0.5,\r\n }}\r\n >\r\n {canSkip ? 'Skip Ad' : `Skip in ${Math.ceil(adTimeRemaining)}s`}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Context Menu */}\r\n {contextMenu.visible && (\r\n <div \r\n style={{ \r\n position: 'absolute', \r\n left: contextMenu.x, \r\n top: contextMenu.y,\r\n zIndex: 50,\r\n minWidth: 200,\r\n background: 'rgba(26,26,26,0.95)',\r\n backdropFilter: 'blur(8px)',\r\n borderRadius: 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n overflow: 'hidden',\r\n }}\r\n onClick={e => e.stopPropagation()}\r\n >\r\n {/* Play/Pause */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { togglePlay(); closeContextMenu(); }}\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n <span style={{ flex: 1 }}>{isPlaying ? 'Pause' : 'Play'}</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>Space</span>\r\n </button>\r\n \r\n {/* Mute */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleMute(); closeContextMenu(); }}\r\n >\r\n {isMuted ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n <span style={{ flex: 1 }}>{isMuted ? 'Unmute' : 'Mute'}</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>M</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* Loop */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleLoop(); closeContextMenu(); }}\r\n >\r\n <RepeatIcon />\r\n <span style={{ flex: 1 }}>{isLoop ? 'Disable Loop' : 'Enable Loop'}</span>\r\n {isLoop && <span style={{ color: accentColor }}>✓</span>}\r\n </button>\r\n\r\n {/* Speed */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => {\r\n const speeds = [0.5, 0.75, 1, 1.25, 1.5, 2];\r\n const idx = speeds.indexOf(playbackRate);\r\n setPlaybackRate(speeds[(idx + 1) % speeds.length]);\r\n closeContextMenu();\r\n }}\r\n >\r\n <SpeedIcon />\r\n <span style={{ flex: 1 }}>Speed: {playbackRate}x</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* PiP */}\r\n {pip && document.pictureInPictureEnabled && (\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { togglePip(); closeContextMenu(); }}\r\n >\r\n <PipIcon />\r\n <span style={{ flex: 1 }}>Picture in Picture</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>P</span>\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleFullscreen(); closeContextMenu(); }}\r\n >\r\n <FullscreenIcon />\r\n <span style={{ flex: 1 }}>Fullscreen</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>F</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* Copy URL */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={copyVideoUrl}\r\n >\r\n <CopyIcon />\r\n <span style={{ flex: 1 }}>Copy Video URL</span>\r\n </button>\r\n\r\n {/* Footer */}\r\n <div style={{ padding: '8px 16px', fontSize: 10, color: 'rgba(255,255,255,0.4)', textAlign: 'center', borderTop: '1px solid rgba(255,255,255,0.1)' }}>\r\n PlexVideo Player © FRAMESET STUDIO\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Controls */}\r\n {controls && !isAdPlaying && (\r\n <div \r\n style={{\r\n position: 'absolute',\r\n bottom: 0,\r\n left: 0,\r\n right: 0,\r\n background: 'linear-gradient(to top, rgba(0,0,0,0.9) 0%, transparent 100%)',\r\n padding: 16,\r\n transition: 'opacity 0.3s',\r\n opacity: showControls ? 1 : 0,\r\n pointerEvents: showControls ? 'auto' : 'none',\r\n }}\r\n >\r\n {/* Progress Bar Container */}\r\n <div \r\n ref={progressRef}\r\n onClick={handleProgressClick}\r\n onMouseMove={handleProgressHover}\r\n onMouseLeave={handleProgressLeave}\r\n style={{\r\n position: 'relative',\r\n height: 8,\r\n background: 'rgba(255,255,255,0.3)',\r\n borderRadius: 4,\r\n marginBottom: 16,\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {/* Buffered bar */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n height: '100%',\r\n width: `${buffered}%`,\r\n background: 'rgba(255,255,255,0.5)',\r\n borderRadius: 4,\r\n }}\r\n />\r\n {/* Played bar */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n height: '100%',\r\n width: `${progressPercent}%`,\r\n background: accentColor,\r\n borderRadius: 4,\r\n }}\r\n />\r\n {/* Handle */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: `${progressPercent}%`,\r\n transform: 'translate(-50%, -50%)',\r\n width: 16,\r\n height: 16,\r\n borderRadius: '50%',\r\n background: accentColor,\r\n boxShadow: '0 2px 8px rgba(0,0,0,0.5)',\r\n }}\r\n />\r\n {/* Time Preview Tooltip */}\r\n {hoverTime && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n bottom: '100%',\r\n left: hoverTime.x,\r\n transform: 'translateX(-50%)',\r\n marginBottom: 8,\r\n padding: '4px 8px',\r\n background: 'rgba(0,0,0,0.9)',\r\n color: '#fff',\r\n fontSize: 12,\r\n borderRadius: 4,\r\n whiteSpace: 'nowrap',\r\n pointerEvents: 'none',\r\n }}\r\n >\r\n {formatTime(hoverTime.time)}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Control Buttons */}\r\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\r\n {/* Play/Pause */}\r\n <button \r\n onClick={togglePlay}\r\n aria-label={isPlaying ? 'Pause' : 'Play'}\r\n style={{\r\n width: 44,\r\n height: 44,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n borderRadius: '50%',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n </button>\r\n\r\n {/* Volume */}\r\n {volumeEnabled && (\r\n <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>\r\n <button \r\n onClick={toggleMute}\r\n aria-label={isMuted ? 'Unmute' : 'Mute'}\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {isMuted || volumeState === 0 ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n </button>\r\n <input\r\n type=\"range\"\r\n min=\"0\"\r\n max=\"1\"\r\n step=\"0.01\"\r\n value={isMuted ? 0 : volumeState}\r\n onChange={e => setVolume(parseFloat(e.target.value))}\r\n style={{ \r\n width: 70,\r\n height: 4,\r\n cursor: 'pointer',\r\n accentColor: accentColor,\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Time */}\r\n <div style={{ color: '#fff', fontSize: 13, fontFamily: 'monospace', marginLeft: 8 }}>\r\n {formatTime(currentTime)} / {formatTime(duration)}\r\n </div>\r\n\r\n {/* Spacer */}\r\n <div style={{ flex: 1 }} />\r\n\r\n {/* Playlist Button */}\r\n {playlist.length > 0 && (\r\n <div style={{ position: 'relative' }}>\r\n <button \r\n onClick={() => setShowPlaylist(!showPlaylist)}\r\n aria-label=\"Playlist\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: showPlaylist ? 'rgba(255,255,255,0.2)' : 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n <PlaylistIcon />\r\n </button>\r\n \r\n {/* Playlist Dropdown */}\r\n {showPlaylist && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n bottom: 48,\r\n right: 0,\r\n width: 280,\r\n maxHeight: 300,\r\n overflowY: 'auto',\r\n background: '#1a1a1a',\r\n borderRadius: 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n border: '1px solid rgba(255,255,255,0.1)',\r\n zIndex: 100,\r\n }}\r\n >\r\n <div style={{ padding: '12px 16px', borderBottom: '1px solid rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.6)', fontSize: 12, fontWeight: 600, textTransform: 'uppercase' }}>\r\n Playlist ({playlist.length} items)\r\n </div>\r\n {playlist.map((item, index) => (\r\n <button\r\n key={item.id}\r\n onClick={() => {\r\n setCurrentPlaylistIndex(index);\r\n onPlaylistItemChange?.(index, item);\r\n setShowPlaylist(false);\r\n setTimeout(() => play(), 100);\r\n }}\r\n style={{\r\n width: '100%',\r\n padding: '10px 16px',\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 12,\r\n background: currentPlaylistIndex === index ? 'rgba(255,255,255,0.1)' : 'transparent',\r\n border: 'none',\r\n color: currentPlaylistIndex === index ? accentColor : '#fff',\r\n cursor: 'pointer',\r\n textAlign: 'left',\r\n fontSize: 14,\r\n }}\r\n >\r\n <span style={{ width: 24, color: 'rgba(255,255,255,0.5)' }}>{index + 1}</span>\r\n <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{item.title}</span>\r\n {currentPlaylistIndex === index && <span>▶</span>}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Settings */}\r\n {playbackSpeed && (\r\n <div style={{ position: 'relative' }}>\r\n <button \r\n onClick={() => setShowSettings(!showSettings)}\r\n aria-label=\"Settings\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: showSettings ? 'rgba(255,255,255,0.2)' : 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n <SettingsIcon />\r\n </button>\r\n \r\n {showSettings && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n bottom: 48,\r\n right: 0,\r\n minWidth: 160,\r\n background: '#1a1a1a',\r\n borderRadius: 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n border: '1px solid rgba(255,255,255,0.1)',\r\n zIndex: 100,\r\n overflow: 'hidden',\r\n }}\r\n >\r\n <div style={{ padding: '10px 16px', borderBottom: '1px solid rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.6)', fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>\r\n Playback Speed\r\n </div>\r\n {playbackSpeeds.map(speed => (\r\n <button\r\n key={speed}\r\n onClick={() => setPlaybackRate(speed)}\r\n style={{\r\n width: '100%',\r\n padding: '10px 16px',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n background: 'transparent',\r\n border: 'none',\r\n color: playbackRate === speed ? accentColor : '#fff',\r\n cursor: 'pointer',\r\n fontSize: 14,\r\n }}\r\n >\r\n <span>{speed === 1 ? 'Normal' : `${speed}x`}</span>\r\n {playbackRate === speed && <span>✓</span>}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* PiP */}\r\n {pip && document.pictureInPictureEnabled && (\r\n <button \r\n onClick={togglePip}\r\n aria-label=\"Picture in Picture\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n <PipIcon />\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n {fullscreen && (\r\n <button \r\n onClick={toggleFullscreen}\r\n aria-label={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n {isFullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Big Play Button */}\r\n {!isPlaying && !isLoading && !error && !isAdPlaying && (\r\n <button\r\n onClick={togglePlay}\r\n aria-label=\"Play\"\r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n width: 80,\r\n height: 80,\r\n borderRadius: '50%',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n border: 'none',\r\n cursor: 'pointer',\r\n backgroundColor: accentColor,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.4)',\r\n transition: 'transform 0.2s',\r\n }}\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"white\" style={{ width: 32, height: 32, marginLeft: 4 }}>\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n </button>\r\n )}\r\n </div>\r\n );\r\n }\r\n);\r\n\r\nPlexVideoPlayer.displayName = 'PlexVideoPlayer';\r\n\r\nexport default PlexVideoPlayer;\r\n","// Progress Bar Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useRef, useState, useCallback, useEffect } from 'react';\r\nimport { formatTime, percentage, clamp } from '../utils/helpers';\r\n\r\ninterface ProgressBarProps {\r\n currentTime: number;\r\n duration: number;\r\n buffered: number;\r\n onSeek: (time: number) => void;\r\n thumbnailPreview?: {\r\n enabled: boolean;\r\n sprites?: string;\r\n interval?: number;\r\n width?: number;\r\n height?: number;\r\n };\r\n disabled?: boolean;\r\n}\r\n\r\nexport const ProgressBar: React.FC<ProgressBarProps> = ({\r\n currentTime,\r\n duration,\r\n buffered,\r\n onSeek,\r\n thumbnailPreview,\r\n disabled = false,\r\n}) => {\r\n const progressRef = useRef<HTMLDivElement>(null);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const [hoverTime, setHoverTime] = useState<number | null>(null);\r\n const [hoverPosition, setHoverPosition] = useState(0);\r\n\r\n const playedPercent = percentage(currentTime, duration);\r\n const bufferedPercent = percentage(buffered, duration);\r\n\r\n const getTimeFromPosition = useCallback(\r\n (clientX: number): number => {\r\n if (!progressRef.current) return 0;\r\n const rect = progressRef.current.getBoundingClientRect();\r\n const percent = clamp((clientX - rect.left) / rect.width, 0, 1);\r\n return percent * duration;\r\n },\r\n [duration]\r\n );\r\n\r\n const handleMouseMove = useCallback(\r\n (e: MouseEvent | React.MouseEvent) => {\r\n if (!progressRef.current) return;\r\n const rect = progressRef.current.getBoundingClientRect();\r\n const clientX = 'clientX' in e ? e.clientX : 0;\r\n const position = clamp(clientX - rect.left, 0, rect.width);\r\n const time = getTimeFromPosition(clientX);\r\n\r\n setHoverPosition(position);\r\n setHoverTime(time);\r\n\r\n if (isDragging) {\r\n onSeek(time);\r\n }\r\n },\r\n [isDragging, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleMouseDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n if (disabled) return;\r\n e.preventDefault();\r\n setIsDragging(true);\r\n const time = getTimeFromPosition(e.clientX);\r\n onSeek(time);\r\n },\r\n [disabled, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleMouseUp = useCallback(() => {\r\n setIsDragging(false);\r\n }, []);\r\n\r\n const handleMouseEnter = useCallback(\r\n (e: React.MouseEvent) => {\r\n const time = getTimeFromPosition(e.clientX);\r\n setHoverTime(time);\r\n },\r\n [getTimeFromPosition]\r\n );\r\n\r\n const handleMouseLeave = useCallback(() => {\r\n setHoverTime(null);\r\n }, []);\r\n\r\n // Global mouse events for dragging\r\n useEffect(() => {\r\n if (isDragging) {\r\n const mouseMoveHandler = (e: MouseEvent) => handleMouseMove(e);\r\n window.addEventListener('mousemove', mouseMoveHandler);\r\n window.addEventListener('mouseup', handleMouseUp);\r\n\r\n return () => {\r\n window.removeEventListener('mousemove', mouseMoveHandler);\r\n window.removeEventListener('mouseup', handleMouseUp);\r\n };\r\n }\r\n return undefined;\r\n }, [isDragging, handleMouseMove, handleMouseUp]);\r\n\r\n // Touch support\r\n const handleTouchStart = useCallback(\r\n (e: React.TouchEvent) => {\r\n if (disabled) return;\r\n e.preventDefault();\r\n const touch = e.touches[0];\r\n const time = getTimeFromPosition(touch.clientX);\r\n setIsDragging(true);\r\n onSeek(time);\r\n },\r\n [disabled, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleTouchMove = useCallback(\r\n (e: React.TouchEvent) => {\r\n if (!isDragging) return;\r\n const touch = e.touches[0];\r\n const time = getTimeFromPosition(touch.clientX);\r\n onSeek(time);\r\n },\r\n [isDragging, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleTouchEnd = useCallback(() => {\r\n setIsDragging(false);\r\n }, []);\r\n\r\n return (\r\n <div\r\n ref={progressRef}\r\n className=\"plex-video-player__progress-container\"\r\n onMouseDown={handleMouseDown}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onMouseMove={(e) => handleMouseMove(e.nativeEvent)}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n role=\"slider\"\r\n aria-label=\"Video progress\"\r\n aria-valuemin={0}\r\n aria-valuemax={duration}\r\n aria-valuenow={currentTime}\r\n aria-valuetext={formatTime(currentTime)}\r\n tabIndex={0}\r\n >\r\n <div className=\"plex-video-player__progress\">\r\n <div\r\n className=\"plex-video-player__progress-buffered\"\r\n style={{ width: `${bufferedPercent}%` }}\r\n />\r\n <div\r\n className=\"plex-video-player__progress-played\"\r\n style={{ width: `${playedPercent}%` }}\r\n />\r\n <div\r\n className=\"plex-video-player__progress-handle\"\r\n style={{ left: `${playedPercent}%` }}\r\n />\r\n </div>\r\n\r\n {/* Thumbnail Preview */}\r\n {thumbnailPreview?.enabled && hoverTime !== null && (\r\n <div\r\n className=\"plex-video-player__thumbnail-preview\"\r\n style={{\r\n left: `${hoverPosition}px`,\r\n width: thumbnailPreview.width || 160,\r\n height: thumbnailPreview.height || 90,\r\n }}\r\n >\r\n {thumbnailPreview.sprites && (\r\n <div\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n backgroundImage: `url(${thumbnailPreview.sprites})`,\r\n backgroundPosition: calculateSpritePosition(\r\n hoverTime,\r\n duration,\r\n thumbnailPreview.interval || 10,\r\n thumbnailPreview.width || 160,\r\n thumbnailPreview.height || 90\r\n ),\r\n backgroundSize: 'cover',\r\n }}\r\n />\r\n )}\r\n <div className=\"plex-video-player__thumbnail-time\">\r\n {formatTime(hoverTime)}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Time tooltip */}\r\n {hoverTime !== null && !thumbnailPreview?.enabled && (\r\n <div\r\n className=\"plex-video-player__thumbnail-preview\"\r\n style={{\r\n left: `${hoverPosition}px`,\r\n width: 'auto',\r\n height: 'auto',\r\n padding: '4px 8px',\r\n }}\r\n >\r\n <span style={{ color: 'white', fontSize: '12px' }}>\r\n {formatTime(hoverTime)}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n// Calculate sprite background position for thumbnail preview\r\nconst calculateSpritePosition = (\r\n time: number,\r\n _duration: number,\r\n interval: number,\r\n width: number,\r\n height: number\r\n): string => {\r\n const index = Math.floor(time / interval);\r\n const columns = 10; // Assuming 10 columns per row in sprite\r\n const row = Math.floor(index / columns);\r\n const col = index % columns;\r\n\r\n return `-${col * width}px -${row * height}px`;\r\n};\r\n\r\nexport default ProgressBar;\r\n","// Icon components for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\n\r\ninterface IconProps {\r\n className?: string;\r\n size?: number;\r\n}\r\n\r\nexport const PlayIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n);\r\n\r\nexport const PauseIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeHighIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeMediumIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeLowIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 9v6h4l5 5V4l-5 5H7z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeMuteIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\r\n </svg>\r\n);\r\n\r\nexport const FullscreenIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\" />\r\n </svg>\r\n);\r\n\r\nexport const FullscreenExitIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\" />\r\n </svg>\r\n);\r\n\r\nexport const PipIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z\" />\r\n </svg>\r\n);\r\n\r\nexport const PipExitIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM5 7h8v6H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const SettingsIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z\" />\r\n </svg>\r\n);\r\n\r\nexport const CaptionsIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z\" />\r\n </svg>\r\n);\r\n\r\nexport const SpeedIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44zm-9.79 6.84a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z\" />\r\n </svg>\r\n);\r\n\r\nexport const QualityIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM7.5 13h2v2H7.5zm4.5 0h2v2h-2zm4.5 0h2v2h-2zM7.5 9h2v2H7.5zm4.5 0h2v2h-2zm4.5 0h2v2h-2z\" />\r\n </svg>\r\n);\r\n\r\nexport const ForwardIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z\" />\r\n </svg>\r\n);\r\n\r\nexport const RewindIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z\" />\r\n </svg>\r\n);\r\n\r\nexport const SkipNextIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z\" />\r\n </svg>\r\n);\r\n\r\nexport const SkipPrevIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 6h2v12H6zm3.5 6l8.5 6V6z\" />\r\n </svg>\r\n);\r\n\r\nexport const ErrorIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" />\r\n </svg>\r\n);\r\n\r\nexport const ExternalLinkIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z\" />\r\n </svg>\r\n);\r\n\r\nexport const CheckIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\" />\r\n </svg>\r\n);\r\n\r\nexport const RepeatIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z\" />\r\n </svg>\r\n);\r\n\r\nexport const CopyIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\" />\r\n </svg>\r\n);\r\n\r\nexport const DownloadIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const ChevronUpIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z\" />\r\n </svg>\r\n);\r\n\r\nexport const ChevronDownIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\" />\r\n </svg>\r\n);\r\n\r\nexport const CloseIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n);\r\n\r\nexport const PlaylistIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M4 10h12v2H4zm0-4h12v2H4zm0 8h8v2H4zm10 0v6l5-3z\" />\r\n </svg>\r\n);\r\n","// Error Display Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\nimport { ErrorIcon } from './Icons';\r\n\r\ninterface ErrorDisplayProps {\r\n error: MediaError | null;\r\n onRetry: () => void;\r\n}\r\n\r\nconst ERROR_MESSAGES: Record<number, string> = {\r\n 1: 'The video playback was aborted.',\r\n 2: 'A network error occurred while loading the video.',\r\n 3: 'The video format is not supported or cannot be decoded.',\r\n 4: 'The video source is not supported.',\r\n};\r\n\r\nexport const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onRetry }) => {\r\n const errorCode = error?.code || 0;\r\n const message = ERROR_MESSAGES[errorCode] || 'An unknown error occurred.';\r\n\r\n return (\r\n <div className=\"plex-video-player__error\">\r\n <ErrorIcon className=\"plex-video-player__error-icon\" size={60} />\r\n <div className=\"plex-video-player__error-message\">{message}</div>\r\n {errorCode > 0 && (\r\n <div className=\"plex-video-player__error-code\">Error Code: {errorCode}</div>\r\n )}\r\n <button\r\n className=\"plex-video-player__error-retry\"\r\n onClick={onRetry}\r\n type=\"button\"\r\n >\r\n Try Again\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport default ErrorDisplay;\r\n","// usePlayer Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useState, useRef, useCallback, useEffect } from 'react';\r\nimport {\r\n PlayerState,\r\n UsePlayerOptions,\r\n UsePlayerReturn,\r\n} from '../types';\r\nimport {\r\n requestFullscreen,\r\n exitFullscreen,\r\n getFullscreenElement,\r\n getBufferedEnd,\r\n isPipSupported,\r\n} from '../utils/helpers';\r\n\r\nconst initialState: PlayerState = {\r\n isPlaying: false,\r\n isPaused: true,\r\n isEnded: false,\r\n isBuffering: false,\r\n isSeeking: false,\r\n isFullscreen: false,\r\n isPip: false,\r\n isMuted: false,\r\n isAdPlaying: false,\r\n volume: 1,\r\n currentTime: 0,\r\n duration: 0,\r\n buffered: 0,\r\n playbackRate: 1,\r\n currentQuality: null,\r\n error: null,\r\n};\r\n\r\nexport const usePlayer = (options: UsePlayerOptions = {}): UsePlayerReturn => {\r\n const {\r\n autoPlay = false,\r\n muted = false,\r\n loop = false,\r\n volume: initialVolume = 1,\r\n playbackRate: initialRate = 1,\r\n } = options;\r\n\r\n const videoRef = useRef<HTMLVideoElement>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [state, setState] = useState<PlayerState>({\r\n ...initialState,\r\n volume: initialVolume,\r\n isMuted: muted,\r\n playbackRate: initialRate,\r\n });\r\n\r\n // Update state helper\r\n const updateState = useCallback((updates: Partial<PlayerState>) => {\r\n setState((prev) => ({ ...prev, ...updates }));\r\n }, []);\r\n\r\n // Play\r\n const play = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n try {\r\n await video.play();\r\n updateState({ isPlaying: true, isPaused: false, isEnded: false });\r\n } catch (error) {\r\n console.error('Play failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Pause\r\n const pause = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.pause();\r\n updateState({ isPlaying: false, isPaused: true });\r\n }, [updateState]);\r\n\r\n // Toggle play/pause\r\n const togglePlay = useCallback(() => {\r\n if (state.isPlaying) {\r\n pause();\r\n } else {\r\n play();\r\n }\r\n }, [state.isPlaying, play, pause]);\r\n\r\n // Seek\r\n const seek = useCallback((time: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const clampedTime = Math.max(0, Math.min(time, video.duration || 0));\r\n video.currentTime = clampedTime;\r\n updateState({ currentTime: clampedTime });\r\n }, [updateState]);\r\n\r\n // Set volume\r\n const setVolume = useCallback((volume: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const clampedVolume = Math.max(0, Math.min(1, volume));\r\n video.volume = clampedVolume;\r\n video.muted = clampedVolume === 0;\r\n updateState({\r\n volume: clampedVolume,\r\n isMuted: clampedVolume === 0,\r\n });\r\n }, [updateState]);\r\n\r\n // Toggle mute\r\n const toggleMute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.muted = !video.muted;\r\n updateState({ isMuted: video.muted });\r\n }, [updateState]);\r\n\r\n // Set playback rate\r\n const setPlaybackRate = useCallback((rate: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.playbackRate = rate;\r\n updateState({ playbackRate: rate });\r\n }, [updateState]);\r\n\r\n // Enter fullscreen\r\n const enterFullscreen = useCallback(async () => {\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n try {\r\n await requestFullscreen(container);\r\n updateState({ isFullscreen: true });\r\n } catch (error) {\r\n console.error('Fullscreen failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Exit fullscreen\r\n const exitFullscreenFn = useCallback(async () => {\r\n try {\r\n await exitFullscreen();\r\n updateState({ isFullscreen: false });\r\n } catch (error) {\r\n console.error('Exit fullscreen failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Toggle fullscreen\r\n const toggleFullscreen = useCallback(async () => {\r\n if (state.isFullscreen) {\r\n await exitFullscreenFn();\r\n } else {\r\n await enterFullscreen();\r\n }\r\n }, [state.isFullscreen, enterFullscreen, exitFullscreenFn]);\r\n\r\n // Enter Picture-in-Picture\r\n const enterPip = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video || !isPipSupported()) return;\r\n\r\n try {\r\n await video.requestPictureInPicture();\r\n updateState({ isPip: true });\r\n } catch (error) {\r\n console.error('PiP failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Exit Picture-in-Picture\r\n const exitPipFn = useCallback(async () => {\r\n if (!document.pictureInPictureElement) return;\r\n\r\n try {\r\n await document.exitPictureInPicture();\r\n updateState({ isPip: false });\r\n } catch (error) {\r\n console.error('Exit PiP failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Toggle Picture-in-Picture\r\n const togglePip = useCallback(async () => {\r\n if (state.isPip) {\r\n await exitPipFn();\r\n } else {\r\n await enterPip();\r\n }\r\n }, [state.isPip, enterPip, exitPipFn]);\r\n\r\n // Video event handlers\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n // Set initial values\r\n video.muted = muted;\r\n video.volume = initialVolume;\r\n video.playbackRate = initialRate;\r\n video.loop = loop;\r\n\r\n const handlers = {\r\n loadedmetadata: () => {\r\n updateState({ duration: video.duration });\r\n },\r\n timeupdate: () => {\r\n updateState({\r\n currentTime: video.currentTime,\r\n buffered: getBufferedEnd(video),\r\n });\r\n },\r\n play: () => {\r\n updateState({ isPlaying: true, isPaused: false, isEnded: false });\r\n },\r\n pause: () => {\r\n updateState({ isPlaying: false, isPaused: true });\r\n },\r\n ended: () => {\r\n updateState({ isPlaying: false, isPaused: true, isEnded: true });\r\n },\r\n waiting: () => {\r\n updateState({ isBuffering: true });\r\n },\r\n canplay: () => {\r\n updateState({ isBuffering: false });\r\n },\r\n seeking: () => {\r\n updateState({ isSeeking: true });\r\n },\r\n seeked: () => {\r\n updateState({ isSeeking: false });\r\n },\r\n volumechange: () => {\r\n updateState({\r\n volume: video.volume,\r\n isMuted: video.muted,\r\n });\r\n },\r\n ratechange: () => {\r\n updateState({ playbackRate: video.playbackRate });\r\n },\r\n error: () => {\r\n updateState({ error: video.error });\r\n },\r\n enterpictureinpicture: () => {\r\n updateState({ isPip: true });\r\n },\r\n leavepictureinpicture: () => {\r\n updateState({ isPip: false });\r\n },\r\n };\r\n\r\n // Add event listeners\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.addEventListener(event, handler);\r\n });\r\n\r\n // Autoplay\r\n if (autoPlay) {\r\n play();\r\n }\r\n\r\n return () => {\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.removeEventListener(event, handler);\r\n });\r\n };\r\n }, [autoPlay, muted, loop, initialVolume, initialRate, play, updateState]);\r\n\r\n // Fullscreen change handler\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!getFullscreenElement();\r\n updateState({ isFullscreen: isFs });\r\n };\r\n\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\r\n document.addEventListener('MSFullscreenChange', handleFullscreenChange);\r\n\r\n return () => {\r\n document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('mozfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('MSFullscreenChange', handleFullscreenChange);\r\n };\r\n }, [updateState]);\r\n\r\n return {\r\n state,\r\n videoRef,\r\n containerRef,\r\n play,\r\n pause,\r\n togglePlay,\r\n seek,\r\n setVolume,\r\n toggleMute,\r\n setPlaybackRate,\r\n enterFullscreen,\r\n exitFullscreen: exitFullscreenFn,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip: exitPipFn,\r\n togglePip,\r\n };\r\n};\r\n\r\nexport default usePlayer;\r\n","// useKeyboard Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useEffect, useCallback } from 'react';\r\nimport { HotkeyConfig } from '../types';\r\n\r\nconst DEFAULT_HOTKEYS: Required<HotkeyConfig> = {\r\n play: 'Space',\r\n mute: 'm',\r\n fullscreen: 'f',\r\n pip: 'p',\r\n seekForward: 'ArrowRight',\r\n seekBackward: 'ArrowLeft',\r\n volumeUp: 'ArrowUp',\r\n volumeDown: 'ArrowDown',\r\n};\r\n\r\ninterface UseKeyboardOptions {\r\n enabled: boolean;\r\n hotkeys?: HotkeyConfig;\r\n onPlay: () => void;\r\n onMute: () => void;\r\n onFullscreen: () => void;\r\n onPip: () => void;\r\n onSeek: (delta: number) => void;\r\n onVolume: (delta: number) => void;\r\n containerRef: React.RefObject<HTMLDivElement>;\r\n}\r\n\r\nexport const useKeyboard = ({\r\n enabled,\r\n hotkeys = {},\r\n onPlay,\r\n onMute,\r\n onFullscreen,\r\n onPip,\r\n onSeek,\r\n onVolume,\r\n containerRef,\r\n}: UseKeyboardOptions): void => {\r\n const mergedHotkeys = { ...DEFAULT_HOTKEYS, ...hotkeys };\r\n\r\n const handleKeyDown = useCallback(\r\n (event: KeyboardEvent) => {\r\n if (!enabled) return;\r\n\r\n // Ignore if typing in an input\r\n const target = event.target as HTMLElement;\r\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\r\n return;\r\n }\r\n\r\n // Check if focus is within the player container\r\n const container = containerRef.current;\r\n if (!container || !container.contains(document.activeElement)) {\r\n return;\r\n }\r\n\r\n const key = event.key;\r\n\r\n switch (key) {\r\n case mergedHotkeys.play:\r\n case ' ':\r\n event.preventDefault();\r\n onPlay();\r\n break;\r\n case mergedHotkeys.mute:\r\n case 'M':\r\n event.preventDefault();\r\n onMute();\r\n break;\r\n case mergedHotkeys.fullscreen:\r\n case 'F':\r\n event.preventDefault();\r\n onFullscreen();\r\n break;\r\n case mergedHotkeys.pip:\r\n case 'P':\r\n event.preventDefault();\r\n onPip();\r\n break;\r\n case mergedHotkeys.seekForward:\r\n event.preventDefault();\r\n onSeek(event.shiftKey ? 30 : 10);\r\n break;\r\n case mergedHotkeys.seekBackward:\r\n event.preventDefault();\r\n onSeek(event.shiftKey ? -30 : -10);\r\n break;\r\n case mergedHotkeys.volumeUp:\r\n event.preventDefault();\r\n onVolume(0.1);\r\n break;\r\n case mergedHotkeys.volumeDown:\r\n event.preventDefault();\r\n onVolume(-0.1);\r\n break;\r\n case '0':\r\n case '1':\r\n case '2':\r\n case '3':\r\n case '4':\r\n case '5':\r\n case '6':\r\n case '7':\r\n case '8':\r\n case '9':\r\n event.preventDefault();\r\n // Seek to percentage of video\r\n onSeek(-Infinity); // Will be handled by caller with percent\r\n break;\r\n default:\r\n break;\r\n }\r\n },\r\n [enabled, mergedHotkeys, onPlay, onMute, onFullscreen, onPip, onSeek, onVolume, containerRef]\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) return;\r\n\r\n document.addEventListener('keydown', handleKeyDown);\r\n\r\n return () => {\r\n document.removeEventListener('keydown', handleKeyDown);\r\n };\r\n }, [enabled, handleKeyDown]);\r\n};\r\n\r\nexport default useKeyboard;\r\n","// Ad Overlay Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\nimport { VastAdInfo } from '../types';\r\nimport { formatTime } from '../utils/helpers';\r\nimport { ExternalLinkIcon } from './Icons';\r\n\r\ninterface AdOverlayProps {\r\n ad: VastAdInfo;\r\n timeRemaining: number;\r\n canSkip: boolean;\r\n onSkip: () => void;\r\n onClick: () => void;\r\n}\r\n\r\nexport const AdOverlay: React.FC<AdOverlayProps> = ({\r\n ad,\r\n timeRemaining,\r\n canSkip,\r\n onSkip,\r\n onClick,\r\n}) => {\r\n const skipText = canSkip\r\n ? 'Skip Ad'\r\n : `Skip in ${Math.ceil(timeRemaining)}s`;\r\n\r\n return (\r\n <div className=\"plex-video-player__ad-overlay\" onClick={onClick}>\r\n {/* Ad Info Badge */}\r\n <div className=\"plex-video-player__ad-info\">\r\n <span className=\"plex-video-player__ad-badge\">Ad</span>\r\n <span>{formatTime(timeRemaining)} remaining</span>\r\n </div>\r\n\r\n {/* Learn More Button */}\r\n {ad.clickThrough && (\r\n <button\r\n className=\"plex-video-player__ad-learn-more\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n onClick();\r\n }}\r\n type=\"button\"\r\n >\r\n <ExternalLinkIcon size={16} />\r\n Learn More\r\n </button>\r\n )}\r\n\r\n {/* Skip Button */}\r\n <button\r\n className=\"plex-video-player__ad-skip\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n if (canSkip) onSkip();\r\n }}\r\n disabled={!canSkip}\r\n type=\"button\"\r\n >\r\n {skipText}\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport default AdOverlay;\r\n","// Loading Spinner Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\n\r\ninterface LoaderProps {\r\n visible: boolean;\r\n}\r\n\r\nexport const Loader: React.FC<LoaderProps> = ({ visible }) => {\r\n if (!visible) return null;\r\n\r\n return (\r\n <div className=\"plex-video-player__loader\">\r\n <div className=\"plex-video-player__loader-spinner\" />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Loader;\r\n","// Settings Menu Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { SettingsIcon, CheckIcon } from './Icons';\r\nimport { VideoSource, TextTrack } from '../types';\r\n\r\ninterface SettingsMenuProps {\r\n playbackRate: number;\r\n playbackSpeeds: number[];\r\n onPlaybackRateChange: (rate: number) => void;\r\n qualityEnabled: boolean;\r\n sources?: VideoSource[];\r\n currentQuality?: string;\r\n onQualityChange?: (quality: string) => void;\r\n captionsEnabled?: boolean;\r\n textTracks?: TextTrack[];\r\n currentTrack?: string;\r\n onTrackChange?: (track: string | null) => void;\r\n}\r\n\r\ntype MenuView = 'main' | 'speed' | 'quality' | 'captions';\r\n\r\nexport const SettingsMenu: React.FC<SettingsMenuProps> = ({\r\n playbackRate,\r\n playbackSpeeds,\r\n onPlaybackRateChange,\r\n qualityEnabled,\r\n sources,\r\n currentQuality,\r\n onQualityChange,\r\n captionsEnabled,\r\n textTracks,\r\n currentTrack,\r\n onTrackChange,\r\n}) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [currentView, setCurrentView] = useState<MenuView>('main');\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Close menu when clicking outside\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\r\n setIsOpen(false);\r\n setCurrentView('main');\r\n }\r\n };\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }\r\n return undefined;\r\n }, [isOpen]);\r\n\r\n const handleToggle = useCallback(() => {\r\n setIsOpen((prev) => !prev);\r\n setCurrentView('main');\r\n }, []);\r\n\r\n const handleSpeedSelect = useCallback(\r\n (speed: number) => {\r\n onPlaybackRateChange(speed);\r\n setCurrentView('main');\r\n },\r\n [onPlaybackRateChange]\r\n );\r\n\r\n const handleQualitySelect = useCallback(\r\n (quality: string) => {\r\n onQualityChange?.(quality);\r\n setCurrentView('main');\r\n },\r\n [onQualityChange]\r\n );\r\n\r\n const handleTrackSelect = useCallback(\r\n (track: string | null) => {\r\n onTrackChange?.(track);\r\n setCurrentView('main');\r\n },\r\n [onTrackChange]\r\n );\r\n\r\n const renderMainMenu = () => (\r\n <>\r\n <div className=\"plex-video-player__settings-title\">Settings</div>\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('speed')}\r\n type=\"button\"\r\n >\r\n <span>Playback Speed</span>\r\n <span>{playbackRate === 1 ? 'Normal' : `${playbackRate}x`}</span>\r\n </button>\r\n {qualityEnabled && sources && sources.length > 1 && (\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('quality')}\r\n type=\"button\"\r\n >\r\n <span>Quality</span>\r\n <span>{currentQuality || 'Auto'}</span>\r\n </button>\r\n )}\r\n {captionsEnabled && textTracks && textTracks.length > 0 && (\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('captions')}\r\n type=\"button\"\r\n >\r\n <span>Captions</span>\r\n <span>{currentTrack || 'Off'}</span>\r\n </button>\r\n )}\r\n </>\r\n );\r\n\r\n const renderSpeedMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Playback Speed\r\n </button>\r\n <div className=\"plex-video-player__speed-menu\">\r\n {playbackSpeeds.map((speed) => (\r\n <button\r\n key={speed}\r\n className={`plex-video-player__speed-btn ${\r\n playbackRate === speed ? 'plex-video-player__speed-btn--active' : ''\r\n }`}\r\n onClick={() => handleSpeedSelect(speed)}\r\n type=\"button\"\r\n >\r\n {speed === 1 ? 'Normal' : `${speed}x`}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n const renderQualityMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Quality\r\n </button>\r\n <div className=\"plex-video-player__quality-menu\">\r\n {sources?.map((source) => (\r\n <button\r\n key={source.quality || source.src}\r\n className={`plex-video-player__settings-item ${\r\n currentQuality === source.quality ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleQualitySelect(source.quality || source.src)}\r\n type=\"button\"\r\n >\r\n <span>{source.label || source.quality || 'Unknown'}</span>\r\n {currentQuality === source.quality && <CheckIcon size={16} />}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n const renderCaptionsMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Captions\r\n </button>\r\n <div className=\"plex-video-player__quality-menu\">\r\n <button\r\n className={`plex-video-player__settings-item ${\r\n !currentTrack ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleTrackSelect(null)}\r\n type=\"button\"\r\n >\r\n <span>Off</span>\r\n {!currentTrack && <CheckIcon size={16} />}\r\n </button>\r\n {textTracks?.map((track) => (\r\n <button\r\n key={track.srclang}\r\n className={`plex-video-player__settings-item ${\r\n currentTrack === track.srclang ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleTrackSelect(track.srclang)}\r\n type=\"button\"\r\n >\r\n <span>{track.label}</span>\r\n {currentTrack === track.srclang && <CheckIcon size={16} />}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n return (\r\n <div className=\"plex-video-player__settings-container\" ref={containerRef}>\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={handleToggle}\r\n aria-label=\"Settings\"\r\n aria-expanded={isOpen}\r\n title=\"Settings\"\r\n type=\"button\"\r\n >\r\n <SettingsIcon />\r\n </button>\r\n {isOpen && (\r\n <div\r\n className={`plex-video-player__settings-menu ${\r\n isOpen ? 'plex-video-player__settings-menu--open' : ''\r\n }`}\r\n >\r\n {currentView === 'main' && renderMainMenu()}\r\n {currentView === 'speed' && renderSpeedMenu()}\r\n {currentView === 'quality' && renderQualityMenu()}\r\n {currentView === 'captions' && renderCaptionsMenu()}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nexport default SettingsMenu;\r\n","// Volume Control Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useCallback } from 'react';\r\nimport {\r\n VolumeHighIcon,\r\n VolumeMediumIcon,\r\n VolumeLowIcon,\r\n VolumeMuteIcon,\r\n} from './Icons';\r\n\r\ninterface VolumeControlProps {\r\n volume: number;\r\n muted: boolean;\r\n onVolumeChange: (volume: number) => void;\r\n onToggleMute: () => void;\r\n}\r\n\r\nexport const VolumeControl: React.FC<VolumeControlProps> = ({\r\n volume,\r\n muted,\r\n onVolumeChange,\r\n onToggleMute,\r\n}) => {\r\n const handleSliderChange = useCallback(\r\n (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const newVolume = parseFloat(e.target.value);\r\n onVolumeChange(newVolume);\r\n },\r\n [onVolumeChange]\r\n );\r\n\r\n const getVolumeIcon = () => {\r\n if (muted || volume === 0) {\r\n return <VolumeMuteIcon />;\r\n }\r\n if (volume < 0.33) {\r\n return <VolumeLowIcon />;\r\n }\r\n if (volume < 0.66) {\r\n return <VolumeMediumIcon />;\r\n }\r\n return <VolumeHighIcon />;\r\n };\r\n\r\n return (\r\n <div className=\"plex-video-player__volume-container\">\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={onToggleMute}\r\n aria-label={muted ? 'Unmute' : 'Mute'}\r\n title={muted ? 'Unmute (M)' : 'Mute (M)'}\r\n type=\"button\"\r\n >\r\n {getVolumeIcon()}\r\n </button>\r\n <div className=\"plex-video-player__volume-slider-container\">\r\n <input\r\n type=\"range\"\r\n className=\"plex-video-player__volume-slider\"\r\n min={0}\r\n max={1}\r\n step={0.01}\r\n value={muted ? 0 : volume}\r\n onChange={handleSliderChange}\r\n aria-label=\"Volume\"\r\n style={{\r\n background: `linear-gradient(to right, var(--plex-primary) 0%, var(--plex-primary) ${\r\n (muted ? 0 : volume) * 100\r\n }%, var(--plex-progress-bg) ${(muted ? 0 : volume) * 100}%, var(--plex-progress-bg) 100%)`,\r\n }}\r\n />\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nexport default VolumeControl;\r\n"],"names":["parseVastXml","xmlString","doc","DOMParser","parseFromString","querySelector","ads","error","querySelectorAll","forEach","adElement","inLine","id","getAttribute","title","textContent","description","undefined","creatives","skipOffset","clickThrough","duration","clickTracking","mediaFiles","trackingEvents","creative","linear","durationStr","parseVastDuration","skipOffsetAttr","mediaFile","url","trim","push","type","width","parseInt","height","bitrate","videoClicks","ct","tracking","event","impressionUrls","impression","length","includes","parts","split","parseFloat","fetchVastAd","async","config","response","fetch","method","headers","Accept","ok","text","result","ad","skipDelay","selectBestMediaFile","video","document","createElement","supportedFiles","filter","file","canPlayType","sort","a","b","resA","resB","test","navigator","userAgent","fireTrackingPixel","Image","src","fireTrackingPixels","urls","convertToAdInfo","useVast","vastConfig","videoRef","onAdStart","onAdEnd","onAdSkip","onAdError","isAdPlaying","setIsAdPlaying","useState","currentAd","setCurrentAd","adTimeRemaining","setAdTimeRemaining","canSkip","setCanSkip","currentVastAd","useRef","originalSrc","originalTime","playedPositions","Set","adIntervalRef","vastConfigs","Array","isArray","playAd","useCallback","current","Error","currentSrc","currentTime","play","start","window","setInterval","remaining","Math","max","percent","firstQuartile","midpoint","thirdQuartile","handleAdEnd","complete","endAd","addEventListener","once","clearInterval","catch","skipAd","skip","handleAdClick","open","checkForAd","position","key","midrollTime","has","shouldPlay","add","useEffect","formatTime","seconds","isFinite","isNaN","hrs","floor","mins","secs","toString","padStart","percentage","value","total","min","clamp","throttle","func","limit","inThrottle","args","setTimeout","isPipSupported","pictureInPictureEnabled","getFullscreenElement","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","requestFullscreen","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","exitFullscreen","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","getBufferedEnd","buffered","i","end","PlayIcon","_jsx","viewBox","fill","className","children","d","PauseIcon","VolumeHighIcon","VolumeMuteIcon","FullscreenIcon","FullscreenExitIcon","PipIcon","SettingsIcon","RepeatIcon","CopyIcon","SpeedIcon","PlaylistIcon","style","PlexVideoPlayer","forwardRef","props","ref","poster","autoPlay","muted","loop","controls","pip","fullscreen","playbackSpeed","playbackSpeeds","volume","volumeEnabled","initialVolume","textTracks","vast","accentColor","controlsTimeout","doubleClickFullscreen","clickToPlay","preload","playlist","onPlay","onPause","onEnded","onTimeUpdate","onProgress","onVolumeChange","onFullscreenChange","onPipChange","onQualityChange","onError","onReady","onPlaylistItemChange","containerRef","progressRef","controlsTimeoutRef","lastClickRef","isPlaying","setIsPlaying","setCurrentTime","setDuration","setBuffered","volumeState","setVolumeState","isMuted","setIsMuted","isFullscreen","setIsFullscreen","isPipActive","setIsPipActive","isLoading","setIsLoading","setError","showControls","setShowControls","playbackRate","setPlaybackRateState","isLoop","setIsLoop","showSettings","setShowSettings","showPlaylist","setShowPlaylist","hoverTime","setHoverTime","contextMenu","setContextMenu","x","y","visible","currentPlaylistIndex","setCurrentPlaylistIndex","sources","useMemo","currentVideoSrc","currentPoster","err","pause","stop","togglePlay","paused","seek","time","setVolume","newVolume","mute","unmute","toggleMute","toggleLoop","enterFullscreen","toggleFullscreen","enterPip","requestPictureInPicture","exitPip","pictureInPictureElement","exitPictureInPicture","togglePip","setPlaybackRate","rate","setQuality","qualityLabel","source","find","s","label","quality","wasPlaying","playNextInPlaylist","nextIndex","handleContextMenu","e","preventDefault","rect","getBoundingClientRect","clientX","left","clientY","top","closeContextMenu","prev","copyVideoUrl","clipboard","writeText","handleProgressClick","handleProgressHover","handleProgressLeave","hideControls","showControlsTemporarily","clearTimeout","handleMouseMove","handleVideoClick","now","Date","handlePlay","handlePause","handleEnded","handleTimeUpdate","handleDurationChange","handleProgressEvent","bufferedEnd","handleVolumeChangeEvent","handleLoadedMetadata","handleWaiting","handlePlaying","handleCanPlay","handleErrorEvent","removeEventListener","handleFullscreenChange","isFs","handleEnterPip","handleLeavePip","handleKeyDown","contains","activeElement","handleClick","useImperativeHandle","getCurrentTime","getDuration","getVolume","isPip","getVideoElement","progressPercent","_jsxs","inset","zIndex","aspectRatio","backgroundColor","overflow","userSelect","fontFamily","onMouseMove","onMouseLeave","onContextMenu","tabIndex","objectFit","playsInline","onClick","map","track","kind","srcLang","srclang","default","display","alignItems","justifyContent","pointerEvents","border","borderTopColor","borderRadius","animation","flexDirection","background","color","marginBottom","fontSize","load","padding","fontWeight","cursor","gap","disabled","bottom","right","opacity","ceil","minWidth","backdropFilter","boxShadow","stopPropagation","textAlign","flex","margin","speeds","idx","indexOf","borderTop","transition","transform","whiteSpace","step","onChange","target","marginLeft","maxHeight","overflowY","borderBottom","textTransform","item","index","textOverflow","letterSpacing","speed","displayName","calculateSpritePosition","_duration","interval","size","xmlns","VolumeMediumIcon","VolumeLowIcon","ErrorIcon","ExternalLinkIcon","CheckIcon","ERROR_MESSAGES","initialState","isPaused","isEnded","isBuffering","isSeeking","currentQuality","DEFAULT_HOTKEYS","seekForward","seekBackward","volumeUp","volumeDown","timeRemaining","onSkip","skipText","onRetry","errorCode","code","message","onSeek","thumbnailPreview","isDragging","setIsDragging","hoverPosition","setHoverPosition","playedPercent","bufferedPercent","getTimeFromPosition","handleMouseDown","handleMouseUp","handleMouseEnter","handleMouseLeave","mouseMoveHandler","handleTouchStart","touch","touches","handleTouchMove","handleTouchEnd","onMouseDown","onMouseEnter","nativeEvent","onTouchStart","onTouchMove","onTouchEnd","role","enabled","sprites","backgroundImage","backgroundPosition","backgroundSize","onPlaybackRateChange","qualityEnabled","captionsEnabled","currentTrack","onTrackChange","isOpen","setIsOpen","currentView","setCurrentView","handleClickOutside","handleToggle","handleSpeedSelect","handleQualitySelect","handleTrackSelect","_Fragment","onToggleMute","handleSliderChange","wait","timeout","extension","pop","toLowerCase","mp4","webm","ogg","ogv","m3u8","mpd","mov","avi","mkv","random","substring","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullscreenEnabled","maxTouchPoints","Number","hotkeys","onMute","onFullscreen","onPip","onVolume","mergedHotkeys","tagName","isContentEditable","container","shiftKey","Infinity","options","initialRate","state","setState","updateState","updates","clampedTime","clampedVolume","exitFullscreenFn","exitPipFn","handlers","loadedmetadata","timeupdate","ended","waiting","canplay","seeking","seeked","volumechange","ratechange","enterpictureinpicture","leavepictureinpicture","Object","entries","handler"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils/vast.ts","../src/hooks/useVast.ts","../src/utils/helpers.ts","../src/components/PlexVideoPlayer.tsx","../src/components/ProgressBar.tsx","../src/components/Icons.tsx","../src/components/ErrorDisplay.tsx","../src/hooks/usePlayer.ts","../src/hooks/useKeyboard.ts","../src/components/AdOverlay.tsx","../src/components/Loader.tsx","../src/components/SettingsMenu.tsx","../src/components/VolumeControl.tsx"],"sourcesContent":["// VAST Ads Parser for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { VastAdInfo, VastConfig } from '../types';\r\n\r\ninterface VastMediaFile {\r\n url: string;\r\n type: string;\r\n width: number;\r\n height: number;\r\n bitrate?: number;\r\n}\r\n\r\ninterface VastAd {\r\n id: string;\r\n title: string;\r\n description?: string;\r\n duration: number;\r\n skipOffset?: number;\r\n clickThrough?: string;\r\n clickTracking?: string[];\r\n impressionUrls: string[];\r\n mediaFiles: VastMediaFile[];\r\n trackingEvents: Record<string, string[]>;\r\n}\r\n\r\ninterface VastResponse {\r\n ads: VastAd[];\r\n error?: string;\r\n}\r\n\r\n/**\r\n * Parse VAST XML response\r\n */\r\nexport const parseVastXml = (xmlString: string): VastResponse => {\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(xmlString, 'text/xml');\r\n \r\n const parseError = doc.querySelector('parsererror');\r\n if (parseError) {\r\n return { ads: [], error: 'Failed to parse VAST XML' };\r\n }\r\n\r\n const ads: VastAd[] = [];\r\n const adElements = doc.querySelectorAll('Ad');\r\n\r\n adElements.forEach((adElement) => {\r\n const inLine = adElement.querySelector('InLine');\r\n if (!inLine) return;\r\n\r\n const id = adElement.getAttribute('id') || '';\r\n const title = inLine.querySelector('AdTitle')?.textContent || '';\r\n const description = inLine.querySelector('Description')?.textContent || undefined;\r\n\r\n // Parse creatives\r\n const creatives = inLine.querySelectorAll('Creative');\r\n let duration = 0;\r\n let skipOffset: number | undefined;\r\n let clickThrough: string | undefined;\r\n const clickTracking: string[] = [];\r\n const mediaFiles: VastMediaFile[] = [];\r\n const trackingEvents: Record<string, string[]> = {};\r\n\r\n creatives.forEach((creative) => {\r\n const linear = creative.querySelector('Linear');\r\n if (!linear) return;\r\n\r\n // Duration\r\n const durationStr = linear.querySelector('Duration')?.textContent;\r\n if (durationStr) {\r\n duration = parseVastDuration(durationStr);\r\n }\r\n\r\n // Skip offset\r\n const skipOffsetAttr = linear.getAttribute('skipoffset');\r\n if (skipOffsetAttr) {\r\n skipOffset = parseVastDuration(skipOffsetAttr);\r\n }\r\n\r\n // Media files\r\n const mediaFileElements = linear.querySelectorAll('MediaFile');\r\n mediaFileElements.forEach((mediaFile) => {\r\n const url = mediaFile.textContent?.trim();\r\n if (url) {\r\n mediaFiles.push({\r\n url,\r\n type: mediaFile.getAttribute('type') || 'video/mp4',\r\n width: parseInt(mediaFile.getAttribute('width') || '0', 10),\r\n height: parseInt(mediaFile.getAttribute('height') || '0', 10),\r\n bitrate: parseInt(mediaFile.getAttribute('bitrate') || '0', 10) || undefined,\r\n });\r\n }\r\n });\r\n\r\n // Video clicks\r\n const videoClicks = linear.querySelector('VideoClicks');\r\n if (videoClicks) {\r\n clickThrough = videoClicks.querySelector('ClickThrough')?.textContent?.trim();\r\n videoClicks.querySelectorAll('ClickTracking').forEach((ct) => {\r\n const url = ct.textContent?.trim();\r\n if (url) clickTracking.push(url);\r\n });\r\n }\r\n\r\n // Tracking events\r\n const trackingEventsEl = linear.querySelectorAll('TrackingEvents Tracking');\r\n trackingEventsEl.forEach((tracking) => {\r\n const event = tracking.getAttribute('event');\r\n const url = tracking.textContent?.trim();\r\n if (event && url) {\r\n if (!trackingEvents[event]) {\r\n trackingEvents[event] = [];\r\n }\r\n trackingEvents[event].push(url);\r\n }\r\n });\r\n });\r\n\r\n // Impression URLs\r\n const impressionUrls: string[] = [];\r\n inLine.querySelectorAll('Impression').forEach((impression) => {\r\n const url = impression.textContent?.trim();\r\n if (url) impressionUrls.push(url);\r\n });\r\n\r\n if (mediaFiles.length > 0) {\r\n ads.push({\r\n id,\r\n title,\r\n description,\r\n duration,\r\n skipOffset,\r\n clickThrough,\r\n clickTracking,\r\n impressionUrls,\r\n mediaFiles,\r\n trackingEvents,\r\n });\r\n }\r\n });\r\n\r\n return { ads };\r\n};\r\n\r\n/**\r\n * Parse VAST duration string (HH:MM:SS or HH:MM:SS.mmm)\r\n */\r\nconst parseVastDuration = (duration: string): number => {\r\n if (duration.includes('%')) {\r\n return -1; // Percentage-based, handle separately\r\n }\r\n\r\n const parts = duration.split(':');\r\n if (parts.length !== 3) return 0;\r\n\r\n const hours = parseInt(parts[0], 10);\r\n const minutes = parseInt(parts[1], 10);\r\n const seconds = parseFloat(parts[2]);\r\n\r\n return hours * 3600 + minutes * 60 + seconds;\r\n};\r\n\r\n/**\r\n * Fetch and parse VAST ad\r\n */\r\nexport const fetchVastAd = async (config: VastConfig): Promise<VastAd | null> => {\r\n try {\r\n const response = await fetch(config.url, {\r\n method: 'GET',\r\n headers: {\r\n 'Accept': 'application/xml',\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n console.error('VAST fetch failed:', response.status);\r\n return null;\r\n }\r\n\r\n const xmlString = await response.text();\r\n const result = parseVastXml(xmlString);\r\n\r\n if (result.error || result.ads.length === 0) {\r\n console.error('VAST parse error:', result.error || 'No ads found');\r\n return null;\r\n }\r\n\r\n const ad = result.ads[0];\r\n \r\n // Apply skip delay from config if not in VAST\r\n if (config.skipDelay !== undefined && ad.skipOffset === undefined) {\r\n ad.skipOffset = config.skipDelay;\r\n }\r\n\r\n return ad;\r\n } catch (error) {\r\n console.error('VAST fetch error:', error);\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Select best media file based on device capabilities\r\n */\r\nexport const selectBestMediaFile = (mediaFiles: VastMediaFile[]): VastMediaFile | null => {\r\n if (mediaFiles.length === 0) return null;\r\n\r\n const video = document.createElement('video');\r\n const supportedFiles = mediaFiles.filter((file) => {\r\n return video.canPlayType(file.type) !== '';\r\n });\r\n\r\n if (supportedFiles.length === 0) return null;\r\n\r\n // Sort by resolution (prefer higher) and bitrate\r\n supportedFiles.sort((a, b) => {\r\n const resA = a.width * a.height;\r\n const resB = b.width * b.height;\r\n if (resA !== resB) return resB - resA;\r\n return (b.bitrate || 0) - (a.bitrate || 0);\r\n });\r\n\r\n // On mobile, prefer lower resolution\r\n const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);\r\n if (isMobile && supportedFiles.length > 1) {\r\n return supportedFiles[supportedFiles.length - 1];\r\n }\r\n\r\n return supportedFiles[0];\r\n};\r\n\r\n/**\r\n * Fire tracking pixel\r\n */\r\nexport const fireTrackingPixel = (url: string): void => {\r\n const img = new Image();\r\n img.src = url;\r\n};\r\n\r\n/**\r\n * Fire multiple tracking pixels\r\n */\r\nexport const fireTrackingPixels = (urls: string[]): void => {\r\n urls.forEach(fireTrackingPixel);\r\n};\r\n\r\n/**\r\n * Convert VAST ad to player-friendly format\r\n */\r\nexport const convertToAdInfo = (ad: VastAd): VastAdInfo => {\r\n return {\r\n id: ad.id,\r\n title: ad.title,\r\n duration: ad.duration,\r\n skipOffset: ad.skipOffset,\r\n clickThrough: ad.clickThrough,\r\n };\r\n};\r\n\r\nexport type { VastAd, VastMediaFile, VastResponse };\r\n","// useVast Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useState, useCallback, useRef, useEffect } from 'react';\r\nimport { VastConfig, VastAdInfo } from '../types';\r\nimport {\r\n fetchVastAd,\r\n selectBestMediaFile,\r\n fireTrackingPixels,\r\n convertToAdInfo,\r\n VastAd,\r\n} from '../utils/vast';\r\n\r\ninterface UseVastOptions {\r\n vastConfig?: VastConfig | VastConfig[];\r\n videoRef: React.RefObject<HTMLVideoElement>;\r\n onAdStart?: (ad: VastAdInfo) => void;\r\n onAdEnd?: () => void;\r\n onAdSkip?: () => void;\r\n onAdError?: (error: Error) => void;\r\n}\r\n\r\ninterface UseVastReturn {\r\n isAdPlaying: boolean;\r\n currentAd: VastAdInfo | null;\r\n adTimeRemaining: number;\r\n canSkip: boolean;\r\n skipAd: () => void;\r\n handleAdClick: () => void;\r\n checkForAd: (currentTime: number, duration: number) => void;\r\n}\r\n\r\nexport const useVast = ({\r\n vastConfig,\r\n videoRef,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n}: UseVastOptions): UseVastReturn => {\r\n const [isAdPlaying, setIsAdPlaying] = useState(false);\r\n const [currentAd, setCurrentAd] = useState<VastAdInfo | null>(null);\r\n const [adTimeRemaining, setAdTimeRemaining] = useState(0);\r\n const [canSkip, setCanSkip] = useState(false);\r\n\r\n const currentVastAd = useRef<VastAd | null>(null);\r\n const originalSrc = useRef<string>('');\r\n const originalTime = useRef<number>(0);\r\n const playedPositions = useRef<Set<string>>(new Set());\r\n const adIntervalRef = useRef<number | null>(null);\r\n\r\n // Normalize config to array\r\n const vastConfigs = Array.isArray(vastConfig) ? vastConfig : vastConfig ? [vastConfig] : [];\r\n\r\n // Play ad\r\n const playAd = useCallback(\r\n async (config: VastConfig) => {\r\n const video = videoRef.current;\r\n if (!video || isAdPlaying) return;\r\n\r\n try {\r\n const ad = await fetchVastAd(config);\r\n if (!ad) {\r\n onAdError?.(new Error('Failed to fetch VAST ad'));\r\n return;\r\n }\r\n\r\n const mediaFile = selectBestMediaFile(ad.mediaFiles);\r\n if (!mediaFile) {\r\n onAdError?.(new Error('No compatible media file found'));\r\n return;\r\n }\r\n\r\n // Store original video state\r\n originalSrc.current = video.currentSrc;\r\n originalTime.current = video.currentTime;\r\n\r\n // Fire impression pixels\r\n fireTrackingPixels(ad.impressionUrls);\r\n\r\n // Set up ad\r\n currentVastAd.current = ad;\r\n setCurrentAd(convertToAdInfo(ad));\r\n setIsAdPlaying(true);\r\n setAdTimeRemaining(ad.duration);\r\n setCanSkip(ad.skipOffset === undefined || ad.skipOffset === 0);\r\n\r\n // Change video source to ad\r\n video.src = mediaFile.url;\r\n video.currentTime = 0;\r\n await video.play();\r\n\r\n // Fire start tracking\r\n if (ad.trackingEvents.start) {\r\n fireTrackingPixels(ad.trackingEvents.start);\r\n }\r\n\r\n onAdStart?.(convertToAdInfo(ad));\r\n\r\n // Start ad timer\r\n adIntervalRef.current = window.setInterval(() => {\r\n const remaining = Math.max(0, ad.duration - video.currentTime);\r\n setAdTimeRemaining(remaining);\r\n\r\n // Check if can skip\r\n if (ad.skipOffset && video.currentTime >= ad.skipOffset) {\r\n setCanSkip(true);\r\n }\r\n\r\n // Track quartiles\r\n const percent = (video.currentTime / ad.duration) * 100;\r\n if (percent >= 25 && ad.trackingEvents.firstQuartile) {\r\n fireTrackingPixels(ad.trackingEvents.firstQuartile);\r\n }\r\n if (percent >= 50 && ad.trackingEvents.midpoint) {\r\n fireTrackingPixels(ad.trackingEvents.midpoint);\r\n }\r\n if (percent >= 75 && ad.trackingEvents.thirdQuartile) {\r\n fireTrackingPixels(ad.trackingEvents.thirdQuartile);\r\n }\r\n }, 250);\r\n\r\n // Handle ad completion\r\n const handleAdEnd = () => {\r\n if (ad.trackingEvents.complete) {\r\n fireTrackingPixels(ad.trackingEvents.complete);\r\n }\r\n endAd();\r\n };\r\n\r\n video.addEventListener('ended', handleAdEnd, { once: true });\r\n } catch (error) {\r\n onAdError?.(error instanceof Error ? error : new Error('Ad playback failed'));\r\n endAd();\r\n }\r\n },\r\n [videoRef, isAdPlaying, onAdStart, onAdError]\r\n );\r\n\r\n // End ad and restore original video\r\n const endAd = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n // Clear interval\r\n if (adIntervalRef.current) {\r\n clearInterval(adIntervalRef.current);\r\n adIntervalRef.current = null;\r\n }\r\n\r\n // Restore original video\r\n if (originalSrc.current) {\r\n video.src = originalSrc.current;\r\n video.currentTime = originalTime.current;\r\n video.play().catch(() => {});\r\n }\r\n\r\n setIsAdPlaying(false);\r\n setCurrentAd(null);\r\n setAdTimeRemaining(0);\r\n setCanSkip(false);\r\n currentVastAd.current = null;\r\n\r\n onAdEnd?.();\r\n }, [videoRef, onAdEnd]);\r\n\r\n // Skip ad\r\n const skipAd = useCallback(() => {\r\n if (!canSkip || !currentVastAd.current) return;\r\n\r\n const ad = currentVastAd.current;\r\n if (ad.trackingEvents.skip) {\r\n fireTrackingPixels(ad.trackingEvents.skip);\r\n }\r\n\r\n onAdSkip?.();\r\n endAd();\r\n }, [canSkip, endAd, onAdSkip]);\r\n\r\n // Handle ad click\r\n const handleAdClick = useCallback(() => {\r\n if (!currentVastAd.current) return;\r\n\r\n const ad = currentVastAd.current;\r\n if (ad.clickThrough) {\r\n window.open(ad.clickThrough, '_blank');\r\n }\r\n if (ad.clickTracking) {\r\n fireTrackingPixels(ad.clickTracking);\r\n }\r\n }, []);\r\n\r\n // Check for ad at position\r\n const checkForAd = useCallback(\r\n (currentTime: number, duration: number) => {\r\n if (isAdPlaying || vastConfigs.length === 0) return;\r\n\r\n vastConfigs.forEach((config) => {\r\n const position = config.position || 'preroll';\r\n const key = `${position}-${config.midrollTime || 0}`;\r\n\r\n if (playedPositions.current.has(key)) return;\r\n\r\n let shouldPlay = false;\r\n\r\n switch (position) {\r\n case 'preroll':\r\n shouldPlay = currentTime === 0;\r\n break;\r\n case 'midroll':\r\n if (config.midrollTime && currentTime >= config.midrollTime) {\r\n shouldPlay = true;\r\n }\r\n break;\r\n case 'postroll':\r\n shouldPlay = currentTime >= duration - 0.5;\r\n break;\r\n }\r\n\r\n if (shouldPlay) {\r\n playedPositions.current.add(key);\r\n playAd(config);\r\n }\r\n });\r\n },\r\n [vastConfigs, isAdPlaying, playAd]\r\n );\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (adIntervalRef.current) {\r\n clearInterval(adIntervalRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n return {\r\n isAdPlaying,\r\n currentAd,\r\n adTimeRemaining,\r\n canSkip,\r\n skipAd,\r\n handleAdClick,\r\n checkForAd,\r\n };\r\n};\r\n\r\nexport default useVast;\r\n","// Utility functions for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\n/**\r\n * Format time in seconds to MM:SS or HH:MM:SS format\r\n */\r\nexport const formatTime = (seconds: number): string => {\r\n if (!isFinite(seconds) || isNaN(seconds)) return '0:00';\r\n\r\n const hrs = Math.floor(seconds / 3600);\r\n const mins = Math.floor((seconds % 3600) / 60);\r\n const secs = Math.floor(seconds % 60);\r\n\r\n if (hrs > 0) {\r\n return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\r\n }\r\n\r\n return `${mins}:${secs.toString().padStart(2, '0')}`;\r\n};\r\n\r\n/**\r\n * Parse time string to seconds\r\n */\r\nexport const parseTime = (time: string): number => {\r\n const parts = time.split(':').map(Number);\r\n if (parts.length === 3) {\r\n return parts[0] * 3600 + parts[1] * 60 + parts[2];\r\n }\r\n if (parts.length === 2) {\r\n return parts[0] * 60 + parts[1];\r\n }\r\n return parts[0] || 0;\r\n};\r\n\r\n/**\r\n * Calculate percentage\r\n */\r\nexport const percentage = (value: number, total: number): number => {\r\n if (total === 0) return 0;\r\n return Math.min(100, Math.max(0, (value / total) * 100));\r\n};\r\n\r\n/**\r\n * Clamp a value between min and max\r\n */\r\nexport const clamp = (value: number, min: number, max: number): number => {\r\n return Math.min(max, Math.max(min, value));\r\n};\r\n\r\n/**\r\n * Throttle function execution\r\n */\r\nexport const throttle = <T extends (...args: unknown[]) => unknown>(\r\n func: T,\r\n limit: number\r\n): ((...args: Parameters<T>) => void) => {\r\n let inThrottle = false;\r\n return (...args: Parameters<T>) => {\r\n if (!inThrottle) {\r\n func(...args);\r\n inThrottle = true;\r\n setTimeout(() => (inThrottle = false), limit);\r\n }\r\n };\r\n};\r\n\r\n/**\r\n * Debounce function execution\r\n */\r\nexport const debounce = <T extends (...args: unknown[]) => unknown>(\r\n func: T,\r\n wait: number\r\n): ((...args: Parameters<T>) => void) => {\r\n let timeout: ReturnType<typeof setTimeout> | null = null;\r\n return (...args: Parameters<T>) => {\r\n if (timeout) clearTimeout(timeout);\r\n timeout = setTimeout(() => func(...args), wait);\r\n };\r\n};\r\n\r\n/**\r\n * Check if fullscreen is supported\r\n */\r\nexport const isFullscreenSupported = (): boolean => {\r\n return !!(\r\n document.fullscreenEnabled ||\r\n (document as Document & { webkitFullscreenEnabled?: boolean }).webkitFullscreenEnabled ||\r\n (document as Document & { mozFullScreenEnabled?: boolean }).mozFullScreenEnabled ||\r\n (document as Document & { msFullscreenEnabled?: boolean }).msFullscreenEnabled\r\n );\r\n};\r\n\r\n/**\r\n * Check if Picture-in-Picture is supported\r\n */\r\nexport const isPipSupported = (): boolean => {\r\n return 'pictureInPictureEnabled' in document && document.pictureInPictureEnabled;\r\n};\r\n\r\n/**\r\n * Get current fullscreen element\r\n */\r\nexport const getFullscreenElement = (): Element | null => {\r\n return (\r\n document.fullscreenElement ||\r\n (document as Document & { webkitFullscreenElement?: Element }).webkitFullscreenElement ||\r\n (document as Document & { mozFullScreenElement?: Element }).mozFullScreenElement ||\r\n (document as Document & { msFullscreenElement?: Element }).msFullscreenElement ||\r\n null\r\n );\r\n};\r\n\r\n/**\r\n * Request fullscreen on element\r\n */\r\nexport const requestFullscreen = async (element: HTMLElement): Promise<void> => {\r\n if (element.requestFullscreen) {\r\n await element.requestFullscreen();\r\n } else if ((element as HTMLElement & { webkitRequestFullscreen?: () => Promise<void> }).webkitRequestFullscreen) {\r\n await (element as HTMLElement & { webkitRequestFullscreen: () => Promise<void> }).webkitRequestFullscreen();\r\n } else if ((element as HTMLElement & { mozRequestFullScreen?: () => Promise<void> }).mozRequestFullScreen) {\r\n await (element as HTMLElement & { mozRequestFullScreen: () => Promise<void> }).mozRequestFullScreen();\r\n } else if ((element as HTMLElement & { msRequestFullscreen?: () => Promise<void> }).msRequestFullscreen) {\r\n await (element as HTMLElement & { msRequestFullscreen: () => Promise<void> }).msRequestFullscreen();\r\n }\r\n};\r\n\r\n/**\r\n * Exit fullscreen\r\n */\r\nexport const exitFullscreen = async (): Promise<void> => {\r\n if (document.exitFullscreen) {\r\n await document.exitFullscreen();\r\n } else if ((document as Document & { webkitExitFullscreen?: () => Promise<void> }).webkitExitFullscreen) {\r\n await (document as Document & { webkitExitFullscreen: () => Promise<void> }).webkitExitFullscreen();\r\n } else if ((document as Document & { mozCancelFullScreen?: () => Promise<void> }).mozCancelFullScreen) {\r\n await (document as Document & { mozCancelFullScreen: () => Promise<void> }).mozCancelFullScreen();\r\n } else if ((document as Document & { msExitFullscreen?: () => Promise<void> }).msExitFullscreen) {\r\n await (document as Document & { msExitFullscreen: () => Promise<void> }).msExitFullscreen();\r\n }\r\n};\r\n\r\n/**\r\n * Detect video type from URL\r\n */\r\nexport const detectVideoType = (url: string): string => {\r\n const extension = url.split('?')[0].split('.').pop()?.toLowerCase();\r\n \r\n const mimeTypes: Record<string, string> = {\r\n mp4: 'video/mp4',\r\n webm: 'video/webm',\r\n ogg: 'video/ogg',\r\n ogv: 'video/ogg',\r\n m3u8: 'application/x-mpegURL',\r\n mpd: 'application/dash+xml',\r\n mov: 'video/quicktime',\r\n avi: 'video/x-msvideo',\r\n mkv: 'video/x-matroska',\r\n };\r\n\r\n return mimeTypes[extension || ''] || 'video/mp4';\r\n};\r\n\r\n/**\r\n * Check if HLS is natively supported\r\n */\r\nexport const isHlsNativelySupported = (): boolean => {\r\n const video = document.createElement('video');\r\n return video.canPlayType('application/vnd.apple.mpegurl') !== '';\r\n};\r\n\r\n/**\r\n * Generate unique ID\r\n */\r\nexport const generateId = (): string => {\r\n return `plex-${Math.random().toString(36).substring(2, 11)}`;\r\n};\r\n\r\n/**\r\n * Check if device is mobile\r\n */\r\nexport const isMobile = (): boolean => {\r\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\r\n navigator.userAgent\r\n );\r\n};\r\n\r\n/**\r\n * Check if device is touch-enabled\r\n */\r\nexport const isTouchDevice = (): boolean => {\r\n return 'ontouchstart' in window || navigator.maxTouchPoints > 0;\r\n};\r\n\r\n/**\r\n * Parse buffered time ranges\r\n */\r\nexport const getBufferedEnd = (video: HTMLVideoElement): number => {\r\n if (video.buffered.length === 0) return 0;\r\n \r\n const currentTime = video.currentTime;\r\n for (let i = 0; i < video.buffered.length; i++) {\r\n if (video.buffered.start(i) <= currentTime && video.buffered.end(i) >= currentTime) {\r\n return video.buffered.end(i);\r\n }\r\n }\r\n \r\n return video.buffered.end(video.buffered.length - 1);\r\n};\r\n\r\n/**\r\n * Check browser support for specific video format\r\n */\r\nexport const canPlayType = (type: string): boolean => {\r\n const video = document.createElement('video');\r\n const result = video.canPlayType(type);\r\n return result === 'probably' || result === 'maybe';\r\n};\r\n","// PlexVideo Player - Main Component with Tailwind CSS\r\n// © FRAMESET STUDIO\r\n\r\nimport React, {\r\n useRef,\r\n useState,\r\n useEffect,\r\n useCallback,\r\n forwardRef,\r\n useImperativeHandle,\r\n useMemo,\r\n} from 'react';\r\nimport { useVast } from '../hooks/useVast';\r\nimport { formatTime, throttle } from '../utils/helpers';\r\nimport type {\r\n PlexVideoPlayerProps,\r\n PlexVideoPlayerRef,\r\n VideoSource,\r\n} from '../types';\r\n\r\n// Icons as inline SVG components\r\nconst PlayIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n);\r\n\r\nconst PauseIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\r\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\r\n </svg>\r\n);\r\n\r\nconst VolumeHighIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\r\n </svg>\r\n);\r\n\r\nconst VolumeMuteIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\r\n </svg>\r\n);\r\n\r\nconst FullscreenIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\" />\r\n </svg>\r\n);\r\n\r\nconst FullscreenExitIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\" />\r\n </svg>\r\n);\r\n\r\nconst PipIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z\" />\r\n </svg>\r\n);\r\n\r\nconst SettingsIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z\" />\r\n </svg>\r\n);\r\n\r\nconst RepeatIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z\" />\r\n </svg>\r\n);\r\n\r\nconst CopyIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\" />\r\n </svg>\r\n);\r\n\r\nconst SpeedIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44zm-9.79 6.84a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z\" />\r\n </svg>\r\n);\r\n\r\nconst PlaylistIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z\" />\r\n </svg>\r\n);\r\n\r\nconst CastIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z\" />\r\n </svg>\r\n);\r\n\r\nconst CloseIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n);\r\n\r\n// Extended props with playlist support\r\nexport interface PlaylistItem {\r\n id: string;\r\n src: string;\r\n title: string;\r\n poster?: string;\r\n duration?: number;\r\n}\r\n\r\ninterface ExtendedPlayerProps extends PlexVideoPlayerProps {\r\n playlist?: PlaylistItem[];\r\n onPlaylistItemChange?: (index: number, item: PlaylistItem) => void;\r\n}\r\n\r\nexport const PlexVideoPlayer = forwardRef<PlexVideoPlayerRef, ExtendedPlayerProps>(\r\n (props, ref) => {\r\n const {\r\n src,\r\n poster,\r\n autoPlay = false,\r\n muted = false,\r\n loop = false,\r\n controls = true,\r\n width = '100%',\r\n className = '',\r\n style = {},\r\n pip = true,\r\n fullscreen = true,\r\n playbackSpeed = true,\r\n playbackSpeeds = [0.5, 0.75, 1, 1.25, 1.5, 2],\r\n volume: volumeEnabled = true,\r\n initialVolume = 1,\r\n textTracks = [],\r\n vast,\r\n accentColor = '#f5c518',\r\n controlsTimeout = 3000,\r\n doubleClickFullscreen = true,\r\n clickToPlay = true,\r\n preload = 'metadata',\r\n playlist = [],\r\n onPlay,\r\n onPause,\r\n onEnded,\r\n onTimeUpdate,\r\n onProgress,\r\n onVolumeChange,\r\n onFullscreenChange,\r\n onPipChange,\r\n onQualityChange,\r\n onError,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n onReady,\r\n onPlaylistItemChange,\r\n } = props;\r\n\r\n // Refs\r\n const videoRef = useRef<HTMLVideoElement>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const progressRef = useRef<HTMLDivElement>(null);\r\n const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null);\r\n const lastClickRef = useRef<number>(0);\r\n\r\n // State\r\n const [isPlaying, setIsPlaying] = useState(false);\r\n const [currentTime, setCurrentTime] = useState(0);\r\n const [duration, setDuration] = useState(0);\r\n const [buffered, setBuffered] = useState(0);\r\n const [volumeState, setVolumeState] = useState(muted ? 0 : initialVolume);\r\n const [isMuted, setIsMuted] = useState(muted);\r\n const [isFullscreen, setIsFullscreen] = useState(false);\r\n const [isPipActive, setIsPipActive] = useState(false);\r\n const [isLoading, setIsLoading] = useState(true);\r\n const [error, setError] = useState<MediaError | null>(null);\r\n const [showControls, setShowControls] = useState(true);\r\n const [playbackRate, setPlaybackRateState] = useState(1);\r\n const [isLoop, setIsLoop] = useState(loop);\r\n const [showSettings, setShowSettings] = useState(false);\r\n const [showPlaylist, setShowPlaylist] = useState(false);\r\n const [hoverTime, setHoverTime] = useState<{ time: number; x: number } | null>(null);\r\n const [previewFrame, setPreviewFrame] = useState<string | null>(null);\r\n const [isMobile, setIsMobile] = useState(false);\r\n const [isTouching, setIsTouching] = useState(false);\r\n const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null);\r\n const previewCanvasRef = useRef<HTMLCanvasElement>(null);\r\n const previewVideoRef = useRef<HTMLVideoElement>(null);\r\n \r\n // Detect mobile device\r\n useEffect(() => {\r\n const checkMobile = () => {\r\n const mobile = window.matchMedia('(max-width: 768px)').matches || \r\n window.matchMedia('(pointer: coarse)').matches ||\r\n 'ontouchstart' in window;\r\n setIsMobile(mobile);\r\n };\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n \r\n // Context menu state\r\n const [contextMenu, setContextMenu] = useState({ x: 0, y: 0, visible: false });\r\n \r\n // Playlist state\r\n const [currentPlaylistIndex, setCurrentPlaylistIndex] = useState(0);\r\n\r\n // Parse sources\r\n const sources = useMemo<VideoSource[]>(() => {\r\n if (typeof src === 'string') {\r\n return [{ src, type: 'video/mp4' }];\r\n }\r\n return src as VideoSource[];\r\n }, [src]);\r\n\r\n // Get current video source\r\n const currentVideoSrc = useMemo(() => {\r\n if (playlist.length > 0 && playlist[currentPlaylistIndex]) {\r\n return playlist[currentPlaylistIndex].src;\r\n }\r\n return sources[0]?.src || '';\r\n }, [playlist, currentPlaylistIndex, sources]);\r\n\r\n const currentPoster = useMemo(() => {\r\n if (playlist.length > 0 && playlist[currentPlaylistIndex]?.poster) {\r\n return playlist[currentPlaylistIndex].poster;\r\n }\r\n return poster;\r\n }, [playlist, currentPlaylistIndex, poster]);\r\n\r\n // VAST ads hook\r\n const {\r\n currentAd,\r\n isAdPlaying,\r\n adTimeRemaining,\r\n canSkip,\r\n skipAd,\r\n handleAdClick,\r\n checkForAd,\r\n } = useVast({\r\n vastConfig: vast,\r\n videoRef,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n });\r\n\r\n // Player control functions\r\n const play = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n try {\r\n await video.play();\r\n } catch (err) {\r\n console.error('Play error:', err);\r\n }\r\n }, []);\r\n\r\n const pause = useCallback(() => {\r\n videoRef.current?.pause();\r\n }, []);\r\n\r\n const stop = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.pause();\r\n video.currentTime = 0;\r\n }, []);\r\n\r\n const togglePlay = useCallback(() => {\r\n if (videoRef.current?.paused) {\r\n play();\r\n } else {\r\n pause();\r\n }\r\n }, [play, pause]);\r\n\r\n const seek = useCallback((time: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.currentTime = Math.max(0, Math.min(time, video.duration || 0));\r\n }, []);\r\n\r\n const setVolume = useCallback((value: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n const newVolume = Math.max(0, Math.min(1, value));\r\n video.volume = newVolume;\r\n video.muted = newVolume === 0;\r\n setVolumeState(newVolume);\r\n setIsMuted(newVolume === 0);\r\n }, []);\r\n\r\n const mute = useCallback(() => {\r\n if (videoRef.current) {\r\n videoRef.current.muted = true;\r\n setIsMuted(true);\r\n }\r\n }, []);\r\n\r\n const unmute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.muted = false;\r\n if (video.volume === 0) {\r\n video.volume = 0.5;\r\n setVolumeState(0.5);\r\n }\r\n setIsMuted(false);\r\n }, []);\r\n\r\n const toggleMute = useCallback(() => {\r\n if (videoRef.current?.muted) {\r\n unmute();\r\n } else {\r\n mute();\r\n }\r\n }, [mute, unmute]);\r\n\r\n const toggleLoop = useCallback(() => {\r\n if (videoRef.current) {\r\n videoRef.current.loop = !videoRef.current.loop;\r\n setIsLoop(videoRef.current.loop);\r\n }\r\n }, []);\r\n\r\n const enterFullscreen = useCallback(async () => {\r\n try {\r\n await containerRef.current?.requestFullscreen();\r\n setIsFullscreen(true);\r\n } catch (err) {\r\n console.error('Fullscreen error:', err);\r\n }\r\n }, []);\r\n\r\n const exitFullscreen = useCallback(async () => {\r\n try {\r\n if (document.fullscreenElement) {\r\n await document.exitFullscreen();\r\n setIsFullscreen(false);\r\n }\r\n } catch (err) {\r\n console.error('Exit fullscreen error:', err);\r\n }\r\n }, []);\r\n\r\n const toggleFullscreen = useCallback(async () => {\r\n if (document.fullscreenElement) {\r\n await exitFullscreen();\r\n } else {\r\n await enterFullscreen();\r\n }\r\n }, [enterFullscreen, exitFullscreen]);\r\n\r\n const enterPip = useCallback(async () => {\r\n try {\r\n await videoRef.current?.requestPictureInPicture();\r\n setIsPipActive(true);\r\n } catch (err) {\r\n console.error('PiP error:', err);\r\n }\r\n }, []);\r\n\r\n const exitPip = useCallback(async () => {\r\n try {\r\n if (document.pictureInPictureElement) {\r\n await document.exitPictureInPicture();\r\n setIsPipActive(false);\r\n }\r\n } catch (err) {\r\n console.error('Exit PiP error:', err);\r\n }\r\n }, []);\r\n\r\n const togglePip = useCallback(async () => {\r\n if (document.pictureInPictureElement) {\r\n await exitPip();\r\n } else {\r\n await enterPip();\r\n }\r\n }, [enterPip, exitPip]);\r\n\r\n const setPlaybackRate = useCallback((rate: number) => {\r\n if (videoRef.current) {\r\n videoRef.current.playbackRate = rate;\r\n setPlaybackRateState(rate);\r\n setShowSettings(false);\r\n }\r\n }, []);\r\n\r\n const setQuality = useCallback((qualityLabel: string) => {\r\n const source = sources.find(s => s.label === qualityLabel || s.quality === qualityLabel);\r\n if (source && videoRef.current) {\r\n const video = videoRef.current;\r\n const wasPlaying = !video.paused;\r\n const time = video.currentTime;\r\n video.src = source.src;\r\n video.currentTime = time;\r\n if (wasPlaying) video.play();\r\n onQualityChange?.(qualityLabel);\r\n }\r\n }, [sources, onQualityChange]);\r\n\r\n // Playlist functions\r\n const playNextInPlaylist = useCallback(() => {\r\n if (playlist.length === 0) return;\r\n const nextIndex = (currentPlaylistIndex + 1) % playlist.length;\r\n setCurrentPlaylistIndex(nextIndex);\r\n onPlaylistItemChange?.(nextIndex, playlist[nextIndex]);\r\n setTimeout(() => play(), 100);\r\n }, [currentPlaylistIndex, playlist, onPlaylistItemChange, play]);\r\n\r\n // Context menu handlers\r\n const handleContextMenu = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n const rect = containerRef.current?.getBoundingClientRect();\r\n if (rect) {\r\n setContextMenu({ \r\n x: e.clientX - rect.left, \r\n y: e.clientY - rect.top, \r\n visible: true \r\n });\r\n }\r\n }, []);\r\n\r\n const closeContextMenu = useCallback(() => {\r\n setContextMenu(prev => ({ ...prev, visible: false }));\r\n }, []);\r\n\r\n const copyVideoUrl = useCallback(() => {\r\n navigator.clipboard.writeText(currentVideoSrc);\r\n closeContextMenu();\r\n }, [currentVideoSrc, closeContextMenu]);\r\n\r\n // Progress bar click handler\r\n const handleProgressClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const percent = (e.clientX - rect.left) / rect.width;\r\n seek(percent * duration);\r\n }\r\n }, [duration, seek]);\r\n\r\n // Progress bar hover handler for time preview with frame capture\r\n const handleProgressHover = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const percent = (e.clientX - rect.left) / rect.width;\r\n const time = Math.max(0, Math.min(duration, percent * duration));\r\n setHoverTime({ time, x: e.clientX - rect.left });\r\n \r\n // Generate frame preview using hidden video element\r\n if (previewVideoRef.current && previewCanvasRef.current) {\r\n const previewVideo = previewVideoRef.current;\r\n const canvas = previewCanvasRef.current;\r\n const ctx = canvas.getContext('2d');\r\n \r\n if (Math.abs(previewVideo.currentTime - time) > 1) {\r\n previewVideo.currentTime = time;\r\n }\r\n \r\n previewVideo.onseeked = () => {\r\n if (ctx) {\r\n canvas.width = 160;\r\n canvas.height = 90;\r\n ctx.drawImage(previewVideo, 0, 0, 160, 90);\r\n setPreviewFrame(canvas.toDataURL('image/jpeg', 0.5));\r\n }\r\n };\r\n }\r\n }\r\n }, [duration]);\r\n\r\n const handleProgressLeave = useCallback(() => {\r\n setHoverTime(null);\r\n setPreviewFrame(null);\r\n }, []);\r\n\r\n // Hide controls logic\r\n const hideControls = useCallback(() => {\r\n if (isPlaying && !showSettings && !showPlaylist) {\r\n setShowControls(false);\r\n }\r\n }, [isPlaying, showSettings, showPlaylist]);\r\n\r\n const showControlsTemporarily = useCallback(() => {\r\n setShowControls(true);\r\n if (controlsTimeoutRef.current) {\r\n clearTimeout(controlsTimeoutRef.current);\r\n }\r\n if (isPlaying) {\r\n controlsTimeoutRef.current = setTimeout(hideControls, isMobile ? 4000 : controlsTimeout);\r\n }\r\n }, [isPlaying, controlsTimeout, hideControls, isMobile]);\r\n\r\n const handleMouseMove = useMemo(\r\n () => throttle(showControlsTemporarily, 200),\r\n [showControlsTemporarily]\r\n );\r\n\r\n // Touch handlers for mobile\r\n const handleTouchStart = useCallback((e: React.TouchEvent) => {\r\n const touch = e.touches[0];\r\n touchStartRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };\r\n setIsTouching(true);\r\n showControlsTemporarily();\r\n }, [showControlsTemporarily]);\r\n\r\n const handleTouchMove = useCallback((e: React.TouchEvent) => {\r\n if (!touchStartRef.current) return;\r\n const touch = e.touches[0];\r\n const deltaX = touch.clientX - touchStartRef.current.x;\r\n const deltaY = touch.clientY - touchStartRef.current.y;\r\n \r\n // Horizontal swipe for seeking (only if significant horizontal movement)\r\n if (Math.abs(deltaX) > Math.abs(deltaY) * 2 && Math.abs(deltaX) > 30) {\r\n e.preventDefault();\r\n const seekDelta = (deltaX / window.innerWidth) * duration * 0.5;\r\n const newTime = Math.max(0, Math.min(duration, currentTime + seekDelta));\r\n // Show preview but don't seek yet\r\n setHoverTime({ time: newTime, x: touch.clientX });\r\n }\r\n }, [duration, currentTime]);\r\n\r\n const handleTouchEnd = useCallback((e: React.TouchEvent) => {\r\n if (!touchStartRef.current) return;\r\n \r\n const touchDuration = Date.now() - touchStartRef.current.time;\r\n const touch = e.changedTouches[0];\r\n const deltaX = touch.clientX - touchStartRef.current.x;\r\n const deltaY = touch.clientY - touchStartRef.current.y;\r\n \r\n // Quick tap - toggle play/controls\r\n if (touchDuration < 200 && Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {\r\n if (showControls) {\r\n togglePlay();\r\n } else {\r\n showControlsTemporarily();\r\n }\r\n }\r\n // Horizontal swipe - seek\r\n else if (Math.abs(deltaX) > Math.abs(deltaY) * 2 && Math.abs(deltaX) > 50) {\r\n const seekDelta = (deltaX / window.innerWidth) * duration * 0.5;\r\n seek(currentTime + seekDelta);\r\n }\r\n // Double tap on sides - skip 10s\r\n else if (touchDuration < 300) {\r\n const rect = containerRef.current?.getBoundingClientRect();\r\n if (rect) {\r\n const tapX = touch.clientX - rect.left;\r\n if (tapX < rect.width * 0.3) {\r\n seek(currentTime - 10);\r\n } else if (tapX > rect.width * 0.7) {\r\n seek(currentTime + 10);\r\n }\r\n }\r\n }\r\n \r\n touchStartRef.current = null;\r\n setIsTouching(false);\r\n setHoverTime(null);\r\n }, [showControls, togglePlay, showControlsTemporarily, duration, currentTime, seek]);\r\n\r\n // Video click handler\r\n const handleVideoClick = useCallback(() => {\r\n closeContextMenu();\r\n if (isMobile) return; // Handle via touch events on mobile\r\n if (!clickToPlay) return;\r\n \r\n const now = Date.now();\r\n if (doubleClickFullscreen && now - lastClickRef.current < 300) {\r\n toggleFullscreen();\r\n } else {\r\n togglePlay();\r\n }\r\n lastClickRef.current = now;\r\n }, [clickToPlay, doubleClickFullscreen, togglePlay, toggleFullscreen, closeContextMenu, isMobile]);\r\n\r\n // Video event handlers\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const handlePlay = () => { setIsPlaying(true); onPlay?.(); };\r\n const handlePause = () => { setIsPlaying(false); onPause?.(); };\r\n const handleEnded = () => {\r\n setIsPlaying(false);\r\n onEnded?.();\r\n if (playlist.length > 0) playNextInPlaylist();\r\n };\r\n const handleTimeUpdate = () => {\r\n setCurrentTime(video.currentTime);\r\n onTimeUpdate?.(video.currentTime);\r\n checkForAd(video.currentTime, video.duration);\r\n };\r\n const handleDurationChange = () => setDuration(video.duration);\r\n const handleProgressEvent = () => {\r\n if (video.buffered.length > 0) {\r\n const bufferedEnd = video.buffered.end(video.buffered.length - 1);\r\n setBuffered((bufferedEnd / video.duration) * 100);\r\n onProgress?.((bufferedEnd / video.duration) * 100);\r\n }\r\n };\r\n const handleVolumeChangeEvent = () => {\r\n setVolumeState(video.volume);\r\n setIsMuted(video.muted);\r\n onVolumeChange?.(video.volume, video.muted);\r\n };\r\n const handleLoadedMetadata = () => {\r\n setDuration(video.duration);\r\n setIsLoading(false);\r\n onReady?.();\r\n };\r\n const handleWaiting = () => setIsLoading(true);\r\n const handlePlaying = () => setIsLoading(false);\r\n const handleCanPlay = () => setIsLoading(false);\r\n const handleErrorEvent = () => {\r\n setError(video.error);\r\n setIsLoading(false);\r\n onError?.(video.error);\r\n };\r\n\r\n video.addEventListener('play', handlePlay);\r\n video.addEventListener('pause', handlePause);\r\n video.addEventListener('ended', handleEnded);\r\n video.addEventListener('timeupdate', handleTimeUpdate);\r\n video.addEventListener('durationchange', handleDurationChange);\r\n video.addEventListener('progress', handleProgressEvent);\r\n video.addEventListener('volumechange', handleVolumeChangeEvent);\r\n video.addEventListener('loadedmetadata', handleLoadedMetadata);\r\n video.addEventListener('waiting', handleWaiting);\r\n video.addEventListener('playing', handlePlaying);\r\n video.addEventListener('canplay', handleCanPlay);\r\n video.addEventListener('error', handleErrorEvent);\r\n\r\n return () => {\r\n video.removeEventListener('play', handlePlay);\r\n video.removeEventListener('pause', handlePause);\r\n video.removeEventListener('ended', handleEnded);\r\n video.removeEventListener('timeupdate', handleTimeUpdate);\r\n video.removeEventListener('durationchange', handleDurationChange);\r\n video.removeEventListener('progress', handleProgressEvent);\r\n video.removeEventListener('volumechange', handleVolumeChangeEvent);\r\n video.removeEventListener('loadedmetadata', handleLoadedMetadata);\r\n video.removeEventListener('waiting', handleWaiting);\r\n video.removeEventListener('playing', handlePlaying);\r\n video.removeEventListener('canplay', handleCanPlay);\r\n video.removeEventListener('error', handleErrorEvent);\r\n };\r\n }, [onPlay, onPause, onEnded, onTimeUpdate, onProgress, onVolumeChange, onReady, onError, checkForAd, playlist.length, playNextInPlaylist]);\r\n\r\n // Fullscreen change listener\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!document.fullscreenElement;\r\n setIsFullscreen(isFs);\r\n onFullscreenChange?.(isFs);\r\n };\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n }, [onFullscreenChange]);\r\n\r\n // PiP change listener\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n const handleEnterPip = () => { setIsPipActive(true); onPipChange?.(true); };\r\n const handleLeavePip = () => { setIsPipActive(false); onPipChange?.(false); };\r\n video.addEventListener('enterpictureinpicture', handleEnterPip);\r\n video.addEventListener('leavepictureinpicture', handleLeavePip);\r\n return () => {\r\n video.removeEventListener('enterpictureinpicture', handleEnterPip);\r\n video.removeEventListener('leavepictureinpicture', handleLeavePip);\r\n };\r\n }, [onPipChange]);\r\n\r\n // Keyboard shortcuts\r\n useEffect(() => {\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n if (!containerRef.current?.contains(document.activeElement)) return;\r\n \r\n switch (e.key) {\r\n case ' ':\r\n case 'k':\r\n e.preventDefault();\r\n togglePlay();\r\n break;\r\n case 'm':\r\n e.preventDefault();\r\n toggleMute();\r\n break;\r\n case 'f':\r\n e.preventDefault();\r\n toggleFullscreen();\r\n break;\r\n case 'ArrowLeft':\r\n e.preventDefault();\r\n seek(currentTime - 10);\r\n break;\r\n case 'ArrowRight':\r\n e.preventDefault();\r\n seek(currentTime + 10);\r\n break;\r\n case 'ArrowUp':\r\n e.preventDefault();\r\n setVolume(volumeState + 0.1);\r\n break;\r\n case 'ArrowDown':\r\n e.preventDefault();\r\n setVolume(volumeState - 0.1);\r\n break;\r\n }\r\n };\r\n \r\n document.addEventListener('keydown', handleKeyDown);\r\n return () => document.removeEventListener('keydown', handleKeyDown);\r\n }, [togglePlay, toggleMute, toggleFullscreen, seek, setVolume, currentTime, volumeState]);\r\n\r\n // Cleanup\r\n useEffect(() => {\r\n return () => {\r\n if (controlsTimeoutRef.current) clearTimeout(controlsTimeoutRef.current);\r\n };\r\n }, []);\r\n\r\n // Close context menu on click outside\r\n useEffect(() => {\r\n const handleClick = () => closeContextMenu();\r\n if (contextMenu.visible) {\r\n document.addEventListener('click', handleClick);\r\n return () => document.removeEventListener('click', handleClick);\r\n }\r\n }, [contextMenu.visible, closeContextMenu]);\r\n\r\n // Imperative handle\r\n useImperativeHandle(ref, () => ({\r\n play, pause, stop, seek, setVolume, mute, unmute, toggleMute,\r\n enterFullscreen, exitFullscreen, toggleFullscreen,\r\n enterPip, exitPip, togglePip, setPlaybackRate, setQuality,\r\n getCurrentTime: () => videoRef.current?.currentTime || 0,\r\n getDuration: () => videoRef.current?.duration || 0,\r\n getVolume: () => videoRef.current?.volume || 0,\r\n isMuted: () => videoRef.current?.muted ?? true,\r\n isPlaying: () => !videoRef.current?.paused,\r\n isFullscreen: () => isFullscreen,\r\n isPip: () => isPipActive,\r\n getVideoElement: () => videoRef.current,\r\n }), [play, pause, stop, seek, setVolume, mute, unmute, toggleMute, enterFullscreen, exitFullscreen, toggleFullscreen, enterPip, exitPip, togglePip, setPlaybackRate, setQuality, isFullscreen, isPipActive]);\r\n\r\n const progressPercent = duration ? (currentTime / duration) * 100 : 0;\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ \r\n position: isFullscreen ? 'fixed' : 'relative',\r\n inset: isFullscreen ? 0 : undefined,\r\n zIndex: isFullscreen ? 9999 : undefined,\r\n width, \r\n aspectRatio: isFullscreen ? 'unset' : '16/9',\r\n height: isFullscreen ? '100%' : undefined,\r\n backgroundColor: '#000',\r\n overflow: 'hidden',\r\n userSelect: 'none',\r\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n WebkitTapHighlightColor: 'transparent',\r\n touchAction: 'manipulation',\r\n ...style \r\n }}\r\n onMouseMove={!isMobile ? handleMouseMove : undefined}\r\n onMouseLeave={() => !isMobile && isPlaying && setShowControls(false)}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onContextMenu={handleContextMenu}\r\n tabIndex={0}\r\n >\r\n {/* Video Element */}\r\n <video\r\n ref={videoRef}\r\n style={{ width: '100%', height: '100%', objectFit: 'contain' }}\r\n src={currentVideoSrc}\r\n poster={currentPoster}\r\n autoPlay={autoPlay}\r\n muted={muted}\r\n loop={isLoop}\r\n preload={preload}\r\n playsInline\r\n onClick={handleVideoClick}\r\n >\r\n {textTracks.map((track, i) => (\r\n <track key={i} kind={track.kind} src={track.src} srcLang={track.srclang} label={track.label} default={track.default} />\r\n ))}\r\n </video>\r\n\r\n {/* Loading Spinner */}\r\n {isLoading && !error && (\r\n <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>\r\n <div \r\n style={{ \r\n width: 48, \r\n height: 48, \r\n border: '4px solid transparent', \r\n borderTopColor: accentColor, \r\n borderRadius: '50%',\r\n animation: 'spin 1s linear infinite',\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Keyframes for spinner */}\r\n <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>\r\n\r\n {/* Error Display */}\r\n {error && (\r\n <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.9)', color: '#fff' }}>\r\n <svg style={{ width: 64, height: 64, marginBottom: 16 }} fill={accentColor} viewBox=\"0 0 24 24\">\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" />\r\n </svg>\r\n <p style={{ fontSize: 18, marginBottom: 8 }}>Error loading video</p>\r\n <button \r\n onClick={() => { setError(null); videoRef.current?.load(); }}\r\n style={{ \r\n padding: '8px 24px', \r\n borderRadius: 4, \r\n border: 'none',\r\n backgroundColor: accentColor, \r\n color: '#000', \r\n fontWeight: 500,\r\n cursor: 'pointer',\r\n }}\r\n >\r\n Retry\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Ad Overlay */}\r\n {isAdPlaying && currentAd && (\r\n <div style={{ position: 'absolute', inset: 0, zIndex: 20 }}>\r\n <div style={{ position: 'absolute', top: 16, left: 16, display: 'flex', alignItems: 'center', gap: 12, padding: '8px 12px', background: 'rgba(0,0,0,0.7)', borderRadius: 4, color: '#fff', fontSize: 14 }}>\r\n <span style={{ padding: '2px 8px', borderRadius: 4, fontSize: 11, fontWeight: 700, backgroundColor: accentColor, color: '#000' }}>AD</span>\r\n <span>{formatTime(adTimeRemaining)} remaining</span>\r\n </div>\r\n <button\r\n onClick={canSkip ? skipAd : undefined}\r\n disabled={!canSkip}\r\n style={{ \r\n position: 'absolute', \r\n bottom: 80, \r\n right: 16, \r\n padding: '8px 20px', \r\n border: '1px solid #fff', \r\n borderRadius: 4, \r\n background: 'transparent',\r\n color: '#fff',\r\n cursor: canSkip ? 'pointer' : 'not-allowed',\r\n opacity: canSkip ? 1 : 0.5,\r\n }}\r\n >\r\n {canSkip ? 'Skip Ad' : `Skip in ${Math.ceil(adTimeRemaining)}s`}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Context Menu */}\r\n {contextMenu.visible && (\r\n <div \r\n style={{ \r\n position: 'absolute', \r\n left: contextMenu.x, \r\n top: contextMenu.y,\r\n zIndex: 50,\r\n minWidth: 200,\r\n background: 'rgba(26,26,26,0.95)',\r\n backdropFilter: 'blur(8px)',\r\n borderRadius: 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n overflow: 'hidden',\r\n }}\r\n onClick={e => e.stopPropagation()}\r\n >\r\n {/* Play/Pause */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { togglePlay(); closeContextMenu(); }}\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n <span style={{ flex: 1 }}>{isPlaying ? 'Pause' : 'Play'}</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>Space</span>\r\n </button>\r\n \r\n {/* Mute */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleMute(); closeContextMenu(); }}\r\n >\r\n {isMuted ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n <span style={{ flex: 1 }}>{isMuted ? 'Unmute' : 'Mute'}</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>M</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* Loop */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleLoop(); closeContextMenu(); }}\r\n >\r\n <RepeatIcon />\r\n <span style={{ flex: 1 }}>{isLoop ? 'Disable Loop' : 'Enable Loop'}</span>\r\n {isLoop && <span style={{ color: accentColor }}>✓</span>}\r\n </button>\r\n\r\n {/* Speed */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => {\r\n const speeds = [0.5, 0.75, 1, 1.25, 1.5, 2];\r\n const idx = speeds.indexOf(playbackRate);\r\n setPlaybackRate(speeds[(idx + 1) % speeds.length]);\r\n closeContextMenu();\r\n }}\r\n >\r\n <SpeedIcon />\r\n <span style={{ flex: 1 }}>Speed: {playbackRate}x</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* PiP */}\r\n {pip && document.pictureInPictureEnabled && (\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { togglePip(); closeContextMenu(); }}\r\n >\r\n <PipIcon />\r\n <span style={{ flex: 1 }}>Picture in Picture</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>P</span>\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleFullscreen(); closeContextMenu(); }}\r\n >\r\n <FullscreenIcon />\r\n <span style={{ flex: 1 }}>Fullscreen</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>F</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* Copy URL */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={copyVideoUrl}\r\n >\r\n <CopyIcon />\r\n <span style={{ flex: 1 }}>Copy Video URL</span>\r\n </button>\r\n\r\n {/* Footer */}\r\n <div style={{ padding: '8px 16px', fontSize: 10, color: 'rgba(255,255,255,0.4)', textAlign: 'center', borderTop: '1px solid rgba(255,255,255,0.1)' }}>\r\n PlexVideo Player © FRAMESET STUDIO\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Controls */}\r\n {controls && !isAdPlaying && (\r\n <div \r\n style={{\r\n position: 'absolute',\r\n bottom: 0,\r\n left: 0,\r\n right: 0,\r\n background: 'linear-gradient(to top, rgba(0,0,0,0.9) 0%, transparent 100%)',\r\n padding: isMobile ? '12px 12px 16px' : 16,\r\n paddingTop: isMobile ? 40 : 16,\r\n transition: 'opacity 0.3s',\r\n opacity: showControls ? 1 : 0,\r\n pointerEvents: showControls ? 'auto' : 'none',\r\n }}\r\n >\r\n {/* Progress Bar Container */}\r\n <div \r\n ref={progressRef}\r\n onClick={handleProgressClick}\r\n onMouseMove={!isMobile ? handleProgressHover : undefined}\r\n onMouseLeave={!isMobile ? handleProgressLeave : undefined}\r\n onTouchMove={(e) => {\r\n if (isMobile) {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const touch = e.touches[0];\r\n const percent = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width));\r\n setHoverTime({ time: percent * duration, x: touch.clientX - rect.left });\r\n }\r\n }\r\n }}\r\n onTouchEnd={(e) => {\r\n if (isMobile && hoverTime) {\r\n seek(hoverTime.time);\r\n setHoverTime(null);\r\n }\r\n }}\r\n style={{\r\n position: 'relative',\r\n height: isMobile ? 12 : 8,\r\n background: 'rgba(255,255,255,0.3)',\r\n borderRadius: isMobile ? 6 : 4,\r\n marginBottom: isMobile ? 12 : 16,\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {/* Buffered bar */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n height: '100%',\r\n width: `${buffered}%`,\r\n background: 'rgba(255,255,255,0.5)',\r\n borderRadius: isMobile ? 6 : 4,\r\n }}\r\n />\r\n {/* Played bar */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n height: '100%',\r\n width: `${progressPercent}%`,\r\n background: accentColor,\r\n borderRadius: isMobile ? 6 : 4,\r\n }}\r\n />\r\n {/* Handle */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: `${progressPercent}%`,\r\n transform: 'translate(-50%, -50%)',\r\n width: isMobile ? 20 : 16,\r\n height: isMobile ? 20 : 16,\r\n borderRadius: '50%',\r\n background: accentColor,\r\n boxShadow: '0 2px 8px rgba(0,0,0,0.5)',\r\n }}\r\n />\r\n {/* Time Preview Tooltip with Frame */}\r\n {hoverTime && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n bottom: '100%',\r\n left: hoverTime.x,\r\n transform: 'translateX(-50%)',\r\n marginBottom: 12,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n pointerEvents: 'none',\r\n }}\r\n >\r\n {/* Frame Preview */}\r\n {previewFrame && (\r\n <div\r\n style={{\r\n width: 160,\r\n height: 90,\r\n borderRadius: 6,\r\n overflow: 'hidden',\r\n marginBottom: 4,\r\n boxShadow: '0 4px 16px rgba(0,0,0,0.6)',\r\n border: `2px solid ${accentColor}`,\r\n }}\r\n >\r\n <img src={previewFrame} alt=\"\" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />\r\n </div>\r\n )}\r\n {/* Time Label */}\r\n <div\r\n style={{\r\n padding: '4px 10px',\r\n background: 'rgba(0,0,0,0.9)',\r\n color: '#fff',\r\n fontSize: 12,\r\n fontWeight: 600,\r\n borderRadius: 4,\r\n whiteSpace: 'nowrap',\r\n }}\r\n >\r\n {formatTime(hoverTime.time)}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Control Buttons */}\r\n <div style={{ display: 'flex', alignItems: 'center', gap: isMobile ? 4 : 8, flexWrap: 'wrap' }}>\r\n {/* Play/Pause */}\r\n <button \r\n onClick={togglePlay}\r\n aria-label={isPlaying ? 'Pause' : 'Play'}\r\n style={{\r\n width: isMobile ? 48 : 44,\r\n height: isMobile ? 48 : 44,\r\n minWidth: isMobile ? 48 : 44,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n borderRadius: '50%',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n padding: 0,\r\n }}\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n </button>\r\n\r\n {/* Volume - hide slider on mobile */}\r\n {volumeEnabled && !isMobile && (\r\n <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>\r\n <button \r\n onClick={toggleMute}\r\n aria-label={isMuted ? 'Unmute' : 'Mute'}\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {isMuted || volumeState === 0 ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n </button>\r\n <input\r\n type=\"range\"\r\n min=\"0\"\r\n max=\"1\"\r\n step=\"0.01\"\r\n value={isMuted ? 0 : volumeState}\r\n onChange={e => setVolume(parseFloat(e.target.value))}\r\n style={{ \r\n width: 70,\r\n height: 4,\r\n cursor: 'pointer',\r\n accentColor: accentColor,\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Mobile volume button (toggle only) */}\r\n {volumeEnabled && isMobile && (\r\n <button \r\n onClick={toggleMute}\r\n aria-label={isMuted ? 'Unmute' : 'Mute'}\r\n style={{\r\n width: 44,\r\n height: 44,\r\n minWidth: 44,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n padding: 0,\r\n }}\r\n >\r\n {isMuted || volumeState === 0 ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n </button>\r\n )}\r\n\r\n {/* Time - compact on mobile */}\r\n <div style={{ \r\n color: '#fff', \r\n fontSize: isMobile ? 11 : 13, \r\n fontFamily: 'monospace', \r\n marginLeft: isMobile ? 4 : 8,\r\n whiteSpace: 'nowrap',\r\n }}>\r\n {formatTime(currentTime)}{!isMobile && ` / ${formatTime(duration)}`}\r\n </div>\r\n\r\n {/* Spacer */}\r\n <div style={{ flex: 1 }} />\r\n\r\n {/* Playlist Button */}\r\n {playlist.length > 0 && (\r\n <button \r\n onClick={() => setShowPlaylist(!showPlaylist)}\r\n aria-label=\"Playlist\"\r\n style={{\r\n width: isMobile ? 44 : 36,\r\n height: isMobile ? 44 : 36,\r\n minWidth: isMobile ? 44 : 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: showPlaylist ? 'rgba(255,255,255,0.2)' : 'transparent',\r\n border: 'none',\r\n color: accentColor,\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n <PlaylistIcon />\r\n </button>\r\n )}\r\n\r\n {/* Cast Button - hide on mobile if not supported */}\r\n {!isMobile && (\r\n <button \r\n onClick={() => {\r\n const video = videoRef.current;\r\n if (video && 'remote' in video) {\r\n (video as any).remote.prompt().catch(() => {\r\n alert('No casting devices found');\r\n });\r\n } else {\r\n alert('Casting not supported');\r\n }\r\n }}\r\n aria-label=\"Cast\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n <CastIcon />\r\n </button>\r\n )}\r\n\r\n {/* Settings */}\r\n {playbackSpeed && (\r\n <div style={{ position: 'relative' }}>\r\n <button \r\n onClick={() => setShowSettings(!showSettings)}\r\n aria-label=\"Settings\"\r\n style={{\r\n width: isMobile ? 44 : 36,\r\n height: isMobile ? 44 : 36,\r\n minWidth: isMobile ? 44 : 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: showSettings ? 'rgba(255,255,255,0.2)' : 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n <SettingsIcon />\r\n </button>\r\n \r\n {showSettings && (\r\n <div\r\n style={{\r\n position: isMobile ? 'fixed' : 'absolute',\r\n bottom: isMobile ? 0 : 48,\r\n right: isMobile ? 0 : 0,\r\n left: isMobile ? 0 : undefined,\r\n minWidth: isMobile ? '100%' : 160,\r\n maxHeight: isMobile ? '50vh' : undefined,\r\n background: '#1a1a1a',\r\n borderRadius: isMobile ? '16px 16px 0 0' : 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n border: isMobile ? 'none' : '1px solid rgba(255,255,255,0.1)',\r\n zIndex: 100,\r\n overflow: 'hidden',\r\n }}\r\n >\r\n <div style={{ padding: '10px 16px', borderBottom: '1px solid rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.6)', fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>\r\n Playback Speed\r\n </div>\r\n {playbackSpeeds.map(speed => (\r\n <button\r\n key={speed}\r\n onClick={() => setPlaybackRate(speed)}\r\n style={{\r\n width: '100%',\r\n padding: isMobile ? '16px 20px' : '10px 16px',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n background: 'transparent',\r\n border: 'none',\r\n color: playbackRate === speed ? accentColor : '#fff',\r\n cursor: 'pointer',\r\n fontSize: isMobile ? 16 : 14,\r\n }}\r\n >\r\n <span>{speed === 1 ? 'Normal' : `${speed}x`}</span>\r\n {playbackRate === speed && <span>✓</span>}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* PiP - hide on mobile (usually not supported well) */}\r\n {pip && !isMobile && document.pictureInPictureEnabled && (\r\n <button \r\n onClick={togglePip}\r\n aria-label=\"Picture in Picture\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n <PipIcon />\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n {fullscreen && (\r\n <button \r\n onClick={toggleFullscreen}\r\n aria-label={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}\r\n style={{\r\n width: isMobile ? 44 : 36,\r\n height: isMobile ? 44 : 36,\r\n minWidth: isMobile ? 44 : 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n {isFullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Mobile gesture hint overlay */}\r\n {isMobile && isTouching && hoverTime && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n padding: '12px 24px',\r\n background: 'rgba(0,0,0,0.8)',\r\n borderRadius: 8,\r\n color: '#fff',\r\n fontSize: 18,\r\n fontWeight: 600,\r\n pointerEvents: 'none',\r\n }}\r\n >\r\n {formatTime(hoverTime.time)}\r\n </div>\r\n )}\r\n\r\n {/* Big Play Button */}\r\n {!isPlaying && !isLoading && !error && !isAdPlaying && (\r\n <button\r\n onClick={togglePlay}\r\n aria-label=\"Play\"\r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n width: isMobile ? 70 : 80,\r\n height: isMobile ? 70 : 80,\r\n borderRadius: '50%',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n border: 'none',\r\n cursor: 'pointer',\r\n backgroundColor: accentColor,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.4)',\r\n transition: 'transform 0.2s',\r\n padding: 0,\r\n }}\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"white\" style={{ width: isMobile ? 28 : 32, height: isMobile ? 28 : 32, marginLeft: 4 }}>\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n </button>\r\n )}\r\n\r\n {/* Playlist Sidebar - slides from right (full screen on mobile) */}\r\n {playlist.length > 0 && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: isMobile ? 'auto' : 0,\r\n bottom: isMobile ? 0 : undefined,\r\n right: 0,\r\n left: isMobile ? 0 : undefined,\r\n width: isMobile ? '100%' : 320,\r\n height: isMobile ? '70%' : '100%',\r\n maxHeight: isMobile ? '70vh' : undefined,\r\n background: isMobile \r\n ? 'linear-gradient(to top, rgba(0,0,0,0.98) 0%, rgba(0,0,0,0.95) 100%)'\r\n : 'linear-gradient(to left, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.85) 100%)',\r\n backdropFilter: 'blur(10px)',\r\n transform: showPlaylist \r\n ? 'translateY(0)' \r\n : (isMobile ? 'translateY(100%)' : 'translateX(100%)'),\r\n transition: 'transform 0.3s ease-out',\r\n zIndex: 60,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n borderLeft: isMobile ? 'none' : '1px solid rgba(255,255,255,0.1)',\r\n borderTop: isMobile ? '1px solid rgba(255,255,255,0.1)' : 'none',\r\n borderRadius: isMobile ? '20px 20px 0 0' : 0,\r\n }}\r\n >\r\n {/* Drag handle for mobile */}\r\n {isMobile && (\r\n <div style={{ \r\n width: 40, \r\n height: 4, \r\n background: 'rgba(255,255,255,0.3)', \r\n borderRadius: 2, \r\n margin: '12px auto 8px',\r\n }} />\r\n )}\r\n \r\n {/* Header */}\r\n <div style={{ \r\n display: 'flex', \r\n alignItems: 'center', \r\n justifyContent: 'space-between', \r\n padding: isMobile ? '12px 20px 16px' : '16px 20px', \r\n borderBottom: '1px solid rgba(255,255,255,0.1)',\r\n }}>\r\n <div>\r\n <div style={{ color: accentColor, fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 2 }}>\r\n Playlist\r\n </div>\r\n <div style={{ color: '#fff', fontSize: 16, fontWeight: 600 }}>\r\n {playlist.length} videos\r\n </div>\r\n </div>\r\n <button\r\n onClick={() => setShowPlaylist(false)}\r\n style={{\r\n width: isMobile ? 44 : 36,\r\n height: isMobile ? 44 : 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'rgba(255,255,255,0.1)',\r\n border: 'none',\r\n borderRadius: '50%',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n <CloseIcon />\r\n </button>\r\n </div>\r\n \r\n {/* Playlist Items */}\r\n <div style={{ flex: 1, overflowY: 'auto', padding: '8px 0' }}>\r\n {playlist.map((item, index) => (\r\n <button\r\n key={item.id}\r\n onClick={() => {\r\n setCurrentPlaylistIndex(index);\r\n onPlaylistItemChange?.(index, item);\r\n setTimeout(() => play(), 100);\r\n }}\r\n style={{\r\n width: '100%',\r\n padding: '12px 20px',\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 12,\r\n background: currentPlaylistIndex === index ? `${accentColor}20` : 'transparent',\r\n border: 'none',\r\n borderLeft: currentPlaylistIndex === index ? `3px solid ${accentColor}` : '3px solid transparent',\r\n cursor: 'pointer',\r\n textAlign: 'left',\r\n transition: 'background 0.2s',\r\n }}\r\n >\r\n {/* Thumbnail */}\r\n <div style={{\r\n width: 100,\r\n height: 56,\r\n borderRadius: 6,\r\n overflow: 'hidden',\r\n background: '#333',\r\n flexShrink: 0,\r\n position: 'relative',\r\n }}>\r\n {item.poster ? (\r\n <img src={item.poster} alt=\"\" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />\r\n ) : (\r\n <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(255,255,255,0.3)' }}>\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 24, height: 24 }}>\r\n <path d=\"M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4z\" />\r\n </svg>\r\n </div>\r\n )}\r\n {/* Playing indicator */}\r\n {currentPlaylistIndex === index && (\r\n <div style={{\r\n position: 'absolute',\r\n inset: 0,\r\n background: 'rgba(0,0,0,0.5)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <div style={{ color: accentColor, fontSize: 20 }}>▶</div>\r\n </div>\r\n )}\r\n {/* Duration badge */}\r\n {item.duration && (\r\n <div style={{\r\n position: 'absolute',\r\n bottom: 4,\r\n right: 4,\r\n padding: '2px 6px',\r\n background: 'rgba(0,0,0,0.8)',\r\n borderRadius: 3,\r\n fontSize: 10,\r\n color: '#fff',\r\n fontWeight: 500,\r\n }}>\r\n {formatTime(item.duration)}\r\n </div>\r\n )}\r\n </div>\r\n \r\n {/* Info */}\r\n <div style={{ flex: 1, minWidth: 0 }}>\r\n <div style={{ \r\n color: currentPlaylistIndex === index ? accentColor : '#fff',\r\n fontSize: 14,\r\n fontWeight: currentPlaylistIndex === index ? 600 : 400,\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n whiteSpace: 'nowrap',\r\n marginBottom: 4,\r\n }}>\r\n {item.title}\r\n </div>\r\n <div style={{ color: 'rgba(255,255,255,0.5)', fontSize: 12 }}>\r\n Video {index + 1}\r\n </div>\r\n </div>\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Hidden video for frame preview */}\r\n <video\r\n ref={previewVideoRef}\r\n src={currentVideoSrc}\r\n style={{ display: 'none' }}\r\n muted\r\n preload=\"metadata\"\r\n />\r\n \r\n {/* Hidden canvas for frame capture */}\r\n <canvas ref={previewCanvasRef} style={{ display: 'none' }} />\r\n </div>\r\n );\r\n }\r\n);\r\n\r\nPlexVideoPlayer.displayName = 'PlexVideoPlayer';\r\n\r\nexport default PlexVideoPlayer;\r\n","// Progress Bar Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useRef, useState, useCallback, useEffect } from 'react';\r\nimport { formatTime, percentage, clamp } from '../utils/helpers';\r\n\r\ninterface ProgressBarProps {\r\n currentTime: number;\r\n duration: number;\r\n buffered: number;\r\n onSeek: (time: number) => void;\r\n thumbnailPreview?: {\r\n enabled: boolean;\r\n sprites?: string;\r\n interval?: number;\r\n width?: number;\r\n height?: number;\r\n };\r\n disabled?: boolean;\r\n}\r\n\r\nexport const ProgressBar: React.FC<ProgressBarProps> = ({\r\n currentTime,\r\n duration,\r\n buffered,\r\n onSeek,\r\n thumbnailPreview,\r\n disabled = false,\r\n}) => {\r\n const progressRef = useRef<HTMLDivElement>(null);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const [hoverTime, setHoverTime] = useState<number | null>(null);\r\n const [hoverPosition, setHoverPosition] = useState(0);\r\n\r\n const playedPercent = percentage(currentTime, duration);\r\n const bufferedPercent = percentage(buffered, duration);\r\n\r\n const getTimeFromPosition = useCallback(\r\n (clientX: number): number => {\r\n if (!progressRef.current) return 0;\r\n const rect = progressRef.current.getBoundingClientRect();\r\n const percent = clamp((clientX - rect.left) / rect.width, 0, 1);\r\n return percent * duration;\r\n },\r\n [duration]\r\n );\r\n\r\n const handleMouseMove = useCallback(\r\n (e: MouseEvent | React.MouseEvent) => {\r\n if (!progressRef.current) return;\r\n const rect = progressRef.current.getBoundingClientRect();\r\n const clientX = 'clientX' in e ? e.clientX : 0;\r\n const position = clamp(clientX - rect.left, 0, rect.width);\r\n const time = getTimeFromPosition(clientX);\r\n\r\n setHoverPosition(position);\r\n setHoverTime(time);\r\n\r\n if (isDragging) {\r\n onSeek(time);\r\n }\r\n },\r\n [isDragging, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleMouseDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n if (disabled) return;\r\n e.preventDefault();\r\n setIsDragging(true);\r\n const time = getTimeFromPosition(e.clientX);\r\n onSeek(time);\r\n },\r\n [disabled, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleMouseUp = useCallback(() => {\r\n setIsDragging(false);\r\n }, []);\r\n\r\n const handleMouseEnter = useCallback(\r\n (e: React.MouseEvent) => {\r\n const time = getTimeFromPosition(e.clientX);\r\n setHoverTime(time);\r\n },\r\n [getTimeFromPosition]\r\n );\r\n\r\n const handleMouseLeave = useCallback(() => {\r\n setHoverTime(null);\r\n }, []);\r\n\r\n // Global mouse events for dragging\r\n useEffect(() => {\r\n if (isDragging) {\r\n const mouseMoveHandler = (e: MouseEvent) => handleMouseMove(e);\r\n window.addEventListener('mousemove', mouseMoveHandler);\r\n window.addEventListener('mouseup', handleMouseUp);\r\n\r\n return () => {\r\n window.removeEventListener('mousemove', mouseMoveHandler);\r\n window.removeEventListener('mouseup', handleMouseUp);\r\n };\r\n }\r\n return undefined;\r\n }, [isDragging, handleMouseMove, handleMouseUp]);\r\n\r\n // Touch support\r\n const handleTouchStart = useCallback(\r\n (e: React.TouchEvent) => {\r\n if (disabled) return;\r\n e.preventDefault();\r\n const touch = e.touches[0];\r\n const time = getTimeFromPosition(touch.clientX);\r\n setIsDragging(true);\r\n onSeek(time);\r\n },\r\n [disabled, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleTouchMove = useCallback(\r\n (e: React.TouchEvent) => {\r\n if (!isDragging) return;\r\n const touch = e.touches[0];\r\n const time = getTimeFromPosition(touch.clientX);\r\n onSeek(time);\r\n },\r\n [isDragging, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleTouchEnd = useCallback(() => {\r\n setIsDragging(false);\r\n }, []);\r\n\r\n return (\r\n <div\r\n ref={progressRef}\r\n className=\"plex-video-player__progress-container\"\r\n onMouseDown={handleMouseDown}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onMouseMove={(e) => handleMouseMove(e.nativeEvent)}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n role=\"slider\"\r\n aria-label=\"Video progress\"\r\n aria-valuemin={0}\r\n aria-valuemax={duration}\r\n aria-valuenow={currentTime}\r\n aria-valuetext={formatTime(currentTime)}\r\n tabIndex={0}\r\n >\r\n <div className=\"plex-video-player__progress\">\r\n <div\r\n className=\"plex-video-player__progress-buffered\"\r\n style={{ width: `${bufferedPercent}%` }}\r\n />\r\n <div\r\n className=\"plex-video-player__progress-played\"\r\n style={{ width: `${playedPercent}%` }}\r\n />\r\n <div\r\n className=\"plex-video-player__progress-handle\"\r\n style={{ left: `${playedPercent}%` }}\r\n />\r\n </div>\r\n\r\n {/* Thumbnail Preview */}\r\n {thumbnailPreview?.enabled && hoverTime !== null && (\r\n <div\r\n className=\"plex-video-player__thumbnail-preview\"\r\n style={{\r\n left: `${hoverPosition}px`,\r\n width: thumbnailPreview.width || 160,\r\n height: thumbnailPreview.height || 90,\r\n }}\r\n >\r\n {thumbnailPreview.sprites && (\r\n <div\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n backgroundImage: `url(${thumbnailPreview.sprites})`,\r\n backgroundPosition: calculateSpritePosition(\r\n hoverTime,\r\n duration,\r\n thumbnailPreview.interval || 10,\r\n thumbnailPreview.width || 160,\r\n thumbnailPreview.height || 90\r\n ),\r\n backgroundSize: 'cover',\r\n }}\r\n />\r\n )}\r\n <div className=\"plex-video-player__thumbnail-time\">\r\n {formatTime(hoverTime)}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Time tooltip */}\r\n {hoverTime !== null && !thumbnailPreview?.enabled && (\r\n <div\r\n className=\"plex-video-player__thumbnail-preview\"\r\n style={{\r\n left: `${hoverPosition}px`,\r\n width: 'auto',\r\n height: 'auto',\r\n padding: '4px 8px',\r\n }}\r\n >\r\n <span style={{ color: 'white', fontSize: '12px' }}>\r\n {formatTime(hoverTime)}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n// Calculate sprite background position for thumbnail preview\r\nconst calculateSpritePosition = (\r\n time: number,\r\n _duration: number,\r\n interval: number,\r\n width: number,\r\n height: number\r\n): string => {\r\n const index = Math.floor(time / interval);\r\n const columns = 10; // Assuming 10 columns per row in sprite\r\n const row = Math.floor(index / columns);\r\n const col = index % columns;\r\n\r\n return `-${col * width}px -${row * height}px`;\r\n};\r\n\r\nexport default ProgressBar;\r\n","// Icon components for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\n\r\ninterface IconProps {\r\n className?: string;\r\n size?: number;\r\n}\r\n\r\nexport const PlayIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n);\r\n\r\nexport const PauseIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeHighIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeMediumIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeLowIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 9v6h4l5 5V4l-5 5H7z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeMuteIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\r\n </svg>\r\n);\r\n\r\nexport const FullscreenIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\" />\r\n </svg>\r\n);\r\n\r\nexport const FullscreenExitIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\" />\r\n </svg>\r\n);\r\n\r\nexport const PipIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z\" />\r\n </svg>\r\n);\r\n\r\nexport const PipExitIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM5 7h8v6H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const SettingsIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z\" />\r\n </svg>\r\n);\r\n\r\nexport const CaptionsIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z\" />\r\n </svg>\r\n);\r\n\r\nexport const SpeedIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44zm-9.79 6.84a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z\" />\r\n </svg>\r\n);\r\n\r\nexport const QualityIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM7.5 13h2v2H7.5zm4.5 0h2v2h-2zm4.5 0h2v2h-2zM7.5 9h2v2H7.5zm4.5 0h2v2h-2zm4.5 0h2v2h-2z\" />\r\n </svg>\r\n);\r\n\r\nexport const ForwardIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z\" />\r\n </svg>\r\n);\r\n\r\nexport const RewindIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z\" />\r\n </svg>\r\n);\r\n\r\nexport const SkipNextIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z\" />\r\n </svg>\r\n);\r\n\r\nexport const SkipPrevIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 6h2v12H6zm3.5 6l8.5 6V6z\" />\r\n </svg>\r\n);\r\n\r\nexport const ErrorIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" />\r\n </svg>\r\n);\r\n\r\nexport const ExternalLinkIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z\" />\r\n </svg>\r\n);\r\n\r\nexport const CheckIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\" />\r\n </svg>\r\n);\r\n\r\nexport const RepeatIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z\" />\r\n </svg>\r\n);\r\n\r\nexport const CopyIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\" />\r\n </svg>\r\n);\r\n\r\nexport const DownloadIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const ChevronUpIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z\" />\r\n </svg>\r\n);\r\n\r\nexport const ChevronDownIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\" />\r\n </svg>\r\n);\r\n\r\nexport const CloseIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n);\r\n\r\nexport const PlaylistIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M4 10h12v2H4zm0-4h12v2H4zm0 8h8v2H4zm10 0v6l5-3z\" />\r\n </svg>\r\n);\r\n","// Error Display Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\nimport { ErrorIcon } from './Icons';\r\n\r\ninterface ErrorDisplayProps {\r\n error: MediaError | null;\r\n onRetry: () => void;\r\n}\r\n\r\nconst ERROR_MESSAGES: Record<number, string> = {\r\n 1: 'The video playback was aborted.',\r\n 2: 'A network error occurred while loading the video.',\r\n 3: 'The video format is not supported or cannot be decoded.',\r\n 4: 'The video source is not supported.',\r\n};\r\n\r\nexport const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onRetry }) => {\r\n const errorCode = error?.code || 0;\r\n const message = ERROR_MESSAGES[errorCode] || 'An unknown error occurred.';\r\n\r\n return (\r\n <div className=\"plex-video-player__error\">\r\n <ErrorIcon className=\"plex-video-player__error-icon\" size={60} />\r\n <div className=\"plex-video-player__error-message\">{message}</div>\r\n {errorCode > 0 && (\r\n <div className=\"plex-video-player__error-code\">Error Code: {errorCode}</div>\r\n )}\r\n <button\r\n className=\"plex-video-player__error-retry\"\r\n onClick={onRetry}\r\n type=\"button\"\r\n >\r\n Try Again\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport default ErrorDisplay;\r\n","// usePlayer Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useState, useRef, useCallback, useEffect } from 'react';\r\nimport {\r\n PlayerState,\r\n UsePlayerOptions,\r\n UsePlayerReturn,\r\n} from '../types';\r\nimport {\r\n requestFullscreen,\r\n exitFullscreen,\r\n getFullscreenElement,\r\n getBufferedEnd,\r\n isPipSupported,\r\n} from '../utils/helpers';\r\n\r\nconst initialState: PlayerState = {\r\n isPlaying: false,\r\n isPaused: true,\r\n isEnded: false,\r\n isBuffering: false,\r\n isSeeking: false,\r\n isFullscreen: false,\r\n isPip: false,\r\n isMuted: false,\r\n isAdPlaying: false,\r\n volume: 1,\r\n currentTime: 0,\r\n duration: 0,\r\n buffered: 0,\r\n playbackRate: 1,\r\n currentQuality: null,\r\n error: null,\r\n};\r\n\r\nexport const usePlayer = (options: UsePlayerOptions = {}): UsePlayerReturn => {\r\n const {\r\n autoPlay = false,\r\n muted = false,\r\n loop = false,\r\n volume: initialVolume = 1,\r\n playbackRate: initialRate = 1,\r\n } = options;\r\n\r\n const videoRef = useRef<HTMLVideoElement>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [state, setState] = useState<PlayerState>({\r\n ...initialState,\r\n volume: initialVolume,\r\n isMuted: muted,\r\n playbackRate: initialRate,\r\n });\r\n\r\n // Update state helper\r\n const updateState = useCallback((updates: Partial<PlayerState>) => {\r\n setState((prev) => ({ ...prev, ...updates }));\r\n }, []);\r\n\r\n // Play\r\n const play = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n try {\r\n await video.play();\r\n updateState({ isPlaying: true, isPaused: false, isEnded: false });\r\n } catch (error) {\r\n console.error('Play failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Pause\r\n const pause = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.pause();\r\n updateState({ isPlaying: false, isPaused: true });\r\n }, [updateState]);\r\n\r\n // Toggle play/pause\r\n const togglePlay = useCallback(() => {\r\n if (state.isPlaying) {\r\n pause();\r\n } else {\r\n play();\r\n }\r\n }, [state.isPlaying, play, pause]);\r\n\r\n // Seek\r\n const seek = useCallback((time: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const clampedTime = Math.max(0, Math.min(time, video.duration || 0));\r\n video.currentTime = clampedTime;\r\n updateState({ currentTime: clampedTime });\r\n }, [updateState]);\r\n\r\n // Set volume\r\n const setVolume = useCallback((volume: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const clampedVolume = Math.max(0, Math.min(1, volume));\r\n video.volume = clampedVolume;\r\n video.muted = clampedVolume === 0;\r\n updateState({\r\n volume: clampedVolume,\r\n isMuted: clampedVolume === 0,\r\n });\r\n }, [updateState]);\r\n\r\n // Toggle mute\r\n const toggleMute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.muted = !video.muted;\r\n updateState({ isMuted: video.muted });\r\n }, [updateState]);\r\n\r\n // Set playback rate\r\n const setPlaybackRate = useCallback((rate: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.playbackRate = rate;\r\n updateState({ playbackRate: rate });\r\n }, [updateState]);\r\n\r\n // Enter fullscreen\r\n const enterFullscreen = useCallback(async () => {\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n try {\r\n await requestFullscreen(container);\r\n updateState({ isFullscreen: true });\r\n } catch (error) {\r\n console.error('Fullscreen failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Exit fullscreen\r\n const exitFullscreenFn = useCallback(async () => {\r\n try {\r\n await exitFullscreen();\r\n updateState({ isFullscreen: false });\r\n } catch (error) {\r\n console.error('Exit fullscreen failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Toggle fullscreen\r\n const toggleFullscreen = useCallback(async () => {\r\n if (state.isFullscreen) {\r\n await exitFullscreenFn();\r\n } else {\r\n await enterFullscreen();\r\n }\r\n }, [state.isFullscreen, enterFullscreen, exitFullscreenFn]);\r\n\r\n // Enter Picture-in-Picture\r\n const enterPip = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video || !isPipSupported()) return;\r\n\r\n try {\r\n await video.requestPictureInPicture();\r\n updateState({ isPip: true });\r\n } catch (error) {\r\n console.error('PiP failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Exit Picture-in-Picture\r\n const exitPipFn = useCallback(async () => {\r\n if (!document.pictureInPictureElement) return;\r\n\r\n try {\r\n await document.exitPictureInPicture();\r\n updateState({ isPip: false });\r\n } catch (error) {\r\n console.error('Exit PiP failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Toggle Picture-in-Picture\r\n const togglePip = useCallback(async () => {\r\n if (state.isPip) {\r\n await exitPipFn();\r\n } else {\r\n await enterPip();\r\n }\r\n }, [state.isPip, enterPip, exitPipFn]);\r\n\r\n // Video event handlers\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n // Set initial values\r\n video.muted = muted;\r\n video.volume = initialVolume;\r\n video.playbackRate = initialRate;\r\n video.loop = loop;\r\n\r\n const handlers = {\r\n loadedmetadata: () => {\r\n updateState({ duration: video.duration });\r\n },\r\n timeupdate: () => {\r\n updateState({\r\n currentTime: video.currentTime,\r\n buffered: getBufferedEnd(video),\r\n });\r\n },\r\n play: () => {\r\n updateState({ isPlaying: true, isPaused: false, isEnded: false });\r\n },\r\n pause: () => {\r\n updateState({ isPlaying: false, isPaused: true });\r\n },\r\n ended: () => {\r\n updateState({ isPlaying: false, isPaused: true, isEnded: true });\r\n },\r\n waiting: () => {\r\n updateState({ isBuffering: true });\r\n },\r\n canplay: () => {\r\n updateState({ isBuffering: false });\r\n },\r\n seeking: () => {\r\n updateState({ isSeeking: true });\r\n },\r\n seeked: () => {\r\n updateState({ isSeeking: false });\r\n },\r\n volumechange: () => {\r\n updateState({\r\n volume: video.volume,\r\n isMuted: video.muted,\r\n });\r\n },\r\n ratechange: () => {\r\n updateState({ playbackRate: video.playbackRate });\r\n },\r\n error: () => {\r\n updateState({ error: video.error });\r\n },\r\n enterpictureinpicture: () => {\r\n updateState({ isPip: true });\r\n },\r\n leavepictureinpicture: () => {\r\n updateState({ isPip: false });\r\n },\r\n };\r\n\r\n // Add event listeners\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.addEventListener(event, handler);\r\n });\r\n\r\n // Autoplay\r\n if (autoPlay) {\r\n play();\r\n }\r\n\r\n return () => {\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.removeEventListener(event, handler);\r\n });\r\n };\r\n }, [autoPlay, muted, loop, initialVolume, initialRate, play, updateState]);\r\n\r\n // Fullscreen change handler\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!getFullscreenElement();\r\n updateState({ isFullscreen: isFs });\r\n };\r\n\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\r\n document.addEventListener('MSFullscreenChange', handleFullscreenChange);\r\n\r\n return () => {\r\n document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('mozfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('MSFullscreenChange', handleFullscreenChange);\r\n };\r\n }, [updateState]);\r\n\r\n return {\r\n state,\r\n videoRef,\r\n containerRef,\r\n play,\r\n pause,\r\n togglePlay,\r\n seek,\r\n setVolume,\r\n toggleMute,\r\n setPlaybackRate,\r\n enterFullscreen,\r\n exitFullscreen: exitFullscreenFn,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip: exitPipFn,\r\n togglePip,\r\n };\r\n};\r\n\r\nexport default usePlayer;\r\n","// useKeyboard Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useEffect, useCallback } from 'react';\r\nimport { HotkeyConfig } from '../types';\r\n\r\nconst DEFAULT_HOTKEYS: Required<HotkeyConfig> = {\r\n play: 'Space',\r\n mute: 'm',\r\n fullscreen: 'f',\r\n pip: 'p',\r\n seekForward: 'ArrowRight',\r\n seekBackward: 'ArrowLeft',\r\n volumeUp: 'ArrowUp',\r\n volumeDown: 'ArrowDown',\r\n};\r\n\r\ninterface UseKeyboardOptions {\r\n enabled: boolean;\r\n hotkeys?: HotkeyConfig;\r\n onPlay: () => void;\r\n onMute: () => void;\r\n onFullscreen: () => void;\r\n onPip: () => void;\r\n onSeek: (delta: number) => void;\r\n onVolume: (delta: number) => void;\r\n containerRef: React.RefObject<HTMLDivElement>;\r\n}\r\n\r\nexport const useKeyboard = ({\r\n enabled,\r\n hotkeys = {},\r\n onPlay,\r\n onMute,\r\n onFullscreen,\r\n onPip,\r\n onSeek,\r\n onVolume,\r\n containerRef,\r\n}: UseKeyboardOptions): void => {\r\n const mergedHotkeys = { ...DEFAULT_HOTKEYS, ...hotkeys };\r\n\r\n const handleKeyDown = useCallback(\r\n (event: KeyboardEvent) => {\r\n if (!enabled) return;\r\n\r\n // Ignore if typing in an input\r\n const target = event.target as HTMLElement;\r\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\r\n return;\r\n }\r\n\r\n // Check if focus is within the player container\r\n const container = containerRef.current;\r\n if (!container || !container.contains(document.activeElement)) {\r\n return;\r\n }\r\n\r\n const key = event.key;\r\n\r\n switch (key) {\r\n case mergedHotkeys.play:\r\n case ' ':\r\n event.preventDefault();\r\n onPlay();\r\n break;\r\n case mergedHotkeys.mute:\r\n case 'M':\r\n event.preventDefault();\r\n onMute();\r\n break;\r\n case mergedHotkeys.fullscreen:\r\n case 'F':\r\n event.preventDefault();\r\n onFullscreen();\r\n break;\r\n case mergedHotkeys.pip:\r\n case 'P':\r\n event.preventDefault();\r\n onPip();\r\n break;\r\n case mergedHotkeys.seekForward:\r\n event.preventDefault();\r\n onSeek(event.shiftKey ? 30 : 10);\r\n break;\r\n case mergedHotkeys.seekBackward:\r\n event.preventDefault();\r\n onSeek(event.shiftKey ? -30 : -10);\r\n break;\r\n case mergedHotkeys.volumeUp:\r\n event.preventDefault();\r\n onVolume(0.1);\r\n break;\r\n case mergedHotkeys.volumeDown:\r\n event.preventDefault();\r\n onVolume(-0.1);\r\n break;\r\n case '0':\r\n case '1':\r\n case '2':\r\n case '3':\r\n case '4':\r\n case '5':\r\n case '6':\r\n case '7':\r\n case '8':\r\n case '9':\r\n event.preventDefault();\r\n // Seek to percentage of video\r\n onSeek(-Infinity); // Will be handled by caller with percent\r\n break;\r\n default:\r\n break;\r\n }\r\n },\r\n [enabled, mergedHotkeys, onPlay, onMute, onFullscreen, onPip, onSeek, onVolume, containerRef]\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) return;\r\n\r\n document.addEventListener('keydown', handleKeyDown);\r\n\r\n return () => {\r\n document.removeEventListener('keydown', handleKeyDown);\r\n };\r\n }, [enabled, handleKeyDown]);\r\n};\r\n\r\nexport default useKeyboard;\r\n","// Ad Overlay Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\nimport { VastAdInfo } from '../types';\r\nimport { formatTime } from '../utils/helpers';\r\nimport { ExternalLinkIcon } from './Icons';\r\n\r\ninterface AdOverlayProps {\r\n ad: VastAdInfo;\r\n timeRemaining: number;\r\n canSkip: boolean;\r\n onSkip: () => void;\r\n onClick: () => void;\r\n}\r\n\r\nexport const AdOverlay: React.FC<AdOverlayProps> = ({\r\n ad,\r\n timeRemaining,\r\n canSkip,\r\n onSkip,\r\n onClick,\r\n}) => {\r\n const skipText = canSkip\r\n ? 'Skip Ad'\r\n : `Skip in ${Math.ceil(timeRemaining)}s`;\r\n\r\n return (\r\n <div className=\"plex-video-player__ad-overlay\" onClick={onClick}>\r\n {/* Ad Info Badge */}\r\n <div className=\"plex-video-player__ad-info\">\r\n <span className=\"plex-video-player__ad-badge\">Ad</span>\r\n <span>{formatTime(timeRemaining)} remaining</span>\r\n </div>\r\n\r\n {/* Learn More Button */}\r\n {ad.clickThrough && (\r\n <button\r\n className=\"plex-video-player__ad-learn-more\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n onClick();\r\n }}\r\n type=\"button\"\r\n >\r\n <ExternalLinkIcon size={16} />\r\n Learn More\r\n </button>\r\n )}\r\n\r\n {/* Skip Button */}\r\n <button\r\n className=\"plex-video-player__ad-skip\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n if (canSkip) onSkip();\r\n }}\r\n disabled={!canSkip}\r\n type=\"button\"\r\n >\r\n {skipText}\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport default AdOverlay;\r\n","// Loading Spinner Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\n\r\ninterface LoaderProps {\r\n visible: boolean;\r\n}\r\n\r\nexport const Loader: React.FC<LoaderProps> = ({ visible }) => {\r\n if (!visible) return null;\r\n\r\n return (\r\n <div className=\"plex-video-player__loader\">\r\n <div className=\"plex-video-player__loader-spinner\" />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Loader;\r\n","// Settings Menu Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { SettingsIcon, CheckIcon } from './Icons';\r\nimport { VideoSource, TextTrack } from '../types';\r\n\r\ninterface SettingsMenuProps {\r\n playbackRate: number;\r\n playbackSpeeds: number[];\r\n onPlaybackRateChange: (rate: number) => void;\r\n qualityEnabled: boolean;\r\n sources?: VideoSource[];\r\n currentQuality?: string;\r\n onQualityChange?: (quality: string) => void;\r\n captionsEnabled?: boolean;\r\n textTracks?: TextTrack[];\r\n currentTrack?: string;\r\n onTrackChange?: (track: string | null) => void;\r\n}\r\n\r\ntype MenuView = 'main' | 'speed' | 'quality' | 'captions';\r\n\r\nexport const SettingsMenu: React.FC<SettingsMenuProps> = ({\r\n playbackRate,\r\n playbackSpeeds,\r\n onPlaybackRateChange,\r\n qualityEnabled,\r\n sources,\r\n currentQuality,\r\n onQualityChange,\r\n captionsEnabled,\r\n textTracks,\r\n currentTrack,\r\n onTrackChange,\r\n}) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [currentView, setCurrentView] = useState<MenuView>('main');\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Close menu when clicking outside\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\r\n setIsOpen(false);\r\n setCurrentView('main');\r\n }\r\n };\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }\r\n return undefined;\r\n }, [isOpen]);\r\n\r\n const handleToggle = useCallback(() => {\r\n setIsOpen((prev) => !prev);\r\n setCurrentView('main');\r\n }, []);\r\n\r\n const handleSpeedSelect = useCallback(\r\n (speed: number) => {\r\n onPlaybackRateChange(speed);\r\n setCurrentView('main');\r\n },\r\n [onPlaybackRateChange]\r\n );\r\n\r\n const handleQualitySelect = useCallback(\r\n (quality: string) => {\r\n onQualityChange?.(quality);\r\n setCurrentView('main');\r\n },\r\n [onQualityChange]\r\n );\r\n\r\n const handleTrackSelect = useCallback(\r\n (track: string | null) => {\r\n onTrackChange?.(track);\r\n setCurrentView('main');\r\n },\r\n [onTrackChange]\r\n );\r\n\r\n const renderMainMenu = () => (\r\n <>\r\n <div className=\"plex-video-player__settings-title\">Settings</div>\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('speed')}\r\n type=\"button\"\r\n >\r\n <span>Playback Speed</span>\r\n <span>{playbackRate === 1 ? 'Normal' : `${playbackRate}x`}</span>\r\n </button>\r\n {qualityEnabled && sources && sources.length > 1 && (\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('quality')}\r\n type=\"button\"\r\n >\r\n <span>Quality</span>\r\n <span>{currentQuality || 'Auto'}</span>\r\n </button>\r\n )}\r\n {captionsEnabled && textTracks && textTracks.length > 0 && (\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('captions')}\r\n type=\"button\"\r\n >\r\n <span>Captions</span>\r\n <span>{currentTrack || 'Off'}</span>\r\n </button>\r\n )}\r\n </>\r\n );\r\n\r\n const renderSpeedMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Playback Speed\r\n </button>\r\n <div className=\"plex-video-player__speed-menu\">\r\n {playbackSpeeds.map((speed) => (\r\n <button\r\n key={speed}\r\n className={`plex-video-player__speed-btn ${\r\n playbackRate === speed ? 'plex-video-player__speed-btn--active' : ''\r\n }`}\r\n onClick={() => handleSpeedSelect(speed)}\r\n type=\"button\"\r\n >\r\n {speed === 1 ? 'Normal' : `${speed}x`}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n const renderQualityMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Quality\r\n </button>\r\n <div className=\"plex-video-player__quality-menu\">\r\n {sources?.map((source) => (\r\n <button\r\n key={source.quality || source.src}\r\n className={`plex-video-player__settings-item ${\r\n currentQuality === source.quality ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleQualitySelect(source.quality || source.src)}\r\n type=\"button\"\r\n >\r\n <span>{source.label || source.quality || 'Unknown'}</span>\r\n {currentQuality === source.quality && <CheckIcon size={16} />}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n const renderCaptionsMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Captions\r\n </button>\r\n <div className=\"plex-video-player__quality-menu\">\r\n <button\r\n className={`plex-video-player__settings-item ${\r\n !currentTrack ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleTrackSelect(null)}\r\n type=\"button\"\r\n >\r\n <span>Off</span>\r\n {!currentTrack && <CheckIcon size={16} />}\r\n </button>\r\n {textTracks?.map((track) => (\r\n <button\r\n key={track.srclang}\r\n className={`plex-video-player__settings-item ${\r\n currentTrack === track.srclang ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleTrackSelect(track.srclang)}\r\n type=\"button\"\r\n >\r\n <span>{track.label}</span>\r\n {currentTrack === track.srclang && <CheckIcon size={16} />}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n return (\r\n <div className=\"plex-video-player__settings-container\" ref={containerRef}>\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={handleToggle}\r\n aria-label=\"Settings\"\r\n aria-expanded={isOpen}\r\n title=\"Settings\"\r\n type=\"button\"\r\n >\r\n <SettingsIcon />\r\n </button>\r\n {isOpen && (\r\n <div\r\n className={`plex-video-player__settings-menu ${\r\n isOpen ? 'plex-video-player__settings-menu--open' : ''\r\n }`}\r\n >\r\n {currentView === 'main' && renderMainMenu()}\r\n {currentView === 'speed' && renderSpeedMenu()}\r\n {currentView === 'quality' && renderQualityMenu()}\r\n {currentView === 'captions' && renderCaptionsMenu()}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nexport default SettingsMenu;\r\n","// Volume Control Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useCallback } from 'react';\r\nimport {\r\n VolumeHighIcon,\r\n VolumeMediumIcon,\r\n VolumeLowIcon,\r\n VolumeMuteIcon,\r\n} from './Icons';\r\n\r\ninterface VolumeControlProps {\r\n volume: number;\r\n muted: boolean;\r\n onVolumeChange: (volume: number) => void;\r\n onToggleMute: () => void;\r\n}\r\n\r\nexport const VolumeControl: React.FC<VolumeControlProps> = ({\r\n volume,\r\n muted,\r\n onVolumeChange,\r\n onToggleMute,\r\n}) => {\r\n const handleSliderChange = useCallback(\r\n (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const newVolume = parseFloat(e.target.value);\r\n onVolumeChange(newVolume);\r\n },\r\n [onVolumeChange]\r\n );\r\n\r\n const getVolumeIcon = () => {\r\n if (muted || volume === 0) {\r\n return <VolumeMuteIcon />;\r\n }\r\n if (volume < 0.33) {\r\n return <VolumeLowIcon />;\r\n }\r\n if (volume < 0.66) {\r\n return <VolumeMediumIcon />;\r\n }\r\n return <VolumeHighIcon />;\r\n };\r\n\r\n return (\r\n <div className=\"plex-video-player__volume-container\">\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={onToggleMute}\r\n aria-label={muted ? 'Unmute' : 'Mute'}\r\n title={muted ? 'Unmute (M)' : 'Mute (M)'}\r\n type=\"button\"\r\n >\r\n {getVolumeIcon()}\r\n </button>\r\n <div className=\"plex-video-player__volume-slider-container\">\r\n <input\r\n type=\"range\"\r\n className=\"plex-video-player__volume-slider\"\r\n min={0}\r\n max={1}\r\n step={0.01}\r\n value={muted ? 0 : volume}\r\n onChange={handleSliderChange}\r\n aria-label=\"Volume\"\r\n style={{\r\n background: `linear-gradient(to right, var(--plex-primary) 0%, var(--plex-primary) ${\r\n (muted ? 0 : volume) * 100\r\n }%, var(--plex-progress-bg) ${(muted ? 0 : volume) * 100}%, var(--plex-progress-bg) 100%)`,\r\n }}\r\n />\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nexport default VolumeControl;\r\n"],"names":["parseVastXml","xmlString","doc","DOMParser","parseFromString","querySelector","ads","error","querySelectorAll","forEach","adElement","inLine","id","getAttribute","title","textContent","description","undefined","creatives","skipOffset","clickThrough","duration","clickTracking","mediaFiles","trackingEvents","creative","linear","durationStr","parseVastDuration","skipOffsetAttr","mediaFile","url","trim","push","type","width","parseInt","height","bitrate","videoClicks","ct","tracking","event","impressionUrls","impression","length","includes","parts","split","parseFloat","fetchVastAd","async","config","response","fetch","method","headers","Accept","ok","text","result","ad","skipDelay","selectBestMediaFile","video","document","createElement","supportedFiles","filter","file","canPlayType","sort","a","b","resA","resB","test","navigator","userAgent","fireTrackingPixel","Image","src","fireTrackingPixels","urls","convertToAdInfo","useVast","vastConfig","videoRef","onAdStart","onAdEnd","onAdSkip","onAdError","isAdPlaying","setIsAdPlaying","useState","currentAd","setCurrentAd","adTimeRemaining","setAdTimeRemaining","canSkip","setCanSkip","currentVastAd","useRef","originalSrc","originalTime","playedPositions","Set","adIntervalRef","vastConfigs","Array","isArray","playAd","useCallback","current","Error","currentSrc","currentTime","play","start","window","setInterval","remaining","Math","max","percent","firstQuartile","midpoint","thirdQuartile","handleAdEnd","complete","endAd","addEventListener","once","clearInterval","catch","skipAd","skip","handleAdClick","open","checkForAd","position","key","midrollTime","has","shouldPlay","add","useEffect","formatTime","seconds","isFinite","isNaN","hrs","floor","mins","secs","toString","padStart","percentage","value","total","min","clamp","throttle","func","limit","inThrottle","args","setTimeout","isPipSupported","pictureInPictureEnabled","getFullscreenElement","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","requestFullscreen","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","exitFullscreen","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","getBufferedEnd","buffered","i","end","PlayIcon","_jsx","viewBox","fill","className","children","d","PauseIcon","VolumeHighIcon","VolumeMuteIcon","FullscreenIcon","FullscreenExitIcon","PipIcon","SettingsIcon","RepeatIcon","CopyIcon","SpeedIcon","PlaylistIcon","style","CastIcon","CloseIcon","PlexVideoPlayer","forwardRef","props","ref","poster","autoPlay","muted","loop","controls","pip","fullscreen","playbackSpeed","playbackSpeeds","volume","volumeEnabled","initialVolume","textTracks","vast","accentColor","controlsTimeout","doubleClickFullscreen","clickToPlay","preload","playlist","onPlay","onPause","onEnded","onTimeUpdate","onProgress","onVolumeChange","onFullscreenChange","onPipChange","onQualityChange","onError","onReady","onPlaylistItemChange","containerRef","progressRef","controlsTimeoutRef","lastClickRef","isPlaying","setIsPlaying","setCurrentTime","setDuration","setBuffered","volumeState","setVolumeState","isMuted","setIsMuted","isFullscreen","setIsFullscreen","isPipActive","setIsPipActive","isLoading","setIsLoading","setError","showControls","setShowControls","playbackRate","setPlaybackRateState","isLoop","setIsLoop","showSettings","setShowSettings","showPlaylist","setShowPlaylist","hoverTime","setHoverTime","previewFrame","setPreviewFrame","isMobile","setIsMobile","isTouching","setIsTouching","touchStartRef","previewCanvasRef","previewVideoRef","checkMobile","mobile","matchMedia","matches","removeEventListener","contextMenu","setContextMenu","x","y","visible","currentPlaylistIndex","setCurrentPlaylistIndex","sources","useMemo","currentVideoSrc","currentPoster","err","pause","stop","togglePlay","paused","seek","time","setVolume","newVolume","mute","unmute","toggleMute","toggleLoop","enterFullscreen","toggleFullscreen","enterPip","requestPictureInPicture","exitPip","pictureInPictureElement","exitPictureInPicture","togglePip","setPlaybackRate","rate","setQuality","qualityLabel","source","find","s","label","quality","wasPlaying","playNextInPlaylist","nextIndex","handleContextMenu","e","preventDefault","rect","getBoundingClientRect","clientX","left","clientY","top","closeContextMenu","prev","copyVideoUrl","clipboard","writeText","handleProgressClick","handleProgressHover","previewVideo","canvas","ctx","getContext","abs","onseeked","drawImage","toDataURL","handleProgressLeave","hideControls","showControlsTemporarily","clearTimeout","handleMouseMove","handleTouchStart","touch","touches","Date","now","handleTouchMove","deltaX","deltaY","seekDelta","innerWidth","newTime","handleTouchEnd","touchDuration","changedTouches","tapX","handleVideoClick","handlePlay","handlePause","handleEnded","handleTimeUpdate","handleDurationChange","handleProgressEvent","bufferedEnd","handleVolumeChangeEvent","handleLoadedMetadata","handleWaiting","handlePlaying","handleCanPlay","handleErrorEvent","handleFullscreenChange","isFs","handleEnterPip","handleLeavePip","handleKeyDown","contains","activeElement","handleClick","useImperativeHandle","getCurrentTime","getDuration","getVolume","isPip","getVideoElement","progressPercent","_jsxs","inset","zIndex","aspectRatio","backgroundColor","overflow","userSelect","fontFamily","WebkitTapHighlightColor","touchAction","onMouseMove","onMouseLeave","onTouchStart","onTouchMove","onTouchEnd","onContextMenu","tabIndex","objectFit","playsInline","onClick","map","track","kind","srcLang","srclang","default","display","alignItems","justifyContent","pointerEvents","border","borderTopColor","borderRadius","animation","flexDirection","background","color","marginBottom","fontSize","load","padding","fontWeight","cursor","gap","disabled","bottom","right","opacity","ceil","minWidth","backdropFilter","boxShadow","stopPropagation","textAlign","flex","margin","speeds","idx","indexOf","borderTop","paddingTop","transition","transform","alt","whiteSpace","flexWrap","step","onChange","target","marginLeft","remote","prompt","alert","maxHeight","borderBottom","textTransform","letterSpacing","speed","borderLeft","overflowY","item","index","flexShrink","textOverflow","displayName","calculateSpritePosition","_duration","interval","size","xmlns","VolumeMediumIcon","VolumeLowIcon","ErrorIcon","ExternalLinkIcon","CheckIcon","ERROR_MESSAGES","initialState","isPaused","isEnded","isBuffering","isSeeking","currentQuality","DEFAULT_HOTKEYS","seekForward","seekBackward","volumeUp","volumeDown","timeRemaining","onSkip","skipText","onRetry","errorCode","code","message","onSeek","thumbnailPreview","isDragging","setIsDragging","hoverPosition","setHoverPosition","playedPercent","bufferedPercent","getTimeFromPosition","handleMouseDown","handleMouseUp","handleMouseEnter","handleMouseLeave","mouseMoveHandler","onMouseDown","onMouseEnter","nativeEvent","role","enabled","sprites","backgroundImage","backgroundPosition","backgroundSize","onPlaybackRateChange","qualityEnabled","captionsEnabled","currentTrack","onTrackChange","isOpen","setIsOpen","currentView","setCurrentView","handleClickOutside","handleToggle","handleSpeedSelect","handleQualitySelect","handleTrackSelect","_Fragment","onToggleMute","handleSliderChange","wait","timeout","extension","pop","toLowerCase","mp4","webm","ogg","ogv","m3u8","mpd","mov","avi","mkv","random","substring","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullscreenEnabled","maxTouchPoints","Number","hotkeys","onMute","onFullscreen","onPip","onVolume","mergedHotkeys","tagName","isContentEditable","container","shiftKey","Infinity","options","initialRate","state","setState","updateState","updates","clampedTime","clampedVolume","exitFullscreenFn","exitPipFn","handlers","loadedmetadata","timeupdate","ended","waiting","canplay","seeking","seeked","volumechange","ratechange","enterpictureinpicture","leavepictureinpicture","Object","entries","handler"],"mappings":"0HAkCO,MAAMA,EAAgBC,IAC3B,MACMC,GADS,IAAIC,WACAC,gBAAgBH,EAAW,YAG9C,GADmBC,EAAIG,cAAc,eAEnC,MAAO,CAAEC,IAAK,GAAIC,MAAO,4BAG3B,MAAMD,EAAgB,GAkGtB,OAjGmBJ,EAAIM,iBAAiB,MAE7BC,QAASC,IAClB,MAAMC,EAASD,EAAUL,cAAc,UACvC,IAAKM,EAAQ,OAEb,MAAMC,EAAKF,EAAUG,aAAa,OAAS,GACrCC,EAAQH,EAAON,cAAc,YAAYU,aAAe,GACxDC,EAAcL,EAAON,cAAc,gBAAgBU,kBAAeE,EAGlEC,EAAYP,EAAOH,iBAAiB,YAC1C,IACIW,EACAC,EAFAC,EAAW,EAGf,MAAMC,EAA0B,GAC1BC,EAA8B,GAC9BC,EAA2C,CAAA,EAEjDN,EAAUT,QAASgB,IACjB,MAAMC,EAASD,EAASpB,cAAc,UACtC,IAAKqB,EAAQ,OAGb,MAAMC,EAAcD,EAAOrB,cAAc,aAAaU,YAClDY,IACFN,EAAWO,EAAkBD,IAI/B,MAAME,EAAiBH,EAAOb,aAAa,cACvCgB,IACFV,EAAaS,EAAkBC,IAIPH,EAAOlB,iBAAiB,aAChCC,QAASqB,IACzB,MAAMC,EAAMD,EAAUf,aAAaiB,OAC/BD,GACFR,EAAWU,KAAK,CACdF,MACAG,KAAMJ,EAAUjB,aAAa,SAAW,YACxCsB,MAAOC,SAASN,EAAUjB,aAAa,UAAY,IAAK,IACxDwB,OAAQD,SAASN,EAAUjB,aAAa,WAAa,IAAK,IAC1DyB,QAASF,SAASN,EAAUjB,aAAa,YAAc,IAAK,UAAOI,MAMzE,MAAMsB,EAAcb,EAAOrB,cAAc,eACrCkC,IACFnB,EAAemB,EAAYlC,cAAc,iBAAiBU,aAAaiB,OACvEO,EAAY/B,iBAAiB,iBAAiBC,QAAS+B,IACrD,MAAMT,EAAMS,EAAGzB,aAAaiB,OACxBD,GAAKT,EAAcW,KAAKF,MAKPL,EAAOlB,iBAAiB,2BAChCC,QAASgC,IACxB,MAAMC,EAAQD,EAAS5B,aAAa,SAC9BkB,EAAMU,EAAS1B,aAAaiB,OAC9BU,GAASX,IACNP,EAAekB,KAClBlB,EAAekB,GAAS,IAE1BlB,EAAekB,GAAOT,KAAKF,QAMjC,MAAMY,EAA2B,GACjChC,EAAOH,iBAAiB,cAAcC,QAASmC,IAC7C,MAAMb,EAAMa,EAAW7B,aAAaiB,OAChCD,GAAKY,EAAeV,KAAKF,KAG3BR,EAAWsB,OAAS,GACtBvC,EAAI2B,KAAK,CACPrB,KACAE,QACAE,cACAK,WACAF,aACAC,eACAE,gBACAqB,iBACApB,aACAC,qBAKC,CAAElB,QAMLsB,EAAqBP,IACzB,GAAIA,EAASyB,SAAS,KACpB,SAGF,MAAMC,EAAQ1B,EAAS2B,MAAM,KAC7B,GAAqB,IAAjBD,EAAMF,OAAc,OAAO,EAM/B,OAAe,KAJDT,SAASW,EAAM,GAAI,IAID,GAHhBX,SAASW,EAAM,GAAI,IACnBE,WAAWF,EAAM,KAQtBG,EAAcC,MAAOC,IAChC,IACE,MAAMC,QAAiBC,MAAMF,EAAOrB,IAAK,CACvCwB,OAAQ,MACRC,QAAS,CACPC,OAAU,qBAId,IAAKJ,EAASK,GAEZ,OAAO,KAGT,MAAMzD,QAAkBoD,EAASM,OAC3BC,EAAS5D,EAAaC,GAE5B,GAAI2D,EAAOrD,OAA+B,IAAtBqD,EAAOtD,IAAIuC,OAE7B,OAAO,KAGT,MAAMgB,EAAKD,EAAOtD,IAAI,GAOtB,YAJyBW,IAArBmC,EAAOU,gBAA6C7C,IAAlB4C,EAAG1C,aACvC0C,EAAG1C,WAAaiC,EAAOU,WAGlBD,CACT,CAAE,MAAOtD,GAEP,OAAO,IACT,GAMWwD,EAAuBxC,IAClC,GAA0B,IAAtBA,EAAWsB,OAAc,OAAO,KAEpC,MAAMmB,EAAQC,SAASC,cAAc,SAC/BC,EAAiB5C,EAAW6C,OAAQC,GACA,KAAjCL,EAAMM,YAAYD,EAAKnC,OAGhC,GAA8B,IAA1BiC,EAAetB,OAAc,OAAO,KAGxCsB,EAAeI,KAAK,CAACC,EAAGC,KACtB,MAAMC,EAAOF,EAAErC,MAAQqC,EAAEnC,OACnBsC,EAAOF,EAAEtC,MAAQsC,EAAEpC,OACzB,OAAIqC,IAASC,EAAaA,EAAOD,GACzBD,EAAEnC,SAAW,IAAMkC,EAAElC,SAAW,KAK1C,MADiB,4BAA4BsC,KAAKC,UAAUC,YAC5CX,EAAetB,OAAS,EAC/BsB,EAAeA,EAAetB,OAAS,GAGzCsB,EAAe,IAMXY,EAAqBhD,KACpB,IAAIiD,OACZC,IAAMlD,GAMCmD,EAAsBC,IACjCA,EAAK1E,QAAQsE,IAMFK,EAAmBvB,IACvB,CACLjD,GAAIiD,EAAGjD,GACPE,MAAO+C,EAAG/C,MACVO,SAAUwC,EAAGxC,SACbF,WAAY0C,EAAG1C,WACfC,aAAcyC,EAAGzC,eC/NRiE,EAAU,EACrBC,aACAC,WACAC,YACAC,UACAC,WACAC,gBAEA,MAAOC,EAAaC,GAAkBC,EAAAA,UAAS,IACxCC,EAAWC,GAAgBF,EAAAA,SAA4B,OACvDG,EAAiBC,GAAsBJ,EAAAA,SAAS,IAChDK,EAASC,GAAcN,EAAAA,UAAS,GAEjCO,EAAgBC,EAAAA,OAAsB,MACtCC,EAAcD,EAAAA,OAAe,IAC7BE,EAAeF,EAAAA,OAAe,GAC9BG,EAAkBH,EAAAA,OAAoB,IAAII,KAC1CC,EAAgBL,EAAAA,OAAsB,MAGtCM,EAAcC,MAAMC,QAAQxB,GAAcA,EAAaA,EAAa,CAACA,GAAc,GAGnFyB,EAASC,cACb7D,MAAOC,IACL,MAAMY,EAAQuB,EAAS0B,QACvB,GAAKjD,IAAS4B,EAEd,IACE,MAAM/B,QAAWX,EAAYE,GAC7B,IAAKS,EAEH,YADA8B,IAAY,IAAIuB,MAAM,4BAIxB,MAAMpF,EAAYiC,EAAoBF,EAAGtC,YACzC,IAAKO,EAEH,YADA6D,IAAY,IAAIuB,MAAM,mCAKxBX,EAAYU,QAAUjD,EAAMmD,WAC5BX,EAAaS,QAAUjD,EAAMoD,YAG7BlC,EAAmBrB,EAAGlB,gBAGtB0D,EAAcY,QAAUpD,EACxBmC,EAAaZ,EAAgBvB,IAC7BgC,GAAe,GACfK,EAAmBrC,EAAGxC,UACtB+E,OAA6BnF,IAAlB4C,EAAG1C,YAA8C,IAAlB0C,EAAG1C,YAG7C6C,EAAMiB,IAAMnD,EAAUC,IACtBiC,EAAMoD,YAAc,QACdpD,EAAMqD,OAGRxD,EAAGrC,eAAe8F,OACpBpC,EAAmBrB,EAAGrC,eAAe8F,OAGvC9B,IAAYJ,EAAgBvB,IAG5B8C,EAAcM,QAAUM,OAAOC,YAAY,KACzC,MAAMC,EAAYC,KAAKC,IAAI,EAAG9D,EAAGxC,SAAW2C,EAAMoD,aAClDlB,EAAmBuB,GAGf5D,EAAG1C,YAAc6C,EAAMoD,aAAevD,EAAG1C,YAC3CiF,GAAW,GAIb,MAAMwB,EAAW5D,EAAMoD,YAAcvD,EAAGxC,SAAY,IAChDuG,GAAW,IAAM/D,EAAGrC,eAAeqG,eACrC3C,EAAmBrB,EAAGrC,eAAeqG,eAEnCD,GAAW,IAAM/D,EAAGrC,eAAesG,UACrC5C,EAAmBrB,EAAGrC,eAAesG,UAEnCF,GAAW,IAAM/D,EAAGrC,eAAeuG,eACrC7C,EAAmBrB,EAAGrC,eAAeuG,gBAEtC,KAGH,MAAMC,EAAc,KACdnE,EAAGrC,eAAeyG,UACpB/C,EAAmBrB,EAAGrC,eAAeyG,UAEvCC,KAGFlE,EAAMmE,iBAAiB,QAASH,EAAa,CAAEI,MAAM,GACvD,CAAE,MAAO7H,GACPoF,IAAYpF,aAAiB2G,MAAQ3G,EAAQ,IAAI2G,MAAM,uBACvDgB,GACF,GAEF,CAAC3C,EAAUK,EAAaJ,EAAWG,IAI/BuC,EAAQlB,EAAAA,YAAY,KACxB,MAAMhD,EAAQuB,EAAS0B,QAClBjD,IAGD2C,EAAcM,UAChBoB,cAAc1B,EAAcM,SAC5BN,EAAcM,QAAU,MAItBV,EAAYU,UACdjD,EAAMiB,IAAMsB,EAAYU,QACxBjD,EAAMoD,YAAcZ,EAAaS,QACjCjD,EAAMqD,OAAOiB,MAAM,SAGrBzC,GAAe,GACfG,EAAa,MACbE,EAAmB,GACnBE,GAAW,GACXC,EAAcY,QAAU,KAExBxB,QACC,CAACF,EAAUE,IAGR8C,EAASvB,EAAAA,YAAY,KACzB,IAAKb,IAAYE,EAAcY,QAAS,OAExC,MAAMpD,EAAKwC,EAAcY,QACrBpD,EAAGrC,eAAegH,MACpBtD,EAAmBrB,EAAGrC,eAAegH,MAGvC9C,MACAwC,KACC,CAAC/B,EAAS+B,EAAOxC,IAGd+C,EAAgBzB,EAAAA,YAAY,KAChC,IAAKX,EAAcY,QAAS,OAE5B,MAAMpD,EAAKwC,EAAcY,QACrBpD,EAAGzC,cACLmG,OAAOmB,KAAK7E,EAAGzC,aAAc,UAE3ByC,EAAGvC,eACL4D,EAAmBrB,EAAGvC,gBAEvB,IAGGqH,EAAa3B,EAAAA,YACjB,CAACI,EAAqB/F,KAChBuE,GAAsC,IAAvBgB,EAAY/D,QAE/B+D,EAAYnG,QAAS2C,IACnB,MAAMwF,EAAWxF,EAAOwF,UAAY,UAC9BC,EAAM,GAAGD,KAAYxF,EAAO0F,aAAe,IAEjD,GAAIrC,EAAgBQ,QAAQ8B,IAAIF,GAAM,OAEtC,IAAIG,GAAa,EAEjB,OAAQJ,GACN,IAAK,UACHI,EAA6B,IAAhB5B,EACb,MACF,IAAK,UACChE,EAAO0F,aAAe1B,GAAehE,EAAO0F,cAC9CE,GAAa,GAEf,MACF,IAAK,WACHA,EAAa5B,GAAe/F,EAAW,GAIvC2H,IACFvC,EAAgBQ,QAAQgC,IAAIJ,GAC5B9B,EAAO3D,OAIb,CAACwD,EAAahB,EAAamB,IAY7B,OARAmC,EAAAA,UAAU,IACD,KACDvC,EAAcM,SAChBoB,cAAc1B,EAAcM,UAG/B,IAEI,CACLrB,cACAG,YACAE,kBACAE,UACAoC,SACAE,gBACAE,eC9OSQ,EAAcC,IACzB,IAAKC,SAASD,IAAYE,MAAMF,GAAU,MAAO,OAEjD,MAAMG,EAAM7B,KAAK8B,MAAMJ,EAAU,MAC3BK,EAAO/B,KAAK8B,MAAOJ,EAAU,KAAQ,IACrCM,EAAOhC,KAAK8B,MAAMJ,EAAU,IAElC,OAAIG,EAAM,EACD,GAAGA,KAAOE,EAAKE,WAAWC,SAAS,EAAG,QAAQF,EAAKC,WAAWC,SAAS,EAAG,OAG5E,GAAGH,KAAQC,EAAKC,WAAWC,SAAS,EAAG,QAoBnCC,EAAa,CAACC,EAAeC,IAC1B,IAAVA,EAAoB,EACjBrC,KAAKsC,IAAI,IAAKtC,KAAKC,IAAI,EAAImC,EAAQC,EAAS,MAMxCE,EAAQ,CAACH,EAAeE,EAAarC,IACzCD,KAAKsC,IAAIrC,EAAKD,KAAKC,IAAIqC,EAAKF,IAMxBI,EAAW,CACtBC,EACAC,KAEA,IAAIC,GAAa,EACjB,MAAO,IAAIC,KACJD,IACHF,KAAQG,GACRD,GAAa,EACbE,WAAW,IAAOF,GAAa,EAAQD,MAkChCI,EAAiB,IACrB,4BAA6BvG,UAAYA,SAASwG,wBAM9CC,EAAuB,IAEhCzG,SAAS0G,mBACR1G,SAA8D2G,yBAC9D3G,SAA2D4G,sBAC3D5G,SAA0D6G,qBAC3D,KAOSC,EAAoB5H,MAAO6H,IAClCA,EAAQD,wBACJC,EAAQD,oBACJC,EAA4EC,8BAC/ED,EAA2EC,0BACxED,EAAyEE,2BAC5EF,EAAwEE,uBACrEF,EAAwEG,2BAC3EH,EAAuEG,uBAOrEC,EAAiBjI,UACxBc,SAASmH,qBACLnH,SAASmH,iBACLnH,SAAuEoH,2BAC1EpH,SAAsEoH,uBACnEpH,SAAsEqH,0BACzErH,SAAqEqH,sBAClErH,SAAmEsH,wBACtEtH,SAAkEsH,oBA2DhEC,EAAkBxH,IAC7B,GAA8B,IAA1BA,EAAMyH,SAAS5I,OAAc,OAAO,EAExC,MAAMuE,EAAcpD,EAAMoD,YAC1B,IAAK,IAAIsE,EAAI,EAAGA,EAAI1H,EAAMyH,SAAS5I,OAAQ6I,IACzC,GAAI1H,EAAMyH,SAASnE,MAAMoE,IAAMtE,GAAepD,EAAMyH,SAASE,IAAID,IAAMtE,EACrE,OAAOpD,EAAMyH,SAASE,IAAID,GAI9B,OAAO1H,EAAMyH,SAASE,IAAI3H,EAAMyH,SAAS5I,OAAS,IC1L9C+I,EAAW,IACfC,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,oBAINC,EAAY,IAChBN,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,sCAINE,EAAiB,IACrBP,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,kMAING,EAAiB,IACrBR,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,sWAINI,EAAiB,IACrBT,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,qFAINK,EAAqB,IACzBV,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,oFAINM,EAAU,IACdX,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,iHAINO,EAAe,IACnBZ,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,osBAINQ,EAAa,IACjBb,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,sEAINS,EAAW,IACfd,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,sIAINU,EAAY,IAChBf,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,yOAINW,EAAe,IACnBhB,MAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAee,MAAO,CAAE3K,MAAO,GAAIE,OAAQ,IAAI4J,SAC3EJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,6FAINa,EAAW,IACflB,MAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAee,MAAO,CAAE3K,MAAO,GAAIE,OAAQ,IAAI4J,SAC3EJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,2MAINc,EAAY,IAChBnB,MAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAee,MAAO,CAAE3K,MAAO,GAAIE,OAAQ,IAAI4J,SAC3EJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,4GAkBCe,EAAkBC,EAAAA,WAC7B,CAACC,EAAOC,KACN,MAAMnI,IACJA,EAAGoI,OACHA,EAAMC,SACNA,GAAW,EAAKC,MAChBA,GAAQ,EAAKC,KACbA,GAAO,EAAKC,SACZA,GAAW,EAAItL,MACfA,EAAQ,OAAM6J,UACdA,EAAY,GAAEc,MACdA,EAAQ,CAAA,EAAEY,IACVA,GAAM,EAAIC,WACVA,GAAa,EAAIC,cACjBA,GAAgB,EAAIC,eACpBA,EAAiB,CAAC,GAAK,IAAM,EAAG,KAAM,IAAK,GAC3CC,OAAQC,GAAgB,EAAIC,cAC5BA,EAAgB,EAACC,WACjBA,EAAa,GAAEC,KACfA,EAAIC,YACJA,EAAc,UAASC,gBACvBA,EAAkB,IAAIC,sBACtBA,GAAwB,EAAIC,YAC5BA,GAAc,EAAIC,QAClBA,EAAU,WAAUC,SACpBA,EAAW,GAAEC,OACbA,EAAMC,QACNA,EAAOC,QACPA,EAAOC,aACPA,EAAYC,WACZA,EAAUC,eACVA,EAAcC,mBACdA,EAAkBC,YAClBA,EAAWC,gBACXA,EAAeC,QACfA,EAAO1J,UACPA,GAASC,QACTA,GAAOC,SACPA,GAAQC,UACRA,GAASwJ,QACTA,GAAOC,qBACPA,IACEjC,EAGE5H,GAAWe,EAAAA,OAAyB,MACpC+I,GAAe/I,EAAAA,OAAuB,MACtCgJ,GAAchJ,EAAAA,OAAuB,MACrCiJ,GAAqBjJ,EAAAA,OAA8B,MACnDkJ,GAAelJ,EAAAA,OAAe,IAG7BmJ,GAAWC,IAAgB5J,EAAAA,UAAS,IACpCsB,GAAauI,IAAkB7J,EAAAA,SAAS,IACxCzE,GAAUuO,IAAe9J,EAAAA,SAAS,IAClC2F,GAAUoE,IAAe/J,EAAAA,SAAS,IAClCgK,GAAaC,IAAkBjK,EAAAA,SAASyH,EAAQ,EAAIS,IACpDgC,GAASC,IAAcnK,EAAAA,SAASyH,IAChC2C,GAAcC,IAAmBrK,EAAAA,UAAS,IAC1CsK,GAAaC,IAAkBvK,EAAAA,UAAS,IACxCwK,GAAWC,IAAgBzK,EAAAA,UAAS,IACpCvF,GAAOiQ,IAAY1K,EAAAA,SAA4B,OAC/C2K,GAAcC,IAAmB5K,EAAAA,UAAS,IAC1C6K,GAAcC,IAAwB9K,EAAAA,SAAS,IAC/C+K,GAAQC,IAAahL,EAAAA,SAAS0H,IAC9BuD,GAAcC,IAAmBlL,EAAAA,UAAS,IAC1CmL,GAAcC,IAAmBpL,EAAAA,UAAS,IAC1CqL,GAAWC,IAAgBtL,EAAAA,SAA6C,OACxEuL,GAAcC,IAAmBxL,EAAAA,SAAwB,OACzDyL,GAAUC,IAAe1L,EAAAA,UAAS,IAClC2L,GAAYC,IAAiB5L,EAAAA,UAAS,GACvC6L,GAAgBrL,EAAAA,OAAsD,MACtEsL,GAAmBtL,EAAAA,OAA0B,MAC7CuL,GAAkBvL,EAAAA,OAAyB,MAGjD4C,EAAAA,UAAU,KACR,MAAM4I,EAAc,KAClB,MAAMC,EAASxK,OAAOyK,WAAW,sBAAsBC,SACxC1K,OAAOyK,WAAW,qBAAqBC,SACvC,iBAAkB1K,OACjCiK,GAAYO,IAId,OAFAD,IACAvK,OAAOY,iBAAiB,SAAU2J,GAC3B,IAAMvK,OAAO2K,oBAAoB,SAAUJ,IACjD,IAGH,MAAOK,GAAaC,IAAkBtM,EAAAA,SAAS,CAAEuM,EAAG,EAAGC,EAAG,EAAGC,SAAS,KAG/DC,GAAsBC,IAA2B3M,EAAAA,SAAS,GAG3D4M,GAAUC,EAAAA,QAAuB,IAClB,iBAAR1N,EACF,CAAC,CAAEA,MAAK/C,KAAM,cAEhB+C,EACN,CAACA,IAGE2N,GAAkBD,EAAAA,QAAQ,IAC1BnE,EAAS3L,OAAS,GAAK2L,EAASgE,IAC3BhE,EAASgE,IAAsBvN,IAEjCyN,GAAQ,IAAIzN,KAAO,GACzB,CAACuJ,EAAUgE,GAAsBE,KAE9BG,GAAgBF,EAAAA,QAAQ,IACxBnE,EAAS3L,OAAS,GAAK2L,EAASgE,KAAuBnF,OAClDmB,EAASgE,IAAsBnF,OAEjCA,EACN,CAACmB,EAAUgE,GAAsBnF,KAG9BtH,UACJA,GAASH,YACTA,GAAWK,gBACXA,GAAeE,QACfA,GAAOoC,OACPA,GAAME,cACNA,GAAaE,WACbA,IACEtD,EAAQ,CACVC,WAAY4I,EACZ3I,YACAC,aACAC,WACAC,YACAC,eAII0B,GAAOL,EAAAA,YAAY7D,UACvB,MAAMa,EAAQuB,GAAS0B,QACvB,GAAKjD,EACL,UACQA,EAAMqD,MACd,CAAE,MAAOyL,GAET,GACC,IAEGC,GAAQ/L,EAAAA,YAAY,KACxBzB,GAAS0B,SAAS8L,SACjB,IAEGC,GAAOhM,EAAAA,YAAY,KACvB,MAAMhD,EAAQuB,GAAS0B,QAClBjD,IACLA,EAAM+O,QACN/O,EAAMoD,YAAc,IACnB,IAEG6L,GAAajM,EAAAA,YAAY,KACzBzB,GAAS0B,SAASiM,OACpB7L,KAEA0L,MAED,CAAC1L,GAAM0L,KAEJI,GAAOnM,cAAaoM,IACxB,MAAMpP,EAAQuB,GAAS0B,QAClBjD,IACLA,EAAMoD,YAAcM,KAAKC,IAAI,EAAGD,KAAKsC,IAAIoJ,EAAMpP,EAAM3C,UAAY,MAChE,IAEGgS,GAAYrM,cAAa8C,IAC7B,MAAM9F,EAAQuB,GAAS0B,QACvB,IAAKjD,EAAO,OACZ,MAAMsP,EAAY5L,KAAKC,IAAI,EAAGD,KAAKsC,IAAI,EAAGF,IAC1C9F,EAAM8J,OAASwF,EACftP,EAAMuJ,MAAsB,IAAd+F,EACdvD,GAAeuD,GACfrD,GAAyB,IAAdqD,IACV,IAEGC,GAAOvM,EAAAA,YAAY,KACnBzB,GAAS0B,UACX1B,GAAS0B,QAAQsG,OAAQ,EACzB0C,IAAW,KAEZ,IAEGuD,GAASxM,EAAAA,YAAY,KACzB,MAAMhD,EAAQuB,GAAS0B,QAClBjD,IACLA,EAAMuJ,OAAQ,EACO,IAAjBvJ,EAAM8J,SACR9J,EAAM8J,OAAS,GACfiC,GAAe,KAEjBE,IAAW,KACV,IAEGwD,GAAazM,EAAAA,YAAY,KACzBzB,GAAS0B,SAASsG,MACpBiG,KAEAD,MAED,CAACA,GAAMC,KAEJE,GAAa1M,EAAAA,YAAY,KACzBzB,GAAS0B,UACX1B,GAAS0B,QAAQuG,MAAQjI,GAAS0B,QAAQuG,KAC1CsD,GAAUvL,GAAS0B,QAAQuG,QAE5B,IAEGmG,GAAkB3M,EAAAA,YAAY7D,UAClC,UACQkM,GAAapI,SAAS8D,qBAC5BoF,IAAgB,EAClB,CAAE,MAAO2C,GAET,GACC,IAEG1H,GAAiBpE,EAAAA,YAAY7D,UACjC,IACMc,SAAS0G,0BACL1G,SAASmH,iBACf+E,IAAgB,GAEpB,CAAE,MAAO2C,GAET,GACC,IAEGc,GAAmB5M,EAAAA,YAAY7D,UAC/Bc,SAAS0G,wBACLS,WAEAuI,MAEP,CAACA,GAAiBvI,KAEfyI,GAAW7M,EAAAA,YAAY7D,UAC3B,UACQoC,GAAS0B,SAAS6M,2BACxBzD,IAAe,EACjB,CAAE,MAAOyC,GAET,GACC,IAEGiB,GAAU/M,EAAAA,YAAY7D,UAC1B,IACMc,SAAS+P,gCACL/P,SAASgQ,uBACf5D,IAAe,GAEnB,CAAE,MAAOyC,GAET,GACC,IAEGoB,GAAYlN,EAAAA,YAAY7D,UACxBc,SAAS+P,8BACLD,WAEAF,MAEP,CAACA,GAAUE,KAERI,GAAkBnN,cAAaoN,IAC/B7O,GAAS0B,UACX1B,GAAS0B,QAAQ0J,aAAeyD,EAChCxD,GAAqBwD,GACrBpD,IAAgB,KAEjB,IAEGqD,GAAarN,cAAasN,IAC9B,MAAMC,EAAS7B,GAAQ8B,KAAKC,GAAKA,EAAEC,QAAUJ,GAAgBG,EAAEE,UAAYL,GAC3E,GAAIC,GAAUhP,GAAS0B,QAAS,CAC9B,MAAMjD,EAAQuB,GAAS0B,QACjB2N,GAAc5Q,EAAMkP,OACpBE,EAAOpP,EAAMoD,YACnBpD,EAAMiB,IAAMsP,EAAOtP,IACnBjB,EAAMoD,YAAcgM,EAChBwB,GAAY5Q,EAAMqD,OACtB4H,IAAkBqF,EACpB,GACC,CAAC5B,GAASzD,IAGP4F,GAAqB7N,EAAAA,YAAY,KACrC,GAAwB,IAApBwH,EAAS3L,OAAc,OAC3B,MAAMiS,GAAatC,GAAuB,GAAKhE,EAAS3L,OACxD4P,GAAwBqC,GACxB1F,KAAuB0F,EAAWtG,EAASsG,IAC3CvK,WAAW,IAAMlD,KAAQ,MACxB,CAACmL,GAAsBhE,EAAUY,GAAsB/H,KAGpD0N,GAAoB/N,cAAagO,IACrCA,EAAEC,iBACF,MAAMC,EAAO7F,GAAapI,SAASkO,wBAC/BD,GACF9C,GAAe,CACbC,EAAG2C,EAAEI,QAAUF,EAAKG,KACpB/C,EAAG0C,EAAEM,QAAUJ,EAAKK,IACpBhD,SAAS,KAGZ,IAEGiD,GAAmBxO,EAAAA,YAAY,KACnCoL,GAAeqD,IAAI,IAAUA,EAAMlD,SAAS,MAC3C,IAEGmD,GAAe1O,EAAAA,YAAY,KAC/BnC,UAAU8Q,UAAUC,UAAUhD,IAC9B4C,MACC,CAAC5C,GAAiB4C,KAGfK,GAAsB7O,cAAagO,IACvC,MAAME,EAAO5F,GAAYrI,SAASkO,wBAClC,GAAID,GAAQ7T,GAAU,CACpB,MAAMuG,GAAWoN,EAAEI,QAAUF,EAAKG,MAAQH,EAAK/S,MAC/CgR,GAAKvL,EAAUvG,GACjB,GACC,CAACA,GAAU8R,KAGR2C,GAAsB9O,cAAagO,IACvC,MAAME,EAAO5F,GAAYrI,SAASkO,wBAClC,GAAID,GAAQ7T,GAAU,CACpB,MAAMuG,GAAWoN,EAAEI,QAAUF,EAAKG,MAAQH,EAAK/S,MACzCiR,EAAO1L,KAAKC,IAAI,EAAGD,KAAKsC,IAAI3I,GAAUuG,EAAUvG,KAItD,GAHA+P,GAAa,CAAEgC,OAAMf,EAAG2C,EAAEI,QAAUF,EAAKG,OAGrCxD,GAAgB5K,SAAW2K,GAAiB3K,QAAS,CACvD,MAAM8O,EAAelE,GAAgB5K,QAC/B+O,EAASpE,GAAiB3K,QAC1BgP,EAAMD,EAAOE,WAAW,MAE1BxO,KAAKyO,IAAIJ,EAAa3O,YAAcgM,GAAQ,IAC9C2C,EAAa3O,YAAcgM,GAG7B2C,EAAaK,SAAW,KAClBH,IACFD,EAAO7T,MAAQ,IACf6T,EAAO3T,OAAS,GAChB4T,EAAII,UAAUN,EAAc,EAAG,EAAG,IAAK,IACvCzE,GAAgB0E,EAAOM,UAAU,aAAc,MAGrD,CACF,GACC,CAACjV,KAEEkV,GAAsBvP,EAAAA,YAAY,KACtCoK,GAAa,MACbE,GAAgB,OACf,IAGGkF,GAAexP,EAAAA,YAAY,MAC3ByI,IAAcsB,IAAiBE,IACjCP,IAAgB,IAEjB,CAACjB,GAAWsB,GAAcE,KAEvBwF,GAA0BzP,EAAAA,YAAY,KAC1C0J,IAAgB,GACZnB,GAAmBtI,SACrByP,aAAanH,GAAmBtI,SAE9BwI,KACFF,GAAmBtI,QAAUsD,WAAWiM,GAAcjF,GAAW,IAAOnD,KAEzE,CAACqB,GAAWrB,EAAiBoI,GAAcjF,KAExCoF,GAAkBhE,EAAAA,QACtB,IAAMzI,EAASuM,GAAyB,KACxC,CAACA,KAIGG,GAAmB5P,cAAagO,IACpC,MAAM6B,EAAQ7B,EAAE8B,QAAQ,GACxBnF,GAAc1K,QAAU,CAAEoL,EAAGwE,EAAMzB,QAAS9C,EAAGuE,EAAMvB,QAASlC,KAAM2D,KAAKC,OACzEtF,IAAc,GACd+E,MACC,CAACA,KAEEQ,GAAkBjQ,cAAagO,IACnC,IAAKrD,GAAc1K,QAAS,OAC5B,MAAM4P,EAAQ7B,EAAE8B,QAAQ,GAClBI,EAASL,EAAMzB,QAAUzD,GAAc1K,QAAQoL,EAC/C8E,EAASN,EAAMvB,QAAU3D,GAAc1K,QAAQqL,EAGrD,GAAI5K,KAAKyO,IAAIe,GAA6B,EAAnBxP,KAAKyO,IAAIgB,IAAezP,KAAKyO,IAAIe,GAAU,GAAI,CACpElC,EAAEC,iBACF,MAAMmC,EAAaF,EAAS3P,OAAO8P,WAAchW,GAAW,GACtDiW,EAAU5P,KAAKC,IAAI,EAAGD,KAAKsC,IAAI3I,GAAU+F,GAAcgQ,IAE7DhG,GAAa,CAAEgC,KAAMkE,EAASjF,EAAGwE,EAAMzB,SACzC,GACC,CAAC/T,GAAU+F,KAERmQ,GAAiBvQ,cAAagO,IAClC,IAAKrD,GAAc1K,QAAS,OAE5B,MAAMuQ,EAAgBT,KAAKC,MAAQrF,GAAc1K,QAAQmM,KACnDyD,EAAQ7B,EAAEyC,eAAe,GACzBP,EAASL,EAAMzB,QAAUzD,GAAc1K,QAAQoL,EAC/C8E,EAASN,EAAMvB,QAAU3D,GAAc1K,QAAQqL,EAGrD,GAAIkF,EAAgB,KAAO9P,KAAKyO,IAAIe,GAAU,IAAMxP,KAAKyO,IAAIgB,GAAU,GACjE1G,GACFwC,KAEAwD,UAIC,GAAI/O,KAAKyO,IAAIe,GAA6B,EAAnBxP,KAAKyO,IAAIgB,IAAezP,KAAKyO,IAAIe,GAAU,GAAI,CACzE,MAAME,EAAaF,EAAS3P,OAAO8P,WAAchW,GAAW,GAC5D8R,GAAK/L,GAAcgQ,EACrB,MAEK,GAAII,EAAgB,IAAK,CAC5B,MAAMtC,EAAO7F,GAAapI,SAASkO,wBACnC,GAAID,EAAM,CACR,MAAMwC,EAAOb,EAAMzB,QAAUF,EAAKG,KAC9BqC,EAAoB,GAAbxC,EAAK/S,MACdgR,GAAK/L,GAAc,IACVsQ,EAAoB,GAAbxC,EAAK/S,OACrBgR,GAAK/L,GAAc,GAEvB,CACF,CAEAuK,GAAc1K,QAAU,KACxByK,IAAc,GACdN,GAAa,OACZ,CAACX,GAAcwC,GAAYwD,GAAyBpV,GAAU+F,GAAa+L,KAGxEwE,GAAmB3Q,EAAAA,YAAY,KAEnC,GADAwO,KACIjE,GAAU,OACd,IAAKjD,EAAa,OAElB,MAAM0I,EAAMD,KAAKC,MACb3I,GAAyB2I,EAAMxH,GAAavI,QAAU,IACxD2M,KAEAX,KAEFzD,GAAavI,QAAU+P,GACtB,CAAC1I,EAAaD,EAAuB4E,GAAYW,GAAkB4B,GAAkBjE,KAGxFrI,EAAAA,UAAU,KACR,MAAMlF,EAAQuB,GAAS0B,QACvB,IAAKjD,EAAO,OAEZ,MAAM4T,EAAa,KAAQlI,IAAa,GAAOjB,OACzCoJ,EAAc,KAAQnI,IAAa,GAAQhB,OAC3CoJ,EAAc,KAClBpI,IAAa,GACbf,MACIH,EAAS3L,OAAS,GAAGgS,MAErBkD,EAAmB,KACvBpI,GAAe3L,EAAMoD,aACrBwH,IAAe5K,EAAMoD,aACrBuB,GAAW3E,EAAMoD,YAAapD,EAAM3C,WAEhC2W,EAAuB,IAAMpI,GAAY5L,EAAM3C,UAC/C4W,EAAsB,KAC1B,GAAIjU,EAAMyH,SAAS5I,OAAS,EAAG,CAC7B,MAAMqV,EAAclU,EAAMyH,SAASE,IAAI3H,EAAMyH,SAAS5I,OAAS,GAC/DgN,GAAaqI,EAAclU,EAAM3C,SAAY,KAC7CwN,IAAcqJ,EAAclU,EAAM3C,SAAY,IAChD,GAEI8W,EAA0B,KAC9BpI,GAAe/L,EAAM8J,QACrBmC,GAAWjM,EAAMuJ,OACjBuB,IAAiB9K,EAAM8J,OAAQ9J,EAAMuJ,QAEjC6K,EAAuB,KAC3BxI,GAAY5L,EAAM3C,UAClBkP,IAAa,GACbpB,QAEIkJ,EAAgB,IAAM9H,IAAa,GACnC+H,EAAgB,IAAM/H,IAAa,GACnCgI,EAAgB,IAAMhI,IAAa,GACnCiI,EAAmB,KACvBhI,GAASxM,EAAMzD,OACfgQ,IAAa,GACbrB,IAAUlL,EAAMzD,QAgBlB,OAbAyD,EAAMmE,iBAAiB,OAAQyP,GAC/B5T,EAAMmE,iBAAiB,QAAS0P,GAChC7T,EAAMmE,iBAAiB,QAAS2P,GAChC9T,EAAMmE,iBAAiB,aAAc4P,GACrC/T,EAAMmE,iBAAiB,iBAAkB6P,GACzChU,EAAMmE,iBAAiB,WAAY8P,GACnCjU,EAAMmE,iBAAiB,eAAgBgQ,GACvCnU,EAAMmE,iBAAiB,iBAAkBiQ,GACzCpU,EAAMmE,iBAAiB,UAAWkQ,GAClCrU,EAAMmE,iBAAiB,UAAWmQ,GAClCtU,EAAMmE,iBAAiB,UAAWoQ,GAClCvU,EAAMmE,iBAAiB,QAASqQ,GAEzB,KACLxU,EAAMkO,oBAAoB,OAAQ0F,GAClC5T,EAAMkO,oBAAoB,QAAS2F,GACnC7T,EAAMkO,oBAAoB,QAAS4F,GACnC9T,EAAMkO,oBAAoB,aAAc6F,GACxC/T,EAAMkO,oBAAoB,iBAAkB8F,GAC5ChU,EAAMkO,oBAAoB,WAAY+F,GACtCjU,EAAMkO,oBAAoB,eAAgBiG,GAC1CnU,EAAMkO,oBAAoB,iBAAkBkG,GAC5CpU,EAAMkO,oBAAoB,UAAWmG,GACrCrU,EAAMkO,oBAAoB,UAAWoG,GACrCtU,EAAMkO,oBAAoB,UAAWqG,GACrCvU,EAAMkO,oBAAoB,QAASsG,KAEpC,CAAC/J,EAAQC,EAASC,EAASC,EAAcC,EAAYC,EAAgBK,GAASD,EAASvG,GAAY6F,EAAS3L,OAAQgS,KAGvH3L,EAAAA,UAAU,KACR,MAAMuP,EAAyB,KAC7B,MAAMC,IAASzU,SAAS0G,kBACxBwF,GAAgBuI,GAChB3J,IAAqB2J,IAGvB,OADAzU,SAASkE,iBAAiB,mBAAoBsQ,GACvC,IAAMxU,SAASiO,oBAAoB,mBAAoBuG,IAC7D,CAAC1J,IAGJ7F,EAAAA,UAAU,KACR,MAAMlF,EAAQuB,GAAS0B,QACvB,IAAKjD,EAAO,OACZ,MAAM2U,EAAiB,KAAQtI,IAAe,GAAOrB,KAAc,IAC7D4J,EAAiB,KAAQvI,IAAe,GAAQrB,KAAc,IAGpE,OAFAhL,EAAMmE,iBAAiB,wBAAyBwQ,GAChD3U,EAAMmE,iBAAiB,wBAAyByQ,GACzC,KACL5U,EAAMkO,oBAAoB,wBAAyByG,GACnD3U,EAAMkO,oBAAoB,wBAAyB0G,KAEpD,CAAC5J,IAGJ9F,EAAAA,UAAU,KACR,MAAM2P,EAAiB7D,IACrB,GAAK3F,GAAapI,SAAS6R,SAAS7U,SAAS8U,eAE7C,OAAQ/D,EAAEnM,KACR,IAAK,IACL,IAAK,IACHmM,EAAEC,iBACFhC,KACA,MACF,IAAK,IACH+B,EAAEC,iBACFxB,KACA,MACF,IAAK,IACHuB,EAAEC,iBACFrB,KACA,MACF,IAAK,YACHoB,EAAEC,iBACF9B,GAAK/L,GAAc,IACnB,MACF,IAAK,aACH4N,EAAEC,iBACF9B,GAAK/L,GAAc,IACnB,MACF,IAAK,UACH4N,EAAEC,iBACF5B,GAAUvD,GAAc,IACxB,MACF,IAAK,YACHkF,EAAEC,iBACF5B,GAAUvD,GAAc,MAM9B,OADA7L,SAASkE,iBAAiB,UAAW0Q,GAC9B,IAAM5U,SAASiO,oBAAoB,UAAW2G,IACpD,CAAC5F,GAAYQ,GAAYG,GAAkBT,GAAME,GAAWjM,GAAa0I,KAG5E5G,EAAAA,UAAU,IACD,KACDqG,GAAmBtI,SAASyP,aAAanH,GAAmBtI,UAEjE,IAGHiC,EAAAA,UAAU,KACR,MAAM8P,EAAc,IAAMxD,KAC1B,GAAIrD,GAAYI,QAEd,OADAtO,SAASkE,iBAAiB,QAAS6Q,GAC5B,IAAM/U,SAASiO,oBAAoB,QAAS8G,IAEpD,CAAC7G,GAAYI,QAASiD,KAGzByD,EAAAA,oBAAoB7L,EAAK,KAAA,CACvB/F,QAAM0L,SAAOC,QAAMG,QAAME,aAAWE,QAAMC,UAAQC,cAClDE,mBAAiBvI,kBAAgBwI,oBACjCC,YAAUE,WAASG,aAAWC,mBAAiBE,cAC/C6E,eAAgB,IAAM3T,GAAS0B,SAASG,aAAe,EACvD+R,YAAa,IAAM5T,GAAS0B,SAAS5F,UAAY,EACjD+X,UAAW,IAAM7T,GAAS0B,SAAS6G,QAAU,EAC7CkC,QAAS,IAAMzK,GAAS0B,SAASsG,QAAS,EAC1CkC,UAAW,KAAOlK,GAAS0B,SAASiM,OACpChD,aAAc,IAAMA,GACpBmJ,MAAO,IAAMjJ,GACbkJ,gBAAiB,IAAM/T,GAAS0B,UAC9B,CAACI,GAAM0L,GAAOC,GAAMG,GAAME,GAAWE,GAAMC,GAAQC,GAAYE,GAAiBvI,GAAgBwI,GAAkBC,GAAUE,GAASG,GAAWC,GAAiBE,GAAYnE,GAAcE,KAE/L,MAAMmJ,GAAkBlY,GAAY+F,GAAc/F,GAAY,IAAM,EAEpE,OACEmY,EAAAA,KAAA,MAAA,CACEpM,IAAKiC,GACLrD,UAAWA,EACXc,MAAO,CACLlE,SAAUsH,GAAe,QAAU,WACnCuJ,MAAOvJ,GAAe,OAAIjP,EAC1ByY,OAAQxJ,GAAe,UAAOjP,EAC9BkB,QACAwX,YAAazJ,GAAe,QAAU,OACtC7N,OAAQ6N,GAAe,YAASjP,EAChC2Y,gBAAiB,OACjBC,SAAU,SACVC,WAAY,OACZC,WAAY,oEACZC,wBAAyB,cACzBC,YAAa,kBACVnN,GAELoN,YAAc3I,QAA6BtQ,EAAlB0V,GACzBwD,aAAc,KAAO5I,IAAY9B,IAAaiB,IAAgB,GAC9D0J,aAAcxD,GACdyD,YAAapD,GACbqD,WAAY/C,GACZgD,cAAexF,GACfyF,SAAU,YAGV3O,EAAAA,IAAA,QAAA,CACEuB,IAAK7H,GACLuH,MAAO,CAAE3K,MAAO,OAAQE,OAAQ,OAAQoY,UAAW,WACnDxV,IAAK2N,GACLvF,OAAQwF,GACRvF,SAAUA,EACVC,MAAOA,EACPC,KAAMqD,GACNtC,QAASA,EACTmM,eACAC,QAAShD,GAAgB1L,SAExBgC,EAAW2M,IAAI,CAACC,EAAOnP,IACtBG,MAAA,QAAA,CAAeiP,KAAMD,EAAMC,KAAM7V,IAAK4V,EAAM5V,IAAK8V,QAASF,EAAMG,QAAStG,MAAOmG,EAAMnG,MAAOuG,QAASJ,EAAMI,SAAhGvP,MAKf4E,KAAc/P,IACbsL,EAAAA,WAAKiB,MAAO,CAAElE,SAAU,WAAY6Q,MAAO,EAAGyB,QAAS,OAAQC,WAAY,SAAUC,eAAgB,SAAUC,cAAe,QAAQpP,SACpIJ,EAAAA,IAAA,MAAA,CACEiB,MAAO,CACL3K,MAAO,GACPE,OAAQ,GACRiZ,OAAQ,wBACRC,eAAgBpN,EAChBqN,aAAc,MACdC,UAAW,+BAOnB5P,EAAAA,IAAA,QAAA,CAAAI,SAAQ,0DAGP1L,IACCiZ,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CAAElE,SAAU,WAAY6Q,MAAO,EAAGyB,QAAS,OAAQQ,cAAe,SAAUP,WAAY,SAAUC,eAAgB,SAAUO,WAAY,kBAAmBC,MAAO,QAAQ3P,SAAA,CACpLJ,MAAA,MAAA,CAAKiB,MAAO,CAAE3K,MAAO,GAAIE,OAAQ,GAAIwZ,aAAc,IAAM9P,KAAMoC,EAAarC,QAAQ,YAAWG,SAC7FJ,MAAA,OAAA,CAAMK,EAAE,uGAEVL,EAAAA,IAAA,IAAA,CAAGiB,MAAO,CAAEgP,SAAU,GAAID,aAAc,GAAG5P,SAAA,wBAC3CJ,EAAAA,IAAA,SAAA,CACE8O,QAAS,KAAQnK,GAAS,MAAOjL,GAAS0B,SAAS8U,QACnDjP,MAAO,CACLkP,QAAS,WACTR,aAAc,EACdF,OAAQ,OACR1B,gBAAiBzL,EACjByN,MAAO,OACPK,WAAY,IACZC,OAAQ,WACTjQ,SAAA,aAQNrG,IAAeG,IACdyT,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CAAElE,SAAU,WAAY6Q,MAAO,EAAGC,OAAQ,IAAIzN,SAAA,CACxDuN,OAAA,MAAA,CAAK1M,MAAO,CAAElE,SAAU,WAAY2M,IAAK,GAAIF,KAAM,GAAI6F,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,WAAYL,WAAY,kBAAmBH,aAAc,EAAGI,MAAO,OAAQE,SAAU,IAAI7P,SAAA,CACvMJ,MAAA,OAAA,CAAMiB,MAAO,CAAEkP,QAAS,UAAWR,aAAc,EAAGM,SAAU,GAAIG,WAAY,IAAKrC,gBAAiBzL,EAAayN,MAAO,QAAQ3P,SAAA,OAChIuN,EAAAA,KAAA,OAAA,CAAAvN,SAAA,CAAO9C,EAAWlD,IAAgB,mBAEpC4F,MAAA,SAAA,CACE8O,QAASxU,GAAUoC,QAAStH,EAC5Bmb,UAAWjW,GACX2G,MAAO,CACLlE,SAAU,WACVyT,OAAQ,GACRC,MAAO,GACPN,QAAS,WACTV,OAAQ,iBACRE,aAAc,EACdG,WAAY,cACZC,MAAO,OACPM,OAAQ/V,GAAU,UAAY,cAC9BoW,QAASpW,GAAU,EAAI,IACxB8F,SAEA9F,GAAU,UAAY,WAAWuB,KAAK8U,KAAKvW,YAMjDkM,GAAYI,SACXiH,OAAA,MAAA,CACE1M,MAAO,CACLlE,SAAU,WACVyM,KAAMlD,GAAYE,EAClBkD,IAAKpD,GAAYG,EACjBoH,OAAQ,GACR+C,SAAU,IACVd,WAAY,sBACZe,eAAgB,YAChBlB,aAAc,EACdmB,UAAW,6BACX9C,SAAU,UAEZc,QAAS3F,GAAKA,EAAE4H,4BAGhBpD,OAAA,SAAA,CACE1M,MAAO,CAAE3K,MAAO,OAAQ+Y,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMlC,QAAS,KAAQ1H,KAAcuC,MAAqBvJ,SAAA,CAEnDwD,GAAY5D,EAAAA,IAACM,MAAeN,EAAAA,IAACD,EAAQ,CAAA,GACtCC,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAEgQ,KAAM,GAAG7Q,SAAGwD,GAAY,QAAU,SACjD5D,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAE8O,MAAO,wBAAyBE,SAAU,IAAI7P,SAAA,aAI/DuN,EAAAA,KAAA,SAAA,CACE1M,MAAO,CAAE3K,MAAO,OAAQ+Y,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMlC,QAAS,KAAQlH,KAAc+B,MAAqBvJ,SAAA,CAEnD+D,GAAUnE,EAAAA,IAACQ,MAAoBR,EAAAA,IAACO,EAAc,CAAA,GAC/CP,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAEgQ,KAAM,YAAM9M,GAAU,SAAW,SAChDnE,cAAMiB,MAAO,CAAE8O,MAAO,wBAAyBE,SAAU,IAAI7P,SAAA,SAG/DJ,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAEzK,OAAQ,EAAGsZ,WAAY,wBAAyBoB,OAAQ,WAGtEvD,EAAAA,eACE1M,MAAO,CAAE3K,MAAO,OAAQ+Y,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMlC,QAAS,KAAQjH,KAAc8B,MAAqBvJ,SAAA,CAEpDJ,EAAAA,IAACa,EAAU,CAAA,GACXb,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAEgQ,KAAM,GAAG7Q,SAAG4E,GAAS,eAAiB,gBACpDA,IAAUhF,EAAAA,YAAMiB,MAAO,CAAE8O,MAAOzN,GAAalC,SAAA,SAIhDuN,EAAAA,KAAA,SAAA,CACE1M,MAAO,CAAE3K,MAAO,OAAQ+Y,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMlC,QAAS,KACP,MAAMqC,EAAS,CAAC,GAAK,IAAM,EAAG,KAAM,IAAK,GACnCC,EAAMD,EAAOE,QAAQvM,IAC3BwD,GAAgB6I,GAAQC,EAAM,GAAKD,EAAOna,SAC1C2S,gBAGF3J,EAAAA,IAACe,MACD4M,EAAAA,KAAA,OAAA,CAAM1M,MAAO,CAAEgQ,KAAM,GAAG7Q,SAAA,CAAA,UAAU0E,GAAY,UAGhD9E,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAEzK,OAAQ,EAAGsZ,WAAY,wBAAyBoB,OAAQ,WAGrErP,GAAOzJ,SAASwG,yBACf+O,EAAAA,KAAA,SAAA,CACE1M,MAAO,CAAE3K,MAAO,OAAQ+Y,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMlC,QAAS,KAAQzG,KAAasB,gBAE9B3J,EAAAA,IAACW,EAAO,CAAA,GACRX,EAAAA,YAAMiB,MAAO,CAAEgQ,KAAM,GAAG7Q,SAAA,uBACxBJ,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAE8O,MAAO,wBAAyBE,SAAU,sBAK7DtC,EAAAA,KAAA,SAAA,CACE1M,MAAO,CAAE3K,MAAO,OAAQ+Y,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMlC,QAAS,KAAQ/G,KAAoB4B,gBAErC3J,EAAAA,IAACS,MACDT,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAEgQ,KAAM,GAAG7Q,SAAA,eACxBJ,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAE8O,MAAO,wBAAyBE,SAAU,IAAI7P,SAAA,SAG/DJ,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAEzK,OAAQ,EAAGsZ,WAAY,wBAAyBoB,OAAQ,WAGtEvD,EAAAA,KAAA,SAAA,CACE1M,MAAO,CAAE3K,MAAO,OAAQ+Y,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMlC,QAASjF,aAET7J,EAAAA,IAACc,MACDd,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAEgQ,KAAM,GAAG7Q,SAAA,sBAI1BJ,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAEkP,QAAS,WAAYF,SAAU,GAAIF,MAAO,wBAAyBiB,UAAW,SAAUM,UAAW,mCAAmClR,SAAA,0CAOvJwB,IAAa7H,IACZ4T,EAAAA,KAAA,MAAA,CACE1M,MAAO,CACLlE,SAAU,WACVyT,OAAQ,EACRhH,KAAM,EACNiH,MAAO,EACPX,WAAY,gEACZK,QAASzK,GAAW,iBAAmB,GACvC6L,WAAY7L,GAAW,GAAK,GAC5B8L,WAAY,eACZd,QAAS9L,GAAe,EAAI,EAC5B4K,cAAe5K,GAAe,OAAS,QACxCxE,SAAA,CAGDuN,EAAAA,YACEpM,IAAKkC,GACLqL,QAAS9E,GACTqE,YAAc3I,QAAiCtQ,EAAtB6U,GACzBqE,aAAe5I,QAAiCtQ,EAAtBsV,GAC1B8D,YAAcrF,IACZ,GAAIzD,GAAU,CACZ,MAAM2D,EAAO5F,GAAYrI,SAASkO,wBAClC,GAAID,GAAQ7T,GAAU,CACpB,MAAMwV,EAAQ7B,EAAE8B,QAAQ,GAClBlP,EAAUF,KAAKC,IAAI,EAAGD,KAAKsC,IAAI,GAAI6M,EAAMzB,QAAUF,EAAKG,MAAQH,EAAK/S,QAC3EiP,GAAa,CAAEgC,KAAMxL,EAAUvG,GAAUgR,EAAGwE,EAAMzB,QAAUF,EAAKG,MACnE,CACF,GAEFiF,WAAatF,IACPzD,IAAYJ,KACdgC,GAAKhC,GAAUiC,MACfhC,GAAa,QAGjBtE,MAAO,CACLlE,SAAU,WACVvG,OAAQkP,GAAW,GAAK,EACxBoK,WAAY,wBACZH,aAAcjK,GAAW,EAAI,EAC7BsK,aAActK,GAAW,GAAK,GAC9B2K,OAAQ,WACTjQ,SAAA,CAGDJ,EAAAA,IAAA,MAAA,CACEiB,MAAO,CACLlE,SAAU,WACV2M,IAAK,EACLF,KAAM,EACNhT,OAAQ,OACRF,MAAO,GAAGsJ,MACVkQ,WAAY,wBACZH,aAAcjK,GAAW,EAAI,KAIjC1F,EAAAA,IAAA,MAAA,CACEiB,MAAO,CACLlE,SAAU,WACV2M,IAAK,EACLF,KAAM,EACNhT,OAAQ,OACRF,MAAO,GAAGoX,MACVoC,WAAYxN,EACZqN,aAAcjK,GAAW,EAAI,KAIjC1F,EAAAA,IAAA,MAAA,CACEiB,MAAO,CACLlE,SAAU,WACV2M,IAAK,MACLF,KAAM,GAAGkE,MACT+D,UAAW,wBACXnb,MAAOoP,GAAW,GAAK,GACvBlP,OAAQkP,GAAW,GAAK,GACxBiK,aAAc,MACdG,WAAYxN,EACZwO,UAAW,+BAIdxL,IACCqI,EAAAA,KAAA,MAAA,CACE1M,MAAO,CACLlE,SAAU,WACVyT,OAAQ,OACRhH,KAAMlE,GAAUkB,EAChBiL,UAAW,mBACXzB,aAAc,GACdX,QAAS,OACTQ,cAAe,SACfP,WAAY,SACZE,cAAe,QAChBpP,SAAA,CAGAoF,IACCxF,EAAAA,IAAA,MAAA,CACEiB,MAAO,CACL3K,MAAO,IACPE,OAAQ,GACRmZ,aAAc,EACd3B,SAAU,SACVgC,aAAc,EACdc,UAAW,6BACXrB,OAAQ,aAAanN,KACtBlC,SAEDJ,MAAA,MAAA,CAAK5G,IAAKoM,GAAckM,IAAI,GAAGzQ,MAAO,CAAE3K,MAAO,OAAQE,OAAQ,OAAQoY,UAAW,aAItF5O,EAAAA,IAAA,MAAA,CACEiB,MAAO,CACLkP,QAAS,WACTL,WAAY,kBACZC,MAAO,OACPE,SAAU,GACVG,WAAY,IACZT,aAAc,EACdgC,WAAY,UACbvR,SAEA9C,EAAWgI,GAAUiC,cAO9BoG,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CAAEoO,QAAS,OAAQC,WAAY,SAAUgB,IAAK5K,GAAW,EAAI,EAAGkM,SAAU,QAAQxR,SAAA,CAE5FJ,EAAAA,IAAA,SAAA,CACE8O,QAAS1H,GAAU,aACPxD,GAAY,QAAU,OAClC3C,MAAO,CACL3K,MAAOoP,GAAW,GAAK,GACvBlP,OAAQkP,GAAW,GAAK,GACxBkL,SAAUlL,GAAW,GAAK,GAC1B2J,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBI,aAAc,MACdG,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRF,QAAS,GACV/P,SAEAwD,GAAY5D,EAAAA,IAACM,EAAS,CAAA,GAAMN,EAAAA,IAACD,QAI/BmC,IAAkBwD,IACjBiI,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CAAEoO,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAGlQ,SAAA,CAC3DJ,EAAAA,IAAA,SAAA,CACE8O,QAASlH,GAAU,aACPzD,GAAU,SAAW,OACjClD,MAAO,CACL3K,MAAO,GACPE,OAAQ,GACR6Y,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,WACTjQ,SAEA+D,IAA2B,IAAhBF,GAAoBjE,EAAAA,IAACQ,EAAc,IAAMR,EAAAA,IAACO,EAAc,CAAA,KAEtEP,EAAAA,IAAA,QAAA,CACE3J,KAAK,QACL8H,IAAI,IACJrC,IAAI,IACJ+V,KAAK,OACL5T,MAAOkG,GAAU,EAAIF,GACrB6N,SAAU3I,GAAK3B,GAAUpQ,WAAW+R,EAAE4I,OAAO9T,QAC7CgD,MAAO,CACL3K,MAAO,GACPE,OAAQ,EACR6Z,OAAQ,UACR/N,YAAaA,QAOpBJ,GAAiBwD,IAChB1F,EAAAA,IAAA,SAAA,CACE8O,QAASlH,gBACGzD,GAAU,SAAW,OACjClD,MAAO,CACL3K,MAAO,GACPE,OAAQ,GACRoa,SAAU,GACVvB,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRF,QAAS,GACV/P,SAEA+D,IAA2B,IAAhBF,GAAoBjE,EAAAA,IAACQ,EAAc,IAAMR,EAAAA,IAACO,EAAc,CAAA,KAKxEoN,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CACV8O,MAAO,OACPE,SAAUvK,GAAW,GAAK,GAC1BwI,WAAY,YACZ8D,WAAYtM,GAAW,EAAI,EAC3BiM,WAAY,UACbvR,SAAA,CACE9C,EAAW/B,KAAemK,IAAY,MAAMpI,EAAW9H,SAI1DwK,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAEgQ,KAAM,KAGnBtO,EAAS3L,OAAS,GACjBgJ,MAAA,SAAA,CACE8O,QAAS,IAAMzJ,IAAiBD,IAAa,aAClC,WACXnE,MAAO,CACL3K,MAAOoP,GAAW,GAAK,GACvBlP,OAAQkP,GAAW,GAAK,GACxBkL,SAAUlL,GAAW,GAAK,GAC1B2J,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY1K,GAAe,wBAA0B,cACrDqK,OAAQ,OACRM,MAAOzN,EACP+N,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV/P,SAEDJ,EAAAA,IAACgB,EAAY,CAAA,MAKf0E,IACA1F,EAAAA,IAAA,SAAA,CACE8O,QAAS,KACP,MAAM3W,EAAQuB,GAAS0B,QACnBjD,GAAS,WAAYA,EACtBA,EAAc8Z,OAAOC,SAASzV,MAAM,KACnC0V,MAAM,8BAGRA,MAAM,0BAET,aACU,OACXlR,MAAO,CACL3K,MAAO,GACPE,OAAQ,GACR6Y,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV/P,SAEDJ,EAAAA,IAACkB,EAAQ,CAAA,KAKZa,GACC4L,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CAAElE,SAAU,YAAYqD,SAAA,CAClCJ,MAAA,SAAA,CACE8O,QAAS,IAAM3J,IAAiBD,IAAa,aAClC,WACXjE,MAAO,CACL3K,MAAOoP,GAAW,GAAK,GACvBlP,OAAQkP,GAAW,GAAK,GACxBkL,SAAUlL,GAAW,GAAK,GAC1B2J,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY5K,GAAe,wBAA0B,cACrDuK,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV/P,SAEDJ,EAAAA,IAACY,EAAY,MAGdsE,IACCyI,EAAAA,KAAA,MAAA,CACE1M,MAAO,CACLlE,SAAU2I,GAAW,QAAU,WAC/B8K,OAAQ9K,GAAW,EAAI,GACvB+K,MAAkB,EAClBjH,KAAM9D,GAAW,OAAItQ,EACrBwb,SAAUlL,GAAW,OAAS,IAC9B0M,UAAW1M,GAAW,YAAStQ,EAC/B0a,WAAY,UACZH,aAAcjK,GAAW,gBAAkB,EAC3CoL,UAAW,6BACXrB,OAAQ/J,GAAW,OAAS,kCAC5BmI,OAAQ,IACRG,SAAU,UACX5N,SAAA,CAEDJ,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAEkP,QAAS,YAAakC,aAAc,kCAAmCtC,MAAO,wBAAyBE,SAAU,GAAIG,WAAY,IAAKkC,cAAe,YAAaC,cAAe,IAAKnS,SAAA,mBAGnM4B,EAAe+M,IAAIyD,GAClB7E,EAAAA,KAAA,SAAA,CAEEmB,QAAS,IAAMxG,GAAgBkK,GAC/BvR,MAAO,CACL3K,MAAO,OACP6Z,QAASzK,GAAW,YAAc,YAClC2J,QAAS,OACTC,WAAY,SACZC,eAAgB,gBAChBO,WAAY,cACZL,OAAQ,OACRM,MAAOjL,KAAiB0N,EAAQlQ,EAAc,OAC9C+N,OAAQ,UACRJ,SAAUvK,GAAW,GAAK,IAC3BtF,SAAA,CAEDJ,EAAAA,IAAA,OAAA,CAAAI,SAAiB,IAAVoS,EAAc,SAAW,GAAGA,OAClC1N,KAAiB0N,GAASxS,MAAA,OAAA,CAAAI,SAAA,QAhBtBoS,UAyBhB3Q,IAAQ6D,IAAYtN,SAASwG,yBAC5BoB,EAAAA,IAAA,SAAA,CACE8O,QAASzG,GAAS,aACP,qBACXpH,MAAO,CACL3K,MAAO,GACPE,OAAQ,GACR6Y,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV/P,SAEDJ,EAAAA,IAACW,EAAO,CAAA,KAKXmB,GACC9B,EAAAA,IAAA,SAAA,CACE8O,QAAS/G,GAAgB,aACb1D,GAAe,kBAAoB,aAC/CpD,MAAO,CACL3K,MAAOoP,GAAW,GAAK,GACvBlP,OAAQkP,GAAW,GAAK,GACxBkL,SAAUlL,GAAW,GAAK,GAC1B2J,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV/P,SAEAiE,GAAerE,EAAAA,IAACU,EAAkB,CAAA,GAAMV,EAAAA,IAACS,EAAc,CAAA,WAQjEiF,IAAYE,IAAcN,IACzBtF,EAAAA,IAAA,MAAA,CACEiB,MAAO,CACLlE,SAAU,WACV2M,IAAK,MACLF,KAAM,MACNiI,UAAW,wBACXtB,QAAS,YACTL,WAAY,kBACZH,aAAc,EACdI,MAAO,OACPE,SAAU,GACVG,WAAY,IACZZ,cAAe,QAChBpP,SAEA9C,EAAWgI,GAAUiC,SAKxB3D,KAAca,KAAc/P,KAAUqF,IACtCiG,EAAAA,IAAA,SAAA,CACE8O,QAAS1H,GAAU,aACR,OACXnG,MAAO,CACLlE,SAAU,WACV2M,IAAK,MACLF,KAAM,MACNiI,UAAW,wBACXnb,MAAOoP,GAAW,GAAK,GACvBlP,OAAQkP,GAAW,GAAK,GACxBiK,aAAc,MACdN,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBE,OAAQ,OACRY,OAAQ,UACRtC,gBAAiBzL,EACjBwO,UAAW,6BACXU,WAAY,iBACZrB,QAAS,GACV/P,SAEDJ,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,QAAQe,MAAO,CAAE3K,MAAOoP,GAAW,GAAK,GAAIlP,OAAQkP,GAAW,GAAK,GAAIsM,WAAY,GAAG5R,SACnHJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,sBAMbsC,EAAS3L,OAAS,GACjB2W,OAAA,MAAA,CACE1M,MAAO,CACLlE,SAAU,WACV2M,IAAKhE,GAAW,OAAS,EACzB8K,OAAQ9K,GAAW,OAAItQ,EACvBqb,MAAO,EACPjH,KAAM9D,GAAW,OAAItQ,EACrBkB,MAAOoP,GAAW,OAAS,IAC3BlP,OAAQkP,GAAW,MAAQ,OAC3B0M,UAAW1M,GAAW,YAAStQ,EAC/B0a,WAAYpK,GACR,sEACA,uEACJmL,eAAgB,aAChBY,UAAWrM,GACP,gBACCM,GAAW,mBAAqB,mBACrC8L,WAAY,0BACZ3D,OAAQ,GACRwB,QAAS,OACTQ,cAAe,SACf4C,WAAY/M,GAAW,OAAS,kCAChC4L,UAAW5L,GAAW,kCAAoC,OAC1DiK,aAAcjK,GAAW,gBAAkB,GAC5CtF,SAAA,CAGAsF,IACC1F,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CACV3K,MAAO,GACPE,OAAQ,EACRsZ,WAAY,wBACZH,aAAc,EACduB,OAAQ,mBAKZvD,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CACVoO,QAAS,OACTC,WAAY,SACZC,eAAgB,gBAChBY,QAASzK,GAAW,iBAAmB,YACvC2M,aAAc,mCACfjS,SAAA,CACCuN,OAAA,MAAA,CAAAvN,SAAA,CACEJ,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAE8O,MAAOzN,EAAa2N,SAAU,GAAIG,WAAY,IAAKkC,cAAe,YAAaC,cAAe,EAAGvC,aAAc,GAAG5P,SAAA,aAGhIuN,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CAAE8O,MAAO,OAAQE,SAAU,GAAIG,WAAY,eACpDzN,EAAS3L,OAAM,gBAGpBgJ,EAAAA,IAAA,SAAA,CACE8O,QAAS,IAAMzJ,IAAgB,GAC/BpE,MAAO,CACL3K,MAAOoP,GAAW,GAAK,GACvBlP,OAAQkP,GAAW,GAAK,GACxB2J,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,wBACZL,OAAQ,OACRE,aAAc,MACdI,MAAO,OACPM,OAAQ,WACTjQ,SAEDJ,EAAAA,IAACmB,EAAS,CAAA,QAKdnB,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAEgQ,KAAM,EAAGyB,UAAW,OAAQvC,QAAS,SAAS/P,SACzDuC,EAASoM,IAAI,CAAC4D,EAAMC,IACnBjF,EAAAA,eAEEmB,QAAS,KACPlI,GAAwBgM,GACxBrP,KAAuBqP,EAAOD,GAC9BjU,WAAW,IAAMlD,KAAQ,MAE3ByF,MAAO,CACL3K,MAAO,OACP6Z,QAAS,YACTd,QAAS,OACTC,WAAY,SACZgB,IAAK,GACLR,WAAYnJ,KAAyBiM,EAAQ,GAAGtQ,MAAkB,cAClEmN,OAAQ,OACRgD,WAAY9L,KAAyBiM,EAAQ,aAAatQ,IAAgB,wBAC1E+N,OAAQ,UACRW,UAAW,OACXQ,WAAY,mBACbpR,SAAA,CAGDuN,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CACV3K,MAAO,IACPE,OAAQ,GACRmZ,aAAc,EACd3B,SAAU,SACV8B,WAAY,OACZ+C,WAAY,EACZ9V,SAAU,YACXqD,SAAA,CACEuS,EAAKnR,OACJxB,EAAAA,IAAA,MAAA,CAAK5G,IAAKuZ,EAAKnR,OAAQkQ,IAAI,GAAGzQ,MAAO,CAAE3K,MAAO,OAAQE,OAAQ,OAAQoY,UAAW,WAEjF5O,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAE3K,MAAO,OAAQE,OAAQ,OAAQ6Y,QAAS,OAAQC,WAAY,SAAUC,eAAgB,SAAUQ,MAAO,yBAAyB3P,SAC5IJ,EAAAA,IAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAee,MAAO,CAAE3K,MAAO,GAAIE,OAAQ,IAAI4J,SAC3EJ,EAAAA,IAAA,OAAA,CAAMK,EAAE,uHAKbsG,KAAyBiM,GACxB5S,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CACVlE,SAAU,WACV6Q,MAAO,EACPkC,WAAY,kBACZT,QAAS,OACTC,WAAY,SACZC,eAAgB,UACjBnP,SACCJ,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CAAE8O,MAAOzN,EAAa2N,SAAU,IAAI7P,SAAA,QAInDuS,EAAKnd,UACJwK,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CACVlE,SAAU,WACVyT,OAAQ,EACRC,MAAO,EACPN,QAAS,UACTL,WAAY,kBACZH,aAAc,EACdM,SAAU,GACVF,MAAO,OACPK,WAAY,KACbhQ,SACE9C,EAAWqV,EAAKnd,eAMvBmY,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CAAEgQ,KAAM,EAAGL,SAAU,GAAGxQ,SAAA,CAClCJ,EAAAA,IAAA,MAAA,CAAKiB,MAAO,CACV8O,MAAOpJ,KAAyBiM,EAAQtQ,EAAc,OACtD2N,SAAU,GACVG,WAAYzJ,KAAyBiM,EAAQ,IAAM,IACnD5E,SAAU,SACV8E,aAAc,WACdnB,WAAY,SACZ3B,aAAc,GACf5P,SACEuS,EAAK1d,QAER0Y,EAAAA,KAAA,MAAA,CAAK1M,MAAO,CAAE8O,MAAO,wBAAyBE,SAAU,IAAI7P,SAAA,CAAA,SACnDwS,EAAQ,UApFdD,EAAK5d,UA8FpBiL,EAAAA,IAAA,QAAA,CACEuB,IAAKyE,GACL5M,IAAK2N,GACL9F,MAAO,CAAEoO,QAAS,QAClB3N,SACAgB,QAAQ,aAIV1C,EAAAA,IAAA,SAAA,CAAQuB,IAAKwE,GAAkB9E,MAAO,CAAEoO,QAAS,eAMzDjO,EAAgB2R,YAAc,wBC33CxBC,EAA0B,CAC9BzL,EACA0L,EACAC,EACA5c,EACAE,KAEA,MAAMoc,EAAQ/W,KAAK8B,MAAM4J,EAAO2L,GAKhC,MAAO,IAFKN,EAFI,GAICtc,QAHLuF,KAAK8B,MAAMiV,EADP,IAImBpc,OCtMxB+J,EAAsC,EAAGJ,YAAWgT,OAAO,MACtEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,kMAICgT,EAAwC,EAAGlT,YAAWgT,OAAO,MACxEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,+FAICiT,EAAqC,EAAGnT,YAAWgT,OAAO,MACrEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,6BAICG,EAAsC,EAAGL,YAAWgT,OAAO,MACtEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,sWAwDCO,EAAoC,EAAGT,YAAWgT,OAAO,MACpEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,osBA+FCkT,EAAiC,EAAGpT,YAAWgT,OAAO,MACjEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,uGAICmT,EAAwC,EAAGrT,YAAWgT,OAAO,MACxEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,yIAICoT,EAAiC,EAAGtT,YAAWgT,OAAO,MACjEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,wDC5QNqT,EAAyC,CAC7C,EAAG,kCACH,EAAG,oDACH,EAAG,0DACH,EAAG,sCCECC,EAA4B,CAChC/P,WAAW,EACXgQ,UAAU,EACVC,SAAS,EACTC,aAAa,EACbC,WAAW,EACX1P,cAAc,EACdmJ,OAAO,EACPrJ,SAAS,EACTpK,aAAa,EACbkI,OAAQ,EACR1G,YAAa,EACb/F,SAAU,EACVoK,SAAU,EACVkF,aAAc,EACdkP,eAAgB,KAChBtf,MAAO,MC3BHuf,EAA0C,CAC9CzY,KAAM,QACNkM,KAAM,IACN5F,WAAY,IACZD,IAAK,IACLqS,YAAa,aACbC,aAAc,YACdC,SAAU,UACVC,WAAY,+BCEqC,EACjDrc,KACAsc,gBACAha,UACAia,SACAzF,cAEA,MAAM0F,EAAWla,EACb,UACA,WAAWuB,KAAK8U,KAAK2D,MAEzB,OACE3G,EAAAA,YAAKxN,UAAU,gCAAgC2O,QAASA,EAAO1O,SAAA,CAE7DuN,EAAAA,YAAKxN,UAAU,6BAA4BC,SAAA,CACzCJ,EAAAA,IAAA,OAAA,CAAMG,UAAU,8CAChBwN,EAAAA,KAAA,OAAA,CAAAvN,SAAA,CAAO9C,EAAWgX,GAAc,mBAIjCtc,EAAGzC,cACFoY,EAAAA,eACExN,UAAU,mCACV2O,QAAU3F,IACRA,EAAE4H,kBACFjC,KAEFzY,KAAK,SAAQ+J,SAAA,CAEbJ,EAAAA,IAACwT,EAAgB,CAACL,KAAM,qBAM5BnT,EAAAA,IAAA,SAAA,CACEG,UAAU,6BACV2O,QAAU3F,IACRA,EAAE4H,kBACEzW,GAASia,KAEfhE,UAAWjW,EACXjE,KAAK,SAAQ+J,SAEZoU,6BJ6FwC,EAAGrU,YAAWgT,OAAO,MACpEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,2UChJ6C,EAAG3L,QAAO+f,cACjE,MAAMC,EAAYhgB,GAAOigB,MAAQ,EAC3BC,EAAUlB,EAAegB,IAAc,6BAE7C,OACE/G,EAAAA,KAAA,MAAA,CAAKxN,UAAU,qCACbH,EAAAA,IAACuT,EAAS,CAACpT,UAAU,gCAAgCgT,KAAM,KAC3DnT,EAAAA,IAAA,MAAA,CAAKG,UAAU,mCAAkCC,SAAEwU,IAClDF,EAAY,GACX/G,EAAAA,KAAA,MAAA,CAAKxN,UAAU,gCAA+BC,SAAA,CAAA,eAAcsU,KAE9D1U,EAAAA,IAAA,SAAA,CACEG,UAAU,iCACV2O,QAAS2F,EACTpe,KAAK,SAAQ+J,SAAA,qFDgK2B,EAAGD,YAAWgT,OAAO,MACnEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,yEApG2C,EAAGF,YAAWgT,OAAO,MAC1EnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,2GAtBuC,EAAGF,YAAWgT,OAAO,MACtEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,oGKxFiC,EAAGqG,aACzCA,EAGH1G,MAAA,MAAA,CAAKG,UAAU,4BAA2BC,SACxCJ,EAAAA,IAAA,MAAA,CAAKG,UAAU,wCAJE,uBLauB,EAAGA,YAAWgT,OAAO,MACjEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,0DA+FoC,EAAGF,YAAWgT,OAAO,MACnEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,8HAtBgC,EAAGF,YAAWgT,OAAO,MAC/DnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,kIAjHiC,EAAGF,YAAWgT,OAAO,MAChEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,kEDE2C,EACrD9E,cACA/F,WACAoK,WACAiV,SACAC,mBACAvE,YAAW,MAEX,MAAM9M,EAAchJ,EAAAA,OAAuB,OACpCsa,EAAYC,GAAiB/a,EAAAA,UAAS,IACtCqL,EAAWC,GAAgBtL,EAAAA,SAAwB,OACnDgb,EAAeC,GAAoBjb,EAAAA,SAAS,GAE7Ckb,EAAgBnX,EAAWzC,EAAa/F,GACxC4f,EAAkBpX,EAAW4B,EAAUpK,GAEvC6f,EAAsBla,cACzBoO,IACC,IAAK9F,EAAYrI,QAAS,OAAO,EACjC,MAAMiO,EAAO5F,EAAYrI,QAAQkO,wBAEjC,OADgBlL,GAAOmL,EAAUF,EAAKG,MAAQH,EAAK/S,MAAO,EAAG,GAC5Cd,GAEnB,CAACA,IAGGsV,EAAkB3P,cACrBgO,IACC,IAAK1F,EAAYrI,QAAS,OAC1B,MAAMiO,EAAO5F,EAAYrI,QAAQkO,wBAC3BC,EAAU,YAAaJ,EAAIA,EAAEI,QAAU,EACvCxM,EAAWqB,EAAMmL,EAAUF,EAAKG,KAAM,EAAGH,EAAK/S,OAC9CiR,EAAO8N,EAAoB9L,GAEjC2L,EAAiBnY,GACjBwI,EAAagC,GAETwN,GACFF,EAAOtN,IAGX,CAACwN,EAAYM,EAAqBR,IAG9BS,EAAkBna,cACrBgO,IACC,GAAIoH,EAAU,OACdpH,EAAEC,iBACF4L,GAAc,GACd,MAAMzN,EAAO8N,EAAoBlM,EAAEI,SACnCsL,EAAOtN,IAET,CAACgJ,EAAU8E,EAAqBR,IAG5BU,EAAgBpa,EAAAA,YAAY,KAChC6Z,GAAc,IACb,IAEGQ,EAAmBra,cACtBgO,IACC,MAAM5B,EAAO8N,EAAoBlM,EAAEI,SACnChE,EAAagC,IAEf,CAAC8N,IAGGI,EAAmBta,EAAAA,YAAY,KACnCoK,EAAa,OACZ,IAGHlI,EAAAA,UAAU,KACR,GAAI0X,EAAY,CACd,MAAMW,EAAoBvM,GAAkB2B,EAAgB3B,GAI5D,OAHAzN,OAAOY,iBAAiB,YAAaoZ,GACrCha,OAAOY,iBAAiB,UAAWiZ,GAE5B,KACL7Z,OAAO2K,oBAAoB,YAAaqP,GACxCha,OAAO2K,oBAAoB,UAAWkP,GAE1C,GAEC,CAACR,EAAYjK,EAAiByK,IAGjC,MAAMxK,EAAmB5P,cACtBgO,IACC,GAAIoH,EAAU,OACdpH,EAAEC,iBACF,MAAM4B,EAAQ7B,EAAE8B,QAAQ,GAClB1D,EAAO8N,EAAoBrK,EAAMzB,SACvCyL,GAAc,GACdH,EAAOtN,IAET,CAACgJ,EAAU8E,EAAqBR,IAG5BzJ,EAAkBjQ,cACrBgO,IACC,IAAK4L,EAAY,OACjB,MAAM/J,EAAQ7B,EAAE8B,QAAQ,GAClB1D,EAAO8N,EAAoBrK,EAAMzB,SACvCsL,EAAOtN,IAET,CAACwN,EAAYM,EAAqBR,IAG9BnJ,EAAiBvQ,EAAAA,YAAY,KACjC6Z,GAAc,IACb,IAEH,OACErH,OAAA,MAAA,CACEpM,IAAKkC,EACLtD,UAAU,wCACVwV,YAAaL,EACbM,aAAcJ,EACdlH,aAAcmH,EACdpH,YAAclF,GAAM2B,EAAgB3B,EAAE0M,aACtCtH,aAAcxD,EACdyD,YAAapD,EACbqD,WAAY/C,EACZoK,KAAK,SAAQ,aACF,iBAAgB,gBACZ,EAAC,gBACDtgB,kBACA+F,EAAW,iBACV+B,EAAW/B,GAC3BoT,SAAU,EAACvO,SAAA,CAEXuN,EAAAA,KAAA,MAAA,CAAKxN,UAAU,8BAA6BC,SAAA,CAC1CJ,EAAAA,IAAA,MAAA,CACEG,UAAU,uCACVc,MAAO,CAAE3K,MAAO,GAAG8e,QAErBpV,EAAAA,IAAA,MAAA,CACEG,UAAU,qCACVc,MAAO,CAAE3K,MAAO,GAAG6e,QAErBnV,EAAAA,IAAA,MAAA,CACEG,UAAU,qCACVc,MAAO,CAAEuI,KAAM,GAAG2L,WAKrBL,GAAkBiB,SAAyB,OAAdzQ,GAC5BqI,EAAAA,KAAA,MAAA,CACExN,UAAU,uCACVc,MAAO,CACLuI,KAAM,GAAGyL,MACT3e,MAAOwe,EAAiBxe,OAAS,IACjCE,OAAQse,EAAiBte,QAAU,IACpC4J,SAAA,CAEA0U,EAAiBkB,SAChBhW,EAAAA,IAAA,MAAA,CACEiB,MAAO,CACL3K,MAAO,OACPE,OAAQ,OACRyf,gBAAiB,OAAOnB,EAAiBkB,WACzCE,mBAAoBlD,EAClB1N,EACA9P,EACAsf,EAAiB5B,UAAY,GAC7B4B,EAAiBxe,OAAS,IAC1Bwe,EAAiBte,QAAU,IAE7B2f,eAAgB,WAItBnW,MAAA,MAAA,CAAKG,UAAU,oCAAmCC,SAC/C9C,EAAWgI,QAMH,OAAdA,IAAuBwP,GAAkBiB,SACxC/V,EAAAA,IAAA,MAAA,CACEG,UAAU,uCACVc,MAAO,CACLuI,KAAM,GAAGyL,MACT3e,MAAO,OACPE,OAAQ,OACR2Z,QAAS,WACV/P,SAEDJ,EAAAA,IAAA,OAAA,CAAMiB,MAAO,CAAE8O,MAAO,QAASE,SAAU,QAAQ7P,SAC9C9C,EAAWgI,+BClCwB,EAAGnF,YAAWgT,OAAO,MACnEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,6MAiBmC,EAAGF,YAAWgT,OAAO,MAClEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,8FM/L6C,EACvDyE,eACA9C,iBACAoU,uBACAC,iBACAxP,UACAmN,iBACA5Q,kBACAkT,kBACAlU,aACAmU,eACAC,oBAEA,MAAOC,EAAQC,GAAazc,EAAAA,UAAS,IAC9B0c,EAAaC,GAAkB3c,EAAAA,SAAmB,QACnDuJ,EAAe/I,EAAAA,OAAuB,MAG5C4C,EAAAA,UAAU,KACR,MAAMwZ,EAAsB1N,IACtB3F,EAAapI,UAAYoI,EAAapI,QAAQ6R,SAAS9D,EAAE4I,UAC3D2E,GAAU,GACVE,EAAe,UAInB,GAAIH,EAEF,OADAre,SAASkE,iBAAiB,YAAaua,GAChC,IAAMze,SAASiO,oBAAoB,YAAawQ,IAGxD,CAACJ,IAEJ,MAAMK,EAAe3b,EAAAA,YAAY,KAC/Bub,EAAW9M,IAAUA,GACrBgN,EAAe,SACd,IAEGG,EAAoB5b,cACvBqX,IACC4D,EAAqB5D,GACrBoE,EAAe,SAEjB,CAACR,IAGGY,EAAsB7b,cACzB2N,IACC1F,IAAkB0F,GAClB8N,EAAe,SAEjB,CAACxT,IAGG6T,EAAoB9b,cACvB6T,IACCwH,IAAgBxH,GAChB4H,EAAe,SAEjB,CAACJ,IAkIH,OACE7I,EAAAA,KAAA,MAAA,CAAKxN,UAAU,wCAAwCoB,IAAKiC,EAAYpD,SAAA,CACtEJ,EAAAA,IAAA,SAAA,CACEG,UAAU,yBACV2O,QAASgI,EAAY,aACV,WAAU,gBACNL,EACfxhB,MAAM,WACNoB,KAAK,SAAQ+J,SAEbJ,EAAAA,IAACY,QAEF6V,GACC9I,EAAAA,YACExN,UAAW,qCACTsW,EAAS,yCAA2C,IACpDrW,SAAA,CAEe,SAAhBuW,GAhJPhJ,OAAAuJ,EAAAA,SAAA,CAAA9W,SAAA,CACEJ,EAAAA,IAAA,MAAA,CAAKG,UAAU,oCAAmCC,SAAA,aAClDuN,EAAAA,KAAA,SAAA,CACExN,UAAU,mCACV2O,QAAS,IAAM8H,EAAe,SAC9BvgB,KAAK,SAAQ+J,SAAA,CAEbJ,EAAAA,IAAA,OAAA,CAAAI,SAAA,mBACAJ,EAAAA,IAAA,OAAA,CAAAI,SAAwB,IAAjB0E,EAAqB,SAAW,GAAGA,UAE3CuR,GAAkBxP,GAAWA,EAAQ7P,OAAS,GAC7C2W,EAAAA,KAAA,SAAA,CACExN,UAAU,mCACV2O,QAAS,IAAM8H,EAAe,WAC9BvgB,KAAK,mBAEL2J,EAAAA,IAAA,OAAA,CAAAI,SAAA,YACAJ,EAAAA,IAAA,OAAA,CAAAI,SAAO4T,GAAkB,YAG5BsC,GAAmBlU,GAAcA,EAAWpL,OAAS,GACpD2W,EAAAA,KAAA,SAAA,CACExN,UAAU,mCACV2O,QAAS,IAAM8H,EAAe,YAC9BvgB,KAAK,SAAQ+J,SAAA,CAEbJ,EAAAA,IAAA,OAAA,CAAAI,SAAA,aACAJ,MAAA,OAAA,CAAAI,SAAOmW,GAAgB,cAsHN,UAAhBI,GA/GPhJ,EAAAA,KAAAuJ,EAAAA,SAAA,CAAA9W,SAAA,CACEJ,MAAA,SAAA,CACEG,UAAU,oCACV2O,QAAS,IAAM8H,EAAe,QAC9B3V,MAAO,CAAEoP,OAAQ,UAAWZ,OAAQ,OAAQK,WAAY,cAAexZ,MAAO,OAAQ0a,UAAW,QACjG3a,KAAK,SAAQ+J,SAAA,qBAIfJ,EAAAA,IAAA,MAAA,CAAKG,UAAU,gCAA+BC,SAC3C4B,EAAe+M,IAAKyD,GACnBxS,EAAAA,IAAA,SAAA,CAEEG,UAAW,iCACT2E,IAAiB0N,EAAQ,uCAAyC,IAEpE1D,QAAS,IAAMiI,EAAkBvE,GACjCnc,KAAK,SAAQ+J,SAEF,IAAVoS,EAAc,SAAW,GAAGA,MAPxBA,SAoGU,YAAhBmE,GArFPhJ,EAAAA,KAAAuJ,WAAA,CAAA9W,SAAA,CACEJ,EAAAA,IAAA,SAAA,CACEG,UAAU,oCACV2O,QAAS,IAAM8H,EAAe,QAC9B3V,MAAO,CAAEoP,OAAQ,UAAWZ,OAAQ,OAAQK,WAAY,cAAexZ,MAAO,OAAQ0a,UAAW,QACjG3a,KAAK,SAAQ+J,SAAA,cAIfJ,EAAAA,IAAA,MAAA,CAAKG,UAAU,kCAAiCC,SAC7CyG,GAASkI,IAAKrG,GACbiF,EAAAA,KAAA,SAAA,CAEExN,UAAW,qCACT6T,IAAmBtL,EAAOI,QAAU,2CAA6C,IAEnFgG,QAAS,IAAMkI,EAAoBtO,EAAOI,SAAWJ,EAAOtP,KAC5D/C,KAAK,SAAQ+J,SAAA,CAEbJ,MAAA,OAAA,CAAAI,SAAOsI,EAAOG,OAASH,EAAOI,SAAW,YACxCkL,IAAmBtL,EAAOI,SAAW9I,EAAAA,IAACyT,EAAS,CAACN,KAAM,OARlDzK,EAAOI,SAAWJ,EAAOtP,WA0Ef,aAAhBud,GA1DPhJ,EAAAA,2BACE3N,MAAA,SAAA,CACEG,UAAU,oCACV2O,QAAS,IAAM8H,EAAe,QAC9B3V,MAAO,CAAEoP,OAAQ,UAAWZ,OAAQ,OAAQK,WAAY,cAAexZ,MAAO,OAAQ0a,UAAW,QACjG3a,KAAK,SAAQ+J,SAAA,eAIfuN,EAAAA,KAAA,MAAA,CAAKxN,UAAU,4CACbwN,EAAAA,KAAA,SAAA,CACExN,UAAW,qCACRoW,EAA4D,GAA7C,4CAElBzH,QAAS,IAAMmI,EAAkB,MACjC5gB,KAAK,SAAQ+J,SAAA,CAEbJ,EAAAA,IAAA,OAAA,CAAAI,SAAA,SACEmW,GAAgBvW,EAAAA,IAACyT,EAAS,CAACN,KAAM,QAEpC/Q,GAAY2M,IAAKC,GAChBrB,EAAAA,KAAA,SAAA,CAEExN,UAAW,qCACToW,IAAiBvH,EAAMG,QAAU,2CAA6C,IAEhFL,QAAS,IAAMmI,EAAkBjI,EAAMG,SACvC9Y,KAAK,mBAEL2J,EAAAA,IAAA,OAAA,CAAAI,SAAO4O,EAAMnG,QACZ0N,IAAiBvH,EAAMG,SAAWnP,EAAAA,IAACyT,EAAS,CAACN,KAAM,OAR/CnE,EAAMG,4CNqB0B,EAAGhP,YAAWgT,OAAO,MACpEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,+DAIqC,EAAGF,YAAWgT,OAAO,MACpEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,oDA1EkC,EAAGF,YAAWgT,OAAO,MACjEnT,EAAAA,IAAA,MAAA,CACEG,UAAWA,EACX7J,MAAO6c,EACP3c,OAAQ2c,EACRlT,QAAQ,YACRC,KAAK,eACLkT,MAAM,sCAENpT,EAAAA,IAAA,OAAA,CAAMK,EAAE,+PO7J+C,EACzD4B,SACAP,QACAuB,iBACAkU,mBAEA,MAAMC,EAAqBjc,cACxBgO,IACC,MAAM1B,EAAYrQ,WAAW+R,EAAE4I,OAAO9T,OACtCgF,EAAewE,IAEjB,CAACxE,IAgBH,OACE0K,OAAA,MAAA,CAAKxN,UAAU,sCAAqCC,SAAA,CAClDJ,EAAAA,cACEG,UAAU,yBACV2O,QAASqI,EAAY,aACTzV,EAAQ,SAAW,OAC/BzM,MAAOyM,EAAQ,aAAe,WAC9BrL,KAAK,SAAQ+J,SAnBbsB,GAAoB,IAAXO,EACJjC,EAAAA,IAACQ,EAAc,IAEpByB,EAAS,IACJjC,EAAAA,IAACsT,EAAa,IAEnBrR,EAAS,IACJjC,EAAAA,IAACqT,EAAgB,IAEnBrT,EAAAA,IAACO,EAAc,MAcpBP,MAAA,MAAA,CAAKG,UAAU,6CAA4CC,SACzDJ,EAAAA,aACE3J,KAAK,QACL8J,UAAU,mCACVhC,IAAK,EACLrC,IAAK,EACL+V,KAAM,IACN5T,MAAOyD,EAAQ,EAAIO,EACnB6P,SAAUsF,eACC,SACXnW,MAAO,CACL6O,WAAY,yEACa,KAAtBpO,EAAQ,EAAIO,gCACsC,KAAtBP,EAAQ,EAAIO,uKVgJ3B5L,IAC1B,MACM0B,EADQK,SAASC,cAAc,SAChBI,YAAYpC,GACjC,MAAkB,aAAX0B,GAAoC,UAAXA,8DAnJV,CACtBuG,EACA+Y,KAEA,IAAIC,EAAgD,KACpD,MAAO,IAAI7Y,KACL6Y,GAASzM,aAAayM,GAC1BA,EAAU5Y,WAAW,IAAMJ,KAAQG,GAAO4Y,+CAqEdnhB,IAC9B,MAAMqhB,EAAYrhB,EAAIiB,MAAM,KAAK,GAAGA,MAAM,KAAKqgB,OAAOC,cActD,MAZ0C,CACxCC,IAAK,YACLC,KAAM,aACNC,IAAK,YACLC,IAAK,YACLC,KAAM,wBACNC,IAAK,uBACLC,IAAK,kBACLC,IAAK,kBACLC,IAAK,oBAGUX,GAAa,KAAO,6JAcb,IACjB,QAAQ1b,KAAKsc,SAASra,SAAS,IAAIsa,UAAU,EAAG,4FA5FpB,OAEjChgB,SAASigB,mBACRjgB,SAA8DkgB,yBAC9DlgB,SAA2DmgB,sBAC3DngB,SAA0DogB,oDA8EzB,IAE0B,KADhDpgB,SAASC,cAAc,SACxBI,YAAY,kDAaH,IACf,iEAAiEM,KACtEC,UAAUC,0DAOe,IACpB,iBAAkByC,QAAU1C,UAAUyf,eAAiB,oBAxKtClR,IACxB,MAAMrQ,EAAQqQ,EAAKpQ,MAAM,KAAK4X,IAAI2J,QAClC,OAAqB,IAAjBxhB,EAAMF,OACU,KAAXE,EAAM,GAAuB,GAAXA,EAAM,GAAUA,EAAM,GAE5B,IAAjBA,EAAMF,OACU,GAAXE,EAAM,GAAUA,EAAM,GAExBA,EAAM,IAAM,gJMFM,EACzB6e,UACA4C,UAAU,CAAA,EACV/V,SACAgW,SACAC,eACAC,QACAjE,SACAkE,WACAvV,mBAEA,MAAMwV,EAAgB,IAAK/E,KAAoB0E,GAEzC3L,EAAgB7R,cACnBtE,IACC,IAAKkf,EAAS,OAGd,MAAMhE,EAASlb,EAAMkb,OACrB,GAAuB,UAAnBA,EAAOkH,SAA0C,aAAnBlH,EAAOkH,SAA0BlH,EAAOmH,kBACxE,OAIF,MAAMC,EAAY3V,EAAapI,QAC/B,IAAK+d,IAAcA,EAAUlM,SAAS7U,SAAS8U,eAC7C,OAKF,OAFYrW,EAAMmG,KAGhB,KAAKgc,EAAcxd,KACnB,IAAK,IACH3E,EAAMuS,iBACNxG,IACA,MACF,KAAKoW,EAActR,KACnB,IAAK,IACH7Q,EAAMuS,iBACNwP,IACA,MACF,KAAKI,EAAclX,WACnB,IAAK,IACHjL,EAAMuS,iBACNyP,IACA,MACF,KAAKG,EAAcnX,IACnB,IAAK,IACHhL,EAAMuS,iBACN0P,IACA,MACF,KAAKE,EAAc9E,YACjBrd,EAAMuS,iBACNyL,EAAOhe,EAAMuiB,SAAW,GAAK,IAC7B,MACF,KAAKJ,EAAc7E,aACjBtd,EAAMuS,iBACNyL,EAAOhe,EAAMuiB,UAAW,IAAM,IAC9B,MACF,KAAKJ,EAAc5E,SACjBvd,EAAMuS,iBACN2P,EAAS,IACT,MACF,KAAKC,EAAc3E,WACjBxd,EAAMuS,iBACN2P,GAAS,IACT,MACF,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACHliB,EAAMuS,iBAENyL,GAAQwE,OAMd,CAACtD,EAASiD,EAAepW,EAAQgW,EAAQC,EAAcC,EAAOjE,EAAQkE,EAAUvV,IAGlFnG,EAAAA,UAAU,KACR,GAAK0Y,EAIL,OAFA3d,SAASkE,iBAAiB,UAAW0Q,GAE9B,KACL5U,SAASiO,oBAAoB,UAAW2G,KAEzC,CAAC+I,EAAS/I,uBD1FU,CAACsM,EAA4B,MACpD,MAAM7X,SACJA,GAAW,EAAKC,MAChBA,GAAQ,EAAKC,KACbA,GAAO,EACPM,OAAQE,EAAgB,EACxB2C,aAAcyU,EAAc,GAC1BD,EAEE5f,EAAWe,EAAAA,OAAyB,MACpC+I,EAAe/I,EAAAA,OAAuB,OACrC+e,EAAOC,GAAYxf,WAAsB,IAC3C0Z,EACH1R,OAAQE,EACRgC,QAASzC,EACToD,aAAcyU,IAIVG,EAAcve,cAAawe,IAC/BF,EAAU7P,IAAI,IAAWA,KAAS+P,MACjC,IAGGne,EAAOL,EAAAA,YAAY7D,UACvB,MAAMa,EAAQuB,EAAS0B,QACvB,GAAKjD,EAEL,UACQA,EAAMqD,OACZke,EAAY,CAAE9V,WAAW,EAAMgQ,UAAU,EAAOC,SAAS,GAC3D,CAAE,MAAOnf,GAET,GACC,CAACglB,IAGExS,EAAQ/L,EAAAA,YAAY,KACxB,MAAMhD,EAAQuB,EAAS0B,QAClBjD,IAELA,EAAM+O,QACNwS,EAAY,CAAE9V,WAAW,EAAOgQ,UAAU,MACzC,CAAC8F,IAGEtS,EAAajM,EAAAA,YAAY,KACzBqe,EAAM5V,UACRsD,IAEA1L,KAED,CAACge,EAAM5V,UAAWpI,EAAM0L,IAGrBI,EAAOnM,cAAaoM,IACxB,MAAMpP,EAAQuB,EAAS0B,QACvB,IAAKjD,EAAO,OAEZ,MAAMyhB,EAAc/d,KAAKC,IAAI,EAAGD,KAAKsC,IAAIoJ,EAAMpP,EAAM3C,UAAY,IACjE2C,EAAMoD,YAAcqe,EACpBF,EAAY,CAAEne,YAAaqe,KAC1B,CAACF,IAGElS,EAAYrM,cAAa8G,IAC7B,MAAM9J,EAAQuB,EAAS0B,QACvB,IAAKjD,EAAO,OAEZ,MAAM0hB,EAAgBhe,KAAKC,IAAI,EAAGD,KAAKsC,IAAI,EAAG8D,IAC9C9J,EAAM8J,OAAS4X,EACf1hB,EAAMuJ,MAA0B,IAAlBmY,EACdH,EAAY,CACVzX,OAAQ4X,EACR1V,QAA2B,IAAlB0V,KAEV,CAACH,IAGE9R,EAAazM,EAAAA,YAAY,KAC7B,MAAMhD,EAAQuB,EAAS0B,QAClBjD,IAELA,EAAMuJ,OAASvJ,EAAMuJ,MACrBgY,EAAY,CAAEvV,QAAShM,EAAMuJ,UAC5B,CAACgY,IAGEpR,EAAkBnN,cAAaoN,IACnC,MAAMpQ,EAAQuB,EAAS0B,QAClBjD,IAELA,EAAM2M,aAAeyD,EACrBmR,EAAY,CAAE5U,aAAcyD,MAC3B,CAACmR,IAGE5R,EAAkB3M,EAAAA,YAAY7D,UAClC,MAAM6hB,EAAY3V,EAAapI,QAC/B,GAAK+d,EAEL,UACQja,EAAkBia,GACxBO,EAAY,CAAErV,cAAc,GAC9B,CAAE,MAAO3P,GAET,GACC,CAACglB,IAGEI,EAAmB3e,EAAAA,YAAY7D,UACnC,UACQiI,IACNma,EAAY,CAAErV,cAAc,GAC9B,CAAE,MAAO3P,GAET,GACC,CAACglB,IAGE3R,EAAmB5M,EAAAA,YAAY7D,UAC/BkiB,EAAMnV,mBACFyV,UAEAhS,KAEP,CAAC0R,EAAMnV,aAAcyD,EAAiBgS,IAGnC9R,EAAW7M,EAAAA,YAAY7D,UAC3B,MAAMa,EAAQuB,EAAS0B,QACvB,GAAKjD,GAAUwG,IAEf,UACQxG,EAAM8P,0BACZyR,EAAY,CAAElM,OAAO,GACvB,CAAE,MAAO9Y,GAET,GACC,CAACglB,IAGEK,EAAY5e,EAAAA,YAAY7D,UAC5B,GAAKc,SAAS+P,wBAEd,UACQ/P,SAASgQ,uBACfsR,EAAY,CAAElM,OAAO,GACvB,CAAE,MAAO9Y,GAET,GACC,CAACglB,IAGErR,EAAYlN,EAAAA,YAAY7D,UACxBkiB,EAAMhM,YACFuM,UAEA/R,KAEP,CAACwR,EAAMhM,MAAOxF,EAAU+R,IAqG3B,OAlGA1c,EAAAA,UAAU,KACR,MAAMlF,EAAQuB,EAAS0B,QACvB,IAAKjD,EAAO,OAGZA,EAAMuJ,MAAQA,EACdvJ,EAAM8J,OAASE,EACfhK,EAAM2M,aAAeyU,EACrBphB,EAAMwJ,KAAOA,EAEb,MAAMqY,EAAW,CACfC,eAAgB,KACdP,EAAY,CAAElkB,SAAU2C,EAAM3C,YAEhC0kB,WAAY,KACVR,EAAY,CACVne,YAAapD,EAAMoD,YACnBqE,SAAUD,EAAexH,MAG7BqD,KAAM,KACJke,EAAY,CAAE9V,WAAW,EAAMgQ,UAAU,EAAOC,SAAS,KAE3D3M,MAAO,KACLwS,EAAY,CAAE9V,WAAW,EAAOgQ,UAAU,KAE5CuG,MAAO,KACLT,EAAY,CAAE9V,WAAW,EAAOgQ,UAAU,EAAMC,SAAS,KAE3DuG,QAAS,KACPV,EAAY,CAAE5F,aAAa,KAE7BuG,QAAS,KACPX,EAAY,CAAE5F,aAAa,KAE7BwG,QAAS,KACPZ,EAAY,CAAE3F,WAAW,KAE3BwG,OAAQ,KACNb,EAAY,CAAE3F,WAAW,KAE3ByG,aAAc,KACZd,EAAY,CACVzX,OAAQ9J,EAAM8J,OACdkC,QAAShM,EAAMuJ,SAGnB+Y,WAAY,KACVf,EAAY,CAAE5U,aAAc3M,EAAM2M,gBAEpCpQ,MAAO,KACLglB,EAAY,CAAEhlB,MAAOyD,EAAMzD,SAE7BgmB,sBAAuB,KACrBhB,EAAY,CAAElM,OAAO,KAEvBmN,sBAAuB,KACrBjB,EAAY,CAAElM,OAAO,MAczB,OATAoN,OAAOC,QAAQb,GAAUplB,QAAQ,EAAEiC,EAAOikB,MACxC3iB,EAAMmE,iBAAiBzF,EAAOikB,KAI5BrZ,GACFjG,IAGK,KACLof,OAAOC,QAAQb,GAAUplB,QAAQ,EAAEiC,EAAOikB,MACxC3iB,EAAMkO,oBAAoBxP,EAAOikB,OAGpC,CAACrZ,EAAUC,EAAOC,EAAMQ,EAAeoX,EAAa/d,EAAMke,IAG7Drc,EAAAA,UAAU,KACR,MAAMuP,EAAyB,KAC7B,MAAMC,IAAShO,IACf6a,EAAY,CAAErV,aAAcwI,KAQ9B,OALAzU,SAASkE,iBAAiB,mBAAoBsQ,GAC9CxU,SAASkE,iBAAiB,yBAA0BsQ,GACpDxU,SAASkE,iBAAiB,sBAAuBsQ,GACjDxU,SAASkE,iBAAiB,qBAAsBsQ,GAEzC,KACLxU,SAASiO,oBAAoB,mBAAoBuG,GACjDxU,SAASiO,oBAAoB,yBAA0BuG,GACvDxU,SAASiO,oBAAoB,sBAAuBuG,GACpDxU,SAASiO,oBAAoB,qBAAsBuG,KAEpD,CAAC8M,IAEG,CACLF,QACA9f,WACA8J,eACAhI,OACA0L,QACAE,aACAE,OACAE,YACAI,aACAU,kBACAR,kBACAvI,eAAgBua,EAChB/R,mBACAC,WACAE,QAAS6R,EACT1R"}
|