v-proximity-prefetch
Version:
Vue plugin that prefetches routes when the mouse approaches links for faster navigation
1 lines • 49 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../src/ProximityPrefetch.vue","../src/vite-plugin.ts","../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * ProximityPrefetch Component\n * \n * This component tracks mouse movements and prefetches routes when the cursor\n * approaches links, improving perceived navigation speed.\n */\nimport { ref, onMounted, onUnmounted, watch, computed } from 'vue';\nimport { useRouter } from 'vue-router';\n\n// Étendre l'interface Window pour inclure notre propriété PPF_DEBUG\ndeclare global {\n interface Window {\n PPF_DEBUG?: boolean;\n }\n}\n\n/**\n * Component props interface\n */\ninterface Props {\n /** Distance threshold in pixels to trigger prefetching */\n threshold?: number;\n /** Interval for periodic checks in milliseconds (0 means reactive to mouse movements) */\n predictionInterval?: number;\n /** Enable debug logging */\n debug?: boolean;\n /** Enable mobile support with viewport-based prefetching */\n mobileSupport?: boolean;\n /** Viewport margin for mobile prefetching */\n viewportMargin?: number;\n /** Enable prefetching of all links on the page at once */\n prefetchAllLinks?: boolean;\n /** Delay before starting to prefetch all links (ms) */\n prefetchAllLinksDelay?: number;\n}\n\n// Check if PPF_DEBUG environment variable is set\nconst PPF_DEBUG = typeof window !== 'undefined' && window.PPF_DEBUG === true;\n\n// Default prop values\nconst props = withDefaults(defineProps<Props>(), {\n threshold: 200,\n predictionInterval: 0,\n debug: false, // Debug disabled by default in production\n mobileSupport: true,\n viewportMargin: 300,\n prefetchAllLinks: false,\n prefetchAllLinksDelay: 1500\n});\n\n// Determine if debug mode is enabled (either from props or PPF_DEBUG global)\nconst isDebugEnabled = computed(() => props.debug || PPF_DEBUG);\n\n// Router access for prefetching\nconst router = useRouter();\n\n// Track mouse position\nconst mousePosition = ref({ x: 0, y: 0 });\n\n// Store already prefetched routes to avoid duplication\nconst prefetchedRoutes = ref<Set<string>>(new Set());\n\n// Store link elements with their position data\nconst links = ref<Array<{ el: HTMLAnchorElement; href: string; rect: DOMRect }>>([]);\n\n// Track if mouse is near any link\nconst isMouseNearLink = ref(false);\n\n// Last time proximity was checked (for throttling)\nconst lastProximityCheck = ref(Date.now());\n\n// Minimum interval between proximity checks (ms)\nconst THROTTLE_INTERVAL = 100;\n\n// Detect if we're on a touch device\nconst isTouchDevice = ref(false);\n\n/**\n * Detect touch devices\n */\nconst detectTouchDevice = (): boolean => {\n return (\n typeof window !== 'undefined' && \n (('ontouchstart' in window) || \n (navigator.maxTouchPoints > 0) || \n (navigator.msMaxTouchPoints > 0))\n );\n};\n\n/**\n * Scan the DOM and update the list of tracked links\n */\nconst updateLinks = () => {\n // Get all anchor elements with href attribute\n const anchors = Array.from(\n document.querySelectorAll('a[href]')\n ) as HTMLAnchorElement[];\n \n if (isDebugEnabled.value) {\n console.debug(`[ProximityPrefetch] Found ${anchors.length} links in the page`);\n }\n \n // Filter and process valid internal links\n links.value = anchors\n .map((el) => {\n const href = el.getAttribute('href');\n \n // Only include internal links (starting with / or without ://) and not anchor links\n if (href && (href.startsWith('/') || !href.includes('://')) && !href.startsWith('#')) {\n const rect = el.getBoundingClientRect();\n \n if (isDebugEnabled.value) {\n console.debug(`[ProximityPrefetch] Link found: ${href}, rect:`, rect);\n }\n \n return {\n el,\n href,\n rect\n };\n }\n return null;\n })\n .filter((link): link is { el: HTMLAnchorElement; href: string; rect: DOMRect } => link !== null);\n};\n\n/**\n * Calculate Euclidean distance between two points\n */\nconst calculateDistance = (x1: number, y1: number, x2: number, y2: number): number => {\n return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);\n};\n\n/**\n * Calculate the center point of a DOMRect\n */\nconst calculateCenterPoint = (rect: DOMRect): { x: number, y: number } => {\n return {\n x: rect.left + rect.width / 2,\n y: rect.top + rect.height / 2\n };\n};\n\n/**\n * Check if a link is in or near the viewport\n */\nconst isLinkInViewport = (rect: DOMRect): boolean => {\n return (\n rect.top >= -props.viewportMargin &&\n rect.left >= -props.viewportMargin &&\n rect.bottom <= window.innerHeight + props.viewportMargin &&\n rect.right <= window.innerWidth + props.viewportMargin\n );\n};\n\n/**\n * Check if mouse is within threshold distance of any link\n * @returns boolean indicating if mouse is near any link\n */\nconst checkProximity = (): boolean => {\n if (!links.value.length) {\n return false;\n }\n \n // Update link rectangles to account for layout changes\n links.value.forEach(link => {\n link.rect = link.el.getBoundingClientRect();\n });\n \n // Calculate distance between mouse and each link\n const linksWithDistance = links.value.map((link) => {\n const center = calculateCenterPoint(link.rect);\n const distance = calculateDistance(\n mousePosition.value.x,\n mousePosition.value.y,\n center.x,\n center.y\n );\n return { ...link, distance };\n });\n \n // Find links within threshold distance\n const closestLinks = linksWithDistance.filter(\n (link) => link.distance < props.threshold\n );\n \n if (isDebugEnabled.value && closestLinks.length > 0) {\n console.debug(`[ProximityPrefetch] ${closestLinks.length} links within threshold ${props.threshold}px`);\n closestLinks.forEach(link => {\n console.debug(`[ProximityPrefetch] Link: ${link.href}, Distance: ${link.distance.toFixed(2)}px`);\n });\n }\n \n return closestLinks.length > 0;\n};\n\n/**\n * Check which links are in or near the viewport (for mobile)\n * @returns boolean indicating if there are links in the viewport\n */\nconst checkViewportLinks = (): boolean => {\n if (!links.value.length) {\n return false;\n }\n \n // Update link rectangles to account for layout changes\n links.value.forEach(link => {\n link.rect = link.el.getBoundingClientRect();\n });\n \n // Find links within viewport (plus margin)\n const visibleLinks = links.value.filter(link => isLinkInViewport(link.rect));\n \n if (isDebugEnabled.value && visibleLinks.length > 0) {\n console.debug(`[ProximityPrefetch] ${visibleLinks.length} links in viewport (plus margin ${props.viewportMargin}px)`);\n }\n \n return visibleLinks.length > 0;\n};\n\n/**\n * Prefetch routes for nearby links\n */\nconst prefetchNearbyRoutes = (): void => {\n // Apply throttling to limit frequency of checks\n const now = Date.now();\n if (now - lastProximityCheck.value < THROTTLE_INTERVAL) {\n return;\n }\n lastProximityCheck.value = now;\n \n // Choose detection strategy based on device type\n let hasNearbyLinks;\n \n if (isTouchDevice.value && props.mobileSupport) {\n // Mobile: viewport-based detection\n hasNearbyLinks = checkViewportLinks();\n } else {\n // Desktop: mouse proximity detection\n hasNearbyLinks = checkProximity();\n }\n \n isMouseNearLink.value = hasNearbyLinks;\n \n // Exit early if no links are nearby\n if (!hasNearbyLinks || !links.value.length) {\n return;\n }\n \n // Update link rectangles to account for layout changes\n links.value.forEach(link => {\n link.rect = link.el.getBoundingClientRect();\n });\n \n let linksToProcess;\n \n if (isTouchDevice.value && props.mobileSupport) {\n // For mobile: Get links in viewport\n linksToProcess = links.value.filter(link => isLinkInViewport(link.rect));\n // Sort by vertical position (links at the top first)\n linksToProcess.sort((a, b) => a.rect.top - b.rect.top);\n } else {\n // For desktop: Calculate distances to find closest links\n const linksWithDistance = links.value.map((link) => {\n const center = calculateCenterPoint(link.rect);\n const distance = calculateDistance(\n mousePosition.value.x,\n mousePosition.value.y,\n center.x,\n center.y\n );\n return { ...link, distance };\n });\n\n // Sort by distance (closest first)\n linksWithDistance.sort((a, b) => a.distance - b.distance);\n\n // Filter for links within threshold\n linksToProcess = linksWithDistance\n .filter((link) => link.distance < props.threshold)\n .map(({ el, href, rect }) => ({ el, href, rect })); // Strip the distance property\n }\n \n // Get routes to prefetch\n const routesToPrefetch = linksToProcess.map((link) => link.href);\n\n // Limit to 3 routes at a time to avoid excessive prefetching\n const MAX_PREFETCH = 3;\n for (const route of routesToPrefetch.slice(0, MAX_PREFETCH)) {\n // Skip already prefetched routes\n if (prefetchedRoutes.value.has(route)) {\n continue;\n }\n \n if (isDebugEnabled.value) {\n console.log('[ProximityPrefetch] Prefetching:', route);\n }\n \n try {\n // Resolve the route to get corresponding route records\n const resolved = router.resolve(route);\n \n // Trigger navigation to prefetch route components without actually navigating\n router.getRoutes().forEach(routeRecord => {\n if (routeRecord.path === resolved.path && routeRecord.components) {\n // Load components without navigating\n const comps = routeRecord.components;\n Object.values(comps).forEach(comp => {\n // Use a safer approach with explicit type casting\n const asyncComp = comp as any;\n \n if (typeof asyncComp === 'function') {\n try {\n // Call component to trigger loading\n asyncComp();\n } catch (e) {\n if (isDebugEnabled.value) {\n console.error('[ProximityPrefetch] Error loading component:', e);\n }\n }\n }\n });\n }\n });\n \n // Mark as prefetched to avoid duplicate prefetching\n prefetchedRoutes.value.add(route);\n \n // Add visual indicator for prefetched links in debug mode\n if (isDebugEnabled.value) {\n // Find all link elements pointing to this route\n const matchingAnchors = Array.from(\n document.querySelectorAll(`a[href=\"${route}\"]`)\n ) as HTMLAnchorElement[];\n \n matchingAnchors.forEach(anchor => {\n // Add a red border directly to the link element if not already applied\n if (!anchor.hasAttribute('data-ppf-debug-applied')) {\n anchor.setAttribute('data-ppf-debug-applied', 'true');\n anchor.classList.add('ppf-debug-highlight');\n anchor.title = `Prefetched: ${route}`;\n }\n });\n }\n } catch (err) {\n if (isDebugEnabled.value) {\n console.error('[ProximityPrefetch] Error prefetching route:', err);\n }\n }\n }\n};\n\n/**\n * Prefetch a single route\n */\nconst prefetchRoute = (route: string): void => {\n // Skip already prefetched routes\n if (prefetchedRoutes.value.has(route)) {\n return;\n }\n \n if (isDebugEnabled.value) {\n console.log('[ProximityPrefetch] Prefetching:', route);\n }\n \n try {\n // Resolve the route to get corresponding route records\n const resolved = router.resolve(route);\n \n // Trigger navigation to prefetch route components without actually navigating\n router.getRoutes().forEach(routeRecord => {\n if (routeRecord.path === resolved.path && routeRecord.components) {\n // Load components without navigating\n const comps = routeRecord.components;\n Object.values(comps).forEach(comp => {\n // Use a safer approach with explicit type casting\n const asyncComp = comp as any;\n \n if (typeof asyncComp === 'function') {\n try {\n // Call component to trigger loading\n asyncComp();\n } catch (e) {\n if (isDebugEnabled.value) {\n console.error('[ProximityPrefetch] Error loading component:', e);\n }\n }\n }\n });\n }\n });\n \n // Mark as prefetched to avoid duplicate prefetching\n prefetchedRoutes.value.add(route);\n \n // Add visual indicator for prefetched links in debug mode\n if (isDebugEnabled.value) {\n // Find all link elements pointing to this route\n const matchingAnchors = Array.from(\n document.querySelectorAll(`a[href=\"${route}\"]`)\n ) as HTMLAnchorElement[];\n \n matchingAnchors.forEach(anchor => {\n // Add a red border directly to the link element if not already applied\n if (!anchor.hasAttribute('data-ppf-debug-applied')) {\n anchor.setAttribute('data-ppf-debug-applied', 'true');\n anchor.classList.add('ppf-debug-highlight');\n anchor.title = `Prefetched: ${route}`;\n }\n });\n }\n } catch (err) {\n if (isDebugEnabled.value) {\n console.error('[ProximityPrefetch] Error prefetching route:', err);\n }\n }\n};\n\n/**\n * Prefetch all links on the page\n */\nconst prefetchAllPageLinks = (): void => {\n updateLinks();\n \n if (!links.value.length) {\n if (isDebugEnabled.value) {\n console.log('[ProximityPrefetch] No links found to prefetch');\n }\n return;\n }\n \n if (isDebugEnabled.value) {\n console.log(`[ProximityPrefetch] Prefetching all links: ${links.value.length} links found`);\n }\n \n // Get unique routes\n const uniqueRoutes = [...new Set(links.value.map(link => link.href))];\n \n if (isDebugEnabled.value) {\n console.log(`[ProximityPrefetch] Unique routes to prefetch: ${uniqueRoutes.length}`);\n }\n \n // Batch prefetching with small delays to avoid network congestion\n let processed = 0;\n const batchSize = 3;\n const batchDelay = 300;\n \n const processBatch = () => {\n const batch = uniqueRoutes.slice(processed, processed + batchSize);\n if (batch.length === 0) return;\n \n for (const route of batch) {\n prefetchRoute(route);\n }\n \n processed += batch.length;\n \n if (processed < uniqueRoutes.length) {\n setTimeout(processBatch, batchDelay);\n } else if (isDebugEnabled.value) {\n console.log(`[ProximityPrefetch] Finished prefetching all links: ${processed} routes prefetched`);\n }\n };\n \n processBatch();\n};\n\n/**\n * Handle mouse movement events\n */\nconst handleMouseMove = (e: MouseEvent): void => {\n mousePosition.value = { x: e.clientX, y: e.clientY };\n};\n\n/**\n * Handle scroll events (for mobile)\n */\nconst handleScroll = (): void => {\n if (isTouchDevice.value && props.mobileSupport) {\n const now = Date.now();\n if (now - lastProximityCheck.value >= THROTTLE_INTERVAL) {\n prefetchNearbyRoutes();\n }\n }\n};\n\n/**\n * Handle touch events (for mobile)\n */\nconst handleTouch = (): void => {\n if (isTouchDevice.value && props.mobileSupport) {\n prefetchNearbyRoutes();\n }\n};\n\n// Register event listeners based on device type\nonMounted(() => {\n // Detect device type\n isTouchDevice.value = detectTouchDevice();\n \n if (isDebugEnabled.value) {\n console.log('[ProximityPrefetch] Component mounted with options:', {\n threshold: props.threshold,\n predictionInterval: props.predictionInterval,\n debug: isDebugEnabled.value,\n mobileSupport: props.mobileSupport,\n viewportMargin: props.viewportMargin,\n deviceType: isTouchDevice.value ? 'Touch device' : 'Desktop device'\n });\n \n // Add debug styles for the visual indicators\n const style = document.createElement('style');\n style.textContent = `\n .ppf-debug-highlight {\n border: 2px solid red !important;\n box-sizing: border-box;\n }\n `;\n document.head.appendChild(style);\n }\n \n // Scan for links after a short delay to ensure DOM is fully loaded\n setTimeout(() => {\n updateLinks();\n if (isDebugEnabled.value) {\n console.log(`[ProximityPrefetch] Initial links detection: ${links.value.length} links found`);\n }\n\n // Initial prefetch on load (particularly important for mobile)\n prefetchNearbyRoutes();\n }, 500);\n\n // Set up MutationObserver to detect new links or changes to existing ones\n const observer = new MutationObserver(() => {\n updateLinks();\n });\n\n // Start observing DOM changes\n observer.observe(document.body, {\n childList: true, // Watch for added/removed nodes\n subtree: true, // Include descendants\n attributes: true, // Watch for attribute changes\n attributeFilter: ['href'] // Only care about href changes\n });\n\n // Configure event listeners based on device type\n if (isTouchDevice.value && props.mobileSupport) {\n // Mobile: use scroll and touch events\n window.addEventListener('scroll', handleScroll, { passive: true });\n window.addEventListener('touchstart', handleTouch, { passive: true });\n window.addEventListener('resize', handleScroll, { passive: true });\n \n if (isDebugEnabled.value) {\n console.log('[ProximityPrefetch] Mobile mode initialized with viewport margin:', props.viewportMargin + 'px');\n }\n } else {\n // Desktop: use mouse events\n window.addEventListener('mousemove', handleMouseMove);\n \n if (isDebugEnabled.value) {\n console.log('[ProximityPrefetch] Desktop mode initialized');\n }\n }\n\n /**\n * Configure prefetching system based on parameters\n */\n let intervalId: number | undefined;\n\n // Two prefetching modes:\n if (props.predictionInterval > 0) {\n // 1. Interval mode: periodic checking\n intervalId = window.setInterval(() => {\n // Only check if mouse has moved (avoid unnecessary checks) for desktop\n // or always check for mobile\n if (isTouchDevice.value || mousePosition.value.x !== 0 || mousePosition.value.y !== 0) {\n prefetchNearbyRoutes();\n }\n }, props.predictionInterval);\n } else if (!isTouchDevice.value) {\n // 2. Reactive mode: check on mouse movements with throttling (desktop only)\n const throttledPrefetch = (): void => {\n const now = Date.now();\n if (now - lastProximityCheck.value >= THROTTLE_INTERVAL) {\n prefetchNearbyRoutes();\n }\n };\n \n // Watch for mouse position changes\n watch(mousePosition, () => {\n throttledPrefetch();\n });\n }\n\n // Prefetch all links if enabled\n if (props.prefetchAllLinks) {\n setTimeout(() => {\n prefetchAllPageLinks();\n }, props.prefetchAllLinksDelay);\n }\n\n // Clean up event listeners and observers on component unmount\n onUnmounted(() => {\n if (isTouchDevice.value && props.mobileSupport) {\n window.removeEventListener('scroll', handleScroll);\n window.removeEventListener('touchstart', handleTouch);\n window.removeEventListener('resize', handleScroll);\n } else {\n window.removeEventListener('mousemove', handleMouseMove);\n }\n \n observer.disconnect();\n if (intervalId) {\n window.clearInterval(intervalId);\n }\n });\n});\n</script>\n\n<template>\n <!-- This component doesn't render anything visually -->\n <!-- It acts as a functionality wrapper that can be placed anywhere in your app -->\n <slot></slot>\n</template>","/**\n * Vue Proximity Prefetch - Vite Plugin\n * \n * This plugin enhances Vue Router applications by adding proximity-based prefetching\n * capabilities, improving user experience through faster route transitions.\n */\n\nimport type { Plugin } from 'vite';\n\n/**\n * Configuration options for the Vue Proximity Prefetch plugin\n */\nexport interface VueProximityPrefetchOptions {\n /**\n * Distance threshold in pixels that triggers prefetching when the cursor\n * approaches a link element\n * @default 200\n */\n threshold?: number;\n \n /**\n * Interval for periodic prediction checks (in milliseconds)\n * When set to 0, checks are triggered by mouse movements\n * @default 0\n */\n predictionInterval?: number;\n \n /**\n * Maximum number of routes to prefetch simultaneously\n * Limits resource usage while still enhancing perceived performance\n * @default 3\n */\n maxPrefetch?: number;\n \n /**\n * Enable debug logging in the console\n * Useful for development and troubleshooting\n * @default false\n */\n debug?: boolean;\n\n /**\n * Enable automatic prefetching without needing to add the Vue component\n * When true, a global script is injected that handles prefetching for all routes\n * @default false\n */\n automaticPrefetch?: boolean;\n \n /**\n * Enable mobile support for touch devices\n * When true, touch events and viewport-based prefetching are enabled\n * @default true\n */\n mobileSupport?: boolean;\n \n /**\n * Viewport margin (in pixels) for prefetching on mobile\n * Links that are within this distance from the viewport will be prefetched\n * @default 300\n */\n viewportMargin?: number;\n \n /**\n * Enable prefetching of all links on the page at once\n * When true, all internal links will be prefetched after page load\n * @default false\n */\n prefetchAllLinks?: boolean;\n \n /**\n * Delay (in milliseconds) before starting to prefetch all links\n * Only used when prefetchAllLinks is true\n * @default 1500\n */\n prefetchAllLinksDelay?: number;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_OPTIONS: Required<VueProximityPrefetchOptions> = {\n threshold: 200,\n predictionInterval: 0,\n maxPrefetch: 3,\n debug: false,\n automaticPrefetch: false,\n mobileSupport: true,\n viewportMargin: 300,\n prefetchAllLinks: false,\n prefetchAllLinksDelay: 1500\n};\n\n/**\n * Generate the prefetch script to be injected in the page\n */\nfunction generatePrefetchScript(options: Required<VueProximityPrefetchOptions>): string {\n return `\n <!-- Injected by Vue Proximity Prefetch Plugin -->\n <script>\n (function() {\n // Set global PPF_DEBUG flag for the Vue component to detect\n window.PPF_DEBUG = ${options.debug};\n \n // Configuration from Vite plugin\n const config = {\n threshold: ${options.threshold},\n predictionInterval: ${options.predictionInterval},\n maxPrefetch: ${options.maxPrefetch},\n debug: ${options.debug} || (typeof window !== 'undefined' && window.PPF_DEBUG === true),\n mobileSupport: ${options.mobileSupport},\n viewportMargin: ${options.viewportMargin},\n prefetchAllLinks: ${options.prefetchAllLinks},\n prefetchAllLinksDelay: ${options.prefetchAllLinksDelay}\n };\n \n // Utils\n const log = config.debug ? console.log.bind(console, '[ProximityPrefetch]') : () => {};\n log('Automatic prefetching enabled with options:', config);\n \n // State variables\n let mousePosition = { x: 0, y: 0 };\n let prefetchedRoutes = new Set();\n let lastCheck = Date.now();\n const THROTTLE_INTERVAL = 100;\n \n // Device detection\n let isTouchDevice = false;\n \n // Detect touch devices\n function detectTouchDevice() {\n return (('ontouchstart' in window) || \n (navigator.maxTouchPoints > 0) || \n (navigator.msMaxTouchPoints > 0));\n }\n \n // Calculate Euclidean distance between two points\n function calculateDistance(x1, y1, x2, y2) {\n return Math.sqrt((x2 - x1) ** 2 + (y1 - y2) ** 2);\n }\n \n // Calculate center point of a DOMRect\n function calculateCenterPoint(rect) {\n return {\n x: rect.left + rect.width / 2,\n y: rect.top + rect.height / 2\n };\n }\n \n // Get all valid links on the page\n function getLinks() {\n const anchors = Array.from(document.querySelectorAll('a[href]'));\n return anchors\n .map((el) => {\n const href = el.getAttribute('href');\n // Only include internal links (starting with / or without ://) and not anchor links\n if (href && (href.startsWith('/') || !href.includes('://')) && !href.startsWith('#')) {\n const rect = el.getBoundingClientRect();\n return { el, href, rect };\n }\n return null;\n })\n .filter(link => link !== null);\n }\n \n // Prefetch a single route\n function prefetchRoute(route) {\n if (prefetchedRoutes.has(route)) return;\n \n try {\n // Create a prefetch link element\n const link = document.createElement('link');\n link.rel = 'prefetch';\n link.href = route;\n link.as = 'document';\n document.head.appendChild(link);\n \n prefetchedRoutes.add(route);\n \n // In debug mode, add a visual indicator around the link\n if (config.debug) {\n // Find all link elements pointing to this route\n const matchingAnchors = Array.from(document.querySelectorAll('a[href=\"' + route + '\"]'));\n \n matchingAnchors.forEach(anchor => {\n // Add a red border directly to the link element if not already applied\n if (!anchor.hasAttribute('data-ppf-debug-applied')) {\n anchor.setAttribute('data-ppf-debug-applied', 'true');\n anchor.classList.add('ppf-debug-highlight');\n anchor.title = 'Prefetched: ' + route;\n }\n });\n }\n \n return true;\n } catch (err) {\n console.error('[ProximityPrefetch] Error prefetching route:', route, err);\n return false;\n }\n }\n \n // Prefetch all links on the page\n function prefetchAllPageLinks() {\n const links = getLinks();\n if (!links.length) return;\n \n log('Prefetching all links on page: ' + links.length + ' links found');\n \n // Get unique routes\n const uniqueRoutes = [...new Set(links.map(link => link.href))];\n \n // Batch prefetching with small delays to avoid network congestion\n let processed = 0;\n const batchSize = 3;\n const batchDelay = 300;\n \n function processBatch() {\n const batch = uniqueRoutes.slice(processed, processed + batchSize);\n if (batch.length === 0) return;\n \n for (const route of batch) {\n prefetchRoute(route);\n }\n \n processed += batch.length;\n \n if (processed < uniqueRoutes.length) {\n setTimeout(processBatch, batchDelay);\n } else if (config.debug) {\n log('Finished prefetching all links: ' + processed + ' routes prefetched');\n }\n }\n \n processBatch();\n }\n \n // Check if a link is in or near the viewport\n function isLinkInViewport(rect) {\n // Check if fully in viewport\n const isVisible = (\n rect.top >= -config.viewportMargin &&\n rect.left >= -config.viewportMargin &&\n rect.bottom <= window.innerHeight + config.viewportMargin &&\n rect.right <= window.innerWidth + config.viewportMargin\n );\n \n return isVisible;\n }\n \n // Check if mouse is near any links (for desktop)\n function checkProximity() {\n const links = getLinks();\n if (!links.length) return false;\n \n // Calculate distance between mouse and each link\n const linksWithDistance = links.map((link) => {\n const center = calculateCenterPoint(link.rect);\n const distance = calculateDistance(\n mousePosition.x,\n mousePosition.y,\n center.x,\n center.y\n );\n return { ...link, distance };\n });\n \n // Find links within threshold distance\n const closestLinks = linksWithDistance.filter(\n (link) => link.distance < config.threshold\n );\n \n if (config.debug && closestLinks.length > 0) {\n log(closestLinks.length + ' links within threshold ' + config.threshold + 'px');\n }\n \n return closestLinks;\n }\n \n // Check which links are in or near viewport (for mobile)\n function checkViewportLinks() {\n const links = getLinks();\n if (!links.length) return false;\n \n // Filter links that are in or near the viewport\n const visibleLinks = links.filter(link => isLinkInViewport(link.rect));\n \n if (config.debug && visibleLinks.length > 0) {\n log(visibleLinks.length + ' links in viewport (plus margin ' + config.viewportMargin + 'px)');\n }\n \n return visibleLinks;\n }\n \n // Prefetch routes when mouse is near links or links are in viewport\n function prefetchNearbyRoutes() {\n const now = Date.now();\n if (now - lastCheck < THROTTLE_INTERVAL) return;\n lastCheck = now;\n \n // Choose detection strategy based on device type\n const links = isTouchDevice ? checkViewportLinks() : checkProximity();\n if (!links || !links.length) return;\n \n // Sort links: by distance for desktop, by position for mobile\n if (isTouchDevice) {\n // On mobile, prioritize links near the top of viewport\n links.sort((a, b) => a.rect.top - b.rect.top);\n } else {\n // On desktop, keep sorting by distance\n links.sort((a, b) => a.distance - b.distance);\n }\n \n // Limit prefetching to maxPrefetch routes\n const routesToPrefetch = links.slice(0, config.maxPrefetch).map(link => link.href);\n \n // Keep track of the first link being processed\n let isFirstPrefetch = !window.PPF_HAS_PREFETCHED;\n \n // Prefetch routes\n for (const route of routesToPrefetch) {\n prefetchRoute(route);\n }\n }\n \n // Initialize\n function init() {\n // Detect device type\n isTouchDevice = detectTouchDevice();\n log('Device detection: ' + (isTouchDevice ? 'Touch device' : 'Desktop device'));\n \n // Add debug styles to the page if in debug mode\n if (config.debug) {\n const style = document.createElement('style');\n style.textContent = \n '.ppf-debug-highlight {' +\n ' box-shadow: 0 0 0 2px red !important;' +\n ' box-sizing: border-box;' +\n '}';\n document.head.appendChild(style);\n }\n \n // If prefetchAllLinks is enabled, prefetch all links after a delay\n if (config.prefetchAllLinks) {\n log('prefetchAllLinks enabled, will prefetch all links after ' + config.prefetchAllLinksDelay + 'ms');\n setTimeout(prefetchAllPageLinks, config.prefetchAllLinksDelay);\n }\n \n if (isTouchDevice && config.mobileSupport) {\n // Mobile approach: viewport-based prefetching\n \n // 1. Check on page load\n prefetchNearbyRoutes();\n \n // 2. Check on scroll with throttling\n window.addEventListener('scroll', () => {\n const now = Date.now();\n if (now - lastCheck > THROTTLE_INTERVAL) {\n prefetchNearbyRoutes();\n }\n }, { passive: true });\n \n // 3. Check on touch events\n window.addEventListener('touchstart', () => {\n prefetchNearbyRoutes();\n }, { passive: true });\n \n // 4. Check periodically if interval is set\n if (config.predictionInterval > 0) {\n setInterval(prefetchNearbyRoutes, config.predictionInterval);\n }\n \n log('Mobile prefetching initialized with viewport margin:', config.viewportMargin + 'px');\n } else {\n // Desktop approach: mouse proximity\n \n // Mouse move listener\n window.addEventListener('mousemove', (e) => {\n mousePosition = { x: e.clientX, y: e.clientY };\n \n // Reactive mode\n if (config.predictionInterval === 0) {\n prefetchNearbyRoutes();\n }\n });\n \n // Interval mode\n if (config.predictionInterval > 0) {\n setInterval(() => {\n if (mousePosition.x !== 0 || mousePosition.y !== 0) {\n prefetchNearbyRoutes();\n }\n }, config.predictionInterval);\n }\n \n log('Desktop proximity prefetching initialized');\n }\n \n // Window resize handler to update link positions\n window.addEventListener('resize', () => {\n // Throttle resize event\n const now = Date.now();\n if (now - lastCheck > THROTTLE_INTERVAL * 2) {\n lastCheck = now;\n prefetchNearbyRoutes();\n }\n }, { passive: true });\n }\n \n // Start when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init);\n } else {\n init();\n }\n })();\n </script>\n `;\n}\n\n/**\n * Creates a Vite plugin that enhances Vue Router with proximity-based prefetching\n * \n * @param options - Configuration options for the prefetching behavior\n * @returns Vite plugin instance\n */\nexport function viteProximityPrefetch(options: VueProximityPrefetchOptions = {}): Plugin {\n // Check if PPF_DEBUG environment variable is set\n const PPF_DEBUG = process.env.PPF_DEBUG === 'true';\n \n // Merge provided options with defaults, respecting PPF_DEBUG\n const resolvedOptions: Required<VueProximityPrefetchOptions> = {\n ...DEFAULT_OPTIONS,\n ...options,\n debug: options.debug || PPF_DEBUG\n };\n\n return {\n name: 'vite-plugin-vue-proximity-prefetch',\n \n configResolved() {\n console.log('Vue Proximity Prefetch Plugin enabled');\n \n if (resolvedOptions.debug) {\n console.log('Options:', {\n threshold: resolvedOptions.threshold,\n predictionInterval: resolvedOptions.predictionInterval,\n maxPrefetch: resolvedOptions.maxPrefetch,\n automaticPrefetch: resolvedOptions.automaticPrefetch,\n debug: resolvedOptions.debug,\n mobileSupport: resolvedOptions.mobileSupport,\n viewportMargin: resolvedOptions.viewportMargin\n });\n }\n },\n \n /**\n * Transform HTML to add prefetch attributes to preloaded modules\n * and inject the automatic prefetching script if enabled\n */\n transformIndexHtml(html) {\n // Add prefetch attribute to module preload links\n const transformedHtml = html.replace(\n /<link rel=\"modulepreload\"/g,\n '<link rel=\"modulepreload\" data-prefetch=\"true\"'\n );\n \n // If automatic prefetching is enabled, inject the script\n if (resolvedOptions.automaticPrefetch) {\n const injectionPoint = '</head>';\n const prefetchScript = generatePrefetchScript(resolvedOptions);\n \n return transformedHtml.replace(\n injectionPoint,\n `${prefetchScript}\\n${injectionPoint}`\n );\n }\n \n return transformedHtml;\n }\n };\n}\n\nexport default viteProximityPrefetch;","/**\n * Vue Proximity Prefetch\n * A Vue plugin that prefetches routes when the mouse approaches links\n * for faster navigation and improved user experience.\n */\n\nimport type { Plugin } from 'vue';\nimport type { App } from '@vue/runtime-core';\nimport ProximityPrefetch from './ProximityPrefetch.vue';\n\n// Export the Vue component for direct usage\nexport { ProximityPrefetch };\n\n// Re-export the Vite plugin\nexport * from './vite-plugin';\n\n/**\n * Component props interface\n */\nexport interface ProximityPrefetchProps {\n /** Distance threshold in pixels to trigger prefetching */\n threshold?: number;\n /** Interval for periodic checks in milliseconds (0 means reactive to mouse movements) */\n predictionInterval?: number;\n /** Enable debug logging */\n debug?: boolean;\n /** Enable mobile support with viewport-based prefetching */\n mobileSupport?: boolean;\n /** Viewport margin for mobile prefetching */\n viewportMargin?: number;\n /** Enable prefetching of all links on the page at once */\n prefetchAllLinks?: boolean;\n /** Delay before starting to prefetch all links (ms) */\n prefetchAllLinksDelay?: number;\n}\n\n/**\n * Plugin configuration options\n */\nexport interface ProximityPrefetchOptions {\n /** Distance threshold in pixels to trigger prefetching */\n threshold?: number;\n /** Interval for periodic checks in milliseconds */\n predictionInterval?: number;\n /** Maximum number of routes to prefetch simultaneously */\n maxPrefetch?: number;\n /** Enable automatic prefetching without the Vue component */\n automaticPrefetch?: boolean;\n /** Enable debug logging */\n debug?: boolean;\n /** Enable mobile support for touch devices */\n mobileSupport?: boolean;\n /** Viewport margin for mobile prefetching */\n viewportMargin?: number;\n /** Enable prefetching of all links on the page at once */\n prefetchAllLinks?: boolean;\n /** Delay before starting to prefetch all links (ms) */\n prefetchAllLinksDelay?: number;\n}\n\n/**\n * Vue plugin for proximity-based route prefetching\n * Usage: app.use(ProximityPrefetchPlugin)\n */\nexport const ProximityPrefetchPlugin: Plugin = {\n install(app: App) {\n // Register the component globally\n app.component('ProximityPrefetch', ProximityPrefetch);\n }\n};"],"names":["THROTTLE_INTERVAL","PPF_DEBUG","props","__props","isDebugEnabled","computed","router","useRouter","mousePosition","ref","prefetchedRoutes","links","isMouseNearLink","lastProximityCheck","isTouchDevice","detectTouchDevice","updateLinks","anchors","el","href","rect","link","calculateDistance","x1","y1","x2","y2","calculateCenterPoint","isLinkInViewport","checkProximity","closestLinks","center","distance","checkViewportLinks","visibleLinks","prefetchNearbyRoutes","now","hasNearbyLinks","linksToProcess","a","b","linksWithDistance","routesToPrefetch","MAX_PREFETCH","route","resolved","routeRecord","comps","comp","asyncComp","e","anchor","err","prefetchRoute","prefetchAllPageLinks","uniqueRoutes","processed","batchSize","batchDelay","processBatch","batch","handleMouseMove","handleScroll","handleTouch","onMounted","style","observer","intervalId","throttledPrefetch","watch","onUnmounted","DEFAULT_OPTIONS","generatePrefetchScript","options","viteProximityPrefetch","resolvedOptions","html","transformedHtml","injectionPoint","prefetchScript","ProximityPrefetchPlugin","app","ProximityPrefetch"],"mappings":"+HAyEMA,EAAoB,iTAnC1B,MAAMC,EAAY,OAAO,OAAW,KAAe,OAAO,YAAc,GAGlEC,EAAQC,EAWRC,EAAiBC,EAAA,SAAS,IAAMH,EAAM,OAASD,CAAS,EAGxDK,EAASC,EAAAA,UAAU,EAGnBC,EAAgBC,EAAAA,IAAI,CAAE,EAAG,EAAG,EAAG,EAAG,EAGlCC,EAAmBD,EAAAA,IAAqB,IAAA,GAAK,EAG7CE,EAAQF,EAAmE,IAAA,EAAE,EAG7EG,EAAkBH,MAAI,EAAK,EAG3BI,EAAqBJ,EAAAA,IAAI,KAAK,IAAA,CAAK,EAMnCK,EAAgBL,MAAI,EAAK,EAKzBM,EAAoB,IAEtB,OAAO,OAAW,MAChB,iBAAkB,QAClB,UAAU,eAAiB,GAC3B,UAAU,iBAAmB,GAO7BC,EAAc,IAAM,CAExB,MAAMC,EAAU,MAAM,KACpB,SAAS,iBAAiB,SAAS,CACrC,EAEIb,EAAe,OACjB,QAAQ,MAAM,6BAA6Ba,EAAQ,MAAM,oBAAoB,EAI/EN,EAAM,MAAQM,EACX,IAAKC,GAAO,CACL,MAAAC,EAAOD,EAAG,aAAa,MAAM,EAGnC,GAAIC,IAASA,EAAK,WAAW,GAAG,GAAK,CAACA,EAAK,SAAS,KAAK,IAAM,CAACA,EAAK,WAAW,GAAG,EAAG,CAC9E,MAAAC,EAAOF,EAAG,sBAAsB,EAEtC,OAAId,EAAe,OACjB,QAAQ,MAAM,mCAAmCe,CAAI,UAAWC,CAAI,EAG/D,CACL,GAAAF,EACA,KAAAC,EACA,KAAAC,CACF,CAAA,CAEK,OAAA,IACR,CAAA,EACA,OAAQC,GAAyEA,IAAS,IAAI,CACnG,EAKMC,EAAoB,CAACC,EAAYC,EAAYC,EAAYC,IACtD,KAAK,MAAMD,EAAKF,IAAO,GAAKG,EAAKF,IAAO,CAAC,EAM5CG,EAAwBP,IACrB,CACL,EAAGA,EAAK,KAAOA,EAAK,MAAQ,EAC5B,EAAGA,EAAK,IAAMA,EAAK,OAAS,CAC9B,GAMIQ,EAAoBR,GAEtBA,EAAK,KAAO,CAAClB,EAAM,gBACnBkB,EAAK,MAAQ,CAAClB,EAAM,gBACpBkB,EAAK,QAAU,OAAO,YAAclB,EAAM,gBAC1CkB,EAAK,OAAS,OAAO,WAAalB,EAAM,eAQtC2B,EAAiB,IAAe,CAChC,GAAA,CAAClB,EAAM,MAAM,OACR,MAAA,GAIHA,EAAA,MAAM,QAAgBU,GAAA,CACrBA,EAAA,KAAOA,EAAK,GAAG,sBAAsB,CAAA,CAC3C,EAeD,MAAMS,EAZoBnB,EAAM,MAAM,IAAKU,GAAS,CAC5C,MAAAU,EAASJ,EAAqBN,EAAK,IAAI,EACvCW,EAAWV,EACfd,EAAc,MAAM,EACpBA,EAAc,MAAM,EACpBuB,EAAO,EACPA,EAAO,CACT,EACO,MAAA,CAAE,GAAGV,EAAM,SAAAW,CAAS,CAAA,CAC5B,EAGsC,OACpCX,GAASA,EAAK,SAAWnB,EAAM,SAClC,EAEA,OAAIE,EAAe,OAAS0B,EAAa,OAAS,IAChD,QAAQ,MAAM,uBAAuBA,EAAa,MAAM,2BAA2B5B,EAAM,SAAS,IAAI,EACtG4B,EAAa,QAAgBT,GAAA,CACnB,QAAA,MAAM,6BAA6BA,EAAK,IAAI,eAAeA,EAAK,SAAS,QAAQ,CAAC,CAAC,IAAI,CAAA,CAChG,GAGIS,EAAa,OAAS,CAC/B,EAMMG,EAAqB,IAAe,CACpC,GAAA,CAACtB,EAAM,MAAM,OACR,MAAA,GAIHA,EAAA,MAAM,QAAgBU,GAAA,CACrBA,EAAA,KAAOA,EAAK,GAAG,sBAAsB,CAAA,CAC3C,EAGK,MAAAa,EAAevB,EAAM,MAAM,UAAeiB,EAAiBP,EAAK,IAAI,CAAC,EAE3E,OAAIjB,EAAe,OAAS8B,EAAa,OAAS,GAChD,QAAQ,MAAM,uBAAuBA,EAAa,MAAM,mCAAmChC,EAAM,cAAc,KAAK,EAG/GgC,EAAa,OAAS,CAC/B,EAKMC,EAAuB,IAAY,CAEjC,MAAAC,EAAM,KAAK,IAAI,EACjB,GAAAA,EAAMvB,EAAmB,MAAQb,EACnC,OAEFa,EAAmB,MAAQuB,EAGvB,IAAAC,EAaJ,GAXIvB,EAAc,OAASZ,EAAM,cAE/BmC,EAAiBJ,EAAmB,EAGpCI,EAAiBR,EAAe,EAGlCjB,EAAgB,MAAQyB,EAGpB,CAACA,GAAkB,CAAC1B,EAAM,MAAM,OAClC,OAIIA,EAAA,MAAM,QAAgBU,GAAA,CACrBA,EAAA,KAAOA,EAAK,GAAG,sBAAsB,CAAA,CAC3C,EAEG,IAAAiB,EAEA,GAAAxB,EAAc,OAASZ,EAAM,cAE/BoC,EAAiB3B,EAAM,MAAM,UAAeiB,EAAiBP,EAAK,IAAI,CAAC,EAExDiB,EAAA,KAAK,CAACC,EAAGC,IAAMD,EAAE,KAAK,IAAMC,EAAE,KAAK,GAAG,MAChD,CAEL,MAAMC,EAAoB9B,EAAM,MAAM,IAAKU,GAAS,CAC5C,MAAAU,EAASJ,EAAqBN,EAAK,IAAI,EACvCW,EAAWV,EACfd,EAAc,MAAM,EACpBA,EAAc,MAAM,EACpBuB,EAAO,EACPA,EAAO,CACT,EACO,MAAA,CAAE,GAAGV,EAAM,SAAAW,CAAS,CAAA,CAC5B,EAGDS,EAAkB,KAAK,CAACF,EAAGC,IAAMD,EAAE,SAAWC,EAAE,QAAQ,EAGvCF,EAAAG,EACd,OAAQpB,GAASA,EAAK,SAAWnB,EAAM,SAAS,EAChD,IAAI,CAAC,CAAE,GAAAgB,EAAI,KAAAC,EAAM,KAAAC,CAAK,KAAO,CAAE,GAAAF,EAAI,KAAAC,EAAM,KAAAC,GAAO,CAAA,CAIrD,MAAMsB,EAAmBJ,EAAe,IAAKjB,GAASA,EAAK,IAAI,EAGzDsB,EAAe,EACrB,UAAWC,KAASF,EAAiB,MAAM,EAAGC,CAAY,EAExD,GAAI,CAAAjC,EAAiB,MAAM,IAAIkC,CAAK,EAIpC,CAAIxC,EAAe,OACT,QAAA,IAAI,mCAAoCwC,CAAK,EAGnD,GAAA,CAEI,MAAAC,EAAWvC,EAAO,QAAQsC,CAAK,EAG9BtC,EAAA,UAAA,EAAY,QAAuBwC,GAAA,CACxC,GAAIA,EAAY,OAASD,EAAS,MAAQC,EAAY,WAAY,CAEhE,MAAMC,EAAQD,EAAY,WAC1B,OAAO,OAAOC,CAAK,EAAE,QAAgBC,GAAA,CAEnC,MAAMC,EAAYD,EAEd,GAAA,OAAOC,GAAc,WACnB,GAAA,CAEQA,EAAA,QACHC,EAAG,CACN9C,EAAe,OACT,QAAA,MAAM,+CAAgD8C,CAAC,CACjE,CAEJ,CACD,CAAA,CACH,CACD,EAGgBxC,EAAA,MAAM,IAAIkC,CAAK,EAG5BxC,EAAe,OAEO,MAAM,KAC5B,SAAS,iBAAiB,WAAWwC,CAAK,IAAI,CAChD,EAEgB,QAAkBO,GAAA,CAE3BA,EAAO,aAAa,wBAAwB,IACxCA,EAAA,aAAa,yBAA0B,MAAM,EAC7CA,EAAA,UAAU,IAAI,qBAAqB,EACnCA,EAAA,MAAQ,eAAeP,CAAK,GACrC,CACD,QAEIQ,EAAK,CACRhD,EAAe,OACT,QAAA,MAAM,+CAAgDgD,CAAG,CACnE,EAGN,EAKMC,EAAiBT,GAAwB,CAE7C,GAAI,CAAAlC,EAAiB,MAAM,IAAIkC,CAAK,EAIpC,CAAIxC,EAAe,OACT,QAAA,IAAI,mCAAoCwC,CAAK,EAGnD,GAAA,CAEI,MAAAC,EAAWvC,EAAO,QAAQsC,CAAK,EAG9BtC,EAAA,UAAA,EAAY,QAAuBwC,GAAA,CACxC,GAAIA,EAAY,OAASD,EAAS,MAAQC,EAAY,WAAY,CAEhE,MAAMC,EAAQD,EAAY,WAC1B,OAAO,OAAOC,CAAK,EAAE,QAAgBC,GAAA,CAEnC,MAAMC,EAAYD,EAEd,GAAA,OAAOC,GAAc,WACnB,GAAA,CAEQA,EAAA,QACHC,EAAG,CACN9C,EAAe,OACT,QAAA,MAAM,+CAAgD8C,CAAC,CACjE,CAEJ,CACD,CAAA,CACH,CACD,EAGgBxC,EAAA,MAAM,IAAIkC,CAAK,EAG5BxC,EAAe,OAEO,MAAM,KAC5B,SAAS,iBAAiB,WAAWwC,CAAK,IAAI,CAChD,EAEgB,QAAkBO,GAAA,CAE3BA,EAAO,aAAa,wBAAwB,IACxCA,EAAA,aAAa,yBAA0B,MAAM,EAC7CA,EAAA,UAAU,IAAI,qBAAqB,EACnCA,EAAA,MAAQ,eAAeP,CAAK,GACrC,CACD,QAEIQ,EAAK,CACRhD,EAAe,OACT,QAAA,MAAM,+CAAgDgD,CAAG,CACnE,EAEJ,EAKME,EAAuB,IAAY,CAGnC,GAFQtC,EAAA,EAER,CAACL,EAAM,MAAM,OAAQ,CACnBP,EAAe,OACjB,QAAQ,IAAI,gDAAgD,EAE9D,MAAA,CAGEA,EAAe,OACjB,QAAQ,IAAI,8CAA8CO,EAAM,MAAM,MAAM,cAAc,EAI5F,MAAM4C,EAAe,CAAC,GAAG,IAAI,IAAI5C,EAAM,MAAM,IAAYU,GAAAA,EAAK,IAAI,CAAC,CAAC,EAEhEjB,EAAe,OACjB,QAAQ,IAAI,kDAAkDmD,EAAa,MAAM,EAAE,EAIrF,IAAIC,EAAY,EAChB,MAAMC,EAAY,EACZC,EAAa,IAEbC,EAAe,IAAM,CACzB,MAAMC,EAAQL,EAAa,MAAMC,EAAWA,EAAYC,CAAS,EAC7D,GAAAG,EAAM,SAAW,EAErB,WAAWhB,KAASgB,EAClBP,EAAcT,CAAK,EAGrBY,GAAaI,EAAM,OAEfJ,EAAYD,EAAa,OAC3B,WAAWI,EAAcD,CAAU,EAC1BtD,EAAe,OAChB,QAAA,IAAI,uDAAuDoD,CAAS,oBAAoB,EAEpG,EAEaG,EAAA,CACf,EAKME,EAAmB,GAAwB,CAC/CrD,EAAc,MAAQ,CAAE,EAAG,EAAE,QAAS,EAAG,EAAE,OAAQ,CACrD,EAKMsD,EAAe,IAAY,CAC3BhD,EAAc,OAASZ,EAAM,eACnB,KAAK,IAAI,EACXW,EAAmB,OAASb,GACfmC,EAAA,CAG3B,EAKM4B,EAAc,IAAY,CAC1BjD,EAAc,OAASZ,EAAM,eACViC,EAAA,CAEzB,EAGA6B,OAAAA,EAAAA,UAAU,IAAM,CAId,GAFAlD,EAAc,MAAQC,EAAkB,EAEpCX,EAAe,MAAO,CACxB,QAAQ,IAAI,sDAAuD,CACjE,UAAWF,EAAM,UACjB,mBAAoBA,EAAM,mBAC1B,MAAOE,EAAe,MACtB,cAAeF,EAAM,cACrB,eAAgBA,EAAM,eACtB,WAAYY,EAAc,MAAQ,eAAiB,gBAAA,CACpD,EAGK,MAAAmD,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,YAAc;AAAA;AAAA;AAAA;AAAA;AAAA,MAMX,SAAA,KAAK,YAAYA,CAAK,CAAA,CAIjC,WAAW,IAAM,CACHjD,EAAA,EACRZ,EAAe,OACjB,QAAQ,IAAI,gDAAgDO,EAAM,MAAM,MAAM,cAAc,EAIzEwB,EAAA,GACpB,GAAG,EAGA,MAAA+B,EAAW,IAAI,iBAAiB,IAAM,CAC9BlD,EAAA,CAAA,CACb,EAGQkD,EAAA,QAAQ,SAAS,KAAM,CAC9B,UAAW,GACX,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,MAAM,CAAA,CACzB,EAGGpD,EAAc,OAASZ,EAAM,eAE/B,OAAO,iBAAiB,SAAU4D,EAAc,CAAE,QAAS,GAAM,EACjE,OAAO,iBAAiB,aAAcC,EAAa,CAAE,QAAS,GAAM,EACpE,OAAO,iBAAiB,SAAUD,EAAc,CAAE,QAAS,GAAM,EAE7D1D,EAAe,OACjB,QAAQ,IAAI,oEAAqEF,EAAM,eAAiB,IAAI,IAIvG,OAAA,iBAAiB,YAAa2D,CAAe,EAEhDzD,EAAe,OACjB,QAAQ,IAAI,8CAA8C,GAO1D,IAAA+D,EAGA,GAAAjE,EAAM,mBAAqB,EAEhBiE,EAAA,OAAO,YAAY,IAAM,EAGhCrD,EAAc,OAASN,EAAc,MAAM,IAAM,GAAKA,EAAc,MAAM,IAAM,IAC7D2B,EAAA,CACvB,EACCjC,EAAM,kBAAkB,UAClB,CAACY,EAAc,MAAO,CAE/B,MAAMsD,EAAoB,IAAY,CACxB,KAAK,IAAI,EACXvD,EAAmB,OAASb,GACfmC,EAAA,CAEzB,EAGAkC,EAAA,MAAM7D,EAAe,IAAM,CACP4D,EAAA,CAAA,CACnB,CAAA,CAIClE,EAAM,kBACR,WAAW,IAAM,CACMoD,EAAA,CAAA,EACpBpD,EAAM,qBAAqB,EAIhCoE,EAAAA,YAAY,IAAM,CACZxD,EAAc,OAASZ,EAAM,eACxB,OAAA,oBAAoB,SAAU4D,CAAY,EAC1C,OAAA,oBAAoB,aAAcC,CAAW,EAC7C,OAAA,oBAAoB,SAAUD,CAAY,GAE1C,OAAA,oBAAoB,YAAaD,CAAe,EAGzDK,EAAS,WAAW,EAChBC,GACF,OAAO,cAAcA,CAAU,CACjC,CACD,CAAA,CACF,6CCzhBKI,EAAyD,CAC7D,UAAW,IACX,mBAAoB,EACpB,YAAa,EACb,MAAO,GACP,kBAAmB,GACnB,cAAe,GACf,eAAgB,IAChB,iBAAkB,GAClB,sBAAuB,IACzB,EAKA,SAASC,EAAuBC,EAAwD,CAC/E,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAKkBA,EAAQ,KAAK;AAAA;AAAA;AAAA;AAAA,qBAInBA,EAAQ,SAAS;AAAA,8BACRA,EAAQ,kBAAkB;AAAA,uBACjCA,EAAQ,WAAW;AAAA,iBACzBA,EAAQ,KAAK;AAAA,yBACLA,EAAQ,aAAa;AAAA,0BACpBA,EAAQ,cAAc;AAAA,4BACpBA,EAAQ,gBAAgB;AAAA,iCACnBA,EAAQ,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAgT9D,CAQgB,SAAAC,EAAsBD,EAAuC,GAAY,CAEjF,MAAAxE,EAAY,QAAQ,IAAI,YAAc,OAGtC0E,EAAyD,CAC7D,GAAGJ,EACH,GAAGE,EACH,MAAOA,EAAQ,OAASxE,CAC1B,EAEO,MAAA,CACL,KAAM,qCAEN,gBAAiB,CACf,QAAQ,IAAI,uCAAuC,EAE/C0E,EAAgB,OAClB,QAAQ,IAAI,WAAY,CACtB,UAAWA,EAAgB,UAC3B,mBAAoBA,EAAgB,mBACpC,YAAaA,EAAgB,YAC7B,kBAAmBA,EAAgB,kBACnC,MAAOA,EAAgB,MACvB,cAAeA,EAAgB,cAC/B,eAAgBA,EAAgB,cAAA,CACjC,CAEL,EAMA,mBAAmBC,EAAM,CAEvB,MAAMC,EAAkBD,EAAK,QAC3B,6BACA,gDACF,EAGA,GAAID,EAAgB,kBAAmB,CACrC,MAAMG,EAAiB,UACjBC,EAAiBP,EAAuBG,CAAe,EAE7D,OAAOE,EAAgB,QACrBC,EACA,GAAGC,CAAc;AAAA,EAAKD,CAAc,EACtC,CAAA,CAGK,OAAAD,CAAA,CAEX,CACF,CC/ZO,MAAMG,EAAkC,CAC7C,QAAQC,EAAU,CAEZA,EAAA,UAAU,oBAAqBC,CAAiB,CAAA,CAExD"}