@beautifi/plugin 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/beautifi.cjs.js +1 -1
- package/dist/beautifi.cjs.js.map +1 -1
- package/dist/beautifi.esm.js +3 -3
- package/dist/beautifi.esm.js.map +1 -1
- package/dist/beautifi.min.js +1 -1
- package/dist/beautifi.min.js.map +1 -1
- package/dist/beautifi.umd.js +1 -1
- package/dist/beautifi.umd.js.map +1 -1
- package/package.json +1 -1
- package/dist/beautifi-lite.cjs.js +0 -2
- package/dist/beautifi-lite.cjs.js.map +0 -1
- package/dist/beautifi-lite.esm.js +0 -212
- package/dist/beautifi-lite.esm.js.map +0 -1
- package/dist/beautifi-lite.min.js +0 -2
- package/dist/beautifi-lite.min.js.map +0 -1
- package/dist/beautifi-lite.umd.js +0 -2
- package/dist/beautifi-lite.umd.js.map +0 -1
- package/dist/sri-hashes.json +0 -46
package/dist/beautifi.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"beautifi.cjs.js","sources":["../src/types.ts","../src/ImageDetector.ts","../src/ViewportObserver.ts","../src/GeminiApiClient.ts","../src/AnimationRenderer.ts","../src/VeoClient.ts","../src/VideoRenderer.ts","../src/index.ts"],"sourcesContent":["/**\n * beautifi - Core Type Definitions\n *\n * This file contains all TypeScript interfaces and types used throughout\n * the beautifi plugin.\n */\n\n/**\n * Animation intensity levels\n */\nexport type AnimationIntensity = 'subtle' | 'medium' | 'strong';\n\n/**\n * Animation type styles\n */\nexport type AnimationType = 'breathing' | 'parallax' | 'sway' | 'auto';\n\n/**\n * Animation states\n */\nexport type AnimationState = 'idle' | 'loading' | 'playing' | 'paused' | 'error';\n\n/**\n * Error codes for different failure scenarios\n */\nexport type LivePhotoErrorCode =\n | 'API_ERROR'\n | 'NETWORK_ERROR'\n | 'TIMEOUT_ERROR'\n | 'VALIDATION_ERROR'\n | 'RATE_LIMIT_ERROR'\n | 'RENDER_ERROR'\n | 'UNKNOWN_ERROR';\n\n/**\n * Event types emitted by the plugin\n */\nexport type LivePhotoEventType =\n | 'init'\n | 'imageDetected'\n | 'animationStart'\n | 'animationComplete'\n | 'animationPause'\n | 'animationResume'\n | 'animationError'\n | 'videoStart'\n | 'videoReady'\n | 'videoError'\n | 'destroy';\n\n/**\n * Animation mode - determines how images are animated\n * - 'auto': CSS animation immediately + AI video in background (default)\n * - 'css': CSS animation only, no video generation\n * - 'video': Wait for video, no CSS animation\n */\nexport type AnimationMode = 'auto' | 'css' | 'video';\n\n/**\n * Video generation state\n */\nexport type VideoState = 'idle' | 'generating' | 'ready' | 'error';\n\n/**\n * Global plugin configuration options\n */\nexport interface LivePhotoOptions {\n /** API key for Gemini backend (required) */\n apiKey: string;\n /** Cloud Functions endpoint URL (optional, uses default if not provided) */\n endpoint?: string;\n /** CSS selector for target images (default: 'img') */\n selector?: string;\n /** Default animation intensity (default: 'subtle') */\n intensity?: AnimationIntensity;\n /** Default animation type (default: 'auto') */\n type?: AnimationType;\n /** Whether animations should loop (default: true) */\n loop?: boolean;\n /** Respect prefers-reduced-motion (default: true) */\n respectReducedMotion?: boolean;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n /** Viewport intersection threshold for lazy loading (default: 0.1) */\n threshold?: number;\n /** Root margin for eager loading (default: '50px') */\n rootMargin?: string;\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n /** Maximum retry attempts (default: 3) */\n maxRetries?: number;\n /** Animation mode: 'auto' | 'css' | 'video' (default: 'auto') */\n mode?: AnimationMode;\n /** Video endpoint URL (if different from main endpoint) */\n videoEndpoint?: string;\n}\n\n/**\n * Per-image animation configuration (from data attributes)\n */\nexport interface AnimationConfig {\n /** Whether animation is enabled for this image */\n enabled: boolean;\n /** Animation intensity level */\n intensity: AnimationIntensity;\n /** Animation type/style */\n type: AnimationType;\n /** Whether animation loops continuously */\n loop: boolean;\n /** Delay before animation starts (ms) */\n delay: number;\n}\n\n/**\n * Animation frame data from Gemini API\n */\nexport interface AnimationFrameData {\n /** Frame timestamp */\n timestamp: number;\n /** Transform data for this frame */\n transform: {\n translateX: number;\n translateY: number;\n scale: number;\n rotate: number;\n };\n /** Optional opacity value */\n opacity?: number;\n}\n\n/**\n * Gemini API response for animation generation\n */\nexport interface GeminiAnimationResponse {\n /** Whether the request was successful */\n success: boolean;\n /** Unique animation ID */\n animationId: string;\n /** Animation duration in milliseconds */\n duration: number;\n /** Frame rate (fps) */\n frameRate: number;\n /** Animation keyframes */\n frames: AnimationFrameData[];\n /** Detected animation type */\n detectedType: AnimationType;\n /** Cache status (hit/miss) */\n cacheStatus?: 'hit' | 'miss';\n /** Error message if success is false */\n error?: string;\n}\n\n/**\n * API request payload\n */\nexport interface GeminiAnimationRequest {\n /** Image URL or hash */\n imageUrl: string;\n /** Image hash for caching */\n imageHash?: string;\n /** Requested animation type */\n type: AnimationType;\n /** Requested intensity */\n intensity: AnimationIntensity;\n /** Whether to loop */\n loop: boolean;\n}\n\n/**\n * Tracked image element with metadata\n */\nexport interface TrackedImage {\n /** Original image element */\n element: HTMLImageElement;\n /** Unique ID for this tracked image */\n id: string;\n /** Current animation state */\n state: AnimationState;\n /** Animation configuration for this image */\n config: AnimationConfig;\n /** API response data (when loaded) */\n animationData?: GeminiAnimationResponse;\n /** Error if animation failed */\n error?: LivePhotoError;\n}\n\n/**\n * Base event payload\n */\nexport interface LivePhotoEventBase {\n /** Event type */\n type: LivePhotoEventType;\n /** Event timestamp */\n timestamp: number;\n}\n\n/**\n * Image-related event payload\n */\nexport interface LivePhotoImageEvent extends LivePhotoEventBase {\n /** Image element involved */\n element: HTMLImageElement;\n /** Image tracking ID */\n imageId: string;\n}\n\n/**\n * Error event payload\n */\nexport interface LivePhotoErrorEvent extends LivePhotoImageEvent {\n type: 'animationError';\n /** Error details */\n error: LivePhotoError;\n}\n\n/**\n * Event handler function type\n */\nexport type LivePhotoEventHandler<T extends LivePhotoEventBase = LivePhotoEventBase> = (\n event: T\n) => void;\n\n/**\n * Viewport observer callback\n */\nexport type ViewportCallback = (image: TrackedImage, isIntersecting: boolean) => void;\n\n/**\n * Custom error class for beautifi\n */\nexport class LivePhotoError extends Error {\n constructor(\n message: string,\n public readonly code: LivePhotoErrorCode,\n public readonly originalError?: Error\n ) {\n super(message);\n this.name = 'LivePhotoError';\n // Ensure prototype chain is set correctly\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, LivePhotoError);\n }\n }\n}\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_OPTIONS: Required<Omit<LivePhotoOptions, 'apiKey'>> = {\n endpoint: 'https://us-central1-magic-mirror-427812.cloudfunctions.net/beautifi-animate/animate',\n selector: 'img',\n intensity: 'subtle',\n type: 'auto',\n loop: true,\n respectReducedMotion: true,\n debug: false,\n threshold: 0.1,\n rootMargin: '50px',\n timeout: 10000,\n maxRetries: 3,\n mode: 'auto',\n videoEndpoint: 'https://us-central1-magic-mirror-427812.cloudfunctions.net/beautifi-animate',\n};\n\n/**\n * Default animation configuration\n */\nexport const DEFAULT_ANIMATION_CONFIG: AnimationConfig = {\n enabled: true,\n intensity: 'subtle',\n type: 'auto',\n loop: true,\n delay: 0,\n};\n","/**\n * Image Detector Module\n *\n * Responsible for finding and tracking images in the DOM that should be animated.\n * Handles CSS selector matching, data attribute parsing, and MutationObserver\n * for dynamically added images.\n */\n\nimport {\n AnimationConfig,\n AnimationIntensity,\n AnimationType,\n TrackedImage,\n DEFAULT_ANIMATION_CONFIG,\n} from './types';\n\n/**\n * Generate a unique ID for tracking images\n */\nfunction generateId(): string {\n return `lmp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Parse data-live-* attributes from an image element\n */\nfunction parseDataAttributes(element: HTMLImageElement): AnimationConfig {\n const enabled = element.dataset.live !== 'false';\n\n const intensityAttr = element.dataset.liveIntensity;\n const intensity: AnimationIntensity =\n intensityAttr === 'subtle' || intensityAttr === 'medium' || intensityAttr === 'strong'\n ? intensityAttr\n : DEFAULT_ANIMATION_CONFIG.intensity;\n\n const typeAttr = element.dataset.liveType;\n const type: AnimationType =\n typeAttr === 'breathing' || typeAttr === 'parallax' || typeAttr === 'sway' || typeAttr === 'auto'\n ? typeAttr\n : DEFAULT_ANIMATION_CONFIG.type;\n\n const loop = element.dataset.liveLoop !== 'false';\n const delay = parseInt(element.dataset.liveDelay || '0', 10) || 0;\n\n return {\n enabled,\n intensity,\n type,\n loop,\n delay,\n };\n}\n\n/**\n * ImageDetector class\n * \n * Detects images in the DOM and parses their animation configuration.\n */\nexport class ImageDetector {\n private selector: string;\n private trackedImages: Map<string, TrackedImage> = new Map();\n private processedElements: WeakSet<HTMLImageElement> = new WeakSet();\n private mutationObserver: MutationObserver | null = null;\n private onImageDetected: ((image: TrackedImage) => void) | null = null;\n private debug: boolean;\n\n constructor(selector: string = 'img', debug: boolean = false) {\n this.selector = selector;\n this.debug = debug;\n }\n\n /**\n * Set callback for when new images are detected\n */\n setOnImageDetected(callback: (image: TrackedImage) => void): void {\n this.onImageDetected = callback;\n }\n\n /**\n * Scan the DOM for images matching the selector\n */\n scan(): TrackedImage[] {\n const elements = document.querySelectorAll<HTMLImageElement>(this.selector);\n const newImages: TrackedImage[] = [];\n\n elements.forEach((element) => {\n if (this.processedElements.has(element)) {\n return;\n }\n\n const config = parseDataAttributes(element);\n\n // Skip if explicitly disabled\n if (!config.enabled) {\n this.processedElements.add(element);\n return;\n }\n\n const trackedImage: TrackedImage = {\n element,\n id: generateId(),\n state: 'idle',\n config,\n };\n\n this.trackedImages.set(trackedImage.id, trackedImage);\n this.processedElements.add(element);\n newImages.push(trackedImage);\n\n if (this.debug) {\n console.log('[LiveMyPhotos] Detected image:', element.src, config);\n }\n });\n\n return newImages;\n }\n\n /**\n * Start observing the DOM for dynamically added images\n */\n observe(): void {\n if (this.mutationObserver) {\n return;\n }\n\n this.mutationObserver = new MutationObserver((mutations) => {\n let shouldScan = false;\n\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (const node of mutation.addedNodes) {\n if (\n node instanceof HTMLImageElement ||\n (node instanceof Element && node.querySelector(this.selector))\n ) {\n shouldScan = true;\n break;\n }\n }\n }\n if (shouldScan) break;\n }\n\n if (shouldScan) {\n const newImages = this.scan();\n newImages.forEach((image) => {\n if (this.onImageDetected) {\n this.onImageDetected(image);\n }\n });\n }\n });\n\n this.mutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n\n /**\n * Get all tracked images\n */\n getTrackedImages(): TrackedImage[] {\n return Array.from(this.trackedImages.values());\n }\n\n /**\n * Get a tracked image by ID\n */\n getTrackedImage(id: string): TrackedImage | undefined {\n return this.trackedImages.get(id);\n }\n\n /**\n * Update a tracked image's state\n */\n updateImageState(id: string, updates: Partial<TrackedImage>): void {\n const image = this.trackedImages.get(id);\n if (image) {\n Object.assign(image, updates);\n }\n }\n\n /**\n * Clean up and stop observing\n */\n destroy(): void {\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n this.trackedImages.clear();\n this.onImageDetected = null;\n }\n}\n","/**\n * Viewport Observer Module\n *\n * Uses IntersectionObserver to track when images enter/leave the viewport.\n * Also handles visibility changes for pausing animations in hidden tabs.\n */\n\nimport { TrackedImage, ViewportCallback } from './types';\n\n/**\n * ViewportObserver class\n *\n * Efficiently tracks image visibility using IntersectionObserver.\n */\nexport class ViewportObserver {\n private observer: IntersectionObserver | null = null;\n private visibilityHandler: (() => void) | null = null;\n private callback: ViewportCallback;\n private threshold: number;\n private rootMargin: string;\n private observedElements: Map<HTMLImageElement, TrackedImage> = new Map();\n private isDocumentVisible: boolean = true;\n private debug: boolean;\n\n constructor(\n callback: ViewportCallback,\n options: {\n threshold?: number;\n rootMargin?: string;\n debug?: boolean;\n } = {}\n ) {\n this.callback = callback;\n this.threshold = options.threshold ?? 0.1;\n this.rootMargin = options.rootMargin ?? '50px';\n this.debug = options.debug ?? false;\n this.isDocumentVisible = typeof document !== 'undefined' ? !document.hidden : true;\n }\n\n /**\n * Initialize the observer\n */\n init(): void {\n if (this.observer) {\n return;\n }\n\n // Create IntersectionObserver\n this.observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n const image = this.observedElements.get(entry.target as HTMLImageElement);\n if (image && this.isDocumentVisible) {\n if (this.debug) {\n console.log(\n '[LiveMyPhotos] Viewport:',\n entry.isIntersecting ? 'enter' : 'leave',\n image.element.src\n );\n }\n this.callback(image, entry.isIntersecting);\n }\n });\n },\n {\n threshold: this.threshold,\n rootMargin: this.rootMargin,\n }\n );\n\n // Listen for visibility changes\n this.visibilityHandler = () => {\n const wasVisible = this.isDocumentVisible;\n this.isDocumentVisible = !document.hidden;\n\n if (wasVisible !== this.isDocumentVisible) {\n if (this.debug) {\n console.log('[LiveMyPhotos] Document visibility:', this.isDocumentVisible);\n }\n\n // Notify all observed images of visibility change\n this.observedElements.forEach((image) => {\n // When document becomes visible, re-check if images are in viewport\n // When hidden, notify that images are \"leaving\" viewport\n this.callback(image, this.isDocumentVisible);\n });\n }\n };\n\n document.addEventListener('visibilitychange', this.visibilityHandler);\n }\n\n /**\n * Start observing an image\n */\n observe(image: TrackedImage): void {\n if (!this.observer) {\n this.init();\n }\n\n if (!this.observedElements.has(image.element)) {\n this.observedElements.set(image.element, image);\n this.observer!.observe(image.element);\n }\n }\n\n /**\n * Stop observing an image\n */\n unobserve(image: TrackedImage): void {\n if (this.observer && this.observedElements.has(image.element)) {\n this.observer.unobserve(image.element);\n this.observedElements.delete(image.element);\n }\n }\n\n /**\n * Check if document is currently visible\n */\n isVisible(): boolean {\n return this.isDocumentVisible;\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n if (this.observer) {\n this.observer.disconnect();\n this.observer = null;\n }\n\n if (this.visibilityHandler) {\n document.removeEventListener('visibilitychange', this.visibilityHandler);\n this.visibilityHandler = null;\n }\n\n this.observedElements.clear();\n }\n}\n","/**\n * Gemini API Client\n *\n * Handles communication with Cloud Functions backend for animation generation.\n * Features: timeout handling, retry with exponential backoff, response caching.\n */\n\nimport {\n GeminiAnimationRequest,\n GeminiAnimationResponse,\n LivePhotoError,\n AnimationType,\n AnimationIntensity,\n} from './types';\n\n/**\n * Configuration for the API client\n */\nexport interface GeminiApiClientConfig {\n /** Cloud Functions endpoint URL */\n endpoint: string;\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n /** Maximum retry attempts (default: 3) */\n maxRetries?: number;\n /** Enable response caching (default: true) */\n enableCache?: boolean;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<Omit<GeminiApiClientConfig, 'endpoint'>> = {\n timeout: 10000,\n maxRetries: 3,\n enableCache: true,\n debug: false,\n};\n\n/**\n * Cache key prefix for localStorage\n */\nconst CACHE_KEY_PREFIX = 'lmp_cache_';\n\n/**\n * Cache TTL in milliseconds (30 days)\n */\nconst CACHE_TTL = 30 * 24 * 60 * 60 * 1000;\n\n/**\n * Cached response structure\n */\ninterface CachedResponse {\n data: GeminiAnimationResponse;\n timestamp: number;\n}\n\n/**\n * GeminiApiClient class\n *\n * Handles fetching animation data from the Cloud Functions backend\n * with retry logic, timeout handling, and caching.\n */\nexport class GeminiApiClient {\n private config: Required<GeminiApiClientConfig>;\n\n constructor(config: GeminiApiClientConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n } as Required<GeminiApiClientConfig>;\n }\n\n /**\n * Fetch animation data for an image\n *\n * @param imageUrl - URL of the image to animate\n * @param options - Animation options\n * @returns Promise resolving to animation response\n */\n async fetch(\n imageUrl: string,\n options: {\n type?: AnimationType;\n intensity?: AnimationIntensity;\n loop?: boolean;\n } = {}\n ): Promise<GeminiAnimationResponse> {\n const imageHash = await this.computeHash(imageUrl);\n\n // Check cache first\n if (this.config.enableCache) {\n const cached = this.getFromCache(imageHash);\n if (cached) {\n if (this.config.debug) {\n console.log('[GeminiApiClient] Cache hit for:', imageUrl);\n }\n return { ...cached, cacheStatus: 'hit' };\n }\n }\n\n // Prepare request\n const request: GeminiAnimationRequest = {\n imageUrl,\n imageHash,\n type: options.type ?? 'auto',\n intensity: options.intensity ?? 'subtle',\n loop: options.loop ?? true,\n };\n\n // Fetch with retry logic\n const response = await this.fetchWithRetry(request);\n\n // Cache successful response\n if (response.success && this.config.enableCache) {\n this.saveToCache(imageHash, response);\n }\n\n return { ...response, cacheStatus: 'miss' };\n }\n\n /**\n * Fetch with retry logic and exponential backoff\n */\n private async fetchWithRetry(\n request: GeminiAnimationRequest,\n attempt: number = 0\n ): Promise<GeminiAnimationResponse> {\n try {\n return await this.fetchWithTimeout(request);\n } catch (error) {\n const isRetryable =\n error instanceof LivePhotoError &&\n (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT_ERROR');\n\n if (isRetryable && attempt < this.config.maxRetries) {\n // Exponential backoff: 1s, 2s, 4s, ...\n const delay = Math.pow(2, attempt) * 1000;\n if (this.config.debug) {\n console.log(\n `[GeminiApiClient] Retry ${attempt + 1}/${this.config.maxRetries} after ${delay}ms`\n );\n }\n await this.sleep(delay);\n return this.fetchWithRetry(request, attempt + 1);\n }\n\n throw error;\n }\n }\n\n /**\n * Fetch with timeout handling\n */\n private async fetchWithTimeout(\n request: GeminiAnimationRequest\n ): Promise<GeminiAnimationResponse> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(this.config.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n if (response.status === 429) {\n throw new LivePhotoError(\n 'Rate limit exceeded',\n 'RATE_LIMIT_ERROR'\n );\n }\n throw new LivePhotoError(\n `API error: ${response.status} ${response.statusText}`,\n 'API_ERROR'\n );\n }\n\n const data = (await response.json()) as GeminiAnimationResponse;\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof LivePhotoError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new LivePhotoError(\n `Request timed out after ${this.config.timeout}ms`,\n 'TIMEOUT_ERROR',\n error\n );\n }\n throw new LivePhotoError(\n `Network error: ${error.message}`,\n 'NETWORK_ERROR',\n error\n );\n }\n\n throw new LivePhotoError('Unknown error occurred', 'UNKNOWN_ERROR');\n }\n }\n\n /**\n * Compute SHA-256 hash of a string (for cache keys)\n */\n async computeHash(input: string): Promise<string> {\n // Use Web Crypto API if available\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const encoder = new TextEncoder();\n const data = encoder.encode(input);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n }\n\n // Fallback: simple hash for environments without crypto.subtle\n let hash = 0;\n for (let i = 0; i < input.length; i++) {\n const char = input.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16);\n }\n\n /**\n * Get cached response if valid\n */\n private getFromCache(imageHash: string): GeminiAnimationResponse | null {\n if (typeof localStorage === 'undefined') {\n return null;\n }\n\n try {\n const cacheKey = CACHE_KEY_PREFIX + imageHash;\n const cached = localStorage.getItem(cacheKey);\n\n if (!cached) {\n return null;\n }\n\n const parsed: CachedResponse = JSON.parse(cached);\n\n // Check if cache is still valid\n if (Date.now() - parsed.timestamp > CACHE_TTL) {\n localStorage.removeItem(cacheKey);\n return null;\n }\n\n return parsed.data;\n } catch {\n return null;\n }\n }\n\n /**\n * Save response to cache\n */\n private saveToCache(imageHash: string, response: GeminiAnimationResponse): void {\n if (typeof localStorage === 'undefined') {\n return;\n }\n\n try {\n const cacheKey = CACHE_KEY_PREFIX + imageHash;\n const cached: CachedResponse = {\n data: response,\n timestamp: Date.now(),\n };\n localStorage.setItem(cacheKey, JSON.stringify(cached));\n } catch {\n // Ignore storage errors (quota exceeded, etc.)\n if (this.config.debug) {\n console.warn('[GeminiApiClient] Failed to cache response');\n }\n }\n }\n\n /**\n * Clear all cached responses\n */\n clearCache(): void {\n if (typeof localStorage === 'undefined') {\n return;\n }\n\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key && key.startsWith(CACHE_KEY_PREFIX)) {\n keysToRemove.push(key);\n }\n }\n keysToRemove.forEach((key) => localStorage.removeItem(key));\n }\n\n /**\n * Sleep utility for retry delays\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","/**\n * Animation Renderer\n *\n * Renders animation frames to image elements using CSS transforms.\n * Supports breathing, parallax, and sway animation types with\n * configurable intensity levels and loop/one-shot modes.\n */\n\nimport {\n GeminiAnimationResponse,\n AnimationType,\n AnimationIntensity,\n TrackedImage,\n} from './types';\n\n/**\n * Intensity multipliers for animation effects\n */\nconst INTENSITY_MULTIPLIERS: Record<AnimationIntensity, number> = {\n subtle: 0.3,\n medium: 0.6,\n strong: 1.0,\n};\n\n/**\n * Animation preset configurations\n */\nconst ANIMATION_PRESETS: Record<\n Exclude<AnimationType, 'auto'>,\n {\n translateX: number;\n translateY: number;\n scale: number;\n rotate: number;\n duration: number;\n }\n> = {\n breathing: {\n translateX: 0,\n translateY: 0,\n scale: 0.02,\n rotate: 0,\n duration: 3000,\n },\n parallax: {\n translateX: 5,\n translateY: 3,\n scale: 0.01,\n rotate: 0,\n duration: 4000,\n },\n sway: {\n translateX: 3,\n translateY: 0,\n scale: 0,\n rotate: 2,\n duration: 2500,\n },\n};\n\n/**\n * Animation renderer configuration\n */\nexport interface AnimationRendererConfig {\n /** Target frame rate (default: 60) */\n frameRate?: number;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\n/**\n * Animation state for a single image\n */\ninterface AnimationState {\n trackedImage: TrackedImage;\n animationData: GeminiAnimationResponse;\n currentFrame: number;\n animationFrameId: number | null;\n startTime: number;\n isPaused: boolean;\n intensity: AnimationIntensity;\n loop: boolean;\n onComplete?: () => void;\n}\n\n/**\n * AnimationRenderer class\n *\n * Handles rendering animation frames to DOM elements using\n * requestAnimationFrame for smooth playback.\n */\nexport class AnimationRenderer {\n private config: Required<AnimationRendererConfig>;\n private animations: Map<string, AnimationState> = new Map();\n\n constructor(config: AnimationRendererConfig = {}) {\n this.config = {\n frameRate: config.frameRate ?? 60,\n debug: config.debug ?? false,\n };\n }\n\n /**\n * Start animation playback for an image\n *\n * @param trackedImage - The tracked image to animate\n * @param animationData - Animation data from Gemini API\n * @param options - Animation options\n */\n play(\n trackedImage: TrackedImage,\n animationData: GeminiAnimationResponse,\n options: {\n intensity?: AnimationIntensity;\n loop?: boolean;\n onComplete?: () => void;\n } = {}\n ): void {\n const { id, element } = trackedImage;\n\n // Stop any existing animation\n this.stop(id);\n\n // Prepare element for animation\n element.style.willChange = 'transform';\n element.style.transformOrigin = 'center center';\n\n const state: AnimationState = {\n trackedImage,\n animationData,\n currentFrame: 0,\n animationFrameId: null,\n startTime: performance.now(),\n isPaused: false,\n intensity: options.intensity ?? trackedImage.config.intensity,\n loop: options.loop ?? trackedImage.config.loop,\n onComplete: options.onComplete,\n };\n\n this.animations.set(id, state);\n\n if (this.config.debug) {\n console.log('[AnimationRenderer] Starting animation for:', id);\n }\n\n this.renderLoop(id);\n }\n\n /**\n * Pause animation for an image\n */\n pause(imageId: string): void {\n const state = this.animations.get(imageId);\n if (state && !state.isPaused) {\n state.isPaused = true;\n if (state.animationFrameId !== null) {\n cancelAnimationFrame(state.animationFrameId);\n state.animationFrameId = null;\n }\n\n if (this.config.debug) {\n console.log('[AnimationRenderer] Paused animation for:', imageId);\n }\n }\n }\n\n /**\n * Resume animation for an image\n */\n resume(imageId: string): void {\n const state = this.animations.get(imageId);\n if (state && state.isPaused) {\n state.isPaused = false;\n state.startTime = performance.now() - this.getElapsedTime(state);\n this.renderLoop(imageId);\n\n if (this.config.debug) {\n console.log('[AnimationRenderer] Resumed animation for:', imageId);\n }\n }\n }\n\n /**\n * Stop animation for an image and reset transforms\n */\n stop(imageId: string): void {\n const state = this.animations.get(imageId);\n if (state) {\n if (state.animationFrameId !== null) {\n cancelAnimationFrame(state.animationFrameId);\n }\n\n // Reset element styles\n state.trackedImage.element.style.transform = '';\n state.trackedImage.element.style.willChange = '';\n\n this.animations.delete(imageId);\n\n if (this.config.debug) {\n console.log('[AnimationRenderer] Stopped animation for:', imageId);\n }\n }\n }\n\n /**\n * Check if an image is currently animating\n */\n isPlaying(imageId: string): boolean {\n const state = this.animations.get(imageId);\n return state !== undefined && !state.isPaused;\n }\n\n /**\n * Get all currently animating image IDs\n */\n getActiveAnimations(): string[] {\n return Array.from(this.animations.keys());\n }\n\n /**\n * Stop all animations and clean up\n */\n destroy(): void {\n for (const imageId of this.animations.keys()) {\n this.stop(imageId);\n }\n this.animations.clear();\n }\n\n /**\n * Main render loop using requestAnimationFrame\n */\n private renderLoop(imageId: string): void {\n const state = this.animations.get(imageId);\n if (!state || state.isPaused) {\n return;\n }\n\n const elapsed = performance.now() - state.startTime;\n const duration = state.animationData.duration || 3000;\n const progress = (elapsed % duration) / duration;\n\n // Check if animation should complete (non-looping)\n if (!state.loop && elapsed >= duration) {\n this.stop(imageId);\n if (state.onComplete) {\n state.onComplete();\n }\n return;\n }\n\n // Render the current frame\n this.renderFrame(state, progress);\n\n // Schedule next frame\n state.animationFrameId = requestAnimationFrame(() => {\n this.renderLoop(imageId);\n });\n }\n\n /**\n * Render a single animation frame\n */\n private renderFrame(state: AnimationState, progress: number): void {\n const { trackedImage, animationData, intensity } = state;\n const multiplier = INTENSITY_MULTIPLIERS[intensity];\n\n let transform: { translateX: number; translateY: number; scale: number; rotate: number };\n\n // Use animation data if available, otherwise generate from type\n if (animationData.frames && animationData.frames.length > 0) {\n const frameIndex = Math.floor(progress * animationData.frames.length);\n const frame = animationData.frames[Math.min(frameIndex, animationData.frames.length - 1)];\n transform = frame.transform;\n } else {\n // Generate animation from detected type\n const animType = animationData.detectedType || 'breathing';\n if (animType !== 'auto') {\n transform = this.generateTransformFromType(animType, progress);\n } else {\n transform = this.generateTransformFromType('breathing', progress);\n }\n }\n\n // Apply intensity multiplier\n const translateX = transform.translateX * multiplier;\n const translateY = transform.translateY * multiplier;\n const scale = 1 + transform.scale * multiplier;\n const rotate = transform.rotate * multiplier;\n\n // Apply CSS transform\n trackedImage.element.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale}) rotate(${rotate}deg)`;\n }\n\n /**\n * Generate transform values for a given animation type\n */\n private generateTransformFromType(\n type: Exclude<AnimationType, 'auto'>,\n progress: number\n ): { translateX: number; translateY: number; scale: number; rotate: number } {\n const preset = ANIMATION_PRESETS[type] || ANIMATION_PRESETS.breathing;\n\n // Use sine wave for smooth oscillation\n const oscillation = Math.sin(progress * Math.PI * 2);\n\n return {\n translateX: preset.translateX * oscillation,\n translateY: preset.translateY * oscillation,\n scale: preset.scale * oscillation,\n rotate: preset.rotate * oscillation,\n };\n }\n\n /**\n * Calculate elapsed time for a paused animation\n */\n private getElapsedTime(state: AnimationState): number {\n const duration = state.animationData.duration || 3000;\n return (state.currentFrame / state.animationData.frameRate) * 1000 % duration;\n }\n}\n","/**\n * Veo API Client\n *\n * Handles communication with Cloud Functions backend for Veo 3.1 video generation.\n * Features: async polling with exponential backoff, localStorage caching, error handling.\n */\n\n/**\n * Animation mode for the plugin\n */\nexport type AnimationMode = 'auto' | 'css' | 'video';\n\n/**\n * Video generation state\n */\nexport type VideoState = 'idle' | 'generating' | 'ready' | 'error';\n\n/**\n * Video generation request\n */\nexport interface VeoGenerationRequest {\n /** Image URL to generate video from */\n imageUrl: string;\n /** Animation prompt for the video */\n animationPrompt?: string;\n /** Aspect ratio (default: auto-detected from image) */\n aspectRatio?: '16:9' | '9:16';\n /** Duration in seconds (default: 4) */\n durationSeconds?: 4 | 6 | 8;\n}\n\n/**\n * Video generation response from backend\n */\nexport interface VeoGenerationResponse {\n success: boolean;\n operationId?: string;\n status?: 'pending' | 'processing' | 'completed' | 'error';\n videoUrl?: string;\n animation?: any; // CSS animation data (from /animate-auto)\n error?: {\n code: string;\n message: string;\n };\n}\n\n/**\n * Cached video entry\n */\ninterface CachedVideo {\n videoUrl: string;\n timestamp: number;\n}\n\n/**\n * Configuration for the Veo client\n */\nexport interface VeoClientConfig {\n /** Backend endpoint URL */\n endpoint: string;\n /** API key for authentication (optional - uses demo limits without) */\n apiKey?: string;\n /** Enable debug logging */\n debug?: boolean;\n /** Polling interval in ms (default: 3000) */\n pollInterval?: number;\n /** Max poll attempts before timeout (default: 60 = 3 minutes) */\n maxPollAttempts?: number;\n /** Cache TTL in ms (default: 30 days) */\n cacheTTL?: number;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG = {\n apiKey: '',\n debug: false,\n pollInterval: 3000,\n maxPollAttempts: 60,\n cacheTTL: 30 * 24 * 60 * 60 * 1000, // 30 days\n};\n\n/**\n * Cache key prefix for localStorage\n */\nconst VIDEO_CACHE_PREFIX = 'beautifi_video_';\n\n/**\n * VeoClient class\n *\n * Handles async video generation with automatic polling and caching.\n */\nexport class VeoClient {\n private config: Required<VeoClientConfig>;\n private pendingOperations: Map<string, AbortController> = new Map();\n\n constructor(config: VeoClientConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n }\n\n /**\n * Start video generation for an image\n *\n * @param request - Video generation request\n * @returns Promise resolving to operation ID\n */\n async startGeneration(request: VeoGenerationRequest): Promise<VeoGenerationResponse> {\n // Check cache first\n const cacheKey = this.computeCacheKey(request.imageUrl);\n const cached = this.getFromCache(cacheKey);\n if (cached) {\n this.log(`📦 Video cached: ${cacheKey.slice(0, 8)}...`);\n return {\n success: true,\n status: 'completed',\n videoUrl: cached,\n };\n }\n\n try {\n const endpoint = this.config.endpoint.replace('/animate', '/animate-auto');\n\n this.log(`🎬 Starting video generation for: ${request.imageUrl.slice(0, 50)}...`);\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.config.apiKey) {\n headers['X-API-Key'] = this.config.apiKey;\n }\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n imageUrl: request.imageUrl,\n animationPrompt: request.animationPrompt,\n aspectRatio: request.aspectRatio,\n durationSeconds: request.durationSeconds,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data: VeoGenerationResponse = await response.json();\n\n if (!data.success) {\n throw new Error(data.error?.message || 'Video generation failed');\n }\n\n this.log(`🚀 Generation started: ${data.operationId}`);\n return data;\n\n } catch (error) {\n this.log(`❌ Start generation error: ${error}`);\n return {\n success: false,\n error: {\n code: 'GENERATION_ERROR',\n message: error instanceof Error ? error.message : 'Failed to start generation',\n },\n };\n }\n }\n\n /**\n * Poll for video generation status until complete\n *\n * @param operationId - Operation ID to poll\n * @param imageUrl - Original image URL (for caching)\n * @param onProgress - Callback for progress updates\n * @returns Promise resolving to video URL or null on failure\n */\n async pollUntilComplete(\n operationId: string,\n imageUrl: string,\n onProgress?: (status: string, attempt: number) => void\n ): Promise<string | null> {\n const controller = new AbortController();\n this.pendingOperations.set(operationId, controller);\n\n try {\n let attempt = 0;\n\n while (attempt < this.config.maxPollAttempts) {\n if (controller.signal.aborted) {\n this.log(`🛑 Polling aborted: ${operationId}`);\n return null;\n }\n\n const result = await this.checkStatus(operationId);\n\n if (onProgress) {\n onProgress(result.status || 'unknown', attempt);\n }\n\n if (result.status === 'completed' && result.videoUrl) {\n this.log(`✅ Video ready: ${result.videoUrl.slice(0, 50)}...`);\n\n // Cache the video URL\n const cacheKey = this.computeCacheKey(imageUrl);\n this.saveToCache(cacheKey, result.videoUrl);\n\n return result.videoUrl;\n }\n\n if (result.status === 'error' || !result.success) {\n this.log(`❌ Generation failed: ${result.error?.message}`);\n return null;\n }\n\n // Exponential backoff: 3s, 4.5s, 6.75s, etc. (max 15s)\n const backoffMs = Math.min(\n this.config.pollInterval * Math.pow(1.5, Math.min(attempt, 5)),\n 15000\n );\n\n this.log(`⏳ Polling (${attempt + 1}/${this.config.maxPollAttempts}): ${result.status}`);\n await this.sleep(backoffMs);\n attempt++;\n }\n\n this.log(`⏰ Polling timeout: ${operationId}`);\n return null;\n\n } finally {\n this.pendingOperations.delete(operationId);\n }\n }\n\n /**\n * Check the status of a video generation operation\n */\n async checkStatus(operationId: string): Promise<VeoGenerationResponse> {\n try {\n const endpoint = this.config.endpoint\n .replace('/animate', '/generate-video/status')\n .replace(/\\/$/, '');\n\n const headers: Record<string, string> = {};\n if (this.config.apiKey) {\n headers['X-API-Key'] = this.config.apiKey;\n }\n\n const response = await fetch(`${endpoint}/${operationId}`, { headers });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return await response.json();\n\n } catch (error) {\n return {\n success: false,\n status: 'error',\n error: {\n code: 'POLL_ERROR',\n message: error instanceof Error ? error.message : 'Status check failed',\n },\n };\n }\n }\n\n /**\n * Cancel a pending video generation operation\n */\n cancelOperation(operationId: string): void {\n const controller = this.pendingOperations.get(operationId);\n if (controller) {\n controller.abort();\n this.pendingOperations.delete(operationId);\n this.log(`🛑 Cancelled: ${operationId}`);\n }\n }\n\n /**\n * Cancel all pending operations\n */\n cancelAllOperations(): void {\n for (const [id, controller] of this.pendingOperations) {\n controller.abort();\n this.log(`🛑 Cancelled: ${id}`);\n }\n this.pendingOperations.clear();\n }\n\n /**\n * Get cached video URL for an image\n */\n getCachedVideo(imageUrl: string): string | null {\n const cacheKey = this.computeCacheKey(imageUrl);\n return this.getFromCache(cacheKey);\n }\n\n /**\n * Compute cache key from image URL\n */\n private computeCacheKey(imageUrl: string): string {\n // Simple hash for cache key\n let hash = 0;\n for (let i = 0; i < imageUrl.length; i++) {\n const char = imageUrl.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(36);\n }\n\n /**\n * Get video URL from localStorage cache\n */\n private getFromCache(cacheKey: string): string | null {\n if (typeof localStorage === 'undefined') return null;\n\n try {\n const raw = localStorage.getItem(VIDEO_CACHE_PREFIX + cacheKey);\n if (!raw) return null;\n\n const cached: CachedVideo = JSON.parse(raw);\n\n // Check if expired\n if (Date.now() - cached.timestamp > this.config.cacheTTL) {\n localStorage.removeItem(VIDEO_CACHE_PREFIX + cacheKey);\n return null;\n }\n\n return cached.videoUrl;\n } catch {\n return null;\n }\n }\n\n /**\n * Save video URL to localStorage cache\n */\n private saveToCache(cacheKey: string, videoUrl: string): void {\n if (typeof localStorage === 'undefined') return;\n\n try {\n const cached: CachedVideo = {\n videoUrl,\n timestamp: Date.now(),\n };\n localStorage.setItem(VIDEO_CACHE_PREFIX + cacheKey, JSON.stringify(cached));\n this.log(`💾 Cached: ${cacheKey}`);\n } catch {\n // localStorage full or unavailable - ignore\n }\n }\n\n /**\n * Clear all cached videos\n */\n clearCache(): void {\n if (typeof localStorage === 'undefined') return;\n\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(VIDEO_CACHE_PREFIX)) {\n keysToRemove.push(key);\n }\n }\n keysToRemove.forEach(key => localStorage.removeItem(key));\n this.log(`🧹 Cleared ${keysToRemove.length} cached videos`);\n }\n\n /**\n * Sleep utility\n */\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n /**\n * Debug logging\n */\n private log(message: string): void {\n if (this.config.debug) {\n console.log(`[VeoClient] ${message}`);\n }\n }\n}\n","/**\n * Video Renderer\n *\n * Renders video cinemagraphs with seamless transitions from CSS animations.\n * Handles video playback, loading states, and fallback to CSS on failure.\n */\n\n/**\n * Video playback state\n */\nexport type VideoPlaybackState = 'loading' | 'buffering' | 'playing' | 'paused' | 'error';\n\n/**\n * Video renderer configuration\n */\nexport interface VideoRendererConfig {\n /** Transition duration for CSS to video swap (ms) */\n transitionDuration?: number;\n /** Enable debug logging */\n debug?: boolean;\n /** Auto-play when video is ready */\n autoPlay?: boolean;\n /** Loop video playback */\n loop?: boolean;\n /** Mute video (required for autoplay) */\n muted?: boolean;\n}\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<VideoRendererConfig> = {\n transitionDuration: 500,\n debug: false,\n autoPlay: true,\n loop: true,\n muted: true,\n};\n\n/**\n * Active video entry\n */\ninterface ActiveVideo {\n imageElement: HTMLImageElement;\n videoElement: HTMLVideoElement;\n wrapperElement: HTMLDivElement;\n state: VideoPlaybackState;\n onFallback?: () => void;\n}\n\n/**\n * VideoRenderer class\n *\n * Handles seamless video overlay and playback with CSS transition effects.\n */\nexport class VideoRenderer {\n private config: Required<VideoRendererConfig>;\n private activeVideos: Map<string, ActiveVideo> = new Map();\n\n constructor(config: VideoRendererConfig = {}) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n }\n\n /**\n * Prepare a video overlay for an image\n *\n * @param imageId - Unique ID for tracking\n * @param imageElement - The image element to overlay\n * @param videoUrl - URL of the video to play\n * @param onFallback - Callback if video fails to load\n * @returns Promise that resolves when video is ready to play\n */\n async prepareVideo(\n imageId: string,\n imageElement: HTMLImageElement,\n videoUrl: string,\n onFallback?: () => void\n ): Promise<boolean> {\n // Check if already prepared\n if (this.activeVideos.has(imageId)) {\n this.log(`⚠️ Video already prepared: ${imageId}`);\n return true;\n }\n\n try {\n // Create wrapper to contain image and video\n const wrapper = this.createWrapper(imageElement);\n\n // Create video element\n const video = this.createVideoElement(videoUrl);\n\n // Add video to wrapper (behind image initially)\n wrapper.insertBefore(video, imageElement);\n\n // Track the video\n const entry: ActiveVideo = {\n imageElement,\n videoElement: video,\n wrapperElement: wrapper,\n state: 'loading',\n onFallback,\n };\n this.activeVideos.set(imageId, entry);\n\n // Wait for video to be ready\n await this.waitForVideoReady(video, entry);\n\n this.log(`✅ Video prepared: ${imageId}`);\n return true;\n\n } catch (error) {\n this.log(`❌ Prepare failed: ${imageId} - ${error}`);\n this.cleanup(imageId);\n onFallback?.();\n return false;\n }\n }\n\n /**\n * Play video and transition from CSS animation\n */\n play(imageId: string): boolean {\n const entry = this.activeVideos.get(imageId);\n if (!entry) {\n this.log(`⚠️ No video found: ${imageId}`);\n return false;\n }\n\n if (entry.state === 'error') {\n this.log(`⚠️ Video in error state: ${imageId}`);\n return false;\n }\n\n try {\n // Fade out image, fade in video\n this.transitionToVideo(entry);\n\n // Start video playback\n entry.videoElement.play().catch(err => {\n this.log(`❌ Play failed: ${err}`);\n entry.state = 'error';\n entry.onFallback?.();\n });\n\n entry.state = 'playing';\n this.log(`▶️ Playing: ${imageId}`);\n return true;\n\n } catch (error) {\n this.log(`❌ Play error: ${error}`);\n entry.state = 'error';\n entry.onFallback?.();\n return false;\n }\n }\n\n /**\n * Pause video playback\n */\n pause(imageId: string): boolean {\n const entry = this.activeVideos.get(imageId);\n if (!entry || entry.state !== 'playing') {\n return false;\n }\n\n entry.videoElement.pause();\n entry.state = 'paused';\n this.log(`⏸️ Paused: ${imageId}`);\n return true;\n }\n\n /**\n * Resume video playback\n */\n resume(imageId: string): boolean {\n const entry = this.activeVideos.get(imageId);\n if (!entry || entry.state !== 'paused') {\n return false;\n }\n\n entry.videoElement.play().catch(() => {\n entry.state = 'error';\n });\n entry.state = 'playing';\n this.log(`▶️ Resumed: ${imageId}`);\n return true;\n }\n\n /**\n * Stop video and remove overlay\n */\n stop(imageId: string): void {\n const entry = this.activeVideos.get(imageId);\n if (!entry) return;\n\n // Transition back to image\n this.transitionToImage(entry);\n\n // Cleanup after transition\n setTimeout(() => {\n this.cleanup(imageId);\n }, this.config.transitionDuration);\n }\n\n /**\n * Check if video is playing for an image\n */\n isPlaying(imageId: string): boolean {\n const entry = this.activeVideos.get(imageId);\n return entry?.state === 'playing';\n }\n\n /**\n * Check if video is prepared for an image\n */\n isPrepared(imageId: string): boolean {\n return this.activeVideos.has(imageId);\n }\n\n /**\n * Get current playback state\n */\n getState(imageId: string): VideoPlaybackState | null {\n return this.activeVideos.get(imageId)?.state || null;\n }\n\n /**\n * Destroy all videos and cleanup\n */\n destroy(): void {\n for (const imageId of this.activeVideos.keys()) {\n this.cleanup(imageId);\n }\n this.log(`🧹 Destroyed all videos`);\n }\n\n /**\n * Create wrapper element around image\n */\n private createWrapper(imageElement: HTMLImageElement): HTMLDivElement {\n // Check if already wrapped\n const existingWrapper = imageElement.parentElement;\n if (existingWrapper?.classList.contains('beautifi-video-wrapper')) {\n return existingWrapper as HTMLDivElement;\n }\n\n const wrapper = document.createElement('div');\n wrapper.className = 'beautifi-video-wrapper';\n wrapper.style.cssText = `\n position: relative;\n display: inline-block;\n width: ${imageElement.offsetWidth}px;\n height: ${imageElement.offsetHeight}px;\n overflow: hidden;\n `;\n\n // Wrap the image\n imageElement.parentNode?.insertBefore(wrapper, imageElement);\n wrapper.appendChild(imageElement);\n\n // Style the image for overlay\n imageElement.style.cssText += `\n position: relative;\n z-index: 2;\n transition: opacity ${this.config.transitionDuration}ms ease-in-out;\n `;\n\n return wrapper;\n }\n\n /**\n * Create video element with proper attributes\n */\n private createVideoElement(videoUrl: string): HTMLVideoElement {\n const video = document.createElement('video');\n video.src = videoUrl;\n video.muted = this.config.muted;\n video.loop = this.config.loop;\n video.playsInline = true;\n video.preload = 'auto';\n video.crossOrigin = 'anonymous';\n\n video.style.cssText = `\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: 1;\n opacity: 0;\n transition: opacity ${this.config.transitionDuration}ms ease-in-out;\n `;\n\n return video;\n }\n\n /**\n * Wait for video to be ready to play\n */\n private waitForVideoReady(video: HTMLVideoElement, entry: ActiveVideo): Promise<void> {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error('Video load timeout'));\n }, 30000); // 30s timeout\n\n video.addEventListener('canplaythrough', () => {\n clearTimeout(timeout);\n entry.state = 'buffering';\n resolve();\n }, { once: true });\n\n video.addEventListener('error', () => {\n clearTimeout(timeout);\n entry.state = 'error';\n reject(new Error('Video load error'));\n }, { once: true });\n\n // Trigger load\n video.load();\n });\n }\n\n /**\n * Transition from image to video\n */\n private transitionToVideo(entry: ActiveVideo): void {\n // Show video\n entry.videoElement.style.opacity = '1';\n\n // Hide image (but keep it for fallback)\n entry.imageElement.style.opacity = '0';\n\n // Remove any CSS animations from image\n entry.imageElement.style.animation = 'none';\n }\n\n /**\n * Transition from video back to image\n */\n private transitionToImage(entry: ActiveVideo): void {\n // Show image\n entry.imageElement.style.opacity = '1';\n\n // Hide video\n entry.videoElement.style.opacity = '0';\n\n // Pause video\n entry.videoElement.pause();\n }\n\n /**\n * Cleanup video element and restore image\n */\n private cleanup(imageId: string): void {\n const entry = this.activeVideos.get(imageId);\n if (!entry) return;\n\n // Restore image\n entry.imageElement.style.opacity = '1';\n entry.imageElement.style.animation = '';\n entry.imageElement.style.position = '';\n entry.imageElement.style.zIndex = '';\n entry.imageElement.style.transition = '';\n\n // Remove video\n entry.videoElement.remove();\n\n // Unwrap if we created the wrapper\n if (entry.wrapperElement.classList.contains('beautifi-video-wrapper')) {\n const parent = entry.wrapperElement.parentNode;\n if (parent) {\n parent.insertBefore(entry.imageElement, entry.wrapperElement);\n entry.wrapperElement.remove();\n }\n }\n\n this.activeVideos.delete(imageId);\n this.log(`🧹 Cleaned up: ${imageId}`);\n }\n\n /**\n * Debug logging\n */\n private log(message: string): void {\n if (this.config.debug) {\n console.log(`[VideoRenderer] ${message}`);\n }\n }\n}\n","/**\n * beautifi - Main Entry Point\n *\n * This is the public API for the beautifi plugin.\n * It coordinates the ImageDetector, ViewportObserver, and animation system.\n */\n\nimport {\n LivePhotoOptions,\n TrackedImage,\n DEFAULT_OPTIONS,\n LivePhotoError,\n AnimationMode,\n} from './types';\nimport { ImageDetector } from './ImageDetector';\nimport { ViewportObserver } from './ViewportObserver';\nimport { GeminiApiClient } from './GeminiApiClient';\nimport { AnimationRenderer } from './AnimationRenderer';\nimport { VeoClient } from './VeoClient';\nimport { VideoRenderer } from './VideoRenderer';\n\n/**\n * beautifiPlugin class\n *\n * Core plugin that orchestrates image detection, viewport observation,\n * and animation coordination.\n */\nclass beautifiPlugin {\n private initialized: boolean = false;\n private options: Required<LivePhotoOptions> | null = null;\n private detector: ImageDetector | null = null;\n private viewportObserver: ViewportObserver | null = null;\n private apiClient: GeminiApiClient | null = null;\n private renderer: AnimationRenderer | null = null;\n private veoClient: VeoClient | null = null;\n private videoRenderer: VideoRenderer | null = null;\n private reducedMotion: boolean = false;\n private pendingVideoGenerations: Map<string, string> = new Map(); // imageId -> operationId\n\n /**\n * Initialize the plugin with configuration options\n *\n * @param options - Plugin configuration\n * @throws Error if apiKey is not provided\n */\n init(options: LivePhotoOptions): void {\n if (!options.apiKey) {\n throw new Error('beautifi: apiKey is required');\n }\n\n // Check for reduced motion preference\n if (typeof window !== 'undefined' && window.matchMedia) {\n this.reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n }\n\n // Merge with defaults\n this.options = {\n ...DEFAULT_OPTIONS,\n ...options,\n } as Required<LivePhotoOptions>;\n\n // If reduced motion is preferred and we respect it, don't animate\n if (this.reducedMotion && this.options.respectReducedMotion) {\n if (this.options.debug) {\n console.log('[beautifi] Reduced motion preference detected, animations disabled');\n }\n this.initialized = true;\n return;\n }\n\n // Create image detector\n this.detector = new ImageDetector(this.options.selector, this.options.debug);\n\n // Create API client\n this.apiClient = new GeminiApiClient({\n endpoint: this.options.endpoint || 'https://us-central1-seedgpt-planter.cloudfunctions.net/beautifi-animate/animate',\n timeout: this.options.timeout,\n maxRetries: this.options.maxRetries,\n enableCache: true,\n debug: this.options.debug,\n });\n\n // Create animation renderer\n this.renderer = new AnimationRenderer({\n debug: this.options.debug,\n });\n\n // Create Veo client and video renderer for automatic video mode\n if (this.options.mode !== 'css') {\n this.veoClient = new VeoClient({\n endpoint: this.options.videoEndpoint || this.options.endpoint,\n debug: this.options.debug,\n });\n\n this.videoRenderer = new VideoRenderer({\n debug: this.options.debug,\n transitionDuration: 500,\n });\n\n if (this.options.debug) {\n console.log('[beautifi] Video mode enabled:', this.options.mode);\n }\n }\n\n // Create viewport observer\n this.viewportObserver = new ViewportObserver(\n (image, isIntersecting) => {\n this.handleVisibilityChange(image, isIntersecting);\n },\n {\n threshold: this.options.threshold,\n rootMargin: this.options.rootMargin,\n debug: this.options.debug,\n }\n );\n\n // Set up image detection callback\n this.detector.setOnImageDetected((image) => {\n this.viewportObserver!.observe(image);\n });\n\n // Initialize viewport observer\n this.viewportObserver.init();\n\n // Scan for existing images\n const images = this.detector.scan();\n images.forEach((image) => {\n this.viewportObserver!.observe(image);\n });\n\n // Start observing DOM mutations\n this.detector.observe();\n\n this.initialized = true;\n\n if (this.options.debug) {\n console.log('[beautifi] Initialized with options:', this.options);\n console.log('[beautifi] Detected', images.length, 'images');\n }\n }\n\n /**\n * Handle image entering/leaving viewport\n */\n private handleVisibilityChange(image: TrackedImage, isIntersecting: boolean): void {\n if (!this.options) return;\n\n if (isIntersecting) {\n // Image entered viewport - queue for animation\n if (image.state === 'idle') {\n this.queueAnimation(image);\n } else if (image.state === 'paused') {\n this.resumeAnimation(image);\n }\n } else {\n // Image left viewport - pause animation\n if (image.state === 'playing') {\n this.pauseAnimation(image);\n }\n }\n }\n\n /**\n * Queue an image for animation\n */\n private async queueAnimation(image: TrackedImage): Promise<void> {\n // Update state to loading\n if (this.detector) {\n this.detector.updateImageState(image.id, { state: 'loading' });\n }\n\n if (this.options?.debug) {\n console.log('[beautifi] Queuing animation for:', image.element.src);\n }\n\n // Apply delay if configured\n if (image.config.delay && image.config.delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, image.config.delay));\n }\n\n // Check for per-image mode override via data attribute\n const imageMode = (image.element.dataset.beautifiMode as AnimationMode) || this.options?.mode || 'auto';\n\n try {\n // Fetch CSS animation data from API\n const animationData = await this.apiClient!.fetch(image.element.src, {\n type: image.config.type,\n intensity: image.config.intensity,\n loop: image.config.loop,\n });\n\n if (!animationData.success) {\n throw new LivePhotoError(\n animationData.error || 'Animation generation failed',\n 'API_ERROR'\n );\n }\n\n // Store animation data on tracked image\n if (this.detector) {\n this.detector.updateImageState(image.id, {\n state: 'playing',\n animationData,\n });\n }\n\n // Start CSS animation playback immediately (for 'auto' and 'css' modes)\n if (imageMode !== 'video') {\n this.renderer!.play(image, animationData, {\n intensity: image.config.intensity,\n loop: image.config.loop,\n onComplete: () => {\n if (this.detector) {\n this.detector.updateImageState(image.id, { state: 'idle' });\n }\n this.emitEvent('animationComplete', image);\n },\n });\n\n this.emitEvent('animationStart', image);\n }\n\n // Start video generation in background (for 'auto' and 'video' modes)\n if (imageMode !== 'css' && this.veoClient && this.videoRenderer) {\n this.startVideoGeneration(image);\n }\n\n } catch (error) {\n // Handle error - graceful degradation to static image\n const livePhotoError = error instanceof LivePhotoError\n ? error\n : new LivePhotoError(\n error instanceof Error ? error.message : 'Unknown error',\n 'UNKNOWN_ERROR',\n error instanceof Error ? error : undefined\n );\n\n if (this.detector) {\n this.detector.updateImageState(image.id, {\n state: 'error',\n error: livePhotoError,\n });\n }\n\n if (this.options?.debug) {\n console.error('[beautifi] Animation error:', livePhotoError);\n }\n\n this.emitEvent('animationError', image, livePhotoError);\n }\n }\n\n /**\n * Start video generation in background\n */\n private async startVideoGeneration(image: TrackedImage): Promise<void> {\n if (!this.veoClient || !this.videoRenderer) return;\n\n // Check if already generating or cached\n const cachedVideo = this.veoClient.getCachedVideo(image.element.src);\n if (cachedVideo) {\n if (this.options?.debug) {\n console.log('[beautifi] Using cached video for:', image.id);\n }\n this.transitionToVideo(image, cachedVideo);\n return;\n }\n\n // Emit video start event\n this.emitVideoEvent('videoStart', image);\n\n if (this.options?.debug) {\n console.log('[beautifi] Starting video generation for:', image.id);\n }\n\n try {\n // Start generation\n const result = await this.veoClient.startGeneration({\n imageUrl: image.element.src,\n });\n\n if (!result.success || !result.operationId) {\n throw new Error(result.error?.message || 'Failed to start video generation');\n }\n\n // If already completed (cached on server side)\n if (result.status === 'completed' && result.videoUrl) {\n this.transitionToVideo(image, result.videoUrl);\n return;\n }\n\n // Track pending operation\n this.pendingVideoGenerations.set(image.id, result.operationId);\n\n // Poll for completion\n const videoUrl = await this.veoClient.pollUntilComplete(\n result.operationId,\n image.element.src,\n (status, attempt) => {\n if (this.options?.debug) {\n console.log(`[beautifi] Video poll ${image.id}: ${status} (attempt ${attempt})`);\n }\n }\n );\n\n // Clear pending\n this.pendingVideoGenerations.delete(image.id);\n\n if (videoUrl) {\n this.transitionToVideo(image, videoUrl);\n } else {\n if (this.options?.debug) {\n console.log('[beautifi] Video generation failed, keeping CSS animation:', image.id);\n }\n this.emitVideoEvent('videoError', image);\n }\n\n } catch (error) {\n this.pendingVideoGenerations.delete(image.id);\n if (this.options?.debug) {\n console.error('[beautifi] Video generation error:', error);\n }\n this.emitVideoEvent('videoError', image);\n // CSS animation continues as fallback\n }\n }\n\n /**\n * Transition from CSS animation to video\n */\n private async transitionToVideo(image: TrackedImage, videoUrl: string): Promise<void> {\n if (!this.videoRenderer) return;\n\n if (this.options?.debug) {\n console.log('[beautifi] Transitioning to video:', image.id);\n }\n\n // Prepare video (load it behind the image)\n const prepared = await this.videoRenderer.prepareVideo(\n image.id,\n image.element,\n videoUrl,\n () => {\n // Fallback: keep CSS animation running\n if (this.options?.debug) {\n console.log('[beautifi] Video fallback to CSS:', image.id);\n }\n }\n );\n\n if (prepared) {\n // Stop CSS animation and start video\n this.renderer?.stop(image.id);\n this.videoRenderer.play(image.id);\n this.emitVideoEvent('videoReady', image, videoUrl);\n }\n }\n\n /**\n * Pause animation for an image\n */\n private pauseAnimation(image: TrackedImage): void {\n if (this.renderer) {\n this.renderer.pause(image.id);\n }\n\n if (this.detector) {\n this.detector.updateImageState(image.id, { state: 'paused' });\n }\n\n if (this.options?.debug) {\n console.log('[beautifi] Pausing animation for:', image.element.src);\n }\n\n this.emitEvent('animationPause', image);\n }\n\n /**\n * Resume animation for an image\n */\n private resumeAnimation(image: TrackedImage): void {\n if (this.renderer) {\n this.renderer.resume(image.id);\n }\n\n if (this.detector) {\n this.detector.updateImageState(image.id, { state: 'playing' });\n }\n\n if (this.options?.debug) {\n console.log('[beautifi] Resuming animation for:', image.element.src);\n }\n\n this.emitEvent('animationResume', image);\n }\n\n /**\n * Emit lifecycle events\n */\n private emitEvent(\n type: 'animationStart' | 'animationComplete' | 'animationPause' | 'animationResume' | 'animationError',\n image: TrackedImage,\n error?: LivePhotoError\n ): void {\n if (typeof CustomEvent !== 'undefined') {\n const detail = {\n type,\n timestamp: Date.now(),\n element: image.element,\n imageId: image.id,\n ...(error && { error }),\n };\n const event = new CustomEvent(`beautifi:${type}`, { detail });\n document.dispatchEvent(event);\n }\n }\n\n /**\n * Emit video-related events\n */\n private emitVideoEvent(\n type: 'videoStart' | 'videoReady' | 'videoError',\n image: TrackedImage,\n videoUrl?: string\n ): void {\n if (typeof CustomEvent !== 'undefined') {\n const detail = {\n type,\n timestamp: Date.now(),\n element: image.element,\n imageId: image.id,\n ...(videoUrl && { videoUrl }),\n };\n const event = new CustomEvent(`beautifi:${type}`, { detail });\n document.dispatchEvent(event);\n }\n }\n /**\n * Check if the plugin has been initialized\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * Get current options (for debugging)\n */\n getOptions(): Required<LivePhotoOptions> | null {\n return this.options;\n }\n\n /**\n * Get all tracked images\n */\n getTrackedImages(): TrackedImage[] {\n return this.detector?.getTrackedImages() ?? [];\n }\n\n /**\n * Destroy the plugin and clean up resources\n */\n destroy(): void {\n // Cancel any pending video operations\n if (this.veoClient) {\n this.veoClient.cancelAllOperations();\n this.veoClient = null;\n }\n\n // Destroy video renderer\n if (this.videoRenderer) {\n this.videoRenderer.destroy();\n this.videoRenderer = null;\n }\n\n if (this.renderer) {\n this.renderer.destroy();\n this.renderer = null;\n }\n\n if (this.detector) {\n this.detector.destroy();\n this.detector = null;\n }\n\n if (this.viewportObserver) {\n this.viewportObserver.destroy();\n this.viewportObserver = null;\n }\n\n this.apiClient = null;\n this.pendingVideoGenerations.clear();\n this.initialized = false;\n this.options = null;\n }\n}\n\n// Create singleton instance\nconst plugin = new beautifiPlugin();\n\n// Public API - Primary brand: beautifi\nexport const beautifi = {\n /**\n * Initialize the plugin\n */\n init: (options: LivePhotoOptions) => plugin.init(options),\n\n /**\n * Check if initialized\n */\n isInitialized: () => plugin.isInitialized(),\n\n /**\n * Get current options\n */\n getOptions: () => plugin.getOptions(),\n\n /**\n * Get tracked images\n */\n getTrackedImages: () => plugin.getTrackedImages(),\n\n /**\n * Destroy and clean up\n */\n destroy: () => plugin.destroy(),\n};\n\n// Backward compatibility alias\nexport const LiveMyPhotos = beautifi;\n\n// Auto-init from script tag\nif (typeof document !== 'undefined') {\n document.addEventListener('DOMContentLoaded', () => {\n // Support both beautifi and legacy live-my-photos script names\n const scriptTag = document.querySelector<HTMLScriptElement>(\n 'script[data-api-key][src*=\"beautifi\"], script[data-api-key][src*=\"live-my-photos\"]'\n );\n\n if (scriptTag) {\n const apiKey = scriptTag.dataset.apiKey;\n const autoInit = scriptTag.dataset.autoInit !== 'false';\n\n if (apiKey && autoInit) {\n beautifi.init({\n apiKey,\n selector: scriptTag.dataset.selector,\n intensity: scriptTag.dataset.intensity as any,\n type: scriptTag.dataset.type as any,\n loop: scriptTag.dataset.loop !== 'false',\n debug: scriptTag.dataset.debug === 'true',\n mode: (scriptTag.dataset.mode as any) || 'auto',\n });\n }\n }\n });\n}\n\n// UMD global export - Primary brand: beautifi\nif (typeof window !== 'undefined') {\n (window as any).beautifi = beautifi;\n // Backward compatibility\n (window as any).LiveMyPhotos = beautifi;\n}\n\n// Export types and utilities\nexport type {\n LivePhotoOptions,\n TrackedImage,\n} from './types';\n\nexport {\n LivePhotoError,\n DEFAULT_OPTIONS,\n DEFAULT_ANIMATION_CONFIG,\n} from './types';\n\nexport { ImageDetector } from './ImageDetector';\nexport { ViewportObserver } from './ViewportObserver';\nexport { GeminiApiClient } from './GeminiApiClient';\nexport { AnimationRenderer } from './AnimationRenderer';\nexport { VeoClient } from './VeoClient';\nexport { VideoRenderer } from './VideoRenderer';\n\nexport default beautifi;\n"],"names":["LivePhotoError","Error","constructor","message","code","originalError","super","this","name","captureStackTrace","DEFAULT_OPTIONS","endpoint","selector","intensity","type","loop","respectReducedMotion","debug","threshold","rootMargin","timeout","maxRetries","mode","videoEndpoint","DEFAULT_ANIMATION_CONFIG","enabled","delay","ImageDetector","trackedImages","Map","processedElements","WeakSet","mutationObserver","onImageDetected","setOnImageDetected","callback","scan","elements","document","querySelectorAll","newImages","forEach","element","has","config","dataset","live","intensityAttr","liveIntensity","typeAttr","liveType","liveLoop","parseInt","liveDelay","parseDataAttributes","add","trackedImage","id","Date","now","Math","random","toString","substr","state","set","push","observe","MutationObserver","mutations","shouldScan","mutation","node","addedNodes","HTMLImageElement","Element","querySelector","image","body","childList","subtree","getTrackedImages","Array","from","values","getTrackedImage","get","updateImageState","updates","Object","assign","destroy","disconnect","clear","ViewportObserver","options","observer","visibilityHandler","observedElements","isDocumentVisible","hidden","init","IntersectionObserver","entries","entry","target","isIntersecting","wasVisible","addEventListener","unobserve","delete","isVisible","removeEventListener","DEFAULT_CONFIG","enableCache","CACHE_KEY_PREFIX","GeminiApiClient","fetch","imageUrl","imageHash","computeHash","cached","getFromCache","cacheStatus","request","response","fetchWithRetry","success","saveToCache","attempt","fetchWithTimeout","error","pow","sleep","controller","AbortController","timeoutId","setTimeout","abort","method","headers","JSON","stringify","signal","clearTimeout","ok","status","statusText","json","input","crypto","subtle","data","TextEncoder","encode","hashBuffer","digest","Uint8Array","map","b","padStart","join","hash","i","length","charCodeAt","abs","localStorage","cacheKey","getItem","parsed","parse","timestamp","removeItem","setItem","clearCache","keysToRemove","key","startsWith","ms","Promise","resolve","INTENSITY_MULTIPLIERS","medium","strong","ANIMATION_PRESETS","breathing","translateX","translateY","scale","rotate","duration","parallax","sway","AnimationRenderer","animations","frameRate","play","animationData","stop","style","willChange","transformOrigin","currentFrame","animationFrameId","startTime","performance","isPaused","onComplete","renderLoop","pause","imageId","cancelAnimationFrame","resume","getElapsedTime","transform","isPlaying","getActiveAnimations","keys","elapsed","progress","renderFrame","requestAnimationFrame","multiplier","frames","frameIndex","floor","min","animType","detectedType","generateTransformFromType","preset","oscillation","sin","PI","apiKey","pollInterval","maxPollAttempts","cacheTTL","VIDEO_CACHE_PREFIX","VeoClient","pendingOperations","startGeneration","computeCacheKey","log","slice","videoUrl","replace","animationPrompt","aspectRatio","durationSeconds","_a","operationId","pollUntilComplete","onProgress","aborted","result","checkStatus","backoffMs","cancelOperation","cancelAllOperations","getCachedVideo","raw","transitionDuration","autoPlay","muted","VideoRenderer","activeVideos","prepareVideo","imageElement","onFallback","wrapper","createWrapper","video","createVideoElement","insertBefore","videoElement","wrapperElement","waitForVideoReady","cleanup","transitionToVideo","catch","err","call","transitionToImage","isPrepared","getState","existingWrapper","parentElement","classList","contains","createElement","className","cssText","offsetWidth","offsetHeight","parentNode","appendChild","src","playsInline","preload","crossOrigin","reject","once","load","opacity","animation","position","zIndex","transition","remove","parent","plugin","initialized","detector","viewportObserver","apiClient","renderer","veoClient","videoRenderer","reducedMotion","pendingVideoGenerations","window","matchMedia","matches","handleVisibilityChange","queueAnimation","resumeAnimation","pauseAnimation","imageMode","beautifiMode","_b","emitEvent","startVideoGeneration","livePhotoError","_c","cachedVideo","emitVideoEvent","_d","_e","CustomEvent","detail","event","dispatchEvent","isInitialized","getOptions","beautifi","LiveMyPhotos","scriptTag","autoInit"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"beautifi.cjs.js","sources":["../src/types.ts","../src/ImageDetector.ts","../src/ViewportObserver.ts","../src/GeminiApiClient.ts","../src/AnimationRenderer.ts","../src/VeoClient.ts","../src/VideoRenderer.ts","../src/index.ts"],"sourcesContent":["/**\n * beautifi - Core Type Definitions\n *\n * This file contains all TypeScript interfaces and types used throughout\n * the beautifi plugin.\n */\n\n/**\n * Animation intensity levels\n */\nexport type AnimationIntensity = 'subtle' | 'medium' | 'strong';\n\n/**\n * Animation type styles\n */\nexport type AnimationType = 'breathing' | 'parallax' | 'sway' | 'auto';\n\n/**\n * Animation states\n */\nexport type AnimationState = 'idle' | 'loading' | 'playing' | 'paused' | 'error';\n\n/**\n * Error codes for different failure scenarios\n */\nexport type LivePhotoErrorCode =\n | 'API_ERROR'\n | 'NETWORK_ERROR'\n | 'TIMEOUT_ERROR'\n | 'VALIDATION_ERROR'\n | 'RATE_LIMIT_ERROR'\n | 'RENDER_ERROR'\n | 'UNKNOWN_ERROR';\n\n/**\n * Event types emitted by the plugin\n */\nexport type LivePhotoEventType =\n | 'init'\n | 'imageDetected'\n | 'animationStart'\n | 'animationComplete'\n | 'animationPause'\n | 'animationResume'\n | 'animationError'\n | 'videoStart'\n | 'videoReady'\n | 'videoError'\n | 'destroy';\n\n/**\n * Animation mode - determines how images are animated\n * - 'auto': CSS animation immediately + AI video in background (default)\n * - 'css': CSS animation only, no video generation\n * - 'video': Wait for video, no CSS animation\n */\nexport type AnimationMode = 'auto' | 'css' | 'video';\n\n/**\n * Video generation state\n */\nexport type VideoState = 'idle' | 'generating' | 'ready' | 'error';\n\n/**\n * Global plugin configuration options\n */\nexport interface LivePhotoOptions {\n /** API key for Gemini backend (required) */\n apiKey: string;\n /** Cloud Functions endpoint URL (optional, uses default if not provided) */\n endpoint?: string;\n /** CSS selector for target images (default: 'img') */\n selector?: string;\n /** Default animation intensity (default: 'subtle') */\n intensity?: AnimationIntensity;\n /** Default animation type (default: 'auto') */\n type?: AnimationType;\n /** Whether animations should loop (default: true) */\n loop?: boolean;\n /** Respect prefers-reduced-motion (default: true) */\n respectReducedMotion?: boolean;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n /** Viewport intersection threshold for lazy loading (default: 0.1) */\n threshold?: number;\n /** Root margin for eager loading (default: '50px') */\n rootMargin?: string;\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n /** Maximum retry attempts (default: 3) */\n maxRetries?: number;\n /** Animation mode: 'auto' | 'css' | 'video' (default: 'auto') */\n mode?: AnimationMode;\n /** Video endpoint URL (if different from main endpoint) */\n videoEndpoint?: string;\n}\n\n/**\n * Per-image animation configuration (from data attributes)\n */\nexport interface AnimationConfig {\n /** Whether animation is enabled for this image */\n enabled: boolean;\n /** Animation intensity level */\n intensity: AnimationIntensity;\n /** Animation type/style */\n type: AnimationType;\n /** Whether animation loops continuously */\n loop: boolean;\n /** Delay before animation starts (ms) */\n delay: number;\n}\n\n/**\n * Animation frame data from Gemini API\n */\nexport interface AnimationFrameData {\n /** Frame timestamp */\n timestamp: number;\n /** Transform data for this frame */\n transform: {\n translateX: number;\n translateY: number;\n scale: number;\n rotate: number;\n };\n /** Optional opacity value */\n opacity?: number;\n}\n\n/**\n * Gemini API response for animation generation\n */\nexport interface GeminiAnimationResponse {\n /** Whether the request was successful */\n success: boolean;\n /** Unique animation ID */\n animationId: string;\n /** Animation duration in milliseconds */\n duration: number;\n /** Frame rate (fps) */\n frameRate: number;\n /** Animation keyframes */\n frames: AnimationFrameData[];\n /** Detected animation type */\n detectedType: AnimationType;\n /** Cache status (hit/miss) */\n cacheStatus?: 'hit' | 'miss';\n /** Error message if success is false */\n error?: string;\n}\n\n/**\n * API request payload\n */\nexport interface GeminiAnimationRequest {\n /** Image URL or hash */\n imageUrl: string;\n /** Image hash for caching */\n imageHash?: string;\n /** Requested animation type */\n type: AnimationType;\n /** Requested intensity */\n intensity: AnimationIntensity;\n /** Whether to loop */\n loop: boolean;\n}\n\n/**\n * Tracked image element with metadata\n */\nexport interface TrackedImage {\n /** Original image element */\n element: HTMLImageElement;\n /** Unique ID for this tracked image */\n id: string;\n /** Current animation state */\n state: AnimationState;\n /** Animation configuration for this image */\n config: AnimationConfig;\n /** API response data (when loaded) */\n animationData?: GeminiAnimationResponse;\n /** Error if animation failed */\n error?: LivePhotoError;\n}\n\n/**\n * Base event payload\n */\nexport interface LivePhotoEventBase {\n /** Event type */\n type: LivePhotoEventType;\n /** Event timestamp */\n timestamp: number;\n}\n\n/**\n * Image-related event payload\n */\nexport interface LivePhotoImageEvent extends LivePhotoEventBase {\n /** Image element involved */\n element: HTMLImageElement;\n /** Image tracking ID */\n imageId: string;\n}\n\n/**\n * Error event payload\n */\nexport interface LivePhotoErrorEvent extends LivePhotoImageEvent {\n type: 'animationError';\n /** Error details */\n error: LivePhotoError;\n}\n\n/**\n * Event handler function type\n */\nexport type LivePhotoEventHandler<T extends LivePhotoEventBase = LivePhotoEventBase> = (\n event: T\n) => void;\n\n/**\n * Viewport observer callback\n */\nexport type ViewportCallback = (image: TrackedImage, isIntersecting: boolean) => void;\n\n/**\n * Custom error class for beautifi\n */\nexport class LivePhotoError extends Error {\n constructor(\n message: string,\n public readonly code: LivePhotoErrorCode,\n public readonly originalError?: Error\n ) {\n super(message);\n this.name = 'LivePhotoError';\n // Ensure prototype chain is set correctly\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, LivePhotoError);\n }\n }\n}\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_OPTIONS: Required<Omit<LivePhotoOptions, 'apiKey'>> = {\n endpoint: 'https://us-central1-seedgpt-planter.cloudfunctions.net/beautifi-api/animate',\n selector: 'img',\n intensity: 'subtle',\n type: 'auto',\n loop: true,\n respectReducedMotion: true,\n debug: false,\n threshold: 0.1,\n rootMargin: '50px',\n timeout: 10000,\n maxRetries: 3,\n mode: 'auto',\n videoEndpoint: 'https://us-central1-seedgpt-planter.cloudfunctions.net/beautifi-api',\n};\n\n/**\n * Default animation configuration\n */\nexport const DEFAULT_ANIMATION_CONFIG: AnimationConfig = {\n enabled: true,\n intensity: 'subtle',\n type: 'auto',\n loop: true,\n delay: 0,\n};\n","/**\n * Image Detector Module\n *\n * Responsible for finding and tracking images in the DOM that should be animated.\n * Handles CSS selector matching, data attribute parsing, and MutationObserver\n * for dynamically added images.\n */\n\nimport {\n AnimationConfig,\n AnimationIntensity,\n AnimationType,\n TrackedImage,\n DEFAULT_ANIMATION_CONFIG,\n} from './types';\n\n/**\n * Generate a unique ID for tracking images\n */\nfunction generateId(): string {\n return `lmp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Parse data-live-* attributes from an image element\n */\nfunction parseDataAttributes(element: HTMLImageElement): AnimationConfig {\n const enabled = element.dataset.live !== 'false';\n\n const intensityAttr = element.dataset.liveIntensity;\n const intensity: AnimationIntensity =\n intensityAttr === 'subtle' || intensityAttr === 'medium' || intensityAttr === 'strong'\n ? intensityAttr\n : DEFAULT_ANIMATION_CONFIG.intensity;\n\n const typeAttr = element.dataset.liveType;\n const type: AnimationType =\n typeAttr === 'breathing' || typeAttr === 'parallax' || typeAttr === 'sway' || typeAttr === 'auto'\n ? typeAttr\n : DEFAULT_ANIMATION_CONFIG.type;\n\n const loop = element.dataset.liveLoop !== 'false';\n const delay = parseInt(element.dataset.liveDelay || '0', 10) || 0;\n\n return {\n enabled,\n intensity,\n type,\n loop,\n delay,\n };\n}\n\n/**\n * ImageDetector class\n * \n * Detects images in the DOM and parses their animation configuration.\n */\nexport class ImageDetector {\n private selector: string;\n private trackedImages: Map<string, TrackedImage> = new Map();\n private processedElements: WeakSet<HTMLImageElement> = new WeakSet();\n private mutationObserver: MutationObserver | null = null;\n private onImageDetected: ((image: TrackedImage) => void) | null = null;\n private debug: boolean;\n\n constructor(selector: string = 'img', debug: boolean = false) {\n this.selector = selector;\n this.debug = debug;\n }\n\n /**\n * Set callback for when new images are detected\n */\n setOnImageDetected(callback: (image: TrackedImage) => void): void {\n this.onImageDetected = callback;\n }\n\n /**\n * Scan the DOM for images matching the selector\n */\n scan(): TrackedImage[] {\n const elements = document.querySelectorAll<HTMLImageElement>(this.selector);\n const newImages: TrackedImage[] = [];\n\n elements.forEach((element) => {\n if (this.processedElements.has(element)) {\n return;\n }\n\n const config = parseDataAttributes(element);\n\n // Skip if explicitly disabled\n if (!config.enabled) {\n this.processedElements.add(element);\n return;\n }\n\n const trackedImage: TrackedImage = {\n element,\n id: generateId(),\n state: 'idle',\n config,\n };\n\n this.trackedImages.set(trackedImage.id, trackedImage);\n this.processedElements.add(element);\n newImages.push(trackedImage);\n\n if (this.debug) {\n console.log('[LiveMyPhotos] Detected image:', element.src, config);\n }\n });\n\n return newImages;\n }\n\n /**\n * Start observing the DOM for dynamically added images\n */\n observe(): void {\n if (this.mutationObserver) {\n return;\n }\n\n this.mutationObserver = new MutationObserver((mutations) => {\n let shouldScan = false;\n\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (const node of mutation.addedNodes) {\n if (\n node instanceof HTMLImageElement ||\n (node instanceof Element && node.querySelector(this.selector))\n ) {\n shouldScan = true;\n break;\n }\n }\n }\n if (shouldScan) break;\n }\n\n if (shouldScan) {\n const newImages = this.scan();\n newImages.forEach((image) => {\n if (this.onImageDetected) {\n this.onImageDetected(image);\n }\n });\n }\n });\n\n this.mutationObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n\n /**\n * Get all tracked images\n */\n getTrackedImages(): TrackedImage[] {\n return Array.from(this.trackedImages.values());\n }\n\n /**\n * Get a tracked image by ID\n */\n getTrackedImage(id: string): TrackedImage | undefined {\n return this.trackedImages.get(id);\n }\n\n /**\n * Update a tracked image's state\n */\n updateImageState(id: string, updates: Partial<TrackedImage>): void {\n const image = this.trackedImages.get(id);\n if (image) {\n Object.assign(image, updates);\n }\n }\n\n /**\n * Clean up and stop observing\n */\n destroy(): void {\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n this.trackedImages.clear();\n this.onImageDetected = null;\n }\n}\n","/**\n * Viewport Observer Module\n *\n * Uses IntersectionObserver to track when images enter/leave the viewport.\n * Also handles visibility changes for pausing animations in hidden tabs.\n */\n\nimport { TrackedImage, ViewportCallback } from './types';\n\n/**\n * ViewportObserver class\n *\n * Efficiently tracks image visibility using IntersectionObserver.\n */\nexport class ViewportObserver {\n private observer: IntersectionObserver | null = null;\n private visibilityHandler: (() => void) | null = null;\n private callback: ViewportCallback;\n private threshold: number;\n private rootMargin: string;\n private observedElements: Map<HTMLImageElement, TrackedImage> = new Map();\n private isDocumentVisible: boolean = true;\n private debug: boolean;\n\n constructor(\n callback: ViewportCallback,\n options: {\n threshold?: number;\n rootMargin?: string;\n debug?: boolean;\n } = {}\n ) {\n this.callback = callback;\n this.threshold = options.threshold ?? 0.1;\n this.rootMargin = options.rootMargin ?? '50px';\n this.debug = options.debug ?? false;\n this.isDocumentVisible = typeof document !== 'undefined' ? !document.hidden : true;\n }\n\n /**\n * Initialize the observer\n */\n init(): void {\n if (this.observer) {\n return;\n }\n\n // Create IntersectionObserver\n this.observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n const image = this.observedElements.get(entry.target as HTMLImageElement);\n if (image && this.isDocumentVisible) {\n if (this.debug) {\n console.log(\n '[LiveMyPhotos] Viewport:',\n entry.isIntersecting ? 'enter' : 'leave',\n image.element.src\n );\n }\n this.callback(image, entry.isIntersecting);\n }\n });\n },\n {\n threshold: this.threshold,\n rootMargin: this.rootMargin,\n }\n );\n\n // Listen for visibility changes\n this.visibilityHandler = () => {\n const wasVisible = this.isDocumentVisible;\n this.isDocumentVisible = !document.hidden;\n\n if (wasVisible !== this.isDocumentVisible) {\n if (this.debug) {\n console.log('[LiveMyPhotos] Document visibility:', this.isDocumentVisible);\n }\n\n // Notify all observed images of visibility change\n this.observedElements.forEach((image) => {\n // When document becomes visible, re-check if images are in viewport\n // When hidden, notify that images are \"leaving\" viewport\n this.callback(image, this.isDocumentVisible);\n });\n }\n };\n\n document.addEventListener('visibilitychange', this.visibilityHandler);\n }\n\n /**\n * Start observing an image\n */\n observe(image: TrackedImage): void {\n if (!this.observer) {\n this.init();\n }\n\n if (!this.observedElements.has(image.element)) {\n this.observedElements.set(image.element, image);\n this.observer!.observe(image.element);\n }\n }\n\n /**\n * Stop observing an image\n */\n unobserve(image: TrackedImage): void {\n if (this.observer && this.observedElements.has(image.element)) {\n this.observer.unobserve(image.element);\n this.observedElements.delete(image.element);\n }\n }\n\n /**\n * Check if document is currently visible\n */\n isVisible(): boolean {\n return this.isDocumentVisible;\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n if (this.observer) {\n this.observer.disconnect();\n this.observer = null;\n }\n\n if (this.visibilityHandler) {\n document.removeEventListener('visibilitychange', this.visibilityHandler);\n this.visibilityHandler = null;\n }\n\n this.observedElements.clear();\n }\n}\n","/**\n * Gemini API Client\n *\n * Handles communication with Cloud Functions backend for animation generation.\n * Features: timeout handling, retry with exponential backoff, response caching.\n */\n\nimport {\n GeminiAnimationRequest,\n GeminiAnimationResponse,\n LivePhotoError,\n AnimationType,\n AnimationIntensity,\n} from './types';\n\n/**\n * Configuration for the API client\n */\nexport interface GeminiApiClientConfig {\n /** Cloud Functions endpoint URL */\n endpoint: string;\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n /** Maximum retry attempts (default: 3) */\n maxRetries?: number;\n /** Enable response caching (default: true) */\n enableCache?: boolean;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG: Required<Omit<GeminiApiClientConfig, 'endpoint'>> = {\n timeout: 10000,\n maxRetries: 3,\n enableCache: true,\n debug: false,\n};\n\n/**\n * Cache key prefix for localStorage\n */\nconst CACHE_KEY_PREFIX = 'lmp_cache_';\n\n/**\n * Cache TTL in milliseconds (30 days)\n */\nconst CACHE_TTL = 30 * 24 * 60 * 60 * 1000;\n\n/**\n * Cached response structure\n */\ninterface CachedResponse {\n data: GeminiAnimationResponse;\n timestamp: number;\n}\n\n/**\n * GeminiApiClient class\n *\n * Handles fetching animation data from the Cloud Functions backend\n * with retry logic, timeout handling, and caching.\n */\nexport class GeminiApiClient {\n private config: Required<GeminiApiClientConfig>;\n\n constructor(config: GeminiApiClientConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n } as Required<GeminiApiClientConfig>;\n }\n\n /**\n * Fetch animation data for an image\n *\n * @param imageUrl - URL of the image to animate\n * @param options - Animation options\n * @returns Promise resolving to animation response\n */\n async fetch(\n imageUrl: string,\n options: {\n type?: AnimationType;\n intensity?: AnimationIntensity;\n loop?: boolean;\n } = {}\n ): Promise<GeminiAnimationResponse> {\n const imageHash = await this.computeHash(imageUrl);\n\n // Check cache first\n if (this.config.enableCache) {\n const cached = this.getFromCache(imageHash);\n if (cached) {\n if (this.config.debug) {\n console.log('[GeminiApiClient] Cache hit for:', imageUrl);\n }\n return { ...cached, cacheStatus: 'hit' };\n }\n }\n\n // Prepare request\n const request: GeminiAnimationRequest = {\n imageUrl,\n imageHash,\n type: options.type ?? 'auto',\n intensity: options.intensity ?? 'subtle',\n loop: options.loop ?? true,\n };\n\n // Fetch with retry logic\n const response = await this.fetchWithRetry(request);\n\n // Cache successful response\n if (response.success && this.config.enableCache) {\n this.saveToCache(imageHash, response);\n }\n\n return { ...response, cacheStatus: 'miss' };\n }\n\n /**\n * Fetch with retry logic and exponential backoff\n */\n private async fetchWithRetry(\n request: GeminiAnimationRequest,\n attempt: number = 0\n ): Promise<GeminiAnimationResponse> {\n try {\n return await this.fetchWithTimeout(request);\n } catch (error) {\n const isRetryable =\n error instanceof LivePhotoError &&\n (error.code === 'NETWORK_ERROR' || error.code === 'TIMEOUT_ERROR');\n\n if (isRetryable && attempt < this.config.maxRetries) {\n // Exponential backoff: 1s, 2s, 4s, ...\n const delay = Math.pow(2, attempt) * 1000;\n if (this.config.debug) {\n console.log(\n `[GeminiApiClient] Retry ${attempt + 1}/${this.config.maxRetries} after ${delay}ms`\n );\n }\n await this.sleep(delay);\n return this.fetchWithRetry(request, attempt + 1);\n }\n\n throw error;\n }\n }\n\n /**\n * Fetch with timeout handling\n */\n private async fetchWithTimeout(\n request: GeminiAnimationRequest\n ): Promise<GeminiAnimationResponse> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(this.config.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n if (response.status === 429) {\n throw new LivePhotoError(\n 'Rate limit exceeded',\n 'RATE_LIMIT_ERROR'\n );\n }\n throw new LivePhotoError(\n `API error: ${response.status} ${response.statusText}`,\n 'API_ERROR'\n );\n }\n\n const data = (await response.json()) as GeminiAnimationResponse;\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof LivePhotoError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new LivePhotoError(\n `Request timed out after ${this.config.timeout}ms`,\n 'TIMEOUT_ERROR',\n error\n );\n }\n throw new LivePhotoError(\n `Network error: ${error.message}`,\n 'NETWORK_ERROR',\n error\n );\n }\n\n throw new LivePhotoError('Unknown error occurred', 'UNKNOWN_ERROR');\n }\n }\n\n /**\n * Compute SHA-256 hash of a string (for cache keys)\n */\n async computeHash(input: string): Promise<string> {\n // Use Web Crypto API if available\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const encoder = new TextEncoder();\n const data = encoder.encode(input);\n const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');\n }\n\n // Fallback: simple hash for environments without crypto.subtle\n let hash = 0;\n for (let i = 0; i < input.length; i++) {\n const char = input.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16);\n }\n\n /**\n * Get cached response if valid\n */\n private getFromCache(imageHash: string): GeminiAnimationResponse | null {\n if (typeof localStorage === 'undefined') {\n return null;\n }\n\n try {\n const cacheKey = CACHE_KEY_PREFIX + imageHash;\n const cached = localStorage.getItem(cacheKey);\n\n if (!cached) {\n return null;\n }\n\n const parsed: CachedResponse = JSON.parse(cached);\n\n // Check if cache is still valid\n if (Date.now() - parsed.timestamp > CACHE_TTL) {\n localStorage.removeItem(cacheKey);\n return null;\n }\n\n return parsed.data;\n } catch {\n return null;\n }\n }\n\n /**\n * Save response to cache\n */\n private saveToCache(imageHash: string, response: GeminiAnimationResponse): void {\n if (typeof localStorage === 'undefined') {\n return;\n }\n\n try {\n const cacheKey = CACHE_KEY_PREFIX + imageHash;\n const cached: CachedResponse = {\n data: response,\n timestamp: Date.now(),\n };\n localStorage.setItem(cacheKey, JSON.stringify(cached));\n } catch {\n // Ignore storage errors (quota exceeded, etc.)\n if (this.config.debug) {\n console.warn('[GeminiApiClient] Failed to cache response');\n }\n }\n }\n\n /**\n * Clear all cached responses\n */\n clearCache(): void {\n if (typeof localStorage === 'undefined') {\n return;\n }\n\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key && key.startsWith(CACHE_KEY_PREFIX)) {\n keysToRemove.push(key);\n }\n }\n keysToRemove.forEach((key) => localStorage.removeItem(key));\n }\n\n /**\n * Sleep utility for retry delays\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","/**\n * Animation Renderer\n *\n * Renders animation frames to image elements using CSS transforms.\n * Supports breathing, parallax, and sway animation types with\n * configurable intensity levels and loop/one-shot modes.\n */\n\nimport {\n GeminiAnimationResponse,\n AnimationType,\n AnimationIntensity,\n TrackedImage,\n} from './types';\n\n/**\n * Intensity multipliers for animation effects\n */\nconst INTENSITY_MULTIPLIERS: Record<AnimationIntensity, number> = {\n subtle: 0.3,\n medium: 0.6,\n strong: 1.0,\n};\n\n/**\n * Animation preset configurations\n */\nconst ANIMATION_PRESETS: Record<\n Exclude<AnimationType, 'auto'>,\n {\n translateX: number;\n translateY: number;\n scale: number;\n rotate: number;\n duration: number;\n }\n> = {\n breathing: {\n translateX: 0,\n translateY: 0,\n scale: 0.02,\n rotate: 0,\n duration: 3000,\n },\n parallax: {\n translateX: 5,\n translateY: 3,\n scale: 0.01,\n rotate: 0,\n duration: 4000,\n },\n sway: {\n translateX: 3,\n translateY: 0,\n scale: 0,\n rotate: 2,\n duration: 2500,\n },\n};\n\n/**\n * Animation renderer configuration\n */\nexport interface AnimationRendererConfig {\n /** Target frame rate (default: 60) */\n frameRate?: number;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\n/**\n * Animation state for a single image\n */\ninterface AnimationState {\n trackedImage: TrackedImage;\n animationData: GeminiAnimationResponse;\n currentFrame: number;\n animationFrameId: number | null;\n startTime: number;\n isPaused: boolean;\n intensity: AnimationIntensity;\n loop: boolean;\n onComplete?: () => void;\n}\n\n/**\n * AnimationRenderer class\n *\n * Handles rendering animation frames to DOM elements using\n * requestAnimationFrame for smooth playback.\n */\nexport class AnimationRenderer {\n private config: Required<AnimationRendererConfig>;\n private animations: Map<string, AnimationState> = new Map();\n\n constructor(config: AnimationRendererConfig = {}) {\n this.config = {\n frameRate: config.frameRate ?? 60,\n debug: config.debug ?? false,\n };\n }\n\n /**\n * Start animation playback for an image\n *\n * @param trackedImage - The tracked image to animate\n * @param animationData - Animation data from Gemini API\n * @param options - Animation options\n */\n play(\n trackedImage: TrackedImage,\n animationData: GeminiAnimationResponse,\n options: {\n intensity?: AnimationIntensity;\n loop?: boolean;\n onComplete?: () => void;\n } = {}\n ): void {\n const { id, element } = trackedImage;\n\n // Stop any existing animation\n this.stop(id);\n\n // Prepare element for animation\n element.style.willChange = 'transform';\n element.style.transformOrigin = 'center center';\n\n const state: AnimationState = {\n trackedImage,\n animationData,\n currentFrame: 0,\n animationFrameId: null,\n startTime: performance.now(),\n isPaused: false,\n intensity: options.intensity ?? trackedImage.config.intensity,\n loop: options.loop ?? trackedImage.config.loop,\n onComplete: options.onComplete,\n };\n\n this.animations.set(id, state);\n\n if (this.config.debug) {\n console.log('[AnimationRenderer] Starting animation for:', id);\n }\n\n this.renderLoop(id);\n }\n\n /**\n * Pause animation for an image\n */\n pause(imageId: string): void {\n const state = this.animations.get(imageId);\n if (state && !state.isPaused) {\n state.isPaused = true;\n if (state.animationFrameId !== null) {\n cancelAnimationFrame(state.animationFrameId);\n state.animationFrameId = null;\n }\n\n if (this.config.debug) {\n console.log('[AnimationRenderer] Paused animation for:', imageId);\n }\n }\n }\n\n /**\n * Resume animation for an image\n */\n resume(imageId: string): void {\n const state = this.animations.get(imageId);\n if (state && state.isPaused) {\n state.isPaused = false;\n state.startTime = performance.now() - this.getElapsedTime(state);\n this.renderLoop(imageId);\n\n if (this.config.debug) {\n console.log('[AnimationRenderer] Resumed animation for:', imageId);\n }\n }\n }\n\n /**\n * Stop animation for an image and reset transforms\n */\n stop(imageId: string): void {\n const state = this.animations.get(imageId);\n if (state) {\n if (state.animationFrameId !== null) {\n cancelAnimationFrame(state.animationFrameId);\n }\n\n // Reset element styles\n state.trackedImage.element.style.transform = '';\n state.trackedImage.element.style.willChange = '';\n\n this.animations.delete(imageId);\n\n if (this.config.debug) {\n console.log('[AnimationRenderer] Stopped animation for:', imageId);\n }\n }\n }\n\n /**\n * Check if an image is currently animating\n */\n isPlaying(imageId: string): boolean {\n const state = this.animations.get(imageId);\n return state !== undefined && !state.isPaused;\n }\n\n /**\n * Get all currently animating image IDs\n */\n getActiveAnimations(): string[] {\n return Array.from(this.animations.keys());\n }\n\n /**\n * Stop all animations and clean up\n */\n destroy(): void {\n for (const imageId of this.animations.keys()) {\n this.stop(imageId);\n }\n this.animations.clear();\n }\n\n /**\n * Main render loop using requestAnimationFrame\n */\n private renderLoop(imageId: string): void {\n const state = this.animations.get(imageId);\n if (!state || state.isPaused) {\n return;\n }\n\n const elapsed = performance.now() - state.startTime;\n const duration = state.animationData.duration || 3000;\n const progress = (elapsed % duration) / duration;\n\n // Check if animation should complete (non-looping)\n if (!state.loop && elapsed >= duration) {\n this.stop(imageId);\n if (state.onComplete) {\n state.onComplete();\n }\n return;\n }\n\n // Render the current frame\n this.renderFrame(state, progress);\n\n // Schedule next frame\n state.animationFrameId = requestAnimationFrame(() => {\n this.renderLoop(imageId);\n });\n }\n\n /**\n * Render a single animation frame\n */\n private renderFrame(state: AnimationState, progress: number): void {\n const { trackedImage, animationData, intensity } = state;\n const multiplier = INTENSITY_MULTIPLIERS[intensity];\n\n let transform: { translateX: number; translateY: number; scale: number; rotate: number };\n\n // Use animation data if available, otherwise generate from type\n if (animationData.frames && animationData.frames.length > 0) {\n const frameIndex = Math.floor(progress * animationData.frames.length);\n const frame = animationData.frames[Math.min(frameIndex, animationData.frames.length - 1)];\n transform = frame.transform;\n } else {\n // Generate animation from detected type\n const animType = animationData.detectedType || 'breathing';\n if (animType !== 'auto') {\n transform = this.generateTransformFromType(animType, progress);\n } else {\n transform = this.generateTransformFromType('breathing', progress);\n }\n }\n\n // Apply intensity multiplier\n const translateX = transform.translateX * multiplier;\n const translateY = transform.translateY * multiplier;\n const scale = 1 + transform.scale * multiplier;\n const rotate = transform.rotate * multiplier;\n\n // Apply CSS transform\n trackedImage.element.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale}) rotate(${rotate}deg)`;\n }\n\n /**\n * Generate transform values for a given animation type\n */\n private generateTransformFromType(\n type: Exclude<AnimationType, 'auto'>,\n progress: number\n ): { translateX: number; translateY: number; scale: number; rotate: number } {\n const preset = ANIMATION_PRESETS[type] || ANIMATION_PRESETS.breathing;\n\n // Use sine wave for smooth oscillation\n const oscillation = Math.sin(progress * Math.PI * 2);\n\n return {\n translateX: preset.translateX * oscillation,\n translateY: preset.translateY * oscillation,\n scale: preset.scale * oscillation,\n rotate: preset.rotate * oscillation,\n };\n }\n\n /**\n * Calculate elapsed time for a paused animation\n */\n private getElapsedTime(state: AnimationState): number {\n const duration = state.animationData.duration || 3000;\n return (state.currentFrame / state.animationData.frameRate) * 1000 % duration;\n }\n}\n","/**\n * Veo API Client\n *\n * Handles communication with Cloud Functions backend for Veo 3.1 video generation.\n * Features: async polling with exponential backoff, localStorage caching, error handling.\n */\n\n/**\n * Animation mode for the plugin\n */\nexport type AnimationMode = 'auto' | 'css' | 'video';\n\n/**\n * Video generation state\n */\nexport type VideoState = 'idle' | 'generating' | 'ready' | 'error';\n\n/**\n * Video generation request\n */\nexport interface VeoGenerationRequest {\n /** Image URL to generate video from */\n imageUrl: string;\n /** Animation prompt for the video */\n animationPrompt?: string;\n /** Aspect ratio (default: auto-detected from image) */\n aspectRatio?: '16:9' | '9:16';\n /** Duration in seconds (default: 4) */\n durationSeconds?: 4 | 6 | 8;\n}\n\n/**\n * Video generation response from backend\n */\nexport interface VeoGenerationResponse {\n success: boolean;\n operationId?: string;\n status?: 'pending' | 'processing' | 'completed' | 'error';\n videoUrl?: string;\n animation?: any; // CSS animation data (from /animate-auto)\n error?: {\n code: string;\n message: string;\n };\n}\n\n/**\n * Cached video entry\n */\ninterface CachedVideo {\n videoUrl: string;\n timestamp: number;\n}\n\n/**\n * Configuration for the Veo client\n */\nexport interface VeoClientConfig {\n /** Backend endpoint URL */\n endpoint: string;\n /** API key for authentication (optional - uses demo limits without) */\n apiKey?: string;\n /** Enable debug logging */\n debug?: boolean;\n /** Polling interval in ms (default: 3000) */\n pollInterval?: number;\n /** Max poll attempts before timeout (default: 60 = 3 minutes) */\n maxPollAttempts?: number;\n /** Cache TTL in ms (default: 30 days) */\n cacheTTL?: number;\n}\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG = {\n apiKey: '',\n debug: false,\n pollInterval: 3000,\n maxPollAttempts: 60,\n cacheTTL: 30 * 24 * 60 * 60 * 1000, // 30 days\n};\n\n/**\n * Cache key prefix for localStorage\n */\nconst VIDEO_CACHE_PREFIX = 'beautifi_video_';\n\n/**\n * VeoClient class\n *\n * Handles async video generation with automatic polling and caching.\n */\nexport class VeoClient {\n private config: Required<VeoClientConfig>;\n private pendingOperations: Map<string, AbortController> = new Map();\n\n constructor(config: VeoClientConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n }\n\n /**\n * Start video generation for an image\n *\n * @param request - Video generation request\n * @returns Promise resolving to operation ID\n */\n async startGeneration(request: VeoGenerationRequest): Promise<VeoGenerationResponse> {\n // Check cache first\n const cacheKey = this.computeCacheKey(request.imageUrl);\n const cached = this.getFromCache(cacheKey);\n if (cached) {\n this.log(`📦 Video cached: ${cacheKey.slice(0, 8)}...`);\n return {\n success: true,\n status: 'completed',\n videoUrl: cached,\n };\n }\n\n try {\n const endpoint = this.config.endpoint.replace('/animate', '/animate-auto');\n\n this.log(`🎬 Starting video generation for: ${request.imageUrl.slice(0, 50)}...`);\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.config.apiKey) {\n headers['X-API-Key'] = this.config.apiKey;\n }\n\n const response = await fetch(endpoint, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n imageUrl: request.imageUrl,\n animationPrompt: request.animationPrompt,\n aspectRatio: request.aspectRatio,\n durationSeconds: request.durationSeconds,\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data: VeoGenerationResponse = await response.json();\n\n if (!data.success) {\n throw new Error(data.error?.message || 'Video generation failed');\n }\n\n this.log(`🚀 Generation started: ${data.operationId}`);\n return data;\n\n } catch (error) {\n this.log(`❌ Start generation error: ${error}`);\n return {\n success: false,\n error: {\n code: 'GENERATION_ERROR',\n message: error instanceof Error ? error.message : 'Failed to start generation',\n },\n };\n }\n }\n\n /**\n * Poll for video generation status until complete\n *\n * @param operationId - Operation ID to poll\n * @param imageUrl - Original image URL (for caching)\n * @param onProgress - Callback for progress updates\n * @returns Promise resolving to video URL or null on failure\n */\n async pollUntilComplete(\n operationId: string,\n imageUrl: string,\n onProgress?: (status: string, attempt: number) => void\n ): Promise<string | null> {\n const controller = new AbortController();\n this.pendingOperations.set(operationId, controller);\n\n try {\n let attempt = 0;\n\n while (attempt < this.config.maxPollAttempts) {\n if (controller.signal.aborted) {\n this.log(`🛑 Polling aborted: ${operationId}`);\n return null;\n }\n\n const result = await this.checkStatus(operationId);\n\n if (onProgress) {\n onProgress(result.status || 'unknown', attempt);\n }\n\n if (result.status === 'completed' && result.videoUrl) {\n this.log(`✅ Video ready: ${result.videoUrl.slice(0, 50)}...`);\n\n // Cache the video URL\n const cacheKey = this.computeCacheKey(imageUrl);\n this.saveToCache(cacheKey, result.videoUrl);\n\n return result.videoUrl;\n }\n\n if (result.status === 'error' || !result.success) {\n this.log(`❌ Generation failed: ${result.error?.message}`);\n return null;\n }\n\n // Exponential backoff: 3s, 4.5s, 6.75s, etc. (max 15s)\n const backoffMs = Math.min(\n this.config.pollInterval * Math.pow(1.5, Math.min(attempt, 5)),\n 15000\n );\n\n this.log(`⏳ Polling (${attempt + 1}/${this.config.maxPollAttempts}): ${result.status}`);\n await this.sleep(backoffMs);\n attempt++;\n }\n\n this.log(`⏰ Polling timeout: ${operationId}`);\n return null;\n\n } finally {\n this.pendingOperations.delete(operationId);\n }\n }\n\n /**\n * Check the status of a video generation operation\n */\n async checkStatus(operationId: string): Promise<VeoGenerationResponse> {\n try {\n const endpoint = this.config.endpoint\n .replace('/animate', '/generate-video/status')\n .replace(/\\/$/, '');\n\n const headers: Record<string, string> = {};\n if (this.config.apiKey) {\n headers['X-API-Key'] = this.config.apiKey;\n }\n\n const response = await fetch(`${endpoint}/${operationId}`, { headers });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return await response.json();\n\n } catch (error) {\n return {\n success: false,\n status: 'error',\n error: {\n code: 'POLL_ERROR',\n message: error instanceof Error ? error.message : 'Status check failed',\n },\n };\n }\n }\n\n /**\n * Cancel a pending video generation operation\n */\n cancelOperation(operationId: string): void {\n const controller = this.pendingOperations.get(operationId);\n if (controller) {\n controller.abort();\n this.pendingOperations.delete(operationId);\n this.log(`🛑 Cancelled: ${operationId}`);\n }\n }\n\n /**\n * Cancel all pending operations\n */\n cancelAllOperations(): void {\n for (const [id, controller] of this.pendingOperations) {\n controller.abort();\n this.log(`🛑 Cancelled: ${id}`);\n }\n this.pendingOperations.clear();\n }\n\n /**\n * Get cached video URL for an image\n */\n getCachedVideo(imageUrl: string): string | null {\n const cacheKey = this.computeCacheKey(imageUrl);\n return this.getFromCache(cacheKey);\n }\n\n /**\n * Compute cache key from image URL\n */\n private computeCacheKey(imageUrl: string): string {\n // Simple hash for cache key\n let hash = 0;\n for (let i = 0; i < imageUrl.length; i++) {\n const char = imageUrl.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(36);\n }\n\n /**\n * Get video URL from localStorage cache\n */\n private getFromCache(cacheKey: string): string | null {\n if (typeof localStorage === 'undefined') return null;\n\n try {\n const raw = localStorage.getItem(VIDEO_CACHE_PREFIX + cacheKey);\n if (!raw) return null;\n\n const cached: CachedVideo = JSON.parse(raw);\n\n // Check if expired\n if (Date.now() - cached.timestamp > this.config.cacheTTL) {\n localStorage.removeItem(VIDEO_CACHE_PREFIX + cacheKey);\n return null;\n }\n\n return cached.videoUrl;\n } catch {\n return null;\n }\n }\n\n /**\n * Save video URL to localStorage cache\n */\n private saveToCache(cacheKey: string, videoUrl: string): void {\n if (typeof localStorage === 'undefined') return;\n\n try {\n const cached: CachedVideo = {\n videoUrl,\n timestamp: Date.now(),\n };\n localStorage.setItem(VIDEO_CACHE_PREFIX + cacheKey, JSON.stringify(cached));\n this.log(`💾 Cached: ${cacheKey}`);\n } catch {\n // localStorage full or unavailable - ignore\n }\n }\n\n /**\n * Clear all cached videos\n */\n clearCache(): void {\n if (typeof localStorage === 'undefined') return;\n\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i);\n if (key?.startsWith(VIDEO_CACHE_PREFIX)) {\n keysToRemove.push(key);\n }\n }\n keysToRemove.forEach(key => localStorage.removeItem(key));\n this.log(`🧹 Cleared ${keysToRemove.length} cached videos`);\n }\n\n /**\n * Sleep utility\n */\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n /**\n * Debug logging\n */\n private log(message: string): void {\n if (this.config.debug) {\n console.log(`[VeoClient] ${message}`);\n }\n }\n}\n","/**\n * Video Renderer\n *\n * Renders video cinemagraphs with seamless transitions from CSS animations.\n * Handles video playback, loading states, and fallback to CSS on failure.\n */\n\n/**\n * Video playback state\n */\nexport type VideoPlaybackState = 'loading' | 'buffering' | 'playing' | 'paused' | 'error';\n\n/**\n * Video renderer configuration\n */\nexport interface VideoRendererConfig {\n /** Transition duration for CSS to video swap (ms) */\n transitionDuration?: number;\n /** Enable debug logging */\n debug?: boolean;\n /** Auto-play when video is ready */\n autoPlay?: boolean;\n /** Loop video playback */\n loop?: boolean;\n /** Mute video (required for autoplay) */\n muted?: boolean;\n}\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<VideoRendererConfig> = {\n transitionDuration: 500,\n debug: false,\n autoPlay: true,\n loop: true,\n muted: true,\n};\n\n/**\n * Active video entry\n */\ninterface ActiveVideo {\n imageElement: HTMLImageElement;\n videoElement: HTMLVideoElement;\n wrapperElement: HTMLDivElement;\n state: VideoPlaybackState;\n onFallback?: () => void;\n}\n\n/**\n * VideoRenderer class\n *\n * Handles seamless video overlay and playback with CSS transition effects.\n */\nexport class VideoRenderer {\n private config: Required<VideoRendererConfig>;\n private activeVideos: Map<string, ActiveVideo> = new Map();\n\n constructor(config: VideoRendererConfig = {}) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n }\n\n /**\n * Prepare a video overlay for an image\n *\n * @param imageId - Unique ID for tracking\n * @param imageElement - The image element to overlay\n * @param videoUrl - URL of the video to play\n * @param onFallback - Callback if video fails to load\n * @returns Promise that resolves when video is ready to play\n */\n async prepareVideo(\n imageId: string,\n imageElement: HTMLImageElement,\n videoUrl: string,\n onFallback?: () => void\n ): Promise<boolean> {\n // Check if already prepared\n if (this.activeVideos.has(imageId)) {\n this.log(`⚠️ Video already prepared: ${imageId}`);\n return true;\n }\n\n try {\n // Create wrapper to contain image and video\n const wrapper = this.createWrapper(imageElement);\n\n // Create video element\n const video = this.createVideoElement(videoUrl);\n\n // Add video to wrapper (behind image initially)\n wrapper.insertBefore(video, imageElement);\n\n // Track the video\n const entry: ActiveVideo = {\n imageElement,\n videoElement: video,\n wrapperElement: wrapper,\n state: 'loading',\n onFallback,\n };\n this.activeVideos.set(imageId, entry);\n\n // Wait for video to be ready\n await this.waitForVideoReady(video, entry);\n\n this.log(`✅ Video prepared: ${imageId}`);\n return true;\n\n } catch (error) {\n this.log(`❌ Prepare failed: ${imageId} - ${error}`);\n this.cleanup(imageId);\n onFallback?.();\n return false;\n }\n }\n\n /**\n * Play video and transition from CSS animation\n */\n play(imageId: string): boolean {\n const entry = this.activeVideos.get(imageId);\n if (!entry) {\n this.log(`⚠️ No video found: ${imageId}`);\n return false;\n }\n\n if (entry.state === 'error') {\n this.log(`⚠️ Video in error state: ${imageId}`);\n return false;\n }\n\n try {\n // Fade out image, fade in video\n this.transitionToVideo(entry);\n\n // Start video playback\n entry.videoElement.play().catch(err => {\n this.log(`❌ Play failed: ${err}`);\n entry.state = 'error';\n entry.onFallback?.();\n });\n\n entry.state = 'playing';\n this.log(`▶️ Playing: ${imageId}`);\n return true;\n\n } catch (error) {\n this.log(`❌ Play error: ${error}`);\n entry.state = 'error';\n entry.onFallback?.();\n return false;\n }\n }\n\n /**\n * Pause video playback\n */\n pause(imageId: string): boolean {\n const entry = this.activeVideos.get(imageId);\n if (!entry || entry.state !== 'playing') {\n return false;\n }\n\n entry.videoElement.pause();\n entry.state = 'paused';\n this.log(`⏸️ Paused: ${imageId}`);\n return true;\n }\n\n /**\n * Resume video playback\n */\n resume(imageId: string): boolean {\n const entry = this.activeVideos.get(imageId);\n if (!entry || entry.state !== 'paused') {\n return false;\n }\n\n entry.videoElement.play().catch(() => {\n entry.state = 'error';\n });\n entry.state = 'playing';\n this.log(`▶️ Resumed: ${imageId}`);\n return true;\n }\n\n /**\n * Stop video and remove overlay\n */\n stop(imageId: string): void {\n const entry = this.activeVideos.get(imageId);\n if (!entry) return;\n\n // Transition back to image\n this.transitionToImage(entry);\n\n // Cleanup after transition\n setTimeout(() => {\n this.cleanup(imageId);\n }, this.config.transitionDuration);\n }\n\n /**\n * Check if video is playing for an image\n */\n isPlaying(imageId: string): boolean {\n const entry = this.activeVideos.get(imageId);\n return entry?.state === 'playing';\n }\n\n /**\n * Check if video is prepared for an image\n */\n isPrepared(imageId: string): boolean {\n return this.activeVideos.has(imageId);\n }\n\n /**\n * Get current playback state\n */\n getState(imageId: string): VideoPlaybackState | null {\n return this.activeVideos.get(imageId)?.state || null;\n }\n\n /**\n * Destroy all videos and cleanup\n */\n destroy(): void {\n for (const imageId of this.activeVideos.keys()) {\n this.cleanup(imageId);\n }\n this.log(`🧹 Destroyed all videos`);\n }\n\n /**\n * Create wrapper element around image\n */\n private createWrapper(imageElement: HTMLImageElement): HTMLDivElement {\n // Check if already wrapped\n const existingWrapper = imageElement.parentElement;\n if (existingWrapper?.classList.contains('beautifi-video-wrapper')) {\n return existingWrapper as HTMLDivElement;\n }\n\n const wrapper = document.createElement('div');\n wrapper.className = 'beautifi-video-wrapper';\n wrapper.style.cssText = `\n position: relative;\n display: inline-block;\n width: ${imageElement.offsetWidth}px;\n height: ${imageElement.offsetHeight}px;\n overflow: hidden;\n `;\n\n // Wrap the image\n imageElement.parentNode?.insertBefore(wrapper, imageElement);\n wrapper.appendChild(imageElement);\n\n // Style the image for overlay\n imageElement.style.cssText += `\n position: relative;\n z-index: 2;\n transition: opacity ${this.config.transitionDuration}ms ease-in-out;\n `;\n\n return wrapper;\n }\n\n /**\n * Create video element with proper attributes\n */\n private createVideoElement(videoUrl: string): HTMLVideoElement {\n const video = document.createElement('video');\n video.src = videoUrl;\n video.muted = this.config.muted;\n video.loop = this.config.loop;\n video.playsInline = true;\n video.preload = 'auto';\n video.crossOrigin = 'anonymous';\n\n video.style.cssText = `\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: 1;\n opacity: 0;\n transition: opacity ${this.config.transitionDuration}ms ease-in-out;\n `;\n\n return video;\n }\n\n /**\n * Wait for video to be ready to play\n */\n private waitForVideoReady(video: HTMLVideoElement, entry: ActiveVideo): Promise<void> {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error('Video load timeout'));\n }, 30000); // 30s timeout\n\n video.addEventListener('canplaythrough', () => {\n clearTimeout(timeout);\n entry.state = 'buffering';\n resolve();\n }, { once: true });\n\n video.addEventListener('error', () => {\n clearTimeout(timeout);\n entry.state = 'error';\n reject(new Error('Video load error'));\n }, { once: true });\n\n // Trigger load\n video.load();\n });\n }\n\n /**\n * Transition from image to video\n */\n private transitionToVideo(entry: ActiveVideo): void {\n // Show video\n entry.videoElement.style.opacity = '1';\n\n // Hide image (but keep it for fallback)\n entry.imageElement.style.opacity = '0';\n\n // Remove any CSS animations from image\n entry.imageElement.style.animation = 'none';\n }\n\n /**\n * Transition from video back to image\n */\n private transitionToImage(entry: ActiveVideo): void {\n // Show image\n entry.imageElement.style.opacity = '1';\n\n // Hide video\n entry.videoElement.style.opacity = '0';\n\n // Pause video\n entry.videoElement.pause();\n }\n\n /**\n * Cleanup video element and restore image\n */\n private cleanup(imageId: string): void {\n const entry = this.activeVideos.get(imageId);\n if (!entry) return;\n\n // Restore image\n entry.imageElement.style.opacity = '1';\n entry.imageElement.style.animation = '';\n entry.imageElement.style.position = '';\n entry.imageElement.style.zIndex = '';\n entry.imageElement.style.transition = '';\n\n // Remove video\n entry.videoElement.remove();\n\n // Unwrap if we created the wrapper\n if (entry.wrapperElement.classList.contains('beautifi-video-wrapper')) {\n const parent = entry.wrapperElement.parentNode;\n if (parent) {\n parent.insertBefore(entry.imageElement, entry.wrapperElement);\n entry.wrapperElement.remove();\n }\n }\n\n this.activeVideos.delete(imageId);\n this.log(`🧹 Cleaned up: ${imageId}`);\n }\n\n /**\n * Debug logging\n */\n private log(message: string): void {\n if (this.config.debug) {\n console.log(`[VideoRenderer] ${message}`);\n }\n }\n}\n","/**\n * beautifi - Main Entry Point\n *\n * This is the public API for the beautifi plugin.\n * It coordinates the ImageDetector, ViewportObserver, and animation system.\n */\n\nimport {\n LivePhotoOptions,\n TrackedImage,\n DEFAULT_OPTIONS,\n LivePhotoError,\n AnimationMode,\n} from './types';\nimport { ImageDetector } from './ImageDetector';\nimport { ViewportObserver } from './ViewportObserver';\nimport { GeminiApiClient } from './GeminiApiClient';\nimport { AnimationRenderer } from './AnimationRenderer';\nimport { VeoClient } from './VeoClient';\nimport { VideoRenderer } from './VideoRenderer';\n\n/**\n * beautifiPlugin class\n *\n * Core plugin that orchestrates image detection, viewport observation,\n * and animation coordination.\n */\nclass beautifiPlugin {\n private initialized: boolean = false;\n private options: Required<LivePhotoOptions> | null = null;\n private detector: ImageDetector | null = null;\n private viewportObserver: ViewportObserver | null = null;\n private apiClient: GeminiApiClient | null = null;\n private renderer: AnimationRenderer | null = null;\n private veoClient: VeoClient | null = null;\n private videoRenderer: VideoRenderer | null = null;\n private reducedMotion: boolean = false;\n private pendingVideoGenerations: Map<string, string> = new Map(); // imageId -> operationId\n\n /**\n * Initialize the plugin with configuration options\n *\n * @param options - Plugin configuration\n * @throws Error if apiKey is not provided\n */\n init(options: LivePhotoOptions): void {\n if (!options.apiKey) {\n throw new Error('beautifi: apiKey is required');\n }\n\n // Check for reduced motion preference\n if (typeof window !== 'undefined' && window.matchMedia) {\n this.reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n }\n\n // Merge with defaults\n this.options = {\n ...DEFAULT_OPTIONS,\n ...options,\n } as Required<LivePhotoOptions>;\n\n // If reduced motion is preferred and we respect it, don't animate\n if (this.reducedMotion && this.options.respectReducedMotion) {\n if (this.options.debug) {\n console.log('[beautifi] Reduced motion preference detected, animations disabled');\n }\n this.initialized = true;\n return;\n }\n\n // Create image detector\n this.detector = new ImageDetector(this.options.selector, this.options.debug);\n\n // Create API client\n this.apiClient = new GeminiApiClient({\n endpoint: this.options.endpoint || 'https://us-central1-seedgpt-planter.cloudfunctions.net/beautifi-api/animate',\n timeout: this.options.timeout,\n maxRetries: this.options.maxRetries,\n enableCache: true,\n debug: this.options.debug,\n });\n\n // Create animation renderer\n this.renderer = new AnimationRenderer({\n debug: this.options.debug,\n });\n\n // Create Veo client and video renderer for automatic video mode\n if (this.options.mode !== 'css') {\n this.veoClient = new VeoClient({\n endpoint: this.options.videoEndpoint || this.options.endpoint,\n debug: this.options.debug,\n });\n\n this.videoRenderer = new VideoRenderer({\n debug: this.options.debug,\n transitionDuration: 500,\n });\n\n if (this.options.debug) {\n console.log('[beautifi] Video mode enabled:', this.options.mode);\n }\n }\n\n // Create viewport observer\n this.viewportObserver = new ViewportObserver(\n (image, isIntersecting) => {\n this.handleVisibilityChange(image, isIntersecting);\n },\n {\n threshold: this.options.threshold,\n rootMargin: this.options.rootMargin,\n debug: this.options.debug,\n }\n );\n\n // Set up image detection callback\n this.detector.setOnImageDetected((image) => {\n this.viewportObserver!.observe(image);\n });\n\n // Initialize viewport observer\n this.viewportObserver.init();\n\n // Scan for existing images\n const images = this.detector.scan();\n images.forEach((image) => {\n this.viewportObserver!.observe(image);\n });\n\n // Start observing DOM mutations\n this.detector.observe();\n\n this.initialized = true;\n\n if (this.options.debug) {\n console.log('[beautifi] Initialized with options:', this.options);\n console.log('[beautifi] Detected', images.length, 'images');\n }\n }\n\n /**\n * Handle image entering/leaving viewport\n */\n private handleVisibilityChange(image: TrackedImage, isIntersecting: boolean): void {\n if (!this.options) return;\n\n if (isIntersecting) {\n // Image entered viewport - queue for animation\n if (image.state === 'idle') {\n this.queueAnimation(image);\n } else if (image.state === 'paused') {\n this.resumeAnimation(image);\n }\n } else {\n // Image left viewport - pause animation\n if (image.state === 'playing') {\n this.pauseAnimation(image);\n }\n }\n }\n\n /**\n * Queue an image for animation\n */\n private async queueAnimation(image: TrackedImage): Promise<void> {\n // Update state to loading\n if (this.detector) {\n this.detector.updateImageState(image.id, { state: 'loading' });\n }\n\n if (this.options?.debug) {\n console.log('[beautifi] Queuing animation for:', image.element.src);\n }\n\n // Apply delay if configured\n if (image.config.delay && image.config.delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, image.config.delay));\n }\n\n // Check for per-image mode override via data attribute\n const imageMode = (image.element.dataset.beautifiMode as AnimationMode) || this.options?.mode || 'auto';\n\n try {\n // Fetch CSS animation data from API\n const animationData = await this.apiClient!.fetch(image.element.src, {\n type: image.config.type,\n intensity: image.config.intensity,\n loop: image.config.loop,\n });\n\n if (!animationData.success) {\n throw new LivePhotoError(\n animationData.error || 'Animation generation failed',\n 'API_ERROR'\n );\n }\n\n // Store animation data on tracked image\n if (this.detector) {\n this.detector.updateImageState(image.id, {\n state: 'playing',\n animationData,\n });\n }\n\n // Start CSS animation playback immediately (for 'auto' and 'css' modes)\n if (imageMode !== 'video') {\n this.renderer!.play(image, animationData, {\n intensity: image.config.intensity,\n loop: image.config.loop,\n onComplete: () => {\n if (this.detector) {\n this.detector.updateImageState(image.id, { state: 'idle' });\n }\n this.emitEvent('animationComplete', image);\n },\n });\n\n this.emitEvent('animationStart', image);\n }\n\n // Start video generation in background (for 'auto' and 'video' modes)\n if (imageMode !== 'css' && this.veoClient && this.videoRenderer) {\n this.startVideoGeneration(image);\n }\n\n } catch (error) {\n // Handle error - graceful degradation to static image\n const livePhotoError = error instanceof LivePhotoError\n ? error\n : new LivePhotoError(\n error instanceof Error ? error.message : 'Unknown error',\n 'UNKNOWN_ERROR',\n error instanceof Error ? error : undefined\n );\n\n if (this.detector) {\n this.detector.updateImageState(image.id, {\n state: 'error',\n error: livePhotoError,\n });\n }\n\n if (this.options?.debug) {\n console.error('[beautifi] Animation error:', livePhotoError);\n }\n\n this.emitEvent('animationError', image, livePhotoError);\n }\n }\n\n /**\n * Start video generation in background\n */\n private async startVideoGeneration(image: TrackedImage): Promise<void> {\n if (!this.veoClient || !this.videoRenderer) return;\n\n // Check if already generating or cached\n const cachedVideo = this.veoClient.getCachedVideo(image.element.src);\n if (cachedVideo) {\n if (this.options?.debug) {\n console.log('[beautifi] Using cached video for:', image.id);\n }\n this.transitionToVideo(image, cachedVideo);\n return;\n }\n\n // Emit video start event\n this.emitVideoEvent('videoStart', image);\n\n if (this.options?.debug) {\n console.log('[beautifi] Starting video generation for:', image.id);\n }\n\n try {\n // Start generation\n const result = await this.veoClient.startGeneration({\n imageUrl: image.element.src,\n });\n\n if (!result.success || !result.operationId) {\n throw new Error(result.error?.message || 'Failed to start video generation');\n }\n\n // If already completed (cached on server side)\n if (result.status === 'completed' && result.videoUrl) {\n this.transitionToVideo(image, result.videoUrl);\n return;\n }\n\n // Track pending operation\n this.pendingVideoGenerations.set(image.id, result.operationId);\n\n // Poll for completion\n const videoUrl = await this.veoClient.pollUntilComplete(\n result.operationId,\n image.element.src,\n (status, attempt) => {\n if (this.options?.debug) {\n console.log(`[beautifi] Video poll ${image.id}: ${status} (attempt ${attempt})`);\n }\n }\n );\n\n // Clear pending\n this.pendingVideoGenerations.delete(image.id);\n\n if (videoUrl) {\n this.transitionToVideo(image, videoUrl);\n } else {\n if (this.options?.debug) {\n console.log('[beautifi] Video generation failed, keeping CSS animation:', image.id);\n }\n this.emitVideoEvent('videoError', image);\n }\n\n } catch (error) {\n this.pendingVideoGenerations.delete(image.id);\n if (this.options?.debug) {\n console.error('[beautifi] Video generation error:', error);\n }\n this.emitVideoEvent('videoError', image);\n // CSS animation continues as fallback\n }\n }\n\n /**\n * Transition from CSS animation to video\n */\n private async transitionToVideo(image: TrackedImage, videoUrl: string): Promise<void> {\n if (!this.videoRenderer) return;\n\n if (this.options?.debug) {\n console.log('[beautifi] Transitioning to video:', image.id);\n }\n\n // Prepare video (load it behind the image)\n const prepared = await this.videoRenderer.prepareVideo(\n image.id,\n image.element,\n videoUrl,\n () => {\n // Fallback: keep CSS animation running\n if (this.options?.debug) {\n console.log('[beautifi] Video fallback to CSS:', image.id);\n }\n }\n );\n\n if (prepared) {\n // Stop CSS animation and start video\n this.renderer?.stop(image.id);\n this.videoRenderer.play(image.id);\n this.emitVideoEvent('videoReady', image, videoUrl);\n }\n }\n\n /**\n * Pause animation for an image\n */\n private pauseAnimation(image: TrackedImage): void {\n if (this.renderer) {\n this.renderer.pause(image.id);\n }\n\n if (this.detector) {\n this.detector.updateImageState(image.id, { state: 'paused' });\n }\n\n if (this.options?.debug) {\n console.log('[beautifi] Pausing animation for:', image.element.src);\n }\n\n this.emitEvent('animationPause', image);\n }\n\n /**\n * Resume animation for an image\n */\n private resumeAnimation(image: TrackedImage): void {\n if (this.renderer) {\n this.renderer.resume(image.id);\n }\n\n if (this.detector) {\n this.detector.updateImageState(image.id, { state: 'playing' });\n }\n\n if (this.options?.debug) {\n console.log('[beautifi] Resuming animation for:', image.element.src);\n }\n\n this.emitEvent('animationResume', image);\n }\n\n /**\n * Emit lifecycle events\n */\n private emitEvent(\n type: 'animationStart' | 'animationComplete' | 'animationPause' | 'animationResume' | 'animationError',\n image: TrackedImage,\n error?: LivePhotoError\n ): void {\n if (typeof CustomEvent !== 'undefined') {\n const detail = {\n type,\n timestamp: Date.now(),\n element: image.element,\n imageId: image.id,\n ...(error && { error }),\n };\n const event = new CustomEvent(`beautifi:${type}`, { detail });\n document.dispatchEvent(event);\n }\n }\n\n /**\n * Emit video-related events\n */\n private emitVideoEvent(\n type: 'videoStart' | 'videoReady' | 'videoError',\n image: TrackedImage,\n videoUrl?: string\n ): void {\n if (typeof CustomEvent !== 'undefined') {\n const detail = {\n type,\n timestamp: Date.now(),\n element: image.element,\n imageId: image.id,\n ...(videoUrl && { videoUrl }),\n };\n const event = new CustomEvent(`beautifi:${type}`, { detail });\n document.dispatchEvent(event);\n }\n }\n /**\n * Check if the plugin has been initialized\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n /**\n * Get current options (for debugging)\n */\n getOptions(): Required<LivePhotoOptions> | null {\n return this.options;\n }\n\n /**\n * Get all tracked images\n */\n getTrackedImages(): TrackedImage[] {\n return this.detector?.getTrackedImages() ?? [];\n }\n\n /**\n * Destroy the plugin and clean up resources\n */\n destroy(): void {\n // Cancel any pending video operations\n if (this.veoClient) {\n this.veoClient.cancelAllOperations();\n this.veoClient = null;\n }\n\n // Destroy video renderer\n if (this.videoRenderer) {\n this.videoRenderer.destroy();\n this.videoRenderer = null;\n }\n\n if (this.renderer) {\n this.renderer.destroy();\n this.renderer = null;\n }\n\n if (this.detector) {\n this.detector.destroy();\n this.detector = null;\n }\n\n if (this.viewportObserver) {\n this.viewportObserver.destroy();\n this.viewportObserver = null;\n }\n\n this.apiClient = null;\n this.pendingVideoGenerations.clear();\n this.initialized = false;\n this.options = null;\n }\n}\n\n// Create singleton instance\nconst plugin = new beautifiPlugin();\n\n// Public API - Primary brand: beautifi\nexport const beautifi = {\n /**\n * Initialize the plugin\n */\n init: (options: LivePhotoOptions) => plugin.init(options),\n\n /**\n * Check if initialized\n */\n isInitialized: () => plugin.isInitialized(),\n\n /**\n * Get current options\n */\n getOptions: () => plugin.getOptions(),\n\n /**\n * Get tracked images\n */\n getTrackedImages: () => plugin.getTrackedImages(),\n\n /**\n * Destroy and clean up\n */\n destroy: () => plugin.destroy(),\n};\n\n// Backward compatibility alias\nexport const LiveMyPhotos = beautifi;\n\n// Auto-init from script tag\nif (typeof document !== 'undefined') {\n document.addEventListener('DOMContentLoaded', () => {\n // Support both beautifi and legacy live-my-photos script names\n const scriptTag = document.querySelector<HTMLScriptElement>(\n 'script[data-api-key][src*=\"beautifi\"], script[data-api-key][src*=\"live-my-photos\"]'\n );\n\n if (scriptTag) {\n const apiKey = scriptTag.dataset.apiKey;\n const autoInit = scriptTag.dataset.autoInit !== 'false';\n\n if (apiKey && autoInit) {\n beautifi.init({\n apiKey,\n selector: scriptTag.dataset.selector,\n intensity: scriptTag.dataset.intensity as any,\n type: scriptTag.dataset.type as any,\n loop: scriptTag.dataset.loop !== 'false',\n debug: scriptTag.dataset.debug === 'true',\n mode: (scriptTag.dataset.mode as any) || 'auto',\n });\n }\n }\n });\n}\n\n// UMD global export - Primary brand: beautifi\nif (typeof window !== 'undefined') {\n (window as any).beautifi = beautifi;\n // Backward compatibility\n (window as any).LiveMyPhotos = beautifi;\n}\n\n// Export types and utilities\nexport type {\n LivePhotoOptions,\n TrackedImage,\n} from './types';\n\nexport {\n LivePhotoError,\n DEFAULT_OPTIONS,\n DEFAULT_ANIMATION_CONFIG,\n} from './types';\n\nexport { ImageDetector } from './ImageDetector';\nexport { ViewportObserver } from './ViewportObserver';\nexport { GeminiApiClient } from './GeminiApiClient';\nexport { AnimationRenderer } from './AnimationRenderer';\nexport { VeoClient } from './VeoClient';\nexport { VideoRenderer } from './VideoRenderer';\n\nexport default beautifi;\n"],"names":["LivePhotoError","Error","constructor","message","code","originalError","super","this","name","captureStackTrace","DEFAULT_OPTIONS","endpoint","selector","intensity","type","loop","respectReducedMotion","debug","threshold","rootMargin","timeout","maxRetries","mode","videoEndpoint","DEFAULT_ANIMATION_CONFIG","enabled","delay","ImageDetector","trackedImages","Map","processedElements","WeakSet","mutationObserver","onImageDetected","setOnImageDetected","callback","scan","elements","document","querySelectorAll","newImages","forEach","element","has","config","dataset","live","intensityAttr","liveIntensity","typeAttr","liveType","liveLoop","parseInt","liveDelay","parseDataAttributes","add","trackedImage","id","Date","now","Math","random","toString","substr","state","set","push","observe","MutationObserver","mutations","shouldScan","mutation","node","addedNodes","HTMLImageElement","Element","querySelector","image","body","childList","subtree","getTrackedImages","Array","from","values","getTrackedImage","get","updateImageState","updates","Object","assign","destroy","disconnect","clear","ViewportObserver","options","observer","visibilityHandler","observedElements","isDocumentVisible","hidden","init","IntersectionObserver","entries","entry","target","isIntersecting","wasVisible","addEventListener","unobserve","delete","isVisible","removeEventListener","DEFAULT_CONFIG","enableCache","CACHE_KEY_PREFIX","GeminiApiClient","fetch","imageUrl","imageHash","computeHash","cached","getFromCache","cacheStatus","request","response","fetchWithRetry","success","saveToCache","attempt","fetchWithTimeout","error","pow","sleep","controller","AbortController","timeoutId","setTimeout","abort","method","headers","JSON","stringify","signal","clearTimeout","ok","status","statusText","json","input","crypto","subtle","data","TextEncoder","encode","hashBuffer","digest","Uint8Array","map","b","padStart","join","hash","i","length","charCodeAt","abs","localStorage","cacheKey","getItem","parsed","parse","timestamp","removeItem","setItem","clearCache","keysToRemove","key","startsWith","ms","Promise","resolve","INTENSITY_MULTIPLIERS","medium","strong","ANIMATION_PRESETS","breathing","translateX","translateY","scale","rotate","duration","parallax","sway","AnimationRenderer","animations","frameRate","play","animationData","stop","style","willChange","transformOrigin","currentFrame","animationFrameId","startTime","performance","isPaused","onComplete","renderLoop","pause","imageId","cancelAnimationFrame","resume","getElapsedTime","transform","isPlaying","getActiveAnimations","keys","elapsed","progress","renderFrame","requestAnimationFrame","multiplier","frames","frameIndex","floor","min","animType","detectedType","generateTransformFromType","preset","oscillation","sin","PI","apiKey","pollInterval","maxPollAttempts","cacheTTL","VIDEO_CACHE_PREFIX","VeoClient","pendingOperations","startGeneration","computeCacheKey","log","slice","videoUrl","replace","animationPrompt","aspectRatio","durationSeconds","_a","operationId","pollUntilComplete","onProgress","aborted","result","checkStatus","backoffMs","cancelOperation","cancelAllOperations","getCachedVideo","raw","transitionDuration","autoPlay","muted","VideoRenderer","activeVideos","prepareVideo","imageElement","onFallback","wrapper","createWrapper","video","createVideoElement","insertBefore","videoElement","wrapperElement","waitForVideoReady","cleanup","transitionToVideo","catch","err","call","transitionToImage","isPrepared","getState","existingWrapper","parentElement","classList","contains","createElement","className","cssText","offsetWidth","offsetHeight","parentNode","appendChild","src","playsInline","preload","crossOrigin","reject","once","load","opacity","animation","position","zIndex","transition","remove","parent","plugin","initialized","detector","viewportObserver","apiClient","renderer","veoClient","videoRenderer","reducedMotion","pendingVideoGenerations","window","matchMedia","matches","handleVisibilityChange","queueAnimation","resumeAnimation","pauseAnimation","imageMode","beautifiMode","_b","emitEvent","startVideoGeneration","livePhotoError","_c","cachedVideo","emitVideoEvent","_d","_e","CustomEvent","detail","event","dispatchEvent","isInitialized","getOptions","beautifi","LiveMyPhotos","scriptTag","autoInit"],"mappings":"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"}
|
package/dist/beautifi.esm.js
CHANGED
|
@@ -10,7 +10,7 @@ class LivePhotoError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
const DEFAULT_OPTIONS = {
|
|
13
|
-
endpoint: "https://us-central1-
|
|
13
|
+
endpoint: "https://us-central1-seedgpt-planter.cloudfunctions.net/beautifi-api/animate",
|
|
14
14
|
selector: "img",
|
|
15
15
|
intensity: "subtle",
|
|
16
16
|
type: "auto",
|
|
@@ -22,7 +22,7 @@ const DEFAULT_OPTIONS = {
|
|
|
22
22
|
timeout: 1e4,
|
|
23
23
|
maxRetries: 3,
|
|
24
24
|
mode: "auto",
|
|
25
|
-
videoEndpoint: "https://us-central1-
|
|
25
|
+
videoEndpoint: "https://us-central1-seedgpt-planter.cloudfunctions.net/beautifi-api"
|
|
26
26
|
};
|
|
27
27
|
const DEFAULT_ANIMATION_CONFIG = {
|
|
28
28
|
enabled: true,
|
|
@@ -1225,7 +1225,7 @@ class beautifiPlugin {
|
|
|
1225
1225
|
}
|
|
1226
1226
|
this.detector = new ImageDetector(this.options.selector, this.options.debug);
|
|
1227
1227
|
this.apiClient = new GeminiApiClient({
|
|
1228
|
-
endpoint: this.options.endpoint || "https://us-central1-seedgpt-planter.cloudfunctions.net/beautifi-
|
|
1228
|
+
endpoint: this.options.endpoint || "https://us-central1-seedgpt-planter.cloudfunctions.net/beautifi-api/animate",
|
|
1229
1229
|
timeout: this.options.timeout,
|
|
1230
1230
|
maxRetries: this.options.maxRetries,
|
|
1231
1231
|
enableCache: true,
|