@snowcone-app/canvas 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/components/embed/ErrorBoundary.tsx","../src/hooks/useAutoExport.ts","../src/hooks/useContentReady.ts","../src/utils/ArtworkPlacement.ts","../src/utils/ImageLoader.ts","../src/components/embed/SnowconeCanvas.tsx","../src/hooks/useCommands.ts","../src/hooks/useSelectedElement.ts","../src/hooks/useCanvasReady.ts","../src/hooks/useTextBinding.ts","../src/hooks/useImageBinding.ts","../src/hooks/useElementByName.ts","../src/hooks/useViewport.ts","../src/rendering/serialize-for-server.ts"],"sourcesContent":["/**\n * ErrorBoundary - Error boundary component for graceful error handling\n * Catches JavaScript errors in component tree and displays fallback UI\n *\n * @example\n * ```tsx\n * import { ErrorBoundary } from '@snowcone-app/canvas/embed';\n *\n * function App() {\n * return (\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <div>\n * <h2>Something went wrong</h2>\n * <pre>{error.message}</pre>\n * <button onClick={reset}>Try again</button>\n * </div>\n * )}\n * onError={(error) => {\n * console.error('Editor error:', error);\n * analytics.track('Editor Error', { message: error.message });\n * }}\n * >\n * <EditorProvider>\n * <Canvas />\n * </EditorProvider>\n * </ErrorBoundary>\n * );\n * }\n * ```\n */\n\nimport { Component, ReactNode, ErrorInfo } from 'react';\nimport { createLogger } from '../../utils/logger.js';\nimport type { CanvasError } from '../../types/index.js';\n\nconst logger = createLogger('ErrorBoundary');\n\nexport interface ErrorBoundaryProps {\n children: ReactNode;\n /** Fallback UI to render when an error is caught. Can be a static ReactNode or a render function. */\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);\n /**\n * Render function for custom error UI.\n * Takes the error and a reset function, returns a ReactNode.\n * When both `fallback` and `renderError` are provided, `renderError` takes precedence.\n */\n renderError?: (error: Error, reset: () => void) => ReactNode;\n /** Callback fired when an error is caught. Receives the raw Error and React ErrorInfo. */\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n /** Callback fired when an error is caught, receiving a structured CanvasError. */\n onCanvasError?: (error: CanvasError) => void;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Error boundary component for graceful error handling.\n * Catches React rendering errors in the component tree and displays fallback UI.\n * Supports both plain Error and structured CanvasError callbacks.\n */\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = {\n hasError: false,\n error: null,\n };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return {\n hasError: true,\n error,\n };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n // Log error to console\n logger.error('Caught error:', error, errorInfo);\n\n // Call custom error handler if provided (plain Error)\n if (this.props.onError) {\n this.props.onError(error, errorInfo);\n }\n\n // Call structured CanvasError handler if provided\n if (this.props.onCanvasError) {\n const canvasError: CanvasError = {\n category: 'unknown',\n message: error.message,\n originalError: error,\n recoverable: false,\n };\n this.props.onCanvasError(canvasError);\n }\n }\n\n resetError = (): void => {\n this.setState({\n hasError: false,\n error: null,\n });\n };\n\n render(): ReactNode {\n if (this.state.hasError && this.state.error) {\n // renderError takes precedence over fallback\n if (this.props.renderError) {\n return this.props.renderError(this.state.error, this.resetError);\n }\n\n // Custom fallback (legacy pattern, still supported)\n if (this.props.fallback) {\n if (typeof this.props.fallback === 'function') {\n return this.props.fallback(this.state.error, this.resetError);\n }\n return this.props.fallback;\n }\n\n // Default fallback UI\n return (\n <div\n style={{\n padding: '24px',\n border: '2px solid var(--danger)',\n borderRadius: '8px',\n backgroundColor: 'color-mix(in oklch, var(--danger) 10%, var(--background))',\n color: 'var(--danger)',\n fontFamily: 'system-ui, sans-serif',\n }}\n >\n <h2 style={{ margin: '0 0 16px 0', fontSize: '18px', fontWeight: 600 }}>\n Something went wrong\n </h2>\n <pre\n style={{\n margin: '0 0 16px 0',\n padding: '12px',\n backgroundColor: 'color-mix(in oklch, var(--danger) 15%, var(--background))',\n borderRadius: '4px',\n fontSize: '14px',\n overflow: 'auto',\n maxHeight: '200px',\n }}\n >\n {this.state.error.message}\n </pre>\n <button\n onClick={this.resetError}\n style={{\n padding: '8px 16px',\n backgroundColor: 'var(--danger)',\n color: 'var(--danger-foreground)',\n border: 'none',\n borderRadius: '4px',\n fontSize: '14px',\n fontWeight: 500,\n cursor: 'pointer',\n }}\n >\n Try again\n </button>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","/**\n * useAutoExport - React hook for automatic export on state changes\n *\n * Features:\n * - Automatically exports when editor state changes\n * - Debounced with maximum wait time (using lodash)\n * - Deep change detection (skips exports when nothing changed)\n * - Configurable timing parameters\n */\n\nimport { useEffect, useRef, useCallback, useState } from 'react';\nimport { AutoExportManager, AutoExportConfig, AutoExportStats, DEFAULT_AUTO_EXPORT_CONFIG } from '../services/AutoExportManager.js';\nimport { subscribeToImageLoads } from '../core/ImageLoadEvents.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { HybridHistoryManager } from '../core/HybridHistoryManager.js';\nimport type { ArtboardElement } from '../core/ArtboardElement.js';\nimport type { TextElement } from '../core/TextElement.js';\nimport type { ImageElement } from '../core/ImageElement.js';\nimport type { GroupElement } from '../core/GroupElement.js';\nimport type { ShapeElement } from '../core/ShapeElement.js';\nimport type { PathElement } from '../core/PathElement.js';\n\nconst logger = createLogger('useAutoExport');\n\ntype EditorElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport interface UseAutoExportOptions {\n /** Auto-export configuration */\n config?: Partial<AutoExportConfig>;\n\n /** Hybrid history manager to listen to for changes */\n historyManager?: HybridHistoryManager;\n\n /** Current artboards */\n artboards: ArtboardElement[];\n\n /** Current elements */\n elements: EditorElement[];\n\n /** Callback to perform the actual export */\n onExport: () => void | Promise<void>;\n\n /**\n * Callback fired immediately when an export is scheduled (before debounce).\n * Use this to show loading indicators right away instead of waiting for export to complete.\n */\n onExportScheduled?: () => void;\n\n /**\n * Whether the canvas is mounted and ready for export operations.\n * When false, exports are deferred until canvas becomes ready.\n * This prevents \"[useExport] Canvas ref not available\" errors.\n */\n isCanvasReady?: boolean;\n}\n\nexport interface UseAutoExportReturn {\n /** Current auto-export statistics */\n stats: AutoExportStats;\n\n /** Update configuration on the fly */\n updateConfig: (config: Partial<AutoExportConfig>) => void;\n\n /** Force an immediate export (bypassing debounce) */\n forceExport: () => Promise<void>;\n\n /** Reset statistics */\n resetStats: () => void;\n\n /** Reset change detection (next export will always trigger) */\n resetChangeDetection: () => void;\n}\n\n/**\n * Hook to manage automatic export on state changes\n *\n * @example\n * ```tsx\n * const { stats, updateConfig } = useAutoExport({\n * config: {\n * enabled: true,\n * debounceMs: 100,\n * maxWaitMs: 1000,\n * },\n * historyManager,\n * artboards,\n * elements,\n * onExport: async () => {\n * await exportAllArtboards();\n * },\n * });\n * ```\n */\nexport function useAutoExport(options: UseAutoExportOptions): UseAutoExportReturn {\n const { config, historyManager, artboards, elements, onExport, onExportScheduled, isCanvasReady = false } = options;\n\n const managerRef = useRef<AutoExportManager | null>(null);\n // Track if we have a pending export waiting for canvas to become ready\n const pendingExportRef = useRef<boolean>(false);\n // Stats stored in ref to avoid re-rendering host component (e.g. SnowconeCanvas)\n // on every auto-export. Only promoted to state on explicit user actions.\n const statsRef = useRef<AutoExportStats>({\n totalExports: 0,\n skippedExports: 0,\n lastExportTime: null,\n avgExportInterval: 0,\n });\n const [stats, setStats] = useState<AutoExportStats>(statsRef.current);\n\n // Ref for onExport — always reads the latest callback at execution time.\n // This eliminates stale-closure bugs: the manager's onExport callback never\n // goes stale because it always dereferences onExportRef.current.\n const onExportRef = useRef(onExport);\n onExportRef.current = onExport;\n\n // Initialize manager\n useEffect(() => {\n\n const manager = new AutoExportManager({\n ...DEFAULT_AUTO_EXPORT_CONFIG,\n ...config,\n });\n\n // Register export callback — uses ref so it always calls the latest onExport.\n // No need to re-register when onExport changes (the ref handles it).\n manager.onExport(async () => {\n try {\n await onExportRef.current();\n\n // Update ref only — no React state update to avoid re-rendering\n // the host component (SnowconeCanvas) on every auto-export\n statsRef.current = manager.getStats();\n } catch (error) {\n logger.error('[useAutoExport] Export failed:', error);\n }\n });\n\n managerRef.current = manager;\n\n // Cleanup on unmount\n return () => {\n manager.destroy();\n };\n }, []); // Only initialize once\n\n // Trigger pending export when canvas becomes ready\n useEffect(() => {\n if (isCanvasReady && pendingExportRef.current && managerRef.current) {\n pendingExportRef.current = false;\n managerRef.current.forceExport();\n }\n }, [isCanvasReady]);\n\n // Update config when it changes\n useEffect(() => {\n if (managerRef.current && config) {\n managerRef.current.updateConfig(config);\n }\n }, [config?.enabled, config?.debounceMs, config?.maxWaitMs]);\n\n // onExport callback changes are handled via onExportRef (set above).\n // No useEffect re-registration needed — eliminates the race condition\n // where the effect fires after the debounced export has already captured\n // a stale callback reference.\n\n // Update onExportScheduled callback when it changes\n useEffect(() => {\n if (managerRef.current && onExportScheduled) {\n managerRef.current.onExportScheduled(onExportScheduled);\n }\n }, [onExportScheduled]);\n\n // Hook into history manager to listen for changes\n useEffect(() => {\n if (!historyManager || !managerRef.current) {\n return;\n }\n\n\n // Subscribe to command executed events\n const unsubscribe = historyManager.onCommandExecuted(() => {\n\n // Schedule export (will be debounced)\n if (managerRef.current) {\n // Gate on canvas ready to prevent \"[useExport] Canvas ref not available\" errors\n if (!isCanvasReady) {\n pendingExportRef.current = true;\n return;\n }\n managerRef.current.scheduleExport();\n }\n });\n\n // Cleanup\n return () => {\n unsubscribe();\n };\n }, [historyManager, isCanvasReady]);\n\n // Also watch for direct state changes (e.g., during drag operations)\n // This ensures exports trigger even when changes bypass command history\n //\n // PERF: We intentionally avoid JSON.stringify here. During drag operations,\n // elements changes on every mouse move. JSON.stringify of all elements was\n // costing 50-200ms per frame — causing visible stutter. Instead we just call\n // scheduleExport() which is a cheap debounce timer reset. The AutoExportManager\n // does its own change detection (via getCurrentStateSnapshot) before actually\n // exporting, so redundant exports are still prevented.\n useEffect(() => {\n if (!managerRef.current) {\n return;\n }\n\n // Gate on canvas ready to prevent \"[useExport] Canvas ref not available\" errors\n if (!isCanvasReady) {\n pendingExportRef.current = true;\n return;\n }\n\n // Schedule export (debounced + manager does its own change detection)\n managerRef.current.scheduleExport();\n }, [elements, artboards, isCanvasReady]);\n\n // Re-trigger export when an image finishes loading.\n // Adding an image element fires auto-export via the elements dep above,\n // but the image hasn't loaded yet so it gets skipped during serialization.\n // This subscription ensures we re-export once the bitmap is available.\n useEffect(() => {\n const unsubscribe = subscribeToImageLoads((elementId) => {\n if (!managerRef.current || !isCanvasReady) return;\n const hasElement = elements.some(e => e.id === elementId);\n if (hasElement) {\n managerRef.current.scheduleExport();\n }\n });\n return unsubscribe;\n }, [elements, isCanvasReady]);\n\n // Return control functions\n const updateConfig = useCallback((newConfig: Partial<AutoExportConfig>) => {\n if (managerRef.current) {\n managerRef.current.updateConfig(newConfig);\n }\n }, []);\n\n const forceExport = useCallback(async () => {\n if (managerRef.current) {\n await managerRef.current.forceExport();\n statsRef.current = managerRef.current.getStats();\n setStats(statsRef.current);\n }\n }, []);\n\n const resetStats = useCallback(() => {\n if (managerRef.current) {\n managerRef.current.resetStats();\n statsRef.current = managerRef.current.getStats();\n setStats(statsRef.current);\n }\n }, []);\n\n const resetChangeDetection = useCallback(() => {\n if (managerRef.current) {\n managerRef.current.resetChangeDetection();\n }\n }, []);\n\n return {\n stats,\n updateConfig,\n forceExport,\n resetStats,\n resetChangeDetection,\n };\n}\n","/**\n * useContentReady - Tracks when canvas content is truly ready for export.\n *\n * Combines multiple signals:\n * 1. DOM + artboards ready (isCanvasReady)\n * 2. Initial elements deserialized and loaded\n * 3. Fonts used by text elements available in the browser\n * 4. At least one render frame completed after all the above\n *\n * Returns a latching boolean — once true, never goes back to false.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { createLogger } from '../utils/logger.js';\nimport type { TextElement } from '../core/TextElement.js';\nimport type { ImageElement } from '../core/ImageElement.js';\nimport type { GroupElement } from '../core/GroupElement.js';\nimport type { ShapeElement } from '../core/ShapeElement.js';\nimport type { PathElement } from '../core/PathElement.js';\n\nconst logger = createLogger('useContentReady');\n\ntype EditorElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport interface UseContentReadyOptions {\n /** DOM mounted and artboards initialized */\n isCanvasReady: boolean;\n /** Current elements in the editor */\n elements: EditorElement[];\n /** Were initialElements provided to SnowconeCanvas? */\n hasInitialElements: boolean;\n /** Have initialElements been deserialized into the editor? */\n initialElementsLoaded: boolean;\n}\n\n/**\n * Extract unique font families from text elements.\n * Only text-based elements (transformType !== 'image' and !== 'group') have fontFamily.\n */\nfunction extractFontFamilies(elements: EditorElement[]): string[] {\n const fonts = new Set<string>();\n for (const el of elements) {\n if ('fontFamily' in el && typeof el.fontFamily === 'string' && el.fontFamily) {\n fonts.add(el.fontFamily);\n }\n // Also check rich text spans for per-character font families\n if ('getRichText' in el && typeof el.getRichText === 'function') {\n try {\n const richText = el.getRichText();\n if (richText?.spans) {\n for (const span of richText.spans) {\n if (span.style?.fontFamily) {\n fonts.add(span.style.fontFamily);\n }\n }\n }\n } catch {\n // Ignore errors reading rich text\n }\n }\n }\n return Array.from(fonts);\n}\n\n/**\n * Check if all fonts are available, and load any that aren't.\n * Uses the standard FontFaceSet API.\n */\nasync function ensureFontsLoaded(fontFamilies: string[]): Promise<void> {\n if (fontFamilies.length === 0) return;\n if (typeof document === 'undefined' || !document.fonts) return;\n\n // CRITICAL: do NOT gate on `document.fonts.check()`. It returns `true` for\n // any specifier whose fonts are *currently satisfiable* — including custom\n // families with no `@font-face` registered, because the browser falls back\n // to a default font and considers the request fulfilled. So a freshly\n // deserialized design that references \"Bangers\" before the Google Fonts\n // `<link>` has loaded its CSS will pass `check()` (no FontFace exists yet\n // → fallback satisfies it), the auto-export gate releases, the worker\n // exports the placement PNG with a serif fallback, and a moment later the\n // `<link>` finishes loading and the on-screen canvas re-renders correctly\n // — leaving the saved placement permanently wrong.\n //\n // `document.fonts.load()` resolves with `[]` for missing fonts, so calling\n // it unconditionally is cheap and gives us the actual \"is this glyph data\n // available?\" semantics we want.\n const pending: Promise<FontFace[]>[] = [];\n for (const family of fontFamilies) {\n const testString = `16px \"${family}\"`;\n pending.push(document.fonts.load(testString));\n }\n await Promise.all(pending);\n\n // iOS Safari quirk: `document.fonts.load()` can resolve before the font is\n // actually activated for *freshly created* canvas contexts (the offscreen\n // `document.createElement('canvas')` used by the main-thread export path,\n // and `OffscreenCanvas` in workers). The on-screen `<canvas>` paints\n // correctly because Safari activates fonts on first DOM use, but the\n // detached canvas the export builds was never DOM-attached. `document.fonts.ready`\n // resolves *after* all in-flight font activation work has settled — that's\n // the sync point detached-canvas font availability needs on iOS.\n //\n // Race with a 250ms timeout — on Chrome `ready` resolves in <1ms post-load,\n // on iOS this is the activation barrier. The race protects against a\n // wedged FontFaceSet (e.g. a `<link>` that never finishes parsing) holding\n // the export gate open indefinitely.\n if (document.fonts.ready) {\n await Promise.race([\n document.fonts.ready,\n new Promise<void>((resolve) => setTimeout(resolve, 250)),\n ]);\n }\n logger.debug(`Awaited ${pending.length} font(s)`);\n}\n\n/**\n * Wait one animation frame so the canvas render loop draws with loaded content.\n */\nfunction waitOneFrame(): Promise<void> {\n return new Promise((resolve) => requestAnimationFrame(() => resolve()));\n}\n\nexport function useContentReady(options: UseContentReadyOptions): boolean {\n const { isCanvasReady, elements, hasInitialElements, initialElementsLoaded } = options;\n const [isContentReady, setIsContentReady] = useState(false);\n const checkingRef = useRef(false);\n\n useEffect(() => {\n // Already latched — nothing to do\n if (isContentReady) return;\n\n // Gate 1: DOM + artboards must be ready\n if (!isCanvasReady) return;\n\n // Gate 2: If initial elements were provided, they must be loaded\n if (hasInitialElements && !initialElementsLoaded) return;\n\n // Gate 3: If initial elements were provided, the elements array must be non-empty\n if (hasInitialElements && elements.length === 0) return;\n\n // Prevent concurrent checks\n if (checkingRef.current) return;\n checkingRef.current = true;\n\n let cancelled = false;\n\n (async () => {\n try {\n // Gate 4: Ensure all fonts used by text elements are loaded\n const fonts = extractFontFamilies(elements);\n if (fonts.length > 0) {\n await ensureFontsLoaded(fonts);\n if (cancelled) return;\n }\n\n // Gate 5: Wait one render frame so the canvas draws with ready content\n await waitOneFrame();\n if (cancelled) return;\n setIsContentReady(true);\n } catch (err) {\n logger.error('Error during content readiness check:', err);\n // Still mark ready on error to avoid blocking exports forever\n if (!cancelled) {\n setIsContentReady(true);\n }\n } finally {\n checkingRef.current = false;\n }\n })();\n\n return () => {\n cancelled = true;\n checkingRef.current = false;\n };\n }, [isCanvasReady, elements, hasInitialElements, initialElementsLoaded, isContentReady]);\n\n return isContentReady;\n}\n","/**\n * ArtworkPlacement - Utility for calculating artwork position and scale\n *\n * Uses \"cover\" logic (like CSS object-fit: cover) to ensure artwork fills\n * the entire artboard while maintaining aspect ratio.\n */\n\n/**\n * 9-point alignment options for positioning artwork\n */\nexport type Alignment = 'tl' | 't' | 'tr' | 'l' | 'c' | 'r' | 'bl' | 'b' | 'br';\n\n/**\n * Scale mode for artwork placement\n * - \"cover\" (default): fills entire area, may crop (like CSS object-fit: cover)\n * - \"contain\": fits entirely inside area, may have empty space (like CSS object-fit: contain)\n */\nexport type ScaleMode = 'cover' | 'contain';\n\n/**\n * Placement configuration for artwork positioning\n */\nexport interface PlacementConfig {\n /** Artboard width in pixels */\n width: number;\n /** Artboard height in pixels */\n height: number;\n /** User scale multiplier (default: 1) */\n scale?: number;\n /** 9-point alignment (default: \"c\" for center) */\n align?: Alignment;\n /** X offset in range -1 to 1 (default: 0) */\n offsetX?: number;\n /** Y offset in range -1 to 1 (default: 0) */\n offsetY?: number;\n /** Scale mode: \"cover\" fills area (may crop), \"contain\" fits inside (may have space). Default: \"cover\" */\n scaleMode?: ScaleMode;\n /** Top margin in pixels (reduces available area in contain mode) */\n marginTop?: number;\n /** Right margin in pixels (reduces available area in contain mode) */\n marginRight?: number;\n /** Bottom margin in pixels (reduces available area in contain mode) */\n marginBottom?: number;\n /** Left margin in pixels (reduces available area in contain mode) */\n marginLeft?: number;\n}\n\n/**\n * Result of artwork placement calculation\n */\nexport interface ArtworkPlacementResult {\n /** Final scale to apply to artwork */\n scale: number;\n /** Final artwork dimensions after scaling */\n width: number;\n height: number;\n /** Position to draw artwork (relative to artboard origin) */\n x: number;\n y: number;\n /** Debug information */\n debug: {\n coverScale: number;\n userScale: number;\n overflow: { x: number; y: number };\n alignment: string;\n appliedOffset: { x: number; y: number };\n };\n}\n\n/**\n * Map string alignment values to 9-point grid\n */\nexport const alignmentMap: Record<string, Alignment> = {\n // Vertical alignments (map to top/center/bottom center)\n 'far-top': 't',\n 'top': 't',\n 'center': 'c',\n 'bottom': 'b',\n 'far-bottom': 'b',\n // Horizontal alignments (map to left/center/right middle)\n 'far-left': 'l',\n 'left': 'l',\n 'right': 'r',\n 'far-right': 'r',\n // Direct 9-point values\n 'tl': 'tl',\n 't': 't',\n 'tr': 'tr',\n 'l': 'l',\n 'c': 'c',\n 'r': 'r',\n 'bl': 'bl',\n 'b': 'b',\n 'br': 'br',\n};\n\n/**\n * Get Alignment from string (supports various naming conventions)\n */\nexport function getAlignment(value: string | Alignment | undefined): Alignment {\n if (!value) return 'c';\n return alignmentMap[value] || 'c';\n}\n\n/**\n * Calculates how to size and position artwork on an artboard\n * Uses \"cover\" logic (like CSS object-fit: cover) to ensure artwork fills the entire area\n *\n * @param artwork - The artwork dimensions\n * @param placement - The placement configuration\n * @returns Positioning and scaling information\n *\n * @example\n * const result = calculateArtworkPlacement(\n * { width: 1000, height: 1000 }, // Square artwork\n * { width: 500, height: 800 } // Tall artboard\n * );\n * // Result: artwork scaled to 800x800, positioned at x: -150, y: 0\n */\nexport function calculateArtworkPlacement(\n artwork: { width: number; height: number },\n placement: PlacementConfig\n): ArtworkPlacementResult {\n // Step 1: Calculate base scale based on scale mode\n const mode = placement.scaleMode || 'cover';\n\n // Margins reduce available area (only in contain mode)\n const mTop = (mode === 'contain' ? placement.marginTop : 0) || 0;\n const mRight = (mode === 'contain' ? placement.marginRight : 0) || 0;\n const mBottom = (mode === 'contain' ? placement.marginBottom : 0) || 0;\n const mLeft = (mode === 'contain' ? placement.marginLeft : 0) || 0;\n\n const effectiveWidth = placement.width - mLeft - mRight;\n const effectiveHeight = placement.height - mTop - mBottom;\n\n const scaleX = effectiveWidth / artwork.width;\n const scaleY = effectiveHeight / artwork.height;\n\n // \"cover\": use LARGER scale so artwork fills entire area (may crop)\n // \"contain\": use SMALLER scale so artwork fits entirely inside (may have space)\n const coverScale = mode === 'contain'\n ? Math.min(scaleX, scaleY)\n : Math.max(scaleX, scaleY);\n\n // Step 2: Apply user's custom scale multiplier\n const userScale = placement.scale || 1;\n const finalScale = coverScale * userScale;\n\n // Step 3: Calculate final artwork dimensions after scaling\n const scaledArtworkWidth = artwork.width * finalScale;\n const scaledArtworkHeight = artwork.height * finalScale;\n\n // Step 4: Calculate base position using alignment\n const alignment = placement.align || 'c'; // default to center\n let x = 0;\n let y = 0;\n\n // Calculate the \"overflow\" relative to effective area\n // In contain mode with margins, overflow is relative to the margin-bounded area\n const overflowX = scaledArtworkWidth - effectiveWidth;\n const overflowY = scaledArtworkHeight - effectiveHeight;\n\n // Apply 9-point alignment\n switch (alignment) {\n // Top row\n case 'tl': // Top-Left\n x = 0;\n y = 0;\n break;\n case 't': // Top-Center\n x = -overflowX / 2;\n y = 0;\n break;\n case 'tr': // Top-Right\n x = -overflowX;\n y = 0;\n break;\n\n // Middle row\n case 'l': // Middle-Left\n x = 0;\n y = -overflowY / 2;\n break;\n case 'c': // Center (default)\n default:\n x = -overflowX / 2;\n y = -overflowY / 2;\n break;\n case 'r': // Middle-Right\n x = -overflowX;\n y = -overflowY / 2;\n break;\n\n // Bottom row\n case 'bl': // Bottom-Left\n x = 0;\n y = -overflowY;\n break;\n case 'b': // Bottom-Center\n x = -overflowX / 2;\n y = -overflowY;\n break;\n case 'br': // Bottom-Right\n x = -overflowX;\n y = -overflowY;\n break;\n }\n\n // Step 5: Apply margin offsets (shift positions into margin-bounded area)\n x += mLeft;\n y += mTop;\n\n // Step 6: Apply user offsets\n // Offsets are in range -1 to 1, so convert to pixel values\n const offsetX = placement.offsetX || 0;\n const offsetY = placement.offsetY || 0;\n\n // The \"range\" is how far we can move the artwork\n const xRange = overflowX;\n const yRange = overflowY;\n\n x += offsetX * xRange;\n y += offsetY * yRange;\n\n // Round to whole pixels\n x = Math.round(x);\n y = Math.round(y);\n\n return {\n // Final scale to apply to artwork\n scale: finalScale,\n\n // Final artwork dimensions after scaling\n width: Math.round(scaledArtworkWidth),\n height: Math.round(scaledArtworkHeight),\n\n // Position to draw artwork (relative to artboard origin)\n x,\n y,\n\n // Debug info\n debug: {\n coverScale,\n userScale,\n overflow: { x: overflowX, y: overflowY },\n alignment,\n appliedOffset: { x: offsetX * xRange, y: offsetY * yRange },\n },\n };\n}\n","/**\n * ImageLoader - Utility for loading images from URLs\n * Provides a clean async API with loading states and error handling\n */\n\nimport { ImageElement } from '../core/ImageElement.js';\nimport type { ImageElementConfig } from '../types/index.js';\nimport { calculateArtworkPlacement, getAlignment, type Alignment, type PlacementConfig, type ScaleMode } from './ArtworkPlacement.js';\n\nexport interface ImageLoadResult {\n success: boolean;\n element?: HTMLImageElement;\n error?: Error;\n width?: number;\n height?: number;\n aspectRatio?: number;\n}\n\nexport interface ImageLoaderOptions {\n /** Timeout in milliseconds (default: 30000 - 30 seconds) */\n timeout?: number;\n /** Number of retry attempts on failure (default: 1) */\n retries?: number;\n /** Retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Whether to validate URL before loading (default: true) */\n validateUrl?: boolean;\n}\n\nexport type ImageLoadingState = 'idle' | 'loading' | 'success' | 'error';\n\nconst DEFAULT_OPTIONS: Required<ImageLoaderOptions> = {\n timeout: 30000,\n retries: 1,\n retryDelay: 1000,\n validateUrl: true,\n};\n\n/**\n * Validate if a string is a valid URL\n */\nexport function isValidImageUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n // Only allow http, https, data, and blob URLs\n return ['http:', 'https:', 'data:', 'blob:'].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\n/**\n * Load an image from a URL\n * Returns a promise that resolves with the loaded HTMLImageElement\n */\nexport async function loadImageFromURL(\n url: string,\n options: ImageLoaderOptions = {}\n): Promise<ImageLoadResult> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n // Validate URL if enabled\n if (opts.validateUrl && !isValidImageUrl(url)) {\n return {\n success: false,\n error: new Error(`Invalid image URL: ${url}`),\n };\n }\n\n let lastError: Error | undefined;\n\n // Attempt to load with retries\n for (let attempt = 0; attempt <= opts.retries; attempt++) {\n try {\n const result = await loadImageOnce(url, opts.timeout);\n return result;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Wait before retrying (unless this was the last attempt)\n if (attempt < opts.retries) {\n await delay(opts.retryDelay);\n }\n }\n }\n\n return {\n success: false,\n error: lastError || new Error('Failed to load image after retries'),\n };\n}\n\n/**\n * Load image once (single attempt)\n */\nfunction loadImageOnce(url: string, timeout: number): Promise<ImageLoadResult> {\n return new Promise((resolve) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let resolved = false;\n\n const cleanup = () => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n };\n\n const handleSuccess = () => {\n if (resolved) return;\n resolved = true;\n cleanup();\n\n const w = img.naturalWidth || img.width;\n const h = img.naturalHeight || img.height;\n resolve({\n success: true,\n element: img,\n width: w,\n height: h,\n aspectRatio: h > 0 ? w / h : 1,\n });\n };\n\n const handleError = (errorMsg: string) => {\n if (resolved) return;\n resolved = true;\n cleanup();\n\n resolve({\n success: false,\n error: new Error(errorMsg),\n });\n };\n\n img.onload = handleSuccess;\n\n img.onerror = () => {\n handleError(`Failed to load image: ${url}`);\n };\n\n // Set timeout\n timeoutId = setTimeout(() => {\n handleError(`Image load timed out after ${timeout}ms: ${url}`);\n }, timeout);\n\n // Start loading\n img.src = url;\n });\n}\n\n/**\n * Preload multiple images in parallel\n * Returns results for all images (some may fail)\n */\nexport async function preloadImages(\n urls: string[],\n options: ImageLoaderOptions = {}\n): Promise<Map<string, ImageLoadResult>> {\n const results = new Map<string, ImageLoadResult>();\n\n const promises = urls.map(async (url) => {\n const result = await loadImageFromURL(url, options);\n results.set(url, result);\n });\n\n await Promise.all(promises);\n return results;\n}\n\n/**\n * Create an ImageElement from a URL with loading state handling\n */\nexport async function createImageElement(\n url: string,\n config: Partial<ImageElementConfig> = {},\n options: ImageLoaderOptions = {}\n): Promise<{\n success: boolean;\n element?: ImageElement;\n error?: Error;\n}> {\n const loadResult = await loadImageFromURL(url, options);\n\n if (!loadResult.success) {\n return {\n success: false,\n error: loadResult.error,\n };\n }\n\n // Create ImageElement with the URL\n // The ImageElement constructor will load the image itself\n const imageElement = new ImageElement({\n ...config,\n imageUrl: url,\n imageAspectRatio: loadResult.aspectRatio,\n transformData: {\n type: 'image',\n width: config.transformData?.width || Math.min(loadResult.width || 400, 400),\n height: config.transformData?.height || Math.min(loadResult.height || 400, 400),\n cropX: config.transformData?.cropX ?? 0,\n cropY: config.transformData?.cropY ?? 0,\n cropWidth: config.transformData?.cropWidth ?? 1,\n cropHeight: config.transformData?.cropHeight ?? 1,\n flipHorizontal: config.transformData?.flipHorizontal ?? false,\n flipVertical: config.transformData?.flipVertical ?? false,\n borderRadius: config.transformData?.borderRadius ?? 0,\n },\n });\n\n return {\n success: true,\n element: imageElement,\n };\n}\n\n/**\n * Configuration for artwork placement on artboard\n */\nexport interface ArtworkPlacementOptions {\n /** Artboard dimensions */\n artboard: {\n width: number;\n height: number;\n /** Artboard position on canvas (for proper element positioning) */\n x: number;\n y: number;\n };\n /** Alignment string (e.g., 'center', 'top', 'bottom-left') */\n alignment?: string | Alignment;\n /** User scale multiplier (default: 1) */\n scale?: number;\n /** X offset in range -1 to 1 */\n offsetX?: number;\n /** Y offset in range -1 to 1 */\n offsetY?: number;\n /** Scale mode: \"cover\" fills area (may crop), \"contain\" fits inside (may have space). Default: \"cover\" */\n scaleMode?: ScaleMode;\n /** Top margin in pixels (reduces available area in contain mode) */\n marginTop?: number;\n /** Right margin in pixels (reduces available area in contain mode) */\n marginRight?: number;\n /** Bottom margin in pixels (reduces available area in contain mode) */\n marginBottom?: number;\n /** Left margin in pixels (reduces available area in contain mode) */\n marginLeft?: number;\n}\n\n/**\n * Create an ImageElement with proper \"cover\" sizing and placement on artboard\n *\n * This uses CSS-like object-fit: cover logic to ensure the artwork completely\n * fills the artboard while maintaining aspect ratio, with 9-point alignment support.\n *\n * @example\n * const result = await createImageElementWithPlacement(\n * 'https://example.com/art.png',\n * {\n * artboard: { width: 1200, height: 1500, x: 250, y: 250 },\n * alignment: 'center',\n * scale: 1\n * }\n * );\n */\nexport async function createImageElementWithPlacement(\n url: string,\n placementOptions: ArtworkPlacementOptions,\n config: Partial<ImageElementConfig> = {},\n loaderOptions: ImageLoaderOptions = {}\n): Promise<{\n success: boolean;\n element?: ImageElement;\n error?: Error;\n placement?: {\n artworkSize: { width: number; height: number };\n finalSize: { width: number; height: number };\n position: { x: number; y: number };\n scale: number;\n };\n}> {\n const loadResult = await loadImageFromURL(url, loaderOptions);\n\n if (!loadResult.success || !loadResult.width || !loadResult.height) {\n return {\n success: false,\n error: loadResult.error || new Error('Failed to get image dimensions'),\n };\n }\n\n // Calculate placement using cover/contain algorithm\n const placementConfig: PlacementConfig = {\n width: placementOptions.artboard.width,\n height: placementOptions.artboard.height,\n scale: placementOptions.scale,\n align: getAlignment(placementOptions.alignment),\n offsetX: placementOptions.offsetX,\n offsetY: placementOptions.offsetY,\n scaleMode: placementOptions.scaleMode,\n marginTop: placementOptions.marginTop,\n marginRight: placementOptions.marginRight,\n marginBottom: placementOptions.marginBottom,\n marginLeft: placementOptions.marginLeft,\n };\n\n const placement = calculateArtworkPlacement(\n { width: loadResult.width, height: loadResult.height },\n placementConfig\n );\n\n // placement.x and placement.y are the TOP-LEFT corner position (relative to artboard origin)\n // ImageElement.x and ImageElement.y represent the CENTER of the image\n // So we need to convert from top-left to center position\n\n // Calculate absolute TOP-LEFT position on canvas (artboard position + relative position)\n const topLeftX = placementOptions.artboard.x + placement.x;\n const topLeftY = placementOptions.artboard.y + placement.y;\n\n // Convert to CENTER position (what ImageElement expects)\n const centerX = topLeftX + placement.width / 2;\n const centerY = topLeftY + placement.height / 2;\n\n\n // Create ImageElement with calculated dimensions and CENTER position\n // Set preserveDimensions: true to prevent the image load callback from\n // recalculating the dimensions (which would break our \"cover\" sizing)\n const imageElement = new ImageElement({\n ...config,\n x: centerX,\n y: centerY,\n imageUrl: url,\n imageAspectRatio: loadResult.aspectRatio,\n preserveDimensions: true, // Don't recalculate dimensions on image load\n transformData: {\n type: 'image',\n width: placement.width,\n height: placement.height,\n cropX: config.transformData?.cropX ?? 0,\n cropY: config.transformData?.cropY ?? 0,\n cropWidth: config.transformData?.cropWidth ?? 1,\n cropHeight: config.transformData?.cropHeight ?? 1,\n flipHorizontal: config.transformData?.flipHorizontal ?? false,\n flipVertical: config.transformData?.flipVertical ?? false,\n borderRadius: config.transformData?.borderRadius ?? 0,\n },\n });\n\n return {\n success: true,\n element: imageElement,\n placement: {\n artworkSize: { width: loadResult.width, height: loadResult.height },\n finalSize: { width: placement.width, height: placement.height },\n position: { x: centerX, y: centerY },\n scale: placement.scale,\n },\n };\n}\n\n/**\n * Check if an image URL is accessible (HEAD request)\n * More efficient than fully loading the image\n */\nexport async function checkImageUrl(url: string): Promise<boolean> {\n try {\n // Use fetch with HEAD to check if the URL is accessible\n const response = await fetch(url, {\n method: 'HEAD',\n mode: 'cors',\n });\n\n // Check if response is an image\n const contentType = response.headers.get('content-type');\n return response.ok && (contentType?.startsWith('image/') || false);\n } catch {\n // Fallback: Try to load the image directly\n // Some servers don't support HEAD requests\n const result = await loadImageFromURL(url, { timeout: 5000, retries: 0 });\n return result.success;\n }\n}\n\n/**\n * Get image dimensions without fully loading it (when possible)\n * Falls back to full load if necessary\n */\nexport async function getImageDimensions(\n url: string\n): Promise<{ width: number; height: number } | null> {\n const result = await loadImageFromURL(url, { timeout: 10000, retries: 0 });\n\n if (result.success && result.width && result.height) {\n return { width: result.width, height: result.height };\n }\n\n return null;\n}\n\n// Helper function\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * SnowconeCanvas - Main embeddable canvas component for e-commerce integration\n *\n * A self-contained canvas editor that wraps EditorProvider and handles:\n * - Artboard initialization and synchronization\n * - Image loading from URL with loading/error states\n * - Auto-export on interval or artboard change\n * - State change callbacks\n *\n * @example\n * ```tsx\n * import { SnowconeCanvas } from '@snowcone-app/canvas';\n *\n * function ProductCustomizer() {\n * return (\n * <SnowconeCanvas\n * artboards={[\n * { name: 'Front', width: 1200, height: 1200, clipShape: 'circle' },\n * { name: 'Back', width: 1200, height: 1200 },\n * ]}\n * initialImage=\"https://example.com/product.png\"\n * autoExportInterval={500}\n * onExport={(exports) => {\n * // exports = { 'Front': dataUrl, 'Back': dataUrl }\n * console.log('Design updated:', exports);\n * }}\n * />\n * );\n * }\n * ```\n */\n\nimport React, { useEffect, useMemo, useRef, useCallback, useState, useImperativeHandle, forwardRef } from 'react';\nimport { EditorProvider, useEditor } from '../../contexts/EditorContext.js';\nimport { ThemeProvider } from '../../contexts/ThemeContext.js';\nimport { KitProvider } from '../../contexts/KitContext.js';\nimport { Canvas } from './Canvas.js';\nimport { ErrorBoundary } from './ErrorBoundary.js';\nimport { useArtboards } from '../../hooks/useArtboards.js';\nimport { useExport } from '../../hooks/useExport.js';\nimport { useAutoExport } from '../../hooks/useAutoExport.js';\nimport { useContentReady } from '../../hooks/useContentReady.js';\nimport { createImageElementWithPlacement } from '../../utils/ImageLoader.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { CanvasRenderer } from '../../core/CanvasRenderer.js';\nimport type { CanvasError } from '../../types/index.js';\n\nconst log = createLogger('SnowconeCanvas');\nimport { Spinner } from './LoadingStates.js';\nimport { ElementFactory } from '../../core/ElementFactory.js';\nimport { resolveKit } from '../../kits/registry.js';\nimport { validateKit } from '../../kits/validation.js';\nimport type { KitPresetId, KitDefinition } from '../../kits/types.js';\nimport type { ClipShape, AnyElementConfig } from '../../types/index.js';\nimport type {\n PieceGuide,\n PieceGuideRendererPiece,\n PieceGuideRendererStyle,\n PieceGuideVisibility,\n} from '../../rendering/PieceGuideRenderer.js';\n\n// Re-export ClipShape type for convenience\nexport type { ClipShape } from '../../types/index.js';\n\n/** 9-point alignment grid values */\nexport type AlignmentPoint = 'tl' | 't' | 'tr' | 'l' | 'c' | 'r' | 'bl' | 'b' | 'br' | 'center' | 'top' | 'bottom' | 'left' | 'right';\n\n/** Events emitted during the export lifecycle */\nexport type ExportStatusEvent =\n | { status: 'scheduled'; artboardId: string }\n | { status: 'rendering'; artboardId: string }\n | { status: 'complete'; artboardId: string; result: Record<string, string | Blob> }\n | { status: 'error'; artboardId: string; error: Error };\n\nexport interface ArtboardConfig {\n /** Display name for the artboard (used as key in export results) */\n name: string;\n /** Width in pixels */\n width: number;\n /** Height in pixels */\n height: number;\n /** Optional clip shape for content masking */\n clipShape?: ClipShape;\n /** Optional background color */\n backgroundColor?: string;\n /** Scale mode for initial image: \"cover\" fills area (may crop), \"contain\" fits inside (may have space). Default: \"cover\" */\n scaleMode?: 'cover' | 'contain';\n /** Top margin in pixels for contain mode (reduces available area) */\n fitMarginTop?: number;\n /** Right margin in pixels for contain mode (reduces available area) */\n fitMarginRight?: number;\n /** Bottom margin in pixels for contain mode (reduces available area) */\n fitMarginBottom?: number;\n /** Left margin in pixels for contain mode (reduces available area) */\n fitMarginLeft?: number;\n /** Alignment override for contain mode (9-point grid: 'tl','t','tr','l','c','r','bl','b','br') */\n fitAlign?: AlignmentPoint;\n}\n\nexport interface CanvasState {\n /** Serialized element configurations for persistence */\n elements: AnyElementConfig[];\n /** Artboard configurations */\n artboards: ArtboardConfig[];\n /** Currently active artboard name */\n activeArtboard: string;\n}\n\n/**\n * Imperative handle for programmatic control of SnowconeCanvas\n */\nexport interface SnowconeCanvasHandle {\n /**\n * Trigger an export of artboards programmatically.\n * Returns a promise that resolves with the exported artboards.\n *\n * @param options Export options\n * @returns Promise resolving to exported artboards keyed by name\n *\n * @example\n * const canvasRef = useRef<SnowconeCanvasHandle>(null);\n * const handleExport = async () => {\n * const exports = await canvasRef.current?.exportArtboards();\n * console.log('Exported:', exports);\n * };\n */\n exportArtboards: (options?: {\n /** Export format */\n format?: 'dataUrl' | 'blob';\n /** Resolution multiplier */\n scale?: number;\n /** Export all artboards or just active */\n all?: boolean;\n }) => Promise<Record<string, string | Blob>>;\n}\n\n/**\n * Configuration for export behavior (scale, format, auto-export).\n *\n * Group these props together instead of passing them individually:\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * exportConfig={{\n * autoExportConfig: { enabled: true, debounceMs: 100, maxWaitMs: 1000 },\n * format: 'blob',\n * exportAll: true,\n * scale: 2,\n * maxSize: 4000,\n * imageFormat: 'webp',\n * imageQuality: 0.85,\n * }}\n * onExportStatus={(event) => { ... }}\n * />\n * ```\n */\nexport interface ExportConfig {\n /**\n * Smart auto-export configuration with debouncing and change detection.\n * Replaces the timer-based `autoExportInterval` with an event-driven approach.\n */\n autoExportConfig?: {\n /** Enable/disable auto-export */\n enabled?: boolean;\n /** Debounce delay - wait this long after last change before exporting (ms) */\n debounceMs?: number;\n /** Maximum wait time - force export after this duration even if changes keep coming (ms) */\n maxWaitMs?: number;\n };\n\n /**\n * Export format: 'dataUrl' (base64 string) or 'blob' (binary).\n * Blob is more efficient for API uploads.\n */\n format?: 'dataUrl' | 'blob';\n\n /**\n * If true, exports all artboards on each interval.\n * If false, only exports the active artboard.\n */\n exportAll?: boolean;\n\n /**\n * Resolution multiplier for exports.\n * Default: 2 (recommended for print quality).\n *\n * Note: This scale is applied intelligently - if the resulting image would\n * exceed `maxSize`, the scale is reduced to cap the largest dimension.\n */\n scale?: number;\n\n /**\n * Maximum export dimension (largest side) in pixels.\n * Default: 4000 (4K).\n *\n * When the artboard dimensions multiplied by scale would exceed this,\n * the effective scale is reduced to cap the output at this size.\n * Set to 0 or Infinity to disable this capping behavior.\n */\n maxSize?: number;\n\n /**\n * Image format for auto-exports.\n * Default: 'png'.\n * Use 'webp' for smaller file sizes with transparency support (good for realtime preview).\n */\n imageFormat?: 'png' | 'jpg' | 'jpeg' | 'webp';\n\n /**\n * Image quality for auto-exports (0-1).\n * Only applies to JPEG and WebP formats. PNG ignores this.\n * Default: 0.92.\n */\n imageQuality?: number;\n}\n\n/**\n * Configuration for the initial image loaded into the canvas.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * imageConfig={{\n * src: 'https://example.com/artwork.png',\n * alignment: 'center',\n * scale: 1.2,\n * scaleMode: 'contain',\n * }}\n * />\n * ```\n */\nexport interface ImageConfig {\n /**\n * URL of initial image to load into the canvas.\n * Shows loading state while fetching, error state on failure.\n */\n src?: string;\n\n /**\n * Alignment for the initial image on the artboard.\n * Uses a 9-point grid: 'tl', 't', 'tr', 'l', 'c', 'r', 'bl', 'b', 'br'\n * Or semantic values: 'center', 'top', 'bottom', 'left', 'right'\n * Default: 'center'\n */\n alignment?: AlignmentPoint;\n\n /**\n * Scale multiplier for the initial image.\n * Default: 1 (image covers artboard exactly)\n * Values > 1 make the image larger, < 1 make it smaller.\n */\n scale?: number;\n\n /**\n * Scale mode for initial image placement.\n * - \"cover\" (default): image fills artboard completely, may crop edges\n * - \"contain\": image fits entirely inside artboard, may have empty space\n */\n scaleMode?: 'cover' | 'contain';\n}\n\n/**\n * Configuration for layout and visual appearance of the canvas.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * layoutConfig={{\n * width: 1200,\n * height: 1200,\n * viewPadding: 0.85,\n * artboardBorderRadius: 12,\n * showToolbar: false,\n * showLayers: false,\n * }}\n * />\n * ```\n */\nexport interface LayoutConfig {\n /**\n * Artboard width (shorthand for single artboard).\n * If `artboards` prop is provided, this is ignored.\n */\n width?: number;\n\n /**\n * Artboard height (shorthand for single artboard).\n * If `artboards` prop is provided, this is ignored.\n */\n height?: number;\n\n /**\n * Controls how much of the container the artboard fills.\n * Value between 0 and 1, where 1 means the artboard fills 100% of the container.\n * Default is 0.9 (90%).\n */\n viewPadding?: number;\n\n /**\n * Border radius for artboard corners in pixels.\n * This is a visual-only effect - exports will still have the full artboard.\n * @default 0\n */\n artboardBorderRadius?: number;\n\n /**\n * Fixed screen-space margin in pixels.\n * When set, the artboard will have this exact left/top margin regardless of zoom level.\n * Overrides viewPadding for positioning.\n */\n fixedMargin?: number;\n\n /**\n * Maximum HTML height in pixels.\n * When the artboard (with vertical fixedMargin padding) would exceed this height,\n * horizontal padding is increased to shrink the artboard until it fits.\n */\n maxHeight?: number;\n\n /**\n * Show toolbar UI.\n * Default: false (canvas-only mode).\n */\n showToolbar?: boolean;\n\n /**\n * Show layers panel.\n * Default: false (canvas-only mode).\n */\n showLayers?: boolean;\n\n /**\n * Whether to show the rotation handle on the canvas.\n * Set to false for embedded mode where rotation is handled via toolbar panel.\n * @default true\n */\n showRotationHandle?: boolean;\n\n /**\n * When true, hides the internal Canvas component.\n * Use this when rendering a separate Canvas (e.g., in fullscreen mode)\n * that shares the same EditorProvider context.\n */\n hideCanvas?: boolean;\n\n /**\n * CSS class name for the canvas wrapper element.\n */\n canvasWrapperClassName?: string;\n\n /**\n * Inline styles for the canvas wrapper element.\n */\n canvasWrapperStyle?: React.CSSProperties;\n\n /**\n * ADR-0054 Phase 4: cutout shapes (in artboard-space px) that get\n * punched through the `<canvas>` element as truly transparent holes,\n * revealing whatever sits behind SnowconeCanvas in the DOM. Internally\n * translated to a CSS `clip-path: path(evenodd, ...)` that accounts\n * for the canvas's padding margin and zoom. Each shape is a rect,\n * rounded rect, or SVG path — the same `SvgPath` shape used by the\n * `VisualGuideOverlay`.\n */\n canvasCutouts?: CanvasCutoutShape[];\n}\n\n/**\n * ADR-0054: piece-guide config passed to `SnowconeCanvas`. The render\n * loop calls `renderPieceGuides` with these values after drawing the\n * artboard and its elements, so boundary/zone/label coordinates are in\n * the artboard's local pixel frame and compose cleanly with the canvas\n * viewport transform.\n */\nexport interface PieceGuidesConfig {\n /** Pieces (artboard-space rects) the guides attach to. */\n pieces: PieceGuideRendererPiece[];\n /** Authored guides keyed by piece id. Pieces without a guide fall back to the piece rect. */\n guides?: Record<string, PieceGuide>;\n /** Per-layer visibility toggles. All default to true. */\n show?: PieceGuideVisibility;\n /** Optional style overrides. */\n style?: PieceGuideRendererStyle;\n}\n\n/** ADR-0054 Phase 4: shape union used for `canvasCutouts`. Mirrors the\n * `SvgPath` shape in `VisualGuideOverlay`. Coordinates are in artboard\n * pixel units (the canvas's intrinsic/native size). */\nexport type CanvasCutoutShape =\n | { kind: \"rect\"; x: number; y: number; width: number; height: number }\n | {\n kind: \"roundedRect\";\n x: number;\n y: number;\n width: number;\n height: number;\n rx?: number;\n ry?: number;\n }\n | { kind: \"path\"; d: string };\n\n/**\n * Props for the SnowconeCanvas component.\n *\n * Props can be passed individually (flat) or grouped using config objects.\n * When both are provided, individual props take precedence over config values.\n *\n * @example Grouped config pattern (recommended for new code)\n * ```tsx\n * <SnowconeCanvas\n * artboards={[{ name: 'Front', width: 1200, height: 1200 }]}\n * imageConfig={{\n * src: 'https://example.com/artwork.png',\n * alignment: 'center',\n * scaleMode: 'contain',\n * }}\n * exportConfig={{\n * autoExportConfig: { enabled: true, debounceMs: 100 },\n * format: 'blob',\n * scale: 2,\n * imageFormat: 'webp',\n * }}\n * layoutConfig={{\n * viewPadding: 0.85,\n * artboardBorderRadius: 12,\n * showRotationHandle: false,\n * }}\n * onExportStatus={(event) => { ... }}\n * />\n * ```\n *\n * @example Flat props (backwards compatible, still fully supported)\n * ```tsx\n * <SnowconeCanvas\n * artboards={[{ name: 'Front', width: 1200, height: 1200 }]}\n * initialImage=\"https://example.com/artwork.png\"\n * autoExportConfig={{ enabled: true, debounceMs: 100 }}\n * autoExportFormat=\"blob\"\n * exportScale={2}\n * viewPadding={0.85}\n * onExportStatus={(event) => { ... }}\n * />\n * ```\n */\nexport interface SnowconeCanvasProps {\n // === Config object props (grouped) ===\n\n /**\n * Grouped export configuration. See {@link ExportConfig}.\n * Individual export props (autoExportConfig, autoExportFormat, etc.) take precedence if also provided.\n */\n exportConfig?: ExportConfig;\n\n /**\n * Grouped initial image configuration. See {@link ImageConfig}.\n * Individual image props (initialImage, initialImageAlignment, etc.) take precedence if also provided.\n */\n imageConfig?: ImageConfig;\n\n /**\n * Grouped layout/appearance configuration. See {@link LayoutConfig}.\n * Individual layout props (width, height, viewPadding, etc.) take precedence if also provided.\n */\n layoutConfig?: LayoutConfig;\n\n // === Kit configuration ===\n\n /**\n * Editor kit configuration. Use a preset name ('compact-customizer', 'pro-studio', 'embed-only')\n * or a custom KitDefinition for full control over layout, capabilities, and behavior.\n * @default 'pro-studio'\n */\n kit?: KitPresetId | KitDefinition;\n\n // === Artboards (placements) ===\n\n /**\n * Array of artboard configurations.\n * Each artboard represents a placement area (e.g., 'Front', 'Back').\n */\n artboards?: ArtboardConfig[];\n\n /**\n * Currently active artboard by name.\n * Used for controlled artboard selection.\n */\n activeArtboard?: string;\n\n /**\n * Callback when user switches artboards.\n * Receives the name of the newly active artboard.\n */\n onArtboardChange?: (name: string) => void;\n\n // === Single artboard shorthand ===\n\n /**\n * Artboard width (shorthand for single artboard).\n * If `artboards` prop is provided, this is ignored.\n * @deprecated Use `layoutConfig.width` instead.\n */\n width?: number;\n\n /**\n * Artboard height (shorthand for single artboard).\n * If `artboards` prop is provided, this is ignored.\n * @deprecated Use `layoutConfig.height` instead.\n */\n height?: number;\n\n // === Initial content ===\n\n /**\n * URL of initial image to load into the canvas.\n * Shows loading state while fetching, error state on failure.\n *\n * The image will be sized to \"cover\" the artboard (like CSS object-fit: cover)\n * and positioned according to `initialImageAlignment`.\n * @deprecated Use `imageConfig.src` instead.\n */\n initialImage?: string;\n\n /**\n * Alignment for the initial image on the artboard.\n * Uses a 9-point grid: 'tl', 't', 'tr', 'l', 'c', 'r', 'bl', 'b', 'br'\n * Or semantic values: 'center', 'top', 'bottom', 'left', 'right'\n *\n * Default: 'center' (places image centered on artboard)\n * @deprecated Use `imageConfig.alignment` instead.\n */\n initialImageAlignment?: AlignmentPoint;\n\n /**\n * Scale multiplier for the initial image.\n * Default: 1 (image covers artboard exactly)\n * Values > 1 make the image larger, < 1 make it smaller.\n * @deprecated Use `imageConfig.scale` instead.\n */\n initialImageScale?: number;\n\n /**\n * Scale mode for initial image placement.\n * - \"cover\" (default): image fills artboard completely, may crop edges\n * - \"contain\": image fits entirely inside artboard, may have empty space\n * @deprecated Use `imageConfig.scaleMode` instead.\n */\n initialImageScaleMode?: 'cover' | 'contain';\n\n /**\n * Serialized element configurations for restoring saved state.\n * Use this to restore a previously saved design.\n */\n initialElements?: AnyElementConfig[];\n\n // === State callbacks ===\n\n /**\n * Called when canvas content changes (elements added, modified, or removed).\n * Use this to persist the design state.\n */\n onChange?: (state: CanvasState) => void;\n\n /**\n * Called when selection changes.\n * Receives null when nothing is selected.\n */\n onSelectionChange?: (elementId: string | null) => void;\n\n // === Auto export ===\n\n /**\n * Interval in milliseconds for automatic exports.\n * Set to 0 or undefined to disable auto-export.\n * Recommended: 500ms for responsive preview updates.\n *\n * @deprecated Use `autoExportConfig` or `exportConfig.autoExportConfig` instead, which provides smart debouncing and change detection rather than fixed-interval polling.\n */\n autoExportInterval?: number;\n\n /**\n * Smart auto-export configuration with debouncing and change detection.\n * Replaces the timer-based `autoExportInterval` with an event-driven approach.\n * @deprecated Use `exportConfig.autoExportConfig` instead.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * autoExportConfig={{\n * enabled: true,\n * debounceMs: 100, // Wait 100ms after last change\n * maxWaitMs: 1000, // Force export after 1s of continuous changes\n * }}\n * onExport={(exports) => console.log('Exported:', exports)}\n * />\n * ```\n */\n autoExportConfig?: {\n /** Enable/disable auto-export */\n enabled?: boolean;\n /** Debounce delay - wait this long after last change before exporting (ms) */\n debounceMs?: number;\n /** Maximum wait time - force export after this duration even if changes keep coming (ms) */\n maxWaitMs?: number;\n };\n\n /**\n * Export format: 'dataUrl' (base64 string) or 'blob' (binary).\n * Blob is more efficient for API uploads.\n * @deprecated Use `exportConfig.format` instead.\n */\n autoExportFormat?: 'dataUrl' | 'blob';\n\n /**\n * If true, exports all artboards on each interval.\n * If false, only exports the active artboard.\n * @deprecated Use `exportConfig.exportAll` instead.\n */\n autoExportAll?: boolean;\n\n /**\n * Resolution multiplier for exports.\n * Default: 2 (recommended for print quality).\n *\n * Note: This scale is applied intelligently - if the resulting image would\n * exceed `maxExportSize`, the scale is reduced to cap the largest dimension.\n * @deprecated Use `exportConfig.scale` instead.\n */\n exportScale?: number;\n\n /**\n * Maximum export dimension (largest side) in pixels.\n * Default: 4000 (4K).\n *\n * When the artboard dimensions multiplied by exportScale would exceed this,\n * the effective scale is reduced to cap the output at this size.\n *\n * For example:\n * - Artboard 3951x4800 with exportScale=2 would normally be 7902x9600\n * - With maxExportSize=4000, it stays at 3951x4800 (scale effectively 1)\n * - Artboard 1200x1200 with exportScale=2 becomes 2400x2400 (under limit)\n *\n * Set to 0 or Infinity to disable this capping behavior.\n * @deprecated Use `exportConfig.maxSize` instead.\n */\n maxExportSize?: number;\n\n /**\n * Image format for auto-exports.\n * Default: 'png'.\n * Use 'webp' for smaller file sizes with transparency support (good for realtime preview).\n * @deprecated Use `exportConfig.imageFormat` instead.\n */\n exportImageFormat?: 'png' | 'jpg' | 'jpeg' | 'webp';\n\n /**\n * Image quality for auto-exports (0-1).\n * Only applies to JPEG and WebP formats. PNG ignores this.\n * Default: 0.92.\n * @deprecated Use `exportConfig.imageQuality` instead.\n */\n exportImageQuality?: number;\n\n /**\n * Called with export results.\n * Results are keyed by artboard name.\n *\n * @deprecated Use `onExportStatus` for a unified export lifecycle callback, or `onExportReady` for on-demand export control.\n */\n onExport?: (exports: Record<string, string | Blob>) => void;\n\n /**\n * Called immediately when an export is scheduled (before debounce).\n * Use this to show loading indicators right away instead of waiting for export to complete.\n * This fires as soon as a change is detected, giving consumers early warning.\n *\n * @deprecated Use `onExportStatus` instead, which provides `{ status: 'scheduled' }` events along with the full export lifecycle.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onExportScheduled={() => {\n * // Show shimmer/loading immediately\n * setIsExporting(true);\n * }}\n * onExport={(exports) => {\n * // Hide shimmer after export completes\n * setIsExporting(false);\n * }}\n * />\n * ```\n */\n onExportScheduled?: () => void;\n\n /**\n * Unified export status callback. Prefer this over individual `onExport`/`onExportScheduled` callbacks.\n *\n * Receives events for each phase of the export lifecycle:\n * - `scheduled` - Export was requested (before debounce completes)\n * - `rendering` - Export rendering has started\n * - `complete` - Export finished successfully with results\n * - `error` - Export failed with an error\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onExportStatus={(event) => {\n * switch (event.status) {\n * case 'scheduled':\n * setIsExporting(true);\n * break;\n * case 'complete':\n * setIsExporting(false);\n * uploadResults(event.result);\n * break;\n * case 'error':\n * setIsExporting(false);\n * showError(event.error);\n * break;\n * }\n * }}\n * />\n * ```\n */\n onExportStatus?: (event: ExportStatusEvent) => void;\n\n /**\n * Callback that provides an export function.\n * Called once when the canvas is ready.\n * Use this for manual/on-demand exports triggered by UI buttons.\n *\n * @example\n * ```tsx\n * const [exportFn, setExportFn] = useState<(() => Promise<Record<string, string | Blob>>) | null>(null);\n *\n * <SnowconeCanvas\n * onExportReady={(fn) => setExportFn(() => fn)}\n * ...\n * />\n *\n * <button onClick={() => exportFn?.()}>Export</button>\n * ```\n */\n onExportReady?: (exportFn: () => Promise<Record<string, string | Blob>>) => void;\n\n // === Image loading callbacks ===\n\n /**\n * Called when initialImage loads successfully.\n */\n onImageLoad?: (src: string) => void;\n\n /**\n * Called when initialImage fails to load.\n */\n onImageError?: (src: string, error: Error) => void;\n\n // === Error handling ===\n\n /**\n * General error callback for recoverable and non-recoverable canvas errors.\n * Receives a structured CanvasError with category, message, and context.\n *\n * This fires for:\n * - Image loading failures (category: 'image')\n * - Export errors (category: 'export')\n * - Element import/deserialization errors (category: 'import')\n * - Unexpected React crashes caught by ErrorBoundary (category: 'unknown')\n *\n * Existing `onExportStatus` and `onImageError` callbacks continue to work.\n * `onError` provides a unified stream of all errors for logging/analytics.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onError={(error) => {\n * console.error(`[${error.category}] ${error.message}`);\n * if (!error.recoverable) {\n * analytics.track('canvas_crash', { category: error.category });\n * }\n * }}\n * />\n * ```\n */\n onError?: (error: CanvasError) => void;\n\n // === Appearance ===\n\n /**\n * Theme name (reserved for future use).\n */\n theme?: string;\n\n /**\n * When true, skips the internal ThemeProvider and inherits theme from parent.\n * Use this when embedding SnowconeCanvas in an app that already manages themes.\n * This prevents the canvas from overriding the parent app's theme on document.documentElement.\n *\n * @default false\n */\n inheritTheme?: boolean;\n\n /**\n * When true, assumes EditorProvider already exists in a parent component.\n * Skips creating the internal EditorProvider so multiple SnowconeCanvas instances\n * can share the same editor state (e.g., when conditionally rendering mobile/desktop layouts).\n *\n * @default false\n */\n externalProvider?: boolean;\n\n /**\n * CSS class name for the container.\n */\n className?: string;\n\n /**\n * Inline styles for the container.\n */\n style?: React.CSSProperties;\n\n /**\n * CSS class name for the canvas wrapper element.\n * Use this to apply CSS masks, clips, or other effects to just the canvas\n * without affecting the overlay elements.\n * @deprecated Use `layoutConfig.canvasWrapperClassName` instead.\n */\n canvasWrapperClassName?: string;\n\n /**\n * Inline styles for the canvas wrapper element.\n * Use this to apply CSS masks, clips, or other effects to just the canvas\n * without affecting the overlay elements.\n * @deprecated Use `layoutConfig.canvasWrapperStyle` instead.\n */\n canvasWrapperStyle?: React.CSSProperties;\n\n /**\n * ADR-0054 Phase 4: shapes punched through the `<canvas>` as truly\n * transparent holes. See matching field on LayoutConfig for details.\n * Takes precedence over `layoutConfig.canvasCutouts`.\n */\n canvasCutouts?: CanvasCutoutShape[];\n\n /**\n * ADR-0054: piece boundary / safe-area / zone / label overlays drawn\n * directly into the canvas render loop (no DOM SVG overlay). Pieces\n * are in artboard-space coordinates (same frame as placements and\n * `canvasCutouts`). `guides` is keyed by piece id. Supply `undefined`\n * or omit to skip piece-guide rendering entirely.\n *\n * Replaces the old `VisualGuideOverlay` sibling-overlay pattern —\n * drawing directly in the ctx means no fixedMargin-unit / aspect-\n * ratio-letterbox / canvas-wrapper-negative-margin shenanigans.\n */\n pieceGuides?: PieceGuidesConfig;\n\n /**\n * ADR-0060: viewport focus mode for multi-piece spread layouts.\n *\n * - Omit / `'spread'` — show the entire artboard with all pieces\n * visible (historical behaviour). The toggle is a no-op for\n * single-piece layouts.\n * - `{ pieceId: 'front' }` — fit the viewport to that piece's\n * rect plus a peek margin so neighbour pieces remain visible\n * at the edges (dimmed) for overflow feedback. The piece id\n * must match an entry in `pieceGuides.pieces[]`; unrecognised\n * ids fall back to spread mode.\n *\n * Element data is unaffected — elements always live in spread\n * (artboard) coordinates regardless of focus. Switching focus is\n * purely an editor viewport / chrome decision.\n */\n pieceFocus?: 'spread' | { pieceId: string };\n\n // === Feature flags ===\n\n /**\n * Show toolbar UI (reserved for future use).\n * Default: false (canvas-only mode).\n * @deprecated Use `layoutConfig.showToolbar` instead.\n */\n showToolbar?: boolean;\n\n /**\n * Show layers panel (reserved for future use).\n * Default: false (canvas-only mode).\n * @deprecated Use `layoutConfig.showLayers` instead.\n */\n showLayers?: boolean;\n\n /**\n * Enable keyboard shortcuts.\n * Default: true.\n */\n enableShortcuts?: boolean;\n\n // === Overlay ===\n\n /**\n * Render custom controls on top of the canvas.\n * Useful for adding menu buttons, toolbars, etc.\n * The overlay is rendered inside the EditorProvider context.\n */\n overlay?: React.ReactNode;\n\n /**\n * When true, hides the internal Canvas component.\n * Use this when rendering a separate Canvas (e.g., in fullscreen mode)\n * that shares the same EditorProvider context.\n * This prevents two Canvas components from fighting over the same refs.\n * @deprecated Use `layoutConfig.hideCanvas` instead.\n */\n hideCanvas?: boolean;\n\n // === View configuration ===\n\n /**\n * Controls how much of the container the artboard fills.\n * Value between 0 and 1, where 1 means the artboard fills 100% of the container.\n * Default is 0.9 (90%), leaving 5% padding on each side.\n * Use smaller values (e.g., 0.8) to show more canvas area around the artboard,\n * which is useful for seeing selection handles that extend beyond the artboard.\n * @deprecated Use `layoutConfig.viewPadding` instead.\n */\n viewPadding?: number;\n\n /**\n * Border radius for artboard corners in pixels.\n * Use this to give the artboard rounded corners in the editor.\n * This is a visual-only effect - exports will still have the full artboard.\n * @default 0\n * @deprecated Use `layoutConfig.artboardBorderRadius` instead.\n */\n artboardBorderRadius?: number;\n\n /**\n * Fixed screen-space margin in pixels.\n * When set, the artboard will have this exact left/top margin regardless of zoom level.\n * This is useful for aligning the artboard with fixed-position UI elements like headers.\n * Overrides viewPadding for positioning (viewPadding still affects zoom calculation).\n * @deprecated Use `layoutConfig.fixedMargin` instead.\n */\n fixedMargin?: number;\n\n /**\n * Maximum HTML height in pixels.\n * When the artboard (with vertical fixedMargin padding) would exceed this height,\n * horizontal padding is increased to shrink the artboard until it fits within\n * this constraint while still respecting vertical fixedMargin.\n * @deprecated Use `layoutConfig.maxHeight` instead.\n */\n maxHeight?: number;\n\n /**\n * Whether to show the rotation handle on the canvas.\n * Set to false for embedded mode where rotation is handled via toolbar panel.\n * @default true\n * @deprecated Use `layoutConfig.showRotationHandle` instead.\n */\n showRotationHandle?: boolean;\n\n /**\n * Called once when the canvas is fully ready for interaction and export.\n * Fires after: artboards initialized, initial elements loaded, fonts ready,\n * and at least one render frame completed.\n */\n onReady?: () => void;\n}\n\ntype ImageLoadingState = 'idle' | 'loading' | 'success' | 'error';\n\n/**\n * Calculate the effective export scale for an artboard.\n * If the scaled dimensions would exceed maxSize, reduces the scale to cap at maxSize.\n *\n * @param width - Artboard width in pixels\n * @param height - Artboard height in pixels\n * @param requestedScale - The requested export scale multiplier\n * @param maxSize - Maximum dimension (largest side) for exported image\n * @returns The effective scale to use (may be lower than requestedScale)\n */\nfunction calculateEffectiveScale(\n width: number,\n height: number,\n requestedScale: number,\n maxSize: number\n): number {\n // If maxSize is 0 or Infinity, don't cap\n if (maxSize <= 0 || !isFinite(maxSize)) {\n return requestedScale;\n }\n\n const scaledWidth = width * requestedScale;\n const scaledHeight = height * requestedScale;\n const largestScaledSide = Math.max(scaledWidth, scaledHeight);\n\n // If already under the limit, use requested scale\n if (largestScaledSide <= maxSize) {\n return requestedScale;\n }\n\n // Calculate the scale that would result in exactly maxSize for the largest side\n const largestSide = Math.max(width, height);\n const cappedScale = maxSize / largestSide;\n\n // Don't scale up if we're capping (scale should be at most 1 if artboard is already >= maxSize)\n return Math.min(cappedScale, requestedScale);\n}\n\n/**\n * Inner component with access to EditorContext\n */\nconst SnowconeCanvasInner = forwardRef<SnowconeCanvasHandle, SnowconeCanvasProps>((props, ref) => {\n // === Merge config objects with individual props (individual props take precedence) ===\n const {\n exportConfig: _exportConfig,\n imageConfig: _imageConfig,\n layoutConfig: _layoutConfig,\n kit,\n artboards: artboardConfigs,\n activeArtboard: controlledActiveArtboard,\n onArtboardChange,\n initialElements,\n onChange,\n onSelectionChange,\n autoExportInterval: autoExportInterval_prop,\n onExport,\n onExportScheduled,\n onExportStatus,\n onExportReady,\n onImageLoad,\n onImageError,\n onError,\n onReady,\n className,\n style,\n enableShortcuts: enableShortcuts_prop,\n overlay,\n } = props;\n\n // Export config: individual props override exportConfig values\n const autoExportInterval = autoExportInterval_prop;\n const autoExportConfig = props.autoExportConfig ?? _exportConfig?.autoExportConfig;\n const autoExportFormat = props.autoExportFormat ?? _exportConfig?.format ?? 'dataUrl';\n const autoExportAll = props.autoExportAll ?? _exportConfig?.exportAll ?? false;\n const exportScale = props.exportScale ?? _exportConfig?.scale ?? 2;\n const maxExportSize = props.maxExportSize ?? _exportConfig?.maxSize ?? 4000;\n const exportImageFormat = props.exportImageFormat ?? _exportConfig?.imageFormat ?? 'png';\n const exportImageQuality = props.exportImageQuality ?? _exportConfig?.imageQuality ?? 0.92;\n\n // Image config: individual props override imageConfig values\n const initialImage = props.initialImage ?? _imageConfig?.src;\n const initialImageAlignment = props.initialImageAlignment ?? _imageConfig?.alignment ?? 'center';\n const initialImageScale = props.initialImageScale ?? _imageConfig?.scale ?? 1;\n const initialImageScaleMode = props.initialImageScaleMode ?? _imageConfig?.scaleMode ?? 'cover';\n\n // Layout config: individual props override layoutConfig values\n const width = props.width ?? _layoutConfig?.width ?? 1200;\n const height = props.height ?? _layoutConfig?.height ?? 1200;\n const viewPadding = props.viewPadding ?? _layoutConfig?.viewPadding ?? 0.9;\n const artboardBorderRadius = props.artboardBorderRadius ?? _layoutConfig?.artboardBorderRadius ?? 0;\n const fixedMargin = props.fixedMargin ?? _layoutConfig?.fixedMargin;\n const maxHeight = props.maxHeight ?? _layoutConfig?.maxHeight;\n const showRotationHandle = props.showRotationHandle ?? _layoutConfig?.showRotationHandle ?? true;\n const hideCanvas = props.hideCanvas ?? _layoutConfig?.hideCanvas ?? false;\n const canvasWrapperClassName = props.canvasWrapperClassName ?? _layoutConfig?.canvasWrapperClassName;\n const canvasWrapperStyle = props.canvasWrapperStyle ?? _layoutConfig?.canvasWrapperStyle;\n const canvasCutouts = props.canvasCutouts ?? _layoutConfig?.canvasCutouts;\n const pieceGuides = props.pieceGuides;\n const pieceFocus = props.pieceFocus;\n const enableShortcuts = enableShortcuts_prop ?? true;\n\n // === Resolve kit configuration ===\n const resolvedKit = useMemo(() => resolveKit(kit ?? 'pro-studio'), [kit]);\n\n // === Validate kit in development ===\n useEffect(() => {\n if (process.env.NODE_ENV === 'development' && resolvedKit) {\n const result = validateKit(resolvedKit);\n if (!result.valid) {\n log.warn('Kit validation warnings:', result.errors);\n }\n if (result.warnings.length > 0) {\n log.warn('Kit validation warnings:', result.warnings);\n }\n }\n }, [resolvedKit]);\n\n // === Error emission helper ===\n // Stable ref to avoid re-creating callbacks when onError changes\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const emitError = useCallback((error: CanvasError) => {\n log.error(`[${error.category}] ${error.message}`, error.originalError);\n onErrorRef.current?.(error);\n }, []);\n\n // Wire CanvasRenderer's static error callback to emitError\n useEffect(() => {\n CanvasRenderer.onRenderError = emitError;\n return () => {\n // Only clear if it's still our callback (avoid removing another instance's callback)\n if (CanvasRenderer.onRenderError === emitError) {\n CanvasRenderer.onRenderError = null;\n }\n };\n }, [emitError]);\n\n const {\n elements,\n setElements,\n selectedId,\n artboardManager,\n refreshArtboards,\n historyManager,\n isCanvasReady,\n setCanvasReady,\n } = useEditor();\n\n const { createArtboard, selectArtboard, artboards } = useArtboards();\n const { exportArtboard, exportAllArtboards, exportArtboardAsBlob, exportAllArtboardsAsBlobs } = useExport();\n\n // Expose imperative handle for programmatic export (ref-based API)\n useImperativeHandle(ref, () => ({\n exportArtboards: async (options = {}) => {\n const format = options.format || autoExportFormat;\n const requestedScale = options.scale || exportScale;\n const all = options.all ?? false;\n\n // Calculate effective scale (capped to maxExportSize)\n let effectiveScale = requestedScale;\n if (maxExportSize > 0 && isFinite(maxExportSize)) {\n for (const ab of artboards) {\n const abEffectiveScale = calculateEffectiveScale(ab.width, ab.height, requestedScale, maxExportSize);\n effectiveScale = Math.min(effectiveScale, abEffectiveScale);\n }\n }\n\n\n const exportOptions = { scale: effectiveScale, format: exportImageFormat as 'png' | 'jpg' | 'jpeg' | 'webp', quality: exportImageQuality };\n\n if (all) {\n // Export all artboards (results keyed by artboard ID)\n const resultsById = format === 'blob'\n ? await exportAllArtboardsAsBlobs(exportOptions)\n : await exportAllArtboards(exportOptions);\n\n // Transform keys from artboard ID to artboard name\n const resultsByName: Record<string, string | Blob> = {};\n for (const [artboardId, result] of Object.entries(resultsById)) {\n const artboard = artboards.find(ab => ab.id === artboardId);\n if (artboard) {\n resultsByName[artboard.name] = result;\n }\n }\n return resultsByName;\n } else {\n // Export active artboard only\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) {\n throw new Error('[SnowconeCanvas] No active artboard found');\n }\n\n if (format === 'blob') {\n const blob = await exportArtboardAsBlob(activeArtboard.id, exportOptions);\n return { [activeArtboard.name]: blob };\n } else {\n const dataUrl = await exportArtboard(activeArtboard.id, exportOptions);\n return { [activeArtboard.name]: dataUrl };\n }\n }\n },\n }), [exportArtboard, exportAllArtboards, exportArtboardAsBlob, exportAllArtboardsAsBlobs, autoExportFormat, exportScale, maxExportSize, exportImageFormat, exportImageQuality, artboardManager, artboards]);\n\n // Provide export function via callback (modern React pattern, works with dynamic imports)\n useEffect(() => {\n if (onExportReady) {\n const exportFn = async () => {\n\n // Get the active artboard\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) {\n throw new Error('[SnowconeCanvas] No active artboard found');\n }\n\n // Calculate effective scale (capped to maxExportSize)\n const effectiveScale = calculateEffectiveScale(\n activeArtboard.width,\n activeArtboard.height,\n exportScale,\n maxExportSize\n );\n\n\n // Export as single artboard (returns string or blob)\n const result = autoExportFormat === 'blob'\n ? await exportArtboardAsBlob(activeArtboard.id, { scale: effectiveScale })\n : await exportArtboard(activeArtboard.id, { scale: effectiveScale });\n\n // Return as Record keyed by artboard name (to match expected interface)\n return { [activeArtboard.name]: result };\n };\n onExportReady(exportFn);\n }\n }, [onExportReady, exportArtboard, exportArtboardAsBlob, artboardManager, autoExportFormat, exportScale, maxExportSize]);\n\n // Image loading state\n const [imageLoadingState, setImageLoadingState] = useState<ImageLoadingState>('idle');\n const [imageError, setImageError] = useState<Error | null>(null);\n const prevImageRef = useRef<string | undefined>(undefined);\n\n // Track if initial setup is complete\n const initializedRef = useRef(false);\n const initialElementsLoadedRef = useRef(false);\n const [initialElementsLoadedState, setInitialElementsLoadedState] = useState(false);\n\n // Export tracking\n const lastExportRef = useRef<number>(0);\n const exportCounterRef = useRef<number>(0);\n\n // Track if initial artboard sync is complete (to suppress premature onArtboardChange calls)\n // During initialization, createArtboard sets each new artboard as active, so the last one\n // ends up active. We only want to fire onArtboardChange after the controlled prop is synced.\n const hasInitialSyncRef = useRef(false);\n\n // === Initialize artboards ===\n useEffect(() => {\n if (initializedRef.current) return;\n initializedRef.current = true;\n\n // Clear existing artboards and create new ones from config\n const configs = artboardConfigs || [{ name: 'Design', width, height }];\n\n // Update the first (default) artboard instead of creating a new one\n const existingArtboards = artboardManager.getAllArtboards();\n if (existingArtboards.length > 0) {\n const firstConfig = configs[0];\n const firstArtboard = existingArtboards[0];\n\n // ADR-0054: piece-guide products want transparent artboards by default.\n // ArtboardElement constructor seeds `backgroundColor = '#ffffff'` and\n // every paint pass that consumes that field is *supposed* to be gated\n // on `!pieceGuides`, but the safety net here means a missed gate\n // can't leak a white frame past the silhouettes once the spread\n // clip lands in CanvasRenderer. Authors who explicitly set a\n // backgroundColor still win.\n const defaultBg = pieceGuides ? 'transparent' : undefined;\n\n // Update the first artboard\n firstArtboard.name = firstConfig.name;\n firstArtboard.width = firstConfig.width;\n firstArtboard.height = firstConfig.height;\n firstArtboard.clipShape = firstConfig.clipShape;\n if (firstConfig.backgroundColor) {\n firstArtboard.backgroundColor = firstConfig.backgroundColor;\n } else if (defaultBg) {\n firstArtboard.backgroundColor = defaultBg;\n }\n\n // Create additional artboards\n for (let i = 1; i < configs.length; i++) {\n createArtboard(configs[i].width, configs[i].height, {\n name: configs[i].name,\n backgroundColor: configs[i].backgroundColor ?? defaultBg,\n clipShape: configs[i].clipShape,\n });\n }\n }\n\n refreshArtboards();\n }, []);\n\n // === Load initial elements (restore from saved state) ===\n useEffect(() => {\n if (!initialElements || initialElements.length === 0) return;\n if (initialElementsLoadedRef.current) return;\n if (!initializedRef.current) return;\n\n initialElementsLoadedRef.current = true;\n setInitialElementsLoadedState(true);\n\n try {\n const deserialized = ElementFactory.createManyFromJSON(initialElements);\n\n // Assign all elements to the first artboard\n const allArtboards = artboardManager.getAllArtboards();\n if (allArtboards.length > 0) {\n const firstArtboard = allArtboards[0];\n for (const el of deserialized) {\n artboardManager.addElementToArtboard(el.id, firstArtboard.id);\n }\n }\n\n setElements(deserialized as typeof elements);\n\n // Custom fonts referenced by deserialised text elements need\n // an `@font-face` rule before the browser can paint them. The\n // canvas package itself doesn't inject those rules at startup —\n // only the font browser does, on user interaction. So on\n // refresh, the saved `fontFamily` falls through to a system\n // font; selecting the element opened the font browser, which\n // injected the CSS link, and the eventual re-render then\n // showed the correct face.\n //\n // Fix: for every unique non-system family in the restored\n // elements, inject a Google Fonts CSS link (best-effort —\n // matches `UnifiedFontService.loadGoogleFont`'s URL shape).\n // Once each link's stylesheet loads (which registers the\n // `@font-face` with the document), `document.fonts.load(...)`\n // pulls the actual font file, and a no-op `setElements`\n // identity update forces one more paint. Monotype fonts\n // fall back gracefully — link load fails, the render keeps\n // the system fallback.\n const familySet = new Set<string>();\n for (const el of deserialized) {\n const family = (el as { fontFamily?: unknown }).fontFamily;\n if (typeof family === 'string' && family.trim().length > 0) {\n familySet.add(family);\n }\n }\n if (familySet.size > 0 && typeof document !== 'undefined') {\n const fontsApi =\n (document as Document & { fonts?: FontFaceSet }).fonts ?? null;\n const linkPromises = Array.from(familySet).map((family) => {\n const linkId = `font-${family.replace(/\\s+/g, '-').toLowerCase()}`;\n let link = document.getElementById(linkId) as HTMLLinkElement | null;\n if (!link) {\n link = document.createElement('link');\n link.id = linkId;\n link.rel = 'stylesheet';\n link.href = `https://fonts.googleapis.com/css2?family=${family.replace(\n /\\s+/g,\n '+',\n )}:wght@400;700&display=swap`;\n document.head.appendChild(link);\n }\n return new Promise<string>((resolve) => {\n // Already loaded? Resolve immediately.\n if ((link as HTMLLinkElement).sheet) {\n resolve(family);\n return;\n }\n const finish = () => resolve(family);\n link!.addEventListener('load', finish, { once: true });\n link!.addEventListener('error', finish, { once: true });\n // Safety timeout — if neither event fires, don't hang.\n setTimeout(finish, 4000);\n });\n });\n Promise.all(linkPromises)\n .then((families) => {\n if (!fontsApi) return undefined;\n return Promise.all(\n families.flatMap((family) => [\n fontsApi.load(`400 16px \"${family}\"`).catch(() => undefined),\n fontsApi.load(`700 16px \"${family}\"`).catch(() => undefined),\n ]),\n );\n })\n .then(() => {\n // New array reference forces dependents (render loop's\n // memoised `elementsByArtboard` keys off `elements`) to\n // re-evaluate, so the canvas re-paints with the now-\n // available font faces.\n setElements((prev) => prev.slice());\n });\n }\n } catch (err) {\n log.error('Failed to load initial elements:', err);\n const importErr = err instanceof Error ? err : new Error(String(err));\n emitError({\n category: 'import',\n message: `Failed to load initial elements: ${importErr.message}`,\n originalError: importErr,\n recoverable: true,\n });\n }\n }, [initialElements, artboardManager, setElements, emitError]);\n\n // === Sync controlled activeArtboard prop ===\n useEffect(() => {\n if (!controlledActiveArtboard) {\n // No controlled prop, but still mark as synced so onArtboardChange can fire\n hasInitialSyncRef.current = true;\n return;\n }\n\n const targetArtboard = artboards.find((ab) => ab.name === controlledActiveArtboard);\n if (targetArtboard) {\n const activeArtboard = artboardManager.getActiveArtboard();\n if (activeArtboard?.name !== controlledActiveArtboard) {\n selectArtboard(targetArtboard.id);\n }\n // Mark as synced - now onArtboardChange can safely fire\n hasInitialSyncRef.current = true;\n }\n }, [controlledActiveArtboard, artboards]);\n\n // === Load initial image into ALL artboards ===\n useEffect(() => {\n if (!initialImage || prevImageRef.current === initialImage) return;\n\n // Skip if elements already exist (viewport switch scenario with externalProvider)\n // When using externalProvider, elements persist across component remounts,\n // so we shouldn't re-add the initial image on each mount\n if (elements.length > 0) {\n // Update ref to prevent future runs, but don't add duplicate images\n prevImageRef.current = initialImage;\n return;\n }\n\n prevImageRef.current = initialImage;\n\n const loadImage = async () => {\n setImageLoadingState('loading');\n setImageError(null);\n\n try {\n // Get ALL artboards to load the image into each one\n const allArtboards = artboardManager.getAllArtboards();\n if (allArtboards.length === 0) {\n throw new Error('No artboards available');\n }\n\n\n // Load image into EACH artboard\n const newElements: typeof elements = [];\n\n for (const artboard of allArtboards) {\n // Find the matching artboard config for per-artboard scaleMode\n const artboardConfig = artboardConfigs?.find(c => c.name === artboard.name);\n const effectiveScaleMode = artboardConfig?.scaleMode || initialImageScaleMode;\n\n // Use the placement function that calculates cover/contain sizing\n const result = await createImageElementWithPlacement(\n initialImage,\n {\n artboard: {\n width: artboard.width,\n height: artboard.height,\n x: artboard.x,\n y: artboard.y,\n },\n alignment: artboardConfig?.fitAlign || initialImageAlignment,\n scale: initialImageScale,\n scaleMode: effectiveScaleMode,\n marginTop: artboardConfig?.fitMarginTop,\n marginRight: artboardConfig?.fitMarginRight,\n marginBottom: artboardConfig?.fitMarginBottom,\n marginLeft: artboardConfig?.fitMarginLeft,\n }\n );\n\n if (result.success && result.element) {\n\n // Add element to this artboard\n artboardManager.addElementToArtboard(result.element.id, artboard.id);\n newElements.push(result.element);\n } else {\n log.error('Failed to load image for artboard:', artboard.name, result.error);\n }\n }\n\n if (newElements.length > 0) {\n setElements((prev) => [...prev, ...newElements]);\n setImageLoadingState('success');\n onImageLoad?.(initialImage);\n } else {\n const loadErr = new Error('Failed to load image into any artboard');\n setImageLoadingState('error');\n setImageError(loadErr);\n onImageError?.(initialImage, loadErr);\n emitError({\n category: 'image',\n message: loadErr.message,\n originalError: loadErr,\n recoverable: true,\n });\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n setImageLoadingState('error');\n setImageError(err);\n onImageError?.(initialImage, err);\n emitError({\n category: 'image',\n message: err.message,\n originalError: err,\n recoverable: true,\n });\n }\n };\n\n loadImage();\n }, [initialImage, initialImageAlignment, initialImageScale, initialImageScaleMode]);\n\n // === Handle selection change callback ===\n useEffect(() => {\n onSelectionChange?.(selectedId);\n }, [selectedId, onSelectionChange]);\n\n // === Handle onChange callback ===\n useEffect(() => {\n if (!onChange) return;\n\n const activeArtboard = artboardManager.getActiveArtboard();\n const state: CanvasState = {\n elements: elements.map((el) => el.toJSON()) as AnyElementConfig[],\n artboards: artboards.map((ab) => ({\n name: ab.name,\n width: ab.width,\n height: ab.height,\n clipShape: ab.clipShape,\n backgroundColor: ab.backgroundColor,\n })),\n activeArtboard: activeArtboard?.name || 'Design',\n };\n\n onChange(state);\n }, [elements, artboards, onChange]);\n\n // === Handle artboard change callback ===\n useEffect(() => {\n if (!onArtboardChange) return;\n\n // Skip until initial sync is complete - during initialization, createArtboard sets\n // each new artboard as active, so the last one ends up active. We wait for the\n // controlled activeArtboard prop to sync before firing this callback.\n if (!hasInitialSyncRef.current) return;\n\n const activeArtboard = artboardManager.getActiveArtboard();\n if (activeArtboard) {\n onArtboardChange(activeArtboard.name);\n }\n }, [artboardManager.getActiveArtboardId()]);\n\n // === Auto export ===\n const triggerExport = useCallback(async () => {\n if (!onExport && !onExportStatus) return;\n\n const exportNumber = ++exportCounterRef.current;\n const startTime = Date.now();\n\n // Calculate effective scale for each artboard (capped to maxExportSize)\n // Use the most restrictive scale to ensure all artboards fit within maxExportSize\n let effectiveScale = exportScale;\n if (maxExportSize > 0 && isFinite(maxExportSize)) {\n for (const ab of artboards) {\n const abEffectiveScale = calculateEffectiveScale(ab.width, ab.height, exportScale, maxExportSize);\n effectiveScale = Math.min(effectiveScale, abEffectiveScale);\n }\n }\n\n // Determine the artboard ID for status events\n const activeAb = artboardManager.getActiveArtboard();\n const statusArtboardId = activeAb?.id ?? 'unknown';\n\n // Fire 'rendering' status event\n onExportStatus?.({ status: 'rendering', artboardId: statusArtboardId });\n\n lastExportRef.current = startTime;\n\n try {\n let results: Record<string, string | Blob>;\n\n const exportOptions = {\n format: exportImageFormat as 'png' | 'jpg' | 'jpeg' | 'webp',\n scale: effectiveScale,\n quality: exportImageQuality,\n };\n\n if (autoExportFormat === 'blob') {\n // Export as Blob (more efficient for API uploads)\n if (autoExportAll) {\n const allResults = await exportAllArtboardsAsBlobs(exportOptions);\n results = {};\n for (const [artboardId, data] of Object.entries(allResults)) {\n const artboard = artboards.find((ab) => ab.id === artboardId);\n if (artboard) {\n results[artboard.name] = data;\n }\n }\n } else {\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) return;\n const blob = await exportArtboardAsBlob(activeArtboard.id, exportOptions);\n results = { [activeArtboard.name]: blob };\n }\n } else {\n // Export as dataUrl (default)\n if (autoExportAll) {\n const allResults = await exportAllArtboards(exportOptions);\n results = {};\n for (const [artboardId, data] of Object.entries(allResults)) {\n const artboard = artboards.find((ab) => ab.id === artboardId);\n if (artboard) {\n results[artboard.name] = data;\n }\n }\n } else {\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) return;\n const dataUrl = await exportArtboard(activeArtboard.id, exportOptions);\n results = { [activeArtboard.name]: dataUrl };\n }\n }\n\n // Fire legacy callback (backwards compat)\n onExport?.(results);\n\n // Fire unified status callback\n onExportStatus?.({ status: 'complete', artboardId: statusArtboardId, result: results });\n } catch (error) {\n // Silently skip if canvas ref not available (canvas still mounting)\n const errorMessage = error instanceof Error ? error.message : String(error);\n if (errorMessage.includes('Canvas ref not available')) {\n // Canvas not ready yet, will retry on next interval\n return;\n }\n const duration = Date.now() - startTime;\n log.error('Export failed (export #' + exportNumber + '):', {\n duration: `${duration}ms`,\n error: errorMessage,\n });\n\n // Fire unified error status callback\n const err = error instanceof Error ? error : new Error(errorMessage);\n onExportStatus?.({ status: 'error', artboardId: statusArtboardId, error: err });\n\n // Fire general onError callback\n emitError({\n category: 'export',\n message: err.message,\n originalError: err,\n artboardId: statusArtboardId,\n recoverable: true,\n });\n }\n }, [onExport, onExportStatus, autoExportAll, autoExportFormat, exportScale, maxExportSize, exportImageFormat, exportImageQuality, exportArtboard, exportAllArtboards, exportArtboardAsBlob, exportAllArtboardsAsBlobs, artboards, artboardManager, elements, autoExportConfig, emitError]);\n\n // === Deprecation warning for autoExportInterval ===\n useEffect(() => {\n if (autoExportInterval !== undefined && autoExportInterval > 0) {\n log.warn(\n 'autoExportInterval is deprecated and ignored. ' +\n 'Use autoExportConfig={{ enabled: true, debounceMs: 100, maxWaitMs: 1000 }} instead.'\n );\n }\n }, []);\n\n // === Debug: Log elements changes ===\n useEffect(() => {\n }, [elements]);\n\n // === Combined scheduled callback (fires both legacy + unified) ===\n const handleExportScheduled = useCallback(() => {\n // Fire legacy callback (backwards compat)\n onExportScheduled?.();\n\n // Fire unified status callback with 'scheduled' event\n if (onExportStatus) {\n const activeAb = artboardManager.getActiveArtboard();\n onExportStatus({ status: 'scheduled', artboardId: activeAb?.id ?? 'unknown' });\n }\n }, [onExportScheduled, onExportStatus, artboardManager]);\n\n // === Content readiness (gates auto-export on full content readiness) ===\n const hasInitialElements = !!initialElements && initialElements.length > 0;\n const isContentReady = useContentReady({\n isCanvasReady,\n elements,\n hasInitialElements,\n initialElementsLoaded: initialElementsLoadedState,\n });\n\n // === Fire onReady callback once content is ready ===\n const hasCalledOnReadyRef = useRef(false);\n useEffect(() => {\n if (isContentReady && !hasCalledOnReadyRef.current) {\n hasCalledOnReadyRef.current = true;\n onReady?.();\n }\n }, [isContentReady, onReady]);\n\n // === Smart auto-export with debouncing and change detection ===\n useAutoExport({\n config: autoExportConfig ? {\n enabled: autoExportConfig.enabled ?? true,\n debounceMs: autoExportConfig.debounceMs ?? 100,\n maxWaitMs: autoExportConfig.maxWaitMs ?? 1000,\n } : {\n enabled: false, // Only use smart export if autoExportConfig is provided\n },\n historyManager,\n artboards: artboards,\n elements,\n onExport: triggerExport,\n onExportScheduled: handleExportScheduled,\n isCanvasReady: isContentReady, // Gate exports until content is fully ready (fonts loaded, elements deserialized)\n });\n\n // === Signal canvas ready when artboards are initialized ===\n // This gates auto-export to prevent \"[useExport] Canvas ref not available\" errors\n useEffect(() => {\n if (artboards.length > 0 && !isCanvasReady) {\n // Small delay to ensure canvas DOM is mounted after artboards are ready\n const timeoutId = requestAnimationFrame(() => {\n setCanvasReady(true);\n });\n return () => cancelAnimationFrame(timeoutId);\n }\n return undefined;\n }, [artboards.length, isCanvasReady, setCanvasReady]);\n\n // === Cleanup canvas ready on unmount ===\n useEffect(() => {\n return () => {\n setCanvasReady(false);\n };\n }, [setCanvasReady]);\n\n // === ErrorBoundary onError handler ===\n // Fires emitError before the ErrorBoundary renders its fallback UI\n // NOTE: Must be declared before early returns to satisfy React hooks rules\n const handleBoundaryError = useCallback((error: Error) => {\n emitError({\n category: 'unknown',\n message: error.message,\n originalError: error,\n recoverable: false,\n });\n }, [emitError]);\n\n // === Render loading state ===\n if (imageLoadingState === 'loading') {\n return (\n <div\n className={className}\n style={{\n ...style,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: 'var(--surface, #f5f5f5)',\n }}\n >\n <Spinner size={48} />\n </div>\n );\n }\n\n // === Render error state ===\n if (imageLoadingState === 'error' && imageError) {\n return (\n <div\n className={className}\n style={{\n ...style,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: 'var(--surface, #f5f5f5)',\n color: 'var(--danger, #dc2626)',\n padding: '1rem',\n }}\n >\n <div className=\"text-center\">\n <div className=\"font-medium\">Failed to load image</div>\n <div className=\"text-sm mt-1 opacity-70\">{imageError.message}</div>\n </div>\n </div>\n );\n }\n\n // === Render canvas ===\n // Note: 'app-modern' class is required for toolbar CSS to work (see src/App.css)\n return (\n <KitProvider kit={resolvedKit}>\n <div className={`app-modern ${className || ''}`} style={{ ...style, position: 'relative' }}>\n <ErrorBoundary\n onError={handleBoundaryError}\n fallback={(error, reset) => (\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '24px',\n minHeight: '200px',\n backgroundColor: 'var(--surface, #f5f5f5)',\n color: 'var(--foreground, #333)',\n fontFamily: 'system-ui, sans-serif',\n textAlign: 'center',\n }}\n >\n <div style={{ fontSize: '18px', fontWeight: 600, marginBottom: '8px' }}>\n Something went wrong\n </div>\n <div style={{ fontSize: '14px', opacity: 0.7, marginBottom: '16px', maxWidth: '400px' }}>\n {error.message}\n </div>\n <button\n onClick={reset}\n style={{\n padding: '8px 20px',\n backgroundColor: 'var(--primary, #333)',\n color: 'var(--primary-foreground, #fff)',\n border: 'none',\n borderRadius: '6px',\n fontSize: '14px',\n fontWeight: 500,\n cursor: 'pointer',\n }}\n >\n Try again\n </button>\n </div>\n )}\n >\n {!hideCanvas && (\n canvasWrapperClassName || canvasWrapperStyle ? (\n <div className={canvasWrapperClassName} style={canvasWrapperStyle}>\n <Canvas\n style={{ width: '100%' }}\n fitPadding={viewPadding}\n artboardBorderRadius={artboardBorderRadius}\n fixedMargin={fixedMargin}\n maxHeight={maxHeight}\n showRotationHandle={showRotationHandle}\n enableShortcuts={enableShortcuts}\n canvasCutouts={canvasCutouts}\n pieceGuides={pieceGuides}\n pieceFocus={pieceFocus}\n />\n </div>\n ) : (\n <Canvas\n style={{ width: '100%' }}\n fitPadding={viewPadding}\n artboardBorderRadius={artboardBorderRadius}\n fixedMargin={fixedMargin}\n maxHeight={maxHeight}\n showRotationHandle={showRotationHandle}\n enableShortcuts={enableShortcuts}\n canvasCutouts={canvasCutouts}\n pieceGuides={pieceGuides}\n pieceFocus={pieceFocus}\n />\n )\n )}\n {overlay}\n </ErrorBoundary>\n </div>\n </KitProvider>\n );\n});\n\nSnowconeCanvasInner.displayName = 'SnowconeCanvasInner';\n\n/**\n * SnowconeCanvas - Self-contained embeddable canvas editor\n * Wraps with ThemeProvider and EditorProvider for full functionality\n *\n * @example With imperative export\n * ```tsx\n * const canvasRef = useRef<SnowconeCanvasHandle>(null);\n *\n * const handleExport = async () => {\n * const exports = await canvasRef.current?.exportArtboards();\n * console.log('Exported:', exports);\n * };\n *\n * return <SnowconeCanvas ref={canvasRef} ... />;\n * ```\n *\n * @example Embedded in app with existing theme\n * ```tsx\n * // When your app already manages themes, use inheritTheme to prevent conflicts\n * <SnowconeCanvas inheritTheme ... />\n * ```\n */\nexport const SnowconeCanvas = forwardRef<SnowconeCanvasHandle, SnowconeCanvasProps>((props, ref) => {\n const { inheritTheme, externalProvider } = props;\n\n // Resolve viewPadding from either flat prop or layoutConfig (flat takes precedence)\n const resolvedViewPadding = props.viewPadding ?? props.layoutConfig?.viewPadding;\n\n const innerContent = (\n <SnowconeCanvasInner ref={ref} {...props} />\n );\n\n // When externalProvider is true, skip creating EditorProvider\n // (assumes parent component already provides one)\n if (externalProvider) {\n return (\n <ThemeProvider defaultTheme=\"light\" passive={inheritTheme}>\n {innerContent}\n </ThemeProvider>\n );\n }\n\n return (\n <ThemeProvider defaultTheme=\"light\" passive={inheritTheme}>\n <EditorProvider viewPadding={resolvedViewPadding}>\n {innerContent}\n </EditorProvider>\n </ThemeProvider>\n );\n});\n\nSnowconeCanvas.displayName = 'SnowconeCanvas';\n\nexport default SnowconeCanvas;\n","/**\n * useCommands - Hook for undo/redo and command history operations\n *\n * Provides a clean interface to command history functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function UndoRedoToolbar() {\n * const { undo, redo, canUndo, canRedo, clearHistory } = useCommands();\n *\n * return (\n * <div className=\"toolbar\">\n * <button onClick={undo} disabled={!canUndo}>\n * Undo (⌘Z)\n * </button>\n * <button onClick={redo} disabled={!canRedo}>\n * Redo (⌘⇧Z)\n * </button>\n * <button onClick={clearHistory}>\n * Clear History\n * </button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useCallback } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('useCommands');\n\nexport interface UseCommandsReturn {\n // Undo/Redo\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n\n // History management\n clearHistory: () => void;\n clearArtboardHistory: (artboardId: string) => void;\n\n // Command execution\n executeElementUpdate: ReturnType<typeof useEditor>['executeElementUpdate'];\n executeAddElement: ReturnType<typeof useEditor>['executeAddElement'];\n executeRemoveElement: ReturnType<typeof useEditor>['executeRemoveElement'];\n executeReorderElement: ReturnType<typeof useEditor>['executeReorderElement'];\n executeCreateArtboard: ReturnType<typeof useEditor>['executeCreateArtboard'];\n executeDeleteArtboard: ReturnType<typeof useEditor>['executeDeleteArtboard'];\n executeUpdateArtboard: ReturnType<typeof useEditor>['executeUpdateArtboard'];\n\n // Batch execution\n executeCommandBatch: (commands: Array<{ execute: () => void; undo: () => void }>) => void;\n}\n\n/**\n * Hook for accessing command history and undo/redo functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { undo, redo, canUndo, canRedo } = useCommands();\n * const { selectedElement } = useEditor();\n *\n * const handleDelete = () => {\n * if (selectedElement) {\n * executeRemoveElement(selectedElement);\n * }\n * };\n *\n * return (\n * <div>\n * <button onClick={undo} disabled={!canUndo}>Undo</button>\n * <button onClick={redo} disabled={!canRedo}>Redo</button>\n * <button onClick={handleDelete}>Delete</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCommands(): UseCommandsReturn {\n const context = useEditor();\n\n // Extract command history methods\n const {\n undo,\n redo,\n canUndo,\n canRedo,\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCommandBatch,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n } = context;\n\n // Clear all history (not exposed in EditorContext yet, so create a placeholder)\n const clearHistory = useCallback(() => {\n logger.warn('[useCommands] clearHistory is not yet implemented in EditorContext');\n }, []);\n\n // Clear artboard-specific history (not exposed in EditorContext yet, so create a placeholder)\n const clearArtboardHistory = useCallback((_artboardId: string) => {\n logger.warn('[useCommands] clearArtboardHistory is not yet implemented in EditorContext');\n }, []);\n\n return {\n // Undo/Redo\n undo,\n redo,\n canUndo,\n canRedo,\n\n // History management\n clearHistory,\n clearArtboardHistory,\n\n // Command execution\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n\n // Batch execution\n executeCommandBatch,\n };\n}\n","import { useEditor } from '../contexts/EditorContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the currently selected element, or null if nothing is selected.\n *\n * This is a convenience wrapper around `useEditor()` that only extracts\n * selection state, keeping consumer components focused on what they need.\n *\n * @example\n * ```tsx\n * function PropertiesPanel() {\n * const selected = useSelectedElement();\n * if (!selected) return <p>Nothing selected</p>;\n * return <p>Selected: {selected.id}</p>;\n * }\n * ```\n */\nexport function useSelectedElement(): EditorElement | null {\n const { selectedElement } = useEditor();\n return selectedElement ?? null;\n}\n","import { useEditor } from '../contexts/EditorContext.js';\n\n/**\n * Returns whether the canvas is fully initialized and ready for operations.\n *\n * The canvas is not ready until the underlying `<canvas>` element is mounted\n * and the editor has completed its initial setup. Use this to gate operations\n * like export or programmatic element manipulation that require a live canvas.\n *\n * @returns `true` when the canvas is mounted and ready, `false` otherwise.\n *\n * @example\n * ```tsx\n * function ExportButton() {\n * const ready = useCanvasReady();\n * return (\n * <button disabled={!ready} onClick={handleExport}>\n * Export PNG\n * </button>\n * );\n * }\n * ```\n */\nexport function useCanvasReady(): boolean {\n const { isCanvasReady } = useEditor();\n return isCanvasReady;\n}\n","/**\n * useTextBinding - Bidirectional text binding between HTML inputs and canvas text elements.\n *\n * Links an external input to one or more canvas text elements by their `name` property.\n * Multiple elements can share the same name — `setText` updates all of them.\n * Changes flow both ways: input → canvas via `setText`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function NameInput() {\n * const { text, setText, isConnected } = useTextBinding('headline');\n * return (\n * <input\n * value={text}\n * onChange={(e) => setText(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/** Result returned by {@link useTextBinding}. */\nexport interface TextBindingResult {\n /** Current plain text content (from first bound element). Empty string if disconnected. */\n text: string;\n /** Update text on all bound elements. No-op if disconnected. */\n setText: (text: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind an HTML input to canvas text elements by their `name` property.\n *\n * Multiple elements can share the same name — `setText` updates all of them.\n * Text is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Text value, setter, element references, and connection status.\n */\nexport function useTextBinding(name: string): TextBindingResult {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setText = useCallback(\n (newText: string) => {\n for (const el of textElements) {\n const cloned = el.clone() as TextElement;\n cloned.setText(newText);\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n if (textElements.length === 0) {\n return { text: '', setText: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n text: textElements[0].getText(),\n setText,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useImageBinding - Bidirectional image URL binding between external inputs and canvas image elements.\n *\n * Links an external input to one or more canvas image elements by their `name` property.\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * Changes flow both ways: input → canvas via `setImageUrl`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function LogoUpload() {\n * const { imageUrl, setImageUrl, isConnected } = useImageBinding('logo');\n * return (\n * <input\n * type=\"url\"\n * value={imageUrl}\n * onChange={(e) => setImageUrl(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback, useRef } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/** How the image fits the element bounds. */\nexport type ImageFitMode = 'cover' | 'contain';\n\n/** Options for {@link useImageBinding}. */\nexport interface ImageBindingOptions {\n /** How the image fits the element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n}\n\n/** Result returned by {@link useImageBinding}. */\nexport interface ImageBindingResult {\n /** Current image URL (from first bound element). Empty string if disconnected. */\n imageUrl: string;\n /** Update image URL on all bound elements. No-op if disconnected. */\n setImageUrl: (url: string) => void;\n /** The first bound element, or null if no matching image element exists. */\n element: EditorElement | null;\n /** All bound image elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching image element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Calculate center-crop values so the visible area fills the original element bounds.\n */\nfunction coverCrop(\n origWidth: number,\n origHeight: number,\n imageAspectRatio: number,\n) {\n const placeholderAR = origWidth / origHeight;\n\n if (imageAspectRatio >= placeholderAR) {\n // Image is wider — match height, crop width\n const frameHeight = origHeight;\n const frameWidth = frameHeight * imageAspectRatio;\n const cropWidth = origWidth / frameWidth;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: (1 - cropWidth) / 2,\n cropY: 0,\n cropWidth,\n cropHeight: 1,\n };\n } else {\n // Image is taller — match width, crop height\n const frameWidth = origWidth;\n const frameHeight = frameWidth / imageAspectRatio;\n const cropHeight = origHeight / frameHeight;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: 0,\n cropY: (1 - cropHeight) / 2,\n cropWidth: 1,\n cropHeight,\n };\n }\n}\n\n/**\n * Bind an external input to canvas image elements by their `name` property.\n *\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * The image URL is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @param options - Optional configuration (fit mode, etc.).\n * @returns Image URL value, setter, element references, and connection status.\n */\nexport function useImageBinding(name: string, options?: ImageBindingOptions): ImageBindingResult {\n const fit = options?.fit ?? 'cover';\n const { elementStore, setElements } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const imageElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is ImageElement => el instanceof ImageElement);\n }, [elementStore, name]);\n\n // Capture the original placeholder dimensions once (from the first time we see each element).\n // All subsequent image replacements fit within this fixed box.\n const origDimsRef = useRef<Map<string, { width: number; height: number }>>(new Map());\n for (const el of imageElements) {\n if (!origDimsRef.current.has(el.id)) {\n const td = el.transformData;\n origDimsRef.current.set(el.id, {\n width: td.width * td.cropWidth,\n height: td.height * td.cropHeight,\n });\n }\n }\n\n // Use refs for values that change on every element update to keep setImageUrl stable.\n // Without this, setImageUrl gets a new identity after every executeElementUpdate call,\n // which causes infinite loops when used in useEffect dependency arrays.\n const imageElementsRef = useRef(imageElements);\n imageElementsRef.current = imageElements;\n const executeRef = useRef(executeElementUpdate);\n executeRef.current = executeElementUpdate;\n const setElementsRef = useRef(setElements);\n setElementsRef.current = setElements;\n\n const setImageUrl = useCallback(\n (url: string) => {\n const currentElements = imageElementsRef.current;\n const exec = executeRef.current;\n const updateElements = setElementsRef.current;\n\n for (const el of currentElements) {\n const orig = origDimsRef.current.get(el.id);\n if (!orig) continue;\n const { width: origW, height: origH } = orig;\n\n const cloned = el.clone() as ImageElement;\n cloned.imageLoaded = false;\n cloned.imageElement = null;\n cloned.isCropping = false;\n cloned.imageUrl = url;\n\n if (fit === 'cover') {\n cloned.preserveDimensions = true;\n\n cloned.onLoadCallback = (loadedElement) => {\n const imgAR = loadedElement.imageAspectRatio || 1;\n const crop = coverCrop(origW, origH, imgAR);\n\n loadedElement.transformData.width = crop.width;\n loadedElement.transformData.height = crop.height;\n loadedElement.transformData.cropX = crop.cropX;\n loadedElement.transformData.cropY = crop.cropY;\n loadedElement.transformData.cropWidth = crop.cropWidth;\n loadedElement.transformData.cropHeight = crop.cropHeight;\n\n // Single atomic update: command history + element store.\n // This ensures auto-export only sees the final cropped state,\n // not an intermediate unloaded state that would produce wrong mockups.\n exec(el, loadedElement);\n };\n } else {\n cloned.preserveDimensions = false;\n\n cloned.onLoadCallback = (loadedElement) => {\n const w = loadedElement.transformData.width;\n const h = loadedElement.transformData.height;\n const scale = Math.min(origW / w, origH / h, 1);\n if (scale < 1) {\n loadedElement.transformData.width = w * scale;\n loadedElement.transformData.height = h * scale;\n }\n loadedElement.transformData.cropX = 0;\n loadedElement.transformData.cropY = 0;\n loadedElement.transformData.cropWidth = 1;\n loadedElement.transformData.cropHeight = 1;\n\n exec(el, loadedElement);\n };\n }\n\n // Start loading — onLoadCallback will update the store via exec()\n // once the image is ready with final dimensions/crop.\n cloned.loadImage(url);\n }\n },\n [fit], // Stable deps only — imageElements/executeElementUpdate/setElements accessed via refs\n );\n\n if (imageElements.length === 0) {\n return { imageUrl: '', setImageUrl: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n imageUrl: imageElements[0].imageUrl,\n setImageUrl,\n element: imageElements[0],\n elements: imageElements,\n isConnected: true,\n };\n}\n","/**\n * useElementByName - Look up an element by its `name` property.\n *\n * General-purpose complement to {@link useElementById}. Returns the first\n * element whose `name` matches, following render order.\n *\n * @example\n * ```tsx\n * function ElementInspector({ elementName }: { elementName: string }) {\n * const element = useElementByName(elementName);\n * if (!element) return <p>Element not found</p>;\n * return <p>Position: ({element.x}, {element.y})</p>;\n * }\n * ```\n *\n * @module\n */\n\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the first element matching the given name, or null.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to look up, or null to skip the lookup.\n * @returns The matching element, or null if no element matches.\n */\nexport function useElementByName(name: string | null): EditorElement | null {\n const { elementStore } = useElementsContext();\n if (!name) return null;\n return elementStore.getByName(name) ?? null;\n}\n","import React from 'react';\nimport { useViewportContext } from '../contexts/ViewportContext.js';\nimport type { PanOffset } from '../contexts/ViewportContext.js';\n\n/**\n * Return type for {@link useViewport}.\n */\nexport interface UseViewportReturn {\n /** Current zoom level (1.0 = 100%) */\n zoom: number;\n /** Current pan offset in world coordinates */\n panOffset: PanOffset;\n /** Increment zoom by one step */\n zoomIn: () => void;\n /** Decrement zoom by one step */\n zoomOut: () => void;\n /** Zoom to fit the active artboard in the viewport */\n zoomToFit: () => void;\n /** Reset zoom to 100% and pan offset to origin */\n resetView: () => void;\n /** Set zoom to an exact value */\n setZoom: React.Dispatch<React.SetStateAction<number>>;\n /** Set pan offset to an exact value */\n setPanOffset: React.Dispatch<React.SetStateAction<PanOffset>>;\n}\n\n/**\n * Returns viewport state (zoom, pan) and control functions.\n *\n * This hook now reads directly from ViewportContext (instead of the full\n * EditorContext), so components using it will only re-render when viewport\n * state changes -- not when elements, selection, or other state changes.\n *\n * @example\n * ```tsx\n * function ZoomBar() {\n * const { zoom, zoomIn, zoomOut, zoomToFit } = useViewport();\n * return (\n * <div>\n * <button onClick={zoomOut}>-</button>\n * <span>{Math.round(zoom * 100)}%</span>\n * <button onClick={zoomIn}>+</button>\n * <button onClick={zoomToFit}>Fit</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useViewport(): UseViewportReturn {\n const { zoom, panOffset, zoomIn, zoomOut, zoomToFit, resetView, setZoom, setPanOffset } = useViewportContext();\n return { zoom, panOffset, zoomIn, zoomOut, zoomToFit, resetView, setZoom, setPanOffset };\n}\n","/**\n * Serialize canvas state (from onChange/toJSON) to the format the export worker expects.\n *\n * This bridges the gap between SnowconeCanvas.onChange() output (element configs with\n * transformType and nested transformData) and the export worker's expected format\n * (elements with 'type' field and flattened image properties).\n *\n * Use this when sending canvas state to a server-side renderer that runs the export\n * worker bundle. The client-side serializeForWorkerExport() requires live element\n * instances; this function works on plain JSON from onChange/toJSON.\n */\n\nimport type { SerializedImageElement } from './renderer-types.js';\n\nexport interface ServerRenderRequest {\n artboards: Array<{\n id: string;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n exportBackground: boolean;\n elements: any[];\n distressTexture?: any;\n imageMask?: any;\n }>;\n}\n\n/**\n * Convert onChange canvas state to the server render format.\n *\n * @param state - The state from SnowconeCanvas onChange callback\n * @returns ServerRenderRequest ready to send via sendCanvasState()\n */\nexport function serializeStateForServer(state: {\n elements?: any[];\n artboards?: Array<{\n name?: string;\n width?: number;\n height?: number;\n backgroundColor?: string;\n clipShape?: any;\n distressTexture?: any;\n imageMask?: any;\n }>;\n activeArtboard?: string;\n}): ServerRenderRequest {\n const srcArtboard = state.artboards?.[0] || { name: 'Front', width: 800, height: 800 };\n const elements = (state.elements || []).map(serializeElement);\n\n return {\n artboards: [{\n id: 'artboard-1',\n name: srcArtboard.name || 'Front',\n x: 0,\n y: 0,\n width: srcArtboard.width || 800,\n height: srcArtboard.height || 800,\n backgroundColor: srcArtboard.backgroundColor || 'transparent',\n exportBackground: false,\n elements,\n distressTexture: srcArtboard.distressTexture,\n imageMask: srcArtboard.imageMask,\n }],\n };\n}\n\n/**\n * Serialize a single element from toJSON() format to export worker format.\n */\nfunction serializeElement(el: any): any {\n const type = el.type || el.transformType;\n\n if (type === 'image') {\n return serializeImageElement(el);\n }\n\n // Text, shape, group, path elements: map transformType → type, keep everything else\n // Text, shape, group, path elements: pass through as-is with type mapped.\n // Builtin mask URLs (builtin-mask:*) are kept as-is — the server worker\n // generates them procedurally via generateMaskBitmap().\n return {\n ...el,\n type,\n };\n}\n\n/**\n * Serialize an image element, flattening transformData to top-level fields\n * as expected by SerializedImageElement and renderImageElement.\n */\nfunction serializeImageElement(el: any): any {\n const td = el.transformData || {};\n const hasMasks = el.masks && el.masks.length > 0;\n\n // Visible dimensions depend on crop and masks\n const fullWidth = td.width || 0;\n const fullHeight = td.height || 0;\n const visibleWidth = hasMasks ? fullWidth : fullWidth * (td.cropWidth ?? 1);\n const visibleHeight = hasMasks ? fullHeight : fullHeight * (td.cropHeight ?? 1);\n\n const serialized: any = {\n id: el.id,\n type: 'image' as const,\n x: el.x || 0,\n y: el.y || 0,\n width: visibleWidth,\n height: visibleHeight,\n rotation: el.rotation || 0,\n imageUrl: el.imageUrl,\n imageAspectRatio: el.imageAspectRatio,\n };\n\n // Crop (only if actually cropped and no masks)\n const isCropped = !hasMasks && (td.cropX !== 0 || td.cropY !== 0 || td.cropWidth !== 1 || td.cropHeight !== 1);\n if (isCropped) {\n // Note: crop values stay as fractions here — the server's Chrome tab\n // will convert to pixels using the actual bitmap dimensions after fetch.\n // The client doesn't have the bitmap dimensions available in onChange state.\n serialized.cropX = td.cropX;\n serialized.cropY = td.cropY;\n serialized.cropWidth = td.cropWidth;\n serialized.cropHeight = td.cropHeight;\n serialized.needsCropPixelConversion = true; // Flag for server to convert\n }\n\n // Flip and border radius\n if (td.flipHorizontal) serialized.flipHorizontal = td.flipHorizontal;\n if (td.flipVertical) serialized.flipVertical = td.flipVertical;\n if (td.borderRadius) serialized.borderRadius = td.borderRadius;\n if (el.opacity !== undefined) serialized.opacity = el.opacity;\n\n // Effects\n if (el.distressEffect) serialized.distressEffect = el.distressEffect;\n if (el.masks?.length > 0) serialized.masks = el.masks;\n if (el.blendMode) serialized.blendMode = el.blendMode;\n if (el.knockoutParts) serialized.knockoutParts = el.knockoutParts;\n if (el.stroke) serialized.stroke = el.stroke;\n\n return serialized;\n}\n"],"names":["logger","createLogger","ErrorBoundary","Component","props","error","errorInfo","canvasError","jsxs","jsx","useAutoExport","options","config","historyManager","artboards","elements","onExport","onExportScheduled","isCanvasReady","managerRef","useRef","pendingExportRef","statsRef","stats","setStats","useState","onExportRef","useEffect","manager","AutoExportManager","DEFAULT_AUTO_EXPORT_CONFIG","unsubscribe","subscribeToImageLoads","elementId","e","updateConfig","useCallback","newConfig","forceExport","resetStats","resetChangeDetection","extractFontFamilies","fonts","el","richText","span","_a","ensureFontsLoaded","fontFamilies","pending","family","testString","resolve","waitOneFrame","useContentReady","hasInitialElements","initialElementsLoaded","isContentReady","setIsContentReady","checkingRef","cancelled","err","alignmentMap","getAlignment","value","calculateArtworkPlacement","artwork","placement","mode","mTop","mRight","mBottom","mLeft","effectiveWidth","effectiveHeight","scaleX","scaleY","coverScale","userScale","finalScale","scaledArtworkWidth","scaledArtworkHeight","alignment","x","y","overflowX","overflowY","offsetX","offsetY","xRange","yRange","DEFAULT_OPTIONS","isValidImageUrl","url","parsed","loadImageFromURL","opts","lastError","attempt","loadImageOnce","delay","timeout","img","timeoutId","resolved","cleanup","handleSuccess","w","h","handleError","errorMsg","createImageElementWithPlacement","placementOptions","loaderOptions","loadResult","placementConfig","topLeftX","topLeftY","centerX","centerY","ImageElement","_b","_c","_d","_e","_f","_g","ms","log","calculateEffectiveScale","width","height","requestedScale","maxSize","scaledWidth","scaledHeight","largestSide","cappedScale","SnowconeCanvasInner","forwardRef","ref","_exportConfig","_imageConfig","_layoutConfig","kit","artboardConfigs","controlledActiveArtboard","onArtboardChange","initialElements","onChange","onSelectionChange","autoExportInterval_prop","onExportStatus","onExportReady","onImageLoad","onImageError","onError","onReady","className","style","enableShortcuts_prop","overlay","autoExportInterval","autoExportConfig","autoExportFormat","autoExportAll","exportScale","maxExportSize","exportImageFormat","exportImageQuality","initialImage","initialImageAlignment","initialImageScale","initialImageScaleMode","viewPadding","artboardBorderRadius","fixedMargin","maxHeight","showRotationHandle","hideCanvas","canvasWrapperClassName","canvasWrapperStyle","canvasCutouts","pieceGuides","pieceFocus","enableShortcuts","resolvedKit","useMemo","resolveKit","process","result","validateKit","onErrorRef","emitError","CanvasRenderer","setElements","selectedId","artboardManager","refreshArtboards","setCanvasReady","useEditor","createArtboard","selectArtboard","useArtboards","exportArtboard","exportAllArtboards","exportArtboardAsBlob","exportAllArtboardsAsBlobs","useExport","useImperativeHandle","format","all","effectiveScale","ab","abEffectiveScale","exportOptions","resultsById","resultsByName","artboardId","artboard","activeArtboard","blob","dataUrl","imageLoadingState","setImageLoadingState","imageError","setImageError","prevImageRef","initializedRef","initialElementsLoadedRef","initialElementsLoadedState","setInitialElementsLoadedState","lastExportRef","exportCounterRef","hasInitialSyncRef","configs","existingArtboards","firstConfig","firstArtboard","defaultBg","i","deserialized","ElementFactory","allArtboards","familySet","fontsApi","linkPromises","linkId","link","finish","families","prev","importErr","targetArtboard","newElements","artboardConfig","c","effectiveScaleMode","loadErr","state","triggerExport","exportNumber","startTime","activeAb","statusArtboardId","results","allResults","data","errorMessage","duration","handleExportScheduled","hasCalledOnReadyRef","handleBoundaryError","Spinner","KitProvider","reset","Canvas","SnowconeCanvas","inheritTheme","externalProvider","resolvedViewPadding","innerContent","ThemeProvider","EditorProvider","useCommands","context","undo","redo","canUndo","canRedo","executeElementUpdate","executeAddElement","executeRemoveElement","executeReorderElement","executeCommandBatch","executeCreateArtboard","executeDeleteArtboard","executeUpdateArtboard","clearHistory","clearArtboardHistory","_artboardId","useSelectedElement","selectedElement","useCanvasReady","NOOP","EMPTY","useTextBinding","name","elementStore","useElementsContext","useCommandContext","textElements","TextElement","setText","newText","cloned","coverCrop","origWidth","origHeight","imageAspectRatio","placeholderAR","frameHeight","frameWidth","cropWidth","cropHeight","useImageBinding","fit","imageElements","origDimsRef","td","imageElementsRef","executeRef","setElementsRef","setImageUrl","currentElements","exec","orig","origW","origH","loadedElement","imgAR","crop","scale","useElementByName","useViewport","zoom","panOffset","zoomIn","zoomOut","zoomToFit","resetView","setZoom","setPanOffset","useViewportContext","serializeStateForServer","srcArtboard","serializeElement","type","serializeImageElement","hasMasks","fullWidth","fullHeight","visibleWidth","visibleHeight","serialized"],"mappings":";;;;;;;;AAoCA,MAAMA,KAASC,EAAa,eAAe;AA4BpC,MAAMC,WAAsBC,GAAkD;AAAA,EACnF,YAAYC,GAA2B;AACrC,UAAMA,CAAK,GAmCb,KAAA,aAAa,MAAY;AACvB,WAAK,SAAS;AAAA,QACZ,UAAU;AAAA,QACV,OAAO;AAAA,MAAA,CACR;AAAA,IACH,GAvCE,KAAK,QAAQ;AAAA,MACX,UAAU;AAAA,MACV,OAAO;AAAA,IAAA;AAAA,EAEX;AAAA,EAEA,OAAO,yBAAyBC,GAAkC;AAChE,WAAO;AAAA,MACL,UAAU;AAAA,MACV,OAAAA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,kBAAkBA,GAAcC,GAA4B;AAU1D,QARAN,GAAO,MAAM,iBAAiBK,GAAOC,CAAS,GAG1C,KAAK,MAAM,WACb,KAAK,MAAM,QAAQD,GAAOC,CAAS,GAIjC,KAAK,MAAM,eAAe;AAC5B,YAAMC,IAA2B;AAAA,QAC/B,UAAU;AAAA,QACV,SAASF,EAAM;AAAA,QACf,eAAeA;AAAA,QACf,aAAa;AAAA,MAAA;AAEf,WAAK,MAAM,cAAcE,CAAW;AAAA,IACtC;AAAA,EACF;AAAA,EASA,SAAoB;AAClB,WAAI,KAAK,MAAM,YAAY,KAAK,MAAM,QAEhC,KAAK,MAAM,cACN,KAAK,MAAM,YAAY,KAAK,MAAM,OAAO,KAAK,UAAU,IAI7D,KAAK,MAAM,WACT,OAAO,KAAK,MAAM,YAAa,aAC1B,KAAK,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,UAAU,IAEvD,KAAK,MAAM,WAKlB,gBAAAC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,iBAAiB;AAAA,UACjB,OAAO;AAAA,UACP,YAAY;AAAA,QAAA;AAAA,QAGd,UAAA;AAAA,UAAA,gBAAAC,EAAC,MAAA,EAAG,OAAO,EAAE,QAAQ,cAAc,UAAU,QAAQ,YAAY,IAAA,GAAO,UAAA,uBAAA,CAExE;AAAA,UACA,gBAAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAO;AAAA,gBACL,QAAQ;AAAA,gBACR,SAAS;AAAA,gBACT,iBAAiB;AAAA,gBACjB,cAAc;AAAA,gBACd,UAAU;AAAA,gBACV,UAAU;AAAA,gBACV,WAAW;AAAA,cAAA;AAAA,cAGZ,UAAA,KAAK,MAAM,MAAM;AAAA,YAAA;AAAA,UAAA;AAAA,UAEpB,gBAAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS,KAAK;AAAA,cACd,OAAO;AAAA,gBACL,SAAS;AAAA,gBACT,iBAAiB;AAAA,gBACjB,OAAO;AAAA,gBACP,QAAQ;AAAA,gBACR,cAAc;AAAA,gBACd,UAAU;AAAA,gBACV,YAAY;AAAA,gBACZ,QAAQ;AAAA,cAAA;AAAA,cAEX,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QAED;AAAA,MAAA;AAAA,IAAA,IAKC,KAAK,MAAM;AAAA,EACpB;AACF;ACtJA,MAAMT,KAASC,EAAa,eAAe;AAuEpC,SAASS,GAAcC,GAAoD;AAChF,QAAM,EAAE,QAAAC,GAAQ,gBAAAC,GAAgB,WAAAC,GAAW,UAAAC,GAAU,UAAAC,GAAU,mBAAAC,GAAmB,eAAAC,IAAgB,GAAA,IAAUP,GAEtGQ,IAAaC,EAAiC,IAAI,GAElDC,IAAmBD,EAAgB,EAAK,GAGxCE,IAAWF,EAAwB;AAAA,IACvC,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,EAAA,CACpB,GACK,CAACG,GAAOC,CAAQ,IAAIC,EAA0BH,EAAS,OAAO,GAK9DI,IAAcN,EAAOJ,CAAQ;AACnC,EAAAU,EAAY,UAAUV,GAGtBW,EAAU,MAAM;AAEd,UAAMC,IAAU,IAAIC,GAAkB;AAAA,MACpC,GAAGC;AAAA,MACH,GAAGlB;AAAA,IAAA,CACJ;AAID,WAAAgB,EAAQ,SAAS,YAAY;AAC3B,UAAI;AACF,cAAMF,EAAY,QAAA,GAIlBJ,EAAS,UAAUM,EAAQ,SAAA;AAAA,MAC7B,SAASvB,GAAO;AACdL,QAAAA,GAAO,MAAM,kCAAkCK,CAAK;AAAA,MACtD;AAAA,IACF,CAAC,GAEDc,EAAW,UAAUS,GAGd,MAAM;AACX,MAAAA,EAAQ,QAAA;AAAA,IACV;AAAA,EACF,GAAG,CAAA,CAAE,GAGLD,EAAU,MAAM;AACd,IAAIT,KAAiBG,EAAiB,WAAWF,EAAW,YAC1DE,EAAiB,UAAU,IAC3BF,EAAW,QAAQ,YAAA;AAAA,EAEvB,GAAG,CAACD,CAAa,CAAC,GAGlBS,EAAU,MAAM;AACd,IAAIR,EAAW,WAAWP,KACxBO,EAAW,QAAQ,aAAaP,CAAM;AAAA,EAE1C,GAAG,CAACA,KAAA,gBAAAA,EAAQ,SAASA,KAAA,gBAAAA,EAAQ,YAAYA,KAAA,gBAAAA,EAAQ,SAAS,CAAC,GAQ3De,EAAU,MAAM;AACd,IAAIR,EAAW,WAAWF,KACxBE,EAAW,QAAQ,kBAAkBF,CAAiB;AAAA,EAE1D,GAAG,CAACA,CAAiB,CAAC,GAGtBU,EAAU,MAAM;AACd,QAAI,CAACd,KAAkB,CAACM,EAAW;AACjC;AAKF,UAAMY,IAAclB,EAAe,kBAAkB,MAAM;AAGzD,UAAIM,EAAW,SAAS;AAEtB,YAAI,CAACD,GAAe;AAClB,UAAAG,EAAiB,UAAU;AAC3B;AAAA,QACF;AACA,QAAAF,EAAW,QAAQ,eAAA;AAAA,MACrB;AAAA,IACF,CAAC;AAGD,WAAO,MAAM;AACX,MAAAY,EAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAClB,GAAgBK,CAAa,CAAC,GAWlCS,EAAU,MAAM;AACd,QAAKR,EAAW,SAKhB;AAAA,UAAI,CAACD,GAAe;AAClB,QAAAG,EAAiB,UAAU;AAC3B;AAAA,MACF;AAGA,MAAAF,EAAW,QAAQ,eAAA;AAAA;AAAA,EACrB,GAAG,CAACJ,GAAUD,GAAWI,CAAa,CAAC,GAMvCS,EAAU,MACYK,GAAsB,CAACC,MAAc;AACvD,QAAI,CAACd,EAAW,WAAW,CAACD,EAAe;AAE3C,IADmBH,EAAS,KAAK,CAAAmB,MAAKA,EAAE,OAAOD,CAAS,KAEtDd,EAAW,QAAQ,eAAA;AAAA,EAEvB,CAAC,GAEA,CAACJ,GAAUG,CAAa,CAAC;AAG5B,QAAMiB,IAAeC,EAAY,CAACC,MAAyC;AACzE,IAAIlB,EAAW,WACbA,EAAW,QAAQ,aAAakB,CAAS;AAAA,EAE7C,GAAG,CAAA,CAAE,GAECC,IAAcF,EAAY,YAAY;AAC1C,IAAIjB,EAAW,YACb,MAAMA,EAAW,QAAQ,YAAA,GACzBG,EAAS,UAAUH,EAAW,QAAQ,SAAA,GACtCK,EAASF,EAAS,OAAO;AAAA,EAE7B,GAAG,CAAA,CAAE,GAECiB,IAAaH,EAAY,MAAM;AACnC,IAAIjB,EAAW,YACbA,EAAW,QAAQ,WAAA,GACnBG,EAAS,UAAUH,EAAW,QAAQ,SAAA,GACtCK,EAASF,EAAS,OAAO;AAAA,EAE7B,GAAG,CAAA,CAAE,GAECkB,IAAuBJ,EAAY,MAAM;AAC7C,IAAIjB,EAAW,WACbA,EAAW,QAAQ,qBAAA;AAAA,EAEvB,GAAG,CAAA,CAAE;AAEL,SAAO;AAAA,IACL,OAAAI;AAAA,IACA,cAAAY;AAAA,IACA,aAAAG;AAAA,IACA,YAAAC;AAAA,IACA,sBAAAC;AAAA,EAAA;AAEJ;AC9PA,MAAMxC,KAASC,EAAa,iBAAiB;AAmB7C,SAASwC,GAAoB1B,GAAqC;;AAChE,QAAM2B,wBAAY,IAAA;AAClB,aAAWC,KAAM5B;AAKf,QAJI,gBAAgB4B,KAAM,OAAOA,EAAG,cAAe,YAAYA,EAAG,cAChED,EAAM,IAAIC,EAAG,UAAU,GAGrB,iBAAiBA,KAAM,OAAOA,EAAG,eAAgB;AACnD,UAAI;AACF,cAAMC,IAAWD,EAAG,YAAA;AACpB,YAAIC,KAAA,QAAAA,EAAU;AACZ,qBAAWC,KAAQD,EAAS;AAC1B,aAAIE,IAAAD,EAAK,UAAL,QAAAC,EAAY,cACdJ,EAAM,IAAIG,EAAK,MAAM,UAAU;AAAA,MAIvC,QAAQ;AAAA,MAER;AAGJ,SAAO,MAAM,KAAKH,CAAK;AACzB;AAMA,eAAeK,GAAkBC,GAAuC;AAEtE,MADIA,EAAa,WAAW,KACxB,OAAO,WAAa,OAAe,CAAC,SAAS,MAAO;AAgBxD,QAAMC,IAAiC,CAAA;AACvC,aAAWC,KAAUF,GAAc;AACjC,UAAMG,IAAa,SAASD,CAAM;AAClC,IAAAD,EAAQ,KAAK,SAAS,MAAM,KAAKE,CAAU,CAAC;AAAA,EAC9C;AACA,QAAM,QAAQ,IAAIF,CAAO,GAerB,SAAS,MAAM,SACjB,MAAM,QAAQ,KAAK;AAAA,IACjB,SAAS,MAAM;AAAA,IACf,IAAI,QAAc,CAACG,MAAY,WAAWA,GAAS,GAAG,CAAC;AAAA,EAAA,CACxD,GAEHpD,GAAO,MAAM,WAAWiD,EAAQ,MAAM,UAAU;AAClD;AAKA,SAASI,KAA8B;AACrC,SAAO,IAAI,QAAQ,CAACD,MAAY,sBAAsB,MAAMA,EAAA,CAAS,CAAC;AACxE;AAEO,SAASE,GAAgB3C,GAA0C;AACxE,QAAM,EAAE,eAAAO,GAAe,UAAAH,GAAU,oBAAAwC,GAAoB,uBAAAC,MAA0B7C,GACzE,CAAC8C,GAAgBC,CAAiB,IAAIjC,EAAS,EAAK,GACpDkC,IAAcvC,EAAO,EAAK;AAEhC,SAAAO,EAAU,MAAM;AAcd,QAZI8B,KAGA,CAACvC,KAGDqC,KAAsB,CAACC,KAGvBD,KAAsBxC,EAAS,WAAW,KAG1C4C,EAAY,QAAS;AACzB,IAAAA,EAAY,UAAU;AAEtB,QAAIC,IAAY;AAEhB,YAAC,YAAY;AACX,UAAI;AAEF,cAAMlB,IAAQD,GAAoB1B,CAAQ;AAQ1C,YAPI2B,EAAM,SAAS,MACjB,MAAMK,GAAkBL,CAAK,GACzBkB,OAIN,MAAMP,GAAA,GACFO,GAAW;AACf,QAAAF,EAAkB,EAAI;AAAA,MACxB,SAASG,GAAK;AACZ7D,QAAAA,GAAO,MAAM,yCAAyC6D,CAAG,GAEpDD,KACHF,EAAkB,EAAI;AAAA,MAE1B,UAAA;AACE,QAAAC,EAAY,UAAU;AAAA,MACxB;AAAA,IACF,GAAA,GAEO,MAAM;AACX,MAAAC,IAAY,IACZD,EAAY,UAAU;AAAA,IACxB;AAAA,EACF,GAAG,CAACzC,GAAeH,GAAUwC,GAAoBC,GAAuBC,CAAc,CAAC,GAEhFA;AACT;ACzGO,MAAMK,KAA0C;AAAA;AAAA,EAErD,WAAW;AAAA,EACX,KAAO;AAAA,EACP,QAAU;AAAA,EACV,QAAU;AAAA,EACV,cAAc;AAAA;AAAA,EAEd,YAAY;AAAA,EACZ,MAAQ;AAAA,EACR,OAAS;AAAA,EACT,aAAa;AAAA;AAAA,EAEb,IAAM;AAAA,EACN,GAAK;AAAA,EACL,IAAM;AAAA,EACN,GAAK;AAAA,EACL,GAAK;AAAA,EACL,GAAK;AAAA,EACL,IAAM;AAAA,EACN,GAAK;AAAA,EACL,IAAM;AACR;AAKO,SAASC,GAAaC,GAAkD;AAC7E,SAAKA,KACEF,GAAaE,CAAK,KAAK;AAChC;AAiBO,SAASC,GACdC,GACAC,GACwB;AAExB,QAAMC,IAAOD,EAAU,aAAa,SAG9BE,KAAQD,MAAS,YAAYD,EAAU,YAAY,MAAM,GACzDG,KAAUF,MAAS,YAAYD,EAAU,cAAc,MAAM,GAC7DI,KAAWH,MAAS,YAAYD,EAAU,eAAe,MAAM,GAC/DK,KAASJ,MAAS,YAAYD,EAAU,aAAa,MAAM,GAE3DM,IAAiBN,EAAU,QAAQK,IAAQF,GAC3CI,IAAkBP,EAAU,SAASE,IAAOE,GAE5CI,IAASF,IAAiBP,EAAQ,OAClCU,IAASF,IAAkBR,EAAQ,QAInCW,IAAaT,MAAS,YACxB,KAAK,IAAIO,GAAQC,CAAM,IACvB,KAAK,IAAID,GAAQC,CAAM,GAGrBE,IAAYX,EAAU,SAAS,GAC/BY,IAAaF,IAAaC,GAG1BE,IAAqBd,EAAQ,QAAQa,GACrCE,IAAsBf,EAAQ,SAASa,GAGvCG,IAAYf,EAAU,SAAS;AACrC,MAAIgB,IAAI,GACJC,IAAI;AAIR,QAAMC,IAAYL,IAAqBP,GACjCa,IAAYL,IAAsBP;AAGxC,UAAQQ,GAAA;AAAA;AAAA,IAEN,KAAK;AACH,MAAAC,IAAI,GACJC,IAAI;AACJ;AAAA,IACF,KAAK;AACH,MAAAD,IAAI,CAACE,IAAY,GACjBD,IAAI;AACJ;AAAA,IACF,KAAK;AACH,MAAAD,IAAI,CAACE,GACLD,IAAI;AACJ;AAAA;AAAA,IAGF,KAAK;AACH,MAAAD,IAAI,GACJC,IAAI,CAACE,IAAY;AACjB;AAAA,IACF,KAAK;AAAA;AAAA,IACL;AACE,MAAAH,IAAI,CAACE,IAAY,GACjBD,IAAI,CAACE,IAAY;AACjB;AAAA,IACF,KAAK;AACH,MAAAH,IAAI,CAACE,GACLD,IAAI,CAACE,IAAY;AACjB;AAAA;AAAA,IAGF,KAAK;AACH,MAAAH,IAAI,GACJC,IAAI,CAACE;AACL;AAAA,IACF,KAAK;AACH,MAAAH,IAAI,CAACE,IAAY,GACjBD,IAAI,CAACE;AACL;AAAA,IACF,KAAK;AACH,MAAAH,IAAI,CAACE,GACLD,IAAI,CAACE;AACL;AAAA,EAAA;AAIJ,EAAAH,KAAKX,GACLY,KAAKf;AAIL,QAAMkB,IAAUpB,EAAU,WAAW,GAC/BqB,IAAUrB,EAAU,WAAW,GAG/BsB,IAASJ,GACTK,KAASJ;AAEf,SAAAH,KAAKI,IAAUE,GACfL,KAAKI,IAAUE,IAGfP,IAAI,KAAK,MAAMA,CAAC,GAChBC,IAAI,KAAK,MAAMA,CAAC,GAET;AAAA;AAAA,IAEL,OAAOL;AAAA;AAAA,IAGP,OAAO,KAAK,MAAMC,CAAkB;AAAA,IACpC,QAAQ,KAAK,MAAMC,CAAmB;AAAA;AAAA,IAGtC,GAAAE;AAAA,IACA,GAAAC;AAAA;AAAA,IAGA,OAAO;AAAA,MACL,YAAAP;AAAA,MACA,WAAAC;AAAA,MACA,UAAU,EAAE,GAAGO,GAAW,GAAGC,EAAA;AAAA,MAC7B,WAAAJ;AAAA,MACA,eAAe,EAAE,GAAGK,IAAUE,GAAQ,GAAGD,IAAUE,GAAA;AAAA,IAAO;AAAA,EAC5D;AAEJ;AC1NA,MAAMC,KAAgD;AAAA,EACpD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,aAAa;AACf;AAKO,SAASC,GAAgBC,GAAsB;AACpD,MAAI;AACF,UAAMC,IAAS,IAAI,IAAID,CAAG;AAE1B,WAAO,CAAC,SAAS,UAAU,SAAS,OAAO,EAAE,SAASC,EAAO,QAAQ;AAAA,EACvE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsBC,GACpBF,GACAlF,IAA8B,IACJ;AAC1B,QAAMqF,IAAO,EAAE,GAAGL,IAAiB,GAAGhF,EAAA;AAGtC,MAAIqF,EAAK,eAAe,CAACJ,GAAgBC,CAAG;AAC1C,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,IAAI,MAAM,sBAAsBA,CAAG,EAAE;AAAA,IAAA;AAIhD,MAAII;AAGJ,WAASC,IAAU,GAAGA,KAAWF,EAAK,SAASE;AAC7C,QAAI;AAEF,aADe,MAAMC,GAAcN,GAAKG,EAAK,OAAO;AAAA,IAEtD,SAAS3F,GAAO;AACd,MAAA4F,IAAY5F,aAAiB,QAAQA,IAAQ,IAAI,MAAM,OAAOA,CAAK,CAAC,GAGhE6F,IAAUF,EAAK,WACjB,MAAMI,GAAMJ,EAAK,UAAU;AAAA,IAE/B;AAGF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,OAAOC,KAAa,IAAI,MAAM,oCAAoC;AAAA,EAAA;AAEtE;AAKA,SAASE,GAAcN,GAAaQ,GAA2C;AAC7E,SAAO,IAAI,QAAQ,CAACjD,MAAY;AAC9B,UAAMkD,IAAM,IAAI,MAAA;AAChB,IAAAA,EAAI,cAAc;AAElB,QAAIC,IAAkD,MAClDC,IAAW;AAEf,UAAMC,IAAU,MAAM;AACpB,MAAIF,MACF,aAAaA,CAAS,GACtBA,IAAY;AAAA,IAEhB,GAEMG,IAAgB,MAAM;AAC1B,UAAIF,EAAU;AACd,MAAAA,IAAW,IACXC,EAAA;AAEA,YAAME,IAAIL,EAAI,gBAAgBA,EAAI,OAC5BM,IAAIN,EAAI,iBAAiBA,EAAI;AACnC,MAAAlD,EAAQ;AAAA,QACN,SAAS;AAAA,QACT,SAASkD;AAAA,QACT,OAAOK;AAAA,QACP,QAAQC;AAAA,QACR,aAAaA,IAAI,IAAID,IAAIC,IAAI;AAAA,MAAA,CAC9B;AAAA,IACH,GAEMC,IAAc,CAACC,MAAqB;AACxC,MAAIN,MACJA,IAAW,IACXC,EAAA,GAEArD,EAAQ;AAAA,QACN,SAAS;AAAA,QACT,OAAO,IAAI,MAAM0D,CAAQ;AAAA,MAAA,CAC1B;AAAA,IACH;AAEA,IAAAR,EAAI,SAASI,GAEbJ,EAAI,UAAU,MAAM;AAClB,MAAAO,EAAY,yBAAyBhB,CAAG,EAAE;AAAA,IAC5C,GAGAU,IAAY,WAAW,MAAM;AAC3B,MAAAM,EAAY,8BAA8BR,CAAO,OAAOR,CAAG,EAAE;AAAA,IAC/D,GAAGQ,CAAO,GAGVC,EAAI,MAAMT;AAAA,EACZ,CAAC;AACH;AAoHA,eAAsBkB,GACpBlB,GACAmB,GACApG,IAAsC,CAAA,GACtCqG,IAAoC,IAWnC;;AACD,QAAMC,IAAa,MAAMnB,GAAiBF,GAAKoB,CAAa;AAE5D,MAAI,CAACC,EAAW,WAAW,CAACA,EAAW,SAAS,CAACA,EAAW;AAC1D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAOA,EAAW,SAAS,IAAI,MAAM,gCAAgC;AAAA,IAAA;AAKzE,QAAMC,IAAmC;AAAA,IACvC,OAAOH,EAAiB,SAAS;AAAA,IACjC,QAAQA,EAAiB,SAAS;AAAA,IAClC,OAAOA,EAAiB;AAAA,IACxB,OAAOjD,GAAaiD,EAAiB,SAAS;AAAA,IAC9C,SAASA,EAAiB;AAAA,IAC1B,SAASA,EAAiB;AAAA,IAC1B,WAAWA,EAAiB;AAAA,IAC5B,WAAWA,EAAiB;AAAA,IAC5B,aAAaA,EAAiB;AAAA,IAC9B,cAAcA,EAAiB;AAAA,IAC/B,YAAYA,EAAiB;AAAA,EAAA,GAGzB7C,IAAYF;AAAA,IAChB,EAAE,OAAOiD,EAAW,OAAO,QAAQA,EAAW,OAAA;AAAA,IAC9CC;AAAA,EAAA,GAQIC,IAAWJ,EAAiB,SAAS,IAAI7C,EAAU,GACnDkD,IAAWL,EAAiB,SAAS,IAAI7C,EAAU,GAGnDmD,IAAUF,IAAWjD,EAAU,QAAQ,GACvCoD,IAAUF,IAAWlD,EAAU,SAAS;AA2B9C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,SAvBmB,IAAIqD,GAAa;AAAA,MACpC,GAAG5G;AAAA,MACH,GAAG0G;AAAA,MACH,GAAGC;AAAA,MACH,UAAU1B;AAAA,MACV,kBAAkBqB,EAAW;AAAA,MAC7B,oBAAoB;AAAA;AAAA,MACpB,eAAe;AAAA,QACb,MAAM;AAAA,QACN,OAAO/C,EAAU;AAAA,QACjB,QAAQA,EAAU;AAAA,QAClB,SAAOrB,IAAAlC,EAAO,kBAAP,gBAAAkC,EAAsB,UAAS;AAAA,QACtC,SAAO2E,IAAA7G,EAAO,kBAAP,gBAAA6G,EAAsB,UAAS;AAAA,QACtC,aAAWC,IAAA9G,EAAO,kBAAP,gBAAA8G,EAAsB,cAAa;AAAA,QAC9C,cAAYC,IAAA/G,EAAO,kBAAP,gBAAA+G,EAAsB,eAAc;AAAA,QAChD,kBAAgBC,IAAAhH,EAAO,kBAAP,gBAAAgH,EAAsB,mBAAkB;AAAA,QACxD,gBAAcC,IAAAjH,EAAO,kBAAP,gBAAAiH,EAAsB,iBAAgB;AAAA,QACpD,gBAAcC,IAAAlH,EAAO,kBAAP,gBAAAkH,EAAsB,iBAAgB;AAAA,MAAA;AAAA,IACtD,CACD;AAAA,IAKC,WAAW;AAAA,MACT,aAAa,EAAE,OAAOZ,EAAW,OAAO,QAAQA,EAAW,OAAA;AAAA,MAC3D,WAAW,EAAE,OAAO/C,EAAU,OAAO,QAAQA,EAAU,OAAA;AAAA,MACvD,UAAU,EAAE,GAAGmD,GAAS,GAAGC,EAAA;AAAA,MAC3B,OAAOpD,EAAU;AAAA,IAAA;AAAA,EACnB;AAEJ;AA0CA,SAASiC,GAAM2B,GAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC3E,MAAY,WAAWA,GAAS2E,CAAE,CAAC;AACzD;ACpWA,MAAMC,IAAM/H,EAAa,gBAAgB;AAu6BzC,SAASgI,GACPC,GACAC,GACAC,GACAC,GACQ;AAER,MAAIA,KAAW,KAAK,CAAC,SAASA,CAAO;AACnC,WAAOD;AAGT,QAAME,IAAcJ,IAAQE,GACtBG,IAAeJ,IAASC;AAI9B,MAH0B,KAAK,IAAIE,GAAaC,CAAY,KAGnCF;AACvB,WAAOD;AAIT,QAAMI,IAAc,KAAK,IAAIN,GAAOC,CAAM,GACpCM,IAAcJ,IAAUG;AAG9B,SAAO,KAAK,IAAIC,GAAaL,CAAc;AAC7C;AAKA,MAAMM,KAAsBC,GAAsD,CAACvI,GAAOwI,MAAQ;AAEhG,QAAM;AAAA,IACJ,cAAcC;AAAA,IACd,aAAaC;AAAA,IACb,cAAcC;AAAA,IACd,KAAAC;AAAA,IACA,WAAWC;AAAA,IACX,gBAAgBC;AAAA,IAChB,kBAAAC;AAAA,IACA,iBAAAC;AAAA,IACA,UAAAC;AAAA,IACA,mBAAAC;AAAA,IACA,oBAAoBC;AAAA,IACpB,UAAAvI;AAAA,IACA,mBAAAC;AAAA,IACA,gBAAAuI;AAAA,IACA,eAAAC;AAAA,IACA,aAAAC;AAAA,IACA,cAAAC;AAAA,IACA,SAAAC;AAAA,IACA,SAAAC;AAAA,IACA,WAAAC;AAAA,IACA,OAAAC;AAAA,IACA,iBAAiBC;AAAA,IACjB,SAAAC;AAAA,EAAA,IACE7J,GAGE8J,KAAqBX,GACrBY,IAAmB/J,EAAM,qBAAoByI,KAAA,gBAAAA,EAAe,mBAC5DuB,IAAmBhK,EAAM,qBAAoByI,KAAA,gBAAAA,EAAe,WAAU,WACtEwB,KAAgBjK,EAAM,kBAAiByI,KAAA,gBAAAA,EAAe,cAAa,IACnEyB,IAAclK,EAAM,gBAAeyI,KAAA,gBAAAA,EAAe,UAAS,GAC3D0B,IAAgBnK,EAAM,kBAAiByI,KAAA,gBAAAA,EAAe,YAAW,KACjE2B,KAAoBpK,EAAM,sBAAqByI,KAAA,gBAAAA,EAAe,gBAAe,OAC7E4B,KAAqBrK,EAAM,uBAAsByI,KAAA,gBAAAA,EAAe,iBAAgB,MAGhF6B,IAAetK,EAAM,iBAAgB0I,KAAA,gBAAAA,EAAc,MACnD6B,KAAwBvK,EAAM,0BAAyB0I,KAAA,gBAAAA,EAAc,cAAa,UAClF8B,KAAoBxK,EAAM,sBAAqB0I,KAAA,gBAAAA,EAAc,UAAS,GACtE+B,KAAwBzK,EAAM,0BAAyB0I,KAAA,gBAAAA,EAAc,cAAa,SAGlFZ,KAAQ9H,EAAM,UAAS2I,KAAA,gBAAAA,EAAe,UAAS,MAC/CZ,KAAS/H,EAAM,WAAU2I,KAAA,gBAAAA,EAAe,WAAU,MAClD+B,KAAc1K,EAAM,gBAAe2I,KAAA,gBAAAA,EAAe,gBAAe,KACjEgC,KAAuB3K,EAAM,yBAAwB2I,KAAA,gBAAAA,EAAe,yBAAwB,GAC5FiC,KAAc5K,EAAM,gBAAe2I,KAAA,gBAAAA,EAAe,cAClDkC,KAAY7K,EAAM,cAAa2I,KAAA,gBAAAA,EAAe,YAC9CmC,KAAqB9K,EAAM,uBAAsB2I,KAAA,gBAAAA,EAAe,uBAAsB,IACtFoC,KAAa/K,EAAM,eAAc2I,KAAA,gBAAAA,EAAe,eAAc,IAC9DqC,KAAyBhL,EAAM,2BAA0B2I,KAAA,gBAAAA,EAAe,yBACxEsC,KAAqBjL,EAAM,uBAAsB2I,KAAA,gBAAAA,EAAe,qBAChEuC,KAAgBlL,EAAM,kBAAiB2I,KAAA,gBAAAA,EAAe,gBACtDwC,KAAcnL,EAAM,aACpBoL,KAAapL,EAAM,YACnBqL,KAAkBzB,KAAwB,IAG1C0B,KAAcC,GAAQ,MAAMC,GAAW5C,KAAO,YAAY,GAAG,CAACA,CAAG,CAAC;AAGxE,EAAArH,EAAU,MAAM;AACd,QAAIkK,GAAQ,IAAI,aAAa,iBAAiBH,IAAa;AACzD,YAAMI,IAASC,GAAYL,EAAW;AACtC,MAAKI,EAAO,SACV9D,EAAI,KAAK,4BAA4B8D,EAAO,MAAM,GAEhDA,EAAO,SAAS,SAAS,KAC3B9D,EAAI,KAAK,4BAA4B8D,EAAO,QAAQ;AAAA,IAExD;AAAA,EACF,GAAG,CAACJ,EAAW,CAAC;AAIhB,QAAMM,KAAa5K,EAAOwI,CAAO;AACjC,EAAAoC,GAAW,UAAUpC;AAErB,QAAMqC,IAAY7J,EAAY,CAAC/B,MAAuB;;AACpD,IAAA2H,EAAI,MAAM,IAAI3H,EAAM,QAAQ,KAAKA,EAAM,OAAO,IAAIA,EAAM,aAAa,IACrEyC,IAAAkJ,GAAW,YAAX,QAAAlJ,EAAA,KAAAkJ,IAAqB3L;AAAA,EACvB,GAAG,CAAA,CAAE;AAGL,EAAAsB,EAAU,OACRuK,GAAe,gBAAgBD,GACxB,MAAM;AAEX,IAAIC,GAAe,kBAAkBD,MACnCC,GAAe,gBAAgB;AAAA,EAEnC,IACC,CAACD,CAAS,CAAC;AAEd,QAAM;AAAA,IACJ,UAAAlL;AAAA,IACA,aAAAoL;AAAA,IACA,YAAAC;AAAA,IACA,iBAAAC;AAAA,IACA,kBAAAC;AAAA,IACA,gBAAAzL;AAAA,IACA,eAAAK;AAAA,IACA,gBAAAqL;AAAA,EAAA,IACEC,GAAA,GAEE,EAAE,gBAAAC,IAAgB,gBAAAC,IAAgB,WAAA5L,EAAA,IAAc6L,GAAA,GAChD,EAAE,gBAAAC,GAAgB,oBAAAC,IAAoB,sBAAAC,GAAsB,2BAAAC,GAAA,IAA8BC,GAAA;AAGhG,EAAAC,GAAoBrE,GAAK,OAAO;AAAA,IAC9B,iBAAiB,OAAOjI,IAAU,OAAO;AACvC,YAAMuM,IAASvM,EAAQ,UAAUyJ,GAC3BhC,IAAiBzH,EAAQ,SAAS2J,GAClC6C,IAAMxM,EAAQ,OAAO;AAG3B,UAAIyM,IAAiBhF;AACrB,UAAImC,IAAgB,KAAK,SAASA,CAAa;AAC7C,mBAAW8C,KAAMvM,GAAW;AAC1B,gBAAMwM,IAAmBrF,GAAwBoF,EAAG,OAAOA,EAAG,QAAQjF,GAAgBmC,CAAa;AACnG,UAAA6C,IAAiB,KAAK,IAAIA,GAAgBE,CAAgB;AAAA,QAC5D;AAIF,YAAMC,IAAgB,EAAE,OAAOH,GAAgB,QAAQ5C,IAAsD,SAASC,GAAA;AAEtH,UAAI0C,GAAK;AAEP,cAAMK,IAAcN,MAAW,SAC3B,MAAMH,GAA0BQ,CAAa,IAC7C,MAAMV,GAAmBU,CAAa,GAGpCE,IAA+C,CAAA;AACrD,mBAAW,CAACC,GAAY5B,CAAM,KAAK,OAAO,QAAQ0B,CAAW,GAAG;AAC9D,gBAAMG,IAAW7M,EAAU,KAAK,CAAAuM,MAAMA,EAAG,OAAOK,CAAU;AAC1D,UAAIC,MACFF,EAAcE,EAAS,IAAI,IAAI7B;AAAA,QAEnC;AACA,eAAO2B;AAAA,MACT,OAAO;AAEL,cAAMG,IAAiBvB,EAAgB,kBAAA;AACvC,YAAI,CAACuB;AACH,gBAAM,IAAI,MAAM,2CAA2C;AAG7D,YAAIV,MAAW,QAAQ;AACrB,gBAAMW,IAAO,MAAMf,EAAqBc,EAAe,IAAIL,CAAa;AACxE,iBAAO,EAAE,CAACK,EAAe,IAAI,GAAGC,EAAA;AAAA,QAClC,OAAO;AACL,gBAAMC,IAAU,MAAMlB,EAAegB,EAAe,IAAIL,CAAa;AACrE,iBAAO,EAAE,CAACK,EAAe,IAAI,GAAGE,EAAA;AAAA,QAClC;AAAA,MACF;AAAA,IACF;AAAA,EAAA,IACE,CAAClB,GAAgBC,IAAoBC,GAAsBC,IAA2B3C,GAAkBE,GAAaC,GAAeC,IAAmBC,IAAoB4B,GAAiBvL,CAAS,CAAC,GAG1Ma,EAAU,MAAM;AACd,IAAI8H,KA0BFA,EAzBiB,YAAY;AAG3B,YAAMmE,IAAiBvB,EAAgB,kBAAA;AACvC,UAAI,CAACuB;AACH,cAAM,IAAI,MAAM,2CAA2C;AAI7D,YAAMR,IAAiBnF;AAAA,QACrB2F,EAAe;AAAA,QACfA,EAAe;AAAA,QACftD;AAAA,QACAC;AAAA,MAAA,GAKIuB,IAAS1B,MAAqB,SAChC,MAAM0C,EAAqBc,EAAe,IAAI,EAAE,OAAOR,GAAgB,IACvE,MAAMR,EAAegB,EAAe,IAAI,EAAE,OAAOR,GAAgB;AAGrE,aAAO,EAAE,CAACQ,EAAe,IAAI,GAAG9B,EAAA;AAAA,IAClC,CACsB;AAAA,EAE1B,GAAG,CAACrC,GAAemD,GAAgBE,GAAsBT,GAAiBjC,GAAkBE,GAAaC,CAAa,CAAC;AAGvH,QAAM,CAACwD,IAAmBC,EAAoB,IAAIvM,EAA4B,MAAM,GAC9E,CAACwM,IAAYC,EAAa,IAAIzM,EAAuB,IAAI,GACzD0M,KAAe/M,EAA2B,MAAS,GAGnDgN,KAAiBhN,EAAO,EAAK,GAC7BiN,KAA2BjN,EAAO,EAAK,GACvC,CAACkN,IAA4BC,EAA6B,IAAI9M,EAAS,EAAK,GAG5E+M,KAAgBpN,EAAe,CAAC,GAChCqN,KAAmBrN,EAAe,CAAC,GAKnCsN,KAAoBtN,EAAO,EAAK;AAGtC,EAAAO,EAAU,MAAM;AACd,QAAIyM,GAAe,QAAS;AAC5B,IAAAA,GAAe,UAAU;AAGzB,UAAMO,IAAU1F,KAAmB,CAAC,EAAE,MAAM,UAAU,OAAAf,IAAO,QAAAC,IAAQ,GAG/DyG,IAAoBvC,EAAgB,gBAAA;AAC1C,QAAIuC,EAAkB,SAAS,GAAG;AAChC,YAAMC,IAAcF,EAAQ,CAAC,GACvBG,IAAgBF,EAAkB,CAAC,GASnCG,IAAYxD,KAAc,gBAAgB;AAGhD,MAAAuD,EAAc,OAAOD,EAAY,MACjCC,EAAc,QAAQD,EAAY,OAClCC,EAAc,SAASD,EAAY,QACnCC,EAAc,YAAYD,EAAY,WAClCA,EAAY,kBACdC,EAAc,kBAAkBD,EAAY,kBACnCE,MACTD,EAAc,kBAAkBC;AAIlC,eAASC,IAAI,GAAGA,IAAIL,EAAQ,QAAQK;AAClC,QAAAvC,GAAekC,EAAQK,CAAC,EAAE,OAAOL,EAAQK,CAAC,EAAE,QAAQ;AAAA,UAClD,MAAML,EAAQK,CAAC,EAAE;AAAA,UACjB,iBAAiBL,EAAQK,CAAC,EAAE,mBAAmBD;AAAA,UAC/C,WAAWJ,EAAQK,CAAC,EAAE;AAAA,QAAA,CACvB;AAAA,IAEL;AAEA,IAAA1C,GAAA;AAAA,EACF,GAAG,CAAA,CAAE,GAGL3K,EAAU,MAAM;AACd,QAAI,GAACyH,KAAmBA,EAAgB,WAAW,MAC/C,CAAAiF,GAAyB,WACxBD,GAAe,SAEpB;AAAA,MAAAC,GAAyB,UAAU,IACnCE,GAA8B,EAAI;AAElC,UAAI;AACF,cAAMU,IAAeC,GAAe,mBAAmB9F,CAAe,GAGhE+F,IAAe9C,EAAgB,gBAAA;AACrC,YAAI8C,EAAa,SAAS,GAAG;AAC3B,gBAAML,IAAgBK,EAAa,CAAC;AACpC,qBAAWxM,KAAMsM;AACf,YAAA5C,EAAgB,qBAAqB1J,EAAG,IAAImM,EAAc,EAAE;AAAA,QAEhE;AAEA,QAAA3C,GAAY8C,CAA+B;AAoB3C,cAAMG,wBAAgB,IAAA;AACtB,mBAAWzM,KAAMsM,GAAc;AAC7B,gBAAM/L,IAAUP,EAAgC;AAChD,UAAI,OAAOO,KAAW,YAAYA,EAAO,KAAA,EAAO,SAAS,KACvDkM,EAAU,IAAIlM,CAAM;AAAA,QAExB;AACA,YAAIkM,EAAU,OAAO,KAAK,OAAO,WAAa,KAAa;AACzD,gBAAMC,IACH,SAAgD,SAAS,MACtDC,IAAe,MAAM,KAAKF,CAAS,EAAE,IAAI,CAAClM,MAAW;AACzD,kBAAMqM,IAAS,QAAQrM,EAAO,QAAQ,QAAQ,GAAG,EAAE,aAAa;AAChE,gBAAIsM,IAAO,SAAS,eAAeD,CAAM;AACzC,mBAAKC,MACHA,IAAO,SAAS,cAAc,MAAM,GACpCA,EAAK,KAAKD,GACVC,EAAK,MAAM,cACXA,EAAK,OAAO,4CAA4CtM,EAAO;AAAA,cAC7D;AAAA,cACA;AAAA,YAAA,CACD,8BACD,SAAS,KAAK,YAAYsM,CAAI,IAEzB,IAAI,QAAgB,CAACpM,MAAY;AAEtC,kBAAKoM,EAAyB,OAAO;AACnC,gBAAApM,EAAQF,CAAM;AACd;AAAA,cACF;AACA,oBAAMuM,IAAS,MAAMrM,EAAQF,CAAM;AACnC,cAAAsM,EAAM,iBAAiB,QAAQC,GAAQ,EAAE,MAAM,IAAM,GACrDD,EAAM,iBAAiB,SAASC,GAAQ,EAAE,MAAM,IAAM,GAEtD,WAAWA,GAAQ,GAAI;AAAA,YACzB,CAAC;AAAA,UACH,CAAC;AACD,kBAAQ,IAAIH,CAAY,EACrB,KAAK,CAACI,MAAa;AAClB,gBAAKL;AACL,qBAAO,QAAQ;AAAA,gBACbK,EAAS,QAAQ,CAACxM,MAAW;AAAA,kBAC3BmM,EAAS,KAAK,aAAanM,CAAM,GAAG,EAAE,MAAM,MAAA;AAAA,mBAAe;AAAA,kBAC3DmM,EAAS,KAAK,aAAanM,CAAM,GAAG,EAAE,MAAM,MAAA;AAAA,mBAAe;AAAA,gBAAA,CAC5D;AAAA,cAAA;AAAA,UAEL,CAAC,EACA,KAAK,MAAM;AAKV,YAAAiJ,GAAY,CAACwD,MAASA,EAAK,MAAA,CAAO;AAAA,UACpC,CAAC;AAAA,QACL;AAAA,MACF,SAAS9L,GAAK;AACZ,QAAAmE,EAAI,MAAM,oCAAoCnE,CAAG;AACjD,cAAM+L,IAAY/L,aAAe,QAAQA,IAAM,IAAI,MAAM,OAAOA,CAAG,CAAC;AACpE,QAAAoI,EAAU;AAAA,UACR,UAAU;AAAA,UACV,SAAS,oCAAoC2D,EAAU,OAAO;AAAA,UAC9D,eAAeA;AAAA,UACf,aAAa;AAAA,QAAA,CACd;AAAA,MACH;AAAA;AAAA,EACF,GAAG,CAACxG,GAAiBiD,GAAiBF,IAAaF,CAAS,CAAC,GAG7DtK,EAAU,MAAM;AACd,QAAI,CAACuH,GAA0B;AAE7B,MAAAwF,GAAkB,UAAU;AAC5B;AAAA,IACF;AAEA,UAAMmB,IAAiB/O,EAAU,KAAK,CAACuM,MAAOA,EAAG,SAASnE,CAAwB;AAClF,QAAI2G,GAAgB;AAClB,YAAMjC,IAAiBvB,EAAgB,kBAAA;AACvC,OAAIuB,KAAA,gBAAAA,EAAgB,UAAS1E,KAC3BwD,GAAemD,EAAe,EAAE,GAGlCnB,GAAkB,UAAU;AAAA,IAC9B;AAAA,EACF,GAAG,CAACxF,GAA0BpI,CAAS,CAAC,GAGxCa,EAAU,MAAM;AACd,QAAI,CAAC+I,KAAgByD,GAAa,YAAYzD,EAAc;AAK5D,QAAI3J,EAAS,SAAS,GAAG;AAEvB,MAAAoN,GAAa,UAAUzD;AACvB;AAAA,IACF;AAEA,IAAAyD,GAAa,UAAUzD,IAEL,YAAY;AAC5B,MAAAsD,GAAqB,SAAS,GAC9BE,GAAc,IAAI;AAElB,UAAI;AAEF,cAAMiB,IAAe9C,EAAgB,gBAAA;AACrC,YAAI8C,EAAa,WAAW;AAC1B,gBAAM,IAAI,MAAM,wBAAwB;AAK1C,cAAMW,IAA+B,CAAA;AAErC,mBAAWnC,KAAYwB,GAAc;AAEnC,gBAAMY,IAAiB9G,KAAA,gBAAAA,EAAiB,KAAK,OAAK+G,EAAE,SAASrC,EAAS,OAChEsC,KAAqBF,KAAA,gBAAAA,EAAgB,cAAalF,IAGlDiB,IAAS,MAAM/E;AAAA,YACnB2D;AAAA,YACA;AAAA,cACE,UAAU;AAAA,gBACR,OAAOiD,EAAS;AAAA,gBAChB,QAAQA,EAAS;AAAA,gBACjB,GAAGA,EAAS;AAAA,gBACZ,GAAGA,EAAS;AAAA,cAAA;AAAA,cAEd,YAAWoC,KAAA,gBAAAA,EAAgB,aAAYpF;AAAA,cACvC,OAAOC;AAAA,cACP,WAAWqF;AAAA,cACX,WAAWF,KAAA,gBAAAA,EAAgB;AAAA,cAC3B,aAAaA,KAAA,gBAAAA,EAAgB;AAAA,cAC7B,cAAcA,KAAA,gBAAAA,EAAgB;AAAA,cAC9B,YAAYA,KAAA,gBAAAA,EAAgB;AAAA,YAAA;AAAA,UAC9B;AAGF,UAAIjE,EAAO,WAAWA,EAAO,WAG3BO,EAAgB,qBAAqBP,EAAO,QAAQ,IAAI6B,EAAS,EAAE,GACnEmC,EAAY,KAAKhE,EAAO,OAAO,KAE/B9D,EAAI,MAAM,sCAAsC2F,EAAS,MAAM7B,EAAO,KAAK;AAAA,QAE/E;AAEA,YAAIgE,EAAY,SAAS;AACvB,UAAA3D,GAAY,CAACwD,MAAS,CAAC,GAAGA,GAAM,GAAGG,CAAW,CAAC,GAC/C9B,GAAqB,SAAS,GAC9BtE,KAAA,QAAAA,EAAcgB;AAAA,aACT;AACL,gBAAMwF,IAAU,IAAI,MAAM,wCAAwC;AAClE,UAAAlC,GAAqB,OAAO,GAC5BE,GAAcgC,CAAO,GACrBvG,KAAA,QAAAA,EAAee,GAAcwF,IAC7BjE,EAAU;AAAA,YACR,UAAU;AAAA,YACV,SAASiE,EAAQ;AAAA,YACjB,eAAeA;AAAA,YACf,aAAa;AAAA,UAAA,CACd;AAAA,QACH;AAAA,MACF,SAAS7P,GAAO;AACd,cAAMwD,IAAMxD,aAAiB,QAAQA,IAAQ,IAAI,MAAM,OAAOA,CAAK,CAAC;AACpE,QAAA2N,GAAqB,OAAO,GAC5BE,GAAcrK,CAAG,GACjB8F,KAAA,QAAAA,EAAee,GAAc7G,IAC7BoI,EAAU;AAAA,UACR,UAAU;AAAA,UACV,SAASpI,EAAI;AAAA,UACb,eAAeA;AAAA,UACf,aAAa;AAAA,QAAA,CACd;AAAA,MACH;AAAA,IACF,GAEA;AAAA,EACF,GAAG,CAAC6G,GAAcC,IAAuBC,IAAmBC,EAAqB,CAAC,GAGlFlJ,EAAU,MAAM;AACd,IAAA2H,KAAA,QAAAA,EAAoB8C;AAAA,EACtB,GAAG,CAACA,IAAY9C,CAAiB,CAAC,GAGlC3H,EAAU,MAAM;AACd,QAAI,CAAC0H,EAAU;AAEf,UAAMuE,IAAiBvB,EAAgB,kBAAA,GACjC8D,IAAqB;AAAA,MACzB,UAAUpP,EAAS,IAAI,CAAC4B,MAAOA,EAAG,QAAQ;AAAA,MAC1C,WAAW7B,EAAU,IAAI,CAACuM,OAAQ;AAAA,QAChC,MAAMA,EAAG;AAAA,QACT,OAAOA,EAAG;AAAA,QACV,QAAQA,EAAG;AAAA,QACX,WAAWA,EAAG;AAAA,QACd,iBAAiBA,EAAG;AAAA,MAAA,EACpB;AAAA,MACF,iBAAgBO,KAAA,gBAAAA,EAAgB,SAAQ;AAAA,IAAA;AAG1C,IAAAvE,EAAS8G,CAAK;AAAA,EAChB,GAAG,CAACpP,GAAUD,GAAWuI,CAAQ,CAAC,GAGlC1H,EAAU,MAAM;AAMd,QALI,CAACwH,KAKD,CAACuF,GAAkB,QAAS;AAEhC,UAAMd,IAAiBvB,EAAgB,kBAAA;AACvC,IAAIuB,KACFzE,EAAiByE,EAAe,IAAI;AAAA,EAExC,GAAG,CAACvB,EAAgB,oBAAA,CAAqB,CAAC;AAG1C,QAAM+D,KAAgBhO,EAAY,YAAY;AAC5C,QAAI,CAACpB,KAAY,CAACwI,EAAgB;AAElC,UAAM6G,IAAe,EAAE5B,GAAiB,SAClC6B,IAAY,KAAK,IAAA;AAIvB,QAAIlD,IAAiB9C;AACrB,QAAIC,IAAgB,KAAK,SAASA,CAAa;AAC7C,iBAAW8C,KAAMvM,GAAW;AAC1B,cAAMwM,IAAmBrF,GAAwBoF,EAAG,OAAOA,EAAG,QAAQ/C,GAAaC,CAAa;AAChG,QAAA6C,IAAiB,KAAK,IAAIA,GAAgBE,CAAgB;AAAA,MAC5D;AAIF,UAAMiD,IAAWlE,EAAgB,kBAAA,GAC3BmE,KAAmBD,KAAA,gBAAAA,EAAU,OAAM;AAGzC,IAAA/G,KAAA,QAAAA,EAAiB,EAAE,QAAQ,aAAa,YAAYgH,MAEpDhC,GAAc,UAAU8B;AAExB,QAAI;AACF,UAAIG;AAEJ,YAAMlD,IAAgB;AAAA,QACpB,QAAQ/C;AAAA,QACR,OAAO4C;AAAA,QACP,SAAS3C;AAAA,MAAA;AAGX,UAAIL,MAAqB;AAEvB,YAAIC,IAAe;AACjB,gBAAMqG,IAAa,MAAM3D,GAA0BQ,CAAa;AAChE,UAAAkD,IAAU,CAAA;AACV,qBAAW,CAAC/C,GAAYiD,CAAI,KAAK,OAAO,QAAQD,CAAU,GAAG;AAC3D,kBAAM/C,IAAW7M,EAAU,KAAK,CAACuM,MAAOA,EAAG,OAAOK,CAAU;AAC5D,YAAIC,MACF8C,EAAQ9C,EAAS,IAAI,IAAIgD;AAAA,UAE7B;AAAA,QACF,OAAO;AACL,gBAAM/C,IAAiBvB,EAAgB,kBAAA;AACvC,cAAI,CAACuB,EAAgB;AACrB,gBAAMC,IAAO,MAAMf,EAAqBc,EAAe,IAAIL,CAAa;AACxE,UAAAkD,IAAU,EAAE,CAAC7C,EAAe,IAAI,GAAGC,EAAA;AAAA,QACrC;AAAA,eAGIxD,IAAe;AACjB,cAAMqG,IAAa,MAAM7D,GAAmBU,CAAa;AACzD,QAAAkD,IAAU,CAAA;AACV,mBAAW,CAAC/C,GAAYiD,CAAI,KAAK,OAAO,QAAQD,CAAU,GAAG;AAC3D,gBAAM/C,IAAW7M,EAAU,KAAK,CAACuM,MAAOA,EAAG,OAAOK,CAAU;AAC5D,UAAIC,MACF8C,EAAQ9C,EAAS,IAAI,IAAIgD;AAAA,QAE7B;AAAA,MACF,OAAO;AACL,cAAM/C,IAAiBvB,EAAgB,kBAAA;AACvC,YAAI,CAACuB,EAAgB;AACrB,cAAME,IAAU,MAAMlB,EAAegB,EAAe,IAAIL,CAAa;AACrE,QAAAkD,IAAU,EAAE,CAAC7C,EAAe,IAAI,GAAGE,EAAA;AAAA,MACrC;AAIF,MAAA9M,KAAA,QAAAA,EAAWyP,IAGXjH,KAAA,QAAAA,EAAiB,EAAE,QAAQ,YAAY,YAAYgH,GAAkB,QAAQC;IAC/E,SAASpQ,GAAO;AAEd,YAAMuQ,IAAevQ,aAAiB,QAAQA,EAAM,UAAU,OAAOA,CAAK;AAC1E,UAAIuQ,EAAa,SAAS,0BAA0B;AAElD;AAEF,YAAMC,IAAW,KAAK,IAAA,IAAQP;AAC9B,MAAAtI,EAAI,MAAM,4BAA4BqI,IAAe,MAAM;AAAA,QACzD,UAAU,GAAGQ,CAAQ;AAAA,QACrB,OAAOD;AAAA,MAAA,CACR;AAGD,YAAM/M,IAAMxD,aAAiB,QAAQA,IAAQ,IAAI,MAAMuQ,CAAY;AACnE,MAAApH,KAAA,QAAAA,EAAiB,EAAE,QAAQ,SAAS,YAAYgH,GAAkB,OAAO3M,MAGzEoI,EAAU;AAAA,QACR,UAAU;AAAA,QACV,SAASpI,EAAI;AAAA,QACb,eAAeA;AAAA,QACf,YAAY2M;AAAA,QACZ,aAAa;AAAA,MAAA,CACd;AAAA,IACH;AAAA,EACF,GAAG,CAACxP,GAAUwI,GAAgBa,IAAeD,GAAkBE,GAAaC,GAAeC,IAAmBC,IAAoBmC,GAAgBC,IAAoBC,GAAsBC,IAA2BjM,GAAWuL,GAAiBtL,GAAUoJ,GAAkB8B,CAAS,CAAC;AAGzR,EAAAtK,EAAU,MAAM;AACd,IAAIuI,OAAuB,UAAaA,KAAqB,KAC3DlC,EAAI;AAAA,MACF;AAAA,IAAA;AAAA,EAIN,GAAG,CAAA,CAAE,GAGLrG,EAAU,MAAM;AAAA,EAChB,GAAG,CAACZ,CAAQ,CAAC;AAGb,QAAM+P,KAAwB1O,EAAY,MAAM;AAK9C,QAHAnB,KAAA,QAAAA,KAGIuI,GAAgB;AAClB,YAAM+G,IAAWlE,EAAgB,kBAAA;AACjC,MAAA7C,EAAe,EAAE,QAAQ,aAAa,aAAY+G,KAAA,gBAAAA,EAAU,OAAM,WAAW;AAAA,IAC/E;AAAA,EACF,GAAG,CAACtP,GAAmBuI,GAAgB6C,CAAe,CAAC,GAGjD9I,KAAqB,CAAC,CAAC6F,KAAmBA,EAAgB,SAAS,GACnE3F,KAAiBH,GAAgB;AAAA,IACrC,eAAApC;AAAA,IACA,UAAAH;AAAA,IACA,oBAAAwC;AAAA,IACA,uBAAuB+K;AAAA,EAAA,CACxB,GAGKyC,KAAsB3P,EAAO,EAAK;AACxC,EAAAO,EAAU,MAAM;AACd,IAAI8B,MAAkB,CAACsN,GAAoB,YACzCA,GAAoB,UAAU,IAC9BlH,KAAA,QAAAA;AAAA,EAEJ,GAAG,CAACpG,IAAgBoG,CAAO,CAAC,GAG5BnJ,GAAc;AAAA,IACZ,QAAQyJ,IAAmB;AAAA,MACzB,SAASA,EAAiB,WAAW;AAAA,MACrC,YAAYA,EAAiB,cAAc;AAAA,MAC3C,WAAWA,EAAiB,aAAa;AAAA,IAAA,IACvC;AAAA,MACF,SAAS;AAAA;AAAA,IAAA;AAAA,IAEX,gBAAAtJ;AAAA,IACA,WAAAC;AAAA,IACA,UAAAC;AAAA,IACA,UAAUqP;AAAA,IACV,mBAAmBU;AAAA,IACnB,eAAerN;AAAA;AAAA,EAAA,CAChB,GAID9B,EAAU,MAAM;AACd,QAAIb,EAAU,SAAS,KAAK,CAACI,IAAe;AAE1C,YAAMqF,IAAY,sBAAsB,MAAM;AAC5C,QAAAgG,GAAe,EAAI;AAAA,MACrB,CAAC;AACD,aAAO,MAAM,qBAAqBhG,CAAS;AAAA,IAC7C;AAAA,EAEF,GAAG,CAACzF,EAAU,QAAQI,IAAeqL,EAAc,CAAC,GAGpD5K,EAAU,MACD,MAAM;AACX,IAAA4K,GAAe,EAAK;AAAA,EACtB,GACC,CAACA,EAAc,CAAC;AAKnB,QAAMyE,KAAsB5O,EAAY,CAAC/B,MAAiB;AACxD,IAAA4L,EAAU;AAAA,MACR,UAAU;AAAA,MACV,SAAS5L,EAAM;AAAA,MACf,eAAeA;AAAA,MACf,aAAa;AAAA,IAAA,CACd;AAAA,EACH,GAAG,CAAC4L,CAAS,CAAC;AAGd,SAAI8B,OAAsB,YAEtB,gBAAAtN;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAAqJ;AAAA,MACA,OAAO;AAAA,QACL,GAAGC;AAAA,QACH,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,MAAA;AAAA,MAGnB,UAAA,gBAAAtJ,EAACwQ,IAAA,EAAQ,MAAM,GAAA,CAAI;AAAA,IAAA;AAAA,EAAA,IAMrBlD,OAAsB,WAAWE,KAEjC,gBAAAxN;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAAqJ;AAAA,MACA,OAAO;AAAA,QACL,GAAGC;AAAA,QACH,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,OAAO;AAAA,QACP,SAAS;AAAA,MAAA;AAAA,MAGX,UAAA,gBAAAvJ,GAAC,OAAA,EAAI,WAAU,eACb,UAAA;AAAA,QAAA,gBAAAC,EAAC,OAAA,EAAI,WAAU,eAAc,UAAA,wBAAoB;AAAA,QACjD,gBAAAA,EAAC,OAAA,EAAI,WAAU,2BAA2B,aAAW,QAAA,CAAQ;AAAA,MAAA,EAAA,CAC/D;AAAA,IAAA;AAAA,EAAA,sBAQHyQ,IAAA,EAAY,KAAKxF,IAChB,UAAA,gBAAAjL,EAAC,SAAI,WAAW,cAAcqJ,KAAa,EAAE,IAAI,OAAO,EAAE,GAAGC,GAAO,UAAU,cAC5E,UAAA,gBAAAvJ;AAAA,IAACN;AAAA,IAAA;AAAA,MACC,SAAS8Q;AAAA,MACT,UAAU,CAAC3Q,GAAO8Q,MAChB,gBAAA3Q;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,OAAO;AAAA,YACL,SAAS;AAAA,YACT,eAAe;AAAA,YACf,YAAY;AAAA,YACZ,gBAAgB;AAAA,YAChB,SAAS;AAAA,YACT,WAAW;AAAA,YACX,iBAAiB;AAAA,YACjB,OAAO;AAAA,YACP,YAAY;AAAA,YACZ,WAAW;AAAA,UAAA;AAAA,UAGb,UAAA;AAAA,YAAA,gBAAAC,EAAC,OAAA,EAAI,OAAO,EAAE,UAAU,QAAQ,YAAY,KAAK,cAAc,MAAA,GAAS,UAAA,uBAAA,CAExE;AAAA,YACA,gBAAAA,EAAC,OAAA,EAAI,OAAO,EAAE,UAAU,QAAQ,SAAS,KAAK,cAAc,QAAQ,UAAU,QAAA,GAC3E,YAAM,SACT;AAAA,YACA,gBAAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS0Q;AAAA,gBACT,OAAO;AAAA,kBACL,SAAS;AAAA,kBACT,iBAAiB;AAAA,kBACjB,OAAO;AAAA,kBACP,QAAQ;AAAA,kBACR,cAAc;AAAA,kBACd,UAAU;AAAA,kBACV,YAAY;AAAA,kBACZ,QAAQ;AAAA,gBAAA;AAAA,gBAEX,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAED;AAAA,QAAA;AAAA,MAAA;AAAA,MAIH,UAAA;AAAA,QAAA,CAAChG,OACAC,MAA0BC,KACxB,gBAAA5K,EAAC,SAAI,WAAW2K,IAAwB,OAAOC,IAC7C,UAAA,gBAAA5K;AAAA,UAAC2Q;AAAA,UAAA;AAAA,YACC,OAAO,EAAE,OAAO,OAAA;AAAA,YAChB,YAAYtG;AAAA,YACZ,sBAAAC;AAAA,YACA,aAAAC;AAAA,YACA,WAAAC;AAAA,YACA,oBAAAC;AAAA,YACA,iBAAAO;AAAA,YACA,eAAAH;AAAA,YACA,aAAAC;AAAA,YACA,YAAAC;AAAA,UAAA;AAAA,QAAA,GAEJ,IAEA,gBAAA/K;AAAA,UAAC2Q;AAAA,UAAA;AAAA,YACC,OAAO,EAAE,OAAO,OAAA;AAAA,YAChB,YAAYtG;AAAA,YACZ,sBAAAC;AAAA,YACA,aAAAC;AAAA,YACA,WAAAC;AAAA,YACA,oBAAAC;AAAA,YACA,iBAAAO;AAAA,YACA,eAAAH;AAAA,YACA,aAAAC;AAAA,YACA,YAAAC;AAAA,UAAA;AAAA,QAAA;AAAA,QAILvB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA,GAEL,EAAA,CACF;AAEJ,CAAC;AAEDvB,GAAoB,cAAc;AAwB3B,MAAM2I,KAAiB1I,GAAsD,CAACvI,GAAOwI,MAAQ;;AAClG,QAAM,EAAE,cAAA0I,GAAc,kBAAAC,EAAA,IAAqBnR,GAGrCoR,IAAsBpR,EAAM,iBAAe0C,IAAA1C,EAAM,iBAAN,gBAAA0C,EAAoB,cAE/D2O,IACJ,gBAAAhR,EAACiI,IAAA,EAAoB,KAAAE,GAAW,GAAGxI,GAAO;AAK5C,SAAImR,sBAECG,IAAA,EAAc,cAAa,SAAQ,SAASJ,GAC1C,UAAAG,GACH,IAKF,gBAAAhR,EAACiR,IAAA,EAAc,cAAa,SAAQ,SAASJ,GAC3C,UAAA,gBAAA7Q,EAACkR,IAAA,EAAe,aAAaH,GAC1B,UAAAC,EAAA,CACH,GACF;AAEJ,CAAC;AAEDJ,GAAe,cAAc;ACz1D7B,MAAMrR,KAASC,EAAa,aAAa;AAoDlC,SAAS2R,KAAiC;AAC/C,QAAMC,IAAUrF,GAAA,GAGV;AAAA,IACJ,MAAAsF;AAAA,IACA,MAAAC;AAAA,IACA,SAAAC;AAAA,IACA,SAAAC;AAAA,IACA,sBAAAC;AAAA,IACA,mBAAAC;AAAA,IACA,sBAAAC;AAAA,IACA,uBAAAC;AAAA,IACA,qBAAAC;AAAA,IACA,uBAAAC;AAAA,IACA,uBAAAC;AAAA,IACA,uBAAAC;AAAA,EAAA,IACEZ,GAGEa,IAAetQ,EAAY,MAAM;AACrC,IAAApC,GAAO,KAAK,oEAAoE;AAAA,EAClF,GAAG,CAAA,CAAE,GAGC2S,IAAuBvQ,EAAY,CAACwQ,MAAwB;AAChE,IAAA5S,GAAO,KAAK,4EAA4E;AAAA,EAC1F,GAAG,CAAA,CAAE;AAEL,SAAO;AAAA;AAAA,IAEL,MAAA8R;AAAA,IACA,MAAAC;AAAA,IACA,SAAAC;AAAA,IACA,SAAAC;AAAA;AAAA,IAGA,cAAAS;AAAA,IACA,sBAAAC;AAAA;AAAA,IAGA,sBAAAT;AAAA,IACA,mBAAAC;AAAA,IACA,sBAAAC;AAAA,IACA,uBAAAC;AAAA,IACA,uBAAAE;AAAA,IACA,uBAAAC;AAAA,IACA,uBAAAC;AAAA;AAAA,IAGA,qBAAAH;AAAA,EAAA;AAEJ;ACtHO,SAASO,KAA2C;AACzD,QAAM,EAAE,iBAAAC,EAAA,IAAoBtG,GAAA;AAC5B,SAAOsG,KAAmB;AAC5B;ACEO,SAASC,KAA0B;AACxC,QAAM,EAAE,eAAA7R,EAAA,IAAkBsL,GAAA;AAC1B,SAAOtL;AACT;ACmBA,MAAM8R,KAAO,MAAM;AAAC,GACdC,KAAkC,CAAA;AAajC,SAASC,GAAeC,GAAiC;AAC9D,QAAM,EAAE,cAAAC,EAAA,IAAiBC,GAAA,GACnB,EAAE,sBAAAnB,EAAA,IAAyBoB,GAAA,GAE3BC,IAAe5H,GAAQ,MACfyH,EAAa,aAAaD,CAAI,EAC/B,OAAO,CAACxQ,MAA0BA,aAAc6Q,EAAW,GACrE,CAACJ,GAAcD,CAAI,CAAC,GAEjBM,IAAUrR;AAAA,IACd,CAACsR,MAAoB;AACnB,iBAAW/Q,KAAM4Q,GAAc;AAC7B,cAAMI,IAAShR,EAAG,MAAA;AAClB,QAAAgR,EAAO,QAAQD,CAAO,GACtBxB,EAAqBvP,GAAIgR,CAAM;AAAA,MACjC;AAAA,IACF;AAAA,IACA,CAACJ,GAAcrB,CAAoB;AAAA,EAAA;AAGrC,SAAIqB,EAAa,WAAW,IACnB,EAAE,MAAM,IAAI,SAASP,IAAM,SAAS,MAAM,UAAUC,IAAO,aAAa,GAAA,IAG1E;AAAA,IACL,MAAMM,EAAa,CAAC,EAAE,QAAA;AAAA,IACtB,SAAAE;AAAA,IACA,SAASF,EAAa,CAAC;AAAA,IACvB,UAAUA;AAAA,IACV,aAAa;AAAA,EAAA;AAEjB;ACnCA,MAAMP,KAAO,MAAM;AAAC,GACdC,KAAkC,CAAA;AAKxC,SAASW,GACPC,GACAC,GACAC,GACA;AACA,QAAMC,IAAgBH,IAAYC;AAElC,MAAIC,KAAoBC,GAAe;AAErC,UAAMC,IAAcH,GACdI,IAAaD,IAAcF,GAC3BI,IAAYN,IAAYK;AAC9B,WAAO;AAAA,MACL,OAAOA;AAAA,MACP,QAAQD;AAAA,MACR,QAAQ,IAAIE,KAAa;AAAA,MACzB,OAAO;AAAA,MACP,WAAAA;AAAA,MACA,YAAY;AAAA,IAAA;AAAA,EAEhB,OAAO;AAEL,UAAMD,IAAaL,GACbI,IAAcC,IAAaH,GAC3BK,IAAaN,IAAaG;AAChC,WAAO;AAAA,MACL,OAAOC;AAAA,MACP,QAAQD;AAAA,MACR,OAAO;AAAA,MACP,QAAQ,IAAIG,KAAc;AAAA,MAC1B,WAAW;AAAA,MACX,YAAAA;AAAA,IAAA;AAAA,EAEJ;AACF;AAcO,SAASC,GAAgBlB,GAAcxS,GAAmD;AAC/F,QAAM2T,KAAM3T,KAAA,gBAAAA,EAAS,QAAO,SACtB,EAAE,cAAAyS,GAAc,aAAAjH,EAAA,IAAgBkH,GAAA,GAChC,EAAE,sBAAAnB,EAAA,IAAyBoB,GAAA,GAE3BiB,IAAgB5I,GAAQ,MAChByH,EAAa,aAAaD,CAAI,EAC/B,OAAO,CAACxQ,MAA2BA,aAAc6E,EAAY,GACvE,CAAC4L,GAAcD,CAAI,CAAC,GAIjBqB,IAAcpT,EAAuD,oBAAI,KAAK;AACpF,aAAWuB,KAAM4R;AACf,QAAI,CAACC,EAAY,QAAQ,IAAI7R,EAAG,EAAE,GAAG;AACnC,YAAM8R,IAAK9R,EAAG;AACd,MAAA6R,EAAY,QAAQ,IAAI7R,EAAG,IAAI;AAAA,QAC7B,OAAO8R,EAAG,QAAQA,EAAG;AAAA,QACrB,QAAQA,EAAG,SAASA,EAAG;AAAA,MAAA,CACxB;AAAA,IACH;AAMF,QAAMC,IAAmBtT,EAAOmT,CAAa;AAC7C,EAAAG,EAAiB,UAAUH;AAC3B,QAAMI,IAAavT,EAAO8Q,CAAoB;AAC9C,EAAAyC,EAAW,UAAUzC;AACrB,QAAM0C,IAAiBxT,EAAO+K,CAAW;AACzC,EAAAyI,EAAe,UAAUzI;AAEzB,QAAM0I,IAAczS;AAAA,IAClB,CAACyD,MAAgB;AACf,YAAMiP,IAAkBJ,EAAiB,SACnCK,IAAOJ,EAAW;AACD,MAAAC,EAAe;AAEtC,iBAAWjS,KAAMmS,GAAiB;AAChC,cAAME,IAAOR,EAAY,QAAQ,IAAI7R,EAAG,EAAE;AAC1C,YAAI,CAACqS,EAAM;AACX,cAAM,EAAE,OAAOC,GAAO,QAAQC,MAAUF,GAElCrB,IAAShR,EAAG,MAAA;AAClB,QAAAgR,EAAO,cAAc,IACrBA,EAAO,eAAe,MACtBA,EAAO,aAAa,IACpBA,EAAO,WAAW9N,GAEdyO,MAAQ,WACVX,EAAO,qBAAqB,IAE5BA,EAAO,iBAAiB,CAACwB,MAAkB;AACzC,gBAAMC,IAAQD,EAAc,oBAAoB,GAC1CE,IAAOzB,GAAUqB,GAAOC,GAAOE,CAAK;AAE1C,UAAAD,EAAc,cAAc,QAAQE,EAAK,OACzCF,EAAc,cAAc,SAASE,EAAK,QAC1CF,EAAc,cAAc,QAAQE,EAAK,OACzCF,EAAc,cAAc,QAAQE,EAAK,OACzCF,EAAc,cAAc,YAAYE,EAAK,WAC7CF,EAAc,cAAc,aAAaE,EAAK,YAK9CN,EAAKpS,GAAIwS,CAAa;AAAA,QACxB,MAEAxB,EAAO,qBAAqB,IAE5BA,EAAO,iBAAiB,CAACwB,MAAkB;AACzC,gBAAMxO,IAAIwO,EAAc,cAAc,OAChCvO,IAAIuO,EAAc,cAAc,QAChCG,IAAQ,KAAK,IAAIL,IAAQtO,GAAGuO,IAAQtO,GAAG,CAAC;AAC9C,UAAI0O,IAAQ,MACVH,EAAc,cAAc,QAAQxO,IAAI2O,GACxCH,EAAc,cAAc,SAASvO,IAAI0O,IAE3CH,EAAc,cAAc,QAAQ,GACpCA,EAAc,cAAc,QAAQ,GACpCA,EAAc,cAAc,YAAY,GACxCA,EAAc,cAAc,aAAa,GAEzCJ,EAAKpS,GAAIwS,CAAa;AAAA,QACxB,IAKFxB,EAAO,UAAU9N,CAAG;AAAA,MACtB;AAAA,IACF;AAAA,IACA,CAACyO,CAAG;AAAA;AAAA,EAAA;AAGN,SAAIC,EAAc,WAAW,IACpB,EAAE,UAAU,IAAI,aAAavB,IAAM,SAAS,MAAM,UAAUC,IAAO,aAAa,GAAA,IAGlF;AAAA,IACL,UAAUsB,EAAc,CAAC,EAAE;AAAA,IAC3B,aAAAM;AAAA,IACA,SAASN,EAAc,CAAC;AAAA,IACxB,UAAUA;AAAA,IACV,aAAa;AAAA,EAAA;AAEjB;AC5LO,SAASgB,GAAiBpC,GAA2C;AAC1E,QAAM,EAAE,cAAAC,EAAA,IAAiBC,GAAA;AACzB,SAAKF,IACEC,EAAa,UAAUD,CAAI,KAAK,OADrB;AAEpB;ACeO,SAASqC,KAAiC;AAC/C,QAAM,EAAE,MAAAC,GAAM,WAAAC,GAAW,QAAAC,GAAQ,SAAAC,GAAS,WAAAC,GAAW,WAAAC,GAAW,SAAAC,GAAS,cAAAC,EAAA,IAAiBC,GAAA;AAC1F,SAAO,EAAE,MAAAR,GAAM,WAAAC,GAAW,QAAAC,GAAQ,SAAAC,GAAS,WAAAC,GAAW,WAAAC,GAAW,SAAAC,GAAS,cAAAC,EAAA;AAC5E;ACfO,SAASE,GAAwB/F,GAYhB;;AACtB,QAAMgG,MAAcrT,IAAAqN,EAAM,cAAN,gBAAArN,EAAkB,OAAM,EAAE,MAAM,SAAS,OAAO,KAAK,QAAQ,IAAA,GAC3E/B,KAAYoP,EAAM,YAAY,CAAA,GAAI,IAAIiG,EAAgB;AAE5D,SAAO;AAAA,IACL,WAAW,CAAC;AAAA,MACV,IAAI;AAAA,MACJ,MAAMD,EAAY,QAAQ;AAAA,MAC1B,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAOA,EAAY,SAAS;AAAA,MAC5B,QAAQA,EAAY,UAAU;AAAA,MAC9B,iBAAiBA,EAAY,mBAAmB;AAAA,MAChD,kBAAkB;AAAA,MAClB,UAAApV;AAAA,MACA,iBAAiBoV,EAAY;AAAA,MAC7B,WAAWA,EAAY;AAAA,IAAA,CACxB;AAAA,EAAA;AAEL;AAKA,SAASC,GAAiBzT,GAAc;AACtC,QAAM0T,IAAO1T,EAAG,QAAQA,EAAG;AAE3B,SAAI0T,MAAS,UACJC,GAAsB3T,CAAE,IAO1B;AAAA,IACL,GAAGA;AAAA,IACH,MAAA0T;AAAA,EAAA;AAEJ;AAMA,SAASC,GAAsB3T,GAAc;;AAC3C,QAAM8R,IAAK9R,EAAG,iBAAiB,CAAA,GACzB4T,IAAW5T,EAAG,SAASA,EAAG,MAAM,SAAS,GAGzC6T,IAAY/B,EAAG,SAAS,GACxBgC,IAAahC,EAAG,UAAU,GAC1BiC,IAAeH,IAAWC,IAAYA,KAAa/B,EAAG,aAAa,IACnEkC,IAAgBJ,IAAWE,IAAaA,KAAchC,EAAG,cAAc,IAEvEmC,IAAkB;AAAA,IACtB,IAAIjU,EAAG;AAAA,IACP,MAAM;AAAA,IACN,GAAGA,EAAG,KAAK;AAAA,IACX,GAAGA,EAAG,KAAK;AAAA,IACX,OAAO+T;AAAA,IACP,QAAQC;AAAA,IACR,UAAUhU,EAAG,YAAY;AAAA,IACzB,UAAUA,EAAG;AAAA,IACb,kBAAkBA,EAAG;AAAA,EAAA;AAKvB,SADkB,CAAC4T,MAAa9B,EAAG,UAAU,KAAKA,EAAG,UAAU,KAAKA,EAAG,cAAc,KAAKA,EAAG,eAAe,OAK1GmC,EAAW,QAAQnC,EAAG,OACtBmC,EAAW,QAAQnC,EAAG,OACtBmC,EAAW,YAAYnC,EAAG,WAC1BmC,EAAW,aAAanC,EAAG,YAC3BmC,EAAW,2BAA2B,KAIpCnC,EAAG,mBAAgBmC,EAAW,iBAAiBnC,EAAG,iBAClDA,EAAG,iBAAcmC,EAAW,eAAenC,EAAG,eAC9CA,EAAG,iBAAcmC,EAAW,eAAenC,EAAG,eAC9C9R,EAAG,YAAY,WAAWiU,EAAW,UAAUjU,EAAG,UAGlDA,EAAG,mBAAgBiU,EAAW,iBAAiBjU,EAAG,mBAClDG,IAAAH,EAAG,UAAH,gBAAAG,EAAU,UAAS,MAAG8T,EAAW,QAAQjU,EAAG,QAC5CA,EAAG,cAAWiU,EAAW,YAAYjU,EAAG,YACxCA,EAAG,kBAAeiU,EAAW,gBAAgBjU,EAAG,gBAChDA,EAAG,WAAQiU,EAAW,SAASjU,EAAG,SAE/BiU;AACT;;"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/components/embed/ErrorBoundary.tsx","../src/hooks/useAutoExport.ts","../src/hooks/useContentReady.ts","../src/utils/ArtworkPlacement.ts","../src/utils/ImageLoader.ts","../src/components/embed/SnowconeCanvas.tsx","../src/hooks/useCommands.ts","../src/hooks/useSelectedElement.ts","../src/hooks/useCanvasReady.ts","../src/hooks/useTextBinding.ts","../src/hooks/useImageBinding.ts","../src/hooks/useElementByName.ts","../src/hooks/useViewport.ts","../src/rendering/serialize-for-server.ts"],"sourcesContent":["/**\n * ErrorBoundary - Error boundary component for graceful error handling\n * Catches JavaScript errors in component tree and displays fallback UI\n *\n * @example\n * ```tsx\n * import { ErrorBoundary } from '@snowcone-app/canvas/embed';\n *\n * function App() {\n * return (\n * <ErrorBoundary\n * fallback={(error, reset) => (\n * <div>\n * <h2>Something went wrong</h2>\n * <pre>{error.message}</pre>\n * <button onClick={reset}>Try again</button>\n * </div>\n * )}\n * onError={(error) => {\n * console.error('Editor error:', error);\n * analytics.track('Editor Error', { message: error.message });\n * }}\n * >\n * <EditorProvider>\n * <Canvas />\n * </EditorProvider>\n * </ErrorBoundary>\n * );\n * }\n * ```\n */\n\nimport { Component, ReactNode, ErrorInfo } from 'react';\nimport { createLogger } from '../../utils/logger.js';\nimport type { CanvasError } from '../../types/index.js';\n\nconst logger = createLogger('ErrorBoundary');\n\nexport interface ErrorBoundaryProps {\n children: ReactNode;\n /** Fallback UI to render when an error is caught. Can be a static ReactNode or a render function. */\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);\n /**\n * Render function for custom error UI.\n * Takes the error and a reset function, returns a ReactNode.\n * When both `fallback` and `renderError` are provided, `renderError` takes precedence.\n */\n renderError?: (error: Error, reset: () => void) => ReactNode;\n /** Callback fired when an error is caught. Receives the raw Error and React ErrorInfo. */\n onError?: (error: Error, errorInfo: ErrorInfo) => void;\n /** Callback fired when an error is caught, receiving a structured CanvasError. */\n onCanvasError?: (error: CanvasError) => void;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Error boundary component for graceful error handling.\n * Catches React rendering errors in the component tree and displays fallback UI.\n * Supports both plain Error and structured CanvasError callbacks.\n */\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = {\n hasError: false,\n error: null,\n };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return {\n hasError: true,\n error,\n };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n // Log error to console\n logger.error('Caught error:', error, errorInfo);\n\n // Call custom error handler if provided (plain Error)\n if (this.props.onError) {\n this.props.onError(error, errorInfo);\n }\n\n // Call structured CanvasError handler if provided\n if (this.props.onCanvasError) {\n const canvasError: CanvasError = {\n category: 'unknown',\n message: error.message,\n originalError: error,\n recoverable: false,\n };\n this.props.onCanvasError(canvasError);\n }\n }\n\n resetError = (): void => {\n this.setState({\n hasError: false,\n error: null,\n });\n };\n\n render(): ReactNode {\n if (this.state.hasError && this.state.error) {\n // renderError takes precedence over fallback\n if (this.props.renderError) {\n return this.props.renderError(this.state.error, this.resetError);\n }\n\n // Custom fallback (legacy pattern, still supported)\n if (this.props.fallback) {\n if (typeof this.props.fallback === 'function') {\n return this.props.fallback(this.state.error, this.resetError);\n }\n return this.props.fallback;\n }\n\n // Default fallback UI\n return (\n <div\n style={{\n padding: '24px',\n border: '2px solid var(--danger)',\n borderRadius: '8px',\n backgroundColor: 'color-mix(in oklch, var(--danger) 10%, var(--background))',\n color: 'var(--danger)',\n fontFamily: 'system-ui, sans-serif',\n }}\n >\n <h2 style={{ margin: '0 0 16px 0', fontSize: '18px', fontWeight: 600 }}>\n Something went wrong\n </h2>\n <pre\n style={{\n margin: '0 0 16px 0',\n padding: '12px',\n backgroundColor: 'color-mix(in oklch, var(--danger) 15%, var(--background))',\n borderRadius: '4px',\n fontSize: '14px',\n overflow: 'auto',\n maxHeight: '200px',\n }}\n >\n {this.state.error.message}\n </pre>\n <button\n onClick={this.resetError}\n style={{\n padding: '8px 16px',\n backgroundColor: 'var(--danger)',\n color: 'var(--danger-foreground)',\n border: 'none',\n borderRadius: '4px',\n fontSize: '14px',\n fontWeight: 500,\n cursor: 'pointer',\n }}\n >\n Try again\n </button>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","/**\n * useAutoExport - React hook for automatic export on state changes\n *\n * Features:\n * - Automatically exports when editor state changes\n * - Debounced with maximum wait time (using lodash)\n * - Deep change detection (skips exports when nothing changed)\n * - Configurable timing parameters\n */\n\nimport { useEffect, useRef, useCallback, useState } from 'react';\nimport { AutoExportManager, AutoExportConfig, AutoExportStats, DEFAULT_AUTO_EXPORT_CONFIG } from '../services/AutoExportManager.js';\nimport { subscribeToImageLoads } from '../core/ImageLoadEvents.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { HybridHistoryManager } from '../core/HybridHistoryManager.js';\nimport type { ArtboardElement } from '../core/ArtboardElement.js';\nimport type { TextElement } from '../core/TextElement.js';\nimport type { ImageElement } from '../core/ImageElement.js';\nimport type { GroupElement } from '../core/GroupElement.js';\nimport type { ShapeElement } from '../core/ShapeElement.js';\nimport type { PathElement } from '../core/PathElement.js';\n\nconst logger = createLogger('useAutoExport');\n\ntype EditorElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport interface UseAutoExportOptions {\n /** Auto-export configuration */\n config?: Partial<AutoExportConfig>;\n\n /** Hybrid history manager to listen to for changes */\n historyManager?: HybridHistoryManager;\n\n /** Current artboards */\n artboards: ArtboardElement[];\n\n /** Current elements */\n elements: EditorElement[];\n\n /** Callback to perform the actual export */\n onExport: () => void | Promise<void>;\n\n /**\n * Callback fired immediately when an export is scheduled (before debounce).\n * Use this to show loading indicators right away instead of waiting for export to complete.\n */\n onExportScheduled?: () => void;\n\n /**\n * Whether the canvas is mounted and ready for export operations.\n * When false, exports are deferred until canvas becomes ready.\n * This prevents \"[useExport] Canvas ref not available\" errors.\n */\n isCanvasReady?: boolean;\n}\n\nexport interface UseAutoExportReturn {\n /** Current auto-export statistics */\n stats: AutoExportStats;\n\n /** Update configuration on the fly */\n updateConfig: (config: Partial<AutoExportConfig>) => void;\n\n /** Force an immediate export (bypassing debounce) */\n forceExport: () => Promise<void>;\n\n /** Reset statistics */\n resetStats: () => void;\n\n /** Reset change detection (next export will always trigger) */\n resetChangeDetection: () => void;\n}\n\n/**\n * Hook to manage automatic export on state changes\n *\n * @example\n * ```tsx\n * const { stats, updateConfig } = useAutoExport({\n * config: {\n * enabled: true,\n * debounceMs: 100,\n * maxWaitMs: 1000,\n * },\n * historyManager,\n * artboards,\n * elements,\n * onExport: async () => {\n * await exportAllArtboards();\n * },\n * });\n * ```\n */\nexport function useAutoExport(options: UseAutoExportOptions): UseAutoExportReturn {\n const { config, historyManager, artboards, elements, onExport, onExportScheduled, isCanvasReady = false } = options;\n\n const managerRef = useRef<AutoExportManager | null>(null);\n // Track if we have a pending export waiting for canvas to become ready\n const pendingExportRef = useRef<boolean>(false);\n // Stats stored in ref to avoid re-rendering host component (e.g. SnowconeCanvas)\n // on every auto-export. Only promoted to state on explicit user actions.\n const statsRef = useRef<AutoExportStats>({\n totalExports: 0,\n skippedExports: 0,\n lastExportTime: null,\n avgExportInterval: 0,\n });\n const [stats, setStats] = useState<AutoExportStats>(statsRef.current);\n\n // Ref for onExport — always reads the latest callback at execution time.\n // This eliminates stale-closure bugs: the manager's onExport callback never\n // goes stale because it always dereferences onExportRef.current.\n const onExportRef = useRef(onExport);\n onExportRef.current = onExport;\n\n // Initialize manager\n useEffect(() => {\n\n const manager = new AutoExportManager({\n ...DEFAULT_AUTO_EXPORT_CONFIG,\n ...config,\n });\n\n // Register export callback — uses ref so it always calls the latest onExport.\n // No need to re-register when onExport changes (the ref handles it).\n manager.onExport(async () => {\n try {\n await onExportRef.current();\n\n // Update ref only — no React state update to avoid re-rendering\n // the host component (SnowconeCanvas) on every auto-export\n statsRef.current = manager.getStats();\n } catch (error) {\n logger.error('[useAutoExport] Export failed:', error);\n }\n });\n\n managerRef.current = manager;\n\n // Cleanup on unmount\n return () => {\n manager.destroy();\n };\n }, []); // Only initialize once\n\n // Trigger pending export when canvas becomes ready\n useEffect(() => {\n if (isCanvasReady && pendingExportRef.current && managerRef.current) {\n pendingExportRef.current = false;\n managerRef.current.forceExport();\n }\n }, [isCanvasReady]);\n\n // Update config when it changes\n useEffect(() => {\n if (managerRef.current && config) {\n managerRef.current.updateConfig(config);\n }\n }, [config?.enabled, config?.debounceMs, config?.maxWaitMs]);\n\n // onExport callback changes are handled via onExportRef (set above).\n // No useEffect re-registration needed — eliminates the race condition\n // where the effect fires after the debounced export has already captured\n // a stale callback reference.\n\n // Update onExportScheduled callback when it changes\n useEffect(() => {\n if (managerRef.current && onExportScheduled) {\n managerRef.current.onExportScheduled(onExportScheduled);\n }\n }, [onExportScheduled]);\n\n // Hook into history manager to listen for changes\n useEffect(() => {\n if (!historyManager || !managerRef.current) {\n return;\n }\n\n\n // Subscribe to command executed events\n const unsubscribe = historyManager.onCommandExecuted(() => {\n\n // Schedule export (will be debounced)\n if (managerRef.current) {\n // Gate on canvas ready to prevent \"[useExport] Canvas ref not available\" errors\n if (!isCanvasReady) {\n pendingExportRef.current = true;\n return;\n }\n managerRef.current.scheduleExport();\n }\n });\n\n // Cleanup\n return () => {\n unsubscribe();\n };\n }, [historyManager, isCanvasReady]);\n\n // Also watch for direct state changes (e.g., during drag operations)\n // This ensures exports trigger even when changes bypass command history\n //\n // PERF: We intentionally avoid JSON.stringify here. During drag operations,\n // elements changes on every mouse move. JSON.stringify of all elements was\n // costing 50-200ms per frame — causing visible stutter. Instead we just call\n // scheduleExport() which is a cheap debounce timer reset. The AutoExportManager\n // does its own change detection (via getCurrentStateSnapshot) before actually\n // exporting, so redundant exports are still prevented.\n useEffect(() => {\n if (!managerRef.current) {\n return;\n }\n\n // Gate on canvas ready to prevent \"[useExport] Canvas ref not available\" errors\n if (!isCanvasReady) {\n pendingExportRef.current = true;\n return;\n }\n\n // Schedule export (debounced + manager does its own change detection)\n managerRef.current.scheduleExport();\n }, [elements, artboards, isCanvasReady]);\n\n // Re-trigger export when an image finishes loading.\n // Adding an image element fires auto-export via the elements dep above,\n // but the image hasn't loaded yet so it gets skipped during serialization.\n // This subscription ensures we re-export once the bitmap is available.\n useEffect(() => {\n const unsubscribe = subscribeToImageLoads((elementId) => {\n if (!managerRef.current || !isCanvasReady) return;\n const hasElement = elements.some(e => e.id === elementId);\n if (hasElement) {\n managerRef.current.scheduleExport();\n }\n });\n return unsubscribe;\n }, [elements, isCanvasReady]);\n\n // Return control functions\n const updateConfig = useCallback((newConfig: Partial<AutoExportConfig>) => {\n if (managerRef.current) {\n managerRef.current.updateConfig(newConfig);\n }\n }, []);\n\n const forceExport = useCallback(async () => {\n if (managerRef.current) {\n await managerRef.current.forceExport();\n statsRef.current = managerRef.current.getStats();\n setStats(statsRef.current);\n }\n }, []);\n\n const resetStats = useCallback(() => {\n if (managerRef.current) {\n managerRef.current.resetStats();\n statsRef.current = managerRef.current.getStats();\n setStats(statsRef.current);\n }\n }, []);\n\n const resetChangeDetection = useCallback(() => {\n if (managerRef.current) {\n managerRef.current.resetChangeDetection();\n }\n }, []);\n\n return {\n stats,\n updateConfig,\n forceExport,\n resetStats,\n resetChangeDetection,\n };\n}\n","/**\n * useContentReady - Tracks when canvas content is truly ready for export.\n *\n * Combines multiple signals:\n * 1. DOM + artboards ready (isCanvasReady)\n * 2. Initial elements deserialized and loaded\n * 3. Fonts used by text elements available in the browser\n * 4. At least one render frame completed after all the above\n *\n * Returns a latching boolean — once true, never goes back to false.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { createLogger } from '../utils/logger.js';\nimport type { TextElement } from '../core/TextElement.js';\nimport type { ImageElement } from '../core/ImageElement.js';\nimport type { GroupElement } from '../core/GroupElement.js';\nimport type { ShapeElement } from '../core/ShapeElement.js';\nimport type { PathElement } from '../core/PathElement.js';\n\nconst logger = createLogger('useContentReady');\n\ntype EditorElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport interface UseContentReadyOptions {\n /** DOM mounted and artboards initialized */\n isCanvasReady: boolean;\n /** Current elements in the editor */\n elements: EditorElement[];\n /** Were initialElements provided to SnowconeCanvas? */\n hasInitialElements: boolean;\n /** Have initialElements been deserialized into the editor? */\n initialElementsLoaded: boolean;\n}\n\n/**\n * Extract unique font families from text elements.\n * Only text-based elements (transformType !== 'image' and !== 'group') have fontFamily.\n */\nfunction extractFontFamilies(elements: EditorElement[]): string[] {\n const fonts = new Set<string>();\n for (const el of elements) {\n if ('fontFamily' in el && typeof el.fontFamily === 'string' && el.fontFamily) {\n fonts.add(el.fontFamily);\n }\n // Also check rich text spans for per-character font families\n if ('getRichText' in el && typeof el.getRichText === 'function') {\n try {\n const richText = el.getRichText();\n if (richText?.spans) {\n for (const span of richText.spans) {\n if (span.style?.fontFamily) {\n fonts.add(span.style.fontFamily);\n }\n }\n }\n } catch {\n // Ignore errors reading rich text\n }\n }\n }\n return Array.from(fonts);\n}\n\n/**\n * Check if all fonts are available, and load any that aren't.\n * Uses the standard FontFaceSet API.\n */\nasync function ensureFontsLoaded(fontFamilies: string[]): Promise<void> {\n if (fontFamilies.length === 0) return;\n if (typeof document === 'undefined' || !document.fonts) return;\n\n // CRITICAL: do NOT gate on `document.fonts.check()`. It returns `true` for\n // any specifier whose fonts are *currently satisfiable* — including custom\n // families with no `@font-face` registered, because the browser falls back\n // to a default font and considers the request fulfilled. So a freshly\n // deserialized design that references \"Bangers\" before the Google Fonts\n // `<link>` has loaded its CSS will pass `check()` (no FontFace exists yet\n // → fallback satisfies it), the auto-export gate releases, the worker\n // exports the placement PNG with a serif fallback, and a moment later the\n // `<link>` finishes loading and the on-screen canvas re-renders correctly\n // — leaving the saved placement permanently wrong.\n //\n // `document.fonts.load()` resolves with `[]` for missing fonts, so calling\n // it unconditionally is cheap and gives us the actual \"is this glyph data\n // available?\" semantics we want.\n const pending: Promise<FontFace[]>[] = [];\n for (const family of fontFamilies) {\n const testString = `16px \"${family}\"`;\n pending.push(document.fonts.load(testString));\n }\n await Promise.all(pending);\n\n // iOS Safari quirk: `document.fonts.load()` can resolve before the font is\n // actually activated for *freshly created* canvas contexts (the offscreen\n // `document.createElement('canvas')` used by the main-thread export path,\n // and `OffscreenCanvas` in workers). The on-screen `<canvas>` paints\n // correctly because Safari activates fonts on first DOM use, but the\n // detached canvas the export builds was never DOM-attached. `document.fonts.ready`\n // resolves *after* all in-flight font activation work has settled — that's\n // the sync point detached-canvas font availability needs on iOS.\n //\n // Race with a 250ms timeout — on Chrome `ready` resolves in <1ms post-load,\n // on iOS this is the activation barrier. The race protects against a\n // wedged FontFaceSet (e.g. a `<link>` that never finishes parsing) holding\n // the export gate open indefinitely.\n if (document.fonts.ready) {\n await Promise.race([\n document.fonts.ready,\n new Promise<void>((resolve) => setTimeout(resolve, 250)),\n ]);\n }\n logger.debug(`Awaited ${pending.length} font(s)`);\n}\n\n/**\n * Wait one animation frame so the canvas render loop draws with loaded content.\n */\nfunction waitOneFrame(): Promise<void> {\n return new Promise((resolve) => requestAnimationFrame(() => resolve()));\n}\n\nexport function useContentReady(options: UseContentReadyOptions): boolean {\n const { isCanvasReady, elements, hasInitialElements, initialElementsLoaded } = options;\n const [isContentReady, setIsContentReady] = useState(false);\n const checkingRef = useRef(false);\n\n useEffect(() => {\n // Already latched — nothing to do\n if (isContentReady) return;\n\n // Gate 1: DOM + artboards must be ready\n if (!isCanvasReady) return;\n\n // Gate 2: If initial elements were provided, they must be loaded\n if (hasInitialElements && !initialElementsLoaded) return;\n\n // Gate 3: If initial elements were provided, the elements array must be non-empty\n if (hasInitialElements && elements.length === 0) return;\n\n // Prevent concurrent checks\n if (checkingRef.current) return;\n checkingRef.current = true;\n\n let cancelled = false;\n\n (async () => {\n try {\n // Gate 4: Ensure all fonts used by text elements are loaded\n const fonts = extractFontFamilies(elements);\n if (fonts.length > 0) {\n await ensureFontsLoaded(fonts);\n if (cancelled) return;\n }\n\n // Gate 5: Wait one render frame so the canvas draws with ready content\n await waitOneFrame();\n if (cancelled) return;\n setIsContentReady(true);\n } catch (err) {\n logger.error('Error during content readiness check:', err);\n // Still mark ready on error to avoid blocking exports forever\n if (!cancelled) {\n setIsContentReady(true);\n }\n } finally {\n checkingRef.current = false;\n }\n })();\n\n return () => {\n cancelled = true;\n checkingRef.current = false;\n };\n }, [isCanvasReady, elements, hasInitialElements, initialElementsLoaded, isContentReady]);\n\n return isContentReady;\n}\n","/**\n * ArtworkPlacement - Utility for calculating artwork position and scale\n *\n * Uses \"cover\" logic (like CSS object-fit: cover) to ensure artwork fills\n * the entire artboard while maintaining aspect ratio.\n */\n\n/**\n * 9-point alignment options for positioning artwork\n */\nexport type Alignment = 'tl' | 't' | 'tr' | 'l' | 'c' | 'r' | 'bl' | 'b' | 'br';\n\n/**\n * Scale mode for artwork placement\n * - \"cover\" (default): fills entire area, may crop (like CSS object-fit: cover)\n * - \"contain\": fits entirely inside area, may have empty space (like CSS object-fit: contain)\n */\nexport type ScaleMode = 'cover' | 'contain';\n\n/**\n * Placement configuration for artwork positioning\n */\nexport interface PlacementConfig {\n /** Artboard width in pixels */\n width: number;\n /** Artboard height in pixels */\n height: number;\n /** User scale multiplier (default: 1) */\n scale?: number;\n /** 9-point alignment (default: \"c\" for center) */\n align?: Alignment;\n /** X offset in range -1 to 1 (default: 0) */\n offsetX?: number;\n /** Y offset in range -1 to 1 (default: 0) */\n offsetY?: number;\n /** Scale mode: \"cover\" fills area (may crop), \"contain\" fits inside (may have space). Default: \"cover\" */\n scaleMode?: ScaleMode;\n /** Top margin in pixels (reduces available area in contain mode) */\n marginTop?: number;\n /** Right margin in pixels (reduces available area in contain mode) */\n marginRight?: number;\n /** Bottom margin in pixels (reduces available area in contain mode) */\n marginBottom?: number;\n /** Left margin in pixels (reduces available area in contain mode) */\n marginLeft?: number;\n}\n\n/**\n * Result of artwork placement calculation\n */\nexport interface ArtworkPlacementResult {\n /** Final scale to apply to artwork */\n scale: number;\n /** Final artwork dimensions after scaling */\n width: number;\n height: number;\n /** Position to draw artwork (relative to artboard origin) */\n x: number;\n y: number;\n /** Debug information */\n debug: {\n coverScale: number;\n userScale: number;\n overflow: { x: number; y: number };\n alignment: string;\n appliedOffset: { x: number; y: number };\n };\n}\n\n/**\n * Map string alignment values to 9-point grid\n */\nexport const alignmentMap: Record<string, Alignment> = {\n // Vertical alignments (map to top/center/bottom center)\n 'far-top': 't',\n 'top': 't',\n 'center': 'c',\n 'bottom': 'b',\n 'far-bottom': 'b',\n // Horizontal alignments (map to left/center/right middle)\n 'far-left': 'l',\n 'left': 'l',\n 'right': 'r',\n 'far-right': 'r',\n // Direct 9-point values\n 'tl': 'tl',\n 't': 't',\n 'tr': 'tr',\n 'l': 'l',\n 'c': 'c',\n 'r': 'r',\n 'bl': 'bl',\n 'b': 'b',\n 'br': 'br',\n};\n\n/**\n * Get Alignment from string (supports various naming conventions)\n */\nexport function getAlignment(value: string | Alignment | undefined): Alignment {\n if (!value) return 'c';\n return alignmentMap[value] || 'c';\n}\n\n/**\n * Calculates how to size and position artwork on an artboard\n * Uses \"cover\" logic (like CSS object-fit: cover) to ensure artwork fills the entire area\n *\n * @param artwork - The artwork dimensions\n * @param placement - The placement configuration\n * @returns Positioning and scaling information\n *\n * @example\n * const result = calculateArtworkPlacement(\n * { width: 1000, height: 1000 }, // Square artwork\n * { width: 500, height: 800 } // Tall artboard\n * );\n * // Result: artwork scaled to 800x800, positioned at x: -150, y: 0\n */\nexport function calculateArtworkPlacement(\n artwork: { width: number; height: number },\n placement: PlacementConfig\n): ArtworkPlacementResult {\n // Step 1: Calculate base scale based on scale mode\n const mode = placement.scaleMode || 'cover';\n\n // Margins reduce available area (only in contain mode)\n const mTop = (mode === 'contain' ? placement.marginTop : 0) || 0;\n const mRight = (mode === 'contain' ? placement.marginRight : 0) || 0;\n const mBottom = (mode === 'contain' ? placement.marginBottom : 0) || 0;\n const mLeft = (mode === 'contain' ? placement.marginLeft : 0) || 0;\n\n const effectiveWidth = placement.width - mLeft - mRight;\n const effectiveHeight = placement.height - mTop - mBottom;\n\n const scaleX = effectiveWidth / artwork.width;\n const scaleY = effectiveHeight / artwork.height;\n\n // \"cover\": use LARGER scale so artwork fills entire area (may crop)\n // \"contain\": use SMALLER scale so artwork fits entirely inside (may have space)\n const coverScale = mode === 'contain'\n ? Math.min(scaleX, scaleY)\n : Math.max(scaleX, scaleY);\n\n // Step 2: Apply user's custom scale multiplier\n const userScale = placement.scale || 1;\n const finalScale = coverScale * userScale;\n\n // Step 3: Calculate final artwork dimensions after scaling\n const scaledArtworkWidth = artwork.width * finalScale;\n const scaledArtworkHeight = artwork.height * finalScale;\n\n // Step 4: Calculate base position using alignment\n const alignment = placement.align || 'c'; // default to center\n let x = 0;\n let y = 0;\n\n // Calculate the \"overflow\" relative to effective area\n // In contain mode with margins, overflow is relative to the margin-bounded area\n const overflowX = scaledArtworkWidth - effectiveWidth;\n const overflowY = scaledArtworkHeight - effectiveHeight;\n\n // Apply 9-point alignment\n switch (alignment) {\n // Top row\n case 'tl': // Top-Left\n x = 0;\n y = 0;\n break;\n case 't': // Top-Center\n x = -overflowX / 2;\n y = 0;\n break;\n case 'tr': // Top-Right\n x = -overflowX;\n y = 0;\n break;\n\n // Middle row\n case 'l': // Middle-Left\n x = 0;\n y = -overflowY / 2;\n break;\n case 'c': // Center (default)\n default:\n x = -overflowX / 2;\n y = -overflowY / 2;\n break;\n case 'r': // Middle-Right\n x = -overflowX;\n y = -overflowY / 2;\n break;\n\n // Bottom row\n case 'bl': // Bottom-Left\n x = 0;\n y = -overflowY;\n break;\n case 'b': // Bottom-Center\n x = -overflowX / 2;\n y = -overflowY;\n break;\n case 'br': // Bottom-Right\n x = -overflowX;\n y = -overflowY;\n break;\n }\n\n // Step 5: Apply margin offsets (shift positions into margin-bounded area)\n x += mLeft;\n y += mTop;\n\n // Step 6: Apply user offsets\n // Offsets are in range -1 to 1, so convert to pixel values\n const offsetX = placement.offsetX || 0;\n const offsetY = placement.offsetY || 0;\n\n // The \"range\" is how far we can move the artwork\n const xRange = overflowX;\n const yRange = overflowY;\n\n x += offsetX * xRange;\n y += offsetY * yRange;\n\n // Round to whole pixels\n x = Math.round(x);\n y = Math.round(y);\n\n return {\n // Final scale to apply to artwork\n scale: finalScale,\n\n // Final artwork dimensions after scaling\n width: Math.round(scaledArtworkWidth),\n height: Math.round(scaledArtworkHeight),\n\n // Position to draw artwork (relative to artboard origin)\n x,\n y,\n\n // Debug info\n debug: {\n coverScale,\n userScale,\n overflow: { x: overflowX, y: overflowY },\n alignment,\n appliedOffset: { x: offsetX * xRange, y: offsetY * yRange },\n },\n };\n}\n","/**\n * ImageLoader - Utility for loading images from URLs\n * Provides a clean async API with loading states and error handling\n */\n\nimport { ImageElement } from '../core/ImageElement.js';\nimport type { ImageElementConfig } from '../types/index.js';\nimport { calculateArtworkPlacement, getAlignment, type Alignment, type PlacementConfig, type ScaleMode } from './ArtworkPlacement.js';\n\nexport interface ImageLoadResult {\n success: boolean;\n element?: HTMLImageElement;\n error?: Error;\n width?: number;\n height?: number;\n aspectRatio?: number;\n}\n\nexport interface ImageLoaderOptions {\n /** Timeout in milliseconds (default: 30000 - 30 seconds) */\n timeout?: number;\n /** Number of retry attempts on failure (default: 1) */\n retries?: number;\n /** Retry delay in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Whether to validate URL before loading (default: true) */\n validateUrl?: boolean;\n}\n\nexport type ImageLoadingState = 'idle' | 'loading' | 'success' | 'error';\n\nconst DEFAULT_OPTIONS: Required<ImageLoaderOptions> = {\n timeout: 30000,\n retries: 1,\n retryDelay: 1000,\n validateUrl: true,\n};\n\n/**\n * Validate if a string is a valid URL\n */\nexport function isValidImageUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n // Only allow http, https, data, and blob URLs\n return ['http:', 'https:', 'data:', 'blob:'].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\n/**\n * Load an image from a URL\n * Returns a promise that resolves with the loaded HTMLImageElement\n */\nexport async function loadImageFromURL(\n url: string,\n options: ImageLoaderOptions = {}\n): Promise<ImageLoadResult> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n\n // Validate URL if enabled\n if (opts.validateUrl && !isValidImageUrl(url)) {\n return {\n success: false,\n error: new Error(`Invalid image URL: ${url}`),\n };\n }\n\n let lastError: Error | undefined;\n\n // Attempt to load with retries\n for (let attempt = 0; attempt <= opts.retries; attempt++) {\n try {\n const result = await loadImageOnce(url, opts.timeout);\n return result;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Wait before retrying (unless this was the last attempt)\n if (attempt < opts.retries) {\n await delay(opts.retryDelay);\n }\n }\n }\n\n return {\n success: false,\n error: lastError || new Error('Failed to load image after retries'),\n };\n}\n\n/**\n * Load image once (single attempt)\n */\nfunction loadImageOnce(url: string, timeout: number): Promise<ImageLoadResult> {\n return new Promise((resolve) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let resolved = false;\n\n const cleanup = () => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n };\n\n const handleSuccess = () => {\n if (resolved) return;\n resolved = true;\n cleanup();\n\n const w = img.naturalWidth || img.width;\n const h = img.naturalHeight || img.height;\n resolve({\n success: true,\n element: img,\n width: w,\n height: h,\n aspectRatio: h > 0 ? w / h : 1,\n });\n };\n\n const handleError = (errorMsg: string) => {\n if (resolved) return;\n resolved = true;\n cleanup();\n\n resolve({\n success: false,\n error: new Error(errorMsg),\n });\n };\n\n img.onload = handleSuccess;\n\n img.onerror = () => {\n handleError(`Failed to load image: ${url}`);\n };\n\n // Set timeout\n timeoutId = setTimeout(() => {\n handleError(`Image load timed out after ${timeout}ms: ${url}`);\n }, timeout);\n\n // Start loading\n img.src = url;\n });\n}\n\n/**\n * Preload multiple images in parallel\n * Returns results for all images (some may fail)\n */\nexport async function preloadImages(\n urls: string[],\n options: ImageLoaderOptions = {}\n): Promise<Map<string, ImageLoadResult>> {\n const results = new Map<string, ImageLoadResult>();\n\n const promises = urls.map(async (url) => {\n const result = await loadImageFromURL(url, options);\n results.set(url, result);\n });\n\n await Promise.all(promises);\n return results;\n}\n\n/**\n * Create an ImageElement from a URL with loading state handling\n */\nexport async function createImageElement(\n url: string,\n config: Partial<ImageElementConfig> = {},\n options: ImageLoaderOptions = {}\n): Promise<{\n success: boolean;\n element?: ImageElement;\n error?: Error;\n}> {\n const loadResult = await loadImageFromURL(url, options);\n\n if (!loadResult.success) {\n return {\n success: false,\n error: loadResult.error,\n };\n }\n\n // Create ImageElement with the URL\n // The ImageElement constructor will load the image itself\n const imageElement = new ImageElement({\n ...config,\n imageUrl: url,\n imageAspectRatio: loadResult.aspectRatio,\n transformData: {\n type: 'image',\n width: config.transformData?.width || Math.min(loadResult.width || 400, 400),\n height: config.transformData?.height || Math.min(loadResult.height || 400, 400),\n cropX: config.transformData?.cropX ?? 0,\n cropY: config.transformData?.cropY ?? 0,\n cropWidth: config.transformData?.cropWidth ?? 1,\n cropHeight: config.transformData?.cropHeight ?? 1,\n flipHorizontal: config.transformData?.flipHorizontal ?? false,\n flipVertical: config.transformData?.flipVertical ?? false,\n borderRadius: config.transformData?.borderRadius ?? 0,\n },\n });\n\n return {\n success: true,\n element: imageElement,\n };\n}\n\n/**\n * Configuration for artwork placement on artboard\n */\nexport interface ArtworkPlacementOptions {\n /** Artboard dimensions */\n artboard: {\n width: number;\n height: number;\n /** Artboard position on canvas (for proper element positioning) */\n x: number;\n y: number;\n };\n /** Alignment string (e.g., 'center', 'top', 'bottom-left') */\n alignment?: string | Alignment;\n /** User scale multiplier (default: 1) */\n scale?: number;\n /** X offset in range -1 to 1 */\n offsetX?: number;\n /** Y offset in range -1 to 1 */\n offsetY?: number;\n /** Scale mode: \"cover\" fills area (may crop), \"contain\" fits inside (may have space). Default: \"cover\" */\n scaleMode?: ScaleMode;\n /** Top margin in pixels (reduces available area in contain mode) */\n marginTop?: number;\n /** Right margin in pixels (reduces available area in contain mode) */\n marginRight?: number;\n /** Bottom margin in pixels (reduces available area in contain mode) */\n marginBottom?: number;\n /** Left margin in pixels (reduces available area in contain mode) */\n marginLeft?: number;\n}\n\n/**\n * Create an ImageElement with proper \"cover\" sizing and placement on artboard\n *\n * This uses CSS-like object-fit: cover logic to ensure the artwork completely\n * fills the artboard while maintaining aspect ratio, with 9-point alignment support.\n *\n * @example\n * const result = await createImageElementWithPlacement(\n * 'https://example.com/art.png',\n * {\n * artboard: { width: 1200, height: 1500, x: 250, y: 250 },\n * alignment: 'center',\n * scale: 1\n * }\n * );\n */\nexport async function createImageElementWithPlacement(\n url: string,\n placementOptions: ArtworkPlacementOptions,\n config: Partial<ImageElementConfig> = {},\n loaderOptions: ImageLoaderOptions = {}\n): Promise<{\n success: boolean;\n element?: ImageElement;\n error?: Error;\n placement?: {\n artworkSize: { width: number; height: number };\n finalSize: { width: number; height: number };\n position: { x: number; y: number };\n scale: number;\n };\n}> {\n const loadResult = await loadImageFromURL(url, loaderOptions);\n\n if (!loadResult.success || !loadResult.width || !loadResult.height) {\n return {\n success: false,\n error: loadResult.error || new Error('Failed to get image dimensions'),\n };\n }\n\n // Calculate placement using cover/contain algorithm\n const placementConfig: PlacementConfig = {\n width: placementOptions.artboard.width,\n height: placementOptions.artboard.height,\n scale: placementOptions.scale,\n align: getAlignment(placementOptions.alignment),\n offsetX: placementOptions.offsetX,\n offsetY: placementOptions.offsetY,\n scaleMode: placementOptions.scaleMode,\n marginTop: placementOptions.marginTop,\n marginRight: placementOptions.marginRight,\n marginBottom: placementOptions.marginBottom,\n marginLeft: placementOptions.marginLeft,\n };\n\n const placement = calculateArtworkPlacement(\n { width: loadResult.width, height: loadResult.height },\n placementConfig\n );\n\n // placement.x and placement.y are the TOP-LEFT corner position (relative to artboard origin)\n // ImageElement.x and ImageElement.y represent the CENTER of the image\n // So we need to convert from top-left to center position\n\n // Calculate absolute TOP-LEFT position on canvas (artboard position + relative position)\n const topLeftX = placementOptions.artboard.x + placement.x;\n const topLeftY = placementOptions.artboard.y + placement.y;\n\n // Convert to CENTER position (what ImageElement expects)\n const centerX = topLeftX + placement.width / 2;\n const centerY = topLeftY + placement.height / 2;\n\n\n // Create ImageElement with calculated dimensions and CENTER position\n // Set preserveDimensions: true to prevent the image load callback from\n // recalculating the dimensions (which would break our \"cover\" sizing)\n const imageElement = new ImageElement({\n ...config,\n x: centerX,\n y: centerY,\n imageUrl: url,\n imageAspectRatio: loadResult.aspectRatio,\n preserveDimensions: true, // Don't recalculate dimensions on image load\n transformData: {\n type: 'image',\n width: placement.width,\n height: placement.height,\n cropX: config.transformData?.cropX ?? 0,\n cropY: config.transformData?.cropY ?? 0,\n cropWidth: config.transformData?.cropWidth ?? 1,\n cropHeight: config.transformData?.cropHeight ?? 1,\n flipHorizontal: config.transformData?.flipHorizontal ?? false,\n flipVertical: config.transformData?.flipVertical ?? false,\n borderRadius: config.transformData?.borderRadius ?? 0,\n },\n });\n\n return {\n success: true,\n element: imageElement,\n placement: {\n artworkSize: { width: loadResult.width, height: loadResult.height },\n finalSize: { width: placement.width, height: placement.height },\n position: { x: centerX, y: centerY },\n scale: placement.scale,\n },\n };\n}\n\n/**\n * Check if an image URL is accessible (HEAD request)\n * More efficient than fully loading the image\n */\nexport async function checkImageUrl(url: string): Promise<boolean> {\n try {\n // Use fetch with HEAD to check if the URL is accessible\n const response = await fetch(url, {\n method: 'HEAD',\n mode: 'cors',\n });\n\n // Check if response is an image\n const contentType = response.headers.get('content-type');\n return response.ok && (contentType?.startsWith('image/') || false);\n } catch {\n // Fallback: Try to load the image directly\n // Some servers don't support HEAD requests\n const result = await loadImageFromURL(url, { timeout: 5000, retries: 0 });\n return result.success;\n }\n}\n\n/**\n * Get image dimensions without fully loading it (when possible)\n * Falls back to full load if necessary\n */\nexport async function getImageDimensions(\n url: string\n): Promise<{ width: number; height: number } | null> {\n const result = await loadImageFromURL(url, { timeout: 10000, retries: 0 });\n\n if (result.success && result.width && result.height) {\n return { width: result.width, height: result.height };\n }\n\n return null;\n}\n\n// Helper function\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * SnowconeCanvas - Main embeddable canvas component for e-commerce integration\n *\n * A self-contained canvas editor that wraps EditorProvider and handles:\n * - Artboard initialization and synchronization\n * - Image loading from URL with loading/error states\n * - Auto-export on interval or artboard change\n * - State change callbacks\n *\n * @example\n * ```tsx\n * import { SnowconeCanvas } from '@snowcone-app/canvas';\n *\n * function ProductCustomizer() {\n * return (\n * <SnowconeCanvas\n * artboards={[\n * { name: 'Front', width: 1200, height: 1200, clipShape: 'circle' },\n * { name: 'Back', width: 1200, height: 1200 },\n * ]}\n * initialImage=\"https://example.com/product.png\"\n * autoExportInterval={500}\n * onExport={(exports) => {\n * // exports = { 'Front': dataUrl, 'Back': dataUrl }\n * console.log('Design updated:', exports);\n * }}\n * />\n * );\n * }\n * ```\n */\n\nimport React, { useEffect, useMemo, useRef, useCallback, useState, useImperativeHandle, forwardRef } from 'react';\nimport { EditorProvider, useEditor } from '../../contexts/EditorContext.js';\nimport { ThemeProvider } from '../../contexts/ThemeContext.js';\nimport { KitProvider } from '../../contexts/KitContext.js';\nimport { Canvas } from './Canvas.js';\nimport { ErrorBoundary } from './ErrorBoundary.js';\nimport { useArtboards } from '../../hooks/useArtboards.js';\nimport { useExport } from '../../hooks/useExport.js';\nimport { useAutoExport } from '../../hooks/useAutoExport.js';\nimport { useContentReady } from '../../hooks/useContentReady.js';\nimport { createImageElementWithPlacement } from '../../utils/ImageLoader.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { CanvasRenderer } from '../../core/CanvasRenderer.js';\nimport type { CanvasError } from '../../types/index.js';\n\nconst log = createLogger('SnowconeCanvas');\nimport { Spinner } from './LoadingStates.js';\nimport { ElementFactory } from '../../core/ElementFactory.js';\nimport { resolveKit } from '../../kits/registry.js';\nimport { validateKit } from '../../kits/validation.js';\nimport type { KitPresetId, KitDefinition } from '../../kits/types.js';\nimport type { ClipShape, AnyElementConfig } from '../../types/index.js';\nimport type {\n PieceGuide,\n PieceGuideRendererPiece,\n PieceGuideRendererStyle,\n PieceGuideVisibility,\n} from '../../rendering/PieceGuideRenderer.js';\n\n// Re-export ClipShape type for convenience\nexport type { ClipShape } from '../../types/index.js';\n\n/** 9-point alignment grid values */\nexport type AlignmentPoint = 'tl' | 't' | 'tr' | 'l' | 'c' | 'r' | 'bl' | 'b' | 'br' | 'center' | 'top' | 'bottom' | 'left' | 'right';\n\n/** Events emitted during the export lifecycle */\nexport type ExportStatusEvent =\n | { status: 'scheduled'; artboardId: string }\n | { status: 'rendering'; artboardId: string }\n | { status: 'complete'; artboardId: string; result: Record<string, string | Blob> }\n | { status: 'error'; artboardId: string; error: Error };\n\nexport interface ArtboardConfig {\n /** Display name for the artboard (used as key in export results) */\n name: string;\n /** Width in pixels */\n width: number;\n /** Height in pixels */\n height: number;\n /** Optional clip shape for content masking */\n clipShape?: ClipShape;\n /** Optional background color */\n backgroundColor?: string;\n /** Scale mode for initial image: \"cover\" fills area (may crop), \"contain\" fits inside (may have space). Default: \"cover\" */\n scaleMode?: 'cover' | 'contain';\n /** Top margin in pixels for contain mode (reduces available area) */\n fitMarginTop?: number;\n /** Right margin in pixels for contain mode (reduces available area) */\n fitMarginRight?: number;\n /** Bottom margin in pixels for contain mode (reduces available area) */\n fitMarginBottom?: number;\n /** Left margin in pixels for contain mode (reduces available area) */\n fitMarginLeft?: number;\n /** Alignment override for contain mode (9-point grid: 'tl','t','tr','l','c','r','bl','b','br') */\n fitAlign?: AlignmentPoint;\n}\n\nexport interface CanvasState {\n /** Serialized element configurations for persistence */\n elements: AnyElementConfig[];\n /** Artboard configurations */\n artboards: ArtboardConfig[];\n /** Currently active artboard name */\n activeArtboard: string;\n}\n\n/**\n * Imperative handle for programmatic control of SnowconeCanvas\n */\nexport interface SnowconeCanvasHandle {\n /**\n * Trigger an export of artboards programmatically.\n * Returns a promise that resolves with the exported artboards.\n *\n * @param options Export options\n * @returns Promise resolving to exported artboards keyed by name\n *\n * @example\n * const canvasRef = useRef<SnowconeCanvasHandle>(null);\n * const handleExport = async () => {\n * const exports = await canvasRef.current?.exportArtboards();\n * console.log('Exported:', exports);\n * };\n */\n exportArtboards: (options?: {\n /** Export format */\n format?: 'dataUrl' | 'blob';\n /** Resolution multiplier */\n scale?: number;\n /** Export all artboards or just active */\n all?: boolean;\n }) => Promise<Record<string, string | Blob>>;\n}\n\n/**\n * Configuration for export behavior (scale, format, auto-export).\n *\n * Group these props together instead of passing them individually:\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * exportConfig={{\n * autoExportConfig: { enabled: true, debounceMs: 100, maxWaitMs: 1000 },\n * format: 'blob',\n * exportAll: true,\n * scale: 2,\n * maxSize: 4000,\n * imageFormat: 'webp',\n * imageQuality: 0.85,\n * }}\n * onExportStatus={(event) => { ... }}\n * />\n * ```\n */\nexport interface ExportConfig {\n /**\n * Smart auto-export configuration with debouncing and change detection.\n * Replaces the timer-based `autoExportInterval` with an event-driven approach.\n */\n autoExportConfig?: {\n /** Enable/disable auto-export */\n enabled?: boolean;\n /** Debounce delay - wait this long after last change before exporting (ms) */\n debounceMs?: number;\n /** Maximum wait time - force export after this duration even if changes keep coming (ms) */\n maxWaitMs?: number;\n };\n\n /**\n * Export format: 'dataUrl' (base64 string) or 'blob' (binary).\n * Blob is more efficient for API uploads.\n */\n format?: 'dataUrl' | 'blob';\n\n /**\n * If true, exports all artboards on each interval.\n * If false, only exports the active artboard.\n */\n exportAll?: boolean;\n\n /**\n * Resolution multiplier for exports.\n * Default: 2 (recommended for print quality).\n *\n * Note: This scale is applied intelligently - if the resulting image would\n * exceed `maxSize`, the scale is reduced to cap the largest dimension.\n */\n scale?: number;\n\n /**\n * Maximum export dimension (largest side) in pixels.\n * Default: 4000 (4K).\n *\n * When the artboard dimensions multiplied by scale would exceed this,\n * the effective scale is reduced to cap the output at this size.\n * Set to 0 or Infinity to disable this capping behavior.\n */\n maxSize?: number;\n\n /**\n * Image format for auto-exports.\n * Default: 'png'.\n * Use 'webp' for smaller file sizes with transparency support (good for realtime preview).\n */\n imageFormat?: 'png' | 'jpg' | 'jpeg' | 'webp';\n\n /**\n * Image quality for auto-exports (0-1).\n * Only applies to JPEG and WebP formats. PNG ignores this.\n * Default: 0.92.\n */\n imageQuality?: number;\n}\n\n/**\n * Configuration for the initial image loaded into the canvas.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * imageConfig={{\n * src: 'https://example.com/artwork.png',\n * alignment: 'center',\n * scale: 1.2,\n * scaleMode: 'contain',\n * }}\n * />\n * ```\n */\nexport interface ImageConfig {\n /**\n * URL of initial image to load into the canvas.\n * Shows loading state while fetching, error state on failure.\n */\n src?: string;\n\n /**\n * Alignment for the initial image on the artboard.\n * Uses a 9-point grid: 'tl', 't', 'tr', 'l', 'c', 'r', 'bl', 'b', 'br'\n * Or semantic values: 'center', 'top', 'bottom', 'left', 'right'\n * Default: 'center'\n */\n alignment?: AlignmentPoint;\n\n /**\n * Scale multiplier for the initial image.\n * Default: 1 (image covers artboard exactly)\n * Values > 1 make the image larger, < 1 make it smaller.\n */\n scale?: number;\n\n /**\n * Scale mode for initial image placement.\n * - \"cover\" (default): image fills artboard completely, may crop edges\n * - \"contain\": image fits entirely inside artboard, may have empty space\n */\n scaleMode?: 'cover' | 'contain';\n}\n\n/**\n * Configuration for layout and visual appearance of the canvas.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * layoutConfig={{\n * width: 1200,\n * height: 1200,\n * viewPadding: 0.85,\n * artboardBorderRadius: 12,\n * showToolbar: false,\n * showLayers: false,\n * }}\n * />\n * ```\n */\nexport interface LayoutConfig {\n /**\n * Artboard width (shorthand for single artboard).\n * If `artboards` prop is provided, this is ignored.\n */\n width?: number;\n\n /**\n * Artboard height (shorthand for single artboard).\n * If `artboards` prop is provided, this is ignored.\n */\n height?: number;\n\n /**\n * Controls how much of the container the artboard fills.\n * Value between 0 and 1, where 1 means the artboard fills 100% of the container.\n * Default is 0.9 (90%).\n */\n viewPadding?: number;\n\n /**\n * Border radius for artboard corners in pixels.\n * This is a visual-only effect - exports will still have the full artboard.\n * @default 0\n */\n artboardBorderRadius?: number;\n\n /**\n * Fixed screen-space margin in pixels.\n * When set, the artboard will have this exact left/top margin regardless of zoom level.\n * Overrides viewPadding for positioning.\n */\n fixedMargin?: number;\n\n /**\n * Maximum HTML height in pixels.\n * When the artboard (with vertical fixedMargin padding) would exceed this height,\n * horizontal padding is increased to shrink the artboard until it fits.\n */\n maxHeight?: number;\n\n /**\n * Show toolbar UI.\n * Default: false (canvas-only mode).\n */\n showToolbar?: boolean;\n\n /**\n * Show layers panel.\n * Default: false (canvas-only mode).\n */\n showLayers?: boolean;\n\n /**\n * Whether to show the rotation handle on the canvas.\n * Set to false for embedded mode where rotation is handled via toolbar panel.\n * @default true\n */\n showRotationHandle?: boolean;\n\n /**\n * When true, hides the internal Canvas component.\n * Use this when rendering a separate Canvas (e.g., in fullscreen mode)\n * that shares the same EditorProvider context.\n */\n hideCanvas?: boolean;\n\n /**\n * CSS class name for the canvas wrapper element.\n */\n canvasWrapperClassName?: string;\n\n /**\n * Inline styles for the canvas wrapper element.\n */\n canvasWrapperStyle?: React.CSSProperties;\n\n /**\n * ADR-0054 Phase 4: cutout shapes (in artboard-space px) that get\n * punched through the `<canvas>` element as truly transparent holes,\n * revealing whatever sits behind SnowconeCanvas in the DOM. Internally\n * translated to a CSS `clip-path: path(evenodd, ...)` that accounts\n * for the canvas's padding margin and zoom. Each shape is a rect,\n * rounded rect, or SVG path — the same `SvgPath` shape used by the\n * `VisualGuideOverlay`.\n */\n canvasCutouts?: CanvasCutoutShape[];\n}\n\n/**\n * ADR-0054: piece-guide config passed to `SnowconeCanvas`. The render\n * loop calls `renderPieceGuides` with these values after drawing the\n * artboard and its elements, so boundary/zone/label coordinates are in\n * the artboard's local pixel frame and compose cleanly with the canvas\n * viewport transform.\n */\nexport interface PieceGuidesConfig {\n /** Pieces (artboard-space rects) the guides attach to. */\n pieces: PieceGuideRendererPiece[];\n /** Authored guides keyed by piece id. Pieces without a guide fall back to the piece rect. */\n guides?: Record<string, PieceGuide>;\n /** Per-layer visibility toggles. All default to true. */\n show?: PieceGuideVisibility;\n /** Optional style overrides. */\n style?: PieceGuideRendererStyle;\n}\n\n/** ADR-0054 Phase 4: shape union used for `canvasCutouts`. Mirrors the\n * `SvgPath` shape in `VisualGuideOverlay`. Coordinates are in artboard\n * pixel units (the canvas's intrinsic/native size). */\nexport type CanvasCutoutShape =\n | { kind: \"rect\"; x: number; y: number; width: number; height: number }\n | {\n kind: \"roundedRect\";\n x: number;\n y: number;\n width: number;\n height: number;\n rx?: number;\n ry?: number;\n }\n | { kind: \"path\"; d: string };\n\n/**\n * Props for the SnowconeCanvas component.\n *\n * Props can be passed individually (flat) or grouped using config objects.\n * When both are provided, individual props take precedence over config values.\n *\n * @example Grouped config pattern (recommended for new code)\n * ```tsx\n * <SnowconeCanvas\n * artboards={[{ name: 'Front', width: 1200, height: 1200 }]}\n * imageConfig={{\n * src: 'https://example.com/artwork.png',\n * alignment: 'center',\n * scaleMode: 'contain',\n * }}\n * exportConfig={{\n * autoExportConfig: { enabled: true, debounceMs: 100 },\n * format: 'blob',\n * scale: 2,\n * imageFormat: 'webp',\n * }}\n * layoutConfig={{\n * viewPadding: 0.85,\n * artboardBorderRadius: 12,\n * showRotationHandle: false,\n * }}\n * onExportStatus={(event) => { ... }}\n * />\n * ```\n *\n * @example Flat props (backwards compatible, still fully supported)\n * ```tsx\n * <SnowconeCanvas\n * artboards={[{ name: 'Front', width: 1200, height: 1200 }]}\n * initialImage=\"https://example.com/artwork.png\"\n * autoExportConfig={{ enabled: true, debounceMs: 100 }}\n * autoExportFormat=\"blob\"\n * exportScale={2}\n * viewPadding={0.85}\n * onExportStatus={(event) => { ... }}\n * />\n * ```\n */\nexport interface SnowconeCanvasProps {\n // === Config object props (grouped) ===\n\n /**\n * Grouped export configuration. See {@link ExportConfig}.\n * Individual export props (autoExportConfig, autoExportFormat, etc.) take precedence if also provided.\n */\n exportConfig?: ExportConfig;\n\n /**\n * Grouped initial image configuration. See {@link ImageConfig}.\n * Individual image props (initialImage, initialImageAlignment, etc.) take precedence if also provided.\n */\n imageConfig?: ImageConfig;\n\n /**\n * Grouped layout/appearance configuration. See {@link LayoutConfig}.\n * Individual layout props (width, height, viewPadding, etc.) take precedence if also provided.\n */\n layoutConfig?: LayoutConfig;\n\n // === Kit configuration ===\n\n /**\n * Editor kit configuration. Use a preset name ('compact-customizer', 'pro-studio', 'embed-only')\n * or a custom KitDefinition for full control over layout, capabilities, and behavior.\n * @default 'pro-studio'\n */\n kit?: KitPresetId | KitDefinition;\n\n // === Artboards (placements) ===\n\n /**\n * Array of artboard configurations.\n * Each artboard represents a placement area (e.g., 'Front', 'Back').\n */\n artboards?: ArtboardConfig[];\n\n /**\n * Currently active artboard by name.\n * Used for controlled artboard selection.\n */\n activeArtboard?: string;\n\n /**\n * Callback when user switches artboards.\n * Receives the name of the newly active artboard.\n */\n onArtboardChange?: (name: string) => void;\n\n // === Single artboard shorthand ===\n\n /**\n * Artboard width (shorthand for single artboard).\n * If `artboards` prop is provided, this is ignored.\n * @deprecated Use `layoutConfig.width` instead.\n */\n width?: number;\n\n /**\n * Artboard height (shorthand for single artboard).\n * If `artboards` prop is provided, this is ignored.\n * @deprecated Use `layoutConfig.height` instead.\n */\n height?: number;\n\n // === Initial content ===\n\n /**\n * URL of initial image to load into the canvas.\n * Shows loading state while fetching, error state on failure.\n *\n * The image will be sized to \"cover\" the artboard (like CSS object-fit: cover)\n * and positioned according to `initialImageAlignment`.\n * @deprecated Use `imageConfig.src` instead.\n */\n initialImage?: string;\n\n /**\n * Alignment for the initial image on the artboard.\n * Uses a 9-point grid: 'tl', 't', 'tr', 'l', 'c', 'r', 'bl', 'b', 'br'\n * Or semantic values: 'center', 'top', 'bottom', 'left', 'right'\n *\n * Default: 'center' (places image centered on artboard)\n * @deprecated Use `imageConfig.alignment` instead.\n */\n initialImageAlignment?: AlignmentPoint;\n\n /**\n * Scale multiplier for the initial image.\n * Default: 1 (image covers artboard exactly)\n * Values > 1 make the image larger, < 1 make it smaller.\n * @deprecated Use `imageConfig.scale` instead.\n */\n initialImageScale?: number;\n\n /**\n * Scale mode for initial image placement.\n * - \"cover\" (default): image fills artboard completely, may crop edges\n * - \"contain\": image fits entirely inside artboard, may have empty space\n * @deprecated Use `imageConfig.scaleMode` instead.\n */\n initialImageScaleMode?: 'cover' | 'contain';\n\n /**\n * Serialized element configurations for restoring saved state.\n * Use this to restore a previously saved design.\n */\n initialElements?: AnyElementConfig[];\n\n // === State callbacks ===\n\n /**\n * Called when canvas content changes (elements added, modified, or removed).\n * Use this to persist the design state.\n */\n onChange?: (state: CanvasState) => void;\n\n /**\n * Called when selection changes.\n * Receives null when nothing is selected.\n */\n onSelectionChange?: (elementId: string | null) => void;\n\n // === Auto export ===\n\n /**\n * Interval in milliseconds for automatic exports.\n * Set to 0 or undefined to disable auto-export.\n * Recommended: 500ms for responsive preview updates.\n *\n * @deprecated Use `autoExportConfig` or `exportConfig.autoExportConfig` instead, which provides smart debouncing and change detection rather than fixed-interval polling.\n */\n autoExportInterval?: number;\n\n /**\n * Smart auto-export configuration with debouncing and change detection.\n * Replaces the timer-based `autoExportInterval` with an event-driven approach.\n * @deprecated Use `exportConfig.autoExportConfig` instead.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * autoExportConfig={{\n * enabled: true,\n * debounceMs: 100, // Wait 100ms after last change\n * maxWaitMs: 1000, // Force export after 1s of continuous changes\n * }}\n * onExport={(exports) => console.log('Exported:', exports)}\n * />\n * ```\n */\n autoExportConfig?: {\n /** Enable/disable auto-export */\n enabled?: boolean;\n /** Debounce delay - wait this long after last change before exporting (ms) */\n debounceMs?: number;\n /** Maximum wait time - force export after this duration even if changes keep coming (ms) */\n maxWaitMs?: number;\n };\n\n /**\n * Export format: 'dataUrl' (base64 string) or 'blob' (binary).\n * Blob is more efficient for API uploads.\n * @deprecated Use `exportConfig.format` instead.\n */\n autoExportFormat?: 'dataUrl' | 'blob';\n\n /**\n * If true, exports all artboards on each interval.\n * If false, only exports the active artboard.\n * @deprecated Use `exportConfig.exportAll` instead.\n */\n autoExportAll?: boolean;\n\n /**\n * Resolution multiplier for exports.\n * Default: 2 (recommended for print quality).\n *\n * Note: This scale is applied intelligently - if the resulting image would\n * exceed `maxExportSize`, the scale is reduced to cap the largest dimension.\n * @deprecated Use `exportConfig.scale` instead.\n */\n exportScale?: number;\n\n /**\n * Maximum export dimension (largest side) in pixels.\n * Default: 4000 (4K).\n *\n * When the artboard dimensions multiplied by exportScale would exceed this,\n * the effective scale is reduced to cap the output at this size.\n *\n * For example:\n * - Artboard 3951x4800 with exportScale=2 would normally be 7902x9600\n * - With maxExportSize=4000, it stays at 3951x4800 (scale effectively 1)\n * - Artboard 1200x1200 with exportScale=2 becomes 2400x2400 (under limit)\n *\n * Set to 0 or Infinity to disable this capping behavior.\n * @deprecated Use `exportConfig.maxSize` instead.\n */\n maxExportSize?: number;\n\n /**\n * Image format for auto-exports.\n * Default: 'png'.\n * Use 'webp' for smaller file sizes with transparency support (good for realtime preview).\n * @deprecated Use `exportConfig.imageFormat` instead.\n */\n exportImageFormat?: 'png' | 'jpg' | 'jpeg' | 'webp';\n\n /**\n * Image quality for auto-exports (0-1).\n * Only applies to JPEG and WebP formats. PNG ignores this.\n * Default: 0.92.\n * @deprecated Use `exportConfig.imageQuality` instead.\n */\n exportImageQuality?: number;\n\n /**\n * Called with export results.\n * Results are keyed by artboard name.\n *\n * @deprecated Use `onExportStatus` for a unified export lifecycle callback, or `onExportReady` for on-demand export control.\n */\n onExport?: (exports: Record<string, string | Blob>) => void;\n\n /**\n * Called immediately when an export is scheduled (before debounce).\n * Use this to show loading indicators right away instead of waiting for export to complete.\n * This fires as soon as a change is detected, giving consumers early warning.\n *\n * @deprecated Use `onExportStatus` instead, which provides `{ status: 'scheduled' }` events along with the full export lifecycle.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onExportScheduled={() => {\n * // Show shimmer/loading immediately\n * setIsExporting(true);\n * }}\n * onExport={(exports) => {\n * // Hide shimmer after export completes\n * setIsExporting(false);\n * }}\n * />\n * ```\n */\n onExportScheduled?: () => void;\n\n /**\n * Unified export status callback. Prefer this over individual `onExport`/`onExportScheduled` callbacks.\n *\n * Receives events for each phase of the export lifecycle:\n * - `scheduled` - Export was requested (before debounce completes)\n * - `rendering` - Export rendering has started\n * - `complete` - Export finished successfully with results\n * - `error` - Export failed with an error\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onExportStatus={(event) => {\n * switch (event.status) {\n * case 'scheduled':\n * setIsExporting(true);\n * break;\n * case 'complete':\n * setIsExporting(false);\n * uploadResults(event.result);\n * break;\n * case 'error':\n * setIsExporting(false);\n * showError(event.error);\n * break;\n * }\n * }}\n * />\n * ```\n */\n onExportStatus?: (event: ExportStatusEvent) => void;\n\n /**\n * Callback that provides an export function.\n * Called once when the canvas is ready.\n * Use this for manual/on-demand exports triggered by UI buttons.\n *\n * @example\n * ```tsx\n * const [exportFn, setExportFn] = useState<(() => Promise<Record<string, string | Blob>>) | null>(null);\n *\n * <SnowconeCanvas\n * onExportReady={(fn) => setExportFn(() => fn)}\n * ...\n * />\n *\n * <button onClick={() => exportFn?.()}>Export</button>\n * ```\n */\n onExportReady?: (exportFn: () => Promise<Record<string, string | Blob>>) => void;\n\n // === Image loading callbacks ===\n\n /**\n * Called when initialImage loads successfully.\n */\n onImageLoad?: (src: string) => void;\n\n /**\n * Called when initialImage fails to load.\n */\n onImageError?: (src: string, error: Error) => void;\n\n // === Error handling ===\n\n /**\n * General error callback for recoverable and non-recoverable canvas errors.\n * Receives a structured CanvasError with category, message, and context.\n *\n * This fires for:\n * - Image loading failures (category: 'image')\n * - Export errors (category: 'export')\n * - Element import/deserialization errors (category: 'import')\n * - Unexpected React crashes caught by ErrorBoundary (category: 'unknown')\n *\n * Existing `onExportStatus` and `onImageError` callbacks continue to work.\n * `onError` provides a unified stream of all errors for logging/analytics.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onError={(error) => {\n * console.error(`[${error.category}] ${error.message}`);\n * if (!error.recoverable) {\n * analytics.track('canvas_crash', { category: error.category });\n * }\n * }}\n * />\n * ```\n */\n onError?: (error: CanvasError) => void;\n\n // === Appearance ===\n\n /**\n * Theme name (reserved for future use).\n */\n theme?: string;\n\n /**\n * When true, skips the internal ThemeProvider and inherits theme from parent.\n * Use this when embedding SnowconeCanvas in an app that already manages themes.\n * This prevents the canvas from overriding the parent app's theme on document.documentElement.\n *\n * @default false\n */\n inheritTheme?: boolean;\n\n /**\n * When true, assumes EditorProvider already exists in a parent component.\n * Skips creating the internal EditorProvider so multiple SnowconeCanvas instances\n * can share the same editor state (e.g., when conditionally rendering mobile/desktop layouts).\n *\n * @default false\n */\n externalProvider?: boolean;\n\n /**\n * CSS class name for the container.\n */\n className?: string;\n\n /**\n * Inline styles for the container.\n */\n style?: React.CSSProperties;\n\n /**\n * CSS class name for the canvas wrapper element.\n * Use this to apply CSS masks, clips, or other effects to just the canvas\n * without affecting the overlay elements.\n * @deprecated Use `layoutConfig.canvasWrapperClassName` instead.\n */\n canvasWrapperClassName?: string;\n\n /**\n * Inline styles for the canvas wrapper element.\n * Use this to apply CSS masks, clips, or other effects to just the canvas\n * without affecting the overlay elements.\n * @deprecated Use `layoutConfig.canvasWrapperStyle` instead.\n */\n canvasWrapperStyle?: React.CSSProperties;\n\n /**\n * ADR-0054 Phase 4: shapes punched through the `<canvas>` as truly\n * transparent holes. See matching field on LayoutConfig for details.\n * Takes precedence over `layoutConfig.canvasCutouts`.\n */\n canvasCutouts?: CanvasCutoutShape[];\n\n /**\n * ADR-0054: piece boundary / safe-area / zone / label overlays drawn\n * directly into the canvas render loop (no DOM SVG overlay). Pieces\n * are in artboard-space coordinates (same frame as placements and\n * `canvasCutouts`). `guides` is keyed by piece id. Supply `undefined`\n * or omit to skip piece-guide rendering entirely.\n *\n * Replaces the old `VisualGuideOverlay` sibling-overlay pattern —\n * drawing directly in the ctx means no fixedMargin-unit / aspect-\n * ratio-letterbox / canvas-wrapper-negative-margin shenanigans.\n */\n pieceGuides?: PieceGuidesConfig;\n\n /**\n * ADR-0060: viewport focus mode for multi-piece spread layouts.\n *\n * - Omit / `'spread'` — show the entire artboard with all pieces\n * visible (historical behaviour). The toggle is a no-op for\n * single-piece layouts.\n * - `{ pieceId: 'front' }` — fit the viewport to that piece's\n * rect plus a peek margin so neighbour pieces remain visible\n * at the edges (dimmed) for overflow feedback. The piece id\n * must match an entry in `pieceGuides.pieces[]`; unrecognised\n * ids fall back to spread mode.\n *\n * Element data is unaffected — elements always live in spread\n * (artboard) coordinates regardless of focus. Switching focus is\n * purely an editor viewport / chrome decision.\n */\n pieceFocus?: 'spread' | { pieceId: string };\n\n // === Feature flags ===\n\n /**\n * Show toolbar UI (reserved for future use).\n * Default: false (canvas-only mode).\n * @deprecated Use `layoutConfig.showToolbar` instead.\n */\n showToolbar?: boolean;\n\n /**\n * Show layers panel (reserved for future use).\n * Default: false (canvas-only mode).\n * @deprecated Use `layoutConfig.showLayers` instead.\n */\n showLayers?: boolean;\n\n /**\n * Enable keyboard shortcuts.\n * Default: true.\n */\n enableShortcuts?: boolean;\n\n // === Overlay ===\n\n /**\n * Render custom controls on top of the canvas.\n * Useful for adding menu buttons, toolbars, etc.\n * The overlay is rendered inside the EditorProvider context.\n */\n overlay?: React.ReactNode;\n\n /**\n * When true, hides the internal Canvas component.\n * Use this when rendering a separate Canvas (e.g., in fullscreen mode)\n * that shares the same EditorProvider context.\n * This prevents two Canvas components from fighting over the same refs.\n * @deprecated Use `layoutConfig.hideCanvas` instead.\n */\n hideCanvas?: boolean;\n\n // === View configuration ===\n\n /**\n * Controls how much of the container the artboard fills.\n * Value between 0 and 1, where 1 means the artboard fills 100% of the container.\n * Default is 0.9 (90%), leaving 5% padding on each side.\n * Use smaller values (e.g., 0.8) to show more canvas area around the artboard,\n * which is useful for seeing selection handles that extend beyond the artboard.\n * @deprecated Use `layoutConfig.viewPadding` instead.\n */\n viewPadding?: number;\n\n /**\n * Border radius for artboard corners in pixels.\n * Use this to give the artboard rounded corners in the editor.\n * This is a visual-only effect - exports will still have the full artboard.\n * @default 0\n * @deprecated Use `layoutConfig.artboardBorderRadius` instead.\n */\n artboardBorderRadius?: number;\n\n /**\n * Fixed screen-space margin in pixels.\n * When set, the artboard will have this exact left/top margin regardless of zoom level.\n * This is useful for aligning the artboard with fixed-position UI elements like headers.\n * Overrides viewPadding for positioning (viewPadding still affects zoom calculation).\n * @deprecated Use `layoutConfig.fixedMargin` instead.\n */\n fixedMargin?: number;\n\n /**\n * Maximum HTML height in pixels.\n * When the artboard (with vertical fixedMargin padding) would exceed this height,\n * horizontal padding is increased to shrink the artboard until it fits within\n * this constraint while still respecting vertical fixedMargin.\n * @deprecated Use `layoutConfig.maxHeight` instead.\n */\n maxHeight?: number;\n\n /**\n * Whether to show the rotation handle on the canvas.\n * Set to false for embedded mode where rotation is handled via toolbar panel.\n * @default true\n * @deprecated Use `layoutConfig.showRotationHandle` instead.\n */\n showRotationHandle?: boolean;\n\n /**\n * Called once when the canvas is fully ready for interaction and export.\n * Fires after: artboards initialized, initial elements loaded, fonts ready,\n * and at least one render frame completed.\n */\n onReady?: () => void;\n}\n\ntype ImageLoadingState = 'idle' | 'loading' | 'success' | 'error';\n\n/**\n * Calculate the effective export scale for an artboard.\n * If the scaled dimensions would exceed maxSize, reduces the scale to cap at maxSize.\n *\n * @param width - Artboard width in pixels\n * @param height - Artboard height in pixels\n * @param requestedScale - The requested export scale multiplier\n * @param maxSize - Maximum dimension (largest side) for exported image\n * @returns The effective scale to use (may be lower than requestedScale)\n */\nfunction calculateEffectiveScale(\n width: number,\n height: number,\n requestedScale: number,\n maxSize: number\n): number {\n // If maxSize is 0 or Infinity, don't cap\n if (maxSize <= 0 || !isFinite(maxSize)) {\n return requestedScale;\n }\n\n const scaledWidth = width * requestedScale;\n const scaledHeight = height * requestedScale;\n const largestScaledSide = Math.max(scaledWidth, scaledHeight);\n\n // If already under the limit, use requested scale\n if (largestScaledSide <= maxSize) {\n return requestedScale;\n }\n\n // Calculate the scale that would result in exactly maxSize for the largest side\n const largestSide = Math.max(width, height);\n const cappedScale = maxSize / largestSide;\n\n // Don't scale up if we're capping (scale should be at most 1 if artboard is already >= maxSize)\n return Math.min(cappedScale, requestedScale);\n}\n\n/**\n * Inner component with access to EditorContext\n */\nconst SnowconeCanvasInner = forwardRef<SnowconeCanvasHandle, SnowconeCanvasProps>((props, ref) => {\n // === Merge config objects with individual props (individual props take precedence) ===\n const {\n exportConfig: _exportConfig,\n imageConfig: _imageConfig,\n layoutConfig: _layoutConfig,\n kit,\n artboards: artboardConfigs,\n activeArtboard: controlledActiveArtboard,\n onArtboardChange,\n initialElements,\n onChange,\n onSelectionChange,\n autoExportInterval: autoExportInterval_prop,\n onExport,\n onExportScheduled,\n onExportStatus,\n onExportReady,\n onImageLoad,\n onImageError,\n onError,\n onReady,\n className,\n style,\n enableShortcuts: enableShortcuts_prop,\n overlay,\n } = props;\n\n // Export config: individual props override exportConfig values\n const autoExportInterval = autoExportInterval_prop;\n const autoExportConfig = props.autoExportConfig ?? _exportConfig?.autoExportConfig;\n const autoExportFormat = props.autoExportFormat ?? _exportConfig?.format ?? 'dataUrl';\n const autoExportAll = props.autoExportAll ?? _exportConfig?.exportAll ?? false;\n const exportScale = props.exportScale ?? _exportConfig?.scale ?? 2;\n const maxExportSize = props.maxExportSize ?? _exportConfig?.maxSize ?? 4000;\n const exportImageFormat = props.exportImageFormat ?? _exportConfig?.imageFormat ?? 'png';\n const exportImageQuality = props.exportImageQuality ?? _exportConfig?.imageQuality ?? 0.92;\n\n // Image config: individual props override imageConfig values\n const initialImage = props.initialImage ?? _imageConfig?.src;\n const initialImageAlignment = props.initialImageAlignment ?? _imageConfig?.alignment ?? 'center';\n const initialImageScale = props.initialImageScale ?? _imageConfig?.scale ?? 1;\n const initialImageScaleMode = props.initialImageScaleMode ?? _imageConfig?.scaleMode ?? 'cover';\n\n // Layout config: individual props override layoutConfig values\n const width = props.width ?? _layoutConfig?.width ?? 1200;\n const height = props.height ?? _layoutConfig?.height ?? 1200;\n const viewPadding = props.viewPadding ?? _layoutConfig?.viewPadding ?? 0.9;\n const artboardBorderRadius = props.artboardBorderRadius ?? _layoutConfig?.artboardBorderRadius ?? 0;\n const fixedMargin = props.fixedMargin ?? _layoutConfig?.fixedMargin;\n const maxHeight = props.maxHeight ?? _layoutConfig?.maxHeight;\n const showRotationHandle = props.showRotationHandle ?? _layoutConfig?.showRotationHandle ?? true;\n const hideCanvas = props.hideCanvas ?? _layoutConfig?.hideCanvas ?? false;\n const canvasWrapperClassName = props.canvasWrapperClassName ?? _layoutConfig?.canvasWrapperClassName;\n const canvasWrapperStyle = props.canvasWrapperStyle ?? _layoutConfig?.canvasWrapperStyle;\n const canvasCutouts = props.canvasCutouts ?? _layoutConfig?.canvasCutouts;\n const pieceGuides = props.pieceGuides;\n const pieceFocus = props.pieceFocus;\n const enableShortcuts = enableShortcuts_prop ?? true;\n\n // === Resolve kit configuration ===\n const resolvedKit = useMemo(() => resolveKit(kit ?? 'pro-studio'), [kit]);\n\n // === Validate kit in development ===\n useEffect(() => {\n if (process.env.NODE_ENV === 'development' && resolvedKit) {\n const result = validateKit(resolvedKit);\n if (!result.valid) {\n log.warn('Kit validation warnings:', result.errors);\n }\n if (result.warnings.length > 0) {\n log.warn('Kit validation warnings:', result.warnings);\n }\n }\n }, [resolvedKit]);\n\n // === Error emission helper ===\n // Stable ref to avoid re-creating callbacks when onError changes\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const emitError = useCallback((error: CanvasError) => {\n log.error(`[${error.category}] ${error.message}`, error.originalError);\n onErrorRef.current?.(error);\n }, []);\n\n // Wire CanvasRenderer's static error callback to emitError\n useEffect(() => {\n CanvasRenderer.onRenderError = emitError;\n return () => {\n // Only clear if it's still our callback (avoid removing another instance's callback)\n if (CanvasRenderer.onRenderError === emitError) {\n CanvasRenderer.onRenderError = null;\n }\n };\n }, [emitError]);\n\n const {\n elements,\n setElements,\n selectedId,\n artboardManager,\n refreshArtboards,\n historyManager,\n isCanvasReady,\n setCanvasReady,\n } = useEditor();\n\n const { createArtboard, selectArtboard, artboards } = useArtboards();\n const { exportArtboard, exportAllArtboards, exportArtboardAsBlob, exportAllArtboardsAsBlobs } = useExport();\n\n // Expose imperative handle for programmatic export (ref-based API)\n useImperativeHandle(ref, () => ({\n exportArtboards: async (options = {}) => {\n const format = options.format || autoExportFormat;\n const requestedScale = options.scale || exportScale;\n const all = options.all ?? false;\n\n // Calculate effective scale (capped to maxExportSize)\n let effectiveScale = requestedScale;\n if (maxExportSize > 0 && isFinite(maxExportSize)) {\n for (const ab of artboards) {\n const abEffectiveScale = calculateEffectiveScale(ab.width, ab.height, requestedScale, maxExportSize);\n effectiveScale = Math.min(effectiveScale, abEffectiveScale);\n }\n }\n\n\n const exportOptions = { scale: effectiveScale, format: exportImageFormat as 'png' | 'jpg' | 'jpeg' | 'webp', quality: exportImageQuality };\n\n if (all) {\n // Export all artboards (results keyed by artboard ID)\n const resultsById = format === 'blob'\n ? await exportAllArtboardsAsBlobs(exportOptions)\n : await exportAllArtboards(exportOptions);\n\n // Transform keys from artboard ID to artboard name\n const resultsByName: Record<string, string | Blob> = {};\n for (const [artboardId, result] of Object.entries(resultsById)) {\n const artboard = artboards.find(ab => ab.id === artboardId);\n if (artboard) {\n resultsByName[artboard.name] = result;\n }\n }\n return resultsByName;\n } else {\n // Export active artboard only\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) {\n throw new Error('[SnowconeCanvas] No active artboard found');\n }\n\n if (format === 'blob') {\n const blob = await exportArtboardAsBlob(activeArtboard.id, exportOptions);\n return { [activeArtboard.name]: blob };\n } else {\n const dataUrl = await exportArtboard(activeArtboard.id, exportOptions);\n return { [activeArtboard.name]: dataUrl };\n }\n }\n },\n }), [exportArtboard, exportAllArtboards, exportArtboardAsBlob, exportAllArtboardsAsBlobs, autoExportFormat, exportScale, maxExportSize, exportImageFormat, exportImageQuality, artboardManager, artboards]);\n\n // Provide export function via callback (modern React pattern, works with dynamic imports)\n useEffect(() => {\n if (onExportReady) {\n const exportFn = async () => {\n\n // Get the active artboard\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) {\n throw new Error('[SnowconeCanvas] No active artboard found');\n }\n\n // Calculate effective scale (capped to maxExportSize)\n const effectiveScale = calculateEffectiveScale(\n activeArtboard.width,\n activeArtboard.height,\n exportScale,\n maxExportSize\n );\n\n\n // Export as single artboard (returns string or blob)\n const result = autoExportFormat === 'blob'\n ? await exportArtboardAsBlob(activeArtboard.id, { scale: effectiveScale })\n : await exportArtboard(activeArtboard.id, { scale: effectiveScale });\n\n // Return as Record keyed by artboard name (to match expected interface)\n return { [activeArtboard.name]: result };\n };\n onExportReady(exportFn);\n }\n }, [onExportReady, exportArtboard, exportArtboardAsBlob, artboardManager, autoExportFormat, exportScale, maxExportSize]);\n\n // Image loading state\n const [imageLoadingState, setImageLoadingState] = useState<ImageLoadingState>('idle');\n const [imageError, setImageError] = useState<Error | null>(null);\n const prevImageRef = useRef<string | undefined>(undefined);\n\n // Track if initial setup is complete\n const initializedRef = useRef(false);\n const initialElementsLoadedRef = useRef(false);\n const [initialElementsLoadedState, setInitialElementsLoadedState] = useState(false);\n\n // Export tracking\n const lastExportRef = useRef<number>(0);\n const exportCounterRef = useRef<number>(0);\n\n // Track if initial artboard sync is complete (to suppress premature onArtboardChange calls)\n // During initialization, createArtboard sets each new artboard as active, so the last one\n // ends up active. We only want to fire onArtboardChange after the controlled prop is synced.\n const hasInitialSyncRef = useRef(false);\n\n // === Initialize artboards ===\n useEffect(() => {\n if (initializedRef.current) return;\n initializedRef.current = true;\n\n // Clear existing artboards and create new ones from config\n const configs = artboardConfigs || [{ name: 'Design', width, height }];\n\n // Update the first (default) artboard instead of creating a new one\n const existingArtboards = artboardManager.getAllArtboards();\n if (existingArtboards.length > 0) {\n const firstConfig = configs[0];\n const firstArtboard = existingArtboards[0];\n\n // ADR-0054: piece-guide products want transparent artboards by default.\n // ArtboardElement constructor seeds `backgroundColor = '#ffffff'` and\n // every paint pass that consumes that field is *supposed* to be gated\n // on `!pieceGuides`, but the safety net here means a missed gate\n // can't leak a white frame past the silhouettes once the spread\n // clip lands in CanvasRenderer. Authors who explicitly set a\n // backgroundColor still win.\n const defaultBg = pieceGuides ? 'transparent' : undefined;\n\n // Update the first artboard\n firstArtboard.name = firstConfig.name;\n firstArtboard.width = firstConfig.width;\n firstArtboard.height = firstConfig.height;\n firstArtboard.clipShape = firstConfig.clipShape;\n if (firstConfig.backgroundColor) {\n firstArtboard.backgroundColor = firstConfig.backgroundColor;\n } else if (defaultBg) {\n firstArtboard.backgroundColor = defaultBg;\n }\n\n // Create additional artboards\n for (let i = 1; i < configs.length; i++) {\n createArtboard(configs[i].width, configs[i].height, {\n name: configs[i].name,\n backgroundColor: configs[i].backgroundColor ?? defaultBg,\n clipShape: configs[i].clipShape,\n });\n }\n }\n\n refreshArtboards();\n }, []);\n\n // === Load initial elements (restore from saved state) ===\n useEffect(() => {\n if (!initialElements || initialElements.length === 0) return;\n if (initialElementsLoadedRef.current) return;\n if (!initializedRef.current) return;\n\n initialElementsLoadedRef.current = true;\n setInitialElementsLoadedState(true);\n\n try {\n const deserialized = ElementFactory.createManyFromJSON(initialElements);\n\n // Assign all elements to the first artboard\n const allArtboards = artboardManager.getAllArtboards();\n if (allArtboards.length > 0) {\n const firstArtboard = allArtboards[0];\n for (const el of deserialized) {\n artboardManager.addElementToArtboard(el.id, firstArtboard.id);\n }\n }\n\n setElements(deserialized as typeof elements);\n\n // Custom fonts referenced by deserialised text elements need\n // an `@font-face` rule before the browser can paint them. The\n // canvas package itself doesn't inject those rules at startup —\n // only the font browser does, on user interaction. So on\n // refresh, the saved `fontFamily` falls through to a system\n // font; selecting the element opened the font browser, which\n // injected the CSS link, and the eventual re-render then\n // showed the correct face.\n //\n // Fix: for every unique non-system family in the restored\n // elements, inject a Google Fonts CSS link (best-effort —\n // matches `UnifiedFontService.loadGoogleFont`'s URL shape).\n // Once each link's stylesheet loads (which registers the\n // `@font-face` with the document), `document.fonts.load(...)`\n // pulls the actual font file, and a no-op `setElements`\n // identity update forces one more paint. Monotype fonts\n // fall back gracefully — link load fails, the render keeps\n // the system fallback.\n const familySet = new Set<string>();\n for (const el of deserialized) {\n const family = (el as { fontFamily?: unknown }).fontFamily;\n if (typeof family === 'string' && family.trim().length > 0) {\n familySet.add(family);\n }\n }\n if (familySet.size > 0 && typeof document !== 'undefined') {\n const fontsApi =\n (document as Document & { fonts?: FontFaceSet }).fonts ?? null;\n const linkPromises = Array.from(familySet).map((family) => {\n const linkId = `font-${family.replace(/\\s+/g, '-').toLowerCase()}`;\n let link = document.getElementById(linkId) as HTMLLinkElement | null;\n if (!link) {\n link = document.createElement('link');\n link.id = linkId;\n link.rel = 'stylesheet';\n link.href = `https://fonts.googleapis.com/css2?family=${family.replace(\n /\\s+/g,\n '+',\n )}:wght@400;700&display=swap`;\n document.head.appendChild(link);\n }\n return new Promise<string>((resolve) => {\n // Already loaded? Resolve immediately.\n if ((link as HTMLLinkElement).sheet) {\n resolve(family);\n return;\n }\n const finish = () => resolve(family);\n link!.addEventListener('load', finish, { once: true });\n link!.addEventListener('error', finish, { once: true });\n // Safety timeout — if neither event fires, don't hang.\n setTimeout(finish, 4000);\n });\n });\n Promise.all(linkPromises)\n .then((families) => {\n if (!fontsApi) return undefined;\n return Promise.all(\n families.flatMap((family) => [\n fontsApi.load(`400 16px \"${family}\"`).catch(() => undefined),\n fontsApi.load(`700 16px \"${family}\"`).catch(() => undefined),\n ]),\n );\n })\n .then(() => {\n // New array reference forces dependents (render loop's\n // memoised `elementsByArtboard` keys off `elements`) to\n // re-evaluate, so the canvas re-paints with the now-\n // available font faces.\n setElements((prev) => prev.slice());\n });\n }\n } catch (err) {\n log.error('Failed to load initial elements:', err);\n const importErr = err instanceof Error ? err : new Error(String(err));\n emitError({\n category: 'import',\n message: `Failed to load initial elements: ${importErr.message}`,\n originalError: importErr,\n recoverable: true,\n });\n }\n }, [initialElements, artboardManager, setElements, emitError]);\n\n // === Sync controlled activeArtboard prop ===\n useEffect(() => {\n if (!controlledActiveArtboard) {\n // No controlled prop, but still mark as synced so onArtboardChange can fire\n hasInitialSyncRef.current = true;\n return;\n }\n\n const targetArtboard = artboards.find((ab) => ab.name === controlledActiveArtboard);\n if (targetArtboard) {\n const activeArtboard = artboardManager.getActiveArtboard();\n if (activeArtboard?.name !== controlledActiveArtboard) {\n selectArtboard(targetArtboard.id);\n }\n // Mark as synced - now onArtboardChange can safely fire\n hasInitialSyncRef.current = true;\n }\n }, [controlledActiveArtboard, artboards]);\n\n // === Load initial image into ALL artboards ===\n useEffect(() => {\n if (!initialImage || prevImageRef.current === initialImage) return;\n\n // Skip if elements already exist (viewport switch scenario with externalProvider)\n // When using externalProvider, elements persist across component remounts,\n // so we shouldn't re-add the initial image on each mount\n if (elements.length > 0) {\n // Update ref to prevent future runs, but don't add duplicate images\n prevImageRef.current = initialImage;\n return;\n }\n\n prevImageRef.current = initialImage;\n\n const loadImage = async () => {\n setImageLoadingState('loading');\n setImageError(null);\n\n try {\n // Get ALL artboards to load the image into each one\n const allArtboards = artboardManager.getAllArtboards();\n if (allArtboards.length === 0) {\n throw new Error('No artboards available');\n }\n\n\n // Load image into EACH artboard\n const newElements: typeof elements = [];\n\n for (const artboard of allArtboards) {\n // Find the matching artboard config for per-artboard scaleMode\n const artboardConfig = artboardConfigs?.find(c => c.name === artboard.name);\n const effectiveScaleMode = artboardConfig?.scaleMode || initialImageScaleMode;\n\n // Use the placement function that calculates cover/contain sizing\n const result = await createImageElementWithPlacement(\n initialImage,\n {\n artboard: {\n width: artboard.width,\n height: artboard.height,\n x: artboard.x,\n y: artboard.y,\n },\n alignment: artboardConfig?.fitAlign || initialImageAlignment,\n scale: initialImageScale,\n scaleMode: effectiveScaleMode,\n marginTop: artboardConfig?.fitMarginTop,\n marginRight: artboardConfig?.fitMarginRight,\n marginBottom: artboardConfig?.fitMarginBottom,\n marginLeft: artboardConfig?.fitMarginLeft,\n }\n );\n\n if (result.success && result.element) {\n\n // Add element to this artboard\n artboardManager.addElementToArtboard(result.element.id, artboard.id);\n newElements.push(result.element);\n } else {\n log.error('Failed to load image for artboard:', artboard.name, result.error);\n }\n }\n\n if (newElements.length > 0) {\n setElements((prev) => [...prev, ...newElements]);\n setImageLoadingState('success');\n onImageLoad?.(initialImage);\n } else {\n const loadErr = new Error('Failed to load image into any artboard');\n setImageLoadingState('error');\n setImageError(loadErr);\n onImageError?.(initialImage, loadErr);\n emitError({\n category: 'image',\n message: loadErr.message,\n originalError: loadErr,\n recoverable: true,\n });\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n setImageLoadingState('error');\n setImageError(err);\n onImageError?.(initialImage, err);\n emitError({\n category: 'image',\n message: err.message,\n originalError: err,\n recoverable: true,\n });\n }\n };\n\n loadImage();\n }, [initialImage, initialImageAlignment, initialImageScale, initialImageScaleMode]);\n\n // === Handle selection change callback ===\n useEffect(() => {\n onSelectionChange?.(selectedId);\n }, [selectedId, onSelectionChange]);\n\n // === Handle onChange callback ===\n useEffect(() => {\n if (!onChange) return;\n\n const activeArtboard = artboardManager.getActiveArtboard();\n const state: CanvasState = {\n elements: elements.map((el) => el.toJSON()) as AnyElementConfig[],\n artboards: artboards.map((ab) => ({\n name: ab.name,\n width: ab.width,\n height: ab.height,\n clipShape: ab.clipShape,\n backgroundColor: ab.backgroundColor,\n })),\n activeArtboard: activeArtboard?.name || 'Design',\n };\n\n onChange(state);\n }, [elements, artboards, onChange]);\n\n // === Handle artboard change callback ===\n useEffect(() => {\n if (!onArtboardChange) return;\n\n // Skip until initial sync is complete - during initialization, createArtboard sets\n // each new artboard as active, so the last one ends up active. We wait for the\n // controlled activeArtboard prop to sync before firing this callback.\n if (!hasInitialSyncRef.current) return;\n\n const activeArtboard = artboardManager.getActiveArtboard();\n if (activeArtboard) {\n onArtboardChange(activeArtboard.name);\n }\n }, [artboardManager.getActiveArtboardId()]);\n\n // === Auto export ===\n const triggerExport = useCallback(async () => {\n if (!onExport && !onExportStatus) return;\n\n const exportNumber = ++exportCounterRef.current;\n const startTime = Date.now();\n\n // Calculate effective scale for each artboard (capped to maxExportSize)\n // Use the most restrictive scale to ensure all artboards fit within maxExportSize\n let effectiveScale = exportScale;\n if (maxExportSize > 0 && isFinite(maxExportSize)) {\n for (const ab of artboards) {\n const abEffectiveScale = calculateEffectiveScale(ab.width, ab.height, exportScale, maxExportSize);\n effectiveScale = Math.min(effectiveScale, abEffectiveScale);\n }\n }\n\n // Determine the artboard ID for status events\n const activeAb = artboardManager.getActiveArtboard();\n const statusArtboardId = activeAb?.id ?? 'unknown';\n\n // Fire 'rendering' status event\n onExportStatus?.({ status: 'rendering', artboardId: statusArtboardId });\n\n lastExportRef.current = startTime;\n\n try {\n let results: Record<string, string | Blob>;\n\n const exportOptions = {\n format: exportImageFormat as 'png' | 'jpg' | 'jpeg' | 'webp',\n scale: effectiveScale,\n quality: exportImageQuality,\n };\n\n if (autoExportFormat === 'blob') {\n // Export as Blob (more efficient for API uploads)\n if (autoExportAll) {\n const allResults = await exportAllArtboardsAsBlobs(exportOptions);\n results = {};\n for (const [artboardId, data] of Object.entries(allResults)) {\n const artboard = artboards.find((ab) => ab.id === artboardId);\n if (artboard) {\n results[artboard.name] = data;\n }\n }\n } else {\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) return;\n const blob = await exportArtboardAsBlob(activeArtboard.id, exportOptions);\n results = { [activeArtboard.name]: blob };\n }\n } else {\n // Export as dataUrl (default)\n if (autoExportAll) {\n const allResults = await exportAllArtboards(exportOptions);\n results = {};\n for (const [artboardId, data] of Object.entries(allResults)) {\n const artboard = artboards.find((ab) => ab.id === artboardId);\n if (artboard) {\n results[artboard.name] = data;\n }\n }\n } else {\n const activeArtboard = artboardManager.getActiveArtboard();\n if (!activeArtboard) return;\n const dataUrl = await exportArtboard(activeArtboard.id, exportOptions);\n results = { [activeArtboard.name]: dataUrl };\n }\n }\n\n // Fire legacy callback (backwards compat)\n onExport?.(results);\n\n // Fire unified status callback\n onExportStatus?.({ status: 'complete', artboardId: statusArtboardId, result: results });\n } catch (error) {\n // Silently skip if canvas ref not available (canvas still mounting)\n const errorMessage = error instanceof Error ? error.message : String(error);\n if (errorMessage.includes('Canvas ref not available')) {\n // Canvas not ready yet, will retry on next interval\n return;\n }\n const duration = Date.now() - startTime;\n log.error('Export failed (export #' + exportNumber + '):', {\n duration: `${duration}ms`,\n error: errorMessage,\n });\n\n // Fire unified error status callback\n const err = error instanceof Error ? error : new Error(errorMessage);\n onExportStatus?.({ status: 'error', artboardId: statusArtboardId, error: err });\n\n // Fire general onError callback\n emitError({\n category: 'export',\n message: err.message,\n originalError: err,\n artboardId: statusArtboardId,\n recoverable: true,\n });\n }\n }, [onExport, onExportStatus, autoExportAll, autoExportFormat, exportScale, maxExportSize, exportImageFormat, exportImageQuality, exportArtboard, exportAllArtboards, exportArtboardAsBlob, exportAllArtboardsAsBlobs, artboards, artboardManager, elements, autoExportConfig, emitError]);\n\n // === Deprecation warning for autoExportInterval ===\n useEffect(() => {\n if (autoExportInterval !== undefined && autoExportInterval > 0) {\n log.warn(\n 'autoExportInterval is deprecated and ignored. ' +\n 'Use autoExportConfig={{ enabled: true, debounceMs: 100, maxWaitMs: 1000 }} instead.'\n );\n }\n }, []);\n\n // === Debug: Log elements changes ===\n useEffect(() => {\n }, [elements]);\n\n // === Combined scheduled callback (fires both legacy + unified) ===\n const handleExportScheduled = useCallback(() => {\n // Fire legacy callback (backwards compat)\n onExportScheduled?.();\n\n // Fire unified status callback with 'scheduled' event\n if (onExportStatus) {\n const activeAb = artboardManager.getActiveArtboard();\n onExportStatus({ status: 'scheduled', artboardId: activeAb?.id ?? 'unknown' });\n }\n }, [onExportScheduled, onExportStatus, artboardManager]);\n\n // === Content readiness (gates auto-export on full content readiness) ===\n const hasInitialElements = !!initialElements && initialElements.length > 0;\n const isContentReady = useContentReady({\n isCanvasReady,\n elements,\n hasInitialElements,\n initialElementsLoaded: initialElementsLoadedState,\n });\n\n // === Fire onReady callback once content is ready ===\n const hasCalledOnReadyRef = useRef(false);\n useEffect(() => {\n if (isContentReady && !hasCalledOnReadyRef.current) {\n hasCalledOnReadyRef.current = true;\n onReady?.();\n }\n }, [isContentReady, onReady]);\n\n // === Smart auto-export with debouncing and change detection ===\n useAutoExport({\n config: autoExportConfig ? {\n enabled: autoExportConfig.enabled ?? true,\n debounceMs: autoExportConfig.debounceMs ?? 100,\n maxWaitMs: autoExportConfig.maxWaitMs ?? 1000,\n } : {\n enabled: false, // Only use smart export if autoExportConfig is provided\n },\n historyManager,\n artboards: artboards,\n elements,\n onExport: triggerExport,\n onExportScheduled: handleExportScheduled,\n isCanvasReady: isContentReady, // Gate exports until content is fully ready (fonts loaded, elements deserialized)\n });\n\n // === Signal canvas ready when artboards are initialized ===\n // This gates auto-export to prevent \"[useExport] Canvas ref not available\" errors\n useEffect(() => {\n if (artboards.length > 0 && !isCanvasReady) {\n // Small delay to ensure canvas DOM is mounted after artboards are ready\n const timeoutId = requestAnimationFrame(() => {\n setCanvasReady(true);\n });\n return () => cancelAnimationFrame(timeoutId);\n }\n return undefined;\n }, [artboards.length, isCanvasReady, setCanvasReady]);\n\n // === Cleanup canvas ready on unmount ===\n useEffect(() => {\n return () => {\n setCanvasReady(false);\n };\n }, [setCanvasReady]);\n\n // === ErrorBoundary onError handler ===\n // Fires emitError before the ErrorBoundary renders its fallback UI\n // NOTE: Must be declared before early returns to satisfy React hooks rules\n const handleBoundaryError = useCallback((error: Error) => {\n emitError({\n category: 'unknown',\n message: error.message,\n originalError: error,\n recoverable: false,\n });\n }, [emitError]);\n\n // === Render loading state ===\n if (imageLoadingState === 'loading') {\n return (\n <div\n className={className}\n style={{\n ...style,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: 'var(--surface, #f5f5f5)',\n }}\n >\n <Spinner size={48} />\n </div>\n );\n }\n\n // === Render error state ===\n if (imageLoadingState === 'error' && imageError) {\n return (\n <div\n className={className}\n style={{\n ...style,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: 'var(--surface, #f5f5f5)',\n color: 'var(--danger, #dc2626)',\n padding: '1rem',\n }}\n >\n <div className=\"text-center\">\n <div className=\"font-medium\">Failed to load image</div>\n <div className=\"text-sm mt-1 opacity-70\">{imageError.message}</div>\n </div>\n </div>\n );\n }\n\n // === Render canvas ===\n // Note: 'app-modern' class is required for toolbar CSS to work (see src/App.css)\n return (\n <KitProvider kit={resolvedKit}>\n <div className={`app-modern ${className || ''}`} style={{ ...style, position: 'relative' }}>\n <ErrorBoundary\n onError={handleBoundaryError}\n fallback={(error, reset) => (\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '24px',\n minHeight: '200px',\n backgroundColor: 'var(--surface, #f5f5f5)',\n color: 'var(--foreground, #333)',\n fontFamily: 'system-ui, sans-serif',\n textAlign: 'center',\n }}\n >\n <div style={{ fontSize: '18px', fontWeight: 600, marginBottom: '8px' }}>\n Something went wrong\n </div>\n <div style={{ fontSize: '14px', opacity: 0.7, marginBottom: '16px', maxWidth: '400px' }}>\n {error.message}\n </div>\n <button\n onClick={reset}\n style={{\n padding: '8px 20px',\n backgroundColor: 'var(--primary, #333)',\n color: 'var(--primary-foreground, #fff)',\n border: 'none',\n borderRadius: '6px',\n fontSize: '14px',\n fontWeight: 500,\n cursor: 'pointer',\n }}\n >\n Try again\n </button>\n </div>\n )}\n >\n {!hideCanvas && (\n canvasWrapperClassName || canvasWrapperStyle ? (\n <div className={canvasWrapperClassName} style={canvasWrapperStyle}>\n <Canvas\n style={{ width: '100%' }}\n fitPadding={viewPadding}\n artboardBorderRadius={artboardBorderRadius}\n fixedMargin={fixedMargin}\n maxHeight={maxHeight}\n showRotationHandle={showRotationHandle}\n enableShortcuts={enableShortcuts}\n canvasCutouts={canvasCutouts}\n pieceGuides={pieceGuides}\n pieceFocus={pieceFocus}\n />\n </div>\n ) : (\n <Canvas\n style={{ width: '100%' }}\n fitPadding={viewPadding}\n artboardBorderRadius={artboardBorderRadius}\n fixedMargin={fixedMargin}\n maxHeight={maxHeight}\n showRotationHandle={showRotationHandle}\n enableShortcuts={enableShortcuts}\n canvasCutouts={canvasCutouts}\n pieceGuides={pieceGuides}\n pieceFocus={pieceFocus}\n />\n )\n )}\n {overlay}\n </ErrorBoundary>\n </div>\n </KitProvider>\n );\n});\n\nSnowconeCanvasInner.displayName = 'SnowconeCanvasInner';\n\n/**\n * SnowconeCanvas - Self-contained embeddable canvas editor\n * Wraps with ThemeProvider and EditorProvider for full functionality\n *\n * @example With imperative export\n * ```tsx\n * const canvasRef = useRef<SnowconeCanvasHandle>(null);\n *\n * const handleExport = async () => {\n * const exports = await canvasRef.current?.exportArtboards();\n * console.log('Exported:', exports);\n * };\n *\n * return <SnowconeCanvas ref={canvasRef} ... />;\n * ```\n *\n * @example Embedded in app with existing theme\n * ```tsx\n * // When your app already manages themes, use inheritTheme to prevent conflicts\n * <SnowconeCanvas inheritTheme ... />\n * ```\n */\nexport const SnowconeCanvas = forwardRef<SnowconeCanvasHandle, SnowconeCanvasProps>((props, ref) => {\n const { inheritTheme, externalProvider } = props;\n\n // Resolve viewPadding from either flat prop or layoutConfig (flat takes precedence)\n const resolvedViewPadding = props.viewPadding ?? props.layoutConfig?.viewPadding;\n\n const innerContent = (\n <SnowconeCanvasInner ref={ref} {...props} />\n );\n\n // When externalProvider is true, skip creating EditorProvider\n // (assumes parent component already provides one)\n if (externalProvider) {\n return (\n <ThemeProvider defaultTheme=\"light\" passive={inheritTheme}>\n {innerContent}\n </ThemeProvider>\n );\n }\n\n return (\n <ThemeProvider defaultTheme=\"light\" passive={inheritTheme}>\n <EditorProvider viewPadding={resolvedViewPadding}>\n {innerContent}\n </EditorProvider>\n </ThemeProvider>\n );\n});\n\nSnowconeCanvas.displayName = 'SnowconeCanvas';\n\nexport default SnowconeCanvas;\n","/**\n * useCommands - Hook for undo/redo and command history operations\n *\n * Provides a clean interface to command history functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function UndoRedoToolbar() {\n * const { undo, redo, canUndo, canRedo, clearHistory } = useCommands();\n *\n * return (\n * <div className=\"toolbar\">\n * <button onClick={undo} disabled={!canUndo}>\n * Undo (⌘Z)\n * </button>\n * <button onClick={redo} disabled={!canRedo}>\n * Redo (⌘⇧Z)\n * </button>\n * <button onClick={clearHistory}>\n * Clear History\n * </button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useCallback } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('useCommands');\n\nexport interface UseCommandsReturn {\n // Undo/Redo\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n\n // History management\n clearHistory: () => void;\n clearArtboardHistory: (artboardId: string) => void;\n\n // Command execution\n executeElementUpdate: ReturnType<typeof useEditor>['executeElementUpdate'];\n executeAddElement: ReturnType<typeof useEditor>['executeAddElement'];\n executeRemoveElement: ReturnType<typeof useEditor>['executeRemoveElement'];\n executeReorderElement: ReturnType<typeof useEditor>['executeReorderElement'];\n executeCreateArtboard: ReturnType<typeof useEditor>['executeCreateArtboard'];\n executeDeleteArtboard: ReturnType<typeof useEditor>['executeDeleteArtboard'];\n executeUpdateArtboard: ReturnType<typeof useEditor>['executeUpdateArtboard'];\n\n // Batch execution\n executeCommandBatch: (commands: Array<{ execute: () => void; undo: () => void }>) => void;\n}\n\n/**\n * Hook for accessing command history and undo/redo functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { undo, redo, canUndo, canRedo } = useCommands();\n * const { selectedElement } = useEditor();\n *\n * const handleDelete = () => {\n * if (selectedElement) {\n * executeRemoveElement(selectedElement);\n * }\n * };\n *\n * return (\n * <div>\n * <button onClick={undo} disabled={!canUndo}>Undo</button>\n * <button onClick={redo} disabled={!canRedo}>Redo</button>\n * <button onClick={handleDelete}>Delete</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCommands(): UseCommandsReturn {\n const context = useEditor();\n\n // Extract command history methods\n const {\n undo,\n redo,\n canUndo,\n canRedo,\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCommandBatch,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n } = context;\n\n // Clear all history (not exposed in EditorContext yet, so create a placeholder)\n const clearHistory = useCallback(() => {\n logger.warn('[useCommands] clearHistory is not yet implemented in EditorContext');\n }, []);\n\n // Clear artboard-specific history (not exposed in EditorContext yet, so create a placeholder)\n const clearArtboardHistory = useCallback((_artboardId: string) => {\n logger.warn('[useCommands] clearArtboardHistory is not yet implemented in EditorContext');\n }, []);\n\n return {\n // Undo/Redo\n undo,\n redo,\n canUndo,\n canRedo,\n\n // History management\n clearHistory,\n clearArtboardHistory,\n\n // Command execution\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n\n // Batch execution\n executeCommandBatch,\n };\n}\n","import { useEditor } from '../contexts/EditorContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the currently selected element, or null if nothing is selected.\n *\n * This is a convenience wrapper around `useEditor()` that only extracts\n * selection state, keeping consumer components focused on what they need.\n *\n * @example\n * ```tsx\n * function PropertiesPanel() {\n * const selected = useSelectedElement();\n * if (!selected) return <p>Nothing selected</p>;\n * return <p>Selected: {selected.id}</p>;\n * }\n * ```\n */\nexport function useSelectedElement(): EditorElement | null {\n const { selectedElement } = useEditor();\n return selectedElement ?? null;\n}\n","import { useEditor } from '../contexts/EditorContext.js';\n\n/**\n * Returns whether the canvas is fully initialized and ready for operations.\n *\n * The canvas is not ready until the underlying `<canvas>` element is mounted\n * and the editor has completed its initial setup. Use this to gate operations\n * like export or programmatic element manipulation that require a live canvas.\n *\n * @returns `true` when the canvas is mounted and ready, `false` otherwise.\n *\n * @example\n * ```tsx\n * function ExportButton() {\n * const ready = useCanvasReady();\n * return (\n * <button disabled={!ready} onClick={handleExport}>\n * Export PNG\n * </button>\n * );\n * }\n * ```\n */\nexport function useCanvasReady(): boolean {\n const { isCanvasReady } = useEditor();\n return isCanvasReady;\n}\n","/**\n * useTextBinding - Bidirectional text binding between HTML inputs and canvas text elements.\n *\n * Links an external input to one or more canvas text elements by their `name` property.\n * Multiple elements can share the same name — `setText` updates all of them.\n * Changes flow both ways: input → canvas via `setText`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function NameInput() {\n * const { text, setText, isConnected } = useTextBinding('headline');\n * return (\n * <input\n * value={text}\n * onChange={(e) => setText(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/** Result returned by {@link useTextBinding}. */\nexport interface TextBindingResult {\n /** Current plain text content (from first bound element). Empty string if disconnected. */\n text: string;\n /** Update text on all bound elements. No-op if disconnected. */\n setText: (text: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind an HTML input to canvas text elements by their `name` property.\n *\n * Multiple elements can share the same name — `setText` updates all of them.\n * Text is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Text value, setter, element references, and connection status.\n */\nexport function useTextBinding(name: string): TextBindingResult {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setText = useCallback(\n (newText: string) => {\n for (const el of textElements) {\n const cloned = el.clone() as TextElement;\n cloned.setText(newText);\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n if (textElements.length === 0) {\n return { text: '', setText: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n text: textElements[0].getText(),\n setText,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useImageBinding - Bidirectional image URL binding between external inputs and canvas image elements.\n *\n * Links an external input to one or more canvas image elements by their `name` property.\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * Changes flow both ways: input → canvas via `setImageUrl`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function LogoUpload() {\n * const { imageUrl, setImageUrl, isConnected } = useImageBinding('logo');\n * return (\n * <input\n * type=\"url\"\n * value={imageUrl}\n * onChange={(e) => setImageUrl(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback, useRef } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/** How the image fits the element bounds. */\nexport type ImageFitMode = 'cover' | 'contain';\n\n/** Options for {@link useImageBinding}. */\nexport interface ImageBindingOptions {\n /** How the image fits the element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n}\n\n/** Result returned by {@link useImageBinding}. */\nexport interface ImageBindingResult {\n /** Current image URL (from first bound element). Empty string if disconnected. */\n imageUrl: string;\n /** Update image URL on all bound elements. No-op if disconnected. */\n setImageUrl: (url: string) => void;\n /** The first bound element, or null if no matching image element exists. */\n element: EditorElement | null;\n /** All bound image elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching image element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Calculate center-crop values so the visible area fills the original element bounds.\n */\nfunction coverCrop(\n origWidth: number,\n origHeight: number,\n imageAspectRatio: number,\n) {\n const placeholderAR = origWidth / origHeight;\n\n if (imageAspectRatio >= placeholderAR) {\n // Image is wider — match height, crop width\n const frameHeight = origHeight;\n const frameWidth = frameHeight * imageAspectRatio;\n const cropWidth = origWidth / frameWidth;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: (1 - cropWidth) / 2,\n cropY: 0,\n cropWidth,\n cropHeight: 1,\n };\n } else {\n // Image is taller — match width, crop height\n const frameWidth = origWidth;\n const frameHeight = frameWidth / imageAspectRatio;\n const cropHeight = origHeight / frameHeight;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: 0,\n cropY: (1 - cropHeight) / 2,\n cropWidth: 1,\n cropHeight,\n };\n }\n}\n\n/**\n * Bind an external input to canvas image elements by their `name` property.\n *\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * The image URL is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @param options - Optional configuration (fit mode, etc.).\n * @returns Image URL value, setter, element references, and connection status.\n */\nexport function useImageBinding(name: string, options?: ImageBindingOptions): ImageBindingResult {\n const fit = options?.fit ?? 'cover';\n const { elementStore, setElements } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n const imageElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is ImageElement => el instanceof ImageElement);\n }, [elementStore, name]);\n\n // Capture the original placeholder dimensions once (from the first time we see each element).\n // All subsequent image replacements fit within this fixed box.\n const origDimsRef = useRef<Map<string, { width: number; height: number }>>(new Map());\n for (const el of imageElements) {\n if (!origDimsRef.current.has(el.id)) {\n const td = el.transformData;\n origDimsRef.current.set(el.id, {\n width: td.width * td.cropWidth,\n height: td.height * td.cropHeight,\n });\n }\n }\n\n // Use refs for values that change on every element update to keep setImageUrl stable.\n // Without this, setImageUrl gets a new identity after every executeElementUpdate call,\n // which causes infinite loops when used in useEffect dependency arrays.\n const imageElementsRef = useRef(imageElements);\n imageElementsRef.current = imageElements;\n const executeRef = useRef(executeElementUpdate);\n executeRef.current = executeElementUpdate;\n const setElementsRef = useRef(setElements);\n setElementsRef.current = setElements;\n\n const setImageUrl = useCallback(\n (url: string) => {\n const currentElements = imageElementsRef.current;\n const exec = executeRef.current;\n const updateElements = setElementsRef.current;\n\n for (const el of currentElements) {\n const orig = origDimsRef.current.get(el.id);\n if (!orig) continue;\n const { width: origW, height: origH } = orig;\n\n const cloned = el.clone() as ImageElement;\n cloned.imageLoaded = false;\n cloned.imageElement = null;\n cloned.isCropping = false;\n cloned.imageUrl = url;\n\n if (fit === 'cover') {\n cloned.preserveDimensions = true;\n\n cloned.onLoadCallback = (loadedElement) => {\n const imgAR = loadedElement.imageAspectRatio || 1;\n const crop = coverCrop(origW, origH, imgAR);\n\n loadedElement.transformData.width = crop.width;\n loadedElement.transformData.height = crop.height;\n loadedElement.transformData.cropX = crop.cropX;\n loadedElement.transformData.cropY = crop.cropY;\n loadedElement.transformData.cropWidth = crop.cropWidth;\n loadedElement.transformData.cropHeight = crop.cropHeight;\n\n // Single atomic update: command history + element store.\n // This ensures auto-export only sees the final cropped state,\n // not an intermediate unloaded state that would produce wrong mockups.\n exec(el, loadedElement);\n };\n } else {\n cloned.preserveDimensions = false;\n\n cloned.onLoadCallback = (loadedElement) => {\n const w = loadedElement.transformData.width;\n const h = loadedElement.transformData.height;\n const scale = Math.min(origW / w, origH / h, 1);\n if (scale < 1) {\n loadedElement.transformData.width = w * scale;\n loadedElement.transformData.height = h * scale;\n }\n loadedElement.transformData.cropX = 0;\n loadedElement.transformData.cropY = 0;\n loadedElement.transformData.cropWidth = 1;\n loadedElement.transformData.cropHeight = 1;\n\n exec(el, loadedElement);\n };\n }\n\n // Start loading — onLoadCallback will update the store via exec()\n // once the image is ready with final dimensions/crop.\n cloned.loadImage(url);\n }\n },\n [fit], // Stable deps only — imageElements/executeElementUpdate/setElements accessed via refs\n );\n\n if (imageElements.length === 0) {\n return { imageUrl: '', setImageUrl: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n imageUrl: imageElements[0].imageUrl,\n setImageUrl,\n element: imageElements[0],\n elements: imageElements,\n isConnected: true,\n };\n}\n","/**\n * useElementByName - Look up an element by its `name` property.\n *\n * General-purpose complement to {@link useElementById}. Returns the first\n * element whose `name` matches, following render order.\n *\n * @example\n * ```tsx\n * function ElementInspector({ elementName }: { elementName: string }) {\n * const element = useElementByName(elementName);\n * if (!element) return <p>Element not found</p>;\n * return <p>Position: ({element.x}, {element.y})</p>;\n * }\n * ```\n *\n * @module\n */\n\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the first element matching the given name, or null.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to look up, or null to skip the lookup.\n * @returns The matching element, or null if no element matches.\n */\nexport function useElementByName(name: string | null): EditorElement | null {\n const { elementStore } = useElementsContext();\n if (!name) return null;\n return elementStore.getByName(name) ?? null;\n}\n","import React from 'react';\nimport { useViewportContext } from '../contexts/ViewportContext.js';\nimport type { PanOffset } from '../contexts/ViewportContext.js';\n\n/**\n * Return type for {@link useViewport}.\n */\nexport interface UseViewportReturn {\n /** Current zoom level (1.0 = 100%) */\n zoom: number;\n /** Current pan offset in world coordinates */\n panOffset: PanOffset;\n /** Increment zoom by one step */\n zoomIn: () => void;\n /** Decrement zoom by one step */\n zoomOut: () => void;\n /** Zoom to fit the active artboard in the viewport */\n zoomToFit: () => void;\n /** Reset zoom to 100% and pan offset to origin */\n resetView: () => void;\n /** Set zoom to an exact value */\n setZoom: React.Dispatch<React.SetStateAction<number>>;\n /** Set pan offset to an exact value */\n setPanOffset: React.Dispatch<React.SetStateAction<PanOffset>>;\n}\n\n/**\n * Returns viewport state (zoom, pan) and control functions.\n *\n * This hook now reads directly from ViewportContext (instead of the full\n * EditorContext), so components using it will only re-render when viewport\n * state changes -- not when elements, selection, or other state changes.\n *\n * @example\n * ```tsx\n * function ZoomBar() {\n * const { zoom, zoomIn, zoomOut, zoomToFit } = useViewport();\n * return (\n * <div>\n * <button onClick={zoomOut}>-</button>\n * <span>{Math.round(zoom * 100)}%</span>\n * <button onClick={zoomIn}>+</button>\n * <button onClick={zoomToFit}>Fit</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useViewport(): UseViewportReturn {\n const { zoom, panOffset, zoomIn, zoomOut, zoomToFit, resetView, setZoom, setPanOffset } = useViewportContext();\n return { zoom, panOffset, zoomIn, zoomOut, zoomToFit, resetView, setZoom, setPanOffset };\n}\n","/**\n * Serialize canvas state (from onChange/toJSON) to the format the export worker expects.\n *\n * This bridges the gap between SnowconeCanvas.onChange() output (element configs with\n * transformType and nested transformData) and the export worker's expected format\n * (elements with 'type' field and flattened image properties).\n *\n * Use this when sending canvas state to a server-side renderer that runs the export\n * worker bundle. The client-side serializeForWorkerExport() requires live element\n * instances; this function works on plain JSON from onChange/toJSON.\n */\n\nimport type { SerializedImageElement } from './renderer-types.js';\n\n/**\n * Wire-schema version of the server-render `canvas_state` payload (ADR-0079).\n *\n * This is a PUBLIC contract: third parties send `ServerRenderRequest` (produced\n * by {@link serializeStateForServer}) to Snowcone's realtime renderer. Bump the\n * MAJOR whenever the element/artboard shape changes incompatibly so a renderer\n * can reject states it cannot render instead of silently mis-rendering. Servers\n * treat a MISSING `schemaVersion` as `1` — legacy clients that predate the\n * stamp keep working.\n */\nexport const CANVAS_STATE_SCHEMA_VERSION = 1;\n\nexport interface ServerRenderRequest {\n /** Wire-schema version — see {@link CANVAS_STATE_SCHEMA_VERSION}. */\n schemaVersion: number;\n artboards: Array<{\n id: string;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n exportBackground: boolean;\n elements: any[];\n distressTexture?: any;\n imageMask?: any;\n }>;\n}\n\n/**\n * Convert onChange canvas state to the server render format.\n *\n * @param state - The state from SnowconeCanvas onChange callback\n * @returns ServerRenderRequest ready to send via sendCanvasState()\n */\nexport function serializeStateForServer(state: {\n elements?: any[];\n artboards?: Array<{\n name?: string;\n width?: number;\n height?: number;\n backgroundColor?: string;\n clipShape?: any;\n distressTexture?: any;\n imageMask?: any;\n }>;\n activeArtboard?: string;\n}): ServerRenderRequest {\n const srcArtboard = state.artboards?.[0] || { name: 'Front', width: 800, height: 800 };\n const elements = (state.elements || []).map(serializeElement);\n\n return {\n schemaVersion: CANVAS_STATE_SCHEMA_VERSION,\n artboards: [{\n id: 'artboard-1',\n name: srcArtboard.name || 'Front',\n x: 0,\n y: 0,\n width: srcArtboard.width || 800,\n height: srcArtboard.height || 800,\n backgroundColor: srcArtboard.backgroundColor || 'transparent',\n exportBackground: false,\n elements,\n distressTexture: srcArtboard.distressTexture,\n imageMask: srcArtboard.imageMask,\n }],\n };\n}\n\n/**\n * Serialize a single element from toJSON() format to export worker format.\n */\nfunction serializeElement(el: any): any {\n const type = el.type || el.transformType;\n\n if (type === 'image') {\n return serializeImageElement(el);\n }\n\n // Text, shape, group, path elements: map transformType → type, keep everything else\n // Text, shape, group, path elements: pass through as-is with type mapped.\n // Builtin mask URLs (builtin-mask:*) are kept as-is — the server worker\n // generates them procedurally via generateMaskBitmap().\n return {\n ...el,\n type,\n };\n}\n\n/**\n * Serialize an image element, flattening transformData to top-level fields\n * as expected by SerializedImageElement and renderImageElement.\n */\nfunction serializeImageElement(el: any): any {\n const td = el.transformData || {};\n const hasMasks = el.masks && el.masks.length > 0;\n\n // Visible dimensions depend on crop and masks\n const fullWidth = td.width || 0;\n const fullHeight = td.height || 0;\n const visibleWidth = hasMasks ? fullWidth : fullWidth * (td.cropWidth ?? 1);\n const visibleHeight = hasMasks ? fullHeight : fullHeight * (td.cropHeight ?? 1);\n\n const serialized: any = {\n id: el.id,\n type: 'image' as const,\n x: el.x || 0,\n y: el.y || 0,\n width: visibleWidth,\n height: visibleHeight,\n rotation: el.rotation || 0,\n imageUrl: el.imageUrl,\n imageAspectRatio: el.imageAspectRatio,\n };\n\n // Crop (only if actually cropped and no masks)\n const isCropped = !hasMasks && (td.cropX !== 0 || td.cropY !== 0 || td.cropWidth !== 1 || td.cropHeight !== 1);\n if (isCropped) {\n // Note: crop values stay as fractions here — the server's Chrome tab\n // will convert to pixels using the actual bitmap dimensions after fetch.\n // The client doesn't have the bitmap dimensions available in onChange state.\n serialized.cropX = td.cropX;\n serialized.cropY = td.cropY;\n serialized.cropWidth = td.cropWidth;\n serialized.cropHeight = td.cropHeight;\n serialized.needsCropPixelConversion = true; // Flag for server to convert\n }\n\n // Flip and border radius\n if (td.flipHorizontal) serialized.flipHorizontal = td.flipHorizontal;\n if (td.flipVertical) serialized.flipVertical = td.flipVertical;\n if (td.borderRadius) serialized.borderRadius = td.borderRadius;\n if (el.opacity !== undefined) serialized.opacity = el.opacity;\n\n // Effects\n if (el.distressEffect) serialized.distressEffect = el.distressEffect;\n if (el.masks?.length > 0) serialized.masks = el.masks;\n if (el.blendMode) serialized.blendMode = el.blendMode;\n if (el.knockoutParts) serialized.knockoutParts = el.knockoutParts;\n if (el.stroke) serialized.stroke = el.stroke;\n\n return serialized;\n}\n"],"names":["logger","createLogger","ErrorBoundary","Component","props","error","errorInfo","canvasError","jsxs","jsx","useAutoExport","options","config","historyManager","artboards","elements","onExport","onExportScheduled","isCanvasReady","managerRef","useRef","pendingExportRef","statsRef","stats","setStats","useState","onExportRef","useEffect","manager","AutoExportManager","DEFAULT_AUTO_EXPORT_CONFIG","unsubscribe","subscribeToImageLoads","elementId","e","updateConfig","useCallback","newConfig","forceExport","resetStats","resetChangeDetection","extractFontFamilies","fonts","el","richText","span","_a","ensureFontsLoaded","fontFamilies","pending","family","testString","resolve","waitOneFrame","useContentReady","hasInitialElements","initialElementsLoaded","isContentReady","setIsContentReady","checkingRef","cancelled","err","alignmentMap","getAlignment","value","calculateArtworkPlacement","artwork","placement","mode","mTop","mRight","mBottom","mLeft","effectiveWidth","effectiveHeight","scaleX","scaleY","coverScale","userScale","finalScale","scaledArtworkWidth","scaledArtworkHeight","alignment","x","y","overflowX","overflowY","offsetX","offsetY","xRange","yRange","DEFAULT_OPTIONS","isValidImageUrl","url","parsed","loadImageFromURL","opts","lastError","attempt","loadImageOnce","delay","timeout","img","timeoutId","resolved","cleanup","handleSuccess","w","h","handleError","errorMsg","createImageElementWithPlacement","placementOptions","loaderOptions","loadResult","placementConfig","topLeftX","topLeftY","centerX","centerY","ImageElement","_b","_c","_d","_e","_f","_g","ms","log","calculateEffectiveScale","width","height","requestedScale","maxSize","scaledWidth","scaledHeight","largestSide","cappedScale","SnowconeCanvasInner","forwardRef","ref","_exportConfig","_imageConfig","_layoutConfig","kit","artboardConfigs","controlledActiveArtboard","onArtboardChange","initialElements","onChange","onSelectionChange","autoExportInterval_prop","onExportStatus","onExportReady","onImageLoad","onImageError","onError","onReady","className","style","enableShortcuts_prop","overlay","autoExportInterval","autoExportConfig","autoExportFormat","autoExportAll","exportScale","maxExportSize","exportImageFormat","exportImageQuality","initialImage","initialImageAlignment","initialImageScale","initialImageScaleMode","viewPadding","artboardBorderRadius","fixedMargin","maxHeight","showRotationHandle","hideCanvas","canvasWrapperClassName","canvasWrapperStyle","canvasCutouts","pieceGuides","pieceFocus","enableShortcuts","resolvedKit","useMemo","resolveKit","process","result","validateKit","onErrorRef","emitError","CanvasRenderer","setElements","selectedId","artboardManager","refreshArtboards","setCanvasReady","useEditor","createArtboard","selectArtboard","useArtboards","exportArtboard","exportAllArtboards","exportArtboardAsBlob","exportAllArtboardsAsBlobs","useExport","useImperativeHandle","format","all","effectiveScale","ab","abEffectiveScale","exportOptions","resultsById","resultsByName","artboardId","artboard","activeArtboard","blob","dataUrl","imageLoadingState","setImageLoadingState","imageError","setImageError","prevImageRef","initializedRef","initialElementsLoadedRef","initialElementsLoadedState","setInitialElementsLoadedState","lastExportRef","exportCounterRef","hasInitialSyncRef","configs","existingArtboards","firstConfig","firstArtboard","defaultBg","i","deserialized","ElementFactory","allArtboards","familySet","fontsApi","linkPromises","linkId","link","finish","families","prev","importErr","targetArtboard","newElements","artboardConfig","c","effectiveScaleMode","loadErr","state","triggerExport","exportNumber","startTime","activeAb","statusArtboardId","results","allResults","data","errorMessage","duration","handleExportScheduled","hasCalledOnReadyRef","handleBoundaryError","Spinner","KitProvider","reset","Canvas","SnowconeCanvas","inheritTheme","externalProvider","resolvedViewPadding","innerContent","ThemeProvider","EditorProvider","useCommands","context","undo","redo","canUndo","canRedo","executeElementUpdate","executeAddElement","executeRemoveElement","executeReorderElement","executeCommandBatch","executeCreateArtboard","executeDeleteArtboard","executeUpdateArtboard","clearHistory","clearArtboardHistory","_artboardId","useSelectedElement","selectedElement","useCanvasReady","NOOP","EMPTY","useTextBinding","name","elementStore","useElementsContext","useCommandContext","textElements","TextElement","setText","newText","cloned","coverCrop","origWidth","origHeight","imageAspectRatio","placeholderAR","frameHeight","frameWidth","cropWidth","cropHeight","useImageBinding","fit","imageElements","origDimsRef","td","imageElementsRef","executeRef","setElementsRef","setImageUrl","currentElements","exec","orig","origW","origH","loadedElement","imgAR","crop","scale","useElementByName","useViewport","zoom","panOffset","zoomIn","zoomOut","zoomToFit","resetView","setZoom","setPanOffset","useViewportContext","CANVAS_STATE_SCHEMA_VERSION","serializeStateForServer","srcArtboard","serializeElement","type","serializeImageElement","hasMasks","fullWidth","fullHeight","visibleWidth","visibleHeight","serialized"],"mappings":"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"}