@frybynite/image-cloud 0.2.6 → 0.2.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"image-cloud-auto-init.js","sources":["../src/config/defaults.ts","../src/engines/AnimationEngine.ts","../src/engines/PathAnimator.ts","../src/engines/EntryAnimationEngine.ts","../src/generators/RandomPlacementGenerator.ts","../src/generators/RadialPlacementGenerator.ts","../src/generators/GridPlacementGenerator.ts","../src/generators/SpiralPlacementGenerator.ts","../src/generators/ClusterPlacementGenerator.ts","../src/generators/WavePlacementGenerator.ts","../src/engines/LayoutEngine.ts","../src/config/types.ts","../src/utils/styleUtils.ts","../src/engines/ZoomEngine.ts","../src/engines/SwipeEngine.ts","../src/loaders/GoogleDriveLoader.ts","../src/loaders/StaticImageLoader.ts","../src/loaders/CompositeLoader.ts","../src/loaders/ImageFilter.ts","../src/styles/functionalStyles.ts","../src/ImageCloud.ts","../src/image-cloud-auto-init.ts"],"sourcesContent":["/**\n * Default configuration for Image Gallery\n * Centralized settings for animation, layout, and API configuration\n */\n\nimport type { ImageCloudConfig, DeepPartial, ImageStylingConfig, ImageStyleState, ShadowPreset, WaveAlgorithmConfig, BouncePathConfig, ElasticPathConfig, WavePathConfig, BouncePreset, ElasticPreset, WavePathPreset, EntryPathConfig, EntryRotationConfig, EntryScaleConfig, ImageConfig, ImageSizingConfig, ImageRotationConfig, ImageVarianceConfig, ResponsiveBreakpoints } from './types';\n\n/**\n * Shadow presets for image styling\n */\nexport const SHADOW_PRESETS: Record<ShadowPreset, string> = Object.freeze({\n 'none': 'none',\n 'sm': '0 2px 4px rgba(0,0,0,0.1)',\n 'md': '0 4px 16px rgba(0,0,0,0.4)',\n 'lg': '0 8px 32px rgba(0,0,0,0.5)',\n 'glow': '0 0 30px rgba(255,255,255,0.6)'\n});\n\n/**\n * Bounce path presets - overshoot and settle animations\n */\nexport const BOUNCE_PRESETS: Record<BouncePreset, BouncePathConfig> = Object.freeze({\n energetic: Object.freeze({ overshoot: 0.25, bounces: 2, decayRatio: 0.5 }),\n playful: Object.freeze({ overshoot: 0.15, bounces: 1, decayRatio: 0.5 }),\n subtle: Object.freeze({ overshoot: 0.08, bounces: 1, decayRatio: 0.5 })\n});\n\n/**\n * Elastic path presets - spring-like oscillation animations\n */\nexport const ELASTIC_PRESETS: Record<ElasticPreset, ElasticPathConfig> = Object.freeze({\n gentle: Object.freeze({ stiffness: 150, damping: 30, mass: 1, oscillations: 2 }),\n bouncy: Object.freeze({ stiffness: 300, damping: 15, mass: 1, oscillations: 4 }),\n wobbly: Object.freeze({ stiffness: 180, damping: 12, mass: 1.5, oscillations: 5 }),\n snappy: Object.freeze({ stiffness: 400, damping: 25, mass: 0.8, oscillations: 2 })\n});\n\n/**\n * Wave path presets - sinusoidal path animations\n */\nexport const WAVE_PATH_PRESETS: Record<WavePathPreset, WavePathConfig> = Object.freeze({\n gentle: Object.freeze({ amplitude: 30, frequency: 1.5, decay: true, decayRate: 0.9, phase: 0 }),\n playful: Object.freeze({ amplitude: 50, frequency: 2.5, decay: true, decayRate: 0.7, phase: 0 }),\n serpentine: Object.freeze({ amplitude: 60, frequency: 3, decay: false, decayRate: 1, phase: 0 }),\n flutter: Object.freeze({ amplitude: 20, frequency: 4, decay: true, decayRate: 0.5, phase: 0 })\n});\n\n/**\n * Default path configuration (linear - no special path effects)\n */\nexport const DEFAULT_PATH_CONFIG: EntryPathConfig = Object.freeze({\n type: 'linear' as const\n});\n\n/**\n * Default entry rotation configuration (no rotation animation)\n */\nexport const DEFAULT_ENTRY_ROTATION: EntryRotationConfig = Object.freeze({\n mode: 'none' as const\n});\n\n/**\n * Default entry scale configuration (no scale animation)\n */\nexport const DEFAULT_ENTRY_SCALE: EntryScaleConfig = Object.freeze({\n mode: 'none' as const\n});\n\n/**\n * Entry scale presets for common effects\n */\nexport const ENTRY_SCALE_PRESETS = Object.freeze({\n grow: Object.freeze({ mode: 'grow' as const, startScale: 0.3 }),\n shrink: Object.freeze({ mode: 'shrink' as const, startScale: 1.5 }),\n pop: Object.freeze({ mode: 'pop' as const, pop: Object.freeze({ overshoot: 1.2, bounces: 1 }) }),\n randomSubtle: Object.freeze({ mode: 'random' as const, range: Object.freeze({ min: 0.7, max: 1.0 }) }),\n randomWide: Object.freeze({ mode: 'random' as const, range: Object.freeze({ min: 0.3, max: 1.5 }) })\n});\n\n/**\n * Default image styling configuration\n */\nexport const DEFAULT_STYLING: ImageStylingConfig = Object.freeze({\n default: Object.freeze({\n border: Object.freeze({\n width: 0,\n color: '#000000',\n radius: 0,\n style: 'solid' as const\n }),\n shadow: 'none' as ShadowPreset,\n filter: Object.freeze({}),\n opacity: 1,\n cursor: 'pointer',\n outline: Object.freeze({\n width: 0,\n color: '#000000',\n style: 'solid' as const,\n offset: 0\n })\n }),\n hover: Object.freeze({\n shadow: 'none' as ShadowPreset\n }),\n focused: Object.freeze({\n shadow: 'none' as ShadowPreset\n })\n});\n\n/**\n * Default wave layout configuration\n */\nexport const DEFAULT_WAVE_CONFIG: WaveAlgorithmConfig = Object.freeze({\n rows: 1,\n amplitude: 100,\n frequency: 2,\n phaseShift: 0,\n synchronization: 'offset' as const\n // Note: Image rotation along wave is now controlled via image.rotation.mode = 'tangent'\n});\n\n/**\n * Default responsive breakpoints for layout\n */\nexport const DEFAULT_RESPONSIVE_BREAKPOINTS: ResponsiveBreakpoints = Object.freeze({\n mobile: Object.freeze({ maxWidth: 767 }),\n tablet: Object.freeze({ maxWidth: 1199 })\n});\n\n/**\n * Default image sizing configuration\n */\nexport const DEFAULT_IMAGE_SIZING: ImageSizingConfig = Object.freeze({\n mode: 'adaptive' as const, // Default to adaptive sizing\n minSize: 50, // Adaptive mode minimum\n maxSize: 400, // Adaptive mode maximum\n variance: Object.freeze({\n min: 1.0, // No variance by default\n max: 1.0\n })\n});\n\n/**\n * Default image rotation configuration\n */\nexport const DEFAULT_IMAGE_ROTATION: ImageRotationConfig = Object.freeze({\n mode: 'none' as const,\n range: Object.freeze({\n min: -15,\n max: 15\n })\n});\n\n/**\n * Default image configuration\n */\nexport const DEFAULT_IMAGE_CONFIG: ImageConfig = Object.freeze({\n sizing: DEFAULT_IMAGE_SIZING,\n rotation: DEFAULT_IMAGE_ROTATION\n});\n\n/**\n * Default configuration object\n * Frozen to prevent accidental modifications\n */\nexport const DEFAULT_CONFIG: ImageCloudConfig = Object.freeze({\n // Unified loader configuration\n loader: Object.freeze({\n type: 'googleDrive' as const,\n googleDrive: Object.freeze({\n apiKey: '', // Must be provided by user\n sources: [], // Must be provided by user\n apiEndpoint: 'https://www.googleapis.com/drive/v3/files',\n allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'],\n debugLogging: false\n }),\n static: Object.freeze({\n sources: [], // Must be provided by user\n validateUrls: true,\n validationTimeout: 5000,\n validationMethod: 'head' as const,\n failOnAllMissing: true,\n allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'],\n debugLogging: false\n })\n }),\n\n // Image sizing and rotation configuration\n image: DEFAULT_IMAGE_CONFIG,\n\n // Pattern-based layout configuration\n layout: Object.freeze({\n algorithm: 'radial' as const,\n scaleDecay: 0, // No decay by default (0-1 for radial/spiral)\n responsive: DEFAULT_RESPONSIVE_BREAKPOINTS,\n targetCoverage: 0.6, // Target 60% of container area\n densityFactor: 1.0, // Default density\n spacing: Object.freeze({\n padding: 50, // padding from viewport edges\n minGap: 20 // minimum spacing between images\n }),\n debugRadials: false,\n debugCenters: false\n }),\n\n // Pattern-based animation configuration\n animation: Object.freeze({\n duration: 600, // milliseconds\n easing: Object.freeze({\n default: 'cubic-bezier(0.4, 0.0, 0.2, 1)', // smooth easing\n bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', // bounce effect\n focus: 'cubic-bezier(0.4, 0.0, 0.2, 1)' // focus/zoom easing\n }),\n queue: Object.freeze({\n enabled: true, // When false, all images display simultaneously\n interval: 150, // ms between processing queue items (when enabled)\n maxConcurrent: undefined // STUB: Not implemented yet\n }),\n performance: Object.freeze({\n useGPU: undefined, // STUB: Not implemented yet\n reduceMotion: undefined // STUB: Not implemented yet\n }),\n entry: Object.freeze({\n start: Object.freeze({\n position: 'nearest-edge' as const, // Default to nearest edge (current behavior)\n offset: 100, // pixels beyond edge\n circular: Object.freeze({\n radius: '120%', // 120% of container diagonal\n distribution: 'even' as const\n })\n }),\n timing: Object.freeze({\n duration: 600, // ms\n stagger: 150 // ms between images\n }),\n easing: 'cubic-bezier(0.25, 1, 0.5, 1)', // smooth deceleration\n path: DEFAULT_PATH_CONFIG,\n rotation: DEFAULT_ENTRY_ROTATION,\n scale: DEFAULT_ENTRY_SCALE\n })\n }),\n\n // Pattern-based interaction configuration\n interaction: Object.freeze({\n focus: Object.freeze({\n scalePercent: 0.8, // 80% of container height\n zIndex: 1000,\n animationDuration: undefined // Use default animation duration\n }),\n navigation: Object.freeze({\n keyboard: undefined, // STUB: Not implemented yet\n swipe: undefined, // STUB: Not implemented yet\n mouseWheel: undefined // STUB: Not implemented yet\n }),\n gestures: Object.freeze({\n pinchToZoom: undefined, // STUB: Not implemented yet\n doubleTapToFocus: undefined // STUB: Not implemented yet\n })\n }),\n\n // Pattern-based rendering configuration\n rendering: Object.freeze({\n responsive: Object.freeze({\n breakpoints: Object.freeze({\n mobile: 768,\n tablet: undefined, // STUB: Not implemented yet\n desktop: undefined // STUB: Not implemented yet\n }),\n mobileDetection: () => {\n if (typeof window === 'undefined') return false;\n return window.innerWidth <= 768;\n }\n }),\n ui: Object.freeze({\n showLoadingSpinner: false,\n showImageCounter: undefined, // STUB: Not implemented yet\n showThumbnails: undefined, // STUB: Not implemented yet\n theme: undefined // STUB: Not implemented yet\n }),\n performance: Object.freeze({\n lazyLoad: undefined, // STUB: Not implemented yet\n preloadCount: undefined, // STUB: Not implemented yet\n imageQuality: undefined // STUB: Not implemented yet\n })\n }),\n\n // Image styling\n styling: DEFAULT_STYLING,\n\n // Debug mode\n debug: false\n});\n\n/**\n * Deep merge a single style state (border, filter, outline, etc.)\n */\nfunction deepMergeStyleState(\n base: ImageStyleState | undefined,\n override: Partial<ImageStyleState> | undefined\n): ImageStyleState {\n if (!base) return override as ImageStyleState || {};\n if (!override) return { ...base };\n\n const merged: ImageStyleState = { ...base };\n\n // Merge border\n if (override.border !== undefined) {\n merged.border = { ...base.border, ...override.border };\n }\n\n // Merge per-side borders\n if (override.borderTop !== undefined) {\n merged.borderTop = { ...base.borderTop, ...override.borderTop };\n }\n if (override.borderRight !== undefined) {\n merged.borderRight = { ...base.borderRight, ...override.borderRight };\n }\n if (override.borderBottom !== undefined) {\n merged.borderBottom = { ...base.borderBottom, ...override.borderBottom };\n }\n if (override.borderLeft !== undefined) {\n merged.borderLeft = { ...base.borderLeft, ...override.borderLeft };\n }\n\n // Merge filter\n if (override.filter !== undefined) {\n merged.filter = { ...base.filter, ...override.filter };\n }\n\n // Merge outline\n if (override.outline !== undefined) {\n merged.outline = { ...base.outline, ...override.outline };\n }\n\n // Override simple properties\n if (override.shadow !== undefined) merged.shadow = override.shadow;\n if (override.opacity !== undefined) merged.opacity = override.opacity;\n if (override.cursor !== undefined) merged.cursor = override.cursor;\n if (override.className !== undefined) merged.className = override.className;\n if (override.objectFit !== undefined) merged.objectFit = override.objectFit;\n if (override.aspectRatio !== undefined) merged.aspectRatio = override.aspectRatio;\n\n // Override per-corner border radius\n if (override.borderRadiusTopLeft !== undefined) merged.borderRadiusTopLeft = override.borderRadiusTopLeft;\n if (override.borderRadiusTopRight !== undefined) merged.borderRadiusTopRight = override.borderRadiusTopRight;\n if (override.borderRadiusBottomRight !== undefined) merged.borderRadiusBottomRight = override.borderRadiusBottomRight;\n if (override.borderRadiusBottomLeft !== undefined) merged.borderRadiusBottomLeft = override.borderRadiusBottomLeft;\n\n return merged;\n}\n\n/**\n * Deep merge styling config with proper state inheritance\n * - hover inherits from default, then applies overrides\n * - focused inherits from default, then applies overrides\n */\nfunction deepMergeStyling(\n defaults: ImageStylingConfig,\n userStyling: Partial<ImageStylingConfig> | undefined\n): ImageStylingConfig {\n if (!userStyling) return { ...defaults };\n\n // First, merge the default state\n const mergedDefault = deepMergeStyleState(defaults.default, userStyling.default);\n\n // Hover inherits from merged default, then user hover overrides\n const mergedHover = deepMergeStyleState(\n deepMergeStyleState(mergedDefault, defaults.hover),\n userStyling.hover\n );\n\n // Focused inherits from merged default, then user focused overrides\n const mergedFocused = deepMergeStyleState(\n deepMergeStyleState(mergedDefault, defaults.focused),\n userStyling.focused\n );\n\n return {\n default: mergedDefault,\n hover: mergedHover,\n focused: mergedFocused\n };\n}\n\n/**\n * Deep merge utility for config objects\n * Merges user config with default config\n */\n/**\n * Deep merge image config with validation\n */\nfunction deepMergeImageConfig(\n defaults: ImageConfig,\n userImage: Partial<ImageConfig> | undefined\n): ImageConfig {\n if (!userImage) return { ...defaults };\n\n const merged: ImageConfig = { ...defaults };\n\n // Deep merge sizing config\n if (userImage.sizing !== undefined) {\n merged.sizing = {\n ...defaults.sizing,\n ...userImage.sizing\n };\n\n // Deep merge variance with validation (min: 0.25-1, max: 1-1.75)\n if (userImage.sizing.variance) {\n const userVariance = userImage.sizing.variance as ImageVarianceConfig;\n const validMin = userVariance.min !== undefined && userVariance.min >= 0.25 && userVariance.min <= 1\n ? userVariance.min\n : defaults.sizing?.variance?.min ?? 1.0;\n const validMax = userVariance.max !== undefined && userVariance.max >= 1 && userVariance.max <= 1.75\n ? userVariance.max\n : defaults.sizing?.variance?.max ?? 1.0;\n merged.sizing!.variance = { min: validMin, max: validMax };\n }\n }\n\n // Deep merge rotation config\n if (userImage.rotation !== undefined) {\n merged.rotation = {\n ...defaults.rotation,\n ...userImage.rotation\n };\n\n // Deep merge rotation range with validation\n if (userImage.rotation.range) {\n const userRange = userImage.rotation.range;\n const validMin = userRange.min !== undefined && userRange.min >= -180 && userRange.min <= 0\n ? userRange.min\n : defaults.rotation?.range?.min ?? -15;\n const validMax = userRange.max !== undefined && userRange.max >= 0 && userRange.max <= 180\n ? userRange.max\n : defaults.rotation?.range?.max ?? 15;\n merged.rotation!.range = { min: validMin, max: validMax };\n }\n }\n\n return merged;\n}\n\n/**\n * Convert legacy layout.rotation config to new image.rotation format\n * This provides backward compatibility with the old API\n */\nfunction convertLegacyRotationConfig(userConfig: DeepPartial<ImageCloudConfig>): Partial<ImageConfig> | undefined {\n const legacyRotation = (userConfig.layout as any)?.rotation;\n if (!legacyRotation) return undefined;\n\n // Legacy format: { enabled: boolean, range: { min, max } }\n if ('enabled' in legacyRotation) {\n return {\n rotation: {\n mode: legacyRotation.enabled ? 'random' : 'none',\n range: legacyRotation.range\n }\n };\n }\n\n return undefined;\n}\n\n/**\n * Convert legacy layout.sizing.variance config to new image.sizing.variance format\n */\nfunction convertLegacyVarianceConfig(userConfig: DeepPartial<ImageCloudConfig>): Partial<ImageConfig> | undefined {\n const legacyVariance = (userConfig.layout as any)?.sizing?.variance;\n if (!legacyVariance) return undefined;\n\n return {\n sizing: {\n mode: 'adaptive', // Legacy variance config implies adaptive mode\n variance: legacyVariance\n }\n };\n}\n\nexport function mergeConfig(\n userConfig: DeepPartial<ImageCloudConfig> = {}\n): ImageCloudConfig {\n // Convert legacy configs to new format\n const legacyRotation = convertLegacyRotationConfig(userConfig);\n const legacyVariance = convertLegacyVarianceConfig(userConfig);\n\n // Combine user image config with converted legacy configs\n // User's explicit image config takes precedence over legacy conversions\n let combinedImageConfig: Partial<ImageConfig> | undefined = userConfig.image as Partial<ImageConfig> | undefined;\n if (legacyRotation || legacyVariance) {\n combinedImageConfig = {\n ...(legacyVariance || {}),\n ...(legacyRotation || {}),\n ...combinedImageConfig\n };\n // Deep merge the rotation config if both exist\n if (combinedImageConfig.rotation && legacyRotation?.rotation && userConfig.image?.rotation) {\n combinedImageConfig.rotation = {\n ...legacyRotation.rotation,\n ...(userConfig.image as any).rotation\n };\n }\n }\n\n const merged: ImageCloudConfig = {\n loader: { ...DEFAULT_CONFIG.loader },\n image: deepMergeImageConfig(DEFAULT_IMAGE_CONFIG, combinedImageConfig),\n layout: { ...DEFAULT_CONFIG.layout },\n animation: { ...DEFAULT_CONFIG.animation },\n interaction: { ...DEFAULT_CONFIG.interaction },\n rendering: { ...DEFAULT_CONFIG.rendering },\n styling: deepMergeStyling(DEFAULT_STYLING, userConfig.styling as Partial<ImageStylingConfig> | undefined),\n debug: DEFAULT_CONFIG.debug\n };\n\n // Deep merge loader config\n if (userConfig.loader) {\n merged.loader = {\n ...DEFAULT_CONFIG.loader,\n ...userConfig.loader\n } as any;\n\n // Deep merge googleDrive config\n if (userConfig.loader.googleDrive) {\n merged.loader.googleDrive = {\n ...DEFAULT_CONFIG.loader.googleDrive!,\n ...userConfig.loader.googleDrive,\n sources: userConfig.loader.googleDrive.sources || DEFAULT_CONFIG.loader.googleDrive!.sources,\n allowedExtensions: userConfig.loader.googleDrive.allowedExtensions ||\n DEFAULT_CONFIG.loader.googleDrive!.allowedExtensions\n };\n }\n\n // Deep merge static config\n if (userConfig.loader.static) {\n merged.loader.static = {\n ...DEFAULT_CONFIG.loader.static!,\n ...userConfig.loader.static,\n sources: userConfig.loader.static.sources || DEFAULT_CONFIG.loader.static!.sources,\n allowedExtensions: userConfig.loader.static.allowedExtensions ||\n DEFAULT_CONFIG.loader.static!.allowedExtensions\n };\n }\n }\n\n // Deep merge layout config\n if (userConfig.layout) {\n merged.layout = {\n ...DEFAULT_CONFIG.layout,\n ...userConfig.layout\n } as any;\n\n // Deep merge responsive breakpoints config\n if (userConfig.layout.responsive) {\n merged.layout.responsive = {\n ...DEFAULT_CONFIG.layout.responsive!,\n mobile: userConfig.layout.responsive.mobile\n ? { ...DEFAULT_CONFIG.layout.responsive!.mobile, ...userConfig.layout.responsive.mobile }\n : DEFAULT_CONFIG.layout.responsive!.mobile,\n tablet: userConfig.layout.responsive.tablet\n ? { ...DEFAULT_CONFIG.layout.responsive!.tablet, ...userConfig.layout.responsive.tablet }\n : DEFAULT_CONFIG.layout.responsive!.tablet\n };\n }\n\n // Deep merge spacing config\n if (userConfig.layout.spacing) {\n merged.layout.spacing = {\n ...DEFAULT_CONFIG.layout.spacing,\n ...userConfig.layout.spacing\n };\n }\n }\n\n // Deep merge animation config\n if (userConfig.animation) {\n merged.animation = {\n ...DEFAULT_CONFIG.animation,\n ...userConfig.animation\n } as any;\n\n // Deep merge easing config\n if (userConfig.animation.easing) {\n merged.animation.easing = {\n ...DEFAULT_CONFIG.animation.easing,\n ...userConfig.animation.easing\n };\n }\n\n // Deep merge queue config\n if (userConfig.animation.queue) {\n merged.animation.queue = {\n ...DEFAULT_CONFIG.animation.queue,\n ...userConfig.animation.queue\n };\n }\n\n // Deep merge performance config\n if (userConfig.animation.performance) {\n merged.animation.performance = {\n ...DEFAULT_CONFIG.animation.performance,\n ...userConfig.animation.performance\n };\n }\n\n // Deep merge entry animation config\n if (userConfig.animation.entry) {\n merged.animation.entry = {\n ...DEFAULT_CONFIG.animation.entry!,\n ...userConfig.animation.entry,\n start: userConfig.animation.entry.start\n ? {\n ...DEFAULT_CONFIG.animation.entry!.start,\n ...userConfig.animation.entry.start,\n circular: userConfig.animation.entry.start.circular\n ? { ...DEFAULT_CONFIG.animation.entry!.start.circular, ...userConfig.animation.entry.start.circular }\n : DEFAULT_CONFIG.animation.entry!.start.circular\n }\n : DEFAULT_CONFIG.animation.entry!.start,\n timing: userConfig.animation.entry.timing\n ? { ...DEFAULT_CONFIG.animation.entry!.timing, ...userConfig.animation.entry.timing }\n : DEFAULT_CONFIG.animation.entry!.timing,\n path: userConfig.animation.entry.path\n ? { ...DEFAULT_PATH_CONFIG, ...userConfig.animation.entry.path }\n : DEFAULT_CONFIG.animation.entry!.path,\n rotation: userConfig.animation.entry.rotation\n ? { ...DEFAULT_ENTRY_ROTATION, ...userConfig.animation.entry.rotation }\n : DEFAULT_CONFIG.animation.entry!.rotation,\n scale: userConfig.animation.entry.scale\n ? { ...DEFAULT_ENTRY_SCALE, ...userConfig.animation.entry.scale }\n : DEFAULT_CONFIG.animation.entry!.scale\n };\n }\n }\n\n // Deep merge interaction config\n if (userConfig.interaction) {\n merged.interaction = {\n ...DEFAULT_CONFIG.interaction,\n ...userConfig.interaction\n } as any;\n\n // Deep merge focus config\n if (userConfig.interaction.focus) {\n merged.interaction.focus = {\n ...DEFAULT_CONFIG.interaction.focus,\n ...userConfig.interaction.focus\n };\n }\n\n // Deep merge navigation config\n if (userConfig.interaction.navigation) {\n merged.interaction.navigation = {\n ...DEFAULT_CONFIG.interaction.navigation,\n ...userConfig.interaction.navigation\n };\n }\n\n // Deep merge gestures config\n if (userConfig.interaction.gestures) {\n merged.interaction.gestures = {\n ...DEFAULT_CONFIG.interaction.gestures,\n ...userConfig.interaction.gestures\n };\n }\n }\n\n // Deep merge rendering config\n if (userConfig.rendering) {\n merged.rendering = {\n ...DEFAULT_CONFIG.rendering,\n ...userConfig.rendering\n } as any;\n\n // Deep merge responsive config\n if (userConfig.rendering.responsive) {\n merged.rendering.responsive = {\n ...DEFAULT_CONFIG.rendering.responsive,\n ...userConfig.rendering.responsive,\n breakpoints: userConfig.rendering.responsive.breakpoints\n ? { ...DEFAULT_CONFIG.rendering.responsive.breakpoints, ...userConfig.rendering.responsive.breakpoints }\n : DEFAULT_CONFIG.rendering.responsive.breakpoints,\n mobileDetection: userConfig.rendering.responsive.mobileDetection\n ? (userConfig.rendering.responsive.mobileDetection as () => boolean)\n : DEFAULT_CONFIG.rendering.responsive.mobileDetection\n };\n }\n\n // Deep merge ui config\n if (userConfig.rendering.ui) {\n merged.rendering.ui = {\n ...DEFAULT_CONFIG.rendering.ui,\n ...userConfig.rendering.ui\n };\n }\n\n // Deep merge performance config\n if (userConfig.rendering.performance) {\n merged.rendering.performance = {\n ...DEFAULT_CONFIG.rendering.performance,\n ...userConfig.rendering.performance\n };\n }\n }\n\n // Merge debug flag\n if (userConfig.debug !== undefined) {\n merged.debug = userConfig.debug;\n }\n\n return merged;\n}\n\n/**\n * Resolve bounce path config from preset and overrides\n */\nexport function resolveBounceConfig(\n preset?: BouncePreset,\n overrides?: Partial<BouncePathConfig>\n): BouncePathConfig {\n const base = preset ? BOUNCE_PRESETS[preset] : BOUNCE_PRESETS.playful;\n return { ...base, ...overrides };\n}\n\n/**\n * Resolve elastic path config from preset and overrides\n */\nexport function resolveElasticConfig(\n preset?: ElasticPreset,\n overrides?: Partial<ElasticPathConfig>\n): ElasticPathConfig {\n const base = preset ? ELASTIC_PRESETS[preset] : ELASTIC_PRESETS.gentle;\n return { ...base, ...overrides };\n}\n\n/**\n * Resolve wave path config from preset and overrides\n */\nexport function resolveWavePathConfig(\n preset?: WavePathPreset,\n overrides?: Partial<WavePathConfig>\n): WavePathConfig {\n const base = preset ? WAVE_PATH_PRESETS[preset] : WAVE_PATH_PRESETS.gentle;\n return { ...base, ...overrides };\n}\n\n/**\n * Debug logger\n */\nexport function debugLog(config: ImageCloudConfig, ...args: unknown[]): void {\n if (config.debug && typeof console !== 'undefined') {\n console.log(...args);\n }\n}","/**\n * AnimationEngine.ts\n * Handles smooth animations with easing for the image cloud\n *\n * Public API:\n * - animateTransform(element, properties, duration, easing)\n * - animateTransformCancellable(element, from, to, duration, easing) - Web Animations API\n * - cancelAnimation(handle, commitStyle) - Cancel and optionally keep current position\n * - getCurrentTransform(element) - Get current transform state mid-animation\n * - hasActiveAnimation(element) - Check if element has active animation\n * - resetTransform(element, originalState)\n * - clearTransition(element)\n * - wait(ms)\n */\n\nimport type { AnimationConfig, TransformParams, ImageLayout, AnimationHandle, AnimationSnapshot } from '../config/types';\n\nexport class AnimationEngine {\n private config: AnimationConfig;\n private activeAnimations: Map<HTMLElement, AnimationHandle> = new Map();\n private animationIdCounter = 0;\n\n constructor(config: AnimationConfig) {\n this.config = config;\n }\n\n /**\n * Build transform string from transform params\n * Always starts with centering transform to match image positioning system\n */\n private buildTransformString(params: TransformParams): string {\n const transforms: string[] = ['translate(-50%, -50%)'];\n\n if (params.x !== undefined || params.y !== undefined) {\n const x = params.x ?? 0;\n const y = params.y ?? 0;\n transforms.push(`translate(${x}px, ${y}px)`);\n }\n\n if (params.rotation !== undefined) {\n transforms.push(`rotate(${params.rotation}deg)`);\n }\n\n if (params.scale !== undefined) {\n transforms.push(`scale(${params.scale})`);\n }\n\n return transforms.join(' ');\n }\n\n /**\n * Start a cancellable transform animation using Web Animations API\n * @param element - The element to animate\n * @param from - Starting transform state\n * @param to - Ending transform state\n * @param duration - Animation duration in ms (optional)\n * @param easing - CSS easing function (optional)\n * @returns AnimationHandle that can be used to cancel or query the animation\n */\n animateTransformCancellable(\n element: HTMLElement,\n from: TransformParams,\n to: TransformParams,\n duration: number | null = null,\n easing: string | null = null\n ): AnimationHandle {\n // Cancel any existing animation on this element\n this.cancelAllAnimations(element);\n\n const animDuration = duration ?? this.config.duration;\n const animEasing = easing ?? this.config.easing.default;\n\n const fromTransform = this.buildTransformString(from);\n const toTransform = this.buildTransformString(to);\n\n // Clear any CSS transitions to avoid conflicts\n element.style.transition = 'none';\n\n // Create Web Animation\n const animation = element.animate(\n [\n { transform: fromTransform },\n { transform: toTransform }\n ],\n {\n duration: animDuration,\n easing: animEasing,\n fill: 'forwards' // Keep final state after animation\n }\n );\n\n const handle: AnimationHandle = {\n id: `anim-${++this.animationIdCounter}`,\n element,\n animation,\n fromState: from,\n toState: to,\n startTime: performance.now(),\n duration: animDuration\n };\n\n this.activeAnimations.set(element, handle);\n\n // Clean up when animation finishes (normally or cancelled)\n animation.finished\n .then(() => {\n // Apply final transform as inline style for consistency\n element.style.transform = toTransform;\n this.activeAnimations.delete(element);\n })\n .catch(() => {\n // Animation was cancelled - cleanup handled by cancelAnimation\n this.activeAnimations.delete(element);\n });\n\n return handle;\n }\n\n /**\n * Cancel an active animation\n * @param handle - The animation handle to cancel\n * @param commitStyle - If true, keeps current position; if false, no style change\n * @returns Snapshot of where the animation was when cancelled\n */\n cancelAnimation(handle: AnimationHandle, commitStyle: boolean = true): AnimationSnapshot {\n const snapshot = this.getCurrentTransform(handle.element);\n\n // Cancel the Web Animation\n handle.animation.cancel();\n\n if (commitStyle) {\n // Apply current position as inline style\n const currentTransform = this.buildTransformString({\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: snapshot.scale\n });\n handle.element.style.transform = currentTransform;\n }\n\n this.activeAnimations.delete(handle.element);\n\n return snapshot;\n }\n\n /**\n * Cancel all animations on an element\n * Uses Web Animations API to find and cancel ALL animations, not just tracked ones\n * @param element - The element to cancel animations for\n */\n cancelAllAnimations(element: HTMLElement): void {\n // Cancel tracked animation\n const handle = this.activeAnimations.get(element);\n if (handle) {\n this.cancelAnimation(handle, false);\n }\n\n // Also cancel any other animations on the element (e.g., completed animations with fill: 'forwards')\n const allAnimations = element.getAnimations();\n for (const anim of allAnimations) {\n anim.cancel();\n }\n }\n\n /**\n * Get current transform state of an element (works mid-animation)\n * Uses DOMMatrix to parse the computed transform\n * @param element - The element to query\n * @returns Current transform snapshot\n */\n getCurrentTransform(element: HTMLElement): AnimationSnapshot {\n const computed = getComputedStyle(element);\n const transformStr = computed.transform;\n\n if (transformStr === 'none' || !transformStr) {\n return { x: 0, y: 0, rotation: 0, scale: 1 };\n }\n\n const matrix = new DOMMatrix(transformStr);\n\n // Extract scale from matrix (for uniform scale)\n const scale = Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b);\n\n // Extract rotation from matrix (in degrees)\n const rotation = Math.atan2(matrix.b, matrix.a) * (180 / Math.PI);\n\n // Extract translation\n // Note: matrix.e and matrix.f include ALL translations including the centering offset\n // The centering is translate(-50%, -50%) which depends on element dimensions\n // Since our transform chain is: translate(-50%, -50%) translate(x, y) rotate scale\n // The additional x,y offset we applied is already baked into e,f along with centering\n // For cross-animation, we need the relative offset, not absolute position\n // We'll return e,f directly and let ZoomEngine handle the interpretation\n const x = matrix.e;\n const y = matrix.f;\n\n return { x, y, rotation, scale };\n }\n\n /**\n * Check if an element has an active animation\n * @param element - The element to check\n * @returns True if animation is in progress\n */\n hasActiveAnimation(element: HTMLElement): boolean {\n return this.activeAnimations.has(element);\n }\n\n /**\n * Get animation handle for an element if it exists\n * @param element - The element to query\n * @returns AnimationHandle or undefined\n */\n getAnimationHandle(element: HTMLElement): AnimationHandle | undefined {\n return this.activeAnimations.get(element);\n }\n\n /**\n * Animate element transform with smooth easing (CSS transitions - legacy method)\n * @param element - The element to animate\n * @param properties - Transform properties {x, y, rotation, scale}\n * @param duration - Animation duration in ms (optional)\n * @param easing - CSS easing function (optional)\n * @returns Promise that resolves when animation completes\n */\n animateTransform(\n element: HTMLElement,\n properties: TransformParams,\n duration: number | null = null,\n easing: string | null = null\n ): Promise<void> {\n return new Promise((resolve) => {\n const animDuration = duration ?? this.config.duration;\n const animEasing = easing ?? this.config.easing.default;\n\n // Apply transition\n element.style.transition = `transform ${animDuration}ms ${animEasing}, box-shadow ${animDuration}ms ${animEasing}`;\n\n // Apply transform using shared helper\n element.style.transform = this.buildTransformString(properties);\n\n // Resolve promise when animation completes\n setTimeout(() => {\n resolve();\n }, animDuration);\n });\n }\n\n /**\n * Reset element to its original transform\n * @param element - The element to reset\n * @param originalState - Original transform state {x, y, rotation, scale}\n * @returns Promise that resolves when animation completes\n */\n resetTransform(element: HTMLElement, originalState: TransformParams | ImageLayout): Promise<void> {\n return this.animateTransform(element, originalState);\n }\n\n /**\n * Remove transition styles from element\n * @param element - The element to clear\n */\n clearTransition(element: HTMLElement): void {\n element.style.transition = '';\n }\n\n /**\n * Utility: Wait for a specified duration\n * @param ms - Milliseconds to wait\n * @returns Promise that resolves after the specified duration\n */\n wait(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}","/**\n * PathAnimator.ts\n * Provides path calculation functions for bounce, elastic, and wave entry animations\n *\n * These animations require JavaScript-driven frame updates because they involve\n * complex mathematical curves that can't be expressed with CSS transitions alone.\n *\n * Public API:\n * - animatePath(element, startPos, endPos, pathConfig, duration, onComplete)\n * - calculateBouncePosition(t, start, end, config)\n * - calculateElasticPosition(t, start, end, config)\n * - calculateWavePosition(t, start, end, config)\n */\n\nimport type {\n BouncePathConfig,\n ElasticPathConfig,\n WavePathConfig,\n EntryPathConfig,\n EntryPathType,\n EntryRotationConfig,\n EntryScaleConfig\n} from '../config/types';\nimport {\n resolveBounceConfig,\n resolveElasticConfig,\n resolveWavePathConfig\n} from '../config/defaults';\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface PathAnimationOptions {\n element: HTMLElement;\n startPosition: Point;\n endPosition: Point;\n pathConfig: EntryPathConfig;\n duration: number;\n imageWidth: number;\n imageHeight: number;\n rotation: number; // Final rotation\n scale: number; // Final scale\n onComplete?: () => void;\n // Rotation animation options\n rotationConfig?: EntryRotationConfig;\n startRotation?: number; // Starting rotation (if different from final)\n // Scale animation options\n scaleConfig?: EntryScaleConfig;\n startScale?: number; // Starting scale (if different from final)\n}\n\n/**\n * Linear interpolation helper\n */\nfunction lerp(start: number, end: number, t: number): number {\n return start + (end - start) * t;\n}\n\n/**\n * Calculate position along a bounce path\n * Overshoot and settle animation\n */\nexport function calculateBouncePosition(\n t: number,\n start: Point,\n end: Point,\n config: BouncePathConfig\n): Point {\n const { overshoot, bounces, decayRatio } = config;\n\n // Direction vector from start to end\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n\n // Calculate keyframe timings based on number of bounces\n const keyframes = generateBounceKeyframes(bounces, decayRatio);\n\n // Find current segment\n let progress = 0;\n let segmentStart = 0;\n let segmentEnd = 1;\n let segmentOvershoot = overshoot;\n let isOvershootPhase = false;\n\n for (let i = 0; i < keyframes.length; i++) {\n if (t <= keyframes[i].time) {\n segmentStart = i === 0 ? 0 : keyframes[i - 1].time;\n segmentEnd = keyframes[i].time;\n segmentOvershoot = keyframes[i].overshoot;\n isOvershootPhase = keyframes[i].isOvershoot;\n break;\n }\n }\n\n // Calculate progress within current segment\n const segmentT = (t - segmentStart) / (segmentEnd - segmentStart);\n\n if (isOvershootPhase) {\n // Ease out into overshoot\n progress = 1 + segmentOvershoot * easeOutQuad(segmentT);\n } else if (segmentStart === 0) {\n // Initial travel to target - ease out\n progress = easeOutQuad(segmentT);\n } else {\n // Settling back from overshoot\n const prevOvershoot = keyframes.find((k, i) =>\n k.time > segmentStart && i > 0 && keyframes[i - 1].isOvershoot\n );\n const fromProgress = 1 + (prevOvershoot?.overshoot || segmentOvershoot);\n progress = lerp(fromProgress, 1, easeOutQuad(segmentT));\n }\n\n return {\n x: start.x + dx * progress,\n y: start.y + dy * progress\n };\n}\n\n/**\n * Generate keyframe timings for bounce animation\n */\nfunction generateBounceKeyframes(\n bounces: number,\n decayRatio: number\n): Array<{ time: number; overshoot: number; isOvershoot: boolean }> {\n const keyframes: Array<{ time: number; overshoot: number; isOvershoot: boolean }> = [];\n\n // Initial travel takes 60% of time\n let currentTime = 0.6;\n keyframes.push({ time: currentTime, overshoot: 0, isOvershoot: false });\n\n let currentOvershoot = 0.15; // Initial overshoot amount\n const remainingTime = 0.4;\n const bounceTime = remainingTime / (bounces * 2);\n\n for (let i = 0; i < bounces; i++) {\n // Overshoot phase\n currentTime += bounceTime;\n keyframes.push({ time: currentTime, overshoot: currentOvershoot, isOvershoot: true });\n\n // Settle phase\n currentTime += bounceTime;\n keyframes.push({ time: currentTime, overshoot: currentOvershoot * decayRatio, isOvershoot: false });\n\n currentOvershoot *= decayRatio;\n }\n\n // Final settle\n keyframes.push({ time: 1, overshoot: 0, isOvershoot: false });\n\n return keyframes;\n}\n\n/**\n * Calculate position along an elastic path\n * Spring-like oscillation animation\n */\nexport function calculateElasticPosition(\n t: number,\n start: Point,\n end: Point,\n config: ElasticPathConfig\n): Point {\n const { stiffness, damping, mass, oscillations } = config;\n\n // Direction vector from start to end\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n\n // Normalized spring physics\n // Natural frequency based on stiffness and mass\n const omega = Math.sqrt(stiffness / mass);\n\n // Damping ratio (normalized)\n const zeta = damping / (2 * Math.sqrt(stiffness * mass));\n\n // Progress using damped harmonic oscillator\n let progress: number;\n\n if (zeta < 1) {\n // Underdamped - oscillates\n const dampedFreq = omega * Math.sqrt(1 - zeta * zeta);\n const envelope = Math.exp(-zeta * omega * t * 3);\n const oscillation = Math.cos(dampedFreq * t * oscillations * Math.PI);\n progress = 1 - envelope * oscillation;\n } else {\n // Critically damped or overdamped - no oscillation\n progress = 1 - Math.exp(-omega * t * 3);\n }\n\n // Clamp progress\n progress = Math.max(0, Math.min(progress, 1.3)); // Allow slight overshoot\n\n return {\n x: start.x + dx * progress,\n y: start.y + dy * progress\n };\n}\n\n/**\n * Calculate position along a wave path\n * Sinusoidal path from start to end\n */\nexport function calculateWavePosition(\n t: number,\n start: Point,\n end: Point,\n config: WavePathConfig\n): Point {\n const { amplitude, frequency, decay, decayRate, phase } = config;\n\n // Direction vector from start to end\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n const length = Math.sqrt(dx * dx + dy * dy);\n\n // Perpendicular vector (normalized)\n const perpX = length > 0 ? -dy / length : 0;\n const perpY = length > 0 ? dx / length : 1;\n\n // Wave calculation\n const wavePhase = frequency * Math.PI * 2 * t + phase;\n const decayFactor = decay ? Math.pow(1 - t, decayRate) : 1;\n const waveOffset = amplitude * Math.sin(wavePhase) * decayFactor;\n\n // Ease out for smooth arrival\n const progressT = easeOutCubic(t);\n\n // Linear interpolation + wave offset\n return {\n x: lerp(start.x, end.x, progressT) + waveOffset * perpX,\n y: lerp(start.y, end.y, progressT) + waveOffset * perpY\n };\n}\n\n/**\n * Easing functions\n */\nfunction easeOutQuad(t: number): number {\n return 1 - (1 - t) * (1 - t);\n}\n\nfunction easeOutCubic(t: number): number {\n return 1 - Math.pow(1 - t, 3);\n}\n\n/**\n * Calculate wobble rotation for a given animation progress\n */\nfunction calculateWobbleRotation(\n progress: number,\n finalRotation: number,\n wobbleConfig: { amplitude: number; frequency: number; decay: boolean }\n): number {\n const { amplitude, frequency, decay } = wobbleConfig;\n\n // Oscillation using sine wave\n const oscillation = Math.sin(progress * frequency * Math.PI * 2);\n\n // Apply decay if enabled (stronger decay toward end)\n const decayFactor = decay ? Math.pow(1 - progress, 2) : 1;\n\n // Calculate wobble offset\n const wobbleOffset = amplitude * oscillation * decayFactor;\n\n return finalRotation + wobbleOffset;\n}\n\n/**\n * Calculate pop scale for a given animation progress\n */\nfunction calculatePopScale(\n progress: number,\n finalScale: number,\n popConfig: { overshoot: number; bounces: number }\n): number {\n const { overshoot, bounces } = popConfig;\n\n // Generate keyframes for bounce effect\n const keyframes: Array<{ time: number; scale: number }> = [];\n\n // Reach overshoot at 50% of animation\n keyframes.push({ time: 0.5, scale: overshoot });\n\n // Add bounces\n let currentOvershoot = overshoot;\n const bounceDecay = 0.5;\n const remainingTime = 0.5;\n const bounceTime = remainingTime / (bounces * 2);\n\n let currentTime = 0.5;\n for (let i = 0; i < bounces; i++) {\n const undershoot = 1 - (currentOvershoot - 1) * bounceDecay;\n currentTime += bounceTime;\n keyframes.push({ time: currentTime, scale: undershoot });\n\n currentOvershoot = 1 + (currentOvershoot - 1) * bounceDecay * bounceDecay;\n currentTime += bounceTime;\n if (i < bounces - 1) {\n keyframes.push({ time: currentTime, scale: currentOvershoot });\n }\n }\n\n keyframes.push({ time: 1, scale: 1 });\n\n // Find current segment\n let currentScale = 1;\n for (let i = 0; i < keyframes.length; i++) {\n if (progress <= keyframes[i].time) {\n const prevTime = i === 0 ? 0 : keyframes[i - 1].time;\n const prevScale = i === 0 ? 1 : keyframes[i - 1].scale;\n const segmentProgress = (progress - prevTime) / (keyframes[i].time - prevTime);\n const easedProgress = easeOutQuad(segmentProgress);\n currentScale = prevScale + (keyframes[i].scale - prevScale) * easedProgress;\n break;\n }\n }\n\n return currentScale * finalScale;\n}\n\n/**\n * Animate an element along a path using requestAnimationFrame\n */\nexport function animatePath(options: PathAnimationOptions): void {\n const {\n element,\n startPosition,\n endPosition,\n pathConfig,\n duration,\n imageWidth,\n imageHeight,\n rotation: finalRotation,\n scale: finalScale,\n onComplete,\n rotationConfig,\n startRotation,\n scaleConfig,\n startScale\n } = options;\n\n const pathType = pathConfig.type;\n\n // Determine if we need to animate rotation\n const animateRotation = startRotation !== undefined && startRotation !== finalRotation;\n const isWobbleMode = rotationConfig?.mode === 'wobble';\n const wobbleConfig = rotationConfig?.wobble || { amplitude: 15, frequency: 3, decay: true };\n const needsRotationAnimation = animateRotation || isWobbleMode;\n\n // Determine if we need to animate scale\n const animateScale = startScale !== undefined && startScale !== finalScale;\n const isPopMode = scaleConfig?.mode === 'pop';\n const popConfig = scaleConfig?.pop || { overshoot: 1.2, bounces: 1 };\n const needsScaleAnimation = animateScale || isPopMode;\n\n // For linear/arc paths WITHOUT rotation or scale animation, use CSS transitions (handled elsewhere)\n if ((pathType === 'linear' || pathType === 'arc') && !needsRotationAnimation && !needsScaleAnimation) {\n if (onComplete) onComplete();\n return;\n }\n\n const startTime = performance.now();\n\n // Build center offset for transform\n const centerOffsetX = -imageWidth / 2;\n const centerOffsetY = -imageHeight / 2;\n\n function tick(currentTime: number): void {\n const elapsed = currentTime - startTime;\n const t = Math.min(elapsed / duration, 1);\n\n // Calculate position based on path type\n let position: Point;\n\n switch (pathType) {\n case 'bounce': {\n const config = resolveBounceConfig(\n pathConfig.bouncePreset,\n pathConfig.bounce\n );\n position = calculateBouncePosition(t, startPosition, endPosition, config);\n break;\n }\n case 'elastic': {\n const config = resolveElasticConfig(\n pathConfig.elasticPreset,\n pathConfig.elastic\n );\n position = calculateElasticPosition(t, startPosition, endPosition, config);\n break;\n }\n case 'wave': {\n const config = resolveWavePathConfig(\n pathConfig.wavePreset,\n pathConfig.wave\n );\n position = calculateWavePosition(t, startPosition, endPosition, config);\n break;\n }\n default:\n position = {\n x: lerp(startPosition.x, endPosition.x, t),\n y: lerp(startPosition.y, endPosition.y, t)\n };\n }\n\n // Calculate translate offset from final position\n const translateX = position.x - endPosition.x;\n const translateY = position.y - endPosition.y;\n\n // Calculate current rotation\n let currentRotation: number;\n if (isWobbleMode) {\n currentRotation = calculateWobbleRotation(t, finalRotation, wobbleConfig);\n } else if (animateRotation) {\n currentRotation = lerp(startRotation!, finalRotation, t);\n } else {\n currentRotation = finalRotation;\n }\n\n // Calculate current scale\n let currentScale: number;\n if (isPopMode) {\n currentScale = calculatePopScale(t, finalScale, popConfig);\n } else if (animateScale) {\n currentScale = lerp(startScale!, finalScale, t);\n } else {\n currentScale = finalScale;\n }\n\n // Apply transform\n element.style.transform =\n `translate(${centerOffsetX}px, ${centerOffsetY}px) ` +\n `translate(${translateX}px, ${translateY}px) ` +\n `rotate(${currentRotation}deg) scale(${currentScale})`;\n\n if (t < 1) {\n requestAnimationFrame(tick);\n } else {\n // Ensure we end exactly at the final position, rotation, and scale\n element.style.transform =\n `translate(${centerOffsetX}px, ${centerOffsetY}px) ` +\n `rotate(${finalRotation}deg) scale(${finalScale})`;\n if (onComplete) onComplete();\n }\n }\n\n requestAnimationFrame(tick);\n}\n\n/**\n * Check if a path type requires JavaScript animation (vs CSS transitions)\n */\nexport function requiresJSAnimation(pathType: EntryPathType): boolean {\n return pathType === 'bounce' || pathType === 'elastic' || pathType === 'wave';\n}\n\n/**\n * Get CSS easing for bounce (approximation for simple bounce)\n * Note: This is only used as a fallback; full bounce uses JS animation\n */\nexport function getBounceCSSEasing(): string {\n return 'cubic-bezier(0.68, -0.55, 0.265, 1.55)';\n}\n","/**\n * EntryAnimationEngine.ts\n * Calculates starting positions for entry animations based on configuration\n *\n * Public API:\n * - calculateStartPosition(finalPosition, imageSize, containerBounds, imageIndex, totalImages)\n * - getAnimationParams(imageIndex)\n */\n\nimport type {\n EntryAnimationConfig,\n EntryStartPosition,\n ContainerBounds,\n LayoutAlgorithm,\n EntryPathConfig,\n EntryPathType,\n EntryRotationConfig,\n EntryRotationMode,\n EntryScaleConfig,\n EntryScaleMode\n} from '../config/types';\nimport { DEFAULT_PATH_CONFIG, DEFAULT_ENTRY_ROTATION, DEFAULT_ENTRY_SCALE } from '../config/defaults';\nimport { requiresJSAnimation } from './PathAnimator';\n\n/** Layout-aware default start positions */\nconst LAYOUT_ENTRY_DEFAULTS: Record<LayoutAlgorithm, EntryStartPosition> = {\n radial: 'center',\n spiral: 'center',\n grid: 'top',\n cluster: 'nearest-edge',\n random: 'nearest-edge',\n wave: 'left'\n};\n\nexport interface StartPosition {\n x: number;\n y: number;\n useScale?: boolean; // For center position, start with scale 0\n}\n\nexport interface AnimationParams {\n startTransform: string;\n duration: number;\n delay: number;\n easing: string;\n}\n\nexport class EntryAnimationEngine {\n private config: EntryAnimationConfig;\n private layoutAlgorithm: LayoutAlgorithm;\n private resolvedStartPosition: EntryStartPosition;\n private pathConfig: EntryPathConfig;\n private rotationConfig: EntryRotationConfig;\n private scaleConfig: EntryScaleConfig;\n\n constructor(config: EntryAnimationConfig, layoutAlgorithm: LayoutAlgorithm) {\n this.config = config;\n this.layoutAlgorithm = layoutAlgorithm;\n\n // Resolve the start position, using layout-aware defaults if not specified\n this.resolvedStartPosition = this.resolveStartPosition();\n\n // Resolve path config\n this.pathConfig = config.path || DEFAULT_PATH_CONFIG;\n\n // Resolve rotation config\n this.rotationConfig = config.rotation || DEFAULT_ENTRY_ROTATION;\n\n // Resolve scale config\n this.scaleConfig = config.scale || DEFAULT_ENTRY_SCALE;\n }\n\n /**\n * Get the effective start position, considering layout-aware defaults\n */\n private resolveStartPosition(): EntryStartPosition {\n // If explicitly configured, use that\n if (this.config.start.position) {\n return this.config.start.position;\n }\n // Otherwise use layout-aware default\n return LAYOUT_ENTRY_DEFAULTS[this.layoutAlgorithm] || 'nearest-edge';\n }\n\n /**\n * Calculate the starting position for an image's entry animation\n */\n calculateStartPosition(\n finalPosition: { x: number; y: number },\n imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n imageIndex: number,\n totalImages: number\n ): StartPosition {\n const position = this.resolvedStartPosition;\n const offset = this.config.start.offset ?? 100;\n\n switch (position) {\n case 'nearest-edge':\n return this.calculateNearestEdge(finalPosition, imageSize, containerBounds, offset);\n\n case 'top':\n return this.calculateEdgePosition('top', finalPosition, imageSize, containerBounds, offset);\n\n case 'bottom':\n return this.calculateEdgePosition('bottom', finalPosition, imageSize, containerBounds, offset);\n\n case 'left':\n return this.calculateEdgePosition('left', finalPosition, imageSize, containerBounds, offset);\n\n case 'right':\n return this.calculateEdgePosition('right', finalPosition, imageSize, containerBounds, offset);\n\n case 'center':\n return this.calculateCenterPosition(containerBounds, finalPosition, imageSize);\n\n case 'random-edge':\n return this.calculateRandomEdge(finalPosition, imageSize, containerBounds, offset);\n\n case 'circular':\n return this.calculateCircularPosition(\n finalPosition,\n imageSize,\n containerBounds,\n imageIndex,\n totalImages\n );\n\n default:\n return this.calculateNearestEdge(finalPosition, imageSize, containerBounds, offset);\n }\n }\n\n /**\n * Calculate start position from the nearest edge (current default behavior)\n */\n private calculateNearestEdge(\n finalPosition: { x: number; y: number },\n imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n offset: number\n ): StartPosition {\n // finalPosition now stores center position directly\n const centerX = finalPosition.x;\n const centerY = finalPosition.y;\n\n const distLeft = centerX;\n const distRight = containerBounds.width - centerX;\n const distTop = centerY;\n const distBottom = containerBounds.height - centerY;\n\n const minDist = Math.min(distLeft, distRight, distTop, distBottom);\n\n let startX = finalPosition.x;\n let startY = finalPosition.y;\n\n if (minDist === distLeft) {\n // Start from left edge\n startX = -(imageSize.width + offset);\n } else if (minDist === distRight) {\n // Start from right edge\n startX = containerBounds.width + offset;\n } else if (minDist === distTop) {\n // Start from top edge\n startY = -(imageSize.height + offset);\n } else {\n // Start from bottom edge\n startY = containerBounds.height + offset;\n }\n\n return { x: startX, y: startY };\n }\n\n /**\n * Calculate start position from a specific edge\n */\n private calculateEdgePosition(\n edge: 'top' | 'bottom' | 'left' | 'right',\n finalPosition: { x: number; y: number },\n imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n offset: number\n ): StartPosition {\n let startX = finalPosition.x;\n let startY = finalPosition.y;\n\n switch (edge) {\n case 'top':\n startY = -(imageSize.height + offset);\n break;\n case 'bottom':\n startY = containerBounds.height + offset;\n break;\n case 'left':\n startX = -(imageSize.width + offset);\n break;\n case 'right':\n startX = containerBounds.width + offset;\n break;\n }\n\n return { x: startX, y: startY };\n }\n\n /**\n * Calculate start position from center with scale animation\n */\n private calculateCenterPosition(\n containerBounds: ContainerBounds,\n _finalPosition: { x: number; y: number },\n _imageSize: { width: number; height: number }\n ): StartPosition {\n // Start at center of container (using center position, not top-left)\n const centerX = containerBounds.width / 2;\n const centerY = containerBounds.height / 2;\n\n return {\n x: centerX,\n y: centerY,\n useScale: true // Signal to use scale animation from 0\n };\n }\n\n /**\n * Calculate start position from a random edge\n */\n private calculateRandomEdge(\n finalPosition: { x: number; y: number },\n imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n offset: number\n ): StartPosition {\n const edges: ('top' | 'bottom' | 'left' | 'right')[] = ['top', 'bottom', 'left', 'right'];\n const randomEdge = edges[Math.floor(Math.random() * edges.length)];\n return this.calculateEdgePosition(randomEdge, finalPosition, imageSize, containerBounds, offset);\n }\n\n /**\n * Calculate start position on a circle around the container\n */\n private calculateCircularPosition(\n _finalPosition: { x: number; y: number },\n _imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n imageIndex: number,\n totalImages: number\n ): StartPosition {\n const circularConfig = this.config.start.circular || {};\n const distribution = circularConfig.distribution || 'even';\n\n // Calculate radius\n let radius: number;\n const radiusConfig = circularConfig.radius || '120%';\n\n if (typeof radiusConfig === 'string' && radiusConfig.endsWith('%')) {\n // Percentage of container diagonal\n const percentage = parseFloat(radiusConfig) / 100;\n const diagonal = Math.sqrt(\n containerBounds.width ** 2 + containerBounds.height ** 2\n );\n radius = diagonal * percentage / 2;\n } else {\n radius = typeof radiusConfig === 'number' ? radiusConfig : 500;\n }\n\n // Calculate angle\n let angle: number;\n if (distribution === 'even') {\n angle = (imageIndex / totalImages) * 2 * Math.PI;\n } else {\n angle = Math.random() * 2 * Math.PI;\n }\n\n // Calculate position on circle, centered on container\n const centerX = containerBounds.width / 2;\n const centerY = containerBounds.height / 2;\n\n // Return center position on circle (not top-left)\n const startX = centerX + Math.cos(angle) * radius;\n const startY = centerY + Math.sin(angle) * radius;\n\n return { x: startX, y: startY };\n }\n\n /**\n * Get animation parameters for an image\n */\n getAnimationParams(imageIndex: number): AnimationParams {\n const duration = this.config.timing.duration;\n const stagger = this.config.timing.stagger;\n const easing = this.config.easing;\n\n return {\n startTransform: '', // Will be computed by caller based on start position\n duration,\n delay: imageIndex * stagger,\n easing\n };\n }\n\n /**\n * Build a CSS transform string for the start position\n * Uses pixel-based centering offset for reliable cross-browser behavior\n */\n buildStartTransform(\n startPosition: StartPosition,\n finalPosition: { x: number; y: number },\n finalRotation: number,\n finalScale: number,\n imageWidth?: number,\n imageHeight?: number,\n startRotation?: number,\n startScale?: number\n ): string {\n // Calculate translation from final to start position\n const translateX = startPosition.x - finalPosition.x;\n const translateY = startPosition.y - finalPosition.y;\n\n // Use start rotation if provided, otherwise use final rotation\n const rotation = startRotation !== undefined ? startRotation : finalRotation;\n\n // Use start scale if provided, otherwise use final scale\n const scale = startScale !== undefined ? startScale : finalScale;\n\n // Use pixel offset if dimensions provided\n const centerOffsetX = imageWidth !== undefined ? -imageWidth / 2 : 0;\n const centerOffsetY = imageHeight !== undefined ? -imageHeight / 2 : 0;\n const centerTranslate = imageWidth !== undefined\n ? `translate(${centerOffsetX}px, ${centerOffsetY}px)`\n : `translate(-50%, -50%)`;\n\n if (startPosition.useScale) {\n // For center position: start at center with scale 0\n return `${centerTranslate} translate(${translateX}px, ${translateY}px) rotate(${rotation}deg) scale(0)`;\n }\n\n // Standard entry: translate from edge, with configured start scale\n return `${centerTranslate} translate(${translateX}px, ${translateY}px) rotate(${rotation}deg) scale(${scale})`;\n }\n\n /**\n * Build the final CSS transform string\n * Uses pixel-based centering offset for reliable cross-browser behavior\n */\n buildFinalTransform(rotation: number, scale: number, imageWidth?: number, imageHeight?: number): string {\n // Use pixel offset if dimensions provided, otherwise fall back to percentage\n if (imageWidth !== undefined && imageHeight !== undefined) {\n const offsetX = -imageWidth / 2;\n const offsetY = -imageHeight / 2;\n return `translate(${offsetX}px, ${offsetY}px) rotate(${rotation}deg) scale(${scale})`;\n }\n return `translate(-50%, -50%) rotate(${rotation}deg) scale(${scale})`;\n }\n\n /**\n * Get the transition CSS for entry animation\n * For JS-animated paths, only animate opacity (transform handled by JS)\n */\n getTransitionCSS(): string {\n const duration = this.config.timing.duration;\n const easing = this.config.easing;\n\n // For paths that use JS animation, don't transition transform\n if (this.requiresJSAnimation()) {\n return `opacity ${duration}ms ease-out`;\n }\n\n return `opacity ${duration}ms ease-out, transform ${duration}ms ${easing}`;\n }\n\n /**\n * Check if the current path type requires JavaScript animation\n */\n requiresJSAnimation(): boolean {\n return requiresJSAnimation(this.pathConfig.type);\n }\n\n /**\n * Get the path configuration\n */\n getPathConfig(): EntryPathConfig {\n return this.pathConfig;\n }\n\n /**\n * Get the path type\n */\n getPathType(): EntryPathType {\n return this.pathConfig.type;\n }\n\n /**\n * Get animation timing configuration\n */\n getTiming(): { duration: number; stagger: number } {\n return {\n duration: this.config.timing.duration,\n stagger: this.config.timing.stagger\n };\n }\n\n /**\n * Get the rotation configuration\n */\n getRotationConfig(): EntryRotationConfig {\n return this.rotationConfig;\n }\n\n /**\n * Get the rotation mode\n */\n getRotationMode(): EntryRotationMode {\n return this.rotationConfig.mode;\n }\n\n /**\n * Calculate the starting rotation for an entry animation\n * @param finalRotation - The final rotation from the layout\n * @returns The starting rotation in degrees\n */\n calculateStartRotation(finalRotation: number): number {\n const mode = this.rotationConfig.mode;\n\n switch (mode) {\n case 'none':\n // No rotation animation - start at final rotation\n return finalRotation;\n\n case 'settle': {\n // Start at a configured rotation and settle to final\n const startConfig = this.rotationConfig.startRotation;\n if (startConfig === undefined) {\n // Default: ±30° random offset from final\n return finalRotation + (Math.random() - 0.5) * 60;\n }\n if (typeof startConfig === 'number') {\n return startConfig;\n }\n // Range: random value between min and max\n const range = startConfig.max - startConfig.min;\n return startConfig.min + Math.random() * range;\n }\n\n case 'spin': {\n // Spin from a rotated position to final\n const spinCount = this.rotationConfig.spinCount ?? 1;\n const direction = this.resolveSpinDirection(finalRotation);\n return finalRotation + (spinCount * 360 * direction);\n }\n\n case 'random':\n // Random starting rotation (±30° from final)\n return finalRotation + (Math.random() - 0.5) * 60;\n\n case 'wobble':\n // Wobble is handled in JS animation, start at final rotation\n return finalRotation;\n\n default:\n return finalRotation;\n }\n }\n\n /**\n * Resolve spin direction based on config\n * @returns 1 for clockwise, -1 for counterclockwise\n */\n private resolveSpinDirection(finalRotation: number): number {\n const direction = this.rotationConfig.direction ?? 'auto';\n\n switch (direction) {\n case 'clockwise':\n return -1; // Negative rotation = clockwise spin to final\n case 'counterclockwise':\n return 1; // Positive rotation = counterclockwise spin to final\n case 'random':\n return Math.random() < 0.5 ? 1 : -1;\n case 'auto':\n default:\n // Auto: choose direction that reduces total rotation distance\n // If final rotation is positive, spin counterclockwise (add positive offset)\n return finalRotation >= 0 ? 1 : -1;\n }\n }\n\n /**\n * Check if the current rotation mode requires JavaScript animation\n * (as opposed to CSS transitions)\n */\n requiresJSRotation(): boolean {\n return this.rotationConfig.mode === 'wobble';\n }\n\n /**\n * Calculate wobble rotation for a given animation progress\n * @param progress - Animation progress from 0 to 1\n * @param finalRotation - The final rotation in degrees\n * @returns The current rotation in degrees\n */\n calculateWobbleRotation(progress: number, finalRotation: number): number {\n if (this.rotationConfig.mode !== 'wobble') {\n return finalRotation;\n }\n\n const wobbleConfig = this.rotationConfig.wobble || {\n amplitude: 15,\n frequency: 3,\n decay: true\n };\n\n const { amplitude, frequency, decay } = wobbleConfig;\n\n // Oscillation using sine wave\n const oscillation = Math.sin(progress * frequency * Math.PI * 2);\n\n // Apply decay if enabled (stronger decay toward end)\n const decayFactor = decay ? Math.pow(1 - progress, 2) : 1;\n\n // Calculate wobble offset\n const wobbleOffset = amplitude * oscillation * decayFactor;\n\n return finalRotation + wobbleOffset;\n }\n\n /**\n * Get the scale configuration\n */\n getScaleConfig(): EntryScaleConfig {\n return this.scaleConfig;\n }\n\n /**\n * Get the scale mode\n */\n getScaleMode(): EntryScaleMode {\n return this.scaleConfig.mode;\n }\n\n /**\n * Calculate the starting scale for an entry animation\n * @param finalScale - The final scale from the layout\n * @returns The starting scale\n */\n calculateStartScale(finalScale: number): number {\n const mode = this.scaleConfig.mode;\n\n switch (mode) {\n case 'none':\n // No scale animation - start at final scale\n return finalScale;\n\n case 'grow': {\n // Start smaller, grow to final\n const startScale = this.scaleConfig.startScale ?? 0.3;\n return startScale * finalScale; // Apply relative to final scale\n }\n\n case 'shrink': {\n // Start larger, shrink to final\n const startScale = this.scaleConfig.startScale ?? 1.5;\n return startScale * finalScale; // Apply relative to final scale\n }\n\n case 'pop':\n // Pop mode uses JS animation, start at final scale\n // (the overshoot/bounce is handled in the animation tick)\n return finalScale;\n\n case 'random': {\n // Random start scale in configured range\n const range = this.scaleConfig.range ?? { min: 0.5, max: 1.0 };\n const randomFactor = range.min + Math.random() * (range.max - range.min);\n return randomFactor * finalScale;\n }\n\n default:\n return finalScale;\n }\n }\n\n /**\n * Check if the current scale mode requires JavaScript animation\n * (as opposed to CSS transitions)\n */\n requiresJSScale(): boolean {\n return this.scaleConfig.mode === 'pop';\n }\n\n /**\n * Calculate pop scale for a given animation progress\n * @param progress - Animation progress from 0 to 1\n * @param finalScale - The final scale value\n * @returns The current scale value with bounce effect\n */\n calculatePopScale(progress: number, finalScale: number): number {\n if (this.scaleConfig.mode !== 'pop') {\n return finalScale;\n }\n\n const popConfig = this.scaleConfig.pop || {\n overshoot: 1.2,\n bounces: 1\n };\n\n const { overshoot, bounces } = popConfig;\n\n // Create keyframes for bounce effect\n // Similar to bounce path but for scale\n const keyframes = this.generateScaleBounceKeyframes(bounces, overshoot);\n\n // Find current segment\n let currentScale = finalScale;\n for (let i = 0; i < keyframes.length; i++) {\n if (progress <= keyframes[i].time) {\n const prevTime = i === 0 ? 0 : keyframes[i - 1].time;\n const prevScale = i === 0 ? finalScale : keyframes[i - 1].scale;\n const segmentProgress = (progress - prevTime) / (keyframes[i].time - prevTime);\n // Smooth easing within segment\n const easedProgress = this.easeOutQuad(segmentProgress);\n currentScale = prevScale + (keyframes[i].scale - prevScale) * easedProgress;\n break;\n }\n }\n\n return currentScale * finalScale;\n }\n\n /**\n * Generate keyframes for scale bounce animation\n */\n private generateScaleBounceKeyframes(\n bounces: number,\n overshoot: number\n ): Array<{ time: number; scale: number }> {\n const keyframes: Array<{ time: number; scale: number }> = [];\n\n // Reach overshoot at 50% of animation\n keyframes.push({ time: 0.5, scale: overshoot });\n\n // Add bounces\n let currentOvershoot = overshoot;\n const bounceDecay = 0.5; // Each bounce is 50% of previous\n const remainingTime = 0.5;\n const bounceTime = remainingTime / (bounces * 2);\n\n let currentTime = 0.5;\n for (let i = 0; i < bounces; i++) {\n // Undershoot (go below 1)\n const undershoot = 1 - (currentOvershoot - 1) * bounceDecay;\n currentTime += bounceTime;\n keyframes.push({ time: currentTime, scale: undershoot });\n\n // Overshoot again (smaller)\n currentOvershoot = 1 + (currentOvershoot - 1) * bounceDecay * bounceDecay;\n currentTime += bounceTime;\n if (i < bounces - 1) {\n keyframes.push({ time: currentTime, scale: currentOvershoot });\n }\n }\n\n // Final settle\n keyframes.push({ time: 1, scale: 1 });\n\n return keyframes;\n }\n\n /**\n * Easing function for smooth transitions\n */\n private easeOutQuad(t: number): number {\n return 1 - (1 - t) * (1 - t);\n }\n}\n","/**\n * RandomPlacementGenerator.ts\n * Generates random overlapping layouts for image cloud\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, ImageConfig } from '../config/types';\n\ninterface RandomLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nexport class RandomPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate random layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides (includes fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(imageCount: number, containerBounds: ContainerBounds, options: RandomLayoutOptions = {}): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n\n const padding = this.config.spacing.padding;\n // Use fixedHeight if provided, otherwise use default 200\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Calculate safe bounds for center positions (accounting for half image size and padding)\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (baseImageSize * estAspectRatio) / 2;\n const halfHeight = baseImageSize / 2;\n\n const maxX = width - padding - halfWidth;\n const maxY = height - padding - halfHeight;\n const minX = padding + halfWidth;\n const minY = padding + halfHeight;\n\n for (let i = 0; i < imageCount; i++) {\n // Random center position within safe bounds\n const x = this.random(minX, maxX);\n const y = this.random(minY, maxY);\n\n // Random rotation within range (only when mode is random)\n const rotation = rotationMode === 'random' ? this.random(minRotation, maxRotation) : 0;\n\n // Random size variance\n const scale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const scaledImageSize = baseImageSize * scale;\n\n const layout: ImageLayout = {\n id: i,\n x,\n y,\n rotation,\n scale,\n baseSize: scaledImageSize\n };\n\n layouts.push(layout);\n }\n\n return layouts;\n }\n\n /**\n * Utility: Generate random number between min and max\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Random number in range\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * RadialPlacementGenerator.ts\n * Generates concentric radial layouts for image cloud\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, ImageConfig } from '../config/types';\n\ninterface RadialLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nexport class RadialPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate radial layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: RadialLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n const { debugRadials } = this.config;\n // Use fixedHeight if provided, otherwise use default 200\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Get scale decay from layout config\n const scaleDecay = this.config.scaleDecay ?? 0;\n\n // Debug color palette\n const debugPalette = ['green', 'blue', 'red', 'yellow', 'orange', 'purple'];\n\n // Use override fixedHeight if provided, else baseImageSize\n const imageSize = options.fixedHeight ?? baseImageSize;\n const cx = width / 2;\n const cy = height / 2;\n\n // Calculate max rings for scale decay calculation\n const estimatedMaxRings = Math.ceil(Math.sqrt(imageCount));\n\n // Add center image (using center position)\n if (imageCount > 0) {\n // Apply variance to center image\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const centerSize = imageSize * varianceScale;\n\n layouts.push({\n id: 0,\n x: cx,\n y: cy,\n rotation: rotationMode === 'random' ? this.random(minRotation * 0.33, maxRotation * 0.33) : 0, // Less rotation for center\n scale: varianceScale,\n baseSize: centerSize,\n zIndex: 100, // Center image is highest\n borderColor: debugRadials ? 'cyan' : undefined\n });\n }\n\n let processedCount = 1;\n let currentRing = 1;\n\n while (processedCount < imageCount) {\n // Calculate scale decay for this ring (center is largest, outer rings smaller)\n const normalizedRing = currentRing / estimatedMaxRings;\n const ringScale = scaleDecay > 0\n ? 1 - (normalizedRing * scaleDecay * 0.5) // Max 50% size reduction\n : 1.0;\n\n // Ring settings\n // Scale X more than Y to create horizontal oval shape\n const radiusY = currentRing * (imageSize * 0.8); // Reduce overlap by 20% (1.0 -> 0.8)\n const radiusX = radiusY * 1.5; // Horizontal stretching factor\n\n const circumference = Math.PI * (3 * (radiusX + radiusY) - Math.sqrt((3 * radiusX + radiusY) * (radiusX + 3 * radiusY))); // Ramanujan's approximation\n\n const estimatedItemWidth = this.estimateWidth(imageSize);\n // Increase density by ~40% (1.1 -> 0.7)\n const itemsInRing = Math.floor(circumference / (estimatedItemWidth * 0.7));\n\n if (itemsInRing === 0) {\n currentRing++;\n continue;\n }\n\n const angleStep = (2 * Math.PI) / itemsInRing;\n\n // Add offset of 20 degrees per ring\n const ringOffset = currentRing * (20 * Math.PI / 180);\n\n for (let i = 0; i < itemsInRing && processedCount < imageCount; i++) {\n const angle = (i * angleStep) + ringOffset;\n\n // Apply variance and scale decay\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const combinedScale = ringScale * varianceScale;\n const scaledImageSize = imageSize * combinedScale;\n\n // Calculate center position of image using elliptical formula (store center, not top-left)\n let x = cx + Math.cos(angle) * radiusX;\n let y = cy + Math.sin(angle) * radiusY;\n\n // Boundary Clamping - clamp center position with conservative estimate\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const padding = this.config.spacing.padding ?? 50;\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (scaledImageSize * estAspectRatio) / 2;\n const halfHeight = scaledImageSize / 2;\n\n // Clamp X (center position)\n if (x - halfWidth < padding) {\n x = padding + halfWidth;\n } else if (x + halfWidth > width - padding) {\n x = width - padding - halfWidth;\n }\n\n // Clamp Y (center position)\n if (y - halfHeight < padding) {\n y = padding + halfHeight;\n } else if (y + halfHeight > height - padding) {\n y = height - padding - halfHeight;\n }\n\n const rotation = rotationMode === 'random' ? this.random(minRotation, maxRotation) : 0;\n\n layouts.push({\n id: processedCount,\n x,\n y,\n rotation,\n scale: combinedScale,\n baseSize: scaledImageSize,\n zIndex: Math.max(1, 100 - currentRing), // Outer rings have lower z-index\n borderColor: debugRadials ? debugPalette[(currentRing - 1) % debugPalette.length] : undefined\n });\n\n processedCount++;\n }\n\n currentRing++;\n }\n\n return layouts;\n }\n\n /**\n * Estimate image width based on height\n * Assumes landscape aspect ratio (approximately 1.4:1)\n * @param height - Image height\n * @returns Estimated width\n */\n private estimateWidth(height: number): number {\n // Assume landscape aspect ratio approx 4:3 or 16:9 on average\n // Using 1.4 ratio\n return height * 1.4;\n }\n\n /**\n * Utility: Generate random number between min and max\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Random number in range\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}","/**\n * GridPlacementGenerator.ts\n * Generates grid-based layouts with optional stagger and jitter\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, GridAlgorithmConfig, ImageConfig } from '../config/types';\n\ninterface GridLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nconst DEFAULT_GRID_CONFIG: GridAlgorithmConfig = {\n columns: 'auto',\n rows: 'auto',\n stagger: 'none',\n jitter: 0,\n overlap: 0,\n fillDirection: 'row',\n alignment: 'center',\n gap: 10,\n overflowOffset: 0.25\n};\n\n// Offset pattern for stacking overflow images within cells\n// Each overflow layer cycles through these directions\n// Corners first, then cardinal directions\nconst OVERFLOW_OFFSET_PATTERN = [\n { x: 1, y: 1 }, // bottom-right\n { x: -1, y: -1 }, // upper-left\n { x: 1, y: -1 }, // upper-right\n { x: -1, y: 1 }, // bottom-left\n { x: -1, y: 0 }, // left\n { x: 1, y: 0 }, // right\n { x: 0, y: -1 }, // up\n { x: 0, y: 1 }, // down\n];\n\nexport class GridPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate grid layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides (includes fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: GridLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n\n const gridConfig = { ...DEFAULT_GRID_CONFIG, ...this.config.grid };\n const padding = this.config.spacing.padding;\n // Use fixedHeight if provided, otherwise use base size from config\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Calculate available space\n const availableWidth = width - (2 * padding);\n const availableHeight = height - (2 * padding);\n\n // Calculate grid dimensions\n const { columns, rows } = this.calculateGridDimensions(\n imageCount,\n availableWidth,\n availableHeight,\n baseImageSize,\n gridConfig\n );\n\n // For stagger layouts, we need n+0.5 cells to fit in available space\n // Calculate cell size accounting for this extra half-cell\n const hasRowStagger = gridConfig.stagger === 'row';\n const hasColumnStagger = gridConfig.stagger === 'column';\n\n // Effective columns/rows for sizing: add 0.5 for stagger\n const effectiveColumns = hasRowStagger ? columns + 0.5 : columns;\n const effectiveRows = hasColumnStagger ? rows + 0.5 : rows;\n\n // Calculate cell size to fit effective count in available space\n const cellWidth = (availableWidth - (gridConfig.gap * (columns - 1))) / effectiveColumns;\n const cellHeight = (availableHeight - (gridConfig.gap * (rows - 1))) / effectiveRows;\n\n // Stagger offset is half a cell\n const staggerOffsetX = hasRowStagger ? cellWidth / 2 : 0;\n const staggerOffsetY = hasColumnStagger ? cellHeight / 2 : 0;\n\n // Calculate image size with overlap factor\n // overlap: 0 = fit in cell, 0.5 = 50% larger, 1.0 = 2x cell size\n const overlapMultiplier = 1 + gridConfig.overlap;\n const cellBasedSize = Math.min(cellWidth, cellHeight) * overlapMultiplier;\n\n // Use fixedHeight if provided, otherwise use cell-based calculation\n // For grid layouts, we respect fixedHeight but cap at cell-based size to avoid overflow\n const imageSize = options.fixedHeight\n ? Math.min(options.fixedHeight, cellBasedSize)\n : cellBasedSize;\n\n // Calculate total grid dimensions for alignment (include stagger offset)\n const totalGridWidth = columns * cellWidth + (columns - 1) * gridConfig.gap + staggerOffsetX;\n const totalGridHeight = rows * cellHeight + (rows - 1) * gridConfig.gap + staggerOffsetY;\n\n // Center the grid in the container\n const gridOffsetX = padding + (availableWidth - totalGridWidth) / 2;\n const gridOffsetY = padding + (availableHeight - totalGridHeight) / 2;\n\n // Detect overflow mode: when both dimensions are fixed and we have more images than cells\n const cellCount = columns * rows;\n const hasFixedGrid = gridConfig.columns !== 'auto' && gridConfig.rows !== 'auto';\n const isOverflowMode = hasFixedGrid && imageCount > cellCount;\n\n if (typeof window !== 'undefined') {\n (window as any).__gridOverflowDebug = {\n gridConfigColumns: gridConfig.columns,\n gridConfigRows: gridConfig.rows,\n columns, rows, cellCount,\n hasFixedGrid, imageCount, isOverflowMode\n };\n }\n\n // Track stack depth for each cell (used in overflow mode)\n const cellStackCount: number[] = isOverflowMode ? new Array(cellCount).fill(0) : [];\n\n // Calculate overflow offset in pixels\n const overflowOffsetPx = Math.min(cellWidth, cellHeight) * gridConfig.overflowOffset;\n\n for (let i = 0; i < imageCount; i++) {\n let col: number;\n let row: number;\n let stackLayer = 0; // 0 = base image, 1+ = overflow layers\n\n if (isOverflowMode && i >= cellCount) {\n // Overflow image: determine target cell and stack layer\n const overflowIndex = i - cellCount;\n const targetCell = overflowIndex % cellCount;\n stackLayer = Math.floor(overflowIndex / cellCount) + 1;\n cellStackCount[targetCell]++;\n\n // Get cell coordinates from target cell index\n if (gridConfig.fillDirection === 'row') {\n col = targetCell % columns;\n row = Math.floor(targetCell / columns);\n } else {\n row = targetCell % rows;\n col = Math.floor(targetCell / rows);\n }\n } else {\n // Base image: normal cell assignment\n if (gridConfig.fillDirection === 'row') {\n col = i % columns;\n row = Math.floor(i / columns);\n } else {\n row = i % rows;\n col = Math.floor(i / rows);\n }\n }\n\n // Base cell position (center of cell)\n let cellCenterX = gridOffsetX + col * (cellWidth + gridConfig.gap) + cellWidth / 2;\n let cellCenterY = gridOffsetY + row * (cellHeight + gridConfig.gap) + cellHeight / 2;\n\n // Apply stagger offset\n if (gridConfig.stagger === 'row' && row % 2 === 1) {\n cellCenterX += cellWidth / 2;\n } else if (gridConfig.stagger === 'column' && col % 2 === 1) {\n cellCenterY += cellHeight / 2;\n }\n\n // Apply overflow offset for stacked images\n if (stackLayer > 0) {\n const patternIndex = (stackLayer - 1) % OVERFLOW_OFFSET_PATTERN.length;\n const pattern = OVERFLOW_OFFSET_PATTERN[patternIndex];\n cellCenterX += pattern.x * overflowOffsetPx;\n cellCenterY += pattern.y * overflowOffsetPx;\n }\n\n // Apply jitter (random offset within cell bounds)\n if (gridConfig.jitter > 0) {\n const maxJitterX = (cellWidth / 2) * gridConfig.jitter;\n const maxJitterY = (cellHeight / 2) * gridConfig.jitter;\n cellCenterX += this.random(-maxJitterX, maxJitterX);\n cellCenterY += this.random(-maxJitterY, maxJitterY);\n }\n\n // Store center position directly (not top-left)\n let x = cellCenterX;\n let y = cellCenterY;\n\n // Handle incomplete row alignment (only for non-overflow mode)\n if (!isOverflowMode && gridConfig.fillDirection === 'row') {\n const itemsInLastRow = imageCount % columns || columns;\n const isLastRow = row === Math.floor((imageCount - 1) / columns);\n\n if (isLastRow && itemsInLastRow < columns) {\n const lastRowWidth = itemsInLastRow * cellWidth + (itemsInLastRow - 1) * gridConfig.gap;\n let alignmentOffset = 0;\n\n if (gridConfig.alignment === 'center') {\n alignmentOffset = (totalGridWidth - lastRowWidth) / 2;\n } else if (gridConfig.alignment === 'end') {\n alignmentOffset = totalGridWidth - lastRowWidth;\n }\n\n x += alignmentOffset;\n }\n }\n\n // Apply variance to create non-uniform look\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const scaledImageSize = imageSize * varianceScale;\n\n // Boundary clamping for center-based positioning\n // Use 1.5 multiplier (3:2 aspect) as reasonable middle ground for mixed portrait/landscape\n const estAspectRatio = 1.5;\n const halfWidth = (scaledImageSize * estAspectRatio) / 2;\n const halfHeight = scaledImageSize / 2;\n const minX = padding + halfWidth;\n const maxX = width - padding - halfWidth;\n const minY = padding + halfHeight;\n const maxY = height - padding - halfHeight;\n\n x = Math.max(minX, Math.min(x, maxX));\n y = Math.max(minY, Math.min(y, maxY));\n\n // Apply rotation when mode is random\n // If jitter > 0, scale rotation by jitter factor; otherwise use full rotation range\n let rotation = 0;\n if (rotationMode === 'random') {\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n if (gridConfig.jitter > 0) {\n // Scale rotation by jitter factor for more subtle effect\n rotation = this.random(minRotation * gridConfig.jitter, maxRotation * gridConfig.jitter);\n } else {\n // Full rotation range even without jitter\n rotation = this.random(minRotation, maxRotation);\n }\n }\n\n // Calculate z-index: in overflow mode, overflow images go BELOW base images\n // Base images get higher z-index so they appear on top\n // Overflow images get progressively lower z-index so they flow under\n let zIndex: number;\n if (isOverflowMode && stackLayer > 0) {\n // Overflow images: lower z-index, decreasing with each layer\n // Layer 1 = 50, Layer 2 = 49, etc.\n zIndex = 50 - stackLayer;\n } else {\n // Base images: higher z-index (100+)\n zIndex = isOverflowMode ? 100 + i : i + 1;\n }\n\n layouts.push({\n id: i,\n x,\n y,\n rotation,\n scale: varianceScale,\n baseSize: scaledImageSize,\n zIndex\n });\n }\n\n return layouts;\n }\n\n /**\n * Calculate optimal grid dimensions based on image count and container\n */\n private calculateGridDimensions(\n imageCount: number,\n availableWidth: number,\n availableHeight: number,\n _baseImageSize: number,\n config: GridAlgorithmConfig\n ): { columns: number; rows: number } {\n let columns: number;\n let rows: number;\n\n if (config.columns !== 'auto' && config.rows !== 'auto') {\n columns = config.columns;\n rows = config.rows;\n } else if (config.columns !== 'auto') {\n columns = config.columns;\n rows = Math.ceil(imageCount / columns);\n } else if (config.rows !== 'auto') {\n rows = config.rows;\n columns = Math.ceil(imageCount / rows);\n } else {\n // Auto-calculate: try to fill the space while maintaining reasonable aspect ratio\n const aspectRatio = availableWidth / availableHeight;\n const imageAspectRatio = 1.4; // Assumed landscape\n\n // Calculate ideal columns based on aspect ratio\n columns = Math.max(1, Math.round(Math.sqrt(imageCount * aspectRatio / imageAspectRatio)));\n rows = Math.ceil(imageCount / columns);\n\n // Ensure we don't have too many empty cells\n while (columns > 1 && (columns - 1) * rows >= imageCount) {\n columns--;\n }\n }\n\n return { columns: Math.max(1, columns), rows: Math.max(1, rows) };\n }\n\n /**\n * Utility: Generate random number between min and max\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * SpiralPlacementGenerator.ts\n * Generates spiral layouts (golden, archimedean, logarithmic)\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, SpiralAlgorithmConfig, ImageConfig } from '../config/types';\n\ninterface SpiralLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\n// Golden angle in radians (~137.5 degrees)\nconst GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));\n\nconst DEFAULT_SPIRAL_CONFIG: SpiralAlgorithmConfig = {\n spiralType: 'golden',\n direction: 'counterclockwise',\n tightness: 1.0,\n scaleDecay: 0,\n startAngle: 0\n};\n\nexport class SpiralPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate spiral layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides (includes fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: SpiralLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n\n const spiralConfig = { ...DEFAULT_SPIRAL_CONFIG, ...this.config.spiral };\n const padding = this.config.spacing.padding;\n // Use fixedHeight if provided, otherwise use base size from config\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Get scale decay from layout config (overrides spiral-specific config)\n const scaleDecay = this.config.scaleDecay ?? spiralConfig.scaleDecay;\n\n // Center of the spiral\n const cx = width / 2;\n const cy = height / 2;\n\n // Calculate available radius (distance from center to edge)\n const maxRadius = Math.min(\n cx - padding - baseImageSize / 2,\n cy - padding - baseImageSize / 2\n );\n\n // Direction multiplier (1 for counterclockwise, -1 for clockwise)\n const directionMultiplier = spiralConfig.direction === 'clockwise' ? -1 : 1;\n\n for (let i = 0; i < imageCount; i++) {\n // Calculate angle based on spiral type\n let angle: number;\n let radius: number;\n\n if (spiralConfig.spiralType === 'golden') {\n // Golden spiral: each point separated by golden angle\n // Creates optimal distribution like sunflower seeds\n angle = i * GOLDEN_ANGLE * directionMultiplier + spiralConfig.startAngle;\n // Radius grows with square root of index for even distribution\n radius = this.calculateGoldenRadius(i, imageCount, maxRadius, spiralConfig.tightness);\n } else if (spiralConfig.spiralType === 'archimedean') {\n // Archimedean spiral: r = a + b*θ (constant spacing between arms)\n const theta = i * 0.5 * spiralConfig.tightness;\n angle = theta * directionMultiplier + spiralConfig.startAngle;\n radius = this.calculateArchimedeanRadius(theta, imageCount, maxRadius, spiralConfig.tightness);\n } else {\n // Logarithmic spiral: r = a * e^(b*θ) (self-similar, appears in nature)\n const theta = i * 0.3 * spiralConfig.tightness;\n angle = theta * directionMultiplier + spiralConfig.startAngle;\n radius = this.calculateLogarithmicRadius(theta, imageCount, maxRadius, spiralConfig.tightness);\n }\n\n // Convert polar to cartesian coordinates (store center position)\n const x = cx + Math.cos(angle) * radius;\n const y = cy + Math.sin(angle) * radius;\n\n // Calculate scale based on decay (center images larger) - use scaleDecay from image config\n const normalizedRadius = radius / maxRadius;\n const decayScale = scaleDecay > 0\n ? 1 - (normalizedRadius * scaleDecay * 0.5) // Max 50% size reduction\n : 1.0;\n\n // Apply variance\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const combinedScale = decayScale * varianceScale;\n\n // Apply scaled image size\n const scaledImageSize = baseImageSize * combinedScale;\n\n // Clamp center positions to keep images within bounds\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (scaledImageSize * estAspectRatio) / 2;\n const halfHeight = scaledImageSize / 2;\n const minX = padding + halfWidth;\n const maxX = width - padding - halfWidth;\n const minY = padding + halfHeight;\n const maxY = height - padding - halfHeight;\n\n const clampedX = Math.max(minX, Math.min(x, maxX));\n const clampedY = Math.max(minY, Math.min(y, maxY));\n\n // Rotation based on mode\n let rotation = 0;\n if (rotationMode === 'random') {\n const baseRotation = (angle * 180 / Math.PI) % 360;\n const rotationVariance = this.random(minRotation, maxRotation);\n rotation = spiralConfig.spiralType === 'golden'\n ? rotationVariance // Pure random for golden (more organic)\n : (baseRotation * 0.1 + rotationVariance * 0.9); // Slight directional bias for others\n } else if (rotationMode === 'tangent') {\n // Tangent rotation: align image to spiral curve\n rotation = this.calculateSpiralTangent(angle, radius, spiralConfig);\n }\n\n // Z-index: center images on top\n const zIndex = imageCount - i;\n\n layouts.push({\n id: i,\n x: clampedX,\n y: clampedY,\n rotation,\n scale: combinedScale,\n baseSize: scaledImageSize,\n zIndex\n });\n }\n\n return layouts;\n }\n\n /**\n * Calculate tangent angle for spiral curve at given position\n * This aligns the image along the spiral's direction of travel\n */\n private calculateSpiralTangent(\n angle: number,\n radius: number,\n spiralConfig: SpiralAlgorithmConfig\n ): number {\n // For different spiral types, the tangent calculation varies\n // The tangent angle is the derivative of the spiral equation\n\n let tangentAngle: number;\n\n if (spiralConfig.spiralType === 'golden') {\n // Golden spiral tangent is approximately perpendicular to radial line\n // Plus a small offset based on the golden angle\n tangentAngle = angle + Math.PI / 2;\n } else if (spiralConfig.spiralType === 'archimedean') {\n // Archimedean spiral: dr/dθ = constant = b\n // tan(ψ) = r / (dr/dθ) = r / b\n // For tightness = 1, b ≈ 1\n const b = 1 / spiralConfig.tightness;\n const psi = Math.atan(radius / b);\n tangentAngle = angle + psi;\n } else {\n // Logarithmic spiral: tangent makes constant angle with radial line\n // This angle depends on the growth rate b\n const b = 0.15 / spiralConfig.tightness;\n const psi = Math.atan(1 / b); // Constant pitch angle\n tangentAngle = angle + psi;\n }\n\n // Convert to degrees and normalize\n const degrees = (tangentAngle * 180 / Math.PI) % 360;\n\n // Adjust so 0 degrees means pointing right\n return degrees - 90;\n }\n\n /**\n * Calculate radius for golden spiral (Vogel's model)\n * Creates even distribution like sunflower seeds\n */\n private calculateGoldenRadius(\n index: number,\n totalImages: number,\n maxRadius: number,\n tightness: number\n ): number {\n // Vogel's model: r = c * sqrt(n)\n // Scaling factor to fit within maxRadius\n const scalingFactor = maxRadius / Math.sqrt(totalImages);\n const radius = scalingFactor * Math.sqrt(index) / tightness;\n return Math.min(radius, maxRadius);\n }\n\n /**\n * Calculate radius for Archimedean spiral\n * r = a + b*θ (constant spacing between arms)\n */\n private calculateArchimedeanRadius(\n theta: number,\n totalImages: number,\n maxRadius: number,\n tightness: number\n ): number {\n // Scale so that the last point is at maxRadius\n const maxTheta = totalImages * 0.5 * tightness;\n const normalizedTheta = theta / maxTheta;\n return normalizedTheta * maxRadius;\n }\n\n /**\n * Calculate radius for logarithmic (equiangular) spiral\n * r = a * e^(b*θ)\n */\n private calculateLogarithmicRadius(\n theta: number,\n totalImages: number,\n maxRadius: number,\n tightness: number\n ): number {\n // Parameters for logarithmic spiral\n const a = maxRadius * 0.05; // Starting radius\n const b = 0.15 / tightness; // Growth rate\n\n const radius = a * Math.exp(b * theta);\n\n // Normalize to fit within maxRadius\n const maxTheta = totalImages * 0.3 * tightness;\n const maxComputedRadius = a * Math.exp(b * maxTheta);\n\n return (radius / maxComputedRadius) * maxRadius;\n }\n\n /**\n * Utility: Generate random number between min and max\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * ClusterPlacementGenerator.ts\n * Generates organic cluster layouts with natural groupings\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, ClusterAlgorithmConfig, ImageConfig } from '../config/types';\n\ninterface ClusterCenter {\n x: number;\n y: number;\n spread: number; // Actual spread for this cluster (may vary if density='varied')\n}\n\ninterface ClusterLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nconst DEFAULT_CLUSTER_CONFIG: ClusterAlgorithmConfig = {\n clusterCount: 'auto',\n clusterSpread: 150,\n clusterSpacing: 200,\n density: 'uniform',\n overlap: 0.3,\n distribution: 'gaussian'\n};\n\nexport class ClusterPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate cluster layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides (includes fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: ClusterLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n\n const clusterConfig = { ...DEFAULT_CLUSTER_CONFIG, ...this.config.cluster };\n const padding = this.config.spacing.padding;\n // Use fixedHeight if provided, otherwise use default 200\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Calculate number of clusters\n const clusterCount = this.calculateClusterCount(\n imageCount,\n clusterConfig.clusterCount,\n width,\n height,\n clusterConfig.clusterSpacing\n );\n\n // Generate cluster centers with spacing constraints\n const clusterCenters = this.generateClusterCenters(\n clusterCount,\n width,\n height,\n padding,\n clusterConfig\n );\n\n // Assign images to clusters (round-robin for even distribution)\n const imagesPerCluster = new Array(clusterCount).fill(0);\n for (let i = 0; i < imageCount; i++) {\n imagesPerCluster[i % clusterCount]++;\n }\n\n let imageIndex = 0;\n\n // Place images in each cluster\n for (let clusterIdx = 0; clusterIdx < clusterCount; clusterIdx++) {\n const cluster = clusterCenters[clusterIdx];\n const imagesInThisCluster = imagesPerCluster[clusterIdx];\n\n for (let i = 0; i < imagesInThisCluster; i++) {\n // Calculate position within cluster\n let offsetX: number;\n let offsetY: number;\n\n if (clusterConfig.distribution === 'gaussian') {\n // Gaussian distribution - most images near center, fewer at edges\n offsetX = this.gaussianRandom() * cluster.spread;\n offsetY = this.gaussianRandom() * cluster.spread;\n } else {\n // Uniform distribution within circle\n const angle = this.random(0, Math.PI * 2);\n const distance = this.random(0, cluster.spread);\n offsetX = Math.cos(angle) * distance;\n offsetY = Math.sin(angle) * distance;\n }\n\n // Apply overlap factor - pulls images closer to center and increases size\n const overlapMultiplier = 1 + clusterConfig.overlap * 0.5;\n const sizeMultiplier = 1 + clusterConfig.overlap * 0.3;\n\n // Reduce offset based on overlap (images cluster tighter)\n offsetX /= overlapMultiplier;\n offsetY /= overlapMultiplier;\n\n // Apply variance\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const combinedScale = sizeMultiplier * varianceScale;\n\n // Calculate image size with overlap factor and variance\n const imageSize = baseImageSize * combinedScale;\n\n // Store center position (not top-left)\n let x = cluster.x + offsetX;\n let y = cluster.y + offsetY;\n\n // Boundary clamping with center position\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (imageSize * estAspectRatio) / 2;\n const halfHeight = imageSize / 2;\n x = Math.max(padding + halfWidth, Math.min(x, width - padding - halfWidth));\n y = Math.max(padding + halfHeight, Math.min(y, height - padding - halfHeight));\n\n // Rotation - more variance for organic feel (only when mode is random)\n const rotation = rotationMode === 'random' ? this.random(minRotation, maxRotation) : 0;\n\n // Z-index: images closer to cluster center are on top\n const distanceFromCenter = Math.sqrt(offsetX * offsetX + offsetY * offsetY);\n const normalizedDistance = distanceFromCenter / cluster.spread;\n const zIndex = Math.round((1 - normalizedDistance) * 50) + 1;\n\n layouts.push({\n id: imageIndex,\n x,\n y,\n rotation,\n scale: combinedScale,\n baseSize: imageSize,\n zIndex\n });\n\n imageIndex++;\n }\n }\n\n return layouts;\n }\n\n /**\n * Calculate optimal number of clusters based on image count and container\n */\n private calculateClusterCount(\n imageCount: number,\n configCount: number | 'auto',\n width: number,\n height: number,\n clusterSpacing: number\n ): number {\n if (configCount !== 'auto') {\n return Math.max(1, Math.min(configCount, imageCount));\n }\n\n // Auto-calculate based on container size and image count\n // Aim for 5-15 images per cluster\n const idealImagesPerCluster = 8;\n const countByImages = Math.max(1, Math.ceil(imageCount / idealImagesPerCluster));\n\n // Also consider container size - how many clusters can fit\n const countBySpace = Math.floor(\n (width / clusterSpacing) * (height / clusterSpacing) * 0.6\n );\n\n return Math.max(1, Math.min(countByImages, countBySpace, 10));\n }\n\n /**\n * Generate cluster center positions with spacing constraints\n */\n private generateClusterCenters(\n count: number,\n width: number,\n height: number,\n padding: number,\n config: ClusterAlgorithmConfig\n ): ClusterCenter[] {\n const centers: ClusterCenter[] = [];\n const maxAttempts = 100;\n\n // Available area for cluster centers\n const minX = padding + config.clusterSpread;\n const maxX = width - padding - config.clusterSpread;\n const minY = padding + config.clusterSpread;\n const maxY = height - padding - config.clusterSpread;\n\n for (let i = 0; i < count; i++) {\n let bestCandidate: ClusterCenter | null = null;\n let bestMinDistance = -1;\n\n // Try multiple random positions and pick the one with best spacing\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const candidate = {\n x: this.random(minX, maxX),\n y: this.random(minY, maxY),\n spread: this.calculateClusterSpread(config)\n };\n\n // Calculate minimum distance to existing clusters\n let minDistance = Infinity;\n for (const existing of centers) {\n const dx = candidate.x - existing.x;\n const dy = candidate.y - existing.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n minDistance = Math.min(minDistance, distance);\n }\n\n // First cluster or better spacing\n if (centers.length === 0 || minDistance > bestMinDistance) {\n bestCandidate = candidate;\n bestMinDistance = minDistance;\n }\n\n // Good enough spacing found\n if (minDistance >= config.clusterSpacing) {\n break;\n }\n }\n\n if (bestCandidate) {\n centers.push(bestCandidate);\n }\n }\n\n return centers;\n }\n\n /**\n * Calculate spread for a cluster (may vary if density='varied')\n */\n private calculateClusterSpread(config: ClusterAlgorithmConfig): number {\n if (config.density === 'uniform') {\n return config.clusterSpread;\n }\n\n // Varied density: spread varies between 50% and 150% of config value\n return config.clusterSpread * this.random(0.5, 1.5);\n }\n\n /**\n * Generate a random number with approximately Gaussian distribution\n * Using Box-Muller transform\n */\n private gaussianRandom(): number {\n let u = 0, v = 0;\n while (u === 0) u = Math.random();\n while (v === 0) v = Math.random();\n\n const value = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);\n\n // Clamp to reasonable range (-3 to 3 std deviations)\n return Math.max(-3, Math.min(3, value)) / 3;\n }\n\n /**\n * Utility: Generate random number between min and max\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * WavePlacementGenerator.ts\n * Generates wave-based layouts for image cloud\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, ImageConfig } from '../config/types';\nimport { DEFAULT_WAVE_CONFIG } from '../config/defaults';\n\ninterface WaveLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nexport class WavePlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate wave layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: WaveLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n // Use fixedHeight if provided, otherwise use default 200\n const baseImageSize = options.fixedHeight ?? 200;\n const padding = this.config.spacing.padding ?? 50;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Use override fixedHeight if provided, else baseImageSize\n const imageSize = options.fixedHeight ?? baseImageSize;\n\n // Get wave configuration, merging user config with defaults\n const waveConfig = {\n ...DEFAULT_WAVE_CONFIG,\n ...this.config.wave\n };\n\n const { rows, amplitude, frequency, phaseShift, synchronization } = waveConfig;\n\n // Calculate images per row (distribute evenly)\n const imagesPerRow = Math.ceil(imageCount / rows);\n\n // Estimate image width based on height and typical aspect ratio\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const estImageWidth = imageSize * estAspectRatio;\n const halfImageWidth = estImageWidth / 2;\n\n // Calculate available horizontal space (accounting for image width at edges)\n // This ensures images don't get compressed against the edges\n const startX = padding + halfImageWidth;\n const endX = width - padding - halfImageWidth;\n const availableWidth = endX - startX;\n\n // Distribute images evenly between startX and endX\n const horizontalSpacing = imagesPerRow > 1 ? availableWidth / (imagesPerRow - 1) : 0;\n\n // Calculate vertical distribution to fill the screen\n // Row centerlines need room for amplitude swing above and below\n const minCenterY = padding + amplitude + (imageSize / 2);\n const maxCenterY = height - padding - amplitude - (imageSize / 2);\n const availableHeight = maxCenterY - minCenterY;\n\n // Calculate spacing between row centerlines\n const rowSpacing = rows > 1 ? availableHeight / (rows - 1) : 0;\n\n let imageIndex = 0;\n\n for (let rowIndex = 0; rowIndex < rows && imageIndex < imageCount; rowIndex++) {\n // Calculate base Y position (centerline) for this row - evenly distributed\n const baseY = rows === 1\n ? (minCenterY + maxCenterY) / 2 // Single row: center vertically\n : minCenterY + (rowIndex * rowSpacing);\n\n // Calculate phase offset based on synchronization mode\n let phase = 0;\n if (synchronization === 'offset') {\n phase = rowIndex * phaseShift;\n } else if (synchronization === 'alternating') {\n phase = rowIndex * Math.PI;\n }\n // If 'synchronized', phase remains 0\n\n // Place images along this wave row\n for (let imgInRow = 0; imgInRow < imagesPerRow && imageIndex < imageCount; imgInRow++) {\n // Calculate center position - evenly distributed within available space\n // For single image, center it; otherwise distribute from startX to endX\n const centerX = imagesPerRow === 1\n ? (startX + endX) / 2\n : startX + (imgInRow * horizontalSpacing);\n\n // Calculate wave displacement based on center position\n const waveY = this.calculateWaveY(centerX, width, amplitude, frequency, phase);\n\n // Store center position directly (CSS transform will handle centering)\n const x = centerX;\n const y = baseY + waveY;\n\n // Apply variance\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const scaledImageSize = imageSize * varianceScale;\n\n // Calculate rotation based on image.rotation.mode\n let rotation = 0;\n if (rotationMode === 'tangent') {\n // Follow wave tangent - images rotate to align with wave curve\n rotation = this.calculateRotation(centerX, width, amplitude, frequency, phase);\n } else if (rotationMode === 'random') {\n // Random rotation within configured range\n rotation = this.random(minRotation, maxRotation);\n }\n // If mode is 'none', rotation stays 0\n\n // Clamp center positions to keep images within bounds\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (scaledImageSize * estAspectRatio) / 2;\n const halfHeight = scaledImageSize / 2;\n const minX = padding + halfWidth;\n const maxX = width - padding - halfWidth;\n const minY = padding + halfHeight;\n const maxY = height - padding - halfHeight;\n\n layouts.push({\n id: imageIndex,\n x: Math.max(minX, Math.min(x, maxX)),\n y: Math.max(minY, Math.min(y, maxY)),\n rotation,\n scale: varianceScale,\n baseSize: scaledImageSize,\n zIndex: imageIndex + 1\n });\n\n imageIndex++;\n }\n }\n\n return layouts;\n }\n\n /**\n * Calculate Y position displacement on wave curve\n * @param x - Horizontal position\n * @param containerWidth - Container width\n * @param amplitude - Wave amplitude\n * @param frequency - Wave frequency\n * @param phase - Phase offset\n * @returns Y displacement from baseline\n */\n private calculateWaveY(\n x: number,\n containerWidth: number,\n amplitude: number,\n frequency: number,\n phase: number\n ): number {\n // Normalize x to [0, 1] range\n const normalizedX = x / containerWidth;\n\n // Calculate wave: y = amplitude * sin(frequency * x * 2π + phase)\n return amplitude * Math.sin(frequency * normalizedX * 2 * Math.PI + phase);\n }\n\n /**\n * Calculate rotation based on wave tangent\n * @param x - Horizontal position\n * @param containerWidth - Container width\n * @param amplitude - Wave amplitude\n * @param frequency - Wave frequency\n * @param phase - Phase offset\n * @returns Rotation angle in degrees\n */\n private calculateRotation(\n x: number,\n containerWidth: number,\n amplitude: number,\n frequency: number,\n phase: number\n ): number {\n // Normalize x to [0, 1] range\n const normalizedX = x / containerWidth;\n\n // Derivative of sine wave: amplitude * frequency * cos(frequency * x * 2π + phase)\n const derivative = amplitude * frequency * 2 * Math.PI * Math.cos(frequency * normalizedX * 2 * Math.PI + phase) / containerWidth;\n\n // Convert derivative to rotation angle in degrees\n return Math.atan(derivative) * (180 / Math.PI);\n }\n\n /**\n * Estimate image width based on height\n /**\n * Utility: Generate random number between min and max\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Random number in range\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * LayoutEngine.ts\n * Generates layouts for image cloud using placement strategies\n *\n * Public API:\n * - generateLayout(imageCount, containerBounds, options)\n * - getOriginalState(imageId)\n * - reset()\n * - updateConfig(newConfig)\n */\n\nimport type { LayoutConfig, ImageLayout, ContainerBounds, PlacementGenerator, AdaptiveSizingResult, ImageConfig, FixedModeHeight, ResponsiveBreakpoints } from '../config/types';\nimport { RandomPlacementGenerator } from '../generators/RandomPlacementGenerator';\nimport { RadialPlacementGenerator } from '../generators/RadialPlacementGenerator';\nimport { GridPlacementGenerator } from '../generators/GridPlacementGenerator';\nimport { SpiralPlacementGenerator } from '../generators/SpiralPlacementGenerator';\nimport { ClusterPlacementGenerator } from '../generators/ClusterPlacementGenerator';\nimport { WavePlacementGenerator } from '../generators/WavePlacementGenerator';\n\nexport interface LayoutEngineConfig {\n layout: LayoutConfig;\n image: ImageConfig;\n}\n\nexport class LayoutEngine {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n private layouts: Map<number, ImageLayout>;\n private generator: PlacementGenerator;\n\n constructor(config: LayoutEngineConfig) {\n this.config = config.layout;\n this.imageConfig = config.image;\n\n this.layouts = new Map(); // Store original states by image ID\n\n // Initialize generator strategy\n this.generator = this.initGenerator();\n }\n\n /**\n * Initialize the appropriate generator based on config type\n * @returns Initialized placement generator\n */\n private initGenerator(): PlacementGenerator {\n switch (this.config.algorithm) {\n case 'radial':\n return new RadialPlacementGenerator(this.config, this.imageConfig);\n case 'grid':\n return new GridPlacementGenerator(this.config, this.imageConfig);\n case 'spiral':\n return new SpiralPlacementGenerator(this.config, this.imageConfig);\n case 'cluster':\n return new ClusterPlacementGenerator(this.config, this.imageConfig);\n case 'wave':\n return new WavePlacementGenerator(this.config, this.imageConfig);\n case 'random':\n default:\n return new RandomPlacementGenerator(this.config, this.imageConfig);\n }\n }\n\n /**\n * Generate layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides for configuration (e.g. fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generateLayout(imageCount: number, containerBounds: ContainerBounds, options: Partial<LayoutConfig> = {}): ImageLayout[] {\n const layouts = this.generator.generate(imageCount, containerBounds, options);\n\n // Store layouts for state retrieval\n layouts.forEach(layout => {\n this.layouts.set(layout.id, layout);\n });\n\n return layouts;\n }\n\n /**\n * Get the original layout state for an image\n * @param imageId - The image ID (number or string)\n * @returns Original layout state or undefined if not found\n */\n getOriginalState(imageId: number | string): ImageLayout | undefined {\n return this.layouts.get(Number(imageId));\n }\n\n /**\n * Reset all stored layouts\n */\n reset(): void {\n this.layouts.clear();\n }\n\n /**\n * Update config dynamically (useful for responsive changes)\n * @param newConfig - Updated configuration\n */\n updateConfig(newConfig: Partial<LayoutEngineConfig>): void {\n // Update layout config\n if (newConfig.layout) {\n Object.assign(this.config, newConfig.layout);\n\n // Reinitialize generator if algorithm changed\n if (newConfig.layout.algorithm && newConfig.layout.algorithm !== this.config.algorithm) {\n this.generator = this.initGenerator();\n }\n }\n\n // Update image config\n if (newConfig.image) {\n Object.assign(this.imageConfig, newConfig.image);\n }\n }\n\n /**\n * Get responsive breakpoints from layout config\n */\n private getBreakpoints(): ResponsiveBreakpoints {\n return this.config.responsive ?? {\n mobile: { maxWidth: 767 },\n tablet: { maxWidth: 1199 }\n };\n }\n\n /**\n * Resolve breakpoint name based on viewport width\n */\n resolveBreakpoint(viewportWidth: number): 'mobile' | 'tablet' | 'screen' {\n const breakpoints = this.getBreakpoints();\n\n if (viewportWidth <= breakpoints.mobile.maxWidth) {\n return 'mobile';\n }\n if (viewportWidth <= breakpoints.tablet.maxWidth) {\n return 'tablet';\n }\n return 'screen';\n }\n\n /**\n * Resolve the effective base height based on image config and current viewport\n * @param viewportWidth - Current viewport width\n * @returns Resolved base height or undefined if should auto-calculate (adaptive mode)\n */\n resolveBaseHeight(viewportWidth: number): number | undefined {\n const sizing = this.imageConfig.sizing;\n\n // If mode is adaptive (or not set), return undefined to signal auto-calculation\n if (!sizing || sizing.mode === 'adaptive') {\n return undefined;\n }\n\n // Fixed or responsive mode - use the height property\n const height = sizing.height;\n\n if (height === undefined) {\n return undefined; // No height specified, fall back to adaptive\n }\n\n if (typeof height === 'number') {\n return height;\n }\n\n // Responsive height for fixed mode\n const responsiveHeight = height as FixedModeHeight;\n const breakpoint = this.resolveBreakpoint(viewportWidth);\n\n // Fallback chain: specific breakpoint -> higher breakpoints -> any defined\n if (breakpoint === 'mobile') {\n return responsiveHeight.mobile ?? responsiveHeight.tablet ?? responsiveHeight.screen;\n }\n if (breakpoint === 'tablet') {\n return responsiveHeight.tablet ?? responsiveHeight.screen ?? responsiveHeight.mobile;\n }\n // screen\n return responsiveHeight.screen ?? responsiveHeight.tablet ?? responsiveHeight.mobile;\n }\n\n /**\n * Calculate adaptive image size based on container dimensions and image count\n * @param containerBounds - Container dimensions {width, height}\n * @param imageCount - Number of images to display\n * @param maxHeight - Maximum height constraint (upper bound)\n * @param viewportWidth - Current viewport width for baseHeight resolution\n * @returns Calculated sizing result with height\n */\n calculateAdaptiveSize(\n containerBounds: ContainerBounds,\n imageCount: number,\n maxHeight: number,\n viewportWidth: number\n ): AdaptiveSizingResult {\n const sizing = this.imageConfig.sizing;\n\n // Check if user specified a fixed height in image config\n const userBaseHeight = this.resolveBaseHeight(viewportWidth);\n\n // If user specified baseHeight (fixed/responsive mode), use it directly\n // Don't clamp to maxHeight since user explicitly chose this value\n if (userBaseHeight !== undefined) {\n return { height: userBaseHeight };\n }\n\n // Adaptive mode - auto-calculate based on container and image count\n const minSize = sizing?.minSize ?? 50;\n const maxSize = sizing?.maxSize ?? 400;\n const targetCoverage = this.config.targetCoverage ?? 0.6;\n const densityFactor = this.config.densityFactor ?? 1.0;\n\n const { width, height } = containerBounds;\n\n // Calculate area-based optimal size\n const containerArea = width * height;\n const targetArea = containerArea * targetCoverage;\n const areaPerImage = targetArea / imageCount;\n\n // Calculate height from area assuming 1.4 aspect ratio (landscape images)\n const aspectRatio = 1.4;\n let calculatedHeight = Math.sqrt(areaPerImage / aspectRatio);\n\n // Apply density factor\n calculatedHeight *= densityFactor;\n\n // Clamp to maximum height constraint\n calculatedHeight = Math.min(calculatedHeight, maxHeight);\n\n // Apply min/max constraints\n let finalHeight = this.clamp(calculatedHeight, minSize, maxSize);\n\n // 'minimize' behavior: force fit below minimum if needed (0.05 floor)\n if (finalHeight === minSize && calculatedHeight < minSize) {\n // Minimum floor is 5% of calculated size\n const floor = Math.max(minSize * 0.05, 20);\n finalHeight = Math.max(floor, calculatedHeight);\n }\n\n return { height: finalHeight };\n }\n\n /**\n * Utility: Clamp a value between min and max\n */\n private clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n }\n}","/**\n * Type definitions for Image Gallery Library\n */\n\n// ============================================================================\n// Core Data Types\n// ============================================================================\n\nexport interface ImageLayout {\n id: number;\n x: number;\n y: number;\n rotation: number;\n scale: number;\n baseSize: number;\n zIndex?: number;\n borderColor?: string;\n}\n\nexport interface ContainerBounds {\n width: number;\n height: number;\n}\n\nexport interface ResponsiveHeight {\n minWidth: number;\n height: number;\n}\n\nexport interface TransformParams {\n x?: number;\n y?: number;\n rotation?: number;\n scale?: number;\n}\n\n// ============================================================================\n// Loader Configuration\n// ============================================================================\n\nexport type GoogleDriveSourceType = 'folder' | 'files';\n\nexport interface GoogleDriveFolderSource {\n type: 'folder';\n folders: string[];\n recursive?: boolean;\n}\n\nexport interface GoogleDriveFilesSource {\n type: 'files';\n files: string[];\n}\n\nexport type GoogleDriveSource = GoogleDriveFolderSource | GoogleDriveFilesSource;\n\nexport interface GoogleDriveLoaderConfig {\n apiKey: string;\n sources: GoogleDriveSource[];\n apiEndpoint?: string;\n allowedExtensions?: string[];\n debugLogging?: boolean;\n}\n\nexport type StaticSourceType = 'urls' | 'path';\n\nexport interface StaticSource {\n type: StaticSourceType;\n urls?: string[];\n basePath?: string;\n files?: string[];\n}\n\nexport interface StaticLoaderConfig {\n sources: StaticSource[];\n validateUrls?: boolean;\n validationTimeout?: number;\n validationMethod?: 'head' | 'simple' | 'none';\n failOnAllMissing?: boolean;\n allowedExtensions?: string[];\n debugLogging?: boolean;\n}\n\nexport interface CompositeLoaderConfigJson {\n loaders: LoaderConfig[];\n debugLogging?: boolean;\n}\n\nexport interface LoaderConfig {\n type: 'googleDrive' | 'static' | 'composite';\n googleDrive?: GoogleDriveLoaderConfig;\n static?: StaticLoaderConfig;\n composite?: CompositeLoaderConfigJson;\n}\n\n// ============================================================================\n// Image Configuration (sizing and rotation)\n// ============================================================================\n\n/**\n * Sizing mode:\n * - 'fixed': single explicit height for all breakpoints\n * - 'responsive': different heights per breakpoint (mobile/tablet/screen)\n * - 'adaptive': auto-calculates based on container size and image count\n */\nexport type SizingMode = 'fixed' | 'responsive' | 'adaptive';\n\n/**\n * Fixed mode height configuration with responsive breakpoints\n * At least one of mobile, tablet, or screen is required\n */\nexport interface FixedModeHeight {\n mobile?: number; // Height for mobile viewports (< responsive.mobile.maxWidth)\n tablet?: number; // Height for tablet viewports (< responsive.tablet.maxWidth)\n screen?: number; // Height for desktop/large screens (>= responsive.tablet.maxWidth)\n}\n\n/**\n * Legacy responsive base height configuration (kept for backward compatibility)\n */\nexport interface ResponsiveBaseHeight {\n default: number; // Base height for large screens\n tablet?: number; // Height for tablet\n mobile?: number; // Height for mobile\n}\n\n/**\n * Image variance configuration\n * Controls random size variation applied to images\n */\nexport interface ImageVarianceConfig {\n min: number; // 0.25-1 (e.g., 0.8)\n max: number; // 1-1.75 (e.g., 1.2)\n}\n\n/**\n * Image sizing configuration\n */\nexport interface ImageSizingConfig {\n mode: SizingMode; // Required: 'fixed', 'responsive', or 'adaptive'\n height?: number | FixedModeHeight; // Fixed/responsive mode: explicit heights\n minSize?: number; // Adaptive mode only: minimum image height (default: 50)\n maxSize?: number; // Adaptive mode only: maximum image height (default: 400)\n variance?: ImageVarianceConfig; // Size variance (min: 0.25-1, max: 1-1.75)\n}\n\n/**\n * Image rotation mode\n */\nexport type ImageRotationMode = 'none' | 'random' | 'tangent';\n\n/**\n * Image rotation range configuration\n */\nexport interface ImageRotationRange {\n min: number; // Negative degrees (-180 to 0)\n max: number; // Positive degrees (0 to 180)\n}\n\n/**\n * Image rotation configuration\n */\nexport interface ImageRotationConfig {\n mode: ImageRotationMode; // default: 'none'\n range?: ImageRotationRange; // Range for random mode\n}\n\n/**\n * Combined image configuration\n */\nexport interface ImageConfig {\n sizing?: ImageSizingConfig;\n rotation?: ImageRotationConfig;\n}\n\n// ============================================================================\n// Layout Configuration\n// ============================================================================\n\nexport interface AdaptiveSizingResult {\n height: number; // Calculated image height\n}\n\n/**\n * Responsive breakpoints configuration for layout\n * Defines viewport width thresholds for mobile and tablet\n */\nexport interface ResponsiveBreakpoints {\n mobile: { maxWidth: number }; // Default: 767\n tablet: { maxWidth: number }; // Default: 1199\n // screen is implicitly > tablet.maxWidth\n}\n\n// Legacy responsive height uses the existing ResponsiveHeight interface (minWidth, height)\n\nexport interface LayoutSpacingConfig {\n padding: number;\n minGap: number;\n}\n\n// Legacy interface kept for backward compatibility in LayoutEngine\nexport interface LegacyLayoutRotationConfig {\n enabled: boolean;\n range: {\n min: number;\n max: number;\n };\n}\n\n// ============================================================================\n// Algorithm-Specific Configuration\n// ============================================================================\n\nexport interface GridAlgorithmConfig {\n columns: number | 'auto';\n rows: number | 'auto';\n stagger: 'none' | 'row' | 'column';\n jitter: number;\n overlap: number;\n fillDirection: 'row' | 'column';\n alignment: 'start' | 'center' | 'end';\n gap: number;\n overflowOffset: number; // 0-0.5, percentage of cell size for stacking overflow images (default: 0.25)\n}\n\nexport interface SpiralAlgorithmConfig {\n spiralType: 'golden' | 'archimedean' | 'logarithmic';\n direction: 'clockwise' | 'counterclockwise';\n tightness: number;\n scaleDecay: number;\n startAngle: number;\n}\n\nexport interface ClusterAlgorithmConfig {\n clusterCount: number | 'auto';\n clusterSpread: number;\n clusterSpacing: number;\n density: 'uniform' | 'varied';\n overlap: number;\n distribution: 'gaussian' | 'uniform';\n}\n\nexport interface WaveAlgorithmConfig {\n rows: number;\n amplitude: number;\n frequency: number;\n phaseShift: number;\n synchronization: 'offset' | 'synchronized' | 'alternating';\n // Note: Image rotation along wave is now controlled via image.rotation.mode = 'tangent'\n}\n\nexport type LayoutAlgorithm = 'random' | 'radial' | 'grid' | 'spiral' | 'cluster' | 'wave';\n\nexport interface LayoutConfig {\n algorithm: LayoutAlgorithm;\n spacing: LayoutSpacingConfig;\n scaleDecay?: number; // For Radial/Spiral - progressive size reduction (0-1, default: 0)\n responsive?: ResponsiveBreakpoints; // Viewport width breakpoints (mobile/tablet)\n targetCoverage?: number; // 0-1, for adaptive sizing (default: 0.6)\n densityFactor?: number; // Controls center point spacing (default: 1.0)\n debugRadials?: boolean;\n debugCenters?: boolean; // Show markers at calculated image center positions\n grid?: GridAlgorithmConfig;\n spiral?: SpiralAlgorithmConfig;\n cluster?: ClusterAlgorithmConfig;\n wave?: WaveAlgorithmConfig;\n}\n\n// ============================================================================\n// Animation Configuration\n// ============================================================================\n\n// Entry Path Types\nexport type EntryPathType = 'linear' | 'arc' | 'bounce' | 'elastic' | 'wave';\n\nexport interface BouncePathConfig {\n overshoot: number; // 0.1-0.3, how far past target (default: 0.15)\n bounces: 1 | 2 | 3; // number of bounces before settling (default: 1)\n decayRatio: number; // 0.3-0.7, each bounce is this % of previous (default: 0.5)\n}\n\nexport type BouncePreset = 'energetic' | 'playful' | 'subtle';\n\nexport interface ElasticPathConfig {\n stiffness: number; // 100-500, higher = faster oscillation (default: 200)\n damping: number; // 10-50, higher = fewer oscillations (default: 20)\n mass: number; // 0.5-3, higher = slower, more momentum (default: 1)\n oscillations: number; // 2-5, visible oscillation count (default: 3)\n}\n\nexport type ElasticPreset = 'gentle' | 'bouncy' | 'wobbly' | 'snappy';\n\nexport interface WavePathConfig {\n amplitude: number; // 20-100px, wave height (default: 40)\n frequency: number; // 1-4, number of complete waves (default: 2)\n decay: boolean; // true = wave diminishes toward target (default: true)\n decayRate: number; // 0.5-1, how fast amplitude decreases (default: 0.8)\n phase: number; // 0-2π, starting phase offset (default: 0)\n}\n\nexport type WavePathPreset = 'gentle' | 'playful' | 'serpentine' | 'flutter';\n\nexport interface EntryPathConfig {\n type: EntryPathType;\n // Preset shortcuts (type-specific)\n bouncePreset?: BouncePreset;\n elasticPreset?: ElasticPreset;\n wavePreset?: WavePathPreset;\n // Type-specific detailed configs (override presets)\n bounce?: Partial<BouncePathConfig>;\n elastic?: Partial<ElasticPathConfig>;\n wave?: Partial<WavePathConfig>;\n}\n\n// Entry Rotation Types\nexport type EntryRotationMode = 'none' | 'settle' | 'spin' | 'wobble' | 'random';\n\nexport interface EntryRotationConfig {\n mode: EntryRotationMode;\n startRotation?: number | { min: number; max: number }; // For 'settle' mode\n spinCount?: number; // For 'spin' mode (default: 1)\n direction?: 'clockwise' | 'counterclockwise' | 'auto' | 'random'; // For 'spin' mode\n wobble?: {\n amplitude: number; // degrees of oscillation (default: 15)\n frequency: number; // oscillations during animation (default: 3)\n decay: boolean; // whether oscillation diminishes (default: true)\n };\n}\n\n// Entry Scale Types\nexport type EntryScaleMode = 'none' | 'grow' | 'shrink' | 'pop' | 'random';\n\nexport interface EntryScalePopConfig {\n overshoot: number; // How much to overshoot final scale (1.1-1.5, default: 1.2)\n bounces: number; // Number of bounces before settling (1-3, default: 1)\n}\n\nexport interface EntryScaleConfig {\n mode: EntryScaleMode;\n startScale?: number; // Fixed start scale for grow/shrink (0.1-4.0)\n range?: { min: number; max: number }; // Random range for 'random' mode\n pop?: EntryScalePopConfig; // Config for 'pop' mode\n}\n\nexport type EntryStartPosition =\n | 'nearest-edge'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'center'\n | 'random-edge'\n | 'circular';\n\nexport interface EntryCircularConfig {\n radius?: number | string; // pixels or percentage like '120%', default: '120%' of container diagonal\n distribution?: 'even' | 'random'; // default: 'even'\n}\n\nexport interface EntryStartConfig {\n position: EntryStartPosition;\n offset?: number; // pixels beyond edge, default: 100\n circular?: EntryCircularConfig;\n}\n\nexport interface EntryTimingConfig {\n duration: number; // default: 600ms\n stagger: number; // default: 150ms\n}\n\nexport interface EntryAnimationConfig {\n start: EntryStartConfig;\n timing: EntryTimingConfig;\n easing: string; // CSS easing, default: 'cubic-bezier(0.25, 1, 0.5, 1)'\n path?: EntryPathConfig; // Animation path type (linear, bounce, elastic, wave)\n rotation?: EntryRotationConfig; // Entry rotation animation\n scale?: EntryScaleConfig; // Entry scale animation\n}\n\nexport interface AnimationEasingConfig {\n default: string;\n bounce: string;\n focus: string;\n}\n\nexport interface AnimationQueueConfig {\n enabled: boolean;\n interval: number;\n maxConcurrent?: number;\n}\n\nexport interface AnimationPerformanceConfig {\n useGPU?: boolean;\n reduceMotion?: boolean;\n}\n\nexport interface AnimationConfig {\n duration: number;\n easing: AnimationEasingConfig;\n queue: AnimationQueueConfig;\n performance?: AnimationPerformanceConfig;\n entry?: EntryAnimationConfig;\n}\n\n// ============================================================================\n// Interaction Configuration\n// ============================================================================\n\nexport interface FocusInteractionConfig {\n scalePercent: number; // Percentage of container (0-1 as fraction, 1-100 as percent)\n zIndex: number;\n animationDuration?: number;\n}\n\nexport interface NavigationInteractionConfig {\n keyboard?: boolean;\n swipe?: boolean;\n mouseWheel?: boolean;\n}\n\nexport interface GestureInteractionConfig {\n pinchToZoom?: boolean;\n doubleTapToFocus?: boolean;\n}\n\nexport interface InteractionConfig {\n focus: FocusInteractionConfig;\n navigation?: NavigationInteractionConfig;\n gestures?: GestureInteractionConfig;\n}\n\n// ============================================================================\n// Rendering Configuration\n// ============================================================================\n\nexport interface ResponsiveRenderingConfig {\n breakpoints: {\n mobile: number;\n tablet?: number;\n desktop?: number;\n };\n mobileDetection: () => boolean;\n}\n\nexport interface UIRenderingConfig {\n showLoadingSpinner: boolean;\n showImageCounter?: boolean;\n showThumbnails?: boolean;\n theme?: 'light' | 'dark' | 'auto';\n}\n\nexport interface PerformanceRenderingConfig {\n lazyLoad?: boolean;\n preloadCount?: number;\n imageQuality?: 'auto' | 'high' | 'medium' | 'low';\n}\n\nexport interface RenderingConfig {\n responsive: ResponsiveRenderingConfig;\n ui: UIRenderingConfig;\n performance?: PerformanceRenderingConfig;\n}\n\n// ============================================================================\n// Main Gallery Configuration\n// ============================================================================\n\nexport interface ImageCloudConfig {\n loader: LoaderConfig;\n image: ImageConfig;\n layout: LayoutConfig;\n animation: AnimationConfig;\n interaction: InteractionConfig;\n rendering: RenderingConfig;\n styling?: ImageStylingConfig;\n debug: boolean;\n}\n\n// Backwards compatibility alias\nexport type GalleryConfig = ImageCloudConfig;\n\nexport interface ImageCloudOptions {\n container?: string | HTMLElement;\n loader?: Partial<LoaderConfig>;\n image?: Partial<ImageConfig>;\n layout?: Partial<LayoutConfig>;\n animation?: Partial<AnimationConfig>;\n interaction?: Partial<InteractionConfig>;\n rendering?: Partial<RenderingConfig>;\n styling?: Partial<ImageStylingConfig>;\n debug?: boolean;\n}\n\n// Backwards compatibility alias\nexport type ImageGalleryOptions = ImageCloudOptions;\n\n// ============================================================================\n// Legacy Configuration Types (for backward compatibility)\n// ============================================================================\n\nexport interface LegacyLayoutConfig {\n baseImageSize?: number;\n rotationRange?: { min: number; max: number };\n type?: 'random' | 'radial';\n debugRadials?: boolean;\n sizeVarianceMin?: number;\n sizeVarianceMax?: number;\n responsiveHeights?: ResponsiveHeight[];\n padding?: number;\n minSpacing?: number;\n minRotation?: number;\n maxRotation?: number;\n}\n\nexport interface LegacyAnimationConfig {\n duration?: number;\n queueInterval?: number;\n easing?: string;\n bounceEasing?: string;\n}\n\nexport interface LegacyZoomConfig {\n focusScale?: number;\n mobileScale?: number; // Deprecated: use scaleTo/scalePercent instead\n focusZIndex?: number;\n}\n\nexport interface LegacyConfig {\n layout?: LegacyLayoutConfig;\n animation?: LegacyAnimationConfig;\n zoom?: LegacyZoomConfig;\n debugLogging?: boolean;\n googleDrive?: {\n apiKey?: string;\n apiEndpoint?: string;\n imageExtensions?: string[];\n };\n loader?: {\n type?: 'googleDrive' | 'static';\n static?: StaticLoaderConfig;\n };\n breakpoints?: {\n mobile?: number;\n tablet?: number;\n desktop?: number;\n };\n isMobile?: () => boolean;\n ui?: {\n showLoadingSpinner?: boolean;\n };\n}\n\nexport interface LegacyImageGalleryOptions {\n containerId?: string;\n loaderType?: 'googleDrive' | 'static';\n folderUrl?: string;\n googleDrive?: {\n apiKey?: string;\n };\n staticLoader?: StaticLoaderConfig;\n config?: LegacyConfig;\n}\n\n// ============================================================================\n// Interface Dependencies\n// ============================================================================\n\nexport interface PlacementGenerator {\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options?: Partial<LayoutConfig>\n ): ImageLayout[];\n}\n\n/**\n * ImageFilter interface for filtering images by extension\n * Implemented by the ImageFilter class in loaders/ImageFilter.ts\n */\nexport interface IImageFilter {\n isAllowed(filename: string): boolean;\n getAllowedExtensions(): string[];\n}\n\n/**\n * ImageLoader interface with consistent lifecycle pattern:\n * 1. Constructor - Initialize with required parameters, throw if missing\n * 2. prepare(filter) - Async discovery of images, accepts filter\n * 3. imagesLength() - Return count of images (after prepare)\n * 4. imageURLs() - Return ordered list of URLs (after prepare)\n */\nexport interface ImageLoader {\n /**\n * Async preparation - discovers images and applies filter\n * Succeeds even if 0 images found (gallery handles empty state)\n * @param filter - Filter to apply to discovered images\n */\n prepare(filter: IImageFilter): Promise<void>;\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare() completes\n */\n imagesLength(): number;\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare() completes\n */\n imageURLs(): string[];\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean;\n}\n\nexport interface GoogleDriveFile {\n id: string;\n name: string;\n mimeType: string;\n parents?: string[];\n}\n\nexport interface GoogleDriveResponse {\n files: GoogleDriveFile[];\n nextPageToken?: string;\n}\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\n// ============================================================================\n// Image Styling Configuration\n// ============================================================================\n\nexport type ShadowPreset = 'none' | 'sm' | 'md' | 'lg' | 'glow';\n\nexport type BorderStyle =\n | 'solid' // Most common - continuous line\n | 'dashed' // Series of dashes\n | 'dotted' // Series of dots\n | 'double' // Two parallel lines\n | 'none' // No border\n | 'groove' // 3D carved into page\n | 'ridge' // 3D raised from page\n | 'inset' // 3D embedded look\n | 'outset' // 3D raised look\n | 'hidden'; // Same as none (for table border conflict resolution)\n\nexport interface BorderConfig {\n width?: number; // pixels, default: 0\n color?: string; // CSS color, default: '#000'\n radius?: number; // pixels, default: 8\n style?: BorderStyle; // default: 'solid'\n}\n\nexport interface DropShadowConfig {\n x: number;\n y: number;\n blur: number;\n color: string;\n}\n\nexport interface FilterConfig {\n grayscale?: number; // 0-1\n blur?: number; // pixels\n brightness?: number; // multiplier (1 = normal)\n contrast?: number; // multiplier\n saturate?: number; // multiplier\n opacity?: number; // 0-1\n sepia?: number; // 0-1\n hueRotate?: number; // degrees\n invert?: number; // 0-1\n dropShadow?: DropShadowConfig | string;\n}\n\nexport interface OutlineConfig {\n width?: number; // pixels\n color?: string; // CSS color\n style?: BorderStyle; // reuses BorderStyle type\n offset?: number; // pixels\n}\n\nexport interface ImageStyleState {\n // CSS class names (space-separated string or array)\n className?: string | string[];\n\n // Border (shorthand applies to all sides)\n border?: BorderConfig;\n borderTop?: Partial<BorderConfig>;\n borderRight?: Partial<BorderConfig>;\n borderBottom?: Partial<BorderConfig>;\n borderLeft?: Partial<BorderConfig>;\n\n // Per-corner border radius (overrides border.radius for specific corners)\n borderRadiusTopLeft?: number;\n borderRadiusTopRight?: number;\n borderRadiusBottomRight?: number;\n borderRadiusBottomLeft?: number;\n\n // Shadow (preset name or custom CSS string)\n shadow?: ShadowPreset | string;\n\n // Filters\n filter?: FilterConfig;\n\n // Other properties\n opacity?: number; // 0-1\n cursor?: string; // CSS cursor value\n outline?: OutlineConfig;\n objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n aspectRatio?: string; // e.g., '16/9', '1/1'\n}\n\nexport interface ImageStylingConfig {\n default?: ImageStyleState;\n hover?: Partial<ImageStyleState>; // inherits from default\n focused?: Partial<ImageStyleState>; // inherits from default\n}\n\n// ============================================================================\n// Focus Animation Types (Cross-Animation Support)\n// ============================================================================\n\n/**\n * State machine states for zoom/focus animations\n */\nexport enum ZoomState {\n IDLE = 'idle', // No focus, no animations\n FOCUSING = 'focusing', // Single image animating in\n FOCUSED = 'focused', // Stable focused state\n UNFOCUSING = 'unfocusing', // Single image animating out\n CROSS_ANIMATING = 'cross_animating' // Two images: one out, one in\n}\n\n/**\n * Handle for a cancellable animation using Web Animations API\n */\nexport interface AnimationHandle {\n id: string;\n element: HTMLElement;\n animation: Animation;\n fromState: TransformParams;\n toState: TransformParams;\n startTime: number;\n duration: number;\n}\n\n/**\n * Snapshot of an element's current transform state\n * Used for capturing position mid-animation\n */\nexport interface AnimationSnapshot {\n x: number;\n y: number;\n rotation: number;\n scale: number;\n}\n\n/**\n * Tracks an image that is currently animating\n */\nexport interface AnimatingImage {\n element: HTMLElement;\n originalState: ImageLayout;\n animationHandle: AnimationHandle;\n direction: 'in' | 'out';\n originalWidth?: number;\n originalHeight?: number;\n}","/**\n * Style utilities for image styling configuration\n */\n\nimport type { ImageStyleState, FilterConfig, BorderConfig, ShadowPreset, DropShadowConfig } from '../config/types';\nimport { SHADOW_PRESETS } from '../config/defaults';\n\n/**\n * Check if a value is a known shadow preset\n */\nfunction isShadowPreset(value: string): value is ShadowPreset {\n return value in SHADOW_PRESETS;\n}\n\n/**\n * Resolve shadow value - converts preset names to CSS values,\n * passes custom CSS strings through directly\n */\nexport function resolveShadow(shadow: ShadowPreset | string | undefined): string {\n if (!shadow) return SHADOW_PRESETS.md;\n if (isShadowPreset(shadow)) return SHADOW_PRESETS[shadow];\n return shadow; // Custom CSS string\n}\n\n/**\n * Build CSS filter string from FilterConfig\n */\nexport function buildFilterString(filter: FilterConfig | undefined): string {\n if (!filter) return '';\n\n const parts: string[] = [];\n\n if (filter.grayscale !== undefined) {\n parts.push(`grayscale(${filter.grayscale})`);\n }\n if (filter.blur !== undefined) {\n parts.push(`blur(${filter.blur}px)`);\n }\n if (filter.brightness !== undefined) {\n parts.push(`brightness(${filter.brightness})`);\n }\n if (filter.contrast !== undefined) {\n parts.push(`contrast(${filter.contrast})`);\n }\n if (filter.saturate !== undefined) {\n parts.push(`saturate(${filter.saturate})`);\n }\n if (filter.opacity !== undefined) {\n parts.push(`opacity(${filter.opacity})`);\n }\n if (filter.sepia !== undefined) {\n parts.push(`sepia(${filter.sepia})`);\n }\n if (filter.hueRotate !== undefined) {\n parts.push(`hue-rotate(${filter.hueRotate}deg)`);\n }\n if (filter.invert !== undefined) {\n parts.push(`invert(${filter.invert})`);\n }\n if (filter.dropShadow !== undefined) {\n if (typeof filter.dropShadow === 'string') {\n parts.push(`drop-shadow(${filter.dropShadow})`);\n } else {\n const ds = filter.dropShadow as DropShadowConfig;\n parts.push(`drop-shadow(${ds.x}px ${ds.y}px ${ds.blur}px ${ds.color})`);\n }\n }\n\n return parts.join(' ');\n}\n\n/**\n * Build border CSS for a single side\n */\nfunction buildSingleBorder(config: BorderConfig | undefined): string {\n if (!config || config.style === 'none' || config.width === 0) {\n return 'none';\n }\n const width = config.width ?? 0;\n const style = config.style ?? 'solid';\n const color = config.color ?? '#000000';\n return `${width}px ${style} ${color}`;\n}\n\n/**\n * CSS properties object type for style application\n */\nexport interface StyleProperties {\n borderRadius?: string;\n borderTopLeftRadius?: string;\n borderTopRightRadius?: string;\n borderBottomRightRadius?: string;\n borderBottomLeftRadius?: string;\n border?: string;\n borderTop?: string;\n borderRight?: string;\n borderBottom?: string;\n borderLeft?: string;\n boxShadow?: string;\n filter?: string;\n opacity?: string;\n cursor?: string;\n outline?: string;\n outlineOffset?: string;\n objectFit?: string;\n aspectRatio?: string;\n}\n\n/**\n * Build complete style properties object from ImageStyleState\n */\nexport function buildStyleProperties(state: ImageStyleState | undefined): StyleProperties {\n if (!state) return {};\n\n const styles: StyleProperties = {};\n\n // Border radius - check for per-corner overrides first\n const hasPerCornerRadius = state.borderRadiusTopLeft !== undefined ||\n state.borderRadiusTopRight !== undefined ||\n state.borderRadiusBottomRight !== undefined ||\n state.borderRadiusBottomLeft !== undefined;\n\n if (hasPerCornerRadius) {\n // Use per-corner radius with fallback to base radius\n const baseRadius = state.border?.radius ?? 0;\n if (state.borderRadiusTopLeft !== undefined) {\n styles.borderTopLeftRadius = `${state.borderRadiusTopLeft}px`;\n } else if (baseRadius) {\n styles.borderTopLeftRadius = `${baseRadius}px`;\n }\n if (state.borderRadiusTopRight !== undefined) {\n styles.borderTopRightRadius = `${state.borderRadiusTopRight}px`;\n } else if (baseRadius) {\n styles.borderTopRightRadius = `${baseRadius}px`;\n }\n if (state.borderRadiusBottomRight !== undefined) {\n styles.borderBottomRightRadius = `${state.borderRadiusBottomRight}px`;\n } else if (baseRadius) {\n styles.borderBottomRightRadius = `${baseRadius}px`;\n }\n if (state.borderRadiusBottomLeft !== undefined) {\n styles.borderBottomLeftRadius = `${state.borderRadiusBottomLeft}px`;\n } else if (baseRadius) {\n styles.borderBottomLeftRadius = `${baseRadius}px`;\n }\n } else if (state.border?.radius !== undefined) {\n styles.borderRadius = `${state.border.radius}px`;\n }\n\n // Check if any per-side border is defined\n const hasPerSideBorder = state.borderTop || state.borderRight || state.borderBottom || state.borderLeft;\n\n if (hasPerSideBorder) {\n // Merge base border with per-side overrides\n const baseBorder = state.border || {};\n\n const topBorder = { ...baseBorder, ...state.borderTop };\n const rightBorder = { ...baseBorder, ...state.borderRight };\n const bottomBorder = { ...baseBorder, ...state.borderBottom };\n const leftBorder = { ...baseBorder, ...state.borderLeft };\n\n styles.borderTop = buildSingleBorder(topBorder);\n styles.borderRight = buildSingleBorder(rightBorder);\n styles.borderBottom = buildSingleBorder(bottomBorder);\n styles.borderLeft = buildSingleBorder(leftBorder);\n } else if (state.border) {\n // Apply uniform border\n styles.border = buildSingleBorder(state.border);\n }\n\n // Shadow\n if (state.shadow !== undefined) {\n styles.boxShadow = resolveShadow(state.shadow);\n }\n\n // Filter - always set to ensure hover filters are properly cleared on mouseleave\n const filterStr = buildFilterString(state.filter);\n styles.filter = filterStr || 'none';\n\n // Opacity\n if (state.opacity !== undefined) {\n styles.opacity = String(state.opacity);\n }\n\n // Cursor\n if (state.cursor !== undefined) {\n styles.cursor = state.cursor;\n }\n\n // Outline\n if (state.outline && state.outline.style !== 'none' && (state.outline.width ?? 0) > 0) {\n const width = state.outline.width ?? 0;\n const style = state.outline.style ?? 'solid';\n const color = state.outline.color ?? '#000000';\n styles.outline = `${width}px ${style} ${color}`;\n if (state.outline.offset !== undefined) {\n styles.outlineOffset = `${state.outline.offset}px`;\n }\n }\n\n // Object fit\n if (state.objectFit !== undefined) {\n styles.objectFit = state.objectFit;\n }\n\n // Aspect ratio\n if (state.aspectRatio !== undefined) {\n styles.aspectRatio = state.aspectRatio;\n }\n\n return styles;\n}\n\n/**\n * Apply style properties to an HTML element\n */\nexport function applyStylesToElement(element: HTMLElement, styles: StyleProperties): void {\n if (styles.borderRadius !== undefined) element.style.borderRadius = styles.borderRadius;\n if (styles.borderTopLeftRadius !== undefined) element.style.borderTopLeftRadius = styles.borderTopLeftRadius;\n if (styles.borderTopRightRadius !== undefined) element.style.borderTopRightRadius = styles.borderTopRightRadius;\n if (styles.borderBottomRightRadius !== undefined) element.style.borderBottomRightRadius = styles.borderBottomRightRadius;\n if (styles.borderBottomLeftRadius !== undefined) element.style.borderBottomLeftRadius = styles.borderBottomLeftRadius;\n if (styles.border !== undefined) element.style.border = styles.border;\n if (styles.borderTop !== undefined) element.style.borderTop = styles.borderTop;\n if (styles.borderRight !== undefined) element.style.borderRight = styles.borderRight;\n if (styles.borderBottom !== undefined) element.style.borderBottom = styles.borderBottom;\n if (styles.borderLeft !== undefined) element.style.borderLeft = styles.borderLeft;\n if (styles.boxShadow !== undefined) element.style.boxShadow = styles.boxShadow;\n if (styles.filter !== undefined) element.style.filter = styles.filter;\n if (styles.opacity !== undefined) element.style.opacity = styles.opacity;\n if (styles.cursor !== undefined) element.style.cursor = styles.cursor;\n if (styles.outline !== undefined) element.style.outline = styles.outline;\n if (styles.outlineOffset !== undefined) element.style.outlineOffset = styles.outlineOffset;\n if (styles.objectFit !== undefined) element.style.objectFit = styles.objectFit;\n if (styles.aspectRatio !== undefined) element.style.aspectRatio = styles.aspectRatio;\n}\n\n/**\n * Resolve className to a space-separated string\n */\nexport function resolveClassName(className: string | string[] | undefined): string {\n if (!className) return '';\n if (Array.isArray(className)) return className.join(' ');\n return className;\n}\n\n/**\n * Apply className to element (additive with existing classes)\n */\nexport function applyClassNameToElement(element: HTMLElement, className: string | string[] | undefined): void {\n const resolved = resolveClassName(className);\n if (resolved) {\n resolved.split(' ').forEach(cls => {\n if (cls.trim()) element.classList.add(cls.trim());\n });\n }\n}\n\n/**\n * Remove className from element\n */\nexport function removeClassNameFromElement(element: HTMLElement, className: string | string[] | undefined): void {\n const resolved = resolveClassName(className);\n if (resolved) {\n resolved.split(' ').forEach(cls => {\n if (cls.trim()) element.classList.remove(cls.trim());\n });\n }\n}\n","/**\n * ZoomEngine.ts\n * Manages zoom/focus behavior for image cloud with cross-animation support\n *\n * Public API:\n * - focusImage(imageElement, containerBounds, originalState)\n * - unfocusImage()\n * - getCurrentFocus()\n * - swapFocus(newImageElement, containerBounds, originalState)\n * - isFocused(imageElement)\n * - isAnimating()\n * - getState()\n * - reset()\n */\n\nimport type {\n FocusInteractionConfig,\n ContainerBounds,\n ImageLayout,\n TransformParams,\n ImageStylingConfig,\n AnimationHandle,\n AnimatingImage\n} from '../config/types';\nimport { ZoomState } from '../config/types';\nimport { AnimationEngine } from './AnimationEngine';\nimport { buildStyleProperties, applyStylesToElement, applyClassNameToElement, removeClassNameFromElement, StyleProperties } from '../utils/styleUtils';\n\ninterface FocusData {\n element: HTMLElement;\n originalState: ImageLayout;\n focusTransform: TransformParams;\n originalZIndex: string;\n originalWidth: number;\n originalHeight: number;\n focusWidth: number;\n focusHeight: number;\n}\n\n// Z-index constants for layering during animations\nconst Z_INDEX = {\n DEFAULT: '',\n UNFOCUSING: 999,\n FOCUSING: 1000,\n FOCUSED: 1000\n};\n\nexport class ZoomEngine {\n private config: FocusInteractionConfig;\n private animationEngine: AnimationEngine;\n\n // State machine\n private state: ZoomState = ZoomState.IDLE;\n private currentFocus: HTMLElement | null = null;\n private focusData: FocusData | null = null;\n\n // Animation tracking for cross-animation\n private outgoing: AnimatingImage | null = null;\n private incoming: AnimatingImage | null = null;\n\n // Generation counter to handle concurrent calls\n private focusGeneration: number = 0;\n\n // Styling\n private defaultStyles: StyleProperties;\n private focusedStyles: StyleProperties;\n private defaultClassName: string | string[] | undefined;\n private focusedClassName: string | string[] | undefined;\n\n constructor(config: FocusInteractionConfig, animationEngine: AnimationEngine, styling?: ImageStylingConfig) {\n this.config = config;\n this.animationEngine = animationEngine;\n\n // Precompute styling properties\n this.defaultStyles = buildStyleProperties(styling?.default);\n this.focusedStyles = buildStyleProperties(styling?.focused);\n this.defaultClassName = styling?.default?.className;\n this.focusedClassName = styling?.focused?.className;\n }\n\n /**\n * Get current state machine state\n */\n getState(): ZoomState {\n return this.state;\n }\n\n /**\n * Check if any animation is in progress\n */\n isAnimating(): boolean {\n return this.state !== ZoomState.IDLE && this.state !== ZoomState.FOCUSED;\n }\n\n /**\n * Normalize scalePercent value\n */\n private normalizeScalePercent(value: number): number {\n return value > 1 ? value / 100 : value;\n }\n\n /**\n * Calculate target dimensions for focused image\n * Returns actual pixel dimensions instead of scale factor for sharper rendering\n */\n private calculateFocusDimensions(imageWidth: number, imageHeight: number, containerBounds: ContainerBounds): { width: number; height: number } {\n const normalizedPercent = this.normalizeScalePercent(this.config.scalePercent);\n const targetHeight = containerBounds.height * normalizedPercent;\n const aspectRatio = imageWidth / imageHeight;\n\n let focusHeight = targetHeight;\n let focusWidth = focusHeight * aspectRatio;\n\n const maxWidth = containerBounds.width * normalizedPercent;\n if (focusWidth > maxWidth) {\n focusWidth = maxWidth;\n focusHeight = focusWidth / aspectRatio;\n }\n\n return { width: focusWidth, height: focusHeight };\n }\n\n /**\n * Calculate the transform needed to center an image (position only, no scale)\n * Scale is handled by animating actual dimensions for sharper rendering\n */\n private calculateFocusTransform(\n containerBounds: ContainerBounds,\n originalState: ImageLayout\n ): TransformParams {\n const centerX = containerBounds.width / 2;\n const centerY = containerBounds.height / 2;\n\n const targetX = centerX - originalState.x;\n const targetY = centerY - originalState.y;\n\n return {\n x: targetX,\n y: targetY,\n rotation: 0,\n scale: 1 // No scale transform - dimensions are animated instead\n };\n }\n\n /**\n * Build transform string for dimension-based zoom (no scale in transform)\n */\n private buildDimensionZoomTransform(params: TransformParams): string {\n const transforms: string[] = ['translate(-50%, -50%)'];\n\n if (params.x !== undefined || params.y !== undefined) {\n const x = params.x ?? 0;\n const y = params.y ?? 0;\n transforms.push(`translate(${x}px, ${y}px)`);\n }\n\n if (params.rotation !== undefined) {\n transforms.push(`rotate(${params.rotation}deg)`);\n }\n\n // Note: scale is intentionally omitted - we animate width/height instead\n\n return transforms.join(' ');\n }\n\n /**\n * Create a Web Animation that animates both transform (position) and dimensions\n * This provides sharper zoom by re-rendering at target size instead of scaling pixels\n */\n private animateWithDimensions(\n element: HTMLElement,\n fromTransform: TransformParams,\n toTransform: TransformParams,\n fromWidth: number,\n fromHeight: number,\n toWidth: number,\n toHeight: number,\n duration: number\n ): Animation {\n const fromTransformStr = this.buildDimensionZoomTransform(fromTransform);\n const toTransformStr = this.buildDimensionZoomTransform(toTransform);\n\n // Clear any CSS transitions to avoid conflicts\n element.style.transition = 'none';\n\n // Create Web Animation with both transform and dimensions\n const animation = element.animate(\n [\n {\n transform: fromTransformStr,\n width: `${fromWidth}px`,\n height: `${fromHeight}px`\n },\n {\n transform: toTransformStr,\n width: `${toWidth}px`,\n height: `${toHeight}px`\n }\n ],\n {\n duration: duration,\n easing: 'cubic-bezier(0.4, 0, 0.2, 1)',\n fill: 'forwards'\n }\n );\n\n return animation;\n }\n\n /**\n * Apply focused styling to an element\n */\n private applyFocusedStyling(element: HTMLElement, zIndex: number): void {\n element.style.zIndex = String(zIndex);\n element.classList.add('fbn-ic-focused');\n applyStylesToElement(element, this.focusedStyles);\n applyClassNameToElement(element, this.focusedClassName);\n }\n\n /**\n * Remove focused styling from an element\n */\n private removeFocusedStyling(element: HTMLElement, originalZIndex: string): void {\n element.style.zIndex = originalZIndex;\n element.classList.remove('fbn-ic-focused');\n removeClassNameFromElement(element, this.focusedClassName);\n applyStylesToElement(element, this.defaultStyles);\n applyClassNameToElement(element, this.defaultClassName);\n }\n\n /**\n * Start focus animation for an image using dimension-based zoom\n * Animates actual width/height for sharper rendering instead of transform scale\n * @param fromTransform - Optional starting transform (for mid-animation reversals)\n * @param fromDimensions - Optional starting dimensions (for mid-animation reversals)\n */\n private startFocusAnimation(\n element: HTMLElement,\n containerBounds: ContainerBounds,\n originalState: ImageLayout,\n fromTransform?: TransformParams,\n fromDimensions?: { width: number; height: number }\n ): AnimatingImage {\n const originalZIndex = element.style.zIndex || '';\n const originalWidth = element.offsetWidth;\n const originalHeight = element.offsetHeight;\n\n // Calculate target dimensions and position\n const focusDimensions = this.calculateFocusDimensions(originalWidth, originalHeight, containerBounds);\n const focusTransform = this.calculateFocusTransform(containerBounds, originalState);\n\n // Apply focused styling immediately\n this.applyFocusedStyling(element, Z_INDEX.FOCUSING);\n\n // Cancel any existing animation\n this.animationEngine.cancelAllAnimations(element);\n\n // Start animation from provided state or original position\n const startTransform: TransformParams = fromTransform ?? {\n x: 0,\n y: 0,\n rotation: originalState.rotation,\n scale: 1 // No scale - using dimensions\n };\n\n const startWidth = fromDimensions?.width ?? originalWidth;\n const startHeight = fromDimensions?.height ?? originalHeight;\n\n const duration = this.config.animationDuration ?? 600;\n\n // Create dimension-based animation\n const animation = this.animateWithDimensions(\n element,\n startTransform,\n focusTransform,\n startWidth,\n startHeight,\n focusDimensions.width,\n focusDimensions.height,\n duration\n );\n\n // Create animation handle\n const handle: AnimationHandle = {\n id: `focus-${Date.now()}`,\n element,\n animation,\n fromState: startTransform,\n toState: focusTransform,\n startTime: performance.now(),\n duration\n };\n\n // Store focus data for later (including dimensions for unfocus)\n this.focusData = {\n element,\n originalState,\n focusTransform,\n originalZIndex,\n originalWidth,\n originalHeight,\n focusWidth: focusDimensions.width,\n focusHeight: focusDimensions.height\n };\n\n return {\n element,\n originalState,\n animationHandle: handle,\n direction: 'in' as const,\n originalWidth,\n originalHeight\n };\n }\n\n /**\n * Start unfocus animation for an image using dimension-based zoom\n * Animates back to original dimensions for consistent behavior\n * @param fromDimensions - Optional starting dimensions (for mid-animation reversals)\n */\n private startUnfocusAnimation(\n element: HTMLElement,\n originalState: ImageLayout,\n fromTransform?: TransformParams,\n fromDimensions?: { width: number; height: number }\n ): AnimatingImage {\n // Set z-index for unfocusing (below incoming)\n element.style.zIndex = String(Z_INDEX.UNFOCUSING);\n\n // Cancel any existing animation\n this.animationEngine.cancelAllAnimations(element);\n\n // Start from current focused state (or provided state for interrupted animations)\n const startTransform = fromTransform ?? this.focusData?.focusTransform ?? { x: 0, y: 0, rotation: 0, scale: 1 };\n const startWidth = fromDimensions?.width ?? this.focusData?.focusWidth ?? element.offsetWidth;\n const startHeight = fromDimensions?.height ?? this.focusData?.focusHeight ?? element.offsetHeight;\n\n // Target is original position and dimensions\n const toState: TransformParams = {\n x: 0,\n y: 0,\n rotation: originalState.rotation,\n scale: 1 // No scale - using dimensions\n };\n\n const targetWidth = this.focusData?.originalWidth ?? element.offsetWidth;\n const targetHeight = this.focusData?.originalHeight ?? element.offsetHeight;\n const duration = this.config.animationDuration ?? 600;\n\n // Create dimension-based animation\n const animation = this.animateWithDimensions(\n element,\n startTransform,\n toState,\n startWidth,\n startHeight,\n targetWidth,\n targetHeight,\n duration\n );\n\n // Create animation handle\n const handle: AnimationHandle = {\n id: `unfocus-${Date.now()}`,\n element,\n animation,\n fromState: startTransform,\n toState: toState,\n startTime: performance.now(),\n duration\n };\n\n return {\n element,\n originalState,\n animationHandle: handle,\n direction: 'out' as const,\n originalWidth: targetWidth,\n originalHeight: targetHeight\n };\n }\n\n /**\n * Handle animation completion\n */\n private async waitForAnimation(handle: AnimationHandle): Promise<void> {\n try {\n await handle.animation.finished;\n } catch {\n // Animation was cancelled - this is expected during interruption\n }\n }\n\n /**\n * Reset an element instantly to its original position and dimensions (no animation)\n */\n private resetElementInstantly(\n element: HTMLElement,\n originalState: ImageLayout,\n originalZIndex: string,\n originalWidth?: number,\n originalHeight?: number\n ): void {\n // Cancel any active animation (including completed animations with fill: 'forwards')\n this.animationEngine.cancelAllAnimations(element);\n\n // Build transform string for original position (no scale - using dimensions)\n const transforms = ['translate(-50%, -50%)'];\n transforms.push('translate(0px, 0px)');\n transforms.push(`rotate(${originalState.rotation}deg)`);\n // No scale in transform - dimensions handle sizing\n\n element.style.transition = 'none';\n element.style.transform = transforms.join(' ');\n\n // Restore original dimensions if provided\n if (originalWidth !== undefined && originalHeight !== undefined) {\n element.style.width = `${originalWidth}px`;\n element.style.height = `${originalHeight}px`;\n }\n\n // Remove focused styling\n this.removeFocusedStyling(element, originalZIndex);\n }\n\n /**\n * Focus (zoom) an image to center of container\n * Implements cross-animation when swapping focus\n */\n async focusImage(\n imageElement: HTMLElement,\n containerBounds: ContainerBounds,\n originalState: ImageLayout\n ): Promise<void> {\n // Same image clicked while already focused - unfocus it\n if (this.currentFocus === imageElement && this.state === ZoomState.FOCUSED) {\n return this.unfocusImage();\n }\n\n // Same image clicked while it's animating in - reverse to unfocus\n if (this.incoming?.element === imageElement && this.state === ZoomState.FOCUSING) {\n // Capture current position and dimensions, then reverse\n const snapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale transform - using dimensions\n };\n const fromDimensions = {\n width: imageElement.offsetWidth,\n height: imageElement.offsetHeight\n };\n\n this.outgoing = this.startUnfocusAnimation(\n imageElement,\n this.incoming.originalState,\n fromTransform,\n fromDimensions\n );\n this.incoming = null;\n this.state = ZoomState.UNFOCUSING;\n\n await this.waitForAnimation(this.outgoing.animationHandle);\n this.removeFocusedStyling(this.outgoing.element, this.focusData?.originalZIndex || '');\n this.outgoing = null;\n this.currentFocus = null;\n this.focusData = null;\n this.state = ZoomState.IDLE;\n return;\n }\n\n // Increment generation to invalidate any previous in-flight calls\n const myGeneration = ++this.focusGeneration;\n\n switch (this.state) {\n case ZoomState.IDLE:\n // Simple focus - no current focus\n this.state = ZoomState.FOCUSING;\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n await this.waitForAnimation(this.incoming.animationHandle);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) return;\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n\n case ZoomState.FOCUSED:\n // Cross-animation: unfocus current while focusing new\n this.state = ZoomState.CROSS_ANIMATING;\n\n // Start outgoing animation for currently focused image\n if (this.currentFocus && this.focusData) {\n this.outgoing = this.startUnfocusAnimation(\n this.currentFocus,\n this.focusData.originalState\n );\n }\n\n // Start incoming animation for new image\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n // Wait for both animations\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(this.incoming.animationHandle)\n ]);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) {\n return;\n }\n\n // Cleanup outgoing\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n this.outgoing = null;\n }\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n\n case ZoomState.FOCUSING:\n // New image clicked while another is focusing\n // Cancel the incoming and start new focus\n if (this.incoming) {\n this.animationEngine.cancelAnimation(this.incoming.animationHandle, false);\n this.resetElementInstantly(\n this.incoming.element,\n this.incoming.originalState,\n this.focusData?.originalZIndex || '',\n this.focusData?.originalWidth,\n this.focusData?.originalHeight\n );\n this.incoming = null;\n }\n\n // Start focusing the new image\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n await this.waitForAnimation(this.incoming.animationHandle);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) return;\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n\n case ZoomState.UNFOCUSING:\n // New image clicked while current is unfocusing\n // Let outgoing continue, start cross-animation\n this.state = ZoomState.CROSS_ANIMATING;\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(this.incoming.animationHandle)\n ]);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) return;\n\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n this.outgoing = null;\n }\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n\n case ZoomState.CROSS_ANIMATING:\n // Handle click during cross-animation\n\n // If clicking the same image that's animating in, ignore (it's already targeting focus)\n if (this.incoming?.element === imageElement) {\n return;\n }\n\n // If clicking the same image that's animating out, let it become the new focus target\n // (reverse direction - it should animate back to focused)\n if (this.outgoing?.element === imageElement) {\n // Cancel outgoing animation and capture current state\n const snapshot = this.animationEngine.cancelAnimation(this.outgoing.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const fromDimensions = {\n width: imageElement.offsetWidth,\n height: imageElement.offsetHeight\n };\n\n // Redirect current incoming to become outgoing\n if (this.incoming) {\n const incomingSnapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const incomingFrom: TransformParams = {\n x: incomingSnapshot.x,\n y: incomingSnapshot.y,\n rotation: incomingSnapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const incomingFromDimensions = {\n width: this.incoming.element.offsetWidth,\n height: this.incoming.element.offsetHeight\n };\n this.outgoing = this.startUnfocusAnimation(\n this.incoming.element,\n this.incoming.originalState,\n incomingFrom,\n incomingFromDimensions\n );\n } else {\n this.outgoing = null;\n }\n\n // Start new incoming for the clicked (formerly outgoing) image\n // Use fromTransform and fromDimensions to continue from current state\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState, fromTransform, fromDimensions);\n\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(this.incoming.animationHandle)\n ]);\n\n if (this.focusGeneration !== myGeneration) return;\n\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n this.outgoing = null;\n }\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n return;\n }\n\n // Third different image clicked during cross-animation\n // 1. Reset outgoing instantly\n // 2. Redirect incoming to become outgoing\n // 3. Start new incoming\n\n // Reset outgoing instantly\n if (this.outgoing) {\n this.animationEngine.cancelAnimation(this.outgoing.animationHandle, false);\n this.resetElementInstantly(\n this.outgoing.element,\n this.outgoing.originalState,\n this.outgoing.originalState.zIndex?.toString() || '',\n this.outgoing.originalWidth,\n this.outgoing.originalHeight\n );\n this.outgoing = null;\n }\n\n // Redirect incoming to outgoing (animate from current position back to original)\n if (this.incoming) {\n const snapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const fromDimensions = {\n width: this.incoming.element.offsetWidth,\n height: this.incoming.element.offsetHeight\n };\n\n this.outgoing = this.startUnfocusAnimation(\n this.incoming.element,\n this.incoming.originalState,\n fromTransform,\n fromDimensions\n );\n }\n\n // Start new incoming\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n // Wait for both\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(this.incoming.animationHandle)\n ]);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) return;\n\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n this.outgoing = null;\n }\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n }\n }\n\n /**\n * Unfocus current image, returning it to original position\n */\n async unfocusImage(): Promise<void> {\n // Increment generation to invalidate any previous in-flight calls\n const myGeneration = ++this.focusGeneration;\n\n if (!this.currentFocus || !this.focusData) {\n // Handle case where we're in FOCUSING state\n if (this.incoming && this.state === ZoomState.FOCUSING) {\n const snapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const fromDimensions = {\n width: this.incoming.element.offsetWidth,\n height: this.incoming.element.offsetHeight\n };\n\n this.outgoing = this.startUnfocusAnimation(\n this.incoming.element,\n this.incoming.originalState,\n fromTransform,\n fromDimensions\n );\n this.incoming = null;\n this.state = ZoomState.UNFOCUSING;\n\n await this.waitForAnimation(this.outgoing.animationHandle);\n\n if (this.focusGeneration !== myGeneration) return;\n\n this.removeFocusedStyling(this.outgoing.element, this.focusData?.originalZIndex || '');\n this.outgoing = null;\n this.focusData = null;\n this.state = ZoomState.IDLE;\n }\n return;\n }\n\n // Handle cross-animation cancellation (ESC during cross-animation)\n if (this.state === ZoomState.CROSS_ANIMATING) {\n // Cancel incoming and animate it back\n if (this.incoming) {\n const snapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const fromDimensions = {\n width: this.incoming.element.offsetWidth,\n height: this.incoming.element.offsetHeight\n };\n\n // Start unfocus for incoming from its current position\n const incomingUnfocus = this.startUnfocusAnimation(\n this.incoming.element,\n this.incoming.originalState,\n fromTransform,\n fromDimensions\n );\n\n // Wait for both outgoing and the redirected incoming\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(incomingUnfocus.animationHandle)\n ]);\n\n if (this.focusGeneration !== myGeneration) return;\n\n // Cleanup\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n }\n this.removeFocusedStyling(incomingUnfocus.element, this.incoming.originalState.zIndex?.toString() || '');\n\n this.outgoing = null;\n this.incoming = null;\n this.currentFocus = null;\n this.focusData = null;\n this.state = ZoomState.IDLE;\n return;\n }\n }\n\n // Normal unfocus from FOCUSED state\n this.state = ZoomState.UNFOCUSING;\n const element = this.currentFocus;\n const originalState = this.focusData.originalState;\n const originalZIndex = this.focusData.originalZIndex;\n\n this.outgoing = this.startUnfocusAnimation(element, originalState);\n\n await this.waitForAnimation(this.outgoing.animationHandle);\n\n if (this.focusGeneration !== myGeneration) return;\n\n this.removeFocusedStyling(element, originalZIndex);\n this.outgoing = null;\n this.currentFocus = null;\n this.focusData = null;\n this.state = ZoomState.IDLE;\n }\n\n /**\n * Swap focus from current image to a new one (alias for focusImage with cross-animation)\n */\n async swapFocus(\n newImageElement: HTMLElement,\n containerBounds: ContainerBounds,\n originalState: ImageLayout\n ): Promise<void> {\n return this.focusImage(newImageElement, containerBounds, originalState);\n }\n\n /**\n * Get currently focused image element\n */\n getCurrentFocus(): HTMLElement | null {\n return this.currentFocus;\n }\n\n /**\n * Check if an image is currently focused (stable state)\n */\n isFocused(imageElement: HTMLElement): boolean {\n return this.currentFocus === imageElement && this.state === ZoomState.FOCUSED;\n }\n\n /**\n * Check if an image is the target of current focus animation\n */\n isTargetingFocus(imageElement: HTMLElement): boolean {\n return this.incoming?.element === imageElement;\n }\n\n /**\n * Check if an image is involved in any focus/animation state\n * Returns true if the image is focused, animating in, or animating out\n * Useful for hover state management - don't apply hover to animating images\n */\n isInvolved(imageElement: HTMLElement): boolean {\n return (\n this.currentFocus === imageElement ||\n this.incoming?.element === imageElement ||\n this.outgoing?.element === imageElement\n );\n }\n\n /**\n * Apply a temporary horizontal drag offset to the focused image\n * Used during swipe gestures for visual feedback\n */\n setDragOffset(offset: number): void {\n if (!this.currentFocus || !this.focusData || this.state !== ZoomState.FOCUSED) return;\n\n const element = this.currentFocus;\n const focusTransform = this.focusData.focusTransform;\n\n // Build transform with additional horizontal offset\n const transforms: string[] = ['translate(-50%, -50%)'];\n const x = (focusTransform.x ?? 0) + offset;\n const y = focusTransform.y ?? 0;\n transforms.push(`translate(${x}px, ${y}px)`);\n if (focusTransform.rotation !== undefined) {\n transforms.push(`rotate(${focusTransform.rotation}deg)`);\n }\n\n element.style.transition = 'none';\n element.style.transform = transforms.join(' ');\n }\n\n /**\n * Clear the drag offset, optionally animating back to center\n * @param animate - If true, animate back to center; if false, snap instantly\n * @param duration - Animation duration in ms (default 150)\n */\n clearDragOffset(animate: boolean, duration: number = 150): void {\n if (!this.currentFocus || !this.focusData || this.state !== ZoomState.FOCUSED) return;\n\n const element = this.currentFocus;\n const focusTransform = this.focusData.focusTransform;\n\n // Build the centered transform (no offset)\n const transforms: string[] = ['translate(-50%, -50%)'];\n const x = focusTransform.x ?? 0;\n const y = focusTransform.y ?? 0;\n transforms.push(`translate(${x}px, ${y}px)`);\n if (focusTransform.rotation !== undefined) {\n transforms.push(`rotate(${focusTransform.rotation}deg)`);\n }\n const centeredTransform = transforms.join(' ');\n\n if (animate) {\n element.style.transition = `transform ${duration}ms ease-out`;\n element.style.transform = centeredTransform;\n // Clear transition after animation completes\n setTimeout(() => {\n if (this.currentFocus === element) {\n element.style.transition = 'none';\n }\n }, duration);\n } else {\n element.style.transition = 'none';\n element.style.transform = centeredTransform;\n }\n }\n\n /**\n * Reset zoom state (cancels all animations)\n */\n reset(): void {\n // Cancel any active animations\n if (this.outgoing) {\n this.animationEngine.cancelAnimation(this.outgoing.animationHandle, false);\n this.resetElementInstantly(\n this.outgoing.element,\n this.outgoing.originalState,\n this.outgoing.originalState.zIndex?.toString() || '',\n this.outgoing.originalWidth,\n this.outgoing.originalHeight\n );\n }\n\n if (this.incoming) {\n this.animationEngine.cancelAnimation(this.incoming.animationHandle, false);\n this.resetElementInstantly(\n this.incoming.element,\n this.incoming.originalState,\n this.focusData?.originalZIndex || '',\n this.focusData?.originalWidth,\n this.focusData?.originalHeight\n );\n }\n\n if (this.currentFocus && this.focusData) {\n this.resetElementInstantly(\n this.currentFocus,\n this.focusData.originalState,\n this.focusData.originalZIndex,\n this.focusData.originalWidth,\n this.focusData.originalHeight\n );\n }\n\n this.state = ZoomState.IDLE;\n this.currentFocus = null;\n this.focusData = null;\n this.outgoing = null;\n this.incoming = null;\n }\n}\n","/**\n * SwipeEngine.ts\n * Handles touch swipe gestures for navigating between focused images\n *\n * Public API:\n * - enable() - Start listening for touch events\n * - disable() - Stop listening (when no image focused)\n * - destroy() - Clean up all event listeners\n * - setDragCallback(callback) - Set callback for drag offset updates\n */\n\n// Constants for swipe detection\nconst SWIPE_THRESHOLD_PX = 50;\nconst SWIPE_VELOCITY_THRESHOLD = 0.5; // px/ms\nconst SWIPE_MIN_DISTANCE_FOR_VELOCITY = 20;\nconst DRAG_DAMPING = 0.3;\nconst SNAP_BACK_DURATION_MS = 150;\nconst HORIZONTAL_ANGLE_THRESHOLD_DEG = 30;\n\ninterface SwipeCallbacks {\n onNext: () => void;\n onPrev: () => void;\n onDragOffset: (offset: number) => void;\n onDragEnd: (navigated: boolean) => void;\n}\n\ninterface TouchState {\n startX: number;\n startY: number;\n startTime: number;\n currentX: number;\n isDragging: boolean;\n isHorizontalSwipe: boolean | null; // null = not determined yet\n}\n\nexport class SwipeEngine {\n private container: HTMLElement;\n private callbacks: SwipeCallbacks;\n private enabled: boolean = false;\n private touchState: TouchState | null = null;\n\n // Track recent touch activity to prevent click-outside from unfocusing\n private recentTouchTimestamp: number = 0;\n private static readonly TOUCH_CLICK_DELAY = 300; // ms to ignore clicks after touch\n\n // Bound event handlers for proper cleanup\n private boundTouchStart: (e: TouchEvent) => void;\n private boundTouchMove: (e: TouchEvent) => void;\n private boundTouchEnd: (e: TouchEvent) => void;\n private boundTouchCancel: (e: TouchEvent) => void;\n\n constructor(container: HTMLElement, callbacks: SwipeCallbacks) {\n this.container = container;\n this.callbacks = callbacks;\n\n // Bind handlers\n this.boundTouchStart = this.handleTouchStart.bind(this);\n this.boundTouchMove = this.handleTouchMove.bind(this);\n this.boundTouchEnd = this.handleTouchEnd.bind(this);\n this.boundTouchCancel = this.handleTouchCancel.bind(this);\n }\n\n /**\n * Start listening for touch events\n */\n enable(): void {\n if (this.enabled) return;\n this.enabled = true;\n\n this.container.addEventListener('touchstart', this.boundTouchStart, { passive: false });\n this.container.addEventListener('touchmove', this.boundTouchMove, { passive: false });\n this.container.addEventListener('touchend', this.boundTouchEnd, { passive: true });\n this.container.addEventListener('touchcancel', this.boundTouchCancel, { passive: true });\n }\n\n /**\n * Stop listening for touch events\n */\n disable(): void {\n if (!this.enabled) return;\n this.enabled = false;\n\n this.container.removeEventListener('touchstart', this.boundTouchStart);\n this.container.removeEventListener('touchmove', this.boundTouchMove);\n this.container.removeEventListener('touchend', this.boundTouchEnd);\n this.container.removeEventListener('touchcancel', this.boundTouchCancel);\n\n // Reset any in-progress drag\n if (this.touchState?.isDragging) {\n this.callbacks.onDragEnd(false);\n }\n this.touchState = null;\n }\n\n /**\n * Clean up all event listeners\n */\n destroy(): void {\n this.disable();\n }\n\n /**\n * Check if a touch interaction happened recently\n * Used to prevent click-outside from unfocusing immediately after touch\n */\n hadRecentTouch(): boolean {\n return Date.now() - this.recentTouchTimestamp < SwipeEngine.TOUCH_CLICK_DELAY;\n }\n\n private handleTouchStart(e: TouchEvent): void {\n if (e.touches.length !== 1) return;\n\n // Mark recent touch to prevent click-outside unfocus\n this.recentTouchTimestamp = Date.now();\n\n const touch = e.touches[0];\n this.touchState = {\n startX: touch.clientX,\n startY: touch.clientY,\n startTime: performance.now(),\n currentX: touch.clientX,\n isDragging: false,\n isHorizontalSwipe: null\n };\n }\n\n private handleTouchMove(e: TouchEvent): void {\n if (!this.touchState || e.touches.length !== 1) return;\n\n const touch = e.touches[0];\n const deltaX = touch.clientX - this.touchState.startX;\n const deltaY = touch.clientY - this.touchState.startY;\n\n // Determine swipe direction on first significant movement\n if (this.touchState.isHorizontalSwipe === null) {\n const totalDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n if (totalDistance > 10) {\n // Calculate angle from horizontal\n const angleRad = Math.atan2(Math.abs(deltaY), Math.abs(deltaX));\n const angleDeg = angleRad * (180 / Math.PI);\n this.touchState.isHorizontalSwipe = angleDeg <= HORIZONTAL_ANGLE_THRESHOLD_DEG;\n }\n }\n\n // If determined to be vertical, don't capture\n if (this.touchState.isHorizontalSwipe === false) {\n return;\n }\n\n // If horizontal swipe, prevent default and track drag\n if (this.touchState.isHorizontalSwipe === true) {\n e.preventDefault();\n this.touchState.isDragging = true;\n this.touchState.currentX = touch.clientX;\n\n // Apply damped offset to focused image\n const dampedOffset = deltaX * DRAG_DAMPING;\n this.callbacks.onDragOffset(dampedOffset);\n }\n }\n\n private handleTouchEnd(_e: TouchEvent): void {\n if (!this.touchState) return;\n\n // Update timestamp to prevent click-outside unfocus\n this.recentTouchTimestamp = Date.now();\n\n const deltaX = this.touchState.currentX - this.touchState.startX;\n const deltaTime = performance.now() - this.touchState.startTime;\n const velocity = Math.abs(deltaX) / deltaTime;\n const absDistance = Math.abs(deltaX);\n\n let navigated = false;\n\n // Only process if it was a horizontal swipe\n if (this.touchState.isHorizontalSwipe === true && this.touchState.isDragging) {\n // Check if swipe threshold met (distance or velocity)\n const thresholdMet =\n absDistance >= SWIPE_THRESHOLD_PX ||\n (velocity >= SWIPE_VELOCITY_THRESHOLD && absDistance >= SWIPE_MIN_DISTANCE_FOR_VELOCITY);\n\n if (thresholdMet) {\n navigated = true;\n if (deltaX < 0) {\n // Swipe left -> next image\n this.callbacks.onNext();\n } else {\n // Swipe right -> previous image\n this.callbacks.onPrev();\n }\n }\n }\n\n // Notify drag end (handles snap-back if not navigated)\n if (this.touchState.isDragging) {\n this.callbacks.onDragEnd(navigated);\n }\n\n this.touchState = null;\n }\n\n private handleTouchCancel(_e: TouchEvent): void {\n if (this.touchState?.isDragging) {\n this.callbacks.onDragEnd(false);\n }\n this.touchState = null;\n }\n}\n\nexport { SNAP_BACK_DURATION_MS };\n","/**\n * GoogleDriveLoader.ts\n * Loads images from a public Google Drive folder\n *\n * Public API:\n * - prepare(filter) - Async discovery of images\n * - imagesLength() - Get count of discovered images\n * - imageURLs() - Get ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n *\n * Helper methods (for advanced usage):\n * - extractFolderId(folderUrl)\n * - manualImageUrls(imageIds)\n */\n\nimport type { ImageLoader, IImageFilter, GoogleDriveResponse, GoogleDriveLoaderConfig, GoogleDriveSource } from '../config/types';\n\nexport class GoogleDriveLoader implements ImageLoader {\n private apiKey: string;\n private apiEndpoint: string;\n private debugLogging: boolean;\n private sources: GoogleDriveSource[];\n\n // State for new interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: Partial<GoogleDriveLoaderConfig> = {}) {\n this.apiKey = config.apiKey ?? '';\n this.apiEndpoint = config.apiEndpoint ?? 'https://www.googleapis.com/drive/v3/files';\n this.debugLogging = config.debugLogging ?? false;\n this.sources = config.sources ?? [];\n\n // Validate that we have sources configured\n if (!this.sources || this.sources.length === 0) {\n throw new Error('GoogleDriveLoader requires at least one source to be configured');\n }\n }\n\n /**\n * Prepare the loader by discovering all images from configured sources\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n for (const source of this.sources) {\n if (source.type === 'folder') {\n for (const folderUrl of source.folders) {\n const recursive = source.recursive !== undefined ? source.recursive : true;\n const urls = await this.loadFromFolder(folderUrl, filter, recursive);\n this._discoveredUrls.push(...urls);\n }\n } else if (source.type === 'files') {\n const urls = await this.loadFiles(source.files, filter);\n this._discoveredUrls.push(...urls);\n }\n }\n\n this._prepared = true;\n }\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('GoogleDriveLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('GoogleDriveLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Extract folder ID from various Google Drive URL formats\n * @param folderUrl - Google Drive folder URL\n * @returns Folder ID or null if invalid\n */\n extractFolderId(folderUrl: string): string | null {\n // Handle various URL formats:\n // https://drive.google.com/drive/folders/FOLDER_ID\n // https://drive.google.com/drive/folders/FOLDER_ID?usp=sharing\n\n const patterns = [\n /\\/folders\\/([a-zA-Z0-9_-]+)/, // Standard format\n /id=([a-zA-Z0-9_-]+)/ // Alternative format\n ];\n\n for (const pattern of patterns) {\n const match = folderUrl.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n }\n\n /**\n * Load images from a Google Drive folder\n * @param folderUrl - Google Drive folder URL\n * @param filter - Filter to apply to discovered images\n * @param recursive - Whether to include images from subfolders\n * @returns Promise resolving to array of image URLs\n */\n private async loadFromFolder(folderUrl: string, filter: IImageFilter, recursive: boolean = true): Promise<string[]> {\n const folderId = this.extractFolderId(folderUrl);\n\n if (!folderId) {\n throw new Error('Invalid Google Drive folder URL. Please check the URL format.');\n }\n\n // If no API key is configured, use direct link method\n if (!this.apiKey || this.apiKey === 'YOUR_API_KEY_HERE') {\n return this.loadImagesDirectly(folderId, filter);\n }\n\n try {\n if (recursive) {\n return await this.loadImagesRecursively(folderId, filter);\n } else {\n return await this.loadImagesFromSingleFolder(folderId, filter);\n }\n } catch (error) {\n console.error('Error loading from Google Drive API:', error);\n // Fallback to direct link method\n return this.loadImagesDirectly(folderId, filter);\n }\n }\n\n /**\n * Load images from a single folder (non-recursive)\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesFromSingleFolder(folderId: string, filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n // Query for all files in this folder\n const query = `'${folderId}' in parents and trashed=false`;\n const fields = 'files(id,name,mimeType,thumbnailLink)';\n const url = `${this.apiEndpoint}?q=${encodeURIComponent(query)}&fields=${fields}&key=${this.apiKey}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data: GoogleDriveResponse = await response.json();\n\n // Filter for valid image files only using the provided filter\n const validFiles = data.files.filter(file =>\n file.mimeType.startsWith('image/') && filter.isAllowed(file.name)\n );\n\n this.log(`Found ${validFiles.length} images in folder ${folderId} (non-recursive)`);\n\n // Add image URLs\n validFiles.forEach(file => {\n imageUrls.push(`https://lh3.googleusercontent.com/d/${file.id}=s1600`);\n this.log(`Added file: ${file.name}`);\n });\n\n return imageUrls;\n }\n\n /**\n * Load specific files by their URLs or IDs\n * @param fileUrls - Array of Google Drive file URLs or IDs\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadFiles(fileUrls: string[], filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n for (const fileUrl of fileUrls) {\n const fileId = this.extractFileId(fileUrl);\n\n if (!fileId) {\n this.log(`Skipping invalid file URL: ${fileUrl}`);\n continue;\n }\n\n // Validate it's an image file\n if (this.apiKey && this.apiKey !== 'YOUR_API_KEY_HERE') {\n try {\n // Get file metadata to verify it's an image\n const metadataUrl = `${this.apiEndpoint}/${fileId}?fields=name,mimeType&key=${this.apiKey}`;\n const response = await fetch(metadataUrl);\n\n if (response.ok) {\n const metadata = await response.json();\n if (metadata.mimeType.startsWith('image/') && filter.isAllowed(metadata.name)) {\n imageUrls.push(`https://lh3.googleusercontent.com/d/${fileId}=s1600`);\n this.log(`Added file: ${metadata.name}`);\n } else {\n this.log(`Skipping non-image file: ${metadata.name} (${metadata.mimeType})`);\n }\n } else {\n this.log(`Failed to fetch metadata for file ${fileId}: ${response.status}`);\n }\n } catch (error) {\n this.log(`Error fetching metadata for file ${fileId}:`, error);\n }\n } else {\n // Without API key, assume it's valid and add it\n imageUrls.push(`https://lh3.googleusercontent.com/d/${fileId}=s1600`);\n }\n }\n\n return imageUrls;\n }\n\n /**\n * Extract file ID from Google Drive file URL\n * @param fileUrl - Google Drive file URL or file ID\n * @returns File ID or null if invalid\n */\n private extractFileId(fileUrl: string): string | null {\n // Handle various URL formats:\n // https://drive.google.com/file/d/FILE_ID/view\n // https://drive.google.com/open?id=FILE_ID\n // FILE_ID (raw ID)\n\n // If it looks like a raw ID (no slashes or protocol), return it\n if (!/[/:.]/.test(fileUrl)) {\n return fileUrl;\n }\n\n const patterns = [\n /\\/file\\/d\\/([a-zA-Z0-9_-]+)/, // Standard file format\n /\\/open\\?id=([a-zA-Z0-9_-]+)/, // Alternative format\n /id=([a-zA-Z0-9_-]+)/ // Generic id parameter\n ];\n\n for (const pattern of patterns) {\n const match = fileUrl.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n }\n\n /**\n * Recursively load images from a folder and all its subfolders\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesRecursively(folderId: string, filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n // Query for all files in this folder\n const query = `'${folderId}' in parents and trashed=false`;\n // Request thumbnailLink for PDFs\n const fields = 'files(id,name,mimeType,thumbnailLink)';\n const url = `${this.apiEndpoint}?q=${encodeURIComponent(query)}&fields=${fields}&key=${this.apiKey}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data: GoogleDriveResponse = await response.json();\n\n // Separate images and folders using the provided filter\n const validFiles = data.files.filter(file =>\n file.mimeType.startsWith('image/') && filter.isAllowed(file.name)\n );\n\n const subfolders = data.files.filter(file =>\n file.mimeType === 'application/vnd.google-apps.folder'\n );\n\n this.log(`Found ${data.files.length} total items in folder ${folderId}`);\n // Log details of all files to see what we are missing\n data.files.forEach(f => this.log(` - File: ${f.name} (${f.mimeType})`));\n\n this.log(`- ${validFiles.length} valid files (images only)`);\n this.log(`- ${subfolders.length} subfolders`);\n\n // Add image URLs from this folder\n validFiles.forEach(file => {\n // Use the reliable thumbnail/preview endpoint for both Images and PDFs\n // This works for public folders and handles file format conversion automatically\n // 'sz=w1000' requests a high-quality preview (1000px width)\n // detailed explanation:\n // 1. \"drive.google.com\" is blocked by ad-blockers (net::ERR_BLOCKED_BY_CLIENT)\n // 2. The API's \"thumbnailLink\" is a signed URL that can expire or fail 403.\n // 3. \"lh3.googleusercontent.com/d/{ID}\" is the permanent CDN link structure.\n // It bypasses the domain block AND the signing issues.\n imageUrls.push(`https://lh3.googleusercontent.com/d/${file.id}=s1600`);\n\n this.log(`Added file: ${file.name}`);\n });\n\n // Recursively process subfolders\n for (const folder of subfolders) {\n this.log(`Loading images from subfolder: ${folder.name}`);\n const subfolderImages = await this.loadImagesRecursively(folder.id, filter);\n imageUrls.push(...subfolderImages);\n }\n\n return imageUrls;\n }\n\n /**\n * Direct loading method (no API key required, but less reliable)\n * Uses embedded folder view to scrape image IDs\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply (not used in fallback mode)\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesDirectly(folderId: string, _filter: IImageFilter): Promise<string[]> {\n // For now, we'll return a method that requires the user to manually provide image IDs\n // or we construct URLs based on a known pattern\n\n // This is a fallback - in production, you'd want to use the API\n // For demo purposes, we can try to fetch the folder page and extract image IDs\n\n try {\n // Attempt to fetch folder page (CORS may block this)\n const folderUrl = `https://drive.google.com/embeddedfolderview?id=${folderId}`;\n const response = await fetch(folderUrl, { mode: 'cors' });\n\n if (!response.ok) {\n throw new Error('Cannot access folder directly (CORS or permissions issue)');\n }\n\n const html = await response.text();\n\n // Try to extract image IDs from HTML (this is fragile and may break)\n const imageIdPattern = /\\/file\\/d\\/([a-zA-Z0-9_-]+)/g;\n const matches = [...html.matchAll(imageIdPattern)];\n const imageIds = [...new Set(matches.map(m => m[1]))];\n\n const imageUrls = imageIds.map(id =>\n `https://drive.google.com/uc?export=view&id=${id}`\n );\n\n return imageUrls;\n\n } catch (error) {\n console.error('Direct loading failed:', error);\n throw new Error(\n 'Unable to load images. Please ensure:\\n' +\n '1. The folder is shared publicly (Anyone with the link can view)\\n' +\n '2. The folder contains image files\\n' +\n '3. Consider adding a Google Drive API key in config.js for better reliability'\n );\n }\n }\n\n /**\n * Manually add image URLs (for testing or when auto-loading fails)\n * @param imageIds - Array of Google Drive file IDs\n * @returns Array of direct image URLs\n */\n manualImageUrls(imageIds: string[]): string[] {\n return imageIds.map(id => `https://drive.google.com/uc?export=view&id=${id}`);\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n}\n","/**\n * StaticImageLoader.ts\n * Loads images from predefined URL sources and local paths\n * Compatible with ImageCloud's loader interface\n *\n * Public API:\n * - prepare(filter) - Async discovery of images\n * - imagesLength() - Get count of discovered images\n * - imageURLs() - Get ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n */\n\nimport type { ImageLoader, IImageFilter, StaticSource, StaticLoaderConfig } from '../config/types';\n\nexport class StaticImageLoader implements ImageLoader {\n private validateUrls: boolean;\n private validationTimeout: number;\n private validationMethod: 'head' | 'simple' | 'none';\n private sources: StaticSource[];\n private debugLogging: boolean;\n\n // State for new interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: Partial<StaticLoaderConfig> = {}) {\n this.validateUrls = config.validateUrls !== false;\n this.validationTimeout = config.validationTimeout ?? 5000;\n this.validationMethod = config.validationMethod ?? 'head';\n this.sources = config.sources ?? [];\n this.debugLogging = config.debugLogging ?? false;\n\n // Validate that we have sources configured\n if (!this.sources || this.sources.length === 0) {\n throw new Error('StaticImageLoader requires at least one source to be configured');\n }\n\n this.log('StaticImageLoader initialized with config:', config);\n }\n\n /**\n * Prepare the loader by discovering all images from configured sources\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n this.log(`Processing ${this.sources.length} source(s)`);\n\n // Process sources sequentially to preserve order\n for (const source of this.sources) {\n try {\n const urls = await this.processSource(source, filter);\n this._discoveredUrls.push(...urls);\n } catch (error) {\n console.warn('Failed to process source:', source, error);\n // Continue processing other sources\n }\n }\n\n this._prepared = true;\n this.log(`Successfully loaded ${this._discoveredUrls.length} image(s)`);\n }\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('StaticImageLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('StaticImageLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Process a single source object\n * @param source - Source configuration with type, urls, basePath, files\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of valid URLs from this source\n */\n private async processSource(source: StaticSource, filter: IImageFilter): Promise<string[]> {\n if (!source || !source.type) {\n console.warn('Invalid source object (missing type):', source);\n return [];\n }\n\n if (source.type === 'urls') {\n return await this.processUrls(source.urls || [], filter);\n } else if (source.type === 'path') {\n return await this.processPath(source.basePath, source.files || [], filter);\n } else {\n console.warn(`Unknown source type: ${source.type}`);\n return [];\n }\n }\n\n /**\n * Process a list of direct URLs\n * @param urls - Array of image URLs\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processUrls(urls: string[], filter: IImageFilter): Promise<string[]> {\n if (!Array.isArray(urls)) {\n console.warn('URLs must be an array:', urls);\n return [];\n }\n\n const validUrls: string[] = [];\n\n for (const url of urls) {\n // Apply filter based on URL filename\n const filename = url.split('/').pop() || url;\n if (!filter.isAllowed(filename)) {\n this.log(`Skipping filtered URL: ${url}`);\n continue;\n }\n\n if (this.validateUrls) {\n const isValid = await this.validateUrl(url);\n if (isValid) {\n validUrls.push(url);\n } else {\n console.warn(`Skipping invalid/missing URL: ${url}`);\n }\n } else {\n // No validation - add all URLs\n validUrls.push(url);\n }\n }\n\n return validUrls;\n }\n\n /**\n * Process a path-based source\n * @param basePath - Base path (relative or absolute)\n * @param files - Array of filenames\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processPath(basePath: string | undefined, files: string[], filter: IImageFilter): Promise<string[]> {\n if (!basePath) {\n console.warn('basePath is required for path-type sources');\n return [];\n }\n\n if (!Array.isArray(files)) {\n console.warn('files must be an array:', files);\n return [];\n }\n\n const validUrls: string[] = [];\n\n for (const file of files) {\n // Apply filter based on filename\n if (!filter.isAllowed(file)) {\n this.log(`Skipping filtered file: ${file}`);\n continue;\n }\n\n const url = this.constructUrl(basePath, file);\n\n if (this.validateUrls) {\n const isValid = await this.validateUrl(url);\n if (isValid) {\n validUrls.push(url);\n } else {\n console.warn(`Skipping invalid/missing file: ${url}`);\n }\n } else {\n // No validation - add all URLs\n validUrls.push(url);\n }\n }\n\n return validUrls;\n }\n\n /**\n * Validate a single URL using HEAD request\n * @param url - URL to validate\n * @returns Promise resolving to true if valid and accessible\n */\n private async validateUrl(url: string): Promise<boolean> {\n if (this.validationMethod === 'none') {\n return true;\n }\n\n if (this.validationMethod === 'simple') {\n // Basic URL format check\n try {\n if (typeof window !== 'undefined') {\n new URL(url, window.location.origin);\n } else {\n new URL(url);\n }\n return true;\n } catch {\n return false;\n }\n }\n\n // validationMethod === 'head' (default)\n // For cross-origin URLs, we can't validate due to CORS\n // So we only validate same-origin URLs\n if (typeof window === 'undefined') {\n return true; // In non-browser environment, assume valid\n }\n\n const isSameOrigin = url.startsWith(window.location.origin) ||\n url.startsWith('/');\n\n if (!isSameOrigin) {\n // Cross-origin URL - assume valid, can't validate due to CORS\n this.log(`Skipping validation for cross-origin URL: ${url}`);\n return true;\n }\n\n // Same-origin URL - validate with HEAD request\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.validationTimeout);\n\n const response = await fetch(url, {\n method: 'HEAD',\n signal: controller.signal\n });\n\n clearTimeout(timeoutId);\n\n if (response.ok) {\n return true;\n } else {\n this.log(`Validation failed for ${url}: HTTP ${response.status}`);\n return false;\n }\n\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n this.log(`Validation timeout for ${url}`);\n } else {\n this.log(`Validation failed for ${url}:`, error.message);\n }\n }\n return false;\n }\n }\n\n /**\n * Construct full URL from basePath and filename\n * @param basePath - Base path (relative or absolute)\n * @param filename - Filename to append\n * @returns Complete URL\n */\n private constructUrl(basePath: string, filename: string): string {\n // Remove trailing slash from basePath\n const cleanBase = basePath.replace(/\\/$/, '');\n\n // Check if basePath is absolute URL\n if (this.isAbsoluteUrl(basePath)) {\n return `${cleanBase}/${filename}`;\n }\n\n // Relative path - prepend current origin\n if (typeof window === 'undefined') {\n return `${cleanBase}/${filename}`; // In non-browser environment, return as-is\n }\n\n const origin = window.location.origin;\n // Ensure basePath starts with /\n const normalizedPath = basePath.startsWith('/') ? basePath : '/' + basePath;\n const cleanPath = normalizedPath.replace(/\\/$/, '');\n\n return `${origin}${cleanPath}/${filename}`;\n }\n\n /**\n * Check if URL is absolute (contains protocol)\n * @param url - URL to check\n * @returns True if absolute URL\n */\n private isAbsoluteUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n}\n","/**\n * CompositeLoader.ts\n * Combines multiple image loaders and loads them in parallel\n *\n * Public API:\n * - prepare(filter) - Async discovery of images from all loaders in parallel\n * - imagesLength() - Get combined count of discovered images\n * - imageURLs() - Get combined ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n */\n\nimport type { ImageLoader, IImageFilter } from '../config/types';\n\nexport interface CompositeLoaderConfig {\n loaders: ImageLoader[];\n debugLogging?: boolean;\n}\n\nexport class CompositeLoader implements ImageLoader {\n private loaders: ImageLoader[];\n private debugLogging: boolean;\n\n // State for interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: CompositeLoaderConfig) {\n this.loaders = config.loaders;\n this.debugLogging = config.debugLogging ?? false;\n\n // Validate that we have at least one loader\n if (!this.loaders || this.loaders.length === 0) {\n throw new Error('CompositeLoader requires at least one loader to be configured');\n }\n\n this.log(`CompositeLoader initialized with ${this.loaders.length} loader(s)`);\n }\n\n /**\n * Prepare all loaders in parallel and combine their results\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n this.log(`Preparing ${this.loaders.length} loader(s) in parallel`);\n\n // Prepare all loaders in parallel\n const preparePromises = this.loaders.map((loader, index) => {\n return loader.prepare(filter).then(() => {\n this.log(`Loader ${index} prepared with ${loader.imagesLength()} images`);\n }).catch(error => {\n console.warn(`Loader ${index} failed to prepare:`, error);\n // Continue with other loaders even if one fails\n });\n });\n\n await Promise.all(preparePromises);\n\n // Combine URLs from all prepared loaders (preserves order of loaders array)\n for (const loader of this.loaders) {\n if (loader.isPrepared()) {\n const urls = loader.imageURLs();\n this._discoveredUrls.push(...urls);\n }\n }\n\n this._prepared = true;\n this.log(`CompositeLoader prepared with ${this._discoveredUrls.length} total images`);\n }\n\n /**\n * Get the combined number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('CompositeLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the combined ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('CompositeLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log('[CompositeLoader]', ...args);\n }\n }\n}\n","/**\n * ImageFilter.ts\n * Filters images by extension, designed for future extensibility\n * (e.g., size filters, date filters, etc.)\n */\n\nexport class ImageFilter {\n private allowedExtensions: string[];\n\n /**\n * Create a new ImageFilter\n * @param extensions - Array of allowed file extensions (without dots)\n * Defaults to common image formats if not provided\n */\n constructor(extensions?: string[]) {\n this.allowedExtensions = extensions || [\n 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'\n ];\n }\n\n /**\n * Check if a filename has an allowed extension\n * @param filename - The filename to check (can include path or query string)\n * @returns True if the file extension is allowed\n */\n isAllowed(filename: string): boolean {\n // Remove query string if present (for URLs like image.jpg?w=800)\n const withoutQuery = filename.split('?')[0];\n const extension = withoutQuery.split('.').pop()?.toLowerCase();\n return extension ? this.allowedExtensions.includes(extension) : false;\n }\n\n /**\n * Get the list of allowed extensions\n * @returns Array of allowed extensions\n */\n getAllowedExtensions(): string[] {\n return [...this.allowedExtensions];\n }\n\n // Future expansion methods:\n // isAllowedSize(sizeBytes: number): boolean\n // isAllowedDate(date: Date): boolean\n // isAllowedDimensions(width: number, height: number): boolean\n}\n","/**\n * Minimal functional CSS required for the library to work.\n * Injected automatically - no external CSS file needed.\n */\nexport const FUNCTIONAL_CSS = `\n.fbn-ic-gallery {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n perspective: 1000px;\n}\n\n.fbn-ic-image {\n position: absolute;\n cursor: pointer;\n transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow 0.6s cubic-bezier(0.4, 0, 0.2, 1),\n filter 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n border 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n outline 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n z-index 0s 0.6s;\n will-change: transform;\n user-select: none;\n backface-visibility: hidden;\n -webkit-backface-visibility: hidden;\n}\n\n.fbn-ic-image.fbn-ic-focused {\n z-index: 1000;\n transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow 0.6s cubic-bezier(0.4, 0, 0.2, 1),\n filter 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n border 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n outline 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n z-index 0s 0s;\n will-change: auto;\n}\n\n.fbn-ic-hidden {\n display: none !important;\n}\n`;\n\n/**\n * Inject functional styles into document head.\n * Idempotent - safe to call multiple times.\n */\nexport function injectFunctionalStyles(): void {\n if (typeof document === 'undefined') return;\n const id = 'fbn-ic-functional-styles';\n if (document.getElementById(id)) return;\n const style = document.createElement('style');\n style.id = id;\n style.textContent = FUNCTIONAL_CSS;\n document.head.appendChild(style);\n}\n","/**\n * ImageCloud.ts\n * Main application class\n * Manages initialization and coordination of the interactive image cloud\n */\n\nimport type { ImageCloudOptions, ImageCloudConfig, ImageLayout, ContainerBounds, ImageLoader, EntryAnimationConfig } from './config/types';\nimport { mergeConfig, DEFAULT_CONFIG } from './config/defaults';\nimport { AnimationEngine } from './engines/AnimationEngine';\nimport { EntryAnimationEngine } from './engines/EntryAnimationEngine';\nimport { LayoutEngine } from './engines/LayoutEngine';\nimport { ZoomEngine } from './engines/ZoomEngine';\nimport { SwipeEngine, SNAP_BACK_DURATION_MS } from './engines/SwipeEngine';\nimport { animatePath } from './engines/PathAnimator';\nimport { GoogleDriveLoader } from './loaders/GoogleDriveLoader';\nimport { StaticImageLoader } from './loaders/StaticImageLoader';\nimport { CompositeLoader } from './loaders/CompositeLoader';\nimport { ImageFilter } from './loaders/ImageFilter';\nimport { buildStyleProperties, applyStylesToElement, applyClassNameToElement, removeClassNameFromElement, StyleProperties } from './utils/styleUtils';\nimport { injectFunctionalStyles } from './styles/functionalStyles';\n\nexport class ImageCloud {\n private containerId: string | null;\n private containerRef: HTMLElement | null;\n\n // Internal state\n private fullConfig: ImageCloudConfig;\n private imagesLoaded: boolean;\n private imageElements: HTMLImageElement[];\n private imageLayouts: ImageLayout[];\n private currentImageHeight: number;\n private currentFocusIndex: number | null;\n private hoveredImage: { element: HTMLImageElement; layout: ImageLayout } | null;\n private resizeTimeout: number | null;\n private displayQueue: HTMLImageElement[];\n private queueInterval: number | null;\n private loadGeneration: number;\n\n // Precomputed styling\n private defaultStyles: StyleProperties;\n private hoverStyles: StyleProperties;\n private defaultClassName: string | string[] | undefined;\n private hoverClassName: string | string[] | undefined;\n\n // Modules\n private animationEngine: AnimationEngine;\n private entryAnimationEngine: EntryAnimationEngine;\n private layoutEngine: LayoutEngine;\n private zoomEngine: ZoomEngine;\n private swipeEngine: SwipeEngine | null;\n private imageLoader: ImageLoader;\n private imageFilter: ImageFilter;\n\n // DOM Elements\n private containerEl: HTMLElement | null;\n private loadingEl: HTMLElement | null;\n private errorEl: HTMLElement | null;\n\n constructor(options: ImageCloudOptions = {}) {\n this.fullConfig = mergeConfig(options);\n\n // Container can be a string ID or an HTMLElement reference\n if (options.container instanceof HTMLElement) {\n this.containerRef = options.container;\n this.containerId = null;\n } else {\n this.containerRef = null;\n this.containerId = options.container || 'imageCloud';\n }\n\n // Internal state\n this.imagesLoaded = false;\n this.imageElements = [];\n this.imageLayouts = [];\n this.currentImageHeight = 225;\n this.currentFocusIndex = null;\n this.hoveredImage = null;\n this.resizeTimeout = null;\n this.displayQueue = [];\n this.queueInterval = null;\n this.loadGeneration = 0;\n\n // Initialize engines with new config structure\n this.animationEngine = new AnimationEngine(this.fullConfig.animation);\n this.layoutEngine = new LayoutEngine({\n layout: this.fullConfig.layout,\n image: this.fullConfig.image\n });\n this.zoomEngine = new ZoomEngine(this.fullConfig.interaction.focus, this.animationEngine, this.fullConfig.styling);\n\n // Precompute styling properties\n this.defaultStyles = buildStyleProperties(this.fullConfig.styling?.default);\n this.hoverStyles = buildStyleProperties(this.fullConfig.styling?.hover);\n this.defaultClassName = this.fullConfig.styling?.default?.className;\n this.hoverClassName = this.fullConfig.styling?.hover?.className;\n\n // Initialize entry animation engine with layout-aware defaults\n const entryConfig = this.fullConfig.animation.entry || DEFAULT_CONFIG.animation.entry!;\n this.entryAnimationEngine = new EntryAnimationEngine(\n entryConfig as EntryAnimationConfig,\n this.fullConfig.layout.algorithm\n );\n\n // SwipeEngine will be initialized after container is available\n this.swipeEngine = null;\n\n // Initialize image filter with configured extensions\n this.imageFilter = this.createImageFilter();\n\n // Initialize image loader based on type\n this.imageLoader = this.createLoader();\n\n // DOM Elements (will be fetched on init)\n this.containerEl = null;\n this.loadingEl = null;\n this.errorEl = null;\n }\n\n /**\n * Create image filter based on config\n */\n private createImageFilter(): ImageFilter {\n const loaderType = this.fullConfig.loader.type;\n\n // Get extensions from the appropriate loader config\n let extensions: string[] | undefined;\n if (loaderType === 'googleDrive') {\n extensions = this.fullConfig.loader.googleDrive?.allowedExtensions;\n } else {\n extensions = this.fullConfig.loader.static?.allowedExtensions;\n }\n\n return new ImageFilter(extensions);\n }\n\n /**\n * Create appropriate image loader based on config\n */\n private createLoader(): ImageLoader {\n return this.createLoaderFromConfig(this.fullConfig.loader);\n }\n\n /**\n * Create a loader from a LoaderConfig object (supports recursive composite loaders)\n */\n private createLoaderFromConfig(config: typeof this.fullConfig.loader): ImageLoader {\n const loaderType = config.type;\n\n if (loaderType === 'static') {\n const staticConfig = config.static!;\n return new StaticImageLoader(staticConfig);\n } else if (loaderType === 'composite') {\n const compositeConfig = config.composite!;\n const childLoaders = compositeConfig.loaders.map(loaderConfig =>\n this.createLoaderFromConfig(loaderConfig)\n );\n return new CompositeLoader({\n loaders: childLoaders,\n debugLogging: compositeConfig.debugLogging\n });\n } else {\n const driveConfig = config.googleDrive!;\n return new GoogleDriveLoader(driveConfig);\n }\n }\n\n /**\n * Initialize the gallery\n */\n async init(): Promise<void> {\n try {\n // Inject functional styles (idempotent)\n injectFunctionalStyles();\n\n // 1. Setup DOM\n if (this.containerRef) {\n this.containerEl = this.containerRef;\n } else {\n this.containerEl = document.getElementById(this.containerId!);\n if (!this.containerEl) {\n throw new Error(`Container #${this.containerId} not found`);\n }\n }\n\n // Add gallery class for CSS scoping\n this.containerEl.classList.add('fbn-ic-gallery');\n\n // Initialize swipe engine for touch navigation\n this.swipeEngine = new SwipeEngine(this.containerEl, {\n onNext: () => this.navigateToNextImage(),\n onPrev: () => this.navigateToPreviousImage(),\n onDragOffset: (offset) => this.zoomEngine.setDragOffset(offset),\n onDragEnd: (navigated) => {\n if (!navigated) {\n // Snap back to center with animation\n this.zoomEngine.clearDragOffset(true, SNAP_BACK_DURATION_MS);\n } else {\n // Clear offset immediately (navigation handles transition)\n this.zoomEngine.clearDragOffset(false);\n }\n }\n });\n\n // Create or bind UI elements\n this.setupUI();\n\n // 2. Setup Event Listeners\n this.setupEventListeners();\n\n // 3. Load Images\n this.logDebug('ImageCloud initialized');\n await this.loadImages();\n\n } catch (error) {\n console.error('Gallery initialization failed:', error);\n if (this.errorEl && error instanceof Error) {\n this.showError('Gallery failed to initialize: ' + error.message);\n }\n }\n }\n\n private setupUI(): void {\n // Look for existing elements or create them\n this.loadingEl = document.getElementById('loading');\n this.errorEl = document.getElementById('error');\n }\n\n private setupEventListeners(): void {\n // Global keyboard events\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n this.zoomEngine.unfocusImage();\n this.currentFocusIndex = null;\n this.swipeEngine?.disable();\n } else if (e.key === 'ArrowRight') {\n this.navigateToNextImage();\n } else if (e.key === 'ArrowLeft') {\n this.navigateToPreviousImage();\n } else if ((e.key === 'Enter' || e.key === ' ') && this.hoveredImage) {\n // Focus the hovered image (works whether or not another image is focused)\n this.handleImageClick(this.hoveredImage.element, this.hoveredImage.layout);\n e.preventDefault(); // Prevent space from scrolling the page\n }\n });\n\n document.addEventListener('click', (e: MouseEvent) => {\n // Ignore clicks that follow touch events (prevents unfocus during swipe)\n if (this.swipeEngine?.hadRecentTouch()) {\n return;\n }\n if (!(e.target as HTMLElement).closest('.fbn-ic-image')) {\n this.zoomEngine.unfocusImage();\n this.currentFocusIndex = null;\n this.swipeEngine?.disable();\n }\n });\n\n // Resize handler\n window.addEventListener('resize', () => this.handleResize());\n }\n\n /**\n * Navigate to the next image (Right arrow)\n */\n private navigateToNextImage(): void {\n if (this.currentFocusIndex === null || this.imageElements.length === 0) return;\n\n const nextIndex = (this.currentFocusIndex + 1) % this.imageElements.length;\n this.navigateToImage(nextIndex);\n }\n\n /**\n * Navigate to the previous image (Left arrow)\n */\n private navigateToPreviousImage(): void {\n if (this.currentFocusIndex === null || this.imageElements.length === 0) return;\n\n const prevIndex = (this.currentFocusIndex - 1 + this.imageElements.length) % this.imageElements.length;\n this.navigateToImage(prevIndex);\n }\n\n /**\n * Navigate to a specific image by index\n */\n private async navigateToImage(index: number): Promise<void> {\n if (index < 0 || index >= this.imageElements.length) return;\n\n const imageElement = this.imageElements[index];\n const layout = this.imageLayouts[index];\n\n if (!imageElement || !layout) return;\n\n // Reuse the same focus mechanism as clicking\n await this.handleImageClick(imageElement, layout);\n }\n\n private handleResize(): void {\n if (!this.imagesLoaded) return;\n\n if (this.resizeTimeout !== null) {\n clearTimeout(this.resizeTimeout);\n }\n\n this.resizeTimeout = window.setTimeout(() => {\n const newHeight = this.getImageHeight();\n\n if (newHeight !== this.currentImageHeight) {\n this.logDebug(`Window resized to new breakpoint (height: ${newHeight}px). Reloading images...`);\n // Reload images with new breakpoint\n this.loadImages();\n } else {\n this.logDebug('Window resized (no breakpoint change)');\n }\n }, 500);\n }\n\n private getImageHeight(): number {\n const width = window.innerWidth;\n const responsive = this.fullConfig.layout.responsive;\n\n // Get sizing config for adaptive mode defaults\n const sizing = this.fullConfig.image.sizing;\n const maxSize = sizing?.maxSize ?? 400;\n\n // Use responsive breakpoints to determine max height\n // These serve as upper bounds for the adaptive sizing\n if (!responsive) {\n // Fallback defaults if responsive not configured\n if (width <= 767) return Math.min(100, maxSize);\n if (width <= 1199) return Math.min(180, maxSize);\n return Math.min(225, maxSize);\n }\n\n if (width <= responsive.mobile.maxWidth) {\n return Math.min(100, maxSize); // Mobile\n }\n if (width <= responsive.tablet.maxWidth) {\n return Math.min(180, maxSize); // Tablet\n }\n return Math.min(225, maxSize); // Screen (desktop)\n }\n\n /**\n * Get container bounds for layout calculations\n */\n private getContainerBounds(): { width: number; height: number } {\n if (!this.containerEl) {\n return { width: window.innerWidth, height: window.innerHeight * 0.7 };\n }\n return {\n width: this.containerEl.offsetWidth,\n height: this.containerEl.offsetHeight || window.innerHeight * 0.7\n };\n }\n\n /**\n * Load images using the unified loader interface\n */\n private async loadImages(): Promise<void> {\n try {\n this.showLoading(true);\n this.hideError();\n this.clearImageCloud();\n\n // Prepare the loader (show spinner during this)\n await this.imageLoader.prepare(this.imageFilter);\n\n // Get image count and URLs from loader\n const imageCount = this.imageLoader.imagesLength();\n let imageUrls = this.imageLoader.imageURLs();\n\n if (imageCount === 0) {\n this.showError('No images found.');\n this.showLoading(false);\n return;\n }\n\n // Calculate adaptive sizing based on container and image count\n const containerBounds = this.getContainerBounds();\n const responsiveHeight = this.getImageHeight();\n const viewportWidth = window.innerWidth;\n\n this.logDebug(`Adaptive sizing input: container=${containerBounds.width}x${containerBounds.height}px, images=${imageCount}, responsiveMax=${responsiveHeight}px`);\n\n const sizingResult = this.layoutEngine.calculateAdaptiveSize(\n containerBounds,\n imageCount,\n responsiveHeight,\n viewportWidth\n );\n\n this.logDebug(`Adaptive sizing result: height=${sizingResult.height}px`);\n\n await this.createImageCloud(imageUrls, sizingResult.height);\n\n this.showLoading(false);\n this.imagesLoaded = true;\n\n } catch (error) {\n console.error('Error loading images:', error);\n if (error instanceof Error) {\n this.showError(error.message || 'Failed to load images.');\n }\n this.showLoading(false);\n }\n }\n\n /**\n * Helper for debug logging\n */\n private logDebug(...args: unknown[]): void {\n if (this.fullConfig.debug && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n\n private async createImageCloud(imageUrls: string[], imageHeight: number): Promise<void> {\n if (!this.containerEl) return;\n\n const containerBounds = this.getContainerBounds();\n this.currentImageHeight = imageHeight;\n\n // Capture current generation to detect stale callbacks\n const currentGeneration = this.loadGeneration;\n\n // Generate layout\n const layouts = this.layoutEngine.generateLayout(imageUrls.length, containerBounds, { fixedHeight: imageHeight } as any);\n this.imageLayouts = layouts;\n\n this.displayQueue = [];\n let processedCount = 0;\n\n // Helper to display a single image with animation\n const displayImage = (img: HTMLImageElement) => {\n if (!this.containerEl) return;\n\n this.containerEl.appendChild(img);\n this.imageElements.push(img);\n\n requestAnimationFrame(() => {\n void img.offsetWidth; // Force reflow\n // Use configured default opacity, or 1 if not specified\n img.style.opacity = this.defaultStyles.opacity ?? '1';\n\n // Check if we need JS animation for path type, rotation, or scale\n const needsJSAnimation = img.dataset.startX &&\n (this.entryAnimationEngine.requiresJSAnimation() ||\n this.entryAnimationEngine.requiresJSRotation() ||\n this.entryAnimationEngine.requiresJSScale() ||\n img.dataset.startRotation !== img.dataset.rotation ||\n img.dataset.startScale !== img.dataset.scale);\n\n if (needsJSAnimation) {\n // Use animatePath for bounce, elastic, wave paths or rotation/scale animation\n const startPosition = {\n x: parseFloat(img.dataset.startX!),\n y: parseFloat(img.dataset.startY!)\n };\n const endPosition = {\n x: parseFloat(img.dataset.endX!),\n y: parseFloat(img.dataset.endY!)\n };\n const imageWidth = parseFloat(img.dataset.imageWidth!);\n const imageHeight = parseFloat(img.dataset.imageHeight!);\n const rotation = parseFloat(img.dataset.rotation!);\n const scale = parseFloat(img.dataset.scale!);\n const startRotation = img.dataset.startRotation\n ? parseFloat(img.dataset.startRotation)\n : rotation;\n const startScale = img.dataset.startScale\n ? parseFloat(img.dataset.startScale)\n : scale;\n const timing = this.entryAnimationEngine.getTiming();\n\n animatePath({\n element: img,\n startPosition,\n endPosition,\n pathConfig: this.entryAnimationEngine.getPathConfig(),\n duration: timing.duration,\n imageWidth,\n imageHeight,\n rotation,\n scale,\n rotationConfig: this.entryAnimationEngine.getRotationConfig(),\n startRotation,\n scaleConfig: this.entryAnimationEngine.getScaleConfig(),\n startScale\n });\n } else {\n // Use CSS transition for linear/arc paths without rotation animation\n const finalTransform = img.dataset.finalTransform || '';\n img.style.transform = finalTransform;\n }\n\n // Debug: log final state for first few images\n const imgIndex = parseInt(img.dataset.imageId || '0');\n if (this.fullConfig.debug && imgIndex < 3) {\n const finalTransform = img.dataset.finalTransform || '';\n console.log(`Image ${imgIndex} final state:`, {\n left: img.style.left,\n top: img.style.top,\n width: img.style.width,\n height: img.style.height,\n computedWidth: img.offsetWidth,\n computedHeight: img.offsetHeight,\n transform: finalTransform,\n pathType: this.entryAnimationEngine.getPathType()\n });\n }\n });\n\n processedCount++;\n };\n\n const startQueueProcessing = () => {\n this.logDebug('Starting queue processing, enabled:', this.fullConfig.animation.queue.enabled);\n\n // If queue is disabled, display all images immediately\n if (!this.fullConfig.animation.queue.enabled) {\n while (this.displayQueue.length > 0) {\n const img = this.displayQueue.shift();\n if (img) {\n displayImage(img);\n }\n }\n return;\n }\n\n // Queue is enabled - stagger images with interval\n // Clear any existing interval before creating new one\n if (this.queueInterval !== null) {\n clearInterval(this.queueInterval);\n }\n this.queueInterval = window.setInterval(() => {\n // Check if this interval is still valid (generation hasn't changed)\n if (currentGeneration !== this.loadGeneration) {\n if (this.queueInterval !== null) {\n clearInterval(this.queueInterval);\n this.queueInterval = null;\n }\n return;\n }\n\n if (this.displayQueue.length > 0) {\n const img = this.displayQueue.shift();\n if (img) {\n displayImage(img);\n }\n }\n\n if (processedCount >= imageUrls.length && this.displayQueue.length === 0) {\n if (this.queueInterval !== null) {\n clearInterval(this.queueInterval);\n this.queueInterval = null;\n }\n }\n }, this.fullConfig.animation.queue.interval);\n };\n\n // Visibility Check\n if ('IntersectionObserver' in window && this.containerEl) {\n const observer = new IntersectionObserver((entries) => {\n entries.forEach(entry => {\n if (entry.isIntersecting) {\n startQueueProcessing();\n observer.disconnect();\n }\n });\n }, { threshold: 0.1, rootMargin: '50px' });\n observer.observe(this.containerEl);\n } else {\n startQueueProcessing();\n }\n\n // Debug: Draw center markers if debugCenters is enabled\n if (this.fullConfig.layout.debugCenters && this.containerEl) {\n // Remove any existing debug markers\n this.containerEl.querySelectorAll('.fbn-ic-debug-center').forEach(el => el.remove());\n\n layouts.forEach((layout, index) => {\n const marker = document.createElement('div');\n marker.className = 'fbn-ic-debug-center';\n marker.style.position = 'absolute';\n marker.style.width = '12px';\n marker.style.height = '12px';\n marker.style.borderRadius = '50%';\n marker.style.backgroundColor = 'red';\n marker.style.border = '2px solid yellow';\n marker.style.zIndex = '9999';\n marker.style.pointerEvents = 'none';\n // Center position: layout.x and layout.y now store the center position directly\n const centerX = layout.x;\n const centerY = layout.y;\n marker.style.left = `${centerX - 6}px`; // Offset by half marker size\n marker.style.top = `${centerY - 6}px`;\n marker.title = `Image ${index}: center (${Math.round(centerX)}, ${Math.round(centerY)})`;\n this.containerEl!.appendChild(marker);\n });\n }\n\n // Create elements\n imageUrls.forEach((url, index) => {\n const img = document.createElement('img');\n // NOTE: img.src is set AFTER onload handler to ensure handler catches cached images\n img.referrerPolicy = 'no-referrer';\n img.classList.add('fbn-ic-image');\n img.dataset.imageId = String(index);\n\n const layout = layouts[index];\n img.style.position = 'absolute';\n img.style.width = 'auto';\n img.style.height = `${imageHeight}px`;\n img.style.left = `${layout.x}px`;\n img.style.top = `${layout.y}px`;\n // Transform will be applied in onload after we know the actual dimensions\n\n // Apply layout-specified border only if no styling config border is set\n if (layout.borderColor && !this.fullConfig.styling?.default?.border) {\n img.style.border = `5px solid ${layout.borderColor}`;\n img.style.boxSizing = 'border-box';\n }\n if (layout.zIndex) img.style.zIndex = String(layout.zIndex);\n\n // Apply default styling state\n applyStylesToElement(img, this.defaultStyles);\n applyClassNameToElement(img, this.defaultClassName);\n\n // Hover event handlers\n // Use isInvolved() to prevent hover styles on images that are focused or animating\n img.addEventListener('mouseenter', () => {\n this.hoveredImage = { element: img, layout };\n if (!this.zoomEngine.isInvolved(img)) {\n applyStylesToElement(img, this.hoverStyles);\n applyClassNameToElement(img, this.hoverClassName);\n }\n });\n\n img.addEventListener('mouseleave', () => {\n this.hoveredImage = null;\n if (!this.zoomEngine.isInvolved(img)) {\n applyStylesToElement(img, this.defaultStyles);\n removeClassNameFromElement(img, this.hoverClassName);\n applyClassNameToElement(img, this.defaultClassName);\n }\n });\n\n img.addEventListener('click', (e: MouseEvent) => {\n e.stopPropagation();\n this.handleImageClick(img, layout);\n });\n\n img.style.opacity = '0';\n img.style.transition = this.entryAnimationEngine.getTransitionCSS();\n\n img.onload = () => {\n // Ignore if generation has changed (stale callback from previous load)\n if (currentGeneration !== this.loadGeneration) {\n return;\n }\n\n const aspectRatio = img.naturalWidth / img.naturalHeight;\n const renderedWidth = imageHeight * aspectRatio;\n\n // Set explicit width so transform calculations are accurate\n img.style.width = `${renderedWidth}px`;\n\n // Use EntryAnimationEngine for start position calculation\n const finalPosition = { x: layout.x, y: layout.y };\n const imageSize = { width: renderedWidth, height: imageHeight };\n\n const startPosition = this.entryAnimationEngine.calculateStartPosition(\n finalPosition,\n imageSize,\n containerBounds,\n index,\n imageUrls.length\n );\n\n // Calculate start rotation based on entry rotation config\n const startRotation = this.entryAnimationEngine.calculateStartRotation(layout.rotation);\n\n // Calculate start scale based on entry scale config\n const startScale = this.entryAnimationEngine.calculateStartScale(layout.scale);\n\n const finalTransform = this.entryAnimationEngine.buildFinalTransform(\n layout.rotation,\n layout.scale,\n renderedWidth,\n imageHeight\n );\n const startTransform = this.entryAnimationEngine.buildStartTransform(\n startPosition,\n finalPosition,\n layout.rotation,\n layout.scale,\n renderedWidth,\n imageHeight,\n startRotation,\n startScale\n );\n\n if (this.fullConfig.debug && index < 3) {\n console.log(`Image ${index}:`, {\n finalPosition,\n imageSize,\n left: layout.x,\n top: layout.y,\n finalTransform,\n renderedWidth,\n renderedHeight: imageHeight\n });\n }\n\n img.style.transform = startTransform;\n img.dataset.finalTransform = finalTransform;\n\n // Store animation data for JS-animated paths (bounce, elastic, wave)\n // or when rotation/scale animation is needed\n const needsJSAnimation = this.entryAnimationEngine.requiresJSAnimation() ||\n this.entryAnimationEngine.requiresJSRotation() ||\n this.entryAnimationEngine.requiresJSScale() ||\n startRotation !== layout.rotation ||\n startScale !== layout.scale;\n\n if (needsJSAnimation) {\n img.dataset.startX = String(startPosition.x);\n img.dataset.startY = String(startPosition.y);\n img.dataset.endX = String(finalPosition.x);\n img.dataset.endY = String(finalPosition.y);\n img.dataset.imageWidth = String(renderedWidth);\n img.dataset.imageHeight = String(imageHeight);\n img.dataset.rotation = String(layout.rotation);\n img.dataset.scale = String(layout.scale);\n img.dataset.startRotation = String(startRotation);\n img.dataset.startScale = String(startScale);\n }\n\n this.displayQueue.push(img);\n };\n\n img.onerror = () => processedCount++;\n\n // Set src AFTER onload handler to ensure it catches cached images\n img.src = url;\n });\n }\n\n private async handleImageClick(imageElement: HTMLImageElement, originalLayout: ImageLayout): Promise<void> {\n if (!this.containerEl) return;\n\n const isFocused = this.zoomEngine.isFocused(imageElement);\n const bounds: ContainerBounds = {\n width: this.containerEl.offsetWidth,\n height: this.containerEl.offsetHeight\n };\n\n if (isFocused) {\n await this.zoomEngine.unfocusImage();\n this.currentFocusIndex = null;\n this.swipeEngine?.disable();\n } else {\n // Track the focused image index for keyboard navigation\n const imageId = imageElement.dataset.imageId;\n this.currentFocusIndex = imageId !== undefined ? parseInt(imageId, 10) : null;\n this.swipeEngine?.enable();\n await this.zoomEngine.focusImage(imageElement, bounds, originalLayout);\n }\n }\n\n /**\n * Clear the image cloud and reset state\n */\n clearImageCloud(): void {\n // Clear queue processing interval to prevent stale images from being added\n if (this.queueInterval !== null) {\n clearInterval(this.queueInterval);\n this.queueInterval = null;\n }\n // Increment generation to invalidate pending image onload handlers\n this.loadGeneration++;\n this.displayQueue = [];\n\n if (this.containerEl) {\n this.containerEl.innerHTML = '';\n }\n this.imageElements = [];\n this.imageLayouts = [];\n this.currentFocusIndex = null;\n this.hoveredImage = null;\n this.layoutEngine.reset();\n this.zoomEngine.reset();\n this.imagesLoaded = false;\n }\n\n private showLoading(show: boolean): void {\n if (!this.fullConfig.rendering.ui.showLoadingSpinner || !this.loadingEl) return;\n if (show) {\n this.loadingEl.classList.remove('fbn-ic-hidden');\n } else {\n this.loadingEl.classList.add('fbn-ic-hidden');\n }\n }\n\n private showError(message: string): void {\n if (!this.errorEl) return;\n this.errorEl.textContent = message;\n this.errorEl.classList.remove('fbn-ic-hidden');\n }\n\n private hideError(): void {\n if (this.errorEl) {\n this.errorEl.classList.add('fbn-ic-hidden');\n }\n }\n\n /**\n * Destroy the gallery and clean up resources\n */\n destroy(): void {\n this.clearImageCloud();\n // Remove event listeners\n if (this.resizeTimeout !== null) {\n clearTimeout(this.resizeTimeout);\n }\n this.swipeEngine?.destroy();\n }\n}\n","/**\n * Image Cloud Library - Auto-Initialization Entry Point\n *\n * Automatically initializes galleries from HTML data attributes\n * Usage: Include this script and add data-image-cloud attribute to containers\n */\n\n// Import CSS as inline string for self-contained bundle\nimport css from './styles/image-cloud.css?inline';\n\nimport { ImageCloud } from './ImageCloud';\n\n/** Inject library styles into <head> (idempotent) */\nfunction injectStyles(): void {\n if (typeof document === 'undefined') return;\n const id = 'fbn-ic-styles';\n if (document.getElementById(id)) return;\n const style = document.createElement('style');\n style.id = id;\n style.textContent = css;\n document.head.appendChild(style);\n}\ninjectStyles();\nimport type { ImageCloudOptions } from './config/types';\n\n/**\n * Auto-initialize galleries from data attributes\n */\nfunction autoInitialize(): void {\n if (typeof document === 'undefined') {\n console.warn('ImageCloud: Document not available (not in browser environment)');\n return;\n }\n\n const initGalleries = () => {\n // Find all elements marked with data-image-cloud or data-image-gallery (legacy)\n const containers = document.querySelectorAll('[data-image-cloud], [data-image-gallery]');\n\n if (containers.length === 0) {\n // Quietly return if no galleries found (normal case for some pages)\n return;\n }\n\n // Initialize each gallery\n containers.forEach(container => {\n const element = container as HTMLElement;\n\n // Container must have an ID for the gallery to work\n if (!element.id) {\n console.error('ImageCloud: Container with data-image-cloud must have an id attribute');\n return;\n }\n\n // Check for JSON configuration\n // Supports data-config (preferred) or data-gallery-config (legacy alias)\n const jsonConfig = element.dataset.config || element.dataset.galleryConfig;\n let options: ImageCloudOptions;\n\n if (jsonConfig) {\n try {\n const parsed = JSON.parse(jsonConfig);\n options = {\n container: element.id,\n ...parsed\n };\n } catch (error) {\n console.error(`ImageCloud: Failed to parse configuration JSON for #${element.id}:`, error);\n return;\n }\n } else {\n console.error(`ImageCloud: Missing configuration for #${element.id}. Add data-config='{...}' attribute.`);\n return;\n }\n\n // Initialize gallery\n const gallery = new ImageCloud(options);\n gallery.init().catch(error => {\n console.error('ImageCloud initialization failed:', error);\n });\n });\n };\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initGalleries);\n } else {\n // DOM is already ready\n initGalleries();\n }\n}\n\n// Auto-run when this module is imported\nautoInitialize();\n\n// Also export for manual control if needed\nexport { autoInitialize };\nexport { ImageCloud } from './ImageCloud';\n// Backwards compatibility\nexport { ImageCloud as ImageGallery } from './ImageCloud';\n"],"names":["SHADOW_PRESETS","BOUNCE_PRESETS","ELASTIC_PRESETS","WAVE_PATH_PRESETS","DEFAULT_PATH_CONFIG","DEFAULT_ENTRY_ROTATION","DEFAULT_ENTRY_SCALE","DEFAULT_STYLING","DEFAULT_WAVE_CONFIG","DEFAULT_RESPONSIVE_BREAKPOINTS","DEFAULT_IMAGE_SIZING","DEFAULT_IMAGE_ROTATION","DEFAULT_IMAGE_CONFIG","DEFAULT_CONFIG","deepMergeStyleState","base","override","merged","deepMergeStyling","defaults","userStyling","mergedDefault","mergedHover","mergedFocused","deepMergeImageConfig","userImage","userVariance","validMin","_b","_a","validMax","_d","_c","userRange","_f","_e","_h","_g","convertLegacyRotationConfig","userConfig","legacyRotation","convertLegacyVarianceConfig","legacyVariance","mergeConfig","combinedImageConfig","resolveBounceConfig","preset","overrides","resolveElasticConfig","resolveWavePathConfig","AnimationEngine","config","params","transforms","x","y","element","from","to","duration","easing","animDuration","animEasing","fromTransform","toTransform","animation","handle","commitStyle","snapshot","currentTransform","allAnimations","anim","transformStr","matrix","scale","rotation","properties","resolve","originalState","ms","lerp","start","end","t","calculateBouncePosition","overshoot","bounces","decayRatio","dx","dy","keyframes","generateBounceKeyframes","progress","segmentStart","segmentEnd","segmentOvershoot","isOvershootPhase","i","segmentT","easeOutQuad","prevOvershoot","k","fromProgress","currentTime","currentOvershoot","bounceTime","calculateElasticPosition","stiffness","damping","mass","oscillations","omega","zeta","dampedFreq","envelope","oscillation","calculateWavePosition","amplitude","frequency","decay","decayRate","phase","length","perpX","perpY","wavePhase","decayFactor","waveOffset","progressT","easeOutCubic","calculateWobbleRotation","finalRotation","wobbleConfig","wobbleOffset","calculatePopScale","finalScale","popConfig","bounceDecay","undershoot","currentScale","prevTime","prevScale","segmentProgress","easedProgress","animatePath","options","startPosition","endPosition","pathConfig","imageWidth","imageHeight","onComplete","rotationConfig","startRotation","scaleConfig","startScale","pathType","animateRotation","isWobbleMode","needsRotationAnimation","animateScale","isPopMode","startTime","centerOffsetX","centerOffsetY","tick","elapsed","position","translateX","translateY","currentRotation","requiresJSAnimation","LAYOUT_ENTRY_DEFAULTS","EntryAnimationEngine","layoutAlgorithm","finalPosition","imageSize","containerBounds","imageIndex","totalImages","offset","centerX","centerY","distLeft","distRight","distTop","distBottom","minDist","startX","startY","edge","_finalPosition","_imageSize","edges","randomEdge","circularConfig","distribution","radius","radiusConfig","percentage","angle","stagger","centerTranslate","offsetX","offsetY","startConfig","range","spinCount","direction","RandomPlacementGenerator","imageConfig","imageCount","layouts","width","height","padding","baseImageSize","rotationMode","minRotation","maxRotation","varianceMin","varianceMax","_i","hasVariance","halfWidth","halfHeight","maxX","maxY","minX","minY","scaledImageSize","layout","min","max","RadialPlacementGenerator","debugRadials","scaleDecay","debugPalette","cx","cy","estimatedMaxRings","varianceScale","centerSize","processedCount","currentRing","normalizedRing","ringScale","radiusY","radiusX","circumference","estimatedItemWidth","itemsInRing","angleStep","ringOffset","combinedScale","DEFAULT_GRID_CONFIG","OVERFLOW_OFFSET_PATTERN","GridPlacementGenerator","gridConfig","availableWidth","availableHeight","columns","rows","hasRowStagger","hasColumnStagger","effectiveColumns","effectiveRows","cellWidth","cellHeight","staggerOffsetX","staggerOffsetY","overlapMultiplier","cellBasedSize","totalGridWidth","totalGridHeight","gridOffsetX","gridOffsetY","cellCount","hasFixedGrid","isOverflowMode","cellStackCount","overflowOffsetPx","col","row","stackLayer","overflowIndex","targetCell","cellCenterX","cellCenterY","patternIndex","pattern","maxJitterX","maxJitterY","itemsInLastRow","lastRowWidth","alignmentOffset","zIndex","_baseImageSize","aspectRatio","GOLDEN_ANGLE","DEFAULT_SPIRAL_CONFIG","SpiralPlacementGenerator","spiralConfig","maxRadius","directionMultiplier","theta","normalizedRadius","decayScale","clampedX","clampedY","baseRotation","rotationVariance","tangentAngle","b","psi","index","tightness","maxTheta","a","maxComputedRadius","DEFAULT_CLUSTER_CONFIG","ClusterPlacementGenerator","clusterConfig","clusterCount","clusterCenters","imagesPerCluster","clusterIdx","cluster","imagesInThisCluster","distance","sizeMultiplier","normalizedDistance","configCount","clusterSpacing","countByImages","countBySpace","count","centers","bestCandidate","bestMinDistance","attempt","candidate","minDistance","existing","u","v","value","WavePlacementGenerator","waveConfig","phaseShift","synchronization","imagesPerRow","halfImageWidth","endX","horizontalSpacing","minCenterY","maxCenterY","rowSpacing","rowIndex","baseY","imgInRow","waveY","containerWidth","normalizedX","derivative","LayoutEngine","imageId","newConfig","viewportWidth","breakpoints","sizing","responsiveHeight","breakpoint","maxHeight","userBaseHeight","minSize","maxSize","targetCoverage","densityFactor","areaPerImage","calculatedHeight","finalHeight","floor","ZoomState","isShadowPreset","resolveShadow","shadow","buildFilterString","filter","parts","ds","buildSingleBorder","style","color","buildStyleProperties","state","styles","baseRadius","baseBorder","topBorder","rightBorder","bottomBorder","leftBorder","filterStr","applyStylesToElement","resolveClassName","className","applyClassNameToElement","resolved","cls","removeClassNameFromElement","Z_INDEX","ZoomEngine","animationEngine","styling","normalizedPercent","targetHeight","focusHeight","focusWidth","maxWidth","targetX","targetY","fromWidth","fromHeight","toWidth","toHeight","fromTransformStr","toTransformStr","originalZIndex","fromDimensions","originalWidth","originalHeight","focusDimensions","focusTransform","startTransform","startWidth","startHeight","toState","targetWidth","imageElement","myGeneration","incomingSnapshot","incomingFrom","incomingFromDimensions","_j","_k","_l","incomingUnfocus","newImageElement","animate","centeredTransform","SWIPE_THRESHOLD_PX","SWIPE_VELOCITY_THRESHOLD","SWIPE_MIN_DISTANCE_FOR_VELOCITY","DRAG_DAMPING","SNAP_BACK_DURATION_MS","HORIZONTAL_ANGLE_THRESHOLD_DEG","_SwipeEngine","container","callbacks","e","touch","deltaX","deltaY","angleDeg","dampedOffset","deltaTime","velocity","absDistance","navigated","SwipeEngine","GoogleDriveLoader","source","folderUrl","recursive","urls","patterns","match","folderId","error","imageUrls","query","url","response","validFiles","file","fileUrls","fileUrl","fileId","metadataUrl","metadata","data","subfolders","f","folder","subfolderImages","_filter","html","imageIdPattern","matches","m","id","imageIds","args","StaticImageLoader","validUrls","filename","basePath","files","controller","timeoutId","cleanBase","origin","cleanPath","CompositeLoader","preparePromises","loader","ImageFilter","extensions","extension","FUNCTIONAL_CSS","injectFunctionalStyles","ImageCloud","entryConfig","loaderType","staticConfig","compositeConfig","childLoaders","loaderConfig","driveConfig","nextIndex","prevIndex","newHeight","responsive","sizingResult","currentGeneration","displayImage","img","timing","finalTransform","imgIndex","startQueueProcessing","observer","entries","entry","el","marker","renderedWidth","originalLayout","isFocused","bounds","show","message","injectStyles","css","autoInitialize","initGalleries","containers","jsonConfig","parsed"],"mappings":"m/EAUaA,KAA+C,OAAO,OAAO;AAAA,EACxE,MAAQ;AAAA,EACR,IAAM;AAAA,EACN,IAAM;AAAA,EACN,IAAM;AAAA,EACN,MAAQ;AACV,CAAC,GAKYC,KAAyD,OAAO,OAAO;AAAA,EAClF,WAAW,OAAO,OAAO,EAAE,WAAW,MAAM,SAAS,GAAG,YAAY,KAAK;AAAA,EACzE,SAAS,OAAO,OAAO,EAAE,WAAW,MAAM,SAAS,GAAG,YAAY,KAAK;AAAA,EACvE,QAAQ,OAAO,OAAO,EAAE,WAAW,MAAM,SAAS,GAAG,YAAY,IAAA,CAAK;AACxE,CAAC,GAKYC,KAA4D,OAAO,OAAO;AAAA,EACrF,QAAQ,OAAO,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,MAAM,GAAG,cAAc,EAAA,CAAG;AAAA,EAC/E,QAAQ,OAAO,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,MAAM,GAAG,cAAc,EAAA,CAAG;AAAA,EAC/E,QAAQ,OAAO,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,MAAM,KAAK,cAAc,EAAA,CAAG;AAAA,EACjF,QAAQ,OAAO,OAAO,EAAE,WAAW,KAAK,SAAS,IAAI,MAAM,KAAK,cAAc,EAAA,CAAG;AACnF,CAAC,GAKYC,KAA4D,OAAO,OAAO;AAAA,EACrF,QAAQ,OAAO,OAAO,EAAE,WAAW,IAAI,WAAW,KAAK,OAAO,IAAM,WAAW,KAAK,OAAO,GAAG;AAAA,EAC9F,SAAS,OAAO,OAAO,EAAE,WAAW,IAAI,WAAW,KAAK,OAAO,IAAM,WAAW,KAAK,OAAO,GAAG;AAAA,EAC/F,YAAY,OAAO,OAAO,EAAE,WAAW,IAAI,WAAW,GAAG,OAAO,IAAO,WAAW,GAAG,OAAO,GAAG;AAAA,EAC/F,SAAS,OAAO,OAAO,EAAE,WAAW,IAAI,WAAW,GAAG,OAAO,IAAM,WAAW,KAAK,OAAO,GAAG;AAC/F,CAAC,GAKYC,KAAuC,OAAO,OAAO;AAAA,EAChE,MAAM;AACR,CAAC,GAKYC,KAA8C,OAAO,OAAO;AAAA,EACvE,MAAM;AACR,CAAC,GAKYC,KAAwC,OAAO,OAAO;AAAA,EACjE,MAAM;AACR,CAAC,GAgBYC,KAAsC,OAAO,OAAO;AAAA,EAC/D,SAAS,OAAO,OAAO;AAAA,IACrB,QAAQ,OAAO,OAAO;AAAA,MACpB,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,IAAA,CACR;AAAA,IACD,QAAQ;AAAA,IACR,QAAQ,OAAO,OAAO,EAAE;AAAA,IACxB,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS,OAAO,OAAO;AAAA,MACrB,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,QAAQ;AAAA,IAAA,CACT;AAAA,EAAA,CACF;AAAA,EACD,OAAO,OAAO,OAAO;AAAA,IACnB,QAAQ;AAAA,EAAA,CACT;AAAA,EACD,SAAS,OAAO,OAAO;AAAA,IACrB,QAAQ;AAAA,EAAA,CACT;AACH,CAAC,GAKYC,KAA2C,OAAO,OAAO;AAAA,EACpE,MAAM;AAAA,EACN,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,iBAAiB;AAAA;AAEnB,CAAC,GAKYC,KAAwD,OAAO,OAAO;AAAA,EACjF,QAAQ,OAAO,OAAO,EAAE,UAAU,KAAK;AAAA,EACvC,QAAQ,OAAO,OAAO,EAAE,UAAU,MAAM;AAC1C,CAAC,GAKYC,KAA0C,OAAO,OAAO;AAAA,EACnE,MAAM;AAAA;AAAA,EACN,SAAS;AAAA;AAAA,EACT,SAAS;AAAA;AAAA,EACT,UAAU,OAAO,OAAO;AAAA,IACtB,KAAK;AAAA;AAAA,IACL,KAAK;AAAA,EAAA,CACN;AACH,CAAC,GAKYC,KAA8C,OAAO,OAAO;AAAA,EACvE,MAAM;AAAA,EACN,OAAO,OAAO,OAAO;AAAA,IACnB,KAAK;AAAA,IACL,KAAK;AAAA,EAAA,CACN;AACH,CAAC,GAKYC,KAAoC,OAAO,OAAO;AAAA,EAC7D,QAAQF;AAAA,EACR,UAAUC;AACZ,CAAC,GAMYE,IAAmC,OAAO,OAAO;AAAA;AAAA,EAE5D,QAAQ,OAAO,OAAO;AAAA,IACpB,MAAM;AAAA,IACN,aAAa,OAAO,OAAO;AAAA,MACzB,QAAQ;AAAA;AAAA,MACR,SAAS,CAAA;AAAA;AAAA,MACT,aAAa;AAAA,MACb,mBAAmB,CAAC,OAAO,QAAQ,OAAO,OAAO,QAAQ,KAAK;AAAA,MAC9D,cAAc;AAAA,IAAA,CACf;AAAA,IACD,QAAQ,OAAO,OAAO;AAAA,MACpB,SAAS,CAAA;AAAA;AAAA,MACT,cAAc;AAAA,MACd,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,kBAAkB;AAAA,MAClB,mBAAmB,CAAC,OAAO,QAAQ,OAAO,OAAO,QAAQ,KAAK;AAAA,MAC9D,cAAc;AAAA,IAAA,CACf;AAAA,EAAA,CACF;AAAA;AAAA,EAGD,OAAOD;AAAA;AAAA,EAGP,QAAQ,OAAO,OAAO;AAAA,IACpB,WAAW;AAAA,IACX,YAAY;AAAA;AAAA,IACZ,YAAYH;AAAA,IACZ,gBAAgB;AAAA;AAAA,IAChB,eAAe;AAAA;AAAA,IACf,SAAS,OAAO,OAAO;AAAA,MACrB,SAAS;AAAA;AAAA,MACT,QAAQ;AAAA;AAAA,IAAA,CACT;AAAA,IACD,cAAc;AAAA,IACd,cAAc;AAAA,EAAA,CACf;AAAA;AAAA,EAGD,WAAW,OAAO,OAAO;AAAA,IACvB,UAAU;AAAA;AAAA,IACV,QAAQ,OAAO,OAAO;AAAA,MACpB,SAAS;AAAA;AAAA,MACT,QAAQ;AAAA;AAAA,MACR,OAAO;AAAA;AAAA,IAAA,CACR;AAAA,IACD,OAAO,OAAO,OAAO;AAAA,MACnB,SAAS;AAAA;AAAA,MACT,UAAU;AAAA;AAAA,MACV,eAAe;AAAA;AAAA,IAAA,CAChB;AAAA,IACD,aAAa,OAAO,OAAO;AAAA,MACzB,QAAQ;AAAA;AAAA,MACR,cAAc;AAAA;AAAA,IAAA,CACf;AAAA,IACD,OAAO,OAAO,OAAO;AAAA,MACnB,OAAO,OAAO,OAAO;AAAA,QACnB,UAAU;AAAA;AAAA,QACV,QAAQ;AAAA;AAAA,QACR,UAAU,OAAO,OAAO;AAAA,UACtB,QAAQ;AAAA;AAAA,UACR,cAAc;AAAA,QAAA,CACf;AAAA,MAAA,CACF;AAAA,MACD,QAAQ,OAAO,OAAO;AAAA,QACpB,UAAU;AAAA;AAAA,QACV,SAAS;AAAA;AAAA,MAAA,CACV;AAAA,MACD,QAAQ;AAAA;AAAA,MACR,MAAML;AAAA,MACN,UAAUC;AAAA,MACV,OAAOC;AAAA,IAAA,CACR;AAAA,EAAA,CACF;AAAA;AAAA,EAGD,aAAa,OAAO,OAAO;AAAA,IACzB,OAAO,OAAO,OAAO;AAAA,MACnB,cAAc;AAAA;AAAA,MACd,QAAQ;AAAA,MACR,mBAAmB;AAAA;AAAA,IAAA,CACpB;AAAA,IACD,YAAY,OAAO,OAAO;AAAA,MACxB,UAAU;AAAA;AAAA,MACV,OAAO;AAAA;AAAA,MACP,YAAY;AAAA;AAAA,IAAA,CACb;AAAA,IACD,UAAU,OAAO,OAAO;AAAA,MACtB,aAAa;AAAA;AAAA,MACb,kBAAkB;AAAA;AAAA,IAAA,CACnB;AAAA,EAAA,CACF;AAAA;AAAA,EAGD,WAAW,OAAO,OAAO;AAAA,IACvB,YAAY,OAAO,OAAO;AAAA,MACxB,aAAa,OAAO,OAAO;AAAA,QACzB,QAAQ;AAAA,QACR,QAAQ;AAAA;AAAA,QACR,SAAS;AAAA;AAAA,MAAA,CACV;AAAA,MACD,iBAAiB,MACX,OAAO,SAAW,MAAoB,KACnC,OAAO,cAAc;AAAA,IAC9B,CACD;AAAA,IACD,IAAI,OAAO,OAAO;AAAA,MAChB,oBAAoB;AAAA,MACpB,kBAAkB;AAAA;AAAA,MAClB,gBAAgB;AAAA;AAAA,MAChB,OAAO;AAAA;AAAA,IAAA,CACR;AAAA,IACD,aAAa,OAAO,OAAO;AAAA,MACzB,UAAU;AAAA;AAAA,MACV,cAAc;AAAA;AAAA,MACd,cAAc;AAAA;AAAA,IAAA,CACf;AAAA,EAAA,CACF;AAAA;AAAA,EAGD,SAASC;AAAA;AAAA,EAGT,OAAO;AACT,CAAC;AAKD,SAASO,GACPC,GACAC,GACiB;AACjB,MAAI,CAACD,EAAM,QAAOC,KAA+B,CAAA;AACjD,MAAI,CAACA,EAAU,QAAO,EAAE,GAAGD,EAAA;AAE3B,QAAME,IAA0B,EAAE,GAAGF,EAAA;AAGrC,SAAIC,EAAS,WAAW,WACtBC,EAAO,SAAS,EAAE,GAAGF,EAAK,QAAQ,GAAGC,EAAS,OAAA,IAI5CA,EAAS,cAAc,WACzBC,EAAO,YAAY,EAAE,GAAGF,EAAK,WAAW,GAAGC,EAAS,UAAA,IAElDA,EAAS,gBAAgB,WAC3BC,EAAO,cAAc,EAAE,GAAGF,EAAK,aAAa,GAAGC,EAAS,YAAA,IAEtDA,EAAS,iBAAiB,WAC5BC,EAAO,eAAe,EAAE,GAAGF,EAAK,cAAc,GAAGC,EAAS,aAAA,IAExDA,EAAS,eAAe,WAC1BC,EAAO,aAAa,EAAE,GAAGF,EAAK,YAAY,GAAGC,EAAS,WAAA,IAIpDA,EAAS,WAAW,WACtBC,EAAO,SAAS,EAAE,GAAGF,EAAK,QAAQ,GAAGC,EAAS,OAAA,IAI5CA,EAAS,YAAY,WACvBC,EAAO,UAAU,EAAE,GAAGF,EAAK,SAAS,GAAGC,EAAS,QAAA,IAI9CA,EAAS,WAAW,WAAWC,EAAO,SAASD,EAAS,SACxDA,EAAS,YAAY,WAAWC,EAAO,UAAUD,EAAS,UAC1DA,EAAS,WAAW,WAAWC,EAAO,SAASD,EAAS,SACxDA,EAAS,cAAc,WAAWC,EAAO,YAAYD,EAAS,YAC9DA,EAAS,cAAc,WAAWC,EAAO,YAAYD,EAAS,YAC9DA,EAAS,gBAAgB,WAAWC,EAAO,cAAcD,EAAS,cAGlEA,EAAS,wBAAwB,WAAWC,EAAO,sBAAsBD,EAAS,sBAClFA,EAAS,yBAAyB,WAAWC,EAAO,uBAAuBD,EAAS,uBACpFA,EAAS,4BAA4B,WAAWC,EAAO,0BAA0BD,EAAS,0BAC1FA,EAAS,2BAA2B,WAAWC,EAAO,yBAAyBD,EAAS,yBAErFC;AACT;AAOA,SAASC,GACPC,GACAC,GACoB;AACpB,MAAI,CAACA,EAAa,QAAO,EAAE,GAAGD,EAAA;AAG9B,QAAME,IAAgBP,GAAoBK,EAAS,SAASC,EAAY,OAAO,GAGzEE,IAAcR;AAAA,IAClBA,GAAoBO,GAAeF,EAAS,KAAK;AAAA,IACjDC,EAAY;AAAA,EAAA,GAIRG,IAAgBT;AAAA,IACpBA,GAAoBO,GAAeF,EAAS,OAAO;AAAA,IACnDC,EAAY;AAAA,EAAA;AAGd,SAAO;AAAA,IACL,SAASC;AAAA,IACT,OAAOC;AAAA,IACP,SAASC;AAAA,EAAA;AAEb;AASA,SAASC,GACPL,GACAM,GACa;;AACb,MAAI,CAACA,EAAW,QAAO,EAAE,GAAGN,EAAA;AAE5B,QAAMF,IAAsB,EAAE,GAAGE,EAAA;AAGjC,MAAIM,EAAU,WAAW,WACvBR,EAAO,SAAS;AAAA,IACd,GAAGE,EAAS;AAAA,IACZ,GAAGM,EAAU;AAAA,EAAA,GAIXA,EAAU,OAAO,WAAU;AAC7B,UAAMC,IAAeD,EAAU,OAAO,UAChCE,IAAWD,EAAa,QAAQ,UAAaA,EAAa,OAAO,QAAQA,EAAa,OAAO,IAC/FA,EAAa,QACbE,KAAAC,IAAAV,EAAS,WAAT,gBAAAU,EAAiB,aAAjB,gBAAAD,EAA2B,QAAO,GAChCE,IAAWJ,EAAa,QAAQ,UAAaA,EAAa,OAAO,KAAKA,EAAa,OAAO,OAC5FA,EAAa,QACbK,KAAAC,IAAAb,EAAS,WAAT,gBAAAa,EAAiB,aAAjB,gBAAAD,EAA2B,QAAO;AACtC,IAAAd,EAAO,OAAQ,WAAW,EAAE,KAAKU,GAAU,KAAKG,EAAA;AAAA,EAClD;AAIF,MAAIL,EAAU,aAAa,WACzBR,EAAO,WAAW;AAAA,IAChB,GAAGE,EAAS;AAAA,IACZ,GAAGM,EAAU;AAAA,EAAA,GAIXA,EAAU,SAAS,QAAO;AAC5B,UAAMQ,IAAYR,EAAU,SAAS,OAC/BE,IAAWM,EAAU,QAAQ,UAAaA,EAAU,OAAO,QAAQA,EAAU,OAAO,IACtFA,EAAU,QACVC,KAAAC,IAAAhB,EAAS,aAAT,gBAAAgB,EAAmB,UAAnB,gBAAAD,EAA0B,QAAO,KAC/BJ,IAAWG,EAAU,QAAQ,UAAaA,EAAU,OAAO,KAAKA,EAAU,OAAO,MACnFA,EAAU,QACVG,KAAAC,IAAAlB,EAAS,aAAT,gBAAAkB,EAAmB,UAAnB,gBAAAD,EAA0B,QAAO;AACrC,IAAAnB,EAAO,SAAU,QAAQ,EAAE,KAAKU,GAAU,KAAKG,EAAA;AAAA,EACjD;AAGF,SAAOb;AACT;AAMA,SAASqB,GAA4BC,GAA6E;;AAChH,QAAMC,KAAkBX,IAAAU,EAAW,WAAX,gBAAAV,EAA2B;AACnD,MAAKW,KAGD,aAAaA;AACf,WAAO;AAAA,MACL,UAAU;AAAA,QACR,MAAMA,EAAe,UAAU,WAAW;AAAA,QAC1C,OAAOA,EAAe;AAAA,MAAA;AAAA,IACxB;AAKN;AAKA,SAASC,GAA4BF,GAA6E;;AAChH,QAAMG,KAAkBd,KAAAC,IAAAU,EAAW,WAAX,gBAAAV,EAA2B,WAA3B,gBAAAD,EAAmC;AAC3D,MAAKc;AAEL,WAAO;AAAA,MACL,QAAQ;AAAA,QACN,MAAM;AAAA;AAAA,QACN,UAAUA;AAAA,MAAA;AAAA,IACZ;AAEJ;AAEO,SAASC,GACdJ,IAA4C,IAC1B;;AAElB,QAAMC,IAAiBF,GAA4BC,CAAU,GACvDG,IAAiBD,GAA4BF,CAAU;AAI7D,MAAIK,IAAwDL,EAAW;AACvE,GAAIC,KAAkBE,OACpBE,IAAsB;AAAA,IACpB,GAAIF,KAAkB,CAAA;AAAA,IACtB,GAAIF,KAAkB,CAAA;AAAA,IACtB,GAAGI;AAAA,EAAA,GAGDA,EAAoB,aAAYJ,KAAA,QAAAA,EAAgB,eAAYX,IAAAU,EAAW,UAAX,QAAAV,EAAkB,cAChFe,EAAoB,WAAW;AAAA,IAC7B,GAAGJ,EAAe;AAAA,IAClB,GAAID,EAAW,MAAc;AAAA,EAAA;AAKnC,QAAMtB,IAA2B;AAAA,IAC/B,QAAQ,EAAE,GAAGJ,EAAe,OAAA;AAAA,IAC5B,OAAOW,GAAqBZ,IAAsBgC,CAAmB;AAAA,IACrE,QAAQ,EAAE,GAAG/B,EAAe,OAAA;AAAA,IAC5B,WAAW,EAAE,GAAGA,EAAe,UAAA;AAAA,IAC/B,aAAa,EAAE,GAAGA,EAAe,YAAA;AAAA,IACjC,WAAW,EAAE,GAAGA,EAAe,UAAA;AAAA,IAC/B,SAASK,GAAiBX,IAAiBgC,EAAW,OAAkD;AAAA,IACxG,OAAO1B,EAAe;AAAA,EAAA;AAIxB,SAAI0B,EAAW,WACbtB,EAAO,SAAS;AAAA,IACd,GAAGJ,EAAe;AAAA,IAClB,GAAG0B,EAAW;AAAA,EAAA,GAIZA,EAAW,OAAO,gBACpBtB,EAAO,OAAO,cAAc;AAAA,IAC1B,GAAGJ,EAAe,OAAO;AAAA,IACzB,GAAG0B,EAAW,OAAO;AAAA,IACrB,SAASA,EAAW,OAAO,YAAY,WAAW1B,EAAe,OAAO,YAAa;AAAA,IACrF,mBAAmB0B,EAAW,OAAO,YAAY,qBAC/C1B,EAAe,OAAO,YAAa;AAAA,EAAA,IAKrC0B,EAAW,OAAO,WACpBtB,EAAO,OAAO,SAAS;AAAA,IACrB,GAAGJ,EAAe,OAAO;AAAA,IACzB,GAAG0B,EAAW,OAAO;AAAA,IACrB,SAASA,EAAW,OAAO,OAAO,WAAW1B,EAAe,OAAO,OAAQ;AAAA,IAC3E,mBAAmB0B,EAAW,OAAO,OAAO,qBAC1C1B,EAAe,OAAO,OAAQ;AAAA,EAAA,KAMlC0B,EAAW,WACbtB,EAAO,SAAS;AAAA,IACd,GAAGJ,EAAe;AAAA,IAClB,GAAG0B,EAAW;AAAA,EAAA,GAIZA,EAAW,OAAO,eACpBtB,EAAO,OAAO,aAAa;AAAA,IACzB,GAAGJ,EAAe,OAAO;AAAA,IACzB,QAAQ0B,EAAW,OAAO,WAAW,SACjC,EAAE,GAAG1B,EAAe,OAAO,WAAY,QAAQ,GAAG0B,EAAW,OAAO,WAAW,WAC/E1B,EAAe,OAAO,WAAY;AAAA,IACtC,QAAQ0B,EAAW,OAAO,WAAW,SACjC,EAAE,GAAG1B,EAAe,OAAO,WAAY,QAAQ,GAAG0B,EAAW,OAAO,WAAW,WAC/E1B,EAAe,OAAO,WAAY;AAAA,EAAA,IAKtC0B,EAAW,OAAO,YACpBtB,EAAO,OAAO,UAAU;AAAA,IACtB,GAAGJ,EAAe,OAAO;AAAA,IACzB,GAAG0B,EAAW,OAAO;AAAA,EAAA,KAMvBA,EAAW,cACbtB,EAAO,YAAY;AAAA,IACjB,GAAGJ,EAAe;AAAA,IAClB,GAAG0B,EAAW;AAAA,EAAA,GAIZA,EAAW,UAAU,WACvBtB,EAAO,UAAU,SAAS;AAAA,IACxB,GAAGJ,EAAe,UAAU;AAAA,IAC5B,GAAG0B,EAAW,UAAU;AAAA,EAAA,IAKxBA,EAAW,UAAU,UACvBtB,EAAO,UAAU,QAAQ;AAAA,IACvB,GAAGJ,EAAe,UAAU;AAAA,IAC5B,GAAG0B,EAAW,UAAU;AAAA,EAAA,IAKxBA,EAAW,UAAU,gBACvBtB,EAAO,UAAU,cAAc;AAAA,IAC7B,GAAGJ,EAAe,UAAU;AAAA,IAC5B,GAAG0B,EAAW,UAAU;AAAA,EAAA,IAKxBA,EAAW,UAAU,UACvBtB,EAAO,UAAU,QAAQ;AAAA,IACvB,GAAGJ,EAAe,UAAU;AAAA,IAC5B,GAAG0B,EAAW,UAAU;AAAA,IACxB,OAAOA,EAAW,UAAU,MAAM,QAC9B;AAAA,MACE,GAAG1B,EAAe,UAAU,MAAO;AAAA,MACnC,GAAG0B,EAAW,UAAU,MAAM;AAAA,MAC9B,UAAUA,EAAW,UAAU,MAAM,MAAM,WACvC,EAAE,GAAG1B,EAAe,UAAU,MAAO,MAAM,UAAU,GAAG0B,EAAW,UAAU,MAAM,MAAM,aACzF1B,EAAe,UAAU,MAAO,MAAM;AAAA,IAAA,IAE5CA,EAAe,UAAU,MAAO;AAAA,IACpC,QAAQ0B,EAAW,UAAU,MAAM,SAC/B,EAAE,GAAG1B,EAAe,UAAU,MAAO,QAAQ,GAAG0B,EAAW,UAAU,MAAM,WAC3E1B,EAAe,UAAU,MAAO;AAAA,IACpC,MAAM0B,EAAW,UAAU,MAAM,OAC7B,EAAE,GAAGnC,IAAqB,GAAGmC,EAAW,UAAU,MAAM,KAAA,IACxD1B,EAAe,UAAU,MAAO;AAAA,IACpC,UAAU0B,EAAW,UAAU,MAAM,WACjC,EAAE,GAAGlC,IAAwB,GAAGkC,EAAW,UAAU,MAAM,SAAA,IAC3D1B,EAAe,UAAU,MAAO;AAAA,IACpC,OAAO0B,EAAW,UAAU,MAAM,QAC9B,EAAE,GAAGjC,IAAqB,GAAGiC,EAAW,UAAU,MAAM,MAAA,IACxD1B,EAAe,UAAU,MAAO;AAAA,EAAA,KAMtC0B,EAAW,gBACbtB,EAAO,cAAc;AAAA,IACnB,GAAGJ,EAAe;AAAA,IAClB,GAAG0B,EAAW;AAAA,EAAA,GAIZA,EAAW,YAAY,UACzBtB,EAAO,YAAY,QAAQ;AAAA,IACzB,GAAGJ,EAAe,YAAY;AAAA,IAC9B,GAAG0B,EAAW,YAAY;AAAA,EAAA,IAK1BA,EAAW,YAAY,eACzBtB,EAAO,YAAY,aAAa;AAAA,IAC9B,GAAGJ,EAAe,YAAY;AAAA,IAC9B,GAAG0B,EAAW,YAAY;AAAA,EAAA,IAK1BA,EAAW,YAAY,aACzBtB,EAAO,YAAY,WAAW;AAAA,IAC5B,GAAGJ,EAAe,YAAY;AAAA,IAC9B,GAAG0B,EAAW,YAAY;AAAA,EAAA,KAM5BA,EAAW,cACbtB,EAAO,YAAY;AAAA,IACjB,GAAGJ,EAAe;AAAA,IAClB,GAAG0B,EAAW;AAAA,EAAA,GAIZA,EAAW,UAAU,eACvBtB,EAAO,UAAU,aAAa;AAAA,IAC5B,GAAGJ,EAAe,UAAU;AAAA,IAC5B,GAAG0B,EAAW,UAAU;AAAA,IACxB,aAAaA,EAAW,UAAU,WAAW,cACzC,EAAE,GAAG1B,EAAe,UAAU,WAAW,aAAa,GAAG0B,EAAW,UAAU,WAAW,gBACzF1B,EAAe,UAAU,WAAW;AAAA,IACxC,iBAAiB0B,EAAW,UAAU,WAAW,kBAC5CA,EAAW,UAAU,WAAW,kBACjC1B,EAAe,UAAU,WAAW;AAAA,EAAA,IAKxC0B,EAAW,UAAU,OACvBtB,EAAO,UAAU,KAAK;AAAA,IACpB,GAAGJ,EAAe,UAAU;AAAA,IAC5B,GAAG0B,EAAW,UAAU;AAAA,EAAA,IAKxBA,EAAW,UAAU,gBACvBtB,EAAO,UAAU,cAAc;AAAA,IAC7B,GAAGJ,EAAe,UAAU;AAAA,IAC5B,GAAG0B,EAAW,UAAU;AAAA,EAAA,KAM1BA,EAAW,UAAU,WACvBtB,EAAO,QAAQsB,EAAW,QAGrBtB;AACT;AAKO,SAAS4B,GACdC,GACAC,GACkB;AAElB,SAAO,EAAE,GADID,IAAS7C,GAAe6C,CAAM,IAAI7C,GAAe,SAC5C,GAAG8C,EAAA;AACvB;AAKO,SAASC,GACdF,GACAC,GACmB;AAEnB,SAAO,EAAE,GADID,IAAS5C,GAAgB4C,CAAM,IAAI5C,GAAgB,QAC9C,GAAG6C,EAAA;AACvB;AAKO,SAASE,GACdH,GACAC,GACgB;AAEhB,SAAO,EAAE,GADID,IAAS3C,GAAkB2C,CAAM,IAAI3C,GAAkB,QAClD,GAAG4C,EAAA;AACvB;ACttBO,MAAMG,GAAgB;AAAA,EAK3B,YAAYC,GAAyB;AAHrC,SAAQ,uCAA0D,IAAA,GAClE,KAAQ,qBAAqB,GAG3B,KAAK,SAASA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBAAqBC,GAAiC;AAC5D,UAAMC,IAAuB,CAAC,uBAAuB;AAErD,QAAID,EAAO,MAAM,UAAaA,EAAO,MAAM,QAAW;AACpD,YAAME,IAAIF,EAAO,KAAK,GAChBG,IAAIH,EAAO,KAAK;AACtB,MAAAC,EAAW,KAAK,aAAaC,CAAC,OAAOC,CAAC,KAAK;AAAA,IAC7C;AAEA,WAAIH,EAAO,aAAa,UACtBC,EAAW,KAAK,UAAUD,EAAO,QAAQ,MAAM,GAG7CA,EAAO,UAAU,UACnBC,EAAW,KAAK,SAASD,EAAO,KAAK,GAAG,GAGnCC,EAAW,KAAK,GAAG;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,4BACEG,GACAC,GACAC,GACAC,IAA0B,MAC1BC,IAAwB,MACP;AAEjB,SAAK,oBAAoBJ,CAAO;AAEhC,UAAMK,IAAeF,KAAY,KAAK,OAAO,UACvCG,IAAaF,KAAU,KAAK,OAAO,OAAO,SAE1CG,IAAgB,KAAK,qBAAqBN,CAAI,GAC9CO,IAAc,KAAK,qBAAqBN,CAAE;AAGhD,IAAAF,EAAQ,MAAM,aAAa;AAG3B,UAAMS,IAAYT,EAAQ;AAAA,MACxB;AAAA,QACE,EAAE,WAAWO,EAAA;AAAA,QACb,EAAE,WAAWC,EAAA;AAAA,MAAY;AAAA,MAE3B;AAAA,QACE,UAAUH;AAAA,QACV,QAAQC;AAAA,QACR,MAAM;AAAA;AAAA,MAAA;AAAA,IACR,GAGII,IAA0B;AAAA,MAC9B,IAAI,QAAQ,EAAE,KAAK,kBAAkB;AAAA,MACrC,SAAAV;AAAA,MACA,WAAAS;AAAA,MACA,WAAWR;AAAA,MACX,SAASC;AAAA,MACT,WAAW,YAAY,IAAA;AAAA,MACvB,UAAUG;AAAA,IAAA;AAGZ,gBAAK,iBAAiB,IAAIL,GAASU,CAAM,GAGzCD,EAAU,SACP,KAAK,MAAM;AAEV,MAAAT,EAAQ,MAAM,YAAYQ,GAC1B,KAAK,iBAAiB,OAAOR,CAAO;AAAA,IACtC,CAAC,EACA,MAAM,MAAM;AAEX,WAAK,iBAAiB,OAAOA,CAAO;AAAA,IACtC,CAAC,GAEIU;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgBA,GAAyBC,IAAuB,IAAyB;AACvF,UAAMC,IAAW,KAAK,oBAAoBF,EAAO,OAAO;AAKxD,QAFAA,EAAO,UAAU,OAAA,GAEbC,GAAa;AAEf,YAAME,IAAmB,KAAK,qBAAqB;AAAA,QACjD,GAAGD,EAAS;AAAA,QACZ,GAAGA,EAAS;AAAA,QACZ,UAAUA,EAAS;AAAA,QACnB,OAAOA,EAAS;AAAA,MAAA,CACjB;AACD,MAAAF,EAAO,QAAQ,MAAM,YAAYG;AAAA,IACnC;AAEA,gBAAK,iBAAiB,OAAOH,EAAO,OAAO,GAEpCE;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoBZ,GAA4B;AAE9C,UAAMU,IAAS,KAAK,iBAAiB,IAAIV,CAAO;AAChD,IAAIU,KACF,KAAK,gBAAgBA,GAAQ,EAAK;AAIpC,UAAMI,IAAgBd,EAAQ,cAAA;AAC9B,eAAWe,KAAQD;AACjB,MAAAC,EAAK,OAAA;AAAA,EAET;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,oBAAoBf,GAAyC;AAE3D,UAAMgB,IADW,iBAAiBhB,CAAO,EACX;AAE9B,QAAIgB,MAAiB,UAAU,CAACA;AAC9B,aAAO,EAAE,GAAG,GAAG,GAAG,GAAG,UAAU,GAAG,OAAO,EAAA;AAG3C,UAAMC,IAAS,IAAI,UAAUD,CAAY,GAGnCE,IAAQ,KAAK,KAAKD,EAAO,IAAIA,EAAO,IAAIA,EAAO,IAAIA,EAAO,CAAC,GAG3DE,IAAW,KAAK,MAAMF,EAAO,GAAGA,EAAO,CAAC,KAAK,MAAM,KAAK,KASxDnB,IAAImB,EAAO,GACXlB,IAAIkB,EAAO;AAEjB,WAAO,EAAE,GAAAnB,GAAG,GAAAC,GAAG,UAAAoB,GAAU,OAAAD,EAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAmBlB,GAA+B;AAChD,WAAO,KAAK,iBAAiB,IAAIA,CAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAmBA,GAAmD;AACpE,WAAO,KAAK,iBAAiB,IAAIA,CAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,iBACEA,GACAoB,GACAjB,IAA0B,MAC1BC,IAAwB,MACT;AACf,WAAO,IAAI,QAAQ,CAACiB,MAAY;AAC9B,YAAMhB,IAAeF,KAAY,KAAK,OAAO,UACvCG,IAAaF,KAAU,KAAK,OAAO,OAAO;AAGhD,MAAAJ,EAAQ,MAAM,aAAa,aAAaK,CAAY,MAAMC,CAAU,gBAAgBD,CAAY,MAAMC,CAAU,IAGhHN,EAAQ,MAAM,YAAY,KAAK,qBAAqBoB,CAAU,GAG9D,WAAW,MAAM;AACf,QAAAC,EAAA;AAAA,MACF,GAAGhB,CAAY;AAAA,IACjB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,eAAeL,GAAsBsB,GAA6D;AAChG,WAAO,KAAK,iBAAiBtB,GAASsB,CAAa;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgBtB,GAA4B;AAC1C,IAAAA,EAAQ,MAAM,aAAa;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAKuB,GAA2B;AAC9B,WAAO,IAAI,QAAQ,CAAAF,MAAW,WAAWA,GAASE,CAAE,CAAC;AAAA,EACvD;AACF;AC3NA,SAASC,GAAKC,GAAeC,GAAaC,GAAmB;AAC3D,SAAOF,KAASC,IAAMD,KAASE;AACjC;AAMO,SAASC,GACdD,GACAF,GACAC,GACA/B,GACO;AACP,QAAM,EAAE,WAAAkC,GAAW,SAAAC,GAAS,YAAAC,EAAA,IAAepC,GAGrCqC,IAAKN,EAAI,IAAID,EAAM,GACnBQ,IAAKP,EAAI,IAAID,EAAM,GAGnBS,IAAYC,GAAwBL,GAASC,CAAU;AAG7D,MAAIK,IAAW,GACXC,IAAe,GACfC,IAAa,GACbC,IAAmBV,GACnBW,IAAmB;AAEvB,WAASC,IAAI,GAAGA,IAAIP,EAAU,QAAQO;AACpC,QAAId,KAAKO,EAAUO,CAAC,EAAE,MAAM;AAC1B,MAAAJ,IAAeI,MAAM,IAAI,IAAIP,EAAUO,IAAI,CAAC,EAAE,MAC9CH,IAAaJ,EAAUO,CAAC,EAAE,MAC1BF,IAAmBL,EAAUO,CAAC,EAAE,WAChCD,IAAmBN,EAAUO,CAAC,EAAE;AAChC;AAAA,IACF;AAIF,QAAMC,KAAYf,IAAIU,MAAiBC,IAAaD;AAEpD,MAAIG;AAEF,IAAAJ,IAAW,IAAIG,IAAmBI,GAAYD,CAAQ;AAAA,WAC7CL,MAAiB;AAE1B,IAAAD,IAAWO,GAAYD,CAAQ;AAAA,OAC1B;AAEL,UAAME,IAAgBV,EAAU;AAAA,MAAK,CAACW,GAAGJ,MACvCI,EAAE,OAAOR,KAAgBI,IAAI,KAAKP,EAAUO,IAAI,CAAC,EAAE;AAAA,IAAA,GAE/CK,IAAe,MAAKF,KAAA,gBAAAA,EAAe,cAAaL;AACtD,IAAAH,IAAWZ,GAAKsB,GAAc,GAAGH,GAAYD,CAAQ,CAAC;AAAA,EACxD;AAEA,SAAO;AAAA,IACL,GAAGjB,EAAM,IAAIO,IAAKI;AAAA,IAClB,GAAGX,EAAM,IAAIQ,IAAKG;AAAA,EAAA;AAEtB;AAKA,SAASD,GACPL,GACAC,GACkE;AAClE,QAAMG,IAA8E,CAAA;AAGpF,MAAIa,IAAc;AAClB,EAAAb,EAAU,KAAK,EAAE,MAAMa,GAAa,WAAW,GAAG,aAAa,IAAO;AAEtE,MAAIC,IAAmB;AAEvB,QAAMC,IADgB,OACcnB,IAAU;AAE9C,WAASW,IAAI,GAAGA,IAAIX,GAASW;AAE3B,IAAAM,KAAeE,GACff,EAAU,KAAK,EAAE,MAAMa,GAAa,WAAWC,GAAkB,aAAa,IAAM,GAGpFD,KAAeE,GACff,EAAU,KAAK,EAAE,MAAMa,GAAa,WAAWC,IAAmBjB,GAAY,aAAa,IAAO,GAElGiB,KAAoBjB;AAItB,SAAAG,EAAU,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,IAAO,GAErDA;AACT;AAMO,SAASgB,GACdvB,GACAF,GACAC,GACA/B,GACO;AACP,QAAM,EAAE,WAAAwD,GAAW,SAAAC,GAAS,MAAAC,GAAM,cAAAC,MAAiB3D,GAG7CqC,IAAKN,EAAI,IAAID,EAAM,GACnBQ,IAAKP,EAAI,IAAID,EAAM,GAInB8B,IAAQ,KAAK,KAAKJ,IAAYE,CAAI,GAGlCG,IAAOJ,KAAW,IAAI,KAAK,KAAKD,IAAYE,CAAI;AAGtD,MAAIjB;AAEJ,MAAIoB,IAAO,GAAG;AAEZ,UAAMC,IAAaF,IAAQ,KAAK,KAAK,IAAIC,IAAOA,CAAI,GAC9CE,IAAW,KAAK,IAAI,CAACF,IAAOD,IAAQ5B,IAAI,CAAC,GACzCgC,IAAc,KAAK,IAAIF,IAAa9B,IAAI2B,IAAe,KAAK,EAAE;AACpE,IAAAlB,IAAW,IAAIsB,IAAWC;AAAA,EAC5B;AAEE,IAAAvB,IAAW,IAAI,KAAK,IAAI,CAACmB,IAAQ5B,IAAI,CAAC;AAIxC,SAAAS,IAAW,KAAK,IAAI,GAAG,KAAK,IAAIA,GAAU,GAAG,CAAC,GAEvC;AAAA,IACL,GAAGX,EAAM,IAAIO,IAAKI;AAAA,IAClB,GAAGX,EAAM,IAAIQ,IAAKG;AAAA,EAAA;AAEtB;AAMO,SAASwB,GACdjC,GACAF,GACAC,GACA/B,GACO;AACP,QAAM,EAAE,WAAAkE,GAAW,WAAAC,GAAW,OAAAC,GAAO,WAAAC,GAAW,OAAAC,MAAUtE,GAGpDqC,IAAKN,EAAI,IAAID,EAAM,GACnBQ,IAAKP,EAAI,IAAID,EAAM,GACnByC,IAAS,KAAK,KAAKlC,IAAKA,IAAKC,IAAKA,CAAE,GAGpCkC,IAAQD,IAAS,IAAI,CAACjC,IAAKiC,IAAS,GACpCE,IAAQF,IAAS,IAAIlC,IAAKkC,IAAS,GAGnCG,IAAYP,IAAY,KAAK,KAAK,IAAInC,IAAIsC,GAC1CK,IAAcP,IAAQ,KAAK,IAAI,IAAIpC,GAAGqC,CAAS,IAAI,GACnDO,IAAaV,IAAY,KAAK,IAAIQ,CAAS,IAAIC,GAG/CE,IAAYC,GAAa9C,CAAC;AAGhC,SAAO;AAAA,IACL,GAAGH,GAAKC,EAAM,GAAGC,EAAI,GAAG8C,CAAS,IAAID,IAAaJ;AAAA,IAClD,GAAG3C,GAAKC,EAAM,GAAGC,EAAI,GAAG8C,CAAS,IAAID,IAAaH;AAAA,EAAA;AAEtD;AAKA,SAASzB,GAAYhB,GAAmB;AACtC,SAAO,KAAK,IAAIA,MAAM,IAAIA;AAC5B;AAEA,SAAS8C,GAAa9C,GAAmB;AACvC,SAAO,IAAI,KAAK,IAAI,IAAIA,GAAG,CAAC;AAC9B;AAKA,SAAS+C,GACPtC,GACAuC,GACAC,GACQ;AACR,QAAM,EAAE,WAAAf,GAAW,WAAAC,GAAW,OAAAC,EAAA,IAAUa,GAGlCjB,IAAc,KAAK,IAAIvB,IAAW0B,IAAY,KAAK,KAAK,CAAC,GAGzDQ,IAAcP,IAAQ,KAAK,IAAI,IAAI3B,GAAU,CAAC,IAAI,GAGlDyC,IAAehB,IAAYF,IAAcW;AAE/C,SAAOK,IAAgBE;AACzB;AAKA,SAASC,GACP1C,GACA2C,GACAC,GACQ;AACR,QAAM,EAAE,WAAAnD,GAAW,SAAAC,EAAA,IAAYkD,GAGzB9C,IAAoD,CAAA;AAG1D,EAAAA,EAAU,KAAK,EAAE,MAAM,KAAK,OAAOL,GAAW;AAG9C,MAAImB,IAAmBnB;AACvB,QAAMoD,IAAc,KAEdhC,IADgB,OACcnB,IAAU;AAE9C,MAAIiB,IAAc;AAClB,WAASN,IAAI,GAAGA,IAAIX,GAASW,KAAK;AAChC,UAAMyC,IAAa,KAAKlC,IAAmB,KAAKiC;AAChD,IAAAlC,KAAeE,GACff,EAAU,KAAK,EAAE,MAAMa,GAAa,OAAOmC,GAAY,GAEvDlC,IAAmB,KAAKA,IAAmB,KAAKiC,IAAcA,GAC9DlC,KAAeE,GACXR,IAAIX,IAAU,KAChBI,EAAU,KAAK,EAAE,MAAMa,GAAa,OAAOC,GAAkB;AAAA,EAEjE;AAEA,EAAAd,EAAU,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG;AAGpC,MAAIiD,IAAe;AACnB,WAAS1C,IAAI,GAAGA,IAAIP,EAAU,QAAQO;AACpC,QAAIL,KAAYF,EAAUO,CAAC,EAAE,MAAM;AACjC,YAAM2C,IAAW3C,MAAM,IAAI,IAAIP,EAAUO,IAAI,CAAC,EAAE,MAC1C4C,IAAY5C,MAAM,IAAI,IAAIP,EAAUO,IAAI,CAAC,EAAE,OAC3C6C,KAAmBlD,IAAWgD,MAAalD,EAAUO,CAAC,EAAE,OAAO2C,IAC/DG,IAAgB5C,GAAY2C,CAAe;AACjD,MAAAH,IAAeE,KAAanD,EAAUO,CAAC,EAAE,QAAQ4C,KAAaE;AAC9D;AAAA,IACF;AAGF,SAAOJ,IAAeJ;AACxB;AAKO,SAASS,GAAYC,GAAqC;AAC/D,QAAM;AAAA,IACJ,SAAAzF;AAAA,IACA,eAAA0F;AAAA,IACA,aAAAC;AAAA,IACA,YAAAC;AAAA,IACA,UAAAzF;AAAA,IACA,YAAA0F;AAAA,IACA,aAAAC;AAAA,IACA,UAAUnB;AAAA,IACV,OAAOI;AAAA,IACP,YAAAgB;AAAA,IACA,gBAAAC;AAAA,IACA,eAAAC;AAAA,IACA,aAAAC;AAAA,IACA,YAAAC;AAAA,EAAA,IACEV,GAEEW,IAAWR,EAAW,MAGtBS,IAAkBJ,MAAkB,UAAaA,MAAkBtB,GACnE2B,KAAeN,KAAA,gBAAAA,EAAgB,UAAS,UACxCpB,KAAeoB,KAAA,gBAAAA,EAAgB,WAAU,EAAE,WAAW,IAAI,WAAW,GAAG,OAAO,GAAA,GAC/EO,IAAyBF,KAAmBC,GAG5CE,IAAeL,MAAe,UAAaA,MAAepB,GAC1D0B,KAAYP,KAAA,gBAAAA,EAAa,UAAS,OAClClB,KAAYkB,KAAA,gBAAAA,EAAa,QAAO,EAAE,WAAW,KAAK,SAAS,EAAA;AAIjE,OAAKE,MAAa,YAAYA,MAAa,UAAU,CAACG,KAA0B,EAHpDC,KAAgBC,IAG0D;AACpG,IAAIV,KAAYA,EAAA;AAChB;AAAA,EACF;AAEA,QAAMW,IAAY,YAAY,IAAA,GAGxBC,IAAgB,CAACd,IAAa,GAC9Be,IAAgB,CAACd,IAAc;AAErC,WAASe,EAAK9D,GAA2B;AACvC,UAAM+D,IAAU/D,IAAc2D,GACxB/E,IAAI,KAAK,IAAImF,IAAU3G,GAAU,CAAC;AAGxC,QAAI4G;AAEJ,YAAQX,GAAA;AAAA,MACN,KAAK,UAAU;AACb,cAAMzG,IAASN;AAAA,UACbuG,EAAW;AAAA,UACXA,EAAW;AAAA,QAAA;AAEb,QAAAmB,IAAWnF,GAAwBD,GAAG+D,GAAeC,GAAahG,CAAM;AACxE;AAAA,MACF;AAAA,MACA,KAAK,WAAW;AACd,cAAMA,IAASH;AAAA,UACboG,EAAW;AAAA,UACXA,EAAW;AAAA,QAAA;AAEb,QAAAmB,IAAW7D,GAAyBvB,GAAG+D,GAAeC,GAAahG,CAAM;AACzE;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AACX,cAAMA,IAASF;AAAA,UACbmG,EAAW;AAAA,UACXA,EAAW;AAAA,QAAA;AAEb,QAAAmB,IAAWnD,GAAsBjC,GAAG+D,GAAeC,GAAahG,CAAM;AACtE;AAAA,MACF;AAAA,MACA;AACE,QAAAoH,IAAW;AAAA,UACT,GAAGvF,GAAKkE,EAAc,GAAGC,EAAY,GAAGhE,CAAC;AAAA,UACzC,GAAGH,GAAKkE,EAAc,GAAGC,EAAY,GAAGhE,CAAC;AAAA,QAAA;AAAA,IAC3C;AAIJ,UAAMqF,IAAaD,EAAS,IAAIpB,EAAY,GACtCsB,IAAaF,EAAS,IAAIpB,EAAY;AAG5C,QAAIuB;AACJ,IAAIZ,IACFY,IAAkBxC,GAAwB/C,GAAGgD,GAAeC,CAAY,IAC/DyB,IACTa,IAAkB1F,GAAKyE,GAAgBtB,GAAehD,CAAC,IAEvDuF,IAAkBvC;AAIpB,QAAIQ;AACJ,IAAIsB,IACFtB,IAAeL,GAAkBnD,GAAGoD,GAAYC,CAAS,IAChDwB,IACTrB,IAAe3D,GAAK2E,GAAapB,GAAYpD,CAAC,IAE9CwD,IAAeJ,GAIjB/E,EAAQ,MAAM,YACZ,aAAa2G,CAAa,OAAOC,CAAa,iBACjCI,CAAU,OAAOC,CAAU,cAC9BC,CAAe,cAAc/B,CAAY,KAEjDxD,IAAI,IACN,sBAAsBkF,CAAI,KAG1B7G,EAAQ,MAAM,YACZ,aAAa2G,CAAa,OAAOC,CAAa,cACpCjC,CAAa,cAAcI,CAAU,KAC7CgB,KAAYA,EAAA;AAAA,EAEpB;AAEA,wBAAsBc,CAAI;AAC5B;AAKO,SAASM,GAAoBf,GAAkC;AACpE,SAAOA,MAAa,YAAYA,MAAa,aAAaA,MAAa;AACzE;ACjbA,MAAMgB,KAAqE;AAAA,EACzE,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AACR;AAeO,MAAMC,GAAqB;AAAA,EAQhC,YAAY1H,GAA8B2H,GAAkC;AAC1E,SAAK,SAAS3H,GACd,KAAK,kBAAkB2H,GAGvB,KAAK,wBAAwB,KAAK,qBAAA,GAGlC,KAAK,aAAa3H,EAAO,QAAQ/C,IAGjC,KAAK,iBAAiB+C,EAAO,YAAY9C,IAGzC,KAAK,cAAc8C,EAAO,SAAS7C;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAA2C;AAEjD,WAAI,KAAK,OAAO,MAAM,WACb,KAAK,OAAO,MAAM,WAGpBsK,GAAsB,KAAK,eAAe,KAAK;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,uBACEG,GACAC,GACAC,GACAC,GACAC,GACe;AACf,UAAMZ,IAAW,KAAK,uBAChBa,IAAS,KAAK,OAAO,MAAM,UAAU;AAE3C,YAAQb,GAAA;AAAA,MACN,KAAK;AACH,eAAO,KAAK,qBAAqBQ,GAAeC,GAAWC,GAAiBG,CAAM;AAAA,MAEpF,KAAK;AACH,eAAO,KAAK,sBAAsB,OAAOL,GAAeC,GAAWC,GAAiBG,CAAM;AAAA,MAE5F,KAAK;AACH,eAAO,KAAK,sBAAsB,UAAUL,GAAeC,GAAWC,GAAiBG,CAAM;AAAA,MAE/F,KAAK;AACH,eAAO,KAAK,sBAAsB,QAAQL,GAAeC,GAAWC,GAAiBG,CAAM;AAAA,MAE7F,KAAK;AACH,eAAO,KAAK,sBAAsB,SAASL,GAAeC,GAAWC,GAAiBG,CAAM;AAAA,MAE9F,KAAK;AACH,eAAO,KAAK,wBAAwBH,GAAiBF,GAAeC,CAAS;AAAA,MAE/E,KAAK;AACH,eAAO,KAAK,oBAAoBD,GAAeC,GAAWC,GAAiBG,CAAM;AAAA,MAEnF,KAAK;AACH,eAAO,KAAK;AAAA,UACVL;AAAA,UACAC;AAAA,UACAC;AAAA,UACAC;AAAA,UACAC;AAAA,QAAA;AAAA,MAGJ;AACE,eAAO,KAAK,qBAAqBJ,GAAeC,GAAWC,GAAiBG,CAAM;AAAA,IAAA;AAAA,EAExF;AAAA;AAAA;AAAA;AAAA,EAKQ,qBACNL,GACAC,GACAC,GACAG,GACe;AAEf,UAAMC,IAAUN,EAAc,GACxBO,IAAUP,EAAc,GAExBQ,IAAWF,GACXG,IAAYP,EAAgB,QAAQI,GACpCI,IAAUH,GACVI,IAAaT,EAAgB,SAASK,GAEtCK,IAAU,KAAK,IAAIJ,GAAUC,GAAWC,GAASC,CAAU;AAEjE,QAAIE,IAASb,EAAc,GACvBc,IAASd,EAAc;AAE3B,WAAIY,MAAYJ,IAEdK,IAAS,EAAEZ,EAAU,QAAQI,KACpBO,MAAYH,IAErBI,IAASX,EAAgB,QAAQG,IACxBO,MAAYF,IAErBI,IAAS,EAAEb,EAAU,SAASI,KAG9BS,IAASZ,EAAgB,SAASG,GAG7B,EAAE,GAAGQ,GAAQ,GAAGC,EAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,sBACNC,GACAf,GACAC,GACAC,GACAG,GACe;AACf,QAAIQ,IAASb,EAAc,GACvBc,IAASd,EAAc;AAE3B,YAAQe,GAAA;AAAA,MACN,KAAK;AACH,QAAAD,IAAS,EAAEb,EAAU,SAASI;AAC9B;AAAA,MACF,KAAK;AACH,QAAAS,IAASZ,EAAgB,SAASG;AAClC;AAAA,MACF,KAAK;AACH,QAAAQ,IAAS,EAAEZ,EAAU,QAAQI;AAC7B;AAAA,MACF,KAAK;AACH,QAAAQ,IAASX,EAAgB,QAAQG;AACjC;AAAA,IAAA;AAGJ,WAAO,EAAE,GAAGQ,GAAQ,GAAGC,EAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,wBACNZ,GACAc,GACAC,GACe;AAEf,UAAMX,IAAUJ,EAAgB,QAAQ,GAClCK,IAAUL,EAAgB,SAAS;AAEzC,WAAO;AAAA,MACL,GAAGI;AAAA,MACH,GAAGC;AAAA,MACH,UAAU;AAAA;AAAA,IAAA;AAAA,EAEd;AAAA;AAAA;AAAA;AAAA,EAKQ,oBACNP,GACAC,GACAC,GACAG,GACe;AACf,UAAMa,IAAiD,CAAC,OAAO,UAAU,QAAQ,OAAO,GAClFC,IAAaD,EAAM,KAAK,MAAM,KAAK,OAAA,IAAWA,EAAM,MAAM,CAAC;AACjE,WAAO,KAAK,sBAAsBC,GAAYnB,GAAeC,GAAWC,GAAiBG,CAAM;AAAA,EACjG;AAAA;AAAA;AAAA;AAAA,EAKQ,0BACNW,GACAC,GACAf,GACAC,GACAC,GACe;AACf,UAAMgB,IAAiB,KAAK,OAAO,MAAM,YAAY,CAAA,GAC/CC,IAAeD,EAAe,gBAAgB;AAGpD,QAAIE;AACJ,UAAMC,IAAeH,EAAe,UAAU;AAE9C,QAAI,OAAOG,KAAiB,YAAYA,EAAa,SAAS,GAAG,GAAG;AAElE,YAAMC,IAAa,WAAWD,CAAY,IAAI;AAI9C,MAAAD,IAHiB,KAAK;AAAA,QACpBpB,EAAgB,SAAS,IAAIA,EAAgB,UAAU;AAAA,MAAA,IAErCsB,IAAa;AAAA,IACnC;AACE,MAAAF,IAAS,OAAOC,KAAiB,WAAWA,IAAe;AAI7D,QAAIE;AACJ,IAAIJ,MAAiB,SACnBI,IAAStB,IAAaC,IAAe,IAAI,KAAK,KAE9CqB,IAAQ,KAAK,OAAA,IAAW,IAAI,KAAK;AAInC,UAAMnB,IAAUJ,EAAgB,QAAQ,GAClCK,IAAUL,EAAgB,SAAS,GAGnCW,IAASP,IAAU,KAAK,IAAImB,CAAK,IAAIH,GACrCR,IAASP,IAAU,KAAK,IAAIkB,CAAK,IAAIH;AAE3C,WAAO,EAAE,GAAGT,GAAQ,GAAGC,EAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmBX,GAAqC;AACtD,UAAMvH,IAAW,KAAK,OAAO,OAAO,UAC9B8I,IAAU,KAAK,OAAO,OAAO,SAC7B7I,IAAS,KAAK,OAAO;AAE3B,WAAO;AAAA,MACL,gBAAgB;AAAA;AAAA,MAChB,UAAAD;AAAA,MACA,OAAOuH,IAAauB;AAAA,MACpB,QAAA7I;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBACEsF,GACA6B,GACA5C,GACAI,GACAc,GACAC,GACAG,GACAE,GACQ;AAER,UAAMa,IAAatB,EAAc,IAAI6B,EAAc,GAC7CN,IAAavB,EAAc,IAAI6B,EAAc,GAG7CpG,IAAW8E,MAAkB,SAAYA,IAAgBtB,GAGzDzD,IAAQiF,MAAe,SAAYA,IAAapB,GAGhD4B,IAAgBd,MAAe,SAAY,CAACA,IAAa,IAAI,GAC7De,IAAgBd,MAAgB,SAAY,CAACA,IAAc,IAAI,GAC/DoD,IAAkBrD,MAAe,SACnC,aAAac,CAAa,OAAOC,CAAa,QAC9C;AAEJ,WAAIlB,EAAc,WAET,GAAGwD,CAAe,cAAclC,CAAU,OAAOC,CAAU,cAAc9F,CAAQ,kBAInF,GAAG+H,CAAe,cAAclC,CAAU,OAAOC,CAAU,cAAc9F,CAAQ,cAAcD,CAAK;AAAA,EAC7G;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAAoBC,GAAkBD,GAAe2E,GAAqBC,GAA8B;AAEtG,QAAID,MAAe,UAAaC,MAAgB,QAAW;AACzD,YAAMqD,IAAU,CAACtD,IAAa,GACxBuD,IAAU,CAACtD,IAAc;AAC/B,aAAO,aAAaqD,CAAO,OAAOC,CAAO,cAAcjI,CAAQ,cAAcD,CAAK;AAAA,IACpF;AACA,WAAO,gCAAgCC,CAAQ,cAAcD,CAAK;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAA2B;AACzB,UAAMf,IAAW,KAAK,OAAO,OAAO,UAC9BC,IAAS,KAAK,OAAO;AAG3B,WAAI,KAAK,wBACA,WAAWD,CAAQ,gBAGrB,WAAWA,CAAQ,0BAA0BA,CAAQ,MAAMC,CAAM;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA+B;AAC7B,WAAO+G,GAAoB,KAAK,WAAW,IAAI;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,cAA6B;AAC3B,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAmD;AACjD,WAAO;AAAA,MACL,UAAU,KAAK,OAAO,OAAO;AAAA,MAC7B,SAAS,KAAK,OAAO,OAAO;AAAA,IAAA;AAAA,EAEhC;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAyC;AACvC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAqC;AACnC,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,uBAAuBxC,GAA+B;AAGpD,YAFa,KAAK,eAAe,MAEzB;AAAA,MACN,KAAK;AAEH,eAAOA;AAAA,MAET,KAAK,UAAU;AAEb,cAAM0E,IAAc,KAAK,eAAe;AACxC,YAAIA,MAAgB;AAElB,iBAAO1E,KAAiB,KAAK,OAAA,IAAW,OAAO;AAEjD,YAAI,OAAO0E,KAAgB;AACzB,iBAAOA;AAGT,cAAMC,IAAQD,EAAY,MAAMA,EAAY;AAC5C,eAAOA,EAAY,MAAM,KAAK,OAAA,IAAWC;AAAA,MAC3C;AAAA,MAEA,KAAK,QAAQ;AAEX,cAAMC,IAAY,KAAK,eAAe,aAAa,GAC7CC,IAAY,KAAK,qBAAqB7E,CAAa;AACzD,eAAOA,IAAiB4E,IAAY,MAAMC;AAAA,MAC5C;AAAA,MAEA,KAAK;AAEH,eAAO7E,KAAiB,KAAK,OAAA,IAAW,OAAO;AAAA,MAEjD,KAAK;AAEH,eAAOA;AAAA,MAET;AACE,eAAOA;AAAA,IAAA;AAAA,EAEb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBAAqBA,GAA+B;AAG1D,YAFkB,KAAK,eAAe,aAAa,QAE3C;AAAA,MACN,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO,KAAK,OAAA,IAAW,MAAM,IAAI;AAAA,MACnC,KAAK;AAAA,MACL;AAGE,eAAOA,KAAiB,IAAI,IAAI;AAAA,IAAA;AAAA,EAEtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,qBAA8B;AAC5B,WAAO,KAAK,eAAe,SAAS;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,wBAAwBvC,GAAkBuC,GAA+B;AACvE,QAAI,KAAK,eAAe,SAAS;AAC/B,aAAOA;AAGT,UAAMC,IAAe,KAAK,eAAe,UAAU;AAAA,MACjD,WAAW;AAAA,MACX,WAAW;AAAA,MACX,OAAO;AAAA,IAAA,GAGH,EAAE,WAAAf,GAAW,WAAAC,GAAW,OAAAC,EAAA,IAAUa,GAGlCjB,IAAc,KAAK,IAAIvB,IAAW0B,IAAY,KAAK,KAAK,CAAC,GAGzDQ,IAAcP,IAAQ,KAAK,IAAI,IAAI3B,GAAU,CAAC,IAAI,GAGlDyC,IAAehB,IAAYF,IAAcW;AAE/C,WAAOK,IAAgBE;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAmC;AACjC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,eAA+B;AAC7B,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoBE,GAA4B;AAG9C,YAFa,KAAK,YAAY,MAEtB;AAAA,MACN,KAAK;AAEH,eAAOA;AAAA,MAET,KAAK;AAGH,gBADmB,KAAK,YAAY,cAAc,OAC9BA;AAAA,MAGtB,KAAK;AAGH,gBADmB,KAAK,YAAY,cAAc,OAC9BA;AAAA,MAGtB,KAAK;AAGH,eAAOA;AAAA,MAET,KAAK,UAAU;AAEb,cAAMuE,IAAQ,KAAK,YAAY,SAAS,EAAE,KAAK,KAAK,KAAK,EAAA;AAEzD,gBADqBA,EAAM,MAAM,KAAK,YAAYA,EAAM,MAAMA,EAAM,QAC9CvE;AAAA,MACxB;AAAA,MAEA;AACE,eAAOA;AAAA,IAAA;AAAA,EAEb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAA2B;AACzB,WAAO,KAAK,YAAY,SAAS;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAkB3C,GAAkB2C,GAA4B;AAC9D,QAAI,KAAK,YAAY,SAAS;AAC5B,aAAOA;AAGT,UAAMC,IAAY,KAAK,YAAY,OAAO;AAAA,MACxC,WAAW;AAAA,MACX,SAAS;AAAA,IAAA,GAGL,EAAE,WAAAnD,GAAW,SAAAC,EAAA,IAAYkD,GAIzB9C,IAAY,KAAK,6BAA6BJ,GAASD,CAAS;AAGtE,QAAIsD,IAAeJ;AACnB,aAAStC,IAAI,GAAGA,IAAIP,EAAU,QAAQO;AACpC,UAAIL,KAAYF,EAAUO,CAAC,EAAE,MAAM;AACjC,cAAM2C,IAAW3C,MAAM,IAAI,IAAIP,EAAUO,IAAI,CAAC,EAAE,MAC1C4C,IAAY5C,MAAM,IAAIsC,IAAa7C,EAAUO,IAAI,CAAC,EAAE,OACpD6C,KAAmBlD,IAAWgD,MAAalD,EAAUO,CAAC,EAAE,OAAO2C,IAE/DG,IAAgB,KAAK,YAAYD,CAAe;AACtD,QAAAH,IAAeE,KAAanD,EAAUO,CAAC,EAAE,QAAQ4C,KAAaE;AAC9D;AAAA,MACF;AAGF,WAAOJ,IAAeJ;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,6BACNjD,GACAD,GACwC;AACxC,UAAMK,IAAoD,CAAA;AAG1D,IAAAA,EAAU,KAAK,EAAE,MAAM,KAAK,OAAOL,GAAW;AAG9C,QAAImB,IAAmBnB;AACvB,UAAMoD,IAAc,KAEdhC,IADgB,OACcnB,IAAU;AAE9C,QAAIiB,IAAc;AAClB,aAASN,IAAI,GAAGA,IAAIX,GAASW,KAAK;AAEhC,YAAMyC,IAAa,KAAKlC,IAAmB,KAAKiC;AAChD,MAAAlC,KAAeE,GACff,EAAU,KAAK,EAAE,MAAMa,GAAa,OAAOmC,GAAY,GAGvDlC,IAAmB,KAAKA,IAAmB,KAAKiC,IAAcA,GAC9DlC,KAAeE,GACXR,IAAIX,IAAU,KAChBI,EAAU,KAAK,EAAE,MAAMa,GAAa,OAAOC,GAAkB;AAAA,IAEjE;AAGA,WAAAd,EAAU,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,GAE7BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,GAAmB;AACrC,WAAO,KAAK,IAAI,MAAM,IAAI;AAAA,EAC5B;AACF;ACrpBO,MAAMuH,GAAuD;AAAA,EAIlE,YAAY9J,GAAsB+J,IAA2B,IAAI;AAC/D,SAAK,SAAS/J,GACd,KAAK,cAAc+J;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAASC,GAAoBlC,GAAkChC,IAA+B,CAAA,GAAmB;;AAC/G,UAAMmE,IAAyB,CAAA,GACzB,EAAE,OAAAC,GAAO,QAAAC,EAAA,IAAWrC,GAEpBsC,IAAU,KAAK,OAAO,QAAQ,SAE9BC,IAAgBvE,EAAQ,eAAe,KAGvCwE,MAAe5L,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,SAAQ,QAClD6L,MAAc1L,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,KACvD2L,MAAcxL,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,IAGvDyL,MAAcvL,KAAAH,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAAG,EAAmC,QAAO,GACxDwL,MAAcC,KAAA1L,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAA0L,EAAmC,QAAO,GACxDC,IAAcH,MAAgB,KAAOC,MAAgB,GAKrDG,IAAaR,IADI,MAC8B,GAC/CS,IAAaT,IAAgB,GAE7BU,IAAOb,IAAQE,IAAUS,GACzBG,IAAOb,IAASC,IAAUU,GAC1BG,IAAOb,IAAUS,GACjBK,IAAOd,IAAUU;AAEvB,aAAShI,IAAI,GAAGA,IAAIkH,GAAYlH,KAAK;AAEnC,YAAM3C,IAAI,KAAK,OAAO8K,GAAMF,CAAI,GAC1B3K,IAAI,KAAK,OAAO8K,GAAMF,CAAI,GAG1BxJ,IAAW8I,MAAiB,WAAW,KAAK,OAAOC,GAAaC,CAAW,IAAI,GAG/EjJ,IAAQqJ,IAAc,KAAK,OAAOH,GAAaC,CAAW,IAAI,GAC9DS,IAAkBd,IAAgB9I,GAElC6J,IAAsB;AAAA,QAC1B,IAAItI;AAAA,QACJ,GAAA3C;AAAA,QACA,GAAAC;AAAA,QACA,UAAAoB;AAAA,QACA,OAAAD;AAAA,QACA,UAAU4J;AAAA,MAAA;AAGZ,MAAAlB,EAAQ,KAAKmB,CAAM;AAAA,IACrB;AAEA,WAAOnB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,OAAOoB,GAAaC,GAAqB;AAC/C,WAAO,KAAK,OAAA,KAAYA,IAAMD,KAAOA;AAAA,EACvC;AACF;ACjFO,MAAME,GAAuD;AAAA,EAIlE,YAAYvL,GAAsB+J,IAA2B,IAAI;AAC/D,SAAK,SAAS/J,GACd,KAAK,cAAc+J;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SACEC,GACAlC,GACAhC,IAA+B,CAAA,GAChB;;AACf,UAAMmE,IAAyB,CAAA,GACzB,EAAE,OAAAC,GAAO,QAAAC,EAAA,IAAWrC,GACpB,EAAE,cAAA0D,MAAiB,KAAK,QAExBnB,IAAgBvE,EAAQ,eAAe,KAGvCwE,MAAe5L,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,SAAQ,QAClD6L,MAAc1L,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,KACvD2L,MAAcxL,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,IAGvDyL,MAAcvL,KAAAH,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAAG,EAAmC,QAAO,GACxDwL,MAAcC,KAAA1L,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAA0L,EAAmC,QAAO,GACxDC,IAAcH,MAAgB,KAAOC,MAAgB,GAGrDe,IAAa,KAAK,OAAO,cAAc,GAGvCC,IAAe,CAAC,SAAS,QAAQ,OAAO,UAAU,UAAU,QAAQ,GAGpE7D,IAAY/B,EAAQ,eAAeuE,GACnCsB,IAAKzB,IAAQ,GACb0B,IAAKzB,IAAS,GAGd0B,IAAoB,KAAK,KAAK,KAAK,KAAK7B,CAAU,CAAC;AAGzD,QAAIA,IAAa,GAAG;AAElB,YAAM8B,IAAgBlB,IAAc,KAAK,OAAOH,GAAaC,CAAW,IAAI,GACtEqB,IAAalE,IAAYiE;AAE/B,MAAA7B,EAAQ,KAAK;AAAA,QACX,IAAI;AAAA,QACJ,GAAG0B;AAAA,QACH,GAAGC;AAAA,QACH,UAAUtB,MAAiB,WAAW,KAAK,OAAOC,IAAc,MAAMC,IAAc,IAAI,IAAI;AAAA;AAAA,QAC5F,OAAOsB;AAAA,QACP,UAAUC;AAAA,QACV,QAAQ;AAAA;AAAA,QACR,aAAaP,IAAe,SAAS;AAAA,MAAA,CACtC;AAAA,IACH;AAEA,QAAIQ,IAAiB,GACjBC,IAAc;AAElB,WAAOD,IAAiBhC,KAAY;AAElC,YAAMkC,IAAiBD,IAAcJ,GAC/BM,IAAYV,IAAa,IAC3B,IAAKS,IAAiBT,IAAa,MACnC,GAIEW,IAAUH,KAAepE,IAAY,MACrCwE,IAAUD,IAAU,KAEpBE,IAAgB,KAAK,MAAM,KAAKD,IAAUD,KAAW,KAAK,MAAM,IAAIC,IAAUD,MAAYC,IAAU,IAAID,EAAQ,IAEhHG,IAAqB,KAAK,cAAc1E,CAAS,GAEjD2E,IAAc,KAAK,MAAMF,KAAiBC,IAAqB,IAAI;AAEzE,UAAIC,MAAgB,GAAG;AACrB,QAAAP;AACA;AAAA,MACF;AAEA,YAAMQ,IAAa,IAAI,KAAK,KAAMD,GAG5BE,IAAaT,KAAe,KAAK,KAAK,KAAK;AAEjD,eAASnJ,IAAI,GAAGA,IAAI0J,KAAeR,IAAiBhC,GAAYlH,KAAK;AACnE,cAAMuG,IAASvG,IAAI2J,IAAaC,GAG1BZ,KAAgBlB,IAAc,KAAK,OAAOH,GAAaC,CAAW,IAAI,GACtEiC,IAAgBR,IAAYL,IAC5BX,KAAkBtD,IAAY8E;AAGpC,YAAIxM,IAAIwL,IAAK,KAAK,IAAItC,CAAK,IAAIgD,GAC3BjM,IAAIwL,IAAK,KAAK,IAAIvC,CAAK,IAAI+C;AAI/B,cAAMhC,IAAU,KAAK,OAAO,QAAQ,WAAW,IAEzCS,IAAaM,KADI,MACgC,GACjDL,IAAaK,KAAkB;AAGrC,QAAIhL,IAAI0K,IAAYT,IAClBjK,IAAIiK,IAAUS,IACL1K,IAAI0K,IAAYX,IAAQE,MACjCjK,IAAI+J,IAAQE,IAAUS,IAIpBzK,IAAI0K,IAAaV,IACnBhK,IAAIgK,IAAUU,IACL1K,IAAI0K,IAAaX,IAASC,MACnChK,IAAI+J,IAASC,IAAUU;AAGzB,cAAMtJ,KAAW8I,MAAiB,WAAW,KAAK,OAAOC,GAAaC,CAAW,IAAI;AAErF,QAAAP,EAAQ,KAAK;AAAA,UACX,IAAI+B;AAAA,UACJ,GAAA7L;AAAA,UACA,GAAAC;AAAA,UACA,UAAAoB;AAAA,UACA,OAAOmL;AAAA,UACP,UAAUxB;AAAA,UACV,QAAQ,KAAK,IAAI,GAAG,MAAMc,CAAW;AAAA;AAAA,UACrC,aAAaT,IAAeE,GAAcO,IAAc,KAAKP,EAAa,MAAM,IAAI;AAAA,QAAA,CACrF,GAEDM;AAAA,MACF;AAEA,MAAAC;AAAA,IACF;AAEA,WAAOhC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,cAAcE,GAAwB;AAG5C,WAAOA,IAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,OAAOkB,GAAaC,GAAqB;AAC/C,WAAO,KAAK,OAAA,KAAYA,IAAMD,KAAOA;AAAA,EACvC;AACF;AChLA,MAAMuB,KAA2C;AAAA,EAC/C,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,eAAe;AAAA,EACf,WAAW;AAAA,EACX,KAAK;AAAA,EACL,gBAAgB;AAClB,GAKMC,KAA0B;AAAA,EAC9B,EAAE,GAAG,GAAG,GAAG,EAAA;AAAA;AAAA,EACX,EAAE,GAAG,IAAI,GAAG,GAAA;AAAA;AAAA,EACZ,EAAE,GAAG,GAAG,GAAG,GAAA;AAAA;AAAA,EACX,EAAE,GAAG,IAAI,GAAG,EAAA;AAAA;AAAA,EACZ,EAAE,GAAG,IAAI,GAAG,EAAA;AAAA;AAAA,EACZ,EAAE,GAAG,GAAG,GAAG,EAAA;AAAA;AAAA,EACX,EAAE,GAAG,GAAG,GAAG,GAAA;AAAA;AAAA,EACX,EAAE,GAAG,GAAG,GAAG,EAAA;AAAA;AACb;AAEO,MAAMC,GAAqD;AAAA,EAIhE,YAAY9M,GAAsB+J,IAA2B,IAAI;AAC/D,SAAK,SAAS/J,GACd,KAAK,cAAc+J;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SACEC,GACAlC,GACAhC,IAA6B,CAAA,GACd;;AACf,UAAMmE,IAAyB,CAAA,GACzB,EAAE,OAAAC,GAAO,QAAAC,EAAA,IAAWrC,GAEpBiF,IAAa,EAAE,GAAGH,IAAqB,GAAG,KAAK,OAAO,KAAA,GACtDxC,IAAU,KAAK,OAAO,QAAQ,SAE9BC,IAAgBvE,EAAQ,eAAe,KAGvCwE,MAAe5L,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,SAAQ,QAGlD+L,MAAc5L,KAAAJ,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAAI,EAAmC,QAAO,GACxD6L,MAAc1L,KAAAJ,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAAI,EAAmC,QAAO,GACxD4L,IAAcH,MAAgB,KAAOC,MAAgB,GAGrDsC,IAAiB9C,IAAS,IAAIE,GAC9B6C,IAAkB9C,IAAU,IAAIC,GAGhC,EAAE,SAAA8C,GAAS,MAAAC,EAAA,IAAS,KAAK;AAAA,MAC7BnD;AAAA,MACAgD;AAAA,MACAC;AAAA,MACA5C;AAAA,MACA0C;AAAA,IAAA,GAKIK,IAAgBL,EAAW,YAAY,OACvCM,IAAmBN,EAAW,YAAY,UAG1CO,IAAmBF,IAAgBF,IAAU,MAAMA,GACnDK,IAAgBF,IAAmBF,IAAO,MAAMA,GAGhDK,KAAaR,IAAkBD,EAAW,OAAOG,IAAU,MAAOI,GAClEG,KAAcR,IAAmBF,EAAW,OAAOI,IAAO,MAAOI,GAGjEG,IAAiBN,IAAgBI,IAAY,IAAI,GACjDG,IAAiBN,IAAmBI,IAAa,IAAI,GAIrDG,IAAoB,IAAIb,EAAW,SACnCc,IAAgB,KAAK,IAAIL,GAAWC,CAAU,IAAIG,GAIlD/F,IAAY/B,EAAQ,cACtB,KAAK,IAAIA,EAAQ,aAAa+H,CAAa,IAC3CA,GAGEC,IAAiBZ,IAAUM,KAAaN,IAAU,KAAKH,EAAW,MAAMW,GACxEK,IAAkBZ,IAAOM,KAAcN,IAAO,KAAKJ,EAAW,MAAMY,GAGpEK,IAAc5D,KAAW4C,IAAiBc,KAAkB,GAC5DG,IAAc7D,KAAW6C,IAAkBc,KAAmB,GAG9DG,IAAYhB,IAAUC,GACtBgB,IAAepB,EAAW,YAAY,UAAUA,EAAW,SAAS,QACpEqB,IAAiBD,KAAgBnE,IAAakE;AAEpD,IAAI,OAAO,SAAW,QACnB,OAAe,sBAAsB;AAAA,MACpC,mBAAmBnB,EAAW;AAAA,MAC9B,gBAAgBA,EAAW;AAAA,MAC3B,SAAAG;AAAA,MAAS,MAAAC;AAAA,MAAM,WAAAe;AAAA,MACf,cAAAC;AAAA,MAAc,YAAAnE;AAAA,MAAY,gBAAAoE;AAAA,IAAA;AAK9B,UAAMC,IAA2BD,IAAiB,IAAI,MAAMF,CAAS,EAAE,KAAK,CAAC,IAAI,CAAA,GAG3EI,IAAmB,KAAK,IAAId,GAAWC,CAAU,IAAIV,EAAW;AAEtE,aAASjK,IAAI,GAAGA,IAAIkH,GAAYlH,KAAK;AACnC,UAAIyL,GACAC,GACAC,IAAa;AAEjB,UAAIL,KAAkBtL,KAAKoL,GAAW;AAEpC,cAAMQ,IAAgB5L,IAAIoL,GACpBS,IAAaD,IAAgBR;AACnC,QAAAO,IAAa,KAAK,MAAMC,IAAgBR,CAAS,IAAI,GACrDG,EAAeM,CAAU,KAGrB5B,EAAW,kBAAkB,SAC/BwB,IAAMI,IAAazB,GACnBsB,IAAM,KAAK,MAAMG,IAAazB,CAAO,MAErCsB,IAAMG,IAAaxB,GACnBoB,IAAM,KAAK,MAAMI,IAAaxB,CAAI;AAAA,MAEtC;AAEE,QAAIJ,EAAW,kBAAkB,SAC/BwB,IAAMzL,IAAIoK,GACVsB,IAAM,KAAK,MAAM1L,IAAIoK,CAAO,MAE5BsB,IAAM1L,IAAIqK,GACVoB,IAAM,KAAK,MAAMzL,IAAIqK,CAAI;AAK7B,UAAIyB,IAAcZ,IAAcO,KAAOf,IAAYT,EAAW,OAAOS,IAAY,GAC7EqB,KAAcZ,IAAcO,KAAOf,IAAaV,EAAW,OAAOU,IAAa;AAUnF,UAPIV,EAAW,YAAY,SAASyB,IAAM,MAAM,IAC9CI,KAAepB,IAAY,IAClBT,EAAW,YAAY,YAAYwB,IAAM,MAAM,MACxDM,MAAepB,IAAa,IAI1BgB,IAAa,GAAG;AAClB,cAAMK,KAAgBL,IAAa,KAAK5B,GAAwB,QAC1DkC,IAAUlC,GAAwBiC,CAAY;AACpD,QAAAF,KAAeG,EAAQ,IAAIT,GAC3BO,MAAeE,EAAQ,IAAIT;AAAA,MAC7B;AAGA,UAAIvB,EAAW,SAAS,GAAG;AACzB,cAAMiC,IAAcxB,IAAY,IAAKT,EAAW,QAC1CkC,IAAcxB,IAAa,IAAKV,EAAW;AACjD,QAAA6B,KAAe,KAAK,OAAO,CAACI,GAAYA,CAAU,GAClDH,MAAe,KAAK,OAAO,CAACI,GAAYA,CAAU;AAAA,MACpD;AAGA,UAAI9O,KAAIyO,GACJxO,KAAIyO;AAGR,UAAI,CAACT,KAAkBrB,EAAW,kBAAkB,OAAO;AACzD,cAAMmC,IAAiBlF,IAAakD,KAAWA;AAG/C,YAFkBsB,MAAQ,KAAK,OAAOxE,IAAa,KAAKkD,CAAO,KAE9CgC,IAAiBhC,GAAS;AACzC,gBAAMiC,KAAeD,IAAiB1B,KAAa0B,IAAiB,KAAKnC,EAAW;AACpF,cAAIqC,KAAkB;AAEtB,UAAIrC,EAAW,cAAc,WAC3BqC,MAAmBtB,IAAiBqB,MAAgB,IAC3CpC,EAAW,cAAc,UAClCqC,KAAkBtB,IAAiBqB,KAGrChP,MAAKiP;AAAA,QACP;AAAA,MACF;AAGA,YAAMtD,KAAgBlB,IAAc,KAAK,OAAOH,GAAaC,CAAW,IAAI,GACtES,KAAkBtD,IAAYiE,IAK9BjB,KAAaM,KADI,MACgC,GACjDL,KAAaK,KAAkB,GAC/BF,KAAOb,IAAUS,IACjBE,KAAOb,IAAQE,IAAUS,IACzBK,KAAOd,IAAUU,IACjBE,KAAOb,IAASC,IAAUU;AAEhC,MAAA3K,KAAI,KAAK,IAAI8K,IAAM,KAAK,IAAI9K,IAAG4K,EAAI,CAAC,GACpC3K,KAAI,KAAK,IAAI8K,IAAM,KAAK,IAAI9K,IAAG4K,EAAI,CAAC;AAIpC,UAAIxJ,KAAW;AACf,UAAI8I,MAAiB,UAAU;AAC7B,cAAMC,MAAcrL,KAAAH,KAAA,KAAK,YAAY,aAAjB,gBAAAA,GAA2B,UAA3B,gBAAAG,EAAkC,QAAO,KACvDsL,MAAcG,KAAA1L,KAAA,KAAK,YAAY,aAAjB,gBAAAA,GAA2B,UAA3B,gBAAA0L,EAAkC,QAAO;AAC7D,QAAIoC,EAAW,SAAS,IAEtBvL,KAAW,KAAK,OAAO+I,IAAcwC,EAAW,QAAQvC,IAAcuC,EAAW,MAAM,IAGvFvL,KAAW,KAAK,OAAO+I,GAAaC,CAAW;AAAA,MAEnD;AAKA,UAAI6E;AACJ,MAAIjB,KAAkBK,IAAa,IAGjCY,KAAS,KAAKZ,IAGdY,KAASjB,IAAiB,MAAMtL,IAAIA,IAAI,GAG1CmH,EAAQ,KAAK;AAAA,QACX,IAAInH;AAAA,QACJ,GAAA3C;AAAA,QACA,GAAAC;AAAA,QACA,UAAAoB;AAAA,QACA,OAAOsK;AAAA,QACP,UAAUX;AAAA,QACV,QAAAkE;AAAA,MAAA,CACD;AAAA,IACH;AAEA,WAAOpF;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,wBACND,GACAgD,GACAC,GACAqC,GACAtP,GACmC;AACnC,QAAIkN,GACAC;AAEJ,QAAInN,EAAO,YAAY,UAAUA,EAAO,SAAS;AAC/C,MAAAkN,IAAUlN,EAAO,SACjBmN,IAAOnN,EAAO;AAAA,aACLA,EAAO,YAAY;AAC5B,MAAAkN,IAAUlN,EAAO,SACjBmN,IAAO,KAAK,KAAKnD,IAAakD,CAAO;AAAA,aAC5BlN,EAAO,SAAS;AACzB,MAAAmN,IAAOnN,EAAO,MACdkN,IAAU,KAAK,KAAKlD,IAAamD,CAAI;AAAA,SAChC;AAEL,YAAMoC,IAAcvC,IAAiBC;AAQrC,WAJAC,IAAU,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,KAAKlD,IAAauF,IAH/B,GAG6D,CAAC,CAAC,GACxFpC,IAAO,KAAK,KAAKnD,IAAakD,CAAO,GAG9BA,IAAU,MAAMA,IAAU,KAAKC,KAAQnD;AAC5C,QAAAkD;AAAA,IAEJ;AAEA,WAAO,EAAE,SAAS,KAAK,IAAI,GAAGA,CAAO,GAAG,MAAM,KAAK,IAAI,GAAGC,CAAI,EAAA;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAO9B,GAAaC,GAAqB;AAC/C,WAAO,KAAK,OAAA,KAAYA,IAAMD,KAAOA;AAAA,EACvC;AACF;AC7TA,MAAMmE,KAAe,KAAK,MAAM,IAAI,KAAK,KAAK,CAAC,IAEzCC,KAA+C;AAAA,EACnD,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AACd;AAEO,MAAMC,GAAuD;AAAA,EAIlE,YAAY1P,GAAsB+J,IAA2B,IAAI;AAC/D,SAAK,SAAS/J,GACd,KAAK,cAAc+J;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SACEC,GACAlC,GACAhC,IAA+B,CAAA,GAChB;;AACf,UAAMmE,IAAyB,CAAA,GACzB,EAAE,OAAAC,GAAO,QAAAC,EAAA,IAAWrC,GAEpB6H,IAAe,EAAE,GAAGF,IAAuB,GAAG,KAAK,OAAO,OAAA,GAC1DrF,IAAU,KAAK,OAAO,QAAQ,SAE9BC,IAAgBvE,EAAQ,eAAe,KAGvCwE,MAAe5L,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,SAAQ,QAClD6L,MAAc1L,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,KACvD2L,MAAcxL,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,IAGvDyL,MAAcvL,KAAAH,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAAG,EAAmC,QAAO,GACxDwL,MAAcC,KAAA1L,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAA0L,EAAmC,QAAO,GACxDC,IAAcH,MAAgB,KAAOC,MAAgB,GAGrDe,IAAa,KAAK,OAAO,cAAckE,EAAa,YAGpDhE,IAAKzB,IAAQ,GACb0B,IAAKzB,IAAS,GAGdyF,IAAY,KAAK;AAAA,MACrBjE,IAAKvB,IAAUC,IAAgB;AAAA,MAC/BuB,IAAKxB,IAAUC,IAAgB;AAAA,IAAA,GAI3BwF,IAAsBF,EAAa,cAAc,cAAc,KAAK;AAE1E,aAAS7M,IAAI,GAAGA,IAAIkH,GAAYlH,KAAK;AAEnC,UAAIuG,GACAH;AAEJ,UAAIyG,EAAa,eAAe;AAG9B,QAAAtG,IAAQvG,IAAI0M,KAAeK,IAAsBF,EAAa,YAE9DzG,IAAS,KAAK,sBAAsBpG,GAAGkH,GAAY4F,GAAWD,EAAa,SAAS;AAAA,eAC3EA,EAAa,eAAe,eAAe;AAEpD,cAAMG,IAAQhN,IAAI,MAAM6M,EAAa;AACrC,QAAAtG,IAAQyG,IAAQD,IAAsBF,EAAa,YACnDzG,IAAS,KAAK,2BAA2B4G,GAAO9F,GAAY4F,GAAWD,EAAa,SAAS;AAAA,MAC/F,OAAO;AAEL,cAAMG,IAAQhN,IAAI,MAAM6M,EAAa;AACrC,QAAAtG,IAAQyG,IAAQD,IAAsBF,EAAa,YACnDzG,IAAS,KAAK,2BAA2B4G,GAAO9F,GAAY4F,GAAWD,EAAa,SAAS;AAAA,MAC/F;AAGA,YAAMxP,IAAIwL,IAAK,KAAK,IAAItC,CAAK,IAAIH,GAC3B9I,IAAIwL,IAAK,KAAK,IAAIvC,CAAK,IAAIH,GAG3B6G,IAAmB7G,IAAS0G,GAC5BI,IAAavE,IAAa,IAC5B,IAAKsE,IAAmBtE,IAAa,MACrC,GAGEK,IAAgBlB,IAAc,KAAK,OAAOH,GAAaC,CAAW,IAAI,GACtEiC,IAAgBqD,IAAalE,GAG7BX,IAAkBd,IAAgBsC,GAKlC9B,IAAaM,IADI,MACgC,GACjDL,IAAaK,IAAkB,GAC/BF,KAAOb,IAAUS,GACjBE,IAAOb,IAAQE,IAAUS,GACzBK,KAAOd,IAAUU,GACjBE,IAAOb,IAASC,IAAUU,GAE1BmF,IAAW,KAAK,IAAIhF,IAAM,KAAK,IAAI9K,GAAG4K,CAAI,CAAC,GAC3CmF,IAAW,KAAK,IAAIhF,IAAM,KAAK,IAAI9K,GAAG4K,CAAI,CAAC;AAGjD,UAAIxJ,IAAW;AACf,UAAI8I,MAAiB,UAAU;AAC7B,cAAM6F,IAAgB9G,IAAQ,MAAM,KAAK,KAAM,KACzC+G,KAAmB,KAAK,OAAO7F,GAAaC,CAAW;AAC7D,QAAAhJ,IAAWmO,EAAa,eAAe,WACnCS,KACCD,IAAe,MAAMC,KAAmB;AAAA,MAC/C,MAAA,CAAW9F,MAAiB,cAE1B9I,IAAW,KAAK,uBAAuB6H,GAAOH,GAAQyG,CAAY;AAIpE,YAAMN,IAASrF,IAAalH;AAE5B,MAAAmH,EAAQ,KAAK;AAAA,QACX,IAAInH;AAAA,QACJ,GAAGmN;AAAA,QACH,GAAGC;AAAA,QACH,UAAA1O;AAAA,QACA,OAAOmL;AAAA,QACP,UAAUxB;AAAA,QACV,QAAAkE;AAAA,MAAA,CACD;AAAA,IACH;AAEA,WAAOpF;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,uBACNZ,GACAH,GACAyG,GACQ;AAIR,QAAIU;AAEJ,QAAIV,EAAa,eAAe;AAG9B,MAAAU,IAAehH,IAAQ,KAAK,KAAK;AAAA,aACxBsG,EAAa,eAAe,eAAe;AAIpD,YAAMW,IAAI,IAAIX,EAAa,WACrBY,IAAM,KAAK,KAAKrH,IAASoH,CAAC;AAChC,MAAAD,IAAehH,IAAQkH;AAAA,IACzB,OAAO;AAGL,YAAMD,IAAI,OAAOX,EAAa,WACxBY,IAAM,KAAK,KAAK,IAAID,CAAC;AAC3B,MAAAD,IAAehH,IAAQkH;AAAA,IACzB;AAMA,WAHiBF,IAAe,MAAM,KAAK,KAAM,MAGhC;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBACNG,GACAxI,GACA4H,GACAa,GACQ;AAIR,UAAMvH,IADgB0G,IAAY,KAAK,KAAK5H,CAAW,IACxB,KAAK,KAAKwI,CAAK,IAAIC;AAClD,WAAO,KAAK,IAAIvH,GAAQ0G,CAAS;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,2BACNE,GACA9H,GACA4H,GACAa,GACQ;AAER,UAAMC,IAAW1I,IAAc,MAAMyI;AAErC,WADwBX,IAAQY,IACPd;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,2BACNE,GACA9H,GACA4H,GACAa,GACQ;AAER,UAAME,IAAIf,IAAY,MAChBU,IAAI,OAAOG,GAEXvH,IAASyH,IAAI,KAAK,IAAIL,IAAIR,CAAK,GAG/BY,IAAW1I,IAAc,MAAMyI,GAC/BG,IAAoBD,IAAI,KAAK,IAAIL,IAAII,CAAQ;AAEnD,WAAQxH,IAAS0H,IAAqBhB;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAOvE,GAAaC,GAAqB;AAC/C,WAAO,KAAK,OAAA,KAAYA,IAAMD,KAAOA;AAAA,EACvC;AACF;ACrPA,MAAMwF,KAAiD;AAAA,EACrD,cAAc;AAAA,EACd,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,cAAc;AAChB;AAEO,MAAMC,GAAwD;AAAA,EAInE,YAAY9Q,GAAsB+J,IAA2B,IAAI;AAC/D,SAAK,SAAS/J,GACd,KAAK,cAAc+J;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SACEC,GACAlC,GACAhC,IAAgC,CAAA,GACjB;;AACf,UAAMmE,IAAyB,CAAA,GACzB,EAAE,OAAAC,GAAO,QAAAC,EAAA,IAAWrC,GAEpBiJ,IAAgB,EAAE,GAAGF,IAAwB,GAAG,KAAK,OAAO,QAAA,GAC5DzG,IAAU,KAAK,OAAO,QAAQ,SAE9BC,IAAgBvE,EAAQ,eAAe,KAGvCwE,MAAe5L,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,SAAQ,QAClD6L,MAAc1L,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,KACvD2L,MAAcxL,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,IAGvDyL,MAAcvL,KAAAH,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAAG,EAAmC,QAAO,GACxDwL,MAAcC,KAAA1L,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAA0L,EAAmC,QAAO,GACxDC,IAAcH,MAAgB,KAAOC,MAAgB,GAGrDsG,IAAe,KAAK;AAAA,MACxBhH;AAAA,MACA+G,EAAc;AAAA,MACd7G;AAAA,MACAC;AAAA,MACA4G,EAAc;AAAA,IAAA,GAIVE,IAAiB,KAAK;AAAA,MAC1BD;AAAA,MACA9G;AAAA,MACAC;AAAA,MACAC;AAAA,MACA2G;AAAA,IAAA,GAIIG,IAAmB,IAAI,MAAMF,CAAY,EAAE,KAAK,CAAC;AACvD,aAASlO,IAAI,GAAGA,IAAIkH,GAAYlH;AAC9B,MAAAoO,EAAiBpO,IAAIkO,CAAY;AAGnC,QAAIjJ,IAAa;AAGjB,aAASoJ,IAAa,GAAGA,IAAaH,GAAcG,KAAc;AAChE,YAAMC,IAAUH,EAAeE,CAAU,GACnCE,IAAsBH,EAAiBC,CAAU;AAEvD,eAASrO,IAAI,GAAGA,IAAIuO,GAAqBvO,KAAK;AAE5C,YAAI0G,GACAC;AAEJ,YAAIsH,EAAc,iBAAiB;AAEjC,UAAAvH,IAAU,KAAK,eAAA,IAAmB4H,EAAQ,QAC1C3H,IAAU,KAAK,eAAA,IAAmB2H,EAAQ;AAAA,aACrC;AAEL,gBAAM/H,IAAQ,KAAK,OAAO,GAAG,KAAK,KAAK,CAAC,GAClCiI,IAAW,KAAK,OAAO,GAAGF,EAAQ,MAAM;AAC9C,UAAA5H,IAAU,KAAK,IAAIH,CAAK,IAAIiI,GAC5B7H,IAAU,KAAK,IAAIJ,CAAK,IAAIiI;AAAA,QAC9B;AAGA,cAAM1D,IAAoB,IAAImD,EAAc,UAAU,KAChDQ,IAAiB,IAAIR,EAAc,UAAU;AAGnD,QAAAvH,KAAWoE,GACXnE,KAAWmE;AAGX,cAAM9B,IAAgBlB,IAAc,KAAK,OAAOH,GAAaC,CAAW,IAAI,GACtEiC,IAAgB4E,IAAiBzF,GAGjCjE,IAAYwC,IAAgBsC;AAGlC,YAAIxM,IAAIiR,EAAQ,IAAI5H,GAChBpJ,IAAIgR,EAAQ,IAAI3H;AAKpB,cAAMoB,KAAahD,IADI,MAC0B,GAC3CiD,IAAajD,IAAY;AAC/B,QAAA1H,IAAI,KAAK,IAAIiK,IAAUS,IAAW,KAAK,IAAI1K,GAAG+J,IAAQE,IAAUS,EAAS,CAAC,GAC1EzK,IAAI,KAAK,IAAIgK,IAAUU,GAAY,KAAK,IAAI1K,GAAG+J,IAASC,IAAUU,CAAU,CAAC;AAG7E,cAAMtJ,KAAW8I,MAAiB,WAAW,KAAK,OAAOC,GAAaC,CAAW,IAAI,GAI/EgH,IADqB,KAAK,KAAKhI,IAAUA,IAAUC,IAAUA,CAAO,IAC1B2H,EAAQ,QAClD/B,IAAS,KAAK,OAAO,IAAImC,KAAsB,EAAE,IAAI;AAE3D,QAAAvH,EAAQ,KAAK;AAAA,UACX,IAAIlC;AAAA,UACJ,GAAA5H;AAAA,UACA,GAAAC;AAAA,UACA,UAAAoB;AAAA,UACA,OAAOmL;AAAA,UACP,UAAU9E;AAAA,UACV,QAAAwH;AAAA,QAAA,CACD,GAEDtH;AAAA,MACF;AAAA,IACF;AAEA,WAAOkC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,sBACND,GACAyH,GACAvH,GACAC,GACAuH,GACQ;AACR,QAAID,MAAgB;AAClB,aAAO,KAAK,IAAI,GAAG,KAAK,IAAIA,GAAazH,CAAU,CAAC;AAMtD,UAAM2H,IAAgB,KAAK,IAAI,GAAG,KAAK,KAAK3H,IADd,CACgD,CAAC,GAGzE4H,IAAe,KAAK;AAAA,MACvB1H,IAAQwH,KAAmBvH,IAASuH,KAAkB;AAAA,IAAA;AAGzD,WAAO,KAAK,IAAI,GAAG,KAAK,IAAIC,GAAeC,GAAc,EAAE,CAAC;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKQ,uBACNC,GACA3H,GACAC,GACAC,GACApK,GACiB;AACjB,UAAM8R,IAA2B,CAAA,GAI3B7G,IAAOb,IAAUpK,EAAO,eACxB+K,IAAOb,IAAQE,IAAUpK,EAAO,eAChCkL,IAAOd,IAAUpK,EAAO,eACxBgL,IAAOb,IAASC,IAAUpK,EAAO;AAEvC,aAAS8C,IAAI,GAAGA,IAAI+O,GAAO/O,KAAK;AAC9B,UAAIiP,IAAsC,MACtCC,IAAkB;AAGtB,eAASC,IAAU,GAAGA,IAAU,KAAaA,KAAW;AACtD,cAAMC,IAAY;AAAA,UAChB,GAAG,KAAK,OAAOjH,GAAMF,CAAI;AAAA,UACzB,GAAG,KAAK,OAAOG,GAAMF,CAAI;AAAA,UACzB,QAAQ,KAAK,uBAAuBhL,CAAM;AAAA,QAAA;AAI5C,YAAImS,IAAc;AAClB,mBAAWC,KAAYN,GAAS;AAC9B,gBAAMzP,IAAK6P,EAAU,IAAIE,EAAS,GAC5B9P,IAAK4P,EAAU,IAAIE,EAAS,GAC5Bd,IAAW,KAAK,KAAKjP,IAAKA,IAAKC,IAAKA,CAAE;AAC5C,UAAA6P,IAAc,KAAK,IAAIA,GAAab,CAAQ;AAAA,QAC9C;AASA,aANIQ,EAAQ,WAAW,KAAKK,IAAcH,OACxCD,IAAgBG,GAChBF,IAAkBG,IAIhBA,KAAenS,EAAO;AACxB;AAAA,MAEJ;AAEA,MAAI+R,KACFD,EAAQ,KAAKC,CAAa;AAAA,IAE9B;AAEA,WAAOD;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuB9R,GAAwC;AACrE,WAAIA,EAAO,YAAY,YACdA,EAAO,gBAITA,EAAO,gBAAgB,KAAK,OAAO,KAAK,GAAG;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAyB;AAC/B,QAAIqS,IAAI,GAAGC,IAAI;AACf,WAAOD,MAAM,IAAG,CAAAA,IAAI,KAAK,OAAA;AACzB,WAAOC,MAAM,IAAG,CAAAA,IAAI,KAAK,OAAA;AAEzB,UAAMC,IAAQ,KAAK,KAAK,KAAO,KAAK,IAAIF,CAAC,CAAC,IAAI,KAAK,IAAI,IAAM,KAAK,KAAKC,CAAC;AAGxE,WAAO,KAAK,IAAI,IAAI,KAAK,IAAI,GAAGC,CAAK,CAAC,IAAI;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAOlH,GAAaC,GAAqB;AAC/C,WAAO,KAAK,OAAA,KAAYA,IAAMD,KAAOA;AAAA,EACvC;AACF;ACjRO,MAAMmH,GAAqD;AAAA,EAIhE,YAAYxS,GAAsB+J,IAA2B,IAAI;AAC/D,SAAK,SAAS/J,GACd,KAAK,cAAc+J;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SACEC,GACAlC,GACAhC,IAA6B,CAAA,GACd;;AACf,UAAMmE,IAAyB,CAAA,GACzB,EAAE,OAAAC,GAAO,QAAAC,EAAA,IAAWrC,GAEpBuC,IAAgBvE,EAAQ,eAAe,KACvCsE,IAAU,KAAK,OAAO,QAAQ,WAAW,IAGzCE,MAAe5L,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,SAAQ,QAClD6L,MAAc1L,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,KACvD2L,MAAcxL,KAAAJ,IAAA,KAAK,YAAY,aAAjB,gBAAAA,EAA2B,UAA3B,gBAAAI,EAAkC,QAAO,IAGvDyL,MAAcvL,KAAAH,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAAG,EAAmC,QAAO,GACxDwL,MAAcC,MAAA1L,IAAA,KAAK,YAAY,WAAjB,gBAAAA,EAAyB,aAAzB,gBAAA0L,GAAmC,QAAO,GACxDC,IAAcH,MAAgB,KAAOC,MAAgB,GAGrD7C,IAAY/B,EAAQ,eAAeuE,GAGnCoI,IAAa;AAAA,MACjB,GAAGpV;AAAA,MACH,GAAG,KAAK,OAAO;AAAA,IAAA,GAGX,EAAE,MAAA8P,GAAM,WAAAjJ,GAAW,WAAAC,GAAW,YAAAuO,GAAY,iBAAAC,MAAoBF,GAG9DG,IAAe,KAAK,KAAK5I,IAAamD,CAAI,GAK1C0F,IADgBhL,IADC,MAEgB,GAIjCY,IAAS2B,IAAUyI,GACnBC,IAAO5I,IAAQE,IAAUyI,GACzB7F,IAAiB8F,IAAOrK,GAGxBsK,IAAoBH,IAAe,IAAI5F,KAAkB4F,IAAe,KAAK,GAI7EI,IAAa5I,IAAUlG,IAAa2D,IAAY,GAChDoL,IAAa9I,IAASC,IAAUlG,IAAa2D,IAAY,GACzDoF,IAAkBgG,IAAaD,GAG/BE,IAAa/F,IAAO,IAAIF,KAAmBE,IAAO,KAAK;AAE7D,QAAIpF,IAAa;AAEjB,aAASoL,IAAW,GAAGA,IAAWhG,KAAQpF,IAAaiC,GAAYmJ,KAAY;AAE7E,YAAMC,KAAQjG,MAAS,KAClB6F,IAAaC,KAAc,IAC5BD,IAAcG,IAAWD;AAG7B,UAAI5O,IAAQ;AACZ,MAAIqO,MAAoB,WACtBrO,IAAQ6O,IAAWT,IACVC,MAAoB,kBAC7BrO,IAAQ6O,IAAW,KAAK;AAK1B,eAASE,IAAW,GAAGA,IAAWT,KAAgB7K,IAAaiC,GAAYqJ,KAAY;AAGrF,cAAMnL,IAAU0K,MAAiB,KAC5BnK,IAASqK,KAAQ,IAClBrK,IAAU4K,IAAWN,GAGnBO,IAAQ,KAAK,eAAepL,GAASgC,GAAOhG,GAAWC,GAAWG,CAAK,GAGvEnE,IAAI+H,GACJ9H,IAAIgT,KAAQE,GAGZxH,KAAgBlB,IAAc,KAAK,OAAOH,GAAaC,CAAW,IAAI,GACtES,KAAkBtD,IAAYiE;AAGpC,YAAItK,KAAW;AACf,QAAI8I,MAAiB,YAEnB9I,KAAW,KAAK,kBAAkB0G,GAASgC,GAAOhG,GAAWC,GAAWG,CAAK,IACpEgG,MAAiB,aAE1B9I,KAAW,KAAK,OAAO+I,GAAaC,CAAW;AAOjD,cAAMK,KAAaM,KADI,MACgC,GACjDL,KAAaK,KAAkB,GAC/BF,KAAOb,IAAUS,IACjBE,KAAOb,IAAQE,IAAUS,IACzBK,KAAOd,IAAUU,IACjBE,KAAOb,IAASC,IAAUU;AAEhC,QAAAb,EAAQ,KAAK;AAAA,UACX,IAAIlC;AAAA,UACJ,GAAG,KAAK,IAAIkD,IAAM,KAAK,IAAI9K,GAAG4K,EAAI,CAAC;AAAA,UACnC,GAAG,KAAK,IAAIG,IAAM,KAAK,IAAI9K,GAAG4K,EAAI,CAAC;AAAA,UACnC,UAAAxJ;AAAA,UACA,OAAOsK;AAAA,UACP,UAAUX;AAAA,UACV,QAAQpD,IAAa;AAAA,QAAA,CACtB,GAEDA;AAAA,MACF;AAAA,IACF;AAEA,WAAOkC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,eACN9J,GACAoT,GACArP,GACAC,GACAG,GACQ;AAER,UAAMkP,IAAcrT,IAAIoT;AAGxB,WAAOrP,IAAY,KAAK,IAAIC,IAAYqP,IAAc,IAAI,KAAK,KAAKlP,CAAK;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,kBACNnE,GACAoT,GACArP,GACAC,GACAG,GACQ;AAER,UAAMkP,IAAcrT,IAAIoT,GAGlBE,IAAavP,IAAYC,IAAY,IAAI,KAAK,KAAK,KAAK,IAAIA,IAAYqP,IAAc,IAAI,KAAK,KAAKlP,CAAK,IAAIiP;AAGnH,WAAO,KAAK,KAAKE,CAAU,KAAK,MAAM,KAAK;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,OAAOpI,GAAaC,GAAqB;AAC/C,WAAO,KAAK,OAAA,KAAYA,IAAMD,KAAOA;AAAA,EACvC;AACF;ACpMO,MAAMqI,GAAa;AAAA,EAMxB,YAAY1T,GAA4B;AACtC,SAAK,SAASA,EAAO,QACrB,KAAK,cAAcA,EAAO,OAE1B,KAAK,8BAAc,IAAA,GAGnB,KAAK,YAAY,KAAK,cAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAoC;AAC1C,YAAQ,KAAK,OAAO,WAAA;AAAA,MAClB,KAAK;AACH,eAAO,IAAIuL,GAAyB,KAAK,QAAQ,KAAK,WAAW;AAAA,MACnE,KAAK;AACH,eAAO,IAAIuB,GAAuB,KAAK,QAAQ,KAAK,WAAW;AAAA,MACjE,KAAK;AACH,eAAO,IAAI4C,GAAyB,KAAK,QAAQ,KAAK,WAAW;AAAA,MACnE,KAAK;AACH,eAAO,IAAIoB,GAA0B,KAAK,QAAQ,KAAK,WAAW;AAAA,MACpE,KAAK;AACH,eAAO,IAAI0B,GAAuB,KAAK,QAAQ,KAAK,WAAW;AAAA,MACjE,KAAK;AAAA,MACL;AACE,eAAO,IAAI1I,GAAyB,KAAK,QAAQ,KAAK,WAAW;AAAA,IAAA;AAAA,EAEvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,eAAeE,GAAoBlC,GAAkChC,IAAiC,CAAA,GAAmB;AACvH,UAAMmE,IAAU,KAAK,UAAU,SAASD,GAAYlC,GAAiBhC,CAAO;AAG5E,WAAAmE,EAAQ,QAAQ,CAAAmB,MAAU;AACxB,WAAK,QAAQ,IAAIA,EAAO,IAAIA,CAAM;AAAA,IACpC,CAAC,GAEMnB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB0J,GAAmD;AAClE,WAAO,KAAK,QAAQ,IAAI,OAAOA,CAAO,CAAC;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,QAAQ,MAAA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAaC,GAA8C;AAEzD,IAAIA,EAAU,WACZ,OAAO,OAAO,KAAK,QAAQA,EAAU,MAAM,GAGvCA,EAAU,OAAO,aAAaA,EAAU,OAAO,cAAc,KAAK,OAAO,cAC3E,KAAK,YAAY,KAAK,cAAA,KAKtBA,EAAU,SACZ,OAAO,OAAO,KAAK,aAAaA,EAAU,KAAK;AAAA,EAEnD;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAwC;AAC9C,WAAO,KAAK,OAAO,cAAc;AAAA,MAC/B,QAAQ,EAAE,UAAU,IAAA;AAAA,MACpB,QAAQ,EAAE,UAAU,KAAA;AAAA,IAAK;AAAA,EAE7B;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkBC,GAAuD;AACvE,UAAMC,IAAc,KAAK,eAAA;AAEzB,WAAID,KAAiBC,EAAY,OAAO,WAC/B,WAELD,KAAiBC,EAAY,OAAO,WAC/B,WAEF;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkBD,GAA2C;AAC3D,UAAME,IAAS,KAAK,YAAY;AAGhC,QAAI,CAACA,KAAUA,EAAO,SAAS;AAC7B;AAIF,UAAM5J,IAAS4J,EAAO;AAEtB,QAAI5J,MAAW;AACb;AAGF,QAAI,OAAOA,KAAW;AACpB,aAAOA;AAIT,UAAM6J,IAAmB7J,GACnB8J,IAAa,KAAK,kBAAkBJ,CAAa;AAGvD,WAAII,MAAe,WACVD,EAAiB,UAAUA,EAAiB,UAAUA,EAAiB,SAE5EC,MAAe,WACVD,EAAiB,UAAUA,EAAiB,UAAUA,EAAiB,SAGzEA,EAAiB,UAAUA,EAAiB,UAAUA,EAAiB;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,sBACElM,GACAkC,GACAkK,GACAL,GACsB;AACtB,UAAME,IAAS,KAAK,YAAY,QAG1BI,IAAiB,KAAK,kBAAkBN,CAAa;AAI3D,QAAIM,MAAmB;AACrB,aAAO,EAAE,QAAQA,EAAA;AAInB,UAAMC,KAAUL,KAAA,gBAAAA,EAAQ,YAAW,IAC7BM,KAAUN,KAAA,gBAAAA,EAAQ,YAAW,KAC7BO,IAAiB,KAAK,OAAO,kBAAkB,KAC/CC,IAAgB,KAAK,OAAO,iBAAiB,GAE7C,EAAE,OAAArK,GAAO,QAAAC,EAAA,IAAWrC,GAKpB0M,IAFgBtK,IAAQC,IACKmK,IACDtK;AAIlC,QAAIyK,IAAmB,KAAK,KAAKD,IADb,GACuC;AAG3D,IAAAC,KAAoBF,GAGpBE,IAAmB,KAAK,IAAIA,GAAkBP,CAAS;AAGvD,QAAIQ,IAAc,KAAK,MAAMD,GAAkBL,GAASC,CAAO;AAG/D,QAAIK,MAAgBN,KAAWK,IAAmBL,GAAS;AAEzD,YAAMO,IAAQ,KAAK,IAAIP,IAAU,MAAM,EAAE;AACzC,MAAAM,IAAc,KAAK,IAAIC,GAAOF,CAAgB;AAAA,IAChD;AAEA,WAAO,EAAE,QAAQC,EAAA;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKQ,MAAMnC,GAAelH,GAAaC,GAAqB;AAC7D,WAAO,KAAK,IAAID,GAAK,KAAK,IAAIC,GAAKiH,CAAK,CAAC;AAAA,EAC3C;AACF;ACgeO,IAAKqC,sBAAAA,OACVA,EAAA,OAAO,QACPA,EAAA,WAAW,YACXA,EAAA,UAAU,WACVA,EAAA,aAAa,cACbA,EAAA,kBAAkB,mBALRA,IAAAA,KAAA,CAAA,CAAA;AC9sBZ,SAASC,GAAetC,GAAsC;AAC5D,SAAOA,KAAS1V;AAClB;AAMO,SAASiY,GAAcC,GAAmD;AAC/E,SAAKA,IACDF,GAAeE,CAAM,IAAUlY,GAAekY,CAAM,IACjDA,IAFalY,GAAe;AAGrC;AAKO,SAASmY,GAAkBC,GAA0C;AAC1E,MAAI,CAACA,EAAQ,QAAO;AAEpB,QAAMC,IAAkB,CAAA;AA6BxB,MA3BID,EAAO,cAAc,UACvBC,EAAM,KAAK,aAAaD,EAAO,SAAS,GAAG,GAEzCA,EAAO,SAAS,UAClBC,EAAM,KAAK,QAAQD,EAAO,IAAI,KAAK,GAEjCA,EAAO,eAAe,UACxBC,EAAM,KAAK,cAAcD,EAAO,UAAU,GAAG,GAE3CA,EAAO,aAAa,UACtBC,EAAM,KAAK,YAAYD,EAAO,QAAQ,GAAG,GAEvCA,EAAO,aAAa,UACtBC,EAAM,KAAK,YAAYD,EAAO,QAAQ,GAAG,GAEvCA,EAAO,YAAY,UACrBC,EAAM,KAAK,WAAWD,EAAO,OAAO,GAAG,GAErCA,EAAO,UAAU,UACnBC,EAAM,KAAK,SAASD,EAAO,KAAK,GAAG,GAEjCA,EAAO,cAAc,UACvBC,EAAM,KAAK,cAAcD,EAAO,SAAS,MAAM,GAE7CA,EAAO,WAAW,UACpBC,EAAM,KAAK,UAAUD,EAAO,MAAM,GAAG,GAEnCA,EAAO,eAAe;AACxB,QAAI,OAAOA,EAAO,cAAe;AAC/B,MAAAC,EAAM,KAAK,eAAeD,EAAO,UAAU,GAAG;AAAA,SACzC;AACL,YAAME,IAAKF,EAAO;AAClB,MAAAC,EAAM,KAAK,eAAeC,EAAG,CAAC,MAAMA,EAAG,CAAC,MAAMA,EAAG,IAAI,MAAMA,EAAG,KAAK,GAAG;AAAA,IACxE;AAGF,SAAOD,EAAM,KAAK,GAAG;AACvB;AAKA,SAASE,GAAkBpV,GAA0C;AACnE,MAAI,CAACA,KAAUA,EAAO,UAAU,UAAUA,EAAO,UAAU;AACzD,WAAO;AAET,QAAMkK,IAAQlK,EAAO,SAAS,GACxBqV,IAAQrV,EAAO,SAAS,SACxBsV,IAAQtV,EAAO,SAAS;AAC9B,SAAO,GAAGkK,CAAK,MAAMmL,CAAK,IAAIC,CAAK;AACrC;AA6BO,SAASC,GAAqBC,GAAqD;;AACxF,MAAI,CAACA,EAAO,QAAO,CAAA;AAEnB,QAAMC,IAA0B,CAAA;AAQhC,MAL2BD,EAAM,wBAAwB,UAC9BA,EAAM,yBAAyB,UAC/BA,EAAM,4BAA4B,UAClCA,EAAM,2BAA2B,QAEpC;AAEtB,UAAME,MAAahX,IAAA8W,EAAM,WAAN,gBAAA9W,EAAc,WAAU;AAC3C,IAAI8W,EAAM,wBAAwB,SAChCC,EAAO,sBAAsB,GAAGD,EAAM,mBAAmB,OAChDE,MACTD,EAAO,sBAAsB,GAAGC,CAAU,OAExCF,EAAM,yBAAyB,SACjCC,EAAO,uBAAuB,GAAGD,EAAM,oBAAoB,OAClDE,MACTD,EAAO,uBAAuB,GAAGC,CAAU,OAEzCF,EAAM,4BAA4B,SACpCC,EAAO,0BAA0B,GAAGD,EAAM,uBAAuB,OACxDE,MACTD,EAAO,0BAA0B,GAAGC,CAAU,OAE5CF,EAAM,2BAA2B,SACnCC,EAAO,yBAAyB,GAAGD,EAAM,sBAAsB,OACtDE,MACTD,EAAO,yBAAyB,GAAGC,CAAU;AAAA,EAEjD,MAAA,GAAWjX,IAAA+W,EAAM,WAAN,gBAAA/W,EAAc,YAAW,WAClCgX,EAAO,eAAe,GAAGD,EAAM,OAAO,MAAM;AAM9C,MAFyBA,EAAM,aAAaA,EAAM,eAAeA,EAAM,gBAAgBA,EAAM,YAEvE;AAEpB,UAAMG,IAAaH,EAAM,UAAU,CAAA,GAE7BI,IAAY,EAAE,GAAGD,GAAY,GAAGH,EAAM,UAAA,GACtCK,IAAc,EAAE,GAAGF,GAAY,GAAGH,EAAM,YAAA,GACxCM,IAAe,EAAE,GAAGH,GAAY,GAAGH,EAAM,aAAA,GACzCO,IAAa,EAAE,GAAGJ,GAAY,GAAGH,EAAM,WAAA;AAE7C,IAAAC,EAAO,YAAYL,GAAkBQ,CAAS,GAC9CH,EAAO,cAAcL,GAAkBS,CAAW,GAClDJ,EAAO,eAAeL,GAAkBU,CAAY,GACpDL,EAAO,aAAaL,GAAkBW,CAAU;AAAA,EAClD,MAAA,CAAWP,EAAM,WAEfC,EAAO,SAASL,GAAkBI,EAAM,MAAM;AAIhD,EAAIA,EAAM,WAAW,WACnBC,EAAO,YAAYX,GAAcU,EAAM,MAAM;AAI/C,QAAMQ,IAAYhB,GAAkBQ,EAAM,MAAM;AAchD,MAbAC,EAAO,SAASO,KAAa,QAGzBR,EAAM,YAAY,WACpBC,EAAO,UAAU,OAAOD,EAAM,OAAO,IAInCA,EAAM,WAAW,WACnBC,EAAO,SAASD,EAAM,SAIpBA,EAAM,WAAWA,EAAM,QAAQ,UAAU,WAAWA,EAAM,QAAQ,SAAS,KAAK,GAAG;AACrF,UAAMtL,IAAQsL,EAAM,QAAQ,SAAS,GAC/BH,IAAQG,EAAM,QAAQ,SAAS,SAC/BF,IAAQE,EAAM,QAAQ,SAAS;AACrC,IAAAC,EAAO,UAAU,GAAGvL,CAAK,MAAMmL,CAAK,IAAIC,CAAK,IACzCE,EAAM,QAAQ,WAAW,WAC3BC,EAAO,gBAAgB,GAAGD,EAAM,QAAQ,MAAM;AAAA,EAElD;AAGA,SAAIA,EAAM,cAAc,WACtBC,EAAO,YAAYD,EAAM,YAIvBA,EAAM,gBAAgB,WACxBC,EAAO,cAAcD,EAAM,cAGtBC;AACT;AAKO,SAASQ,GAAqB5V,GAAsBoV,GAA+B;AACxF,EAAIA,EAAO,iBAAiB,WAAWpV,EAAQ,MAAM,eAAeoV,EAAO,eACvEA,EAAO,wBAAwB,WAAWpV,EAAQ,MAAM,sBAAsBoV,EAAO,sBACrFA,EAAO,yBAAyB,WAAWpV,EAAQ,MAAM,uBAAuBoV,EAAO,uBACvFA,EAAO,4BAA4B,WAAWpV,EAAQ,MAAM,0BAA0BoV,EAAO,0BAC7FA,EAAO,2BAA2B,WAAWpV,EAAQ,MAAM,yBAAyBoV,EAAO,yBAC3FA,EAAO,WAAW,WAAWpV,EAAQ,MAAM,SAASoV,EAAO,SAC3DA,EAAO,cAAc,WAAWpV,EAAQ,MAAM,YAAYoV,EAAO,YACjEA,EAAO,gBAAgB,WAAWpV,EAAQ,MAAM,cAAcoV,EAAO,cACrEA,EAAO,iBAAiB,WAAWpV,EAAQ,MAAM,eAAeoV,EAAO,eACvEA,EAAO,eAAe,WAAWpV,EAAQ,MAAM,aAAaoV,EAAO,aACnEA,EAAO,cAAc,WAAWpV,EAAQ,MAAM,YAAYoV,EAAO,YACjEA,EAAO,WAAW,WAAWpV,EAAQ,MAAM,SAASoV,EAAO,SAC3DA,EAAO,YAAY,WAAWpV,EAAQ,MAAM,UAAUoV,EAAO,UAC7DA,EAAO,WAAW,WAAWpV,EAAQ,MAAM,SAASoV,EAAO,SAC3DA,EAAO,YAAY,WAAWpV,EAAQ,MAAM,UAAUoV,EAAO,UAC7DA,EAAO,kBAAkB,WAAWpV,EAAQ,MAAM,gBAAgBoV,EAAO,gBACzEA,EAAO,cAAc,WAAWpV,EAAQ,MAAM,YAAYoV,EAAO,YACjEA,EAAO,gBAAgB,WAAWpV,EAAQ,MAAM,cAAcoV,EAAO;AAC3E;AAKO,SAASS,GAAiBC,GAAkD;AACjF,SAAKA,IACD,MAAM,QAAQA,CAAS,IAAUA,EAAU,KAAK,GAAG,IAChDA,IAFgB;AAGzB;AAKO,SAASC,GAAwB/V,GAAsB8V,GAAgD;AAC5G,QAAME,IAAWH,GAAiBC,CAAS;AAC3C,EAAIE,KACFA,EAAS,MAAM,GAAG,EAAE,QAAQ,CAAAC,MAAO;AACjC,IAAIA,EAAI,UAAQjW,EAAQ,UAAU,IAAIiW,EAAI,MAAM;AAAA,EAClD,CAAC;AAEL;AAKO,SAASC,GAA2BlW,GAAsB8V,GAAgD;AAC/G,QAAME,IAAWH,GAAiBC,CAAS;AAC3C,EAAIE,KACFA,EAAS,MAAM,GAAG,EAAE,QAAQ,CAAAC,MAAO;AACjC,IAAIA,EAAI,UAAQjW,EAAQ,UAAU,OAAOiW,EAAI,MAAM;AAAA,EACrD,CAAC;AAEL;ACpOA,MAAME,KAAU;AAAA,EAEd,YAAY;AAAA,EACZ,UAAU;AAEZ;AAEO,MAAMC,GAAW;AAAA,EAsBtB,YAAYzW,GAAgC0W,GAAkCC,GAA8B;;AAjB5G,SAAQ,QAAmB/B,EAAU,MACrC,KAAQ,eAAmC,MAC3C,KAAQ,YAA8B,MAGtC,KAAQ,WAAkC,MAC1C,KAAQ,WAAkC,MAG1C,KAAQ,kBAA0B,GAShC,KAAK,SAAS5U,GACd,KAAK,kBAAkB0W,GAGvB,KAAK,gBAAgBnB,GAAqBoB,KAAA,gBAAAA,EAAS,OAAO,GAC1D,KAAK,gBAAgBpB,GAAqBoB,KAAA,gBAAAA,EAAS,OAAO,GAC1D,KAAK,oBAAmBjY,IAAAiY,KAAA,gBAAAA,EAAS,YAAT,gBAAAjY,EAAkB,WAC1C,KAAK,oBAAmBD,IAAAkY,KAAA,gBAAAA,EAAS,YAAT,gBAAAlY,EAAkB;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,WAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACrB,WAAO,KAAK,UAAUmW,EAAU,QAAQ,KAAK,UAAUA,EAAU;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsBrC,GAAuB;AACnD,WAAOA,IAAQ,IAAIA,IAAQ,MAAMA;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,yBAAyBrM,GAAoBC,GAAqB2B,GAAqE;AAC7I,UAAM8O,IAAoB,KAAK,sBAAsB,KAAK,OAAO,YAAY,GACvEC,IAAe/O,EAAgB,SAAS8O,GACxCrH,IAAcrJ,IAAaC;AAEjC,QAAI2Q,IAAcD,GACdE,IAAaD,IAAcvH;AAE/B,UAAMyH,IAAWlP,EAAgB,QAAQ8O;AACzC,WAAIG,IAAaC,MACfD,IAAaC,GACbF,IAAcC,IAAaxH,IAGtB,EAAE,OAAOwH,GAAY,QAAQD,EAAA;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,wBACNhP,GACAnG,GACiB;AACjB,UAAMuG,IAAUJ,EAAgB,QAAQ,GAClCK,IAAUL,EAAgB,SAAS,GAEnCmP,IAAU/O,IAAUvG,EAAc,GAClCuV,IAAU/O,IAAUxG,EAAc;AAExC,WAAO;AAAA,MACL,GAAGsV;AAAA,MACH,GAAGC;AAAA,MACH,UAAU;AAAA,MACV,OAAO;AAAA;AAAA,IAAA;AAAA,EAEX;AAAA;AAAA;AAAA;AAAA,EAKQ,4BAA4BjX,GAAiC;AACnE,UAAMC,IAAuB,CAAC,uBAAuB;AAErD,QAAID,EAAO,MAAM,UAAaA,EAAO,MAAM,QAAW;AACpD,YAAME,IAAIF,EAAO,KAAK,GAChBG,IAAIH,EAAO,KAAK;AACtB,MAAAC,EAAW,KAAK,aAAaC,CAAC,OAAOC,CAAC,KAAK;AAAA,IAC7C;AAEA,WAAIH,EAAO,aAAa,UACtBC,EAAW,KAAK,UAAUD,EAAO,QAAQ,MAAM,GAK1CC,EAAW,KAAK,GAAG;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBACNG,GACAO,GACAC,GACAsW,GACAC,GACAC,GACAC,GACA9W,GACW;AACX,UAAM+W,IAAmB,KAAK,4BAA4B3W,CAAa,GACjE4W,IAAiB,KAAK,4BAA4B3W,CAAW;AAGnE,WAAAR,EAAQ,MAAM,aAAa,QAGTA,EAAQ;AAAA,MACxB;AAAA,QACE;AAAA,UACE,WAAWkX;AAAA,UACX,OAAO,GAAGJ,CAAS;AAAA,UACnB,QAAQ,GAAGC,CAAU;AAAA,QAAA;AAAA,QAEvB;AAAA,UACE,WAAWI;AAAA,UACX,OAAO,GAAGH,CAAO;AAAA,UACjB,QAAQ,GAAGC,CAAQ;AAAA,QAAA;AAAA,MACrB;AAAA,MAEF;AAAA,QACE,UAAA9W;AAAA,QACA,QAAQ;AAAA,QACR,MAAM;AAAA,MAAA;AAAA,IACR;AAAA,EAIJ;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoBH,GAAsBgP,GAAsB;AACtE,IAAAhP,EAAQ,MAAM,SAAS,OAAOgP,CAAM,GACpChP,EAAQ,UAAU,IAAI,gBAAgB,GACtC4V,GAAqB5V,GAAS,KAAK,aAAa,GAChD+V,GAAwB/V,GAAS,KAAK,gBAAgB;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAqBA,GAAsBoX,GAA8B;AAC/E,IAAApX,EAAQ,MAAM,SAASoX,GACvBpX,EAAQ,UAAU,OAAO,gBAAgB,GACzCkW,GAA2BlW,GAAS,KAAK,gBAAgB,GACzD4V,GAAqB5V,GAAS,KAAK,aAAa,GAChD+V,GAAwB/V,GAAS,KAAK,gBAAgB;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,oBACNA,GACAyH,GACAnG,GACAf,GACA8W,GACgB;AAChB,UAAMD,IAAiBpX,EAAQ,MAAM,UAAU,IACzCsX,IAAgBtX,EAAQ,aACxBuX,IAAiBvX,EAAQ,cAGzBwX,IAAkB,KAAK,yBAAyBF,GAAeC,GAAgB9P,CAAe,GAC9FgQ,IAAiB,KAAK,wBAAwBhQ,GAAiBnG,CAAa;AAGlF,SAAK,oBAAoBtB,GAASmW,GAAQ,QAAQ,GAGlD,KAAK,gBAAgB,oBAAoBnW,CAAO;AAGhD,UAAM0X,IAAkCnX,KAAiB;AAAA,MACvD,GAAG;AAAA,MACH,GAAG;AAAA,MACH,UAAUe,EAAc;AAAA,MACxB,OAAO;AAAA;AAAA,IAAA,GAGHqW,KAAaN,KAAA,gBAAAA,EAAgB,UAASC,GACtCM,KAAcP,KAAA,gBAAAA,EAAgB,WAAUE,GAExCpX,IAAW,KAAK,OAAO,qBAAqB,KAG5CM,IAAY,KAAK;AAAA,MACrBT;AAAA,MACA0X;AAAA,MACAD;AAAA,MACAE;AAAA,MACAC;AAAA,MACAJ,EAAgB;AAAA,MAChBA,EAAgB;AAAA,MAChBrX;AAAA,IAAA,GAIIO,IAA0B;AAAA,MAC9B,IAAI,SAAS,KAAK,IAAA,CAAK;AAAA,MACvB,SAAAV;AAAA,MACA,WAAAS;AAAA,MACA,WAAWiX;AAAA,MACX,SAASD;AAAA,MACT,WAAW,YAAY,IAAA;AAAA,MACvB,UAAAtX;AAAA,IAAA;AAIF,gBAAK,YAAY;AAAA,MACf,SAAAH;AAAA,MACA,eAAAsB;AAAA,MACA,gBAAAmW;AAAA,MACA,gBAAAL;AAAA,MACA,eAAAE;AAAA,MACA,gBAAAC;AAAA,MACA,YAAYC,EAAgB;AAAA,MAC5B,aAAaA,EAAgB;AAAA,IAAA,GAGxB;AAAA,MACL,SAAAxX;AAAA,MACA,eAAAsB;AAAA,MACA,iBAAiBZ;AAAA,MACjB,WAAW;AAAA,MACX,eAAA4W;AAAA,MACA,gBAAAC;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,sBACNvX,GACAsB,GACAf,GACA8W,GACgB;;AAEhB,IAAArX,EAAQ,MAAM,SAAS,OAAOmW,GAAQ,UAAU,GAGhD,KAAK,gBAAgB,oBAAoBnW,CAAO;AAGhD,UAAM0X,IAAiBnX,OAAiBlC,IAAA,KAAK,cAAL,gBAAAA,EAAgB,mBAAkB,EAAE,GAAG,GAAG,GAAG,GAAG,UAAU,GAAG,OAAO,EAAA,GACtGsZ,KAAaN,KAAA,gBAAAA,EAAgB,YAASjZ,IAAA,KAAK,cAAL,gBAAAA,EAAgB,eAAc4B,EAAQ,aAC5E4X,KAAcP,KAAA,gBAAAA,EAAgB,aAAU7Y,IAAA,KAAK,cAAL,gBAAAA,EAAgB,gBAAewB,EAAQ,cAG/E6X,IAA2B;AAAA,MAC/B,GAAG;AAAA,MACH,GAAG;AAAA,MACH,UAAUvW,EAAc;AAAA,MACxB,OAAO;AAAA;AAAA,IAAA,GAGHwW,MAAcvZ,IAAA,KAAK,cAAL,gBAAAA,EAAgB,kBAAiByB,EAAQ,aACvDwW,MAAe7X,IAAA,KAAK,cAAL,gBAAAA,EAAgB,mBAAkBqB,EAAQ,cACzDG,IAAW,KAAK,OAAO,qBAAqB,KAG5CM,IAAY,KAAK;AAAA,MACrBT;AAAA,MACA0X;AAAA,MACAG;AAAA,MACAF;AAAA,MACAC;AAAA,MACAE;AAAA,MACAtB;AAAA,MACArW;AAAA,IAAA,GAIIO,IAA0B;AAAA,MAC9B,IAAI,WAAW,KAAK,IAAA,CAAK;AAAA,MACzB,SAAAV;AAAA,MACA,WAAAS;AAAA,MACA,WAAWiX;AAAA,MACX,SAAAG;AAAA,MACA,WAAW,YAAY,IAAA;AAAA,MACvB,UAAA1X;AAAA,IAAA;AAGF,WAAO;AAAA,MACL,SAAAH;AAAA,MACA,eAAAsB;AAAA,MACA,iBAAiBZ;AAAA,MACjB,WAAW;AAAA,MACX,eAAeoX;AAAA,MACf,gBAAgBtB;AAAA,IAAA;AAAA,EAEpB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAAiB9V,GAAwC;AACrE,QAAI;AACF,YAAMA,EAAO,UAAU;AAAA,IACzB,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBACNV,GACAsB,GACA8V,GACAE,GACAC,GACM;AAEN,SAAK,gBAAgB,oBAAoBvX,CAAO;AAGhD,UAAMH,IAAa,CAAC,uBAAuB;AAC3C,IAAAA,EAAW,KAAK,qBAAqB,GACrCA,EAAW,KAAK,UAAUyB,EAAc,QAAQ,MAAM,GAGtDtB,EAAQ,MAAM,aAAa,QAC3BA,EAAQ,MAAM,YAAYH,EAAW,KAAK,GAAG,GAGzCyX,MAAkB,UAAaC,MAAmB,WACpDvX,EAAQ,MAAM,QAAQ,GAAGsX,CAAa,MACtCtX,EAAQ,MAAM,SAAS,GAAGuX,CAAc,OAI1C,KAAK,qBAAqBvX,GAASoX,CAAc;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WACJW,GACAtQ,GACAnG,GACe;;AAEf,QAAI,KAAK,iBAAiByW,KAAgB,KAAK,UAAUxD,EAAU;AACjE,aAAO,KAAK,aAAA;AAId,UAAIlW,IAAA,KAAK,aAAL,gBAAAA,EAAe,aAAY0Z,KAAgB,KAAK,UAAUxD,EAAU,UAAU;AAEhF,YAAM3T,IAAW,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAI,GACnFL,IAAiC;AAAA,QACrC,GAAGK,EAAS;AAAA,QACZ,GAAGA,EAAS;AAAA,QACZ,UAAUA,EAAS;AAAA,QACnB,OAAO;AAAA;AAAA,MAAA,GAEHyW,IAAiB;AAAA,QACrB,OAAOU,EAAa;AAAA,QACpB,QAAQA,EAAa;AAAA,MAAA;AAGvB,WAAK,WAAW,KAAK;AAAA,QACnBA;AAAA,QACA,KAAK,SAAS;AAAA,QACdxX;AAAA,QACA8W;AAAA,MAAA,GAEF,KAAK,WAAW,MAChB,KAAK,QAAQ9C,EAAU,YAEvB,MAAM,KAAK,iBAAiB,KAAK,SAAS,eAAe,GACzD,KAAK,qBAAqB,KAAK,SAAS,WAASnW,IAAA,KAAK,cAAL,gBAAAA,EAAgB,mBAAkB,EAAE,GACrF,KAAK,WAAW,MAChB,KAAK,eAAe,MACpB,KAAK,YAAY,MACjB,KAAK,QAAQmW,EAAU;AACvB;AAAA,IACF;AAGA,UAAMyD,IAAe,EAAE,KAAK;AAE5B,YAAQ,KAAK,OAAA;AAAA,MACX,KAAKzD,EAAU;AAQb,YANA,KAAK,QAAQA,EAAU,UACvB,KAAK,WAAW,KAAK,oBAAoBwD,GAActQ,GAAiBnG,CAAa,GAErF,MAAM,KAAK,iBAAiB,KAAK,SAAS,eAAe,GAGrD,KAAK,oBAAoB0W,EAAc;AAE3C,aAAK,eAAeD,GACpB,KAAK,WAAW,MAChB,KAAK,QAAQxD,EAAU;AACvB;AAAA,MAEF,KAAKA,EAAU;AAsBb,YApBA,KAAK,QAAQA,EAAU,iBAGnB,KAAK,gBAAgB,KAAK,cAC5B,KAAK,WAAW,KAAK;AAAA,UACnB,KAAK;AAAA,UACL,KAAK,UAAU;AAAA,QAAA,IAKnB,KAAK,WAAW,KAAK,oBAAoBwD,GAActQ,GAAiBnG,CAAa,GAGrF,MAAM,QAAQ,IAAI;AAAA,UAChB,KAAK,WAAW,KAAK,iBAAiB,KAAK,SAAS,eAAe,IAAI,QAAQ,QAAA;AAAA,UAC/E,KAAK,iBAAiB,KAAK,SAAS,eAAe;AAAA,QAAA,CACpD,GAGG,KAAK,oBAAoB0W;AAC3B;AAIF,QAAI,KAAK,aACP,KAAK,qBAAqB,KAAK,SAAS,WAASxZ,IAAA,KAAK,SAAS,cAAc,WAA5B,gBAAAA,EAAoC,eAAc,EAAE,GACrG,KAAK,WAAW,OAGlB,KAAK,eAAeuZ,GACpB,KAAK,WAAW,MAChB,KAAK,QAAQxD,EAAU;AACvB;AAAA,MAEF,KAAKA,EAAU;AAqBb,YAlBI,KAAK,aACP,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAK,GACzE,KAAK;AAAA,UACH,KAAK,SAAS;AAAA,UACd,KAAK,SAAS;AAAA,YACdhW,IAAA,KAAK,cAAL,gBAAAA,EAAgB,mBAAkB;AAAA,WAClCI,IAAA,KAAK,cAAL,gBAAAA,EAAgB;AAAA,WAChBD,IAAA,KAAK,cAAL,gBAAAA,EAAgB;AAAA,QAAA,GAElB,KAAK,WAAW,OAIlB,KAAK,WAAW,KAAK,oBAAoBqZ,GAActQ,GAAiBnG,CAAa,GAErF,MAAM,KAAK,iBAAiB,KAAK,SAAS,eAAe,GAGrD,KAAK,oBAAoB0W,EAAc;AAE3C,aAAK,eAAeD,GACpB,KAAK,WAAW,MAChB,KAAK,QAAQxD,EAAU;AACvB;AAAA,MAEF,KAAKA,EAAU;AAYb,YATA,KAAK,QAAQA,EAAU,iBACvB,KAAK,WAAW,KAAK,oBAAoBwD,GAActQ,GAAiBnG,CAAa,GAErF,MAAM,QAAQ,IAAI;AAAA,UAChB,KAAK,WAAW,KAAK,iBAAiB,KAAK,SAAS,eAAe,IAAI,QAAQ,QAAA;AAAA,UAC/E,KAAK,iBAAiB,KAAK,SAAS,eAAe;AAAA,QAAA,CACpD,GAGG,KAAK,oBAAoB0W,EAAc;AAE3C,QAAI,KAAK,aACP,KAAK,qBAAqB,KAAK,SAAS,WAASnZ,IAAA,KAAK,SAAS,cAAc,WAA5B,gBAAAA,EAAoC,eAAc,EAAE,GACrG,KAAK,WAAW,OAGlB,KAAK,eAAekZ,GACpB,KAAK,WAAW,MAChB,KAAK,QAAQxD,EAAU;AACvB;AAAA,MAEF,KAAKA,EAAU;AAIb,cAAI3V,IAAA,KAAK,aAAL,gBAAAA,EAAe,aAAYmZ;AAC7B;AAKF,cAAIzN,IAAA,KAAK,aAAL,gBAAAA,EAAe,aAAYyN,GAAc;AAE3C,gBAAMnX,IAAW,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAI,GACnFL,IAAiC;AAAA,YACrC,GAAGK,EAAS;AAAA,YACZ,GAAGA,EAAS;AAAA,YACZ,UAAUA,EAAS;AAAA,YACnB,OAAO;AAAA;AAAA,UAAA,GAEHyW,IAAiB;AAAA,YACrB,OAAOU,EAAa;AAAA,YACpB,QAAQA,EAAa;AAAA,UAAA;AAIvB,cAAI,KAAK,UAAU;AACjB,kBAAME,IAAmB,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAI,GAC3FC,IAAgC;AAAA,cACpC,GAAGD,EAAiB;AAAA,cACpB,GAAGA,EAAiB;AAAA,cACpB,UAAUA,EAAiB;AAAA,cAC3B,OAAO;AAAA;AAAA,YAAA,GAEHE,IAAyB;AAAA,cAC7B,OAAO,KAAK,SAAS,QAAQ;AAAA,cAC7B,QAAQ,KAAK,SAAS,QAAQ;AAAA,YAAA;AAEhC,iBAAK,WAAW,KAAK;AAAA,cACnB,KAAK,SAAS;AAAA,cACd,KAAK,SAAS;AAAA,cACdD;AAAA,cACAC;AAAA,YAAA;AAAA,UAEJ;AACE,iBAAK,WAAW;AAYlB,cAPA,KAAK,WAAW,KAAK,oBAAoBJ,GAActQ,GAAiBnG,GAAef,GAAe8W,CAAc,GAEpH,MAAM,QAAQ,IAAI;AAAA,YAChB,KAAK,WAAW,KAAK,iBAAiB,KAAK,SAAS,eAAe,IAAI,QAAQ,QAAA;AAAA,YAC/E,KAAK,iBAAiB,KAAK,SAAS,eAAe;AAAA,UAAA,CACpD,GAEG,KAAK,oBAAoBW,EAAc;AAE3C,UAAI,KAAK,aACP,KAAK,qBAAqB,KAAK,SAAS,WAASI,IAAA,KAAK,SAAS,cAAc,WAA5B,gBAAAA,EAAoC,eAAc,EAAE,GACrG,KAAK,WAAW,OAGlB,KAAK,eAAeL,GACpB,KAAK,WAAW,MAChB,KAAK,QAAQxD,EAAU;AACvB;AAAA,QACF;AAqBA,YAbI,KAAK,aACP,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAK,GACzE,KAAK;AAAA,UACH,KAAK,SAAS;AAAA,UACd,KAAK,SAAS;AAAA,YACd8D,IAAA,KAAK,SAAS,cAAc,WAA5B,gBAAAA,EAAoC,eAAc;AAAA,UAClD,KAAK,SAAS;AAAA,UACd,KAAK,SAAS;AAAA,QAAA,GAEhB,KAAK,WAAW,OAId,KAAK,UAAU;AACjB,gBAAMzX,IAAW,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAI,GACnFL,IAAiC;AAAA,YACrC,GAAGK,EAAS;AAAA,YACZ,GAAGA,EAAS;AAAA,YACZ,UAAUA,EAAS;AAAA,YACnB,OAAO;AAAA;AAAA,UAAA,GAEHyW,IAAiB;AAAA,YACrB,OAAO,KAAK,SAAS,QAAQ;AAAA,YAC7B,QAAQ,KAAK,SAAS,QAAQ;AAAA,UAAA;AAGhC,eAAK,WAAW,KAAK;AAAA,YACnB,KAAK,SAAS;AAAA,YACd,KAAK,SAAS;AAAA,YACd9W;AAAA,YACA8W;AAAA,UAAA;AAAA,QAEJ;AAYA,YATA,KAAK,WAAW,KAAK,oBAAoBU,GAActQ,GAAiBnG,CAAa,GAGrF,MAAM,QAAQ,IAAI;AAAA,UAChB,KAAK,WAAW,KAAK,iBAAiB,KAAK,SAAS,eAAe,IAAI,QAAQ,QAAA;AAAA,UAC/E,KAAK,iBAAiB,KAAK,SAAS,eAAe;AAAA,QAAA,CACpD,GAGG,KAAK,oBAAoB0W,EAAc;AAE3C,QAAI,KAAK,aACP,KAAK,qBAAqB,KAAK,SAAS,WAASM,IAAA,KAAK,SAAS,cAAc,WAA5B,gBAAAA,EAAoC,eAAc,EAAE,GACrG,KAAK,WAAW,OAGlB,KAAK,eAAeP,GACpB,KAAK,WAAW,MAChB,KAAK,QAAQxD,EAAU;AACvB;AAAA,IAAA;AAAA,EAEN;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAA8B;;AAElC,UAAMyD,IAAe,EAAE,KAAK;AAE5B,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,WAAW;AAEzC,UAAI,KAAK,YAAY,KAAK,UAAUzD,EAAU,UAAU;AACtD,cAAM3T,IAAW,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAI,GACnFL,IAAiC;AAAA,UACrC,GAAGK,EAAS;AAAA,UACZ,GAAGA,EAAS;AAAA,UACZ,UAAUA,EAAS;AAAA,UACnB,OAAO;AAAA;AAAA,QAAA,GAEHyW,IAAiB;AAAA,UACrB,OAAO,KAAK,SAAS,QAAQ;AAAA,UAC7B,QAAQ,KAAK,SAAS,QAAQ;AAAA,QAAA;AAchC,YAXA,KAAK,WAAW,KAAK;AAAA,UACnB,KAAK,SAAS;AAAA,UACd,KAAK,SAAS;AAAA,UACd9W;AAAA,UACA8W;AAAA,QAAA,GAEF,KAAK,WAAW,MAChB,KAAK,QAAQ9C,EAAU,YAEvB,MAAM,KAAK,iBAAiB,KAAK,SAAS,eAAe,GAErD,KAAK,oBAAoByD,EAAc;AAE3C,aAAK,qBAAqB,KAAK,SAAS,WAAS3Z,IAAA,KAAK,cAAL,gBAAAA,EAAgB,mBAAkB,EAAE,GACrF,KAAK,WAAW,MAChB,KAAK,YAAY,MACjB,KAAK,QAAQkW,EAAU;AAAA,MACzB;AACA;AAAA,IACF;AAGA,QAAI,KAAK,UAAUA,EAAU,mBAEvB,KAAK,UAAU;AACjB,YAAM3T,IAAW,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAI,GACnFL,IAAiC;AAAA,QACrC,GAAGK,EAAS;AAAA,QACZ,GAAGA,EAAS;AAAA,QACZ,UAAUA,EAAS;AAAA,QACnB,OAAO;AAAA;AAAA,MAAA,GAEHyW,IAAiB;AAAA,QACrB,OAAO,KAAK,SAAS,QAAQ;AAAA,QAC7B,QAAQ,KAAK,SAAS,QAAQ;AAAA,MAAA,GAI1BkB,IAAkB,KAAK;AAAA,QAC3B,KAAK,SAAS;AAAA,QACd,KAAK,SAAS;AAAA,QACdhY;AAAA,QACA8W;AAAA,MAAA;AASF,UALA,MAAM,QAAQ,IAAI;AAAA,QAChB,KAAK,WAAW,KAAK,iBAAiB,KAAK,SAAS,eAAe,IAAI,QAAQ,QAAA;AAAA,QAC/E,KAAK,iBAAiBkB,EAAgB,eAAe;AAAA,MAAA,CACtD,GAEG,KAAK,oBAAoBP,EAAc;AAG3C,MAAI,KAAK,YACP,KAAK,qBAAqB,KAAK,SAAS,WAAS5Z,IAAA,KAAK,SAAS,cAAc,WAA5B,gBAAAA,EAAoC,eAAc,EAAE,GAEvG,KAAK,qBAAqBma,EAAgB,WAAS/Z,IAAA,KAAK,SAAS,cAAc,WAA5B,gBAAAA,EAAoC,eAAc,EAAE,GAEvG,KAAK,WAAW,MAChB,KAAK,WAAW,MAChB,KAAK,eAAe,MACpB,KAAK,YAAY,MACjB,KAAK,QAAQ+V,EAAU;AACvB;AAAA,IACF;AAIF,SAAK,QAAQA,EAAU;AACvB,UAAMvU,IAAU,KAAK,cACfsB,IAAgB,KAAK,UAAU,eAC/B8V,IAAiB,KAAK,UAAU;AAMtC,IAJA,KAAK,WAAW,KAAK,sBAAsBpX,GAASsB,CAAa,GAEjE,MAAM,KAAK,iBAAiB,KAAK,SAAS,eAAe,GAErD,KAAK,oBAAoB0W,MAE7B,KAAK,qBAAqBhY,GAASoX,CAAc,GACjD,KAAK,WAAW,MAChB,KAAK,eAAe,MACpB,KAAK,YAAY,MACjB,KAAK,QAAQ7C,EAAU;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UACJiE,GACA/Q,GACAnG,GACe;AACf,WAAO,KAAK,WAAWkX,GAAiB/Q,GAAiBnG,CAAa;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAsC;AACpC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAUyW,GAAoC;AAC5C,WAAO,KAAK,iBAAiBA,KAAgB,KAAK,UAAUxD,EAAU;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiBwD,GAAoC;;AACnD,aAAO1Z,IAAA,KAAK,aAAL,gBAAAA,EAAe,aAAY0Z;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAWA,GAAoC;;AAC7C,WACE,KAAK,iBAAiBA,OACtB1Z,IAAA,KAAK,aAAL,gBAAAA,EAAe,aAAY0Z,OAC3B3Z,IAAA,KAAK,aAAL,gBAAAA,EAAe,aAAY2Z;AAAA,EAE/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAcnQ,GAAsB;AAClC,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,aAAa,KAAK,UAAU2M,EAAU,QAAS;AAE/E,UAAMvU,IAAU,KAAK,cACfyX,IAAiB,KAAK,UAAU,gBAGhC5X,IAAuB,CAAC,uBAAuB,GAC/CC,KAAK2X,EAAe,KAAK,KAAK7P,GAC9B7H,IAAI0X,EAAe,KAAK;AAC9B,IAAA5X,EAAW,KAAK,aAAaC,CAAC,OAAOC,CAAC,KAAK,GACvC0X,EAAe,aAAa,UAC9B5X,EAAW,KAAK,UAAU4X,EAAe,QAAQ,MAAM,GAGzDzX,EAAQ,MAAM,aAAa,QAC3BA,EAAQ,MAAM,YAAYH,EAAW,KAAK,GAAG;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB4Y,GAAkBtY,IAAmB,KAAW;AAC9D,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,aAAa,KAAK,UAAUoU,EAAU,QAAS;AAE/E,UAAMvU,IAAU,KAAK,cACfyX,IAAiB,KAAK,UAAU,gBAGhC5X,IAAuB,CAAC,uBAAuB,GAC/CC,IAAI2X,EAAe,KAAK,GACxB1X,IAAI0X,EAAe,KAAK;AAC9B,IAAA5X,EAAW,KAAK,aAAaC,CAAC,OAAOC,CAAC,KAAK,GACvC0X,EAAe,aAAa,UAC9B5X,EAAW,KAAK,UAAU4X,EAAe,QAAQ,MAAM;AAEzD,UAAMiB,IAAoB7Y,EAAW,KAAK,GAAG;AAE7C,IAAI4Y,KACFzY,EAAQ,MAAM,aAAa,aAAaG,CAAQ,eAChDH,EAAQ,MAAM,YAAY0Y,GAE1B,WAAW,MAAM;AACf,MAAI,KAAK,iBAAiB1Y,MACxBA,EAAQ,MAAM,aAAa;AAAA,IAE/B,GAAGG,CAAQ,MAEXH,EAAQ,MAAM,aAAa,QAC3BA,EAAQ,MAAM,YAAY0Y;AAAA,EAE9B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;;AAEZ,IAAI,KAAK,aACP,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAK,GACzE,KAAK;AAAA,MACH,KAAK,SAAS;AAAA,MACd,KAAK,SAAS;AAAA,QACdra,IAAA,KAAK,SAAS,cAAc,WAA5B,gBAAAA,EAAoC,eAAc;AAAA,MAClD,KAAK,SAAS;AAAA,MACd,KAAK,SAAS;AAAA,IAAA,IAId,KAAK,aACP,KAAK,gBAAgB,gBAAgB,KAAK,SAAS,iBAAiB,EAAK,GACzE,KAAK;AAAA,MACH,KAAK,SAAS;AAAA,MACd,KAAK,SAAS;AAAA,QACdD,IAAA,KAAK,cAAL,gBAAAA,EAAgB,mBAAkB;AAAA,OAClCI,IAAA,KAAK,cAAL,gBAAAA,EAAgB;AAAA,OAChBD,IAAA,KAAK,cAAL,gBAAAA,EAAgB;AAAA,IAAA,IAIhB,KAAK,gBAAgB,KAAK,aAC5B,KAAK;AAAA,MACH,KAAK;AAAA,MACL,KAAK,UAAU;AAAA,MACf,KAAK,UAAU;AAAA,MACf,KAAK,UAAU;AAAA,MACf,KAAK,UAAU;AAAA,IAAA,GAInB,KAAK,QAAQgW,EAAU,MACvB,KAAK,eAAe,MACpB,KAAK,YAAY,MACjB,KAAK,WAAW,MAChB,KAAK,WAAW;AAAA,EAClB;AACF;AC77BA,MAAMoE,KAAqB,IACrBC,KAA2B,KAC3BC,KAAkC,IAClCC,KAAe,KACfC,KAAwB,KACxBC,KAAiC,IAkB1BC,KAAN,MAAMA,GAAY;AAAA,EAgBvB,YAAYC,GAAwBC,GAA2B;AAb/D,SAAQ,UAAmB,IAC3B,KAAQ,aAAgC,MAGxC,KAAQ,uBAA+B,GAUrC,KAAK,YAAYD,GACjB,KAAK,YAAYC,GAGjB,KAAK,kBAAkB,KAAK,iBAAiB,KAAK,IAAI,GACtD,KAAK,iBAAiB,KAAK,gBAAgB,KAAK,IAAI,GACpD,KAAK,gBAAgB,KAAK,eAAe,KAAK,IAAI,GAClD,KAAK,mBAAmB,KAAK,kBAAkB,KAAK,IAAI;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACb,IAAI,KAAK,YACT,KAAK,UAAU,IAEf,KAAK,UAAU,iBAAiB,cAAc,KAAK,iBAAiB,EAAE,SAAS,IAAO,GACtF,KAAK,UAAU,iBAAiB,aAAa,KAAK,gBAAgB,EAAE,SAAS,IAAO,GACpF,KAAK,UAAU,iBAAiB,YAAY,KAAK,eAAe,EAAE,SAAS,IAAM,GACjF,KAAK,UAAU,iBAAiB,eAAe,KAAK,kBAAkB,EAAE,SAAS,IAAM;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;;AACd,IAAK,KAAK,YACV,KAAK,UAAU,IAEf,KAAK,UAAU,oBAAoB,cAAc,KAAK,eAAe,GACrE,KAAK,UAAU,oBAAoB,aAAa,KAAK,cAAc,GACnE,KAAK,UAAU,oBAAoB,YAAY,KAAK,aAAa,GACjE,KAAK,UAAU,oBAAoB,eAAe,KAAK,gBAAgB,IAGnE9a,IAAA,KAAK,eAAL,QAAAA,EAAiB,cACnB,KAAK,UAAU,UAAU,EAAK,GAEhC,KAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,QAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAA0B;AACxB,WAAO,KAAK,IAAA,IAAQ,KAAK,uBAAuB4a,GAAY;AAAA,EAC9D;AAAA,EAEQ,iBAAiBG,GAAqB;AAC5C,QAAIA,EAAE,QAAQ,WAAW,EAAG;AAG5B,SAAK,uBAAuB,KAAK,IAAA;AAEjC,UAAMC,IAAQD,EAAE,QAAQ,CAAC;AACzB,SAAK,aAAa;AAAA,MAChB,QAAQC,EAAM;AAAA,MACd,QAAQA,EAAM;AAAA,MACd,WAAW,YAAY,IAAA;AAAA,MACvB,UAAUA,EAAM;AAAA,MAChB,YAAY;AAAA,MACZ,mBAAmB;AAAA,IAAA;AAAA,EAEvB;AAAA,EAEQ,gBAAgBD,GAAqB;AAC3C,QAAI,CAAC,KAAK,cAAcA,EAAE,QAAQ,WAAW,EAAG;AAEhD,UAAMC,IAAQD,EAAE,QAAQ,CAAC,GACnBE,IAASD,EAAM,UAAU,KAAK,WAAW,QACzCE,IAASF,EAAM,UAAU,KAAK,WAAW;AAG/C,QAAI,KAAK,WAAW,sBAAsB,QAClB,KAAK,KAAKC,IAASA,IAASC,IAASA,CAAM,IAC7C,IAAI;AAGtB,YAAMC,IADW,KAAK,MAAM,KAAK,IAAID,CAAM,GAAG,KAAK,IAAID,CAAM,CAAC,KACjC,MAAM,KAAK;AACxC,WAAK,WAAW,oBAAoBE,KAAYR;AAAA,IAClD;AAIF,QAAI,KAAK,WAAW,sBAAsB,MAKtC,KAAK,WAAW,sBAAsB,IAAM;AAC9C,MAAAI,EAAE,eAAA,GACF,KAAK,WAAW,aAAa,IAC7B,KAAK,WAAW,WAAWC,EAAM;AAGjC,YAAMI,IAAeH,IAASR;AAC9B,WAAK,UAAU,aAAaW,CAAY;AAAA,IAC1C;AAAA,EACF;AAAA,EAEQ,eAAe9a,GAAsB;AAC3C,QAAI,CAAC,KAAK,WAAY;AAGtB,SAAK,uBAAuB,KAAK,IAAA;AAEjC,UAAM2a,IAAS,KAAK,WAAW,WAAW,KAAK,WAAW,QACpDI,IAAY,YAAY,IAAA,IAAQ,KAAK,WAAW,WAChDC,IAAW,KAAK,IAAIL,CAAM,IAAII,GAC9BE,IAAc,KAAK,IAAIN,CAAM;AAEnC,QAAIO,IAAY;AAGhB,IAAI,KAAK,WAAW,sBAAsB,MAAQ,KAAK,WAAW,eAG9DD,KAAejB,MACdgB,KAAYf,MAA4BgB,KAAef,QAGxDgB,IAAY,IACRP,IAAS,IAEX,KAAK,UAAU,OAAA,IAGf,KAAK,UAAU,OAAA,IAMjB,KAAK,WAAW,cAClB,KAAK,UAAU,UAAUO,CAAS,GAGpC,KAAK,aAAa;AAAA,EACpB;AAAA,EAEQ,kBAAkBlb,GAAsB;;AAC9C,KAAIN,IAAA,KAAK,eAAL,QAAAA,EAAiB,cACnB,KAAK,UAAU,UAAU,EAAK,GAEhC,KAAK,aAAa;AAAA,EACpB;AACF;AApKE4a,GAAwB,oBAAoB;AARvC,IAAMa,KAANb;AClBA,MAAMc,GAAyC;AAAA,EAUpD,YAAYpa,IAA2C,IAAI;AAOzD,QAVF,KAAQ,YAAqB,IAC7B,KAAQ,kBAA4B,CAAA,GAGlC,KAAK,SAASA,EAAO,UAAU,IAC/B,KAAK,cAAcA,EAAO,eAAe,6CACzC,KAAK,eAAeA,EAAO,gBAAgB,IAC3C,KAAK,UAAUA,EAAO,WAAW,CAAA,GAG7B,CAAC,KAAK,WAAW,KAAK,QAAQ,WAAW;AAC3C,YAAM,IAAI,MAAM,iEAAiE;AAAA,EAErF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQiV,GAAqC;AACjD,SAAK,kBAAkB,CAAA;AAEvB,eAAWoF,KAAU,KAAK;AACxB,UAAIA,EAAO,SAAS;AAClB,mBAAWC,KAAaD,EAAO,SAAS;AACtC,gBAAME,IAAYF,EAAO,cAAc,SAAYA,EAAO,YAAY,IAChEG,IAAO,MAAM,KAAK,eAAeF,GAAWrF,GAAQsF,CAAS;AACnE,eAAK,gBAAgB,KAAK,GAAGC,CAAI;AAAA,QACnC;AAAA,eACSH,EAAO,SAAS,SAAS;AAClC,cAAMG,IAAO,MAAM,KAAK,UAAUH,EAAO,OAAOpF,CAAM;AACtD,aAAK,gBAAgB,KAAK,GAAGuF,CAAI;AAAA,MACnC;AAGF,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAuB;AACrB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,0DAA0D;AAE5E,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAsB;AACpB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,uDAAuD;AAEzE,WAAO,CAAC,GAAG,KAAK,eAAe;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgBF,GAAkC;AAKhD,UAAMG,IAAW;AAAA,MACf;AAAA;AAAA,MACA;AAAA;AAAA,IAAA;AAGF,eAAW1L,KAAW0L,GAAU;AAC9B,YAAMC,IAAQJ,EAAU,MAAMvL,CAAO;AACrC,UAAI2L,KAASA,EAAM,CAAC;AAClB,eAAOA,EAAM,CAAC;AAAA,IAElB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,eAAeJ,GAAmBrF,GAAsBsF,IAAqB,IAAyB;AAClH,UAAMI,IAAW,KAAK,gBAAgBL,CAAS;AAE/C,QAAI,CAACK;AACH,YAAM,IAAI,MAAM,+DAA+D;AAIjF,QAAI,CAAC,KAAK,UAAU,KAAK,WAAW;AAClC,aAAO,KAAK,mBAAmBA,GAAU1F,CAAM;AAGjD,QAAI;AACF,aAAIsF,IACK,MAAM,KAAK,sBAAsBI,GAAU1F,CAAM,IAEjD,MAAM,KAAK,2BAA2B0F,GAAU1F,CAAM;AAAA,IAEjE,SAAS2F,GAAO;AACd,qBAAQ,MAAM,wCAAwCA,CAAK,GAEpD,KAAK,mBAAmBD,GAAU1F,CAAM;AAAA,IACjD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,2BAA2B0F,GAAkB1F,GAAyC;AAClG,UAAM4F,IAAsB,CAAA,GAGtBC,IAAQ,IAAIH,CAAQ,kCAEpBI,IAAM,GAAG,KAAK,WAAW,MAAM,mBAAmBD,CAAK,CAAC,qDAAyB,KAAK,MAAM,IAE5FE,IAAW,MAAM,MAAMD,CAAG;AAEhC,QAAI,CAACC,EAAS;AACZ,YAAM,IAAI,MAAM,uBAAuBA,EAAS,MAAM,IAAIA,EAAS,UAAU,EAAE;AAMjF,UAAMC,KAH4B,MAAMD,EAAS,KAAA,GAGzB,MAAM;AAAA,MAAO,CAAAE,MACnCA,EAAK,SAAS,WAAW,QAAQ,KAAKjG,EAAO,UAAUiG,EAAK,IAAI;AAAA,IAAA;AAGlE,gBAAK,IAAI,SAASD,EAAW,MAAM,qBAAqBN,CAAQ,kBAAkB,GAGlFM,EAAW,QAAQ,CAAAC,MAAQ;AACzB,MAAAL,EAAU,KAAK,uCAAuCK,EAAK,EAAE,QAAQ,GACrE,KAAK,IAAI,eAAeA,EAAK,IAAI,EAAE;AAAA,IACrC,CAAC,GAEML;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,UAAUM,GAAoBlG,GAAyC;AACnF,UAAM4F,IAAsB,CAAA;AAE5B,eAAWO,KAAWD,GAAU;AAC9B,YAAME,IAAS,KAAK,cAAcD,CAAO;AAEzC,UAAI,CAACC,GAAQ;AACX,aAAK,IAAI,8BAA8BD,CAAO,EAAE;AAChD;AAAA,MACF;AAGA,UAAI,KAAK,UAAU,KAAK,WAAW;AACjC,YAAI;AAEF,gBAAME,IAAc,GAAG,KAAK,WAAW,IAAID,CAAM,6BAA6B,KAAK,MAAM,IACnFL,IAAW,MAAM,MAAMM,CAAW;AAExC,cAAIN,EAAS,IAAI;AACf,kBAAMO,IAAW,MAAMP,EAAS,KAAA;AAChC,YAAIO,EAAS,SAAS,WAAW,QAAQ,KAAKtG,EAAO,UAAUsG,EAAS,IAAI,KAC1EV,EAAU,KAAK,uCAAuCQ,CAAM,QAAQ,GACpE,KAAK,IAAI,eAAeE,EAAS,IAAI,EAAE,KAEvC,KAAK,IAAI,4BAA4BA,EAAS,IAAI,KAAKA,EAAS,QAAQ,GAAG;AAAA,UAE/E;AACE,iBAAK,IAAI,qCAAqCF,CAAM,KAAKL,EAAS,MAAM,EAAE;AAAA,QAE9E,SAASJ,GAAO;AACd,eAAK,IAAI,oCAAoCS,CAAM,KAAKT,CAAK;AAAA,QAC/D;AAAA;AAGA,QAAAC,EAAU,KAAK,uCAAuCQ,CAAM,QAAQ;AAAA,IAExE;AAEA,WAAOR;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAcO,GAAgC;AAOpD,QAAI,CAAC,QAAQ,KAAKA,CAAO;AACvB,aAAOA;AAGT,UAAMX,IAAW;AAAA,MACf;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IAAA;AAGF,eAAW1L,KAAW0L,GAAU;AAC9B,YAAMC,IAAQU,EAAQ,MAAMrM,CAAO;AACnC,UAAI2L,KAASA,EAAM,CAAC;AAClB,eAAOA,EAAM,CAAC;AAAA,IAElB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,sBAAsBC,GAAkB1F,GAAyC;AAC7F,UAAM4F,IAAsB,CAAA,GAGtBC,IAAQ,IAAIH,CAAQ,kCAGpBI,IAAM,GAAG,KAAK,WAAW,MAAM,mBAAmBD,CAAK,CAAC,qDAAyB,KAAK,MAAM,IAE5FE,IAAW,MAAM,MAAMD,CAAG;AAEhC,QAAI,CAACC,EAAS;AACZ,YAAM,IAAI,MAAM,uBAAuBA,EAAS,MAAM,IAAIA,EAAS,UAAU,EAAE;AAGjF,UAAMQ,IAA4B,MAAMR,EAAS,KAAA,GAG3CC,IAAaO,EAAK,MAAM;AAAA,MAAO,CAAAN,MACnCA,EAAK,SAAS,WAAW,QAAQ,KAAKjG,EAAO,UAAUiG,EAAK,IAAI;AAAA,IAAA,GAG5DO,IAAaD,EAAK,MAAM;AAAA,MAAO,CAAAN,MACnCA,EAAK,aAAa;AAAA,IAAA;AAGpB,SAAK,IAAI,SAASM,EAAK,MAAM,MAAM,0BAA0Bb,CAAQ,EAAE,GAEvEa,EAAK,MAAM,QAAQ,CAAAE,MAAK,KAAK,IAAI,YAAYA,EAAE,IAAI,KAAKA,EAAE,QAAQ,GAAG,CAAC,GAEtE,KAAK,IAAI,KAAKT,EAAW,MAAM,4BAA4B,GAC3D,KAAK,IAAI,KAAKQ,EAAW,MAAM,aAAa,GAG5CR,EAAW,QAAQ,CAAAC,MAAQ;AASzB,MAAAL,EAAU,KAAK,uCAAuCK,EAAK,EAAE,QAAQ,GAErE,KAAK,IAAI,eAAeA,EAAK,IAAI,EAAE;AAAA,IACrC,CAAC;AAGD,eAAWS,KAAUF,GAAY;AAC/B,WAAK,IAAI,kCAAkCE,EAAO,IAAI,EAAE;AACxD,YAAMC,IAAkB,MAAM,KAAK,sBAAsBD,EAAO,IAAI1G,CAAM;AAC1E,MAAA4F,EAAU,KAAK,GAAGe,CAAe;AAAA,IACnC;AAEA,WAAOf;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,mBAAmBF,GAAkBkB,GAA0C;AAO3F,QAAI;AAEF,YAAMvB,IAAY,kDAAkDK,CAAQ,IACtEK,IAAW,MAAM,MAAMV,GAAW,EAAE,MAAM,QAAQ;AAExD,UAAI,CAACU,EAAS;AACZ,cAAM,IAAI,MAAM,2DAA2D;AAG7E,YAAMc,IAAO,MAAMd,EAAS,KAAA,GAGtBe,IAAiB,gCACjBC,IAAU,CAAC,GAAGF,EAAK,SAASC,CAAc,CAAC;AAOjD,aANiB,CAAC,GAAG,IAAI,IAAIC,EAAQ,IAAI,CAAAC,MAAKA,EAAE,CAAC,CAAC,CAAC,CAAC,EAEzB;AAAA,QAAI,CAAAC,MAC7B,8CAA8CA,CAAE;AAAA,MAAA;AAAA,IAKpD,SAAStB,GAAO;AACd,oBAAQ,MAAM,0BAA0BA,CAAK,GACvC,IAAI;AAAA,QACR;AAAA;AAAA;AAAA;AAAA,MAAA;AAAA,IAKJ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgBuB,GAA8B;AAC5C,WAAOA,EAAS,IAAI,CAAAD,MAAM,8CAA8CA,CAAE,EAAE;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,OAAOE,GAAuB;AACpC,IAAI,KAAK,gBAAgB,OAAO,UAAY,OAC1C,QAAQ,IAAI,GAAGA,CAAI;AAAA,EAEvB;AACF;AC5XO,MAAMC,GAAyC;AAAA,EAWpD,YAAYrc,IAAsC,IAAI;AAQpD,QAXF,KAAQ,YAAqB,IAC7B,KAAQ,kBAA4B,CAAA,GAGlC,KAAK,eAAeA,EAAO,iBAAiB,IAC5C,KAAK,oBAAoBA,EAAO,qBAAqB,KACrD,KAAK,mBAAmBA,EAAO,oBAAoB,QACnD,KAAK,UAAUA,EAAO,WAAW,CAAA,GACjC,KAAK,eAAeA,EAAO,gBAAgB,IAGvC,CAAC,KAAK,WAAW,KAAK,QAAQ,WAAW;AAC3C,YAAM,IAAI,MAAM,iEAAiE;AAGnF,SAAK,IAAI,8CAA8CA,CAAM;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQiV,GAAqC;AACjD,SAAK,kBAAkB,CAAA,GAEvB,KAAK,IAAI,cAAc,KAAK,QAAQ,MAAM,YAAY;AAGtD,eAAWoF,KAAU,KAAK;AACxB,UAAI;AACF,cAAMG,IAAO,MAAM,KAAK,cAAcH,GAAQpF,CAAM;AACpD,aAAK,gBAAgB,KAAK,GAAGuF,CAAI;AAAA,MACnC,SAASI,GAAO;AACd,gBAAQ,KAAK,6BAA6BP,GAAQO,CAAK;AAAA,MAEzD;AAGF,SAAK,YAAY,IACjB,KAAK,IAAI,uBAAuB,KAAK,gBAAgB,MAAM,WAAW;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAuB;AACrB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,0DAA0D;AAE5E,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAsB;AACpB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,uDAAuD;AAEzE,WAAO,CAAC,GAAG,KAAK,eAAe;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,cAAcP,GAAsBpF,GAAyC;AACzF,WAAI,CAACoF,KAAU,CAACA,EAAO,QACrB,QAAQ,KAAK,yCAAyCA,CAAM,GACrD,CAAA,KAGLA,EAAO,SAAS,SACX,MAAM,KAAK,YAAYA,EAAO,QAAQ,CAAA,GAAIpF,CAAM,IAC9CoF,EAAO,SAAS,SAClB,MAAM,KAAK,YAAYA,EAAO,UAAUA,EAAO,SAAS,CAAA,GAAIpF,CAAM,KAEzE,QAAQ,KAAK,wBAAwBoF,EAAO,IAAI,EAAE,GAC3C,CAAA;AAAA,EAEX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,YAAYG,GAAgBvF,GAAyC;AACjF,QAAI,CAAC,MAAM,QAAQuF,CAAI;AACrB,qBAAQ,KAAK,0BAA0BA,CAAI,GACpC,CAAA;AAGT,UAAM8B,IAAsB,CAAA;AAE5B,eAAWvB,KAAOP,GAAM;AAEtB,YAAM+B,IAAWxB,EAAI,MAAM,GAAG,EAAE,SAASA;AACzC,UAAI,CAAC9F,EAAO,UAAUsH,CAAQ,GAAG;AAC/B,aAAK,IAAI,0BAA0BxB,CAAG,EAAE;AACxC;AAAA,MACF;AAEA,MAAI,KAAK,eACS,MAAM,KAAK,YAAYA,CAAG,IAExCuB,EAAU,KAAKvB,CAAG,IAElB,QAAQ,KAAK,iCAAiCA,CAAG,EAAE,IAIrDuB,EAAU,KAAKvB,CAAG;AAAA,IAEtB;AAEA,WAAOuB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,YAAYE,GAA8BC,GAAiBxH,GAAyC;AAChH,QAAI,CAACuH;AACH,qBAAQ,KAAK,4CAA4C,GAClD,CAAA;AAGT,QAAI,CAAC,MAAM,QAAQC,CAAK;AACtB,qBAAQ,KAAK,2BAA2BA,CAAK,GACtC,CAAA;AAGT,UAAMH,IAAsB,CAAA;AAE5B,eAAWpB,KAAQuB,GAAO;AAExB,UAAI,CAACxH,EAAO,UAAUiG,CAAI,GAAG;AAC3B,aAAK,IAAI,2BAA2BA,CAAI,EAAE;AAC1C;AAAA,MACF;AAEA,YAAMH,IAAM,KAAK,aAAayB,GAAUtB,CAAI;AAE5C,MAAI,KAAK,eACS,MAAM,KAAK,YAAYH,CAAG,IAExCuB,EAAU,KAAKvB,CAAG,IAElB,QAAQ,KAAK,kCAAkCA,CAAG,EAAE,IAItDuB,EAAU,KAAKvB,CAAG;AAAA,IAEtB;AAEA,WAAOuB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,YAAYvB,GAA+B;AACvD,QAAI,KAAK,qBAAqB;AAC5B,aAAO;AAGT,QAAI,KAAK,qBAAqB;AAE5B,UAAI;AACF,eAAI,OAAO,SAAW,MACpB,IAAI,IAAIA,GAAK,OAAO,SAAS,MAAM,IAEnC,IAAI,IAAIA,CAAG,GAEN;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAMF,QAAI,OAAO,SAAW;AACpB,aAAO;AAMT,QAAI,EAHiBA,EAAI,WAAW,OAAO,SAAS,MAAM,KACrCA,EAAI,WAAW,GAAG;AAIrC,kBAAK,IAAI,6CAA6CA,CAAG,EAAE,GACpD;AAIT,QAAI;AACF,YAAM2B,IAAa,IAAI,gBAAA,GACjBC,IAAY,WAAW,MAAMD,EAAW,MAAA,GAAS,KAAK,iBAAiB,GAEvE1B,IAAW,MAAM,MAAMD,GAAK;AAAA,QAChC,QAAQ;AAAA,QACR,QAAQ2B,EAAW;AAAA,MAAA,CACpB;AAID,aAFA,aAAaC,CAAS,GAElB3B,EAAS,KACJ,MAEP,KAAK,IAAI,yBAAyBD,CAAG,UAAUC,EAAS,MAAM,EAAE,GACzD;AAAA,IAGX,SAASJ,GAAO;AACd,aAAIA,aAAiB,UACfA,EAAM,SAAS,eACjB,KAAK,IAAI,0BAA0BG,CAAG,EAAE,IAExC,KAAK,IAAI,yBAAyBA,CAAG,KAAKH,EAAM,OAAO,IAGpD;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,aAAa4B,GAAkBD,GAA0B;AAE/D,UAAMK,IAAYJ,EAAS,QAAQ,OAAO,EAAE;AAG5C,QAAI,KAAK,cAAcA,CAAQ;AAC7B,aAAO,GAAGI,CAAS,IAAIL,CAAQ;AAIjC,QAAI,OAAO,SAAW;AACpB,aAAO,GAAGK,CAAS,IAAIL,CAAQ;AAGjC,UAAMM,IAAS,OAAO,SAAS,QAGzBC,KADiBN,EAAS,WAAW,GAAG,IAAIA,IAAW,MAAMA,GAClC,QAAQ,OAAO,EAAE;AAElD,WAAO,GAAGK,CAAM,GAAGC,CAAS,IAAIP,CAAQ;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAcxB,GAAsB;AAC1C,QAAI;AACF,iBAAI,IAAIA,CAAG,GACJ;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,OAAOqB,GAAuB;AACpC,IAAI,KAAK,gBAAgB,OAAO,UAAY,OAC1C,QAAQ,IAAI,GAAGA,CAAI;AAAA,EAEvB;AACF;AC9SO,MAAMW,GAAuC;AAAA,EAQlD,YAAY/c,GAA+B;AAKzC,QARF,KAAQ,YAAqB,IAC7B,KAAQ,kBAA4B,CAAA,GAGlC,KAAK,UAAUA,EAAO,SACtB,KAAK,eAAeA,EAAO,gBAAgB,IAGvC,CAAC,KAAK,WAAW,KAAK,QAAQ,WAAW;AAC3C,YAAM,IAAI,MAAM,+DAA+D;AAGjF,SAAK,IAAI,oCAAoC,KAAK,QAAQ,MAAM,YAAY;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAQiV,GAAqC;AACjD,SAAK,kBAAkB,CAAA,GAEvB,KAAK,IAAI,aAAa,KAAK,QAAQ,MAAM,wBAAwB;AAGjE,UAAM+H,IAAkB,KAAK,QAAQ,IAAI,CAACC,GAAQzM,MACzCyM,EAAO,QAAQhI,CAAM,EAAE,KAAK,MAAM;AACvC,WAAK,IAAI,UAAUzE,CAAK,kBAAkByM,EAAO,cAAc,SAAS;AAAA,IAC1E,CAAC,EAAE,MAAM,CAAArC,MAAS;AAChB,cAAQ,KAAK,UAAUpK,CAAK,uBAAuBoK,CAAK;AAAA,IAE1D,CAAC,CACF;AAED,UAAM,QAAQ,IAAIoC,CAAe;AAGjC,eAAWC,KAAU,KAAK;AACxB,UAAIA,EAAO,cAAc;AACvB,cAAMzC,IAAOyC,EAAO,UAAA;AACpB,aAAK,gBAAgB,KAAK,GAAGzC,CAAI;AAAA,MACnC;AAGF,SAAK,YAAY,IACjB,KAAK,IAAI,iCAAiC,KAAK,gBAAgB,MAAM,eAAe;AAAA,EACtF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAuB;AACrB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,wDAAwD;AAE1E,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAsB;AACpB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,qDAAqD;AAEvE,WAAO,CAAC,GAAG,KAAK,eAAe;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,OAAO4B,GAAuB;AACpC,IAAI,KAAK,gBAAgB,OAAO,UAAY,OAC1C,QAAQ,IAAI,qBAAqB,GAAGA,CAAI;AAAA,EAE5C;AACF;ACvGO,MAAMc,GAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQvB,YAAYC,GAAuB;AACjC,SAAK,oBAAoBA,KAAc;AAAA,MACrC;AAAA,MAAO;AAAA,MAAQ;AAAA,MAAO;AAAA,MAAO;AAAA,MAAQ;AAAA,IAAA;AAAA,EAEzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAUZ,GAA2B;;AAGnC,UAAMa,KAAY1e,IADG6d,EAAS,MAAM,GAAG,EAAE,CAAC,EACX,MAAM,GAAG,EAAE,IAAA,MAAxB,gBAAA7d,EAA+B;AACjD,WAAO0e,IAAY,KAAK,kBAAkB,SAASA,CAAS,IAAI;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,uBAAiC;AAC/B,WAAO,CAAC,GAAG,KAAK,iBAAiB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAMF;ACxCO,MAAMC,KAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CvB,SAASC,KAA+B;AAC7C,MAAI,OAAO,WAAa,IAAa;AACrC,QAAMpB,IAAK;AACX,MAAI,SAAS,eAAeA,CAAE,EAAG;AACjC,QAAM7G,IAAQ,SAAS,cAAc,OAAO;AAC5C,EAAAA,EAAM,KAAK6G,GACX7G,EAAM,cAAcgI,IACpB,SAAS,KAAK,YAAYhI,CAAK;AACjC;ACrCO,MAAMkI,GAAW;AAAA,EAqCtB,YAAYzX,IAA6B,IAAI;;AAC3C,SAAK,aAAatG,GAAYsG,CAAO,GAGjCA,EAAQ,qBAAqB,eAC/B,KAAK,eAAeA,EAAQ,WAC5B,KAAK,cAAc,SAEnB,KAAK,eAAe,MACpB,KAAK,cAAcA,EAAQ,aAAa,eAI1C,KAAK,eAAe,IACpB,KAAK,gBAAgB,CAAA,GACrB,KAAK,eAAe,CAAA,GACpB,KAAK,qBAAqB,KAC1B,KAAK,oBAAoB,MACzB,KAAK,eAAe,MACpB,KAAK,gBAAgB,MACrB,KAAK,eAAe,CAAA,GACpB,KAAK,gBAAgB,MACrB,KAAK,iBAAiB,GAGtB,KAAK,kBAAkB,IAAI/F,GAAgB,KAAK,WAAW,SAAS,GACpE,KAAK,eAAe,IAAI2T,GAAa;AAAA,MACnC,QAAQ,KAAK,WAAW;AAAA,MACxB,OAAO,KAAK,WAAW;AAAA,IAAA,CACxB,GACD,KAAK,aAAa,IAAI+C,GAAW,KAAK,WAAW,YAAY,OAAO,KAAK,iBAAiB,KAAK,WAAW,OAAO,GAGjH,KAAK,gBAAgBlB,IAAqB7W,IAAA,KAAK,WAAW,YAAhB,gBAAAA,EAAyB,OAAO,GAC1E,KAAK,cAAc6W,IAAqB9W,IAAA,KAAK,WAAW,YAAhB,gBAAAA,EAAyB,KAAK,GACtE,KAAK,oBAAmBG,KAAAC,IAAA,KAAK,WAAW,YAAhB,gBAAAA,EAAyB,YAAzB,gBAAAD,EAAkC,WAC1D,KAAK,kBAAiBG,KAAAC,IAAA,KAAK,WAAW,YAAhB,gBAAAA,EAAyB,UAAzB,gBAAAD,EAAgC;AAGtD,UAAMye,IAAc,KAAK,WAAW,UAAU,SAAS9f,EAAe,UAAU;AAChF,SAAK,uBAAuB,IAAIgK;AAAA,MAC9B8V;AAAA,MACA,KAAK,WAAW,OAAO;AAAA,IAAA,GAIzB,KAAK,cAAc,MAGnB,KAAK,cAAc,KAAK,kBAAA,GAGxB,KAAK,cAAc,KAAK,aAAA,GAGxB,KAAK,cAAc,MACnB,KAAK,YAAY,MACjB,KAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAiC;;AACvC,UAAMC,IAAa,KAAK,WAAW,OAAO;AAG1C,QAAIN;AACJ,WAAIM,MAAe,gBACjBN,KAAaze,IAAA,KAAK,WAAW,OAAO,gBAAvB,gBAAAA,EAAoC,oBAEjDye,KAAa1e,IAAA,KAAK,WAAW,OAAO,WAAvB,gBAAAA,EAA+B,mBAGvC,IAAIye,GAAYC,CAAU;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAA4B;AAClC,WAAO,KAAK,uBAAuB,KAAK,WAAW,MAAM;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuBnd,GAAoD;AACjF,UAAMyd,IAAazd,EAAO;AAE1B,QAAIyd,MAAe,UAAU;AAC3B,YAAMC,IAAe1d,EAAO;AAC5B,aAAO,IAAIqc,GAAkBqB,CAAY;AAAA,IAC3C,WAAWD,MAAe,aAAa;AACrC,YAAME,IAAkB3d,EAAO,WACzB4d,IAAeD,EAAgB,QAAQ;AAAA,QAAI,CAAAE,MAC/C,KAAK,uBAAuBA,CAAY;AAAA,MAAA;AAE1C,aAAO,IAAId,GAAgB;AAAA,QACzB,SAASa;AAAA,QACT,cAAcD,EAAgB;AAAA,MAAA,CAC/B;AAAA,IACH,OAAO;AACL,YAAMG,IAAc9d,EAAO;AAC3B,aAAO,IAAIoa,GAAkB0D,CAAW;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI;AAKF,UAHAR,GAAA,GAGI,KAAK;AACP,aAAK,cAAc,KAAK;AAAA,eAExB,KAAK,cAAc,SAAS,eAAe,KAAK,WAAY,GACxD,CAAC,KAAK;AACR,cAAM,IAAI,MAAM,cAAc,KAAK,WAAW,YAAY;AAK9D,WAAK,YAAY,UAAU,IAAI,gBAAgB,GAG/C,KAAK,cAAc,IAAInD,GAAY,KAAK,aAAa;AAAA,QACnD,QAAQ,MAAM,KAAK,oBAAA;AAAA,QACnB,QAAQ,MAAM,KAAK,wBAAA;AAAA,QACnB,cAAc,CAAClS,MAAW,KAAK,WAAW,cAAcA,CAAM;AAAA,QAC9D,WAAW,CAACiS,MAAc;AACxB,UAAKA,IAKH,KAAK,WAAW,gBAAgB,EAAK,IAHrC,KAAK,WAAW,gBAAgB,IAAMd,EAAqB;AAAA,QAK/D;AAAA,MAAA,CACD,GAGD,KAAK,QAAA,GAGL,KAAK,oBAAA,GAGL,KAAK,SAAS,wBAAwB,GACtC,MAAM,KAAK,WAAA;AAAA,IAEb,SAASwB,GAAO;AACd,cAAQ,MAAM,kCAAkCA,CAAK,GACjD,KAAK,WAAWA,aAAiB,SACnC,KAAK,UAAU,mCAAmCA,EAAM,OAAO;AAAA,IAEnE;AAAA,EACF;AAAA,EAEQ,UAAgB;AAEtB,SAAK,YAAY,SAAS,eAAe,SAAS,GAClD,KAAK,UAAU,SAAS,eAAe,OAAO;AAAA,EAChD;AAAA,EAEQ,sBAA4B;AAElC,aAAS,iBAAiB,WAAW,CAACnB,MAAqB;;AACzD,MAAIA,EAAE,QAAQ,YACZ,KAAK,WAAW,aAAA,GAChB,KAAK,oBAAoB,OACzB/a,IAAA,KAAK,gBAAL,QAAAA,EAAkB,aACT+a,EAAE,QAAQ,eACnB,KAAK,oBAAA,IACIA,EAAE,QAAQ,cACnB,KAAK,wBAAA,KACKA,EAAE,QAAQ,WAAWA,EAAE,QAAQ,QAAQ,KAAK,iBAEtD,KAAK,iBAAiB,KAAK,aAAa,SAAS,KAAK,aAAa,MAAM,GACzEA,EAAE,eAAA;AAAA,IAEN,CAAC,GAED,SAAS,iBAAiB,SAAS,CAACA,MAAkB;;AAEpD,OAAI/a,IAAA,KAAK,gBAAL,QAAAA,EAAkB,oBAGhB+a,EAAE,OAAuB,QAAQ,eAAe,MACpD,KAAK,WAAW,aAAA,GAChB,KAAK,oBAAoB,OACzBhb,IAAA,KAAK,gBAAL,QAAAA,EAAkB;AAAA,IAEtB,CAAC,GAGD,OAAO,iBAAiB,UAAU,MAAM,KAAK,cAAc;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAA4B;AAClC,QAAI,KAAK,sBAAsB,QAAQ,KAAK,cAAc,WAAW,EAAG;AAExE,UAAMsf,KAAa,KAAK,oBAAoB,KAAK,KAAK,cAAc;AACpE,SAAK,gBAAgBA,CAAS;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKQ,0BAAgC;AACtC,QAAI,KAAK,sBAAsB,QAAQ,KAAK,cAAc,WAAW,EAAG;AAExE,UAAMC,KAAa,KAAK,oBAAoB,IAAI,KAAK,cAAc,UAAU,KAAK,cAAc;AAChG,SAAK,gBAAgBA,CAAS;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAAgBxN,GAA8B;AAC1D,QAAIA,IAAQ,KAAKA,KAAS,KAAK,cAAc,OAAQ;AAErD,UAAM4H,IAAe,KAAK,cAAc5H,CAAK,GACvCpF,IAAS,KAAK,aAAaoF,CAAK;AAEtC,IAAI,CAAC4H,KAAgB,CAAChN,KAGtB,MAAM,KAAK,iBAAiBgN,GAAchN,CAAM;AAAA,EAClD;AAAA,EAEQ,eAAqB;AAC3B,IAAK,KAAK,iBAEN,KAAK,kBAAkB,QACzB,aAAa,KAAK,aAAa,GAGjC,KAAK,gBAAgB,OAAO,WAAW,MAAM;AAC3C,YAAM6S,IAAY,KAAK,eAAA;AAEvB,MAAIA,MAAc,KAAK,sBACrB,KAAK,SAAS,6CAA6CA,CAAS,0BAA0B,GAE9F,KAAK,WAAA,KAEL,KAAK,SAAS,uCAAuC;AAAA,IAEzD,GAAG,GAAG;AAAA,EACR;AAAA,EAEQ,iBAAyB;AAC/B,UAAM/T,IAAQ,OAAO,YACfgU,IAAa,KAAK,WAAW,OAAO,YAGpCnK,IAAS,KAAK,WAAW,MAAM,QAC/BM,KAAUN,KAAA,gBAAAA,EAAQ,YAAW;AAInC,WAAKmK,IAODhU,KAASgU,EAAW,OAAO,WACtB,KAAK,IAAI,KAAK7J,CAAO,IAE1BnK,KAASgU,EAAW,OAAO,WACtB,KAAK,IAAI,KAAK7J,CAAO,IAEvB,KAAK,IAAI,KAAKA,CAAO,IAXtBnK,KAAS,MAAY,KAAK,IAAI,KAAKmK,CAAO,IAC1CnK,KAAS,OAAa,KAAK,IAAI,KAAKmK,CAAO,IACxC,KAAK,IAAI,KAAKA,CAAO;AAAA,EAUhC;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAwD;AAC9D,WAAK,KAAK,cAGH;AAAA,MACL,OAAO,KAAK,YAAY;AAAA,MACxB,QAAQ,KAAK,YAAY,gBAAgB,OAAO,cAAc;AAAA,IAAA,IAJvD,EAAE,OAAO,OAAO,YAAY,QAAQ,OAAO,cAAc,IAAA;AAAA,EAMpE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAA4B;AACxC,QAAI;AACF,WAAK,YAAY,EAAI,GACrB,KAAK,UAAA,GACL,KAAK,gBAAA,GAGL,MAAM,KAAK,YAAY,QAAQ,KAAK,WAAW;AAG/C,YAAMrK,IAAa,KAAK,YAAY,aAAA;AACpC,UAAI6Q,IAAY,KAAK,YAAY,UAAA;AAEjC,UAAI7Q,MAAe,GAAG;AACpB,aAAK,UAAU,kBAAkB,GACjC,KAAK,YAAY,EAAK;AACtB;AAAA,MACF;AAGA,YAAMlC,IAAkB,KAAK,mBAAA,GACvBkM,IAAmB,KAAK,eAAA,GACxBH,IAAgB,OAAO;AAE7B,WAAK,SAAS,oCAAoC/L,EAAgB,KAAK,IAAIA,EAAgB,MAAM,cAAckC,CAAU,mBAAmBgK,CAAgB,IAAI;AAEhK,YAAMmK,IAAe,KAAK,aAAa;AAAA,QACrCrW;AAAA,QACAkC;AAAA,QACAgK;AAAA,QACAH;AAAA,MAAA;AAGF,WAAK,SAAS,kCAAkCsK,EAAa,MAAM,IAAI,GAEvE,MAAM,KAAK,iBAAiBtD,GAAWsD,EAAa,MAAM,GAE1D,KAAK,YAAY,EAAK,GACtB,KAAK,eAAe;AAAA,IAEtB,SAASvD,GAAO;AACd,cAAQ,MAAM,yBAAyBA,CAAK,GACxCA,aAAiB,SACnB,KAAK,UAAUA,EAAM,WAAW,wBAAwB,GAE1D,KAAK,YAAY,EAAK;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAYwB,GAAuB;AACzC,IAAI,KAAK,WAAW,SAAS,OAAO,UAAY,OAC9C,QAAQ,IAAI,GAAGA,CAAI;AAAA,EAEvB;AAAA,EAEA,MAAc,iBAAiBvB,GAAqB1U,GAAoC;AACtF,QAAI,CAAC,KAAK,YAAa;AAEvB,UAAM2B,IAAkB,KAAK,mBAAA;AAC7B,SAAK,qBAAqB3B;AAG1B,UAAMiY,IAAoB,KAAK,gBAGzBnU,IAAU,KAAK,aAAa,eAAe4Q,EAAU,QAAQ/S,GAAiB,EAAE,aAAa3B,GAAoB;AACvH,SAAK,eAAe8D,GAEpB,KAAK,eAAe,CAAA;AACpB,QAAI+B,IAAiB;AAGrB,UAAMqS,IAAe,CAACC,MAA0B;AAC9C,MAAK,KAAK,gBAEV,KAAK,YAAY,YAAYA,CAAG,GAChC,KAAK,cAAc,KAAKA,CAAG,GAE3B,sBAAsB,MAAM;AAa1B,YAZKA,EAAI,aAETA,EAAI,MAAM,UAAU,KAAK,cAAc,WAAW,KAGzBA,EAAI,QAAQ,WAClC,KAAK,qBAAqB,oBAAA,KAC1B,KAAK,qBAAqB,mBAAA,KAC1B,KAAK,qBAAqB,gBAAA,KAC1BA,EAAI,QAAQ,kBAAkBA,EAAI,QAAQ,YAC1CA,EAAI,QAAQ,eAAeA,EAAI,QAAQ,QAEpB;AAEpB,gBAAMvY,IAAgB;AAAA,YACpB,GAAG,WAAWuY,EAAI,QAAQ,MAAO;AAAA,YACjC,GAAG,WAAWA,EAAI,QAAQ,MAAO;AAAA,UAAA,GAE7BtY,IAAc;AAAA,YAClB,GAAG,WAAWsY,EAAI,QAAQ,IAAK;AAAA,YAC/B,GAAG,WAAWA,EAAI,QAAQ,IAAK;AAAA,UAAA,GAE3BpY,IAAa,WAAWoY,EAAI,QAAQ,UAAW,GAC/CnY,IAAc,WAAWmY,EAAI,QAAQ,WAAY,GACjD9c,IAAW,WAAW8c,EAAI,QAAQ,QAAS,GAC3C/c,IAAQ,WAAW+c,EAAI,QAAQ,KAAM,GACrChY,IAAgBgY,EAAI,QAAQ,gBAC9B,WAAWA,EAAI,QAAQ,aAAa,IACpC9c,GACEgF,IAAa8X,EAAI,QAAQ,aAC3B,WAAWA,EAAI,QAAQ,UAAU,IACjC/c,GACEgd,IAAS,KAAK,qBAAqB,UAAA;AAEzC,UAAA1Y,GAAY;AAAA,YACV,SAASyY;AAAA,YACT,eAAAvY;AAAA,YACA,aAAAC;AAAA,YACA,YAAY,KAAK,qBAAqB,cAAA;AAAA,YACtC,UAAUuY,EAAO;AAAA,YACjB,YAAArY;AAAA,YACA,aAAAC;AAAAA,YACA,UAAA3E;AAAA,YACA,OAAAD;AAAA,YACA,gBAAgB,KAAK,qBAAqB,kBAAA;AAAA,YAC1C,eAAA+E;AAAA,YACA,aAAa,KAAK,qBAAqB,eAAA;AAAA,YACvC,YAAAE;AAAA,UAAA,CACD;AAAA,QACH,OAAO;AAEL,gBAAMgY,IAAiBF,EAAI,QAAQ,kBAAkB;AACrD,UAAAA,EAAI,MAAM,YAAYE;AAAA,QACxB;AAGA,cAAMC,IAAW,SAASH,EAAI,QAAQ,WAAW,GAAG;AACpD,YAAI,KAAK,WAAW,SAASG,IAAW,GAAG;AACzC,gBAAMD,IAAiBF,EAAI,QAAQ,kBAAkB;AACrD,kBAAQ,IAAI,SAASG,CAAQ,iBAAiB;AAAA,YAC5C,MAAMH,EAAI,MAAM;AAAA,YAChB,KAAKA,EAAI,MAAM;AAAA,YACf,OAAOA,EAAI,MAAM;AAAA,YACjB,QAAQA,EAAI,MAAM;AAAA,YAClB,eAAeA,EAAI;AAAA,YACnB,gBAAgBA,EAAI;AAAA,YACpB,WAAWE;AAAA,YACX,UAAU,KAAK,qBAAqB,YAAA;AAAA,UAAY,CACjD;AAAA,QACH;AAAA,MACF,CAAC,GAEDxS;AAAA,IACF,GAEM0S,IAAuB,MAAM;AAIjC,UAHA,KAAK,SAAS,uCAAuC,KAAK,WAAW,UAAU,MAAM,OAAO,GAGxF,CAAC,KAAK,WAAW,UAAU,MAAM,SAAS;AAC5C,eAAO,KAAK,aAAa,SAAS,KAAG;AACnC,gBAAMJ,IAAM,KAAK,aAAa,MAAA;AAC9B,UAAIA,KACFD,EAAaC,CAAG;AAAA,QAEpB;AACA;AAAA,MACF;AAIA,MAAI,KAAK,kBAAkB,QACzB,cAAc,KAAK,aAAa,GAElC,KAAK,gBAAgB,OAAO,YAAY,MAAM;AAE5C,YAAIF,MAAsB,KAAK,gBAAgB;AAC7C,UAAI,KAAK,kBAAkB,SACzB,cAAc,KAAK,aAAa,GAChC,KAAK,gBAAgB;AAEvB;AAAA,QACF;AAEA,YAAI,KAAK,aAAa,SAAS,GAAG;AAChC,gBAAME,IAAM,KAAK,aAAa,MAAA;AAC9B,UAAIA,KACFD,EAAaC,CAAG;AAAA,QAEpB;AAEA,QAAItS,KAAkB6O,EAAU,UAAU,KAAK,aAAa,WAAW,KACjE,KAAK,kBAAkB,SACzB,cAAc,KAAK,aAAa,GAChC,KAAK,gBAAgB;AAAA,MAG3B,GAAG,KAAK,WAAW,UAAU,MAAM,QAAQ;AAAA,IAC7C;AAGA,QAAI,0BAA0B,UAAU,KAAK,aAAa;AACxD,YAAM8D,IAAW,IAAI,qBAAqB,CAACC,MAAY;AACrD,QAAAA,EAAQ,QAAQ,CAAAC,MAAS;AACvB,UAAIA,EAAM,mBACRH,EAAA,GACAC,EAAS,WAAA;AAAA,QAEb,CAAC;AAAA,MACH,GAAG,EAAE,WAAW,KAAK,YAAY,QAAQ;AACzC,MAAAA,EAAS,QAAQ,KAAK,WAAW;AAAA,IACnC;AACE,MAAAD,EAAA;AAIF,IAAI,KAAK,WAAW,OAAO,gBAAgB,KAAK,gBAE9C,KAAK,YAAY,iBAAiB,sBAAsB,EAAE,QAAQ,CAAAI,MAAMA,EAAG,QAAQ,GAEnF7U,EAAQ,QAAQ,CAACmB,GAAQoF,MAAU;AACjC,YAAMuO,IAAS,SAAS,cAAc,KAAK;AAC3C,MAAAA,EAAO,YAAY,uBACnBA,EAAO,MAAM,WAAW,YACxBA,EAAO,MAAM,QAAQ,QACrBA,EAAO,MAAM,SAAS,QACtBA,EAAO,MAAM,eAAe,OAC5BA,EAAO,MAAM,kBAAkB,OAC/BA,EAAO,MAAM,SAAS,oBACtBA,EAAO,MAAM,SAAS,QACtBA,EAAO,MAAM,gBAAgB;AAE7B,YAAM7W,IAAUkD,EAAO,GACjBjD,IAAUiD,EAAO;AACvB,MAAA2T,EAAO,MAAM,OAAO,GAAG7W,IAAU,CAAC,MAClC6W,EAAO,MAAM,MAAM,GAAG5W,IAAU,CAAC,MACjC4W,EAAO,QAAQ,SAASvO,CAAK,aAAa,KAAK,MAAMtI,CAAO,CAAC,KAAK,KAAK,MAAMC,CAAO,CAAC,KACrF,KAAK,YAAa,YAAY4W,CAAM;AAAA,IACtC,CAAC,IAIHlE,EAAU,QAAQ,CAACE,GAAKvK,MAAU;;AAChC,YAAM8N,IAAM,SAAS,cAAc,KAAK;AAExC,MAAAA,EAAI,iBAAiB,eACrBA,EAAI,UAAU,IAAI,cAAc,GAChCA,EAAI,QAAQ,UAAU,OAAO9N,CAAK;AAElC,YAAMpF,IAASnB,EAAQuG,CAAK;AAC5B,MAAA8N,EAAI,MAAM,WAAW,YACrBA,EAAI,MAAM,QAAQ,QAClBA,EAAI,MAAM,SAAS,GAAGnY,CAAW,MACjCmY,EAAI,MAAM,OAAO,GAAGlT,EAAO,CAAC,MAC5BkT,EAAI,MAAM,MAAM,GAAGlT,EAAO,CAAC,MAIvBA,EAAO,eAAe,GAAC3M,KAAAC,IAAA,KAAK,WAAW,YAAhB,gBAAAA,EAAyB,YAAzB,QAAAD,EAAkC,YAC3D6f,EAAI,MAAM,SAAS,aAAalT,EAAO,WAAW,IAClDkT,EAAI,MAAM,YAAY,eAEpBlT,EAAO,WAAQkT,EAAI,MAAM,SAAS,OAAOlT,EAAO,MAAM,IAG1D6K,GAAqBqI,GAAK,KAAK,aAAa,GAC5ClI,GAAwBkI,GAAK,KAAK,gBAAgB,GAIlDA,EAAI,iBAAiB,cAAc,MAAM;AACvC,aAAK,eAAe,EAAE,SAASA,GAAK,QAAAlT,EAAA,GAC/B,KAAK,WAAW,WAAWkT,CAAG,MACjCrI,GAAqBqI,GAAK,KAAK,WAAW,GAC1ClI,GAAwBkI,GAAK,KAAK,cAAc;AAAA,MAEpD,CAAC,GAEDA,EAAI,iBAAiB,cAAc,MAAM;AACvC,aAAK,eAAe,MACf,KAAK,WAAW,WAAWA,CAAG,MACjCrI,GAAqBqI,GAAK,KAAK,aAAa,GAC5C/H,GAA2B+H,GAAK,KAAK,cAAc,GACnDlI,GAAwBkI,GAAK,KAAK,gBAAgB;AAAA,MAEtD,CAAC,GAEDA,EAAI,iBAAiB,SAAS,CAAC7E,MAAkB;AAC/C,QAAAA,EAAE,gBAAA,GACF,KAAK,iBAAiB6E,GAAKlT,CAAM;AAAA,MACnC,CAAC,GAEDkT,EAAI,MAAM,UAAU,KACpBA,EAAI,MAAM,aAAa,KAAK,qBAAqB,iBAAA,GAEjDA,EAAI,SAAS,MAAM;AAEjB,YAAIF,MAAsB,KAAK;AAC7B;AAGF,cAAM7O,IAAc+O,EAAI,eAAeA,EAAI,eACrCU,IAAgB7Y,IAAcoJ;AAGpC,QAAA+O,EAAI,MAAM,QAAQ,GAAGU,CAAa;AAGlC,cAAMpX,IAAgB,EAAE,GAAGwD,EAAO,GAAG,GAAGA,EAAO,EAAA,GACzCvD,IAAY,EAAE,OAAOmX,GAAe,QAAQ7Y,EAAA,GAE5CJ,IAAgB,KAAK,qBAAqB;AAAA,UAC9C6B;AAAA,UACAC;AAAA,UACAC;AAAA,UACA0I;AAAA,UACAqK,EAAU;AAAA,QAAA,GAINvU,IAAgB,KAAK,qBAAqB,uBAAuB8E,EAAO,QAAQ,GAGhF5E,IAAa,KAAK,qBAAqB,oBAAoB4E,EAAO,KAAK,GAEvEoT,IAAiB,KAAK,qBAAqB;AAAA,UAC/CpT,EAAO;AAAA,UACPA,EAAO;AAAA,UACP4T;AAAA,UACA7Y;AAAA,QAAA,GAEI4R,IAAiB,KAAK,qBAAqB;AAAA,UAC/ChS;AAAA,UACA6B;AAAA,UACAwD,EAAO;AAAA,UACPA,EAAO;AAAA,UACP4T;AAAA,UACA7Y;AAAA,UACAG;AAAA,UACAE;AAAA,QAAA;AAGF,QAAI,KAAK,WAAW,SAASgK,IAAQ,KACnC,QAAQ,IAAI,SAASA,CAAK,KAAK;AAAA,UAC7B,eAAA5I;AAAA,UACA,WAAAC;AAAA,UACA,MAAMuD,EAAO;AAAA,UACb,KAAKA,EAAO;AAAA,UACZ,gBAAAoT;AAAA,UACA,eAAAQ;AAAA,UACA,gBAAgB7Y;AAAA,QAAA,CACjB,GAGHmY,EAAI,MAAM,YAAYvG,GACtBuG,EAAI,QAAQ,iBAAiBE,IAIJ,KAAK,qBAAqB,oBAAA,KACjD,KAAK,qBAAqB,mBAAA,KAC1B,KAAK,qBAAqB,gBAAA,KAC1BlY,MAAkB8E,EAAO,YACzB5E,MAAe4E,EAAO,WAGtBkT,EAAI,QAAQ,SAAS,OAAOvY,EAAc,CAAC,GAC3CuY,EAAI,QAAQ,SAAS,OAAOvY,EAAc,CAAC,GAC3CuY,EAAI,QAAQ,OAAO,OAAO1W,EAAc,CAAC,GACzC0W,EAAI,QAAQ,OAAO,OAAO1W,EAAc,CAAC,GACzC0W,EAAI,QAAQ,aAAa,OAAOU,CAAa,GAC7CV,EAAI,QAAQ,cAAc,OAAOnY,CAAW,GAC5CmY,EAAI,QAAQ,WAAW,OAAOlT,EAAO,QAAQ,GAC7CkT,EAAI,QAAQ,QAAQ,OAAOlT,EAAO,KAAK,GACvCkT,EAAI,QAAQ,gBAAgB,OAAOhY,CAAa,GAChDgY,EAAI,QAAQ,aAAa,OAAO9X,CAAU,IAG5C,KAAK,aAAa,KAAK8X,CAAG;AAAA,MAC5B,GAEAA,EAAI,UAAU,MAAMtS,KAGpBsS,EAAI,MAAMvD;AAAA,IACZ,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,iBAAiB3C,GAAgC6G,GAA4C;;AACzG,QAAI,CAAC,KAAK,YAAa;AAEvB,UAAMC,IAAY,KAAK,WAAW,UAAU9G,CAAY,GAClD+G,IAA0B;AAAA,MAC9B,OAAO,KAAK,YAAY;AAAA,MACxB,QAAQ,KAAK,YAAY;AAAA,IAAA;AAG3B,QAAID;AACF,YAAM,KAAK,WAAW,aAAA,GACtB,KAAK,oBAAoB,OACzBxgB,IAAA,KAAK,gBAAL,QAAAA,EAAkB;AAAA,SACb;AAEL,YAAMiV,IAAUyE,EAAa,QAAQ;AACrC,WAAK,oBAAoBzE,MAAY,SAAY,SAASA,GAAS,EAAE,IAAI,OACzElV,IAAA,KAAK,gBAAL,QAAAA,EAAkB,UAClB,MAAM,KAAK,WAAW,WAAW2Z,GAAc+G,GAAQF,CAAc;AAAA,IACvE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAwB;AAEtB,IAAI,KAAK,kBAAkB,SACzB,cAAc,KAAK,aAAa,GAChC,KAAK,gBAAgB,OAGvB,KAAK,kBACL,KAAK,eAAe,CAAA,GAEhB,KAAK,gBACP,KAAK,YAAY,YAAY,KAE/B,KAAK,gBAAgB,CAAA,GACrB,KAAK,eAAe,CAAA,GACpB,KAAK,oBAAoB,MACzB,KAAK,eAAe,MACpB,KAAK,aAAa,MAAA,GAClB,KAAK,WAAW,MAAA,GAChB,KAAK,eAAe;AAAA,EACtB;AAAA,EAEQ,YAAYG,GAAqB;AACvC,IAAI,CAAC,KAAK,WAAW,UAAU,GAAG,sBAAsB,CAAC,KAAK,cAC1DA,IACF,KAAK,UAAU,UAAU,OAAO,eAAe,IAE/C,KAAK,UAAU,UAAU,IAAI,eAAe;AAAA,EAEhD;AAAA,EAEQ,UAAUC,GAAuB;AACvC,IAAK,KAAK,YACV,KAAK,QAAQ,cAAcA,GAC3B,KAAK,QAAQ,UAAU,OAAO,eAAe;AAAA,EAC/C;AAAA,EAEQ,YAAkB;AACxB,IAAI,KAAK,WACP,KAAK,QAAQ,UAAU,IAAI,eAAe;AAAA,EAE9C;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;;AACd,SAAK,gBAAA,GAED,KAAK,kBAAkB,QACzB,aAAa,KAAK,aAAa,IAEjC3gB,IAAA,KAAK,gBAAL,QAAAA,EAAkB;AAAA,EACpB;AACF;AC9yBA,SAAS4gB,KAAqB;AAC5B,MAAI,OAAO,WAAa,IAAa;AACrC,QAAMpD,IAAK;AACX,MAAI,SAAS,eAAeA,CAAE,EAAG;AACjC,QAAM7G,IAAQ,SAAS,cAAc,OAAO;AAC5C,EAAAA,EAAM,KAAK6G,GACX7G,EAAM,cAAckK,IACpB,SAAS,KAAK,YAAYlK,CAAK;AACjC;AACAiK,GAAA;AAMA,SAASE,KAAuB;AAC9B,MAAI,OAAO,WAAa,KAAa;AACnC,YAAQ,KAAK,iEAAiE;AAC9E;AAAA,EACF;AAEA,QAAMC,IAAgB,MAAM;AAE1B,UAAMC,IAAa,SAAS,iBAAiB,0CAA0C;AAEvF,IAAIA,EAAW,WAAW,KAM1BA,EAAW,QAAQ,CAAAnG,MAAa;AAC9B,YAAMlZ,IAAUkZ;AAGhB,UAAI,CAAClZ,EAAQ,IAAI;AACf,gBAAQ,MAAM,uEAAuE;AACrF;AAAA,MACF;AAIA,YAAMsf,IAAatf,EAAQ,QAAQ,UAAUA,EAAQ,QAAQ;AAC7D,UAAIyF;AAEJ,UAAI6Z;AACF,YAAI;AACF,gBAAMC,IAAS,KAAK,MAAMD,CAAU;AACpC,UAAA7Z,IAAU;AAAA,YACR,WAAWzF,EAAQ;AAAA,YACnB,GAAGuf;AAAA,UAAA;AAAA,QAEP,SAAShF,GAAO;AACd,kBAAQ,MAAM,uDAAuDva,EAAQ,EAAE,KAAKua,CAAK;AACzF;AAAA,QACF;AAAA,WACK;AACL,gBAAQ,MAAM,0CAA0Cva,EAAQ,EAAE,sCAAsC;AACxG;AAAA,MACF;AAIA,MADgB,IAAIkd,GAAWzX,CAAO,EAC9B,KAAA,EAAO,MAAM,CAAA8U,MAAS;AAC5B,gBAAQ,MAAM,qCAAqCA,CAAK;AAAA,MAC1D,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAGA,EAAI,SAAS,eAAe,YAC1B,SAAS,iBAAiB,oBAAoB6E,CAAa,IAG3DA,EAAA;AAEJ;AAGAD,GAAA;"}
1
+ {"version":3,"file":"image-cloud-auto-init.js","sources":["../src/config/defaults.ts","../src/engines/AnimationEngine.ts","../src/engines/PathAnimator.ts","../src/engines/EntryAnimationEngine.ts","../src/generators/RandomPlacementGenerator.ts","../src/generators/RadialPlacementGenerator.ts","../src/generators/GridPlacementGenerator.ts","../src/generators/SpiralPlacementGenerator.ts","../src/generators/ClusterPlacementGenerator.ts","../src/generators/WavePlacementGenerator.ts","../src/engines/LayoutEngine.ts","../src/config/types.ts","../src/utils/styleUtils.ts","../src/engines/ZoomEngine.ts","../src/engines/SwipeEngine.ts","../src/loaders/GoogleDriveLoader.ts","../src/loaders/StaticImageLoader.ts","../src/loaders/CompositeLoader.ts","../src/loaders/ImageFilter.ts","../src/styles/functionalStyles.ts","../src/ImageCloud.ts","../src/image-cloud-auto-init.ts"],"sourcesContent":["/**\n * Default configuration for Image Gallery\n * Centralized settings for animation, layout, and API configuration\n */\n\nimport type { ImageCloudConfig, DeepPartial, ImageStylingConfig, ImageStyleState, ShadowPreset, WaveAlgorithmConfig, BouncePathConfig, ElasticPathConfig, WavePathConfig, BouncePreset, ElasticPreset, WavePathPreset, EntryPathConfig, EntryRotationConfig, EntryScaleConfig, ImageConfig, ImageSizingConfig, ImageRotationConfig, ImageVarianceConfig, ResponsiveBreakpoints } from './types';\n\n/**\n * Shadow presets for image styling\n */\nexport const SHADOW_PRESETS: Record<ShadowPreset, string> = Object.freeze({\n 'none': 'none',\n 'sm': '0 2px 4px rgba(0,0,0,0.1)',\n 'md': '0 4px 16px rgba(0,0,0,0.4)',\n 'lg': '0 8px 32px rgba(0,0,0,0.5)',\n 'glow': '0 0 30px rgba(255,255,255,0.6)'\n});\n\n/**\n * Bounce path presets - overshoot and settle animations\n */\nexport const BOUNCE_PRESETS: Record<BouncePreset, BouncePathConfig> = Object.freeze({\n energetic: Object.freeze({ overshoot: 0.25, bounces: 2, decayRatio: 0.5 }),\n playful: Object.freeze({ overshoot: 0.15, bounces: 1, decayRatio: 0.5 }),\n subtle: Object.freeze({ overshoot: 0.08, bounces: 1, decayRatio: 0.5 })\n});\n\n/**\n * Elastic path presets - spring-like oscillation animations\n */\nexport const ELASTIC_PRESETS: Record<ElasticPreset, ElasticPathConfig> = Object.freeze({\n gentle: Object.freeze({ stiffness: 150, damping: 30, mass: 1, oscillations: 2 }),\n bouncy: Object.freeze({ stiffness: 300, damping: 15, mass: 1, oscillations: 4 }),\n wobbly: Object.freeze({ stiffness: 180, damping: 12, mass: 1.5, oscillations: 5 }),\n snappy: Object.freeze({ stiffness: 400, damping: 25, mass: 0.8, oscillations: 2 })\n});\n\n/**\n * Wave path presets - sinusoidal path animations\n */\nexport const WAVE_PATH_PRESETS: Record<WavePathPreset, WavePathConfig> = Object.freeze({\n gentle: Object.freeze({ amplitude: 30, frequency: 1.5, decay: true, decayRate: 0.9, phase: 0 }),\n playful: Object.freeze({ amplitude: 50, frequency: 2.5, decay: true, decayRate: 0.7, phase: 0 }),\n serpentine: Object.freeze({ amplitude: 60, frequency: 3, decay: false, decayRate: 1, phase: 0 }),\n flutter: Object.freeze({ amplitude: 20, frequency: 4, decay: true, decayRate: 0.5, phase: 0 })\n});\n\n/**\n * Default path configuration (linear - no special path effects)\n */\nexport const DEFAULT_PATH_CONFIG: EntryPathConfig = Object.freeze({\n type: 'linear' as const\n});\n\n/**\n * Default entry rotation configuration (no rotation animation)\n */\nexport const DEFAULT_ENTRY_ROTATION: EntryRotationConfig = Object.freeze({\n mode: 'none' as const\n});\n\n/**\n * Default entry scale configuration (no scale animation)\n */\nexport const DEFAULT_ENTRY_SCALE: EntryScaleConfig = Object.freeze({\n mode: 'none' as const\n});\n\n/**\n * Entry scale presets for common effects\n */\nexport const ENTRY_SCALE_PRESETS = Object.freeze({\n grow: Object.freeze({ mode: 'grow' as const, startScale: 0.3 }),\n shrink: Object.freeze({ mode: 'shrink' as const, startScale: 1.5 }),\n pop: Object.freeze({ mode: 'pop' as const, pop: Object.freeze({ overshoot: 1.2, bounces: 1 }) }),\n randomSubtle: Object.freeze({ mode: 'random' as const, range: Object.freeze({ min: 0.7, max: 1.0 }) }),\n randomWide: Object.freeze({ mode: 'random' as const, range: Object.freeze({ min: 0.3, max: 1.5 }) })\n});\n\n/**\n * Default image styling configuration\n */\nexport const DEFAULT_STYLING: ImageStylingConfig = Object.freeze({\n default: Object.freeze({\n border: Object.freeze({\n width: 0,\n color: '#000000',\n radius: 0,\n style: 'solid' as const\n }),\n shadow: 'none' as ShadowPreset,\n filter: Object.freeze({}),\n opacity: 1,\n cursor: 'pointer',\n outline: Object.freeze({\n width: 0,\n color: '#000000',\n style: 'solid' as const,\n offset: 0\n })\n }),\n hover: Object.freeze({\n shadow: 'none' as ShadowPreset\n }),\n focused: Object.freeze({\n shadow: 'none' as ShadowPreset\n })\n});\n\n/**\n * Default wave layout configuration\n */\nexport const DEFAULT_WAVE_CONFIG: WaveAlgorithmConfig = Object.freeze({\n rows: 1,\n amplitude: 100,\n frequency: 2,\n phaseShift: 0,\n synchronization: 'offset' as const\n // Note: Image rotation along wave is now controlled via image.rotation.mode = 'tangent'\n});\n\n/**\n * Default responsive breakpoints for layout\n */\nexport const DEFAULT_RESPONSIVE_BREAKPOINTS: ResponsiveBreakpoints = Object.freeze({\n mobile: Object.freeze({ maxWidth: 767 }),\n tablet: Object.freeze({ maxWidth: 1199 })\n});\n\n/**\n * Default image sizing configuration\n */\nexport const DEFAULT_IMAGE_SIZING: ImageSizingConfig = Object.freeze({\n mode: 'adaptive' as const, // Default to adaptive sizing\n minSize: 50, // Adaptive mode minimum\n maxSize: 400, // Adaptive mode maximum\n variance: Object.freeze({\n min: 1.0, // No variance by default\n max: 1.0\n })\n});\n\n/**\n * Default image rotation configuration\n */\nexport const DEFAULT_IMAGE_ROTATION: ImageRotationConfig = Object.freeze({\n mode: 'none' as const,\n range: Object.freeze({\n min: -15,\n max: 15\n })\n});\n\n/**\n * Default image configuration\n */\nexport const DEFAULT_IMAGE_CONFIG: ImageConfig = Object.freeze({\n sizing: DEFAULT_IMAGE_SIZING,\n rotation: DEFAULT_IMAGE_ROTATION\n});\n\n/**\n * Default configuration object\n * Frozen to prevent accidental modifications\n */\nexport const DEFAULT_CONFIG: ImageCloudConfig = Object.freeze({\n // Unified loader configuration\n loader: Object.freeze({\n type: 'googleDrive' as const,\n googleDrive: Object.freeze({\n apiKey: '', // Must be provided by user\n sources: [], // Must be provided by user\n apiEndpoint: 'https://www.googleapis.com/drive/v3/files',\n allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'],\n debugLogging: false\n }),\n static: Object.freeze({\n sources: [], // Must be provided by user\n validateUrls: true,\n validationTimeout: 5000,\n validationMethod: 'head' as const,\n failOnAllMissing: true,\n allowedExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'],\n debugLogging: false\n })\n }),\n\n // Image sizing and rotation configuration\n image: DEFAULT_IMAGE_CONFIG,\n\n // Pattern-based layout configuration\n layout: Object.freeze({\n algorithm: 'radial' as const,\n scaleDecay: 0, // No decay by default (0-1 for radial/spiral)\n responsive: DEFAULT_RESPONSIVE_BREAKPOINTS,\n targetCoverage: 0.6, // Target 60% of container area\n densityFactor: 1.0, // Default density\n spacing: Object.freeze({\n padding: 50, // padding from viewport edges\n minGap: 20 // minimum spacing between images\n }),\n debugRadials: false,\n debugCenters: false\n }),\n\n // Pattern-based animation configuration\n animation: Object.freeze({\n duration: 600, // milliseconds\n easing: Object.freeze({\n default: 'cubic-bezier(0.4, 0.0, 0.2, 1)', // smooth easing\n bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', // bounce effect\n focus: 'cubic-bezier(0.4, 0.0, 0.2, 1)' // focus/zoom easing\n }),\n queue: Object.freeze({\n enabled: true, // When false, all images display simultaneously\n interval: 150, // ms between processing queue items (when enabled)\n maxConcurrent: undefined // STUB: Not implemented yet\n }),\n performance: Object.freeze({\n useGPU: undefined, // STUB: Not implemented yet\n reduceMotion: undefined // STUB: Not implemented yet\n }),\n entry: Object.freeze({\n start: Object.freeze({\n position: 'nearest-edge' as const, // Default to nearest edge (current behavior)\n offset: 100, // pixels beyond edge\n circular: Object.freeze({\n radius: '120%', // 120% of container diagonal\n distribution: 'even' as const\n })\n }),\n timing: Object.freeze({\n duration: 600, // ms\n stagger: 150 // ms between images\n }),\n easing: 'cubic-bezier(0.25, 1, 0.5, 1)', // smooth deceleration\n path: DEFAULT_PATH_CONFIG,\n rotation: DEFAULT_ENTRY_ROTATION,\n scale: DEFAULT_ENTRY_SCALE\n })\n }),\n\n // Pattern-based interaction configuration\n interaction: Object.freeze({\n focus: Object.freeze({\n scalePercent: 0.8, // 80% of container height\n zIndex: 1000,\n animationDuration: undefined // Use default animation duration\n }),\n navigation: Object.freeze({\n keyboard: undefined, // STUB: Not implemented yet\n swipe: undefined, // STUB: Not implemented yet\n mouseWheel: undefined // STUB: Not implemented yet\n }),\n gestures: Object.freeze({\n pinchToZoom: undefined, // STUB: Not implemented yet\n doubleTapToFocus: undefined // STUB: Not implemented yet\n })\n }),\n\n // Pattern-based rendering configuration\n rendering: Object.freeze({\n responsive: Object.freeze({\n breakpoints: Object.freeze({\n mobile: 768,\n tablet: undefined, // STUB: Not implemented yet\n desktop: undefined // STUB: Not implemented yet\n }),\n mobileDetection: () => {\n if (typeof window === 'undefined') return false;\n return window.innerWidth <= 768;\n }\n }),\n ui: Object.freeze({\n showLoadingSpinner: false,\n showImageCounter: undefined, // STUB: Not implemented yet\n showThumbnails: undefined, // STUB: Not implemented yet\n theme: undefined // STUB: Not implemented yet\n }),\n performance: Object.freeze({\n lazyLoad: undefined, // STUB: Not implemented yet\n preloadCount: undefined, // STUB: Not implemented yet\n imageQuality: undefined // STUB: Not implemented yet\n })\n }),\n\n // Image styling\n styling: DEFAULT_STYLING,\n\n // Debug mode\n debug: false\n});\n\n/**\n * Deep merge a single style state (border, filter, outline, etc.)\n */\nfunction deepMergeStyleState(\n base: ImageStyleState | undefined,\n override: Partial<ImageStyleState> | undefined\n): ImageStyleState {\n if (!base) return override as ImageStyleState || {};\n if (!override) return { ...base };\n\n const merged: ImageStyleState = { ...base };\n\n // Merge border\n if (override.border !== undefined) {\n merged.border = { ...base.border, ...override.border };\n }\n\n // Merge per-side borders\n if (override.borderTop !== undefined) {\n merged.borderTop = { ...base.borderTop, ...override.borderTop };\n }\n if (override.borderRight !== undefined) {\n merged.borderRight = { ...base.borderRight, ...override.borderRight };\n }\n if (override.borderBottom !== undefined) {\n merged.borderBottom = { ...base.borderBottom, ...override.borderBottom };\n }\n if (override.borderLeft !== undefined) {\n merged.borderLeft = { ...base.borderLeft, ...override.borderLeft };\n }\n\n // Merge filter\n if (override.filter !== undefined) {\n merged.filter = { ...base.filter, ...override.filter };\n }\n\n // Merge outline\n if (override.outline !== undefined) {\n merged.outline = { ...base.outline, ...override.outline };\n }\n\n // Override simple properties\n if (override.shadow !== undefined) merged.shadow = override.shadow;\n if (override.opacity !== undefined) merged.opacity = override.opacity;\n if (override.cursor !== undefined) merged.cursor = override.cursor;\n if (override.className !== undefined) merged.className = override.className;\n if (override.objectFit !== undefined) merged.objectFit = override.objectFit;\n if (override.aspectRatio !== undefined) merged.aspectRatio = override.aspectRatio;\n\n // Override per-corner border radius\n if (override.borderRadiusTopLeft !== undefined) merged.borderRadiusTopLeft = override.borderRadiusTopLeft;\n if (override.borderRadiusTopRight !== undefined) merged.borderRadiusTopRight = override.borderRadiusTopRight;\n if (override.borderRadiusBottomRight !== undefined) merged.borderRadiusBottomRight = override.borderRadiusBottomRight;\n if (override.borderRadiusBottomLeft !== undefined) merged.borderRadiusBottomLeft = override.borderRadiusBottomLeft;\n\n return merged;\n}\n\n/**\n * Deep merge styling config with proper state inheritance\n * - hover inherits from default, then applies overrides\n * - focused inherits from default, then applies overrides\n */\nfunction deepMergeStyling(\n defaults: ImageStylingConfig,\n userStyling: Partial<ImageStylingConfig> | undefined\n): ImageStylingConfig {\n if (!userStyling) return { ...defaults };\n\n // First, merge the default state\n const mergedDefault = deepMergeStyleState(defaults.default, userStyling.default);\n\n // Hover inherits from merged default, then user hover overrides\n const mergedHover = deepMergeStyleState(\n deepMergeStyleState(mergedDefault, defaults.hover),\n userStyling.hover\n );\n\n // Focused inherits from merged default, then user focused overrides\n const mergedFocused = deepMergeStyleState(\n deepMergeStyleState(mergedDefault, defaults.focused),\n userStyling.focused\n );\n\n return {\n default: mergedDefault,\n hover: mergedHover,\n focused: mergedFocused\n };\n}\n\n/**\n * Deep merge utility for config objects\n * Merges user config with default config\n */\n/**\n * Deep merge image config with validation\n */\nfunction deepMergeImageConfig(\n defaults: ImageConfig,\n userImage: Partial<ImageConfig> | undefined\n): ImageConfig {\n if (!userImage) return { ...defaults };\n\n const merged: ImageConfig = { ...defaults };\n\n // Deep merge sizing config\n if (userImage.sizing !== undefined) {\n merged.sizing = {\n ...defaults.sizing,\n ...userImage.sizing\n };\n\n // Deep merge variance with validation (min: 0.25-1, max: 1-1.75)\n if (userImage.sizing.variance) {\n const userVariance = userImage.sizing.variance as ImageVarianceConfig;\n const validMin = userVariance.min !== undefined && userVariance.min >= 0.25 && userVariance.min <= 1\n ? userVariance.min\n : defaults.sizing?.variance?.min ?? 1.0;\n const validMax = userVariance.max !== undefined && userVariance.max >= 1 && userVariance.max <= 1.75\n ? userVariance.max\n : defaults.sizing?.variance?.max ?? 1.0;\n merged.sizing!.variance = { min: validMin, max: validMax };\n }\n }\n\n // Deep merge rotation config\n if (userImage.rotation !== undefined) {\n merged.rotation = {\n ...defaults.rotation,\n ...userImage.rotation\n };\n\n // Deep merge rotation range with validation\n if (userImage.rotation.range) {\n const userRange = userImage.rotation.range;\n const validMin = userRange.min !== undefined && userRange.min >= -180 && userRange.min <= 0\n ? userRange.min\n : defaults.rotation?.range?.min ?? -15;\n const validMax = userRange.max !== undefined && userRange.max >= 0 && userRange.max <= 180\n ? userRange.max\n : defaults.rotation?.range?.max ?? 15;\n merged.rotation!.range = { min: validMin, max: validMax };\n }\n }\n\n return merged;\n}\n\n/**\n * Convert legacy layout.rotation config to new image.rotation format\n * This provides backward compatibility with the old API\n */\nfunction convertLegacyRotationConfig(userConfig: DeepPartial<ImageCloudConfig>): Partial<ImageConfig> | undefined {\n const legacyRotation = (userConfig.layout as any)?.rotation;\n if (!legacyRotation) return undefined;\n\n // Legacy format: { enabled: boolean, range: { min, max } }\n if ('enabled' in legacyRotation) {\n return {\n rotation: {\n mode: legacyRotation.enabled ? 'random' : 'none',\n range: legacyRotation.range\n }\n };\n }\n\n return undefined;\n}\n\n/**\n * Convert legacy layout.sizing.variance config to new image.sizing.variance format\n */\nfunction convertLegacyVarianceConfig(userConfig: DeepPartial<ImageCloudConfig>): Partial<ImageConfig> | undefined {\n const legacyVariance = (userConfig.layout as any)?.sizing?.variance;\n if (!legacyVariance) return undefined;\n\n return {\n sizing: {\n mode: 'adaptive', // Legacy variance config implies adaptive mode\n variance: legacyVariance\n }\n };\n}\n\nexport function mergeConfig(\n userConfig: DeepPartial<ImageCloudConfig> = {}\n): ImageCloudConfig {\n // Convert legacy configs to new format\n const legacyRotation = convertLegacyRotationConfig(userConfig);\n const legacyVariance = convertLegacyVarianceConfig(userConfig);\n\n // Combine user image config with converted legacy configs\n // User's explicit image config takes precedence over legacy conversions\n let combinedImageConfig: Partial<ImageConfig> | undefined = userConfig.image as Partial<ImageConfig> | undefined;\n if (legacyRotation || legacyVariance) {\n combinedImageConfig = {\n ...(legacyVariance || {}),\n ...(legacyRotation || {}),\n ...combinedImageConfig\n };\n // Deep merge the rotation config if both exist\n if (combinedImageConfig.rotation && legacyRotation?.rotation && userConfig.image?.rotation) {\n combinedImageConfig.rotation = {\n ...legacyRotation.rotation,\n ...(userConfig.image as any).rotation\n };\n }\n }\n\n const merged: ImageCloudConfig = {\n loader: { ...DEFAULT_CONFIG.loader },\n image: deepMergeImageConfig(DEFAULT_IMAGE_CONFIG, combinedImageConfig),\n layout: { ...DEFAULT_CONFIG.layout },\n animation: { ...DEFAULT_CONFIG.animation },\n interaction: { ...DEFAULT_CONFIG.interaction },\n rendering: { ...DEFAULT_CONFIG.rendering },\n styling: deepMergeStyling(DEFAULT_STYLING, userConfig.styling as Partial<ImageStylingConfig> | undefined),\n debug: DEFAULT_CONFIG.debug\n };\n\n // Deep merge loader config\n if (userConfig.loader) {\n merged.loader = {\n ...DEFAULT_CONFIG.loader,\n ...userConfig.loader\n } as any;\n\n // Deep merge googleDrive config\n if (userConfig.loader.googleDrive) {\n merged.loader.googleDrive = {\n ...DEFAULT_CONFIG.loader.googleDrive!,\n ...userConfig.loader.googleDrive,\n sources: userConfig.loader.googleDrive.sources || DEFAULT_CONFIG.loader.googleDrive!.sources,\n allowedExtensions: userConfig.loader.googleDrive.allowedExtensions ||\n DEFAULT_CONFIG.loader.googleDrive!.allowedExtensions\n };\n }\n\n // Deep merge static config\n if (userConfig.loader.static) {\n merged.loader.static = {\n ...DEFAULT_CONFIG.loader.static!,\n ...userConfig.loader.static,\n sources: userConfig.loader.static.sources || DEFAULT_CONFIG.loader.static!.sources,\n allowedExtensions: userConfig.loader.static.allowedExtensions ||\n DEFAULT_CONFIG.loader.static!.allowedExtensions\n };\n }\n }\n\n // Deep merge layout config\n if (userConfig.layout) {\n merged.layout = {\n ...DEFAULT_CONFIG.layout,\n ...userConfig.layout\n } as any;\n\n // Deep merge responsive breakpoints config\n if (userConfig.layout.responsive) {\n merged.layout.responsive = {\n ...DEFAULT_CONFIG.layout.responsive!,\n mobile: userConfig.layout.responsive.mobile\n ? { ...DEFAULT_CONFIG.layout.responsive!.mobile, ...userConfig.layout.responsive.mobile }\n : DEFAULT_CONFIG.layout.responsive!.mobile,\n tablet: userConfig.layout.responsive.tablet\n ? { ...DEFAULT_CONFIG.layout.responsive!.tablet, ...userConfig.layout.responsive.tablet }\n : DEFAULT_CONFIG.layout.responsive!.tablet\n };\n }\n\n // Deep merge spacing config\n if (userConfig.layout.spacing) {\n merged.layout.spacing = {\n ...DEFAULT_CONFIG.layout.spacing,\n ...userConfig.layout.spacing\n };\n }\n }\n\n // Deep merge animation config\n if (userConfig.animation) {\n merged.animation = {\n ...DEFAULT_CONFIG.animation,\n ...userConfig.animation\n } as any;\n\n // Deep merge easing config\n if (userConfig.animation.easing) {\n merged.animation.easing = {\n ...DEFAULT_CONFIG.animation.easing,\n ...userConfig.animation.easing\n };\n }\n\n // Deep merge queue config\n if (userConfig.animation.queue) {\n merged.animation.queue = {\n ...DEFAULT_CONFIG.animation.queue,\n ...userConfig.animation.queue\n };\n }\n\n // Deep merge performance config\n if (userConfig.animation.performance) {\n merged.animation.performance = {\n ...DEFAULT_CONFIG.animation.performance,\n ...userConfig.animation.performance\n };\n }\n\n // Deep merge entry animation config\n if (userConfig.animation.entry) {\n merged.animation.entry = {\n ...DEFAULT_CONFIG.animation.entry!,\n ...userConfig.animation.entry,\n start: userConfig.animation.entry.start\n ? {\n ...DEFAULT_CONFIG.animation.entry!.start,\n ...userConfig.animation.entry.start,\n circular: userConfig.animation.entry.start.circular\n ? { ...DEFAULT_CONFIG.animation.entry!.start.circular, ...userConfig.animation.entry.start.circular }\n : DEFAULT_CONFIG.animation.entry!.start.circular\n }\n : DEFAULT_CONFIG.animation.entry!.start,\n timing: userConfig.animation.entry.timing\n ? { ...DEFAULT_CONFIG.animation.entry!.timing, ...userConfig.animation.entry.timing }\n : DEFAULT_CONFIG.animation.entry!.timing,\n path: userConfig.animation.entry.path\n ? { ...DEFAULT_PATH_CONFIG, ...userConfig.animation.entry.path }\n : DEFAULT_CONFIG.animation.entry!.path,\n rotation: userConfig.animation.entry.rotation\n ? { ...DEFAULT_ENTRY_ROTATION, ...userConfig.animation.entry.rotation }\n : DEFAULT_CONFIG.animation.entry!.rotation,\n scale: userConfig.animation.entry.scale\n ? { ...DEFAULT_ENTRY_SCALE, ...userConfig.animation.entry.scale }\n : DEFAULT_CONFIG.animation.entry!.scale\n };\n }\n }\n\n // Deep merge interaction config\n if (userConfig.interaction) {\n merged.interaction = {\n ...DEFAULT_CONFIG.interaction,\n ...userConfig.interaction\n } as any;\n\n // Deep merge focus config\n if (userConfig.interaction.focus) {\n merged.interaction.focus = {\n ...DEFAULT_CONFIG.interaction.focus,\n ...userConfig.interaction.focus\n };\n }\n\n // Deep merge navigation config\n if (userConfig.interaction.navigation) {\n merged.interaction.navigation = {\n ...DEFAULT_CONFIG.interaction.navigation,\n ...userConfig.interaction.navigation\n };\n }\n\n // Deep merge gestures config\n if (userConfig.interaction.gestures) {\n merged.interaction.gestures = {\n ...DEFAULT_CONFIG.interaction.gestures,\n ...userConfig.interaction.gestures\n };\n }\n }\n\n // Deep merge rendering config\n if (userConfig.rendering) {\n merged.rendering = {\n ...DEFAULT_CONFIG.rendering,\n ...userConfig.rendering\n } as any;\n\n // Deep merge responsive config\n if (userConfig.rendering.responsive) {\n merged.rendering.responsive = {\n ...DEFAULT_CONFIG.rendering.responsive,\n ...userConfig.rendering.responsive,\n breakpoints: userConfig.rendering.responsive.breakpoints\n ? { ...DEFAULT_CONFIG.rendering.responsive.breakpoints, ...userConfig.rendering.responsive.breakpoints }\n : DEFAULT_CONFIG.rendering.responsive.breakpoints,\n mobileDetection: userConfig.rendering.responsive.mobileDetection\n ? (userConfig.rendering.responsive.mobileDetection as () => boolean)\n : DEFAULT_CONFIG.rendering.responsive.mobileDetection\n };\n }\n\n // Deep merge ui config\n if (userConfig.rendering.ui) {\n merged.rendering.ui = {\n ...DEFAULT_CONFIG.rendering.ui,\n ...userConfig.rendering.ui\n };\n }\n\n // Deep merge performance config\n if (userConfig.rendering.performance) {\n merged.rendering.performance = {\n ...DEFAULT_CONFIG.rendering.performance,\n ...userConfig.rendering.performance\n };\n }\n }\n\n // Merge debug flag\n if (userConfig.debug !== undefined) {\n merged.debug = userConfig.debug;\n }\n\n return merged;\n}\n\n/**\n * Resolve bounce path config from preset and overrides\n */\nexport function resolveBounceConfig(\n preset?: BouncePreset,\n overrides?: Partial<BouncePathConfig>\n): BouncePathConfig {\n const base = preset ? BOUNCE_PRESETS[preset] : BOUNCE_PRESETS.playful;\n return { ...base, ...overrides };\n}\n\n/**\n * Resolve elastic path config from preset and overrides\n */\nexport function resolveElasticConfig(\n preset?: ElasticPreset,\n overrides?: Partial<ElasticPathConfig>\n): ElasticPathConfig {\n const base = preset ? ELASTIC_PRESETS[preset] : ELASTIC_PRESETS.gentle;\n return { ...base, ...overrides };\n}\n\n/**\n * Resolve wave path config from preset and overrides\n */\nexport function resolveWavePathConfig(\n preset?: WavePathPreset,\n overrides?: Partial<WavePathConfig>\n): WavePathConfig {\n const base = preset ? WAVE_PATH_PRESETS[preset] : WAVE_PATH_PRESETS.gentle;\n return { ...base, ...overrides };\n}\n\n/**\n * Debug logger\n */\nexport function debugLog(config: ImageCloudConfig, ...args: unknown[]): void {\n if (config.debug && typeof console !== 'undefined') {\n console.log(...args);\n }\n}","/**\n * AnimationEngine.ts\n * Handles smooth animations with easing for the image cloud\n *\n * Public API:\n * - animateTransform(element, properties, duration, easing)\n * - animateTransformCancellable(element, from, to, duration, easing) - Web Animations API\n * - cancelAnimation(handle, commitStyle) - Cancel and optionally keep current position\n * - getCurrentTransform(element) - Get current transform state mid-animation\n * - hasActiveAnimation(element) - Check if element has active animation\n * - resetTransform(element, originalState)\n * - clearTransition(element)\n * - wait(ms)\n */\n\nimport type { AnimationConfig, TransformParams, ImageLayout, AnimationHandle, AnimationSnapshot } from '../config/types';\n\nexport class AnimationEngine {\n private config: AnimationConfig;\n private activeAnimations: Map<HTMLElement, AnimationHandle> = new Map();\n private animationIdCounter = 0;\n\n constructor(config: AnimationConfig) {\n this.config = config;\n }\n\n /**\n * Build transform string from transform params\n * Always starts with centering transform to match image positioning system\n */\n private buildTransformString(params: TransformParams): string {\n const transforms: string[] = ['translate(-50%, -50%)'];\n\n if (params.x !== undefined || params.y !== undefined) {\n const x = params.x ?? 0;\n const y = params.y ?? 0;\n transforms.push(`translate(${x}px, ${y}px)`);\n }\n\n if (params.rotation !== undefined) {\n transforms.push(`rotate(${params.rotation}deg)`);\n }\n\n if (params.scale !== undefined) {\n transforms.push(`scale(${params.scale})`);\n }\n\n return transforms.join(' ');\n }\n\n /**\n * Start a cancellable transform animation using Web Animations API\n * @param element - The element to animate\n * @param from - Starting transform state\n * @param to - Ending transform state\n * @param duration - Animation duration in ms (optional)\n * @param easing - CSS easing function (optional)\n * @returns AnimationHandle that can be used to cancel or query the animation\n */\n animateTransformCancellable(\n element: HTMLElement,\n from: TransformParams,\n to: TransformParams,\n duration: number | null = null,\n easing: string | null = null\n ): AnimationHandle {\n // Cancel any existing animation on this element\n this.cancelAllAnimations(element);\n\n const animDuration = duration ?? this.config.duration;\n const animEasing = easing ?? this.config.easing.default;\n\n const fromTransform = this.buildTransformString(from);\n const toTransform = this.buildTransformString(to);\n\n // Clear any CSS transitions to avoid conflicts\n element.style.transition = 'none';\n\n // Create Web Animation\n const animation = element.animate(\n [\n { transform: fromTransform },\n { transform: toTransform }\n ],\n {\n duration: animDuration,\n easing: animEasing,\n fill: 'forwards' // Keep final state after animation\n }\n );\n\n const handle: AnimationHandle = {\n id: `anim-${++this.animationIdCounter}`,\n element,\n animation,\n fromState: from,\n toState: to,\n startTime: performance.now(),\n duration: animDuration\n };\n\n this.activeAnimations.set(element, handle);\n\n // Clean up when animation finishes (normally or cancelled)\n animation.finished\n .then(() => {\n // Apply final transform as inline style for consistency\n element.style.transform = toTransform;\n this.activeAnimations.delete(element);\n })\n .catch(() => {\n // Animation was cancelled - cleanup handled by cancelAnimation\n this.activeAnimations.delete(element);\n });\n\n return handle;\n }\n\n /**\n * Cancel an active animation\n * @param handle - The animation handle to cancel\n * @param commitStyle - If true, keeps current position; if false, no style change\n * @returns Snapshot of where the animation was when cancelled\n */\n cancelAnimation(handle: AnimationHandle, commitStyle: boolean = true): AnimationSnapshot {\n const snapshot = this.getCurrentTransform(handle.element);\n\n // Cancel the Web Animation\n handle.animation.cancel();\n\n if (commitStyle) {\n // Apply current position as inline style\n const currentTransform = this.buildTransformString({\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: snapshot.scale\n });\n handle.element.style.transform = currentTransform;\n }\n\n this.activeAnimations.delete(handle.element);\n\n return snapshot;\n }\n\n /**\n * Cancel all animations on an element\n * Uses Web Animations API to find and cancel ALL animations, not just tracked ones\n * @param element - The element to cancel animations for\n */\n cancelAllAnimations(element: HTMLElement): void {\n // Cancel tracked animation\n const handle = this.activeAnimations.get(element);\n if (handle) {\n this.cancelAnimation(handle, false);\n }\n\n // Also cancel any other animations on the element (e.g., completed animations with fill: 'forwards')\n const allAnimations = element.getAnimations();\n for (const anim of allAnimations) {\n anim.cancel();\n }\n }\n\n /**\n * Get current transform state of an element (works mid-animation)\n * Uses DOMMatrix to parse the computed transform\n * @param element - The element to query\n * @returns Current transform snapshot\n */\n getCurrentTransform(element: HTMLElement): AnimationSnapshot {\n const computed = getComputedStyle(element);\n const transformStr = computed.transform;\n\n if (transformStr === 'none' || !transformStr) {\n return { x: 0, y: 0, rotation: 0, scale: 1 };\n }\n\n const matrix = new DOMMatrix(transformStr);\n\n // Extract scale from matrix (for uniform scale)\n const scale = Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b);\n\n // Extract rotation from matrix (in degrees)\n const rotation = Math.atan2(matrix.b, matrix.a) * (180 / Math.PI);\n\n // Extract translation\n // Note: matrix.e and matrix.f include ALL translations including the centering offset\n // The centering is translate(-50%, -50%) which depends on element dimensions\n // Since our transform chain is: translate(-50%, -50%) translate(x, y) rotate scale\n // The additional x,y offset we applied is already baked into e,f along with centering\n // For cross-animation, we need the relative offset, not absolute position\n // We'll return e,f directly and let ZoomEngine handle the interpretation\n const x = matrix.e;\n const y = matrix.f;\n\n return { x, y, rotation, scale };\n }\n\n /**\n * Check if an element has an active animation\n * @param element - The element to check\n * @returns True if animation is in progress\n */\n hasActiveAnimation(element: HTMLElement): boolean {\n return this.activeAnimations.has(element);\n }\n\n /**\n * Get animation handle for an element if it exists\n * @param element - The element to query\n * @returns AnimationHandle or undefined\n */\n getAnimationHandle(element: HTMLElement): AnimationHandle | undefined {\n return this.activeAnimations.get(element);\n }\n\n /**\n * Animate element transform with smooth easing (CSS transitions - legacy method)\n * @param element - The element to animate\n * @param properties - Transform properties {x, y, rotation, scale}\n * @param duration - Animation duration in ms (optional)\n * @param easing - CSS easing function (optional)\n * @returns Promise that resolves when animation completes\n */\n animateTransform(\n element: HTMLElement,\n properties: TransformParams,\n duration: number | null = null,\n easing: string | null = null\n ): Promise<void> {\n return new Promise((resolve) => {\n const animDuration = duration ?? this.config.duration;\n const animEasing = easing ?? this.config.easing.default;\n\n // Apply transition\n element.style.transition = `transform ${animDuration}ms ${animEasing}, box-shadow ${animDuration}ms ${animEasing}`;\n\n // Apply transform using shared helper\n element.style.transform = this.buildTransformString(properties);\n\n // Resolve promise when animation completes\n setTimeout(() => {\n resolve();\n }, animDuration);\n });\n }\n\n /**\n * Reset element to its original transform\n * @param element - The element to reset\n * @param originalState - Original transform state {x, y, rotation, scale}\n * @returns Promise that resolves when animation completes\n */\n resetTransform(element: HTMLElement, originalState: TransformParams | ImageLayout): Promise<void> {\n return this.animateTransform(element, originalState);\n }\n\n /**\n * Remove transition styles from element\n * @param element - The element to clear\n */\n clearTransition(element: HTMLElement): void {\n element.style.transition = '';\n }\n\n /**\n * Utility: Wait for a specified duration\n * @param ms - Milliseconds to wait\n * @returns Promise that resolves after the specified duration\n */\n wait(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}","/**\n * PathAnimator.ts\n * Provides path calculation functions for bounce, elastic, and wave entry animations\n *\n * These animations require JavaScript-driven frame updates because they involve\n * complex mathematical curves that can't be expressed with CSS transitions alone.\n *\n * Public API:\n * - animatePath(element, startPos, endPos, pathConfig, duration, onComplete)\n * - calculateBouncePosition(t, start, end, config)\n * - calculateElasticPosition(t, start, end, config)\n * - calculateWavePosition(t, start, end, config)\n */\n\nimport type {\n BouncePathConfig,\n ElasticPathConfig,\n WavePathConfig,\n EntryPathConfig,\n EntryPathType,\n EntryRotationConfig,\n EntryScaleConfig\n} from '../config/types';\nimport {\n resolveBounceConfig,\n resolveElasticConfig,\n resolveWavePathConfig\n} from '../config/defaults';\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface PathAnimationOptions {\n element: HTMLElement;\n startPosition: Point;\n endPosition: Point;\n pathConfig: EntryPathConfig;\n duration: number;\n imageWidth: number;\n imageHeight: number;\n rotation: number; // Final rotation\n scale: number; // Final scale\n onComplete?: () => void;\n // Rotation animation options\n rotationConfig?: EntryRotationConfig;\n startRotation?: number; // Starting rotation (if different from final)\n // Scale animation options\n scaleConfig?: EntryScaleConfig;\n startScale?: number; // Starting scale (if different from final)\n}\n\n/**\n * Linear interpolation helper\n */\nfunction lerp(start: number, end: number, t: number): number {\n return start + (end - start) * t;\n}\n\n/**\n * Calculate position along a bounce path\n * Overshoot and settle animation\n */\nexport function calculateBouncePosition(\n t: number,\n start: Point,\n end: Point,\n config: BouncePathConfig\n): Point {\n const { overshoot, bounces, decayRatio } = config;\n\n // Direction vector from start to end\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n\n // Calculate keyframe timings based on number of bounces\n const keyframes = generateBounceKeyframes(bounces, decayRatio);\n\n // Find current segment\n let progress = 0;\n let segmentStart = 0;\n let segmentEnd = 1;\n let segmentOvershoot = overshoot;\n let isOvershootPhase = false;\n\n for (let i = 0; i < keyframes.length; i++) {\n if (t <= keyframes[i].time) {\n segmentStart = i === 0 ? 0 : keyframes[i - 1].time;\n segmentEnd = keyframes[i].time;\n segmentOvershoot = keyframes[i].overshoot;\n isOvershootPhase = keyframes[i].isOvershoot;\n break;\n }\n }\n\n // Calculate progress within current segment\n const segmentT = (t - segmentStart) / (segmentEnd - segmentStart);\n\n if (isOvershootPhase) {\n // Ease out into overshoot\n progress = 1 + segmentOvershoot * easeOutQuad(segmentT);\n } else if (segmentStart === 0) {\n // Initial travel to target - ease out\n progress = easeOutQuad(segmentT);\n } else {\n // Settling back from overshoot\n const prevOvershoot = keyframes.find((k, i) =>\n k.time > segmentStart && i > 0 && keyframes[i - 1].isOvershoot\n );\n const fromProgress = 1 + (prevOvershoot?.overshoot || segmentOvershoot);\n progress = lerp(fromProgress, 1, easeOutQuad(segmentT));\n }\n\n return {\n x: start.x + dx * progress,\n y: start.y + dy * progress\n };\n}\n\n/**\n * Generate keyframe timings for bounce animation\n */\nfunction generateBounceKeyframes(\n bounces: number,\n decayRatio: number\n): Array<{ time: number; overshoot: number; isOvershoot: boolean }> {\n const keyframes: Array<{ time: number; overshoot: number; isOvershoot: boolean }> = [];\n\n // Initial travel takes 60% of time\n let currentTime = 0.6;\n keyframes.push({ time: currentTime, overshoot: 0, isOvershoot: false });\n\n let currentOvershoot = 0.15; // Initial overshoot amount\n const remainingTime = 0.4;\n const bounceTime = remainingTime / (bounces * 2);\n\n for (let i = 0; i < bounces; i++) {\n // Overshoot phase\n currentTime += bounceTime;\n keyframes.push({ time: currentTime, overshoot: currentOvershoot, isOvershoot: true });\n\n // Settle phase\n currentTime += bounceTime;\n keyframes.push({ time: currentTime, overshoot: currentOvershoot * decayRatio, isOvershoot: false });\n\n currentOvershoot *= decayRatio;\n }\n\n // Final settle\n keyframes.push({ time: 1, overshoot: 0, isOvershoot: false });\n\n return keyframes;\n}\n\n/**\n * Calculate position along an elastic path\n * Spring-like oscillation animation\n */\nexport function calculateElasticPosition(\n t: number,\n start: Point,\n end: Point,\n config: ElasticPathConfig\n): Point {\n const { stiffness, damping, mass, oscillations } = config;\n\n // Direction vector from start to end\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n\n // Normalized spring physics\n // Natural frequency based on stiffness and mass\n const omega = Math.sqrt(stiffness / mass);\n\n // Damping ratio (normalized)\n const zeta = damping / (2 * Math.sqrt(stiffness * mass));\n\n // Progress using damped harmonic oscillator\n let progress: number;\n\n if (zeta < 1) {\n // Underdamped - oscillates\n const dampedFreq = omega * Math.sqrt(1 - zeta * zeta);\n const envelope = Math.exp(-zeta * omega * t * 3);\n const oscillation = Math.cos(dampedFreq * t * oscillations * Math.PI);\n progress = 1 - envelope * oscillation;\n } else {\n // Critically damped or overdamped - no oscillation\n progress = 1 - Math.exp(-omega * t * 3);\n }\n\n // Clamp progress\n progress = Math.max(0, Math.min(progress, 1.3)); // Allow slight overshoot\n\n return {\n x: start.x + dx * progress,\n y: start.y + dy * progress\n };\n}\n\n/**\n * Calculate position along a wave path\n * Sinusoidal path from start to end\n */\nexport function calculateWavePosition(\n t: number,\n start: Point,\n end: Point,\n config: WavePathConfig\n): Point {\n const { amplitude, frequency, decay, decayRate, phase } = config;\n\n // Direction vector from start to end\n const dx = end.x - start.x;\n const dy = end.y - start.y;\n const length = Math.sqrt(dx * dx + dy * dy);\n\n // Perpendicular vector (normalized)\n const perpX = length > 0 ? -dy / length : 0;\n const perpY = length > 0 ? dx / length : 1;\n\n // Wave calculation\n const wavePhase = frequency * Math.PI * 2 * t + phase;\n const decayFactor = decay ? Math.pow(1 - t, decayRate) : 1;\n const waveOffset = amplitude * Math.sin(wavePhase) * decayFactor;\n\n // Ease out for smooth arrival\n const progressT = easeOutCubic(t);\n\n // Linear interpolation + wave offset\n return {\n x: lerp(start.x, end.x, progressT) + waveOffset * perpX,\n y: lerp(start.y, end.y, progressT) + waveOffset * perpY\n };\n}\n\n/**\n * Easing functions\n */\nfunction easeOutQuad(t: number): number {\n return 1 - (1 - t) * (1 - t);\n}\n\nfunction easeOutCubic(t: number): number {\n return 1 - Math.pow(1 - t, 3);\n}\n\n/**\n * Calculate wobble rotation for a given animation progress\n */\nfunction calculateWobbleRotation(\n progress: number,\n finalRotation: number,\n wobbleConfig: { amplitude: number; frequency: number; decay: boolean }\n): number {\n const { amplitude, frequency, decay } = wobbleConfig;\n\n // Oscillation using sine wave\n const oscillation = Math.sin(progress * frequency * Math.PI * 2);\n\n // Apply decay if enabled (stronger decay toward end)\n const decayFactor = decay ? Math.pow(1 - progress, 2) : 1;\n\n // Calculate wobble offset\n const wobbleOffset = amplitude * oscillation * decayFactor;\n\n return finalRotation + wobbleOffset;\n}\n\n/**\n * Calculate pop scale for a given animation progress\n */\nfunction calculatePopScale(\n progress: number,\n finalScale: number,\n popConfig: { overshoot: number; bounces: number }\n): number {\n const { overshoot, bounces } = popConfig;\n\n // Generate keyframes for bounce effect\n const keyframes: Array<{ time: number; scale: number }> = [];\n\n // Reach overshoot at 50% of animation\n keyframes.push({ time: 0.5, scale: overshoot });\n\n // Add bounces\n let currentOvershoot = overshoot;\n const bounceDecay = 0.5;\n const remainingTime = 0.5;\n const bounceTime = remainingTime / (bounces * 2);\n\n let currentTime = 0.5;\n for (let i = 0; i < bounces; i++) {\n const undershoot = 1 - (currentOvershoot - 1) * bounceDecay;\n currentTime += bounceTime;\n keyframes.push({ time: currentTime, scale: undershoot });\n\n currentOvershoot = 1 + (currentOvershoot - 1) * bounceDecay * bounceDecay;\n currentTime += bounceTime;\n if (i < bounces - 1) {\n keyframes.push({ time: currentTime, scale: currentOvershoot });\n }\n }\n\n keyframes.push({ time: 1, scale: 1 });\n\n // Find current segment\n let currentScale = 1;\n for (let i = 0; i < keyframes.length; i++) {\n if (progress <= keyframes[i].time) {\n const prevTime = i === 0 ? 0 : keyframes[i - 1].time;\n const prevScale = i === 0 ? 1 : keyframes[i - 1].scale;\n const segmentProgress = (progress - prevTime) / (keyframes[i].time - prevTime);\n const easedProgress = easeOutQuad(segmentProgress);\n currentScale = prevScale + (keyframes[i].scale - prevScale) * easedProgress;\n break;\n }\n }\n\n return currentScale * finalScale;\n}\n\n/**\n * Animate an element along a path using requestAnimationFrame\n */\nexport function animatePath(options: PathAnimationOptions): void {\n const {\n element,\n startPosition,\n endPosition,\n pathConfig,\n duration,\n imageWidth,\n imageHeight,\n rotation: finalRotation,\n scale: finalScale,\n onComplete,\n rotationConfig,\n startRotation,\n scaleConfig,\n startScale\n } = options;\n\n const pathType = pathConfig.type;\n\n // Determine if we need to animate rotation\n const animateRotation = startRotation !== undefined && startRotation !== finalRotation;\n const isWobbleMode = rotationConfig?.mode === 'wobble';\n const wobbleConfig = rotationConfig?.wobble || { amplitude: 15, frequency: 3, decay: true };\n const needsRotationAnimation = animateRotation || isWobbleMode;\n\n // Determine if we need to animate scale\n const animateScale = startScale !== undefined && startScale !== finalScale;\n const isPopMode = scaleConfig?.mode === 'pop';\n const popConfig = scaleConfig?.pop || { overshoot: 1.2, bounces: 1 };\n const needsScaleAnimation = animateScale || isPopMode;\n\n // For linear/arc paths WITHOUT rotation or scale animation, use CSS transitions (handled elsewhere)\n if ((pathType === 'linear' || pathType === 'arc') && !needsRotationAnimation && !needsScaleAnimation) {\n if (onComplete) onComplete();\n return;\n }\n\n const startTime = performance.now();\n\n // Build center offset for transform\n const centerOffsetX = -imageWidth / 2;\n const centerOffsetY = -imageHeight / 2;\n\n function tick(currentTime: number): void {\n const elapsed = currentTime - startTime;\n const t = Math.min(elapsed / duration, 1);\n\n // Calculate position based on path type\n let position: Point;\n\n switch (pathType) {\n case 'bounce': {\n const config = resolveBounceConfig(\n pathConfig.bouncePreset,\n pathConfig.bounce\n );\n position = calculateBouncePosition(t, startPosition, endPosition, config);\n break;\n }\n case 'elastic': {\n const config = resolveElasticConfig(\n pathConfig.elasticPreset,\n pathConfig.elastic\n );\n position = calculateElasticPosition(t, startPosition, endPosition, config);\n break;\n }\n case 'wave': {\n const config = resolveWavePathConfig(\n pathConfig.wavePreset,\n pathConfig.wave\n );\n position = calculateWavePosition(t, startPosition, endPosition, config);\n break;\n }\n default:\n position = {\n x: lerp(startPosition.x, endPosition.x, t),\n y: lerp(startPosition.y, endPosition.y, t)\n };\n }\n\n // Calculate translate offset from final position\n const translateX = position.x - endPosition.x;\n const translateY = position.y - endPosition.y;\n\n // Calculate current rotation\n let currentRotation: number;\n if (isWobbleMode) {\n currentRotation = calculateWobbleRotation(t, finalRotation, wobbleConfig);\n } else if (animateRotation) {\n currentRotation = lerp(startRotation!, finalRotation, t);\n } else {\n currentRotation = finalRotation;\n }\n\n // Calculate current scale\n let currentScale: number;\n if (isPopMode) {\n currentScale = calculatePopScale(t, finalScale, popConfig);\n } else if (animateScale) {\n currentScale = lerp(startScale!, finalScale, t);\n } else {\n currentScale = finalScale;\n }\n\n // Apply transform\n element.style.transform =\n `translate(${centerOffsetX}px, ${centerOffsetY}px) ` +\n `translate(${translateX}px, ${translateY}px) ` +\n `rotate(${currentRotation}deg) scale(${currentScale})`;\n\n if (t < 1) {\n requestAnimationFrame(tick);\n } else {\n // Ensure we end exactly at the final position, rotation, and scale\n element.style.transform =\n `translate(${centerOffsetX}px, ${centerOffsetY}px) ` +\n `rotate(${finalRotation}deg) scale(${finalScale})`;\n if (onComplete) onComplete();\n }\n }\n\n requestAnimationFrame(tick);\n}\n\n/**\n * Check if a path type requires JavaScript animation (vs CSS transitions)\n */\nexport function requiresJSAnimation(pathType: EntryPathType): boolean {\n return pathType === 'bounce' || pathType === 'elastic' || pathType === 'wave';\n}\n\n/**\n * Get CSS easing for bounce (approximation for simple bounce)\n * Note: This is only used as a fallback; full bounce uses JS animation\n */\nexport function getBounceCSSEasing(): string {\n return 'cubic-bezier(0.68, -0.55, 0.265, 1.55)';\n}\n","/**\n * EntryAnimationEngine.ts\n * Calculates starting positions for entry animations based on configuration\n *\n * Public API:\n * - calculateStartPosition(finalPosition, imageSize, containerBounds, imageIndex, totalImages)\n * - getAnimationParams(imageIndex)\n */\n\nimport type {\n EntryAnimationConfig,\n EntryStartPosition,\n ContainerBounds,\n LayoutAlgorithm,\n EntryPathConfig,\n EntryPathType,\n EntryRotationConfig,\n EntryRotationMode,\n EntryScaleConfig,\n EntryScaleMode\n} from '../config/types';\nimport { DEFAULT_PATH_CONFIG, DEFAULT_ENTRY_ROTATION, DEFAULT_ENTRY_SCALE } from '../config/defaults';\nimport { requiresJSAnimation } from './PathAnimator';\n\n/** Layout-aware default start positions */\nconst LAYOUT_ENTRY_DEFAULTS: Record<LayoutAlgorithm, EntryStartPosition> = {\n radial: 'center',\n spiral: 'center',\n grid: 'top',\n cluster: 'nearest-edge',\n random: 'nearest-edge',\n wave: 'left'\n};\n\nexport interface StartPosition {\n x: number;\n y: number;\n useScale?: boolean; // For center position, start with scale 0\n}\n\nexport interface AnimationParams {\n startTransform: string;\n duration: number;\n delay: number;\n easing: string;\n}\n\nexport class EntryAnimationEngine {\n private config: EntryAnimationConfig;\n private layoutAlgorithm: LayoutAlgorithm;\n private resolvedStartPosition: EntryStartPosition;\n private pathConfig: EntryPathConfig;\n private rotationConfig: EntryRotationConfig;\n private scaleConfig: EntryScaleConfig;\n\n constructor(config: EntryAnimationConfig, layoutAlgorithm: LayoutAlgorithm) {\n this.config = config;\n this.layoutAlgorithm = layoutAlgorithm;\n\n // Resolve the start position, using layout-aware defaults if not specified\n this.resolvedStartPosition = this.resolveStartPosition();\n\n // Resolve path config\n this.pathConfig = config.path || DEFAULT_PATH_CONFIG;\n\n // Resolve rotation config\n this.rotationConfig = config.rotation || DEFAULT_ENTRY_ROTATION;\n\n // Resolve scale config\n this.scaleConfig = config.scale || DEFAULT_ENTRY_SCALE;\n }\n\n /**\n * Get the effective start position, considering layout-aware defaults\n */\n private resolveStartPosition(): EntryStartPosition {\n // If explicitly configured, use that\n if (this.config.start.position) {\n return this.config.start.position;\n }\n // Otherwise use layout-aware default\n return LAYOUT_ENTRY_DEFAULTS[this.layoutAlgorithm] || 'nearest-edge';\n }\n\n /**\n * Calculate the starting position for an image's entry animation\n */\n calculateStartPosition(\n finalPosition: { x: number; y: number },\n imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n imageIndex: number,\n totalImages: number\n ): StartPosition {\n const position = this.resolvedStartPosition;\n const offset = this.config.start.offset ?? 100;\n\n switch (position) {\n case 'nearest-edge':\n return this.calculateNearestEdge(finalPosition, imageSize, containerBounds, offset);\n\n case 'top':\n return this.calculateEdgePosition('top', finalPosition, imageSize, containerBounds, offset);\n\n case 'bottom':\n return this.calculateEdgePosition('bottom', finalPosition, imageSize, containerBounds, offset);\n\n case 'left':\n return this.calculateEdgePosition('left', finalPosition, imageSize, containerBounds, offset);\n\n case 'right':\n return this.calculateEdgePosition('right', finalPosition, imageSize, containerBounds, offset);\n\n case 'center':\n return this.calculateCenterPosition(containerBounds, finalPosition, imageSize);\n\n case 'random-edge':\n return this.calculateRandomEdge(finalPosition, imageSize, containerBounds, offset);\n\n case 'circular':\n return this.calculateCircularPosition(\n finalPosition,\n imageSize,\n containerBounds,\n imageIndex,\n totalImages\n );\n\n default:\n return this.calculateNearestEdge(finalPosition, imageSize, containerBounds, offset);\n }\n }\n\n /**\n * Calculate start position from the nearest edge (current default behavior)\n */\n private calculateNearestEdge(\n finalPosition: { x: number; y: number },\n imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n offset: number\n ): StartPosition {\n // finalPosition now stores center position directly\n const centerX = finalPosition.x;\n const centerY = finalPosition.y;\n\n const distLeft = centerX;\n const distRight = containerBounds.width - centerX;\n const distTop = centerY;\n const distBottom = containerBounds.height - centerY;\n\n const minDist = Math.min(distLeft, distRight, distTop, distBottom);\n\n let startX = finalPosition.x;\n let startY = finalPosition.y;\n\n if (minDist === distLeft) {\n // Start from left edge\n startX = -(imageSize.width + offset);\n } else if (minDist === distRight) {\n // Start from right edge\n startX = containerBounds.width + offset;\n } else if (minDist === distTop) {\n // Start from top edge\n startY = -(imageSize.height + offset);\n } else {\n // Start from bottom edge\n startY = containerBounds.height + offset;\n }\n\n return { x: startX, y: startY };\n }\n\n /**\n * Calculate start position from a specific edge\n */\n private calculateEdgePosition(\n edge: 'top' | 'bottom' | 'left' | 'right',\n finalPosition: { x: number; y: number },\n imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n offset: number\n ): StartPosition {\n let startX = finalPosition.x;\n let startY = finalPosition.y;\n\n switch (edge) {\n case 'top':\n startY = -(imageSize.height + offset);\n break;\n case 'bottom':\n startY = containerBounds.height + offset;\n break;\n case 'left':\n startX = -(imageSize.width + offset);\n break;\n case 'right':\n startX = containerBounds.width + offset;\n break;\n }\n\n return { x: startX, y: startY };\n }\n\n /**\n * Calculate start position from center with scale animation\n */\n private calculateCenterPosition(\n containerBounds: ContainerBounds,\n _finalPosition: { x: number; y: number },\n _imageSize: { width: number; height: number }\n ): StartPosition {\n // Start at center of container (using center position, not top-left)\n const centerX = containerBounds.width / 2;\n const centerY = containerBounds.height / 2;\n\n return {\n x: centerX,\n y: centerY,\n useScale: true // Signal to use scale animation from 0\n };\n }\n\n /**\n * Calculate start position from a random edge\n */\n private calculateRandomEdge(\n finalPosition: { x: number; y: number },\n imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n offset: number\n ): StartPosition {\n const edges: ('top' | 'bottom' | 'left' | 'right')[] = ['top', 'bottom', 'left', 'right'];\n const randomEdge = edges[Math.floor(Math.random() * edges.length)];\n return this.calculateEdgePosition(randomEdge, finalPosition, imageSize, containerBounds, offset);\n }\n\n /**\n * Calculate start position on a circle around the container\n */\n private calculateCircularPosition(\n _finalPosition: { x: number; y: number },\n _imageSize: { width: number; height: number },\n containerBounds: ContainerBounds,\n imageIndex: number,\n totalImages: number\n ): StartPosition {\n const circularConfig = this.config.start.circular || {};\n const distribution = circularConfig.distribution || 'even';\n\n // Calculate radius\n let radius: number;\n const radiusConfig = circularConfig.radius || '120%';\n\n if (typeof radiusConfig === 'string' && radiusConfig.endsWith('%')) {\n // Percentage of container diagonal\n const percentage = parseFloat(radiusConfig) / 100;\n const diagonal = Math.sqrt(\n containerBounds.width ** 2 + containerBounds.height ** 2\n );\n radius = diagonal * percentage / 2;\n } else {\n radius = typeof radiusConfig === 'number' ? radiusConfig : 500;\n }\n\n // Calculate angle\n let angle: number;\n if (distribution === 'even') {\n angle = (imageIndex / totalImages) * 2 * Math.PI;\n } else {\n angle = Math.random() * 2 * Math.PI;\n }\n\n // Calculate position on circle, centered on container\n const centerX = containerBounds.width / 2;\n const centerY = containerBounds.height / 2;\n\n // Return center position on circle (not top-left)\n const startX = centerX + Math.cos(angle) * radius;\n const startY = centerY + Math.sin(angle) * radius;\n\n return { x: startX, y: startY };\n }\n\n /**\n * Get animation parameters for an image\n */\n getAnimationParams(imageIndex: number): AnimationParams {\n const duration = this.config.timing.duration;\n const stagger = this.config.timing.stagger;\n const easing = this.config.easing;\n\n return {\n startTransform: '', // Will be computed by caller based on start position\n duration,\n delay: imageIndex * stagger,\n easing\n };\n }\n\n /**\n * Build a CSS transform string for the start position\n * Uses pixel-based centering offset for reliable cross-browser behavior\n */\n buildStartTransform(\n startPosition: StartPosition,\n finalPosition: { x: number; y: number },\n finalRotation: number,\n finalScale: number,\n imageWidth?: number,\n imageHeight?: number,\n startRotation?: number,\n startScale?: number\n ): string {\n // Calculate translation from final to start position\n const translateX = startPosition.x - finalPosition.x;\n const translateY = startPosition.y - finalPosition.y;\n\n // Use start rotation if provided, otherwise use final rotation\n const rotation = startRotation !== undefined ? startRotation : finalRotation;\n\n // Use start scale if provided, otherwise use final scale\n const scale = startScale !== undefined ? startScale : finalScale;\n\n // Use pixel offset if dimensions provided\n const centerOffsetX = imageWidth !== undefined ? -imageWidth / 2 : 0;\n const centerOffsetY = imageHeight !== undefined ? -imageHeight / 2 : 0;\n const centerTranslate = imageWidth !== undefined\n ? `translate(${centerOffsetX}px, ${centerOffsetY}px)`\n : `translate(-50%, -50%)`;\n\n if (startPosition.useScale) {\n // For center position: start at center with scale 0\n return `${centerTranslate} translate(${translateX}px, ${translateY}px) rotate(${rotation}deg) scale(0)`;\n }\n\n // Standard entry: translate from edge, with configured start scale\n return `${centerTranslate} translate(${translateX}px, ${translateY}px) rotate(${rotation}deg) scale(${scale})`;\n }\n\n /**\n * Build the final CSS transform string\n * Uses pixel-based centering offset for reliable cross-browser behavior\n */\n buildFinalTransform(rotation: number, scale: number, imageWidth?: number, imageHeight?: number): string {\n // Use pixel offset if dimensions provided, otherwise fall back to percentage\n if (imageWidth !== undefined && imageHeight !== undefined) {\n const offsetX = -imageWidth / 2;\n const offsetY = -imageHeight / 2;\n return `translate(${offsetX}px, ${offsetY}px) rotate(${rotation}deg) scale(${scale})`;\n }\n return `translate(-50%, -50%) rotate(${rotation}deg) scale(${scale})`;\n }\n\n /**\n * Get the transition CSS for entry animation\n * For JS-animated paths, only animate opacity (transform handled by JS)\n */\n getTransitionCSS(): string {\n const duration = this.config.timing.duration;\n const easing = this.config.easing;\n\n // For paths that use JS animation, don't transition transform\n if (this.requiresJSAnimation()) {\n return `opacity ${duration}ms ease-out`;\n }\n\n return `opacity ${duration}ms ease-out, transform ${duration}ms ${easing}`;\n }\n\n /**\n * Check if the current path type requires JavaScript animation\n */\n requiresJSAnimation(): boolean {\n return requiresJSAnimation(this.pathConfig.type);\n }\n\n /**\n * Get the path configuration\n */\n getPathConfig(): EntryPathConfig {\n return this.pathConfig;\n }\n\n /**\n * Get the path type\n */\n getPathType(): EntryPathType {\n return this.pathConfig.type;\n }\n\n /**\n * Get animation timing configuration\n */\n getTiming(): { duration: number; stagger: number } {\n return {\n duration: this.config.timing.duration,\n stagger: this.config.timing.stagger\n };\n }\n\n /**\n * Get the rotation configuration\n */\n getRotationConfig(): EntryRotationConfig {\n return this.rotationConfig;\n }\n\n /**\n * Get the rotation mode\n */\n getRotationMode(): EntryRotationMode {\n return this.rotationConfig.mode;\n }\n\n /**\n * Calculate the starting rotation for an entry animation\n * @param finalRotation - The final rotation from the layout\n * @returns The starting rotation in degrees\n */\n calculateStartRotation(finalRotation: number): number {\n const mode = this.rotationConfig.mode;\n\n switch (mode) {\n case 'none':\n // No rotation animation - start at final rotation\n return finalRotation;\n\n case 'settle': {\n // Start at a configured rotation and settle to final\n const startConfig = this.rotationConfig.startRotation;\n if (startConfig === undefined) {\n // Default: ±30° random offset from final\n return finalRotation + (Math.random() - 0.5) * 60;\n }\n if (typeof startConfig === 'number') {\n return startConfig;\n }\n // Range: random value between min and max\n const range = startConfig.max - startConfig.min;\n return startConfig.min + Math.random() * range;\n }\n\n case 'spin': {\n // Spin from a rotated position to final\n const spinCount = this.rotationConfig.spinCount ?? 1;\n const direction = this.resolveSpinDirection(finalRotation);\n return finalRotation + (spinCount * 360 * direction);\n }\n\n case 'random':\n // Random starting rotation (±30° from final)\n return finalRotation + (Math.random() - 0.5) * 60;\n\n case 'wobble':\n // Wobble is handled in JS animation, start at final rotation\n return finalRotation;\n\n default:\n return finalRotation;\n }\n }\n\n /**\n * Resolve spin direction based on config\n * @returns 1 for clockwise, -1 for counterclockwise\n */\n private resolveSpinDirection(finalRotation: number): number {\n const direction = this.rotationConfig.direction ?? 'auto';\n\n switch (direction) {\n case 'clockwise':\n return -1; // Negative rotation = clockwise spin to final\n case 'counterclockwise':\n return 1; // Positive rotation = counterclockwise spin to final\n case 'random':\n return Math.random() < 0.5 ? 1 : -1;\n case 'auto':\n default:\n // Auto: choose direction that reduces total rotation distance\n // If final rotation is positive, spin counterclockwise (add positive offset)\n return finalRotation >= 0 ? 1 : -1;\n }\n }\n\n /**\n * Check if the current rotation mode requires JavaScript animation\n * (as opposed to CSS transitions)\n */\n requiresJSRotation(): boolean {\n return this.rotationConfig.mode === 'wobble';\n }\n\n /**\n * Calculate wobble rotation for a given animation progress\n * @param progress - Animation progress from 0 to 1\n * @param finalRotation - The final rotation in degrees\n * @returns The current rotation in degrees\n */\n calculateWobbleRotation(progress: number, finalRotation: number): number {\n if (this.rotationConfig.mode !== 'wobble') {\n return finalRotation;\n }\n\n const wobbleConfig = this.rotationConfig.wobble || {\n amplitude: 15,\n frequency: 3,\n decay: true\n };\n\n const { amplitude, frequency, decay } = wobbleConfig;\n\n // Oscillation using sine wave\n const oscillation = Math.sin(progress * frequency * Math.PI * 2);\n\n // Apply decay if enabled (stronger decay toward end)\n const decayFactor = decay ? Math.pow(1 - progress, 2) : 1;\n\n // Calculate wobble offset\n const wobbleOffset = amplitude * oscillation * decayFactor;\n\n return finalRotation + wobbleOffset;\n }\n\n /**\n * Get the scale configuration\n */\n getScaleConfig(): EntryScaleConfig {\n return this.scaleConfig;\n }\n\n /**\n * Get the scale mode\n */\n getScaleMode(): EntryScaleMode {\n return this.scaleConfig.mode;\n }\n\n /**\n * Calculate the starting scale for an entry animation\n * @param finalScale - The final scale from the layout\n * @returns The starting scale\n */\n calculateStartScale(finalScale: number): number {\n const mode = this.scaleConfig.mode;\n\n switch (mode) {\n case 'none':\n // No scale animation - start at final scale\n return finalScale;\n\n case 'grow': {\n // Start smaller, grow to final\n const startScale = this.scaleConfig.startScale ?? 0.3;\n return startScale * finalScale; // Apply relative to final scale\n }\n\n case 'shrink': {\n // Start larger, shrink to final\n const startScale = this.scaleConfig.startScale ?? 1.5;\n return startScale * finalScale; // Apply relative to final scale\n }\n\n case 'pop':\n // Pop mode uses JS animation, start at final scale\n // (the overshoot/bounce is handled in the animation tick)\n return finalScale;\n\n case 'random': {\n // Random start scale in configured range\n const range = this.scaleConfig.range ?? { min: 0.5, max: 1.0 };\n const randomFactor = range.min + Math.random() * (range.max - range.min);\n return randomFactor * finalScale;\n }\n\n default:\n return finalScale;\n }\n }\n\n /**\n * Check if the current scale mode requires JavaScript animation\n * (as opposed to CSS transitions)\n */\n requiresJSScale(): boolean {\n return this.scaleConfig.mode === 'pop';\n }\n\n /**\n * Calculate pop scale for a given animation progress\n * @param progress - Animation progress from 0 to 1\n * @param finalScale - The final scale value\n * @returns The current scale value with bounce effect\n */\n calculatePopScale(progress: number, finalScale: number): number {\n if (this.scaleConfig.mode !== 'pop') {\n return finalScale;\n }\n\n const popConfig = this.scaleConfig.pop || {\n overshoot: 1.2,\n bounces: 1\n };\n\n const { overshoot, bounces } = popConfig;\n\n // Create keyframes for bounce effect\n // Similar to bounce path but for scale\n const keyframes = this.generateScaleBounceKeyframes(bounces, overshoot);\n\n // Find current segment\n let currentScale = finalScale;\n for (let i = 0; i < keyframes.length; i++) {\n if (progress <= keyframes[i].time) {\n const prevTime = i === 0 ? 0 : keyframes[i - 1].time;\n const prevScale = i === 0 ? finalScale : keyframes[i - 1].scale;\n const segmentProgress = (progress - prevTime) / (keyframes[i].time - prevTime);\n // Smooth easing within segment\n const easedProgress = this.easeOutQuad(segmentProgress);\n currentScale = prevScale + (keyframes[i].scale - prevScale) * easedProgress;\n break;\n }\n }\n\n return currentScale * finalScale;\n }\n\n /**\n * Generate keyframes for scale bounce animation\n */\n private generateScaleBounceKeyframes(\n bounces: number,\n overshoot: number\n ): Array<{ time: number; scale: number }> {\n const keyframes: Array<{ time: number; scale: number }> = [];\n\n // Reach overshoot at 50% of animation\n keyframes.push({ time: 0.5, scale: overshoot });\n\n // Add bounces\n let currentOvershoot = overshoot;\n const bounceDecay = 0.5; // Each bounce is 50% of previous\n const remainingTime = 0.5;\n const bounceTime = remainingTime / (bounces * 2);\n\n let currentTime = 0.5;\n for (let i = 0; i < bounces; i++) {\n // Undershoot (go below 1)\n const undershoot = 1 - (currentOvershoot - 1) * bounceDecay;\n currentTime += bounceTime;\n keyframes.push({ time: currentTime, scale: undershoot });\n\n // Overshoot again (smaller)\n currentOvershoot = 1 + (currentOvershoot - 1) * bounceDecay * bounceDecay;\n currentTime += bounceTime;\n if (i < bounces - 1) {\n keyframes.push({ time: currentTime, scale: currentOvershoot });\n }\n }\n\n // Final settle\n keyframes.push({ time: 1, scale: 1 });\n\n return keyframes;\n }\n\n /**\n * Easing function for smooth transitions\n */\n private easeOutQuad(t: number): number {\n return 1 - (1 - t) * (1 - t);\n }\n}\n","/**\n * RandomPlacementGenerator.ts\n * Generates random overlapping layouts for image cloud\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, ImageConfig } from '../config/types';\n\ninterface RandomLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nexport class RandomPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate random layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides (includes fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(imageCount: number, containerBounds: ContainerBounds, options: RandomLayoutOptions = {}): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n\n const padding = this.config.spacing.padding;\n // Use fixedHeight if provided, otherwise use default 200\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Calculate safe bounds for center positions (accounting for half image size and padding)\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (baseImageSize * estAspectRatio) / 2;\n const halfHeight = baseImageSize / 2;\n\n const maxX = width - padding - halfWidth;\n const maxY = height - padding - halfHeight;\n const minX = padding + halfWidth;\n const minY = padding + halfHeight;\n\n for (let i = 0; i < imageCount; i++) {\n // Random center position within safe bounds\n const x = this.random(minX, maxX);\n const y = this.random(minY, maxY);\n\n // Random rotation within range (only when mode is random)\n const rotation = rotationMode === 'random' ? this.random(minRotation, maxRotation) : 0;\n\n // Random size variance\n const scale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const scaledImageSize = baseImageSize * scale;\n\n const layout: ImageLayout = {\n id: i,\n x,\n y,\n rotation,\n scale,\n baseSize: scaledImageSize\n };\n\n layouts.push(layout);\n }\n\n return layouts;\n }\n\n /**\n * Utility: Generate random number between min and max\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Random number in range\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * RadialPlacementGenerator.ts\n * Generates concentric radial layouts for image cloud\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, ImageConfig } from '../config/types';\n\ninterface RadialLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nexport class RadialPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate radial layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: RadialLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n const { debugRadials } = this.config;\n // Use fixedHeight if provided, otherwise use default 200\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Get scale decay from layout config\n const scaleDecay = this.config.scaleDecay ?? 0;\n\n // Debug color palette\n const debugPalette = ['green', 'blue', 'red', 'yellow', 'orange', 'purple'];\n\n // Use override fixedHeight if provided, else baseImageSize\n const imageSize = options.fixedHeight ?? baseImageSize;\n const cx = width / 2;\n const cy = height / 2;\n\n // Calculate max rings for scale decay calculation\n const estimatedMaxRings = Math.ceil(Math.sqrt(imageCount));\n\n // Add center image (using center position)\n if (imageCount > 0) {\n // Apply variance to center image\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const centerSize = imageSize * varianceScale;\n\n layouts.push({\n id: 0,\n x: cx,\n y: cy,\n rotation: rotationMode === 'random' ? this.random(minRotation * 0.33, maxRotation * 0.33) : 0, // Less rotation for center\n scale: varianceScale,\n baseSize: centerSize,\n zIndex: 100, // Center image is highest\n borderColor: debugRadials ? 'cyan' : undefined\n });\n }\n\n let processedCount = 1;\n let currentRing = 1;\n\n while (processedCount < imageCount) {\n // Calculate scale decay for this ring (center is largest, outer rings smaller)\n const normalizedRing = currentRing / estimatedMaxRings;\n const ringScale = scaleDecay > 0\n ? 1 - (normalizedRing * scaleDecay * 0.5) // Max 50% size reduction\n : 1.0;\n\n // Ring settings\n // Scale X more than Y to create horizontal oval shape\n const radiusY = currentRing * (imageSize * 0.8); // Reduce overlap by 20% (1.0 -> 0.8)\n const radiusX = radiusY * 1.5; // Horizontal stretching factor\n\n const circumference = Math.PI * (3 * (radiusX + radiusY) - Math.sqrt((3 * radiusX + radiusY) * (radiusX + 3 * radiusY))); // Ramanujan's approximation\n\n const estimatedItemWidth = this.estimateWidth(imageSize);\n // Increase density by ~40% (1.1 -> 0.7)\n const itemsInRing = Math.floor(circumference / (estimatedItemWidth * 0.7));\n\n if (itemsInRing === 0) {\n currentRing++;\n continue;\n }\n\n const angleStep = (2 * Math.PI) / itemsInRing;\n\n // Add offset of 20 degrees per ring\n const ringOffset = currentRing * (20 * Math.PI / 180);\n\n for (let i = 0; i < itemsInRing && processedCount < imageCount; i++) {\n const angle = (i * angleStep) + ringOffset;\n\n // Apply variance and scale decay\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const combinedScale = ringScale * varianceScale;\n const scaledImageSize = imageSize * combinedScale;\n\n // Calculate center position of image using elliptical formula (store center, not top-left)\n let x = cx + Math.cos(angle) * radiusX;\n let y = cy + Math.sin(angle) * radiusY;\n\n // Boundary Clamping - clamp center position with conservative estimate\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const padding = this.config.spacing.padding ?? 50;\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (scaledImageSize * estAspectRatio) / 2;\n const halfHeight = scaledImageSize / 2;\n\n // Clamp X (center position)\n if (x - halfWidth < padding) {\n x = padding + halfWidth;\n } else if (x + halfWidth > width - padding) {\n x = width - padding - halfWidth;\n }\n\n // Clamp Y (center position)\n if (y - halfHeight < padding) {\n y = padding + halfHeight;\n } else if (y + halfHeight > height - padding) {\n y = height - padding - halfHeight;\n }\n\n const rotation = rotationMode === 'random' ? this.random(minRotation, maxRotation) : 0;\n\n layouts.push({\n id: processedCount,\n x,\n y,\n rotation,\n scale: combinedScale,\n baseSize: scaledImageSize,\n zIndex: Math.max(1, 100 - currentRing), // Outer rings have lower z-index\n borderColor: debugRadials ? debugPalette[(currentRing - 1) % debugPalette.length] : undefined\n });\n\n processedCount++;\n }\n\n currentRing++;\n }\n\n return layouts;\n }\n\n /**\n * Estimate image width based on height\n * Assumes landscape aspect ratio (approximately 1.4:1)\n * @param height - Image height\n * @returns Estimated width\n */\n private estimateWidth(height: number): number {\n // Assume landscape aspect ratio approx 4:3 or 16:9 on average\n // Using 1.4 ratio\n return height * 1.4;\n }\n\n /**\n * Utility: Generate random number between min and max\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Random number in range\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}","/**\n * GridPlacementGenerator.ts\n * Generates grid-based layouts with optional stagger and jitter\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, GridAlgorithmConfig, ImageConfig } from '../config/types';\n\ninterface GridLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nconst DEFAULT_GRID_CONFIG: GridAlgorithmConfig = {\n columns: 'auto',\n rows: 'auto',\n stagger: 'none',\n jitter: 0,\n overlap: 0,\n fillDirection: 'row',\n alignment: 'center',\n gap: 10,\n overflowOffset: 0.25\n};\n\n// Offset pattern for stacking overflow images within cells\n// Each overflow layer cycles through these directions\n// Corners first, then cardinal directions\nconst OVERFLOW_OFFSET_PATTERN = [\n { x: 1, y: 1 }, // bottom-right\n { x: -1, y: -1 }, // upper-left\n { x: 1, y: -1 }, // upper-right\n { x: -1, y: 1 }, // bottom-left\n { x: -1, y: 0 }, // left\n { x: 1, y: 0 }, // right\n { x: 0, y: -1 }, // up\n { x: 0, y: 1 }, // down\n];\n\nexport class GridPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate grid layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides (includes fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: GridLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n\n const gridConfig = { ...DEFAULT_GRID_CONFIG, ...this.config.grid };\n const padding = this.config.spacing.padding;\n // Use fixedHeight if provided, otherwise use base size from config\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Calculate available space\n const availableWidth = width - (2 * padding);\n const availableHeight = height - (2 * padding);\n\n // Calculate grid dimensions\n const { columns, rows } = this.calculateGridDimensions(\n imageCount,\n availableWidth,\n availableHeight,\n baseImageSize,\n gridConfig\n );\n\n // For stagger layouts, we need n+0.5 cells to fit in available space\n // Calculate cell size accounting for this extra half-cell\n const hasRowStagger = gridConfig.stagger === 'row';\n const hasColumnStagger = gridConfig.stagger === 'column';\n\n // Effective columns/rows for sizing: add 0.5 for stagger\n const effectiveColumns = hasRowStagger ? columns + 0.5 : columns;\n const effectiveRows = hasColumnStagger ? rows + 0.5 : rows;\n\n // Calculate cell size to fit effective count in available space\n const cellWidth = (availableWidth - (gridConfig.gap * (columns - 1))) / effectiveColumns;\n const cellHeight = (availableHeight - (gridConfig.gap * (rows - 1))) / effectiveRows;\n\n // Stagger offset is half a cell\n const staggerOffsetX = hasRowStagger ? cellWidth / 2 : 0;\n const staggerOffsetY = hasColumnStagger ? cellHeight / 2 : 0;\n\n // Calculate image size with overlap factor\n // overlap: 0 = fit in cell, 0.5 = 50% larger, 1.0 = 2x cell size\n const overlapMultiplier = 1 + gridConfig.overlap;\n const cellBasedSize = Math.min(cellWidth, cellHeight) * overlapMultiplier;\n\n // Use fixedHeight if provided, otherwise use cell-based calculation\n // For grid layouts, we respect fixedHeight but cap at cell-based size to avoid overflow\n const imageSize = options.fixedHeight\n ? Math.min(options.fixedHeight, cellBasedSize)\n : cellBasedSize;\n\n // Calculate total grid dimensions for alignment (include stagger offset)\n const totalGridWidth = columns * cellWidth + (columns - 1) * gridConfig.gap + staggerOffsetX;\n const totalGridHeight = rows * cellHeight + (rows - 1) * gridConfig.gap + staggerOffsetY;\n\n // Center the grid in the container\n const gridOffsetX = padding + (availableWidth - totalGridWidth) / 2;\n const gridOffsetY = padding + (availableHeight - totalGridHeight) / 2;\n\n // Detect overflow mode: when both dimensions are fixed and we have more images than cells\n const cellCount = columns * rows;\n const hasFixedGrid = gridConfig.columns !== 'auto' && gridConfig.rows !== 'auto';\n const isOverflowMode = hasFixedGrid && imageCount > cellCount;\n\n if (typeof window !== 'undefined') {\n (window as any).__gridOverflowDebug = {\n gridConfigColumns: gridConfig.columns,\n gridConfigRows: gridConfig.rows,\n columns, rows, cellCount,\n hasFixedGrid, imageCount, isOverflowMode\n };\n }\n\n // Track stack depth for each cell (used in overflow mode)\n const cellStackCount: number[] = isOverflowMode ? new Array(cellCount).fill(0) : [];\n\n // Calculate overflow offset in pixels\n const overflowOffsetPx = Math.min(cellWidth, cellHeight) * gridConfig.overflowOffset;\n\n for (let i = 0; i < imageCount; i++) {\n let col: number;\n let row: number;\n let stackLayer = 0; // 0 = base image, 1+ = overflow layers\n\n if (isOverflowMode && i >= cellCount) {\n // Overflow image: determine target cell and stack layer\n const overflowIndex = i - cellCount;\n const targetCell = overflowIndex % cellCount;\n stackLayer = Math.floor(overflowIndex / cellCount) + 1;\n cellStackCount[targetCell]++;\n\n // Get cell coordinates from target cell index\n if (gridConfig.fillDirection === 'row') {\n col = targetCell % columns;\n row = Math.floor(targetCell / columns);\n } else {\n row = targetCell % rows;\n col = Math.floor(targetCell / rows);\n }\n } else {\n // Base image: normal cell assignment\n if (gridConfig.fillDirection === 'row') {\n col = i % columns;\n row = Math.floor(i / columns);\n } else {\n row = i % rows;\n col = Math.floor(i / rows);\n }\n }\n\n // Base cell position (center of cell)\n let cellCenterX = gridOffsetX + col * (cellWidth + gridConfig.gap) + cellWidth / 2;\n let cellCenterY = gridOffsetY + row * (cellHeight + gridConfig.gap) + cellHeight / 2;\n\n // Apply stagger offset\n if (gridConfig.stagger === 'row' && row % 2 === 1) {\n cellCenterX += cellWidth / 2;\n } else if (gridConfig.stagger === 'column' && col % 2 === 1) {\n cellCenterY += cellHeight / 2;\n }\n\n // Apply overflow offset for stacked images\n if (stackLayer > 0) {\n const patternIndex = (stackLayer - 1) % OVERFLOW_OFFSET_PATTERN.length;\n const pattern = OVERFLOW_OFFSET_PATTERN[patternIndex];\n cellCenterX += pattern.x * overflowOffsetPx;\n cellCenterY += pattern.y * overflowOffsetPx;\n }\n\n // Apply jitter (random offset within cell bounds)\n if (gridConfig.jitter > 0) {\n const maxJitterX = (cellWidth / 2) * gridConfig.jitter;\n const maxJitterY = (cellHeight / 2) * gridConfig.jitter;\n cellCenterX += this.random(-maxJitterX, maxJitterX);\n cellCenterY += this.random(-maxJitterY, maxJitterY);\n }\n\n // Store center position directly (not top-left)\n let x = cellCenterX;\n let y = cellCenterY;\n\n // Handle incomplete row alignment (only for non-overflow mode)\n if (!isOverflowMode && gridConfig.fillDirection === 'row') {\n const itemsInLastRow = imageCount % columns || columns;\n const isLastRow = row === Math.floor((imageCount - 1) / columns);\n\n if (isLastRow && itemsInLastRow < columns) {\n const lastRowWidth = itemsInLastRow * cellWidth + (itemsInLastRow - 1) * gridConfig.gap;\n let alignmentOffset = 0;\n\n if (gridConfig.alignment === 'center') {\n alignmentOffset = (totalGridWidth - lastRowWidth) / 2;\n } else if (gridConfig.alignment === 'end') {\n alignmentOffset = totalGridWidth - lastRowWidth;\n }\n\n x += alignmentOffset;\n }\n }\n\n // Apply variance to create non-uniform look\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const scaledImageSize = imageSize * varianceScale;\n\n // Boundary clamping for center-based positioning\n // Use 1.5 multiplier (3:2 aspect) as reasonable middle ground for mixed portrait/landscape\n const estAspectRatio = 1.5;\n const halfWidth = (scaledImageSize * estAspectRatio) / 2;\n const halfHeight = scaledImageSize / 2;\n const minX = padding + halfWidth;\n const maxX = width - padding - halfWidth;\n const minY = padding + halfHeight;\n const maxY = height - padding - halfHeight;\n\n x = Math.max(minX, Math.min(x, maxX));\n y = Math.max(minY, Math.min(y, maxY));\n\n // Apply rotation when mode is random\n // If jitter > 0, scale rotation by jitter factor; otherwise use full rotation range\n let rotation = 0;\n if (rotationMode === 'random') {\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n if (gridConfig.jitter > 0) {\n // Scale rotation by jitter factor for more subtle effect\n rotation = this.random(minRotation * gridConfig.jitter, maxRotation * gridConfig.jitter);\n } else {\n // Full rotation range even without jitter\n rotation = this.random(minRotation, maxRotation);\n }\n }\n\n // Calculate z-index: in overflow mode, overflow images go BELOW base images\n // Base images get higher z-index so they appear on top\n // Overflow images get progressively lower z-index so they flow under\n let zIndex: number;\n if (isOverflowMode && stackLayer > 0) {\n // Overflow images: lower z-index, decreasing with each layer\n // Layer 1 = 50, Layer 2 = 49, etc.\n zIndex = 50 - stackLayer;\n } else {\n // Base images: higher z-index (100+)\n zIndex = isOverflowMode ? 100 + i : i + 1;\n }\n\n layouts.push({\n id: i,\n x,\n y,\n rotation,\n scale: varianceScale,\n baseSize: scaledImageSize,\n zIndex\n });\n }\n\n return layouts;\n }\n\n /**\n * Calculate optimal grid dimensions based on image count and container\n */\n private calculateGridDimensions(\n imageCount: number,\n availableWidth: number,\n availableHeight: number,\n _baseImageSize: number,\n config: GridAlgorithmConfig\n ): { columns: number; rows: number } {\n let columns: number;\n let rows: number;\n\n if (config.columns !== 'auto' && config.rows !== 'auto') {\n columns = config.columns;\n rows = config.rows;\n } else if (config.columns !== 'auto') {\n columns = config.columns;\n rows = Math.ceil(imageCount / columns);\n } else if (config.rows !== 'auto') {\n rows = config.rows;\n columns = Math.ceil(imageCount / rows);\n } else {\n // Auto-calculate: try to fill the space while maintaining reasonable aspect ratio\n const aspectRatio = availableWidth / availableHeight;\n const imageAspectRatio = 1.4; // Assumed landscape\n\n // Calculate ideal columns based on aspect ratio\n columns = Math.max(1, Math.round(Math.sqrt(imageCount * aspectRatio / imageAspectRatio)));\n rows = Math.ceil(imageCount / columns);\n\n // Ensure we don't have too many empty cells\n while (columns > 1 && (columns - 1) * rows >= imageCount) {\n columns--;\n }\n }\n\n return { columns: Math.max(1, columns), rows: Math.max(1, rows) };\n }\n\n /**\n * Utility: Generate random number between min and max\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * SpiralPlacementGenerator.ts\n * Generates spiral layouts (golden, archimedean, logarithmic)\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, SpiralAlgorithmConfig, ImageConfig } from '../config/types';\n\ninterface SpiralLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\n// Golden angle in radians (~137.5 degrees)\nconst GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));\n\nconst DEFAULT_SPIRAL_CONFIG: SpiralAlgorithmConfig = {\n spiralType: 'golden',\n direction: 'counterclockwise',\n tightness: 1.0,\n scaleDecay: 0,\n startAngle: 0\n};\n\nexport class SpiralPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate spiral layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides (includes fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: SpiralLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n\n const spiralConfig = { ...DEFAULT_SPIRAL_CONFIG, ...this.config.spiral };\n const padding = this.config.spacing.padding;\n // Use fixedHeight if provided, otherwise use base size from config\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Get scale decay from layout config (overrides spiral-specific config)\n const scaleDecay = this.config.scaleDecay ?? spiralConfig.scaleDecay;\n\n // Center of the spiral\n const cx = width / 2;\n const cy = height / 2;\n\n // Calculate available radius (distance from center to edge)\n const maxRadius = Math.min(\n cx - padding - baseImageSize / 2,\n cy - padding - baseImageSize / 2\n );\n\n // Direction multiplier (1 for counterclockwise, -1 for clockwise)\n const directionMultiplier = spiralConfig.direction === 'clockwise' ? -1 : 1;\n\n for (let i = 0; i < imageCount; i++) {\n // Calculate angle based on spiral type\n let angle: number;\n let radius: number;\n\n if (spiralConfig.spiralType === 'golden') {\n // Golden spiral: each point separated by golden angle\n // Creates optimal distribution like sunflower seeds\n angle = i * GOLDEN_ANGLE * directionMultiplier + spiralConfig.startAngle;\n // Radius grows with square root of index for even distribution\n radius = this.calculateGoldenRadius(i, imageCount, maxRadius, spiralConfig.tightness);\n } else if (spiralConfig.spiralType === 'archimedean') {\n // Archimedean spiral: r = a + b*θ (constant spacing between arms)\n const theta = i * 0.5 * spiralConfig.tightness;\n angle = theta * directionMultiplier + spiralConfig.startAngle;\n radius = this.calculateArchimedeanRadius(theta, imageCount, maxRadius, spiralConfig.tightness);\n } else {\n // Logarithmic spiral: r = a * e^(b*θ) (self-similar, appears in nature)\n const theta = i * 0.3 * spiralConfig.tightness;\n angle = theta * directionMultiplier + spiralConfig.startAngle;\n radius = this.calculateLogarithmicRadius(theta, imageCount, maxRadius, spiralConfig.tightness);\n }\n\n // Convert polar to cartesian coordinates (store center position)\n const x = cx + Math.cos(angle) * radius;\n const y = cy + Math.sin(angle) * radius;\n\n // Calculate scale based on decay (center images larger) - use scaleDecay from image config\n const normalizedRadius = radius / maxRadius;\n const decayScale = scaleDecay > 0\n ? 1 - (normalizedRadius * scaleDecay * 0.5) // Max 50% size reduction\n : 1.0;\n\n // Apply variance\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const combinedScale = decayScale * varianceScale;\n\n // Apply scaled image size\n const scaledImageSize = baseImageSize * combinedScale;\n\n // Clamp center positions to keep images within bounds\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (scaledImageSize * estAspectRatio) / 2;\n const halfHeight = scaledImageSize / 2;\n const minX = padding + halfWidth;\n const maxX = width - padding - halfWidth;\n const minY = padding + halfHeight;\n const maxY = height - padding - halfHeight;\n\n const clampedX = Math.max(minX, Math.min(x, maxX));\n const clampedY = Math.max(minY, Math.min(y, maxY));\n\n // Rotation based on mode\n let rotation = 0;\n if (rotationMode === 'random') {\n const baseRotation = (angle * 180 / Math.PI) % 360;\n const rotationVariance = this.random(minRotation, maxRotation);\n rotation = spiralConfig.spiralType === 'golden'\n ? rotationVariance // Pure random for golden (more organic)\n : (baseRotation * 0.1 + rotationVariance * 0.9); // Slight directional bias for others\n } else if (rotationMode === 'tangent') {\n // Tangent rotation: align image to spiral curve\n rotation = this.calculateSpiralTangent(angle, radius, spiralConfig);\n }\n\n // Z-index: center images on top\n const zIndex = imageCount - i;\n\n layouts.push({\n id: i,\n x: clampedX,\n y: clampedY,\n rotation,\n scale: combinedScale,\n baseSize: scaledImageSize,\n zIndex\n });\n }\n\n return layouts;\n }\n\n /**\n * Calculate tangent angle for spiral curve at given position\n * This aligns the image along the spiral's direction of travel\n */\n private calculateSpiralTangent(\n angle: number,\n radius: number,\n spiralConfig: SpiralAlgorithmConfig\n ): number {\n // For different spiral types, the tangent calculation varies\n // The tangent angle is the derivative of the spiral equation\n\n let tangentAngle: number;\n\n if (spiralConfig.spiralType === 'golden') {\n // Golden spiral tangent is approximately perpendicular to radial line\n // Plus a small offset based on the golden angle\n tangentAngle = angle + Math.PI / 2;\n } else if (spiralConfig.spiralType === 'archimedean') {\n // Archimedean spiral: dr/dθ = constant = b\n // tan(ψ) = r / (dr/dθ) = r / b\n // For tightness = 1, b ≈ 1\n const b = 1 / spiralConfig.tightness;\n const psi = Math.atan(radius / b);\n tangentAngle = angle + psi;\n } else {\n // Logarithmic spiral: tangent makes constant angle with radial line\n // This angle depends on the growth rate b\n const b = 0.15 / spiralConfig.tightness;\n const psi = Math.atan(1 / b); // Constant pitch angle\n tangentAngle = angle + psi;\n }\n\n // Convert to degrees and normalize\n const degrees = (tangentAngle * 180 / Math.PI) % 360;\n\n // Adjust so 0 degrees means pointing right\n return degrees - 90;\n }\n\n /**\n * Calculate radius for golden spiral (Vogel's model)\n * Creates even distribution like sunflower seeds\n */\n private calculateGoldenRadius(\n index: number,\n totalImages: number,\n maxRadius: number,\n tightness: number\n ): number {\n // Vogel's model: r = c * sqrt(n)\n // Scaling factor to fit within maxRadius\n const scalingFactor = maxRadius / Math.sqrt(totalImages);\n const radius = scalingFactor * Math.sqrt(index) / tightness;\n return Math.min(radius, maxRadius);\n }\n\n /**\n * Calculate radius for Archimedean spiral\n * r = a + b*θ (constant spacing between arms)\n */\n private calculateArchimedeanRadius(\n theta: number,\n totalImages: number,\n maxRadius: number,\n tightness: number\n ): number {\n // Scale so that the last point is at maxRadius\n const maxTheta = totalImages * 0.5 * tightness;\n const normalizedTheta = theta / maxTheta;\n return normalizedTheta * maxRadius;\n }\n\n /**\n * Calculate radius for logarithmic (equiangular) spiral\n * r = a * e^(b*θ)\n */\n private calculateLogarithmicRadius(\n theta: number,\n totalImages: number,\n maxRadius: number,\n tightness: number\n ): number {\n // Parameters for logarithmic spiral\n const a = maxRadius * 0.05; // Starting radius\n const b = 0.15 / tightness; // Growth rate\n\n const radius = a * Math.exp(b * theta);\n\n // Normalize to fit within maxRadius\n const maxTheta = totalImages * 0.3 * tightness;\n const maxComputedRadius = a * Math.exp(b * maxTheta);\n\n return (radius / maxComputedRadius) * maxRadius;\n }\n\n /**\n * Utility: Generate random number between min and max\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * ClusterPlacementGenerator.ts\n * Generates organic cluster layouts with natural groupings\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, ClusterAlgorithmConfig, ImageConfig } from '../config/types';\n\ninterface ClusterCenter {\n x: number;\n y: number;\n spread: number; // Actual spread for this cluster (may vary if density='varied')\n}\n\ninterface ClusterLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nconst DEFAULT_CLUSTER_CONFIG: ClusterAlgorithmConfig = {\n clusterCount: 'auto',\n clusterSpread: 150,\n clusterSpacing: 200,\n density: 'uniform',\n overlap: 0.3,\n distribution: 'gaussian'\n};\n\nexport class ClusterPlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate cluster layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides (includes fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: ClusterLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n\n const clusterConfig = { ...DEFAULT_CLUSTER_CONFIG, ...this.config.cluster };\n const padding = this.config.spacing.padding;\n // Use fixedHeight if provided, otherwise use default 200\n const baseImageSize = options.fixedHeight ?? 200;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Calculate number of clusters\n const clusterCount = this.calculateClusterCount(\n imageCount,\n clusterConfig.clusterCount,\n width,\n height,\n clusterConfig.clusterSpacing\n );\n\n // Generate cluster centers with spacing constraints\n const clusterCenters = this.generateClusterCenters(\n clusterCount,\n width,\n height,\n padding,\n clusterConfig\n );\n\n // Assign images to clusters (round-robin for even distribution)\n const imagesPerCluster = new Array(clusterCount).fill(0);\n for (let i = 0; i < imageCount; i++) {\n imagesPerCluster[i % clusterCount]++;\n }\n\n let imageIndex = 0;\n\n // Place images in each cluster\n for (let clusterIdx = 0; clusterIdx < clusterCount; clusterIdx++) {\n const cluster = clusterCenters[clusterIdx];\n const imagesInThisCluster = imagesPerCluster[clusterIdx];\n\n for (let i = 0; i < imagesInThisCluster; i++) {\n // Calculate position within cluster\n let offsetX: number;\n let offsetY: number;\n\n if (clusterConfig.distribution === 'gaussian') {\n // Gaussian distribution - most images near center, fewer at edges\n offsetX = this.gaussianRandom() * cluster.spread;\n offsetY = this.gaussianRandom() * cluster.spread;\n } else {\n // Uniform distribution within circle\n const angle = this.random(0, Math.PI * 2);\n const distance = this.random(0, cluster.spread);\n offsetX = Math.cos(angle) * distance;\n offsetY = Math.sin(angle) * distance;\n }\n\n // Apply overlap factor - pulls images closer to center and increases size\n const overlapMultiplier = 1 + clusterConfig.overlap * 0.5;\n const sizeMultiplier = 1 + clusterConfig.overlap * 0.3;\n\n // Reduce offset based on overlap (images cluster tighter)\n offsetX /= overlapMultiplier;\n offsetY /= overlapMultiplier;\n\n // Apply variance\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const combinedScale = sizeMultiplier * varianceScale;\n\n // Calculate image size with overlap factor and variance\n const imageSize = baseImageSize * combinedScale;\n\n // Store center position (not top-left)\n let x = cluster.x + offsetX;\n let y = cluster.y + offsetY;\n\n // Boundary clamping with center position\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (imageSize * estAspectRatio) / 2;\n const halfHeight = imageSize / 2;\n x = Math.max(padding + halfWidth, Math.min(x, width - padding - halfWidth));\n y = Math.max(padding + halfHeight, Math.min(y, height - padding - halfHeight));\n\n // Rotation - more variance for organic feel (only when mode is random)\n const rotation = rotationMode === 'random' ? this.random(minRotation, maxRotation) : 0;\n\n // Z-index: images closer to cluster center are on top\n const distanceFromCenter = Math.sqrt(offsetX * offsetX + offsetY * offsetY);\n const normalizedDistance = distanceFromCenter / cluster.spread;\n const zIndex = Math.round((1 - normalizedDistance) * 50) + 1;\n\n layouts.push({\n id: imageIndex,\n x,\n y,\n rotation,\n scale: combinedScale,\n baseSize: imageSize,\n zIndex\n });\n\n imageIndex++;\n }\n }\n\n return layouts;\n }\n\n /**\n * Calculate optimal number of clusters based on image count and container\n */\n private calculateClusterCount(\n imageCount: number,\n configCount: number | 'auto',\n width: number,\n height: number,\n clusterSpacing: number\n ): number {\n if (configCount !== 'auto') {\n return Math.max(1, Math.min(configCount, imageCount));\n }\n\n // Auto-calculate based on container size and image count\n // Aim for 5-15 images per cluster\n const idealImagesPerCluster = 8;\n const countByImages = Math.max(1, Math.ceil(imageCount / idealImagesPerCluster));\n\n // Also consider container size - how many clusters can fit\n const countBySpace = Math.floor(\n (width / clusterSpacing) * (height / clusterSpacing) * 0.6\n );\n\n return Math.max(1, Math.min(countByImages, countBySpace, 10));\n }\n\n /**\n * Generate cluster center positions with spacing constraints\n */\n private generateClusterCenters(\n count: number,\n width: number,\n height: number,\n padding: number,\n config: ClusterAlgorithmConfig\n ): ClusterCenter[] {\n const centers: ClusterCenter[] = [];\n const maxAttempts = 100;\n\n // Available area for cluster centers\n const minX = padding + config.clusterSpread;\n const maxX = width - padding - config.clusterSpread;\n const minY = padding + config.clusterSpread;\n const maxY = height - padding - config.clusterSpread;\n\n for (let i = 0; i < count; i++) {\n let bestCandidate: ClusterCenter | null = null;\n let bestMinDistance = -1;\n\n // Try multiple random positions and pick the one with best spacing\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const candidate = {\n x: this.random(minX, maxX),\n y: this.random(minY, maxY),\n spread: this.calculateClusterSpread(config)\n };\n\n // Calculate minimum distance to existing clusters\n let minDistance = Infinity;\n for (const existing of centers) {\n const dx = candidate.x - existing.x;\n const dy = candidate.y - existing.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n minDistance = Math.min(minDistance, distance);\n }\n\n // First cluster or better spacing\n if (centers.length === 0 || minDistance > bestMinDistance) {\n bestCandidate = candidate;\n bestMinDistance = minDistance;\n }\n\n // Good enough spacing found\n if (minDistance >= config.clusterSpacing) {\n break;\n }\n }\n\n if (bestCandidate) {\n centers.push(bestCandidate);\n }\n }\n\n return centers;\n }\n\n /**\n * Calculate spread for a cluster (may vary if density='varied')\n */\n private calculateClusterSpread(config: ClusterAlgorithmConfig): number {\n if (config.density === 'uniform') {\n return config.clusterSpread;\n }\n\n // Varied density: spread varies between 50% and 150% of config value\n return config.clusterSpread * this.random(0.5, 1.5);\n }\n\n /**\n * Generate a random number with approximately Gaussian distribution\n * Using Box-Muller transform\n */\n private gaussianRandom(): number {\n let u = 0, v = 0;\n while (u === 0) u = Math.random();\n while (v === 0) v = Math.random();\n\n const value = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);\n\n // Clamp to reasonable range (-3 to 3 std deviations)\n return Math.max(-3, Math.min(3, value)) / 3;\n }\n\n /**\n * Utility: Generate random number between min and max\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * WavePlacementGenerator.ts\n * Generates wave-based layouts for image cloud\n */\n\nimport type { PlacementGenerator, ImageLayout, ContainerBounds, LayoutConfig, ImageConfig } from '../config/types';\nimport { DEFAULT_WAVE_CONFIG } from '../config/defaults';\n\ninterface WaveLayoutOptions extends Partial<LayoutConfig> {\n fixedHeight?: number;\n}\n\nexport class WavePlacementGenerator implements PlacementGenerator {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n\n constructor(config: LayoutConfig, imageConfig: ImageConfig = {}) {\n this.config = config;\n this.imageConfig = imageConfig;\n }\n\n /**\n * Generate wave layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides\n * @returns Array of layout objects with position, rotation, scale\n */\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options: WaveLayoutOptions = {}\n ): ImageLayout[] {\n const layouts: ImageLayout[] = [];\n const { width, height } = containerBounds;\n // Use fixedHeight if provided, otherwise use default 200\n const baseImageSize = options.fixedHeight ?? 200;\n const padding = this.config.spacing.padding ?? 50;\n\n // Get rotation config from image config\n const rotationMode = this.imageConfig.rotation?.mode ?? 'none';\n const minRotation = this.imageConfig.rotation?.range?.min ?? -15;\n const maxRotation = this.imageConfig.rotation?.range?.max ?? 15;\n\n // Get variance config from image config\n const varianceMin = this.imageConfig.sizing?.variance?.min ?? 1.0;\n const varianceMax = this.imageConfig.sizing?.variance?.max ?? 1.0;\n const hasVariance = varianceMin !== 1.0 || varianceMax !== 1.0;\n\n // Use override fixedHeight if provided, else baseImageSize\n const imageSize = options.fixedHeight ?? baseImageSize;\n\n // Get wave configuration, merging user config with defaults\n const waveConfig = {\n ...DEFAULT_WAVE_CONFIG,\n ...this.config.wave\n };\n\n const { rows, amplitude, frequency, phaseShift, synchronization } = waveConfig;\n\n // Calculate images per row (distribute evenly)\n const imagesPerRow = Math.ceil(imageCount / rows);\n\n // Estimate image width based on height and typical aspect ratio\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const estImageWidth = imageSize * estAspectRatio;\n const halfImageWidth = estImageWidth / 2;\n\n // Calculate available horizontal space (accounting for image width at edges)\n // This ensures images don't get compressed against the edges\n const startX = padding + halfImageWidth;\n const endX = width - padding - halfImageWidth;\n const availableWidth = endX - startX;\n\n // Distribute images evenly between startX and endX\n const horizontalSpacing = imagesPerRow > 1 ? availableWidth / (imagesPerRow - 1) : 0;\n\n // Calculate vertical distribution to fill the screen\n // Row centerlines need room for amplitude swing above and below\n const minCenterY = padding + amplitude + (imageSize / 2);\n const maxCenterY = height - padding - amplitude - (imageSize / 2);\n const availableHeight = maxCenterY - minCenterY;\n\n // Calculate spacing between row centerlines\n const rowSpacing = rows > 1 ? availableHeight / (rows - 1) : 0;\n\n let imageIndex = 0;\n\n for (let rowIndex = 0; rowIndex < rows && imageIndex < imageCount; rowIndex++) {\n // Calculate base Y position (centerline) for this row - evenly distributed\n const baseY = rows === 1\n ? (minCenterY + maxCenterY) / 2 // Single row: center vertically\n : minCenterY + (rowIndex * rowSpacing);\n\n // Calculate phase offset based on synchronization mode\n let phase = 0;\n if (synchronization === 'offset') {\n phase = rowIndex * phaseShift;\n } else if (synchronization === 'alternating') {\n phase = rowIndex * Math.PI;\n }\n // If 'synchronized', phase remains 0\n\n // Place images along this wave row\n for (let imgInRow = 0; imgInRow < imagesPerRow && imageIndex < imageCount; imgInRow++) {\n // Calculate center position - evenly distributed within available space\n // For single image, center it; otherwise distribute from startX to endX\n const centerX = imagesPerRow === 1\n ? (startX + endX) / 2\n : startX + (imgInRow * horizontalSpacing);\n\n // Calculate wave displacement based on center position\n const waveY = this.calculateWaveY(centerX, width, amplitude, frequency, phase);\n\n // Store center position directly (CSS transform will handle centering)\n const x = centerX;\n const y = baseY + waveY;\n\n // Apply variance\n const varianceScale = hasVariance ? this.random(varianceMin, varianceMax) : 1.0;\n const scaledImageSize = imageSize * varianceScale;\n\n // Calculate rotation based on image.rotation.mode\n let rotation = 0;\n if (rotationMode === 'tangent') {\n // Follow wave tangent - images rotate to align with wave curve\n rotation = this.calculateRotation(centerX, width, amplitude, frequency, phase);\n } else if (rotationMode === 'random') {\n // Random rotation within configured range\n rotation = this.random(minRotation, maxRotation);\n }\n // If mode is 'none', rotation stays 0\n\n // Clamp center positions to keep images within bounds\n // Use 16:9 aspect ratio (1.78) as maximum to handle most landscape images\n const estAspectRatio = 1.5; // 3:2 - balanced for mixed portrait/landscape\n const halfWidth = (scaledImageSize * estAspectRatio) / 2;\n const halfHeight = scaledImageSize / 2;\n const minX = padding + halfWidth;\n const maxX = width - padding - halfWidth;\n const minY = padding + halfHeight;\n const maxY = height - padding - halfHeight;\n\n layouts.push({\n id: imageIndex,\n x: Math.max(minX, Math.min(x, maxX)),\n y: Math.max(minY, Math.min(y, maxY)),\n rotation,\n scale: varianceScale,\n baseSize: scaledImageSize,\n zIndex: imageIndex + 1\n });\n\n imageIndex++;\n }\n }\n\n return layouts;\n }\n\n /**\n * Calculate Y position displacement on wave curve\n * @param x - Horizontal position\n * @param containerWidth - Container width\n * @param amplitude - Wave amplitude\n * @param frequency - Wave frequency\n * @param phase - Phase offset\n * @returns Y displacement from baseline\n */\n private calculateWaveY(\n x: number,\n containerWidth: number,\n amplitude: number,\n frequency: number,\n phase: number\n ): number {\n // Normalize x to [0, 1] range\n const normalizedX = x / containerWidth;\n\n // Calculate wave: y = amplitude * sin(frequency * x * 2π + phase)\n return amplitude * Math.sin(frequency * normalizedX * 2 * Math.PI + phase);\n }\n\n /**\n * Calculate rotation based on wave tangent\n * @param x - Horizontal position\n * @param containerWidth - Container width\n * @param amplitude - Wave amplitude\n * @param frequency - Wave frequency\n * @param phase - Phase offset\n * @returns Rotation angle in degrees\n */\n private calculateRotation(\n x: number,\n containerWidth: number,\n amplitude: number,\n frequency: number,\n phase: number\n ): number {\n // Normalize x to [0, 1] range\n const normalizedX = x / containerWidth;\n\n // Derivative of sine wave: amplitude * frequency * cos(frequency * x * 2π + phase)\n const derivative = amplitude * frequency * 2 * Math.PI * Math.cos(frequency * normalizedX * 2 * Math.PI + phase) / containerWidth;\n\n // Convert derivative to rotation angle in degrees\n return Math.atan(derivative) * (180 / Math.PI);\n }\n\n /**\n * Estimate image width based on height\n /**\n * Utility: Generate random number between min and max\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Random number in range\n */\n private random(min: number, max: number): number {\n return Math.random() * (max - min) + min;\n }\n}\n","/**\n * LayoutEngine.ts\n * Generates layouts for image cloud using placement strategies\n *\n * Public API:\n * - generateLayout(imageCount, containerBounds, options)\n * - getOriginalState(imageId)\n * - reset()\n * - updateConfig(newConfig)\n */\n\nimport type { LayoutConfig, ImageLayout, ContainerBounds, PlacementGenerator, AdaptiveSizingResult, ImageConfig, FixedModeHeight, ResponsiveBreakpoints } from '../config/types';\nimport { RandomPlacementGenerator } from '../generators/RandomPlacementGenerator';\nimport { RadialPlacementGenerator } from '../generators/RadialPlacementGenerator';\nimport { GridPlacementGenerator } from '../generators/GridPlacementGenerator';\nimport { SpiralPlacementGenerator } from '../generators/SpiralPlacementGenerator';\nimport { ClusterPlacementGenerator } from '../generators/ClusterPlacementGenerator';\nimport { WavePlacementGenerator } from '../generators/WavePlacementGenerator';\n\nexport interface LayoutEngineConfig {\n layout: LayoutConfig;\n image: ImageConfig;\n}\n\nexport class LayoutEngine {\n private config: LayoutConfig;\n private imageConfig: ImageConfig;\n private layouts: Map<number, ImageLayout>;\n private generator: PlacementGenerator;\n\n constructor(config: LayoutEngineConfig) {\n this.config = config.layout;\n this.imageConfig = config.image;\n\n this.layouts = new Map(); // Store original states by image ID\n\n // Initialize generator strategy\n this.generator = this.initGenerator();\n }\n\n /**\n * Initialize the appropriate generator based on config type\n * @returns Initialized placement generator\n */\n private initGenerator(): PlacementGenerator {\n switch (this.config.algorithm) {\n case 'radial':\n return new RadialPlacementGenerator(this.config, this.imageConfig);\n case 'grid':\n return new GridPlacementGenerator(this.config, this.imageConfig);\n case 'spiral':\n return new SpiralPlacementGenerator(this.config, this.imageConfig);\n case 'cluster':\n return new ClusterPlacementGenerator(this.config, this.imageConfig);\n case 'wave':\n return new WavePlacementGenerator(this.config, this.imageConfig);\n case 'random':\n default:\n return new RandomPlacementGenerator(this.config, this.imageConfig);\n }\n }\n\n /**\n * Generate layout positions for images\n * @param imageCount - Number of images to layout\n * @param containerBounds - Container dimensions {width, height}\n * @param options - Optional overrides for configuration (e.g. fixedHeight)\n * @returns Array of layout objects with position, rotation, scale\n */\n generateLayout(imageCount: number, containerBounds: ContainerBounds, options: Partial<LayoutConfig> = {}): ImageLayout[] {\n const layouts = this.generator.generate(imageCount, containerBounds, options);\n\n // Store layouts for state retrieval\n layouts.forEach(layout => {\n this.layouts.set(layout.id, layout);\n });\n\n return layouts;\n }\n\n /**\n * Get the original layout state for an image\n * @param imageId - The image ID (number or string)\n * @returns Original layout state or undefined if not found\n */\n getOriginalState(imageId: number | string): ImageLayout | undefined {\n return this.layouts.get(Number(imageId));\n }\n\n /**\n * Reset all stored layouts\n */\n reset(): void {\n this.layouts.clear();\n }\n\n /**\n * Update config dynamically (useful for responsive changes)\n * @param newConfig - Updated configuration\n */\n updateConfig(newConfig: Partial<LayoutEngineConfig>): void {\n // Update layout config\n if (newConfig.layout) {\n Object.assign(this.config, newConfig.layout);\n\n // Reinitialize generator if algorithm changed\n if (newConfig.layout.algorithm && newConfig.layout.algorithm !== this.config.algorithm) {\n this.generator = this.initGenerator();\n }\n }\n\n // Update image config\n if (newConfig.image) {\n Object.assign(this.imageConfig, newConfig.image);\n }\n }\n\n /**\n * Get responsive breakpoints from layout config\n */\n private getBreakpoints(): ResponsiveBreakpoints {\n return this.config.responsive ?? {\n mobile: { maxWidth: 767 },\n tablet: { maxWidth: 1199 }\n };\n }\n\n /**\n * Resolve breakpoint name based on viewport width\n */\n resolveBreakpoint(viewportWidth: number): 'mobile' | 'tablet' | 'screen' {\n const breakpoints = this.getBreakpoints();\n\n if (viewportWidth <= breakpoints.mobile.maxWidth) {\n return 'mobile';\n }\n if (viewportWidth <= breakpoints.tablet.maxWidth) {\n return 'tablet';\n }\n return 'screen';\n }\n\n /**\n * Resolve the effective base height based on image config and current viewport\n * @param viewportWidth - Current viewport width\n * @returns Resolved base height or undefined if should auto-calculate (adaptive mode)\n */\n resolveBaseHeight(viewportWidth: number): number | undefined {\n const sizing = this.imageConfig.sizing;\n\n // If mode is adaptive (or not set), return undefined to signal auto-calculation\n if (!sizing || sizing.mode === 'adaptive') {\n return undefined;\n }\n\n // Fixed or responsive mode - use the height property\n const height = sizing.height;\n\n if (height === undefined) {\n return undefined; // No height specified, fall back to adaptive\n }\n\n if (typeof height === 'number') {\n return height;\n }\n\n // Responsive height for fixed mode\n const responsiveHeight = height as FixedModeHeight;\n const breakpoint = this.resolveBreakpoint(viewportWidth);\n\n // Fallback chain: specific breakpoint -> higher breakpoints -> any defined\n if (breakpoint === 'mobile') {\n return responsiveHeight.mobile ?? responsiveHeight.tablet ?? responsiveHeight.screen;\n }\n if (breakpoint === 'tablet') {\n return responsiveHeight.tablet ?? responsiveHeight.screen ?? responsiveHeight.mobile;\n }\n // screen\n return responsiveHeight.screen ?? responsiveHeight.tablet ?? responsiveHeight.mobile;\n }\n\n /**\n * Calculate adaptive image size based on container dimensions and image count\n * @param containerBounds - Container dimensions {width, height}\n * @param imageCount - Number of images to display\n * @param maxHeight - Maximum height constraint (upper bound)\n * @param viewportWidth - Current viewport width for baseHeight resolution\n * @returns Calculated sizing result with height\n */\n calculateAdaptiveSize(\n containerBounds: ContainerBounds,\n imageCount: number,\n maxHeight: number,\n viewportWidth: number\n ): AdaptiveSizingResult {\n const sizing = this.imageConfig.sizing;\n\n // Check if user specified a fixed height in image config\n const userBaseHeight = this.resolveBaseHeight(viewportWidth);\n\n // If user specified baseHeight (fixed/responsive mode), use it directly\n // Don't clamp to maxHeight since user explicitly chose this value\n if (userBaseHeight !== undefined) {\n return { height: userBaseHeight };\n }\n\n // Adaptive mode - auto-calculate based on container and image count\n const minSize = sizing?.minSize ?? 50;\n const maxSize = sizing?.maxSize ?? 400;\n const targetCoverage = this.config.targetCoverage ?? 0.6;\n const densityFactor = this.config.densityFactor ?? 1.0;\n\n const { width, height } = containerBounds;\n\n // Calculate area-based optimal size\n const containerArea = width * height;\n const targetArea = containerArea * targetCoverage;\n const areaPerImage = targetArea / imageCount;\n\n // Calculate height from area assuming 1.4 aspect ratio (landscape images)\n const aspectRatio = 1.4;\n let calculatedHeight = Math.sqrt(areaPerImage / aspectRatio);\n\n // Apply density factor\n calculatedHeight *= densityFactor;\n\n // Clamp to maximum height constraint\n calculatedHeight = Math.min(calculatedHeight, maxHeight);\n\n // Apply min/max constraints\n let finalHeight = this.clamp(calculatedHeight, minSize, maxSize);\n\n // 'minimize' behavior: force fit below minimum if needed (0.05 floor)\n if (finalHeight === minSize && calculatedHeight < minSize) {\n // Minimum floor is 5% of calculated size\n const floor = Math.max(minSize * 0.05, 20);\n finalHeight = Math.max(floor, calculatedHeight);\n }\n\n return { height: finalHeight };\n }\n\n /**\n * Utility: Clamp a value between min and max\n */\n private clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n }\n}","/**\n * Type definitions for Image Gallery Library\n */\n\n// ============================================================================\n// Core Data Types\n// ============================================================================\n\nexport interface ImageLayout {\n id: number;\n x: number;\n y: number;\n rotation: number;\n scale: number;\n baseSize: number;\n zIndex?: number;\n borderColor?: string;\n}\n\nexport interface ContainerBounds {\n width: number;\n height: number;\n}\n\nexport interface ResponsiveHeight {\n minWidth: number;\n height: number;\n}\n\nexport interface TransformParams {\n x?: number;\n y?: number;\n rotation?: number;\n scale?: number;\n}\n\n// ============================================================================\n// Loader Configuration\n// ============================================================================\n\nexport type GoogleDriveSourceType = 'folder' | 'files';\n\nexport interface GoogleDriveFolderSource {\n type: 'folder';\n folders: string[];\n recursive?: boolean;\n}\n\nexport interface GoogleDriveFilesSource {\n type: 'files';\n files: string[];\n}\n\nexport type GoogleDriveSource = GoogleDriveFolderSource | GoogleDriveFilesSource;\n\nexport interface GoogleDriveLoaderConfig {\n apiKey: string;\n sources: GoogleDriveSource[];\n apiEndpoint?: string;\n allowedExtensions?: string[];\n debugLogging?: boolean;\n}\n\nexport type StaticSourceType = 'urls' | 'path';\n\nexport interface StaticSource {\n type: StaticSourceType;\n urls?: string[];\n basePath?: string;\n files?: string[];\n}\n\nexport interface StaticLoaderConfig {\n sources: StaticSource[];\n validateUrls?: boolean;\n validationTimeout?: number;\n validationMethod?: 'head' | 'simple' | 'none';\n failOnAllMissing?: boolean;\n allowedExtensions?: string[];\n debugLogging?: boolean;\n}\n\nexport interface CompositeLoaderConfigJson {\n loaders: LoaderConfig[];\n debugLogging?: boolean;\n}\n\nexport interface LoaderConfig {\n type: 'googleDrive' | 'static' | 'composite';\n googleDrive?: GoogleDriveLoaderConfig;\n static?: StaticLoaderConfig;\n composite?: CompositeLoaderConfigJson;\n}\n\n// ============================================================================\n// Image Configuration (sizing and rotation)\n// ============================================================================\n\n/**\n * Sizing mode:\n * - 'fixed': single explicit height for all breakpoints\n * - 'responsive': different heights per breakpoint (mobile/tablet/screen)\n * - 'adaptive': auto-calculates based on container size and image count\n */\nexport type SizingMode = 'fixed' | 'responsive' | 'adaptive';\n\n/**\n * Fixed mode height configuration with responsive breakpoints\n * At least one of mobile, tablet, or screen is required\n */\nexport interface FixedModeHeight {\n mobile?: number; // Height for mobile viewports (< responsive.mobile.maxWidth)\n tablet?: number; // Height for tablet viewports (< responsive.tablet.maxWidth)\n screen?: number; // Height for desktop/large screens (>= responsive.tablet.maxWidth)\n}\n\n/**\n * Legacy responsive base height configuration (kept for backward compatibility)\n */\nexport interface ResponsiveBaseHeight {\n default: number; // Base height for large screens\n tablet?: number; // Height for tablet\n mobile?: number; // Height for mobile\n}\n\n/**\n * Image variance configuration\n * Controls random size variation applied to images\n */\nexport interface ImageVarianceConfig {\n min: number; // 0.25-1 (e.g., 0.8)\n max: number; // 1-1.75 (e.g., 1.2)\n}\n\n/**\n * Image sizing configuration\n */\nexport interface ImageSizingConfig {\n mode: SizingMode; // Required: 'fixed', 'responsive', or 'adaptive'\n height?: number | FixedModeHeight; // Fixed/responsive mode: explicit heights\n minSize?: number; // Adaptive mode only: minimum image height (default: 50)\n maxSize?: number; // Adaptive mode only: maximum image height (default: 400)\n variance?: ImageVarianceConfig; // Size variance (min: 0.25-1, max: 1-1.75)\n}\n\n/**\n * Image rotation mode\n */\nexport type ImageRotationMode = 'none' | 'random' | 'tangent';\n\n/**\n * Image rotation range configuration\n */\nexport interface ImageRotationRange {\n min: number; // Negative degrees (-180 to 0)\n max: number; // Positive degrees (0 to 180)\n}\n\n/**\n * Image rotation configuration\n */\nexport interface ImageRotationConfig {\n mode: ImageRotationMode; // default: 'none'\n range?: ImageRotationRange; // Range for random mode\n}\n\n/**\n * Combined image configuration\n */\nexport interface ImageConfig {\n sizing?: ImageSizingConfig;\n rotation?: ImageRotationConfig;\n}\n\n// ============================================================================\n// Layout Configuration\n// ============================================================================\n\nexport interface AdaptiveSizingResult {\n height: number; // Calculated image height\n}\n\n/**\n * Responsive breakpoints configuration for layout\n * Defines viewport width thresholds for mobile and tablet\n */\nexport interface ResponsiveBreakpoints {\n mobile: { maxWidth: number }; // Default: 767\n tablet: { maxWidth: number }; // Default: 1199\n // screen is implicitly > tablet.maxWidth\n}\n\n// Legacy responsive height uses the existing ResponsiveHeight interface (minWidth, height)\n\nexport interface LayoutSpacingConfig {\n padding: number;\n minGap: number;\n}\n\n// Legacy interface kept for backward compatibility in LayoutEngine\nexport interface LegacyLayoutRotationConfig {\n enabled: boolean;\n range: {\n min: number;\n max: number;\n };\n}\n\n// ============================================================================\n// Algorithm-Specific Configuration\n// ============================================================================\n\nexport interface GridAlgorithmConfig {\n columns: number | 'auto';\n rows: number | 'auto';\n stagger: 'none' | 'row' | 'column';\n jitter: number;\n overlap: number;\n fillDirection: 'row' | 'column';\n alignment: 'start' | 'center' | 'end';\n gap: number;\n overflowOffset: number; // 0-0.5, percentage of cell size for stacking overflow images (default: 0.25)\n}\n\nexport interface SpiralAlgorithmConfig {\n spiralType: 'golden' | 'archimedean' | 'logarithmic';\n direction: 'clockwise' | 'counterclockwise';\n tightness: number;\n scaleDecay: number;\n startAngle: number;\n}\n\nexport interface ClusterAlgorithmConfig {\n clusterCount: number | 'auto';\n clusterSpread: number;\n clusterSpacing: number;\n density: 'uniform' | 'varied';\n overlap: number;\n distribution: 'gaussian' | 'uniform';\n}\n\nexport interface WaveAlgorithmConfig {\n rows: number;\n amplitude: number;\n frequency: number;\n phaseShift: number;\n synchronization: 'offset' | 'synchronized' | 'alternating';\n // Note: Image rotation along wave is now controlled via image.rotation.mode = 'tangent'\n}\n\nexport type LayoutAlgorithm = 'random' | 'radial' | 'grid' | 'spiral' | 'cluster' | 'wave';\n\nexport interface LayoutConfig {\n algorithm: LayoutAlgorithm;\n spacing: LayoutSpacingConfig;\n scaleDecay?: number; // For Radial/Spiral - progressive size reduction (0-1, default: 0)\n responsive?: ResponsiveBreakpoints; // Viewport width breakpoints (mobile/tablet)\n targetCoverage?: number; // 0-1, for adaptive sizing (default: 0.6)\n densityFactor?: number; // Controls center point spacing (default: 1.0)\n debugRadials?: boolean;\n debugCenters?: boolean; // Show markers at calculated image center positions\n grid?: GridAlgorithmConfig;\n spiral?: SpiralAlgorithmConfig;\n cluster?: ClusterAlgorithmConfig;\n wave?: WaveAlgorithmConfig;\n}\n\n// ============================================================================\n// Animation Configuration\n// ============================================================================\n\n// Entry Path Types\nexport type EntryPathType = 'linear' | 'arc' | 'bounce' | 'elastic' | 'wave';\n\nexport interface BouncePathConfig {\n overshoot: number; // 0.1-0.3, how far past target (default: 0.15)\n bounces: 1 | 2 | 3; // number of bounces before settling (default: 1)\n decayRatio: number; // 0.3-0.7, each bounce is this % of previous (default: 0.5)\n}\n\nexport type BouncePreset = 'energetic' | 'playful' | 'subtle';\n\nexport interface ElasticPathConfig {\n stiffness: number; // 100-500, higher = faster oscillation (default: 200)\n damping: number; // 10-50, higher = fewer oscillations (default: 20)\n mass: number; // 0.5-3, higher = slower, more momentum (default: 1)\n oscillations: number; // 2-5, visible oscillation count (default: 3)\n}\n\nexport type ElasticPreset = 'gentle' | 'bouncy' | 'wobbly' | 'snappy';\n\nexport interface WavePathConfig {\n amplitude: number; // 20-100px, wave height (default: 40)\n frequency: number; // 1-4, number of complete waves (default: 2)\n decay: boolean; // true = wave diminishes toward target (default: true)\n decayRate: number; // 0.5-1, how fast amplitude decreases (default: 0.8)\n phase: number; // 0-2π, starting phase offset (default: 0)\n}\n\nexport type WavePathPreset = 'gentle' | 'playful' | 'serpentine' | 'flutter';\n\nexport interface EntryPathConfig {\n type: EntryPathType;\n // Preset shortcuts (type-specific)\n bouncePreset?: BouncePreset;\n elasticPreset?: ElasticPreset;\n wavePreset?: WavePathPreset;\n // Type-specific detailed configs (override presets)\n bounce?: Partial<BouncePathConfig>;\n elastic?: Partial<ElasticPathConfig>;\n wave?: Partial<WavePathConfig>;\n}\n\n// Entry Rotation Types\nexport type EntryRotationMode = 'none' | 'settle' | 'spin' | 'wobble' | 'random';\n\nexport interface EntryRotationConfig {\n mode: EntryRotationMode;\n startRotation?: number | { min: number; max: number }; // For 'settle' mode\n spinCount?: number; // For 'spin' mode (default: 1)\n direction?: 'clockwise' | 'counterclockwise' | 'auto' | 'random'; // For 'spin' mode\n wobble?: {\n amplitude: number; // degrees of oscillation (default: 15)\n frequency: number; // oscillations during animation (default: 3)\n decay: boolean; // whether oscillation diminishes (default: true)\n };\n}\n\n// Entry Scale Types\nexport type EntryScaleMode = 'none' | 'grow' | 'shrink' | 'pop' | 'random';\n\nexport interface EntryScalePopConfig {\n overshoot: number; // How much to overshoot final scale (1.1-1.5, default: 1.2)\n bounces: number; // Number of bounces before settling (1-3, default: 1)\n}\n\nexport interface EntryScaleConfig {\n mode: EntryScaleMode;\n startScale?: number; // Fixed start scale for grow/shrink (0.1-4.0)\n range?: { min: number; max: number }; // Random range for 'random' mode\n pop?: EntryScalePopConfig; // Config for 'pop' mode\n}\n\nexport type EntryStartPosition =\n | 'nearest-edge'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'center'\n | 'random-edge'\n | 'circular';\n\nexport interface EntryCircularConfig {\n radius?: number | string; // pixels or percentage like '120%', default: '120%' of container diagonal\n distribution?: 'even' | 'random'; // default: 'even'\n}\n\nexport interface EntryStartConfig {\n position: EntryStartPosition;\n offset?: number; // pixels beyond edge, default: 100\n circular?: EntryCircularConfig;\n}\n\nexport interface EntryTimingConfig {\n duration: number; // default: 600ms\n stagger: number; // default: 150ms\n}\n\nexport interface EntryAnimationConfig {\n start: EntryStartConfig;\n timing: EntryTimingConfig;\n easing: string; // CSS easing, default: 'cubic-bezier(0.25, 1, 0.5, 1)'\n path?: EntryPathConfig; // Animation path type (linear, bounce, elastic, wave)\n rotation?: EntryRotationConfig; // Entry rotation animation\n scale?: EntryScaleConfig; // Entry scale animation\n}\n\nexport interface AnimationEasingConfig {\n default: string;\n bounce: string;\n focus: string;\n}\n\nexport interface AnimationQueueConfig {\n enabled: boolean;\n interval: number;\n maxConcurrent?: number;\n}\n\nexport interface AnimationPerformanceConfig {\n useGPU?: boolean;\n reduceMotion?: boolean;\n}\n\nexport interface AnimationConfig {\n duration: number;\n easing: AnimationEasingConfig;\n queue: AnimationQueueConfig;\n performance?: AnimationPerformanceConfig;\n entry?: EntryAnimationConfig;\n}\n\n// ============================================================================\n// Interaction Configuration\n// ============================================================================\n\nexport interface FocusInteractionConfig {\n scalePercent: number; // Percentage of container (0-1 as fraction, 1-100 as percent)\n zIndex: number;\n animationDuration?: number;\n}\n\nexport interface NavigationInteractionConfig {\n keyboard?: boolean;\n swipe?: boolean;\n mouseWheel?: boolean;\n}\n\nexport interface GestureInteractionConfig {\n pinchToZoom?: boolean;\n doubleTapToFocus?: boolean;\n}\n\nexport interface InteractionConfig {\n focus: FocusInteractionConfig;\n navigation?: NavigationInteractionConfig;\n gestures?: GestureInteractionConfig;\n}\n\n// ============================================================================\n// Rendering Configuration\n// ============================================================================\n\nexport interface ResponsiveRenderingConfig {\n breakpoints: {\n mobile: number;\n tablet?: number;\n desktop?: number;\n };\n mobileDetection: () => boolean;\n}\n\nexport interface UIRenderingConfig {\n showLoadingSpinner: boolean;\n showImageCounter?: boolean;\n showThumbnails?: boolean;\n theme?: 'light' | 'dark' | 'auto';\n}\n\nexport interface PerformanceRenderingConfig {\n lazyLoad?: boolean;\n preloadCount?: number;\n imageQuality?: 'auto' | 'high' | 'medium' | 'low';\n}\n\nexport interface RenderingConfig {\n responsive: ResponsiveRenderingConfig;\n ui: UIRenderingConfig;\n performance?: PerformanceRenderingConfig;\n}\n\n// ============================================================================\n// Main Gallery Configuration\n// ============================================================================\n\nexport interface ImageCloudConfig {\n loader: LoaderConfig;\n image: ImageConfig;\n layout: LayoutConfig;\n animation: AnimationConfig;\n interaction: InteractionConfig;\n rendering: RenderingConfig;\n styling?: ImageStylingConfig;\n debug: boolean;\n}\n\n// Backwards compatibility alias\nexport type GalleryConfig = ImageCloudConfig;\n\nexport interface ImageCloudOptions {\n container?: string | HTMLElement;\n loader?: Partial<LoaderConfig>;\n image?: Partial<ImageConfig>;\n layout?: Partial<LayoutConfig>;\n animation?: Partial<AnimationConfig>;\n interaction?: Partial<InteractionConfig>;\n rendering?: Partial<RenderingConfig>;\n styling?: Partial<ImageStylingConfig>;\n debug?: boolean;\n}\n\n// Backwards compatibility alias\nexport type ImageGalleryOptions = ImageCloudOptions;\n\n// ============================================================================\n// Legacy Configuration Types (for backward compatibility)\n// ============================================================================\n\nexport interface LegacyLayoutConfig {\n baseImageSize?: number;\n rotationRange?: { min: number; max: number };\n type?: 'random' | 'radial';\n debugRadials?: boolean;\n sizeVarianceMin?: number;\n sizeVarianceMax?: number;\n responsiveHeights?: ResponsiveHeight[];\n padding?: number;\n minSpacing?: number;\n minRotation?: number;\n maxRotation?: number;\n}\n\nexport interface LegacyAnimationConfig {\n duration?: number;\n queueInterval?: number;\n easing?: string;\n bounceEasing?: string;\n}\n\nexport interface LegacyZoomConfig {\n focusScale?: number;\n mobileScale?: number; // Deprecated: use scaleTo/scalePercent instead\n focusZIndex?: number;\n}\n\nexport interface LegacyConfig {\n layout?: LegacyLayoutConfig;\n animation?: LegacyAnimationConfig;\n zoom?: LegacyZoomConfig;\n debugLogging?: boolean;\n googleDrive?: {\n apiKey?: string;\n apiEndpoint?: string;\n imageExtensions?: string[];\n };\n loader?: {\n type?: 'googleDrive' | 'static';\n static?: StaticLoaderConfig;\n };\n breakpoints?: {\n mobile?: number;\n tablet?: number;\n desktop?: number;\n };\n isMobile?: () => boolean;\n ui?: {\n showLoadingSpinner?: boolean;\n };\n}\n\nexport interface LegacyImageGalleryOptions {\n containerId?: string;\n loaderType?: 'googleDrive' | 'static';\n folderUrl?: string;\n googleDrive?: {\n apiKey?: string;\n };\n staticLoader?: StaticLoaderConfig;\n config?: LegacyConfig;\n}\n\n// ============================================================================\n// Interface Dependencies\n// ============================================================================\n\nexport interface PlacementGenerator {\n generate(\n imageCount: number,\n containerBounds: ContainerBounds,\n options?: Partial<LayoutConfig>\n ): ImageLayout[];\n}\n\n/**\n * ImageFilter interface for filtering images by extension\n * Implemented by the ImageFilter class in loaders/ImageFilter.ts\n */\nexport interface IImageFilter {\n isAllowed(filename: string): boolean;\n getAllowedExtensions(): string[];\n}\n\n/**\n * ImageLoader interface with consistent lifecycle pattern:\n * 1. Constructor - Initialize with required parameters, throw if missing\n * 2. prepare(filter) - Async discovery of images, accepts filter\n * 3. imagesLength() - Return count of images (after prepare)\n * 4. imageURLs() - Return ordered list of URLs (after prepare)\n */\nexport interface ImageLoader {\n /**\n * Async preparation - discovers images and applies filter\n * Succeeds even if 0 images found (gallery handles empty state)\n * @param filter - Filter to apply to discovered images\n */\n prepare(filter: IImageFilter): Promise<void>;\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare() completes\n */\n imagesLength(): number;\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare() completes\n */\n imageURLs(): string[];\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean;\n}\n\nexport interface GoogleDriveFile {\n id: string;\n name: string;\n mimeType: string;\n parents?: string[];\n}\n\nexport interface GoogleDriveResponse {\n files: GoogleDriveFile[];\n nextPageToken?: string;\n}\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\n// ============================================================================\n// Image Styling Configuration\n// ============================================================================\n\nexport type ShadowPreset = 'none' | 'sm' | 'md' | 'lg' | 'glow';\n\nexport type BorderStyle =\n | 'solid' // Most common - continuous line\n | 'dashed' // Series of dashes\n | 'dotted' // Series of dots\n | 'double' // Two parallel lines\n | 'none' // No border\n | 'groove' // 3D carved into page\n | 'ridge' // 3D raised from page\n | 'inset' // 3D embedded look\n | 'outset' // 3D raised look\n | 'hidden'; // Same as none (for table border conflict resolution)\n\nexport interface BorderConfig {\n width?: number; // pixels, default: 0\n color?: string; // CSS color, default: '#000'\n radius?: number; // pixels, default: 8\n style?: BorderStyle; // default: 'solid'\n}\n\nexport interface DropShadowConfig {\n x: number;\n y: number;\n blur: number;\n color: string;\n}\n\nexport interface FilterConfig {\n grayscale?: number; // 0-1\n blur?: number; // pixels\n brightness?: number; // multiplier (1 = normal)\n contrast?: number; // multiplier\n saturate?: number; // multiplier\n opacity?: number; // 0-1\n sepia?: number; // 0-1\n hueRotate?: number; // degrees\n invert?: number; // 0-1\n dropShadow?: DropShadowConfig | string;\n}\n\nexport interface OutlineConfig {\n width?: number; // pixels\n color?: string; // CSS color\n style?: BorderStyle; // reuses BorderStyle type\n offset?: number; // pixels\n}\n\nexport interface ImageStyleState {\n // CSS class names (space-separated string or array)\n className?: string | string[];\n\n // Border (shorthand applies to all sides)\n border?: BorderConfig;\n borderTop?: Partial<BorderConfig>;\n borderRight?: Partial<BorderConfig>;\n borderBottom?: Partial<BorderConfig>;\n borderLeft?: Partial<BorderConfig>;\n\n // Per-corner border radius (overrides border.radius for specific corners)\n borderRadiusTopLeft?: number;\n borderRadiusTopRight?: number;\n borderRadiusBottomRight?: number;\n borderRadiusBottomLeft?: number;\n\n // Shadow (preset name or custom CSS string)\n shadow?: ShadowPreset | string;\n\n // Filters\n filter?: FilterConfig;\n\n // Other properties\n opacity?: number; // 0-1\n cursor?: string; // CSS cursor value\n outline?: OutlineConfig;\n objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n aspectRatio?: string; // e.g., '16/9', '1/1'\n}\n\nexport interface ImageStylingConfig {\n default?: ImageStyleState;\n hover?: Partial<ImageStyleState>; // inherits from default\n focused?: Partial<ImageStyleState>; // inherits from default\n}\n\n// ============================================================================\n// Focus Animation Types (Cross-Animation Support)\n// ============================================================================\n\n/**\n * State machine states for zoom/focus animations\n */\nexport enum ZoomState {\n IDLE = 'idle', // No focus, no animations\n FOCUSING = 'focusing', // Single image animating in\n FOCUSED = 'focused', // Stable focused state\n UNFOCUSING = 'unfocusing', // Single image animating out\n CROSS_ANIMATING = 'cross_animating' // Two images: one out, one in\n}\n\n/**\n * Handle for a cancellable animation using Web Animations API\n */\nexport interface AnimationHandle {\n id: string;\n element: HTMLElement;\n animation: Animation;\n fromState: TransformParams;\n toState: TransformParams;\n startTime: number;\n duration: number;\n}\n\n/**\n * Snapshot of an element's current transform state\n * Used for capturing position mid-animation\n */\nexport interface AnimationSnapshot {\n x: number;\n y: number;\n rotation: number;\n scale: number;\n}\n\n/**\n * Tracks an image that is currently animating\n */\nexport interface AnimatingImage {\n element: HTMLElement;\n originalState: ImageLayout;\n animationHandle: AnimationHandle;\n direction: 'in' | 'out';\n originalWidth?: number;\n originalHeight?: number;\n}","/**\n * Style utilities for image styling configuration\n */\n\nimport type { ImageStyleState, FilterConfig, BorderConfig, ShadowPreset, DropShadowConfig } from '../config/types';\nimport { SHADOW_PRESETS } from '../config/defaults';\n\n/**\n * Check if a value is a known shadow preset\n */\nfunction isShadowPreset(value: string): value is ShadowPreset {\n return value in SHADOW_PRESETS;\n}\n\n/**\n * Resolve shadow value - converts preset names to CSS values,\n * passes custom CSS strings through directly\n */\nexport function resolveShadow(shadow: ShadowPreset | string | undefined): string {\n if (!shadow) return SHADOW_PRESETS.md;\n if (isShadowPreset(shadow)) return SHADOW_PRESETS[shadow];\n return shadow; // Custom CSS string\n}\n\n/**\n * Build CSS filter string from FilterConfig\n */\nexport function buildFilterString(filter: FilterConfig | undefined): string {\n if (!filter) return '';\n\n const parts: string[] = [];\n\n if (filter.grayscale !== undefined) {\n parts.push(`grayscale(${filter.grayscale})`);\n }\n if (filter.blur !== undefined) {\n parts.push(`blur(${filter.blur}px)`);\n }\n if (filter.brightness !== undefined) {\n parts.push(`brightness(${filter.brightness})`);\n }\n if (filter.contrast !== undefined) {\n parts.push(`contrast(${filter.contrast})`);\n }\n if (filter.saturate !== undefined) {\n parts.push(`saturate(${filter.saturate})`);\n }\n if (filter.opacity !== undefined) {\n parts.push(`opacity(${filter.opacity})`);\n }\n if (filter.sepia !== undefined) {\n parts.push(`sepia(${filter.sepia})`);\n }\n if (filter.hueRotate !== undefined) {\n parts.push(`hue-rotate(${filter.hueRotate}deg)`);\n }\n if (filter.invert !== undefined) {\n parts.push(`invert(${filter.invert})`);\n }\n if (filter.dropShadow !== undefined) {\n if (typeof filter.dropShadow === 'string') {\n parts.push(`drop-shadow(${filter.dropShadow})`);\n } else {\n const ds = filter.dropShadow as DropShadowConfig;\n parts.push(`drop-shadow(${ds.x}px ${ds.y}px ${ds.blur}px ${ds.color})`);\n }\n }\n\n return parts.join(' ');\n}\n\n/**\n * Build border CSS for a single side\n */\nfunction buildSingleBorder(config: BorderConfig | undefined): string {\n if (!config || config.style === 'none' || config.width === 0) {\n return 'none';\n }\n const width = config.width ?? 0;\n const style = config.style ?? 'solid';\n const color = config.color ?? '#000000';\n return `${width}px ${style} ${color}`;\n}\n\n/**\n * CSS properties object type for style application\n */\nexport interface StyleProperties {\n borderRadius?: string;\n borderTopLeftRadius?: string;\n borderTopRightRadius?: string;\n borderBottomRightRadius?: string;\n borderBottomLeftRadius?: string;\n border?: string;\n borderTop?: string;\n borderRight?: string;\n borderBottom?: string;\n borderLeft?: string;\n boxShadow?: string;\n filter?: string;\n opacity?: string;\n cursor?: string;\n outline?: string;\n outlineOffset?: string;\n objectFit?: string;\n aspectRatio?: string;\n}\n\n/**\n * Build complete style properties object from ImageStyleState\n */\nexport function buildStyleProperties(state: ImageStyleState | undefined): StyleProperties {\n if (!state) return {};\n\n const styles: StyleProperties = {};\n\n // Border radius - check for per-corner overrides first\n const hasPerCornerRadius = state.borderRadiusTopLeft !== undefined ||\n state.borderRadiusTopRight !== undefined ||\n state.borderRadiusBottomRight !== undefined ||\n state.borderRadiusBottomLeft !== undefined;\n\n if (hasPerCornerRadius) {\n // Use per-corner radius with fallback to base radius\n const baseRadius = state.border?.radius ?? 0;\n if (state.borderRadiusTopLeft !== undefined) {\n styles.borderTopLeftRadius = `${state.borderRadiusTopLeft}px`;\n } else if (baseRadius) {\n styles.borderTopLeftRadius = `${baseRadius}px`;\n }\n if (state.borderRadiusTopRight !== undefined) {\n styles.borderTopRightRadius = `${state.borderRadiusTopRight}px`;\n } else if (baseRadius) {\n styles.borderTopRightRadius = `${baseRadius}px`;\n }\n if (state.borderRadiusBottomRight !== undefined) {\n styles.borderBottomRightRadius = `${state.borderRadiusBottomRight}px`;\n } else if (baseRadius) {\n styles.borderBottomRightRadius = `${baseRadius}px`;\n }\n if (state.borderRadiusBottomLeft !== undefined) {\n styles.borderBottomLeftRadius = `${state.borderRadiusBottomLeft}px`;\n } else if (baseRadius) {\n styles.borderBottomLeftRadius = `${baseRadius}px`;\n }\n } else if (state.border?.radius !== undefined) {\n styles.borderRadius = `${state.border.radius}px`;\n }\n\n // Check if any per-side border is defined\n const hasPerSideBorder = state.borderTop || state.borderRight || state.borderBottom || state.borderLeft;\n\n if (hasPerSideBorder) {\n // Merge base border with per-side overrides\n const baseBorder = state.border || {};\n\n const topBorder = { ...baseBorder, ...state.borderTop };\n const rightBorder = { ...baseBorder, ...state.borderRight };\n const bottomBorder = { ...baseBorder, ...state.borderBottom };\n const leftBorder = { ...baseBorder, ...state.borderLeft };\n\n styles.borderTop = buildSingleBorder(topBorder);\n styles.borderRight = buildSingleBorder(rightBorder);\n styles.borderBottom = buildSingleBorder(bottomBorder);\n styles.borderLeft = buildSingleBorder(leftBorder);\n } else if (state.border) {\n // Apply uniform border\n styles.border = buildSingleBorder(state.border);\n }\n\n // Shadow\n if (state.shadow !== undefined) {\n styles.boxShadow = resolveShadow(state.shadow);\n }\n\n // Filter - always set to ensure hover filters are properly cleared on mouseleave\n const filterStr = buildFilterString(state.filter);\n styles.filter = filterStr || 'none';\n\n // Opacity\n if (state.opacity !== undefined) {\n styles.opacity = String(state.opacity);\n }\n\n // Cursor\n if (state.cursor !== undefined) {\n styles.cursor = state.cursor;\n }\n\n // Outline\n if (state.outline && state.outline.style !== 'none' && (state.outline.width ?? 0) > 0) {\n const width = state.outline.width ?? 0;\n const style = state.outline.style ?? 'solid';\n const color = state.outline.color ?? '#000000';\n styles.outline = `${width}px ${style} ${color}`;\n if (state.outline.offset !== undefined) {\n styles.outlineOffset = `${state.outline.offset}px`;\n }\n }\n\n // Object fit\n if (state.objectFit !== undefined) {\n styles.objectFit = state.objectFit;\n }\n\n // Aspect ratio\n if (state.aspectRatio !== undefined) {\n styles.aspectRatio = state.aspectRatio;\n }\n\n return styles;\n}\n\n/**\n * Apply style properties to an HTML element\n */\nexport function applyStylesToElement(element: HTMLElement, styles: StyleProperties): void {\n if (styles.borderRadius !== undefined) element.style.borderRadius = styles.borderRadius;\n if (styles.borderTopLeftRadius !== undefined) element.style.borderTopLeftRadius = styles.borderTopLeftRadius;\n if (styles.borderTopRightRadius !== undefined) element.style.borderTopRightRadius = styles.borderTopRightRadius;\n if (styles.borderBottomRightRadius !== undefined) element.style.borderBottomRightRadius = styles.borderBottomRightRadius;\n if (styles.borderBottomLeftRadius !== undefined) element.style.borderBottomLeftRadius = styles.borderBottomLeftRadius;\n if (styles.border !== undefined) element.style.border = styles.border;\n if (styles.borderTop !== undefined) element.style.borderTop = styles.borderTop;\n if (styles.borderRight !== undefined) element.style.borderRight = styles.borderRight;\n if (styles.borderBottom !== undefined) element.style.borderBottom = styles.borderBottom;\n if (styles.borderLeft !== undefined) element.style.borderLeft = styles.borderLeft;\n if (styles.boxShadow !== undefined) element.style.boxShadow = styles.boxShadow;\n if (styles.filter !== undefined) element.style.filter = styles.filter;\n if (styles.opacity !== undefined) element.style.opacity = styles.opacity;\n if (styles.cursor !== undefined) element.style.cursor = styles.cursor;\n if (styles.outline !== undefined) element.style.outline = styles.outline;\n if (styles.outlineOffset !== undefined) element.style.outlineOffset = styles.outlineOffset;\n if (styles.objectFit !== undefined) element.style.objectFit = styles.objectFit;\n if (styles.aspectRatio !== undefined) element.style.aspectRatio = styles.aspectRatio;\n}\n\n/**\n * Resolve className to a space-separated string\n */\nexport function resolveClassName(className: string | string[] | undefined): string {\n if (!className) return '';\n if (Array.isArray(className)) return className.join(' ');\n return className;\n}\n\n/**\n * Apply className to element (additive with existing classes)\n */\nexport function applyClassNameToElement(element: HTMLElement, className: string | string[] | undefined): void {\n const resolved = resolveClassName(className);\n if (resolved) {\n resolved.split(' ').forEach(cls => {\n if (cls.trim()) element.classList.add(cls.trim());\n });\n }\n}\n\n/**\n * Remove className from element\n */\nexport function removeClassNameFromElement(element: HTMLElement, className: string | string[] | undefined): void {\n const resolved = resolveClassName(className);\n if (resolved) {\n resolved.split(' ').forEach(cls => {\n if (cls.trim()) element.classList.remove(cls.trim());\n });\n }\n}\n","/**\n * ZoomEngine.ts\n * Manages zoom/focus behavior for image cloud with cross-animation support\n *\n * Public API:\n * - focusImage(imageElement, containerBounds, originalState)\n * - unfocusImage()\n * - getCurrentFocus()\n * - swapFocus(newImageElement, containerBounds, originalState)\n * - isFocused(imageElement)\n * - isAnimating()\n * - getState()\n * - reset()\n */\n\nimport type {\n FocusInteractionConfig,\n ContainerBounds,\n ImageLayout,\n TransformParams,\n ImageStylingConfig,\n AnimationHandle,\n AnimatingImage\n} from '../config/types';\nimport { ZoomState } from '../config/types';\nimport { AnimationEngine } from './AnimationEngine';\nimport { buildStyleProperties, applyStylesToElement, applyClassNameToElement, removeClassNameFromElement, StyleProperties } from '../utils/styleUtils';\n\ninterface FocusData {\n element: HTMLElement;\n originalState: ImageLayout;\n focusTransform: TransformParams;\n originalZIndex: string;\n originalWidth: number;\n originalHeight: number;\n focusWidth: number;\n focusHeight: number;\n}\n\n// Z-index constants for layering during animations\nconst Z_INDEX = {\n DEFAULT: '',\n UNFOCUSING: 999,\n FOCUSING: 1000,\n FOCUSED: 1000\n};\n\nexport class ZoomEngine {\n private config: FocusInteractionConfig;\n private animationEngine: AnimationEngine;\n\n // State machine\n private state: ZoomState = ZoomState.IDLE;\n private currentFocus: HTMLElement | null = null;\n private focusData: FocusData | null = null;\n\n // Animation tracking for cross-animation\n private outgoing: AnimatingImage | null = null;\n private incoming: AnimatingImage | null = null;\n\n // Generation counter to handle concurrent calls\n private focusGeneration: number = 0;\n\n // Styling\n private defaultStyles: StyleProperties;\n private focusedStyles: StyleProperties;\n private defaultClassName: string | string[] | undefined;\n private focusedClassName: string | string[] | undefined;\n\n constructor(config: FocusInteractionConfig, animationEngine: AnimationEngine, styling?: ImageStylingConfig) {\n this.config = config;\n this.animationEngine = animationEngine;\n\n // Precompute styling properties\n this.defaultStyles = buildStyleProperties(styling?.default);\n this.focusedStyles = buildStyleProperties(styling?.focused);\n this.defaultClassName = styling?.default?.className;\n this.focusedClassName = styling?.focused?.className;\n }\n\n /**\n * Get current state machine state\n */\n getState(): ZoomState {\n return this.state;\n }\n\n /**\n * Check if any animation is in progress\n */\n isAnimating(): boolean {\n return this.state !== ZoomState.IDLE && this.state !== ZoomState.FOCUSED;\n }\n\n /**\n * Normalize scalePercent value\n */\n private normalizeScalePercent(value: number): number {\n return value > 1 ? value / 100 : value;\n }\n\n /**\n * Calculate target dimensions for focused image\n * Returns actual pixel dimensions instead of scale factor for sharper rendering\n */\n private calculateFocusDimensions(imageWidth: number, imageHeight: number, containerBounds: ContainerBounds): { width: number; height: number } {\n const normalizedPercent = this.normalizeScalePercent(this.config.scalePercent);\n const targetHeight = containerBounds.height * normalizedPercent;\n const aspectRatio = imageWidth / imageHeight;\n\n let focusHeight = targetHeight;\n let focusWidth = focusHeight * aspectRatio;\n\n const maxWidth = containerBounds.width * normalizedPercent;\n if (focusWidth > maxWidth) {\n focusWidth = maxWidth;\n focusHeight = focusWidth / aspectRatio;\n }\n\n return { width: focusWidth, height: focusHeight };\n }\n\n /**\n * Calculate the transform needed to center an image (position only, no scale)\n * Scale is handled by animating actual dimensions for sharper rendering\n */\n private calculateFocusTransform(\n containerBounds: ContainerBounds,\n originalState: ImageLayout\n ): TransformParams {\n const centerX = containerBounds.width / 2;\n const centerY = containerBounds.height / 2;\n\n const targetX = centerX - originalState.x;\n const targetY = centerY - originalState.y;\n\n return {\n x: targetX,\n y: targetY,\n rotation: 0,\n scale: 1 // No scale transform - dimensions are animated instead\n };\n }\n\n /**\n * Build transform string for dimension-based zoom (no scale in transform)\n */\n private buildDimensionZoomTransform(params: TransformParams): string {\n const transforms: string[] = ['translate(-50%, -50%)'];\n\n if (params.x !== undefined || params.y !== undefined) {\n const x = params.x ?? 0;\n const y = params.y ?? 0;\n transforms.push(`translate(${x}px, ${y}px)`);\n }\n\n if (params.rotation !== undefined) {\n transforms.push(`rotate(${params.rotation}deg)`);\n }\n\n // Note: scale is intentionally omitted - we animate width/height instead\n\n return transforms.join(' ');\n }\n\n /**\n * Create a Web Animation that animates both transform (position) and dimensions\n * This provides sharper zoom by re-rendering at target size instead of scaling pixels\n */\n private animateWithDimensions(\n element: HTMLElement,\n fromTransform: TransformParams,\n toTransform: TransformParams,\n fromWidth: number,\n fromHeight: number,\n toWidth: number,\n toHeight: number,\n duration: number\n ): Animation {\n const fromTransformStr = this.buildDimensionZoomTransform(fromTransform);\n const toTransformStr = this.buildDimensionZoomTransform(toTransform);\n\n // Clear any CSS transitions to avoid conflicts\n element.style.transition = 'none';\n\n // Create Web Animation with both transform and dimensions\n const animation = element.animate(\n [\n {\n transform: fromTransformStr,\n width: `${fromWidth}px`,\n height: `${fromHeight}px`\n },\n {\n transform: toTransformStr,\n width: `${toWidth}px`,\n height: `${toHeight}px`\n }\n ],\n {\n duration: duration,\n easing: 'cubic-bezier(0.4, 0, 0.2, 1)',\n fill: 'forwards'\n }\n );\n\n return animation;\n }\n\n /**\n * Apply focused styling to an element\n */\n private applyFocusedStyling(element: HTMLElement, zIndex: number): void {\n element.style.zIndex = String(zIndex);\n element.classList.add('fbn-ic-focused');\n applyStylesToElement(element, this.focusedStyles);\n applyClassNameToElement(element, this.focusedClassName);\n }\n\n /**\n * Remove focused styling from an element\n */\n private removeFocusedStyling(element: HTMLElement, originalZIndex: string): void {\n element.style.zIndex = originalZIndex;\n element.classList.remove('fbn-ic-focused');\n removeClassNameFromElement(element, this.focusedClassName);\n applyStylesToElement(element, this.defaultStyles);\n applyClassNameToElement(element, this.defaultClassName);\n }\n\n /**\n * Start focus animation for an image using dimension-based zoom\n * Animates actual width/height for sharper rendering instead of transform scale\n * @param fromTransform - Optional starting transform (for mid-animation reversals)\n * @param fromDimensions - Optional starting dimensions (for mid-animation reversals)\n */\n private startFocusAnimation(\n element: HTMLElement,\n containerBounds: ContainerBounds,\n originalState: ImageLayout,\n fromTransform?: TransformParams,\n fromDimensions?: { width: number; height: number }\n ): AnimatingImage {\n const originalZIndex = element.style.zIndex || '';\n const originalWidth = element.offsetWidth;\n const originalHeight = element.offsetHeight;\n\n // Calculate target dimensions and position\n const focusDimensions = this.calculateFocusDimensions(originalWidth, originalHeight, containerBounds);\n const focusTransform = this.calculateFocusTransform(containerBounds, originalState);\n\n // Apply focused styling immediately\n this.applyFocusedStyling(element, Z_INDEX.FOCUSING);\n\n // Cancel any existing animation\n this.animationEngine.cancelAllAnimations(element);\n\n // Start animation from provided state or original position\n const startTransform: TransformParams = fromTransform ?? {\n x: 0,\n y: 0,\n rotation: originalState.rotation,\n scale: 1 // No scale - using dimensions\n };\n\n const startWidth = fromDimensions?.width ?? originalWidth;\n const startHeight = fromDimensions?.height ?? originalHeight;\n\n const duration = this.config.animationDuration ?? 600;\n\n // Create dimension-based animation\n const animation = this.animateWithDimensions(\n element,\n startTransform,\n focusTransform,\n startWidth,\n startHeight,\n focusDimensions.width,\n focusDimensions.height,\n duration\n );\n\n // Create animation handle\n const handle: AnimationHandle = {\n id: `focus-${Date.now()}`,\n element,\n animation,\n fromState: startTransform,\n toState: focusTransform,\n startTime: performance.now(),\n duration\n };\n\n // Store focus data for later (including dimensions for unfocus)\n this.focusData = {\n element,\n originalState,\n focusTransform,\n originalZIndex,\n originalWidth,\n originalHeight,\n focusWidth: focusDimensions.width,\n focusHeight: focusDimensions.height\n };\n\n return {\n element,\n originalState,\n animationHandle: handle,\n direction: 'in' as const,\n originalWidth,\n originalHeight\n };\n }\n\n /**\n * Start unfocus animation for an image using dimension-based zoom\n * Animates back to original dimensions for consistent behavior\n * @param fromDimensions - Optional starting dimensions (for mid-animation reversals)\n */\n private startUnfocusAnimation(\n element: HTMLElement,\n originalState: ImageLayout,\n fromTransform?: TransformParams,\n fromDimensions?: { width: number; height: number }\n ): AnimatingImage {\n // Set z-index for unfocusing (below incoming)\n element.style.zIndex = String(Z_INDEX.UNFOCUSING);\n\n // Cancel any existing animation\n this.animationEngine.cancelAllAnimations(element);\n\n // Start from current focused state (or provided state for interrupted animations)\n const startTransform = fromTransform ?? this.focusData?.focusTransform ?? { x: 0, y: 0, rotation: 0, scale: 1 };\n const startWidth = fromDimensions?.width ?? this.focusData?.focusWidth ?? element.offsetWidth;\n const startHeight = fromDimensions?.height ?? this.focusData?.focusHeight ?? element.offsetHeight;\n\n // Target is original position and dimensions\n const toState: TransformParams = {\n x: 0,\n y: 0,\n rotation: originalState.rotation,\n scale: 1 // No scale - using dimensions\n };\n\n const targetWidth = this.focusData?.originalWidth ?? element.offsetWidth;\n const targetHeight = this.focusData?.originalHeight ?? element.offsetHeight;\n const duration = this.config.animationDuration ?? 600;\n\n // Create dimension-based animation\n const animation = this.animateWithDimensions(\n element,\n startTransform,\n toState,\n startWidth,\n startHeight,\n targetWidth,\n targetHeight,\n duration\n );\n\n // Create animation handle\n const handle: AnimationHandle = {\n id: `unfocus-${Date.now()}`,\n element,\n animation,\n fromState: startTransform,\n toState: toState,\n startTime: performance.now(),\n duration\n };\n\n return {\n element,\n originalState,\n animationHandle: handle,\n direction: 'out' as const,\n originalWidth: targetWidth,\n originalHeight: targetHeight\n };\n }\n\n /**\n * Handle animation completion\n */\n private async waitForAnimation(handle: AnimationHandle): Promise<void> {\n try {\n await handle.animation.finished;\n } catch {\n // Animation was cancelled - this is expected during interruption\n }\n }\n\n /**\n * Reset an element instantly to its original position and dimensions (no animation)\n */\n private resetElementInstantly(\n element: HTMLElement,\n originalState: ImageLayout,\n originalZIndex: string,\n originalWidth?: number,\n originalHeight?: number\n ): void {\n // Cancel any active animation (including completed animations with fill: 'forwards')\n this.animationEngine.cancelAllAnimations(element);\n\n // Build transform string for original position (no scale - using dimensions)\n const transforms = ['translate(-50%, -50%)'];\n transforms.push('translate(0px, 0px)');\n transforms.push(`rotate(${originalState.rotation}deg)`);\n // No scale in transform - dimensions handle sizing\n\n element.style.transition = 'none';\n element.style.transform = transforms.join(' ');\n\n // Restore original dimensions if provided\n if (originalWidth !== undefined && originalHeight !== undefined) {\n element.style.width = `${originalWidth}px`;\n element.style.height = `${originalHeight}px`;\n }\n\n // Remove focused styling\n this.removeFocusedStyling(element, originalZIndex);\n }\n\n /**\n * Focus (zoom) an image to center of container\n * Implements cross-animation when swapping focus\n */\n async focusImage(\n imageElement: HTMLElement,\n containerBounds: ContainerBounds,\n originalState: ImageLayout\n ): Promise<void> {\n // Same image clicked while already focused - unfocus it\n if (this.currentFocus === imageElement && this.state === ZoomState.FOCUSED) {\n return this.unfocusImage();\n }\n\n // Same image clicked while it's animating in - reverse to unfocus\n if (this.incoming?.element === imageElement && this.state === ZoomState.FOCUSING) {\n // Capture current position and dimensions, then reverse\n const snapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale transform - using dimensions\n };\n const fromDimensions = {\n width: imageElement.offsetWidth,\n height: imageElement.offsetHeight\n };\n\n this.outgoing = this.startUnfocusAnimation(\n imageElement,\n this.incoming.originalState,\n fromTransform,\n fromDimensions\n );\n this.incoming = null;\n this.state = ZoomState.UNFOCUSING;\n\n await this.waitForAnimation(this.outgoing.animationHandle);\n this.removeFocusedStyling(this.outgoing.element, this.focusData?.originalZIndex || '');\n this.outgoing = null;\n this.currentFocus = null;\n this.focusData = null;\n this.state = ZoomState.IDLE;\n return;\n }\n\n // Increment generation to invalidate any previous in-flight calls\n const myGeneration = ++this.focusGeneration;\n\n switch (this.state) {\n case ZoomState.IDLE:\n // Simple focus - no current focus\n this.state = ZoomState.FOCUSING;\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n await this.waitForAnimation(this.incoming.animationHandle);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) return;\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n\n case ZoomState.FOCUSED:\n // Cross-animation: unfocus current while focusing new\n this.state = ZoomState.CROSS_ANIMATING;\n\n // Start outgoing animation for currently focused image\n if (this.currentFocus && this.focusData) {\n this.outgoing = this.startUnfocusAnimation(\n this.currentFocus,\n this.focusData.originalState\n );\n }\n\n // Start incoming animation for new image\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n // Wait for both animations\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(this.incoming.animationHandle)\n ]);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) {\n return;\n }\n\n // Cleanup outgoing\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n this.outgoing = null;\n }\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n\n case ZoomState.FOCUSING:\n // New image clicked while another is focusing\n // Cancel the incoming and start new focus\n if (this.incoming) {\n this.animationEngine.cancelAnimation(this.incoming.animationHandle, false);\n this.resetElementInstantly(\n this.incoming.element,\n this.incoming.originalState,\n this.focusData?.originalZIndex || '',\n this.focusData?.originalWidth,\n this.focusData?.originalHeight\n );\n this.incoming = null;\n }\n\n // Start focusing the new image\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n await this.waitForAnimation(this.incoming.animationHandle);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) return;\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n\n case ZoomState.UNFOCUSING:\n // New image clicked while current is unfocusing\n // Let outgoing continue, start cross-animation\n this.state = ZoomState.CROSS_ANIMATING;\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(this.incoming.animationHandle)\n ]);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) return;\n\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n this.outgoing = null;\n }\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n\n case ZoomState.CROSS_ANIMATING:\n // Handle click during cross-animation\n\n // If clicking the same image that's animating in, ignore (it's already targeting focus)\n if (this.incoming?.element === imageElement) {\n return;\n }\n\n // If clicking the same image that's animating out, let it become the new focus target\n // (reverse direction - it should animate back to focused)\n if (this.outgoing?.element === imageElement) {\n // Cancel outgoing animation and capture current state\n const snapshot = this.animationEngine.cancelAnimation(this.outgoing.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const fromDimensions = {\n width: imageElement.offsetWidth,\n height: imageElement.offsetHeight\n };\n\n // Redirect current incoming to become outgoing\n if (this.incoming) {\n const incomingSnapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const incomingFrom: TransformParams = {\n x: incomingSnapshot.x,\n y: incomingSnapshot.y,\n rotation: incomingSnapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const incomingFromDimensions = {\n width: this.incoming.element.offsetWidth,\n height: this.incoming.element.offsetHeight\n };\n this.outgoing = this.startUnfocusAnimation(\n this.incoming.element,\n this.incoming.originalState,\n incomingFrom,\n incomingFromDimensions\n );\n } else {\n this.outgoing = null;\n }\n\n // Start new incoming for the clicked (formerly outgoing) image\n // Use fromTransform and fromDimensions to continue from current state\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState, fromTransform, fromDimensions);\n\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(this.incoming.animationHandle)\n ]);\n\n if (this.focusGeneration !== myGeneration) return;\n\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n this.outgoing = null;\n }\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n return;\n }\n\n // Third different image clicked during cross-animation\n // 1. Reset outgoing instantly\n // 2. Redirect incoming to become outgoing\n // 3. Start new incoming\n\n // Reset outgoing instantly\n if (this.outgoing) {\n this.animationEngine.cancelAnimation(this.outgoing.animationHandle, false);\n this.resetElementInstantly(\n this.outgoing.element,\n this.outgoing.originalState,\n this.outgoing.originalState.zIndex?.toString() || '',\n this.outgoing.originalWidth,\n this.outgoing.originalHeight\n );\n this.outgoing = null;\n }\n\n // Redirect incoming to outgoing (animate from current position back to original)\n if (this.incoming) {\n const snapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const fromDimensions = {\n width: this.incoming.element.offsetWidth,\n height: this.incoming.element.offsetHeight\n };\n\n this.outgoing = this.startUnfocusAnimation(\n this.incoming.element,\n this.incoming.originalState,\n fromTransform,\n fromDimensions\n );\n }\n\n // Start new incoming\n this.incoming = this.startFocusAnimation(imageElement, containerBounds, originalState);\n\n // Wait for both\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(this.incoming.animationHandle)\n ]);\n\n // Check if we're still the active generation\n if (this.focusGeneration !== myGeneration) return;\n\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n this.outgoing = null;\n }\n\n this.currentFocus = imageElement;\n this.incoming = null;\n this.state = ZoomState.FOCUSED;\n break;\n }\n }\n\n /**\n * Unfocus current image, returning it to original position\n */\n async unfocusImage(): Promise<void> {\n // Increment generation to invalidate any previous in-flight calls\n const myGeneration = ++this.focusGeneration;\n\n if (!this.currentFocus || !this.focusData) {\n // Handle case where we're in FOCUSING state\n if (this.incoming && this.state === ZoomState.FOCUSING) {\n const snapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const fromDimensions = {\n width: this.incoming.element.offsetWidth,\n height: this.incoming.element.offsetHeight\n };\n\n this.outgoing = this.startUnfocusAnimation(\n this.incoming.element,\n this.incoming.originalState,\n fromTransform,\n fromDimensions\n );\n this.incoming = null;\n this.state = ZoomState.UNFOCUSING;\n\n await this.waitForAnimation(this.outgoing.animationHandle);\n\n if (this.focusGeneration !== myGeneration) return;\n\n this.removeFocusedStyling(this.outgoing.element, this.focusData?.originalZIndex || '');\n this.outgoing = null;\n this.focusData = null;\n this.state = ZoomState.IDLE;\n }\n return;\n }\n\n // Handle cross-animation cancellation (ESC during cross-animation)\n if (this.state === ZoomState.CROSS_ANIMATING) {\n // Cancel incoming and animate it back\n if (this.incoming) {\n const snapshot = this.animationEngine.cancelAnimation(this.incoming.animationHandle, true);\n const fromTransform: TransformParams = {\n x: snapshot.x,\n y: snapshot.y,\n rotation: snapshot.rotation,\n scale: 1 // No scale - using dimensions\n };\n const fromDimensions = {\n width: this.incoming.element.offsetWidth,\n height: this.incoming.element.offsetHeight\n };\n\n // Start unfocus for incoming from its current position\n const incomingUnfocus = this.startUnfocusAnimation(\n this.incoming.element,\n this.incoming.originalState,\n fromTransform,\n fromDimensions\n );\n\n // Wait for both outgoing and the redirected incoming\n await Promise.all([\n this.outgoing ? this.waitForAnimation(this.outgoing.animationHandle) : Promise.resolve(),\n this.waitForAnimation(incomingUnfocus.animationHandle)\n ]);\n\n if (this.focusGeneration !== myGeneration) return;\n\n // Cleanup\n if (this.outgoing) {\n this.removeFocusedStyling(this.outgoing.element, this.outgoing.originalState.zIndex?.toString() || '');\n }\n this.removeFocusedStyling(incomingUnfocus.element, this.incoming.originalState.zIndex?.toString() || '');\n\n this.outgoing = null;\n this.incoming = null;\n this.currentFocus = null;\n this.focusData = null;\n this.state = ZoomState.IDLE;\n return;\n }\n }\n\n // Normal unfocus from FOCUSED state\n this.state = ZoomState.UNFOCUSING;\n const element = this.currentFocus;\n const originalState = this.focusData.originalState;\n const originalZIndex = this.focusData.originalZIndex;\n\n this.outgoing = this.startUnfocusAnimation(element, originalState);\n\n await this.waitForAnimation(this.outgoing.animationHandle);\n\n if (this.focusGeneration !== myGeneration) return;\n\n this.removeFocusedStyling(element, originalZIndex);\n this.outgoing = null;\n this.currentFocus = null;\n this.focusData = null;\n this.state = ZoomState.IDLE;\n }\n\n /**\n * Swap focus from current image to a new one (alias for focusImage with cross-animation)\n */\n async swapFocus(\n newImageElement: HTMLElement,\n containerBounds: ContainerBounds,\n originalState: ImageLayout\n ): Promise<void> {\n return this.focusImage(newImageElement, containerBounds, originalState);\n }\n\n /**\n * Get currently focused image element\n */\n getCurrentFocus(): HTMLElement | null {\n return this.currentFocus;\n }\n\n /**\n * Check if an image is currently focused (stable state)\n */\n isFocused(imageElement: HTMLElement): boolean {\n return this.currentFocus === imageElement && this.state === ZoomState.FOCUSED;\n }\n\n /**\n * Check if an image is the target of current focus animation\n */\n isTargetingFocus(imageElement: HTMLElement): boolean {\n return this.incoming?.element === imageElement;\n }\n\n /**\n * Check if an image is involved in any focus/animation state\n * Returns true if the image is focused, animating in, or animating out\n * Useful for hover state management - don't apply hover to animating images\n */\n isInvolved(imageElement: HTMLElement): boolean {\n return (\n this.currentFocus === imageElement ||\n this.incoming?.element === imageElement ||\n this.outgoing?.element === imageElement\n );\n }\n\n /**\n * Apply a temporary horizontal drag offset to the focused image\n * Used during swipe gestures for visual feedback\n */\n setDragOffset(offset: number): void {\n if (!this.currentFocus || !this.focusData || this.state !== ZoomState.FOCUSED) return;\n\n const element = this.currentFocus;\n const focusTransform = this.focusData.focusTransform;\n\n // Build transform with additional horizontal offset\n const transforms: string[] = ['translate(-50%, -50%)'];\n const x = (focusTransform.x ?? 0) + offset;\n const y = focusTransform.y ?? 0;\n transforms.push(`translate(${x}px, ${y}px)`);\n if (focusTransform.rotation !== undefined) {\n transforms.push(`rotate(${focusTransform.rotation}deg)`);\n }\n\n element.style.transition = 'none';\n element.style.transform = transforms.join(' ');\n }\n\n /**\n * Clear the drag offset, optionally animating back to center\n * @param animate - If true, animate back to center; if false, snap instantly\n * @param duration - Animation duration in ms (default 150)\n */\n clearDragOffset(animate: boolean, duration: number = 150): void {\n if (!this.currentFocus || !this.focusData || this.state !== ZoomState.FOCUSED) return;\n\n const element = this.currentFocus;\n const focusTransform = this.focusData.focusTransform;\n\n // Build the centered transform (no offset)\n const transforms: string[] = ['translate(-50%, -50%)'];\n const x = focusTransform.x ?? 0;\n const y = focusTransform.y ?? 0;\n transforms.push(`translate(${x}px, ${y}px)`);\n if (focusTransform.rotation !== undefined) {\n transforms.push(`rotate(${focusTransform.rotation}deg)`);\n }\n const centeredTransform = transforms.join(' ');\n\n if (animate) {\n element.style.transition = `transform ${duration}ms ease-out`;\n element.style.transform = centeredTransform;\n // Clear transition after animation completes\n setTimeout(() => {\n if (this.currentFocus === element) {\n element.style.transition = 'none';\n }\n }, duration);\n } else {\n element.style.transition = 'none';\n element.style.transform = centeredTransform;\n }\n }\n\n /**\n * Reset zoom state (cancels all animations)\n */\n reset(): void {\n // Cancel any active animations\n if (this.outgoing) {\n this.animationEngine.cancelAnimation(this.outgoing.animationHandle, false);\n this.resetElementInstantly(\n this.outgoing.element,\n this.outgoing.originalState,\n this.outgoing.originalState.zIndex?.toString() || '',\n this.outgoing.originalWidth,\n this.outgoing.originalHeight\n );\n }\n\n if (this.incoming) {\n this.animationEngine.cancelAnimation(this.incoming.animationHandle, false);\n this.resetElementInstantly(\n this.incoming.element,\n this.incoming.originalState,\n this.focusData?.originalZIndex || '',\n this.focusData?.originalWidth,\n this.focusData?.originalHeight\n );\n }\n\n if (this.currentFocus && this.focusData) {\n this.resetElementInstantly(\n this.currentFocus,\n this.focusData.originalState,\n this.focusData.originalZIndex,\n this.focusData.originalWidth,\n this.focusData.originalHeight\n );\n }\n\n this.state = ZoomState.IDLE;\n this.currentFocus = null;\n this.focusData = null;\n this.outgoing = null;\n this.incoming = null;\n }\n}\n","/**\n * SwipeEngine.ts\n * Handles touch swipe gestures for navigating between focused images\n *\n * Public API:\n * - enable() - Start listening for touch events\n * - disable() - Stop listening (when no image focused)\n * - destroy() - Clean up all event listeners\n * - setDragCallback(callback) - Set callback for drag offset updates\n */\n\n// Constants for swipe detection\nconst SWIPE_THRESHOLD_PX = 50;\nconst SWIPE_VELOCITY_THRESHOLD = 0.5; // px/ms\nconst SWIPE_MIN_DISTANCE_FOR_VELOCITY = 20;\nconst DRAG_DAMPING = 0.3;\nconst SNAP_BACK_DURATION_MS = 150;\nconst HORIZONTAL_ANGLE_THRESHOLD_DEG = 30;\n\ninterface SwipeCallbacks {\n onNext: () => void;\n onPrev: () => void;\n onDragOffset: (offset: number) => void;\n onDragEnd: (navigated: boolean) => void;\n}\n\ninterface TouchState {\n startX: number;\n startY: number;\n startTime: number;\n currentX: number;\n isDragging: boolean;\n isHorizontalSwipe: boolean | null; // null = not determined yet\n}\n\nexport class SwipeEngine {\n private container: HTMLElement;\n private callbacks: SwipeCallbacks;\n private enabled: boolean = false;\n private touchState: TouchState | null = null;\n\n // Track recent touch activity to prevent click-outside from unfocusing\n private recentTouchTimestamp: number = 0;\n private static readonly TOUCH_CLICK_DELAY = 300; // ms to ignore clicks after touch\n\n // Bound event handlers for proper cleanup\n private boundTouchStart: (e: TouchEvent) => void;\n private boundTouchMove: (e: TouchEvent) => void;\n private boundTouchEnd: (e: TouchEvent) => void;\n private boundTouchCancel: (e: TouchEvent) => void;\n\n constructor(container: HTMLElement, callbacks: SwipeCallbacks) {\n this.container = container;\n this.callbacks = callbacks;\n\n // Bind handlers\n this.boundTouchStart = this.handleTouchStart.bind(this);\n this.boundTouchMove = this.handleTouchMove.bind(this);\n this.boundTouchEnd = this.handleTouchEnd.bind(this);\n this.boundTouchCancel = this.handleTouchCancel.bind(this);\n }\n\n /**\n * Start listening for touch events\n */\n enable(): void {\n if (this.enabled) return;\n this.enabled = true;\n\n // Set touch-action to allow vertical scroll but let JS handle horizontal\n this.container.style.touchAction = 'pan-y';\n\n this.container.addEventListener('touchstart', this.boundTouchStart, { passive: false });\n this.container.addEventListener('touchmove', this.boundTouchMove, { passive: false });\n this.container.addEventListener('touchend', this.boundTouchEnd, { passive: true });\n this.container.addEventListener('touchcancel', this.boundTouchCancel, { passive: true });\n }\n\n /**\n * Stop listening for touch events\n */\n disable(): void {\n if (!this.enabled) return;\n this.enabled = false;\n\n // Restore default touch-action\n this.container.style.touchAction = '';\n\n this.container.removeEventListener('touchstart', this.boundTouchStart);\n this.container.removeEventListener('touchmove', this.boundTouchMove);\n this.container.removeEventListener('touchend', this.boundTouchEnd);\n this.container.removeEventListener('touchcancel', this.boundTouchCancel);\n\n // Reset any in-progress drag\n if (this.touchState?.isDragging) {\n this.callbacks.onDragEnd(false);\n }\n this.touchState = null;\n }\n\n /**\n * Clean up all event listeners\n */\n destroy(): void {\n this.disable();\n }\n\n /**\n * Check if a touch interaction happened recently\n * Used to prevent click-outside from unfocusing immediately after touch\n */\n hadRecentTouch(): boolean {\n return Date.now() - this.recentTouchTimestamp < SwipeEngine.TOUCH_CLICK_DELAY;\n }\n\n private handleTouchStart(e: TouchEvent): void {\n if (e.touches.length !== 1) return;\n\n // Mark recent touch to prevent click-outside unfocus\n this.recentTouchTimestamp = Date.now();\n\n const touch = e.touches[0];\n this.touchState = {\n startX: touch.clientX,\n startY: touch.clientY,\n startTime: performance.now(),\n currentX: touch.clientX,\n isDragging: false,\n isHorizontalSwipe: null\n };\n }\n\n private handleTouchMove(e: TouchEvent): void {\n if (!this.touchState || e.touches.length !== 1) return;\n\n const touch = e.touches[0];\n const deltaX = touch.clientX - this.touchState.startX;\n const deltaY = touch.clientY - this.touchState.startY;\n\n // Determine swipe direction on first significant movement\n if (this.touchState.isHorizontalSwipe === null) {\n const totalDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n if (totalDistance > 10) {\n // Calculate angle from horizontal\n const angleRad = Math.atan2(Math.abs(deltaY), Math.abs(deltaX));\n const angleDeg = angleRad * (180 / Math.PI);\n this.touchState.isHorizontalSwipe = angleDeg <= HORIZONTAL_ANGLE_THRESHOLD_DEG;\n }\n }\n\n // If determined to be vertical, don't capture\n if (this.touchState.isHorizontalSwipe === false) {\n return;\n }\n\n // If horizontal swipe, prevent default and track drag\n if (this.touchState.isHorizontalSwipe === true) {\n e.preventDefault();\n this.touchState.isDragging = true;\n this.touchState.currentX = touch.clientX;\n\n // Apply damped offset to focused image\n const dampedOffset = deltaX * DRAG_DAMPING;\n this.callbacks.onDragOffset(dampedOffset);\n }\n }\n\n private handleTouchEnd(_e: TouchEvent): void {\n if (!this.touchState) return;\n\n // Update timestamp to prevent click-outside unfocus\n this.recentTouchTimestamp = Date.now();\n\n const deltaX = this.touchState.currentX - this.touchState.startX;\n const deltaTime = performance.now() - this.touchState.startTime;\n const velocity = Math.abs(deltaX) / deltaTime;\n const absDistance = Math.abs(deltaX);\n\n let navigated = false;\n\n // Only process if it was a horizontal swipe\n if (this.touchState.isHorizontalSwipe === true && this.touchState.isDragging) {\n // Check if swipe threshold met (distance or velocity)\n const thresholdMet =\n absDistance >= SWIPE_THRESHOLD_PX ||\n (velocity >= SWIPE_VELOCITY_THRESHOLD && absDistance >= SWIPE_MIN_DISTANCE_FOR_VELOCITY);\n\n if (thresholdMet) {\n navigated = true;\n if (deltaX < 0) {\n // Swipe left -> next image\n this.callbacks.onNext();\n } else {\n // Swipe right -> previous image\n this.callbacks.onPrev();\n }\n }\n }\n\n // Notify drag end (handles snap-back if not navigated)\n if (this.touchState.isDragging) {\n this.callbacks.onDragEnd(navigated);\n }\n\n this.touchState = null;\n }\n\n private handleTouchCancel(_e: TouchEvent): void {\n if (this.touchState?.isDragging) {\n this.callbacks.onDragEnd(false);\n }\n this.touchState = null;\n }\n}\n\nexport { SNAP_BACK_DURATION_MS };\n","/**\n * GoogleDriveLoader.ts\n * Loads images from a public Google Drive folder\n *\n * Public API:\n * - prepare(filter) - Async discovery of images\n * - imagesLength() - Get count of discovered images\n * - imageURLs() - Get ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n *\n * Helper methods (for advanced usage):\n * - extractFolderId(folderUrl)\n * - manualImageUrls(imageIds)\n */\n\nimport type { ImageLoader, IImageFilter, GoogleDriveResponse, GoogleDriveLoaderConfig, GoogleDriveSource } from '../config/types';\n\nexport class GoogleDriveLoader implements ImageLoader {\n private apiKey: string;\n private apiEndpoint: string;\n private debugLogging: boolean;\n private sources: GoogleDriveSource[];\n\n // State for new interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: Partial<GoogleDriveLoaderConfig> = {}) {\n this.apiKey = config.apiKey ?? '';\n this.apiEndpoint = config.apiEndpoint ?? 'https://www.googleapis.com/drive/v3/files';\n this.debugLogging = config.debugLogging ?? false;\n this.sources = config.sources ?? [];\n\n // Validate that we have sources configured\n if (!this.sources || this.sources.length === 0) {\n throw new Error('GoogleDriveLoader requires at least one source to be configured');\n }\n }\n\n /**\n * Prepare the loader by discovering all images from configured sources\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n for (const source of this.sources) {\n if (source.type === 'folder') {\n for (const folderUrl of source.folders) {\n const recursive = source.recursive !== undefined ? source.recursive : true;\n const urls = await this.loadFromFolder(folderUrl, filter, recursive);\n this._discoveredUrls.push(...urls);\n }\n } else if (source.type === 'files') {\n const urls = await this.loadFiles(source.files, filter);\n this._discoveredUrls.push(...urls);\n }\n }\n\n this._prepared = true;\n }\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('GoogleDriveLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('GoogleDriveLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Extract folder ID from various Google Drive URL formats\n * @param folderUrl - Google Drive folder URL\n * @returns Folder ID or null if invalid\n */\n extractFolderId(folderUrl: string): string | null {\n // Handle various URL formats:\n // https://drive.google.com/drive/folders/FOLDER_ID\n // https://drive.google.com/drive/folders/FOLDER_ID?usp=sharing\n\n const patterns = [\n /\\/folders\\/([a-zA-Z0-9_-]+)/, // Standard format\n /id=([a-zA-Z0-9_-]+)/ // Alternative format\n ];\n\n for (const pattern of patterns) {\n const match = folderUrl.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n }\n\n /**\n * Load images from a Google Drive folder\n * @param folderUrl - Google Drive folder URL\n * @param filter - Filter to apply to discovered images\n * @param recursive - Whether to include images from subfolders\n * @returns Promise resolving to array of image URLs\n */\n private async loadFromFolder(folderUrl: string, filter: IImageFilter, recursive: boolean = true): Promise<string[]> {\n const folderId = this.extractFolderId(folderUrl);\n\n if (!folderId) {\n throw new Error('Invalid Google Drive folder URL. Please check the URL format.');\n }\n\n // If no API key is configured, use direct link method\n if (!this.apiKey || this.apiKey === 'YOUR_API_KEY_HERE') {\n return this.loadImagesDirectly(folderId, filter);\n }\n\n try {\n if (recursive) {\n return await this.loadImagesRecursively(folderId, filter);\n } else {\n return await this.loadImagesFromSingleFolder(folderId, filter);\n }\n } catch (error) {\n console.error('Error loading from Google Drive API:', error);\n // Fallback to direct link method\n return this.loadImagesDirectly(folderId, filter);\n }\n }\n\n /**\n * Load images from a single folder (non-recursive)\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesFromSingleFolder(folderId: string, filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n // Query for all files in this folder\n const query = `'${folderId}' in parents and trashed=false`;\n const fields = 'files(id,name,mimeType,thumbnailLink)';\n const url = `${this.apiEndpoint}?q=${encodeURIComponent(query)}&fields=${fields}&key=${this.apiKey}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data: GoogleDriveResponse = await response.json();\n\n // Filter for valid image files only using the provided filter\n const validFiles = data.files.filter(file =>\n file.mimeType.startsWith('image/') && filter.isAllowed(file.name)\n );\n\n this.log(`Found ${validFiles.length} images in folder ${folderId} (non-recursive)`);\n\n // Add image URLs\n validFiles.forEach(file => {\n imageUrls.push(`https://lh3.googleusercontent.com/d/${file.id}=s1600`);\n this.log(`Added file: ${file.name}`);\n });\n\n return imageUrls;\n }\n\n /**\n * Load specific files by their URLs or IDs\n * @param fileUrls - Array of Google Drive file URLs or IDs\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadFiles(fileUrls: string[], filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n for (const fileUrl of fileUrls) {\n const fileId = this.extractFileId(fileUrl);\n\n if (!fileId) {\n this.log(`Skipping invalid file URL: ${fileUrl}`);\n continue;\n }\n\n // Validate it's an image file\n if (this.apiKey && this.apiKey !== 'YOUR_API_KEY_HERE') {\n try {\n // Get file metadata to verify it's an image\n const metadataUrl = `${this.apiEndpoint}/${fileId}?fields=name,mimeType&key=${this.apiKey}`;\n const response = await fetch(metadataUrl);\n\n if (response.ok) {\n const metadata = await response.json();\n if (metadata.mimeType.startsWith('image/') && filter.isAllowed(metadata.name)) {\n imageUrls.push(`https://lh3.googleusercontent.com/d/${fileId}=s1600`);\n this.log(`Added file: ${metadata.name}`);\n } else {\n this.log(`Skipping non-image file: ${metadata.name} (${metadata.mimeType})`);\n }\n } else {\n this.log(`Failed to fetch metadata for file ${fileId}: ${response.status}`);\n }\n } catch (error) {\n this.log(`Error fetching metadata for file ${fileId}:`, error);\n }\n } else {\n // Without API key, assume it's valid and add it\n imageUrls.push(`https://lh3.googleusercontent.com/d/${fileId}=s1600`);\n }\n }\n\n return imageUrls;\n }\n\n /**\n * Extract file ID from Google Drive file URL\n * @param fileUrl - Google Drive file URL or file ID\n * @returns File ID or null if invalid\n */\n private extractFileId(fileUrl: string): string | null {\n // Handle various URL formats:\n // https://drive.google.com/file/d/FILE_ID/view\n // https://drive.google.com/open?id=FILE_ID\n // FILE_ID (raw ID)\n\n // If it looks like a raw ID (no slashes or protocol), return it\n if (!/[/:.]/.test(fileUrl)) {\n return fileUrl;\n }\n\n const patterns = [\n /\\/file\\/d\\/([a-zA-Z0-9_-]+)/, // Standard file format\n /\\/open\\?id=([a-zA-Z0-9_-]+)/, // Alternative format\n /id=([a-zA-Z0-9_-]+)/ // Generic id parameter\n ];\n\n for (const pattern of patterns) {\n const match = fileUrl.match(pattern);\n if (match && match[1]) {\n return match[1];\n }\n }\n\n return null;\n }\n\n /**\n * Recursively load images from a folder and all its subfolders\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesRecursively(folderId: string, filter: IImageFilter): Promise<string[]> {\n const imageUrls: string[] = [];\n\n // Query for all files in this folder\n const query = `'${folderId}' in parents and trashed=false`;\n // Request thumbnailLink for PDFs\n const fields = 'files(id,name,mimeType,thumbnailLink)';\n const url = `${this.apiEndpoint}?q=${encodeURIComponent(query)}&fields=${fields}&key=${this.apiKey}`;\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}`);\n }\n\n const data: GoogleDriveResponse = await response.json();\n\n // Separate images and folders using the provided filter\n const validFiles = data.files.filter(file =>\n file.mimeType.startsWith('image/') && filter.isAllowed(file.name)\n );\n\n const subfolders = data.files.filter(file =>\n file.mimeType === 'application/vnd.google-apps.folder'\n );\n\n this.log(`Found ${data.files.length} total items in folder ${folderId}`);\n // Log details of all files to see what we are missing\n data.files.forEach(f => this.log(` - File: ${f.name} (${f.mimeType})`));\n\n this.log(`- ${validFiles.length} valid files (images only)`);\n this.log(`- ${subfolders.length} subfolders`);\n\n // Add image URLs from this folder\n validFiles.forEach(file => {\n // Use the reliable thumbnail/preview endpoint for both Images and PDFs\n // This works for public folders and handles file format conversion automatically\n // 'sz=w1000' requests a high-quality preview (1000px width)\n // detailed explanation:\n // 1. \"drive.google.com\" is blocked by ad-blockers (net::ERR_BLOCKED_BY_CLIENT)\n // 2. The API's \"thumbnailLink\" is a signed URL that can expire or fail 403.\n // 3. \"lh3.googleusercontent.com/d/{ID}\" is the permanent CDN link structure.\n // It bypasses the domain block AND the signing issues.\n imageUrls.push(`https://lh3.googleusercontent.com/d/${file.id}=s1600`);\n\n this.log(`Added file: ${file.name}`);\n });\n\n // Recursively process subfolders\n for (const folder of subfolders) {\n this.log(`Loading images from subfolder: ${folder.name}`);\n const subfolderImages = await this.loadImagesRecursively(folder.id, filter);\n imageUrls.push(...subfolderImages);\n }\n\n return imageUrls;\n }\n\n /**\n * Direct loading method (no API key required, but less reliable)\n * Uses embedded folder view to scrape image IDs\n * @param folderId - Google Drive folder ID\n * @param filter - Filter to apply (not used in fallback mode)\n * @returns Promise resolving to array of image URLs\n */\n private async loadImagesDirectly(folderId: string, _filter: IImageFilter): Promise<string[]> {\n // For now, we'll return a method that requires the user to manually provide image IDs\n // or we construct URLs based on a known pattern\n\n // This is a fallback - in production, you'd want to use the API\n // For demo purposes, we can try to fetch the folder page and extract image IDs\n\n try {\n // Attempt to fetch folder page (CORS may block this)\n const folderUrl = `https://drive.google.com/embeddedfolderview?id=${folderId}`;\n const response = await fetch(folderUrl, { mode: 'cors' });\n\n if (!response.ok) {\n throw new Error('Cannot access folder directly (CORS or permissions issue)');\n }\n\n const html = await response.text();\n\n // Try to extract image IDs from HTML (this is fragile and may break)\n const imageIdPattern = /\\/file\\/d\\/([a-zA-Z0-9_-]+)/g;\n const matches = [...html.matchAll(imageIdPattern)];\n const imageIds = [...new Set(matches.map(m => m[1]))];\n\n const imageUrls = imageIds.map(id =>\n `https://drive.google.com/uc?export=view&id=${id}`\n );\n\n return imageUrls;\n\n } catch (error) {\n console.error('Direct loading failed:', error);\n throw new Error(\n 'Unable to load images. Please ensure:\\n' +\n '1. The folder is shared publicly (Anyone with the link can view)\\n' +\n '2. The folder contains image files\\n' +\n '3. Consider adding a Google Drive API key in config.js for better reliability'\n );\n }\n }\n\n /**\n * Manually add image URLs (for testing or when auto-loading fails)\n * @param imageIds - Array of Google Drive file IDs\n * @returns Array of direct image URLs\n */\n manualImageUrls(imageIds: string[]): string[] {\n return imageIds.map(id => `https://drive.google.com/uc?export=view&id=${id}`);\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n}\n","/**\n * StaticImageLoader.ts\n * Loads images from predefined URL sources and local paths\n * Compatible with ImageCloud's loader interface\n *\n * Public API:\n * - prepare(filter) - Async discovery of images\n * - imagesLength() - Get count of discovered images\n * - imageURLs() - Get ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n */\n\nimport type { ImageLoader, IImageFilter, StaticSource, StaticLoaderConfig } from '../config/types';\n\nexport class StaticImageLoader implements ImageLoader {\n private validateUrls: boolean;\n private validationTimeout: number;\n private validationMethod: 'head' | 'simple' | 'none';\n private sources: StaticSource[];\n private debugLogging: boolean;\n\n // State for new interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: Partial<StaticLoaderConfig> = {}) {\n this.validateUrls = config.validateUrls !== false;\n this.validationTimeout = config.validationTimeout ?? 5000;\n this.validationMethod = config.validationMethod ?? 'head';\n this.sources = config.sources ?? [];\n this.debugLogging = config.debugLogging ?? false;\n\n // Validate that we have sources configured\n if (!this.sources || this.sources.length === 0) {\n throw new Error('StaticImageLoader requires at least one source to be configured');\n }\n\n this.log('StaticImageLoader initialized with config:', config);\n }\n\n /**\n * Prepare the loader by discovering all images from configured sources\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n this.log(`Processing ${this.sources.length} source(s)`);\n\n // Process sources sequentially to preserve order\n for (const source of this.sources) {\n try {\n const urls = await this.processSource(source, filter);\n this._discoveredUrls.push(...urls);\n } catch (error) {\n console.warn('Failed to process source:', source, error);\n // Continue processing other sources\n }\n }\n\n this._prepared = true;\n this.log(`Successfully loaded ${this._discoveredUrls.length} image(s)`);\n }\n\n /**\n * Get the number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('StaticImageLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('StaticImageLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Process a single source object\n * @param source - Source configuration with type, urls, basePath, files\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of valid URLs from this source\n */\n private async processSource(source: StaticSource, filter: IImageFilter): Promise<string[]> {\n if (!source || !source.type) {\n console.warn('Invalid source object (missing type):', source);\n return [];\n }\n\n if (source.type === 'urls') {\n return await this.processUrls(source.urls || [], filter);\n } else if (source.type === 'path') {\n return await this.processPath(source.basePath, source.files || [], filter);\n } else {\n console.warn(`Unknown source type: ${source.type}`);\n return [];\n }\n }\n\n /**\n * Process a list of direct URLs\n * @param urls - Array of image URLs\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processUrls(urls: string[], filter: IImageFilter): Promise<string[]> {\n if (!Array.isArray(urls)) {\n console.warn('URLs must be an array:', urls);\n return [];\n }\n\n const validUrls: string[] = [];\n\n for (const url of urls) {\n // Apply filter based on URL filename\n const filename = url.split('/').pop() || url;\n if (!filter.isAllowed(filename)) {\n this.log(`Skipping filtered URL: ${url}`);\n continue;\n }\n\n if (this.validateUrls) {\n const isValid = await this.validateUrl(url);\n if (isValid) {\n validUrls.push(url);\n } else {\n console.warn(`Skipping invalid/missing URL: ${url}`);\n }\n } else {\n // No validation - add all URLs\n validUrls.push(url);\n }\n }\n\n return validUrls;\n }\n\n /**\n * Process a path-based source\n * @param basePath - Base path (relative or absolute)\n * @param files - Array of filenames\n * @param filter - Filter to apply to discovered images\n * @returns Promise resolving to array of validated URLs\n */\n private async processPath(basePath: string | undefined, files: string[], filter: IImageFilter): Promise<string[]> {\n if (!basePath) {\n console.warn('basePath is required for path-type sources');\n return [];\n }\n\n if (!Array.isArray(files)) {\n console.warn('files must be an array:', files);\n return [];\n }\n\n const validUrls: string[] = [];\n\n for (const file of files) {\n // Apply filter based on filename\n if (!filter.isAllowed(file)) {\n this.log(`Skipping filtered file: ${file}`);\n continue;\n }\n\n const url = this.constructUrl(basePath, file);\n\n if (this.validateUrls) {\n const isValid = await this.validateUrl(url);\n if (isValid) {\n validUrls.push(url);\n } else {\n console.warn(`Skipping invalid/missing file: ${url}`);\n }\n } else {\n // No validation - add all URLs\n validUrls.push(url);\n }\n }\n\n return validUrls;\n }\n\n /**\n * Validate a single URL using HEAD request\n * @param url - URL to validate\n * @returns Promise resolving to true if valid and accessible\n */\n private async validateUrl(url: string): Promise<boolean> {\n if (this.validationMethod === 'none') {\n return true;\n }\n\n if (this.validationMethod === 'simple') {\n // Basic URL format check\n try {\n if (typeof window !== 'undefined') {\n new URL(url, window.location.origin);\n } else {\n new URL(url);\n }\n return true;\n } catch {\n return false;\n }\n }\n\n // validationMethod === 'head' (default)\n // For cross-origin URLs, we can't validate due to CORS\n // So we only validate same-origin URLs\n if (typeof window === 'undefined') {\n return true; // In non-browser environment, assume valid\n }\n\n const isSameOrigin = url.startsWith(window.location.origin) ||\n url.startsWith('/');\n\n if (!isSameOrigin) {\n // Cross-origin URL - assume valid, can't validate due to CORS\n this.log(`Skipping validation for cross-origin URL: ${url}`);\n return true;\n }\n\n // Same-origin URL - validate with HEAD request\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.validationTimeout);\n\n const response = await fetch(url, {\n method: 'HEAD',\n signal: controller.signal\n });\n\n clearTimeout(timeoutId);\n\n if (response.ok) {\n return true;\n } else {\n this.log(`Validation failed for ${url}: HTTP ${response.status}`);\n return false;\n }\n\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n this.log(`Validation timeout for ${url}`);\n } else {\n this.log(`Validation failed for ${url}:`, error.message);\n }\n }\n return false;\n }\n }\n\n /**\n * Construct full URL from basePath and filename\n * @param basePath - Base path (relative or absolute)\n * @param filename - Filename to append\n * @returns Complete URL\n */\n private constructUrl(basePath: string, filename: string): string {\n // Remove trailing slash from basePath\n const cleanBase = basePath.replace(/\\/$/, '');\n\n // Check if basePath is absolute URL\n if (this.isAbsoluteUrl(basePath)) {\n return `${cleanBase}/${filename}`;\n }\n\n // Relative path - prepend current origin\n if (typeof window === 'undefined') {\n return `${cleanBase}/${filename}`; // In non-browser environment, return as-is\n }\n\n const origin = window.location.origin;\n // Ensure basePath starts with /\n const normalizedPath = basePath.startsWith('/') ? basePath : '/' + basePath;\n const cleanPath = normalizedPath.replace(/\\/$/, '');\n\n return `${origin}${cleanPath}/${filename}`;\n }\n\n /**\n * Check if URL is absolute (contains protocol)\n * @param url - URL to check\n * @returns True if absolute URL\n */\n private isAbsoluteUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n}\n","/**\n * CompositeLoader.ts\n * Combines multiple image loaders and loads them in parallel\n *\n * Public API:\n * - prepare(filter) - Async discovery of images from all loaders in parallel\n * - imagesLength() - Get combined count of discovered images\n * - imageURLs() - Get combined ordered list of image URLs\n * - isPrepared() - Check if loader has been prepared\n */\n\nimport type { ImageLoader, IImageFilter } from '../config/types';\n\nexport interface CompositeLoaderConfig {\n loaders: ImageLoader[];\n debugLogging?: boolean;\n}\n\nexport class CompositeLoader implements ImageLoader {\n private loaders: ImageLoader[];\n private debugLogging: boolean;\n\n // State for interface\n private _prepared: boolean = false;\n private _discoveredUrls: string[] = [];\n\n constructor(config: CompositeLoaderConfig) {\n this.loaders = config.loaders;\n this.debugLogging = config.debugLogging ?? false;\n\n // Validate that we have at least one loader\n if (!this.loaders || this.loaders.length === 0) {\n throw new Error('CompositeLoader requires at least one loader to be configured');\n }\n\n this.log(`CompositeLoader initialized with ${this.loaders.length} loader(s)`);\n }\n\n /**\n * Prepare all loaders in parallel and combine their results\n * @param filter - Filter to apply to discovered images\n */\n async prepare(filter: IImageFilter): Promise<void> {\n this._discoveredUrls = [];\n\n this.log(`Preparing ${this.loaders.length} loader(s) in parallel`);\n\n // Prepare all loaders in parallel\n const preparePromises = this.loaders.map((loader, index) => {\n return loader.prepare(filter).then(() => {\n this.log(`Loader ${index} prepared with ${loader.imagesLength()} images`);\n }).catch(error => {\n console.warn(`Loader ${index} failed to prepare:`, error);\n // Continue with other loaders even if one fails\n });\n });\n\n await Promise.all(preparePromises);\n\n // Combine URLs from all prepared loaders (preserves order of loaders array)\n for (const loader of this.loaders) {\n if (loader.isPrepared()) {\n const urls = loader.imageURLs();\n this._discoveredUrls.push(...urls);\n }\n }\n\n this._prepared = true;\n this.log(`CompositeLoader prepared with ${this._discoveredUrls.length} total images`);\n }\n\n /**\n * Get the combined number of discovered images\n * @throws Error if called before prepare()\n */\n imagesLength(): number {\n if (!this._prepared) {\n throw new Error('CompositeLoader.imagesLength() called before prepare()');\n }\n return this._discoveredUrls.length;\n }\n\n /**\n * Get the combined ordered list of image URLs\n * @throws Error if called before prepare()\n */\n imageURLs(): string[] {\n if (!this._prepared) {\n throw new Error('CompositeLoader.imageURLs() called before prepare()');\n }\n return [...this._discoveredUrls];\n }\n\n /**\n * Check if the loader has been prepared\n */\n isPrepared(): boolean {\n return this._prepared;\n }\n\n /**\n * Debug logging helper\n * @param args - Arguments to log\n */\n private log(...args: unknown[]): void {\n if (this.debugLogging && typeof console !== 'undefined') {\n console.log('[CompositeLoader]', ...args);\n }\n }\n}\n","/**\n * ImageFilter.ts\n * Filters images by extension, designed for future extensibility\n * (e.g., size filters, date filters, etc.)\n */\n\nexport class ImageFilter {\n private allowedExtensions: string[];\n\n /**\n * Create a new ImageFilter\n * @param extensions - Array of allowed file extensions (without dots)\n * Defaults to common image formats if not provided\n */\n constructor(extensions?: string[]) {\n this.allowedExtensions = extensions || [\n 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'\n ];\n }\n\n /**\n * Check if a filename has an allowed extension\n * @param filename - The filename to check (can include path or query string)\n * @returns True if the file extension is allowed\n */\n isAllowed(filename: string): boolean {\n // Remove query string if present (for URLs like image.jpg?w=800)\n const withoutQuery = filename.split('?')[0];\n const extension = withoutQuery.split('.').pop()?.toLowerCase();\n return extension ? this.allowedExtensions.includes(extension) : false;\n }\n\n /**\n * Get the list of allowed extensions\n * @returns Array of allowed extensions\n */\n getAllowedExtensions(): string[] {\n return [...this.allowedExtensions];\n }\n\n // Future expansion methods:\n // isAllowedSize(sizeBytes: number): boolean\n // isAllowedDate(date: Date): boolean\n // isAllowedDimensions(width: number, height: number): boolean\n}\n","/**\n * Minimal functional CSS required for the library to work.\n * Injected automatically - no external CSS file needed.\n */\nexport const FUNCTIONAL_CSS = `\n.fbn-ic-gallery {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n perspective: 1000px;\n}\n\n.fbn-ic-image {\n position: absolute;\n cursor: pointer;\n transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow 0.6s cubic-bezier(0.4, 0, 0.2, 1),\n filter 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n border 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n outline 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n z-index 0s 0.6s;\n will-change: transform;\n user-select: none;\n backface-visibility: hidden;\n -webkit-backface-visibility: hidden;\n}\n\n.fbn-ic-image.fbn-ic-focused {\n z-index: 1000;\n transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow 0.6s cubic-bezier(0.4, 0, 0.2, 1),\n filter 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n border 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n outline 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n z-index 0s 0s;\n will-change: auto;\n}\n\n.fbn-ic-hidden {\n display: none !important;\n}\n`;\n\n/**\n * Inject functional styles into document head.\n * Idempotent - safe to call multiple times.\n */\nexport function injectFunctionalStyles(): void {\n if (typeof document === 'undefined') return;\n const id = 'fbn-ic-functional-styles';\n if (document.getElementById(id)) return;\n const style = document.createElement('style');\n style.id = id;\n style.textContent = FUNCTIONAL_CSS;\n document.head.appendChild(style);\n}\n","/**\n * ImageCloud.ts\n * Main application class\n * Manages initialization and coordination of the interactive image cloud\n */\n\nimport type { ImageCloudOptions, ImageCloudConfig, ImageLayout, ContainerBounds, ImageLoader, EntryAnimationConfig } from './config/types';\nimport { mergeConfig, DEFAULT_CONFIG } from './config/defaults';\nimport { AnimationEngine } from './engines/AnimationEngine';\nimport { EntryAnimationEngine } from './engines/EntryAnimationEngine';\nimport { LayoutEngine } from './engines/LayoutEngine';\nimport { ZoomEngine } from './engines/ZoomEngine';\nimport { SwipeEngine, SNAP_BACK_DURATION_MS } from './engines/SwipeEngine';\nimport { animatePath } from './engines/PathAnimator';\nimport { GoogleDriveLoader } from './loaders/GoogleDriveLoader';\nimport { StaticImageLoader } from './loaders/StaticImageLoader';\nimport { CompositeLoader } from './loaders/CompositeLoader';\nimport { ImageFilter } from './loaders/ImageFilter';\nimport { buildStyleProperties, applyStylesToElement, applyClassNameToElement, removeClassNameFromElement, StyleProperties } from './utils/styleUtils';\nimport { injectFunctionalStyles } from './styles/functionalStyles';\n\nexport class ImageCloud {\n private containerId: string | null;\n private containerRef: HTMLElement | null;\n\n // Internal state\n private fullConfig: ImageCloudConfig;\n private imagesLoaded: boolean;\n private imageElements: HTMLImageElement[];\n private imageLayouts: ImageLayout[];\n private currentImageHeight: number;\n private currentFocusIndex: number | null;\n private hoveredImage: { element: HTMLImageElement; layout: ImageLayout } | null;\n private resizeTimeout: number | null;\n private displayQueue: HTMLImageElement[];\n private queueInterval: number | null;\n private loadGeneration: number;\n\n // Precomputed styling\n private defaultStyles: StyleProperties;\n private hoverStyles: StyleProperties;\n private defaultClassName: string | string[] | undefined;\n private hoverClassName: string | string[] | undefined;\n\n // Modules\n private animationEngine: AnimationEngine;\n private entryAnimationEngine: EntryAnimationEngine;\n private layoutEngine: LayoutEngine;\n private zoomEngine: ZoomEngine;\n private swipeEngine: SwipeEngine | null;\n private imageLoader: ImageLoader;\n private imageFilter: ImageFilter;\n\n // DOM Elements\n private containerEl: HTMLElement | null;\n private loadingEl: HTMLElement | null;\n private errorEl: HTMLElement | null;\n\n constructor(options: ImageCloudOptions = {}) {\n this.fullConfig = mergeConfig(options);\n\n // Container can be a string ID or an HTMLElement reference\n if (options.container instanceof HTMLElement) {\n this.containerRef = options.container;\n this.containerId = null;\n } else {\n this.containerRef = null;\n this.containerId = options.container || 'imageCloud';\n }\n\n // Internal state\n this.imagesLoaded = false;\n this.imageElements = [];\n this.imageLayouts = [];\n this.currentImageHeight = 225;\n this.currentFocusIndex = null;\n this.hoveredImage = null;\n this.resizeTimeout = null;\n this.displayQueue = [];\n this.queueInterval = null;\n this.loadGeneration = 0;\n\n // Initialize engines with new config structure\n this.animationEngine = new AnimationEngine(this.fullConfig.animation);\n this.layoutEngine = new LayoutEngine({\n layout: this.fullConfig.layout,\n image: this.fullConfig.image\n });\n this.zoomEngine = new ZoomEngine(this.fullConfig.interaction.focus, this.animationEngine, this.fullConfig.styling);\n\n // Precompute styling properties\n this.defaultStyles = buildStyleProperties(this.fullConfig.styling?.default);\n this.hoverStyles = buildStyleProperties(this.fullConfig.styling?.hover);\n this.defaultClassName = this.fullConfig.styling?.default?.className;\n this.hoverClassName = this.fullConfig.styling?.hover?.className;\n\n // Initialize entry animation engine with layout-aware defaults\n const entryConfig = this.fullConfig.animation.entry || DEFAULT_CONFIG.animation.entry!;\n this.entryAnimationEngine = new EntryAnimationEngine(\n entryConfig as EntryAnimationConfig,\n this.fullConfig.layout.algorithm\n );\n\n // SwipeEngine will be initialized after container is available\n this.swipeEngine = null;\n\n // Initialize image filter with configured extensions\n this.imageFilter = this.createImageFilter();\n\n // Initialize image loader based on type\n this.imageLoader = this.createLoader();\n\n // DOM Elements (will be fetched on init)\n this.containerEl = null;\n this.loadingEl = null;\n this.errorEl = null;\n }\n\n /**\n * Create image filter based on config\n */\n private createImageFilter(): ImageFilter {\n const loaderType = this.fullConfig.loader.type;\n\n // Get extensions from the appropriate loader config\n let extensions: string[] | undefined;\n if (loaderType === 'googleDrive') {\n extensions = this.fullConfig.loader.googleDrive?.allowedExtensions;\n } else {\n extensions = this.fullConfig.loader.static?.allowedExtensions;\n }\n\n return new ImageFilter(extensions);\n }\n\n /**\n * Create appropriate image loader based on config\n */\n private createLoader(): ImageLoader {\n return this.createLoaderFromConfig(this.fullConfig.loader);\n }\n\n /**\n * Create a loader from a LoaderConfig object (supports recursive composite loaders)\n */\n private createLoaderFromConfig(config: typeof this.fullConfig.loader): ImageLoader {\n const loaderType = config.type;\n\n if (loaderType === 'static') {\n const staticConfig = config.static!;\n return new StaticImageLoader(staticConfig);\n } else if (loaderType === 'composite') {\n const compositeConfig = config.composite!;\n const childLoaders = compositeConfig.loaders.map(loaderConfig =>\n this.createLoaderFromConfig(loaderConfig)\n );\n return new CompositeLoader({\n loaders: childLoaders,\n debugLogging: compositeConfig.debugLogging\n });\n } else {\n const driveConfig = config.googleDrive!;\n return new GoogleDriveLoader(driveConfig);\n }\n }\n\n /**\n * Initialize the gallery\n */\n async init(): Promise<void> {\n try {\n // Inject functional styles (idempotent)\n injectFunctionalStyles();\n\n // 1. Setup DOM\n if (this.containerRef) {\n this.containerEl = this.containerRef;\n } else {\n this.containerEl = document.getElementById(this.containerId!);\n if (!this.containerEl) {\n throw new Error(`Container #${this.containerId} not found`);\n }\n }\n\n // Add gallery class for CSS scoping\n this.containerEl.classList.add('fbn-ic-gallery');\n\n // Initialize swipe engine for touch navigation\n this.swipeEngine = new SwipeEngine(this.containerEl, {\n onNext: () => this.navigateToNextImage(),\n onPrev: () => this.navigateToPreviousImage(),\n onDragOffset: (offset) => this.zoomEngine.setDragOffset(offset),\n onDragEnd: (navigated) => {\n if (!navigated) {\n // Snap back to center with animation\n this.zoomEngine.clearDragOffset(true, SNAP_BACK_DURATION_MS);\n } else {\n // Clear offset immediately (navigation handles transition)\n this.zoomEngine.clearDragOffset(false);\n }\n }\n });\n\n // Create or bind UI elements\n this.setupUI();\n\n // 2. Setup Event Listeners\n this.setupEventListeners();\n\n // 3. Load Images\n this.logDebug('ImageCloud initialized');\n await this.loadImages();\n\n } catch (error) {\n console.error('Gallery initialization failed:', error);\n if (this.errorEl && error instanceof Error) {\n this.showError('Gallery failed to initialize: ' + error.message);\n }\n }\n }\n\n private setupUI(): void {\n // Look for existing elements or create them\n this.loadingEl = document.getElementById('loading');\n this.errorEl = document.getElementById('error');\n }\n\n private setupEventListeners(): void {\n // Global keyboard events\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n this.zoomEngine.unfocusImage();\n this.currentFocusIndex = null;\n this.swipeEngine?.disable();\n } else if (e.key === 'ArrowRight') {\n this.navigateToNextImage();\n } else if (e.key === 'ArrowLeft') {\n this.navigateToPreviousImage();\n } else if ((e.key === 'Enter' || e.key === ' ') && this.hoveredImage) {\n // Focus the hovered image (works whether or not another image is focused)\n this.handleImageClick(this.hoveredImage.element, this.hoveredImage.layout);\n e.preventDefault(); // Prevent space from scrolling the page\n }\n });\n\n document.addEventListener('click', (e: MouseEvent) => {\n // Ignore clicks that follow touch events (prevents unfocus during swipe)\n if (this.swipeEngine?.hadRecentTouch()) {\n return;\n }\n if (!(e.target as HTMLElement).closest('.fbn-ic-image')) {\n this.zoomEngine.unfocusImage();\n this.currentFocusIndex = null;\n this.swipeEngine?.disable();\n }\n });\n\n // Resize handler\n window.addEventListener('resize', () => this.handleResize());\n }\n\n /**\n * Navigate to the next image (Right arrow)\n */\n private navigateToNextImage(): void {\n if (this.currentFocusIndex === null || this.imageElements.length === 0) return;\n\n const nextIndex = (this.currentFocusIndex + 1) % this.imageElements.length;\n this.navigateToImage(nextIndex);\n }\n\n /**\n * Navigate to the previous image (Left arrow)\n */\n private navigateToPreviousImage(): void {\n if (this.currentFocusIndex === null || this.imageElements.length === 0) return;\n\n const prevIndex = (this.currentFocusIndex - 1 + this.imageElements.length) % this.imageElements.length;\n this.navigateToImage(prevIndex);\n }\n\n /**\n * Navigate to a specific image by index\n */\n private async navigateToImage(index: number): Promise<void> {\n if (index < 0 || index >= this.imageElements.length) return;\n\n const imageElement = this.imageElements[index];\n const layout = this.imageLayouts[index];\n\n if (!imageElement || !layout) return;\n\n // Reuse the same focus mechanism as clicking\n await this.handleImageClick(imageElement, layout);\n }\n\n private handleResize(): void {\n if (!this.imagesLoaded) return;\n\n if (this.resizeTimeout !== null) {\n clearTimeout(this.resizeTimeout);\n }\n\n this.resizeTimeout = window.setTimeout(() => {\n const newHeight = this.getImageHeight();\n\n if (newHeight !== this.currentImageHeight) {\n this.logDebug(`Window resized to new breakpoint (height: ${newHeight}px). Reloading images...`);\n // Reload images with new breakpoint\n this.loadImages();\n } else {\n this.logDebug('Window resized (no breakpoint change)');\n }\n }, 500);\n }\n\n private getImageHeight(): number {\n const width = window.innerWidth;\n const responsive = this.fullConfig.layout.responsive;\n\n // Get sizing config for adaptive mode defaults\n const sizing = this.fullConfig.image.sizing;\n const maxSize = sizing?.maxSize ?? 400;\n\n // Use responsive breakpoints to determine max height\n // These serve as upper bounds for the adaptive sizing\n if (!responsive) {\n // Fallback defaults if responsive not configured\n if (width <= 767) return Math.min(100, maxSize);\n if (width <= 1199) return Math.min(180, maxSize);\n return Math.min(225, maxSize);\n }\n\n if (width <= responsive.mobile.maxWidth) {\n return Math.min(100, maxSize); // Mobile\n }\n if (width <= responsive.tablet.maxWidth) {\n return Math.min(180, maxSize); // Tablet\n }\n return Math.min(225, maxSize); // Screen (desktop)\n }\n\n /**\n * Get container bounds for layout calculations\n */\n private getContainerBounds(): { width: number; height: number } {\n if (!this.containerEl) {\n return { width: window.innerWidth, height: window.innerHeight * 0.7 };\n }\n return {\n width: this.containerEl.offsetWidth,\n height: this.containerEl.offsetHeight || window.innerHeight * 0.7\n };\n }\n\n /**\n * Load images using the unified loader interface\n */\n private async loadImages(): Promise<void> {\n try {\n this.showLoading(true);\n this.hideError();\n this.clearImageCloud();\n\n // Prepare the loader (show spinner during this)\n await this.imageLoader.prepare(this.imageFilter);\n\n // Get image count and URLs from loader\n const imageCount = this.imageLoader.imagesLength();\n let imageUrls = this.imageLoader.imageURLs();\n\n if (imageCount === 0) {\n this.showError('No images found.');\n this.showLoading(false);\n return;\n }\n\n // Calculate adaptive sizing based on container and image count\n const containerBounds = this.getContainerBounds();\n const responsiveHeight = this.getImageHeight();\n const viewportWidth = window.innerWidth;\n\n this.logDebug(`Adaptive sizing input: container=${containerBounds.width}x${containerBounds.height}px, images=${imageCount}, responsiveMax=${responsiveHeight}px`);\n\n const sizingResult = this.layoutEngine.calculateAdaptiveSize(\n containerBounds,\n imageCount,\n responsiveHeight,\n viewportWidth\n );\n\n this.logDebug(`Adaptive sizing result: height=${sizingResult.height}px`);\n\n await this.createImageCloud(imageUrls, sizingResult.height);\n\n this.showLoading(false);\n this.imagesLoaded = true;\n\n } catch (error) {\n console.error('Error loading images:', error);\n if (error instanceof Error) {\n this.showError(error.message || 'Failed to load images.');\n }\n this.showLoading(false);\n }\n }\n\n /**\n * Helper for debug logging\n */\n private logDebug(...args: unknown[]): void {\n if (this.fullConfig.debug && typeof console !== 'undefined') {\n console.log(...args);\n }\n }\n\n private async createImageCloud(imageUrls: string[], imageHeight: number): Promise<void> {\n if (!this.containerEl) return;\n\n const containerBounds = this.getContainerBounds();\n this.currentImageHeight = imageHeight;\n\n // Capture current generation to detect stale callbacks\n const currentGeneration = this.loadGeneration;\n\n // Generate layout\n const layouts = this.layoutEngine.generateLayout(imageUrls.length, containerBounds, { fixedHeight: imageHeight } as any);\n this.imageLayouts = layouts;\n\n this.displayQueue = [];\n let processedCount = 0;\n\n // Helper to display a single image with animation\n const displayImage = (img: HTMLImageElement) => {\n if (!this.containerEl) return;\n\n this.containerEl.appendChild(img);\n this.imageElements.push(img);\n\n requestAnimationFrame(() => {\n void img.offsetWidth; // Force reflow\n // Use configured default opacity, or 1 if not specified\n img.style.opacity = this.defaultStyles.opacity ?? '1';\n\n // Check if we need JS animation for path type, rotation, or scale\n const needsJSAnimation = img.dataset.startX &&\n (this.entryAnimationEngine.requiresJSAnimation() ||\n this.entryAnimationEngine.requiresJSRotation() ||\n this.entryAnimationEngine.requiresJSScale() ||\n img.dataset.startRotation !== img.dataset.rotation ||\n img.dataset.startScale !== img.dataset.scale);\n\n if (needsJSAnimation) {\n // Use animatePath for bounce, elastic, wave paths or rotation/scale animation\n const startPosition = {\n x: parseFloat(img.dataset.startX!),\n y: parseFloat(img.dataset.startY!)\n };\n const endPosition = {\n x: parseFloat(img.dataset.endX!),\n y: parseFloat(img.dataset.endY!)\n };\n const imageWidth = parseFloat(img.dataset.imageWidth!);\n const imageHeight = parseFloat(img.dataset.imageHeight!);\n const rotation = parseFloat(img.dataset.rotation!);\n const scale = parseFloat(img.dataset.scale!);\n const startRotation = img.dataset.startRotation\n ? parseFloat(img.dataset.startRotation)\n : rotation;\n const startScale = img.dataset.startScale\n ? parseFloat(img.dataset.startScale)\n : scale;\n const timing = this.entryAnimationEngine.getTiming();\n\n animatePath({\n element: img,\n startPosition,\n endPosition,\n pathConfig: this.entryAnimationEngine.getPathConfig(),\n duration: timing.duration,\n imageWidth,\n imageHeight,\n rotation,\n scale,\n rotationConfig: this.entryAnimationEngine.getRotationConfig(),\n startRotation,\n scaleConfig: this.entryAnimationEngine.getScaleConfig(),\n startScale\n });\n } else {\n // Use CSS transition for linear/arc paths without rotation animation\n const finalTransform = img.dataset.finalTransform || '';\n img.style.transform = finalTransform;\n }\n\n // Debug: log final state for first few images\n const imgIndex = parseInt(img.dataset.imageId || '0');\n if (this.fullConfig.debug && imgIndex < 3) {\n const finalTransform = img.dataset.finalTransform || '';\n console.log(`Image ${imgIndex} final state:`, {\n left: img.style.left,\n top: img.style.top,\n width: img.style.width,\n height: img.style.height,\n computedWidth: img.offsetWidth,\n computedHeight: img.offsetHeight,\n transform: finalTransform,\n pathType: this.entryAnimationEngine.getPathType()\n });\n }\n });\n\n processedCount++;\n };\n\n const startQueueProcessing = () => {\n this.logDebug('Starting queue processing, enabled:', this.fullConfig.animation.queue.enabled);\n\n // If queue is disabled, display all images immediately\n if (!this.fullConfig.animation.queue.enabled) {\n while (this.displayQueue.length > 0) {\n const img = this.displayQueue.shift();\n if (img) {\n displayImage(img);\n }\n }\n return;\n }\n\n // Queue is enabled - stagger images with interval\n // Clear any existing interval before creating new one\n if (this.queueInterval !== null) {\n clearInterval(this.queueInterval);\n }\n this.queueInterval = window.setInterval(() => {\n // Check if this interval is still valid (generation hasn't changed)\n if (currentGeneration !== this.loadGeneration) {\n if (this.queueInterval !== null) {\n clearInterval(this.queueInterval);\n this.queueInterval = null;\n }\n return;\n }\n\n if (this.displayQueue.length > 0) {\n const img = this.displayQueue.shift();\n if (img) {\n displayImage(img);\n }\n }\n\n if (processedCount >= imageUrls.length && this.displayQueue.length === 0) {\n if (this.queueInterval !== null) {\n clearInterval(this.queueInterval);\n this.queueInterval = null;\n }\n }\n }, this.fullConfig.animation.queue.interval);\n };\n\n // Visibility Check\n if ('IntersectionObserver' in window && this.containerEl) {\n const observer = new IntersectionObserver((entries) => {\n entries.forEach(entry => {\n if (entry.isIntersecting) {\n startQueueProcessing();\n observer.disconnect();\n }\n });\n }, { threshold: 0.1, rootMargin: '50px' });\n observer.observe(this.containerEl);\n } else {\n startQueueProcessing();\n }\n\n // Debug: Draw center markers if debugCenters is enabled\n if (this.fullConfig.layout.debugCenters && this.containerEl) {\n // Remove any existing debug markers\n this.containerEl.querySelectorAll('.fbn-ic-debug-center').forEach(el => el.remove());\n\n layouts.forEach((layout, index) => {\n const marker = document.createElement('div');\n marker.className = 'fbn-ic-debug-center';\n marker.style.position = 'absolute';\n marker.style.width = '12px';\n marker.style.height = '12px';\n marker.style.borderRadius = '50%';\n marker.style.backgroundColor = 'red';\n marker.style.border = '2px solid yellow';\n marker.style.zIndex = '9999';\n marker.style.pointerEvents = 'none';\n // Center position: layout.x and layout.y now store the center position directly\n const centerX = layout.x;\n const centerY = layout.y;\n marker.style.left = `${centerX - 6}px`; // Offset by half marker size\n marker.style.top = `${centerY - 6}px`;\n marker.title = `Image ${index}: center (${Math.round(centerX)}, ${Math.round(centerY)})`;\n this.containerEl!.appendChild(marker);\n });\n }\n\n // Create elements\n imageUrls.forEach((url, index) => {\n const img = document.createElement('img');\n // NOTE: img.src is set AFTER onload handler to ensure handler catches cached images\n img.referrerPolicy = 'no-referrer';\n img.classList.add('fbn-ic-image');\n img.dataset.imageId = String(index);\n\n const layout = layouts[index];\n img.style.position = 'absolute';\n img.style.width = 'auto';\n img.style.height = `${imageHeight}px`;\n img.style.left = `${layout.x}px`;\n img.style.top = `${layout.y}px`;\n // Transform will be applied in onload after we know the actual dimensions\n\n // Apply layout-specified border only if no styling config border is set\n if (layout.borderColor && !this.fullConfig.styling?.default?.border) {\n img.style.border = `5px solid ${layout.borderColor}`;\n img.style.boxSizing = 'border-box';\n }\n if (layout.zIndex) img.style.zIndex = String(layout.zIndex);\n\n // Apply default styling state\n applyStylesToElement(img, this.defaultStyles);\n applyClassNameToElement(img, this.defaultClassName);\n\n // Hover event handlers\n // Use isInvolved() to prevent hover styles on images that are focused or animating\n img.addEventListener('mouseenter', () => {\n this.hoveredImage = { element: img, layout };\n if (!this.zoomEngine.isInvolved(img)) {\n applyStylesToElement(img, this.hoverStyles);\n applyClassNameToElement(img, this.hoverClassName);\n }\n });\n\n img.addEventListener('mouseleave', () => {\n this.hoveredImage = null;\n if (!this.zoomEngine.isInvolved(img)) {\n applyStylesToElement(img, this.defaultStyles);\n removeClassNameFromElement(img, this.hoverClassName);\n applyClassNameToElement(img, this.defaultClassName);\n }\n });\n\n img.addEventListener('click', (e: MouseEvent) => {\n e.stopPropagation();\n this.handleImageClick(img, layout);\n });\n\n img.style.opacity = '0';\n img.style.transition = this.entryAnimationEngine.getTransitionCSS();\n\n img.onload = () => {\n // Ignore if generation has changed (stale callback from previous load)\n if (currentGeneration !== this.loadGeneration) {\n return;\n }\n\n const aspectRatio = img.naturalWidth / img.naturalHeight;\n const renderedWidth = imageHeight * aspectRatio;\n\n // Set explicit width so transform calculations are accurate\n img.style.width = `${renderedWidth}px`;\n\n // Use EntryAnimationEngine for start position calculation\n const finalPosition = { x: layout.x, y: layout.y };\n const imageSize = { width: renderedWidth, height: imageHeight };\n\n const startPosition = this.entryAnimationEngine.calculateStartPosition(\n finalPosition,\n imageSize,\n containerBounds,\n index,\n imageUrls.length\n );\n\n // Calculate start rotation based on entry rotation config\n const startRotation = this.entryAnimationEngine.calculateStartRotation(layout.rotation);\n\n // Calculate start scale based on entry scale config\n const startScale = this.entryAnimationEngine.calculateStartScale(layout.scale);\n\n const finalTransform = this.entryAnimationEngine.buildFinalTransform(\n layout.rotation,\n layout.scale,\n renderedWidth,\n imageHeight\n );\n const startTransform = this.entryAnimationEngine.buildStartTransform(\n startPosition,\n finalPosition,\n layout.rotation,\n layout.scale,\n renderedWidth,\n imageHeight,\n startRotation,\n startScale\n );\n\n if (this.fullConfig.debug && index < 3) {\n console.log(`Image ${index}:`, {\n finalPosition,\n imageSize,\n left: layout.x,\n top: layout.y,\n finalTransform,\n renderedWidth,\n renderedHeight: imageHeight\n });\n }\n\n img.style.transform = startTransform;\n img.dataset.finalTransform = finalTransform;\n\n // Store animation data for JS-animated paths (bounce, elastic, wave)\n // or when rotation/scale animation is needed\n const needsJSAnimation = this.entryAnimationEngine.requiresJSAnimation() ||\n this.entryAnimationEngine.requiresJSRotation() ||\n this.entryAnimationEngine.requiresJSScale() ||\n startRotation !== layout.rotation ||\n startScale !== layout.scale;\n\n if (needsJSAnimation) {\n img.dataset.startX = String(startPosition.x);\n img.dataset.startY = String(startPosition.y);\n img.dataset.endX = String(finalPosition.x);\n img.dataset.endY = String(finalPosition.y);\n img.dataset.imageWidth = String(renderedWidth);\n img.dataset.imageHeight = String(imageHeight);\n img.dataset.rotation = String(layout.rotation);\n img.dataset.scale = String(layout.scale);\n img.dataset.startRotation = String(startRotation);\n img.dataset.startScale = String(startScale);\n }\n\n this.displayQueue.push(img);\n };\n\n img.onerror = () => processedCount++;\n\n // Set src AFTER onload handler to ensure it catches cached images\n img.src = url;\n });\n }\n\n private async handleImageClick(imageElement: HTMLImageElement, originalLayout: ImageLayout): Promise<void> {\n if (!this.containerEl) return;\n\n const isFocused = this.zoomEngine.isFocused(imageElement);\n const bounds: ContainerBounds = {\n width: this.containerEl.offsetWidth,\n height: this.containerEl.offsetHeight\n };\n\n if (isFocused) {\n await this.zoomEngine.unfocusImage();\n this.currentFocusIndex = null;\n this.swipeEngine?.disable();\n } else {\n // Track the focused image index for keyboard navigation\n const imageId = imageElement.dataset.imageId;\n this.currentFocusIndex = imageId !== undefined ? parseInt(imageId, 10) : null;\n this.swipeEngine?.enable();\n await this.zoomEngine.focusImage(imageElement, bounds, originalLayout);\n }\n }\n\n /**\n * Clear the image cloud and reset state\n */\n clearImageCloud(): void {\n // Clear queue processing interval to prevent stale images from being added\n if (this.queueInterval !== null) {\n clearInterval(this.queueInterval);\n this.queueInterval = null;\n }\n // Increment generation to invalidate pending image onload handlers\n this.loadGeneration++;\n this.displayQueue = [];\n\n if (this.containerEl) {\n this.containerEl.innerHTML = '';\n }\n this.imageElements = [];\n this.imageLayouts = [];\n this.currentFocusIndex = null;\n this.hoveredImage = null;\n this.layoutEngine.reset();\n this.zoomEngine.reset();\n this.imagesLoaded = false;\n }\n\n private showLoading(show: boolean): void {\n if (!this.fullConfig.rendering.ui.showLoadingSpinner || !this.loadingEl) return;\n if (show) {\n this.loadingEl.classList.remove('fbn-ic-hidden');\n } else {\n this.loadingEl.classList.add('fbn-ic-hidden');\n }\n }\n\n private showError(message: string): void {\n if (!this.errorEl) return;\n this.errorEl.textContent = message;\n this.errorEl.classList.remove('fbn-ic-hidden');\n }\n\n private hideError(): void {\n if (this.errorEl) {\n this.errorEl.classList.add('fbn-ic-hidden');\n }\n }\n\n /**\n * Destroy the gallery and clean up resources\n */\n destroy(): void {\n this.clearImageCloud();\n // Remove event listeners\n if (this.resizeTimeout !== null) {\n clearTimeout(this.resizeTimeout);\n }\n this.swipeEngine?.destroy();\n }\n}\n","/**\n * Image Cloud Library - Auto-Initialization Entry Point\n *\n * Automatically initializes galleries from HTML data attributes\n * Usage: Include this script and add data-image-cloud attribute to containers\n */\n\n// Import CSS as inline string for self-contained bundle\nimport css from './styles/image-cloud.css?inline';\n\nimport { ImageCloud } from './ImageCloud';\n\n/** Inject library styles into <head> (idempotent) */\nfunction injectStyles(): void {\n if (typeof document === 'undefined') return;\n const id = 'fbn-ic-styles';\n if (document.getElementById(id)) return;\n const style = document.createElement('style');\n style.id = id;\n style.textContent = css;\n document.head.appendChild(style);\n}\ninjectStyles();\nimport type { ImageCloudOptions } from './config/types';\n\n/**\n * Auto-initialize galleries from data attributes\n */\nfunction autoInitialize(): void {\n if (typeof document === 'undefined') {\n console.warn('ImageCloud: Document not available (not in browser environment)');\n return;\n }\n\n const initGalleries = () => {\n // Find all elements marked with data-image-cloud or data-image-gallery (legacy)\n const containers = document.querySelectorAll('[data-image-cloud], [data-image-gallery]');\n\n if (containers.length === 0) {\n // Quietly return if no galleries found (normal case for some pages)\n return;\n }\n\n // Initialize each gallery\n containers.forEach(container => {\n const element = container as HTMLElement;\n\n // Container must have an ID for the gallery to work\n if (!element.id) {\n console.error('ImageCloud: Container with data-image-cloud must have an id attribute');\n return;\n }\n\n // Check for JSON configuration\n // Supports data-config (preferred) or data-gallery-config (legacy alias)\n const jsonConfig = element.dataset.config || element.dataset.galleryConfig;\n let options: ImageCloudOptions;\n\n if (jsonConfig) {\n try {\n const parsed = JSON.parse(jsonConfig);\n options = {\n container: element.id,\n ...parsed\n };\n } catch (error) {\n console.error(`ImageCloud: Failed to parse configuration JSON for #${element.id}:`, error);\n return;\n }\n } else {\n console.error(`ImageCloud: Missing configuration for #${element.id}. Add data-config='{...}' attribute.`);\n return;\n }\n\n // Initialize gallery\n const gallery = new ImageCloud(options);\n gallery.init().catch(error => {\n console.error('ImageCloud initialization failed:', error);\n });\n });\n };\n\n // Initialize when DOM is ready\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initGalleries);\n } else {\n // DOM is already ready\n initGalleries();\n }\n}\n\n// Auto-run when this module is imported\nautoInitialize();\n\n// Also export for manual control if needed\nexport { autoInitialize };\nexport { ImageCloud } from './ImageCloud';\n// Backwards compatibility\nexport { ImageCloud as ImageGallery } from './ImageCloud';\n"],"names":["SHADOW_PRESETS","BOUNCE_PRESETS","ELASTIC_PRESETS","WAVE_PATH_PRESETS","DEFAULT_PATH_CONFIG","DEFAULT_ENTRY_ROTATION","DEFAULT_ENTRY_SCALE","DEFAULT_STYLING","DEFAULT_WAVE_CONFIG","DEFAULT_RESPONSIVE_BREAKPOINTS","DEFAULT_IMAGE_SIZING","DEFAULT_IMAGE_ROTATION","DEFAULT_IMAGE_CONFIG","DEFAULT_CONFIG","deepMergeStyleState","base","override","merged","deepMergeStyling","defaults","userStyling","mergedDefault","mergedHover","mergedFocused","deepMergeImageConfig","userImage","userVariance","validMin","_b","_a","validMax","_d","_c","userRange","_f","_e","_h","_g","convertLegacyRotationConfig","userConfig","legacyRotation","convertLegacyVarianceConfig","legacyVariance","mergeConfig","combinedImageConfig","resolveBounceConfig","preset","overrides","resolveElasticConfig","resolveWavePathConfig","AnimationEngine","config","params","transforms","x","y","element","from","to","duration","easing","animDuration","animEasing","fromTransform","toTransform","animation","handle","commitStyle","snapshot","currentTransform","allAnimations","anim","transformStr","matrix","scale","rotation","properties","resolve","originalState","ms","lerp","start","end","t","calculateBouncePosition","overshoot","bounces","decayRatio","dx","dy","keyframes","generateBounceKeyframes","progress","segmentStart","segmentEnd","segmentOvershoot","isOvershootPhase","i","segmentT","easeOutQuad","prevOvershoot","k","fromProgress","currentTime","currentOvershoot","bounceTime","calculateElasticPosition","stiffness","damping","mass","oscillations","omega","zeta","dampedFreq","envelope","oscillation","calculateWavePosition","amplitude","frequency","decay","decayRate","phase","length","perpX","perpY","wavePhase","decayFactor","waveOffset","progressT","easeOutCubic","calculateWobbleRotation","finalRotation","wobbleConfig","wobbleOffset","calculatePopScale","finalScale","popConfig","bounceDecay","undershoot","currentScale","prevTime","prevScale","segmentProgress","easedProgress","animatePath","options","startPosition","endPosition","pathConfig","imageWidth","imageHeight","onComplete","rotationConfig","startRotation","scaleConfig","startScale","pathType","animateRotation","isWobbleMode","needsRotationAnimation","animateScale","isPopMode","startTime","centerOffsetX","centerOffsetY","tick","elapsed","position","translateX","translateY","currentRotation","requiresJSAnimation","LAYOUT_ENTRY_DEFAULTS","EntryAnimationEngine","layoutAlgorithm","finalPosition","imageSize","containerBounds","imageIndex","totalImages","offset","centerX","centerY","distLeft","distRight","distTop","distBottom","minDist","startX","startY","edge","_finalPosition","_imageSize","edges","randomEdge","circularConfig","distribution","radius","radiusConfig","percentage","angle","stagger","centerTranslate","offsetX","offsetY","startConfig","range","spinCount","direction","RandomPlacementGenerator","imageConfig","imageCount","layouts","width","height","padding","baseImageSize","rotationMode","minRotation","maxRotation","varianceMin","varianceMax","_i","hasVariance","halfWidth","halfHeight","maxX","maxY","minX","minY","scaledImageSize","layout","min","max","RadialPlacementGenerator","debugRadials","scaleDecay","debugPalette","cx","cy","estimatedMaxRings","varianceScale","centerSize","processedCount","currentRing","normalizedRing","ringScale","radiusY","radiusX","circumference","estimatedItemWidth","itemsInRing","angleStep","ringOffset","combinedScale","DEFAULT_GRID_CONFIG","OVERFLOW_OFFSET_PATTERN","GridPlacementGenerator","gridConfig","availableWidth","availableHeight","columns","rows","hasRowStagger","hasColumnStagger","effectiveColumns","effectiveRows","cellWidth","cellHeight","staggerOffsetX","staggerOffsetY","overlapMultiplier","cellBasedSize","totalGridWidth","totalGridHeight","gridOffsetX","gridOffsetY","cellCount","hasFixedGrid","isOverflowMode","cellStackCount","overflowOffsetPx","col","row","stackLayer","overflowIndex","targetCell","cellCenterX","cellCenterY","patternIndex","pattern","maxJitterX","maxJitterY","itemsInLastRow","lastRowWidth","alignmentOffset","zIndex","_baseImageSize","aspectRatio","GOLDEN_ANGLE","DEFAULT_SPIRAL_CONFIG","SpiralPlacementGenerator","spiralConfig","maxRadius","directionMultiplier","theta","normalizedRadius","decayScale","clampedX","clampedY","baseRotation","rotationVariance","tangentAngle","b","psi","index","tightness","maxTheta","a","maxComputedRadius","DEFAULT_CLUSTER_CONFIG","ClusterPlacementGenerator","clusterConfig","clusterCount","clusterCenters","imagesPerCluster","clusterIdx","cluster","imagesInThisCluster","distance","sizeMultiplier","normalizedDistance","configCount","clusterSpacing","countByImages","countBySpace","count","centers","bestCandidate","bestMinDistance","attempt","candidate","minDistance","existing","u","v","value","WavePlacementGenerator","waveConfig","phaseShift","synchronization","imagesPerRow","halfImageWidth","endX","horizontalSpacing","minCenterY","maxCenterY","rowSpacing","rowIndex","baseY","imgInRow","waveY","containerWidth","normalizedX","derivative","LayoutEngine","imageId","newConfig","viewportWidth","breakpoints","sizing","responsiveHeight","breakpoint","maxHeight","userBaseHeight","minSize","maxSize","targetCoverage","densityFactor","areaPerImage","calculatedHeight","finalHeight","floor","ZoomState","isShadowPreset","resolveShadow","shadow","buildFilterString","filter","parts","ds","buildSingleBorder","style","color","buildStyleProperties","state","styles","baseRadius","baseBorder","topBorder","rightBorder","bottomBorder","leftBorder","filterStr","applyStylesToElement","resolveClassName","className","applyClassNameToElement","resolved","cls","removeClassNameFromElement","Z_INDEX","ZoomEngine","animationEngine","styling","normalizedPercent","targetHeight","focusHeight","focusWidth","maxWidth","targetX","targetY","fromWidth","fromHeight","toWidth","toHeight","fromTransformStr","toTransformStr","originalZIndex","fromDimensions","originalWidth","originalHeight","focusDimensions","focusTransform","startTransform","startWidth","startHeight","toState","targetWidth","imageElement","myGeneration","incomingSnapshot","incomingFrom","incomingFromDimensions","_j","_k","_l","incomingUnfocus","newImageElement","animate","centeredTransform","SWIPE_THRESHOLD_PX","SWIPE_VELOCITY_THRESHOLD","SWIPE_MIN_DISTANCE_FOR_VELOCITY","DRAG_DAMPING","SNAP_BACK_DURATION_MS","HORIZONTAL_ANGLE_THRESHOLD_DEG","_SwipeEngine","container","callbacks","e","touch","deltaX","deltaY","angleDeg","dampedOffset","deltaTime","velocity","absDistance","navigated","SwipeEngine","GoogleDriveLoader","source","folderUrl","recursive","urls","patterns","match","folderId","error","imageUrls","query","url","response","validFiles","file","fileUrls","fileUrl","fileId","metadataUrl","metadata","data","subfolders","f","folder","subfolderImages","_filter","html","imageIdPattern","matches","m","id","imageIds","args","StaticImageLoader","validUrls","filename","basePath","files","controller","timeoutId","cleanBase","origin","cleanPath","CompositeLoader","preparePromises","loader","ImageFilter","extensions","extension","FUNCTIONAL_CSS","injectFunctionalStyles","ImageCloud","entryConfig","loaderType","staticConfig","compositeConfig","childLoaders","loaderConfig","driveConfig","nextIndex","prevIndex","newHeight","responsive","sizingResult","currentGeneration","displayImage","img","timing","finalTransform","imgIndex","startQueueProcessing","observer","entries","entry","el","marker","renderedWidth","originalLayout","isFocused","bounds","show","message","injectStyles","css","autoInitialize","initGalleries","containers","jsonConfig","parsed"],"mappings":"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"}