UNPKG

kinetic-slider

Version:

A WebGL-powered kinetic slider component using PIXI.js

1 lines 64.9 kB
{"version":3,"file":"KineticSlider.cjs","sources":["../../src/KineticSlider.tsx"],"sourcesContent":["import React, { useRef, useState, useEffect, useCallback } from 'react';\nimport styles from './KineticSlider.module.css';\nimport type { KineticSliderProps } from './types';\nimport { Application, Sprite, Container, DisplacementFilter } from 'pixi.js';\nimport ResourceManager from './managers/ResourceManager';\nimport {AtlasManager} from './managers/AtlasManager';\nimport RenderScheduler from './managers/RenderScheduler';\nimport { UpdateType } from './managers/UpdateTypes';\nimport { ThrottleStrategy } from './managers/FrameThrottler';\nimport AnimationCoordinator from './managers/AnimationCoordinator';\nimport SlidingWindowManager from './managers/SlidingWindowManager';\nimport { FilterFactory } from './filters';\nimport { type FilterConfig, type FilterType } from './filters/types';\n\n// Import hooks directly\nimport { useDisplacementEffects } from './hooks';\nimport { useFilters } from './hooks';\nimport { useSlides } from './hooks';\nimport { useTextContainers } from './hooks';\nimport { useMouseTracking } from './hooks';\nimport { useIdleTimer } from './hooks';\nimport { useNavigation } from './hooks';\nimport { useExternalNav } from './hooks';\nimport { useTouchSwipe } from './hooks';\nimport { useMouseDrag } from './hooks';\nimport { useTextTilt } from './hooks';\nimport { useResizeHandler } from './hooks';\nimport { loadKineticSliderDependencies } from './ImportHelpers';\nimport { preloadKineticSliderAssets } from './utils/assetPreload';\n\n// Development environment check\nconst isDevelopment = import.meta.env?.MODE === 'development';\n\n// Define the filter coordination event name\nconst FILTER_COORDINATION_EVENT = 'kinetic-slider:filter-update';\n\n/**\n * Creates an interactive image slider with various displacement and transition effects\n */\nconst KineticSlider: React.FC<KineticSliderProps> = ({\n // Content sources\n images = [],\n texts = [],\n slidesBasePath = '/images/',\n\n // Displacement settings\n backgroundDisplacementSpriteLocation = '/images/background-displace.jpg',\n cursorDisplacementSpriteLocation = '/images/cursor-displace.png',\n cursorImgEffect = true,\n cursorTextEffect = true,\n cursorScaleIntensity = 0.65,\n cursorMomentum = 0.14,\n\n // Cursor displacement sizing options\n cursorDisplacementSizing = 'natural',\n cursorDisplacementWidth,\n cursorDisplacementHeight,\n\n // Text styling\n textTitleColor = 'white',\n textTitleSize = 64,\n mobileTextTitleSize = 40,\n textTitleLetterspacing = 2,\n textTitleFontFamily,\n textSubTitleColor = 'white',\n textSubTitleSize = 24,\n mobileTextSubTitleSize = 18,\n textSubTitleLetterspacing = 1,\n textSubTitleOffsetTop = 10,\n mobileTextSubTitleOffsetTop = 5,\n textSubTitleFontFamily,\n\n // Animation settings\n maxContainerShiftFraction = 0.05,\n swipeScaleIntensity = 2,\n transitionScaleIntensity = 30,\n\n // Navigation settings\n externalNav = false,\n navElement = { prev: '.main-nav.prev', next: '.main-nav.next' },\n buttonMode = false,\n\n // Filter configurations\n imageFilters,\n textFilters,\n\n // Atlas configuration\n slidesAtlas = 'slides-atlas',\n effectsAtlas = 'effects-atlas',\n useEffectsAtlas = false,\n useSlidesAtlas = false\n }) => {\n // Debug log the props\n console.log(\"KineticSlider received props:\", {\n useSlidesAtlas,\n useEffectsAtlas,\n slidesAtlas,\n effectsAtlas\n });\n\n // Core references\n const sliderRef = useRef<HTMLDivElement>(null);\n const [isClient, setIsClient] = useState(false);\n const [currentSlideIndex, setCurrentSlideIndex] = useState(0);\n const [isInteracting, setIsInteracting] = useState(false);\n const [isAppReady, setIsAppReady] = useState(false);\n const [assetsLoaded, setAssetsLoaded] = useState(false);\n const cursorActiveRef = useRef<boolean>(false);\n\n // Initialize the render scheduler (singleton)\n const scheduler = RenderScheduler.getInstance();\n\n // Create ResourceManager instance with unique ID\n const resourceManagerRef = useRef<ResourceManager | null>(null);\n\n // Create SlidingWindowManager reference\n const slidingWindowManagerRef = useRef<SlidingWindowManager | null>(null);\n\n // Initialize the animation coordinator and set the resource manager\n const animationCoordinator = AnimationCoordinator.getInstance();\n\n // Configure the scheduler for optimal performance\n useEffect(() => {\n // Only run on client-side\n if (typeof window === 'undefined') return;\n\n scheduler.configureThrottling({\n targetFps: 60,\n strategy: ThrottleStrategy.PRIORITY\n });\n\n return () => {\n // Clear any pending updates related to this component when unmounting\n scheduler.clearQueue();\n };\n }, [scheduler]);\n\n // Create AtlasManager instance\n const atlasManagerRef = useRef<AtlasManager | null>(null);\n\n // Set up Pixi app\n const appRef = useRef<Application | null>(null);\n const slidesRef = useRef<Sprite[]>([]);\n const textContainersRef = useRef<Container[]>([]);\n const backgroundDisplacementSpriteRef = useRef<Sprite | null>(null);\n const cursorDisplacementSpriteRef = useRef<Sprite | null>(null);\n const bgDispFilterRef = useRef<DisplacementFilter | null>(null);\n const cursorDispFilterRef = useRef<DisplacementFilter | null>(null);\n const currentIndexRef = useRef<number>(0);\n\n // Client-side initialization\n useEffect(() => {\n setIsClient(true);\n }, []);\n\n // Initialize ResourceManager and AtlasManager on mount\n useEffect(() => {\n const componentId = `kinetic-slider-${Math.random().toString(36).substring(2, 9)}`;\n\n // Initialize ResourceManager\n resourceManagerRef.current = new ResourceManager(componentId, {\n enableMetrics: true,\n enableShaderPooling: true,\n logLevel: import.meta.env.NODE_ENV === 'development' ? 'debug' : 'warn'\n });\n\n // Initialize FilterFactory with lazy loading\n FilterFactory.initialize({\n enableShaderPooling: true,\n enableDebug: import.meta.env.NODE_ENV === 'development',\n lazyLoadConfig: {\n unloadTimeoutMs: 120000, // 2 minutes\n maxCachedModules: 20,\n enablePrefetching: true,\n retryFailedLoads: true,\n maxRetries: 3\n }\n });\n\n // Initialize AtlasManager with resource manager\n atlasManagerRef.current = new AtlasManager({\n debug: true,\n preferAtlas: true,\n cacheFrameTextures: true,\n basePath: '/atlas'\n }, resourceManagerRef.current);\n\n // Set the resource manager for the animation coordinator\n if (resourceManagerRef.current) {\n animationCoordinator.setResourceManager(resourceManagerRef.current);\n }\n\n // Initialize SlidingWindowManager with default window size and initial index\n slidingWindowManagerRef.current = new SlidingWindowManager({\n totalSlides: images.length,\n initialIndex: 0,\n windowSize: 2, // Default: current slide ±2\n debug: import.meta.env.NODE_ENV === 'development'\n }, resourceManagerRef.current);\n\n return () => {\n // Mark as unmounting to prevent new resource allocation\n if (resourceManagerRef.current) {\n console.log(\"Component unmounting - disposing all resources\");\n resourceManagerRef.current.markUnmounting();\n // Dispose all tracked resources\n resourceManagerRef.current.dispose();\n resourceManagerRef.current = null;\n }\n\n // Clean up atlas manager\n if (atlasManagerRef.current) {\n atlasManagerRef.current.dispose();\n atlasManagerRef.current = null;\n }\n\n // Clear the sliding window manager\n slidingWindowManagerRef.current = null;\n };\n }, [images.length, animationCoordinator]);\n\n // Create a pixi refs object for hooks\n const pixiRefs = {\n app: appRef,\n slides: slidesRef,\n textContainers: textContainersRef,\n backgroundDisplacementSprite: backgroundDisplacementSpriteRef,\n cursorDisplacementSprite: cursorDisplacementSpriteRef,\n bgDispFilter: bgDispFilterRef,\n cursorDispFilter: cursorDispFilterRef,\n currentIndex: currentIndexRef\n };\n\n // Props object for hooks\n const hookProps = {\n images,\n texts,\n slidesBasePath,\n backgroundDisplacementSpriteLocation,\n cursorDisplacementSpriteLocation,\n cursorImgEffect,\n cursorTextEffect,\n cursorScaleIntensity,\n cursorMomentum,\n cursorDisplacementSizing,\n cursorDisplacementWidth,\n cursorDisplacementHeight,\n textTitleColor,\n textTitleSize,\n mobileTextTitleSize,\n textTitleLetterspacing,\n textTitleFontFamily,\n textSubTitleColor,\n textSubTitleSize,\n mobileTextSubTitleSize,\n textSubTitleLetterspacing,\n textSubTitleOffsetTop,\n mobileTextSubTitleOffsetTop,\n textSubTitleFontFamily,\n maxContainerShiftFraction,\n swipeScaleIntensity,\n transitionScaleIntensity,\n imageFilters,\n textFilters,\n slidesAtlas,\n effectsAtlas,\n useSlidesAtlas,\n useEffectsAtlas\n };\n\n // Enhanced hook params with resource and atlas managers\n const hookParams = {\n sliderRef,\n pixi: pixiRefs,\n props: hookProps,\n resourceManager: resourceManagerRef.current,\n atlasManager: atlasManagerRef.current,\n slidingWindowManager: slidingWindowManagerRef.current\n };\n\n // Use displacement effects\n const { showDisplacementEffects, hideDisplacementEffects } = useDisplacementEffects({\n sliderRef,\n bgDispFilterRef,\n cursorDispFilterRef,\n backgroundDisplacementSpriteRef,\n cursorDisplacementSpriteRef,\n appRef,\n backgroundDisplacementSpriteLocation,\n cursorDisplacementSpriteLocation,\n cursorImgEffect,\n cursorScaleIntensity,\n cursorDisplacementSizing,\n cursorDisplacementWidth,\n cursorDisplacementHeight,\n resourceManager: resourceManagerRef.current,\n atlasManager: atlasManagerRef.current || undefined,\n effectsAtlas,\n useEffectsAtlas\n });\n\n // Use filter effects\n const {\n updateFilterIntensities,\n resetAllFilters,\n activateFilterEffects,\n isInitialized: filtersInitialized,\n isActive: filtersActive,\n setFiltersActive\n } = useFilters(hookParams);\n\n // Coordinate filter states with interaction state\n useEffect(() => {\n if (!isAppReady || !assetsLoaded) {\n console.log(\"Skipping filter coordination - not ready\", {\n filtersInitialized,\n isAppReady,\n assetsLoaded\n });\n return;\n }\n\n // Try to initialize filters if they're not already initialized\n if (!filtersInitialized && typeof activateFilterEffects === 'function') {\n console.log(\"Attempting to initialize filters during coordination\");\n activateFilterEffects();\n }\n\n console.log(`Filter coordination - isInteracting: ${isInteracting}, filtersActive: ${filtersActive}`);\n\n if (isInteracting) {\n // When interaction starts, activate both displacement and custom filters\n console.log(\"Interaction started - activating effects\");\n showDisplacementEffects();\n\n // First try to use the dedicated activation function\n if (typeof activateFilterEffects === 'function') {\n console.log(\"Using activateFilterEffects\");\n activateFilterEffects();\n } else {\n // Fallback to updateFilterIntensities\n console.log(\"Using updateFilterIntensities\");\n updateFilterIntensities(true, true);\n }\n } else {\n // When interaction ends, deactivate both\n console.log(\"Interaction ended - deactivating effects\");\n hideDisplacementEffects();\n updateFilterIntensities(false);\n }\n }, [\n isInteracting,\n isAppReady,\n assetsLoaded,\n filtersInitialized,\n filtersActive,\n showDisplacementEffects,\n hideDisplacementEffects,\n updateFilterIntensities,\n activateFilterEffects\n ]);\n\n // Reset filters when component unmounts or when app/assets state changes\n useEffect(() => {\n if (!isAppReady || !assetsLoaded) {\n resetAllFilters();\n }\n return () => {\n resetAllFilters();\n };\n }, [isAppReady, assetsLoaded, resetAllFilters]);\n\n // Preload assets including fonts and atlases\n useEffect(() => {\n if (typeof window === 'undefined' || !isClient) return;\n\n const loadAssets = async () => {\n try {\n console.log(\"Preloading assets and fonts...\");\n\n // Preload atlases first\n if (atlasManagerRef.current) {\n // Load the slides atlas\n if (slidesAtlas) {\n await atlasManagerRef.current.loadAtlas(\n slidesAtlas,\n `/atlas/${slidesAtlas}.json`,\n `/atlas/${slidesAtlas}.png`\n );\n }\n\n // Load the effects atlas\n if (effectsAtlas) {\n await atlasManagerRef.current.loadAtlas(\n effectsAtlas,\n `/atlas/${effectsAtlas}.json`,\n `/atlas/${effectsAtlas}.png`\n );\n }\n }\n\n // Then preload any remaining assets (as fallback)\n await preloadKineticSliderAssets(\n images,\n backgroundDisplacementSpriteLocation,\n cursorDisplacementSpriteLocation,\n textTitleFontFamily,\n textSubTitleFontFamily\n );\n\n setAssetsLoaded(true);\n console.log(\"Assets and fonts preloaded successfully\");\n } catch (error) {\n console.error(\"Failed to preload assets:\", error);\n // Continue anyway so the component doesn't totally fail\n setAssetsLoaded(true);\n }\n };\n\n loadAssets();\n }, [\n isClient,\n images,\n backgroundDisplacementSpriteLocation,\n cursorDisplacementSpriteLocation,\n textTitleFontFamily,\n textSubTitleFontFamily,\n slidesAtlas,\n effectsAtlas\n ]);\n\n // Initialize Pixi.js application\n useEffect(() => {\n if (typeof window === 'undefined' || !sliderRef.current || appRef.current || !assetsLoaded) return;\n\n const initPixi = async () => {\n try {\n console.log(\"Loading PixiJS dependencies...\");\n // Load all dependencies first\n const { gsap, pixi, pixiPlugin } = await loadKineticSliderDependencies();\n\n // Only register plugins in browser\n if (typeof window !== 'undefined' && pixiPlugin) {\n // Register GSAP plugins\n gsap.registerPlugin(pixiPlugin);\n\n // Check if we have the actual plugin (not the mock)\n if (pixiPlugin.registerPIXI) {\n pixiPlugin.registerPIXI(pixi);\n }\n }\n\n console.log(\"Creating Pixi.js application...\");\n\n // Create Pixi application\n const app = new Application();\n await app.init({\n width: sliderRef.current?.clientWidth || 800,\n height: sliderRef.current?.clientHeight || 600,\n backgroundAlpha: 0,\n resizeTo: sliderRef.current || undefined,\n });\n\n // Track the application with the resource manager\n if (resourceManagerRef.current) {\n resourceManagerRef.current.trackPixiApp(app);\n }\n\n // Add canvas to DOM\n if (sliderRef.current) {\n sliderRef.current.appendChild(app.canvas);\n }\n\n // Store reference\n appRef.current = app;\n\n // Create main container\n const stage = new Container();\n app.stage.addChild(stage);\n\n // Track the stage with the resource manager\n if (resourceManagerRef.current) {\n resourceManagerRef.current.trackDisplayObject(stage);\n }\n\n // Set app as ready\n setIsAppReady(true);\n\n console.log(\"Pixi.js application initialized\");\n } catch (error) {\n console.error(\"Failed to initialize Pixi.js application:\", error);\n }\n };\n\n initPixi();\n\n // Cleanup on unmount\n return () => {\n if (appRef.current) {\n if (sliderRef.current) {\n const canvas = sliderRef.current.querySelector('canvas');\n if (canvas) {\n sliderRef.current.removeChild(canvas);\n }\n }\n\n // Note: We don't need to manually destroy the app here\n // as the ResourceManager will handle it during disposal\n appRef.current = null;\n setIsAppReady(false);\n }\n };\n }, [sliderRef.current, assetsLoaded]);\n\n // Prefetch filters based on props\n useEffect(() => {\n // Only run on client-side after app is initialized and props are loaded\n if (typeof window === 'undefined' || !isAppReady) return;\n\n // Skip if no filter configurations\n if (!hookProps.imageFilters && !hookProps.textFilters) return;\n\n // Helper to extract filter types from configurations\n const getFilterTypes = (config?: any): FilterType[] => {\n if (!config) return [];\n\n const configs = Array.isArray(config) ? config : [config];\n return configs\n .filter(c => c && c.type && c.enabled !== false)\n .map(c => c.type as FilterType);\n };\n\n // Get unique filter types from both image and text filters\n const imageFilterTypes = getFilterTypes(hookProps.imageFilters);\n const textFilterTypes = getFilterTypes(hookProps.textFilters);\n const allFilterTypes = [...new Set([...imageFilterTypes, ...textFilterTypes])];\n\n // Prefetch all filter types\n if (allFilterTypes.length > 0) {\n console.log(`Prefetching ${allFilterTypes.length} filter types:`, allFilterTypes);\n FilterFactory.prefetchFilterModules(allFilterTypes as any, 'high');\n }\n }, [isAppReady, hookProps.imageFilters, hookProps.textFilters]);\n\n // Handle slide transitions\n const handleSlideChange = useCallback((newIndex: number) => {\n // Update the current index\n currentIndexRef.current = newIndex;\n setCurrentSlideIndex(newIndex);\n\n // Activate filters for the new slide\n if (filtersInitialized) {\n // If cursor is within the canvas, apply filters immediately with critical priority\n if (isInteracting) {\n console.log(\"Cursor is within canvas - applying filters immediately with critical priority\");\n\n // Schedule displacement effects with critical priority\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.DISPLACEMENT_EFFECT,\n () => {\n showDisplacementEffects();\n },\n 'critical'\n );\n\n // Schedule filter activation with critical priority\n // Force the filter activation to use the new slide index\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.FILTER_UPDATE,\n () => {\n // Explicitly update the current index ref before activating filters\n currentIndexRef.current = newIndex;\n\n // Force filter activation for the new slide\n if (typeof activateFilterEffects === 'function') {\n activateFilterEffects();\n\n // Double-check that filters are active\n setFiltersActive(true);\n }\n },\n 'critical'\n );\n } else {\n // Use activateFilterEffects to properly apply filters to the new slide\n activateFilterEffects();\n\n // Schedule filter deactivation if not interacting\n const transitionDuration = 1000; // 1 second transition\n setTimeout(() => {\n if (!isInteracting) { // Check again in case interaction started during transition\n updateFilterIntensities(false);\n hideDisplacementEffects();\n }\n }, transitionDuration);\n }\n }\n }, [\n filtersInitialized,\n isInteracting,\n activateFilterEffects,\n updateFilterIntensities,\n hideDisplacementEffects,\n showDisplacementEffects,\n scheduler,\n setFiltersActive\n ]);\n\n // Use slides hook with transition handler\n const { nextSlide, prevSlide } = useSlides({\n ...hookParams,\n onSlideChange: handleSlideChange\n });\n\n // Use text containers\n useTextContainers({\n sliderRef,\n appRef,\n slidesRef,\n textContainersRef,\n currentIndex: currentIndexRef,\n buttonMode,\n texts,\n textTitleColor,\n textTitleSize,\n mobileTextTitleSize,\n textTitleLetterspacing,\n textTitleFontFamily,\n textSubTitleColor,\n textSubTitleSize,\n mobileTextSubTitleSize,\n textSubTitleLetterspacing,\n textSubTitleOffsetTop,\n mobileTextSubTitleOffsetTop,\n textSubTitleFontFamily,\n resourceManager: resourceManagerRef.current\n });\n\n // Use mouse tracking\n useMouseTracking({\n ...hookParams,\n backgroundDisplacementSpriteRef,\n cursorDisplacementSpriteRef,\n cursorImgEffect,\n cursorMomentum\n });\n\n // Use idle timer\n useIdleTimer({\n sliderRef,\n cursorActive: cursorActiveRef,\n bgDispFilterRef,\n cursorDispFilterRef,\n cursorImgEffect,\n defaultBgFilterScale: 20,\n defaultCursorFilterScale: 10,\n resourceManager: resourceManagerRef.current\n });\n\n /**\n * Handles transition to the next slide\n * Updates state and reapplies effects after transition\n */\n const handleNext = useCallback(() => {\n if (!appRef.current || !isAppReady || slidesRef.current.length === 0) return;\n const nextIndex = (currentSlideIndex + 1) % slidesRef.current.length;\n\n // Schedule slide transition with high priority\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.SLIDE_TRANSITION,\n () => {\n // First transition the slide\n nextSlide(nextIndex);\n\n // Update the current index\n currentIndexRef.current = nextIndex;\n setCurrentSlideIndex(nextIndex);\n\n // Always schedule effect reapplication after a short delay\n // regardless of whether we're interacting or not\n setTimeout(() => {\n console.log(\"Scheduling effects after slide change (next)\");\n\n // Schedule displacement effects reapplication\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.DISPLACEMENT_EFFECT,\n () => {\n showDisplacementEffects();\n },\n // Use critical priority if cursor is within the canvas\n isInteracting ? 'critical' : undefined\n );\n\n // Schedule filter update with appropriate priority based on interaction state\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.FILTER_UPDATE,\n () => {\n // Ensure current index is set correctly\n currentIndexRef.current = nextIndex;\n\n // Use activateFilterEffects to properly apply filters to the new slide\n if (typeof activateFilterEffects === 'function') {\n activateFilterEffects();\n\n // If cursor is within canvas, ensure filters are active\n if (isInteracting) {\n setFiltersActive(true);\n }\n }\n },\n // Use critical priority if cursor is within the canvas\n isInteracting ? 'critical' : undefined\n );\n }, 100); // Short delay to allow transition to start\n }\n );\n }, [\n appRef,\n isAppReady,\n slidesRef,\n currentSlideIndex,\n nextSlide,\n isInteracting,\n showDisplacementEffects,\n activateFilterEffects,\n scheduler,\n setFiltersActive\n ]);\n\n /**\n * Handles transition to the previous slide\n * Updates state and reapplies effects after transition\n */\n const handlePrev = useCallback(() => {\n if (!appRef.current || !isAppReady || slidesRef.current.length === 0) return;\n const prevIndex = (currentSlideIndex - 1 + slidesRef.current.length) % slidesRef.current.length;\n\n // Schedule slide transition with high priority\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.SLIDE_TRANSITION,\n () => {\n // First transition the slide\n prevSlide(prevIndex);\n\n // Update the current index\n currentIndexRef.current = prevIndex;\n setCurrentSlideIndex(prevIndex);\n\n // Always schedule effect reapplication after a short delay\n // regardless of whether we're interacting or not\n setTimeout(() => {\n console.log(\"Scheduling effects after slide change (prev)\");\n\n // Schedule displacement effects reapplication\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.DISPLACEMENT_EFFECT,\n () => {\n showDisplacementEffects();\n },\n // Use critical priority if cursor is within the canvas\n isInteracting ? 'critical' : undefined\n );\n\n // Schedule filter update with appropriate priority based on interaction state\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.FILTER_UPDATE,\n () => {\n // Ensure current index is set correctly\n currentIndexRef.current = prevIndex;\n\n // Use activateFilterEffects to properly apply filters to the new slide\n if (typeof activateFilterEffects === 'function') {\n activateFilterEffects();\n\n // If cursor is within canvas, ensure filters are active\n if (isInteracting) {\n setFiltersActive(true);\n }\n }\n },\n // Use critical priority if cursor is within the canvas\n isInteracting ? 'critical' : undefined\n );\n }, 100); // Short delay to allow transition to start\n }\n );\n }, [\n appRef,\n isAppReady,\n slidesRef,\n currentSlideIndex,\n prevSlide,\n isInteracting,\n showDisplacementEffects,\n activateFilterEffects,\n scheduler,\n setFiltersActive\n ]);\n\n // Apply hooks only when appRef is available and ready - NOW updateFilterIntensities is defined before being referenced\n useEffect(() => {\n // Skip if app is not initialized\n if (!appRef.current || !isAppReady) return;\n\n // Update current index ref when state changes\n currentIndexRef.current = currentSlideIndex;\n\n // Note: We no longer need to handle filter updates here as they are now handled directly\n // in the navigation functions (handleNext/handlePrev)\n }, [appRef.current, currentSlideIndex, isAppReady]);\n\n // Use navigation\n useNavigation({\n onNext: handleNext,\n onPrev: handlePrev,\n enableKeyboardNav: true\n });\n\n // Use external navigation if enabled\n useExternalNav({\n externalNav,\n navElement,\n handleNext,\n handlePrev\n });\n\n // Use touch swipe\n useTouchSwipe({\n sliderRef,\n onSwipeLeft: handleNext,\n onSwipeRight: handlePrev\n });\n\n // Use mouse drag\n useMouseDrag({\n sliderRef,\n slidesRef,\n currentIndex: currentIndexRef,\n swipeScaleIntensity,\n swipeDistance: typeof window !== 'undefined' ? window.innerWidth * 0.2 : 200,\n onSwipeLeft: handleNext,\n onSwipeRight: handlePrev\n });\n\n // Use text tilt\n useTextTilt({\n sliderRef,\n textContainersRef,\n currentIndex: currentIndexRef,\n cursorTextEffect,\n maxContainerShiftFraction,\n bgDispFilterRef,\n cursorDispFilterRef,\n cursorImgEffect\n });\n\n // Use resize handler\n useResizeHandler({\n sliderRef,\n appRef,\n slidesRef,\n textContainersRef,\n backgroundDisplacementSpriteRef,\n cursorDisplacementSpriteRef\n });\n\n /**\n * Handles mouse enter events on the slider element\n * Activates displacement effects and filter intensities\n * Now uses scheduler for batched updates\n */\n const handleMouseEnter = useCallback(() => {\n if (!isAppReady) return;\n console.log(\"Mouse entered the slider - activating effects immediately\", {\n filtersInitialized,\n filtersActive,\n hasActivateFunction: typeof activateFilterEffects === 'function',\n hasUpdateFunction: typeof updateFilterIntensities === 'function',\n currentSlideIndex\n });\n\n // Update cursor active state\n cursorActiveRef.current = true;\n\n // Set interaction state immediately to ensure proper coordination\n setIsInteracting(true);\n\n // Schedule displacement effects with critical priority\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.DISPLACEMENT_EFFECT,\n () => {\n // Apply displacement effects immediately\n showDisplacementEffects();\n console.log(\"Displacement effects activated\");\n },\n 'critical'\n );\n\n // Schedule filter activation with critical priority\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.FILTER_UPDATE,\n () => {\n // Force initialize filters if not already initialized\n if (!filtersInitialized && typeof activateFilterEffects === 'function') {\n console.log(\"Filters not initialized, initializing now\");\n activateFilterEffects();\n } else if (typeof activateFilterEffects === 'function') {\n // Use the dedicated activation function as the primary method\n console.log(\"Using activateFilterEffects function for slide\", currentSlideIndex);\n activateFilterEffects();\n } else if (typeof updateFilterIntensities === 'function') {\n // Only use updateFilterIntensities as fallback\n console.log(\"Using updateFilterIntensities function with force=true\");\n updateFilterIntensities(true, true);\n }\n\n // Explicitly set filters as active\n setFiltersActive(true);\n\n console.log(\"Filter activation completed with critical priority\");\n },\n 'critical'\n );\n\n // Schedule a final render update to ensure all changes are applied\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.FILTER_UPDATE,\n () => {\n console.log(\"Final render update after mouse enter completed\");\n\n // Double-check filter state after render\n console.log(\"Filter state after render:\", {\n filtersActive,\n bgDispFilterEnabled: bgDispFilterRef.current?.enabled,\n cursorDispFilterEnabled: cursorDispFilterRef.current?.enabled,\n currentSlideIndex\n });\n },\n 'critical'\n );\n }, [\n isAppReady,\n showDisplacementEffects,\n updateFilterIntensities,\n activateFilterEffects,\n filtersInitialized,\n filtersActive,\n setFiltersActive,\n currentSlideIndex,\n scheduler\n ]);\n\n /**\n * Handles mouse leave event\n */\n const handleMouseLeave = useCallback(() => {\n if (!isAppReady) return;\n\n if (isDevelopment) {\n console.log('[KineticSlider] Mouse left slider - deactivating all effects');\n }\n\n // Set interaction state immediately\n setIsInteracting(false);\n\n // Explicitly dispatch filter coordination events for both displacement filters\n const event1 = new CustomEvent(FILTER_COORDINATION_EVENT, {\n detail: {\n type: 'background-displacement',\n intensity: 0,\n timestamp: Date.now(),\n source: 'slider-component',\n priority: 'critical'\n }\n });\n window.dispatchEvent(event1);\n\n const event2 = new CustomEvent(FILTER_COORDINATION_EVENT, {\n detail: {\n type: 'cursor-displacement',\n intensity: 0,\n timestamp: Date.now(),\n source: 'slider-component',\n priority: 'critical'\n }\n });\n window.dispatchEvent(event2);\n\n // Force the filters to deactivate with critical priority\n updateFilterIntensities(false, true);\n\n // Schedule a final update after mouse leave to ensure everything is cleaned up\n scheduler.scheduleTypedUpdate(\n 'slider',\n UpdateType.FILTER_UPDATE,\n () => {\n if (isDevelopment) {\n console.log('[KineticSlider] Final render update after mouse leave');\n }\n },\n 'critical'\n );\n }, [isAppReady, setIsInteracting, updateFilterIntensities, scheduler]);\n\n // Handle component cleanup\n useEffect(() => {\n return () => {\n // Reset all filters\n resetAllFilters();\n\n // Clear any pending filter updates\n if (resourceManagerRef.current) {\n resourceManagerRef.current.clearPendingUpdates();\n }\n\n // Clear any scheduled filter transitions\n const transitionTimeouts = Array.from(document.querySelectorAll(`[data-slider-id=\"${sliderRef.current?.id}\"]`))\n .map(el => parseInt(el.getAttribute('data-timeout-id') || '0'))\n .filter(id => id > 0);\n\n transitionTimeouts.forEach(clearTimeout);\n };\n }, [resetAllFilters]);\n\n // Error boundary for filter operations\n useEffect(() => {\n const handleError = (error: Error) => {\n console.error('Filter system error:', error);\n // Attempt recovery by resetting filters\n resetAllFilters();\n // Hide all effects\n hideDisplacementEffects();\n };\n\n window.addEventListener('error', (e) => handleError(e.error));\n return () => window.removeEventListener('error', (e) => handleError(e.error));\n }, [resetAllFilters, hideDisplacementEffects]);\n\n // Add a new useEffect to handle cleanup\n useEffect(() => {\n return () => {\n // Clean up any resources when component unmounts\n if (resourceManagerRef.current) {\n // Use the existing methods instead of the non-existent 'cleanup' method\n resourceManagerRef.current.clearPendingUpdates();\n resourceManagerRef.current.markUnmounting();\n }\n };\n }, []);\n\n // FPS monitoring for filter optimization\n useEffect(() => {\n if (!resourceManagerRef.current) return;\n\n let frameCount = 0;\n let lastTime = performance.now();\n let fps = 60;\n\n // Function to calculate FPS and optimize filters\n const monitorPerformance = () => {\n frameCount++;\n const currentTime = performance.now();\n const elapsed = currentTime - lastTime;\n\n // Update FPS approximately every second\n if (elapsed > 1000) {\n fps = (frameCount * 1000) / elapsed;\n frameCount = 0;\n lastTime = currentTime;\n\n // Auto-optimize filters based on current FPS\n if (resourceManagerRef.current && fps < 55) {\n resourceManagerRef.current.autoOptimizeFilters(fps, 55);\n }\n }\n\n performanceMonitorId = requestAnimationFrame(monitorPerformance);\n };\n\n // Start performance monitoring\n let performanceMonitorId = requestAnimationFrame(monitorPerformance);\n\n // Cleanup\n return () => {\n if (performanceMonitorId) {\n cancelAnimationFrame(performanceMonitorId);\n }\n };\n }, [resourceManagerRef]);\n\n // Render component\n return (\n <div\n className={styles.kineticSlider}\n ref={sliderRef}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n >\n {/* Placeholder while loading */}\n {(!isAppReady || !assetsLoaded) && (\n <div className={styles.placeholder}>\n <div className={styles.loadingIndicator}>\n <div className={styles.spinner}></div>\n <div>Loading slider...</div>\n </div>\n </div>\n )}\n\n {/* Navigation buttons - only render on client and if external nav is not enabled */}\n {!externalNav && isClient && (\n <nav>\n <button onClick={handlePrev} className={styles.prev}>\n Prev\n </button>\n <button onClick={handleNext} className={styles.next}>\n Next\n </button>\n </nav>\n )}\n </div>\n );\n};\n\nexport default KineticSlider;"],"names":["useRef","useState","RenderScheduler","AnimationCoordinator","useEffect","ThrottleStrategy","ResourceManager","FilterFactory","AtlasManager","SlidingWindowManager","useDisplacementEffects","useFilters","preloadKineticSliderAssets","loadKineticSliderDependencies","Application","Container","useCallback","UpdateType","useSlides","useTextContainers","useMouseTracking","useIdleTimer","useNavigation","useExternalNav","useTouchSwipe","useMouseDrag","useTextTilt","useResizeHandler","jsxs","styles","jsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAM,yBAA4B,GAAA,8BAAA;AAKlC,MAAM,gBAA8C,CAAC;AAAA;AAAA,EAEK,SAAS,EAAC;AAAA,EACV,QAAQ,EAAC;AAAA,EACT,cAAiB,GAAA,UAAA;AAAA;AAAA,EAGjB,oCAAuC,GAAA,iCAAA;AAAA,EACvC,gCAAmC,GAAA,6BAAA;AAAA,EACnC,eAAkB,GAAA,IAAA;AAAA,EAClB,gBAAmB,GAAA,IAAA;AAAA,EACnB,oBAAuB,GAAA,IAAA;AAAA,EACvB,cAAiB,GAAA,IAAA;AAAA;AAAA,EAGjB,wBAA2B,GAAA,SAAA;AAAA,EAC3B,uBAAA;AAAA,EACA,wBAAA;AAAA;AAAA,EAGA,cAAiB,GAAA,OAAA;AAAA,EACjB,aAAgB,GAAA,EAAA;AAAA,EAChB,mBAAsB,GAAA,EAAA;AAAA,EACtB,sBAAyB,GAAA,CAAA;AAAA,EACzB,mBAAA;AAAA,EACA,iBAAoB,GAAA,OAAA;AAAA,EACpB,gBAAmB,GAAA,EAAA;AAAA,EACnB,sBAAyB,GAAA,EAAA;AAAA,EACzB,yBAA4B,GAAA,CAAA;AAAA,EAC5B,qBAAwB,GAAA,EAAA;AAAA,EACxB,2BAA8B,GAAA,CAAA;AAAA,EAC9B,sBAAA;AAAA;AAAA,EAGA,yBAA4B,GAAA,IAAA;AAAA,EAC5B,mBAAsB,GAAA,CAAA;AAAA,EACtB,wBAA2B,GAAA,EAAA;AAAA;AAAA,EAG3B,WAAc,GAAA,KAAA;AAAA,EACd,UAAa,GAAA,EAAE,IAAM,EAAA,gBAAA,EAAkB,MAAM,gBAAiB,EAAA;AAAA,EAC9D,UAAa,GAAA,KAAA;AAAA;AAAA,EAGb,YAAA;AAAA,EACA,WAAA;AAAA;AAAA,EAGA,WAAc,GAAA,cAAA;AAAA,EACd,YAAe,GAAA,eAAA;AAAA,EACf,eAAkB,GAAA,KAAA;AAAA,EAClB,cAAiB,GAAA;AACrB,CAAM,KAAA;AAExD,EAAA,OAAA,CAAQ,IAAI,+BAAiC,EAAA;AAAA,IACzC,cAAA;AAAA,IACA,eAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACH,CAAA;AAGD,EAAM,MAAA,SAAA,GAAYA,aAAuB,IAAI,CAAA;AAC7C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIC,eAAS,KAAK,CAAA;AAC9C,EAAA,MAAM,CAAC,iBAAA,EAAmB,oBAAoB,CAAA,GAAIA,eAAS,CAAC,CAAA;AAC5D,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIA,eAAS,KAAK,CAAA;AACxD,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIA,eAAS,KAAK,CAAA;AAClD,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIA,eAAS,KAAK,CAAA;AACtD,EAAM,MAAA,eAAA,GAAkBD,aAAgB,KAAK,CAAA;AAG7C,EAAM,MAAA,SAAA,GAAYE,gCAAgB,WAAY,EAAA;AAG9C,EAAM,MAAA,kBAAA,GAAqBF,aAA+B,IAAI,CAAA;AAG9D,EAAM,MAAA,uBAAA,GAA0BA,aAAoC,IAAI,CAAA;AAGxE,EAAM,MAAA,oBAAA,GAAuBG,0CAAqB,WAAY,EAAA;AAG9D,EAAAC,eAAA,CAAU,MAAM;AAEZ,IAAI,IAAA,OAAO,WAAW,WAAa,EAAA;AAEnC,IAAA,SAAA,CAAU,mBAAoB,CAAA;AAAA,MAC1B,SAAW,EAAA,EAAA;AAAA,MACX,UAAUC,+BAAiB,CAAA;AAAA,KAC9B,CAAA;AAED,IAAA,OAAO,MAAM;AAET,MAAA,SAAA,CAAU,UAAW,EAAA;AAAA,KACzB;AAAA,GACJ,EAAG,CAAC,SAAS,CAAC,CAAA;AAGd,EAAM,MAAA,eAAA,GAAkBL,aAA4B,IAAI,CAAA;AAGxD,EAAM,MAAA,MAAA,GAASA,aAA2B,IAAI,CAAA;AAC9C,EAAM,MAAA,SAAA,GAAYA,YAAiB,CAAA,EAAE,CAAA;AACrC,EAAM,MAAA,iBAAA,GAAoBA,YAAoB,CAAA,EAAE,CAAA;AAChD,EAAM,MAAA,+BAAA,GAAkCA,aAAsB,IAAI,CAAA;AAClE,EAAM,MAAA,2BAAA,GAA8BA,aAAsB,IAAI,CAAA;AAC9D,EAAM,MAAA,eAAA,GAAkBA,aAAkC,IAAI,CAAA;AAC9D,EAAM,MAAA,mBAAA,GAAsBA,aAAkC,IAAI,CAAA;AAClE,EAAM,MAAA,eAAA,GAAkBA,aAAe,CAAC,CAAA;AAGxC,EAAAI,eAAA,CAAU,MAAM;AACZ,IAAA,WAAA,CAAY,IAAI,CAAA;AAAA,GACpB,EAAG,EAAE,CAAA;AAGL,EAAAA,eAAA,CAAU,MAAM;AACZ,IAAM,MAAA,WAAA,GAAc,CAAkB,eAAA,EAAA,IAAA,CAAK,MAAO,EAAA,CAAE,QAAS,CAAA,EAAE,CAAE,CAAA,SAAA,CAAU,CAAG,EAAA,CAAC,CAAC,CAAA,CAAA;AAGhF,IAAmB,kBAAA,CAAA,OAAA,GAAU,IAAIE,uBAAA,CAAgB,WAAa,EAAA;AAAA,MAC1D,aAAe,EAAA,IAAA;AAAA,MACf,mBAAqB,EAAA,IAAA;AAAA,MACrB,QAAA,EAAiE;AAAA,KACpE,CAAA;AAGD,IAAAC,2BAAA,CAAc,UAAW,CAAA;AAAA,MACrB,mBAAqB,EAAA,IAAA;AAAA,MACrB,WAAa,EAAA,KAAA;AAAA,MACb,cAAgB,EAAA;AAAA,QACZ,eAAiB,EAAA,IAAA;AAAA;AAAA,QACjB,gBAAkB,EAAA,EAAA;AAAA,QAClB,iBAAmB,EAAA,IAAA;AAAA,QACnB,gBAAkB,EAAA,IAAA;AAAA,QAClB,UAAY,EAAA;AAAA;AAChB,KACH,CAAA;AAGD,IAAgB,eAAA,CAAA,OAAA,GAAU,IAAIC,yBAAa,CAAA;AAAA,MACvC,KAAO,EAAA,IAAA;AAAA,MACP,WAAa,EAAA,IAAA;AAAA,MACb,kBAAoB,EAAA,IAAA;AAAA,MACpB,QAAU,EAAA;AAAA,KACd,EAAG,mBAAmB,OAAO,CAAA;AAG7B,IAAA,IAAI,mBAAmB,OAAS,EAAA;AAC5B,MAAqB,oBAAA,CAAA,kBAAA,CAAmB,mBAAmB,OAAO,CAAA;AAAA;AAItE,IAAwB,uBAAA,CAAA,OAAA,GAAU,IAAIC,4BAAqB,CAAA;AAAA,MACvD,aAAa,MAAO,CAAA,MAAA;AAAA,MACpB,YAAc,EAAA,CAAA;AAAA,MACd,UAAY,EAAA,CAAA;AAAA;AAAA,MACZ,KAAO,EAAA;AAAA,KACX,EAAG,mBAAmB,OAAO,CAAA;AAE7B,IAAA,OAAO,MAAM;AAET,MAAA,IAAI,mBAAmB,OAAS,EAAA;AAC5B,QAAA,OAAA,CAAQ,IAAI,gDAAgD,CAAA;AAC5D,QAAA,kBAAA,CAAmB,QAAQ,cAAe,EAAA;AAE1C,QAAA,kBAAA,CAAmB,QAAQ,OAAQ,EAAA;AACnC,QAAA,kBAAA,CAAmB,OAAU,GAAA,IAAA;AAAA;AAIjC,MAAA,IAAI,gBAAgB,OAAS,EAAA;AACzB,QAAA,eAAA,CAAgB,QAAQ,OAAQ,EAAA;AAChC,QAAA,eAAA,CAAgB,OAAU,GAAA,IAAA;AAAA;AAI9B,MAAA,uBAAA,CAAwB,OAAU,GAAA,IAAA;AAAA,KACtC;AAAA,GACD,EAAA,CAAC,MAAO,CAAA,MAAA,EAAQ,oBAAoB,CAAC,CAAA;AAGxC,EAAA,MAAM,QAAW,GAAA;AAAA,IACb,GAAK,EAAA,MAAA;AAAA,IACL,MAAQ,EAAA,SAAA;AAAA,IACR,cAAgB,EAAA,iBAAA;AAAA,IAChB,4BAA8B,EAAA,+BAAA;AAAA,IAC9B,wBAA0B,EAAA,2BAAA;AAAA,IAC1B,YAAc,EAAA,eAAA;AAAA,IACd,gBAAkB,EAAA,mBAAA;AAAA,IAClB,YAAc,EAAA;AAAA,GAClB;AAGA,EAAA,MAAM,SAAY,GAAA;AAAA,IACd,MAAA;AAAA,IACA,KAAA;AAAA,IACA,cAAA;AAAA,IACA,oCAAA;AAAA,IACA,gCAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,oBAAA;AAAA,IACA,cAAA;AAAA,IACA,wBAAA;AAAA,IACA,uBAAA;AAAA,IACA,wBAAA;AAAA,IACA,cAAA;AAAA,IACA,aAAA;AAAA,IACA,mBAAA;AAAA,IACA,sBAAA;AAAA,IACA,mBAAA;AAAA,IACA,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA,sBAAA;AAAA,IACA,yBAAA;AAAA,IACA,qBAAA;AAAA,IACA,2BAAA;AAAA,IACA,sBAAA;AAAA,IACA,yBAAA;AAAA,IACA,mBAAA;AAAA,IACA,wBAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,GACJ;AAGA,EAAA,MAAM,UAAa,GAAA;AAAA,IACf,SAAA;AAAA,IACA,IAAM,EAAA,QAAA;AAAA,IACN,KAAO,EAAA,SAAA;AAAA,IACP,iBAAiB,kBAAmB,CAAA,OAAA;AAAA,IACpC,cAAc,eAAgB,CAAA,OAAA;AAAA,IAC9B,sBAAsB,uBAAwB,CAAA;AAAA,GAClD;AAGA,EAAA,MAAM,EAAE,uBAAA,EAAyB,uBAAwB,EAAA,GAAIC,6CAAuB,CAAA;AAAA,IAChF,SAAA;AAAA,IACA,eAAA;AAAA,IACA,mBAAA;AAAA,IACA,+BAAA;AAAA,IACA,2BAAA;AAAA,IACA,MAAA;AAAA,IACA,oCAAA;AAAA,IACA,gCAAA;AAAA,IACA,eAAA;AAAA,IACA,oBAAA;AAAA,IACA,wBAAA;AAAA,IACA,uBAAA;AAAA,IACA,wBAAA;AAAA,IACA,iBAAiB,kBAAmB,CAAA,OAAA;AAAA,IACpC,YAAA,EAAc,gBAAgB,OAAW,IAAA,MAAA;AAAA,IACzC,YAAA;AAAA,IACA;AAAA,GACH,CAAA;AAGD,EAAM,MAAA;AAAA,IACF,uBAAA;AAAA,IACA,eAAA;AAAA,IACA,qBAAA;AAAA,IACA,aAAe,EAAA,kBAAA;AAAA,IACf,QAAU,