@snowcone-app/canvas 0.31.0 → 0.32.0

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.
Files changed (66) hide show
  1. package/ERROR_CONTRACT.md +11 -0
  2. package/STYLING_CONTRACT.md +3 -3
  3. package/dist/{CanvasRenderer-hpJ05JGO.js → CanvasRenderer-1j-twIbs.js} +443 -431
  4. package/dist/{CanvasRenderer-hpJ05JGO.js.map → CanvasRenderer-1j-twIbs.js.map} +1 -1
  5. package/dist/{CanvasStateV1-jLOibojY.js → CanvasStateV1-BmC1JfPv.js} +810 -802
  6. package/dist/{CanvasStateV1-jLOibojY.js.map → CanvasStateV1-BmC1JfPv.js.map} +1 -1
  7. package/dist/ContextualToolbars-BKU8IoAM.js +6474 -0
  8. package/dist/ContextualToolbars-BKU8IoAM.js.map +1 -0
  9. package/dist/{EditorCanvasImpl-P2GHPNA3.js → EditorCanvasImpl-4voOsj2k.js} +7 -7
  10. package/dist/{EditorCanvasImpl-P2GHPNA3.js.map → EditorCanvasImpl-4voOsj2k.js.map} +1 -1
  11. package/dist/{ExportManager-Bkzzyxgw.js → ExportManager-Ci81K6xA.js} +662 -518
  12. package/dist/ExportManager-Ci81K6xA.js.map +1 -0
  13. package/dist/{FontAnalyzer-BYhwxDhe.js → FontAnalyzer-BrKY6XYn.js} +1256 -1146
  14. package/dist/FontAnalyzer-BrKY6XYn.js.map +1 -0
  15. package/dist/{ImagePanel-CEr0_FYy.js → ImagePanel-ntJBepby.js} +6 -6
  16. package/dist/{ImagePanel-CEr0_FYy.js.map → ImagePanel-ntJBepby.js.map} +1 -1
  17. package/dist/{SnowconeCanvas-Fa0isFpO.js → SnowconeCanvas-CgQdiBXv.js} +10 -10
  18. package/dist/{SnowconeCanvas-Fa0isFpO.js.map → SnowconeCanvas-CgQdiBXv.js.map} +1 -1
  19. package/dist/advanced.mjs +14 -14
  20. package/dist/components/ImageToolbar.d.ts +8 -0
  21. package/dist/components/ShapeToolbar.d.ts +8 -0
  22. package/dist/components/TextToolbar.d.ts +5 -1
  23. package/dist/components/toolbars/EmbeddedToolbarLayout.d.ts +4 -0
  24. package/dist/components/toolbars/GroupElementToolbar.d.ts +8 -0
  25. package/dist/components/toolbars/SecondaryPanels.d.ts +24 -1
  26. package/dist/components/toolbars/index.d.ts +2 -2
  27. package/dist/core/BaseElement.d.ts +2 -1
  28. package/dist/core/shape-fill.d.ts +31 -0
  29. package/dist/{custom-icons-Co4rcW0S.js → custom-icons-DTWtkJ9N.js} +2 -2
  30. package/dist/{custom-icons-Co4rcW0S.js.map → custom-icons-DTWtkJ9N.js.map} +1 -1
  31. package/dist/{design-builder-CBG2hKc8.js → design-builder-BpbYUYtK.js} +55 -55
  32. package/dist/{design-builder-CBG2hKc8.js.map → design-builder-BpbYUYtK.js.map} +1 -1
  33. package/dist/{embed-D4TIzO7g.js → embed-C69x0jHc.js} +10 -10
  34. package/dist/{embed-D4TIzO7g.js.map → embed-C69x0jHc.js.map} +1 -1
  35. package/dist/embed.mjs +7 -7
  36. package/dist/errors/editorError.d.ts +7 -0
  37. package/dist/{guards-DgZuwVgu.js → guards-BhaQgJrE.js} +2 -2
  38. package/dist/{guards-DgZuwVgu.js.map → guards-BhaQgJrE.js.map} +1 -1
  39. package/dist/hooks/elementPatch.d.ts +8 -3
  40. package/dist/index.mjs +9 -9
  41. package/dist/internals.mjs +123 -123
  42. package/dist/internals.mjs.map +1 -1
  43. package/dist/rendering/LayerEffectsRenderer.d.ts +24 -0
  44. package/dist/rendering/renderer-types.d.ts +3 -1
  45. package/dist/state/CanvasStateV1.d.ts +17 -0
  46. package/dist/style.css +1 -1
  47. package/dist/{template-fields-CvFcr5zy.js → template-fields-D-2oLhCi.js} +5 -5
  48. package/dist/{template-fields-CvFcr5zy.js.map → template-fields-D-2oLhCi.js.map} +1 -1
  49. package/dist/testing.mjs +3 -3
  50. package/dist/types/index.d.ts +84 -1
  51. package/dist/types/public.d.ts +3 -2
  52. package/dist/{useCommands-bPXY9rCh.js → useCommands-DwnZHUg8.js} +435 -418
  53. package/dist/useCommands-DwnZHUg8.js.map +1 -0
  54. package/dist/{usePieceGuideBundle-Bj98nPk0.js → usePieceGuideBundle-CnSmYrmM.js} +2 -2
  55. package/dist/{usePieceGuideBundle-Bj98nPk0.js.map → usePieceGuideBundle-CnSmYrmM.js.map} +1 -1
  56. package/dist/utils/featureApplied.d.ts +4 -0
  57. package/dist/{validation-CsTlSvJJ.js → validation-DyUOjRGa.js} +11 -11
  58. package/dist/{validation-CsTlSvJJ.js.map → validation-DyUOjRGa.js.map} +1 -1
  59. package/dist/workers/export-worker.bundle.string.d.ts +1 -1
  60. package/example/package.json +1 -1
  61. package/package.json +1 -1
  62. package/dist/ContextualToolbars-DRbuUE7W.js +0 -6061
  63. package/dist/ContextualToolbars-DRbuUE7W.js.map +0 -1
  64. package/dist/ExportManager-Bkzzyxgw.js.map +0 -1
  65. package/dist/FontAnalyzer-BYhwxDhe.js.map +0 -1
  66. package/dist/useCommands-bPXY9rCh.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FontAnalyzer-BrKY6XYn.js","sources":["../../../node_modules/.pnpm/vite-plugin-node-polyfills@0.24.0_rollup@4.60.3_vite@5.4.21_@types+node@25.7.0_lightningcss@1.32.0_terser@5.47.1_/node_modules/vite-plugin-node-polyfills/shims/process/dist/index.js","../src/core/ArtboardElement.ts","../src/core/ArtboardManager.ts","../src/core/RotationUtils.ts","../src/core/BaseElement.ts","../src/core/Transform.ts","../src/fonts/google-fonts.ts","../src/constants.ts","../src/utils/logger.ts","../src/core/ImageLoadEvents.ts","../src/core/ImageCache.ts","../src/core/image-adjustments.ts","../src/core/ImageElement.ts","../src/core/paint-registry.ts","../src/types/index.ts","../src/core/TextElement.ts","../src/core/text-spacing.ts","../src/core/TextMetrics.ts","../src/core/rich-text-layout.ts","../src/core/ResizeUtils.ts","../src/transforms/CustomTransform.ts","../src/core/GeometryUtils.ts","../src/transforms/CircleTransform.ts","../src/transforms/ArchTransform.ts","../src/transforms/defaults.ts","../src/transforms/WaveTransform.ts","../src/transforms/FlagTransform.ts","../src/transforms/LeanTransform.ts","../src/transforms/AscendTransform.ts","../src/errors/editorError.ts","../src/core/shape-fill.ts","../src/core/ShapeElement.ts","../src/core/PathElement.ts","../src/transforms/registry.ts","../src/core/GroupElement.ts","../src/core/ElementStore.ts","../src/core/CommandHistory.ts","../src/core/HybridHistoryManager.ts","../src/utils/FontAnalyzer.ts"],"sourcesContent":["function getDefaultExportFromCjs (x) {\n\treturn x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;\n}\n\nvar browser = {exports: {}};\n\n// shim for using process in browser\nvar process = browser.exports = {};\n\n// cached from whatever global is present so that test runners that stub it\n// don't break things. But we need to wrap it in a try catch in case it is\n// wrapped in strict mode code which doesn't define any globals. It's inside a\n// function because try/catches deoptimize in certain engines.\n\nvar cachedSetTimeout;\nvar cachedClearTimeout;\n\nfunction defaultSetTimout() {\n throw new Error('setTimeout has not been defined');\n}\nfunction defaultClearTimeout () {\n throw new Error('clearTimeout has not been defined');\n}\n(function () {\n try {\n if (typeof setTimeout === 'function') {\n cachedSetTimeout = setTimeout;\n } else {\n cachedSetTimeout = defaultSetTimout;\n }\n } catch (e) {\n cachedSetTimeout = defaultSetTimout;\n }\n try {\n if (typeof clearTimeout === 'function') {\n cachedClearTimeout = clearTimeout;\n } else {\n cachedClearTimeout = defaultClearTimeout;\n }\n } catch (e) {\n cachedClearTimeout = defaultClearTimeout;\n }\n} ());\nfunction runTimeout(fun) {\n if (cachedSetTimeout === setTimeout) {\n //normal enviroments in sane situations\n return setTimeout(fun, 0);\n }\n // if setTimeout wasn't available but was latter defined\n if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {\n cachedSetTimeout = setTimeout;\n return setTimeout(fun, 0);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedSetTimeout(fun, 0);\n } catch(e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedSetTimeout.call(null, fun, 0);\n } catch(e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error\n return cachedSetTimeout.call(this, fun, 0);\n }\n }\n\n\n}\nfunction runClearTimeout(marker) {\n if (cachedClearTimeout === clearTimeout) {\n //normal enviroments in sane situations\n return clearTimeout(marker);\n }\n // if clearTimeout wasn't available but was latter defined\n if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {\n cachedClearTimeout = clearTimeout;\n return clearTimeout(marker);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedClearTimeout(marker);\n } catch (e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedClearTimeout.call(null, marker);\n } catch (e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.\n // Some versions of I.E. have different rules for clearTimeout vs setTimeout\n return cachedClearTimeout.call(this, marker);\n }\n }\n\n\n\n}\nvar queue = [];\nvar draining = false;\nvar currentQueue;\nvar queueIndex = -1;\n\nfunction cleanUpNextTick() {\n if (!draining || !currentQueue) {\n return;\n }\n draining = false;\n if (currentQueue.length) {\n queue = currentQueue.concat(queue);\n } else {\n queueIndex = -1;\n }\n if (queue.length) {\n drainQueue();\n }\n}\n\nfunction drainQueue() {\n if (draining) {\n return;\n }\n var timeout = runTimeout(cleanUpNextTick);\n draining = true;\n\n var len = queue.length;\n while(len) {\n currentQueue = queue;\n queue = [];\n while (++queueIndex < len) {\n if (currentQueue) {\n currentQueue[queueIndex].run();\n }\n }\n queueIndex = -1;\n len = queue.length;\n }\n currentQueue = null;\n draining = false;\n runClearTimeout(timeout);\n}\n\nprocess.nextTick = function (fun) {\n var args = new Array(arguments.length - 1);\n if (arguments.length > 1) {\n for (var i = 1; i < arguments.length; i++) {\n args[i - 1] = arguments[i];\n }\n }\n queue.push(new Item(fun, args));\n if (queue.length === 1 && !draining) {\n runTimeout(drainQueue);\n }\n};\n\n// v8 likes predictible objects\nfunction Item(fun, array) {\n this.fun = fun;\n this.array = array;\n}\nItem.prototype.run = function () {\n this.fun.apply(null, this.array);\n};\nprocess.title = 'browser';\nprocess.browser = true;\nprocess.env = {};\nprocess.argv = [];\nprocess.version = ''; // empty string to avoid regexp issues\nprocess.versions = {};\n\nfunction noop() {}\n\nprocess.on = noop;\nprocess.addListener = noop;\nprocess.once = noop;\nprocess.off = noop;\nprocess.removeListener = noop;\nprocess.removeAllListeners = noop;\nprocess.emit = noop;\nprocess.prependListener = noop;\nprocess.prependOnceListener = noop;\n\nprocess.listeners = function (name) { return [] };\n\nprocess.binding = function (name) {\n throw new Error('process.binding is not supported');\n};\n\nprocess.cwd = function () { return '/' };\nprocess.chdir = function (dir) {\n throw new Error('process.chdir is not supported');\n};\nprocess.umask = function() { return 0; };\n\nvar browserExports = browser.exports;\nconst process$1 = /*@__PURE__*/getDefaultExportFromCjs(browserExports);\n\nexport { process$1 as default, process$1 as process };\n//# sourceMappingURL=index.js.map\n","/**\n * ArtboardElement - Container for elements with fixed bounds (like Figma frames)\n * Unlike GroupElement, artboards have:\n * - Fixed dimensions (width/height)\n * - Background color\n * - Export boundaries\n * - Named for organization\n */\n\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { BoundingBox, Point, ArtboardConfig, ArtboardBackgroundType, ArtboardDistressTexture, ArtboardImageMask, ArtboardRole, ClipShape } from '../types/index.js';\n\nlet nextArtboardId = 1;\n\nexport class ArtboardElement {\n id: string;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n backgroundType: ArtboardBackgroundType;\n backgroundTexture?: string;\n exportBackground: boolean;\n transformType: 'artboard' = 'artboard';\n\n // Clip shape for artboard content (affects both rendering and export)\n clipShape?: ClipShape;\n\n // Preview only - NOT exported to PNG (for t-shirt color preview)\n previewBackgroundColor?: string;\n\n // Pacing role for multi-page templates (ADR-0086) — template metadata,\n // never sent on the server render wire\n role?: ArtboardRole;\n\n // Artboard-level distress texture\n distressTexture?: ArtboardDistressTexture;\n\n // Artboard-level image mask\n imageMask?: ArtboardImageMask;\n\n // Children elements (stored by ID for serialization, actual elements managed externally)\n private elementIds: Set<string>;\n\n constructor(config: Partial<ArtboardConfig> = {}) {\n this.id = config.id || `artboard-${nextArtboardId++}`;\n this.name = config.name || `Artboard ${nextArtboardId}`;\n this.x = config.x !== undefined ? config.x : 0;\n this.y = config.y !== undefined ? config.y : 0;\n this.width = config.width || 1920;\n this.height = config.height || 1080;\n this.backgroundColor = config.backgroundColor || '#ffffff';\n\n // Determine background type from config or backgroundColor\n if (config.backgroundType) {\n this.backgroundType = config.backgroundType;\n } else if (config.backgroundColor === 'transparent') {\n this.backgroundType = 'transparent';\n } else if (config.backgroundTexture) {\n this.backgroundType = 'texture';\n } else {\n this.backgroundType = 'color';\n }\n\n this.backgroundTexture = config.backgroundTexture;\n this.exportBackground = config.exportBackground ?? false; // Defaults to false\n this.clipShape = config.clipShape; // undefined means no clipping (same as 'rectangle')\n this.previewBackgroundColor = config.previewBackgroundColor;\n this.role = config.role;\n this.distressTexture = config.distressTexture ? { ...config.distressTexture } : undefined;\n this.imageMask = config.imageMask ? { ...config.imageMask } : undefined;\n this.elementIds = new Set(config.elementIds || []);\n }\n\n /**\n * Get bounding box of the artboard\n */\n getBoundingBox(): BoundingBox {\n return {\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n };\n }\n\n /**\n * Get center point of the artboard\n */\n getCenter(): Point {\n return {\n x: this.x + this.width / 2,\n y: this.y + this.height / 2,\n };\n }\n\n /**\n * Check if a point is within artboard bounds\n */\n containsPoint(px: number, py: number): boolean {\n return px >= this.x && px <= this.x + this.width && py >= this.y && py <= this.y + this.height;\n }\n\n /**\n * Check if an element's bounding box is within artboard bounds\n */\n containsElement(element: TextElement | ImageElement | GroupElement): boolean {\n const bbox = element.getBoundingBox();\n return (\n bbox.x >= this.x &&\n bbox.y >= this.y &&\n bbox.x + bbox.width <= this.x + this.width &&\n bbox.y + bbox.height <= this.y + this.height\n );\n }\n\n /**\n * Add an element ID to this artboard\n */\n addElementId(elementId: string): void {\n this.elementIds.add(elementId);\n }\n\n /**\n * Remove an element ID from this artboard\n */\n removeElementId(elementId: string): void {\n this.elementIds.delete(elementId);\n }\n\n /**\n * Check if artboard contains an element ID\n */\n hasElementId(elementId: string): boolean {\n return this.elementIds.has(elementId);\n }\n\n /**\n * Get all element IDs in this artboard\n */\n getElementIds(): string[] {\n return Array.from(this.elementIds);\n }\n\n /**\n * Get number of elements in artboard\n */\n getElementCount(): number {\n return this.elementIds.size;\n }\n\n /**\n * Clear all element IDs\n */\n clearElementIds(): void {\n this.elementIds.clear();\n }\n\n /**\n * Test if a point hits the artboard border (for selection)\n * Returns true if point is near the border (within tolerance)\n */\n hitTestBorder(px: number, py: number, tolerance: number = 5): boolean {\n const inXRange = px >= this.x - tolerance && px <= this.x + this.width + tolerance;\n const inYRange = py >= this.y - tolerance && py <= this.y + this.height + tolerance;\n\n // Check if point is near any edge\n const nearLeft = Math.abs(px - this.x) <= tolerance && inYRange;\n const nearRight = Math.abs(px - (this.x + this.width)) <= tolerance && inYRange;\n const nearTop = Math.abs(py - this.y) <= tolerance && inXRange;\n const nearBottom = Math.abs(py - (this.y + this.height)) <= tolerance && inXRange;\n\n return nearLeft || nearRight || nearTop || nearBottom;\n }\n\n /**\n * Clone the artboard\n */\n clone(): ArtboardElement {\n return new ArtboardElement({\n id: this.id,\n name: this.name,\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n backgroundColor: this.backgroundColor,\n backgroundType: this.backgroundType,\n backgroundTexture: this.backgroundTexture,\n exportBackground: this.exportBackground,\n // ClipShape is a string-or-object union — deep-copy object variants so the\n // clone doesn't share nested path/pieces data with the original\n clipShape: typeof this.clipShape === 'object' ? structuredClone(this.clipShape) : this.clipShape,\n previewBackgroundColor: this.previewBackgroundColor,\n role: this.role,\n distressTexture: this.distressTexture ? { ...this.distressTexture } : undefined,\n imageMask: this.imageMask ? { ...this.imageMask } : undefined,\n elementIds: Array.from(this.elementIds),\n });\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ArtboardConfig {\n return {\n id: this.id,\n name: this.name,\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n backgroundColor: this.backgroundColor,\n backgroundType: this.backgroundType,\n ...(this.backgroundTexture && { backgroundTexture: this.backgroundTexture }),\n exportBackground: this.exportBackground,\n elementIds: Array.from(this.elementIds),\n transformType: 'artboard' as const,\n // Clip shape for content clipping\n ...(this.clipShape && { clipShape: this.clipShape }),\n // Preview only - saved to project file, but NOT exported to PNG\n ...(this.previewBackgroundColor && { previewBackgroundColor: this.previewBackgroundColor }),\n // Pacing role for multi-page templates — saved to project file, stripped from the server wire\n ...(this.role && { role: this.role }),\n // Artboard-level distress texture\n ...(this.distressTexture && { distressTexture: { ...this.distressTexture } }),\n // Artboard-level image mask\n ...(this.imageMask && { imageMask: { ...this.imageMask } }),\n };\n }\n\n /**\n * Create from JSON\n */\n static fromJSON(config: ArtboardConfig): ArtboardElement {\n return new ArtboardElement(config);\n }\n\n /**\n * Update artboard properties\n */\n updateProperties(\n updates: Partial<{\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n backgroundType: ArtboardBackgroundType;\n backgroundTexture: string;\n exportBackground: boolean;\n clipShape: ClipShape;\n distressTexture: ArtboardDistressTexture | undefined;\n imageMask: ArtboardImageMask | undefined;\n }>\n ): void {\n if (updates.name !== undefined) this.name = updates.name;\n if (updates.x !== undefined) this.x = updates.x;\n if (updates.y !== undefined) this.y = updates.y;\n if (updates.width !== undefined) this.width = Math.max(100, updates.width); // Min width\n if (updates.height !== undefined) this.height = Math.max(100, updates.height); // Min height\n if (updates.backgroundColor !== undefined) this.backgroundColor = updates.backgroundColor;\n if (updates.backgroundType !== undefined) this.backgroundType = updates.backgroundType;\n if (updates.backgroundTexture !== undefined) this.backgroundTexture = updates.backgroundTexture;\n if (updates.exportBackground !== undefined) this.exportBackground = updates.exportBackground;\n if (updates.clipShape !== undefined) this.clipShape = updates.clipShape;\n if ('distressTexture' in updates) this.distressTexture = updates.distressTexture ? { ...updates.distressTexture } : undefined;\n if ('imageMask' in updates) this.imageMask = updates.imageMask ? { ...updates.imageMask } : undefined;\n }\n\n /**\n * Resize artboard (with constraints)\n */\n resize(newWidth: number, newHeight: number): void {\n this.width = Math.max(100, newWidth);\n this.height = Math.max(100, newHeight);\n }\n\n /**\n * Move artboard\n */\n move(newX: number, newY: number): void {\n this.x = newX;\n this.y = newY;\n }\n}\n\nexport default ArtboardElement;\n","/**\n * ArtboardManager - Manages artboard lifecycle and element-to-artboard mapping\n * Handles creation, deletion, and tracking of artboards\n */\n\nimport { ArtboardElement } from './ArtboardElement.js';\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { ArtboardConfig } from '../types/index.js';\n\nexport class ArtboardManager {\n private artboards: Map<string, ArtboardElement>;\n private elementToArtboard: Map<string, string>; // elementId -> artboardId\n private activeArtboardId: string | null;\n\n constructor() {\n this.artboards = new Map();\n this.elementToArtboard = new Map();\n this.activeArtboardId = null;\n }\n\n /**\n * Create a new artboard\n *\n * NOTE: This method sets the newly created artboard as active. When creating\n * multiple artboards in a loop (e.g., during initialization), the LAST artboard\n * will end up as active. If you need a specific artboard to be active after\n * batch creation, call setActiveArtboard() explicitly afterward.\n *\n * SnowconeCanvas handles this via hasInitialSyncRef - it suppresses onArtboardChange\n * callbacks until the controlled activeArtboard prop has been synced.\n */\n createArtboard(config: Partial<ArtboardConfig> = {}): ArtboardElement {\n const artboard = new ArtboardElement(config);\n this.artboards.set(artboard.id, artboard);\n\n // Always set newly created artboard as active\n // See note above about implications for batch creation\n this.activeArtboardId = artboard.id;\n\n return artboard;\n }\n\n /**\n * Delete an artboard\n * Returns the IDs of elements that were on the artboard\n */\n deleteArtboard(artboardId: string): string[] {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return [];\n }\n\n // Get all element IDs from the artboard\n const elementIds = artboard.getElementIds();\n\n // Remove element-to-artboard mappings\n elementIds.forEach((elementId) => {\n this.elementToArtboard.delete(elementId);\n });\n\n // Remove artboard\n this.artboards.delete(artboardId);\n\n // Update active artboard if deleted\n if (this.activeArtboardId === artboardId) {\n // Set to first available artboard or null\n const remainingArtboards = Array.from(this.artboards.keys());\n this.activeArtboardId = remainingArtboards.length > 0 ? remainingArtboards[0] : null;\n }\n\n return elementIds;\n }\n\n /**\n * Get an artboard by ID\n */\n getArtboard(artboardId: string): ArtboardElement | undefined {\n return this.artboards.get(artboardId);\n }\n\n /**\n * Get all artboards\n */\n getAllArtboards(): ArtboardElement[] {\n return Array.from(this.artboards.values());\n }\n\n /**\n * Get artboard IDs\n */\n getArtboardIds(): string[] {\n return Array.from(this.artboards.keys());\n }\n\n /**\n * Set active artboard\n */\n setActiveArtboard(artboardId: string | null): void {\n if (artboardId === null || this.artboards.has(artboardId)) {\n this.activeArtboardId = artboardId;\n }\n }\n\n /**\n * Get active artboard\n */\n getActiveArtboard(): ArtboardElement | null {\n return this.activeArtboardId ? this.artboards.get(this.activeArtboardId) || null : null;\n }\n\n /**\n * Get active artboard ID\n */\n getActiveArtboardId(): string | null {\n return this.activeArtboardId;\n }\n\n /**\n * Add an element to an artboard\n */\n addElementToArtboard(elementId: string, artboardId: string): boolean {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return false;\n }\n\n // Remove from previous artboard if exists\n const previousArtboardId = this.elementToArtboard.get(elementId);\n if (previousArtboardId) {\n const previousArtboard = this.artboards.get(previousArtboardId);\n previousArtboard?.removeElementId(elementId);\n }\n\n // Add to new artboard\n artboard.addElementId(elementId);\n this.elementToArtboard.set(elementId, artboardId);\n return true;\n }\n\n /**\n * Remove an element from its artboard\n */\n removeElementFromArtboard(elementId: string): void {\n const artboardId = this.elementToArtboard.get(elementId);\n if (artboardId) {\n const artboard = this.artboards.get(artboardId);\n artboard?.removeElementId(elementId);\n this.elementToArtboard.delete(elementId);\n }\n }\n\n /**\n * Get the artboard that contains an element\n */\n getArtboardForElement(elementId: string): ArtboardElement | null {\n const artboardId = this.elementToArtboard.get(elementId);\n return artboardId ? this.artboards.get(artboardId) || null : null;\n }\n\n /**\n * Get artboard ID for an element\n */\n getArtboardIdForElement(elementId: string): string | null {\n return this.elementToArtboard.get(elementId) || null;\n }\n\n /**\n * Get all elements on an artboard\n * Note: Returns element IDs, not actual element objects\n */\n getElementsOnArtboard(artboardId: string): string[] {\n const artboard = this.artboards.get(artboardId);\n return artboard ? artboard.getElementIds() : [];\n }\n\n /**\n * Find artboard at a point (for selection)\n */\n findArtboardAtPoint(x: number, y: number): ArtboardElement | null {\n // Check in reverse order (top artboard first)\n const artboards = Array.from(this.artboards.values()).reverse();\n\n for (const artboard of artboards) {\n if (artboard.containsPoint(x, y)) {\n return artboard;\n }\n }\n\n return null;\n }\n\n /**\n * Find artboard border at point (for resize handles)\n */\n findArtboardBorderAtPoint(x: number, y: number, tolerance: number = 5): ArtboardElement | null {\n const artboards = Array.from(this.artboards.values()).reverse();\n\n for (const artboard of artboards) {\n if (artboard.hitTestBorder(x, y, tolerance)) {\n return artboard;\n }\n }\n\n return null;\n }\n\n /**\n * Determine which artboard an element should belong to based on position\n */\n findArtboardForPosition(x: number, y: number): ArtboardElement | null {\n // Find first artboard that contains this point\n return this.findArtboardAtPoint(x, y);\n }\n\n /**\n * Update artboard properties\n */\n updateArtboard(artboardId: string, updates: Partial<ArtboardConfig>): boolean {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return false;\n }\n\n artboard.updateProperties(updates);\n return true;\n }\n\n /**\n * Rename an artboard\n */\n renameArtboard(artboardId: string, name: string): boolean {\n return this.updateArtboard(artboardId, { name });\n }\n\n /**\n * Check if an artboard exists\n */\n hasArtboard(artboardId: string): boolean {\n return this.artboards.has(artboardId);\n }\n\n /**\n * Get artboard count\n */\n getArtboardCount(): number {\n return this.artboards.size;\n }\n\n /**\n * Reorder artboards by moving the artboard at fromIndex to toIndex.\n * Preserves Map insertion order by rebuilding the internal Map.\n */\n reorderArtboards(fromIndex: number, toIndex: number): void {\n const entries = Array.from(this.artboards.entries());\n if (fromIndex < 0 || fromIndex >= entries.length) return;\n if (toIndex < 0 || toIndex >= entries.length) return;\n if (fromIndex === toIndex) return;\n\n const [moved] = entries.splice(fromIndex, 1);\n entries.splice(toIndex, 0, moved);\n\n this.artboards = new Map(entries);\n }\n\n /**\n * Clear all artboards\n */\n clear(): void {\n this.artboards.clear();\n this.elementToArtboard.clear();\n this.activeArtboardId = null;\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): {\n artboards: ArtboardConfig[];\n activeArtboardId: string | null;\n } {\n return {\n artboards: Array.from(this.artboards.values()).map((a) => a.toJSON()),\n activeArtboardId: this.activeArtboardId,\n };\n }\n\n /**\n * Load from JSON\n */\n fromJSON(data: { artboards: ArtboardConfig[]; activeArtboardId: string | null }): void {\n this.clear();\n\n // Restore artboards\n data.artboards.forEach((config) => {\n const artboard = ArtboardElement.fromJSON(config);\n this.artboards.set(artboard.id, artboard);\n\n // Restore element mappings\n artboard.getElementIds().forEach((elementId) => {\n this.elementToArtboard.set(elementId, artboard.id);\n });\n });\n\n // Restore active artboard\n this.activeArtboardId = data.activeArtboardId;\n }\n\n /**\n * Validate element-to-artboard mappings\n * Ensures all elements have valid artboard assignments\n */\n validateMappings(elements: (TextElement | ImageElement | GroupElement)[]): void {\n const elementIds = new Set(elements.map((e) => e.id));\n\n // Remove mappings for elements that no longer exist\n for (const [elementId, _artboardId] of this.elementToArtboard.entries()) {\n if (!elementIds.has(elementId)) {\n this.removeElementFromArtboard(elementId);\n }\n }\n\n // Ensure all artboards have valid element IDs\n for (const artboard of this.artboards.values()) {\n const validElementIds = artboard.getElementIds().filter((id) => elementIds.has(id));\n artboard.clearElementIds();\n validElementIds.forEach((id) => artboard.addElementId(id));\n }\n }\n}\n\nexport default ArtboardManager;\n","/**\n * RotationUtils - Centralized rotation convention utilities\n *\n * Convention: Clockwise rotations in UI are represented as positive degrees,\n * but canvas rendering uses counter-clockwise rotation (hence the negative sign).\n */\nexport const RotationUtils = {\n /**\n * Forward transform (local → world, for rendering)\n * Converts degrees to radians with the canvas convention (negative = clockwise)\n * @param {number} degrees - Rotation in degrees (positive = clockwise in UI)\n * @returns {number} Rotation in radians for canvas context\n */\n toRadians(degrees: number): number {\n return (-degrees * Math.PI) / 180;\n },\n\n /**\n * Inverse transform (world → local, for hit testing)\n * Converts degrees to radians without negation\n * @param {number} degrees - Rotation in degrees\n * @returns {number} Rotation in radians\n */\n toRadiansInverse(degrees: number): number {\n return (degrees * Math.PI) / 180;\n },\n\n /**\n * Normalize angle to -180 to +180 range\n * @param {number} degrees - Angle in degrees\n * @returns {number} Normalized angle in range [-180, 180]\n */\n normalize(degrees: number): number {\n let normalized = ((degrees % 360) + 360) % 360;\n if (normalized > 180) {\n normalized -= 360;\n }\n return normalized;\n },\n};\n","/**\n * BaseElement - Abstract base class for all canvas elements\n * Defines common interface and shared behavior for all element types\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport type {\n TransformType,\n AnyTransformData,\n BoundingBox,\n Point,\n ResizeAnchor,\n BlendMode,\n KnockoutConfig,\n StrokeConfig,\n MaskDefinition,\n DistressEffect,\n ShadowConfig,\n BaseElementConfig,\n TransformStartData,\n} from '../types/index.js';\n\nlet nextId = 1;\n\n/**\n * Reset the ID counter (useful for testing)\n */\nexport function resetElementIdCounter(): void {\n nextId = 1;\n}\n\nexport abstract class BaseElement {\n id: string;\n x: number;\n y: number;\n rotation: number;\n opacity: number;\n transformType: TransformType;\n transformData: AnyTransformData;\n\n // Effects properties\n blendMode?: BlendMode;\n knockoutParts?: KnockoutConfig;\n stroke?: StrokeConfig;\n masks?: MaskDefinition[];\n distressEffect?: DistressEffect;\n shadow?: ShadowConfig;\n\n // Layer properties\n name?: string;\n visible?: boolean;\n locked?: boolean;\n\n /** Whether this element clips content below it (derived from blendMode === 'clip') */\n get isClipping(): boolean {\n return this.blendMode === 'clip';\n }\n\n set isClipping(value: boolean | undefined) {\n if (value) {\n this.blendMode = 'clip';\n if (!this.knockoutParts) {\n this.knockoutParts = { fill: true, scope: 'group' };\n }\n } else if (this.blendMode === 'clip') {\n this.blendMode = 'normal';\n }\n }\n\n constructor(config: Partial<BaseElementConfig> = {}) {\n this.id = config.id || `element-${nextId++}`;\n this.x = config.x !== undefined ? config.x : 100;\n this.y = config.y !== undefined ? config.y : 100;\n this.rotation = config.rotation !== undefined ? config.rotation : 0;\n this.opacity = config.opacity !== undefined ? config.opacity : 1;\n\n // Transform-specific data (overridden by subclasses)\n this.transformType = config.transformType || 'custom';\n this.transformData = (config.transformData as AnyTransformData) || ({} as AnyTransformData);\n\n // Effects properties\n this.blendMode = config.blendMode;\n this.knockoutParts = config.knockoutParts;\n this.stroke = config.stroke;\n this.masks = config.masks;\n this.distressEffect = config.distressEffect;\n this.shadow = config.shadow;\n\n // Legacy migration: isClipping without blendMode → set blendMode = 'clip'\n if (config.isClipping && !config.blendMode) {\n this.blendMode = 'clip';\n if (!this.knockoutParts) {\n this.knockoutParts = { fill: true, scope: 'group' };\n }\n }\n\n // Layer properties\n this.name = config.name;\n this.visible = config.visible !== undefined ? config.visible : true;\n this.locked = config.locked !== undefined ? config.locked : false;\n }\n\n /**\n * Get bounding box in world coordinates\n * Used for transform math (resize, rotation, handles)\n * Must be implemented by subclasses\n */\n abstract getBoundingBox(): BoundingBox;\n\n /**\n * Get visual bounding box (tight fit around actual rendered content)\n * Used for selection display - should match what user sees\n * Default: same as getBoundingBox(), override for tighter fit\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (the point around which element rotates)\n * Default: returns element's position (x, y)\n * Override if rotation should be around a different point\n */\n getRotationAnchor(): Point {\n return { x: this.x, y: this.y };\n }\n\n /**\n * Test if a point hits this element\n * Uses visual bounding box for better user experience\n */\n hitTest(px: number, py: number): boolean {\n const visualBbox = this.getVisualBoundingBox();\n const rotationAnchor = this.getRotationAnchor();\n\n // Transform point to local coordinates (undo rotation)\n const rotationRad = RotationUtils.toRadiansInverse(this.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to rotation anchor\n const dx = px - rotationAnchor.x;\n const dy = py - rotationAnchor.y;\n\n // Rotate\n const localX = dx * cos - dy * sin;\n const localY = dx * sin + dy * cos;\n\n // Translate back\n const transformedX = rotationAnchor.x + localX;\n const transformedY = rotationAnchor.y + localY;\n\n // Test against axis-aligned visual bbox\n return (\n transformedX >= visualBbox.x &&\n transformedX <= visualBbox.x + visualBbox.width &&\n transformedY >= visualBbox.y &&\n transformedY <= visualBbox.y + visualBbox.height\n );\n }\n\n /**\n * Render the element\n * Must be implemented by subclasses\n */\n abstract render(ctx: CanvasRenderingContext2D, isSelected?: boolean, isHovered?: boolean): void;\n\n /**\n * Apply canvas transform (translate, rotate)\n * Common implementation for all elements\n */\n applyCanvasTransform(ctx: CanvasRenderingContext2D): void {\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n }\n\n /**\n * Serialize to JSON\n * Returns base element properties - subclasses should extend this\n */\n toJSON(): BaseElementConfig {\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n rotation: this.rotation,\n opacity: this.opacity,\n transformType: this.transformType,\n transformData: { ...this.transformData },\n // Effects properties (only include if defined)\n ...(this.blendMode && { blendMode: this.blendMode }),\n ...(this.knockoutParts && { knockoutParts: { ...this.knockoutParts } }),\n ...(this.stroke && {\n stroke: {\n ...this.stroke,\n ...(this.stroke.dashArray && { dashArray: [...this.stroke.dashArray] }),\n },\n }),\n // maskElement is a nested config object — structuredClone so clones don't share it\n ...(this.masks && {\n masks: this.masks.map((m) => ({ ...m, maskElement: structuredClone(m.maskElement) })),\n }),\n ...(this.distressEffect && { distressEffect: { ...this.distressEffect } }),\n ...(this.shadow && { shadow: { ...this.shadow } }),\n // Layer properties (only include if defined)\n ...(this.name && { name: this.name }),\n ...(this.visible !== undefined && { visible: this.visible }),\n ...(this.locked !== undefined && { locked: this.locked }),\n ...(this.isClipping && { isClipping: true }),\n };\n }\n\n /**\n * Clone this element\n * Uses toJSON() to serialize and reconstruct\n */\n abstract clone(): BaseElement;\n\n /**\n * Update position during drag\n */\n move(dx: number, dy: number): void {\n this.x += dx;\n this.y += dy;\n }\n\n /**\n * Update rotation\n */\n setRotation(newRotation: number): void {\n this.rotation = newRotation;\n }\n\n /**\n * Get enabled resize anchors for this element type\n * Default: all 8 anchors\n * Override in subclasses if needed\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Handle resize transform\n * Must be implemented by subclasses\n */\n abstract resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void | boolean;\n\n /**\n * Called when transform starts\n * Returns data to be passed to resize()\n */\n getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n width: bbox.width,\n height: bbox.height,\n rotation: this.rotation,\n transformData: { ...this.transformData },\n // Capture stroke width for proportional scaling during resize\n strokeWidth: this.stroke?.width,\n };\n }\n}\n\nexport default BaseElement;\n","/**\n * Transform - Centralized coordinate transformation utilities\n *\n * ROTATION CONVENTION:\n * -------------------\n * - UI: Positive rotation = CLOCKWISE (e.g., +45° rotates 45° clockwise)\n * - Canvas rendering: Uses ctx.rotate() with NEGATIVE angle (negative = clockwise in canvas)\n * - Mathematical convention: Standard rotation matrices\n *\n * COORDINATE SPACES:\n * -----------------\n * - WORLD space: The canvas coordinate system (absolute positions)\n * - LOCAL space: The element's own coordinate system (relative to element center, unrotated)\n *\n * TRANSFORMS:\n * ----------\n * - FORWARD transform (local → world): Used for rendering, uses NEGATIVE angle\n * - INVERSE transform (world → local): Used for hit testing and input, uses POSITIVE angle\n *\n * WHY THE SIGN DIFFERENCE?\n * -----------------------\n * Canvas rotation is counter-clockwise by default. To make positive rotations appear\n * clockwise in the UI, we negate the angle when rendering. For coordinate transforms:\n * - To go from local→world (rendering), we use the RENDERING angle (negative)\n * - To go from world→local (input), we UNDO the rendering rotation (positive)\n *\n * EXAMPLE:\n * -------\n * An element at (100, 100) rotated 45° clockwise:\n * - Rendering: ctx.rotate((-45 * Math.PI) / 180) // negative = clockwise\n * - Mouse at (150, 120) in world coords\n * - Convert to local: use +45° to undo the -45° rendering rotation\n * - Result: local coordinates relative to element's center\n */\n\n/** Minimal interface for objects that can be used with Transform */\nexport interface Transformable {\n x: number;\n y: number;\n rotation: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- extensible interface for elements with varying properties\n [key: string]: any;\n}\n\nexport class Transform {\n element: Transformable;\n\n /**\n * Create a transform helper for an element\n * @param element - Element with x, y, rotation properties\n */\n constructor(element: Transformable) {\n this.element = element;\n }\n\n /**\n * Convert a point from world coordinates to local coordinates\n * This is an INVERSE transform (world → local)\n * Uses positive angle with standard rotation matrix formula\n *\n * @param worldX - X coordinate in world space\n * @param worldY - Y coordinate in world space\n * @returns Point in local coordinates\n */\n worldToLocal(worldX: number, worldY: number) {\n // Get positive angle for inverse transform\n const rad = (this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Translate to element's origin\n const dx = worldX - this.element.x;\n const dy = worldY - this.element.y;\n\n // Apply rotation (same formula as forward, but this represents viewing from rotated frame)\n return {\n x: dx * cos - dy * sin,\n y: dx * sin + dy * cos,\n };\n }\n\n /**\n * Convert a point from local coordinates to world coordinates\n * This is a FORWARD transform (local → world)\n * Uses NEGATIVE angle matching the rendering convention\n *\n * @param localX - X coordinate in local space\n * @param localY - Y coordinate in local space\n * @returns Point in world coordinates\n */\n localToWorld(localX: number, localY: number) {\n // Get negative angle for forward transform (matches rendering)\n const rad = (-this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Apply forward rotation\n const rotX = localX * cos - localY * sin;\n const rotY = localX * sin + localY * cos;\n\n // Translate to world position\n return {\n x: this.element.x + rotX,\n y: this.element.y + rotY,\n };\n }\n\n /**\n * Convert a delta (movement/offset) from world space to local space\n * This is useful for drag operations - no translation, just rotation\n * Uses positive angle with standard rotation matrix formula\n *\n * @param dx - Delta X in world space\n * @param dy - Delta Y in world space\n * @returns Delta in local space\n */\n worldDeltaToLocal(dx: number, dy: number) {\n const rad = (this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n return {\n dx: dx * cos - dy * sin,\n dy: dx * sin + dy * cos,\n };\n }\n\n /**\n * Convert a delta (movement/offset) from local space to world space\n * Uses NEGATIVE angle (forward transform, matches rendering)\n *\n * @param dx - Delta X in local space\n * @param dy - Delta Y in local space\n * @returns Delta in world space\n */\n localDeltaToWorld(dx: number, dy: number) {\n const rad = (-this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n return {\n dx: dx * cos - dy * sin,\n dy: dx * sin + dy * cos,\n };\n }\n\n /**\n * Rotate a point around a custom anchor point\n * Useful for rotation handles and pivot-based operations\n *\n * @param pointX - Point X in world space\n * @param pointY - Point Y in world space\n * @param anchorX - Anchor X in world space\n * @param anchorY - Anchor Y in world space\n * @param angleDegrees - Rotation angle in degrees (positive = clockwise in UI)\n * @returns Rotated point in world space\n */\n static rotatePointAroundAnchor(pointX: number, pointY: number, anchorX: number, anchorY: number, angleDegrees: number) {\n // Use negative angle for forward transform (matches rendering convention)\n const rad = (-angleDegrees * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Translate to anchor origin\n const dx = pointX - anchorX;\n const dy = pointY - anchorY;\n\n // Rotate\n const rotX = dx * cos - dy * sin;\n const rotY = dx * sin + dy * cos;\n\n // Translate back\n return {\n x: anchorX + rotX,\n y: anchorY + rotY,\n };\n }\n\n /**\n * Get rotation angle in radians for rendering (forward transform)\n * Returns NEGATIVE angle (positive UI rotation → clockwise canvas rotation)\n *\n * @returns Rotation in radians for use with ctx.rotate()\n */\n getRenderingAngle() {\n return (-this.element.rotation * Math.PI) / 180;\n }\n\n /**\n * Get rotation angle in radians for inverse transforms\n * Returns POSITIVE angle (for converting world → local)\n *\n * @returns Rotation in radians for inverse transforms\n */\n getInverseAngle() {\n return (this.element.rotation * Math.PI) / 180;\n }\n\n /**\n * Get precomputed cos/sin for rendering (forward transform)\n * Useful when you need to do multiple transformations with the same angle\n *\n * @returns Cosine and sine of rendering angle\n */\n getRenderingCosSin() {\n const rad = this.getRenderingAngle();\n return {\n cos: Math.cos(rad),\n sin: Math.sin(rad),\n };\n }\n\n /**\n * Get precomputed cos/sin for inverse transforms\n * Useful when you need to do multiple transformations with the same angle\n *\n * @returns Cosine and sine of inverse angle\n */\n getInverseCosSin() {\n const rad = this.getInverseAngle();\n return {\n cos: Math.cos(rad),\n sin: Math.sin(rad),\n };\n }\n\n /**\n * Create a transform for an element snapshot (from startData)\n * Useful during drag operations when you need to use the original rotation\n *\n * @param elementSnapshot - Object with x, y, rotation properties\n * @returns Transform instance for the snapshot\n */\n static fromSnapshot(elementSnapshot: Transformable) {\n return new Transform(elementSnapshot);\n }\n}\n\nexport default Transform;\n","/**\n * Google Fonts configuration for Snowcone Canvas\n * Extensive list of fonts optimized for t-shirt and merchandise design\n */\n\nexport type FontCategory =\n | 'sans-serif'\n | 'serif'\n | 'slab-serif' // NEW - Bold, chunky serifs (popular for t-shirts!)\n | 'display'\n | 'script'\n | 'vintage'\n | 'decorative'\n | 'monospace'\n | 'system';\n\nexport type FontSource = 'google' | 'system';\n\nexport interface FontDefinition {\n name: string;\n category: FontCategory;\n weights?: number[]; // Available font weights (e.g., 400, 700)\n\n // Whether this needs to be loaded from Google Fonts\n googleFont: boolean;\n\n // Metadata\n source?: FontSource;\n description?: string;\n}\n\n/**\n * Extensive font list for t-shirt design (~212 fonts)\n * Organized by category for easy browsing.\n *\n * Note: We deliberately DO NOT include \"system\" fonts (Arial, Impact,\n * Times New Roman, Helvetica, etc.). Those aren't on Google Fonts and\n * aren't installed on every platform we ship to — Android in\n * particular ships only Roboto + Noto + a handful of others, so any\n * design referencing Impact / Verdana / Tahoma renders as a default\n * sans there. Sticking to Google Fonts means whatever a user picks\n * looks the same on iOS, macOS, Windows, Android, and Chrome OS.\n */\nexport const TSHIRT_FONTS: FontDefinition[] = [\n // Bold/Display Fonts - Perfect for statements and headlines (49 fonts)\n {\n name: 'Bebas Neue',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Modern condensed display',\n },\n {\n name: 'Bevan',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold slab serif display',\n },\n {\n name: 'Bigshot One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold elegant display',\n },\n {\n name: 'Anton',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Super bold display font',\n },\n {\n name: 'Oswald',\n category: 'display',\n weights: [200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Condensed sans-serif',\n },\n {\n name: 'Archivo Black',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy weight display',\n },\n {\n name: 'Alfa Slab One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold slab serif, varsity style',\n },\n {\n name: 'Bowlby One SC',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Blocky display, sports style',\n },\n {\n name: 'Black Ops One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Military/stencil style',\n },\n {\n name: 'Abril Fatface',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold high-contrast display',\n },\n {\n name: 'Bangers',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Comic book style',\n },\n {\n name: 'Titan One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy impactful display',\n },\n {\n name: 'Lilita One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded display font',\n },\n {\n name: 'Fugaz One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold geometric display',\n },\n {\n name: 'Russo One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold sans display',\n },\n {\n name: 'Passion One',\n category: 'display',\n weights: [400, 700, 900],\n googleFont: true,\n description: 'Condensed bold display',\n },\n {\n name: 'Righteous',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: '1980s-style display',\n },\n {\n name: 'Bungee',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold urban/street style',\n },\n {\n name: 'Barlow Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Condensed sans display',\n },\n {\n name: 'Staatliches',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold condensed display',\n },\n {\n name: 'Rubik Mono One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Blocky monoline display',\n },\n {\n name: 'Ultra',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Ultra bold serif',\n },\n {\n name: 'Audiowide',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Tech/futuristic display',\n },\n {\n name: 'Changa One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded bold display',\n },\n {\n name: 'Lobster',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold script display',\n },\n {\n name: 'Coda',\n category: 'display',\n weights: [400, 800],\n googleFont: true,\n description: 'Bold humanist sans',\n },\n {\n name: 'Teko',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Narrow sans display',\n },\n {\n name: 'Fredoka One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded bold display',\n },\n {\n name: 'Caprasimo',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Retro rounded display',\n },\n {\n name: 'Fjalla One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold condensed sans',\n },\n {\n name: 'Secular One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Hebrew-inspired display',\n },\n {\n name: 'Saira',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Semi-condensed sans display',\n },\n {\n name: 'Barlow Semi Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Semi-condensed low-contrast',\n },\n {\n name: 'Exo',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric technological display',\n },\n {\n name: 'Saira Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Condensed display font',\n },\n {\n name: 'Fredoka',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Rounded friendly display',\n },\n {\n name: 'Comfortaa',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Rounded geometric display',\n },\n {\n name: 'Questrial',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Simple sans-serif display',\n },\n {\n name: 'Lexend',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Variable sans for readability',\n },\n {\n name: 'Darker Grotesque',\n category: 'display',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary grotesque sans',\n },\n {\n name: 'Bakbak One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy condensed display',\n },\n {\n name: 'Turret Road',\n category: 'display',\n weights: [200, 300, 400, 500, 700, 800],\n googleFont: true,\n description: 'Geometric display font',\n },\n {\n name: 'Zilla Slab Highlight',\n category: 'display',\n weights: [400, 700],\n googleFont: true,\n description: 'Highlighted slab serif',\n },\n {\n name: 'Bree Serif',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Upright italic slab',\n },\n {\n name: 'Overpass',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Highway-inspired sans',\n },\n {\n name: 'Archivo',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'Chakra Petch',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Futuristic Thai-inspired',\n },\n {\n name: 'Concert One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded grotesque display',\n },\n\n // Sans Serif - Modern and clean (44 fonts)\n {\n name: 'Roboto',\n category: 'sans-serif',\n weights: [100, 300, 400, 500, 700, 900],\n googleFont: true,\n description: 'Clean, modern sans-serif',\n },\n {\n name: 'Montserrat',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric sans-serif',\n },\n {\n name: 'Open Sans',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Highly readable',\n },\n {\n name: 'Poppins',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric with personality',\n },\n {\n name: 'Work Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary sans-serif',\n },\n {\n name: 'Raleway',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Elegant sans-serif',\n },\n {\n name: 'Lato',\n category: 'sans-serif',\n weights: [100, 300, 400, 700, 900],\n googleFont: true,\n description: 'Warm sans-serif',\n },\n {\n name: 'Nunito',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Rounded sans-serif',\n },\n {\n name: 'Outfit',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern geometric sans',\n },\n {\n name: 'Inter',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Clean interface font',\n },\n {\n name: 'Quicksand',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Friendly rounded sans',\n },\n {\n name: 'Rubik',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Slightly rounded sans',\n },\n {\n name: 'Barlow',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Low-contrast sans',\n },\n {\n name: 'Josefin Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Geometric vintage sans',\n },\n {\n name: 'Exo 2',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary geometric',\n },\n {\n name: 'Kanit',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern loopless Thai',\n },\n {\n name: 'Hind',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Clean humanist sans',\n },\n {\n name: 'Alata',\n category: 'sans-serif',\n weights: [400],\n googleFont: true,\n description: 'Geometric sans',\n },\n {\n name: 'Urbanist',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric low-contrast',\n },\n {\n name: 'Manrope',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Modern geometric sans',\n },\n {\n name: 'Be Vietnam Pro',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern Vietnamese-optimized sans',\n },\n {\n name: 'Belanosima',\n category: 'sans-serif',\n weights: [400, 600, 700],\n googleFont: true,\n description: 'Contemporary sans-serif',\n },\n {\n name: 'DM Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Low-contrast geometric sans',\n },\n {\n name: 'Source Sans 3',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Adobe sans-serif family',\n },\n {\n name: 'Source Sans Pro',\n category: 'sans-serif',\n weights: [200, 300, 400, 600, 700, 900],\n googleFont: true,\n description: 'Adobe sans-serif (legacy)',\n },\n {\n name: 'IBM Plex Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'IBM corporate sans',\n },\n {\n name: 'Karla',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'PT Sans',\n category: 'sans-serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Russian universal sans',\n },\n {\n name: 'Fira Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Mozilla humanist sans',\n },\n {\n name: 'Space Grotesk',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Proportional space age sans',\n },\n {\n name: 'Syne',\n category: 'sans-serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Geometric variable sans',\n },\n {\n name: 'Libre Franklin',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Interpretation of Franklin Gothic',\n },\n {\n name: 'Alegreya Sans',\n category: 'sans-serif',\n weights: [100, 300, 400, 500, 700, 800, 900],\n googleFont: true,\n description: 'Humanist sans companion',\n },\n {\n name: 'Archivo Narrow',\n category: 'sans-serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Narrow grotesque sans',\n },\n {\n name: 'Chivo',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'Neuton',\n category: 'sans-serif',\n weights: [200, 300, 400, 700, 800],\n googleFont: true,\n description: 'Serif-adjacent sans',\n },\n {\n name: 'Prompt',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Loopless Thai sans',\n },\n {\n name: 'Noto Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Google universal sans',\n },\n {\n name: 'Nunito Sans',\n category: 'sans-serif',\n weights: [200, 300, 400, 600, 700, 800, 900],\n googleFont: true,\n description: 'Rounded sans-serif',\n },\n {\n name: 'Proza Libre',\n category: 'sans-serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Humanist sans-serif',\n },\n {\n name: 'Cabin',\n category: 'sans-serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Humanist sans inspired by Edward Johnston',\n },\n {\n name: 'Signika',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Sans-serif for signage',\n },\n {\n name: 'Public Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Strong neutral sans',\n },\n {\n name: 'Plus Jakarta Sans',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Geometric neo-grotesque',\n },\n {\n name: 'Red Hat Display',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Display variant of Red Hat sans',\n },\n\n // Script/Handwritten - Personal and creative (26 fonts)\n {\n name: 'Pacifico',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Retro surf script',\n },\n {\n name: 'Dancing Script',\n category: 'script',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Elegant handwriting',\n },\n {\n name: 'Satisfy',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten',\n },\n {\n name: 'Permanent Marker',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bold marker style',\n },\n {\n name: 'Caveat',\n category: 'script',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Modern handwritten',\n },\n {\n name: 'Shadows Into Light',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Friendly handwriting',\n },\n {\n name: 'Kaushan Script',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bold script',\n },\n {\n name: 'Amatic SC',\n category: 'script',\n weights: [400, 700],\n googleFont: true,\n description: 'Hand-drawn style',\n },\n {\n name: 'Indie Flower',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwriting',\n },\n {\n name: 'Cookie',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Brush script',\n },\n {\n name: 'Sacramento',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Elegant script',\n },\n {\n name: 'Allura',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Formal script',\n },\n {\n name: 'Great Vibes',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Elegant calligraphy',\n },\n {\n name: 'Yellowtail',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Flat-nib script',\n },\n {\n name: 'Courgette',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual upright script',\n },\n {\n name: 'Beth Ellen',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Handwritten marker style',\n },\n {\n name: 'Covered By Your Grace',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten',\n },\n {\n name: 'Nothing You Could Do',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Hand-drawn lettering',\n },\n {\n name: 'Homemade Apple',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Thin marker handwriting',\n },\n {\n name: 'Reenie Beanie',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten note style',\n },\n {\n name: 'Patrick Hand',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Handwriting font',\n },\n {\n name: 'Architects Daughter',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Architect-style handwriting',\n },\n {\n name: 'Handlee',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwriting',\n },\n {\n name: 'Zeyada',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bouncy handwritten script',\n },\n {\n name: 'Gloria Hallelujah',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Comic-style handwriting',\n },\n {\n name: 'Bad Script',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual Cyrillic script',\n },\n\n // Serif - Classic and elegant (28 fonts)\n {\n name: 'Playfair Display',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'High-contrast elegant',\n },\n {\n name: 'Merriweather',\n category: 'serif',\n weights: [300, 400, 700, 900],\n googleFont: true,\n description: 'Readable serif',\n },\n {\n name: 'Lora',\n category: 'serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Contemporary serif',\n },\n {\n name: 'Crimson Text',\n category: 'serif',\n weights: [400, 600, 700],\n googleFont: true,\n description: 'Classic book typography',\n },\n {\n name: 'PT Serif',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Transitional serif',\n },\n {\n name: 'Libre Baskerville',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Classic serif',\n },\n {\n name: 'Bitter',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary slab serif',\n },\n {\n name: 'Cormorant Garamond',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Display serif',\n },\n {\n name: 'Cinzel',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Classical serif capitals',\n },\n {\n name: 'Zilla Slab',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Contemporary slab serif',\n },\n {\n name: 'Cardo',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Large text serif',\n },\n {\n name: 'Spectral',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Efficient serif',\n },\n {\n name: 'Literata',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern serif',\n },\n {\n name: 'Cantata One',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Humanist slab serif',\n },\n {\n name: 'Bellefair',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Elegant high-contrast serif',\n },\n {\n name: 'Source Serif Pro',\n category: 'serif',\n weights: [200, 300, 400, 600, 700, 900],\n googleFont: true,\n description: 'Adobe serif family',\n },\n {\n name: 'Cormorant',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Display serif typeface',\n },\n {\n name: 'Eczar',\n category: 'serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Hybrid serif for screen',\n },\n {\n name: 'Alegreya',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Humanist serif',\n },\n {\n name: 'Fraunces',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Variable display serif',\n },\n {\n name: 'Inknut Antiqua',\n category: 'serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Display serif',\n },\n {\n name: 'BioRhyme',\n category: 'serif',\n weights: [200, 300, 400, 700, 800],\n googleFont: true,\n description: 'Rounded slab serif',\n },\n {\n name: 'Slabo 27px',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Display serif at 27px',\n },\n {\n name: 'Slabo 13px',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Text serif at 13px',\n },\n {\n name: 'Crimson Pro',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern oldstyle serif',\n },\n {\n name: 'Noto Serif',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Google universal serif',\n },\n {\n name: 'EB Garamond',\n category: 'serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Classical Garamond revival',\n },\n {\n name: 'Vollkorn',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Quiet, moderate serif',\n },\n {\n name: 'Young Serif',\n category: 'serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Modern retro serif',\n },\n\n // Vintage/Retro - Nostalgic and stylish (49 fonts)\n // Art Deco & 1920s-30s Style\n {\n name: 'Monoton',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco display',\n },\n {\n name: 'Limelight',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco geometric sans',\n },\n {\n name: 'Poiret One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco constructivist',\n },\n {\n name: 'Italiana',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco calligraphic',\n },\n {\n name: 'Federo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art nouveau geometric',\n },\n {\n name: 'Dorsa',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco display',\n },\n\n // Groovy/Psychedelic 60s-70s Style\n {\n name: 'Boogaloo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Groovy rounded display',\n },\n {\n name: 'Gorditas',\n category: 'vintage',\n weights: [400, 700],\n googleFont: true,\n description: 'Bubble slab with hearts',\n },\n {\n name: 'Shrikhand',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro curved display',\n },\n {\n name: 'Kumar One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Rounded bubble display',\n },\n {\n name: 'Chicle',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Bubble gum style',\n },\n {\n name: 'Flavors',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro bubble font',\n },\n {\n name: 'Chango',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Groovy rounded bold',\n },\n {\n name: 'Purple Purse',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Hippie funky casual',\n },\n {\n name: 'Super Dream',\n category: 'vintage',\n weights: [400],\n googleFont: false,\n description: '70s groovy bubble font',\n },\n\n // Retro Script & Sign Painting\n {\n name: 'Yesteryear',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage cursive script',\n },\n {\n name: 'Mrs Sheppards',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Old American script',\n },\n {\n name: 'Condiment',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage sign design',\n },\n\n // Western/Cowboy Style\n {\n name: 'Rye',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western Victorian',\n },\n {\n name: 'Sancreek',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western slab serif',\n },\n {\n name: 'Smokum',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western playful slab',\n },\n {\n name: 'Diplomata',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Bold western display',\n },\n {\n name: 'Ewert',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Ornamental wood type',\n },\n {\n name: 'Macondo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western carnival style',\n },\n {\n name: 'Wellfleet',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage slab serif',\n },\n {\n name: 'Ranchers',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western slab serif',\n },\n\n // Circus/Carnival/Theater\n {\n name: 'Bungee Shade',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Layered circus display',\n },\n {\n name: 'Peralta',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Rounded circus style',\n },\n {\n name: 'Kelly Slab',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro slab serif',\n },\n {\n name: 'Snippet',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage condensed',\n },\n\n // Retro Gaming & Tech\n {\n name: 'Press Start 2P',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: '8-bit retro gaming',\n },\n {\n name: 'VT323',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Terminal monospace',\n },\n\n // Victorian/Gothic/Medieval\n {\n name: 'UnifrakturCook',\n category: 'vintage',\n weights: [700],\n googleFont: true,\n description: 'Gothic blackletter',\n },\n {\n name: 'Sevillana',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Victorian ornate',\n },\n {\n name: 'Uncial Antiqua',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Medieval uncial',\n },\n {\n name: 'Piedra',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Stone carved display',\n },\n\n // Horror/Heavy Metal\n {\n name: 'Creepster',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Horror Halloween style',\n },\n {\n name: 'Nosifer',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Horror dripping',\n },\n {\n name: 'Eater',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Zombie horror',\n },\n {\n name: 'Metal Mania',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Heavy metal style',\n },\n {\n name: 'Butcherman',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Zombified horror',\n },\n\n // Typewriter & Classic\n {\n name: 'Special Elite',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Typewriter style',\n },\n\n // Soviet/Constructivist\n {\n name: 'Stalinist One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Soviet constructivist',\n },\n {\n name: 'Ruslan Display',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Russian display style',\n },\n\n // Additional Retro Display\n {\n name: 'Corben',\n category: 'vintage',\n weights: [400, 700],\n googleFont: true,\n description: 'Mid-century rounded',\n },\n {\n name: 'Kranky',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Quirky handwritten retro',\n },\n\n // Decorative/Unique (10+ fonts)\n {\n name: 'Orbitron',\n category: 'decorative',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Futuristic geometric',\n },\n {\n name: 'Playball',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Baseball script',\n },\n {\n name: 'Sedgwick Ave Display',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Graffiti style',\n },\n {\n name: 'Modak',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Rounded heavy display',\n },\n {\n name: 'Fascinate',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Broadway/theater',\n },\n {\n name: 'Bungee Inline',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Inline display',\n },\n {\n name: 'Emblema One',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Decorative caps',\n },\n {\n name: 'Fascinate Inline',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Inline Broadway',\n },\n {\n name: 'New Rocker',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Rock and roll',\n },\n {\n name: 'Bigelow Rules',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Playful cursive display',\n },\n\n // Monospace (useful for tech designs)\n {\n name: 'Roboto Mono',\n category: 'monospace',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Modern monospace',\n },\n {\n name: 'Courier Prime',\n category: 'monospace',\n weights: [400, 700],\n googleFont: true,\n description: 'Typewriter monospace',\n },\n {\n name: 'Space Mono',\n category: 'monospace',\n weights: [400, 700],\n googleFont: true,\n description: 'Retro-futuristic mono',\n },\n {\n name: 'Inconsolata',\n category: 'monospace',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Humanist monospace',\n },\n];\n\n/**\n * Generate Google Fonts CSS URL for all fonts\n */\nexport function getGoogleFontsUrl(): string {\n const googleFonts = TSHIRT_FONTS.filter((f) => f.googleFont);\n\n const fontSpecs = googleFonts.map((font) => {\n const weights = font.weights || [400];\n const weightStr = weights.join(';');\n return `${font.name.replace(/ /g, '+')}:wght@${weightStr}`;\n });\n\n return `https://fonts.googleapis.com/css2?${fontSpecs.join('&')}&display=swap`;\n}\n\n/**\n * Get font names only (for dropdown)\n */\nexport function getFontNames(): string[] {\n return TSHIRT_FONTS.map((f) => f.name);\n}\n\n/**\n * Get fonts grouped by category\n */\nexport function getFontsByCategory(): Record<FontCategory, FontDefinition[]> {\n const grouped: Record<string, FontDefinition[]> = {};\n\n TSHIRT_FONTS.forEach((font) => {\n if (!grouped[font.category]) {\n grouped[font.category] = [];\n }\n grouped[font.category].push(font);\n });\n\n return grouped as Record<FontCategory, FontDefinition[]>;\n}\n\n/**\n * Category display names\n */\nexport const CATEGORY_LABELS: Record<FontCategory, string> = {\n system: 'System Fonts',\n display: 'Bold & Display',\n 'sans-serif': 'Sans Serif',\n serif: 'Serif',\n 'slab-serif': 'Slab Serif', // NEW - Chunky serifs for bold designs\n script: 'Script & Handwritten',\n vintage: 'Vintage & Retro',\n decorative: 'Decorative & Unique',\n monospace: 'Monospace',\n};\n","/**\n * Central constants file for theme values, colors, and magic numbers\n * Eliminates duplication across the codebase\n */\n\nimport { getFontNames } from './fonts/google-fonts.js';\n\n/**\n * Get the current theme's accent color from HeroUI CSS variables\n * Returns oklch() color from --accent variable\n */\nexport function getThemeAccentColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0)'; // Fallback for SSR (black for default light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--accent').trim();\n return color || 'oklch(0% 0 0)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's accent foreground color from HeroUI CSS variables\n * This is the contrasting color for text/icons on accent backgrounds\n */\nexport function getThemeAccentForegroundColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(100% 0 0)'; // Fallback for SSR (white)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--accent-foreground').trim();\n return color || 'oklch(100% 0 0)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme name from the data-theme attribute\n */\nexport function getCurrentTheme(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'light';\n }\n \n if (element) {\n const themeAttr = element.getAttribute('data-theme');\n if (themeAttr) return themeAttr;\n \n const closestTheme = element.closest('[data-theme]');\n if (closestTheme) {\n return closestTheme.getAttribute('data-theme') || 'light';\n }\n }\n \n return document.documentElement.getAttribute('data-theme') || 'light';\n}\n\n/**\n * Get the default shape fill color based on the current theme\n * Returns colors that complement each theme's aesthetic\n */\nexport function getThemeShapeFillColor(): string {\n const theme = getCurrentTheme();\n\n // Use theme-appropriate blue shades for shapes\n if (theme.includes('dark')) {\n return '#60a5fa'; // Lighter blue for better visibility on dark backgrounds\n } else {\n return '#3b82f6'; // Classic blue for light theme\n }\n}\n\n/**\n * Get the spacing indicator color from the current theme\n */\nexport function getThemeSpacingColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#FF0000'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-spacing-indicator').trim();\n return color || '#FF0000'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's accent color with alpha transparency\n * Handles both oklch() colors (from HeroUI theme) and hex colors (legacy)\n */\nexport function getThemeAccentColorWithAlpha(alpha: number = 0.1, element?: HTMLElement): string {\n const color = getThemeAccentColor(element);\n\n // If it's an oklch color, add alpha channel\n if (color.startsWith('oklch(')) {\n // oklch(L C H) → oklch(L C H / alpha)\n const withoutClosing = color.slice(0, -1); // Remove closing )\n return `${withoutClosing} / ${alpha})`;\n }\n\n // Legacy hex color support\n if (color.startsWith('#')) {\n const hex = color.replace('#', '');\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n // Fallback - return color as-is\n return color;\n}\n\n/**\n * Get the current theme's text selection color from CSS variables\n * Returns the theme-specific selection highlight color (already includes opacity)\n */\nexport function getThemeTextSelectionColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.45)'; // Fallback for SSR (default light theme - black at 45%)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-text-selection').trim();\n return color || 'oklch(0% 0 0 / 0.45)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's hover accent color\n */\nexport function getThemeAccentHoverColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#b5f747'; // Fallback for SSR (light theme default)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-accent-hover').trim();\n return color || '#b5f747'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's hover accent color with alpha channel\n */\nexport function getThemeAccentHoverColorWithAlpha(alpha: number = 0.1, element?: HTMLElement): string {\n const color = getThemeAccentHoverColor(element);\n\n // If it's an oklch color, add alpha channel\n if (color.startsWith('oklch(')) {\n // oklch(L C H) → oklch(L C H / alpha)\n const withoutClosing = color.slice(0, -1); // Remove closing )\n return `${withoutClosing} / ${alpha})`;\n }\n\n // Legacy hex color support\n if (color.startsWith('#')) {\n const hex = color.replace('#', '');\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n // Fallback - return color as-is\n return color;\n}\n\n/**\n * Get the background color for canvas-rendered tooltips.\n * Uses --foreground so the tooltip is dark in light mode and light in dark mode,\n * ensuring contrast against the typically light artboard canvas.\n */\nexport function getThemeTooltipBackground(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89)'; // Fallback for SSR (dark for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n return color || 'oklch(0.2103 0.0059 285.89)';\n}\n\n/**\n * Get the text color for canvas-rendered tooltips.\n * Uses --background so text contrasts with getThemeTooltipBackground().\n */\nexport function getThemeTooltipForeground(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.9607 0 0)'; // Fallback for SSR (light for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--background').trim();\n return color || 'oklch(0.9607 0 0)';\n}\n\n/**\n * Get the shadow color for canvas-rendered tooltips.\n * Derives from the tooltip background at 0.2 opacity.\n */\nexport function getThemeTooltipShadowColor(element?: HTMLElement): string {\n const bg = getThemeTooltipBackground(element);\n if (bg.startsWith('oklch(')) {\n return `${bg.slice(0, -1)} / 0.2)`;\n }\n return 'rgba(0,0,0,0.2)';\n}\n\n/**\n * Get the border color for artboard outlines.\n * Uses --divider for a theme-appropriate muted border that adapts to light/dark mode.\n */\nexport function getThemeArtboardBorderColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.2)'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--divider').trim();\n return color || 'oklch(0% 0 0 / 0.2)';\n}\n\n/**\n * Get the label text color for artboard names.\n * Uses --foreground at 50% opacity for a muted appearance that adapts to light/dark mode.\n */\nexport function getThemeArtboardLabelColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.5)'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n if (color.startsWith('oklch(')) {\n return `${color.slice(0, -1)} / 0.5)`;\n }\n return color || 'oklch(0% 0 0 / 0.5)';\n}\n\n/**\n * Get the current theme's hover border color (uses hover accent color)\n */\nexport function getThemeHoverBorderColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#b5f747'; // Fallback for SSR (uses hover accent color)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-accent-hover-border').trim();\n return color || '#b5f747'; // Fallback if variable not found\n}\n\n/**\n * Get the pen tool path stroke color.\n * Uses --foreground so the path line is dark in light mode and light in dark mode,\n * ensuring visibility against both white and dark artboard backgrounds.\n */\nexport function getThemePenPathColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89)'; // Fallback for SSR (dark for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n return color || 'oklch(0.2103 0.0059 285.89)';\n}\n\n/**\n * Get the fill color for pen tool anchor points.\n * Uses --background so anchor circles are visible against the accent-color stroke on both light and dark themes.\n */\nexport function getThemePenAnchorFillColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(1 0 0)'; // Fallback for SSR (white, suitable for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--background').trim();\n return color || 'oklch(1 0 0)';\n}\n\n/**\n * Get the pen tool bezier handle color (control lines and dots).\n * Uses --foreground at 50% opacity for a muted appearance that adapts to light/dark mode.\n */\nexport function getThemePenHandleColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89 / 0.5)'; // Fallback for SSR (muted dark)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n if (color.startsWith('oklch(')) {\n return `${color.slice(0, -1)} / 0.5)`;\n }\n return color || 'oklch(0.2103 0.0059 285.89 / 0.5)';\n}\n\n// Selection and UI colors - dynamic based on current theme\n// Call these functions at render time (not at import time) to get the current theme color.\nexport function getSelectionColor(): string {\n return getThemeAccentColor();\n}\nexport function getSelectionFillColor(): string {\n return getThemeAccentColorWithAlpha(0.1);\n}\nexport const HOVER_COLOR = '#fbbf24'; // Amber\n\n// Preview rendering settings (UI only, never exported)\nexport const PREVIEW_ELEMENT_OPACITY = 1.0; // Full opacity\n\n// Default element colors\nexport const DEFAULT_ARTBOARD_COLOR = '#ffffff'; // White\n// Note: DEFAULT_SHAPE_FILL_COLOR is deprecated - use getThemeShapeFillColor() instead\nexport const DEFAULT_SHAPE_FILL_COLOR = '#3b82f6'; // Blue (fallback only)\n\n// Handle dimensions\nexport const HANDLE_SIZE = 12;\nexport const HANDLE_RADIUS = 6;\nexport const ROTATION_HANDLE_DISTANCE = 20; // Distance from bottom edge to rotation handle\nexport const HANDLE_HIT_TOLERANCE = 8; // Hit test tolerance for handles\n\n// Crop handle dimensions - VISUAL sizes (what users see)\nexport const CORNER_HANDLE_VISUAL_RADIUS = 6; // Visual radius of circular corner handles\nexport const EDGE_HANDLE_VISUAL_LENGTH = 24; // Visual length of edge rectangles\n\n// Crop handle dimensions - HIT ZONE sizes (larger for comfortable touch/mouse interaction)\nexport const CORNER_HANDLE_HIT_RADIUS = 16; // Hit zone radius for circular corner handles\nexport const EDGE_HANDLE_LENGTH = 36; // Hit zone length for edge handles\nexport const EDGE_HANDLE_HIT_WIDTH = 24; // Hit zone WIDTH for edge handles (perpendicular to edge)\n\n// Snap system\nexport const ALIGNMENT_SNAP_THRESHOLD = 2; // Distance threshold for edge/center alignment snapping in pixels\nexport const SPACING_SNAP_THRESHOLD = 4; // Distance threshold for spacing pattern snapping in pixels\n\n// Spacing indicator colors\n// Note: Use getThemeSpacingColor() for dynamic theme-based colors\nexport const SPACING_LABEL_TEXT_COLOR = '#FFFFFF'; // White text for spacing labels\nexport const SPACING_LABEL_BORDER_RADIUS = 2; // Border radius for spacing labels in pixels\n\n// Crop constraints\nexport const MIN_CROP_SIZE = 0.1; // Minimum crop size (10% of image)\n\n// Font options - imported from Google Fonts configuration\nexport const FONT_FAMILIES = getFontNames();\n\nexport const FONT_SIZES = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 64, 72, 96, 128];\n\n// Text layout\nexport const HORIZONTAL_PADDING = 0;\nexport const LINE_HEIGHT_MULTIPLIER = 1.2;\n\n// Transform constraints\nexport const MIN_WIDTH = 50;\nexport const MIN_FONT_SIZE = 8;\nexport const MAX_FONT_SIZE = 999;\n\n// Names that, even though they're allowed CSS values, we do NOT\n// want to load from Google Fonts. Used by the export worker and\n// FontAnalyzer as a deny-list. Kept for safety when legacy designs\n// reference these family names — we don't want to chase a 404 on\n// Google's CDN trying to download \"Impact\" from there. The font\n// browser doesn't expose any of these for selection (see\n// `TSHIRT_FONTS` in `fonts/google-fonts.ts`).\nexport const SYSTEM_FONTS = new Set([\n 'Arial', 'Arial Black', 'Verdana', 'Tahoma', 'Trebuchet MS',\n 'Impact', 'Times New Roman', 'Georgia', 'Garamond', 'Courier New',\n 'Brush Script MT', 'Palatino', 'Baskerville', 'Helvetica',\n 'Helvetica Neue', 'Comic Sans MS', 'Lucida Console', 'Monaco',\n 'Consolas', 'Courier',\n]);\n\n// Wave transform\nexport const WAVE_SKEW_FACTOR = 0.3;\nexport const WAVE_PATH_STEPS = 100;\n","/**\n * Centralized debug logger utility\n * Replaces scattered console.log statements with a consistent, filterable logging system\n */\n\nexport enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n NONE = 4,\n}\n\nclass Logger {\n private level: LogLevel = LogLevel.DEBUG;\n private enabled = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production'; // Only enabled in development by default\n\n /**\n * Set the minimum log level to display\n */\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n\n /**\n * Enable or disable logging globally\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /**\n * Get current log level\n */\n getLevel(): LogLevel {\n return this.level;\n }\n\n /**\n * Check if logging is enabled\n */\n isEnabled(): boolean {\n return this.enabled;\n }\n\n /**\n * Log debug message (verbose, development only)\n */\n debug(_message: string, ..._args: unknown[]): void {\n // Debug logging intentionally no-ops in production\n }\n\n /**\n * Log info message (general information)\n */\n info(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.INFO) {\n console.info(`[INFO] ${message}`, ...args);\n }\n }\n\n /**\n * Log warning message (potential issues)\n */\n warn(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.WARN) {\n console.warn(`[WARN] ${message}`, ...args);\n }\n }\n\n /**\n * Log error message (critical issues)\n */\n error(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.ERROR) {\n console.error(`[ERROR] ${message}`, ...args);\n }\n }\n\n /**\n * Create a scoped logger with a prefix for a specific module\n */\n scope(scopeName: string): ScopedLogger {\n return new ScopedLogger(this, scopeName);\n }\n}\n\n/**\n * Scoped logger for specific modules/components\n */\nclass ScopedLogger {\n constructor(private logger: Logger, private scopeName: string) {}\n\n debug(message: string, ...args: unknown[]): void {\n this.logger.debug(`[${this.scopeName}] ${message}`, ...args);\n }\n\n info(message: string, ...args: unknown[]): void {\n this.logger.info(`[${this.scopeName}] ${message}`, ...args);\n }\n\n warn(message: string, ...args: unknown[]): void {\n this.logger.warn(`[${this.scopeName}] ${message}`, ...args);\n }\n\n error(message: string, ...args: unknown[]): void {\n this.logger.error(`[${this.scopeName}] ${message}`, ...args);\n }\n}\n\n// Export singleton instance\nexport const logger = new Logger();\n\n// Export scoped logger factory\nexport const createLogger = (scopeName: string): ScopedLogger => {\n return logger.scope(scopeName);\n};\n\n// Usage examples:\n// import { logger } from '@/utils/logger';\n// logger.debug('Canvas rendered', { width, height });\n// logger.info('Element added', element);\n// logger.warn('Performance issue detected', duration);\n// logger.error('Failed to export', error);\n\n// Or create a scoped logger:\n// import { createLogger } from '@/utils/logger';\n// const log = createLogger('CanvasEditor');\n// log.debug('Resize started');\n","/**\n * ImageLoadEvents - Simple event system for notifying when images finish loading\n *\n * This solves the issue where the canvas shows \"Loading...\" even after an image\n * has loaded, because React doesn't know the element's internal state changed.\n *\n * When ImageElement finishes loading an image, it emits an event.\n * CanvasEditor subscribes to this event and triggers a re-render.\n */\n\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('ImageLoadEvents');\n\ntype ImageLoadListener = (elementId: string) => void;\n\nconst listeners = new Set<ImageLoadListener>();\n\n/**\n * Subscribe to image load events\n * @param listener - Callback called when any ImageElement finishes loading\n * @returns Unsubscribe function\n */\nexport function subscribeToImageLoads(listener: ImageLoadListener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/**\n * Emit an image load event\n * Called by ImageElement when an image finishes loading\n * @param elementId - The ID of the element that finished loading\n */\nexport function emitImageLoaded(elementId: string): void {\n listeners.forEach(listener => {\n try {\n listener(elementId);\n } catch (error) {\n logger.error('Listener error:', error);\n }\n });\n}\n","/**\n * Shared image cache for HTMLImageElement instances.\n * Prevents duplicate loading of the same URL across elements.\n * Reference-counted -- images are evicted when no elements reference them.\n *\n * Usage:\n * const cache = ImageCache.getInstance();\n * const img = cache.acquire('https://example.com/photo.jpg');\n * // ... use img ...\n * cache.release('https://example.com/photo.jpg');\n */\n\ninterface CacheEntry {\n image: HTMLImageElement;\n refCount: number;\n loaded: boolean;\n}\n\nexport class ImageCache {\n private static instance: ImageCache;\n private cache: Map<string, CacheEntry>;\n\n private constructor() {\n this.cache = new Map();\n }\n\n /**\n * Get the singleton ImageCache instance.\n */\n static getInstance(): ImageCache {\n if (!ImageCache.instance) {\n ImageCache.instance = new ImageCache();\n }\n return ImageCache.instance;\n }\n\n /**\n * Acquire an HTMLImageElement for the given URL.\n * If the URL is already cached, increments the reference count and returns the existing element.\n * If not cached, creates a new HTMLImageElement, starts loading, and caches it.\n *\n * @param url - The image URL to load\n * @returns The cached or newly created HTMLImageElement\n */\n acquire(url: string): HTMLImageElement {\n const existing = this.cache.get(url);\n if (existing) {\n existing.refCount++;\n return existing.image;\n }\n\n // Create new entry\n const img = new Image();\n img.crossOrigin = 'anonymous';\n\n const entry: CacheEntry = {\n image: img,\n refCount: 1,\n loaded: false,\n };\n\n img.onload = () => {\n entry.loaded = true;\n };\n\n img.onerror = () => {\n // Mark as loaded (with error) so callers can detect failure\n // The image element itself will have naturalWidth === 0\n entry.loaded = true;\n };\n\n this.cache.set(url, entry);\n\n // Don't attempt to load synthetic URIs (e.g. builtin-mask:splatter).\n // The caller is responsible for setting img.src to a real URL or data URL.\n if (!url.includes(':') || /^https?:\\/\\/|^data:|^blob:/.test(url)) {\n img.src = url;\n }\n\n return img;\n }\n\n /**\n * Release a reference to a cached image.\n * When the reference count reaches zero, the entry is evicted from the cache.\n *\n * @param url - The image URL to release\n */\n release(url: string): void {\n const entry = this.cache.get(url);\n if (!entry) return;\n\n entry.refCount--;\n if (entry.refCount <= 0) {\n this.cache.delete(url);\n }\n }\n\n /**\n * Get a cached HTMLImageElement without incrementing the reference count.\n * Returns undefined if the URL is not in the cache.\n *\n * @param url - The image URL to look up\n */\n get(url: string): HTMLImageElement | undefined {\n return this.cache.get(url)?.image;\n }\n\n /**\n * Check if a URL is in the cache.\n *\n * @param url - The image URL to check\n */\n has(url: string): boolean {\n return this.cache.has(url);\n }\n\n /**\n * Get cache statistics.\n *\n * @returns Object with entry count and total reference count across all entries\n */\n getStats(): { entries: number; totalRefs: number } {\n let totalRefs = 0;\n for (const entry of this.cache.values()) {\n totalRefs += entry.refCount;\n }\n return { entries: this.cache.size, totalRefs };\n }\n\n /**\n * Clear the entire cache.\n * Primarily for testing -- removes all entries regardless of reference count.\n */\n clear(): void {\n this.cache.clear();\n }\n}\n","/**\n * Image adjustments — MODEL-side helpers + the dependency-inversion seam to\n * the pixel pipeline (ADR-0085 / D9, same shape as `core/paint-registry.ts`).\n *\n * The element model (ImageElement, elementPatch, featureApplied) mounts with\n * `Editor.Root` in every composition — including the no-canvas widget — so its\n * static import graph must stay renderer-free (CI-gated by\n * scripts/check-import-graph.mjs). This module therefore holds only:\n *\n * - the pure value helpers (neutrality, normalization, cache keys), and\n * - a registry seam for the adjusted-source provider.\n *\n * The actual pixel pass (`src/rendering/image-adjustments.ts`) registers\n * itself here at module evaluation; it lives inside `Editor.Canvas`'s lazy\n * chunk via `rendering/element-painters.ts`, so by the time any\n * `ImageElement.render()` paints, the provider is present. A miss is an\n * internal wiring bug and throws with the fix in the message.\n */\n\nimport type { ImageAdjustments } from '../types/index.js';\n\n/** The neutral adjustments value — equal to omitting `adjustments` entirely. */\nexport const NEUTRAL_ADJUSTMENTS: Readonly<ImageAdjustments> = Object.freeze({\n brightness: 1,\n contrast: 1,\n saturation: 1,\n});\n\nconst EPSILON = 0.001;\n\n/** True when the adjustments are absent or all channels are ~1 (no-op). */\nexport function isNeutralAdjustments(adjustments: ImageAdjustments | undefined | null): boolean {\n if (!adjustments) return true;\n return (\n Math.abs(adjustments.brightness - 1) < EPSILON &&\n Math.abs(adjustments.contrast - 1) < EPSILON &&\n Math.abs(adjustments.saturation - 1) < EPSILON\n );\n}\n\n/**\n * Canonicalize an adjustments value: returns `undefined` for neutral input\n * (the stored neutral state is ABSENCE), otherwise a fresh copy with each\n * channel rounded to 2 decimals (so slider jitter maps to stable cache keys).\n */\nexport function normalizeAdjustments(\n adjustments: ImageAdjustments | undefined | null\n): ImageAdjustments | undefined {\n if (isNeutralAdjustments(adjustments)) return undefined;\n const round = (v: number) => Math.round(v * 100) / 100;\n return {\n brightness: round(adjustments!.brightness),\n contrast: round(adjustments!.contrast),\n saturation: round(adjustments!.saturation),\n };\n}\n\n/**\n * Stable cache key for an adjustments value. Neutral (or absent) input maps\n * to `'neutral'`; everything else encodes the three channels.\n */\nexport function adjustmentsCacheKey(adjustments: ImageAdjustments | undefined | null): string {\n if (isNeutralAdjustments(adjustments)) return 'neutral';\n const a = adjustments!;\n return `b${a.brightness}c${a.contrast}s${a.saturation}`;\n}\n\n// ---------------------------------------------------------------------------\n// Adjusted-source provider seam\n// ---------------------------------------------------------------------------\n\n/** Any drawable image source that exposes pixel dimensions. */\nexport type AdjustableImageSource = (HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap) & {\n width: number;\n height: number;\n};\n\n/** Produces a drawable source with the adjustments baked in (cached). */\nexport type AdjustedImageSourceProvider = (\n source: AdjustableImageSource,\n adjustments: ImageAdjustments\n) => AdjustableImageSource;\n\nlet adjustedImageSourceProvider: AdjustedImageSourceProvider | null = null;\n\n/** Heavy side only: called by `src/rendering/image-adjustments.ts` at module evaluation. */\nexport function registerAdjustedImageSourceProvider(provider: AdjustedImageSourceProvider): void {\n adjustedImageSourceProvider = provider;\n}\n\n/**\n * Get a drawable source with the adjustments baked in. Neutral adjustments\n * return the original source WITHOUT touching the provider (the no-canvas\n * widget never paints, so it never needs one). Non-neutral adjustments with\n * no registered provider are an internal wiring bug — throws with the fix.\n */\nexport function getAdjustedImageSource(\n source: AdjustableImageSource,\n adjustments: ImageAdjustments | undefined | null\n): AdjustableImageSource {\n if (isNeutralAdjustments(adjustments)) return source;\n if (!adjustedImageSourceProvider) {\n throw new Error(\n `[image-adjustments] no adjusted-source provider registered. Painting an image with ` +\n `non-neutral adjustments requires the rendering engine: import ` +\n `'src/rendering/image-adjustments.js' (rendering/element-painters does this for every ` +\n `paint driver; a direct element.render() call in a test must import it too).`\n );\n }\n return adjustedImageSourceProvider(source, adjustments!);\n}\n","/**\n * ImageElement - Element for displaying images\n * Images maintain aspect ratio and can be dragged, scaled, and rotated\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { Transform } from './Transform.js';\nimport { CORNER_HANDLE_HIT_RADIUS, EDGE_HANDLE_LENGTH, EDGE_HANDLE_HIT_WIDTH } from '../constants.js';\nimport { emitImageLoaded } from './ImageLoadEvents.js';\nimport { ImageCache } from './ImageCache.js';\nimport { createLogger } from '../utils/logger.js';\nimport { getAdjustedImageSource, normalizeAdjustments, type AdjustableImageSource } from './image-adjustments.js';\nimport type { ImageTransformData, ImageElementConfig, ResizeAnchor, BoundingBox, Point, TransformStartData } from '../types/index.js';\n\nconst logger = createLogger('ImageElement');\n\n/** Image loading state for retry logic and UI feedback */\nexport type ImageLoadState = 'loading' | 'loaded' | 'error' | 'retrying';\n\n/** Default timeout for image loading in milliseconds */\nconst IMAGE_LOAD_TIMEOUT_MS = 30000;\n\n/** Maximum number of retry attempts */\nconst IMAGE_LOAD_MAX_RETRIES = 3;\n\n/** Base delay for exponential backoff in milliseconds */\nconst IMAGE_LOAD_BASE_DELAY_MS = 1000;\n\n/** Helper to access transformData as ImageTransformData from TransformStartData */\nfunction imageTransformData(startData: TransformStartData): ImageTransformData {\n return startData.transformData as unknown as ImageTransformData;\n}\n\nexport class ImageElement extends BaseElement {\n declare transformType: 'image';\n declare transformData: ImageTransformData;\n imageUrl: string;\n imageLoaded: boolean;\n imageElement: HTMLImageElement | null;\n imageAspectRatio: number;\n isCropping: boolean;\n onLoadCallback: ((element: ImageElement) => void) | null;\n isSvg: boolean; // Track if this is an SVG image\n preserveDimensions: boolean; // If true, don't recalculate dimensions on image load\n imageLoadState: ImageLoadState; // Current image loading state\n imageLoadError: Error | null; // Error from last failed load attempt\n private svgBlobUrl: string | null = null; // Track blob URL for SVG high-DPI rendering cleanup\n private loadTimeoutId: ReturnType<typeof setTimeout> | null = null; // Timeout for current load attempt\n private retryAttempt: number = 0; // Current retry attempt number\n private cachedImageUrl: string | null = null; // Track URL acquired from ImageCache for release on destroy\n\n constructor(config: Partial<ImageElementConfig> = {}) {\n super(config);\n this.transformType = 'image';\n\n // Initialize transformData with proper ImageTransformData type\n this.transformData = {\n type: 'image',\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\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 // Brightness/contrast/saturation — neutral input ({1,1,1} or absent)\n // canonicalizes to ABSENCE so \"no adjustments\" has exactly one shape.\n const adjustments = normalizeAdjustments(config.transformData?.adjustments);\n if (adjustments) {\n this.transformData.adjustments = adjustments;\n }\n\n // Image-specific properties\n this.imageUrl = config.imageUrl || '';\n this.imageLoaded = false;\n this.imageElement = null;\n this.imageAspectRatio = config.imageAspectRatio || 1; // width / height\n this.isSvg = config.imageUrl?.toLowerCase().endsWith('.svg') || false;\n this.preserveDimensions = config.preserveDimensions ?? false;\n this.imageLoadState = config.imageUrl ? 'loading' : 'loaded';\n this.imageLoadError = null;\n\n // Crop mode state\n this.isCropping = false;\n\n // Callback for when image loads and dimensions change\n this.onLoadCallback = config.onLoadCallback || null;\n\n // Load the image\n if (this.imageUrl) {\n this.loadImage(this.imageUrl);\n }\n }\n\n /**\n * Load image from URL\n * For SVG files, we render to a high-DPI canvas to maintain quality\n */\n loadImage(url: string): void {\n const isSvg = url.toLowerCase().endsWith('.svg');\n this.isSvg = isSvg;\n\n if (isSvg) {\n // For SVG: Load at high resolution and convert to canvas\n this.loadSvgAsHighDpi(url);\n } else {\n // For regular images: Load normally\n this.loadRegularImage(url);\n }\n }\n\n /**\n * Load regular raster image (PNG, JPG, etc.) with timeout and retry logic.\n * Uses exponential backoff: 1s, 2s, 4s between retries.\n */\n private loadRegularImage(url: string): void {\n this.retryAttempt = 0;\n this.imageLoadError = null;\n this.imageLoadState = 'loading';\n this.attemptLoadRegularImage(url);\n }\n\n /**\n * Single attempt to load a regular image with timeout.\n * Called by loadRegularImage and retry logic.\n *\n * Uses the shared ImageCache to avoid duplicate loading of the same URL\n * across multiple ImageElement instances.\n */\n private attemptLoadRegularImage(url: string): void {\n // Clean up any previous timeout\n this.clearLoadTimeout();\n\n // Release previous cache entry if switching URLs\n this.releaseImageCache();\n\n const imageCache = ImageCache.getInstance();\n const img = imageCache.acquire(url);\n this.cachedImageUrl = url;\n\n let settled = false;\n\n const settle = () => {\n settled = true;\n this.clearLoadTimeout();\n };\n\n // If the image is already loaded (from cache), handle immediately\n if (img.complete && img.naturalWidth > 0) {\n settle();\n this.imageElement = img;\n this.imageLoaded = true;\n this.imageLoadState = 'loaded';\n this.imageLoadError = null;\n this.imageAspectRatio = img.naturalWidth / img.naturalHeight;\n\n if (!this.preserveDimensions) {\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n emitImageLoaded(this.id);\n return;\n }\n\n // Store original handlers in case the image already has them from the cache\n const originalOnload = img.onload;\n const originalOnerror = img.onerror;\n\n img.onload = (ev) => {\n if (settled) return;\n settle();\n\n this.imageElement = img;\n this.imageLoaded = true;\n this.imageLoadState = 'loaded';\n this.imageLoadError = null;\n this.imageAspectRatio = img.naturalWidth / img.naturalHeight;\n\n // Only recalculate dimensions if not explicitly provided (preserveDimensions flag)\n // This allows callers to provide explicit \"cover\" sizing that won't be overwritten\n if (!this.preserveDimensions) {\n // Update display height to maintain aspect ratio based on current width\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n // Notify parent that dimensions have changed\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n // Emit global event to trigger canvas re-render\n // This ensures the \"Loading...\" placeholder is replaced with the actual image\n emitImageLoaded(this.id);\n\n // Call original handler if present (for cache bookkeeping)\n if (originalOnload && originalOnload !== img.onload) {\n (originalOnload as EventListener)(ev);\n }\n };\n\n img.onerror = (ev) => {\n if (settled) return;\n settle();\n\n const error = new Error(`Failed to load image: ${url}`);\n this.handleLoadFailure(url, error);\n\n // Call original handler if present\n if (originalOnerror && originalOnerror !== img.onerror) {\n (originalOnerror as EventListener)(ev as Event);\n }\n };\n\n // Set timeout for this attempt\n this.loadTimeoutId = setTimeout(() => {\n if (settled) return;\n settle();\n\n // Abort the in-progress load by clearing the src\n img.src = '';\n const error = new Error(`Image load timed out after ${IMAGE_LOAD_TIMEOUT_MS}ms: ${url}`);\n this.handleLoadFailure(url, error);\n }, IMAGE_LOAD_TIMEOUT_MS);\n\n // If the image is newly created by the cache, src is already set.\n // If it's a retry on a previously failed image, we need to re-set src.\n if (!img.src || img.src !== url) {\n img.src = url;\n }\n\n // Re-check complete after attaching handlers — the image may have loaded\n // between the first check (line 146) and the handler attachment above.\n // This race is common with data URLs and cached images where the browser\n // fires onload synchronously during img.src assignment.\n if (!settled && img.complete && img.naturalWidth > 0) {\n img.onload?.(new Event('load'));\n }\n }\n\n /**\n * Handle a load failure - either retry with backoff or set error state.\n */\n private handleLoadFailure(url: string, error: Error): void {\n this.retryAttempt++;\n\n if (this.retryAttempt < IMAGE_LOAD_MAX_RETRIES) {\n // Exponential backoff: 1s, 2s, 4s, ...\n const delay = IMAGE_LOAD_BASE_DELAY_MS * Math.pow(2, this.retryAttempt - 1);\n logger.warn(`Image load attempt ${this.retryAttempt} failed, retrying in ${delay}ms:`, url);\n this.imageLoadState = 'retrying';\n this.imageLoadError = error;\n\n // Emit event so placeholder updates to show \"Retrying...\"\n emitImageLoaded(this.id);\n\n this.loadTimeoutId = setTimeout(() => {\n this.attemptLoadRegularImage(url);\n }, delay);\n } else {\n // All retries exhausted\n logger.error(`Image load failed after ${IMAGE_LOAD_MAX_RETRIES} attempts:`, url);\n this.imageLoaded = false;\n this.imageLoadState = 'error';\n this.imageLoadError = error;\n\n // Emit event so placeholder updates to show error state\n emitImageLoaded(this.id);\n }\n }\n\n /**\n * Clear any pending load timeout\n */\n private clearLoadTimeout(): void {\n if (this.loadTimeoutId !== null) {\n clearTimeout(this.loadTimeoutId);\n this.loadTimeoutId = null;\n }\n }\n\n /**\n * Release the current image from the shared cache.\n * Called when switching URLs or destroying the element.\n */\n private releaseImageCache(): void {\n if (this.cachedImageUrl) {\n ImageCache.getInstance().release(this.cachedImageUrl);\n this.cachedImageUrl = null;\n }\n }\n\n /**\n * Manually retry loading the image after a failure.\n * Resets retry count and starts fresh.\n */\n retryImageLoad(): void {\n if (!this.imageUrl) return;\n\n logger.info('Manual retry triggered for image:', this.imageUrl);\n this.clearLoadTimeout();\n this.imageLoaded = false;\n this.imageElement = null;\n\n if (this.isSvg) {\n this.imageLoadState = 'loading';\n this.imageLoadError = null;\n this.loadSvgAsHighDpi(this.imageUrl);\n } else {\n this.loadRegularImage(this.imageUrl);\n }\n }\n\n /**\n * Revoke any existing SVG blob URL to prevent memory leaks\n * iOS Safari has strict blob URL limits; this prevents accumulation\n */\n private revokeSvgBlobUrl(): void {\n if (this.svgBlobUrl) {\n URL.revokeObjectURL(this.svgBlobUrl);\n this.svgBlobUrl = null;\n }\n }\n\n /**\n * Load SVG and convert to high-DPI canvas for crisp rendering\n * SVG is rendered at 4x resolution to maintain quality when scaled\n */\n private loadSvgAsHighDpi(url: string): void {\n // Revoke any existing SVG blob URL before creating a new one\n this.revokeSvgBlobUrl();\n const tempImg = new Image();\n tempImg.crossOrigin = 'anonymous';\n\n tempImg.onload = () => {\n // Render SVG to a high-DPI canvas (4x resolution)\n const scale = 4; // 4x resolution for crisp quality\n const canvas = document.createElement('canvas');\n canvas.width = tempImg.width * scale;\n canvas.height = tempImg.height * scale;\n\n const ctx = canvas.getContext('2d')!;\n ctx.scale(scale, scale);\n ctx.drawImage(tempImg, 0, 0);\n\n // Convert canvas to blob and create object URL\n canvas.toBlob((blob) => {\n if (!blob) {\n logger.error('Failed to convert SVG to blob');\n this.imageLoaded = false;\n return;\n }\n\n // Store blob URL for later cleanup (iOS Safari memory management)\n this.svgBlobUrl = URL.createObjectURL(blob);\n\n // Now load the high-DPI version\n this.imageElement = new Image();\n this.imageElement.crossOrigin = 'anonymous';\n this.imageElement.onload = () => {\n this.imageLoaded = true;\n this.imageAspectRatio = this.imageElement!.width / this.imageElement!.height;\n\n // Only recalculate dimensions if not explicitly provided (preserveDimensions flag)\n if (!this.preserveDimensions) {\n // Update display height to maintain aspect ratio\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n // Notify parent\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n // Emit global event to trigger canvas re-render\n // This ensures the \"Loading...\" placeholder is replaced with the actual image\n emitImageLoaded(this.id);\n\n // Note: We no longer revoke the blob URL here via setTimeout\n // Instead, it's tracked in this.svgBlobUrl and cleaned up via destroy()\n };\n\n this.imageElement.src = this.svgBlobUrl;\n }, 'image/png');\n };\n\n tempImg.onerror = () => {\n logger.error('Failed to load SVG:', url);\n this.imageLoaded = false;\n };\n\n tempImg.src = url;\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n * When not in crop mode, returns only the cropped area dimensions\n */\n getBoundingBox(): BoundingBox {\n if (this.isCropping) {\n // In crop mode: Need to show the full image frame\n // this.x, this.y is at the center of the visible crop area\n // We need to calculate where the full frame would be, accounting for rotation and flips\n\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Offset from crop center to frame center in local space (before rotation and flip)\n let localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n let localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n // Account for flips: when flipped, the offset direction is reversed\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localOffsetX *= flipH;\n localOffsetY *= flipV;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n const worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n const worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n\n // Calculate frame center in world space\n const frameCenterX = this.x + worldOffsetX;\n const frameCenterY = this.y + worldOffsetY;\n\n return {\n x: frameCenterX - this.transformData.width / 2,\n y: frameCenterY - this.transformData.height / 2,\n width: this.transformData.width,\n height: this.transformData.height,\n };\n } else {\n // Not in crop mode: only the cropped area\n // this.x, this.y is already at the center of the cropped area\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n return {\n x: this.x - cropWidth / 2,\n y: this.y - cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n }\n\n /**\n * Get visual bounding box - returns the cropped area bounds\n * This is used for group bounds calculation and should represent the visible area\n */\n getVisualBoundingBox(): BoundingBox {\n // Always return the cropped area, even during crop mode\n // This ensures group bounds update correctly while cropping\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n return {\n x: this.x - cropWidth / 2,\n y: this.y - cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n\n /**\n * Get rotation anchor (center of the visible image area)\n * In crop mode: returns the center of the full frame\n * Not in crop mode: returns this.x, this.y (center of cropped area)\n */\n getRotationAnchor(): Point {\n if (this.isCropping) {\n // In crop mode: return the center of the full frame\n // this.x, this.y is at the crop center, so we need to calculate the frame center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Offset from crop center to frame center in local space (before rotation and flip)\n let localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n let localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n // Account for flips: when flipped, the offset direction is reversed\n // This is because flip inverts the coordinate system\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localOffsetX *= flipH;\n localOffsetY *= flipV;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n const worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n const worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n\n // Calculate frame center in world space\n return {\n x: this.x + worldOffsetX,\n y: this.y + worldOffsetY,\n };\n } else {\n // Not in crop mode: this.x, this.y is already at the center of the visible area\n return {\n x: this.x,\n y: this.y,\n };\n }\n }\n\n /**\n * Hit test for image - checks if point is inside the visible (cropped) area\n */\n hitTest(worldX: number, worldY: number): boolean {\n // Convert to local coordinates (relative to this.x, this.y)\n const local = this.worldToLocal(worldX, worldY);\n\n if (this.isCropping) {\n // In crop mode: hit test against full image frame\n // Calculate where the full frame is relative to the crop center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n const localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n const halfWidth = this.transformData.width / 2;\n const halfHeight = this.transformData.height / 2;\n\n return (\n local.x >= localOffsetX - halfWidth &&\n local.x <= localOffsetX + halfWidth &&\n local.y >= localOffsetY - halfHeight &&\n local.y <= localOffsetY + halfHeight\n );\n } else {\n // Not in crop mode: hit test against cropped area only\n // The crop is centered at this.x, this.y, so just check the crop size\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n const halfCropWidth = cropWidth / 2;\n const halfCropHeight = cropHeight / 2;\n\n return (\n local.x >= -halfCropWidth && local.x <= halfCropWidth && local.y >= -halfCropHeight && local.y <= halfCropHeight\n );\n }\n }\n\n /**\n * Render the image\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n if (!this.imageLoaded || !this.imageElement) {\n // Show placeholder while loading\n this.renderPlaceholder(ctx);\n return;\n }\n\n // Brightness/contrast/saturation are baked into a cached copy of the\n // SOURCE bitmap (one pixel pass per adjustments value, then pure cache\n // hits) so crop / flip / border-radius below composite the adjusted\n // pixels — identical order to the export worker's renderImageElement.\n const source = getAdjustedImageSource(\n this.imageElement as AdjustableImageSource,\n this.transformData.adjustments\n );\n const sourceWidth = source.width;\n const sourceHeight = source.height;\n\n ctx.save();\n\n // Apply element opacity\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Translate to the visible area center and rotate\n ctx.translate(this.x, this.y);\n // Negate rotation for clockwise-negative convention\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Apply flip transformations\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n ctx.scale(flipH, flipV);\n\n if (this.isCropping) {\n // In crop mode: render the full image frame\n // Calculate where the frame is relative to the crop center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n const localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n const x = localOffsetX - this.transformData.width / 2;\n const y = localOffsetY - this.transformData.height / 2;\n\n // Apply border radius if set\n const borderRadius = this.transformData.borderRadius || 0;\n if (borderRadius > 0) {\n ctx.save();\n const radius = Math.min(\n (borderRadius / 100) * Math.min(this.transformData.width, this.transformData.height),\n this.transformData.width / 2,\n this.transformData.height / 2\n );\n ctx.beginPath();\n ctx.roundRect(x, y, this.transformData.width, this.transformData.height, radius);\n ctx.clip();\n }\n\n // Draw the full image\n ctx.drawImage(\n source,\n 0,\n 0,\n sourceWidth,\n sourceHeight,\n x,\n y,\n this.transformData.width,\n this.transformData.height\n );\n\n if (borderRadius > 0) {\n ctx.restore();\n }\n } else if (this.masks && this.masks.length > 0) {\n // Has masks: render the full image without crop clipping.\n // The mask shape defines the visible area instead of the crop rectangle.\n // Crop values (cropX/Y) still offset the image within the mask.\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Calculate where the full image should be drawn so the crop offset\n // positions the image content within the mask shape\n const fullImageX = x - this.transformData.cropX * this.transformData.width;\n const fullImageY = y - this.transformData.cropY * this.transformData.height;\n\n ctx.drawImage(\n source,\n 0,\n 0,\n sourceWidth,\n sourceHeight,\n fullImageX,\n fullImageY,\n this.transformData.width,\n this.transformData.height\n );\n } else {\n // Not in crop mode: render only the cropped area\n // The visible crop is centered at the origin\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Apply border radius clipping\n const borderRadius = this.transformData.borderRadius || 0;\n ctx.save();\n const radius = Math.min((borderRadius / 100) * Math.min(cropWidth, cropHeight), cropWidth / 2, cropHeight / 2);\n ctx.beginPath();\n ctx.roundRect(x, y, cropWidth, cropHeight, radius);\n ctx.clip();\n\n // Draw the full image, but only the cropped area is visible due to clipping\n // Calculate where the full image should be drawn so the crop appears at the origin\n const fullImageX = x - this.transformData.cropX * this.transformData.width;\n const fullImageY = y - this.transformData.cropY * this.transformData.height;\n\n ctx.drawImage(\n source,\n 0,\n 0,\n sourceWidth,\n sourceHeight,\n fullImageX,\n fullImageY,\n this.transformData.width,\n this.transformData.height\n );\n\n ctx.restore();\n }\n\n ctx.restore();\n }\n\n /**\n * Render placeholder based on current image load state.\n * Shows different visuals for loading, retrying, error, and no-image states.\n */\n renderPlaceholder(ctx: CanvasRenderingContext2D): void {\n ctx.save();\n\n // Translate to the visible area center and rotate\n ctx.translate(this.x, this.y);\n // Negate rotation for clockwise-negative convention\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // For placeholder, just show the crop size centered\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Choose background and message based on load state\n let bgColor: string;\n let textColor: string;\n let message: string;\n\n if (this.imageLoadState === 'error') {\n bgColor = '#f0e0e0'; // Light red/pink tint for error\n textColor = '#993333';\n message = 'Image failed to load';\n } else if (this.imageLoadState === 'retrying') {\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = 'Retrying...';\n } else if (this.imageLoadState === 'loading') {\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = 'Loading...';\n } else {\n // No URL set or other state\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = this.imageUrl ? 'Loading...' : 'No image';\n }\n\n // Draw background rectangle\n ctx.fillStyle = bgColor;\n ctx.fillRect(x, y, cropWidth, cropHeight);\n\n // Draw placeholder text at center\n ctx.fillStyle = textColor;\n ctx.font = '14px system-ui, -apple-system, sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(message, 0, 0);\n\n ctx.restore();\n }\n\n /**\n * Handle resize - maintain aspect ratio\n * @returns {boolean} true if resize was applied\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n // Calculate the proposed scale from startData\n // In crop mode: scale relative to frame dimensions\n // Not in crop mode: scale relative to visible (cropped) dimensions\n let widthScale, heightScale;\n if (this.isCropping) {\n // In crop mode: newWidth/newHeight are based on frame bounding box\n widthScale = newWidth / imageTransformData(startData).width;\n heightScale = newHeight / imageTransformData(startData).height;\n } else {\n // Not in crop mode: newWidth/newHeight are based on cropped area bounding box\n // We need to scale relative to the visible crop dimensions\n widthScale = newWidth / (startData.width ?? 1);\n heightScale = newHeight / (startData.height ?? 1);\n }\n const proposedScale = (widthScale + heightScale) / 2;\n\n // Remap anchor when flipped\n let effectiveAnchor = anchor;\n if (this.isCropping) {\n effectiveAnchor = this.remapAnchorForFlips(anchor, startData);\n }\n\n // Apply crop constraints if in crop mode\n let finalScale = proposedScale;\n if (this.isCropping) {\n const currentScaleRelativeToStart = this.transformData.width / imageTransformData(startData).width;\n\n // Only apply constraint when trying to shrink\n if (proposedScale < currentScaleRelativeToStart) {\n finalScale = this.calculateMinScaleForCropBounds(\n effectiveAnchor,\n startData,\n proposedScale,\n currentScaleRelativeToStart\n );\n }\n }\n\n // Apply scale to both dimensions while maintaining aspect ratio\n this.transformData.width = imageTransformData(startData).width * finalScale;\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // For crop mode, we need to get the actual frame bounds, not the crop bounds\n // startData.x, startData.y is the crop center in crop mode, frame center otherwise\n // We need to calculate the frame center and corners\n\n let frameCenterX, frameCenterY;\n\n if (this.isCropping) {\n // Calculate where the frame center was in world coordinates\n // Crop center in frame-local coordinates (frame center is at 0,0 in local coords)\n // Frame spans from (-width/2, -height/2) to (width/2, height/2)\n // Crop spans from (cropX * width - width/2, cropY * height - height/2) to (...)\n const oldCropCenterXLocal =\n (imageTransformData(startData).cropX + imageTransformData(startData).cropWidth / 2) * imageTransformData(startData).width -\n imageTransformData(startData).width / 2;\n const oldCropCenterYLocal =\n (imageTransformData(startData).cropY + imageTransformData(startData).cropHeight / 2) * imageTransformData(startData).height -\n imageTransformData(startData).height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const localOffsetX = oldCropCenterXLocal;\n const localOffsetY = oldCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n // NOTE: No flip transformation needed here!\n // The local offsets are stored in unflipped coordinate space,\n // and this.x/this.y are also in unflipped world space.\n // The flip only affects rendering, not the coordinate calculations.\n const startTransform = Transform.fromSnapshot(startData);\n const worldOffset = startTransform.localDeltaToWorld(localOffsetX, localOffsetY);\n const worldOffsetX = worldOffset.dx;\n const worldOffsetY = worldOffset.dy;\n\n // startData.x, startData.y is crop center, so frame center is:\n frameCenterX = startData.x - worldOffsetX;\n frameCenterY = startData.y - worldOffsetY;\n } else {\n // Not in crop mode: startData.x, startData.y is the crop center (visual center)\n // We need to calculate the frame center from the crop center\n const oldCropCenterXLocal =\n (imageTransformData(startData).cropX + imageTransformData(startData).cropWidth / 2) * imageTransformData(startData).width -\n imageTransformData(startData).width / 2;\n const oldCropCenterYLocal =\n (imageTransformData(startData).cropY + imageTransformData(startData).cropHeight / 2) * imageTransformData(startData).height -\n imageTransformData(startData).height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const localOffsetX = oldCropCenterXLocal;\n const localOffsetY = oldCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n const startTransform = Transform.fromSnapshot(startData);\n const worldOffset = startTransform.localDeltaToWorld(localOffsetX, localOffsetY);\n const worldOffsetX = worldOffset.dx;\n const worldOffsetY = worldOffset.dy;\n\n // startData.x, startData.y is crop center, so frame center is:\n frameCenterX = startData.x - worldOffsetX;\n frameCenterY = startData.y - worldOffsetY;\n }\n\n // Calculate the FIXED corner point in world coordinates at start\n // In crop mode, the fixed corner is the OPPOSITE of the dragged anchor\n // Use effectiveAnchor (which accounts for flips) instead of anchor\n let fixedCornerWorldX, fixedCornerWorldY;\n\n // Fixed corner positions in local frame coordinates (unrotated, centered at frame center)\n // These are the OPPOSITE corners from the anchor being dragged\n let fixedCornerLocalX = 0;\n let fixedCornerLocalY = 0;\n\n if (effectiveAnchor === 'top-left') {\n // Dragging top-left, so fix bottom-right\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'top-right') {\n // Dragging top-right, so fix bottom-left\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'bottom-left') {\n // Dragging bottom-left, so fix top-right\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'bottom-right') {\n // Dragging bottom-right, so fix top-left\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'middle-left') {\n // Dragging left edge, so fix right edge\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-right') {\n // Dragging right edge, so fix left edge\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-top') {\n // Dragging top edge, so fix bottom edge\n fixedCornerLocalX = 0;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'middle-bottom') {\n // Dragging bottom edge, so fix top edge\n fixedCornerLocalX = 0;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n }\n\n // Rotate fixed corner to world coordinates (forward transform: local → world)\n // NOTE: No flip transformation needed!\n // The fixed corner positions are in unflipped local space,\n // and we convert them directly to unflipped world space.\n const startTransformForCorner = Transform.fromSnapshot(startData);\n const fixedCornerWorldOffset = startTransformForCorner.localDeltaToWorld(fixedCornerLocalX, fixedCornerLocalY);\n const fixedCornerWorldOffsetX = fixedCornerWorldOffset.dx;\n const fixedCornerWorldOffsetY = fixedCornerWorldOffset.dy;\n\n fixedCornerWorldX = frameCenterX + fixedCornerWorldOffsetX;\n fixedCornerWorldY = frameCenterY + fixedCornerWorldOffsetY;\n\n // Calculate new frame center position\n // The fixed corner (opposite of the dragged anchor) should stay at fixedCornerWorldX/Y\n // We need to find where the frame center should be so that the fixed corner ends up there\n\n // Calculate where the fixed corner will be in the NEW frame's local coordinates\n // (same relative position as before, but with new dimensions)\n let newFixedCornerLocalX = 0;\n let newFixedCornerLocalY = 0;\n\n if (effectiveAnchor === 'top-left') {\n // Fixed corner is bottom-right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'top-right') {\n // Fixed corner is bottom-left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'bottom-left') {\n // Fixed corner is top-right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = -this.transformData.height / 2;\n } else if (effectiveAnchor === 'bottom-right') {\n // Fixed corner is top-left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = -this.transformData.height / 2;\n } else if (effectiveAnchor === 'middle-left') {\n // Fixed edge is right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-right') {\n // Fixed edge is left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-top') {\n // Fixed edge is bottom\n newFixedCornerLocalX = 0;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'middle-bottom') {\n // Fixed edge is top\n newFixedCornerLocalX = 0;\n newFixedCornerLocalY = -this.transformData.height / 2;\n }\n\n // Rotate the new fixed corner position to world coordinates (forward transform: local → world)\n // NOTE: No flip transformation needed!\n const newTransform = new Transform(this);\n const newFixedCornerWorldOffset = newTransform.localDeltaToWorld(newFixedCornerLocalX, newFixedCornerLocalY);\n const newFixedCornerWorldOffsetX = newFixedCornerWorldOffset.dx;\n const newFixedCornerWorldOffsetY = newFixedCornerWorldOffset.dy;\n\n // Calculate new frame center: the fixed corner stays at the same world position\n // fixedCornerWorld = frameCenter + fixedCornerWorldOffset\n // Therefore: frameCenter = fixedCornerWorld - fixedCornerWorldOffset\n const newFrameCenterX = fixedCornerWorldX - newFixedCornerWorldOffsetX;\n const newFrameCenterY = fixedCornerWorldY - newFixedCornerWorldOffsetY;\n\n // In crop mode, we need to keep the crop zone at the same world size and position\n if (this.isCropping) {\n // The crop zone should stay the same size AND position in world coordinates\n // Calculate the old crop center in world coordinates\n const oldCropCenterWorldX = startData.x;\n const oldCropCenterWorldY = startData.y;\n\n // Calculate the old crop size in world coordinates\n const oldCropWorldWidth = imageTransformData(startData).cropWidth * imageTransformData(startData).width;\n const oldCropWorldHeight = imageTransformData(startData).cropHeight * imageTransformData(startData).height;\n\n // The crop center stays at the same world position\n this.x = oldCropCenterWorldX;\n this.y = oldCropCenterWorldY;\n\n // Calculate new normalized crop dimensions (fixed world size / new frame size)\n const newCropWidthNorm = oldCropWorldWidth / this.transformData.width;\n const newCropHeightNorm = oldCropWorldHeight / this.transformData.height;\n\n // Now figure out where the crop is positioned within the new frame\n // Get the offset from new frame center to crop center in world coords\n const worldOffsetX = oldCropCenterWorldX - newFrameCenterX;\n const worldOffsetY = oldCropCenterWorldY - newFrameCenterY;\n\n // Convert to local coords (relative to the new frame) - inverse transform: world → local\n const newFrameTransform = new Transform(this);\n const localOffset = newFrameTransform.worldDeltaToLocal(worldOffsetX, worldOffsetY);\n const localOffsetX = localOffset.dx;\n const localOffsetY = localOffset.dy;\n\n // The crop center in local frame coordinates (from frame top-left)\n const cropCenterXLocal = this.transformData.width / 2 + localOffsetX;\n const cropCenterYLocal = this.transformData.height / 2 + localOffsetY;\n\n // Convert to normalized coordinates (0-1) and get top-left corner\n const newCropX = cropCenterXLocal / this.transformData.width - newCropWidthNorm / 2;\n const newCropY = cropCenterYLocal / this.transformData.height - newCropHeightNorm / 2;\n\n // Update crop parameters\n // No clamping needed - we already checked bounds and reverted if out of bounds\n this.transformData.cropX = newCropX;\n this.transformData.cropY = newCropY;\n this.transformData.cropWidth = newCropWidthNorm;\n this.transformData.cropHeight = newCropHeightNorm;\n } else {\n // Not in crop mode: this.x, this.y should be at the crop center (visual center)\n // Calculate the new crop center from the new frame center\n\n // Calculate crop center position in the NEW frame's local coordinates\n const newCropCenterXLocal =\n (this.transformData.cropX + this.transformData.cropWidth / 2) * this.transformData.width -\n this.transformData.width / 2;\n const newCropCenterYLocal =\n (this.transformData.cropY + this.transformData.cropHeight / 2) * this.transformData.height -\n this.transformData.height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const newLocalOffsetX = newCropCenterXLocal;\n const newLocalOffsetY = newCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n const newTransform = new Transform(this);\n const newWorldOffset = newTransform.localDeltaToWorld(newLocalOffsetX, newLocalOffsetY);\n const newWorldOffsetX = newWorldOffset.dx;\n const newWorldOffsetY = newWorldOffset.dy;\n\n // Crop center = frame center + offset\n this.x = newFrameCenterX + newWorldOffsetX;\n this.y = newFrameCenterY + newWorldOffsetY;\n }\n\n return true; // Resize was applied successfully\n }\n\n /**\n * Get enabled anchors - only corner handles for images\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Enter crop mode\n * Just sets the flag - position stays at the cropped area center\n */\n enterCropMode(): void {\n this.isCropping = true;\n }\n\n /**\n * Exit crop mode\n * Just clears the flag - position stays the same\n */\n exitCropMode(): void {\n this.isCropping = false;\n }\n\n /**\n * Get crop box bounds in local coordinates (before rotation)\n * Returns the rectangle where the crop box should be rendered\n * Local coordinates are relative to this.x, this.y (the center of the cropped area)\n */\n getCropBoxBounds(): BoundingBox | null {\n if (!this.isCropping) return null;\n\n // In crop mode, this.x/this.y is at the center of the cropped area (what's visible)\n // The crop box represents this visible area, so it's always centered at the origin\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Since this.x, this.y is the crop center, the crop box is centered at origin in local coords\n return {\n x: -cropWidth / 2,\n y: -cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n\n /**\n * Get crop box corners in world coordinates (accounting for rotation)\n * Returns array of {x, y} points for each corner\n */\n getCropBoxWorldCorners(): Point[] | null {\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return null;\n\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n\n // Define corners in local space\n const corners = [\n { x: cropBox.x, y: cropBox.y }, // top-left\n { x: cropBox.x + cropBox.width, y: cropBox.y }, // top-right\n { x: cropBox.x, y: cropBox.y + cropBox.height }, // bottom-left\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height }, // bottom-right\n ];\n\n // Transform to world coordinates\n return corners.map((corner) => ({\n x: this.x + corner.x * cos - corner.y * sin,\n y: this.y + corner.x * sin + corner.y * cos,\n }));\n }\n\n /**\n * Convert world coordinates to local coordinates (accounting for rotation and flips)\n * Local coordinates are relative to this.x, this.y (the center of the visible area)\n */\n worldToLocal(worldX: number, worldY: number): Point {\n // Translate relative to the visible area center\n const dx = worldX - this.x;\n const dy = worldY - this.y;\n\n // Rotate back (inverse rotation) - positive to undo the negative forward rotation\n const cos = Math.cos(RotationUtils.toRadiansInverse(this.rotation));\n const sin = Math.sin(RotationUtils.toRadiansInverse(this.rotation));\n\n let localX = dx * cos - dy * sin;\n let localY = dx * sin + dy * cos;\n\n // Apply inverse flip transformation\n // Since flip is a scale by ±1, the inverse is the same operation\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localX *= flipH;\n localY *= flipV;\n\n return {\n x: localX,\n y: localY,\n };\n }\n\n /**\n * Convert local coordinates to world coordinates (accounting for rotation and flips)\n * Inverse of worldToLocal\n * Canvas transformations are applied in reverse: scale -> rotate -> translate\n */\n localToWorld(localX: number, localY: number): Point {\n // Apply flip transformation first (ctx.scale is applied first to points)\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n let flippedX = localX * flipH;\n let flippedY = localY * flipV;\n\n // Then rotate (ctx.rotate is applied second)\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n\n const rotatedX = flippedX * cos - flippedY * sin;\n const rotatedY = flippedX * sin + flippedY * cos;\n\n // Finally translate to world position (ctx.translate is applied last)\n return {\n x: this.x + rotatedX,\n y: this.y + rotatedY,\n };\n }\n\n /**\n * Hit test for crop box handles in world coordinates\n * Returns {type: 'corner'|'edge', anchor: string} if hit, null otherwise\n * @param worldX - X coordinate in world space\n * @param worldY - Y coordinate in world space\n * @param zoom - Current zoom level (used to scale hit areas for consistent interaction)\n */\n hitTestCropHandle(\n worldX: number,\n worldY: number,\n zoom: number = 1.0\n ): { type: 'corner' | 'edge'; anchor: string; worldX: number; worldY: number } | null {\n if (!this.isCropping) return null;\n\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return null;\n\n // Convert world coordinates to local\n const local = this.worldToLocal(worldX, worldY);\n\n // Scale hit areas by 1/zoom to maintain consistent screen-space hit targets\n // When zoomed out (zoom < 1), hit areas need to be larger in world space\n const hitScale = 1 / zoom;\n\n // Crop handle constants - scaled for zoom\n const CORNER_HIT_RADIUS_SCALED = CORNER_HANDLE_HIT_RADIUS * hitScale;\n const EDGE_LENGTH_SCALED = EDGE_HANDLE_LENGTH * hitScale;\n const EDGE_WIDTH_SCALED = EDGE_HANDLE_HIT_WIDTH * hitScale; // Width perpendicular to edge\n\n // Test corner handles first (higher priority)\n // Circular hit zones for corners\n const corners = [\n { x: cropBox.x, y: cropBox.y, anchor: 'top-left' },\n { x: cropBox.x + cropBox.width, y: cropBox.y, anchor: 'top-right' },\n { x: cropBox.x, y: cropBox.y + cropBox.height, anchor: 'bottom-left' },\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height, anchor: 'bottom-right' },\n ];\n\n for (const corner of corners) {\n const dx = local.x - corner.x;\n const dy = local.y - corner.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n // Circular hit zone\n if (distance <= CORNER_HIT_RADIUS_SCALED) {\n const worldCorner = this.localToWorld(corner.x, corner.y);\n return { type: 'corner', anchor: corner.anchor, worldX: worldCorner.x, worldY: worldCorner.y };\n }\n }\n\n // Test edge handles (rectangles - wide for top/bottom, tall for left/right)\n const edges = [\n { x: cropBox.x + cropBox.width / 2, y: cropBox.y, anchor: 'top', orientation: 'horizontal' },\n { x: cropBox.x + cropBox.width / 2, y: cropBox.y + cropBox.height, anchor: 'bottom', orientation: 'horizontal' },\n { x: cropBox.x, y: cropBox.y + cropBox.height / 2, anchor: 'left', orientation: 'vertical' },\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height / 2, anchor: 'right', orientation: 'vertical' },\n ];\n\n for (const edge of edges) {\n const dx = local.x - edge.x;\n const dy = local.y - edge.y;\n\n // Check if within the rectangular bounds\n // Use large hit zones for comfortable touch/mouse interaction\n let halfWidth, halfHeight;\n if (edge.orientation === 'horizontal') {\n // Top/bottom: wide along edge, uses EDGE_WIDTH for perpendicular hit zone\n halfWidth = EDGE_LENGTH_SCALED / 2;\n halfHeight = EDGE_WIDTH_SCALED / 2;\n } else {\n // Left/right: tall along edge, uses EDGE_WIDTH for perpendicular hit zone\n halfWidth = EDGE_WIDTH_SCALED / 2;\n halfHeight = EDGE_LENGTH_SCALED / 2;\n }\n\n if (Math.abs(dx) <= halfWidth && Math.abs(dy) <= halfHeight) {\n // Convert edge position from local to world coordinates to determine screen position\n const worldEdge = this.localToWorld(edge.x, edge.y);\n return { type: 'edge', anchor: edge.anchor, worldX: worldEdge.x, worldY: worldEdge.y };\n }\n }\n\n return null;\n }\n\n /**\n * Hit test for inside crop box in world coordinates\n * Used for dragging the image within the crop area\n */\n hitTestCropBox(worldX: number, worldY: number): boolean {\n if (!this.isCropping) return false;\n\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return false;\n\n // Convert world coordinates to local\n const local = this.worldToLocal(worldX, worldY);\n\n return (\n local.x >= cropBox.x &&\n local.x <= cropBox.x + cropBox.width &&\n local.y >= cropBox.y &&\n local.y <= cropBox.y + cropBox.height\n );\n }\n\n /**\n * Update crop box (normalized 0-1 coordinates)\n * @param {number} cropX - Normalized X position (0-1)\n * @param {number} cropY - Normalized Y position (0-1)\n * @param {number} cropWidth - Normalized width (0-1)\n * @param {number} cropHeight - Normalized height (0-1)\n * @param {boolean} adjustPosition - Whether to adjust this.x, this.y to keep visual position stable (default: false)\n */\n updateCrop(\n cropX: number,\n cropY: number,\n cropWidth: number,\n cropHeight: number,\n adjustPosition: boolean = false\n ): void {\n // Ensure minimum crop size\n const minSize = 0.1;\n\n // Clamp width and height first (ensure they're valid sizes)\n let clampedWidth = Math.max(minSize, Math.min(1, cropWidth));\n let clampedHeight = Math.max(minSize, Math.min(1, cropHeight));\n\n // Then clamp position so crop box stays within 0-1 bounds\n // If position + size > 1, adjust position to keep right/bottom edge at 1\n let clampedX = Math.max(0, Math.min(1 - clampedWidth, cropX));\n let clampedY = Math.max(0, Math.min(1 - clampedHeight, cropY));\n\n // Final safety check: if the box is still outside bounds, reduce size\n if (clampedX + clampedWidth > 1) {\n clampedWidth = 1 - clampedX;\n }\n if (clampedY + clampedHeight > 1) {\n clampedHeight = 1 - clampedY;\n }\n\n // If adjustPosition is true, calculate offset to keep visual position stable\n let worldOffsetX = 0;\n let worldOffsetY = 0;\n\n if (adjustPosition) {\n // Calculate the old crop center in local coordinates\n const oldCropCenterX =\n this.transformData.cropX * this.transformData.width +\n (this.transformData.cropWidth * this.transformData.width) / 2;\n const oldCropCenterY =\n this.transformData.cropY * this.transformData.height +\n (this.transformData.cropHeight * this.transformData.height) / 2;\n\n // Calculate the new crop center in local coordinates\n const newCropCenterX = clampedX * this.transformData.width + (clampedWidth * this.transformData.width) / 2;\n const newCropCenterY = clampedY * this.transformData.height + (clampedHeight * this.transformData.height) / 2;\n\n // Calculate the offset between old and new crop centers in local space\n const localOffsetX = newCropCenterX - oldCropCenterX;\n const localOffsetY = newCropCenterY - oldCropCenterY;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n }\n\n // Update the crop parameters\n this.transformData.cropX = clampedX;\n this.transformData.cropY = clampedY;\n this.transformData.cropWidth = clampedWidth;\n this.transformData.cropHeight = clampedHeight;\n\n // Adjust this.x, this.y if requested\n if (adjustPosition) {\n this.x += worldOffsetX;\n this.y += worldOffsetY;\n }\n }\n\n /**\n * Get the local position of the corner opposite to the given anchor\n * Corners are relative to frame center\n */\n private getOppositeCornerLocalPosition(\n anchor: ResizeAnchor,\n width: number,\n height: number\n ): { x: number; y: number } {\n const halfW = width / 2;\n const halfH = height / 2;\n\n const oppositeCorners: Record<ResizeAnchor, { x: number; y: number }> = {\n 'top-left': { x: halfW, y: halfH }, // Opposite is bottom-right\n 'top-right': { x: -halfW, y: halfH }, // Opposite is bottom-left\n 'bottom-left': { x: halfW, y: -halfH }, // Opposite is top-right\n 'bottom-right': { x: -halfW, y: -halfH }, // Opposite is top-left\n top: { x: 0, y: halfH }, // Opposite is bottom edge\n bottom: { x: 0, y: -halfH }, // Opposite is top edge\n left: { x: halfW, y: 0 }, // Opposite is right edge\n right: { x: -halfW, y: 0 }, // Opposite is left edge\n 'middle-top': { x: 0, y: halfH }, // Opposite is bottom edge\n 'middle-bottom': { x: 0, y: -halfH }, // Opposite is top edge\n 'middle-left': { x: halfW, y: 0 }, // Opposite is right edge\n 'middle-right': { x: -halfW, y: 0 }, // Opposite is left edge\n };\n\n return oppositeCorners[anchor];\n }\n\n /**\n * Calculate frame center position from crop center position\n * In crop mode, this.x/y is the crop center, not the frame center\n */\n private calculateFrameCenterFromCropCenter(startData: TransformStartData): { x: number; y: number } {\n const cropWidth = imageTransformData(startData).width * imageTransformData(startData).cropWidth;\n const cropHeight = imageTransformData(startData).height * imageTransformData(startData).cropHeight;\n\n // Offset from crop center to frame center in local space\n // NOTE: No flip transformation needed here!\n // The local offsets are stored in unflipped coordinate space,\n // and this.x/this.y are also in unflipped world space.\n // The flip only affects rendering, not the coordinate calculations.\n const localOffsetX =\n imageTransformData(startData).width / 2 -\n (imageTransformData(startData).cropX * imageTransformData(startData).width + cropWidth / 2);\n const localOffsetY =\n imageTransformData(startData).height / 2 -\n (imageTransformData(startData).cropY * imageTransformData(startData).height + cropHeight / 2);\n\n // Rotate to world space\n const transform = new Transform(startData);\n const worldOffset = transform.localDeltaToWorld(localOffsetX, localOffsetY);\n\n // Frame center = crop center + offset\n return {\n x: startData.x + worldOffset.dx,\n y: startData.y + worldOffset.dy,\n };\n }\n\n /**\n * Get the position of the fixed corner (opposite of anchor) in world coordinates\n */\n private getFixedCornerWorldPosition(anchor: ResizeAnchor, startData: TransformStartData): { x: number; y: number } {\n // Determine fixed corner position in start frame's local coords\n const fixedCornerLocal = this.getOppositeCornerLocalPosition(\n anchor,\n imageTransformData(startData).width,\n imageTransformData(startData).height\n );\n\n // Calculate start frame center in world coords\n const startFrameCenter = this.calculateFrameCenterFromCropCenter(startData);\n\n // Transform to world coords\n const startTransform = new Transform(startData);\n const fixedCornerWorldOffset = startTransform.localDeltaToWorld(fixedCornerLocal.x, fixedCornerLocal.y);\n\n return {\n x: startFrameCenter.x + fixedCornerWorldOffset.dx,\n y: startFrameCenter.y + fixedCornerWorldOffset.dy,\n };\n }\n\n /**\n * Calculate where the frame center would be after resize\n * The opposite corner from the anchor stays fixed in world space\n */\n private calculateFrameCenterAfterResize(\n scale: number,\n anchor: ResizeAnchor,\n startData: TransformStartData\n ): { x: number; y: number } {\n // Get the fixed corner position in world coords (opposite of dragged anchor)\n const fixedCornerWorld = this.getFixedCornerWorldPosition(anchor, startData);\n\n // Calculate new frame dimensions\n const newFrameWidth = imageTransformData(startData).width * scale;\n const newFrameHeight = newFrameWidth / this.imageAspectRatio;\n\n // Get fixed corner position in new frame's local coords\n const fixedCornerLocal = this.getOppositeCornerLocalPosition(anchor, newFrameWidth, newFrameHeight);\n\n // Transform fixed corner local position to world coords\n const transform = new Transform({\n x: 0, // Will calculate frame center\n y: 0,\n rotation: startData.rotation,\n });\n\n const fixedCornerWorldOffset = transform.localDeltaToWorld(fixedCornerLocal.x, fixedCornerLocal.y);\n\n // Frame center = fixed corner world position - fixed corner offset in world coords\n return {\n x: fixedCornerWorld.x - fixedCornerWorldOffset.dx,\n y: fixedCornerWorld.y - fixedCornerWorldOffset.dy,\n };\n }\n\n /**\n * Calculate crop state (position and size in normalized coords) at a given scale\n * This is the core geometric calculation that determines where the crop zone\n * would be positioned within the frame after resize.\n */\n private calculateCropStateAtScale(\n scale: number,\n anchor: ResizeAnchor,\n startData: TransformStartData\n ): { cropX: number; cropY: number; cropWidth: number; cropHeight: number } {\n // Calculate new frame dimensions\n const newFrameWidth = imageTransformData(startData).width * scale;\n const newFrameHeight = newFrameWidth / this.imageAspectRatio;\n\n // Crop zone world size (constant)\n const cropWorldWidth = imageTransformData(startData).cropWidth * imageTransformData(startData).width;\n const cropWorldHeight = imageTransformData(startData).cropHeight * imageTransformData(startData).height;\n\n // Crop zone normalized size in new frame\n const cropNormWidth = cropWorldWidth / newFrameWidth;\n const cropNormHeight = cropWorldHeight / newFrameHeight;\n\n // Calculate new frame center position\n // The frame resizes with the opposite corner staying fixed\n const newFrameCenter = this.calculateFrameCenterAfterResize(scale, anchor, startData);\n\n // Calculate crop center position (stays at same world position)\n const cropCenterWorld = {\n x: startData.x, // In crop mode, this.x/y is the crop center\n y: startData.y,\n };\n\n // Calculate offset from new frame center to crop center (in world coords)\n const worldOffsetX = cropCenterWorld.x - newFrameCenter.x;\n const worldOffsetY = cropCenterWorld.y - newFrameCenter.y;\n\n // Convert offset to local frame coordinates\n const transform = new Transform({\n x: newFrameCenter.x,\n y: newFrameCenter.y,\n rotation: startData.rotation,\n });\n\n const localOffset = transform.worldDeltaToLocal(worldOffsetX, worldOffsetY);\n\n // Calculate crop center in frame-local coordinates\n const cropCenterLocalX = newFrameWidth / 2 + localOffset.dx;\n const cropCenterLocalY = newFrameHeight / 2 + localOffset.dy;\n\n // Convert to normalized coordinates (0-1)\n const cropX = cropCenterLocalX / newFrameWidth - cropNormWidth / 2;\n const cropY = cropCenterLocalY / newFrameHeight - cropNormHeight / 2;\n\n return {\n cropX,\n cropY,\n cropWidth: cropNormWidth,\n cropHeight: cropNormHeight,\n };\n }\n\n /**\n * Check if a given scale keeps crop within frame bounds\n */\n private isScaleValidForCropBounds(scale: number, anchor: ResizeAnchor, startData: TransformStartData): boolean {\n const EPSILON = 0.0001; // Tolerance for floating point comparison\n\n // Calculate what the crop state would be at this scale\n const cropState = this.calculateCropStateAtScale(scale, anchor, startData);\n\n // Check if crop width/height exceed 1.0 (impossible - crop larger than frame)\n if (cropState.cropWidth > 1.0 + EPSILON || cropState.cropHeight > 1.0 + EPSILON) {\n return false;\n }\n\n // Check if crop is within bounds [0, 1] in both dimensions\n const cropLeft = cropState.cropX;\n const cropRight = cropState.cropX + cropState.cropWidth;\n const cropTop = cropState.cropY;\n const cropBottom = cropState.cropY + cropState.cropHeight;\n\n // Crop must be fully within frame: 0 <= crop edges <= 1\n const withinBounds =\n cropLeft >= -EPSILON && cropTop >= -EPSILON && cropRight <= 1.0 + EPSILON && cropBottom <= 1.0 + EPSILON;\n\n return withinBounds;\n }\n\n /**\n * Calculate minimum scale that keeps crop within frame bounds\n * Uses binary search to find the largest scale >= proposedScale that satisfies constraints\n */\n private calculateMinScaleForCropBounds(\n anchor: ResizeAnchor,\n startData: TransformStartData,\n proposedScale: number,\n currentScale: number\n ): number {\n const EPSILON = 0.0001; // Tolerance for floating point comparison\n const MAX_ITERATIONS = 20; // More than enough for precision\n\n // If proposed scale is already valid, use it\n if (this.isScaleValidForCropBounds(proposedScale, anchor, startData)) {\n return proposedScale;\n }\n\n // Binary search for the minimum valid scale\n // We're looking for the smallest scale that still satisfies constraints\n let lo = proposedScale; // Known invalid (too small)\n let hi = currentScale; // Known valid (current state)\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n const testScale = (lo + hi) / 2;\n\n if (this.isScaleValidForCropBounds(testScale, anchor, startData)) {\n // This scale works - try going smaller (closer to proposed)\n hi = testScale;\n } else {\n // This scale fails - need to go larger\n lo = testScale;\n }\n\n // Early exit if we've converged\n if (Math.abs(hi - lo) < EPSILON) {\n break;\n }\n }\n\n return hi; // Return the smallest valid scale we found\n }\n\n /**\n * Remap anchor to account for flips\n */\n private remapAnchorForFlips(anchor: ResizeAnchor, startData: TransformStartData): ResizeAnchor {\n let effectiveAnchor = anchor;\n const flipH = imageTransformData(startData).flipHorizontal;\n const flipV = imageTransformData(startData).flipVertical;\n\n if (flipH) {\n if (effectiveAnchor.includes('left')) {\n effectiveAnchor = effectiveAnchor.replace('left', 'right') as ResizeAnchor;\n } else if (effectiveAnchor.includes('right')) {\n effectiveAnchor = effectiveAnchor.replace('right', 'left') as ResizeAnchor;\n }\n }\n\n if (flipV) {\n if (effectiveAnchor.includes('top')) {\n effectiveAnchor = effectiveAnchor.replace('top', 'bottom') as ResizeAnchor;\n } else if (effectiveAnchor.includes('bottom')) {\n effectiveAnchor = effectiveAnchor.replace('bottom', 'top') as ResizeAnchor;\n }\n }\n\n return effectiveAnchor;\n }\n\n /**\n * Clone this element\n */\n clone(): ImageElement {\n const json = this.toJSON();\n // CRITICAL: Remove imageUrl to prevent constructor from loading image again\n // We'll manually assign the already-loaded image element instead\n const { imageUrl, ...configWithoutUrl } = json;\n\n const cloned = new ImageElement(configWithoutUrl);\n\n // Manually set image properties from the original element\n cloned.imageUrl = this.imageUrl;\n cloned.imageElement = this.imageElement;\n cloned.imageLoaded = this.imageLoaded;\n cloned.imageAspectRatio = this.imageAspectRatio;\n cloned.isCropping = this.isCropping; // Preserve crop mode state\n cloned.onLoadCallback = this.onLoadCallback; // Preserve load callback\n cloned.isSvg = this.isSvg; // Preserve SVG flag\n cloned.imageLoadState = this.imageLoadState; // Preserve load state\n cloned.imageLoadError = this.imageLoadError; // Preserve load error\n cloned.preserveDimensions = this.preserveDimensions; // Preserve dimension lock for cover crop\n\n return cloned;\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ImageElementConfig & { id: string; type: 'image'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'image' as const,\n transformType: 'image' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n imageUrl: this.imageUrl,\n imageAspectRatio: this.imageAspectRatio,\n preserveDimensions: this.preserveDimensions, // dimension lock for cover crop must survive save→load\n transformData: {\n ...this.transformData,\n // Deep-copy the nested adjustments object so serialized state (and\n // clones built from it) never alias this element's live state.\n ...(this.transformData.adjustments ? { adjustments: { ...this.transformData.adjustments } } : {}),\n },\n };\n }\n\n /**\n * Clean up resources held by this element\n * Call this when the element is being removed from the canvas to prevent memory leaks\n * Particularly important for iOS Safari which has strict memory limits\n */\n destroy(): void {\n // Clear any pending load timeout or retry\n this.clearLoadTimeout();\n\n // Release image from shared cache (decrements refCount, evicts at 0)\n this.releaseImageCache();\n\n // Revoke SVG blob URL if present\n this.revokeSvgBlobUrl();\n\n // Clear references to allow garbage collection\n this.imageElement = null;\n this.onLoadCallback = null;\n this.imageLoadError = null;\n }\n}\n\nexport default ImageElement;\n","/**\n * Paint registry — the dependency-inversion seam between the element MODEL\n * and the rendering engine (ADR-0085 / D9, the P1 element/renderer split).\n *\n * The element classes (TextElement subclasses, GroupElement) are STATE: they\n * mount with `Editor.Root` in every composition, including the no-canvas\n * personalization widget, so their static import graph must stay renderer-free\n * (CI-gated by scripts/check-import-graph.mjs). Their `render()` methods used\n * to statically import the shared paint functions from `src/rendering/*`,\n * dragging the whole paint pipeline into the always-mounted graph.\n *\n * Inversion: the model looks paint functions up HERE at paint time; the heavy\n * side (`src/rendering/element-painters.ts`, inside `Editor.Canvas`'s lazy\n * chunk) registers them at module evaluation. Every module that DRIVES element\n * painting (CanvasRenderer, ExportManager, ElementPreviewRenderer) imports the\n * painters module, so by the time any `element.render()` runs the registry is\n * populated — a miss is an internal wiring bug, not a user-reachable state,\n * and throws with the fix in the message.\n */\n\n/** Paints one serialized text-transform element. The serialized shape is the\n * element's `toJSON()` output; the heavy side narrows it to its concrete\n * Serialized*Element type. */\nexport type TextTransformPainter = (\n ctx: CanvasRenderingContext2D,\n serialized: object,\n) => void;\n\n/** The subset of an element the group-clipping compositor needs — structural\n * on purpose so this module never imports an element class. */\nexport interface PaintableChild {\n isClipping?: boolean;\n transformType: string;\n render(ctx: CanvasRenderingContext2D, isSelected?: boolean, isHovered?: boolean): void;\n getBoundingBox(): { x: number; y: number; width: number; height: number };\n}\n\n/** Composites a group's children honoring `isClipping` masks. */\nexport type GroupClippingPainter = (\n ctx: CanvasRenderingContext2D,\n children: PaintableChild[],\n) => void;\n\nconst textTransformPainters = new Map<string, TextTransformPainter>();\nlet groupClippingPainter: GroupClippingPainter | null = null;\n\nfunction missingPainter(what: string): Error {\n return new Error(\n `[paint-registry] no painter registered for ${what}. Element painting requires the ` +\n `rendering engine: import 'src/rendering/element-painters.js' (every paint driver — ` +\n `CanvasRenderer, ExportManager, ElementPreviewRenderer — does this; a direct ` +\n `element.render() call in a test must import it too).`,\n );\n}\n\nexport function registerTextTransformPainter(type: string, painter: TextTransformPainter): void {\n textTransformPainters.set(type, painter);\n}\n\nexport function getTextTransformPainter(type: string): TextTransformPainter {\n const painter = textTransformPainters.get(type);\n if (!painter) throw missingPainter(`text transform \"${type}\"`);\n return painter;\n}\n\nexport function registerGroupClippingPainter(painter: GroupClippingPainter): void {\n groupClippingPainter = painter;\n}\n\nexport function getGroupClippingPainter(): GroupClippingPainter {\n if (!groupClippingPainter) throw missingPainter('group clipping');\n return groupClippingPainter;\n}\n","/**\n * Comprehensive TypeScript type definitions for custom-canvas\n * Uses discriminated unions for type-safe transform handling\n */\n\n// ============================================================================\n// Core Element Types\n// ============================================================================\n\nexport type TransformType =\n | 'custom'\n | 'distort'\n | 'circle'\n | 'lean'\n | 'arch'\n | 'ascend'\n | 'wave'\n | 'flag'\n | 'image'\n | 'group'\n | 'artboard'\n | 'shape'\n | 'path';\n\nexport interface BoundingBox {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport type ResizeAnchor =\n | 'top-left'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-right'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'middle-left'\n | 'middle-right'\n | 'middle-top'\n | 'middle-bottom';\n\nexport type TextAlign = 'left' | 'center' | 'right';\n\n// ============================================================================\n// Transform Data Types (Discriminated Unions)\n// ============================================================================\n\nexport interface CustomTransformData {\n type: 'custom';\n controlPoints: Point[];\n /** Container width for text wrapping */\n width: number;\n}\n\nexport interface DistortTransformData {\n type: 'distort';\n [key: string]: unknown; // Placeholder until DistortTransform is implemented\n}\n\nexport interface CircleTransformData {\n type: 'circle';\n radius: number;\n scale: number;\n reverse: boolean;\n}\n\nexport interface LeanTransformData {\n type: 'lean';\n leanAmount: number; // -0.5 to 0.5\n /** Container width for text layout */\n width: number;\n}\n\nexport interface ArchTransformData {\n type: 'arch';\n archHeight: number; // -1 to 1\n /** Container width for text layout */\n width: number;\n}\n\nexport interface AscendTransformData {\n type: 'ascend';\n ascendAngle: number; // -30 to 30 degrees\n /** Container width for text layout */\n width: number;\n}\n\nexport interface WaveTransformData {\n type: 'wave';\n amplitude: number; // 0 to 1\n frequency: number; // 1 to 5\n /** Container width for text layout */\n width: number;\n}\n\nexport interface FlagTransformData {\n type: 'flag';\n amplitude: number; // 0 to 1\n frequency: number; // 1 to 5\n /** Container width for text layout */\n width: number;\n}\n\n/**\n * Color adjustments for image elements. Each channel is a multiplier where\n * `1` is neutral (no change); the canonical neutral state is the ABSENCE of\n * the `adjustments` object (omit = neutral, never `{1, 1, 1}`).\n *\n * Semantics match the CSS Filter Effects spec functions `brightness()`,\n * `contrast()` and `saturate()`, applied in that order to the SOURCE bitmap\n * before crop / flip / border-radius compositing. The math lives in\n * `rendering/image-adjustments.ts` and is shared verbatim by the live\n * renderer and the export worker, so live and exported pixels always match.\n */\nexport interface ImageAdjustments {\n /** Brightness multiplier — 1 neutral, 0 black, > 1 brighter. */\n brightness: number;\n /** Contrast multiplier — 1 neutral, 0 solid grey, > 1 punchier. */\n contrast: number;\n /** Saturation multiplier — 1 neutral, 0 grayscale, > 1 more vivid. */\n saturation: number;\n}\n\nexport interface ImageTransformData {\n type: 'image';\n width: number;\n height: number;\n // Crop properties (normalized 0-1)\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n // Flip properties\n flipHorizontal: boolean;\n flipVertical: boolean;\n // Border radius (0-50)\n borderRadius: number;\n /** Brightness/contrast/saturation — omitted when neutral. See {@link ImageAdjustments}. */\n adjustments?: ImageAdjustments;\n}\n\nexport interface GroupTransformData {\n type: 'group';\n}\n\nexport interface ArtboardTransformData {\n type: 'artboard';\n}\n\n// Shape types and data\nexport type ShapeType = 'rectangle' | 'circle' | 'ellipse' | 'triangle' | 'polygon' | 'star' | 'line';\n\n/** One color stop on a gradient fill. `offset` is 0..1 along the gradient line. */\nexport interface GradientStop {\n offset: number;\n color: string;\n}\n\n/**\n * Shape fill — a discriminated union so an invalid mix (e.g. a \"gradient\n * enabled\" flag with no stops) is unrepresentable.\n *\n * - `solid` — a single CSS color.\n * - `linear` — CSS-like angle semantics: `0` = up (bottom → top),\n * `90` = right (left → right), increasing clockwise. The gradient line is\n * sized to the shape's local bounds exactly like CSS `linear-gradient`\n * (line length = |w·sin θ| + |h·cos θ|), so offset 0/1 land on the\n * shape's edges at every angle.\n * - `radial` — centered on the shape, radius = farthest corner\n * (√(w² + h²) / 2), matching CSS `radial-gradient(circle farthest-corner)`.\n *\n * Gradients need ≥ 2 stops with offsets in 0..1 — violations throw\n * `E_INVALID_FILL` at every write boundary (constructor, `updateElement`\n * patch), dev AND prod.\n *\n * Back-compat: when `fill` is absent, the legacy `fillColor` string renders\n * as a solid fill (it is the wire format of every saved design). When both\n * are present, `fill` wins.\n */\nexport type ShapeFill =\n | { type: 'solid'; color: string }\n | { type: 'linear'; angle: number; stops: GradientStop[] }\n | { type: 'radial'; stops: GradientStop[] };\n\nexport interface ShapeTransformData {\n type: 'shape';\n shapeType: ShapeType;\n\n // Common properties\n width: number;\n height: number;\n\n // Rectangle-specific\n borderRadius?: number; // 0-50 (percentage)\n\n // Circle/Ellipse-specific\n radiusX?: number;\n radiusY?: number;\n\n // Polygon-specific\n sides?: number; // 3-20\n\n // Star-specific\n points?: number; // 3-20\n innerRadius?: number; // 0-1 (relative to outer radius)\n\n // Fill properties\n /**\n * Legacy solid fill color. Still the default write path for solid fills;\n * ignored when `fill` is present.\n */\n fillColor?: string;\n fillOpacity?: number; // 0-1\n /**\n * Rich fill (solid or gradient). Wins over `fillColor` when present —\n * see {@link ShapeFill} for angle/geometry semantics and validation.\n */\n fill?: ShapeFill;\n}\n\n// ============================================================================\n// Path Types (Custom Bezier Paths)\n// ============================================================================\n\n/**\n * Path point types define how curve handles behave\n */\nexport type PathPointType =\n | 'corner' // Sharp corner, no curve handles\n | 'smooth' // Smooth curve, handles are mirrored and equal length\n | 'bezier'; // Asymmetric curve, independent handle control\n\n/**\n * A single point in a path with optional bezier control handles\n * All coordinates are relative to the path element's position\n */\nexport interface PathPoint {\n id: string;\n x: number;\n y: number;\n type: PathPointType;\n\n // Control handles for bezier curves (relative to point position)\n // handleIn: control point for curve coming INTO this point\n // handleOut: control point for curve going OUT from this point\n handleIn?: Point;\n handleOut?: Point;\n}\n\n/**\n * Transform data for custom path elements\n */\nexport interface PathTransformData {\n type: 'path';\n\n // Array of points defining the path\n points: PathPoint[];\n\n // Whether the path is closed (connects last point to first)\n closed: boolean;\n\n // Bounding box dimensions (cached for performance)\n width: number;\n height: number;\n\n // Fill properties (only apply if path is closed)\n fillEnabled?: boolean;\n fillColor?: string;\n fillOpacity?: number;\n\n // Stroke properties (always apply)\n strokeEnabled?: boolean;\n strokeColor?: string;\n strokeWidth?: number;\n}\n\n// Discriminated union of all transform data types\nexport type AnyTransformData =\n | CustomTransformData\n | DistortTransformData\n | CircleTransformData\n | LeanTransformData\n | ArchTransformData\n | AscendTransformData\n | WaveTransformData\n | FlagTransformData\n | ImageTransformData\n | GroupTransformData\n | ArtboardTransformData\n | ShapeTransformData\n | PathTransformData;\n\n// ============================================================================\n// Transform Start Data (for interaction state)\n// ============================================================================\n\nexport interface TransformStartData {\n id: string;\n x: number;\n y: number;\n rotation: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- transformData is intentionally flexible; consumers cast to specific types\n transformData: Record<string, any>;\n // For bounding box operations\n width?: number;\n height?: number;\n // For text elements\n fontSize?: number;\n richText?: unknown;\n lineCount?: number;\n // For visual bbox (added by InteractionStateMachine)\n visualX?: number;\n visualY?: number;\n visualWidth?: number;\n visualHeight?: number;\n bboxX?: number;\n bboxY?: number;\n // For image crop mode\n cropX?: number;\n cropY?: number;\n cropWidth?: number;\n cropHeight?: number;\n // For stroke scaling during resize\n strokeWidth?: number;\n // For group operations\n childrenStartData?: TransformStartData[];\n // Allow additional properties for transform-specific start data\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Blend Modes & Compositing\n// ============================================================================\n\n/**\n * Standard blend modes — map 1:1 to canvas `globalCompositeOperation` values.\n * The element composites AS A UNIT onto the layers below it (Photoshop layer\n * semantics), handled by LayerEffectsRenderer in both the live renderer and\n * the export worker.\n */\nexport type StandardBlendMode = 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten';\n\n/** All standard blend modes, in UI display order. */\nexport const STANDARD_BLEND_MODES: readonly StandardBlendMode[] = [\n 'multiply',\n 'screen',\n 'overlay',\n 'darken',\n 'lighten',\n];\n\nexport type BlendMode =\n | 'normal' // Standard blending\n | 'knockout' // Cuts through to artboard (destination-out)\n | 'clip' // Clips content to shape (destination-in)\n | StandardBlendMode; // multiply | screen | overlay | darken | lighten\n\nexport type CompositingScope = 'group' | 'artboard';\nexport type KnockoutScope = CompositingScope; // backward compat alias\n\nexport interface CompositingConfig {\n fill?: boolean; // Element fill participates in compositing\n stroke?: boolean; // Element stroke participates in compositing\n scope?: CompositingScope; // 'group' (default) or 'artboard'\n}\nexport type KnockoutConfig = CompositingConfig; // backward compat alias\n\n// ============================================================================\n// Stroke System\n// ============================================================================\n\nexport interface StrokeConfig {\n enabled: boolean;\n color: string;\n width: number;\n\n // Style options\n dashArray?: number[]; // [5, 5] for dashed lines\n lineCap?: 'butt' | 'round' | 'square';\n lineJoin?: 'miter' | 'round' | 'bevel';\n miterLimit?: number;\n\n // Visual effects\n opacity?: number; // 0-1\n feather?: number; // 0-20 blur radius\n}\n\n// ============================================================================\n// Drop Shadow System\n// ============================================================================\n\n/**\n * Drop shadow cast by an element's final silhouette (after masks, stroke,\n * distress). All distances are WORLD-SPACE pixels — the shadow scales with\n * canvas zoom and export resolution exactly like the element content does\n * (it is artwork, not zoom-invariant chrome).\n */\nexport interface ShadowConfig {\n enabled: boolean;\n /** Any CSS color (hex, rgb(a), named). Alpha multiplies with `opacity`. */\n color: string;\n /** Blur radius in world-space px (>= 0) */\n blur: number;\n /** Horizontal offset in world-space px (positive = right) */\n offsetX: number;\n /** Vertical offset in world-space px (positive = down) */\n offsetY: number;\n /** Shadow strength 0-1 (default 1), multiplies the color's own alpha */\n opacity?: number;\n}\n\n/**\n * Rendering context metadata passed to renderers\n * Used to customize rendering behavior for specific contexts (e.g., knockout, export, preview)\n */\nexport interface RenderingContext {\n /** True when rendering for knockout compositing (affects stroke color/opacity) */\n isKnockout?: boolean;\n /** True when rendering for export (may skip certain preview-only features) */\n isExport?: boolean;\n /** True when rendering for mask application */\n isMask?: boolean;\n /** True when the caller has already applied element positioning (translate/rotate) */\n positionApplied?: boolean;\n}\n\n// ============================================================================\n// Mask System\n// ============================================================================\n\nexport type MaskType =\n | 'clip' // Hard edge clipping (text, shape clipping)\n | 'alpha' // Alpha/transparency mask\n | 'luma' // Luminosity mask\n | 'distress'; // Texture/grunge mask (HIGH PRIORITY for t-shirt design)\n\nexport interface MaskDefinition {\n id: string;\n type: MaskType;\n\n // The element that defines the mask\n // Positioned relative to the masked element\n maskElement: AnyElementConfig;\n\n // Mask properties\n inverted?: boolean; // Invert the mask\n feather?: number; // Blur edges (0-100)\n opacity?: number; // Mask strength (0-1)\n blendMode?: string; // How this mask combines with other masks\n}\n\n// ============================================================================\n// Distress Effects (HIGH PRIORITY for t-shirt design)\n// ============================================================================\n\nexport type DistressStyle = 'worn' | 'cracked' | 'grunge' | 'retro' | 'custom';\n\nexport interface DistressEffect {\n enabled: boolean;\n style: DistressStyle;\n intensity: number; // 0-100 (overall effect strength)\n\n // Effect parameters\n fadeAmount?: number; // 0-100 (color fading)\n grainAmount?: number; // 0-100 (film grain/noise)\n scratchAmount?: number; // 0-100 (scratch marks)\n edgeWear?: number; // 0-100 (worn edges)\n\n // Custom texture (for 'custom' style)\n textureUrl?: string; // URL to distress texture image\n textureOpacity?: number; // 0-1 (how strong the texture is)\n textureBlendMode?: 'multiply' | 'screen' | 'overlay';\n\n // Random seed (for consistent results)\n seed?: number;\n}\n\n// ============================================================================\n// Glyph & OpenType Feature Types\n// ============================================================================\n\n/**\n * Per-character glyph override\n * Allows substituting specific characters with alternate glyphs from the font\n */\nexport interface GlyphOverride {\n /** Index of the character in the text string */\n charIndex: number;\n /** Glyph index in the font file */\n glyphIndex: number;\n /** Unicode codepoint of the original character */\n unicode: string;\n /** Human-readable name of the alternate (e.g., \"Swash A\", \"Fancy G\") */\n alternateName?: string;\n}\n\n/**\n * OpenType feature settings for advanced typography\n * Features are applied globally to the entire text element\n */\nexport interface OpenTypeFeatures {\n /** Standard ligatures (fi, fl, etc.) */\n liga?: boolean;\n /** Discretionary ligatures (ct, st, etc.) */\n dlig?: boolean;\n /** Contextual alternates */\n calt?: boolean;\n /** Swash alternates */\n swsh?: boolean;\n /** Small capitals */\n smcp?: boolean;\n /** All small capitals */\n c2sc?: boolean;\n /** Oldstyle figures (0-9) */\n onum?: boolean;\n /** Lining figures (0-9) */\n lnum?: boolean;\n /** Tabular figures */\n tnum?: boolean;\n /** Proportional figures */\n pnum?: boolean;\n /** Stylistic sets (ss01-ss20) */\n ss01?: boolean;\n ss02?: boolean;\n ss03?: boolean;\n ss04?: boolean;\n ss05?: boolean;\n ss06?: boolean;\n ss07?: boolean;\n ss08?: boolean;\n ss09?: boolean;\n ss10?: boolean;\n ss11?: boolean;\n ss12?: boolean;\n ss13?: boolean;\n ss14?: boolean;\n ss15?: boolean;\n ss16?: boolean;\n ss17?: boolean;\n ss18?: boolean;\n ss19?: boolean;\n ss20?: boolean;\n /** Character variants (cv01-cv99) - common ones */\n cv01?: boolean;\n cv02?: boolean;\n cv03?: boolean;\n cv04?: boolean;\n cv05?: boolean;\n /** Glyph composition/decomposition */\n ccmp?: boolean;\n /** Localized forms */\n locl?: boolean;\n /** Kerning */\n kern?: boolean;\n /** Mark positioning */\n mark?: boolean;\n /** Mark-to-mark positioning */\n mkmk?: boolean;\n}\n\n/**\n * Metadata about available glyphs for a specific character\n */\nexport interface GlyphAlternate {\n /** Glyph index in the font file */\n glyphIndex: number;\n /** Unicode codepoint this glyph represents */\n unicode: string;\n /** Human-readable name (e.g., \"A.swash\", \"g.alt01\") */\n name: string;\n /** Category of alternate (swash, stylistic, etc.) */\n category?: 'default' | 'swash' | 'stylistic' | 'contextual' | 'ligature';\n /** Preview data URL (base64-encoded image) */\n previewDataUrl?: string;\n}\n\n// ============================================================================\n// Rich Text System (Character-Level Formatting)\n// ============================================================================\n\n/**\n * Character-level styling properties\n * Each property is optional - undefined means inherit from element defaults\n */\nexport interface CharacterStyle {\n /** Text color (CSS color string) */\n color?: string;\n /** Font family name */\n fontFamily?: string;\n /** Font size in pixels */\n fontSize?: number;\n /** Bold weight */\n bold?: boolean;\n /** Italic style */\n italic?: boolean;\n /** Underline decoration */\n underline?: boolean;\n /** Strikethrough decoration */\n strikethrough?: boolean;\n /**\n * Extra advance added after EACH character, in world-space pixels (CSS\n * `letter-spacing` model — the trailing character also gets the spacing, so\n * a line's width grows by `spacing × charCount`). Undefined inherits the\n * element-level `letterSpacing`.\n *\n * Applied by the straight ('custom') text transform only. Curved text\n * transforms (circle, arch, wave, lean, ascend, flag) lay glyphs along\n * their own path math and INTENTIONALLY ignore letter-spacing — see the\n * \"curved transforms ignore letterSpacing\" test in transform-renderer.test.ts.\n */\n letterSpacing?: number;\n}\n\n/**\n * A span of text with uniform styling\n * Used for efficient storage of rich text (avoids per-character arrays)\n */\nexport interface TextSpan {\n /** The text content of this span */\n text: string;\n /** Style properties for this span (optional properties inherit from element defaults) */\n style: CharacterStyle;\n}\n\n/**\n * Rich text data structure with helper methods\n * Stores text as an array of styled spans for efficient rendering\n */\nexport class RichText {\n spans: TextSpan[];\n\n constructor(spans: TextSpan[] = []) {\n // Copy spans (and their style objects) — normalize() merges/mutates spans, and\n // adopting the caller's array would alias clone and original (or mutate the caller).\n this.spans =\n spans.length > 0\n ? spans.map((s) => ({ ...s, style: { ...s.style } }))\n : [{ text: '', style: {} }];\n this.normalize();\n }\n\n /**\n * Create RichText from plain text string (no special formatting)\n */\n static fromPlainText(text: string, style: CharacterStyle = {}): RichText {\n return new RichText([{ text, style }]);\n }\n\n /**\n * Get the full text content (all spans concatenated)\n */\n getText(): string {\n return this.spans.map((s) => s.text).join('');\n }\n\n /**\n * Get the total character count\n */\n getLength(): number {\n return this.getText().length;\n }\n\n /**\n * Get the style at a specific character index\n * @param charIndex Character index (0-based)\n * @returns The style object for the span containing this character\n */\n getStyleAt(charIndex: number): CharacterStyle {\n // Handle empty RichText\n if (this.spans.length === 0) return {};\n\n // Handle negative index - return first span style\n if (charIndex < 0) return this.spans[0].style;\n\n let currentIndex = 0;\n for (const span of this.spans) {\n if (charIndex < currentIndex + span.text.length) {\n return span.style;\n }\n currentIndex += span.text.length;\n }\n\n // At or past end - return LAST span's style (not empty object)\n // This ensures new characters typed at end inherit the previous style\n return this.spans[this.spans.length - 1].style;\n }\n\n /**\n * Apply a style to a character range\n * @param start Start index (inclusive)\n * @param end End index (exclusive)\n * @param style Style to apply (merged with existing styles)\n */\n applyStyle(start: number, end: number, style: CharacterStyle): void {\n if (start >= end || start < 0 || end > this.getLength()) {\n return;\n }\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Case 1: Span is completely before the range\n if (spanEnd <= start) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Case 2: Span is completely after the range\n if (spanStart >= end) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Case 3: Span overlaps with range - split it\n const overlapStart = Math.max(start, spanStart);\n const overlapEnd = Math.min(end, spanEnd);\n\n // Before overlap\n if (overlapStart > spanStart) {\n newSpans.push({\n text: span.text.substring(0, overlapStart - spanStart),\n style: span.style,\n });\n }\n\n // Overlap region - apply new style\n newSpans.push({\n text: span.text.substring(overlapStart - spanStart, overlapEnd - spanStart),\n style: { ...span.style, ...style },\n });\n\n // After overlap\n if (overlapEnd < spanEnd) {\n newSpans.push({\n text: span.text.substring(overlapEnd - spanStart),\n style: span.style,\n });\n }\n\n currentIndex = spanEnd;\n }\n\n this.spans = newSpans;\n this.normalize();\n }\n\n /**\n * Insert text at a specific position with a given style\n * @param index Character index to insert at\n * @param text Text to insert\n * @param style Style for the inserted text\n */\n insert(index: number, text: string, style: CharacterStyle): void {\n if (text.length === 0) return;\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Insert point is before this span\n if (index <= spanStart && newSpans.length === 0) {\n newSpans.push({ text, style });\n }\n\n // Insert point is within this span\n if (index > spanStart && index <= spanEnd) {\n const offset = index - spanStart;\n // Split the span\n newSpans.push({\n text: span.text.substring(0, offset),\n style: span.style,\n });\n newSpans.push({ text, style });\n newSpans.push({\n text: span.text.substring(offset),\n style: span.style,\n });\n currentIndex = spanEnd;\n continue;\n }\n\n newSpans.push(span);\n currentIndex = spanEnd;\n }\n\n // Insert at end if we haven't inserted yet\n if (index >= this.getLength() && !newSpans.some((s) => s.text === text)) {\n newSpans.push({ text, style });\n }\n\n this.spans = newSpans;\n this.normalize();\n }\n\n /**\n * Delete text in a character range\n * @param start Start index (inclusive)\n * @param end End index (exclusive)\n */\n delete(start: number, end: number): void {\n // Clamp to valid range instead of silently failing\n const length = this.getLength();\n start = Math.max(0, Math.min(start, length));\n end = Math.max(0, Math.min(end, length));\n\n // Nothing to delete if range is empty after clamping\n if (start >= end) {\n return;\n }\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Span is completely before deletion range\n if (spanEnd <= start) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Span is completely after deletion range\n if (spanStart >= end) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Span overlaps with deletion range\n const deleteStart = Math.max(start, spanStart);\n const deleteEnd = Math.min(end, spanEnd);\n\n // Keep text before deletion\n if (deleteStart > spanStart) {\n newSpans.push({\n text: span.text.substring(0, deleteStart - spanStart),\n style: span.style,\n });\n }\n\n // Keep text after deletion\n if (deleteEnd < spanEnd) {\n newSpans.push({\n text: span.text.substring(deleteEnd - spanStart),\n style: span.style,\n });\n }\n\n currentIndex = spanEnd;\n }\n\n this.spans = newSpans.length > 0 ? newSpans : [{ text: '', style: {} }];\n this.normalize();\n }\n\n /**\n * Normalize spans by:\n * 1. Removing empty spans\n * 2. Merging adjacent spans with identical styles\n * This prevents fragmentation and improves performance\n */\n private normalize(): void {\n // Step 1: Remove empty spans\n this.spans = this.spans.filter((s) => s.text !== '');\n\n // Step 2: Ensure at least one span exists (empty span for empty RichText)\n if (this.spans.length === 0) {\n this.spans = [{ text: '', style: {} }];\n return;\n }\n\n // Step 3: Merge adjacent spans with identical styles\n if (this.spans.length <= 1) return;\n\n const merged: TextSpan[] = [];\n let current = this.spans[0];\n\n for (let i = 1; i < this.spans.length; i++) {\n const next = this.spans[i];\n\n // Check if styles are identical\n if (this.stylesEqual(current.style, next.style)) {\n // Merge spans\n current = {\n text: current.text + next.text,\n style: current.style,\n };\n } else {\n merged.push(current);\n current = next;\n }\n }\n\n merged.push(current);\n this.spans = merged;\n }\n\n /**\n * Check if two styles are equal\n */\n private stylesEqual(a: CharacterStyle, b: CharacterStyle): boolean {\n return (\n a.color === b.color &&\n a.fontFamily === b.fontFamily &&\n a.fontSize === b.fontSize &&\n a.bold === b.bold &&\n a.italic === b.italic &&\n a.underline === b.underline &&\n a.strikethrough === b.strikethrough &&\n a.letterSpacing === b.letterSpacing\n );\n }\n\n /**\n * Clone this RichText instance\n */\n clone(): RichText {\n return new RichText(\n this.spans.map((span) => ({\n text: span.text,\n style: { ...span.style },\n }))\n );\n }\n\n /**\n * Clear a specific style property from all spans.\n * Used when element-level property changes should override character-level.\n * After clearing, spans will inherit the property from element defaults.\n * @param property The style property to clear (e.g., 'fontFamily', 'bold')\n */\n clearStyleProperty(property: keyof CharacterStyle): void {\n for (const span of this.spans) {\n if (span.style[property] !== undefined) {\n delete span.style[property];\n }\n }\n this.normalize();\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): { spans: TextSpan[] } {\n // Deep-copy so serialized output never aliases the live spans\n return { spans: this.spans.map((s) => ({ ...s, style: { ...s.style } })) };\n }\n\n /**\n * Deserialize from JSON\n */\n static fromJSON(json: { spans: TextSpan[] }): RichText {\n return new RichText(json.spans);\n }\n}\n\n// ============================================================================\n// Element Configuration Types (Discriminated Unions)\n// ============================================================================\n\nexport interface BaseElementConfig {\n id?: string;\n x?: number;\n y?: number;\n rotation?: number;\n opacity?: number;\n\n // Transform type and data (specific to each element type)\n transformType?: TransformType;\n transformData?: Partial<AnyTransformData>;\n\n // Compositing\n blendMode?: BlendMode;\n knockoutParts?: KnockoutConfig;\n\n // Effects\n stroke?: StrokeConfig;\n masks?: MaskDefinition[];\n distressEffect?: DistressEffect;\n shadow?: ShadowConfig;\n\n // Layer properties (for layers panel)\n name?: string;\n visible?: boolean;\n locked?: boolean;\n\n // Clipping mask - when true, this element clips all elements below it in the same group\n isClipping?: boolean;\n\n /**\n * Name of the artboard (placement) this element lives on — the public\n * artboard-membership tag for multi-placement editors.\n *\n * `SnowconeCanvas.onChange` stamps it on every element it emits, and\n * `initialElements` restores an element onto its named artboard, so a saved\n * multi-artboard design round-trips instead of collapsing onto the first\n * artboard. `serializeStateForServer` uses it to emit each artboard with its\n * own elements (and strips it from the wire payload). Elements without it\n * keep the legacy behavior: the first artboard.\n */\n artboard?: string;\n}\n\nexport interface BaseTextElementConfig extends BaseElementConfig {\n // Rich text support (new)\n richText?: RichText | { spans: TextSpan[] };\n // Legacy plain text (kept for backward compatibility)\n text?: string;\n // Element-level style defaults (used when richText spans don't specify)\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n textAlign?: TextAlign;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n /**\n * Element-level default letter-spacing in world-space pixels (extra advance\n * after each character, CSS model). Rich-text spans may override per\n * character via `CharacterStyle.letterSpacing`. Default: 0 (no spacing).\n * Straight ('custom') transform only — curved transforms ignore it.\n */\n letterSpacing?: number;\n /**\n * Element-level line-height MULTIPLIER for multi-line text\n * (lineSpacing = fontSize × lineHeight). Default: 1.2\n * (LINE_HEIGHT_MULTIPLIER) — the value all existing designs were laid out\n * with, so omitting it never reflows. Straight ('custom') transform only.\n */\n lineHeight?: number;\n\n // Glyph & OpenType features\n glyphOverrides?: GlyphOverride[];\n openTypeFeatures?: OpenTypeFeatures;\n}\n\n// Text element configs with specific transform types\nexport interface CustomElementConfig extends BaseTextElementConfig {\n transformType: 'custom';\n transformData?: Partial<CustomTransformData>;\n}\n\nexport interface DistortElementConfig extends BaseTextElementConfig {\n transformType: 'distort';\n transformData?: Partial<DistortTransformData>;\n}\n\nexport interface CircleElementConfig extends BaseTextElementConfig {\n transformType: 'circle';\n transformData?: Partial<CircleTransformData>;\n}\n\nexport interface LeanElementConfig extends BaseTextElementConfig {\n transformType: 'lean';\n transformData?: Partial<LeanTransformData>;\n}\n\nexport interface ArchElementConfig extends BaseTextElementConfig {\n transformType: 'arch';\n transformData?: Partial<ArchTransformData>;\n}\n\nexport interface AscendElementConfig extends BaseTextElementConfig {\n transformType: 'ascend';\n transformData?: Partial<AscendTransformData>;\n}\n\nexport interface WaveElementConfig extends BaseTextElementConfig {\n transformType: 'wave';\n transformData?: Partial<WaveTransformData>;\n}\n\nexport interface FlagElementConfig extends BaseTextElementConfig {\n transformType: 'flag';\n transformData?: Partial<FlagTransformData>;\n}\n\nexport interface ImageElementConfig extends BaseElementConfig {\n transformType: 'image';\n transformData?: Partial<ImageTransformData>;\n imageUrl?: string;\n imageAspectRatio?: number;\n /**\n * Callback invoked when the image finishes loading.\n *\n * The parameter is an `ImageElement` instance (clone of the loaded element).\n * Import `ImageElement` from `@snowcone-app/canvas/advanced` to narrow the type.\n *\n * @example\n * ```ts\n * import type { ImageElement } from '@snowcone-app/canvas/advanced';\n * const config: ImageElementConfig = {\n * transformType: 'image',\n * onLoadCallback(el) {\n * const img = el as ImageElement;\n * console.log(img.id, img.transformData.width);\n * },\n * };\n * ```\n */\n // Method syntax provides bivariant parameter checking, allowing callers to\n // type the parameter as ImageElement without importing it in this file.\n onLoadCallback?(element: BaseElementConfig & { readonly id: string; readonly transformType: 'image' }): void;\n /**\n * If true, dimensions (width/height) won't be recalculated when the image loads.\n * Use this when you've explicitly calculated dimensions (e.g., for \"cover\" sizing).\n */\n preserveDimensions?: boolean;\n}\n\nexport interface GroupElementConfig extends BaseElementConfig {\n transformType: 'group';\n transformData?: Partial<GroupTransformData>;\n children?: AnyElementConfig[];\n}\n\nexport interface ShapeElementConfig extends BaseElementConfig {\n transformType: 'shape';\n transformData?: Partial<ShapeTransformData>;\n}\n\nexport interface PathElementConfig extends BaseElementConfig {\n transformType: 'path';\n transformData?: Partial<PathTransformData>;\n}\n\nexport type ArtboardBackgroundType = 'color' | 'transparent' | 'texture';\n\n// Artboard-level distress texture (applied after all elements render)\nexport interface ArtboardDistressTexture {\n enabled: boolean;\n textureUrl: string; // URL to grayscale JPEG texture\n intensity: number; // 0-100\n}\n\n// Artboard-level image mask (applied after distress texture)\nexport interface ArtboardImageMask {\n enabled: boolean;\n imageUrl: string;\n maskType: 'clip' | 'alpha' | 'luma';\n opacity: number; // 0-100\n inverted?: boolean;\n}\n\n// Clip shape types for artboard content clipping\nexport type ClipShape =\n | 'rectangle' // Full artboard bounds (default, no clipping)\n | 'circle' // Circle inscribed in artboard\n | { type: 'rounded'; radius: number } // Rounded rectangle with specified corner radius\n | { type: 'path'; d: string } // Custom SVG path data\n // Union of multiple piece-local SVG paths, each translated to its\n // (x, y) inside the artboard frame and optionally rotated by 0/90/180/270\n // around the piece center. Used for ADR-0054 multi-piece spreads\n // (e.g. AFOOES hoodie) so artwork visually clips to the union of piece\n // silhouettes instead of the bounding rectangle. `baseWidth`/`baseHeight`\n // are the path's pre-rotation extents (the piece's unrotated bbox);\n // needed only when `rotation` is non-zero so we can rotate around the\n // piece's geometric center.\n | {\n type: 'composite-path';\n pieces: Array<{\n d: string;\n x: number;\n y: number;\n rotation?: 0 | 90 | 180 | 270;\n baseWidth?: number;\n baseHeight?: number;\n }>;\n };\n\n/**\n * Pacing role of an artboard in a multi-page template (ADR-0086 albums).\n *\n * Client/template metadata: auto-fill uses it to know which spreads clone as\n * the page count grows (`repeating`) and which stay fixed (`cover` /\n * `front-matter` / `back-matter`). It never reaches the server render wire —\n * `serializeStateForServer` strips it like the element `artboard` membership tag.\n */\nexport type ArtboardRole = 'cover' | 'front-matter' | 'repeating' | 'back-matter';\n\nexport interface ArtboardConfig {\n id?: string;\n name?: string;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n backgroundColor?: string;\n backgroundType?: ArtboardBackgroundType;\n backgroundTexture?: string; // Texture ID or URL\n exportBackground?: boolean; // Whether to export background (defaults to false)\n elementIds?: string[];\n transformType?: 'artboard';\n\n // Pacing role for multi-page templates (ADR-0086) — template metadata,\n // never sent on the server render wire\n role?: ArtboardRole;\n\n // Clip shape for artboard content\n // - 'rectangle' or undefined: No clipping (content renders to artboard edges)\n // - 'circle': Content clipped to circle inscribed in artboard\n // - { type: 'rounded', radius: number }: Rounded rectangle corners\n // - { type: 'path', d: string }: Custom SVG path\n clipShape?: ClipShape;\n\n // Preview only - NOT exported to PNG (for t-shirt color preview)\n previewBackgroundColor?: string;\n\n // Artboard-level distress texture (applied after all elements, before UI layers)\n distressTexture?: ArtboardDistressTexture;\n\n // Artboard-level image mask (applied after distress texture)\n imageMask?: ArtboardImageMask;\n}\n\n// Discriminated union of all element configs\nexport type AnyElementConfig =\n | CustomElementConfig\n | DistortElementConfig\n | CircleElementConfig\n | LeanElementConfig\n | ArchElementConfig\n | AscendElementConfig\n | WaveElementConfig\n | FlagElementConfig\n | ImageElementConfig\n | GroupElementConfig\n | ShapeElementConfig\n | PathElementConfig;\n\n// Helper type for text-only elements (excludes image, shape, and path)\nexport type TextOnlyElementConfig = Exclude<\n AnyElementConfig,\n ImageElementConfig | ShapeElementConfig | PathElementConfig\n>;\n\n// ============================================================================\n// Interaction Types\n// ============================================================================\n\nexport type InteractionMode = 'idle' | 'drag' | 'resize' | 'rotate' | 'crop' | 'pen' | 'edit-path' | 'pinch';\n\nexport interface InteractionContext<T = unknown> {\n mode: InteractionMode;\n startX: number;\n startY: number;\n startData: TransformStartData;\n activeHandle?: ResizeAnchor;\n element: T; // Will be TextElement or its subclasses\n}\n\n// ============================================================================\n// Command History Types\n// ============================================================================\n\nexport interface Command {\n execute(): void;\n undo(): void;\n}\n\nexport interface ElementUpdate<T = unknown> {\n oldElement: T;\n newElement: T;\n}\n\nexport interface MultiElementUpdate<T = unknown> {\n updates: Map<string, ElementUpdate<T>>;\n}\n\n// ============================================================================\n// Selection Types\n// ============================================================================\n\nexport type SelectionState =\n | { type: 'none' }\n | { type: 'single'; elementId: string }\n | { type: 'multiple'; elementIds: Set<string> };\n\n// ============================================================================\n// Snap System Types\n// ============================================================================\n\nexport interface SnapGuide {\n type: 'vertical' | 'horizontal';\n sourceId?: string;\n targetId?: string;\n // For vertical guides\n x?: number;\n y1?: number;\n y2?: number;\n // For horizontal guides\n y?: number;\n x1?: number;\n x2?: number;\n}\n\nexport interface SnapResult {\n x: number;\n y: number;\n snappedX: boolean;\n snappedY: boolean;\n guides: SnapGuide[];\n}\n\n// ============================================================================\n// Spacing Indicator Types\n// ============================================================================\n\nexport interface SpacingIndicator {\n type: 'horizontal' | 'vertical';\n distance: number;\n isSnapTarget?: boolean; // If true, render in green to show snap will happen\n isAltHover?: boolean; // If true, render in purple to show Alt+hover measurements\n // For horizontal indicators\n x1?: number;\n x2?: number;\n y?: number;\n // For vertical indicators\n x?: number;\n y1?: number;\n y2?: number;\n // Label position\n labelX: number;\n labelY: number;\n}\n\n// ============================================================================\n// Resize Types\n// ============================================================================\n\nexport interface ResizeResult {\n newWidth: number;\n newHeight: number;\n newX: number;\n newY: number;\n newRotation: number;\n}\n\nexport interface ResizeParams<T = unknown> {\n element: T;\n anchor: ResizeAnchor;\n dx: number;\n dy: number;\n startData: TransformStartData;\n maintainAspectRatio?: boolean;\n}\n\n// ============================================================================\n// Render Types\n// ============================================================================\n\nexport interface RenderContext {\n ctx: CanvasRenderingContext2D;\n isSelected: boolean;\n isHovered?: boolean;\n scale?: number;\n}\n\n// ============================================================================\n// Transform Handle Types\n// ============================================================================\n\nexport interface HandleInfo {\n anchor: ResizeAnchor;\n x: number;\n y: number;\n cursor: string;\n}\n\n// ============================================================================\n// Transform Control Types (for UI)\n// ============================================================================\n\nexport interface TransformControl {\n key: string;\n label: string;\n type?: 'checkbox' | 'slider' | 'select';\n defaultValue?: number | boolean | string;\n defaultInternalValue?: number;\n step?: number;\n options?: Array<{ value: string; label: string }>;\n toSlider?: (internal: number) => number;\n fromSlider?: (slider: number) => number;\n toDisplay?: (internal: number) => string;\n visibleWhen?: (data: Record<string, unknown>) => boolean;\n}\n\nexport interface TransformTypeDefinition {\n id: TransformType;\n label: string;\n Component: new (config?: Partial<BaseTextElementConfig>) => unknown; // Element class constructor\n}\n\n// ============================================================================\n// Utility Types\n// ============================================================================\n\n// Extract element type by transform type\nexport type ExtractElementByType<T extends TransformType> = Extract<AnyElementConfig, { transformType: T }>;\n\n// Extract transform data by type\nexport type ExtractTransformData<T extends TransformType> = Extract<AnyTransformData, { type: T }>;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n// Transform data type guards\nexport function isCustomTransform(data: AnyTransformData): data is CustomTransformData {\n return data.type === 'custom';\n}\n\nexport function isDistortTransform(data: AnyTransformData): data is DistortTransformData {\n return data.type === 'distort';\n}\n\nexport function isCircleTransform(data: AnyTransformData): data is CircleTransformData {\n return data.type === 'circle';\n}\n\nexport function isLeanTransform(data: AnyTransformData): data is LeanTransformData {\n return data.type === 'lean';\n}\n\nexport function isArchTransform(data: AnyTransformData): data is ArchTransformData {\n return data.type === 'arch';\n}\n\nexport function isAscendTransform(data: AnyTransformData): data is AscendTransformData {\n return data.type === 'ascend';\n}\n\nexport function isWaveTransform(data: AnyTransformData): data is WaveTransformData {\n return data.type === 'wave';\n}\n\nexport function isFlagTransform(data: AnyTransformData): data is FlagTransformData {\n return data.type === 'flag';\n}\n\nexport function isImageTransform(data: AnyTransformData): data is ImageTransformData {\n return data.type === 'image';\n}\n\nexport function isGroupTransform(data: AnyTransformData): data is GroupTransformData {\n return data.type === 'group';\n}\n\nexport function isArtboardTransform(data: AnyTransformData): data is ArtboardTransformData {\n return data.type === 'artboard';\n}\n\n// Element config type guards\nexport function isTextElementConfig(config: AnyElementConfig): config is TextOnlyElementConfig {\n return config.transformType !== 'image' && config.transformType !== 'group';\n}\n\nexport function isImageElementConfig(config: AnyElementConfig): config is ImageElementConfig {\n return config.transformType === 'image';\n}\n\nexport function isCustomElementConfig(config: AnyElementConfig): config is CustomElementConfig {\n return config.transformType === 'custom';\n}\n\nexport function isCircleElementConfig(config: AnyElementConfig): config is CircleElementConfig {\n return config.transformType === 'circle';\n}\n\nexport function isLeanElementConfig(config: AnyElementConfig): config is LeanElementConfig {\n return config.transformType === 'lean';\n}\n\nexport function isArchElementConfig(config: AnyElementConfig): config is ArchElementConfig {\n return config.transformType === 'arch';\n}\n\nexport function isAscendElementConfig(config: AnyElementConfig): config is AscendElementConfig {\n return config.transformType === 'ascend';\n}\n\nexport function isWaveElementConfig(config: AnyElementConfig): config is WaveElementConfig {\n return config.transformType === 'wave';\n}\n\nexport function isFlagElementConfig(config: AnyElementConfig): config is FlagElementConfig {\n return config.transformType === 'flag';\n}\n\nexport function isGroupElementConfig(config: AnyElementConfig): config is GroupElementConfig {\n return config.transformType === 'group';\n}\n\nexport function isShapeElementConfig(config: AnyElementConfig): config is ShapeElementConfig {\n return config.transformType === 'shape';\n}\n\nexport function isPathElementConfig(config: AnyElementConfig): config is PathElementConfig {\n return config.transformType === 'path';\n}\n\n// ============================================================================\n// Transform Data Type Guards\n// ============================================================================\n\nexport function isShapeTransform(data: AnyTransformData): data is ShapeTransformData {\n return data.type === 'shape';\n}\n\nexport function isPathTransform(data: AnyTransformData): data is PathTransformData {\n return data.type === 'path';\n}\n\n// ============================================================================\n// New Feature Type Guards\n// ============================================================================\n\n// Stroke type guard\nexport function hasStroke(config: AnyElementConfig): boolean {\n return config.stroke?.enabled === true;\n}\n\n// Masks type guard\nexport function hasMasks(config: AnyElementConfig): boolean {\n return Array.isArray(config.masks) && config.masks.length > 0;\n}\n\n// Knockout type guard (also matches clip mode)\nexport function isKnockout(config: AnyElementConfig): boolean {\n return config.blendMode === 'knockout' || config.blendMode === 'clip';\n}\n\n/**\n * Standard blend mode type guard. Narrows a BlendMode (or raw string from\n * serialized JSON) to the modes that map 1:1 onto globalCompositeOperation.\n * 'normal', 'knockout', 'clip' and undefined are all NOT standard blend modes\n * — knockout/clip keep their special compositing path untouched.\n */\nexport function isStandardBlendMode(mode: BlendMode | string | undefined | null): mode is StandardBlendMode {\n return (STANDARD_BLEND_MODES as readonly string[]).includes(mode as string);\n}\n\n// Drop shadow type guard\nexport function hasShadow(config: AnyElementConfig): boolean {\n return config.shadow?.enabled === true;\n}\n\n// Distress effect type guard\nexport function hasDistressEffect(config: AnyElementConfig): boolean {\n return config.distressEffect?.enabled === true;\n}\n\n// ============================================================================\n// Canvas Error Types\n// ============================================================================\n\n/**\n * Categories of errors that can occur within the canvas.\n *\n * - 'render': Errors during canvas rendering (element draw, compositing)\n * - 'export': Errors during artboard export (PNG generation, worker failures)\n * - 'import': Errors loading or deserializing saved state / initial elements\n * - 'image': Errors loading images (network failures, CORS, decode errors)\n * - 'worker': Errors in the export web worker\n * - 'state': Errors in state management (artboard creation, element updates)\n * - 'unknown': Uncategorized errors caught by the ErrorBoundary\n */\nexport type CanvasErrorCategory = 'render' | 'export' | 'import' | 'image' | 'worker' | 'state' | 'unknown';\n\n/**\n * Structured error type for all canvas errors.\n * Provides consistent error reporting with category, context, and recoverability info.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onError={(error) => {\n * if (error.category === 'image' && error.recoverable) {\n * showToast('Image failed to load, please try another');\n * } else if (!error.recoverable) {\n * reportCrash(error);\n * }\n * }}\n * />\n * ```\n */\nexport interface CanvasError {\n /** The category of this error */\n category: CanvasErrorCategory;\n /** Human-readable error message */\n message: string;\n /** The original Error object, if available */\n originalError?: Error;\n /** The element ID involved, if applicable */\n elementId?: string;\n /** The artboard ID involved, if applicable */\n artboardId?: string;\n /** Whether the canvas can continue operating after this error */\n recoverable: boolean;\n}\n","/**\n * TextElement - Base class for all text-based elements\n * Extends BaseElement with text-specific properties and behavior\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { getTextTransformPainter } from './paint-registry.js';\nimport { MIN_FONT_SIZE, MAX_FONT_SIZE } from '../constants.js';\nimport type {\n TextAlign,\n BoundingBox,\n BaseTextElementConfig,\n ResizeAnchor,\n GlyphOverride,\n OpenTypeFeatures,\n CharacterStyle,\n TransformStartData,\n} from '../types/index.js';\nimport { RichText } from '../types/index.js';\n\nexport class TextElement extends BaseElement {\n // Rich text support\n richText?: RichText;\n // Legacy plain text (kept for backward compatibility)\n text: string;\n // Element-level style defaults (used when richText spans don't specify)\n fontSize: number;\n fontFamily: string;\n color: string;\n textAlign: TextAlign;\n bold: boolean;\n italic: boolean;\n underline: boolean;\n strikethrough: boolean;\n /**\n * Element-level letter-spacing in world-space px (extra advance after each\n * character, CSS model). Per-character override via\n * `CharacterStyle.letterSpacing`. undefined = 0. Custom transform only —\n * curved transforms intentionally ignore it.\n */\n letterSpacing?: number;\n /**\n * Line-height multiplier (lineSpacing = fontSize × lineHeight).\n * undefined = LINE_HEIGHT_MULTIPLIER (1.2), the historical layout.\n * Custom transform only.\n */\n lineHeight?: number;\n\n // Glyph and OpenType features\n glyphOverrides?: GlyphOverride[];\n openTypeFeatures?: OpenTypeFeatures;\n\n constructor(config: Partial<BaseTextElementConfig> = {}) {\n super(config);\n\n // Set default transform type for text elements\n this.transformType = config.transformType || 'custom';\n\n // Element-level defaults\n this.fontSize = config.fontSize || 72;\n this.fontFamily = config.fontFamily || 'Arial';\n this.color = config.color || '#333333';\n this.bold = config.bold !== undefined ? config.bold : false;\n this.italic = config.italic !== undefined ? config.italic : false;\n this.underline = config.underline !== undefined ? config.underline : false;\n this.strikethrough = config.strikethrough !== undefined ? config.strikethrough : false;\n this.textAlign = config.textAlign || 'center';\n this.letterSpacing = config.letterSpacing;\n this.lineHeight = config.lineHeight;\n\n // Rich text initialization with backward compatibility\n if (config.richText) {\n // New format: use richText\n if (config.richText instanceof RichText) {\n this.richText = config.richText;\n } else {\n // Deserialize from JSON\n this.richText = RichText.fromJSON(config.richText);\n }\n this.text = this.richText.getText();\n } else if (config.text !== undefined) {\n // Legacy format: migrate to richText\n // Create richText from plain text with EMPTY style so it inherits from element defaults\n this.text = config.text;\n this.richText = RichText.fromPlainText(config.text, {});\n } else {\n // No text provided: default\n this.text = 'Text';\n this.richText = RichText.fromPlainText('Text', {});\n }\n\n // Glyph and OpenType features\n this.glyphOverrides = config.glyphOverrides;\n this.openTypeFeatures = config.openTypeFeatures;\n }\n\n /**\n * Get bounding box in world coordinates\n * Must be implemented by subclasses\n */\n getBoundingBox(): BoundingBox {\n throw new Error('getBoundingBox() must be implemented by subclass');\n }\n\n /**\n * Render the element.\n *\n * Paints through the paint registry (P1 element/renderer split): the shared\n * paint function for this transformType is registered by the rendering\n * engine (`src/rendering/element-painters.ts`), so the element model never\n * statically imports the paint pipeline. Selection and hover visuals are\n * handled by SelectionRenderer and HoverRenderer.\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n getTextTransformPainter(this.transformType)(ctx, this.toJSON());\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): BaseTextElementConfig {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n // Serialize rich text\n ...(this.richText && { richText: this.richText.toJSON() }),\n // Keep legacy text for backward compatibility\n text: this.text,\n fontSize: this.fontSize,\n fontFamily: this.fontFamily,\n color: this.color,\n textAlign: this.textAlign,\n bold: this.bold,\n italic: this.italic,\n underline: this.underline,\n strikethrough: this.strikethrough,\n // Typography properties (only include if defined)\n ...(this.letterSpacing !== undefined && { letterSpacing: this.letterSpacing }),\n ...(this.lineHeight !== undefined && { lineHeight: this.lineHeight }),\n ...(this.openTypeFeatures && { openTypeFeatures: { ...this.openTypeFeatures } }),\n ...(this.glyphOverrides && { glyphOverrides: this.glyphOverrides.map((g) => ({ ...g })) }),\n };\n }\n\n /**\n * Clone this element\n */\n clone(): TextElement {\n const Constructor = this.constructor as new (config: Partial<BaseTextElementConfig>) => TextElement;\n return new Constructor(this.toJSON());\n }\n\n /**\n * Update text content\n * @param newText - Plain text to set (resets to element defaults)\n */\n setText(newText: string): void {\n this.text = newText;\n // Update richText as well - use empty style to inherit from element defaults\n this.richText = RichText.fromPlainText(newText, {});\n }\n\n /**\n * Get the plain text content\n */\n getText(): string {\n return this.richText?.getText() || this.text;\n }\n\n /**\n * Get the rich text object\n */\n getRichText(): RichText {\n if (!this.richText) {\n // Migrate on-demand if needed - use empty style to inherit from element defaults\n this.richText = RichText.fromPlainText(this.text, {});\n }\n return this.richText;\n }\n\n /**\n * Set the rich text object\n */\n setRichText(newRichText: RichText): void {\n this.richText = newRichText;\n this.text = newRichText.getText();\n }\n\n /**\n * Get the element-level default style\n */\n getDefaultStyle(): CharacterStyle {\n return {\n color: this.color,\n fontFamily: this.fontFamily,\n fontSize: this.fontSize,\n bold: this.bold,\n italic: this.italic,\n underline: this.underline,\n strikethrough: this.strikethrough,\n ...(this.letterSpacing !== undefined && { letterSpacing: this.letterSpacing }),\n };\n }\n\n /**\n * Apply character-level formatting to a text range\n */\n applyFormatting(start: number, end: number, style: CharacterStyle): void {\n const richText = this.getRichText();\n richText.applyStyle(start, end, style);\n this.text = richText.getText();\n }\n\n /**\n * Update font size\n */\n setFontSize(newFontSize: number): void {\n this.fontSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, newFontSize));\n }\n\n /**\n * Update color\n * Updates both element-level default and all rich text spans that currently match the old color\n */\n setColor(newColor: string): void {\n const oldColor = this.color;\n this.color = newColor;\n\n // Also update all rich text spans that have the old color or inherit from element default\n if (this.richText) {\n for (let i = 0; i < this.richText.spans.length; i++) {\n const span = this.richText.spans[i];\n if (span.style.color === undefined || span.style.color === oldColor) {\n span.style.color = newColor;\n }\n }\n }\n }\n\n /**\n * Handle resize transform\n * Must be implemented by subclasses\n */\n resize(_anchor: ResizeAnchor, _newWidth: number, _newHeight: number, _startData: TransformStartData): void | boolean {\n throw new Error('resize() must be implemented by subclass');\n }\n\n /**\n * Called when transform starts\n * Returns data to be passed to resize()\n */\n override getTransformStartData(): TransformStartData {\n const baseData = super.getTransformStartData();\n return {\n ...baseData,\n fontSize: this.fontSize,\n // Capture rich text state for proportional scaling during resize\n richText: this.richText ? this.richText.clone() : null,\n };\n }\n}\n\nexport default TextElement;\n","/**\n * text-spacing — the ONE advance computation for letter-spaced text.\n *\n * Letter-spacing model (CSS `letter-spacing` / canvas `ctx.letterSpacing`\n * semantics): every character's advance grows by `letterSpacing` world-space\n * pixels, INCLUDING the last character of a run. So:\n *\n * spacedWidth(text) = ctx.measureText(text).width + spacing × charCount\n * advance(char at index i) = ctx.measureText(prefix(i+1)).width − measureText(prefix(i)).width + spacing\n * cursor x after N chars = ctx.measureText(prefix(N)).width + spacing × N\n *\n * The uniform per-character `+spacing` is what keeps cursor math, selection\n * rects, hover chrome, stroke outlines, and painted glyphs in lockstep — every\n * site sums the SAME advances. Do not invent a second model (e.g. \"between\n * characters only\", spacing × (n−1)); a mixed model drifts the caret.\n *\n * Painting is done per-character at kerning-preserving offsets (each glyph is\n * positioned by the measured width of its prefix, so kerning pairs inside the\n * run stay intact) rather than via the native `ctx.letterSpacing` property,\n * which is not implemented across all engines we ship to. When spacing is 0\n * the fast path is a single fillText/strokeText — byte-identical to the\n * pre-letter-spacing renderer, which is what the \"default unchanged\" tests pin.\n *\n * Worker-compatible: no DOM access; operates on whatever 2D context (canvas or\n * OffscreenCanvas) the caller already configured (`ctx.font` must be set).\n *\n * SCOPE: straight ('custom') text transform only. Curved transforms (circle,\n * arch, wave, lean, ascend, flag) place glyphs along their own path math and\n * intentionally ignore letter-spacing (pinned in transform-renderer.test.ts).\n */\n\ntype AnyRenderContext = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n/** Minimal context shape needed for measurement-only callers (tests, layout). */\nexport interface MeasureOnlyContext {\n measureText(text: string): { width: number };\n}\n\n/**\n * Count user-perceived characters (code points, not UTF-16 units) so\n * surrogate pairs (emoji, PUA glyphs) get ONE spacing slot, matching the\n * per-character paint loop which iterates with Array.from.\n */\nexport function countSpacedChars(text: string): number {\n return Array.from(text).length;\n}\n\n/**\n * Add letter-spacing to a raw measured width.\n * `spacedTextWidth(ctx.measureText(t).width, t, s)` — every char gets +s.\n */\nexport function spacedTextWidth(rawWidth: number, text: string, letterSpacing: number | undefined): number {\n if (!letterSpacing) return rawWidth;\n return rawWidth + letterSpacing * countSpacedChars(text);\n}\n\n/**\n * Measure text with letter-spacing using an already-configured context\n * (ctx.font must be set by the caller).\n */\nexport function measureTextWithSpacing(\n ctx: MeasureOnlyContext,\n text: string,\n letterSpacing: number | undefined\n): number {\n return spacedTextWidth(ctx.measureText(text).width, text, letterSpacing);\n}\n\n/**\n * Per-character x offsets (relative to the run's left edge) for a\n * letter-spaced run. offsets[i] is where character i is painted; a trailing\n * entry offsets[n] is the total advance (== spacedTextWidth). Kerning inside\n * the run is preserved because each offset is the measured width of the full\n * prefix, not a sum of isolated char widths.\n */\nexport function getSpacedCharOffsets(\n ctx: MeasureOnlyContext,\n text: string,\n letterSpacing: number\n): number[] {\n const chars = Array.from(text);\n const offsets: number[] = [0];\n let prefix = '';\n for (let i = 0; i < chars.length; i++) {\n prefix += chars[i];\n offsets.push(ctx.measureText(prefix).width + letterSpacing * (i + 1));\n }\n return offsets;\n}\n\n/** Resolve the left-anchored start x for a run drawn under the ctx's textAlign. */\nfunction leftAnchoredStartX(ctx: AnyRenderContext, x: number, totalWidth: number): number {\n switch (ctx.textAlign) {\n case 'center':\n return x - totalWidth / 2;\n case 'right':\n case 'end':\n return x - totalWidth;\n default:\n // 'left' | 'start'\n return x;\n }\n}\n\ntype DrawOp = 'fillText' | 'strokeText';\n\nfunction drawTextWithSpacing(\n op: DrawOp,\n ctx: AnyRenderContext,\n text: string,\n x: number,\n y: number,\n letterSpacing: number | undefined\n): void {\n if (!letterSpacing || text.length === 0) {\n // Fast path — byte-identical to the pre-letter-spacing renderer.\n ctx[op](text, x, y);\n return;\n }\n\n const offsets = getSpacedCharOffsets(ctx, text, letterSpacing);\n const totalWidth = offsets[offsets.length - 1];\n const startX = leftAnchoredStartX(ctx, x, totalWidth);\n\n const previousAlign = ctx.textAlign;\n ctx.textAlign = 'left';\n const chars = Array.from(text);\n for (let i = 0; i < chars.length; i++) {\n ctx[op](chars[i], startX + offsets[i], y);\n }\n ctx.textAlign = previousAlign;\n}\n\n/**\n * Drop-in replacement for `ctx.fillText(text, x, y)` that honors\n * letter-spacing. Respects the ctx's current `textAlign` (the spaced total\n * width — trailing spacing included — is what gets centered/right-aligned,\n * matching CSS). Zero/undefined spacing calls fillText once, unchanged.\n */\nexport function fillTextWithSpacing(\n ctx: AnyRenderContext,\n text: string,\n x: number,\n y: number,\n letterSpacing: number | undefined\n): void {\n drawTextWithSpacing('fillText', ctx, text, x, y, letterSpacing);\n}\n\n/**\n * Drop-in replacement for `ctx.strokeText(text, x, y)` that honors\n * letter-spacing. Same alignment semantics as fillTextWithSpacing.\n */\nexport function strokeTextWithSpacing(\n ctx: AnyRenderContext,\n text: string,\n x: number,\n y: number,\n letterSpacing: number | undefined\n): void {\n drawTextWithSpacing('strokeText', ctx, text, x, y, letterSpacing);\n}\n","/**\n * TextMetrics - Shared utilities for text measurement and layout\n * Reduces duplication across transform types\n */\n\nimport { LINE_HEIGHT_MULTIPLIER } from '../constants.js';\nimport { spacedTextWidth } from './text-spacing.js';\n\n/**\n * Optional typography knobs threaded through layout/measurement.\n * Omitting them (or passing {}) reproduces the historical layout EXACTLY:\n * letterSpacing 0, lineHeight LINE_HEIGHT_MULTIPLIER (1.2).\n */\nexport interface TextLayoutOptions {\n /** Extra advance per character in px (CSS letter-spacing model). */\n letterSpacing?: number;\n /** Line-height multiplier (lineSpacing = fontSize × lineHeight). */\n lineHeight?: number;\n}\n\n// Reusable canvas for text measurements (works in both main thread and worker)\nlet _measureCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;\n\n/**\n * Get or create a canvas for text measurements.\n * Uses OffscreenCanvas in worker contexts where document is unavailable.\n * @private\n */\nfunction getMeasureCanvas(): HTMLCanvasElement | OffscreenCanvas {\n if (!_measureCanvas) {\n if (typeof document !== 'undefined') {\n _measureCanvas = document.createElement('canvas');\n } else if (typeof OffscreenCanvas !== 'undefined') {\n _measureCanvas = new OffscreenCanvas(1, 1);\n } else {\n throw new Error('No canvas available for text measurement');\n }\n }\n return _measureCanvas;\n}\n\n/**\n * Build font string with optional bold and italic\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether to use bold weight\n * @param {boolean} italic - Whether to use italic style\n * @returns {string} Font string for ctx.font\n */\nexport function buildFontString(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): string {\n // Build font string parts, only including non-default values\n const parts: string[] = [];\n\n // Only add italic if true (omit 'normal' as it's the default)\n if (italic) {\n parts.push('italic');\n }\n\n // Only add bold if true (omit 'normal' as it's the default)\n if (bold) {\n parts.push('bold');\n }\n\n // Always include font size and family\n parts.push(`${fontSize}px`);\n parts.push(fontFamily);\n\n return parts.join(' ');\n}\n\n/**\n * Word wrap text to fit within a given width\n * @param {string} text - Text to wrap\n * @param {number} maxWidth - Maximum width in pixels\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {number} lockedLineCount - Optional locked line count (forces exact number of lines during corner resize)\n * @param {boolean} strict - If true, disable tolerance (wrap exactly at maxWidth, matching richText behavior)\n * @param {TextLayoutOptions} opts - Optional letterSpacing (affects wrap points); omitting = legacy layout\n * @returns {string[]} Array of lines\n */\nexport function wrapText(\n text: string,\n maxWidth: number,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n lockedLineCount?: number,\n strict: boolean = false,\n opts?: TextLayoutOptions\n): string[] {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n const letterSpacing = opts?.letterSpacing ?? 0;\n /** Letter-spacing-aware width — identical to ctx.measureText when spacing is 0. */\n const measure = (t: string): number => spacedTextWidth(ctx.measureText(t).width, t, letterSpacing);\n\n // If text is all whitespace, return as-is (no wrapping needed)\n if (text.trim().length === 0) {\n return [text];\n }\n\n // Handle explicit newlines: split by \\n first, wrap each line separately\n if (text.includes('\\n')) {\n const explicitLines = text.split('\\n');\n const allWrappedLines: string[] = [];\n\n for (let i = 0; i < explicitLines.length; i++) {\n const line = explicitLines[i];\n const wrappedLine = wrapText(line, maxWidth, fontSize, fontFamily, bold, italic, undefined, strict, opts);\n allWrappedLines.push(...wrappedLine);\n }\n\n return allWrappedLines;\n }\n\n // Preserve leading and trailing spaces\n const leadingSpacesMatch = text.match(/^\\s*/);\n const trailingSpacesMatch = text.match(/\\s*$/);\n const leadingSpaces = leadingSpacesMatch ? leadingSpacesMatch[0] : '';\n const trailingSpaces = trailingSpacesMatch ? trailingSpacesMatch[0] : '';\n const trimmedText = text.substring(leadingSpaces.length, text.length - trailingSpaces.length);\n\n const words = trimmedText.split(' ');\n\n // If line count is locked (during corner resize), force that exact number of lines\n if (lockedLineCount !== undefined && lockedLineCount > 0) {\n if (lockedLineCount === 1) {\n return [leadingSpaces + words.join(' ') + trailingSpaces];\n }\n\n // Distribute words as evenly as possible across locked line count\n const lines: string[] = [];\n const wordsPerLine = Math.ceil(words.length / lockedLineCount);\n\n for (let i = 0; i < words.length; i += wordsPerLine) {\n const lineWords = words.slice(i, i + wordsPerLine);\n lines.push(lineWords.join(' '));\n }\n\n // Add leading spaces to first line, trailing spaces to last line\n if (lines.length > 0) {\n lines[0] = leadingSpaces + lines[0];\n lines[lines.length - 1] = lines[lines.length - 1] + trailingSpaces;\n }\n\n return lines;\n }\n\n // Normal wrapping logic (no lock)\n // First pass: check if all text fits on one line with generous tolerance\n const allText = words.join(' ');\n // CRITICAL FIX: Measure WITH leading/trailing spaces to avoid \"fits but doesn't fit\" bug\n // where we measure \"Foo\" but return \" Foo\" which is wider\n const allTextWithSpaces = leadingSpaces + allText + trailingSpaces;\n const allTextWidth = measure(allTextWithSpaces);\n\n // Use a percentage-based tolerance that scales with maxWidth\n // This provides consistent behavior regardless of text size during resize\n // At smaller sizes, use higher percentages to provide more stability\n // In strict mode, use zero tolerance to match richText wrapping behavior\n const isSmallSize = maxWidth < 200;\n const wrapTolerancePercent = strict ? 0 : (isSmallSize ? 0.08 : 0.05);\n const wrapTolerance = strict ? 0 : Math.max(20, maxWidth * wrapTolerancePercent);\n\n // If text fits on one line (with tolerance), keep it that way\n if (allTextWidth <= maxWidth + wrapTolerance) {\n return [allTextWithSpaces];\n }\n\n // Otherwise, perform word wrapping with smaller percentage-based tolerance\n const lines: string[] = [];\n let currentLine = '';\n const lineTolerancePercent = strict ? 0 : (isSmallSize ? 0.05 : 0.03);\n const lineTolerance = strict ? 0 : Math.max(15, maxWidth * lineTolerancePercent);\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i];\n const wordWidth = measure(word);\n\n // Check if word itself is too wide (needs character-level breaking)\n if (wordWidth > maxWidth + lineTolerance) {\n // Finish current line if it has content\n if (currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = '';\n }\n\n // Break word at character level\n const chars = Array.from(word);\n let charBuffer = '';\n\n for (const char of chars) {\n const testBuffer = charBuffer + char;\n const bufferWidth = measure(testBuffer);\n\n if (bufferWidth > maxWidth + lineTolerance && charBuffer.length > 0) {\n // Finish current line with buffer\n lines.push(charBuffer);\n charBuffer = char;\n } else {\n charBuffer = testBuffer;\n }\n }\n\n // Set remaining buffer as current line\n currentLine = charBuffer;\n } else {\n // Word fits on a line - check if we need to wrap\n const testLine = currentLine.length > 0 ? currentLine + ' ' + word : word;\n const testLineWidth = measure(testLine);\n\n if (testLineWidth > maxWidth + lineTolerance && currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine = testLine;\n }\n }\n }\n\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n // Add leading spaces to first line, trailing spaces to last line\n // CRITICAL FIX: Check if adding spaces makes lines too wide\n if (lines.length > 0) {\n // Check if adding leading space makes first line too wide\n if (leadingSpaces.length > 0) {\n const firstLineWithSpace = leadingSpaces + lines[0];\n const firstLineWidth = measure(firstLineWithSpace);\n\n if (firstLineWidth > maxWidth + lineTolerance) {\n // Leading space doesn't fit on same line as content - put it on its own line\n // This matches rich text behavior where \" Foo\" wraps to [\" \", \"Foo\"]\n lines.unshift(leadingSpaces);\n } else {\n // Fits - add to first line\n lines[0] = firstLineWithSpace;\n }\n }\n\n // Check if adding trailing space makes last line too wide\n if (trailingSpaces.length > 0) {\n const lastIdx = lines.length - 1;\n const lastLineWithSpace = lines[lastIdx] + trailingSpaces;\n const lastLineWidth = measure(lastLineWithSpace);\n\n if (lastLineWidth > maxWidth + lineTolerance) {\n // Trailing space doesn't fit - put it on its own line\n lines.push(trailingSpaces);\n } else {\n // Fits - add to last line\n lines[lastIdx] = lastLineWithSpace;\n }\n }\n }\n\n return lines;\n}\n\n/**\n * Measure width of text\n * @param {string} text - Text to measure\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {number} letterSpacing - Extra advance per character in px (default 0 — legacy width)\n * @returns {number} Width in pixels\n */\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n letterSpacing: number = 0\n): number {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n return spacedTextWidth(ctx.measureText(text).width, text, letterSpacing);\n}\n\n/**\n * Get font metrics for accurate text positioning\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @returns {Object} {ascent, descent, height}\n */\nexport function getFontMetrics(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): { ascent: number; descent: number; height: number } {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n // Measure sample text with tall ascenders and deep descenders\n const sampleText = 'ÁÉÍgjpqy';\n const metrics = ctx.measureText(sampleText);\n\n const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;\n const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;\n const height = ascent + descent;\n\n return { ascent, descent, height };\n}\n\n/**\n * Calculate visual bounds for multi-line text\n * @param {string[]} lines - Array of text lines\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {number} lineHeight - Line height multiplier (e.g., 1.2)\n * @returns {Object} {width, height}\n */\nexport function calculateTextBounds(\n lines: string[],\n fontSize: number,\n fontFamily: string,\n lineHeight: number = 1.2\n): { width: number; height: number } {\n const fontMetrics = getFontMetrics(fontSize, fontFamily);\n\n // Find max width of all lines\n let maxWidth = 0;\n for (const line of lines) {\n const width = measureTextWidth(line, fontSize, fontFamily);\n maxWidth = Math.max(maxWidth, width);\n }\n\n // Calculate height\n let height;\n if (lines.length === 1) {\n height = fontMetrics.height;\n } else {\n // All lines except last use full lineHeight, last line uses measured height\n const lineSpacing = fontSize * lineHeight;\n height = (lines.length - 1) * lineSpacing + fontMetrics.height;\n }\n\n return { width: maxWidth, height };\n}\n\n/**\n * Render multi-line text with alignment and text decorations\n * @param {CanvasRenderingContext2D} ctx - Canvas context\n * @param {string[]} lines - Array of text lines\n * @param {number} x - X position (meaning depends on alignment)\n * @param {number} y - Y position (top of first line baseline)\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {string} alignment - 'left', 'center', or 'right'\n * @param {number} containerWidth - Width of text container (for alignment)\n * @param {number} horizontalPadding - Padding on sides\n * @param {number} lineHeight - Line height multiplier\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {boolean} underline - Whether text is underlined\n * @param {boolean} strikethrough - Whether text has strikethrough\n */\nexport function renderMultilineText(\n ctx: CanvasRenderingContext2D,\n lines: string[],\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n alignment: CanvasTextAlign = 'left',\n containerWidth: number = 0,\n horizontalPadding: number = 0,\n lineHeight: number = 1.2,\n bold: boolean = false,\n italic: boolean = false,\n underline: boolean = false,\n strikethrough: boolean = false\n): void {\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = alignment;\n\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const lineSpacing = fontSize * lineHeight;\n\n lines.forEach((line: string, index: number) => {\n let xPos = x + horizontalPadding;\n\n if (alignment === 'center') {\n xPos = x + containerWidth / 2;\n } else if (alignment === 'right') {\n xPos = x + containerWidth - horizontalPadding;\n }\n\n const yPos = y + fontMetrics.ascent + index * lineSpacing;\n ctx.fillText(line, xPos, yPos);\n\n // Draw underline if enabled\n if (underline) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let underlineX = xPos;\n\n if (alignment === 'center') {\n underlineX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n underlineX = xPos - lineWidth;\n }\n\n const underlineY = yPos + fontSize * 0.1; // Slightly below baseline\n ctx.beginPath();\n ctx.moveTo(underlineX, underlineY);\n ctx.lineTo(underlineX + lineWidth, underlineY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n\n // Draw strikethrough if enabled\n if (strikethrough) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let strikeX = xPos;\n\n if (alignment === 'center') {\n strikeX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n strikeX = xPos - lineWidth;\n }\n\n const strikeY = yPos - fontMetrics.ascent * 0.4; // Middle of text\n ctx.beginPath();\n ctx.moveTo(strikeX, strikeY);\n ctx.lineTo(strikeX + lineWidth, strikeY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n });\n}\n\n/**\n * Calculate container height for text with given line count\n * @param {number} lineCount - Number of lines\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {number} lineHeight - Line height multiplier\n * @returns {number} Container height in pixels\n */\nexport function calculateContainerHeight(\n lineCount: number,\n fontSize: number,\n fontFamily: string,\n lineHeight: number = 1.2\n): number {\n if (lineCount === 1) {\n return fontSize * lineHeight;\n }\n\n const fontMetrics = getFontMetrics(fontSize, fontFamily);\n const lineSpacing = fontSize * lineHeight;\n return (lineCount - 1) * lineSpacing + fontMetrics.height;\n}\n\n/**\n * Calculate visual bounds for wrapped text with space collapsing applied\n * This matches the actual rendering behavior where trailing spaces on wrapped lines are hidden\n *\n * @param {string} text - Text to measure\n * @param {number} maxWidth - Maximum width for wrapping (or Infinity for no wrapping)\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {number} lockedLineCount - Optional locked line count (forces exact number of lines during corner resize)\n * @param {boolean} strict - If true, disable tolerance (wrap exactly at maxWidth)\n * @param {TextLayoutOptions} opts - Optional letterSpacing/lineHeight; omitting = legacy layout\n * @returns {Object} {width, height, lines} - Adjusted dimensions and line array\n */\nexport function calculateVisualBoundsWithSpaceCollapsing(\n text: string,\n maxWidth: number,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n lockedLineCount?: number,\n strict: boolean = false,\n opts?: TextLayoutOptions\n): { width: number; height: number; lines: string[] } {\n const letterSpacing = opts?.letterSpacing ?? 0;\n // Get wrapped lines (which preserve trailing spaces)\n const lines = wrapText(text, maxWidth, fontSize, fontFamily, bold, italic, lockedLineCount, strict, opts);\n\n // Determine paragraph boundaries (lines separated by explicit \\n)\n // In wrapText, all lines from word-wrapping are from the same paragraph\n // Only explicit \\n characters create paragraph boundaries\n const hasExplicitNewlines = text.includes('\\n');\n\n // If no explicit newlines, all wrapped lines are from one paragraph\n // First line is paragraph start, last line is paragraph end\n // Middle lines are neither (so their trailing spaces get hidden)\n const paragraphMetadata = lines.map((_, index) => ({\n isParagraphStart: !hasExplicitNewlines && index === 0,\n isParagraphEnd: !hasExplicitNewlines && index === lines.length - 1,\n }));\n\n // Calculate metrics for each line with space collapsing applied\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n let maxLineWidth = 0;\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n\n // Apply space collapsing and collect visible lines\n // CRITICAL: We track total line count (including empty lines after collapsing)\n // to match cursor positions and selection bounds\n const visibleLines: string[] = [];\n let totalLineCount = 0;\n\n lines.forEach((line, index) => {\n const isParagraphStart = paragraphMetadata[index].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[index].isParagraphEnd;\n\n // Apply space layout rules (matching renderRichTextFillOnly logic)\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleText = visibleText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleText = visibleText.substring(0, visibleText.length - trailingSpacesCount);\n }\n }\n\n // Count this line regardless of whether it's empty\n totalLineCount++;\n\n // Only measure width for non-empty lines\n if (visibleText.length > 0) {\n visibleLines.push(visibleText);\n\n // Measure the visible text width (letter-spacing-aware)\n const lineWidth = spacedTextWidth(ctx.measureText(visibleText).width, visibleText, letterSpacing);\n maxLineWidth = Math.max(maxLineWidth, lineWidth);\n }\n });\n\n // Calculate height based on TOTAL line count (including empty lines)\n // This ensures selection bounds and cursor positions align correctly\n // Example: \" Foo\" wrapping to [\" \", \"Foo\"] has 2 lines even though first is empty\n let height;\n // Element-level multiplier when provided; the shared constant otherwise\n // (consistency with renderers — they resolve the same way)\n const lineHeight = opts?.lineHeight ?? LINE_HEIGHT_MULTIPLIER;\n if (totalLineCount === 1) {\n height = fontMetrics.height;\n } else if (totalLineCount === 0) {\n // No lines at all - return zero height\n height = 0;\n } else {\n const lineSpacing = fontSize * lineHeight;\n height = (totalLineCount - 1) * lineSpacing + fontMetrics.height;\n }\n\n return {\n width: maxLineWidth,\n height: height,\n lines: lines,\n };\n}\n\nexport default {\n wrapText,\n measureTextWidth,\n getFontMetrics,\n calculateTextBounds,\n renderMultilineText,\n calculateContainerHeight,\n calculateVisualBoundsWithSpaceCollapsing,\n};\n","/**\n * Rich-text LAYOUT — line splitting and word wrapping for per-span styled\n * text. Moved out of `src/rendering/rich-text-renderer.ts` (P1 element/\n * renderer split): these functions are pure measurement + layout, and the\n * element MODEL needs them for geometry (`CustomTransform.getBoundingBox`\n * computes wrapped-line heights), so they must live outside the paint\n * pipeline's import graph. The renderer re-exports them for its callers.\n *\n * Works in both the main thread and the export worker (canvas-based\n * measurement with an OffscreenCanvas fallback, like TextMetrics).\n */\n\nimport { buildFontString } from './TextMetrics.js';\nimport { spacedTextWidth } from './text-spacing.js';\nimport type { CharacterStyle } from '../types/index.js';\nimport { RichText } from '../types/index.js';\n\ntype MeasureContext = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\n/** Worker-compatible measurement context (mirrors TextMetrics.ts) */\nlet _measureCtx: MeasureContext | null = null;\n\nfunction getMeasureContext(): MeasureContext {\n if (!_measureCtx) {\n if (typeof OffscreenCanvas !== 'undefined') {\n const canvas = new OffscreenCanvas(1, 1);\n _measureCtx = canvas.getContext('2d') as OffscreenCanvasRenderingContext2D;\n } else if (typeof document !== 'undefined') {\n const canvas = document.createElement('canvas');\n _measureCtx = canvas.getContext('2d')!;\n } else {\n throw new Error('No canvas context available');\n }\n }\n return _measureCtx;\n}\n\n/**\n * Wrap rich text spans to fit within a given width, preserving character-level formatting\n *\n * This function handles character-level formatting correctly by:\n * 1. First merging consecutive spans with the same style\n * 2. Finding word boundaries (spaces) across spans\n * 3. Wrapping at word boundaries, not at span boundaries\n */\nexport function wrapRichTextSpans(\n spans: Array<{ text: string; style: CharacterStyle }>,\n maxWidth: number,\n element: {\n fontSize: number;\n fontFamily: string;\n bold?: boolean;\n italic?: boolean;\n /** Element-level letter-spacing default (px per character). */\n letterSpacing?: number;\n }\n): Array<{ text: string; style: CharacterStyle }[]> {\n const ctx = getMeasureContext();\n const lines: Array<{ text: string; style: CharacterStyle }[]> = [];\n\n // Helper to measure a text segment with specific styling\n const measureText = (text: string, style: CharacterStyle): number => {\n const fontSize = style.fontSize || element.fontSize;\n const fontFamily = style.fontFamily || element.fontFamily;\n const bold = style.bold !== undefined ? style.bold : element.bold || false;\n const italic = style.italic !== undefined ? style.italic : element.italic || false;\n const letterSpacing = style.letterSpacing !== undefined ? style.letterSpacing : element.letterSpacing ?? 0;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n return spacedTextWidth(ctx.measureText(text).width, text, letterSpacing);\n };\n\n // Helper to check if two styles match\n const stylesMatch = (s1: CharacterStyle, s2: CharacterStyle): boolean => {\n return (\n s1.fontSize === s2.fontSize &&\n s1.fontFamily === s2.fontFamily &&\n s1.color === s2.color &&\n s1.bold === s2.bold &&\n s1.italic === s2.italic &&\n s1.underline === s2.underline &&\n s1.strikethrough === s2.strikethrough &&\n s1.letterSpacing === s2.letterSpacing\n );\n };\n\n // Step 1: Merge consecutive spans with identical styles\n const mergedSpans: Array<{ text: string; style: CharacterStyle }> = [];\n spans.forEach((span) => {\n if (mergedSpans.length > 0 && stylesMatch(mergedSpans[mergedSpans.length - 1].style, span.style)) {\n mergedSpans[mergedSpans.length - 1].text += span.text;\n } else {\n mergedSpans.push({ text: span.text, style: span.style });\n }\n });\n\n // Step 2: Reconstruct text with character positions tracking which span each character belongs to\n let fullText = '';\n const charToSpan: number[] = []; // Maps character index to span index\n mergedSpans.forEach((span, spanIndex) => {\n for (let i = 0; i < span.text.length; i++) {\n fullText += span.text[i];\n charToSpan.push(spanIndex);\n }\n });\n\n // Step 3: Split text into words (by spaces)\n const words: Array<{ text: string; startIdx: number; endIdx: number }> = [];\n let currentWord = '';\n let wordStart = 0;\n\n for (let i = 0; i < fullText.length; i++) {\n const char = fullText[i];\n if (char === ' ') {\n if (currentWord.length > 0) {\n words.push({ text: currentWord, startIdx: wordStart, endIdx: i - 1 });\n currentWord = '';\n }\n // Add space as a separate \"word\"\n words.push({ text: ' ', startIdx: i, endIdx: i });\n } else {\n if (currentWord.length === 0) {\n wordStart = i;\n }\n currentWord += char;\n }\n }\n if (currentWord.length > 0) {\n words.push({ text: currentWord, startIdx: wordStart, endIdx: fullText.length - 1 });\n }\n\n // Step 4: Wrap words into lines\n let currentLine: { text: string; style: CharacterStyle }[] = [];\n let currentLineWidth = 0;\n\n const finishLine = () => {\n if (currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = [];\n currentLineWidth = 0;\n }\n };\n\n words.forEach((word, _wordIdx) => {\n // Build spans for this word from the original merged spans\n const wordSpans: Array<{ text: string; style: CharacterStyle }> = [];\n let currentSpanIdx = charToSpan[word.startIdx];\n let currentText = '';\n\n for (let charIdx = word.startIdx; charIdx <= word.endIdx; charIdx++) {\n const spanIdx = charToSpan[charIdx];\n if (spanIdx !== currentSpanIdx) {\n // Style changed, finish current span\n wordSpans.push({ text: currentText, style: mergedSpans[currentSpanIdx].style });\n currentText = fullText[charIdx];\n currentSpanIdx = spanIdx;\n } else {\n currentText += fullText[charIdx];\n }\n }\n if (currentText.length > 0) {\n wordSpans.push({ text: currentText, style: mergedSpans[currentSpanIdx].style });\n }\n\n // Measure word width\n const wordWidth = wordSpans.reduce((sum, span) => sum + measureText(span.text, span.style), 0);\n\n // Check if we need to wrap\n if (word.text === ' ') {\n // Space character - always add to current line (will be trimmed later based on paragraph boundaries)\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, wordSpans[0].style)) {\n lastSpan.text += ' ';\n } else {\n currentLine.push({ text: ' ', style: wordSpans[0].style });\n }\n } else {\n // Leading space - preserve it (width calc will trim if not paragraph start)\n currentLine.push({ text: ' ', style: wordSpans[0].style });\n }\n currentLineWidth += wordWidth;\n } else {\n // Regular word\n // Check if word itself is too wide (needs character-level breaking)\n if (wordWidth > maxWidth) {\n // Word is too wide - break it character by character\n // First, finish current line if it has content\n if (currentLine.length > 0) {\n finishLine();\n }\n\n // Break word at character level\n for (const span of wordSpans) {\n const chars = Array.from(span.text);\n let charBuffer = '';\n\n for (const char of chars) {\n const charWidth = measureText(char, span.style);\n\n // Check if adding this character would exceed max width\n if (currentLineWidth + charWidth > maxWidth && currentLineWidth > 0) {\n // Finish current line with buffer\n if (charBuffer.length > 0) {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += charBuffer;\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n charBuffer = '';\n }\n finishLine();\n }\n\n charBuffer += char;\n currentLineWidth += charWidth;\n }\n\n // Add remaining buffer to current line\n if (charBuffer.length > 0) {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += charBuffer;\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n }\n }\n } else {\n // Word fits on a line - check if we need to wrap to fit it\n if (currentLineWidth + wordWidth > maxWidth && currentLine.length > 0) {\n // Don't remove trailing space - it will be hidden during rendering based on paragraph rules\n // Removing it here breaks cursor position mapping\n finishLine();\n }\n\n // Add word spans to current line\n wordSpans.forEach((span) => {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += span.text;\n } else {\n currentLine.push({ ...span });\n }\n } else {\n currentLine.push({ ...span });\n }\n });\n currentLineWidth += wordWidth;\n }\n }\n });\n\n finishLine();\n\n // If no lines were created, return at least one empty line\n if (lines.length === 0) {\n lines.push([]);\n }\n\n return lines;\n}\n\n/**\n * Split rich text into lines by explicit newlines, preserving span formatting\n */\nexport function splitRichTextIntoLines(richText: RichText): Array<{ text: string; style: CharacterStyle }[]> {\n const lines: Array<{ text: string; style: CharacterStyle }[]> = [];\n let currentLine: { text: string; style: CharacterStyle }[] = [];\n\n richText.spans.forEach((span) => {\n // Split span text by newlines\n const parts = span.text.split('\\n');\n\n parts.forEach((part, i) => {\n if (part.length > 0) {\n // Add non-empty text to current line\n currentLine.push({ text: part, style: span.style });\n }\n\n // If this isn't the last part, we hit a newline\n if (i < parts.length - 1) {\n lines.push(currentLine);\n currentLine = [];\n }\n });\n });\n\n // Add the last line if it has content\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n // If no lines were created, return at least one empty line\n if (lines.length === 0) {\n lines.push([]);\n }\n\n return lines;\n}\n","/**\n * ResizeUtils - Common resize logic shared across transforms\n * Eliminates duplication in transform resize() methods\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport { MIN_WIDTH } from '../constants.js';\nimport type { TextElement } from './TextElement.js';\nimport type { TransformStartData } from '../types/index.js';\nimport { RichText } from '../types/index.js';\n\n/** Helper to get width from transform data (which has index signature) */\nfunction getTransformWidth(data: Record<string, unknown>): number | undefined {\n return typeof data.width === 'number' ? data.width : undefined;\n}\n\n/** Helper to set width on transform data */\nfunction setTransformWidth(data: Record<string, unknown>, value: number): void {\n data.width = value;\n}\n\n/**\n * Handle corner resize (uniform scaling)\n * Corner handles scale both fontSize and width proportionally\n */\nexport function handleCornerResize(element: TextElement, newWidth: number, startData: TransformStartData) {\n // Use width change only (X-axis in local coordinates) for scale factor\n // This provides intuitive image-like scaling behavior\n // For text elements with transformData.width, use that instead of bbox width to avoid\n // feedback loops from text wrapping changing the bbox dimensions\n const startTransformData = startData.transformData as Record<string, unknown>;\n const startTransformWidth = getTransformWidth(startTransformData);\n const startWidth = startTransformWidth !== undefined ? startTransformWidth : (startData.width ?? 0);\n const scale = newWidth / startWidth;\n\n // Apply uniform scale to font size\n // Always update font size to match the scale - this is critical to prevent\n // text wrapping flicker where width changes but fontSize lags behind\n // At smaller sizes, round to whole pixels for more stability\n // (captured before setFontSize() below mutates element.fontSize)\n const startFontSize = startData.fontSize ?? element.fontSize;\n const calculatedFontSize = startFontSize * scale;\n let newFontSize;\n if (calculatedFontSize < 16) {\n // At very small sizes, round to whole pixels\n newFontSize = Math.round(calculatedFontSize);\n } else {\n // At larger sizes, round to 0.5px increments\n newFontSize = Math.round(calculatedFontSize * 2) / 2;\n }\n element.setFontSize(newFontSize);\n\n // ALSO scale all character-level font sizes in rich text\n // Scale from the ORIGINAL startData.richText, not current state\n if (startData.richText && startData.richText instanceof RichText && typeof element.setRichText === 'function') {\n const originalRichText = startData.richText;\n const scaledRichText = originalRichText.clone();\n\n // Scale each span's fontSize by the same scale factor\n for (const span of scaledRichText.spans) {\n if (span.style.fontSize !== undefined) {\n const scaledCharSize = span.style.fontSize * scale;\n // Apply same rounding logic as element-level fontSize\n if (scaledCharSize < 16) {\n span.style.fontSize = Math.round(scaledCharSize);\n } else {\n span.style.fontSize = Math.round(scaledCharSize * 2) / 2;\n }\n }\n }\n // Update the element with scaled rich text\n element.setRichText(scaledRichText);\n }\n\n // Apply uniform scale to width\n const elemTransformData = element.transformData as Record<string, unknown>;\n const currentTransformWidth = getTransformWidth(elemTransformData);\n if (currentTransformWidth !== undefined && startTransformWidth !== undefined) {\n // Scale the container by the QUANTIZED fontSize ratio, not the raw scale.\n // Text width is linear in fontSize, so keeping width ∝ actual fontSize\n // guarantees text that fit at the start still fits after the resize.\n // Using the raw scale while fontSize is quantized separately leaves the\n // container fractionally too narrow at some scales — the text re-wraps\n // mid-resize (visible reflow jump, and bbox height stops scaling\n // proportionally). Math.ceil for the same reason: never round the\n // container below what the scaled text needs.\n const effectiveScale = startFontSize > 0 ? newFontSize / startFontSize : scale;\n const scaledWidth = startTransformWidth * effectiveScale;\n // Use a very small minimum (20px) to allow proportional scaling at small sizes\n const minWidth = 20;\n setTransformWidth(elemTransformData, Math.max(minWidth, Math.ceil(scaledWidth)));\n }\n\n // Position adjustment is handled by ResizeHandler's fixed corner logic\n // Don't adjust position here to avoid double adjustment\n}\n\n/**\n * Handle side resize (width only, no fontSize change)\n * Side handles only change width while keeping fontSize constant\n */\nexport function handleSideResize(element: TextElement, anchor: string, newWidth: number, startData: TransformStartData) {\n // Only handle left/right side anchors\n if (anchor !== 'middle-left' && anchor !== 'middle-right') {\n return;\n }\n\n // CRITICAL: Do NOT change fontSize for side handles\n element.fontSize = startData.fontSize ?? element.fontSize;\n\n // Update width only\n const elemTransformData = element.transformData as Record<string, unknown>;\n const currentTransformWidth = getTransformWidth(elemTransformData);\n if (currentTransformWidth !== undefined) {\n setTransformWidth(elemTransformData, Math.max(MIN_WIDTH, newWidth));\n\n // For center-based transforms, adjust position to keep opposite edge fixed\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Calculate the change in width\n const startTransformData = startData.transformData as Record<string, unknown>;\n const startTransformWidth = getTransformWidth(startTransformData);\n const oldWidth = startTransformWidth ?? startData.width ?? 0;\n const newTransformWidth = getTransformWidth(elemTransformData) ?? 0;\n const widthChange = newTransformWidth - oldWidth;\n\n // The center needs to shift by half the width change\n if (anchor === 'middle-left') {\n // Left edge moving, right edge fixed\n element.x = startData.x - (widthChange / 2) * cos;\n element.y = startData.y - (widthChange / 2) * sin;\n } else if (anchor === 'middle-right') {\n // Right edge moving, left edge fixed\n element.x = startData.x + (widthChange / 2) * cos;\n element.y = startData.y + (widthChange / 2) * sin;\n }\n }\n}\n\n/**\n * Standard resize implementation for transforms with width-based containers\n * Handles both corner (uniform scale) and side (width only) handles\n */\nexport function standardResize(element: TextElement, anchor: string, newWidth: number, _newHeight: number, startData: TransformStartData) {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n\n if (isCornerHandle) {\n handleCornerResize(element, newWidth, startData);\n } else if (isSideHandle) {\n handleSideResize(element, anchor, newWidth, startData);\n }\n}\n","/**\n * CustomTransform - Straight text (simplest transform)\n * This is the baseline implementation that others build upon\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth, wrapText, calculateVisualBoundsWithSpaceCollapsing, getFontMetrics } from '../core/TextMetrics.js';\nimport { getTextTransformPainter } from '../core/paint-registry.js';\nimport { splitRichTextIntoLines, wrapRichTextSpans } from '../core/rich-text-layout.js';\nimport { standardResize } from '../core/ResizeUtils.js';\nimport { MIN_WIDTH, HORIZONTAL_PADDING, LINE_HEIGHT_MULTIPLIER } from '../constants.js';\nimport type { CustomElementConfig, CustomTransformData, BoundingBox, ResizeAnchor, CharacterStyle, TransformStartData } from '../types/index.js';\n\nexport class CustomTransform extends TextElement {\n declare transformData: CustomTransformData;\n _lockedLineCount?: number; // Temporary property used during resize operations only\n\n constructor(config: Partial<CustomElementConfig> = {}) {\n super(config);\n this.transformType = 'custom';\n // _lockedLineCount is not restored from config - it's temporary and not serialized\n\n // Custom transform data: controlPoints and width\n // If no width is provided, auto-fit to text content\n const data = config.transformData;\n const width =\n data?.width !== undefined\n ? data.width\n : Math.max(\n MIN_WIDTH,\n measureTextWidth(this.text, this.fontSize, this.fontFamily, this.bold, this.italic, this.letterSpacing ?? 0) +\n HORIZONTAL_PADDING * 2\n );\n\n this.transformData = {\n type: 'custom',\n controlPoints: data?.controlPoints ?? [],\n width: width,\n };\n }\n\n /**\n * Calculate height for rich text considering per-character fonts\n * Returns the total height based on the tallest font in each line\n */\n private calculateRichTextHeight(): number {\n const richText = this.getRichText();\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Split into lines and wrap\n const explicitLines = splitRichTextIntoLines(richText);\n\n // Build wrapped lines\n const wrappedLines: Array<{ text: string; style: CharacterStyle }[]> = [];\n\n explicitLines.forEach((lineSpans) => {\n const wrapped = wrapRichTextSpans(lineSpans, availableWidth, {\n fontSize: this.fontSize,\n fontFamily: this.fontFamily,\n bold: this.bold,\n italic: this.italic,\n letterSpacing: this.letterSpacing,\n });\n wrapped.forEach((wLine) => {\n wrappedLines.push(wLine);\n });\n });\n\n if (wrappedLines.length === 0) {\n return 0;\n }\n\n // Calculate max font height across all spans to get proper total height\n // This ensures the bounding box encompasses all characters regardless of font size\n let maxFontHeight = 0;\n let maxAscent = 0;\n\n wrappedLines.forEach((lineSpans) => {\n lineSpans.forEach((span) => {\n const spanFontSize = span.style.fontSize !== undefined ? span.style.fontSize : this.fontSize;\n const spanFontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : this.fontFamily;\n const spanBold = span.style.bold !== undefined ? span.style.bold : this.bold;\n const spanItalic = span.style.italic !== undefined ? span.style.italic : this.italic;\n\n const metrics = getFontMetrics(spanFontSize, spanFontFamily, spanBold, spanItalic);\n maxFontHeight = Math.max(maxFontHeight, metrics.height);\n maxAscent = Math.max(maxAscent, metrics.ascent);\n });\n });\n\n // Use element-level line spacing for consistency with rendering\n const lineSpacing = this.fontSize * (this.lineHeight ?? LINE_HEIGHT_MULTIPLIER);\n\n // Calculate total height\n // The rendering positions lines at lineSpacing intervals, using element-level font metrics\n // But individual characters can extend beyond this if they have larger fonts\n // We need to account for:\n // 1. The space taken by line positioning: (numLines - 1) * lineSpacing\n // 2. The height of the tallest font (to ensure all characters fit)\n if (wrappedLines.length === 1) {\n return maxFontHeight;\n } else {\n // For multi-line: standard line spacing + room for tallest font\n const elementMetrics = getFontMetrics(this.fontSize, this.fontFamily, this.bold, this.italic);\n const standardHeight = (wrappedLines.length - 1) * lineSpacing + elementMetrics.height;\n\n // Check if any span extends beyond the standard height\n // The extra height needed is the difference between max ascent and element ascent\n // (since taller fonts extend upward from the baseline)\n const extraAscent = Math.max(0, maxAscent - elementMetrics.ascent);\n const extraDescent = Math.max(0, (maxFontHeight - maxAscent) - (elementMetrics.height - elementMetrics.ascent));\n\n return standardHeight + extraAscent + extraDescent;\n }\n }\n\n /**\n * Check if rich text has per-character formatting that differs from element defaults\n */\n private hasPerCharacterFormatting(): boolean {\n const richText = this.getRichText();\n\n for (const span of richText.spans) {\n // Check if any span has explicit fontSize or fontFamily that differs from element defaults\n if (span.style.fontSize !== undefined && span.style.fontSize !== this.fontSize) {\n return true;\n }\n if (span.style.fontFamily !== undefined && span.style.fontFamily !== this.fontFamily) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Get bounding box in world coordinates\n * For straight text with center-based positioning (x, y is the center)\n * This should match the actual text bounds including wrapping\n */\n getBoundingBox(): BoundingBox {\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Check if we have per-character formatting that affects height\n let height: number;\n if (this.hasPerCharacterFormatting()) {\n // Use rich text calculation for per-character fonts\n height = this.calculateRichTextHeight();\n } else {\n // Use standard calculation for uniform text\n const result = calculateVisualBoundsWithSpaceCollapsing(\n this.text,\n availableWidth,\n this.fontSize,\n this.fontFamily,\n this.bold,\n this.italic,\n this._lockedLineCount,\n true, // strict: wrap exactly at maxWidth\n { letterSpacing: this.letterSpacing, lineHeight: this.lineHeight }\n );\n height = result.height;\n }\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n /**\n * Get visual bounding box (container bounds for selection display)\n * For CustomTransform, this shows the container width (transformData.width)\n * and the actual height based on text wrapping\n * (x, y) is the center of the element\n */\n getVisualBoundingBox(): BoundingBox {\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Check if we have per-character formatting that affects height\n let visualHeight: number;\n if (this.hasPerCharacterFormatting()) {\n // Use rich text calculation for per-character fonts\n visualHeight = this.calculateRichTextHeight();\n } else {\n // Use standard calculation for uniform text\n const result = calculateVisualBoundsWithSpaceCollapsing(\n this.text,\n availableWidth,\n this.fontSize,\n this.fontFamily,\n this.bold,\n this.italic,\n this._lockedLineCount,\n true, // strict: wrap exactly at maxWidth\n { letterSpacing: this.letterSpacing, lineHeight: this.lineHeight }\n );\n visualHeight = result.height;\n }\n\n // Use the container width (transformData.width) for the visual box\n // This shows the resizable container, not just the text\n const visualWidth = this.transformData.width;\n\n // Return container bounds (centered at x, y)\n return {\n x: this.x - visualWidth / 2,\n y: this.y - visualHeight / 2,\n width: visualWidth,\n height: visualHeight,\n };\n }\n\n /**\n * Get rotation anchor (center of the visual bounding box)\n * CustomTransform rotates around the center of the actual text, including multi-line\n */\n getRotationAnchor(): { x: number; y: number } {\n const visualBbox = this.getVisualBoundingBox();\n return {\n x: visualBbox.x + visualBbox.width / 2,\n y: visualBbox.y + visualBbox.height / 2,\n };\n }\n\n /**\n * Render the straight text\n * (x, y) is the center of the element, we rotate around this center point\n * Now uses shared rendering function that works in both main thread and worker\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n // No inline outline drawing needed here.\n\n const elementOpacity = this.opacity ?? 1;\n const hasStroke = !!this.stroke?.enabled;\n\n // When element has opacity < 1 AND a stroke, render to an offscreen canvas\n // at full opacity to prevent stroke/fill compounding (same fix as ShapeElement).\n if (elementOpacity < 1 && hasStroke) {\n this.renderWithOffscreen(ctx, elementOpacity);\n return;\n }\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (elementOpacity !== 1) {\n ctx.globalAlpha = elementOpacity;\n }\n\n // Render text (fill + stroke handled inside the registered painter)\n getTextTransformPainter('custom')(ctx, this.toJSON());\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n /**\n * Render stroke+fill to an offscreen canvas at full opacity, then composite\n * at the element's opacity. Prevents stroke inner half showing through fill.\n */\n private renderWithOffscreen(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n const strokeWidth = this.stroke?.width || 0;\n const padding = strokeWidth + 20;\n const bbox = this.getVisualBoundingBox();\n const offW = Math.ceil(bbox.width + padding * 2);\n const offH = Math.ceil(bbox.height + padding * 2);\n\n const offCanvas = document.createElement('canvas');\n offCanvas.width = offW;\n offCanvas.height = offH;\n const offCtx = offCanvas.getContext('2d');\n if (!offCtx) {\n // Fallback: render directly with compounding\n const prev = ctx.globalAlpha;\n ctx.globalAlpha = elementOpacity;\n getTextTransformPainter('custom')(ctx, this.toJSON());\n ctx.globalAlpha = prev;\n return;\n }\n\n // Shift transforms so the element renders centered on the offscreen canvas\n const offsetX = bbox.x + bbox.width / 2;\n const offsetY = bbox.y + bbox.height / 2;\n\n offCtx.translate(offW / 2 - offsetX, offH / 2 - offsetY);\n\n // Render at full opacity\n getTextTransformPainter('custom')(offCtx, this.toJSON());\n\n // Composite onto main canvas at element opacity\n ctx.save();\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, offsetX - offW / 2, offsetY - offH / 2);\n ctx.restore();\n }\n\n /**\n * Override getTransformStartData to capture initial line count\n */\n override getTransformStartData(): TransformStartData {\n const startData = super.getTransformStartData();\n\n // Capture current line count at current width\n // This calculates how the text is CURRENTLY wrapping (without any lock from previous resizes)\n // because _lockedLineCount is not serialized and won't be present on the element\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n const lines = wrapText(this.text, availableWidth, this.fontSize, this.fontFamily, this.bold, this.italic, undefined, false, {\n letterSpacing: this.letterSpacing,\n });\n startData.lineCount = lines.length;\n\n return startData;\n }\n\n /**\n * Handle resize using standard resize logic\n * Corner handles: uniform scale (fontSize and width both change) - line count locked\n * Side handles: only width changes - text re-wraps naturally\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n // Lock should already be set in startResize() for corner handles\n // For side handles, clear the lock to allow natural re-wrapping\n const isCornerHandle =\n anchor === 'top-left' || anchor === 'top-right' || anchor === 'bottom-left' || anchor === 'bottom-right';\n\n if (!isCornerHandle) {\n // Clear lock for side handles (which should allow text to re-wrap)\n this._lockedLineCount = undefined;\n }\n\n // Perform the standard resize\n standardResize(this, anchor, newWidth, newHeight, startData);\n\n // CRITICAL FIX: After resize, if we have a lock, ensure width is sufficient for the locked line count\n // This prevents the text from wrapping to more lines when the lock is cleared\n if (this._lockedLineCount !== undefined && this._lockedLineCount > 0) {\n // Calculate the actual text width (all words on one line if locked to 1 line)\n const fullTextWidth = measureTextWidth(\n this.text,\n this.fontSize,\n this.fontFamily,\n this.bold,\n this.italic,\n this.letterSpacing ?? 0\n );\n\n // Calculate minimum width needed to fit text on the locked line count\n // For 1 line: need full text width + padding (no extra buffer for proper alignment with images)\n // For multiple lines: use proportional estimate\n const minWidth = fullTextWidth / this._lockedLineCount + HORIZONTAL_PADDING * 2;\n\n // Ensure width is never below minimum\n if (this.transformData.width < minWidth) {\n this.transformData.width = Math.ceil(minWidth);\n }\n }\n }\n\n /**\n * Override setText - don't auto-adjust width\n * Let text wrap naturally within the current container width\n */\n setText(newText: string): void {\n super.setText(newText);\n // Don't auto-adjust width - maintain current container width\n // This allows text to wrap to multiple lines as needed\n\n // Clear locked line count when text content changes\n this._lockedLineCount = undefined;\n }\n\n /**\n * Get enabled anchors\n * Straight text supports all 8 handles\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'middle-left', 'middle-right'];\n }\n\n /**\n * Clone this element\n * Override to preserve temporary _lockedLineCount during resize operations\n * (but it still won't be saved in JSON exports)\n */\n clone(): CustomTransform {\n const cloned = super.clone() as CustomTransform;\n\n // Preserve the lock state during cloning (for smooth resize)\n // This won't be saved in JSON because toJSON() doesn't include it\n if (this._lockedLineCount !== undefined) {\n cloned._lockedLineCount = this._lockedLineCount;\n }\n\n return cloned;\n }\n\n /**\n * Serialize with transform data\n * NOTE: _lockedLineCount is intentionally NOT serialized\n * The lock is temporary during resize and should not persist after\n */\n toJSON(): CustomElementConfig {\n const json = {\n ...super.toJSON(),\n transformType: 'custom' as const,\n transformData: {\n type: 'custom' as const,\n // Copy point objects so serialized output never aliases the live array\n controlPoints: this.transformData.controlPoints.map((p) => ({ ...p })),\n width: this.transformData.width,\n },\n };\n\n // _lockedLineCount is NOT included in serialization\n // This ensures the lock doesn't persist after a resize operation ends\n\n return json;\n }\n}\n\nexport default CustomTransform;\n","/**\n * Geometry utilities for text element transformations\n * Ported from shared/utils.js and enhanced for custom canvas\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport { ROTATION_HANDLE_DISTANCE } from '../constants.js';\nimport type { BoundingBox, Point, ResizeAnchor } from '../types/index.js';\n\n/**\n * Calculate position to keep opposite corner fixed during resize\n * This is the core algorithm that makes corner dragging feel natural\n *\n * @param {Object} startData - Transform start state {x, y, width, height}\n * @param {Object} newDimensions - New dimensions {width, height}\n * @param {string} activeAnchor - Which corner is being dragged\n * @param {number} rotation - Rotation in degrees\n * @returns {Object} New position {x, y}\n */\nexport function calculateFixedCornerPosition(startData: { x: number; y: number; width: number; height: number }, newDimensions: { width: number; height: number }, activeAnchor: string, rotation: number) {\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Determine which corner should stay fixed (in local coords at START)\n let fixedLocalX = 0;\n let fixedLocalY = 0;\n\n if (activeAnchor === 'top-left') {\n // Fix bottom-right\n fixedLocalX = startData.width;\n fixedLocalY = startData.height;\n } else if (activeAnchor === 'top-right') {\n // Fix bottom-left\n fixedLocalX = 0;\n fixedLocalY = startData.height;\n } else if (activeAnchor === 'bottom-left') {\n // Fix top-right\n fixedLocalX = startData.width;\n fixedLocalY = 0;\n } else if (activeAnchor === 'bottom-right') {\n // Fix top-left\n fixedLocalX = 0;\n fixedLocalY = 0;\n }\n\n // Calculate the fixed corner's position in world coordinates (from start of transform)\n const fixedWorldX = startData.x + (fixedLocalX * cos - fixedLocalY * sin);\n const fixedWorldY = startData.y + (fixedLocalX * sin + fixedLocalY * cos);\n\n // Where that corner is NOW in the new dimensions\n let newFixedLocalX = 0;\n let newFixedLocalY = 0;\n\n if (activeAnchor === 'top-left') {\n newFixedLocalX = newDimensions.width;\n newFixedLocalY = newDimensions.height;\n } else if (activeAnchor === 'top-right') {\n newFixedLocalX = 0;\n newFixedLocalY = newDimensions.height;\n } else if (activeAnchor === 'bottom-left') {\n newFixedLocalX = newDimensions.width;\n newFixedLocalY = 0;\n } else if (activeAnchor === 'bottom-right') {\n newFixedLocalX = 0;\n newFixedLocalY = 0;\n }\n\n // Calculate new position so the fixed corner stays in the same world position\n const newX = fixedWorldX - (newFixedLocalX * cos - newFixedLocalY * sin);\n const newY = fixedWorldY - (newFixedLocalX * sin + newFixedLocalY * cos);\n\n return { x: newX, y: newY };\n}\n\n/**\n * Calculate rotation handle position\n * Positioned below the bottom edge, centered horizontally\n * Distance scales inversely with zoom to maintain consistent screen space distance\n *\n * @param {Object} bbox - Bounding box {x, y, width, height}\n * @param {number} rotation - Rotation in DEGREES (not radians - function converts internally)\n * @param {Object} rotationAnchor - Point to rotate around {x, y}\n * @param {number} zoom - Current zoom level (default 1.0)\n * @returns {Object} Handle position {x, y}\n *\n * IMPORTANT: This function expects rotation in DEGREES and converts to radians internally.\n * Do NOT pass radians to this function or you'll get incorrect positioning.\n * Example: Pass 30 for 30°, NOT 0.524 radians\n */\nexport function calculateRotationHandlePosition(bbox: BoundingBox, rotation: number, rotationAnchor: Point, zoom: number = 1.0) {\n // Local coordinates relative to bbox top-left: center of bottom edge + distance\n // Scale distance inversely with zoom to maintain consistent screen space distance\n // At zoom=0.33: distance = 70 / 0.33 = 212px world → 70px screen\n const localX = bbox.width / 2;\n const localY = bbox.height + (ROTATION_HANDLE_DISTANCE / zoom);\n\n // World position before rotation\n const worldX = bbox.x + localX;\n const worldY = bbox.y + localY;\n\n // Rotate around the rotation anchor\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to rotation anchor, rotate, translate back\n const dx = worldX - rotationAnchor.x;\n const dy = worldY - rotationAnchor.y;\n\n const rotatedX = dx * cos - dy * sin;\n const rotatedY = dx * sin + dy * cos;\n\n return {\n x: rotationAnchor.x + rotatedX,\n y: rotationAnchor.y + rotatedY,\n };\n}\n\n/**\n * Calculate all 8 resize handle positions\n *\n * @param {Object} bbox - Bounding box {x, y, width, height}\n * @param {number} rotation - Rotation in degrees\n * @param {Object} rotationAnchor - Point to rotate around {x, y}\n * @returns {Object[]} Array of handles with {type, anchor, x, y, cursor}\n */\nexport function calculateResizeHandles(bbox: BoundingBox, rotation: number, rotationAnchor: Point) {\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Transform a local point (relative to bbox top-left) to world coordinates\n // Then rotate around the rotation anchor\n const transform = (localX: number, localY: number) => {\n // World position before rotation\n const worldX = bbox.x + localX;\n const worldY = bbox.y + localY;\n\n // Translate to rotation anchor, rotate, translate back\n const dx = worldX - rotationAnchor.x;\n const dy = worldY - rotationAnchor.y;\n\n const rotatedX = dx * cos - dy * sin;\n const rotatedY = dx * sin + dy * cos;\n\n return {\n x: rotationAnchor.x + rotatedX,\n y: rotationAnchor.y + rotatedY,\n };\n };\n\n const { width, height } = bbox;\n\n // Helper to create a handle with cursor based on world position\n const createHandle = (type: string, anchor: ResizeAnchor, localX: number, localY: number) => {\n const worldPos = transform(localX, localY);\n const cursor = getCursorForWorldPosition(\n worldPos.x,\n worldPos.y,\n rotationAnchor.x,\n rotationAnchor.y,\n type,\n rotation,\n anchor\n );\n return {\n type,\n anchor,\n ...worldPos,\n cursor,\n };\n };\n\n return [\n // Corner handles\n createHandle('corner', 'top-left', 0, 0),\n createHandle('corner', 'top-right', width, 0),\n createHandle('corner', 'bottom-left', 0, height),\n createHandle('corner', 'bottom-right', width, height),\n // Side handles\n createHandle('edge', 'middle-left', 0, height / 2),\n createHandle('edge', 'middle-right', width, height / 2),\n createHandle('edge', 'middle-top', width / 2, 0),\n createHandle('edge', 'middle-bottom', width / 2, height),\n ];\n}\n\n/**\n * Test if a point is inside a circle\n */\nexport function hitTestCircle(px: number, py: number, cx: number, cy: number, radius: number): boolean {\n const dx = px - cx;\n const dy = py - cy;\n return dx * dx + dy * dy <= radius * radius;\n}\n\n/**\n * Test if a point is inside a rotated rectangle\n */\nexport function hitTestRect(px: number, py: number, rect: BoundingBox, rotation: number): boolean {\n // Transform point to local coordinates\n // Positive rotation to undo the negative forward rotation\n const rotationRad = RotationUtils.toRadiansInverse(rotation); // Inverse rotation\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to origin\n const translatedX = px - rect.x;\n const translatedY = py - rect.y;\n\n // Rotate\n const localX = translatedX * cos - translatedY * sin;\n const localY = translatedX * sin + translatedY * cos;\n\n // Test against axis-aligned box\n return localX >= 0 && localX <= rect.width && localY >= 0 && localY <= rect.height;\n}\n\n/**\n * Measure text width using canvas API\n * @deprecated Use TextMetrics.measureTextWidth instead\n */\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontFamily: string = 'Arial',\n bold: boolean = false,\n italic: boolean = false\n): number {\n // Re-export from TextMetrics for backwards compatibility\n // This will be removed in a future version\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d')!;\n const weight = bold ? 'bold' : 'normal';\n const style = italic ? 'italic' : 'normal';\n ctx.font = `${style} ${weight} ${fontSize}px ${fontFamily}`;\n return ctx.measureText(text).width;\n}\n\n/**\n * Calculate bounding box center\n */\nexport function getBoundingBoxCenter(bbox: BoundingBox) {\n return {\n x: bbox.x + bbox.width / 2,\n y: bbox.y + bbox.height / 2,\n };\n}\n\n/**\n * Calculate angle between two points\n */\nexport function calculateAngle(centerX: number, centerY: number, pointX: number, pointY: number): number {\n return Math.atan2(pointY - centerY, pointX - centerX);\n}\n\n/**\n * Rotate a point around a center\n */\nexport function rotatePoint(px: number, py: number, cx: number, cy: number, angle: number) {\n const cos = Math.cos(angle);\n const sin = Math.sin(angle);\n const dx = px - cx;\n const dy = py - cy;\n\n return {\n x: cx + (dx * cos - dy * sin),\n y: cy + (dx * sin + dy * cos),\n };\n}\n\n/**\n * Calculate distance between two points\n */\nexport function distance(x1: number, y1: number, x2: number, y2: number): number {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return Math.sqrt(dx * dx + dy * dy);\n}\n\n/**\n * Calculate distance from a point to a horizontal line\n */\nexport function distanceToHorizontalLine(_px: number, py: number, lineY: number): number {\n return Math.abs(py - lineY);\n}\n\n/**\n * Calculate distance from a point to a vertical line\n */\nexport function distanceToVerticalLine(px: number, _py: number, lineX: number): number {\n return Math.abs(px - lineX);\n}\n\n/**\n * Calculate distance from a point to a line segment\n * @param {number} px - Point X\n * @param {number} py - Point Y\n * @param {number} x1 - Line start X\n * @param {number} y1 - Line start Y\n * @param {number} x2 - Line end X\n * @param {number} y2 - Line end Y\n * @returns {number} Distance to line segment\n */\nexport function distanceToLineSegment(px: number, py: number, x1: number, y1: number, x2: number, y2: number): number {\n const lineLength = distance(x1, y1, x2, y2);\n if (lineLength === 0) return distance(px, py, x1, y1);\n\n // Calculate parameter t that determines closest point on line\n const t = Math.max(0, Math.min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / (lineLength * lineLength)));\n\n // Calculate closest point on line segment\n const closestX = x1 + t * (x2 - x1);\n const closestY = y1 + t * (y2 - y1);\n\n return distance(px, py, closestX, closestY);\n}\n\n/**\n * Check if two values are within a threshold\n * Useful for snapping detection\n */\nexport function isNear(value1: number, value2: number, threshold: number): boolean {\n return Math.abs(value1 - value2) <= threshold;\n}\n\n/**\n * Get the correct resize cursor accounting for rotation\n * @param {string} anchor - Handle anchor position (e.g., 'top-left', 'top', 'right')\n * @param {number} rotation - Rotation in degrees\n * @returns {string} CSS cursor value\n */\nexport function getRotatedResizeCursor(anchor: string, rotation: number): string {\n // Base cursor angles for each anchor (in degrees, 0 = east)\n const baseCursorAngles: Record<string, number> = {\n top: -90, // ns-resize (north-south)\n bottom: -90, // ns-resize\n left: 0, // ew-resize (east-west)\n right: 0, // ew-resize\n 'top-left': -45, // nwse-resize (northwest-southeast)\n 'bottom-right': -45, // nwse-resize\n 'top-right': 45, // nesw-resize (northeast-southwest)\n 'bottom-left': 45, // nesw-resize\n };\n\n // Get base angle for this anchor\n const baseAngle = baseCursorAngles[anchor] || 0;\n\n // Add rotation to get final angle\n let finalAngle = baseAngle + rotation;\n\n // Normalize to 0-180 range (cursors repeat every 180 degrees)\n finalAngle = ((finalAngle % 180) + 180) % 180;\n\n // Map angle ranges to cursor types\n // Each cursor covers a 45-degree range\n if (finalAngle >= 157.5 || finalAngle < 22.5) {\n return 'ew-resize';\n } else if (finalAngle >= 22.5 && finalAngle < 67.5) {\n return 'nwse-resize';\n } else if (finalAngle >= 67.5 && finalAngle < 112.5) {\n return 'ns-resize';\n } else if (finalAngle >= 112.5 && finalAngle < 157.5) {\n return 'nesw-resize';\n }\n\n return 'default';\n}\n\n/**\n * Get cursor for a handle based on its world position relative to a center point\n * @param {number} handleWorldX - Handle X position in world coordinates\n * @param {number} handleWorldY - Handle Y position in world coordinates\n * @param {number} centerWorldX - Center X position in world coordinates\n * @param {number} centerWorldY - Center Y position in world coordinates\n * @param {string} handleType - 'corner' or 'edge'\n * @param {number} rotation - Element rotation in degrees\n * @param {string} anchor - Handle anchor position (e.g., 'top-left', 'bottom-right')\n * @returns {string} CSS cursor value\n */\nexport function getCursorForWorldPosition(\n handleWorldX: number,\n handleWorldY: number,\n centerWorldX: number,\n centerWorldY: number,\n handleType: string,\n rotation: number = 0,\n anchor: string = ''\n): string {\n // Calculate angle from center to handle in world space\n const dx = handleWorldX - centerWorldX;\n const dy = handleWorldY - centerWorldY;\n\n // Calculate angle in degrees (0 = east, 90 = south, 180 = west, 270 = north)\n let angle = Math.atan2(dy, dx) * (180 / Math.PI);\n\n // Normalize to 0-360\n angle = ((angle % 360) + 360) % 360;\n\n if (handleType === 'corner') {\n // For non-rotated elements (rotation = 0), always show diagonal cursors for corners\n // This provides more intuitive UX even for wide/tall elements where the angle\n // calculation would otherwise suggest horizontal/vertical cursors\n const isUnrotated = Math.abs(rotation) < 0.1; // Within 0.1 degrees of zero\n if (isUnrotated) {\n // Map corner anchors to their diagonal cursors\n if (anchor === 'top-left' || anchor === 'bottom-right') {\n return 'nwse-resize';\n } else if (anchor === 'top-right' || anchor === 'bottom-left') {\n return 'nesw-resize';\n }\n }\n\n // For rotated elements, calculate cursor based on actual angle\n // Determine which quadrant and return appropriate diagonal cursor\n // nwse-resize: top-left to bottom-right (NW-SE diagonal, ~315-45° and ~135-225°)\n // nesw-resize: top-right to bottom-left (NE-SW diagonal, ~45-135° and ~225-315°)\n\n // Normalize to 0-180 for cursor selection (cursors repeat every 180°)\n const normalizedAngle = angle % 180;\n\n // Map to cursor based on angle ranges\n if (normalizedAngle >= 157.5 || normalizedAngle < 22.5) {\n // East/West: horizontal resize\n return 'ew-resize';\n } else if (normalizedAngle >= 22.5 && normalizedAngle < 67.5) {\n // Southeast/Northwest diagonal\n return 'nwse-resize';\n } else if (normalizedAngle >= 67.5 && normalizedAngle < 112.5) {\n // South/North: vertical resize\n return 'ns-resize';\n } else {\n // Southwest/Northeast diagonal\n return 'nesw-resize';\n }\n } else {\n // For edges, determine if it's more horizontal or vertical\n const normalizedAngle = angle % 180;\n\n if (normalizedAngle >= 45 && normalizedAngle < 135) {\n // More vertical\n return 'ns-resize';\n } else {\n // More horizontal\n return 'ew-resize';\n }\n }\n}\n\n/**\n * Canvas Zoom Utilities\n *\n * CRITICAL: Canvas transforms affect visual properties differently:\n * - Line widths ARE affected by canvas transforms (scale with zoom)\n * - Font sizes in ctx.font are NOT affected by canvas transforms\n * - Dash patterns ARE affected by canvas transforms\n *\n * Use these utilities to maintain consistent visual appearance at all zoom levels.\n */\n\n/**\n * Extract the current canvas scale factor from the transform matrix.\n * This accounts for zoom, DPR (device pixel ratio), and any other scaling.\n *\n * @param ctx - Canvas rendering context\n * @returns Current scale factor (e.g., 0.33 for 33% zoom, 1.0 for 100%)\n *\n * @example\n * const scale = getCanvasScale(ctx); // 0.33 at 33% zoom\n * ctx.lineWidth = 3 / scale; // Renders as 3px visually at any zoom\n */\nexport function getCanvasScale(ctx: CanvasRenderingContext2D): number {\n // Fallback for test environments where getTransform() might not be available\n if (!ctx.getTransform) {\n return 1.0;\n }\n\n const transform = ctx.getTransform();\n // Calculate magnitude of the transform matrix (Euclidean norm of the first column)\n // This gives us the overall scale factor regardless of rotation\n return Math.sqrt(transform.a * transform.a + transform.b * transform.b);\n}\n\n/**\n * Set a line width that maintains consistent visual thickness at all zoom levels.\n *\n * Canvas transforms affect line widths, so a 3px line at 33% zoom appears as 1px.\n * This function applies inverse scaling to counteract the zoom effect.\n *\n * @param ctx - Canvas rendering context\n * @param desiredWidth - The visual width you want (in screen pixels)\n *\n * @example\n * // Selection border that's always 3px thick on screen\n * setZoomInvariantLineWidth(ctx, 3);\n * ctx.stroke();\n */\nexport function setZoomInvariantLineWidth(ctx: CanvasRenderingContext2D, desiredWidth: number): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? desiredWidth / scale : desiredWidth;\n}\n\n/**\n * Set a line dash pattern that maintains consistent visual appearance at all zoom levels.\n *\n * Canvas transforms affect dash patterns, so [8, 8] at 33% zoom appears as [2.64, 2.64].\n * This function applies inverse scaling to counteract the zoom effect.\n *\n * @param ctx - Canvas rendering context\n * @param pattern - The dash pattern you want (in screen pixels), e.g., [8, 8] for \"8px dash, 8px gap\"\n *\n * @example\n * // Dotted guide line that's always [8, 8] pattern on screen\n * setZoomInvariantLineWidth(ctx, 3);\n * setZoomInvariantDash(ctx, [8, 8]);\n * ctx.stroke();\n */\nexport function setZoomInvariantDash(ctx: CanvasRenderingContext2D, pattern: number[]): void {\n const scale = getCanvasScale(ctx);\n ctx.setLineDash(scale > 0 ? pattern.map(value => value / scale) : pattern);\n}\n\n/**\n * Helper to set both line width and dash pattern with zoom-invariant values.\n * Commonly used for selection visualization and guide lines.\n *\n * @param ctx - Canvas rendering context\n * @param width - Desired line width (in screen pixels)\n * @param dashPattern - Desired dash pattern (in screen pixels)\n *\n * @example\n * // Dotted guide line for selected arch text\n * ctx.strokeStyle = getThemeAccentColor();\n * setZoomInvariantStroke(ctx, 3, [8, 8]);\n * ctx.beginPath();\n * // ... draw path ...\n * ctx.stroke();\n * ctx.setLineDash([]); // Reset to solid\n */\nexport function setZoomInvariantStroke(\n ctx: CanvasRenderingContext2D,\n width: number,\n dashPattern: number[] = []\n): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? width / scale : width;\n if (dashPattern.length > 0) {\n ctx.setLineDash(scale > 0 ? dashPattern.map(value => value / scale) : dashPattern);\n }\n}\n\n/**\n * Set a dash pattern that scales with viewport size (appears longer when zoomed out).\n *\n * This creates viewport-adaptive dashes that maintain better visibility when viewing\n * a larger area of the canvas. The dashes are specified in world-space, so they scale\n * naturally with the zoom level:\n * - At 100% zoom: dashes appear at base size\n * - At 33% zoom (larger viewport): dashes appear smaller on screen but proportional to viewport\n * - At 300% zoom (zoomed in): dashes appear larger on screen but proportional to viewport\n *\n * @param ctx - Canvas rendering context\n * @param basePattern - Dash pattern in world-space pixels\n *\n * @example\n * // Guide line with world-space dashes (use larger values than screen-space)\n * setZoomInvariantLineWidth(ctx, 3); // Keep line width consistent\n * setViewportProportionalDash(ctx, [30, 15]); // Use larger values for visibility\n * ctx.stroke();\n */\nexport function setViewportProportionalDash(ctx: CanvasRenderingContext2D, basePattern: number[]): void {\n // Use pattern directly in world-space - canvas transform scales it naturally\n // Larger base values ensure visibility when zoomed out\n ctx.setLineDash(basePattern);\n}\n\n/**\n * Set both line width and dash pattern with hybrid scaling.\n * Line width stays zoom-invariant (consistent screen thickness).\n * Dash pattern uses world-space values (scales with viewport).\n *\n * This is the recommended approach for guide lines and selection visualization,\n * providing consistent visual thickness while maintaining proportional dash spacing.\n *\n * @param ctx - Canvas rendering context\n * @param width - Desired line width (in screen pixels, zoom-invariant)\n * @param basePattern - Dash pattern in world-space pixels (use larger values than screen-space)\n *\n * @example\n * // Circle transform guide: consistent thickness, viewport-proportional dashes\n * ctx.strokeStyle = getThemeAccentColor();\n * setViewportProportionalStroke(ctx, 3, [30, 15]); // 30px dashes in world-space\n * ctx.arc(0, 0, radius, 0, Math.PI * 2);\n * ctx.stroke();\n * ctx.setLineDash([]); // Reset\n */\nexport function setViewportProportionalStroke(\n ctx: CanvasRenderingContext2D,\n width: number,\n basePattern: number[] = []\n): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? width / scale : width; // Zoom-invariant width\n if (basePattern.length > 0) {\n ctx.setLineDash(basePattern); // Viewport-proportional dashes (world-space)\n }\n}\n","/**\n * CircleTransform - Text follows a circular path\n * Ported from EditableTextPath.jsx circle mode\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport {\n measureTextWidth,\n calculateFixedCornerPosition,\n} from '../core/GeometryUtils.js';\nimport type { CircleElementConfig, CircleTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class CircleTransform extends TextElement {\n declare transformData: CircleTransformData;\n\n constructor(config: Partial<CircleElementConfig> = {}) {\n super(config);\n this.transformType = 'circle';\n\n // Circle transform data: center point and radius point\n // Position (x, y) is the center of the circle\n const data = config.transformData;\n this.transformData = {\n type: 'circle',\n radius: data?.radius ?? 100,\n scale: data?.scale ?? 1,\n reverse: data?.reverse ?? false,\n };\n }\n\n /**\n * Get bounding box in world coordinates\n * For circle, this is center ± radius (diameter)\n * This is used for transform math (resize, rotation)\n */\n getBoundingBox(): BoundingBox {\n const effectiveRadius = this.transformData.radius * this.transformData.scale;\n const diameter = effectiveRadius * 2;\n\n return {\n x: this.x - effectiveRadius,\n y: this.y - effectiveRadius,\n width: diameter,\n height: diameter,\n };\n }\n\n /**\n * Get visual bounding box (tight fit around actual text)\n * For circular text, calculate the arc bounds where text actually appears\n */\n getVisualBoundingBox(): BoundingBox {\n const effectiveRadius = this.transformData.radius * this.transformData.scale;\n const effectiveFontSize = this.fontSize * this.transformData.scale;\n\n // Measure text to know the arc span\n const actualTextWidth = measureTextWidth(this.text, effectiveFontSize, this.fontFamily, this.bold, this.italic);\n const arcAngle = actualTextWidth / effectiveRadius; // Radians\n\n // Text is centered at top (-PI/2), calculate bounds\n // The text extends arcAngle/2 on each side\n const startAngle = -Math.PI / 2 - arcAngle / 2;\n const endAngle = -Math.PI / 2 + arcAngle / 2;\n\n // Find min/max x and y for the text arc\n // Include some padding for character height (extend radius outward)\n // If circle is too small for the font, clamp innerRadius to a small positive value\n const innerRadius =\n effectiveRadius > effectiveFontSize / 2 ? effectiveRadius - effectiveFontSize / 2 : effectiveRadius * 0.1; // Use 10% of radius as minimum\n const outerRadius = effectiveRadius + effectiveFontSize / 2;\n\n let minX = Infinity,\n maxX = -Infinity;\n let minY = Infinity,\n maxY = -Infinity;\n\n // Sample points along the arc\n const samples = 10;\n for (let i = 0; i <= samples; i++) {\n const angle = startAngle + (endAngle - startAngle) * (i / samples);\n\n // Inner and outer points\n const innerX = this.x + Math.cos(angle) * innerRadius;\n const innerY = this.y + Math.sin(angle) * innerRadius;\n const outerX = this.x + Math.cos(angle) * outerRadius;\n const outerY = this.y + Math.sin(angle) * outerRadius;\n\n minX = Math.min(minX, innerX, outerX);\n maxX = Math.max(maxX, innerX, outerX);\n minY = Math.min(minY, innerY, outerY);\n maxY = Math.max(maxY, innerY, outerY);\n }\n\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n /**\n * Render the circular text\n * Uses canvas arc path like SVG path in original\n * Now uses shared rendering function\n */\n\n /**\n * Handle resize\n * For circle, we maintain uniform scale and keep scale on the transform\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n const circleStartData = startData.transformData as unknown as CircleTransformData;\n\n // Calculate uniform scale from average of width/height change\n const avgDimension = (newWidth + newHeight) / 2;\n const startAvgDimension = (startData.width! + startData.height!) / 2;\n const scaleChange = avgDimension / startAvgDimension;\n\n // Apply scale change relative to the starting scale\n const newScale = circleStartData.scale * scaleChange;\n\n // Clamp scale\n const clampedScale = Math.max(0.1, Math.min(10, newScale));\n this.transformData.scale = clampedScale;\n\n // Calculate new position to keep opposite corner fixed\n const bbox = this.getBoundingBox();\n const newPosition = calculateFixedCornerPosition(\n {\n x: startData.x - circleStartData.radius * circleStartData.scale,\n y: startData.y - circleStartData.radius * circleStartData.scale,\n width: circleStartData.radius * 2 * circleStartData.scale,\n height: circleStartData.radius * 2 * circleStartData.scale,\n },\n { width: bbox.width, height: bbox.height },\n anchor,\n this.rotation\n );\n\n if (newPosition) {\n // Position is top-left of bbox, but we need center\n this.x = newPosition.x + bbox.width / 2;\n this.y = newPosition.y + bbox.height / 2;\n }\n }\n\n /**\n * Get enabled anchors\n * Circle supports only corner handles (uniform scale)\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Get effective font size (base fontSize * scale)\n * This is what the user sees\n */\n getEffectiveFontSize(): number {\n return Math.round(this.fontSize * this.transformData.scale * 10) / 10;\n }\n\n /**\n * Set effective font size (adjusts base fontSize to achieve desired effective size)\n */\n setEffectiveFontSize(targetSize: number): void {\n if (Math.abs(this.transformData.scale - 1) > 0.01) {\n // Calculate what base fontSize should be\n const newBaseFontSize = targetSize / this.transformData.scale;\n this.setFontSize(newBaseFontSize);\n } else {\n // Scale is 1, just set directly\n this.setFontSize(targetSize);\n }\n }\n\n /**\n * Serialize with transform data\n */\n toJSON(): CircleElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'circle',\n transformData: {\n type: 'circle',\n radius: this.transformData.radius,\n scale: this.transformData.scale,\n reverse: this.transformData.reverse,\n },\n };\n }\n\n /**\n * Get transform start data\n */\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n width: bbox.width,\n height: bbox.height,\n fontSize: this.fontSize,\n rotation: this.rotation,\n transformData: {\n type: 'circle',\n radius: this.transformData.radius,\n scale: this.transformData.scale,\n reverse: this.transformData.reverse,\n },\n };\n }\n}\n\nexport default CircleTransform;\n","/**\n * ArchTransform - Text curves upward in an arc\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport type { ArchElementConfig, ArchTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class ArchTransform extends TextElement {\n declare transformData: ArchTransformData;\n\n constructor(config: Partial<ArchElementConfig> = {}) {\n super(config);\n this.transformType = 'arch';\n\n // Arch transform data: archHeight controls the curve (-1 to 1)\n // Positive = arch up, Negative = arch down\n const data = config.transformData;\n this.transformData = {\n type: 'arch',\n archHeight: data?.archHeight ?? 0.5,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const archHeightAbs = Math.abs(this.transformData.archHeight);\n const height = this.fontSize * (1.2 + archHeightAbs);\n\n // Positive arch goes upward (negative y direction)\n // Negative arch goes downward (positive y direction)\n const yOffset = this.transformData.archHeight > 0 ? -height : 0;\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y + yOffset,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For arch, we need to calculate bounds that encompass all skewed characters\n // We simulate the rendering to find the actual min/max positions\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n const radius = this.transformData.width / 2;\n\n const skewFactor = 0.3;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / radius; // -1 to 1\n\n // Calculate y position on parabola\n const lineY = (Math.pow(normalizedX, 2) - 1) * this.transformData.archHeight * this.fontSize;\n\n // Calculate the slope of the curve at this point for skew\n const slope = 2 * normalizedX * this.transformData.archHeight;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const archStartData = startData.transformData as ArchTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale based on width change only (X-axis in local coords)\n //\n // Design decision: We use ONLY the width change (X-axis in local coordinates) to determine\n // the scale factor. This provides intuitive image-like scaling behavior where:\n // - Horizontal mouse movement controls the scale\n // - Aspect ratio is maintained (font size scales proportionally with width)\n // - The dragged corner follows the mouse cursor precisely\n //\n // For arch text, both fontSize and arch width scale together uniformly\n const scale = newWidth / startData.width!;\n\n // Apply uniform scale to font size\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n // Apply uniform scale to arch width\n const scaledWidth = archStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n\n // Position adjustment is handled by ResizeHandler's fixed corner logic\n // Don't adjust position here to avoid double adjustment\n } else if (isSideHandle) {\n // Side handle: only width changes (fontSize MUST stay the same)\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n // CRITICAL: Do NOT change fontSize for side handles\n // Ensure fontSize stays exactly as it was\n this.fontSize = startData.fontSize!;\n\n // Update width only\n this.transformData.width = Math.max(50, newWidth);\n\n // For arch text, position (x,y) is at the CENTER of the arch (see render method)\n // When resizing with side handles, we want to keep the OPPOSITE edge fixed\n // This means the center point needs to move by half the width change\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n // Left edge is moving, right edge should stay fixed\n // Center moves by -deltaWidth/2 (moves left in local coords)\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n // Right edge is moving, left edge should stay fixed\n // Center moves by +deltaWidth/2 (moves right in local coords)\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n // Note: middle-top and middle-bottom don't affect arch dimensions\n // since height is determined by fontSize and archHeight\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Arch supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): ArchElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'arch',\n transformData: {\n type: 'arch',\n archHeight: this.transformData.archHeight,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'arch',\n archHeight: this.transformData.archHeight,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default ArchTransform;\n","/**\n * Transform Default Values\n * Single source of truth for all transform default parameters\n */\n\n// Wave Transform Defaults\nexport const WAVE_DEFAULTS = {\n amplitude: 0.2, // 20% - subtle wave effect\n frequency: 1.2, // Smooth curve\n width: 200,\n} as const;\n\n// Flag Transform Defaults\nexport const FLAG_DEFAULTS = {\n amplitude: 0.2, // 20% - subtle flag wave\n frequency: 2, // Standard flag frequency\n width: 200,\n} as const;\n\n// Arch Transform Defaults\nexport const ARCH_DEFAULTS = {\n archHeight: 0.5, // 50% - centered arch\n width: 200,\n} as const;\n\n// Circle Transform Defaults\nexport const CIRCLE_DEFAULTS = {\n radius: 100,\n reverse: false,\n} as const;\n\n// Lean Transform Defaults\nexport const LEAN_DEFAULTS = {\n leanAmount: 0, // 0 - no lean by default\n width: 200,\n} as const;\n\n// Ascend Transform Defaults\nexport const ASCEND_DEFAULTS = {\n ascendAngle: -15, // -15° - nice upward ascent\n width: 200,\n} as const;\n\n// Custom Transform Defaults\nexport const CUSTOM_DEFAULTS = {\n width: 200,\n} as const;\n","/**\n * WaveTransform - Text follows a sine wave pattern\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { standardResize } from '../core/ResizeUtils.js';\nimport { WAVE_SKEW_FACTOR } from '../constants.js';\nimport { WAVE_DEFAULTS } from './defaults.js';\nimport type { WaveElementConfig, WaveTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class WaveTransform extends TextElement {\n declare transformData: WaveTransformData;\n\n constructor(config: Partial<WaveElementConfig> = {}) {\n super(config);\n this.transformType = 'wave';\n\n // Wave transform data: amplitude and frequency parameters\n const data = config.transformData;\n this.transformData = {\n type: 'wave',\n amplitude: data?.amplitude ?? WAVE_DEFAULTS.amplitude,\n frequency: data?.frequency ?? WAVE_DEFAULTS.frequency,\n width: data?.width ?? WAVE_DEFAULTS.width,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const amplitudeAbs = Math.abs(this.transformData.amplitude);\n const height = this.fontSize * (1.2 + amplitudeAbs * 2);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For wave, we need to calculate bounds that encompass all skewed characters\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / (this.transformData.width / 2); // -1 to 1\n\n // Calculate y position on sine wave\n const lineY =\n this.transformData.amplitude * this.fontSize * Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope of the sine wave at this point for skew\n const slope =\n this.transformData.amplitude *\n this.transformData.frequency *\n Math.PI *\n Math.cos(this.transformData.frequency * Math.PI * normalizedX);\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*WAVE_SKEW_FACTOR, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*WAVE_SKEW_FACTOR*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * WAVE_SKEW_FACTOR * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n standardResize(this, anchor, newWidth, newHeight, startData);\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Wave supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Serialize with transform data\n */\n toJSON(): WaveElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'wave',\n transformData: {\n type: 'wave',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default WaveTransform;\n","/**\n * FlagTransform - Text waves from the center point outward like a flag\n * Similar to wave but amplitude increases from center to edges\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { FLAG_DEFAULTS } from './defaults.js';\nimport type { FlagElementConfig, FlagTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class FlagTransform extends TextElement {\n declare transformData: FlagTransformData;\n\n constructor(config: Partial<FlagElementConfig> = {}) {\n super(config);\n this.transformType = 'flag';\n\n // Flag transform data: amplitude and frequency parameters\n // Amplitude increases from center to edges\n const data = config.transformData;\n this.transformData = {\n type: 'flag',\n amplitude: data?.amplitude ?? FLAG_DEFAULTS.amplitude,\n frequency: data?.frequency ?? FLAG_DEFAULTS.frequency,\n width: data?.width ?? FLAG_DEFAULTS.width,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const amplitudeAbs = Math.abs(this.transformData.amplitude);\n const height = this.fontSize * (1.2 + amplitudeAbs * 2);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For flag, we need to calculate bounds that encompass all skewed characters\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n const skewFactor = 0.3;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / (this.transformData.width / 2); // -1 to 1\n\n // Flag effect: amplitude increases from center to edges\n const distanceFromCenter = Math.abs(normalizedX);\n const flagAmplitude = this.transformData.amplitude * distanceFromCenter;\n\n // Calculate y position on flag wave\n const lineY = flagAmplitude * this.fontSize * Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope for skew\n const cosComponent =\n flagAmplitude *\n this.transformData.frequency *\n Math.PI *\n Math.cos(this.transformData.frequency * Math.PI * normalizedX);\n const sinComponent =\n this.transformData.amplitude *\n Math.sign(normalizedX) *\n Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n const slope = cosComponent + sinComponent;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const flagStartData = startData.transformData as FlagTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale\n const scale = newWidth / startData.width!;\n\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n const scaledWidth = flagStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n this.fontSize = startData.fontSize!;\n this.transformData.width = Math.max(50, newWidth);\n\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Flag supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): FlagElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'flag',\n transformData: {\n type: 'flag',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'flag',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default FlagTransform;\n","/**\n * LeanTransform - Text with slanted/leaning effect\n * Creates a skewed perspective\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport type { LeanElementConfig, LeanTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class LeanTransform extends TextElement {\n declare transformData: LeanTransformData;\n\n constructor(config: Partial<LeanElementConfig> = {}) {\n super(config);\n this.transformType = 'lean';\n\n // Lean transform data: controls the slant (-1 to 1)\n // Positive = slant left, Negative = slant right\n const data = config.transformData;\n this.transformData = {\n type: 'lean',\n leanAmount: data?.leanAmount ?? (data as unknown as Record<string, number> | undefined)?.angleAmount ?? 0,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const leanAmountAbs = Math.abs(this.transformData.leanAmount);\n const height = this.fontSize * (1.2 + leanAmountAbs * 0.5);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For lean, the entire text block is skewed, so we need to calculate the transformed bounds\n const actualTextWidth = measureTextWidth(this.text, this.fontSize, this.fontFamily);\n const width = actualTextWidth;\n const height = this.fontSize * 1.2;\n\n // Calculate the skew transformation matrix applied in render()\n // Negate to match the reversed direction in render()\n const skewAngle = (-this.transformData.leanAmount * Math.PI) / 4;\n const skewX = Math.tan(skewAngle);\n\n // Define the four corners of the text block before skew (centered)\n const corners = [\n { x: -width / 2, y: -height / 2 }, // top-left\n { x: width / 2, y: -height / 2 }, // top-right\n { x: -width / 2, y: height / 2 }, // bottom-left\n { x: width / 2, y: height / 2 }, // bottom-right\n ];\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Apply skew transformation [1, 0, tan(skewAngle), 1] to each corner\n corners.forEach((corner) => {\n // Skew transform: x' = x + tan(skewAngle)*y, y' = y\n const skewedX = corner.x + skewX * corner.y;\n const skewedY = corner.y;\n\n minX = Math.min(minX, skewedX);\n maxX = Math.max(maxX, skewedX);\n minY = Math.min(minY, skewedY);\n maxY = Math.max(maxY, skewedY);\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const leanStartData = startData.transformData as LeanTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale\n const scale = newWidth / startData.width!;\n\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n const scaledWidth = leanStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n this.fontSize = startData.fontSize!;\n this.transformData.width = Math.max(50, newWidth);\n\n const deltaWidth = newWidth - leanStartData.width;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Lean transform applies a uniform skew to the entire text block\n // Only corner handles make sense for uniform scaling\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): LeanElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'lean',\n transformData: {\n type: 'lean',\n leanAmount: this.transformData.leanAmount,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'lean',\n leanAmount: this.transformData.leanAmount,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default LeanTransform;\n","/**\n * AscendTransform - Text ascends along a straight diagonal line at ~30 degrees\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport type { AscendElementConfig, AscendTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class AscendTransform extends TextElement {\n declare transformData: AscendTransformData;\n\n constructor(config: Partial<AscendElementConfig> = {}) {\n super(config);\n this.transformType = 'ascend';\n\n // Ascend transform data: angle in degrees\n // Internal: -30° (max up) to +30° (max down)\n // Display: +100% (max up) to -100% (max down)\n const data = config.transformData;\n this.transformData = {\n type: 'ascend',\n ascendAngle: data?.ascendAngle ?? -15,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const angleRad = (this.transformData.ascendAngle * Math.PI) / 180;\n const width = this.transformData.width;\n\n // Calculate the vertical rise based on the width and angle\n const rise = width * Math.tan(angleRad);\n\n // Account for font size in height calculation\n const height = Math.abs(rise) + this.fontSize * 1.2;\n\n // Determine y offset based on angle direction\n const yOffset = rise > 0 ? -height : 0;\n\n return {\n x: this.x - width / 2,\n y: this.y + yOffset,\n width: width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For ascend, we need to calculate bounds that encompass all skewed characters\n // We simulate the rendering to find the actual min/max positions\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n const angleRad = (this.transformData.ascendAngle * Math.PI) / 180;\n const slope = Math.tan(angleRad);\n const skewFactor = 1.0;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n\n // Position on diagonal line\n const lineY = centerX * slope;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const ascendStartData = startData.transformData as AscendTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale based on width change\n const scale = newWidth / startData.width!;\n\n // Apply uniform scale to font size\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n // Apply uniform scale to width\n const scaledWidth = ascendStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes (fontSize stays the same)\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n // Keep fontSize constant\n this.fontSize = startData.fontSize!;\n\n // Update width only\n this.transformData.width = Math.max(50, newWidth);\n\n // Adjust center position to keep opposite edge fixed\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n // Left edge is moving, right edge should stay fixed\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n // Right edge is moving, left edge should stay fixed\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Ascend supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): AscendElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'ascend',\n transformData: {\n type: 'ascend',\n ascendAngle: this.transformData.ascendAngle,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'ascend',\n ascendAngle: this.transformData.ascendAngle,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default AscendTransform;\n","/**\n * Structured error contract for the `Editor.*` composable primitives (ADR-0085,\n * decision E3): every public throw carries a stable machine-readable code plus\n * an `error.data` payload, with the human/agent message RENDERED from that\n * data. Tests assert on `code` + `data` fields, never on prose substrings.\n *\n * Message style (E2): verifiable facts from data in hand only — name the\n * violated contract, the offending part, the valid alternatives, and the exact\n * corrective code. Never heuristic guesses.\n */\n\n/** Stable codes for composition/runtime violations. ADR-0085 catalog. */\nexport type EditorErrorCode =\n /** An `Editor.*` part mounted outside `<Editor.Root>`. */\n | 'E_OUTSIDE_ROOT'\n /** A layer token (or its name) matches no layer in the mounted design. */\n | 'E_LAYER_NOT_FOUND'\n /** The layer exists but is not the kind the part binds (e.g. an image\n * layer passed to `Editor.TextField`). */\n | 'E_LAYER_WRONG_KIND'\n /** `Editor.TextField` received a bare string (or other non-token) — layers\n * bind by `design.layer(\"name\")` token only (ADR-0085 §1). */\n | 'E_LAYER_TOKEN_REQUIRED'\n /** Programmatic rename/delete of a layer a mounted TextField is bound to. */\n | 'E_BOUND_LAYER_MUTATION'\n /** Two `Editor.Canvas` parts mounted under one Root. */\n | 'E_DUPLICATE_CANVAS'\n /** A part that needs a mounted `Editor.Canvas` found none under this Root. */\n | 'E_REQUIRES_CANVAS'\n /** `Editor.Buy` mounted without product context. */\n | 'E_BUY_NO_PRODUCT'\n /** The lazy renderer chunk failed to load (network/CDN). Retryable. */\n | 'E_CHUNK_LOAD_FAILED'\n /** Embed wiring invalid at mount (missing grant/mockupIds, variant or\n * placement mismatch) — the throw-mode upgrade of `validateEmbed` (F3). */\n | 'E_EMBED_CONFIG'\n /** `productToRenderConfig`: the catalog product has no renderable image\n * placement / no mockup scenes, or the requested placement doesn't exist. */\n | 'E_PRODUCT_NOT_RENDERABLE'\n /** `design().artboard()`: an artboard with this label already exists in the\n * design (artboard names are placement labels — unique by contract). */\n | 'E_DUPLICATE_ARTBOARD'\n /** `usePlacements().select()`: the label names no artboard in the design\n * (ADR-0088). Lists the valid placement labels. */\n | 'E_UNKNOWN_PLACEMENT'\n /** `select()` named an option key/choice that doesn't exist on the product\n * (Buy-owned selection, ADR-0085 §2 / P2). */\n | 'E_UNKNOWN_OPTION'\n /** `design({ colors })`: two color roles share one (normalized) hex value.\n * `useColorBinding` matches by VALUE, so colliding roles would capture each\n * other's elements — rejected at mint time instead of failing subtly. */\n | 'E_COLOR_ROLE_COLLISION'\n /** `design({ colors })`: a role's value is not a `#RGB`/`#RRGGBB` hex\n * (value-matched bindings normalize hex; other syntaxes can't bind). */\n | 'E_COLOR_ROLE_INVALID'\n /** `design.color(name)`: no color role with this name — lists the design's\n * declared roles. */\n | 'E_COLOR_ROLE_NOT_FOUND'\n /** `Editor.Preview loadingOverlay` got `false`/an unknown value. A mockup\n * preview ALWAYS has a render-in-flight treatment (a server render takes\n * real time, and the loader paints OVER the still-visible previous\n * mockup) — keep the default, pass a variant/custom element, or declare\n * `\"host-owned\"` with your own indicator. Silence-then-pop-in is a\n * composition mistake, not a preference. */\n | 'E_PREVIEW_NO_LOADER'\n /** `updateElement(target, patch)`: a patch key doesn't apply to the target\n * element's kind (e.g. `fontSize` on a shape), or a value is out of range\n * (opacity outside 0..1, fontSize/width/height <= 0, non-finite number,\n * wrong primitive type). The message names the element kind and lists the\n * valid keys for it. Dev AND prod. */\n | 'E_INVALID_PATCH'\n /** A shape `fill` value is not a valid `ShapeFill` union member: unknown\n * `type` (valid: \"solid\" | \"linear\" | \"radial\"), a gradient with fewer\n * than 2 stops, a stop `offset` outside 0..1 (or non-finite), a non-string\n * stop `color`, or a non-finite linear `angle`. Thrown wherever a fill\n * enters the model — `new ShapeElement(...)` and `updateElement` patches.\n * Dev AND prod. */\n | 'E_INVALID_FILL'\n /** `addElement(spec)`: there is no active artboard to add the element to.\n * Create or select an artboard first (`<Editor.Root>` seeds one\n * automatically; with a raw `EditorProvider` use `executeCreateArtboard`).\n * Replaces the old `handleAddElement` window.alert — agents read throws,\n * not dialogs. */\n | 'E_NO_ACTIVE_ARTBOARD'\n /** `assertRenders`: the shop's asset-origin allowlist denied a design asset\n * (translated from the server's 403 — F4). */\n | 'E_ASSET_FORBIDDEN'\n /** `assertRenders`: the render failed or produced no usable image (F4). */\n | 'E_RENDER_FAILED'\n /** `assertRenders`: the mutated render is byte-identical to the seed render —\n * the binding under test is dead (the two-render contract, F4). */\n | 'E_BINDING_INERT'\n /** `assertRenders`: the design has no named text layer to mutate and no\n * `mutate` override was given (F4). */\n | 'E_NO_MUTABLE_BINDING'\n /** `auditEditor()` called with no `<Editor.Root>` mounted — the imperative\n * audit reads the most-recent mounted Root's registry (#10). */\n | 'E_AUDIT_NO_ROOT'\n /** A custom (brand) font failed to load from its hosted URL — the binary\n * fetch failed (CORS, 404, network) or FontFace rejected the bytes.\n * Custom fonts NEVER fall back silently to a system face: the design\n * would render with a different typeface/metrics everywhere it's\n * consumed. `data` carries `family`, `url`, `weight`, `style`. Fix the\n * URL (must serve the font file over a plain cross-origin GET) and\n * re-register. Dev AND prod. */\n | 'E_FONT_LOAD_FAILED'\n /** `computeDistribute()` called with fewer than 3 movable elements —\n * distributing needs something between the two ends. Gate the action on\n * `useAlignDistribute().canDistribute` instead of calling below the\n * threshold (the toolbar hides its distribute buttons for exactly this\n * reason). */\n | 'E_DISTRIBUTE_MIN_THREE';\n\nexport interface EditorErrorData {\n code: EditorErrorCode;\n /** Slug under https://developers.snowcone.app/ for the violated contract. */\n docSlug?: string;\n /** Valid alternatives when the violation is a bad identifier. */\n validNames?: readonly string[];\n /** Structured facts about the offending values. */\n [key: string]: unknown;\n}\n\n/**\n * The one error class every `Editor.*` invariant throws. `code` is the stable\n * test/agent contract; `message` is rendered from `data` and may be reworded.\n */\nexport class EditorError extends Error {\n readonly code: EditorErrorCode;\n readonly data: EditorErrorData;\n\n constructor(code: EditorErrorCode, message: string, data: Omit<EditorErrorData, 'code'> = {}) {\n super(`[Editor] ${code}: ${message}`);\n this.name = 'EditorError';\n this.code = code;\n this.data = { code, ...data };\n }\n}\n\nexport function isEditorError(err: unknown): err is EditorError {\n return err instanceof Error && err.name === 'EditorError' && 'code' in err;\n}\n\n/** True outside production bundles — mirrors the embed's existing typeof-guarded\n * pattern (non-Node bundlers may not define `process`). */\nexport function isDevOrTest(): boolean {\n return typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';\n}\n","/**\n * Shape fill resolution — solid colors and gradients (`ShapeFill` union).\n *\n * Works in both main thread AND Web Worker (OffscreenCanvas) — no DOM access.\n * Every shape paint pass (ShapeElement live render, shape-renderer worker\n * export, ElementRenderUtils mask/knockout fallback) resolves its\n * `ctx.fillStyle` through {@link createShapeFillStyle} so live and exported\n * pixels can never disagree.\n *\n * Geometry is computed in the shape's LOCAL coordinate space (shape centered\n * on the origin, bounds `[-w/2, -h/2, w, h]`) — exactly the space every\n * caller paints in after its translate/rotate.\n *\n * Angle convention (CSS-like, pinned by shape-fill.test.ts):\n * - `0` = up (gradient runs bottom → top; first stop at the bottom edge)\n * - `90` = right (left → right), increasing clockwise\n * - Gradient line length matches the CSS spec: |w·sin θ| + |h·cos θ|,\n * so offsets 0 and 1 land exactly on the shape's bounding edges at\n * every angle.\n *\n * Radial gradients are centered with radius = farthest corner\n * (√(w² + h²) / 2), matching CSS `radial-gradient(circle farthest-corner)`.\n */\n\nimport { EditorError } from '../errors/editorError.js';\nimport type { ShapeFill, GradientStop } from '../types/index.js';\n\n/** Default shape fill — kept in sync with the renderers' historical fallback. */\nexport const DEFAULT_SHAPE_FILL_COLOR = '#3b82f6';\n\nconst FILL_TYPES = ['solid', 'linear', 'radial'] as const;\n\n/** The minimal gradient-factory surface — satisfied by both 2D context types. */\nexport interface GradientCapableContext {\n createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient;\n createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient;\n}\n\nfunction invalidFill(message: string, data: Record<string, unknown>): EditorError {\n return new EditorError('E_INVALID_FILL', message, data);\n}\n\n/**\n * Validate a `fill` value at a model write boundary. Throws `E_INVALID_FILL`\n * (dev AND prod) with the offending value attached; never a silent no-op.\n */\nexport function assertValidShapeFill(fill: unknown): asserts fill is ShapeFill {\n if (typeof fill !== 'object' || fill === null) {\n throw invalidFill(\n `shape fill must be an object ({ type: \"solid\" | \"linear\" | \"radial\", ... }), got ${JSON.stringify(fill)}.`,\n { fill },\n );\n }\n const f = fill as { type?: unknown; color?: unknown; angle?: unknown; stops?: unknown };\n if (f.type === 'solid') {\n if (typeof f.color !== 'string') {\n throw invalidFill(`solid fill \"color\" must be a string, got ${JSON.stringify(f.color)}.`, { fill });\n }\n return;\n }\n if (f.type === 'linear' || f.type === 'radial') {\n if (f.type === 'linear' && (typeof f.angle !== 'number' || !Number.isFinite(f.angle))) {\n throw invalidFill(\n `linear fill \"angle\" must be a finite number of degrees (0 = up, 90 = right), got ${JSON.stringify(f.angle)}.`,\n { fill },\n );\n }\n if (!Array.isArray(f.stops) || f.stops.length < 2) {\n throw invalidFill(\n `${f.type} fill needs at least 2 stops, got ${Array.isArray(f.stops) ? f.stops.length : JSON.stringify(f.stops)}.`,\n { fill },\n );\n }\n for (const stop of f.stops as Array<{ offset?: unknown; color?: unknown }>) {\n if (\n typeof stop !== 'object' ||\n stop === null ||\n typeof stop.offset !== 'number' ||\n !Number.isFinite(stop.offset) ||\n stop.offset < 0 ||\n stop.offset > 1\n ) {\n throw invalidFill(\n `gradient stop \"offset\" must be a finite number in 0..1, got ${JSON.stringify(stop)}.`,\n { fill, stop },\n );\n }\n if (typeof stop.color !== 'string') {\n throw invalidFill(`gradient stop \"color\" must be a string, got ${JSON.stringify(stop)}.`, { fill, stop });\n }\n }\n return;\n }\n throw invalidFill(\n `unknown shape fill type ${JSON.stringify(f.type)}. Valid types: ${JSON.stringify(FILL_TYPES)}.`,\n { fill, validNames: FILL_TYPES },\n );\n}\n\n/** Deep-copy a fill so clones/serialized configs never alias stops arrays. */\nexport function cloneShapeFill(fill: ShapeFill): ShapeFill {\n switch (fill.type) {\n case 'solid':\n return { type: 'solid', color: fill.color };\n case 'linear':\n return { type: 'linear', angle: fill.angle, stops: fill.stops.map((s) => ({ ...s })) };\n case 'radial':\n return { type: 'radial', stops: fill.stops.map((s) => ({ ...s })) };\n }\n}\n\nfunction addStops(gradient: CanvasGradient, stops: GradientStop[]): CanvasGradient {\n for (const stop of stops) {\n // Clamp defensively: a pre-validation value sneaking in via old saved\n // state must not throw inside the render loop (DOMException territory).\n gradient.addColorStop(Math.min(1, Math.max(0, stop.offset)), stop.color);\n }\n return gradient;\n}\n\n/**\n * Resolve the paint style for a shape's fill in LOCAL coordinates (shape\n * center at the origin). `fill` wins when present; otherwise the legacy\n * `fillColor` solid (then the historical default blue).\n */\nexport function createShapeFillStyle(\n ctx: GradientCapableContext,\n transformData: { fill?: ShapeFill; fillColor?: string; width?: number; height?: number },\n): string | CanvasGradient {\n const { fill } = transformData;\n if (!fill) return transformData.fillColor || DEFAULT_SHAPE_FILL_COLOR;\n if (fill.type === 'solid') return fill.color;\n\n const width = transformData.width ?? 100;\n const height = transformData.height ?? 100;\n\n if (fill.type === 'linear') {\n const theta = (fill.angle * Math.PI) / 180;\n // Direction the gradient travels (canvas y grows downward):\n // 0° = up → (0, -1); 90° = right → (1, 0).\n const dx = Math.sin(theta);\n const dy = -Math.cos(theta);\n // CSS gradient-line length: |w·sin θ| + |h·cos θ| (offsets 0/1 touch the edges).\n const halfLength = (Math.abs(width * dx) + Math.abs(height * dy)) / 2;\n return addStops(\n ctx.createLinearGradient(-dx * halfLength, -dy * halfLength, dx * halfLength, dy * halfLength),\n fill.stops,\n );\n }\n\n // radial — centered, farthest-corner radius (CSS default ending shape reach).\n const radius = Math.sqrt(width * width + height * height) / 2;\n return addStops(ctx.createRadialGradient(0, 0, 0, 0, 0, radius), fill.stops);\n}\n\n/**\n * CSS preview string for UI swatches (toolbar fill button, panel icon).\n * Mirrors the canvas geometry: same angle convention, radial = centered circle.\n */\nexport function shapeFillToCss(fill: ShapeFill | undefined, fallbackColor?: string): string {\n if (!fill) return fallbackColor || DEFAULT_SHAPE_FILL_COLOR;\n switch (fill.type) {\n case 'solid':\n return fill.color;\n case 'linear':\n return `linear-gradient(${fill.angle}deg, ${fill.stops\n .map((s) => `${s.color} ${s.offset * 100}%`)\n .join(', ')})`;\n case 'radial':\n return `radial-gradient(circle, ${fill.stops.map((s) => `${s.color} ${s.offset * 100}%`).join(', ')})`;\n }\n}\n","/**\n * ShapeElement - Element for displaying geometric shapes\n * Supports rectangles, circles, ellipses, triangles, polygons, stars, and lines\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { getThemeShapeFillColor } from '../constants.js';\nimport { assertValidShapeFill, cloneShapeFill, createShapeFillStyle } from './shape-fill.js';\nimport type {\n ShapeTransformData,\n ShapeElementConfig,\n ResizeAnchor,\n BoundingBox,\n Point,\n ShapeType,\n TransformStartData,\n} from '../types/index.js';\n\nexport class ShapeElement extends BaseElement {\n declare transformType: 'shape';\n declare transformData: ShapeTransformData;\n\n constructor(config: Partial<ShapeElementConfig> = {}) {\n super(config);\n this.transformType = 'shape';\n\n // Validate the rich fill at the model boundary — E_INVALID_FILL, dev AND\n // prod, never a silent no-op (a bad fill would otherwise surface as a\n // confusing render-loop failure).\n const fill = config.transformData?.fill;\n if (fill !== undefined) assertValidShapeFill(fill);\n\n // Initialize transformData with proper ShapeTransformData type\n const defaultShapeType: ShapeType = 'rectangle';\n this.transformData = {\n type: 'shape',\n shapeType: config.transformData?.shapeType || defaultShapeType,\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\n borderRadius: config.transformData?.borderRadius ?? 3, // Default 3% radius (~6px on 200x200 shape, matches hover border)\n radiusX: config.transformData?.radiusX,\n radiusY: config.transformData?.radiusY,\n sides: config.transformData?.sides ?? 5,\n points: config.transformData?.points ?? 5,\n innerRadius: config.transformData?.innerRadius ?? 0.4,\n fillColor: config.transformData?.fillColor || getThemeShapeFillColor(),\n fillOpacity: config.transformData?.fillOpacity ?? 1,\n // Deep-copy so a config's stops array is never aliased by the element\n ...(fill !== undefined && { fill: cloneShapeFill(fill) }),\n };\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n */\n getBoundingBox(): BoundingBox {\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - this.transformData.height / 2,\n width: this.transformData.width,\n height: this.transformData.height,\n };\n }\n\n /**\n * Tight visual bounding box for selection chrome.\n *\n * `getBoundingBox()` returns the conceptual `width × height` rect\n * the resize handles act on. Many shape types (circle, polygon,\n * star) render *inscribed* in that rect — their visible extent is\n * smaller than the box, leaving empty corners. The selection\n * border / handles use this tighter bbox so it hugs what the user\n * actually sees instead of including the empty padding.\n *\n * For shapes that fill their box (rectangle, ellipse, triangle,\n * line) this falls through to `getBoundingBox()`.\n *\n * Stroke is intentionally excluded — same convention as\n * `getBoundingBox()`. The chrome aligns with the *fill* edge.\n */\n getVisualBoundingBox(): BoundingBox {\n const { shapeType, width, height } = this.transformData;\n\n // Circle: inscribed in min(width, height) square. Tight bbox is\n // a square of side `min(w,h)` centered on (x, y).\n if (shapeType === 'circle') {\n const r = Math.min(width, height) / 2;\n return {\n x: this.x - r,\n y: this.y - r,\n width: r * 2,\n height: r * 2,\n };\n }\n\n // Polygon / star: inscribed in a circle of radius `min(w,h)/2`.\n // Walk the same vertex sequence the renderer uses to get the\n // exact tight bbox. Star alternates outer / inner radii — both\n // contribute to the bounds.\n if (shapeType === 'polygon' || shapeType === 'star') {\n const r = Math.min(width, height) / 2;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\n if (shapeType === 'polygon') {\n const sides = this.transformData.sides || 5;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (px < minX) minX = px;\n if (px > maxX) maxX = px;\n if (py < minY) minY = py;\n if (py > maxY) maxY = py;\n }\n } else {\n const points = this.transformData.points || 5;\n const outerRadius = r;\n const innerRadius = outerRadius * (this.transformData.innerRadius ?? 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const rad = i % 2 === 0 ? outerRadius : innerRadius;\n const px = rad * Math.cos(angle);\n const py = rad * Math.sin(angle);\n if (px < minX) minX = px;\n if (px > maxX) maxX = px;\n if (py < minY) minY = py;\n if (py > maxY) maxY = py;\n }\n }\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n // Rectangle, rounded-rect, ellipse, triangle, line — these fill\n // their conceptual box, so the standard bbox is already tight.\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (center of the shape)\n */\n getRotationAnchor(): Point {\n return { x: this.x, y: this.y };\n }\n\n /**\n * Render the shape\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n const elementOpacity = this.opacity ?? 1;\n const hasStroke = !!this.stroke?.enabled;\n\n // When element opacity < 1 AND stroke is present, render fill+stroke\n // at full opacity on a temp canvas to avoid compounding opacity where\n // fill and stroke overlap (e.g., 50% + 50% ≠ 75%).\n if (elementOpacity < 1 && hasStroke) {\n this.renderWithOffscreen(ctx, elementOpacity);\n return;\n }\n\n ctx.save();\n\n // Translate to shape center and rotate\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Set fill style with combined opacity (element opacity * fill opacity).\n // Gradient fills are computed in local coords (we just translated to the\n // shape center), so the gradient rotates with the shape.\n ctx.fillStyle = createShapeFillStyle(ctx, this.transformData);\n const fillOpacity = this.transformData.fillOpacity ?? 1;\n ctx.globalAlpha = elementOpacity * fillOpacity;\n\n // Render the specific shape (fill)\n this.renderShape(ctx);\n\n ctx.restore();\n\n // Render stroke if enabled (after restore so we get a fresh transform state)\n if (hasStroke) {\n this.renderStroke(ctx, elementOpacity);\n }\n }\n\n /**\n * Render fill+stroke to a temporary canvas at full opacity, then composite\n * at element opacity. Prevents fill/stroke overlap from compounding.\n */\n private renderWithOffscreen(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n const { width, height } = this.transformData;\n const strokeWidth = this.stroke?.width || 2;\n const padding = strokeWidth + 2;\n const offW = Math.ceil(width + padding * 2);\n const offH = Math.ceil(height + padding * 2);\n\n const offCanvas = document.createElement('canvas');\n offCanvas.width = offW;\n offCanvas.height = offH;\n const offCtx = offCanvas.getContext('2d');\n if (!offCtx) {\n // Fallback: render directly (with compounding)\n this.renderDirect(ctx, elementOpacity);\n return;\n }\n\n // Render fill+stroke at full opacity, centered on the temp canvas\n offCtx.save();\n offCtx.translate(offW / 2, offH / 2);\n\n // Fill\n offCtx.fillStyle = createShapeFillStyle(offCtx, this.transformData);\n const fillOpacity = this.transformData.fillOpacity ?? 1;\n offCtx.globalAlpha = fillOpacity;\n offCtx.beginPath();\n this.traceShapePath(offCtx);\n offCtx.fill();\n offCtx.restore();\n\n // Stroke\n offCtx.save();\n offCtx.translate(offW / 2, offH / 2);\n const stroke = this.stroke!;\n offCtx.strokeStyle = stroke.color || '#000000';\n offCtx.lineWidth = stroke.width || 2;\n offCtx.lineCap = stroke.lineCap || 'round';\n offCtx.lineJoin = stroke.lineJoin || 'round';\n offCtx.globalAlpha = stroke.opacity ?? 1;\n if (stroke.dashArray && stroke.dashArray.length > 0) {\n offCtx.setLineDash(stroke.dashArray);\n }\n offCtx.beginPath();\n this.traceShapePath(offCtx);\n offCtx.stroke();\n offCtx.restore();\n\n // Composite onto main canvas at element opacity\n ctx.save();\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, -offW / 2, -offH / 2);\n ctx.restore();\n }\n\n /**\n * Direct render (no offscreen) — used as fallback\n */\n private renderDirect(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n ctx.save();\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n ctx.fillStyle = createShapeFillStyle(ctx, this.transformData);\n ctx.globalAlpha = elementOpacity * (this.transformData.fillOpacity ?? 1);\n this.renderShape(ctx);\n ctx.restore();\n\n if (this.stroke?.enabled) {\n this.renderStroke(ctx, elementOpacity);\n }\n }\n\n /**\n * Trace the shape path without filling or stroking — used by offscreen render\n */\n private traceShapePath(ctx: CanvasRenderingContext2D): void {\n const { shapeType, width, height } = this.transformData;\n\n switch (shapeType) {\n case 'rectangle': {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n break;\n }\n case 'circle': {\n const radius = Math.min(width, height) / 2;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n case 'ellipse':\n ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2);\n break;\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.moveTo(0, -halfHeight);\n ctx.lineTo(halfWidth, halfHeight);\n ctx.lineTo(-halfWidth, halfHeight);\n ctx.closePath();\n break;\n }\n case 'polygon': {\n const sides = this.transformData.sides || 5;\n const r = Math.min(width, height) / 2;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'star': {\n const points = this.transformData.points || 5;\n const outerRadius = Math.min(width, height) / 2;\n const innerRadius = outerRadius * (this.transformData.innerRadius || 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const r = i % 2 === 0 ? outerRadius : innerRadius;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'line': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.rect(-halfWidth, -halfHeight, width, height);\n break;\n }\n default:\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n }\n\n /**\n * Render the stroke for this shape element\n */\n private renderStroke(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n ctx.save();\n\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n const stroke = this.stroke!;\n ctx.strokeStyle = stroke.color || '#000000';\n ctx.lineWidth = stroke.width || 2;\n ctx.lineCap = stroke.lineCap || 'round';\n ctx.lineJoin = stroke.lineJoin || 'round';\n ctx.globalAlpha = elementOpacity * (stroke.opacity ?? 1);\n\n if (stroke.dashArray && stroke.dashArray.length > 0) {\n ctx.setLineDash(stroke.dashArray);\n }\n\n // Recreate the shape path for stroking\n const { shapeType, width, height } = this.transformData;\n ctx.beginPath();\n\n switch (shapeType) {\n case 'rectangle': {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n break;\n }\n case 'circle': {\n const radius = Math.min(width, height) / 2;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n case 'ellipse':\n ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2);\n break;\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.moveTo(0, -halfHeight);\n ctx.lineTo(halfWidth, halfHeight);\n ctx.lineTo(-halfWidth, halfHeight);\n ctx.closePath();\n break;\n }\n case 'polygon': {\n const sides = this.transformData.sides || 5;\n const radius = Math.min(width, height) / 2;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = radius * Math.cos(angle);\n const py = radius * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'star': {\n const points = this.transformData.points || 5;\n const outerRadius = Math.min(width, height) / 2;\n const innerRadius = outerRadius * (this.transformData.innerRadius || 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const r = i % 2 === 0 ? outerRadius : innerRadius;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'line': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.rect(-halfWidth, -halfHeight, width, height);\n break;\n }\n default:\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n\n ctx.stroke();\n ctx.restore();\n }\n\n /**\n * Render the specific shape based on shapeType\n */\n private renderShape(ctx: CanvasRenderingContext2D): void {\n const { shapeType, width, height } = this.transformData;\n\n ctx.beginPath();\n\n switch (shapeType) {\n case 'rectangle':\n this.renderRectangle(ctx, width, height);\n break;\n case 'circle':\n this.renderCircle(ctx, Math.min(width, height) / 2);\n break;\n case 'ellipse':\n this.renderEllipse(ctx, width / 2, height / 2);\n break;\n case 'triangle':\n this.renderTriangle(ctx, width, height);\n break;\n case 'polygon':\n this.renderPolygon(ctx, Math.min(width, height) / 2);\n break;\n case 'star':\n this.renderStar(ctx, Math.min(width, height) / 2);\n break;\n case 'line':\n this.renderLine(ctx, width, height);\n return; // Line uses stroke, not fill\n default:\n // Fallback to rectangle\n this.renderRectangle(ctx, width, height);\n }\n\n ctx.fill();\n }\n\n /**\n * Render a rectangle (with optional border radius)\n */\n private renderRectangle(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n }\n\n /**\n * Render a circle\n */\n private renderCircle(ctx: CanvasRenderingContext2D, radius: number): void {\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n }\n\n /**\n * Render an ellipse\n */\n private renderEllipse(ctx: CanvasRenderingContext2D, radiusX: number, radiusY: number): void {\n ctx.ellipse(0, 0, radiusX, radiusY, 0, 0, Math.PI * 2);\n }\n\n /**\n * Render a triangle (isosceles, pointing up)\n */\n private renderTriangle(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n\n ctx.moveTo(0, -halfHeight); // Top point\n ctx.lineTo(halfWidth, halfHeight); // Bottom right\n ctx.lineTo(-halfWidth, halfHeight); // Bottom left\n ctx.closePath();\n }\n\n /**\n * Render a regular polygon\n */\n private renderPolygon(ctx: CanvasRenderingContext2D, radius: number): void {\n const sides = Math.max(3, Math.min(20, this.transformData.sides || 5));\n const angleStep = (Math.PI * 2) / sides;\n const startAngle = -Math.PI / 2; // Start at top\n\n for (let i = 0; i <= sides; i++) {\n const angle = startAngle + angleStep * i;\n const x = Math.cos(angle) * radius;\n const y = Math.sin(angle) * radius;\n\n if (i === 0) {\n ctx.moveTo(x, y);\n } else {\n ctx.lineTo(x, y);\n }\n }\n\n ctx.closePath();\n }\n\n /**\n * Render a star\n */\n private renderStar(ctx: CanvasRenderingContext2D, outerRadius: number): void {\n const points = Math.max(3, Math.min(20, this.transformData.points || 5));\n const innerRadiusRatio = Math.max(0.1, Math.min(0.9, this.transformData.innerRadius || 0.4));\n const innerRadius = outerRadius * innerRadiusRatio;\n const angleStep = Math.PI / points;\n const startAngle = -Math.PI / 2; // Start at top\n\n for (let i = 0; i < points * 2; i++) {\n const angle = startAngle + angleStep * i;\n const radius = i % 2 === 0 ? outerRadius : innerRadius;\n const x = Math.cos(angle) * radius;\n const y = Math.sin(angle) * radius;\n\n if (i === 0) {\n ctx.moveTo(x, y);\n } else {\n ctx.lineTo(x, y);\n }\n }\n\n ctx.closePath();\n }\n\n /**\n * Render a line\n */\n private renderLine(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n // Draw line as a stroke from left to right (or top to bottom if rotated)\n const halfWidth = width / 2;\n const thickness = height;\n\n ctx.moveTo(-halfWidth, 0);\n ctx.lineTo(halfWidth, 0);\n\n // Use stroke instead of fill (the fill style still applies — a gradient\n // fill paints along the line's local bounds)\n ctx.strokeStyle = createShapeFillStyle(ctx, this.transformData);\n ctx.lineWidth = thickness;\n ctx.lineCap = 'round';\n ctx.stroke();\n }\n\n /**\n * Handle resize - maintain aspect ratio for circles, free resize for others\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n const { shapeType } = this.transformData;\n\n // For circles and regular polygons, maintain aspect ratio\n if (shapeType === 'circle' || shapeType === 'polygon' || shapeType === 'star') {\n const scale = Math.max(newWidth / startData.transformData.width, newHeight / startData.transformData.height);\n this.transformData.width = startData.transformData.width * scale;\n this.transformData.height = startData.transformData.height * scale;\n } else if (shapeType === 'line') {\n // For lines, only allow width (length) to change, keep height (thickness) constant\n this.transformData.width = newWidth;\n this.transformData.height = startData.transformData.height;\n } else {\n // For other shapes, allow free resize\n this.transformData.width = newWidth;\n this.transformData.height = newHeight;\n }\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // Calculate fixed corner position (opposite of dragged anchor)\n const fixedCorner = this.getFixedCorner(anchor, startData);\n\n // Update position so the fixed corner stays in place\n // Get the opposite anchor (the one that should stay fixed)\n const oppositeAnchor = this.getOppositeAnchor(anchor);\n const newBbox = this.getBoundingBox();\n const fixedCornerOffset = this.getCornerOffset(oppositeAnchor, newBbox);\n\n this.x = fixedCorner.x - fixedCornerOffset.x;\n this.y = fixedCorner.y - fixedCornerOffset.y;\n\n return true;\n }\n\n /**\n * Get the opposite anchor (the one that should stay fixed during resize)\n */\n private getOppositeAnchor(anchor: ResizeAnchor): ResizeAnchor {\n const opposites: Record<ResizeAnchor, ResizeAnchor> = {\n 'top-left': 'bottom-right',\n 'top-right': 'bottom-left',\n 'bottom-left': 'top-right',\n 'bottom-right': 'top-left',\n 'middle-left': 'middle-right',\n 'middle-right': 'middle-left',\n 'middle-top': 'middle-bottom',\n 'middle-bottom': 'middle-top',\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n };\n return opposites[anchor];\n }\n\n /**\n * Get the position of the fixed corner (opposite of dragged anchor)\n */\n private getFixedCorner(anchor: ResizeAnchor, startData: TransformStartData): Point {\n const startBbox = {\n x: startData.x - startData.transformData.width / 2,\n y: startData.y - startData.transformData.height / 2,\n width: startData.transformData.width,\n height: startData.transformData.height,\n };\n\n let fixedX: number, fixedY: number;\n\n switch (anchor) {\n case 'top-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'top-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'bottom-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y;\n break;\n case 'bottom-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y;\n break;\n case 'middle-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-top':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'middle-bottom':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y;\n break;\n default:\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height / 2;\n }\n\n return { x: fixedX, y: fixedY };\n }\n\n /**\n * Get the offset of a corner from the center\n */\n private getCornerOffset(anchor: ResizeAnchor, bbox: BoundingBox): Point {\n let offsetX: number, offsetY: number;\n\n switch (anchor) {\n case 'top-left':\n offsetX = -bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'top-right':\n offsetX = bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'bottom-left':\n offsetX = -bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'bottom-right':\n offsetX = bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'middle-left':\n offsetX = -bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-right':\n offsetX = bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-top':\n offsetX = 0;\n offsetY = -bbox.height / 2;\n break;\n case 'middle-bottom':\n offsetX = 0;\n offsetY = bbox.height / 2;\n break;\n default:\n offsetX = 0;\n offsetY = 0;\n }\n\n return { x: offsetX, y: offsetY };\n }\n\n /**\n * Get enabled anchors - all 8 anchors for most shapes, only corners for circles/polygons\n */\n getEnabledAnchors(): ResizeAnchor[] {\n const { shapeType } = this.transformData;\n\n // Circles and regular polygons: only corner handles (to maintain aspect ratio)\n if (shapeType === 'circle' || shapeType === 'polygon' || shapeType === 'star') {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n // Lines: only left/right handles for length adjustment\n if (shapeType === 'line') {\n return ['middle-left', 'middle-right'];\n }\n\n // Other shapes: all 8 handles\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Clone this element\n */\n clone(): ShapeElement {\n return new ShapeElement(this.toJSON());\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ShapeElementConfig & { id: string; type: 'shape'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'shape' as const,\n transformType: 'shape' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n transformData: {\n ...this.transformData,\n // Deep-copy gradient stops — a spread would alias the stops array\n // between the element and its serialized config (fidelity sweep pin)\n ...(this.transformData.fill && { fill: cloneShapeFill(this.transformData.fill) }),\n },\n };\n }\n}\n\nexport default ShapeElement;\n","/**\n * PathElement - Element for custom bezier paths\n * Supports user-drawn shapes with editable control points\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { getThemeShapeFillColor } from '../constants.js';\nimport type {\n PathTransformData,\n PathElementConfig,\n PathPoint,\n ResizeAnchor,\n BoundingBox,\n Point,\n TransformStartData,\n} from '../types/index.js';\n\nexport class PathElement extends BaseElement {\n declare transformType: 'path';\n declare transformData: PathTransformData;\n\n constructor(config: Partial<PathElementConfig> = {}) {\n super(config);\n this.transformType = 'path';\n\n // Initialize transformData with proper PathTransformData type\n this.transformData = {\n type: 'path',\n points: config.transformData?.points || [],\n closed: config.transformData?.closed ?? false,\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\n fillEnabled: config.transformData?.fillEnabled ?? false,\n fillColor: config.transformData?.fillColor || getThemeShapeFillColor(),\n fillOpacity: config.transformData?.fillOpacity ?? 1,\n strokeEnabled: config.transformData?.strokeEnabled ?? true,\n strokeColor: config.transformData?.strokeColor || '#000000',\n strokeWidth: config.transformData?.strokeWidth || 2,\n };\n\n // If no points provided, start with empty path\n // Points will be added via pen tool\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n * For paths, calculates actual bounds from path points\n * The bounding box is centered around the element position (this.x, this.y)\n */\n getBoundingBox(): BoundingBox {\n const localBounds = this.calculatePathBounds();\n\n // The bounding box is centered around the element position\n return {\n x: this.x - localBounds.width / 2,\n y: this.y - localBounds.height / 2,\n width: localBounds.width,\n height: localBounds.height,\n };\n }\n\n /**\n * Get visual bounding box (same as regular bounding box for paths)\n * Used for selection display - wraps tightly around actual path\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (center of the path's bounding box)\n */\n getRotationAnchor(): Point {\n // For path elements, rotation happens around the element position\n // which should be the center of the local bounds\n return {\n x: this.x,\n y: this.y,\n };\n }\n\n /**\n * Calculate a point on a cubic Bezier curve\n * @param p0 Start point\n * @param p1 First control point\n * @param p2 Second control point\n * @param p3 End point\n * @param t Parameter from 0 to 1\n */\n private bezierPoint(p0: Point, p1: Point, p2: Point, p3: Point, t: number): Point {\n const mt = 1 - t;\n const mt2 = mt * mt;\n const mt3 = mt2 * mt;\n const t2 = t * t;\n const t3 = t2 * t;\n\n return {\n x: mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x,\n y: mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y,\n };\n }\n\n /**\n * Calculate bounding box from path points\n * Returns bbox in local coordinates (relative to element position)\n */\n private calculatePathBounds(): BoundingBox {\n const { points } = this.transformData;\n\n if (points.length === 0) {\n return { x: 0, y: 0, width: 0, height: 0 };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n // Calculate bounds including Bezier curve extrema\n for (let i = 0; i < points.length; i++) {\n const currentPoint = points[i];\n const nextPoint = points[(i + 1) % points.length];\n\n // Skip last segment if path is not closed\n if (i === points.length - 1 && !this.transformData.closed) {\n // Include the last point\n minX = Math.min(minX, currentPoint.x);\n minY = Math.min(minY, currentPoint.y);\n maxX = Math.max(maxX, currentPoint.x);\n maxY = Math.max(maxY, currentPoint.y);\n break;\n }\n\n // Include the current anchor point\n minX = Math.min(minX, currentPoint.x);\n minY = Math.min(minY, currentPoint.y);\n maxX = Math.max(maxX, currentPoint.x);\n maxY = Math.max(maxY, currentPoint.y);\n\n // Check if this segment has a curve\n const hasCurve = currentPoint.handleOut || nextPoint.handleIn;\n\n if (hasCurve) {\n // Calculate Bezier curve bounds by sampling points along the curve\n const p0 = { x: currentPoint.x, y: currentPoint.y };\n const p1 = {\n x: currentPoint.x + (currentPoint.handleOut?.x || 0),\n y: currentPoint.y + (currentPoint.handleOut?.y || 0),\n };\n const p2 = {\n x: nextPoint.x + (nextPoint.handleIn?.x || 0),\n y: nextPoint.y + (nextPoint.handleIn?.y || 0),\n };\n const p3 = { x: nextPoint.x, y: nextPoint.y };\n\n // Sample the curve at multiple points to find extrema\n const samples = 20;\n for (let t = 0; t <= samples; t++) {\n const u = t / samples;\n const point = this.bezierPoint(p0, p1, p2, p3, u);\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n }\n }\n\n // Add stroke width to bounds if stroke is enabled\n const strokeWidth = this.transformData.strokeEnabled ? this.transformData.strokeWidth || 2 : 0;\n const halfStroke = strokeWidth / 2;\n\n return {\n x: minX - halfStroke,\n y: minY - halfStroke,\n width: maxX - minX + strokeWidth,\n height: maxY - minY + strokeWidth,\n };\n }\n\n /**\n * Update cached bounding box dimensions\n */\n updateBounds(): void {\n const bounds = this.calculatePathBounds();\n this.transformData.width = Math.max(1, bounds.width);\n this.transformData.height = Math.max(1, bounds.height);\n }\n\n /**\n * Render the path\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n const { points, closed, fillEnabled, fillColor, fillOpacity, strokeEnabled, strokeColor, strokeWidth } =\n this.transformData;\n\n if (points.length === 0) {\n return; // Nothing to render\n }\n\n ctx.save();\n\n // Translate to path center and rotate\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Offset to center the path around the rotation point\n const localBounds = this.calculatePathBounds();\n const centerOffsetX = localBounds.x + localBounds.width / 2;\n const centerOffsetY = localBounds.y + localBounds.height / 2;\n ctx.translate(-centerOffsetX, -centerOffsetY);\n\n // Build the path\n ctx.beginPath();\n this.renderPath(ctx);\n\n // Fill if enabled and path is closed\n if (fillEnabled && closed && fillColor) {\n ctx.fillStyle = fillColor;\n ctx.globalAlpha = fillOpacity ?? 1;\n ctx.fill();\n }\n\n // Stroke if enabled\n if (strokeEnabled && strokeColor && strokeWidth) {\n ctx.strokeStyle = strokeColor;\n ctx.lineWidth = strokeWidth;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n ctx.globalAlpha = 1;\n ctx.stroke();\n }\n\n ctx.restore();\n }\n\n /**\n * Render the path using Canvas API bezier curves\n */\n private renderPath(ctx: CanvasRenderingContext2D): void {\n const { points, closed } = this.transformData;\n\n if (points.length === 0) return;\n\n // Start at first point\n const firstPoint = points[0];\n ctx.moveTo(firstPoint.x, firstPoint.y);\n\n // Draw segments between points\n for (let i = 0; i < points.length; i++) {\n const currentPoint = points[i];\n const nextPoint = points[(i + 1) % points.length];\n\n // Skip last segment if path is not closed\n if (i === points.length - 1 && !closed) {\n break;\n }\n\n // Check if we need to draw a curve or straight line\n const hasCurve = currentPoint.handleOut || nextPoint.handleIn;\n\n if (hasCurve) {\n // Bezier curve\n const cp1x = currentPoint.x + (currentPoint.handleOut?.x || 0);\n const cp1y = currentPoint.y + (currentPoint.handleOut?.y || 0);\n const cp2x = nextPoint.x + (nextPoint.handleIn?.x || 0);\n const cp2y = nextPoint.y + (nextPoint.handleIn?.y || 0);\n\n ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, nextPoint.x, nextPoint.y);\n } else {\n // Straight line\n ctx.lineTo(nextPoint.x, nextPoint.y);\n }\n }\n\n if (closed) {\n ctx.closePath();\n }\n }\n\n /**\n * Handle resize - scale all points relative to center\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n const oldWidth = startData.transformData.width;\n const oldHeight = startData.transformData.height;\n\n if (oldWidth === 0 || oldHeight === 0) {\n return false; // Can't resize empty path\n }\n\n // Calculate scale factors\n const scaleX = newWidth / oldWidth;\n const scaleY = newHeight / oldHeight;\n\n // Get the center of the original bounds to scale from\n // We need to reconstruct the original bounds from startData\n const startPoints = startData.transformData.points as PathPoint[];\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity;\n for (const point of startPoints) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n // Scale all points and handles from the center\n this.transformData.points = startPoints.map((point) => ({\n ...point,\n x: centerX + (point.x - centerX) * scaleX,\n y: centerY + (point.y - centerY) * scaleY,\n handleIn: point.handleIn ? { x: point.handleIn.x * scaleX, y: point.handleIn.y * scaleY } : undefined,\n handleOut: point.handleOut ? { x: point.handleOut.x * scaleX, y: point.handleOut.y * scaleY } : undefined,\n }));\n\n // Update dimensions\n this.transformData.width = newWidth;\n this.transformData.height = newHeight;\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // Calculate fixed corner position (opposite of dragged anchor)\n const fixedCorner = this.getFixedCorner(anchor, startData);\n\n // Update position so the fixed corner stays in place\n const oppositeAnchor = this.getOppositeAnchor(anchor);\n const newBbox = this.getBoundingBox();\n const fixedCornerOffset = this.getCornerOffset(oppositeAnchor, newBbox);\n\n this.x = fixedCorner.x - fixedCornerOffset.x;\n this.y = fixedCorner.y - fixedCornerOffset.y;\n\n return true;\n }\n\n /**\n * Get the opposite anchor (the one that should stay fixed during resize)\n */\n private getOppositeAnchor(anchor: ResizeAnchor): ResizeAnchor {\n const opposites: Record<ResizeAnchor, ResizeAnchor> = {\n 'top-left': 'bottom-right',\n 'top-right': 'bottom-left',\n 'bottom-left': 'top-right',\n 'bottom-right': 'top-left',\n 'middle-left': 'middle-right',\n 'middle-right': 'middle-left',\n 'middle-top': 'middle-bottom',\n 'middle-bottom': 'middle-top',\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n };\n return opposites[anchor];\n }\n\n /**\n * Get the position of the fixed corner (opposite of dragged anchor)\n */\n private getFixedCorner(anchor: ResizeAnchor, startData: TransformStartData): Point {\n const startBbox = {\n x: startData.x - startData.transformData.width / 2,\n y: startData.y - startData.transformData.height / 2,\n width: startData.transformData.width,\n height: startData.transformData.height,\n };\n\n let fixedX: number, fixedY: number;\n\n switch (anchor) {\n case 'top-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'top-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'bottom-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y;\n break;\n case 'bottom-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y;\n break;\n case 'middle-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-top':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'middle-bottom':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y;\n break;\n default:\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height / 2;\n }\n\n return { x: fixedX, y: fixedY };\n }\n\n /**\n * Get the offset of a corner from the center\n */\n private getCornerOffset(anchor: ResizeAnchor, bbox: BoundingBox): Point {\n let offsetX: number, offsetY: number;\n\n switch (anchor) {\n case 'top-left':\n offsetX = -bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'top-right':\n offsetX = bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'bottom-left':\n offsetX = -bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'bottom-right':\n offsetX = bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'middle-left':\n offsetX = -bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-right':\n offsetX = bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-top':\n offsetX = 0;\n offsetY = -bbox.height / 2;\n break;\n case 'middle-bottom':\n offsetX = 0;\n offsetY = bbox.height / 2;\n break;\n default:\n offsetX = 0;\n offsetY = 0;\n }\n\n return { x: offsetX, y: offsetY };\n }\n\n /**\n * Get enabled anchors - all 8 anchors for free resize\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Clone this element\n */\n clone(): PathElement {\n return new PathElement(this.toJSON());\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): PathElementConfig & { id: string; type: 'path'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'path' as const,\n transformType: 'path' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n transformData: {\n ...this.transformData,\n // Deep clone points array\n points: this.transformData.points.map((p) => ({\n ...p,\n handleIn: p.handleIn ? { ...p.handleIn } : undefined,\n handleOut: p.handleOut ? { ...p.handleOut } : undefined,\n })),\n },\n };\n }\n}\n\nexport default PathElement;\n","/**\n * Centralized transform registry\n * Single source of truth for all transform types and their configurations\n */\n\nimport {\n CustomTransform,\n CircleTransform,\n ArchTransform,\n WaveTransform,\n FlagTransform,\n LeanTransform,\n AscendTransform,\n} from './index.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { PathElement } from '../core/PathElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { WAVE_DEFAULTS, FLAG_DEFAULTS, ARCH_DEFAULTS, LEAN_DEFAULTS, ASCEND_DEFAULTS } from './defaults.js';\nimport type { TransformType, TransformControl } from '../types/index.js';\n\n/**\n * Transform control configurations\n * Defines the sliders and their properties for each transform type\n */\nexport const TRANSFORM_CONTROLS: Partial<Record<TransformType, TransformControl[]>> = {\n circle: [\n {\n key: 'reverse',\n label: 'Reverse Direction',\n type: 'checkbox',\n defaultValue: false,\n },\n ],\n arch: [\n {\n key: 'archHeight',\n label: 'Arch Height',\n defaultInternalValue: ARCH_DEFAULTS.archHeight,\n // Slider 0-100 maps to internal -1 to 1\n toSlider: (internal: number) => ((internal + 1) / 2) * 100,\n fromSlider: (slider: number) => (slider / 100) * 2 - 1,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n ],\n wave: [\n {\n key: 'amplitude',\n label: 'Wave Amplitude',\n defaultInternalValue: WAVE_DEFAULTS.amplitude,\n // Slider 0-100 maps to internal -0.5 to 0.5 (-50% to +50%)\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => (slider / 100) - 0.5,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n {\n key: 'frequency',\n label: 'Wave Frequency',\n defaultInternalValue: WAVE_DEFAULTS.frequency,\n step: 5, // 5% steps for smoother control\n // Slider 0-100 maps to internal 1 to 5\n toSlider: (internal: number) => ((internal - 1) / 4) * 100,\n fromSlider: (slider: number) => 1 + (slider / 100) * 4,\n toDisplay: (internal: number) => internal.toFixed(1),\n },\n ],\n flag: [\n {\n key: 'amplitude',\n label: 'Flag Amplitude',\n defaultInternalValue: FLAG_DEFAULTS.amplitude,\n // Slider 0-100 maps to internal -0.5 to 0.5 (-50% to +50%)\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => (slider / 100) - 0.5,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n {\n key: 'frequency',\n label: 'Flag Frequency',\n defaultInternalValue: FLAG_DEFAULTS.frequency,\n step: 5,\n toSlider: (internal: number) => ((internal - 1) / 4) * 100,\n fromSlider: (slider: number) => 1 + (slider / 100) * 4,\n toDisplay: (internal: number) => internal.toFixed(1),\n },\n ],\n lean: [\n {\n key: 'leanAmount',\n label: 'Lean Amount',\n defaultInternalValue: LEAN_DEFAULTS.leanAmount,\n // Slider 0-100 maps to internal -0.5 to 0.5\n // Display as -100% to 100%\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => slider / 100 - 0.5,\n toDisplay: (internal: number) => `${(internal * 200).toFixed(0)}%`,\n },\n ],\n ascend: [\n {\n key: 'ascendAngle',\n label: 'Ascend Amount',\n defaultInternalValue: ASCEND_DEFAULTS.ascendAngle,\n // Slider 0-100 maps to internal angle +30° to -30°\n // Display: slider 0 = -100% (down), slider 50 = 0%, slider 100 = +100% (up)\n toSlider: (internal: number) => ((30 - internal) / 60) * 100,\n fromSlider: (slider: number) => 30 - (slider / 100) * 60,\n toDisplay: (internal: number) => `${((-internal / 30) * 100).toFixed(0)}%`,\n },\n ],\n shape: [\n {\n key: 'shapeType',\n label: 'Shape Type',\n type: 'select',\n options: [\n { value: 'rectangle', label: 'Rectangle' },\n { value: 'circle', label: 'Circle' },\n { value: 'ellipse', label: 'Ellipse' },\n { value: 'triangle', label: 'Triangle' },\n { value: 'polygon', label: 'Polygon' },\n { value: 'star', label: 'Star' },\n { value: 'line', label: 'Line' },\n ],\n defaultValue: 'rectangle',\n },\n {\n key: 'borderRadius',\n label: 'Border Radius',\n defaultInternalValue: 0,\n // Slider 0-100 maps to internal 0 to 50\n toSlider: (internal: number) => (internal / 50) * 100,\n fromSlider: (slider: number) => (slider / 100) * 50,\n toDisplay: (internal: number) => `${internal.toFixed(0)}%`,\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'rectangle',\n },\n {\n key: 'sides',\n label: 'Sides',\n defaultInternalValue: 5,\n step: 1,\n // Slider 0-100 maps to internal 3 to 20\n toSlider: (internal: number) => ((internal - 3) / 17) * 100,\n fromSlider: (slider: number) => Math.round(3 + (slider / 100) * 17),\n toDisplay: (internal: number) => internal.toFixed(0),\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'polygon',\n },\n {\n key: 'points',\n label: 'Points',\n defaultInternalValue: 5,\n step: 1,\n // Slider 0-100 maps to internal 3 to 20\n toSlider: (internal: number) => ((internal - 3) / 17) * 100,\n fromSlider: (slider: number) => Math.round(3 + (slider / 100) * 17),\n toDisplay: (internal: number) => internal.toFixed(0),\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'star',\n },\n {\n key: 'innerRadius',\n label: 'Inner Radius',\n defaultInternalValue: 0.4,\n // Slider 0-100 maps to internal 0.1 to 0.9\n toSlider: (internal: number) => ((internal - 0.1) / 0.8) * 100,\n fromSlider: (slider: number) => 0.1 + (slider / 100) * 0.8,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'star',\n },\n {\n key: 'fillOpacity',\n label: 'Fill Opacity',\n defaultInternalValue: 1,\n // Slider 0-100 maps to internal 0 to 1\n toSlider: (internal: number) => internal * 100,\n fromSlider: (slider: number) => slider / 100,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n ],\n};\n\n/**\n * Transform definition shape used in the registry\n */\nexport interface TransformDef {\n id: string;\n label: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Constructors have varying signatures across element types\n Component: new (config: any) => any;\n}\n\n/**\n * Transform type definitions (built-in)\n * Combines metadata (id, label) with component class\n */\nexport const TRANSFORM_TYPES: TransformDef[] = [\n { id: 'custom', label: 'CUSTOM', Component: CustomTransform },\n // NOTE: distort transform is not yet implemented. Removed from registry\n // to prevent it from appearing in UI that iterates over registered transforms.\n // Re-add with a valid Component when implementation is ready.\n { id: 'circle', label: 'CIRCLE', Component: CircleTransform },\n { id: 'lean', label: 'LEAN', Component: LeanTransform },\n { id: 'arch', label: 'ARCH', Component: ArchTransform },\n { id: 'ascend', label: 'ASCEND', Component: AscendTransform },\n { id: 'wave', label: 'WAVE', Component: WaveTransform },\n { id: 'flag', label: 'FLAG', Component: FlagTransform },\n { id: 'shape', label: 'SHAPE', Component: ShapeElement },\n { id: 'path', label: 'PATH', Component: PathElement },\n { id: 'image', label: 'IMAGE', Component: ImageElement },\n // Use a getter to break circular dependency: GroupElement imports getTransformById\n // from this module, so GroupElement may be in the TDZ during module evaluation.\n // The getter defers access until runtime when both modules are fully initialized.\n { id: 'group', label: 'GROUP', get Component() { return GroupElement; } } as TransformDef,\n];\n\n/** Set of built-in transform type IDs (immutable) */\nconst BUILTIN_IDS = new Set(TRANSFORM_TYPES.map((t) => t.id));\n\n/** Dynamic transforms registered at runtime via the plugin API */\nconst _dynamicTransforms = new Map<string, TransformDef>();\n\n/**\n * Check if a transform type ID belongs to a built-in type\n */\nexport function isBuiltinTransformType(id: string): boolean {\n return BUILTIN_IDS.has(id);\n}\n\n/**\n * Register a dynamic transform type at runtime.\n * Throws if the id conflicts with a built-in type or is already registered.\n */\nexport function registerTransform(id: string, def: TransformDef): void {\n if (BUILTIN_IDS.has(id)) {\n throw new Error(\n `Cannot register transform type \"${id}\": conflicts with built-in type`\n );\n }\n if (_dynamicTransforms.has(id)) {\n throw new Error(\n `Cannot register transform type \"${id}\": already registered as a custom type`\n );\n }\n _dynamicTransforms.set(id, def);\n}\n\n/**\n * Unregister a dynamic transform type.\n * Returns true if the type was removed, false if it was not found.\n * Throws if attempting to unregister a built-in type.\n */\nexport function unregisterTransform(id: string): boolean {\n if (BUILTIN_IDS.has(id)) {\n throw new Error(\n `Cannot unregister transform type \"${id}\": built-in types cannot be removed`\n );\n }\n return _dynamicTransforms.delete(id);\n}\n\n/**\n * Get all registered transforms (built-in + dynamic)\n */\nexport function getAllTransforms(): TransformDef[] {\n return [...TRANSFORM_TYPES, ...Array.from(_dynamicTransforms.values())];\n}\n\n/**\n * Get transform definition by id (built-in or dynamic)\n */\nexport function getTransformById(id: string): TransformDef | undefined {\n const builtin = TRANSFORM_TYPES.find((t) => t.id === id);\n if (builtin) return builtin;\n return _dynamicTransforms.get(id);\n}\n\n/**\n * Get transform controls by id\n */\nexport function getTransformControls(id: TransformType): TransformControl[] {\n return TRANSFORM_CONTROLS[id] || [];\n}\n\n/**\n * Reset all dynamic transforms (for testing only)\n * @internal\n */\nexport function _resetDynamicTransforms(): void {\n _dynamicTransforms.clear();\n}\n","/**\n * GroupElement - Container element for grouping multiple elements together\n *\n * Key Design:\n * - Extends BaseElement for common element behavior\n * - Children maintain world coordinates (not local)\n * - Transforms propagate to all children\n * - Calculates bounds from children positions\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { TextElement } from './TextElement.js';\nimport { ImageElement } from './ImageElement.js';\nimport { ShapeElement } from './ShapeElement.js';\nimport { PathElement } from './PathElement.js';\nimport { Transform } from './Transform.js';\nimport type { AnyElementConfig, ImageTransformData } from '../types/index.js';\nimport { getThemeAccentColor } from '../constants.js';\nimport { getGroupClippingPainter } from './paint-registry.js';\nimport { getTransformById } from '../transforms/registry.js';\nimport type {\n GroupElementConfig,\n GroupTransformData,\n BoundingBox,\n Point,\n ResizeAnchor,\n TransformStartData,\n} from '../types/index.js';\n\n/** Child element types that can be contained in a group */\ntype ChildElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport class GroupElement extends BaseElement {\n declare transformType: 'group';\n declare transformData: GroupTransformData;\n children: ChildElement[];\n isGroup: boolean = true;\n\n // Cache the base bounding box dimensions (calculated at rotation=0 or when children change)\n private _baseBBoxWidth: number = 0;\n private _baseBBoxHeight: number = 0;\n\n // Cache the rotation center (should stay fixed during rotation)\n private _rotationCenterX: number = 0;\n private _rotationCenterY: number = 0;\n\n constructor(config: Partial<GroupElementConfig> = {}) {\n super(config);\n this.transformType = 'group';\n\n // Handle children - they might be element instances or JSON objects\n const rawChildren = config.children || [];\n this.children = rawChildren.map((child) => {\n // If it's already an element instance (has methods), use it directly\n if (child instanceof BaseElement) {\n return child as ChildElement;\n }\n // Otherwise, it's a JSON object - need to reconstruct it\n // We can't import ElementFactory here due to circular dependency,\n // so we'll reconstruct children manually based on their transformType\n return this._reconstructChildFromJSON(child as unknown as Record<string, unknown>);\n });\n\n this.transformData = {\n type: 'group',\n };\n\n // Calculate initial bounds from children\n if (this.children.length > 0) {\n this.updateBoundsFromChildren(true); // Force calculation on initialization (also sets rotation center)\n }\n }\n\n /**\n * Reconstruct a child element from JSON using the transform registry.\n * Looks up the Component class by transformType and constructs it.\n * Adding new element types only requires updating the registry, not this file.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- JSON deserialization: config comes from toJSON() and constructors accept Partial<Config> with varying signatures\n private _reconstructChildFromJSON(config: any): ChildElement {\n const transformType = config.transformType || config.type;\n const transformDef = getTransformById(transformType);\n\n if (!transformDef || !transformDef.Component) {\n throw new Error(`Unknown transform type: ${transformType}`);\n }\n\n // Registry Components all extend BaseElement which satisfies ChildElement union\n return new transformDef.Component(config) as ChildElement;\n }\n\n /**\n * Calculate group bounds from all children\n * Returns axis-aligned bounding box containing all children\n */\n getBoundingBox(): BoundingBox {\n if (this.children.length === 0) {\n return { x: this.x - 50, y: this.y - 50, width: 100, height: 100 };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n this.children.forEach((child) => {\n const bbox = child.getVisualBoundingBox();\n const corners = this._getRotatedCorners(child, bbox);\n\n corners.forEach((corner) => {\n minX = Math.min(minX, corner.x);\n minY = Math.min(minY, corner.y);\n maxX = Math.max(maxX, corner.x);\n maxY = Math.max(maxY, corner.y);\n });\n });\n\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n /**\n * Visual bounding box uses the oriented bounding box for groups\n * This ensures handles and hover effects stay aligned with the dotted border during rotation\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getOrientedBoundingBox();\n }\n\n /**\n * Get rotation anchor - returns the cached rotation center\n * This ensures consistent rotation behavior even if this.x/this.y change\n */\n getRotationAnchor(): Point {\n return {\n x: this._rotationCenterX || this.x,\n y: this._rotationCenterY || this.y,\n };\n }\n\n /**\n * Get the four corners of an element's bounding box in world space\n */\n private _getRotatedCorners(element: ChildElement, bbox: BoundingBox): Point[] {\n const transform = new Transform(element);\n\n // Calculate the center of the bounding box\n const centerX = bbox.x + bbox.width / 2;\n const centerY = bbox.y + bbox.height / 2;\n\n // Calculate offsets from the element's center to the bbox center\n const offsetX = centerX - element.x;\n const offsetY = centerY - element.y;\n\n // Define corners relative to the bbox center\n const corners = [\n { localX: offsetX - bbox.width / 2, localY: offsetY - bbox.height / 2 },\n { localX: offsetX + bbox.width / 2, localY: offsetY - bbox.height / 2 },\n { localX: offsetX - bbox.width / 2, localY: offsetY + bbox.height / 2 },\n { localX: offsetX + bbox.width / 2, localY: offsetY + bbox.height / 2 },\n ];\n\n return corners.map((corner) => transform.localToWorld(corner.localX, corner.localY));\n }\n\n /**\n * Update group center and size from children\n * @param forceRecalcDimensions - Force recalculation of base dimensions (use when children are modified, not during rotation)\n */\n updateBoundsFromChildren(forceRecalcDimensions: boolean = false): void {\n const bbox = this.getBoundingBox();\n this.x = bbox.x + bbox.width / 2;\n this.y = bbox.y + bbox.height / 2;\n\n // Only recalculate base dimensions when explicitly requested or when cache is empty\n // NEVER recalculate during rotation - the cache should remain stable\n if (forceRecalcDimensions || this._baseBBoxWidth === 0 || this._baseBBoxHeight === 0) {\n const dims = this._calculateInitialDimensions();\n this._baseBBoxWidth = dims.width;\n this._baseBBoxHeight = dims.height;\n // Update rotation center when dimensions are recalculated (group creation or children changed)\n this._rotationCenterX = this.x;\n this._rotationCenterY = this.y;\n }\n }\n\n /**\n * Calculate the axis-aligned bounding box dimensions at current state\n * This should only be called when the group is created or when children change (not during rotation)\n *\n * Like multi-selection, we simply cache the current axis-aligned bbox dimensions\n * and use them for rendering the selection border.\n */\n private _calculateInitialDimensions(): { width: number; height: number } {\n if (this.children.length === 0) {\n return { width: 100, height: 100 };\n }\n\n // Simply use the current axis-aligned bounding box dimensions\n // This is the same approach as multi-selection OBB initialization\n const bbox = this.getBoundingBox();\n const result = {\n width: bbox.width,\n height: bbox.height,\n };\n\n return result;\n }\n\n /**\n * Render the group with clipping support\n * Elements with isClipping=true clip all elements below them in the group\n */\n render(ctx: CanvasRenderingContext2D, isSelected: boolean = false, isHovered: boolean = false): void {\n // Filter out invisible children\n const visibleChildren = this.children.filter((child) => child.visible !== false);\n\n // Check if any visible children have clipping enabled\n const hasClipping = visibleChildren.some((child) => child.isClipping);\n\n\n if (!hasClipping) {\n // No clipping - render normally\n visibleChildren.forEach((child) => {\n child.render(ctx, false);\n });\n } else {\n // Has clipping — composite via the registered painter (the\n // 'destination-in' mask compositing is paint-pipeline code; the model\n // graph must stay renderer-free — P1 element/renderer split).\n getGroupClippingPainter()(ctx, visibleChildren);\n }\n\n // Render group outline when selected or hovered\n if (isSelected) {\n this._renderGroupOutline(ctx, 1.0);\n } else if (isHovered) {\n this._renderGroupOutline(ctx, 0.3);\n }\n }\n\n /**\n * Render dashed outline around group\n * Uses an oriented bounding box that doesn't change size during rotation\n */\n private _renderGroupOutline(ctx: CanvasRenderingContext2D, opacity: number = 1.0): void {\n // Get the oriented bounding box (tight fit ignoring rotation)\n const obb = this.getOrientedBoundingBox();\n\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = 2;\n ctx.setLineDash([5, 5]);\n\n // Apply rotation around the CACHED rotation center (not current this.x, this.y)\n // The OBB is already centered at the rotation center\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n if (this.rotation !== 0) {\n ctx.translate(centerX, centerY);\n ctx.rotate((-this.rotation * Math.PI) / 180);\n ctx.translate(-centerX, -centerY);\n }\n\n // Draw the rectangle using OBB coordinates\n ctx.strokeRect(obb.x, obb.y, obb.width, obb.height);\n ctx.setLineDash([]);\n ctx.restore();\n }\n\n /**\n * Get oriented bounding box (uses cached base dimensions for stability during rotation)\n *\n * This follows the same approach as multi-selection:\n * - Cache the initial axis-aligned bbox dimensions when group is created\n * - Use those cached dimensions during rotation (never recalculate)\n * - This keeps the selection border tight and stable during rotation\n */\n private getOrientedBoundingBox(): BoundingBox {\n if (this.children.length === 0) {\n return { x: this.x - 50, y: this.y - 50, width: 100, height: 100 };\n }\n\n // Use cached base dimensions if available, otherwise calculate current bbox\n let width: number, height: number;\n\n if (this._baseBBoxWidth > 0 && this._baseBBoxHeight > 0) {\n // Use cached dimensions (set when group was created or children changed)\n width = this._baseBBoxWidth;\n height = this._baseBBoxHeight;\n } else {\n // Fall back to current bbox (happens on first render before cache is set)\n const bbox = this.getBoundingBox();\n width = bbox.width;\n height = bbox.height;\n\n // Cache for future use\n this._baseBBoxWidth = width;\n this._baseBBoxHeight = height;\n }\n\n // Return box centered at the cached rotation center (not this.x/this.y which might drift)\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n return {\n x: centerX - width / 2,\n y: centerY - height / 2,\n width,\n height,\n };\n }\n\n /**\n * Move the group (moves all children)\n */\n move(dx: number, dy: number): void {\n // Update group center position\n this.x += dx;\n this.y += dy;\n\n // Update cached rotation center to follow the group\n this._rotationCenterX += dx;\n this._rotationCenterY += dy;\n\n // Move all children by the same amount\n this.children.forEach((child) => {\n child.move(dx, dy);\n });\n }\n\n /**\n * Rotate the group (rotates all children around group center)\n */\n setRotation(newRotation: number): void {\n const deltaRotation = newRotation - this.rotation;\n this.rotation = newRotation;\n\n // Use cached rotation center for consistency\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n this.children.forEach((child) => {\n // Rotate child's position around the cached group center\n const rotated = Transform.rotatePointAroundAnchor(child.x, child.y, centerX, centerY, deltaRotation);\n child.x = rotated.x;\n child.y = rotated.y;\n\n // Add rotation to child's own rotation\n child.setRotation(child.rotation + deltaRotation);\n });\n\n // Update this.x and this.y to stay at the rotation center\n // This prevents drift during rotation\n this.x = centerX;\n this.y = centerY;\n }\n\n /**\n * Resize the group (scales all children proportionally)\n * Uses the same fixed-corner approach as multi-selection resize\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n if (!startData.childrenStartData || !startData.width || !startData.height) {\n return;\n }\n\n const scaleX = newWidth / startData.width;\n const scaleY = newHeight / startData.height;\n const scale = (scaleX + scaleY) / 2; // Uniform scale\n\n // Calculate the fixed corner in LOCAL space (unrotated)\n let localFixedX: number, localFixedY: number;\n\n switch (anchor) {\n case 'top-left':\n localFixedX = startData.width / 2;\n localFixedY = startData.height / 2;\n break;\n case 'top-right':\n localFixedX = -startData.width / 2;\n localFixedY = startData.height / 2;\n break;\n case 'bottom-left':\n localFixedX = startData.width / 2;\n localFixedY = -startData.height / 2;\n break;\n case 'bottom-right':\n localFixedX = -startData.width / 2;\n localFixedY = -startData.height / 2;\n break;\n default:\n // Fallback to center\n localFixedX = 0;\n localFixedY = 0;\n }\n\n // Rotate the fixed corner to world space using the group's rotation\n const rotationRad = (-startData.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const fixedX = startData.x + (localFixedX * cos - localFixedY * sin);\n const fixedY = startData.y + (localFixedX * sin + localFixedY * cos);\n\n this.children.forEach((child, index) => {\n const childStart = startData.childrenStartData![index];\n\n // Scale position offset from the FIXED corner (not center)\n // This is the same approach as multi-selection resize\n const offsetX = childStart.x - fixedX;\n const offsetY = childStart.y - fixedY;\n const newX = fixedX + offsetX * scale;\n const newY = fixedY + offsetY * scale;\n\n child.x = newX;\n child.y = newY;\n\n // Scale child dimensions using the same approach as multi-selection resize\n if (child instanceof ImageElement) {\n // For images, scale the transform data directly\n const childTransform = childStart.transformData as unknown as ImageTransformData;\n const newChildWidth = childTransform.width * scale;\n const newChildHeight = childTransform.height * scale;\n child.transformData.width = newChildWidth;\n child.transformData.height = newChildHeight;\n } else if (child instanceof GroupElement) {\n // For nested groups, recursively resize\n const newChildWidth = (childStart.width ?? 0) * scale;\n const newChildHeight = (childStart.height ?? 0) * scale;\n child.resize(anchor, newChildWidth, newChildHeight, childStart);\n } else {\n // For text elements, use the element's resize method (handles transform-specific logic)\n const newChildWidth = (childStart.width ?? 0) * scale;\n const newChildHeight = (childStart.height ?? 0) * scale;\n child.resize(anchor, newChildWidth, newChildHeight, childStart);\n }\n });\n\n // Scale the cached dimensions directly (proportional to the scale factor)\n this._baseBBoxWidth = startData.width! * scale;\n this._baseBBoxHeight = startData.height! * scale;\n\n // Calculate the new group center by scaling the offset from the fixed corner\n const centerOffsetX = startData.x - fixedX;\n const centerOffsetY = startData.y - fixedY;\n this.x = fixedX + centerOffsetX * scale;\n this.y = fixedY + centerOffsetY * scale;\n\n // Update the rotation center to the new center position\n this._rotationCenterX = this.x;\n this._rotationCenterY = this.y;\n }\n\n /**\n * Get transform start data (includes children data)\n * Use cached base dimensions instead of axis-aligned bbox for consistency with visual border\n */\n getTransformStartData(): TransformStartData {\n // Use the cached base dimensions which match the oriented bounding box (visual border)\n // This prevents the jump when resizing a rotated group\n const width = this._baseBBoxWidth > 0 ? this._baseBBoxWidth : this.getBoundingBox().width;\n const height = this._baseBBoxHeight > 0 ? this._baseBBoxHeight : this.getBoundingBox().height;\n\n // Use the cached rotation center coordinates to match where the visual border is actually drawn\n // This prevents position jump when starting a resize\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n return {\n id: this.id,\n x: centerX,\n y: centerY,\n width: width,\n height: height,\n rotation: this.rotation,\n transformData: this.transformData as unknown as Record<string, unknown>,\n childrenStartData: this.children.map((child) => child.getTransformStartData()),\n };\n }\n\n /**\n * Hit test - check if point is inside group's oriented bounding box\n * This allows clicking anywhere within the selection border to drag the group\n */\n hitTest(x: number, y: number): boolean {\n // Use the oriented bounding box (matches the visual selection border)\n const obb = this.getOrientedBoundingBox();\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n // If not rotated, use simple axis-aligned test\n if (this.rotation === 0) {\n const isInside = x >= obb.x && x <= obb.x + obb.width && y >= obb.y && y <= obb.y + obb.height;\n if (isInside) {\n }\n return isInside;\n }\n\n // For rotated groups, transform the click point to local coordinates\n // then check against the unrotated rectangle\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate point to origin (relative to rotation center)\n const dx = x - centerX;\n const dy = y - centerY;\n\n // Rotate point back to local space (inverse rotation)\n const localX = centerX + (dx * cos - dy * sin);\n const localY = centerY + (dx * sin + dy * cos);\n\n // Check if the local point is within the OBB rectangle\n const isInside = localX >= obb.x && localX <= obb.x + obb.width && localY >= obb.y && localY <= obb.y + obb.height;\n\n if (isInside) {\n }\n\n return isInside;\n }\n\n /**\n * Only corner handles for groups (uniform scale)\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): GroupElementConfig {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n transformType: 'group',\n transformData: {\n type: 'group',\n },\n children: this.children.map((child) => child.toJSON()) as AnyElementConfig[],\n };\n }\n\n /**\n * Clone the group (deep copy with children)\n */\n clone(): GroupElement {\n const clonedChildren = this.children.map((child) => child.clone());\n const clonedGroup = new GroupElement({\n id: this.id,\n x: this.x,\n y: this.y,\n rotation: this.rotation,\n // Children are element instances here; the constructor handles both instances and configs\n children: clonedChildren as unknown as AnyElementConfig[],\n });\n // Preserve cached dimensions to avoid recalculation during rotation\n clonedGroup._baseBBoxWidth = this._baseBBoxWidth;\n clonedGroup._baseBBoxHeight = this._baseBBoxHeight;\n // Preserve cached rotation center\n clonedGroup._rotationCenterX = this._rotationCenterX;\n clonedGroup._rotationCenterY = this._rotationCenterY;\n\n // Copy opacity\n clonedGroup.opacity = this.opacity;\n\n // Copy layer properties\n clonedGroup.name = this.name;\n clonedGroup.visible = this.visible;\n clonedGroup.locked = this.locked;\n\n // Copy effects properties (blendMode covers isClipping via getter)\n if (this.blendMode) clonedGroup.blendMode = this.blendMode;\n if (this.knockoutParts) clonedGroup.knockoutParts = { ...this.knockoutParts };\n if (this.stroke) {\n clonedGroup.stroke = {\n ...this.stroke,\n ...(this.stroke.dashArray && { dashArray: [...this.stroke.dashArray] }),\n };\n }\n // maskElement is a nested config object — structuredClone so clones don't share it\n if (this.masks) {\n clonedGroup.masks = this.masks.map((m) => ({\n ...m,\n maskElement: structuredClone(m.maskElement),\n }));\n }\n if (this.distressEffect) clonedGroup.distressEffect = { ...this.distressEffect };\n if (this.shadow) clonedGroup.shadow = { ...this.shadow };\n\n return clonedGroup;\n }\n\n /**\n * Add a child to the group\n */\n addChild(child: ChildElement): void {\n this.children.push(child);\n this.updateBoundsFromChildren(true); // Recalculate dimensions when children added\n }\n\n /**\n * Remove a child from the group\n */\n removeChild(childId: string): boolean {\n const index = this.children.findIndex((c) => c.id === childId);\n if (index >= 0) {\n this.children.splice(index, 1);\n this.updateBoundsFromChildren(true); // Recalculate dimensions when children removed\n return true;\n }\n return false;\n }\n\n /**\n * Get all children (for edit mode)\n */\n getChildren(): ChildElement[] {\n return [...this.children];\n }\n\n /**\n * Find which child (if any) was clicked at the given point\n * Returns the child element or null if no child was hit\n */\n hitTestChild(x: number, y: number): ChildElement | null {\n // Test children in reverse order (top to bottom)\n for (let i = this.children.length - 1; i >= 0; i--) {\n const child = this.children[i];\n if (child.hitTest(x, y)) {\n return child;\n }\n }\n return null;\n }\n}\n","/**\n * ElementStore - Normalized element storage with O(1) lookups by ID.\n *\n * Replaces the flat array pattern (`EditorElement[]`) with a Map-based\n * structure while preserving render order via an ordered ID list.\n *\n * Immutable update pattern: every mutation method returns a new instance\n * so React can detect state changes via referential equality.\n *\n * @example\n * ```ts\n * const store = new ElementStore(elements);\n * const el = store.get('element-1'); // O(1) lookup\n * const next = store.update(updatedElement); // Returns new instance\n * const arr = next.toArray(); // Backwards-compatible array\n * ```\n *\n * @module\n */\n\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { ShapeElement } from './ShapeElement.js';\nimport type { PathElement } from './PathElement.js';\n\n/** Union of all element types that can be stored. */\nexport type StoreElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport class ElementStore {\n private byId: Map<string, StoreElement>;\n private order: string[]; // Maintains render order\n private _cachedArray: StoreElement[] | null = null;\n\n constructor(elements?: StoreElement[]) {\n this.byId = new Map();\n this.order = [];\n if (elements) {\n for (const el of elements) {\n this.byId.set(el.id, el);\n this.order.push(el.id);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Read operations (O(1) where possible)\n // ---------------------------------------------------------------------------\n\n /** O(1) lookup by ID. */\n get(id: string): StoreElement | undefined {\n return this.byId.get(id);\n }\n\n /** Check whether the store contains an element with the given ID. */\n has(id: string): boolean {\n return this.byId.has(id);\n }\n\n /** Get all elements in render order. */\n getAll(): StoreElement[] {\n return this.order.map((id) => this.byId.get(id)!);\n }\n\n /** Number of elements in the store. */\n get size(): number {\n return this.byId.size;\n }\n\n /** Get the ordered ID list (read-only copy). */\n getOrder(): readonly string[] {\n return this.order;\n }\n\n /** O(n) lookup by name. Returns the first element with the given name, or undefined. */\n getByName(name: string): StoreElement | undefined {\n for (const id of this.order) {\n const el = this.byId.get(id);\n if (el && el.name === name) return el;\n }\n return undefined;\n }\n\n /** O(n) lookup by name. Returns all elements with the given name in render order. */\n getAllByName(name: string): StoreElement[] {\n const result: StoreElement[] = [];\n for (const id of this.order) {\n const el = this.byId.get(id);\n if (el && el.name === name) result.push(el);\n }\n return result;\n }\n\n // ---------------------------------------------------------------------------\n // Immutable mutation operations (return new ElementStore instances)\n // ---------------------------------------------------------------------------\n\n /** Replace an element in-place (preserves order). */\n update(element: StoreElement): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n // If the element didn't exist, append to order\n if (!this.byId.has(element.id)) {\n next.order.push(element.id);\n }\n return next;\n }\n\n /**\n * Replace an element found by a predicate with a new element.\n * This mirrors `setElements(prev => prev.map(el => el.id === id ? newEl : el))`.\n */\n updateById(id: string, element: StoreElement): ElementStore {\n if (!this.byId.has(id)) return this;\n const next = this.clone();\n // If the id changed (unlikely but safe), remove old and insert new at same position\n if (id !== element.id) {\n next.byId.delete(id);\n const idx = next.order.indexOf(id);\n if (idx !== -1) next.order[idx] = element.id;\n }\n next.byId.set(element.id, element);\n return next;\n }\n\n /** Add an element at the end of the render order. */\n add(element: StoreElement): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n if (!this.byId.has(element.id)) {\n next.order.push(element.id);\n }\n return next;\n }\n\n /** Insert an element after a specific element ID. */\n insertAfter(element: StoreElement, afterId: string): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n const idx = next.order.indexOf(afterId);\n if (idx !== -1) {\n next.order.splice(idx + 1, 0, element.id);\n } else {\n // Fallback: append to end\n next.order.push(element.id);\n }\n return next;\n }\n\n /** Remove an element by ID. */\n remove(id: string): ElementStore {\n if (!this.byId.has(id)) return this;\n const next = this.clone();\n next.byId.delete(id);\n next.order = next.order.filter((oid) => oid !== id);\n return next;\n }\n\n /** Reorder element to a new index in the render order. */\n reorder(id: string, newIndex: number): ElementStore {\n const next = this.clone();\n next.order = next.order.filter((oid) => oid !== id);\n next.order.splice(newIndex, 0, id);\n return next;\n }\n\n /**\n * Apply a batch update: replace elements whose IDs match.\n * Returns a new store. Useful for applying multiple updates at once\n * (e.g., reordering by an ID array from undo/redo).\n */\n replaceAll(elements: StoreElement[]): ElementStore {\n return new ElementStore(elements);\n }\n\n /**\n * Filter elements by predicate while preserving order.\n * Returns a new store containing only elements that match.\n */\n filter(predicate: (el: StoreElement) => boolean): ElementStore {\n const filtered = this.getAll().filter(predicate);\n return new ElementStore(filtered);\n }\n\n /**\n * Map over elements in render order, returning a new store.\n * The mapping function receives each element and returns a (possibly modified) element.\n */\n map(fn: (el: StoreElement) => StoreElement): ElementStore {\n const mapped = this.getAll().map(fn);\n return new ElementStore(mapped);\n }\n\n /**\n * Set a completely new ordering for all elements.\n * IDs not in the new order are dropped; new IDs not in the store are ignored.\n */\n setOrder(newOrder: string[]): ElementStore {\n const next = this.clone();\n next.order = newOrder.filter((id) => next.byId.has(id));\n return next;\n }\n\n // ---------------------------------------------------------------------------\n // Conversion helpers\n // ---------------------------------------------------------------------------\n\n /** Convert to a plain array (backwards-compatible with `EditorElement[]`). Cached since the store is immutable. */\n toArray(): StoreElement[] {\n if (this._cachedArray === null) {\n this._cachedArray = this.getAll();\n }\n return this._cachedArray;\n }\n\n /** Create from a plain array. */\n static fromArray(elements: StoreElement[]): ElementStore {\n return new ElementStore(elements);\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n /** Create a shallow clone (new Map/Array, same element references). */\n private clone(): ElementStore {\n const store = new ElementStore();\n store.byId = new Map(this.byId);\n store.order = [...this.order];\n return store;\n }\n}\n","/**\n * CommandHistory - Manages undo/redo with the Command pattern\n * Stores operations as commands that can be undone and redone\n */\n\nimport type { BaseElement } from './BaseElement.js';\nimport type { Command as CommandInterface } from '../types/index.js';\nimport { ArtboardElement } from './ArtboardElement.js';\nimport { ArtboardManager } from './ArtboardManager.js';\n\n/**\n * Base Command class\n * All commands must implement execute() and undo()\n */\nexport class Command implements CommandInterface {\n /**\n * Execute the command\n */\n execute(): void {\n throw new Error('Command.execute() must be implemented');\n }\n\n /**\n * Undo the command\n */\n undo(): void {\n throw new Error('Command.undo() must be implemented');\n }\n}\n\n/**\n * UpdateElementCommand - Command to update an element's properties\n */\nexport class UpdateElementCommand extends Command {\n elementId: string;\n oldElement: BaseElement | null;\n newElement: BaseElement | null;\n onUpdate: (element: BaseElement) => void;\n\n constructor(\n elementId: string,\n oldElement: BaseElement | null,\n newElement: BaseElement | null,\n onUpdate: (element: BaseElement) => void\n ) {\n super();\n this.elementId = elementId;\n this.oldElement = oldElement ? oldElement.clone() : null;\n this.newElement = newElement ? newElement.clone() : null;\n this.onUpdate = onUpdate;\n }\n\n execute(): void {\n if (this.newElement) {\n this.onUpdate(this.newElement);\n }\n }\n\n undo(): void {\n if (this.oldElement) {\n this.onUpdate(this.oldElement);\n }\n }\n}\n\n/**\n * AddElementCommand - Command to add a new element\n */\nexport class AddElementCommand extends Command {\n element: BaseElement;\n onAdd: (element: BaseElement) => void;\n onRemove: (id: string) => void;\n\n constructor(element: BaseElement, onAdd: (element: BaseElement) => void, onRemove: (id: string) => void) {\n super();\n this.element = element.clone();\n this.onAdd = onAdd;\n this.onRemove = onRemove;\n }\n\n execute(): void {\n this.onAdd(this.element);\n }\n\n undo(): void {\n this.onRemove(this.element.id);\n }\n}\n\n/**\n * RemoveElementCommand - Command to remove an element\n */\nexport class RemoveElementCommand extends Command {\n element: BaseElement;\n onAdd: (element: BaseElement) => void;\n onRemove: (id: string) => void;\n\n constructor(element: BaseElement, onAdd: (element: BaseElement) => void, onRemove: (id: string) => void) {\n super();\n this.element = element.clone();\n this.onAdd = onAdd;\n this.onRemove = onRemove;\n }\n\n execute(): void {\n this.onRemove(this.element.id);\n }\n\n undo(): void {\n this.onAdd(this.element);\n }\n}\n\n/**\n * BatchCommand - Execute multiple commands as one atomic operation\n */\nexport class BatchCommand extends Command {\n commands: Command[];\n\n constructor(commands: Command[] = []) {\n super();\n this.commands = commands;\n }\n\n execute(): void {\n this.commands.forEach((cmd) => cmd.execute());\n }\n\n undo(): void {\n // Undo in reverse order\n for (let i = this.commands.length - 1; i >= 0; i--) {\n this.commands[i].undo();\n }\n }\n}\n\n/**\n * CompoundCommand - Wraps an array of commands into a single undo entry.\n * Used by executeBatch() to group multiple operations atomically.\n *\n * - execute(): runs all commands in order\n * - undo(): reverts all commands in reverse order\n */\nexport class CompoundCommand extends Command {\n private commands: Command[];\n\n constructor(commands: Command[]) {\n super();\n this.commands = commands;\n }\n\n execute(): void {\n this.commands.forEach((cmd) => cmd.execute());\n }\n\n undo(): void {\n // Undo in reverse order for correct rollback\n for (let i = this.commands.length - 1; i >= 0; i--) {\n this.commands[i].undo();\n }\n }\n}\n\n/**\n * CommandHistory - Manages undo/redo history\n */\nexport class CommandHistory {\n history: Command[];\n currentIndex: number;\n maxSize: number;\n\n constructor(maxSize: number = 50) {\n this.history = []; // Array of executed commands\n this.currentIndex = -1; // Current position in history\n this.maxSize = maxSize;\n }\n\n /**\n * Execute a command and add it to history\n */\n execute(command: Command): void {\n // Execute the command\n command.execute();\n\n // Remove any commands after current index (redo history)\n this.history = this.history.slice(0, this.currentIndex + 1);\n\n // Add new command to history\n this.history.push(command);\n this.currentIndex++;\n\n // Limit history size\n if (this.history.length > this.maxSize) {\n this.history.shift();\n this.currentIndex--;\n }\n }\n\n /**\n * Execute multiple commands as a single atomic undo entry.\n * All commands are wrapped in a CompoundCommand so that\n * undo/redo treats the entire batch as one operation.\n *\n * No-op if commands array is empty.\n */\n executeBatch(commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n\n const compound = new CompoundCommand(commands);\n this.execute(compound);\n }\n\n /**\n * Undo the last command\n */\n undo(): boolean {\n if (!this.canUndo()) {\n return false;\n }\n\n const command = this.history[this.currentIndex];\n command.undo();\n this.currentIndex--;\n return true;\n }\n\n /**\n * Redo the next command\n */\n redo(): boolean {\n if (!this.canRedo()) {\n return false;\n }\n\n this.currentIndex++;\n const command = this.history[this.currentIndex];\n command.execute();\n return true;\n }\n\n /**\n * Check if undo is available\n */\n canUndo(): boolean {\n return this.currentIndex >= 0;\n }\n\n /**\n * Check if redo is available\n */\n canRedo(): boolean {\n return this.currentIndex < this.history.length - 1;\n }\n\n /**\n * Clear all history\n */\n clear(): void {\n this.history = [];\n this.currentIndex = -1;\n }\n\n /**\n * Get current state info\n */\n getState(): { canUndo: boolean; canRedo: boolean; historySize: number; currentIndex: number } {\n return {\n canUndo: this.canUndo(),\n canRedo: this.canRedo(),\n historySize: this.history.length,\n currentIndex: this.currentIndex,\n };\n }\n}\n\n/**\n * CreateArtboardCommand - Command to create a new artboard\n */\nexport class CreateArtboardCommand extends Command {\n artboard: ArtboardElement;\n artboardManager: ArtboardManager;\n\n constructor(artboard: ArtboardElement, artboardManager: ArtboardManager) {\n super();\n this.artboard = artboard.clone();\n this.artboardManager = artboardManager;\n }\n\n execute(): void {\n // Re-create the artboard in the manager\n const artboard = this.artboard.clone();\n this.artboardManager.createArtboard({\n id: artboard.id,\n name: artboard.name,\n x: artboard.x,\n y: artboard.y,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n });\n }\n\n undo(): void {\n this.artboardManager.deleteArtboard(this.artboard.id);\n }\n}\n\n/**\n * DeleteArtboardCommand - Command to delete an artboard\n */\nexport class DeleteArtboardCommand extends Command {\n artboard: ArtboardElement;\n artboardManager: ArtboardManager;\n elementsOnArtboard: string[];\n\n constructor(artboard: ArtboardElement, artboardManager: ArtboardManager) {\n super();\n this.artboard = artboard.clone();\n this.artboardManager = artboardManager;\n this.elementsOnArtboard = artboard.getElementIds();\n }\n\n execute(): void {\n this.artboardManager.deleteArtboard(this.artboard.id);\n }\n\n undo(): void {\n // Re-create the artboard\n const artboard = this.artboard.clone();\n this.artboardManager.createArtboard({\n id: artboard.id,\n name: artboard.name,\n x: artboard.x,\n y: artboard.y,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n elementIds: this.elementsOnArtboard,\n });\n\n // Restore element mappings\n this.elementsOnArtboard.forEach((elementId) => {\n this.artboardManager.addElementToArtboard(elementId, artboard.id);\n });\n }\n}\n\n/**\n * UpdateArtboardCommand - Command to update artboard properties\n */\nexport class UpdateArtboardCommand extends Command {\n artboardId: string;\n oldProperties: Partial<ArtboardElement>;\n newProperties: Partial<ArtboardElement>;\n artboardManager: ArtboardManager;\n\n constructor(\n artboardId: string,\n oldProperties: Partial<ArtboardElement>,\n newProperties: Partial<ArtboardElement>,\n artboardManager: ArtboardManager\n ) {\n super();\n this.artboardId = artboardId;\n this.oldProperties = { ...oldProperties };\n this.newProperties = { ...newProperties };\n this.artboardManager = artboardManager;\n }\n\n execute(): void {\n this.artboardManager.updateArtboard(this.artboardId, this.newProperties);\n }\n\n undo(): void {\n this.artboardManager.updateArtboard(this.artboardId, this.oldProperties);\n }\n}\n\n/**\n * ReorderElementCommand - Command to reorder elements in the list\n */\nexport class ReorderElementCommand extends Command {\n draggedId: string;\n targetId: string;\n position: 'before' | 'after';\n oldOrder: string[];\n newOrder: string[];\n onReorder: (elementIds: string[]) => void;\n\n constructor(\n draggedId: string,\n targetId: string,\n position: 'before' | 'after',\n currentOrder: string[],\n onReorder: (elementIds: string[]) => void\n ) {\n super();\n this.draggedId = draggedId;\n this.targetId = targetId;\n this.position = position;\n this.oldOrder = [...currentOrder];\n this.onReorder = onReorder;\n\n // Calculate new order\n this.newOrder = this.calculateNewOrder();\n }\n\n private calculateNewOrder(): string[] {\n const order = [...this.oldOrder];\n\n // Remove dragged element\n const draggedIndex = order.indexOf(this.draggedId);\n if (draggedIndex === -1) return order;\n order.splice(draggedIndex, 1);\n\n // Find target index in the new array (after removal)\n const targetIndex = order.indexOf(this.targetId);\n if (targetIndex === -1) return order;\n\n // Insert at the appropriate position\n const insertIndex = this.position === 'before' ? targetIndex : targetIndex + 1;\n order.splice(insertIndex, 0, this.draggedId);\n\n return order;\n }\n\n execute(): void {\n this.onReorder(this.newOrder);\n }\n\n undo(): void {\n this.onReorder(this.oldOrder);\n }\n}\n\nexport default CommandHistory;\n","/**\n * HybridHistoryManager - Two-tier undo/redo system\n *\n * Manages:\n * - Global history: Artboard create/delete/rename operations\n * - Per-artboard history: Element operations within each artboard\n *\n * Smart undo/redo automatically determines which history to use based on:\n * - Active artboard\n * - Last operation type\n */\n\nimport { CommandHistory, Command } from './CommandHistory.js';\nimport type { ArtboardManager } from './ArtboardManager.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('HybridHistoryManager');\n\ntype HistoryType = 'global' | 'artboard';\n\ninterface HistoryEntry {\n type: HistoryType;\n artboardId?: string; // For artboard-level operations\n timestamp: number;\n}\n\nexport class HybridHistoryManager {\n private globalHistory: CommandHistory;\n private artboardHistories: Map<string, CommandHistory>;\n private lastOperation: HistoryEntry | null;\n private artboardManager: ArtboardManager;\n private maxHistorySize: number;\n private commandExecutedCallbacks: Array<(command: Command, type: HistoryType, artboardId?: string) => void> = [];\n\n constructor(artboardManager: ArtboardManager, maxHistorySize: number = 50) {\n this.globalHistory = new CommandHistory(maxHistorySize);\n this.artboardHistories = new Map();\n this.lastOperation = null;\n this.artboardManager = artboardManager;\n this.maxHistorySize = maxHistorySize;\n }\n\n /**\n * Register a callback to be called whenever a command is executed\n * Returns an unsubscribe function\n */\n onCommandExecuted(callback: (command: Command, type: HistoryType, artboardId?: string) => void): () => void {\n this.commandExecutedCallbacks.push(callback);\n return () => {\n const index = this.commandExecutedCallbacks.indexOf(callback);\n if (index > -1) {\n this.commandExecutedCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Notify all callbacks that a command was executed\n */\n private notifyCommandExecuted(command: Command, type: HistoryType, artboardId?: string): void {\n for (const callback of this.commandExecutedCallbacks) {\n try {\n callback(command, type, artboardId);\n } catch (error) {\n logger.error('Error in command executed callback:', error);\n }\n }\n }\n\n /**\n * Execute a global command (artboard-level operation)\n */\n executeGlobal(command: Command): void {\n this.globalHistory.execute(command);\n this.lastOperation = {\n type: 'global',\n timestamp: Date.now(),\n };\n this.notifyCommandExecuted(command, 'global');\n }\n\n /**\n * Execute a batch of commands as a single global undo entry.\n * No-op if commands array is empty.\n */\n executeBatchGlobal(commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n this.globalHistory.executeBatch(commands);\n this.lastOperation = {\n type: 'global',\n timestamp: Date.now(),\n };\n // Notify with the compound command that was created\n const compoundCommand = this.globalHistory.history[this.globalHistory.currentIndex];\n this.notifyCommandExecuted(compoundCommand, 'global');\n }\n\n /**\n * Execute a batch of commands as a single undo entry on a specific artboard.\n * No-op if commands array is empty.\n */\n executeBatchOnArtboard(artboardId: string, commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n history.executeBatch(commands);\n this.lastOperation = {\n type: 'artboard',\n artboardId,\n timestamp: Date.now(),\n };\n const compoundCommand = history.history[history.currentIndex];\n this.notifyCommandExecuted(compoundCommand, 'artboard', artboardId);\n }\n\n /**\n * Execute a command on a specific artboard\n */\n executeOnArtboard(artboardId: string, command: Command): void {\n // Get or create history for this artboard\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n\n history.execute(command);\n this.lastOperation = {\n type: 'artboard',\n artboardId,\n timestamp: Date.now(),\n };\n this.notifyCommandExecuted(command, 'artboard', artboardId);\n }\n\n /**\n * Peek at the command currently on top of an artboard's history (the entry\n * the next `undoArtboard` would undo), or null. Used by `updateElement`'s\n * coalescing to verify its last command is still the live tip before\n * folding a new patch into it.\n */\n peekArtboard(artboardId: string): Command | null {\n const history = this.artboardHistories.get(artboardId);\n if (!history) return null;\n return history.history[history.currentIndex] ?? null;\n }\n\n /**\n * Smart undo - determines which history to use\n */\n undo(): boolean {\n // If there's a last operation, undo from that history\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.undoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.undoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise, try active artboard first, then global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n return this.undoArtboard(activeArtboardId);\n }\n\n if (this.canUndoGlobal()) {\n return this.undoGlobal();\n }\n\n return false;\n }\n\n /**\n * Smart redo - determines which history to use\n */\n redo(): boolean {\n // If there's a last operation, redo from that history\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.redoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.redoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise, try active artboard first, then global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n return this.redoArtboard(activeArtboardId);\n }\n\n if (this.canRedoGlobal()) {\n return this.redoGlobal();\n }\n\n return false;\n }\n\n /**\n * Undo global operation\n */\n undoGlobal(): boolean {\n const result = this.globalHistory.undo();\n if (result) {\n this.updateLastOperation('global');\n }\n return result;\n }\n\n /**\n * Redo global operation\n */\n redoGlobal(): boolean {\n const result = this.globalHistory.redo();\n if (result) {\n this.updateLastOperation('global');\n }\n return result;\n }\n\n /**\n * Undo artboard operation\n */\n undoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n if (!history) {\n return false;\n }\n\n const result = history.undo();\n if (result) {\n this.updateLastOperation('artboard', artboardId);\n }\n return result;\n }\n\n /**\n * Redo artboard operation\n */\n redoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n if (!history) {\n return false;\n }\n\n const result = history.redo();\n if (result) {\n this.updateLastOperation('artboard', artboardId);\n }\n return result;\n }\n\n /**\n * Check if global undo is available\n */\n canUndoGlobal(): boolean {\n return this.globalHistory.canUndo();\n }\n\n /**\n * Check if global redo is available\n */\n canRedoGlobal(): boolean {\n return this.globalHistory.canRedo();\n }\n\n /**\n * Check if artboard undo is available\n */\n canUndoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n return history ? history.canUndo() : false;\n }\n\n /**\n * Check if artboard redo is available\n */\n canRedoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n return history ? history.canRedo() : false;\n }\n\n /**\n * Check if any undo is available (smart check)\n */\n canUndo(): boolean {\n // Check last operation first\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.canUndoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.canUndoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise check active artboard or global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n return true;\n }\n\n return this.canUndoGlobal();\n }\n\n /**\n * Check if any redo is available (smart check)\n */\n canRedo(): boolean {\n // Check last operation first\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.canRedoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.canRedoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise check active artboard or global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n return true;\n }\n\n return this.canRedoGlobal();\n }\n\n /**\n * Clear all histories\n */\n clear(): void {\n this.globalHistory.clear();\n this.artboardHistories.clear();\n this.lastOperation = null;\n }\n\n /**\n * Clear history for a specific artboard\n */\n clearArtboardHistory(artboardId: string): void {\n this.artboardHistories.delete(artboardId);\n if (this.lastOperation?.artboardId === artboardId) {\n this.lastOperation = null;\n }\n }\n\n /**\n * Get history for a specific artboard (create if doesn't exist)\n */\n getArtboardHistory(artboardId: string): CommandHistory {\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n return history;\n }\n\n /**\n * Get global history\n */\n getGlobalHistory(): CommandHistory {\n return this.globalHistory;\n }\n\n /**\n * Get state of all histories\n */\n getState(): {\n global: {\n canUndo: boolean;\n canRedo: boolean;\n historySize: number;\n };\n artboards: Map<\n string,\n {\n canUndo: boolean;\n canRedo: boolean;\n historySize: number;\n }\n >;\n lastOperation: HistoryEntry | null;\n } {\n const globalState = this.globalHistory.getState();\n const artboardStates = new Map();\n\n this.artboardHistories.forEach((history, artboardId) => {\n const state = history.getState();\n artboardStates.set(artboardId, {\n canUndo: state.canUndo,\n canRedo: state.canRedo,\n historySize: state.historySize,\n });\n });\n\n return {\n global: {\n canUndo: globalState.canUndo,\n canRedo: globalState.canRedo,\n historySize: globalState.historySize,\n },\n artboards: artboardStates,\n lastOperation: this.lastOperation,\n };\n }\n\n /**\n * Update last operation tracker\n */\n private updateLastOperation(type: HistoryType, artboardId?: string): void {\n this.lastOperation = {\n type,\n artboardId,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Get description of what will be undone\n */\n getUndoDescription(): string {\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return 'Undo artboard operation';\n } else if (this.lastOperation.artboardId) {\n const artboard = this.artboardManager.getArtboard(this.lastOperation.artboardId);\n return artboard ? `Undo in \"${artboard.name}\"` : 'Undo element operation';\n }\n }\n\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n const artboard = this.artboardManager.getArtboard(activeArtboardId);\n return artboard ? `Undo in \"${artboard.name}\"` : 'Undo element operation';\n }\n\n if (this.canUndoGlobal()) {\n return 'Undo artboard operation';\n }\n\n return 'Nothing to undo';\n }\n\n /**\n * Get description of what will be redone\n */\n getRedoDescription(): string {\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return 'Redo artboard operation';\n } else if (this.lastOperation.artboardId) {\n const artboard = this.artboardManager.getArtboard(this.lastOperation.artboardId);\n return artboard ? `Redo in \"${artboard.name}\"` : 'Redo element operation';\n }\n }\n\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n const artboard = this.artboardManager.getArtboard(activeArtboardId);\n return artboard ? `Redo in \"${artboard.name}\"` : 'Redo element operation';\n }\n\n if (this.canRedoGlobal()) {\n return 'Redo artboard operation';\n }\n\n return 'Nothing to redo';\n }\n}\n\nexport default HybridHistoryManager;\n","/**\n * FontAnalyzer - Utility for analyzing fonts and extracting glyph alternates\n *\n * Features:\n * - Fetch and parse font files using fontkit v2.0+ (supports WOFF2 natively in browser)\n * - Extract glyph alternates from GSUB tables\n * - Generate preview images for glyphs\n * - Cache parsed fonts for performance\n */\n\nimport type { GlyphAlternate } from '../types';\nimport { SYSTEM_FONTS } from '../constants.js';\nimport { createLogger } from './logger.js';\n\n/**\n * Minimal type for fontkit Font objects.\n * The fontkit library doesn't export clean TS types for browser usage,\n * so we define the subset we use here.\n */\nexport interface FontkitGlyph {\n id: number;\n name: string;\n advanceWidth: number;\n codePoints?: number[];\n bbox?: { minX: number; minY: number; maxX: number; maxY: number };\n path: {\n toSVG: () => string | null;\n toPath?: () => string | null;\n };\n}\n\nexport interface FontkitGlyphRun {\n glyphs: FontkitGlyph[];\n positions: Array<{ xOffset: number; yOffset: number }>;\n}\n\nexport interface FontkitFont {\n unitsPerEm: number;\n numGlyphs: number;\n GSUB?: {\n featureList?: Array<{\n tag: string;\n feature?: { lookupListIndexes?: number[] };\n }>;\n lookupList?: { lookups?: Array<{ lookupType: number; subtables?: Record<string, unknown>[] }> };\n };\n GPOS?: {\n featureList?: Array<{ tag: string }>;\n };\n layout(text: string, features: Record<string, boolean>, script?: string): FontkitGlyphRun;\n getGlyph(glyphId: number): FontkitGlyph | null;\n glyphForCodePoint(codePoint: number): FontkitGlyph | null;\n}\n\nconst log = createLogger('FontAnalyzer');\n\n// Fontkit is lazy-loaded to reduce initial bundle size (~1MB)\n// It's only needed for OpenType features, glyph alternates, and PUA characters\nlet fontkitModule: typeof import('fontkit') | null = null;\n\nasync function getFontkit() {\n if (!fontkitModule) {\n fontkitModule = await import('fontkit');\n }\n return fontkitModule;\n}\n\ninterface FontCacheEntry {\n font: FontkitFont;\n url: string;\n timestamp: number;\n}\n\nclass FontAnalyzerService {\n private fontCache: Map<string, FontCacheEntry> = new Map();\n private failedFonts: Set<string> = new Set(); // Track fonts that failed to load (CORS, network errors)\n private readonly CACHE_DURATION = 1000 * 60 * 30; // 30 minutes\n private readonly MAX_CACHE_SIZE = 5; // Limit cache to prevent iOS Safari memory crashes\n\n /**\n * Evict the oldest font from cache if over the limit\n * Uses LRU (Least Recently Used) strategy based on timestamp\n */\n private evictOldestFont(): void {\n if (this.fontCache.size <= this.MAX_CACHE_SIZE) return;\n\n // Find oldest entry by timestamp\n let oldestKey: string | null = null;\n let oldestTime = Date.now();\n\n this.fontCache.forEach((entry, key) => {\n if (entry.timestamp < oldestTime) {\n oldestTime = entry.timestamp;\n oldestKey = key;\n }\n });\n\n if (oldestKey) {\n this.fontCache.delete(oldestKey);\n }\n }\n\n /**\n * Check if a font is a system font\n */\n private isSystemFont(fontFamily: string): boolean {\n return SYSTEM_FONTS.has(fontFamily);\n }\n\n /**\n * Get Google Fonts API URL for a specific font family\n */\n private getGoogleFontUrl(fontFamily: string, weight: number = 400): string {\n // Google Fonts API v2 URL format\n const formattedFamily = fontFamily.replace(/\\s+/g, '+');\n return `https://fonts.googleapis.com/css2?family=${formattedFamily}:wght@${weight}&display=swap`;\n }\n\n /**\n * Extract actual font file URL from Google Fonts CSS\n */\n private async extractFontFileUrl(fontFamily: string, weight: number = 400): Promise<string | null> {\n try {\n const cssUrl = this.getGoogleFontUrl(fontFamily, weight);\n\n const response = await fetch(cssUrl);\n const css = await response.text();\n\n\n // Extract all @font-face blocks\n const fontFaceRegex = /@font-face\\s*{([^}]*)}/g;\n const fontFaces = Array.from(css.matchAll(fontFaceRegex));\n\n if (fontFaces.length === 0) {\n log.warn('No @font-face found in CSS');\n return null;\n }\n\n\n // Look for the @font-face with the most complete character coverage\n // Priority: 1) No unicode-range (complete), 2) \"latin\" subset, 3) Largest unicode-range\n let bestUrl: string | null = null;\n let bestScore = -1;\n\n for (let i = 0; i < fontFaces.length; i++) {\n const fontFaceContent = fontFaces[i][1];\n const urlMatch = fontFaceContent.match(/url\\((https:\\/\\/fonts\\.gstatic\\.com\\/[^)]+)\\)/);\n const hasUnicodeRange = fontFaceContent.includes('unicode-range');\n const unicodeRangeMatch = fontFaceContent.match(/unicode-range:\\s*([^;]+);/);\n\n if (!urlMatch) continue;\n\n const url = urlMatch[1];\n let score = 0;\n\n // No unicode-range = complete font (best)\n if (!hasUnicodeRange) {\n score = 1000;\n }\n // Count unicode ranges (more = better coverage)\n else if (unicodeRangeMatch) {\n const rangeContent = unicodeRangeMatch[1];\n score = (rangeContent.match(/U\\+/g) || []).length;\n }\n\n\n if (score > bestScore) {\n bestScore = score;\n bestUrl = url;\n }\n }\n\n if (bestUrl) {\n return bestUrl;\n }\n\n log.warn('No font URL found in CSS for', fontFamily);\n return null;\n } catch (error) {\n log.error('Error extracting font file URL:', error);\n return null;\n }\n }\n\n /**\n * Load and parse a font file\n */\n async loadFont(fontFamily: string, weight: number = 400): Promise<FontkitFont | null> {\n // System fonts are already available in the browser, no need to load\n if (this.isSystemFont(fontFamily)) {\n return null;\n }\n\n const cacheKey = `${fontFamily}-${weight}`;\n\n // Check if font previously failed (avoids repeated CORS/network errors)\n if (this.failedFonts.has(cacheKey)) {\n return null;\n }\n\n // Check cache\n const cached = this.fontCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {\n // Update timestamp on access (LRU behavior)\n cached.timestamp = Date.now();\n return cached.font;\n }\n\n try {\n\n // Get font file URL\n const fontUrl = await this.extractFontFileUrl(fontFamily, weight);\n if (!fontUrl) {\n // Mark as failed to avoid repeated attempts (especially for CORS errors)\n this.failedFonts.add(cacheKey);\n return null;\n }\n\n\n // Fetch font file as ArrayBuffer\n const response = await fetch(fontUrl);\n if (!response.ok) {\n log.error(`Font fetch failed: ${response.status} ${response.statusText}`);\n this.failedFonts.add(cacheKey);\n return null;\n }\n\n const arrayBuffer = await response.arrayBuffer();\n\n // Parse with fontkit (supports WOFF2, WOFF, TTF, OTF natively)\n const buffer = new Uint8Array(arrayBuffer);\n const fontkit = await getFontkit();\n // fontkit types expect Buffer but Uint8Array is accepted at runtime in browser environments\n const fontOrCollection = fontkit.create(buffer as unknown as Buffer);\n\n // Handle FontCollection (TTC files) vs single Font\n const font =\n 'fonts' in fontOrCollection\n ? fontOrCollection.fonts[0] // Get first font from collection\n : fontOrCollection; // Single font\n\n\n // Cache the result\n this.fontCache.set(cacheKey, {\n font,\n url: fontUrl,\n timestamp: Date.now(),\n });\n\n // Evict oldest font if cache is full (prevents iOS Safari memory crashes)\n this.evictOldestFont();\n\n return font;\n } catch (error) {\n log.error(`Error loading font ${fontFamily}:`, error);\n // Mark as failed to avoid repeated network/CORS errors\n this.failedFonts.add(cacheKey);\n return null;\n }\n }\n\n /**\n * Get font from cache synchronously (for rendering)\n * Returns null if font is not cached - font should be pre-loaded before rendering\n */\n getFontSync(fontFamily: string, weight: number = 400): FontkitFont | null {\n const cacheKey = `${fontFamily}-${weight}`;\n const cached = this.fontCache.get(cacheKey);\n\n if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {\n // Update timestamp on access (LRU behavior - recently used fonts stay in cache)\n cached.timestamp = Date.now();\n return cached.font;\n }\n\n return null;\n }\n\n /**\n * Clear font cache (useful for debugging or forcing reload)\n * Also clears failed fonts to allow retry\n */\n clearCache(fontFamily?: string, weight?: number): void {\n if (fontFamily) {\n const cacheKey = `${fontFamily}-${weight || 400}`;\n this.fontCache.delete(cacheKey);\n this.failedFonts.delete(cacheKey);\n } else {\n this.fontCache.clear();\n this.failedFonts.clear();\n }\n }\n\n /**\n * Get all glyphs in a font (for browsing)\n */\n async getAllGlyphs(\n fontFamily: string,\n weight: number = 400,\n limit: number = 1500 // Increased limit to catch more alternates\n ): Promise<GlyphAlternate[]> {\n // System fonts don't have accessible glyph data via Google Fonts\n if (this.isSystemFont(fontFamily)) {\n return [];\n }\n\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return [];\n }\n\n const glyphs: GlyphAlternate[] = [];\n\n\n // Iterate through all glyphs in the font\n const maxGlyphs = Math.min(font.numGlyphs, limit);\n for (let glyphId = 0; glyphId < maxGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Skip .notdef and other special glyphs\n if (glyph.name === '.notdef' || glyph.name.startsWith('.null')) {\n continue;\n }\n\n // Get unicode value if available\n const codePoints = glyph.codePoints || [];\n let unicode = codePoints.length > 0 ? String.fromCodePoint(codePoints[0]) : '';\n\n // If no unicode, try to extract the base character from the glyph name\n // For glyphs like \"T.alt\", \"W.swash\", extract \"T\" or \"W\"\n if (!unicode || unicode.length === 0) {\n const baseCharMatch = glyph.name.match(/^([A-Za-z0-9])[._]/);\n if (baseCharMatch) {\n unicode = baseCharMatch[1];\n }\n }\n\n // Generate a friendly name\n let friendlyName = glyph.name;\n if (unicode && unicode.length > 0 && /\\S/.test(unicode)) {\n // Try to create a more readable name\n if (glyph.name.includes('.swash')) {\n friendlyName = `${unicode} Swash`;\n } else if (glyph.name.match(/\\.alt(\\d+)?/)) {\n const num = glyph.name.match(/\\.alt(\\d+)/)?.[1] || '';\n friendlyName = `${unicode} Alternate${num ? ' ' + num : ''}`;\n } else if (glyph.name.match(/\\.ss(\\d+)/)) {\n const num = glyph.name.match(/\\.ss(\\d+)/)?.[1];\n friendlyName = `${unicode} Stylistic Set ${num}`;\n } else {\n // Use the unicode character as a prefix\n friendlyName = `${unicode} - ${glyph.name}`;\n }\n }\n\n // Categorize the glyph - check for various alternate patterns\n let category: GlyphAlternate['category'] = 'default';\n const nameLower = glyph.name.toLowerCase();\n\n if (nameLower.includes('swash') || nameLower.includes('cswh')) {\n category = 'swash';\n } else if (glyph.name.match(/\\.(alt|ss\\d+|cv\\d+|salt|ornament)/)) {\n category = 'stylistic';\n } else if (glyph.name.match(/_\\d+$/)) {\n // Pattern like T_001, T_002\n category = 'stylistic';\n } else if (nameLower.match(/liga|dlig/)) {\n category = 'ligature';\n }\n\n // Only show alternates, swashes, and ligatures\n // Skip anything categorized as \"default\" - those aren't alternate glyphs\n if (category === 'default') {\n continue;\n }\n\n // Only add glyphs that have a valid unicode character (skip if we couldn't determine one)\n if (unicode && unicode.length > 0) {\n glyphs.push({\n glyphIndex: glyph.id,\n unicode: unicode,\n name: friendlyName,\n category,\n previewDataUrl: this.generateGlyphPreview(font, glyph.id, 80),\n });\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n return glyphs;\n }\n\n /**\n * Get glyph alternates for a specific character\n */\n async getGlyphAlternates(fontFamily: string, character: string, weight: number = 400): Promise<GlyphAlternate[]> {\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return [];\n }\n\n const alternates: GlyphAlternate[] = [];\n\n // Get default glyph\n const codePoint = character.codePointAt(0);\n if (codePoint === undefined) {\n return [];\n }\n\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n if (defaultGlyph) {\n alternates.push({\n glyphIndex: defaultGlyph.id,\n unicode: character,\n name: defaultGlyph.name || 'Default',\n category: 'default',\n previewDataUrl: this.generateGlyphPreview(font, defaultGlyph.id, 80),\n });\n }\n\n // Search for glyphs by name pattern\n // Many fonts have alternates as separate glyphs (e.g., \"W.swash\", \"W.alt01\", \"W_001\")\n const baseName = defaultGlyph?.name;\n\n // Try to iterate through all glyphs\n if (baseName) {\n let foundCount = 0;\n\n // Fontkit doesn't expose _glyphs directly, but we can iterate by glyph ID\n for (let glyphId = 0; glyphId < font.numGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Check if this glyph name is a variant of the base character\n // Patterns: W.swash, W.alt01, W_001, W.ss01, etc.\n const namePatterns = [\n new RegExp(`^${baseName}\\\\.(swash|alt|ss\\\\d+|salt|ornament)`, 'i'),\n new RegExp(`^${baseName}_\\\\d+$`, 'i'),\n new RegExp(`^${baseName}\\\\.\\\\d+$`, 'i'),\n ];\n\n const isVariant = namePatterns.some((pattern) => pattern.test(glyph.name));\n\n if (isVariant && glyph.id !== defaultGlyph.id) {\n foundCount++;\n\n // Extract a friendly name from the glyph name\n let friendlyName = glyph.name;\n if (glyph.name.includes('.swash')) {\n friendlyName = `${character} Swash`;\n } else if (glyph.name.match(/\\.alt(\\d+)?/)) {\n const num = glyph.name.match(/\\.alt(\\d+)/)?.[1] || '';\n friendlyName = `${character} Alternate${num ? ' ' + num : ''}`;\n } else if (glyph.name.match(/\\.ss(\\d+)/)) {\n const num = glyph.name.match(/\\.ss(\\d+)/)?.[1];\n friendlyName = `${character} Stylistic Set ${num}`;\n }\n\n alternates.push({\n glyphIndex: glyph.id,\n unicode: character,\n name: friendlyName,\n category: 'stylistic',\n previewDataUrl: this.generateGlyphPreview(font, glyph.id, 80),\n });\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n }\n\n // Check GSUB table for alternates\n if (font.GSUB) {\n const gsub = font.GSUB;\n\n // Look for single substitution features (e.g., swsh, ss01, etc.)\n if (gsub.featureList) {\n for (const feature of gsub.featureList) {\n const tag = feature.tag;\n\n // Skip if not a relevant feature\n if (!this.isRelevantFeature(tag)) {\n continue;\n }\n\n // Look through lookups\n if (feature.feature && feature.feature.lookupListIndexes) {\n for (const lookupIndex of feature.feature.lookupListIndexes) {\n const lookup = gsub.lookupList?.lookups?.[lookupIndex];\n if (!lookup) continue;\n\n // Single substitution (Type 1)\n if (lookup.lookupType === 1) {\n for (const subtable of lookup.subtables || []) {\n const coverage = subtable.coverage as Record<string, unknown> | undefined;\n if (!coverage) continue;\n\n // Check if our character is in the coverage\n const coverageIndex = this.getCoverageIndex(coverage, defaultGlyph!.id);\n if (coverageIndex !== -1) {\n const substituteGlyphIndex = this.getSubstituteGlyph(subtable as Record<string, unknown>, defaultGlyph!.id);\n if (substituteGlyphIndex !== null) {\n alternates.push({\n glyphIndex: substituteGlyphIndex,\n unicode: character,\n name: `${character} (${tag})`,\n category: this.categorizeFeature(tag) as GlyphAlternate['category'],\n previewDataUrl: this.generateGlyphPreview(font, substituteGlyphIndex, 80),\n });\n }\n }\n }\n }\n\n // Alternate substitution (Type 3)\n else if (lookup.lookupType === 3) {\n for (const subtable of lookup.subtables || []) {\n const coverage = subtable.coverage as Record<string, unknown> | undefined;\n if (!coverage) continue;\n\n const coverageIndex = this.getCoverageIndex(coverage, defaultGlyph!.id);\n const altSubtable = subtable as Record<string, unknown> & { alternateSets?: number[][] };\n if (coverageIndex !== -1 && altSubtable.alternateSets) {\n const alternateSet = altSubtable.alternateSets[coverageIndex];\n if (alternateSet) {\n for (const altGlyphIndex of alternateSet) {\n alternates.push({\n glyphIndex: altGlyphIndex,\n unicode: character,\n name: `${character} (${tag} alt)`,\n category: this.categorizeFeature(tag),\n previewDataUrl: this.generateGlyphPreview(font, altGlyphIndex, 80),\n });\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n return alternates;\n }\n\n /**\n * Get available OpenType features for a font\n */\n async getAvailableFeatures(fontFamily: string, weight: number = 400): Promise<string[]> {\n\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n log.warn(`Font ${fontFamily} failed to load`);\n return [];\n }\n\n try {\n const features: string[] = [];\n\n // Extract features from GSUB table\n if (font.GSUB && font.GSUB.featureList) {\n const uniqueFeatures = new Set<string>();\n for (const feature of font.GSUB.featureList) {\n if (feature.tag) {\n uniqueFeatures.add(feature.tag);\n }\n }\n features.push(...Array.from(uniqueFeatures));\n }\n\n // Extract features from GPOS table (positioning features like kern)\n if (font.GPOS && font.GPOS.featureList) {\n const uniqueFeatures = new Set<string>();\n for (const feature of font.GPOS.featureList) {\n if (feature.tag) {\n uniqueFeatures.add(feature.tag);\n }\n }\n features.push(...Array.from(uniqueFeatures));\n }\n\n return features;\n } catch (error) {\n log.error(`Error getting features for ${fontFamily}:`, error);\n return [];\n }\n }\n\n /**\n * Generate a preview image for a glyph\n */\n private generateGlyphPreview(font: FontkitFont, glyphIndex: number, size: number = 80): string {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n\n if (!ctx) {\n return '';\n }\n\n try {\n const glyph = font.getGlyph(glyphIndex);\n if (!glyph) {\n return '';\n }\n\n // Clear canvas\n ctx.clearRect(0, 0, size, size);\n\n // Get glyph bounding box\n const bbox = glyph.bbox;\n if (!bbox) {\n return '';\n }\n\n const glyphWidth = bbox.maxX - bbox.minX;\n const glyphHeight = bbox.maxY - bbox.minY;\n\n if (glyphWidth === 0 || glyphHeight === 0) {\n return '';\n }\n\n // Use more conservative padding to prevent clipping (60% instead of 80%)\n const padding = 0.6;\n const scale = Math.min(size / glyphWidth, size / glyphHeight) * padding;\n\n // Center the glyph more carefully\n // Calculate the center of the glyph in its own coordinate space\n const glyphCenterX = (bbox.minX + bbox.maxX) / 2;\n const glyphCenterY = (bbox.minY + bbox.maxY) / 2;\n\n // Canvas center\n const canvasCenterX = size / 2;\n const canvasCenterY = size / 2;\n\n // Get SVG path and render it\n const svgPath = glyph.path.toSVG();\n if (!svgPath) {\n return '';\n }\n\n // Create Path2D from SVG path\n const path2D = new Path2D(svgPath);\n\n // Apply transformations\n ctx.save();\n\n // Move to canvas center\n ctx.translate(canvasCenterX, canvasCenterY);\n\n // Scale (flip Y for font coordinates)\n ctx.scale(scale, -scale);\n\n // Move glyph center to origin\n ctx.translate(-glyphCenterX, -glyphCenterY);\n\n ctx.fillStyle = '#000000';\n ctx.fill(path2D);\n ctx.restore();\n\n // Convert to data URL\n return canvas.toDataURL('image/png');\n } catch (error) {\n log.error('Error generating glyph preview:', error);\n return '';\n }\n }\n\n /**\n * Check if a feature tag is relevant for glyph alternates\n */\n private isRelevantFeature(tag: string): boolean {\n const relevantFeatures = [\n 'swsh',\n 'cswh',\n 'salt',\n 'ss01',\n 'ss02',\n 'ss03',\n 'ss04',\n 'ss05',\n 'ss06',\n 'ss07',\n 'ss08',\n 'ss09',\n 'ss10',\n 'ss11',\n 'ss12',\n 'ss13',\n 'ss14',\n 'ss15',\n 'ss16',\n 'ss17',\n 'ss18',\n 'ss19',\n 'ss20',\n 'cv01',\n 'cv02',\n 'cv03',\n 'cv04',\n 'cv05',\n 'cv06',\n 'cv07',\n 'cv08',\n 'cv09',\n 'cv10',\n 'aalt',\n 'dlig',\n 'liga',\n ];\n return relevantFeatures.includes(tag);\n }\n\n /**\n * Categorize a feature tag\n */\n private categorizeFeature(tag: string): GlyphAlternate['category'] {\n if (tag.startsWith('swsh') || tag === 'cswh') return 'swash';\n if (tag.startsWith('ss') || tag.startsWith('cv') || tag === 'salt') return 'stylistic';\n if (tag === 'liga' || tag === 'dlig') return 'ligature';\n if (tag === 'calt') return 'contextual';\n return 'stylistic';\n }\n\n /**\n * Get coverage index for a glyph\n */\n private getCoverageIndex(coverage: Record<string, unknown>, glyphIndex: number): number {\n if (!coverage) return -1;\n\n // Format 1: List of glyph IDs\n if (coverage.format === 1 && coverage.glyphs) {\n return (coverage.glyphs as number[]).indexOf(glyphIndex);\n }\n\n // Format 2: Ranges\n if (coverage.format === 2 && coverage.ranges) {\n const ranges = coverage.ranges as Array<{ start: number; end: number; index: number }>;\n for (let i = 0; i < ranges.length; i++) {\n const range = ranges[i];\n if (glyphIndex >= range.start && glyphIndex <= range.end) {\n return range.index + (glyphIndex - range.start);\n }\n }\n }\n\n return -1;\n }\n\n /**\n * Get substitute glyph from a single substitution subtable\n */\n private getSubstituteGlyph(subtable: Record<string, unknown>, glyphIndex: number): number | null {\n if (!subtable) return null;\n\n // Format 1: Delta\n if (subtable.substFormat === 1 && typeof subtable.deltaGlyphId === 'number') {\n return glyphIndex + subtable.deltaGlyphId;\n }\n\n // Format 2: Mapping\n if (subtable.substFormat === 2 && subtable.substitute) {\n const coverageIndex = this.getCoverageIndex(subtable.coverage as Record<string, unknown>, glyphIndex);\n const substitute = subtable.substitute as number[];\n if (coverageIndex !== -1 && substitute[coverageIndex] !== undefined) {\n return substitute[coverageIndex];\n }\n }\n\n return null;\n }\n\n /**\n * Get the count of alternate glyphs in a font (fast - no preview generation)\n */\n async getAlternateGlyphCount(fontFamily: string, weight: number = 400): Promise<number> {\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return 0;\n }\n\n let count = 0;\n\n // Iterate through all glyphs in the font\n const maxGlyphs = Math.min(font.numGlyphs, 1500);\n for (let glyphId = 0; glyphId < maxGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Skip .notdef and other special glyphs\n if (glyph.name === '.notdef' || glyph.name.startsWith('.null')) {\n continue;\n }\n\n // Get unicode value if available\n const codePoints = glyph.codePoints || [];\n let unicode = codePoints.length > 0 ? String.fromCodePoint(codePoints[0]) : '';\n\n // If no unicode, try to extract the base character from the glyph name\n if (!unicode || unicode.length === 0) {\n const baseCharMatch = glyph.name.match(/^([A-Za-z0-9])[._]/);\n if (baseCharMatch) {\n unicode = baseCharMatch[1];\n }\n }\n\n // Categorize the glyph - check for various alternate patterns\n let category = 'default';\n const nameLower = glyph.name.toLowerCase();\n\n if (nameLower.includes('swash') || nameLower.includes('cswh')) {\n category = 'swash';\n } else if (glyph.name.match(/\\.(alt|ss\\d+|cv\\d+|salt|ornament)/)) {\n category = 'stylistic';\n } else if (glyph.name.match(/_\\d+$/)) {\n category = 'stylistic';\n } else if (nameLower.match(/liga|dlig/)) {\n category = 'ligature';\n }\n\n // Only count alternates, swashes, and ligatures\n if (category !== 'default' && unicode && unicode.length > 0) {\n count++;\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n return count;\n }\n\n /**\n * Get cache statistics\n */\n getCacheStats(): { size: number; entries: string[] } {\n return {\n size: this.fontCache.size,\n entries: Array.from(this.fontCache.keys()),\n };\n }\n}\n\n// Export singleton instance\nexport const FontAnalyzer = new FontAnalyzerService();\n"],"names":["getDefaultExportFromCjs","x","browser","process","cachedSetTimeout","cachedClearTimeout","defaultSetTimout","defaultClearTimeout","runTimeout","fun","runClearTimeout","marker","queue","draining","currentQueue","queueIndex","cleanUpNextTick","drainQueue","timeout","len","args","i","Item","array","noop","name","dir","browserExports","process$1","nextArtboardId","ArtboardElement","config","px","py","element","bbox","elementId","tolerance","inXRange","inYRange","nearLeft","nearRight","nearTop","nearBottom","updates","newWidth","newHeight","newX","newY","ArtboardManager","artboard","artboardId","elementIds","remainingArtboards","previousArtboardId","previousArtboard","y","artboards","fromIndex","toIndex","entries","moved","a","data","elements","e","_artboardId","validElementIds","id","RotationUtils","degrees","normalized","nextId","BaseElement","value","visualBbox","rotationAnchor","rotationRad","cos","sin","dx","dy","localX","localY","transformedX","transformedY","ctx","m","newRotation","_a","Transform","worldX","worldY","rad","rotX","rotY","pointX","pointY","anchorX","anchorY","angleDegrees","elementSnapshot","TSHIRT_FONTS","getFontNames","f","CATEGORY_LABELS","getThemeAccentColor","target","getCurrentTheme","getThemeShapeFillColor","getThemeSpacingColor","getThemeAccentColorWithAlpha","alpha","color","hex","r","g","b","getThemeTextSelectionColor","getThemeAccentHoverColor","getThemeTooltipBackground","getThemeTooltipForeground","getThemeTooltipShadowColor","bg","getThemeArtboardBorderColor","getThemeArtboardLabelColor","getThemeHoverBorderColor","getThemePenPathColor","getThemePenAnchorFillColor","getThemePenHandleColor","PREVIEW_ELEMENT_OPACITY","DEFAULT_ARTBOARD_COLOR","ROTATION_HANDLE_DISTANCE","CORNER_HANDLE_VISUAL_RADIUS","EDGE_HANDLE_VISUAL_LENGTH","CORNER_HANDLE_HIT_RADIUS","EDGE_HANDLE_LENGTH","EDGE_HANDLE_HIT_WIDTH","SPACING_SNAP_THRESHOLD","SPACING_LABEL_TEXT_COLOR","SPACING_LABEL_BORDER_RADIUS","MIN_CROP_SIZE","FONT_FAMILIES","FONT_SIZES","HORIZONTAL_PADDING","LINE_HEIGHT_MULTIPLIER","MIN_WIDTH","MIN_FONT_SIZE","MAX_FONT_SIZE","SYSTEM_FONTS","WAVE_SKEW_FACTOR","LogLevel","Logger","level","enabled","_message","_args","message","scopeName","ScopedLogger","logger","createLogger","listeners","subscribeToImageLoads","listener","emitImageLoaded","error","ImageCache","url","existing","img","entry","totalRefs","NEUTRAL_ADJUSTMENTS","EPSILON","isNeutralAdjustments","adjustments","normalizeAdjustments","round","v","adjustmentsCacheKey","adjustedImageSourceProvider","registerAdjustedImageSourceProvider","provider","getAdjustedImageSource","source","IMAGE_LOAD_TIMEOUT_MS","IMAGE_LOAD_MAX_RETRIES","IMAGE_LOAD_BASE_DELAY_MS","imageTransformData","startData","ImageElement","_b","_c","_d","_e","_f","_g","_h","_i","_j","_k","isSvg","settled","settle","originalOnload","originalOnerror","ev","delay","tempImg","canvas","blob","cropWidth","cropHeight","localOffsetX","localOffsetY","flipH","flipV","worldOffsetX","worldOffsetY","frameCenterX","frameCenterY","local","halfWidth","halfHeight","halfCropWidth","halfCropHeight","_isSelected","_isHovered","sourceWidth","sourceHeight","borderRadius","radius","fullImageX","fullImageY","bgColor","textColor","anchor","widthScale","heightScale","proposedScale","effectiveAnchor","finalScale","currentScaleRelativeToStart","oldCropCenterXLocal","oldCropCenterYLocal","worldOffset","fixedCornerWorldX","fixedCornerWorldY","fixedCornerLocalX","fixedCornerLocalY","fixedCornerWorldOffset","fixedCornerWorldOffsetX","fixedCornerWorldOffsetY","newFixedCornerLocalX","newFixedCornerLocalY","newFixedCornerWorldOffset","newFixedCornerWorldOffsetX","newFixedCornerWorldOffsetY","newFrameCenterX","newFrameCenterY","oldCropCenterWorldX","oldCropCenterWorldY","oldCropWorldWidth","oldCropWorldHeight","newCropWidthNorm","newCropHeightNorm","localOffset","cropCenterXLocal","cropCenterYLocal","newCropX","newCropY","newCropCenterXLocal","newCropCenterYLocal","newLocalOffsetX","newLocalOffsetY","newWorldOffset","newWorldOffsetX","newWorldOffsetY","cropBox","corner","flippedX","flippedY","rotatedX","rotatedY","zoom","hitScale","CORNER_HIT_RADIUS_SCALED","EDGE_LENGTH_SCALED","EDGE_WIDTH_SCALED","corners","worldCorner","edges","edge","worldEdge","cropX","cropY","adjustPosition","clampedWidth","clampedHeight","clampedX","clampedY","oldCropCenterX","oldCropCenterY","newCropCenterX","newCropCenterY","width","height","halfW","halfH","fixedCornerLocal","startFrameCenter","scale","fixedCornerWorld","newFrameWidth","newFrameHeight","cropWorldWidth","cropWorldHeight","cropNormWidth","cropNormHeight","newFrameCenter","cropCenterWorld","cropCenterLocalX","cropCenterLocalY","cropState","cropLeft","cropRight","cropTop","cropBottom","currentScale","lo","hi","testScale","json","imageUrl","configWithoutUrl","cloned","textTransformPainters","groupClippingPainter","missingPainter","what","registerTextTransformPainter","type","painter","getTextTransformPainter","registerGroupClippingPainter","getGroupClippingPainter","STANDARD_BLEND_MODES","RichText","spans","s","text","style","charIndex","currentIndex","span","start","end","newSpans","spanStart","spanEnd","overlapStart","overlapEnd","index","offset","length","deleteStart","deleteEnd","merged","current","next","property","isCustomTransform","isCircleTransform","isArchTransform","isWaveTransform","isFlagTransform","isImageTransform","isGroupTransform","isTextElementConfig","isImageElementConfig","isCustomElementConfig","isCircleElementConfig","isGroupElementConfig","isShapeElementConfig","isPathElementConfig","isShapeTransform","isPathTransform","hasStroke","hasMasks","isKnockout","isStandardBlendMode","mode","hasDistressEffect","TextElement","previousAlpha","Constructor","newText","newRichText","richText","newFontSize","newColor","oldColor","_anchor","_newWidth","_newHeight","_startData","countSpacedChars","spacedTextWidth","rawWidth","letterSpacing","getSpacedCharOffsets","chars","offsets","prefix","leftAnchoredStartX","totalWidth","drawTextWithSpacing","op","startX","previousAlign","fillTextWithSpacing","strokeTextWithSpacing","_measureCanvas","getMeasureCanvas","buildFontString","fontSize","fontFamily","bold","italic","parts","wrapText","maxWidth","lockedLineCount","strict","opts","measure","t","explicitLines","allWrappedLines","line","wrappedLine","leadingSpacesMatch","trailingSpacesMatch","leadingSpaces","trailingSpaces","words","lines","wordsPerLine","lineWords","allText","allTextWithSpaces","allTextWidth","isSmallSize","wrapTolerance","currentLine","lineTolerance","word","charBuffer","char","testBuffer","testLine","firstLineWithSpace","lastIdx","lastLineWithSpace","measureTextWidth","getFontMetrics","metrics","ascent","descent","calculateVisualBoundsWithSpaceCollapsing","hasExplicitNewlines","paragraphMetadata","_","maxLineWidth","fontMetrics","totalLineCount","isParagraphStart","isParagraphEnd","visibleText","leadingSpacesCount","trailingSpacesCount","lineWidth","lineHeight","lineSpacing","_measureCtx","getMeasureContext","wrapRichTextSpans","measureText","stylesMatch","s1","s2","mergedSpans","fullText","charToSpan","spanIndex","currentWord","wordStart","currentLineWidth","finishLine","_wordIdx","wordSpans","currentSpanIdx","currentText","charIdx","spanIdx","wordWidth","sum","lastSpan","charWidth","splitRichTextIntoLines","part","getTransformWidth","setTransformWidth","handleCornerResize","startTransformData","startTransformWidth","startWidth","startFontSize","calculatedFontSize","scaledRichText","scaledCharSize","elemTransformData","effectiveScale","scaledWidth","handleSideResize","oldWidth","widthChange","standardResize","isCornerHandle","isSideHandle","CustomTransform","availableWidth","wrappedLines","lineSpans","wLine","maxFontHeight","maxAscent","spanFontSize","spanFontFamily","spanBold","spanItalic","elementMetrics","standardHeight","extraAscent","extraDescent","visualHeight","visualWidth","elementOpacity","padding","offW","offH","offCanvas","offCtx","prev","offsetX","offsetY","minWidth","p","calculateFixedCornerPosition","newDimensions","activeAnchor","rotation","fixedLocalX","fixedLocalY","fixedWorldX","fixedWorldY","newFixedLocalX","newFixedLocalY","calculateRotationHandlePosition","calculateResizeHandles","transform","createHandle","worldPos","cursor","getCursorForWorldPosition","hitTestCircle","cx","cy","hitTestRect","rect","translatedX","translatedY","weight","getBoundingBoxCenter","calculateAngle","centerX","centerY","isNear","value1","value2","threshold","handleWorldX","handleWorldY","centerWorldX","centerWorldY","handleType","angle","normalizedAngle","getCanvasScale","setZoomInvariantStroke","dashPattern","CircleTransform","effectiveRadius","diameter","effectiveFontSize","arcAngle","startAngle","endAngle","innerRadius","outerRadius","minX","maxX","minY","maxY","samples","innerX","innerY","outerX","outerY","circleStartData","avgDimension","startAvgDimension","scaleChange","newScale","clampedScale","newPosition","targetSize","newBaseFontSize","ArchTransform","archHeightAbs","yOffset","charWidths","w","skewFactor","currentX","_char","normalizedX","lineY","slope","skewedX","skewedY","archStartData","deltaWidth","centerShift","WAVE_DEFAULTS","FLAG_DEFAULTS","ARCH_DEFAULTS","LEAN_DEFAULTS","ASCEND_DEFAULTS","WaveTransform","amplitudeAbs","FlagTransform","distanceFromCenter","flagAmplitude","cosComponent","sinComponent","flagStartData","LeanTransform","leanAmountAbs","skewAngle","skewX","leanStartData","AscendTransform","angleRad","rise","ascendStartData","EditorError","code","isEditorError","err","isDevOrTest","DEFAULT_SHAPE_FILL_COLOR","FILL_TYPES","invalidFill","assertValidShapeFill","fill","stop","cloneShapeFill","addStops","gradient","stops","createShapeFillStyle","transformData","theta","halfLength","shapeFillToCss","fallbackColor","ShapeElement","_l","defaultShapeType","shapeType","sides","points","fillOpacity","stroke","radiusX","radiusY","angleStep","innerRadiusRatio","thickness","fixedCorner","oppositeAnchor","newBbox","fixedCornerOffset","startBbox","fixedX","fixedY","PathElement","localBounds","p0","p1","p2","p3","mt","mt2","mt3","t2","t3","currentPoint","nextPoint","u","point","strokeWidth","halfStroke","bounds","closed","fillEnabled","fillColor","strokeEnabled","strokeColor","centerOffsetX","centerOffsetY","firstPoint","cp1x","cp1y","cp2x","cp2y","oldHeight","scaleX","scaleY","startPoints","TRANSFORM_CONTROLS","internal","slider","TRANSFORM_TYPES","GroupElement","BUILTIN_IDS","_dynamicTransforms","registerTransform","def","unregisterTransform","getTransformById","builtin","getTransformControls","rawChildren","child","transformType","transformDef","forceRecalcDimensions","dims","isSelected","isHovered","visibleChildren","opacity","obb","deltaRotation","rotated","localFixedX","localFixedY","childStart","childTransform","newChildWidth","newChildHeight","clonedChildren","clonedGroup","childId","c","ElementStore","el","result","idx","afterId","oid","newIndex","predicate","filtered","fn","mapped","newOrder","store","Command","UpdateElementCommand","oldElement","newElement","onUpdate","AddElementCommand","onAdd","onRemove","RemoveElementCommand","CompoundCommand","commands","cmd","CommandHistory","maxSize","command","compound","CreateArtboardCommand","artboardManager","DeleteArtboardCommand","UpdateArtboardCommand","oldProperties","newProperties","ReorderElementCommand","draggedId","targetId","position","currentOrder","onReorder","order","draggedIndex","targetIndex","insertIndex","HybridHistoryManager","maxHistorySize","callback","compoundCommand","history","activeArtboardId","globalState","artboardStates","state","log","fontkitModule","getFontkit","FontAnalyzerService","oldestKey","oldestTime","key","cssUrl","css","fontFaceRegex","fontFaces","bestUrl","bestScore","fontFaceContent","urlMatch","hasUnicodeRange","unicodeRangeMatch","score","cacheKey","cached","fontUrl","response","arrayBuffer","buffer","fontOrCollection","font","limit","glyphs","maxGlyphs","glyphId","glyph","codePoints","unicode","baseCharMatch","friendlyName","num","category","nameLower","character","alternates","codePoint","defaultGlyph","baseName","foundCount","pattern","gsub","feature","tag","lookupIndex","lookup","subtable","coverage","substituteGlyphIndex","coverageIndex","altSubtable","alternateSet","altGlyphIndex","features","uniqueFeatures","glyphIndex","size","glyphWidth","glyphHeight","glyphCenterX","glyphCenterY","canvasCenterX","canvasCenterY","svgPath","path2D","ranges","range","substitute","count","FontAnalyzer"],"mappings":"AAAA,SAASA,GAAyBC,GAAG;AACpC,SAAOA,KAAKA,EAAE,cAAc,OAAO,UAAU,eAAe,KAAKA,GAAG,SAAS,IAAIA,EAAE,UAAaA;AACjG;AAEA,IAAIC,KAAU,EAAC,SAAS,GAAE,GAGtBC,IAAUD,GAAQ,UAAU,CAAA,GAO5BE,GACAC;AAEJ,SAASC,KAAmB;AACxB,QAAM,IAAI,MAAM,iCAAiC;AACrD;AACA,SAASC,KAAuB;AAC5B,QAAM,IAAI,MAAM,mCAAmC;AACvD;AAAA,CACC,WAAY;AACT,MAAI;AACA,IAAI,OAAO,cAAe,aACtBH,IAAmB,aAEnBA,IAAmBE;AAAA,EAE3B,QAAY;AACR,IAAAF,IAAmBE;AAAA,EACvB;AACA,MAAI;AACA,IAAI,OAAO,gBAAiB,aACxBD,IAAqB,eAErBA,IAAqBE;AAAA,EAE7B,QAAY;AACR,IAAAF,IAAqBE;AAAA,EACzB;AACJ,GAAC;AACD,SAASC,GAAWC,GAAK;AACrB,MAAIL,MAAqB;AAErB,WAAO,WAAWK,GAAK,CAAC;AAG5B,OAAKL,MAAqBE,MAAoB,CAACF,MAAqB;AAChE,WAAAA,IAAmB,YACZ,WAAWK,GAAK,CAAC;AAE5B,MAAI;AAEA,WAAOL,EAAiBK,GAAK,CAAC;AAAA,EAClC,QAAU;AACN,QAAI;AAEA,aAAOL,EAAiB,KAAK,MAAMK,GAAK,CAAC;AAAA,IAC7C,QAAU;AAEN,aAAOL,EAAiB,KAAK,MAAMK,GAAK,CAAC;AAAA,IAC7C;AAAA,EACJ;AAGJ;AACA,SAASC,GAAgBC,GAAQ;AAC7B,MAAIN,MAAuB;AAEvB,WAAO,aAAaM,CAAM;AAG9B,OAAKN,MAAuBE,MAAuB,CAACF,MAAuB;AACvE,WAAAA,IAAqB,cACd,aAAaM,CAAM;AAE9B,MAAI;AAEA,WAAON,EAAmBM,CAAM;AAAA,EACpC,QAAW;AACP,QAAI;AAEA,aAAON,EAAmB,KAAK,MAAMM,CAAM;AAAA,IAC/C,QAAW;AAGP,aAAON,EAAmB,KAAK,MAAMM,CAAM;AAAA,IAC/C;AAAA,EACJ;AAIJ;AACA,IAAIC,IAAQ,CAAA,GACRC,IAAW,IACXC,GACAC,KAAa;AAEjB,SAASC,KAAkB;AACvB,EAAI,CAACH,KAAY,CAACC,MAGlBD,IAAW,IACPC,EAAa,SACbF,IAAQE,EAAa,OAAOF,CAAK,IAEjCG,KAAa,IAEbH,EAAM,UACNK,GAAU;AAElB;AAEA,SAASA,KAAa;AAClB,MAAI,CAAAJ,GAGJ;AAAA,QAAIK,IAAUV,GAAWQ,EAAe;AACxC,IAAAH,IAAW;AAGX,aADIM,IAAMP,EAAM,QACVO,KAAK;AAGP,WAFAL,IAAeF,GACfA,IAAQ,CAAA,GACD,EAAEG,KAAaI;AAClB,QAAIL,KACAA,EAAaC,EAAU,EAAE,IAAG;AAGpC,MAAAA,KAAa,IACbI,IAAMP,EAAM;AAAA,IAChB;AACA,IAAAE,IAAe,MACfD,IAAW,IACXH,GAAgBQ,CAAO;AAAA;AAC3B;AAEAf,EAAQ,WAAW,SAAUM,GAAK;AAC9B,MAAIW,IAAO,IAAI,MAAM,UAAU,SAAS,CAAC;AACzC,MAAI,UAAU,SAAS;AACnB,aAASC,IAAI,GAAGA,IAAI,UAAU,QAAQA;AAClC,MAAAD,EAAKC,IAAI,CAAC,IAAI,UAAUA,CAAC;AAGjC,EAAAT,EAAM,KAAK,IAAIU,GAAKb,GAAKW,CAAI,CAAC,GAC1BR,EAAM,WAAW,KAAK,CAACC,KACvBL,GAAWS,EAAU;AAE7B;AAGA,SAASK,GAAKb,GAAKc,GAAO;AACtB,OAAK,MAAMd,GACX,KAAK,QAAQc;AACjB;AACAD,GAAK,UAAU,MAAM,WAAY;AAC7B,OAAK,IAAI,MAAM,MAAM,KAAK,KAAK;AACnC;AACAnB,EAAQ,QAAQ;AAChBA,EAAQ,UAAU;AAClBA,EAAQ,MAAM,CAAA;AACdA,EAAQ,OAAO,CAAA;AACfA,EAAQ,UAAU;AAClBA,EAAQ,WAAW,CAAA;AAEnB,SAASqB,IAAO;AAAC;AAEjBrB,EAAQ,KAAKqB;AACbrB,EAAQ,cAAcqB;AACtBrB,EAAQ,OAAOqB;AACfrB,EAAQ,MAAMqB;AACdrB,EAAQ,iBAAiBqB;AACzBrB,EAAQ,qBAAqBqB;AAC7BrB,EAAQ,OAAOqB;AACfrB,EAAQ,kBAAkBqB;AAC1BrB,EAAQ,sBAAsBqB;AAE9BrB,EAAQ,YAAY,SAAUsB,GAAM;AAAE,SAAO,CAAA;AAAG;AAEhDtB,EAAQ,UAAU,SAAUsB,GAAM;AAC9B,QAAM,IAAI,MAAM,kCAAkC;AACtD;AAEAtB,EAAQ,MAAM,WAAY;AAAE,SAAO;AAAI;AACvCA,EAAQ,QAAQ,SAAUuB,GAAK;AAC3B,QAAM,IAAI,MAAM,gCAAgC;AACpD;AACAvB,EAAQ,QAAQ,WAAW;AAAE,SAAO;AAAG;AAEvC,IAAIwB,KAAiBzB,GAAQ;AACxB,MAAC0B,KAAyB,gBAAA5B,GAAwB2B,EAAc;AClLrE,IAAIE,KAAiB;AAEd,MAAMC,GAAgB;AAAA,EAgC3B,YAAYC,IAAkC,IAAI;AArBlD,SAAA,gBAA4B,YAsB1B,KAAK,KAAKA,EAAO,MAAM,YAAYF,IAAgB,IACnD,KAAK,OAAOE,EAAO,QAAQ,YAAYF,EAAc,IACrD,KAAK,IAAIE,EAAO,MAAM,SAAYA,EAAO,IAAI,GAC7C,KAAK,IAAIA,EAAO,MAAM,SAAYA,EAAO,IAAI,GAC7C,KAAK,QAAQA,EAAO,SAAS,MAC7B,KAAK,SAASA,EAAO,UAAU,MAC/B,KAAK,kBAAkBA,EAAO,mBAAmB,WAG7CA,EAAO,iBACT,KAAK,iBAAiBA,EAAO,iBACpBA,EAAO,oBAAoB,gBACpC,KAAK,iBAAiB,gBACbA,EAAO,oBAChB,KAAK,iBAAiB,YAEtB,KAAK,iBAAiB,SAGxB,KAAK,oBAAoBA,EAAO,mBAChC,KAAK,mBAAmBA,EAAO,oBAAoB,IACnD,KAAK,YAAYA,EAAO,WACxB,KAAK,yBAAyBA,EAAO,wBACrC,KAAK,OAAOA,EAAO,MACnB,KAAK,kBAAkBA,EAAO,kBAAkB,EAAE,GAAGA,EAAO,oBAAoB,QAChF,KAAK,YAAYA,EAAO,YAAY,EAAE,GAAGA,EAAO,cAAc,QAC9D,KAAK,aAAa,IAAI,IAAIA,EAAO,cAAc,CAAA,CAAE;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,iBAA8B;AAC5B,WAAO;AAAA,MACL,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,IAAA;AAAA,EAEjB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAmB;AACjB,WAAO;AAAA,MACL,GAAG,KAAK,IAAI,KAAK,QAAQ;AAAA,MACzB,GAAG,KAAK,IAAI,KAAK,SAAS;AAAA,IAAA;AAAA,EAE9B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAcC,GAAYC,GAAqB;AAC7C,WAAOD,KAAM,KAAK,KAAKA,KAAM,KAAK,IAAI,KAAK,SAASC,KAAM,KAAK,KAAKA,KAAM,KAAK,IAAI,KAAK;AAAA,EAC1F;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgBC,GAA6D;AAC3E,UAAMC,IAAOD,EAAQ,eAAA;AACrB,WACEC,EAAK,KAAK,KAAK,KACfA,EAAK,KAAK,KAAK,KACfA,EAAK,IAAIA,EAAK,SAAS,KAAK,IAAI,KAAK,SACrCA,EAAK,IAAIA,EAAK,UAAU,KAAK,IAAI,KAAK;AAAA,EAE1C;AAAA;AAAA;AAAA;AAAA,EAKA,aAAaC,GAAyB;AACpC,SAAK,WAAW,IAAIA,CAAS;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgBA,GAAyB;AACvC,SAAK,WAAW,OAAOA,CAAS;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAaA,GAA4B;AACvC,WAAO,KAAK,WAAW,IAAIA,CAAS;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAA0B;AACxB,WAAO,MAAM,KAAK,KAAK,UAAU;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA0B;AACxB,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAwB;AACtB,SAAK,WAAW,MAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAcJ,GAAYC,GAAYI,IAAoB,GAAY;AACpE,UAAMC,IAAWN,KAAM,KAAK,IAAIK,KAAaL,KAAM,KAAK,IAAI,KAAK,QAAQK,GACnEE,IAAWN,KAAM,KAAK,IAAII,KAAaJ,KAAM,KAAK,IAAI,KAAK,SAASI,GAGpEG,IAAW,KAAK,IAAIR,IAAK,KAAK,CAAC,KAAKK,KAAaE,GACjDE,IAAY,KAAK,IAAIT,KAAM,KAAK,IAAI,KAAK,MAAM,KAAKK,KAAaE,GACjEG,IAAU,KAAK,IAAIT,IAAK,KAAK,CAAC,KAAKI,KAAaC,GAChDK,IAAa,KAAK,IAAIV,KAAM,KAAK,IAAI,KAAK,OAAO,KAAKI,KAAaC;AAEzE,WAAOE,KAAYC,KAAaC,KAAWC;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,QAAyB;AACvB,WAAO,IAAIb,GAAgB;AAAA,MACzB,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,iBAAiB,KAAK;AAAA,MACtB,gBAAgB,KAAK;AAAA,MACrB,mBAAmB,KAAK;AAAA,MACxB,kBAAkB,KAAK;AAAA;AAAA;AAAA,MAGvB,WAAW,OAAO,KAAK,aAAc,WAAW,gBAAgB,KAAK,SAAS,IAAI,KAAK;AAAA,MACvF,wBAAwB,KAAK;AAAA,MAC7B,MAAM,KAAK;AAAA,MACX,iBAAiB,KAAK,kBAAkB,EAAE,GAAG,KAAK,oBAAoB;AAAA,MACtE,WAAW,KAAK,YAAY,EAAE,GAAG,KAAK,cAAc;AAAA,MACpD,YAAY,MAAM,KAAK,KAAK,UAAU;AAAA,IAAA,CACvC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,SAAyB;AACvB,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,iBAAiB,KAAK;AAAA,MACtB,gBAAgB,KAAK;AAAA,MACrB,GAAI,KAAK,qBAAqB,EAAE,mBAAmB,KAAK,kBAAA;AAAA,MACxD,kBAAkB,KAAK;AAAA,MACvB,YAAY,MAAM,KAAK,KAAK,UAAU;AAAA,MACtC,eAAe;AAAA;AAAA,MAEf,GAAI,KAAK,aAAa,EAAE,WAAW,KAAK,UAAA;AAAA;AAAA,MAExC,GAAI,KAAK,0BAA0B,EAAE,wBAAwB,KAAK,uBAAA;AAAA;AAAA,MAElE,GAAI,KAAK,QAAQ,EAAE,MAAM,KAAK,KAAA;AAAA;AAAA,MAE9B,GAAI,KAAK,mBAAmB,EAAE,iBAAiB,EAAE,GAAG,KAAK,kBAAgB;AAAA;AAAA,MAEzE,GAAI,KAAK,aAAa,EAAE,WAAW,EAAE,GAAG,KAAK,UAAA,EAAU;AAAA,IAAE;AAAA,EAE7D;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,SAASC,GAAyC;AACvD,WAAO,IAAID,GAAgBC,CAAM;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,iBACEa,GAcM;AACN,IAAIA,EAAQ,SAAS,WAAW,KAAK,OAAOA,EAAQ,OAChDA,EAAQ,MAAM,WAAW,KAAK,IAAIA,EAAQ,IAC1CA,EAAQ,MAAM,WAAW,KAAK,IAAIA,EAAQ,IAC1CA,EAAQ,UAAU,WAAW,KAAK,QAAQ,KAAK,IAAI,KAAKA,EAAQ,KAAK,IACrEA,EAAQ,WAAW,WAAW,KAAK,SAAS,KAAK,IAAI,KAAKA,EAAQ,MAAM,IACxEA,EAAQ,oBAAoB,WAAW,KAAK,kBAAkBA,EAAQ,kBACtEA,EAAQ,mBAAmB,WAAW,KAAK,iBAAiBA,EAAQ,iBACpEA,EAAQ,sBAAsB,WAAW,KAAK,oBAAoBA,EAAQ,oBAC1EA,EAAQ,qBAAqB,WAAW,KAAK,mBAAmBA,EAAQ,mBACxEA,EAAQ,cAAc,WAAW,KAAK,YAAYA,EAAQ,YAC1D,qBAAqBA,MAAS,KAAK,kBAAkBA,EAAQ,kBAAkB,EAAE,GAAGA,EAAQ,gBAAA,IAAoB,SAChH,eAAeA,MAAS,KAAK,YAAYA,EAAQ,YAAY,EAAE,GAAGA,EAAQ,UAAA,IAAc;AAAA,EAC9F;AAAA;AAAA;AAAA;AAAA,EAKA,OAAOC,GAAkBC,GAAyB;AAChD,SAAK,QAAQ,KAAK,IAAI,KAAKD,CAAQ,GACnC,KAAK,SAAS,KAAK,IAAI,KAAKC,CAAS;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,KAAKC,GAAcC,GAAoB;AACrC,SAAK,IAAID,GACT,KAAK,IAAIC;AAAA,EACX;AACF;ACvRO,MAAMC,GAAgB;AAAA,EAK3B,cAAc;AACZ,SAAK,gCAAgB,IAAA,GACrB,KAAK,wCAAwB,IAAA,GAC7B,KAAK,mBAAmB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,eAAelB,IAAkC,IAAqB;AACpE,UAAMmB,IAAW,IAAIpB,GAAgBC,CAAM;AAC3C,gBAAK,UAAU,IAAImB,EAAS,IAAIA,CAAQ,GAIxC,KAAK,mBAAmBA,EAAS,IAE1BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAeC,GAA8B;AAC3C,UAAMD,IAAW,KAAK,UAAU,IAAIC,CAAU;AAC9C,QAAI,CAACD;AACH,aAAO,CAAA;AAIT,UAAME,IAAaF,EAAS,cAAA;AAW5B,QARAE,EAAW,QAAQ,CAAChB,MAAc;AAChC,WAAK,kBAAkB,OAAOA,CAAS;AAAA,IACzC,CAAC,GAGD,KAAK,UAAU,OAAOe,CAAU,GAG5B,KAAK,qBAAqBA,GAAY;AAExC,YAAME,IAAqB,MAAM,KAAK,KAAK,UAAU,MAAM;AAC3D,WAAK,mBAAmBA,EAAmB,SAAS,IAAIA,EAAmB,CAAC,IAAI;AAAA,IAClF;AAEA,WAAOD;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAYD,GAAiD;AAC3D,WAAO,KAAK,UAAU,IAAIA,CAAU;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAqC;AACnC,WAAO,MAAM,KAAK,KAAK,UAAU,QAAQ;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,iBAA2B;AACzB,WAAO,MAAM,KAAK,KAAK,UAAU,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkBA,GAAiC;AACjD,KAAIA,MAAe,QAAQ,KAAK,UAAU,IAAIA,CAAU,OACtD,KAAK,mBAAmBA;AAAA,EAE5B;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA4C;AAC1C,WAAO,KAAK,oBAAmB,KAAK,UAAU,IAAI,KAAK,gBAAgB,KAAK;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAqC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqBf,GAAmBe,GAA6B;AACnE,UAAMD,IAAW,KAAK,UAAU,IAAIC,CAAU;AAC9C,QAAI,CAACD;AACH,aAAO;AAIT,UAAMI,IAAqB,KAAK,kBAAkB,IAAIlB,CAAS;AAC/D,QAAIkB,GAAoB;AACtB,YAAMC,IAAmB,KAAK,UAAU,IAAID,CAAkB;AAC9D,MAAAC,KAAA,QAAAA,EAAkB,gBAAgBnB;AAAA,IACpC;AAGA,WAAAc,EAAS,aAAad,CAAS,GAC/B,KAAK,kBAAkB,IAAIA,GAAWe,CAAU,GACzC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,0BAA0Bf,GAAyB;AACjD,UAAMe,IAAa,KAAK,kBAAkB,IAAIf,CAAS;AACvD,QAAIe,GAAY;AACd,YAAMD,IAAW,KAAK,UAAU,IAAIC,CAAU;AAC9C,MAAAD,KAAA,QAAAA,EAAU,gBAAgBd,IAC1B,KAAK,kBAAkB,OAAOA,CAAS;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAsBA,GAA2C;AAC/D,UAAMe,IAAa,KAAK,kBAAkB,IAAIf,CAAS;AACvD,WAAOe,KAAa,KAAK,UAAU,IAAIA,CAAU,KAAK;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,wBAAwBf,GAAkC;AACxD,WAAO,KAAK,kBAAkB,IAAIA,CAAS,KAAK;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsBe,GAA8B;AAClD,UAAMD,IAAW,KAAK,UAAU,IAAIC,CAAU;AAC9C,WAAOD,IAAWA,EAAS,cAAA,IAAkB,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoBjD,GAAWuD,GAAmC;AAEhE,UAAMC,IAAY,MAAM,KAAK,KAAK,UAAU,OAAA,CAAQ,EAAE,QAAA;AAEtD,eAAWP,KAAYO;AACrB,UAAIP,EAAS,cAAcjD,GAAGuD,CAAC;AAC7B,eAAON;AAIX,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,0BAA0BjD,GAAWuD,GAAWnB,IAAoB,GAA2B;AAC7F,UAAMoB,IAAY,MAAM,KAAK,KAAK,UAAU,OAAA,CAAQ,EAAE,QAAA;AAEtD,eAAWP,KAAYO;AACrB,UAAIP,EAAS,cAAcjD,GAAGuD,GAAGnB,CAAS;AACxC,eAAOa;AAIX,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,wBAAwBjD,GAAWuD,GAAmC;AAEpE,WAAO,KAAK,oBAAoBvD,GAAGuD,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAeL,GAAoBP,GAA2C;AAC5E,UAAMM,IAAW,KAAK,UAAU,IAAIC,CAAU;AAC9C,WAAKD,KAILA,EAAS,iBAAiBN,CAAO,GAC1B,MAJE;AAAA,EAKX;AAAA;AAAA;AAAA;AAAA,EAKA,eAAeO,GAAoB1B,GAAuB;AACxD,WAAO,KAAK,eAAe0B,GAAY,EAAE,MAAA1B,GAAM;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY0B,GAA6B;AACvC,WAAO,KAAK,UAAU,IAAIA,CAAU;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,mBAA2B;AACzB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiBO,GAAmBC,GAAuB;AACzD,UAAMC,IAAU,MAAM,KAAK,KAAK,UAAU,SAAS;AAGnD,QAFIF,IAAY,KAAKA,KAAaE,EAAQ,UACtCD,IAAU,KAAKA,KAAWC,EAAQ,UAClCF,MAAcC,EAAS;AAE3B,UAAM,CAACE,CAAK,IAAID,EAAQ,OAAOF,GAAW,CAAC;AAC3C,IAAAE,EAAQ,OAAOD,GAAS,GAAGE,CAAK,GAEhC,KAAK,YAAY,IAAI,IAAID,CAAO;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,UAAU,MAAA,GACf,KAAK,kBAAkB,MAAA,GACvB,KAAK,mBAAmB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,SAGE;AACA,WAAO;AAAA,MACL,WAAW,MAAM,KAAK,KAAK,UAAU,QAAQ,EAAE,IAAI,CAACE,MAAMA,EAAE,QAAQ;AAAA,MACpE,kBAAkB,KAAK;AAAA,IAAA;AAAA,EAE3B;AAAA;AAAA;AAAA;AAAA,EAKA,SAASC,GAA8E;AACrF,SAAK,MAAA,GAGLA,EAAK,UAAU,QAAQ,CAAChC,MAAW;AACjC,YAAMmB,IAAWpB,GAAgB,SAASC,CAAM;AAChD,WAAK,UAAU,IAAImB,EAAS,IAAIA,CAAQ,GAGxCA,EAAS,cAAA,EAAgB,QAAQ,CAACd,MAAc;AAC9C,aAAK,kBAAkB,IAAIA,GAAWc,EAAS,EAAE;AAAA,MACnD,CAAC;AAAA,IACH,CAAC,GAGD,KAAK,mBAAmBa,EAAK;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiBC,GAA+D;AAC9E,UAAMZ,IAAa,IAAI,IAAIY,EAAS,IAAI,CAACC,MAAMA,EAAE,EAAE,CAAC;AAGpD,eAAW,CAAC7B,GAAW8B,CAAW,KAAK,KAAK,kBAAkB;AAC5D,MAAKd,EAAW,IAAIhB,CAAS,KAC3B,KAAK,0BAA0BA,CAAS;AAK5C,eAAWc,KAAY,KAAK,UAAU,OAAA,GAAU;AAC9C,YAAMiB,IAAkBjB,EAAS,gBAAgB,OAAO,CAACkB,MAAOhB,EAAW,IAAIgB,CAAE,CAAC;AAClF,MAAAlB,EAAS,gBAAA,GACTiB,EAAgB,QAAQ,CAACC,MAAOlB,EAAS,aAAakB,CAAE,CAAC;AAAA,IAC3D;AAAA,EACF;AACF;ACpUO,MAAMC,IAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO3B,UAAUC,GAAyB;AACjC,WAAQ,CAACA,IAAU,KAAK,KAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAAiBA,GAAyB;AACxC,WAAQA,IAAU,KAAK,KAAM;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAUA,GAAyB;AACjC,QAAIC,KAAeD,IAAU,MAAO,OAAO;AAC3C,WAAIC,IAAa,QACfA,KAAc,MAETA;AAAA,EACT;AACF;ACjBA,IAAIC,KAAS;AASN,MAAeC,GAAY;AAAA;AAAA,EAuBhC,IAAI,aAAsB;AACxB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA,EAEA,IAAI,WAAWC,GAA4B;AACzC,IAAIA,KACF,KAAK,YAAY,QACZ,KAAK,kBACR,KAAK,gBAAgB,EAAE,MAAM,IAAM,OAAO,QAAA,MAEnC,KAAK,cAAc,WAC5B,KAAK,YAAY;AAAA,EAErB;AAAA,EAEA,YAAY3C,IAAqC,IAAI;AACnD,SAAK,KAAKA,EAAO,MAAM,WAAWyC,IAAQ,IAC1C,KAAK,IAAIzC,EAAO,MAAM,SAAYA,EAAO,IAAI,KAC7C,KAAK,IAAIA,EAAO,MAAM,SAAYA,EAAO,IAAI,KAC7C,KAAK,WAAWA,EAAO,aAAa,SAAYA,EAAO,WAAW,GAClE,KAAK,UAAUA,EAAO,YAAY,SAAYA,EAAO,UAAU,GAG/D,KAAK,gBAAgBA,EAAO,iBAAiB,UAC7C,KAAK,gBAAiBA,EAAO,iBAAuC,CAAA,GAGpE,KAAK,YAAYA,EAAO,WACxB,KAAK,gBAAgBA,EAAO,eAC5B,KAAK,SAASA,EAAO,QACrB,KAAK,QAAQA,EAAO,OACpB,KAAK,iBAAiBA,EAAO,gBAC7B,KAAK,SAASA,EAAO,QAGjBA,EAAO,cAAc,CAACA,EAAO,cAC/B,KAAK,YAAY,QACZ,KAAK,kBACR,KAAK,gBAAgB,EAAE,MAAM,IAAM,OAAO,QAAA,KAK9C,KAAK,OAAOA,EAAO,MACnB,KAAK,UAAUA,EAAO,YAAY,SAAYA,EAAO,UAAU,IAC/D,KAAK,SAASA,EAAO,WAAW,SAAYA,EAAO,SAAS;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,uBAAoC;AAClC,WAAO,KAAK,eAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAA2B;AACzB,WAAO,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQC,GAAYC,GAAqB;AACvC,UAAM0C,IAAa,KAAK,qBAAA,GAClBC,IAAiB,KAAK,kBAAA,GAGtBC,IAAcR,EAAc,iBAAiB,KAAK,QAAQ,GAC1DS,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW,GAG1BG,IAAKhD,IAAK4C,EAAe,GACzBK,IAAKhD,IAAK2C,EAAe,GAGzBM,IAASF,IAAKF,IAAMG,IAAKF,GACzBI,IAASH,IAAKD,IAAME,IAAKH,GAGzBM,IAAeR,EAAe,IAAIM,GAClCG,IAAeT,EAAe,IAAIO;AAGxC,WACEC,KAAgBT,EAAW,KAC3BS,KAAgBT,EAAW,IAAIA,EAAW,SAC1CU,KAAgBV,EAAW,KAC3BU,KAAgBV,EAAW,IAAIA,EAAW;AAAA,EAE9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,qBAAqBW,GAAqC;AACxD,IAAAA,EAAI,UAAU,KAAK,GAAG,KAAK,CAAC,GAC5BA,EAAI,OAAOjB,EAAc,UAAU,KAAK,QAAQ,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAA4B;AAC1B,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,UAAU,KAAK;AAAA,MACf,SAAS,KAAK;AAAA,MACd,eAAe,KAAK;AAAA,MACpB,eAAe,EAAE,GAAG,KAAK,cAAA;AAAA;AAAA,MAEzB,GAAI,KAAK,aAAa,EAAE,WAAW,KAAK,UAAA;AAAA,MACxC,GAAI,KAAK,iBAAiB,EAAE,eAAe,EAAE,GAAG,KAAK,gBAAc;AAAA,MACnE,GAAI,KAAK,UAAU;AAAA,QACjB,QAAQ;AAAA,UACN,GAAG,KAAK;AAAA,UACR,GAAI,KAAK,OAAO,aAAa,EAAE,WAAW,CAAC,GAAG,KAAK,OAAO,SAAS,EAAA;AAAA,QAAE;AAAA,MACvE;AAAA;AAAA,MAGF,GAAI,KAAK,SAAS;AAAA,QAChB,OAAO,KAAK,MAAM,IAAI,CAACkB,OAAO,EAAE,GAAGA,GAAG,aAAa,gBAAgBA,EAAE,WAAW,IAAI;AAAA,MAAA;AAAA,MAEtF,GAAI,KAAK,kBAAkB,EAAE,gBAAgB,EAAE,GAAG,KAAK,iBAAe;AAAA,MACtE,GAAI,KAAK,UAAU,EAAE,QAAQ,EAAE,GAAG,KAAK,SAAO;AAAA;AAAA,MAE9C,GAAI,KAAK,QAAQ,EAAE,MAAM,KAAK,KAAA;AAAA,MAC9B,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAA;AAAA,MAClD,GAAI,KAAK,WAAW,UAAa,EAAE,QAAQ,KAAK,OAAA;AAAA,MAChD,GAAI,KAAK,cAAc,EAAE,YAAY,GAAA;AAAA,IAAK;AAAA,EAE9C;AAAA;AAAA;AAAA;AAAA,EAWA,KAAKP,GAAYC,GAAkB;AACjC,SAAK,KAAKD,GACV,KAAK,KAAKC;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA,EAKA,YAAYO,GAA2B;AACrC,SAAK,WAAWA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoC;AAClC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,wBAA4C;AJrQ9C,QAAAC;AIsQI,UAAMtD,IAAO,KAAK,eAAA;AAClB,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,OAAOA,EAAK;AAAA,MACZ,QAAQA,EAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf,eAAe,EAAE,GAAG,KAAK,cAAA;AAAA;AAAA,MAEzB,cAAasD,IAAA,KAAK,WAAL,gBAAAA,EAAa;AAAA,IAAA;AAAA,EAE9B;AACF;ACvOO,MAAMC,EAAU;AAAA;AAAA;AAAA;AAAA;AAAA,EAOrB,YAAYxD,GAAwB;AAClC,SAAK,UAAUA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,aAAayD,GAAgBC,GAAgB;AAE3C,UAAMC,IAAO,KAAK,QAAQ,WAAW,KAAK,KAAM,KAC1Cf,IAAM,KAAK,IAAIe,CAAG,GAClBd,IAAM,KAAK,IAAIc,CAAG,GAGlBb,IAAKW,IAAS,KAAK,QAAQ,GAC3BV,IAAKW,IAAS,KAAK,QAAQ;AAGjC,WAAO;AAAA,MACL,GAAGZ,IAAKF,IAAMG,IAAKF;AAAA,MACnB,GAAGC,IAAKD,IAAME,IAAKH;AAAA,IAAA;AAAA,EAEvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,aAAaI,GAAgBC,GAAgB;AAE3C,UAAMU,IAAO,CAAC,KAAK,QAAQ,WAAW,KAAK,KAAM,KAC3Cf,IAAM,KAAK,IAAIe,CAAG,GAClBd,IAAM,KAAK,IAAIc,CAAG,GAGlBC,IAAOZ,IAASJ,IAAMK,IAASJ,GAC/BgB,IAAOb,IAASH,IAAMI,IAASL;AAGrC,WAAO;AAAA,MACL,GAAG,KAAK,QAAQ,IAAIgB;AAAA,MACpB,GAAG,KAAK,QAAQ,IAAIC;AAAA,IAAA;AAAA,EAExB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,kBAAkBf,GAAYC,GAAY;AACxC,UAAMY,IAAO,KAAK,QAAQ,WAAW,KAAK,KAAM,KAC1Cf,IAAM,KAAK,IAAIe,CAAG,GAClBd,IAAM,KAAK,IAAIc,CAAG;AAExB,WAAO;AAAA,MACL,IAAIb,IAAKF,IAAMG,IAAKF;AAAA,MACpB,IAAIC,IAAKD,IAAME,IAAKH;AAAA,IAAA;AAAA,EAExB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,kBAAkBE,GAAYC,GAAY;AACxC,UAAMY,IAAO,CAAC,KAAK,QAAQ,WAAW,KAAK,KAAM,KAC3Cf,IAAM,KAAK,IAAIe,CAAG,GAClBd,IAAM,KAAK,IAAIc,CAAG;AAExB,WAAO;AAAA,MACL,IAAIb,IAAKF,IAAMG,IAAKF;AAAA,MACpB,IAAIC,IAAKD,IAAME,IAAKH;AAAA,IAAA;AAAA,EAExB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OAAO,wBAAwBkB,GAAgBC,GAAgBC,GAAiBC,GAAiBC,GAAsB;AAErH,UAAMP,IAAO,CAACO,IAAe,KAAK,KAAM,KAClCtB,IAAM,KAAK,IAAIe,CAAG,GAClBd,IAAM,KAAK,IAAIc,CAAG,GAGlBb,IAAKgB,IAASE,GACdjB,IAAKgB,IAASE,GAGdL,IAAOd,IAAKF,IAAMG,IAAKF,GACvBgB,IAAOf,IAAKD,IAAME,IAAKH;AAG7B,WAAO;AAAA,MACL,GAAGoB,IAAUJ;AAAA,MACb,GAAGK,IAAUJ;AAAA,IAAA;AAAA,EAEjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,oBAAoB;AAClB,WAAQ,CAAC,KAAK,QAAQ,WAAW,KAAK,KAAM;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAkB;AAChB,WAAQ,KAAK,QAAQ,WAAW,KAAK,KAAM;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,qBAAqB;AACnB,UAAMF,IAAM,KAAK,kBAAA;AACjB,WAAO;AAAA,MACL,KAAK,KAAK,IAAIA,CAAG;AAAA,MACjB,KAAK,KAAK,IAAIA,CAAG;AAAA,IAAA;AAAA,EAErB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmB;AACjB,UAAMA,IAAM,KAAK,gBAAA;AACjB,WAAO;AAAA,MACL,KAAK,KAAK,IAAIA,CAAG;AAAA,MACjB,KAAK,KAAK,IAAIA,CAAG;AAAA,IAAA;AAAA,EAErB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,aAAaQ,GAAgC;AAClD,WAAO,IAAIX,EAAUW,CAAe;AAAA,EACtC;AACF;ACjMO,MAAMC,KAAiC;AAAA;AAAA,EAE5C;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,GAAG;AAAA,IACvB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,GAAG;AAAA,IACvB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,GAAG;AAAA,IACvB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACrD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACjC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,IAC5B,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA;AAAA,EAKf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IACtC,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,GAAG;AAAA,IACb,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA;AAAA,EAIf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAC3C,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,GAAG;AAAA,IAClB,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAAA,IAChD,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAEjB;AAoBO,SAASC,KAAyB;AACvC,SAAOD,GAAa,IAAI,CAACE,MAAMA,EAAE,IAAI;AACvC;AAqBO,MAAMC,KAAgD;AAAA,EAC3D,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,cAAc;AAAA,EACd,OAAO;AAAA,EACP,cAAc;AAAA;AAAA,EACd,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AACb;ACxiDO,SAASC,GAAoBxE,GAA+B;AACjE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAASzE,KAAW,SAAS;AAEnC,SADc,iBAAiByE,CAAM,EAAE,iBAAiB,UAAU,EAAE,KAAA,KACpD;AAClB;AAkBO,SAASC,GAAgB1E,GAA+B;AAC7D,SAAI,OAAO,SAAW,OAAe,OAAO,WAAa,MAChD,UAaF,SAAS,gBAAgB,aAAa,YAAY,KAAK;AAChE;AAMO,SAAS2E,KAAiC;AAI/C,SAHcD,GAAA,EAGJ,SAAS,MAAM,IAChB,YAEA;AAEX;AAKO,SAASE,GAAqB5E,GAA+B;AAClE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAASzE,KAAW,SAAS;AAEnC,SADc,iBAAiByE,CAAM,EAAE,iBAAiB,2BAA2B,EAAE,KAAA,KACrE;AAClB;AAMO,SAASI,GAA6BC,IAAgB,KAAK9E,GAA+B;AAC/F,QAAM+E,IAAQP,GAAoBxE,CAAO;AAGzC,MAAI+E,EAAM,WAAW,QAAQ;AAG3B,WAAO,GADgBA,EAAM,MAAM,GAAG,EAAE,CAChB,MAAMD,CAAK;AAIrC,MAAIC,EAAM,WAAW,GAAG,GAAG;AACzB,UAAMC,IAAMD,EAAM,QAAQ,KAAK,EAAE,GAC3BE,IAAI,SAASD,EAAI,UAAU,GAAG,CAAC,GAAG,EAAE,GACpCE,IAAI,SAASF,EAAI,UAAU,GAAG,CAAC,GAAG,EAAE,GACpCG,IAAI,SAASH,EAAI,UAAU,GAAG,CAAC,GAAG,EAAE;AAC1C,WAAO,QAAQC,CAAC,KAAKC,CAAC,KAAKC,CAAC,KAAKL,CAAK;AAAA,EACxC;AAGA,SAAOC;AACT;AAMO,SAASK,GAA2BpF,GAA+B;AACxE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAASzE,KAAW,SAAS;AAEnC,SADc,iBAAiByE,CAAM,EAAE,iBAAiB,wBAAwB,EAAE,KAAA,KAClE;AAClB;AAKO,SAASY,GAAyBrF,GAA+B;AACtE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAASzE,KAAW,SAAS;AAEnC,SADc,iBAAiByE,CAAM,EAAE,iBAAiB,sBAAsB,EAAE,KAAA,KAChE;AAClB;AAiCO,SAASa,GAA0BtF,GAA+B;AACvE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAASzE,KAAW,SAAS;AAEnC,SADc,iBAAiByE,CAAM,EAAE,iBAAiB,cAAc,EAAE,KAAA,KACxD;AAClB;AAMO,SAASc,GAA0BvF,GAA+B;AACvE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAASzE,KAAW,SAAS;AAEnC,SADc,iBAAiByE,CAAM,EAAE,iBAAiB,cAAc,EAAE,KAAA,KACxD;AAClB;AAMO,SAASe,GAA2BxF,GAA+B;AACxE,QAAMyF,IAAKH,GAA0BtF,CAAO;AAC5C,SAAIyF,EAAG,WAAW,QAAQ,IACjB,GAAGA,EAAG,MAAM,GAAG,EAAE,CAAC,YAEpB;AACT;AAMO,SAASC,GAA4B1F,GAA+B;AACzE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAAoB,SAAS;AAEnC,SADc,iBAAiBA,CAAM,EAAE,iBAAiB,WAAW,EAAE,KAAA,KACrD;AAClB;AAMO,SAASkB,GAA2B3F,GAA+B;AACxE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAAoB,SAAS,iBAC7BM,IAAQ,iBAAiBN,CAAM,EAAE,iBAAiB,cAAc,EAAE,KAAA;AACxE,SAAIM,EAAM,WAAW,QAAQ,IACpB,GAAGA,EAAM,MAAM,GAAG,EAAE,CAAC,YAEvBA,KAAS;AAClB;AAKO,SAASa,GAAyB5F,GAA+B;AACtE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAAoB,SAAS;AAEnC,SADc,iBAAiBA,CAAM,EAAE,iBAAiB,6BAA6B,EAAE,KAAA,KACvE;AAClB;AAOO,SAASoB,GAAqB7F,GAA+B;AAClE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAAoB,SAAS;AAEnC,SADc,iBAAiBA,CAAM,EAAE,iBAAiB,cAAc,EAAE,KAAA,KACxD;AAClB;AAMO,SAASqB,GAA2B9F,GAA+B;AACxE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAAoB,SAAS;AAEnC,SADc,iBAAiBA,CAAM,EAAE,iBAAiB,cAAc,EAAE,KAAA,KACxD;AAClB;AAMO,SAASsB,GAAuB/F,GAA+B;AACpE,MAAI,OAAO,SAAW,OAAe,OAAO,WAAa;AACvD,WAAO;AAET,QAAMyE,IAAoB,SAAS,iBAC7BM,IAAQ,iBAAiBN,CAAM,EAAE,iBAAiB,cAAc,EAAE,KAAA;AACxE,SAAIM,EAAM,WAAW,QAAQ,IACpB,GAAGA,EAAM,MAAM,GAAG,EAAE,CAAC,YAEvBA,KAAS;AAClB;AAaO,MAAMiB,KAA0B,GAG1BC,KAAyB,WAOzBC,KAA2B,IAI3BC,KAA8B,GAC9BC,KAA4B,IAG5BC,KAA2B,IAC3BC,KAAqB,IACrBC,KAAwB,IAIxBC,KAAyB,GAIzBC,KAA2B,WAC3BC,KAA8B,GAG9BC,KAAgB,KAGhBC,KAAgBvC,GAAA,GAEhBwC,KAAa,CAAC,GAAG,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,GAAG,GAG5EC,IAAqB,GACrBC,KAAyB,KAGzBC,KAAY,IACZC,KAAgB,GAChBC,KAAgB,KAShBC,yBAAmB,IAAI;AAAA,EAClC;AAAA,EAAS;AAAA,EAAe;AAAA,EAAW;AAAA,EAAU;AAAA,EAC7C;AAAA,EAAU;AAAA,EAAmB;AAAA,EAAW;AAAA,EAAY;AAAA,EACpD;AAAA,EAAmB;AAAA,EAAY;AAAA,EAAe;AAAA,EAC9C;AAAA,EAAkB;AAAA,EAAiB;AAAA,EAAkB;AAAA,EACrD;AAAA,EAAY;AACd,CAAC,GAGYC,KAAmB;AChWzB,IAAKC,uBAAAA,OACVA,EAAAA,EAAA,QAAQ,CAAA,IAAR,SACAA,EAAAA,EAAA,OAAO,CAAA,IAAP,QACAA,EAAAA,EAAA,OAAO,CAAA,IAAP,QACAA,EAAAA,EAAA,QAAQ,CAAA,IAAR,SACAA,EAAAA,EAAA,OAAO,CAAA,IAAP,QALUA,IAAAA,MAAA,CAAA,CAAA;AAQZ,MAAMC,GAAO;AAAA,EAAb,cAAA;ARbA,QAAA/D;AQcE,SAAQ,QAAkB,GAC1B,KAAQ,UAAU,OAAOtF,KAAY,SAAeA,IAAAA,GAAQ,QAARA,gBAAAA,EAAa,cAAa;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9E,SAASsJ,GAAuB;AAC9B,SAAK,QAAQA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,WAAWC,GAAwB;AACjC,SAAK,UAAUA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAMC,MAAqBC,GAAwB;AAAA,EAEnD;AAAA;AAAA;AAAA;AAAA,EAKA,KAAKC,MAAoBzI,GAAuB;AAC9C,IAAI,KAAK,WAAW,KAAK,SAAS,KAChC,QAAQ,KAAK,UAAUyI,CAAO,IAAI,GAAGzI,CAAI;AAAA,EAE7C;AAAA;AAAA;AAAA;AAAA,EAKA,KAAKyI,MAAoBzI,GAAuB;AAC9C,IAAI,KAAK,WAAW,KAAK,SAAS,KAChC,QAAQ,KAAK,UAAUyI,CAAO,IAAI,GAAGzI,CAAI;AAAA,EAE7C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAMyI,MAAoBzI,GAAuB;AAC/C,IAAI,KAAK,WAAW,KAAK,SAAS,KAChC,QAAQ,MAAM,WAAWyI,CAAO,IAAI,GAAGzI,CAAI;AAAA,EAE/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM0I,GAAiC;AACrC,WAAO,IAAIC,GAAa,MAAMD,CAAS;AAAA,EACzC;AACF;AAKA,MAAMC,GAAa;AAAA,EACjB,YAAoBC,GAAwBF,GAAmB;AAA3C,SAAA,SAAAE,GAAwB,KAAA,YAAAF;AAAA,EAAoB;AAAA,EAEhE,MAAMD,MAAoBzI,GAAuB;AAC/C,SAAK,OAAO,MAAM,IAAI,KAAK,SAAS,KAAKyI,CAAO,IAAI,GAAGzI,CAAI;AAAA,EAC7D;AAAA,EAEA,KAAKyI,MAAoBzI,GAAuB;AAC9C,SAAK,OAAO,KAAK,IAAI,KAAK,SAAS,KAAKyI,CAAO,IAAI,GAAGzI,CAAI;AAAA,EAC5D;AAAA,EAEA,KAAKyI,MAAoBzI,GAAuB;AAC9C,SAAK,OAAO,KAAK,IAAI,KAAK,SAAS,KAAKyI,CAAO,IAAI,GAAGzI,CAAI;AAAA,EAC5D;AAAA,EAEA,MAAMyI,MAAoBzI,GAAuB;AAC/C,SAAK,OAAO,MAAM,IAAI,KAAK,SAAS,KAAKyI,CAAO,IAAI,GAAGzI,CAAI;AAAA,EAC7D;AACF;AAGO,MAAM4I,KAAS,IAAIR,GAAA,GAGbS,KAAe,CAACH,MACpBE,GAAO,MAAMF,CAAS,GCvGzBE,KAASC,GAAa,iBAAiB,GAIvCC,yBAAgB,IAAA;AAOf,SAASC,GAAsBC,GAAyC;AAC7E,SAAAF,GAAU,IAAIE,CAAQ,GACf,MAAM;AACX,IAAAF,GAAU,OAAOE,CAAQ;AAAA,EAC3B;AACF;AAOO,SAASC,GAAgBjI,GAAyB;AACvD,EAAA8H,GAAU,QAAQ,CAAAE,MAAY;AAC5B,QAAI;AACF,MAAAA,EAAShI,CAAS;AAAA,IACpB,SAASkI,GAAO;AACdN,MAAAA,GAAO,MAAM,mBAAmBM,CAAK;AAAA,IACvC;AAAA,EACF,CAAC;AACH;ACzBO,MAAMC,EAAW;AAAA,EAId,cAAc;AACpB,SAAK,4BAAY,IAAA;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,cAA0B;AAC/B,WAAKA,EAAW,aACdA,EAAW,WAAW,IAAIA,EAAA,IAErBA,EAAW;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,QAAQC,GAA+B;AACrC,UAAMC,IAAW,KAAK,MAAM,IAAID,CAAG;AACnC,QAAIC;AACF,aAAAA,EAAS,YACFA,EAAS;AAIlB,UAAMC,IAAM,IAAI,MAAA;AAChB,IAAAA,EAAI,cAAc;AAElB,UAAMC,IAAoB;AAAA,MACxB,OAAOD;AAAA,MACP,UAAU;AAAA,MACV,QAAQ;AAAA,IAAA;AAGV,WAAAA,EAAI,SAAS,MAAM;AACjB,MAAAC,EAAM,SAAS;AAAA,IACjB,GAEAD,EAAI,UAAU,MAAM;AAGlB,MAAAC,EAAM,SAAS;AAAA,IACjB,GAEA,KAAK,MAAM,IAAIH,GAAKG,CAAK,IAIrB,CAACH,EAAI,SAAS,GAAG,KAAK,6BAA6B,KAAKA,CAAG,OAC7DE,EAAI,MAAMF,IAGLE;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAQF,GAAmB;AACzB,UAAMG,IAAQ,KAAK,MAAM,IAAIH,CAAG;AAChC,IAAKG,MAELA,EAAM,YACFA,EAAM,YAAY,KACpB,KAAK,MAAM,OAAOH,CAAG;AAAA,EAEzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAIA,GAA2C;AVxGjD,QAAA/E;AUyGI,YAAOA,IAAA,KAAK,MAAM,IAAI+E,CAAG,MAAlB,gBAAA/E,EAAqB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI+E,GAAsB;AACxB,WAAO,KAAK,MAAM,IAAIA,CAAG;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAmD;AACjD,QAAII,IAAY;AAChB,eAAWD,KAAS,KAAK,MAAM,OAAA;AAC7B,MAAAC,KAAaD,EAAM;AAErB,WAAO,EAAE,SAAS,KAAK,MAAM,MAAM,WAAAC,EAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAc;AACZ,SAAK,MAAM,MAAA;AAAA,EACb;AACF;ACnHO,MAAMC,KAAkD,OAAO,OAAO;AAAA,EAC3E,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,YAAY;AACd,CAAC,GAEKC,KAAU;AAGT,SAASC,GAAqBC,GAA2D;AAC9F,SAAKA,IAEH,KAAK,IAAIA,EAAY,aAAa,CAAC,IAAIF,MACvC,KAAK,IAAIE,EAAY,WAAW,CAAC,IAAIF,MACrC,KAAK,IAAIE,EAAY,aAAa,CAAC,IAAIF,KAJhB;AAM3B;AAOO,SAASG,GACdD,GAC8B;AAC9B,MAAID,GAAqBC,CAAW,EAAG;AACvC,QAAME,IAAQ,CAACC,MAAc,KAAK,MAAMA,IAAI,GAAG,IAAI;AACnD,SAAO;AAAA,IACL,YAAYD,EAAMF,EAAa,UAAU;AAAA,IACzC,UAAUE,EAAMF,EAAa,QAAQ;AAAA,IACrC,YAAYE,EAAMF,EAAa,UAAU;AAAA,EAAA;AAE7C;AAMO,SAASI,GAAoBJ,GAA0D;AAC5F,MAAID,GAAqBC,CAAW,EAAG,QAAO;AAC9C,QAAMlH,IAAIkH;AACV,SAAO,IAAIlH,EAAE,UAAU,IAAIA,EAAE,QAAQ,IAAIA,EAAE,UAAU;AACvD;AAkBA,IAAIuH,KAAkE;AAG/D,SAASC,GAAoCC,GAA6C;AAC/F,EAAAF,KAA8BE;AAChC;AAQO,SAASC,GACdC,GACAT,GACuB;AACvB,MAAID,GAAqBC,CAAW,EAAG,QAAOS;AAC9C,MAAI,CAACJ;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAMJ,SAAOA,GAA4BI,GAAQT,CAAY;AACzD;AC/FA,MAAMhB,KAASC,GAAa,cAAc,GAMpCyB,KAAwB,KAGxBC,KAAyB,GAGzBC,KAA2B;AAGjC,SAASC,EAAmBC,GAAmD;AAC7E,SAAOA,EAAU;AACnB;AAEO,MAAMC,WAAqBtH,GAAY;AAAA;AAAA,EAkB5C,YAAY1C,IAAsC,IAAI;AZpDxD,QAAA0D,GAAAuG,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC;AYqDI,UAAM1K,CAAM,GANd,KAAQ,aAA4B,MACpC,KAAQ,gBAAsD,MAC9D,KAAQ,eAAuB,GAC/B,KAAQ,iBAAgC,MAItC,KAAK,gBAAgB,SAGrB,KAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,SAAO0D,IAAA1D,EAAO,kBAAP,gBAAA0D,EAAsB,UAAS;AAAA,MACtC,UAAQuG,IAAAjK,EAAO,kBAAP,gBAAAiK,EAAsB,WAAU;AAAA,MACxC,SAAOC,IAAAlK,EAAO,kBAAP,gBAAAkK,EAAsB,UAAS;AAAA,MACtC,SAAOC,IAAAnK,EAAO,kBAAP,gBAAAmK,EAAsB,UAAS;AAAA,MACtC,aAAWC,IAAApK,EAAO,kBAAP,gBAAAoK,EAAsB,cAAa;AAAA,MAC9C,cAAYC,IAAArK,EAAO,kBAAP,gBAAAqK,EAAsB,eAAc;AAAA,MAChD,kBAAgBC,IAAAtK,EAAO,kBAAP,gBAAAsK,EAAsB,mBAAkB;AAAA,MACxD,gBAAcC,IAAAvK,EAAO,kBAAP,gBAAAuK,EAAsB,iBAAgB;AAAA,MACpD,gBAAcC,IAAAxK,EAAO,kBAAP,gBAAAwK,EAAsB,iBAAgB;AAAA,IAAA;AAKtD,UAAMvB,IAAcC,IAAqBuB,IAAAzK,EAAO,kBAAP,gBAAAyK,EAAsB,WAAW;AAC1E,IAAIxB,MACF,KAAK,cAAc,cAAcA,IAInC,KAAK,WAAWjJ,EAAO,YAAY,IACnC,KAAK,cAAc,IACnB,KAAK,eAAe,MACpB,KAAK,mBAAmBA,EAAO,oBAAoB,GACnD,KAAK,UAAQ0K,IAAA1K,EAAO,aAAP,gBAAA0K,EAAiB,cAAc,SAAS,YAAW,IAChE,KAAK,qBAAqB1K,EAAO,sBAAsB,IACvD,KAAK,iBAAiBA,EAAO,WAAW,YAAY,UACpD,KAAK,iBAAiB,MAGtB,KAAK,aAAa,IAGlB,KAAK,iBAAiBA,EAAO,kBAAkB,MAG3C,KAAK,YACP,KAAK,UAAU,KAAK,QAAQ;AAAA,EAEhC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAUyI,GAAmB;AAC3B,UAAMkC,IAAQlC,EAAI,YAAA,EAAc,SAAS,MAAM;AAC/C,SAAK,QAAQkC,GAETA,IAEF,KAAK,iBAAiBlC,CAAG,IAGzB,KAAK,iBAAiBA,CAAG;AAAA,EAE7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAiBA,GAAmB;AAC1C,SAAK,eAAe,GACpB,KAAK,iBAAiB,MACtB,KAAK,iBAAiB,WACtB,KAAK,wBAAwBA,CAAG;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,wBAAwBA,GAAmB;AZtIrD,QAAA/E;AYwII,SAAK,iBAAA,GAGL,KAAK,kBAAA;AAGL,UAAMiF,IADaH,EAAW,YAAA,EACP,QAAQC,CAAG;AAClC,SAAK,iBAAiBA;AAEtB,QAAImC,IAAU;AAEd,UAAMC,IAAS,MAAM;AACnB,MAAAD,IAAU,IACV,KAAK,iBAAA;AAAA,IACP;AAGA,QAAIjC,EAAI,YAAYA,EAAI,eAAe,GAAG;AACxC,MAAAkC,EAAA,GACA,KAAK,eAAelC,GACpB,KAAK,cAAc,IACnB,KAAK,iBAAiB,UACtB,KAAK,iBAAiB,MACtB,KAAK,mBAAmBA,EAAI,eAAeA,EAAI,eAE1C,KAAK,uBACR,KAAK,cAAc,SAAS,KAAK,cAAc,QAAQ,KAAK,mBAG1D,KAAK,kBACP,KAAK,eAAe,KAAK,OAAO,GAGlCL,GAAgB,KAAK,EAAE;AACvB;AAAA,IACF;AAGA,UAAMwC,IAAiBnC,EAAI,QACrBoC,IAAkBpC,EAAI;AAE5B,IAAAA,EAAI,SAAS,CAACqC,MAAO;AACnB,MAAIJ,MACJC,EAAA,GAEA,KAAK,eAAelC,GACpB,KAAK,cAAc,IACnB,KAAK,iBAAiB,UACtB,KAAK,iBAAiB,MACtB,KAAK,mBAAmBA,EAAI,eAAeA,EAAI,eAI1C,KAAK,uBAER,KAAK,cAAc,SAAS,KAAK,cAAc,QAAQ,KAAK,mBAI1D,KAAK,kBACP,KAAK,eAAe,KAAK,OAAO,GAKlCL,GAAgB,KAAK,EAAE,GAGnBwC,KAAkBA,MAAmBnC,EAAI,UAC1CmC,EAAiCE,CAAE;AAAA,IAExC,GAEArC,EAAI,UAAU,CAACqC,MAAO;AACpB,UAAIJ,EAAS;AACb,MAAAC,EAAA;AAEA,YAAMtC,IAAQ,IAAI,MAAM,yBAAyBE,CAAG,EAAE;AACtD,WAAK,kBAAkBA,GAAKF,CAAK,GAG7BwC,KAAmBA,MAAoBpC,EAAI,WAC5CoC,EAAkCC,CAAW;AAAA,IAElD,GAGA,KAAK,gBAAgB,WAAW,MAAM;AACpC,UAAIJ,EAAS;AACb,MAAAC,EAAA,GAGAlC,EAAI,MAAM;AACV,YAAMJ,IAAQ,IAAI,MAAM,8BAA8BoB,EAAqB,OAAOlB,CAAG,EAAE;AACvF,WAAK,kBAAkBA,GAAKF,CAAK;AAAA,IACnC,GAAGoB,EAAqB,IAIpB,CAAChB,EAAI,OAAOA,EAAI,QAAQF,OAC1BE,EAAI,MAAMF,IAOR,CAACmC,KAAWjC,EAAI,YAAYA,EAAI,eAAe,OACjDjF,IAAAiF,EAAI,WAAJ,QAAAjF,EAAA,KAAAiF,GAAa,IAAI,MAAM,MAAM;AAAA,EAEjC;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkBF,GAAaF,GAAoB;AAGzD,QAFA,KAAK,gBAED,KAAK,eAAeqB,IAAwB;AAE9C,YAAMqB,IAAQpB,KAA2B,KAAK,IAAI,GAAG,KAAK,eAAe,CAAC;AAC1E5B,MAAAA,GAAO,KAAK,sBAAsB,KAAK,YAAY,wBAAwBgD,CAAK,OAAOxC,CAAG,GAC1F,KAAK,iBAAiB,YACtB,KAAK,iBAAiBF,GAGtBD,GAAgB,KAAK,EAAE,GAEvB,KAAK,gBAAgB,WAAW,MAAM;AACpC,aAAK,wBAAwBG,CAAG;AAAA,MAClC,GAAGwC,CAAK;AAAA,IACV;AAEEhD,MAAAA,GAAO,MAAM,2BAA2B2B,EAAsB,cAAcnB,CAAG,GAC/E,KAAK,cAAc,IACnB,KAAK,iBAAiB,SACtB,KAAK,iBAAiBF,GAGtBD,GAAgB,KAAK,EAAE;AAAA,EAE3B;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;AAC/B,IAAI,KAAK,kBAAkB,SACzB,aAAa,KAAK,aAAa,GAC/B,KAAK,gBAAgB;AAAA,EAEzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAA0B;AAChC,IAAI,KAAK,mBACPE,EAAW,YAAA,EAAc,QAAQ,KAAK,cAAc,GACpD,KAAK,iBAAiB;AAAA,EAE1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAuB;AACrB,IAAK,KAAK,aAEVP,GAAO,KAAK,qCAAqC,KAAK,QAAQ,GAC9D,KAAK,iBAAA,GACL,KAAK,cAAc,IACnB,KAAK,eAAe,MAEhB,KAAK,SACP,KAAK,iBAAiB,WACtB,KAAK,iBAAiB,MACtB,KAAK,iBAAiB,KAAK,QAAQ,KAEnC,KAAK,iBAAiB,KAAK,QAAQ;AAAA,EAEvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAyB;AAC/B,IAAI,KAAK,eACP,IAAI,gBAAgB,KAAK,UAAU,GACnC,KAAK,aAAa;AAAA,EAEtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAiBQ,GAAmB;AAE1C,SAAK,iBAAA;AACL,UAAMyC,IAAU,IAAI,MAAA;AACpB,IAAAA,EAAQ,cAAc,aAEtBA,EAAQ,SAAS,MAAM;AAGrB,YAAMC,IAAS,SAAS,cAAc,QAAQ;AAC9C,MAAAA,EAAO,QAAQD,EAAQ,QAAQ,GAC/BC,EAAO,SAASD,EAAQ,SAAS;AAEjC,YAAM3H,IAAM4H,EAAO,WAAW,IAAI;AAClC,MAAA5H,EAAI,MAAM,GAAO,CAAK,GACtBA,EAAI,UAAU2H,GAAS,GAAG,CAAC,GAG3BC,EAAO,OAAO,CAACC,MAAS;AACtB,YAAI,CAACA,GAAM;AACTnD,UAAAA,GAAO,MAAM,+BAA+B,GAC5C,KAAK,cAAc;AACnB;AAAA,QACF;AAGA,aAAK,aAAa,IAAI,gBAAgBmD,CAAI,GAG1C,KAAK,eAAe,IAAI,MAAA,GACxB,KAAK,aAAa,cAAc,aAChC,KAAK,aAAa,SAAS,MAAM;AAC/B,eAAK,cAAc,IACnB,KAAK,mBAAmB,KAAK,aAAc,QAAQ,KAAK,aAAc,QAGjE,KAAK,uBAER,KAAK,cAAc,SAAS,KAAK,cAAc,QAAQ,KAAK,mBAI1D,KAAK,kBACP,KAAK,eAAe,KAAK,OAAO,GAKlC9C,GAAgB,KAAK,EAAE;AAAA,QAIzB,GAEA,KAAK,aAAa,MAAM,KAAK;AAAA,MAC/B,GAAG,WAAW;AAAA,IAChB,GAEA4C,EAAQ,UAAU,MAAM;AACtBjD,MAAAA,GAAO,MAAM,uBAAuBQ,CAAG,GACvC,KAAK,cAAc;AAAA,IACrB,GAEAyC,EAAQ,MAAMzC;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAA8B;AAC5B,QAAI,KAAK,YAAY;AAKnB,YAAM4C,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc;AAGlE,UAAIC,IACF,KAAK,cAAc,QAAQ,KAAK,KAAK,cAAc,QAAQ,KAAK,cAAc,QAAQF,IAAY,IAChGG,IACF,KAAK,cAAc,SAAS,KAAK,KAAK,cAAc,QAAQ,KAAK,cAAc,SAASF,IAAa;AAGvG,YAAMG,IAAQ,KAAK,cAAc,iBAAiB,KAAK,GACjDC,IAAQ,KAAK,cAAc,eAAe,KAAK;AACrD,MAAAH,KAAgBE,GAChBD,KAAgBE;AAGhB,YAAM3I,IAAM,KAAK,IAAIT,EAAc,UAAU,KAAK,QAAQ,CAAC,GACrDU,IAAM,KAAK,IAAIV,EAAc,UAAU,KAAK,QAAQ,CAAC,GACrDqJ,IAAeJ,IAAexI,IAAMyI,IAAexI,GACnD4I,IAAeL,IAAevI,IAAMwI,IAAezI,GAGnD8I,IAAe,KAAK,IAAIF,GACxBG,IAAe,KAAK,IAAIF;AAE9B,aAAO;AAAA,QACL,GAAGC,IAAe,KAAK,cAAc,QAAQ;AAAA,QAC7C,GAAGC,IAAe,KAAK,cAAc,SAAS;AAAA,QAC9C,OAAO,KAAK,cAAc;AAAA,QAC1B,QAAQ,KAAK,cAAc;AAAA,MAAA;AAAA,IAE/B,OAAO;AAGL,YAAMT,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc;AAElE,aAAO;AAAA,QACL,GAAG,KAAK,IAAID,IAAY;AAAA,QACxB,GAAG,KAAK,IAAIC,IAAa;AAAA,QACzB,OAAOD;AAAA,QACP,QAAQC;AAAA,MAAA;AAAA,IAEZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,uBAAoC;AAGlC,UAAMD,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc;AAElE,WAAO;AAAA,MACL,GAAG,KAAK,IAAID,IAAY;AAAA,MACxB,GAAG,KAAK,IAAIC,IAAa;AAAA,MACzB,OAAOD;AAAA,MACP,QAAQC;AAAA,IAAA;AAAA,EAEZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAA2B;AACzB,QAAI,KAAK,YAAY;AAGnB,YAAMD,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc;AAGlE,UAAIC,IACF,KAAK,cAAc,QAAQ,KAAK,KAAK,cAAc,QAAQ,KAAK,cAAc,QAAQF,IAAY,IAChGG,IACF,KAAK,cAAc,SAAS,KAAK,KAAK,cAAc,QAAQ,KAAK,cAAc,SAASF,IAAa;AAIvG,YAAMG,IAAQ,KAAK,cAAc,iBAAiB,KAAK,GACjDC,IAAQ,KAAK,cAAc,eAAe,KAAK;AACrD,MAAAH,KAAgBE,GAChBD,KAAgBE;AAGhB,YAAM3I,IAAM,KAAK,IAAIT,EAAc,UAAU,KAAK,QAAQ,CAAC,GACrDU,IAAM,KAAK,IAAIV,EAAc,UAAU,KAAK,QAAQ,CAAC,GACrDqJ,IAAeJ,IAAexI,IAAMyI,IAAexI,GACnD4I,IAAeL,IAAevI,IAAMwI,IAAezI;AAGzD,aAAO;AAAA,QACL,GAAG,KAAK,IAAI4I;AAAA,QACZ,GAAG,KAAK,IAAIC;AAAA,MAAA;AAAA,IAEhB;AAEE,aAAO;AAAA,QACL,GAAG,KAAK;AAAA,QACR,GAAG,KAAK;AAAA,MAAA;AAAA,EAGd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQhI,GAAgBC,GAAyB;AAE/C,UAAMkI,IAAQ,KAAK,aAAanI,GAAQC,CAAM;AAE9C,QAAI,KAAK,YAAY;AAGnB,YAAMwH,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc,YAE5DC,IACJ,KAAK,cAAc,QAAQ,KAAK,KAAK,cAAc,QAAQ,KAAK,cAAc,QAAQF,IAAY,IAC9FG,IACJ,KAAK,cAAc,SAAS,KAAK,KAAK,cAAc,QAAQ,KAAK,cAAc,SAASF,IAAa,IAEjGU,IAAY,KAAK,cAAc,QAAQ,GACvCC,IAAa,KAAK,cAAc,SAAS;AAE/C,aACEF,EAAM,KAAKR,IAAeS,KAC1BD,EAAM,KAAKR,IAAeS,KAC1BD,EAAM,KAAKP,IAAeS,KAC1BF,EAAM,KAAKP,IAAeS;AAAA,IAE9B,OAAO;AAGL,YAAMZ,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc,YAC5DY,IAAgBb,IAAY,GAC5Bc,IAAiBb,IAAa;AAEpC,aACES,EAAM,KAAK,CAACG,KAAiBH,EAAM,KAAKG,KAAiBH,EAAM,KAAK,CAACI,KAAkBJ,EAAM,KAAKI;AAAA,IAEtG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO5I,GAA+B6I,IAAuB,IAAOC,IAAsB,IAAa;AACrG,QAAI,CAAC,KAAK,eAAe,CAAC,KAAK,cAAc;AAE3C,WAAK,kBAAkB9I,CAAG;AAC1B;AAAA,IACF;AAMA,UAAMmG,IAASD;AAAA,MACb,KAAK;AAAA,MACL,KAAK,cAAc;AAAA,IAAA,GAEf6C,IAAc5C,EAAO,OACrB6C,IAAe7C,EAAO;AAE5B,IAAAnG,EAAI,KAAA,GAGA,KAAK,YAAY,UAAa,KAAK,YAAY,MACjDA,EAAI,cAAc,KAAK,UAIzBA,EAAI,UAAU,KAAK,GAAG,KAAK,CAAC,GAE5BA,EAAI,OAAOjB,EAAc,UAAU,KAAK,QAAQ,CAAC;AAGjD,UAAMmJ,IAAQ,KAAK,cAAc,iBAAiB,KAAK,GACjDC,IAAQ,KAAK,cAAc,eAAe,KAAK;AAGrD,QAFAnI,EAAI,MAAMkI,GAAOC,CAAK,GAElB,KAAK,YAAY;AAGnB,YAAML,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc,YAE5DC,IACJ,KAAK,cAAc,QAAQ,KAAK,KAAK,cAAc,QAAQ,KAAK,cAAc,QAAQF,IAAY,IAC9FG,IACJ,KAAK,cAAc,SAAS,KAAK,KAAK,cAAc,QAAQ,KAAK,cAAc,SAASF,IAAa,IAEjGpN,IAAIqN,IAAe,KAAK,cAAc,QAAQ,GAC9C9J,IAAI+J,IAAe,KAAK,cAAc,SAAS,GAG/CgB,IAAe,KAAK,cAAc,gBAAgB;AACxD,UAAIA,IAAe,GAAG;AACpB,QAAAjJ,EAAI,KAAA;AACJ,cAAMkJ,IAAS,KAAK;AAAA,UACjBD,IAAe,MAAO,KAAK,IAAI,KAAK,cAAc,OAAO,KAAK,cAAc,MAAM;AAAA,UACnF,KAAK,cAAc,QAAQ;AAAA,UAC3B,KAAK,cAAc,SAAS;AAAA,QAAA;AAE9B,QAAAjJ,EAAI,UAAA,GACJA,EAAI,UAAUrF,GAAGuD,GAAG,KAAK,cAAc,OAAO,KAAK,cAAc,QAAQgL,CAAM,GAC/ElJ,EAAI,KAAA;AAAA,MACN;AAGA,MAAAA,EAAI;AAAA,QACFmG;AAAA,QACA;AAAA,QACA;AAAA,QACA4C;AAAA,QACAC;AAAA,QACArO;AAAA,QACAuD;AAAA,QACA,KAAK,cAAc;AAAA,QACnB,KAAK,cAAc;AAAA,MAAA,GAGjB+K,IAAe,KACjBjJ,EAAI,QAAA;AAAA,IAER,WAAW,KAAK,SAAS,KAAK,MAAM,SAAS,GAAG;AAI9C,YAAM8H,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc,YAE5DpN,IAAI,CAACmN,IAAY,GACjB5J,IAAI,CAAC6J,IAAa,GAIlBoB,IAAaxO,IAAI,KAAK,cAAc,QAAQ,KAAK,cAAc,OAC/DyO,IAAalL,IAAI,KAAK,cAAc,QAAQ,KAAK,cAAc;AAErE,MAAA8B,EAAI;AAAA,QACFmG;AAAA,QACA;AAAA,QACA;AAAA,QACA4C;AAAA,QACAC;AAAA,QACAG;AAAA,QACAC;AAAA,QACA,KAAK,cAAc;AAAA,QACnB,KAAK,cAAc;AAAA,MAAA;AAAA,IAEvB,OAAO;AAGL,YAAMtB,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc,YAE5DpN,IAAI,CAACmN,IAAY,GACjB5J,IAAI,CAAC6J,IAAa,GAGlBkB,IAAe,KAAK,cAAc,gBAAgB;AACxD,MAAAjJ,EAAI,KAAA;AACJ,YAAMkJ,IAAS,KAAK,IAAKD,IAAe,MAAO,KAAK,IAAInB,GAAWC,CAAU,GAAGD,IAAY,GAAGC,IAAa,CAAC;AAC7G,MAAA/H,EAAI,UAAA,GACJA,EAAI,UAAUrF,GAAGuD,GAAG4J,GAAWC,GAAYmB,CAAM,GACjDlJ,EAAI,KAAA;AAIJ,YAAMmJ,IAAaxO,IAAI,KAAK,cAAc,QAAQ,KAAK,cAAc,OAC/DyO,IAAalL,IAAI,KAAK,cAAc,QAAQ,KAAK,cAAc;AAErE,MAAA8B,EAAI;AAAA,QACFmG;AAAA,QACA;AAAA,QACA;AAAA,QACA4C;AAAA,QACAC;AAAA,QACAG;AAAA,QACAC;AAAA,QACA,KAAK,cAAc;AAAA,QACnB,KAAK,cAAc;AAAA,MAAA,GAGrBpJ,EAAI,QAAA;AAAA,IACN;AAEA,IAAAA,EAAI,QAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkBA,GAAqC;AACrD,IAAAA,EAAI,KAAA,GAGJA,EAAI,UAAU,KAAK,GAAG,KAAK,CAAC,GAE5BA,EAAI,OAAOjB,EAAc,UAAU,KAAK,QAAQ,CAAC;AAGjD,UAAM+I,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc,YAE5DpN,IAAI,CAACmN,IAAY,GACjB5J,IAAI,CAAC6J,IAAa;AAGxB,QAAIsB,GACAC,GACA/E;AAEJ,IAAI,KAAK,mBAAmB,WAC1B8E,IAAU,WACVC,IAAY,WACZ/E,IAAU,0BACD,KAAK,mBAAmB,cACjC8E,IAAU,WACVC,IAAY,QACZ/E,IAAU,iBACD,KAAK,mBAAmB,aACjC8E,IAAU,WACVC,IAAY,QACZ/E,IAAU,iBAGV8E,IAAU,WACVC,IAAY,QACZ/E,IAAU,KAAK,WAAW,eAAe,aAI3CvE,EAAI,YAAYqJ,GAChBrJ,EAAI,SAASrF,GAAGuD,GAAG4J,GAAWC,CAAU,GAGxC/H,EAAI,YAAYsJ,GAChBtJ,EAAI,OAAO,6CACXA,EAAI,YAAY,UAChBA,EAAI,eAAe,UACnBA,EAAI,SAASuE,GAAS,GAAG,CAAC,GAE1BvE,EAAI,QAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA,EAMS,OAAOuJ,GAAsBhM,GAAkBC,GAAmBgJ,GAAwC;AAIjH,QAAIgD,GAAYC;AAChB,IAAI,KAAK,cAEPD,IAAajM,IAAWgJ,EAAmBC,CAAS,EAAE,OACtDiD,IAAcjM,IAAY+I,EAAmBC,CAAS,EAAE,WAIxDgD,IAAajM,KAAYiJ,EAAU,SAAS,IAC5CiD,IAAcjM,KAAagJ,EAAU,UAAU;AAEjD,UAAMkD,KAAiBF,IAAaC,KAAe;AAGnD,QAAIE,IAAkBJ;AACtB,IAAI,KAAK,eACPI,IAAkB,KAAK,oBAAoBJ,GAAQ/C,CAAS;AAI9D,QAAIoD,IAAaF;AACjB,QAAI,KAAK,YAAY;AACnB,YAAMG,IAA8B,KAAK,cAAc,QAAQtD,EAAmBC,CAAS,EAAE;AAG7F,MAAIkD,IAAgBG,MAClBD,IAAa,KAAK;AAAA,QAChBD;AAAA,QACAnD;AAAA,QACAkD;AAAA,QACAG;AAAA,MAAA;AAAA,IAGN;AAGA,SAAK,cAAc,QAAQtD,EAAmBC,CAAS,EAAE,QAAQoD,GACjE,KAAK,cAAc,SAAS,KAAK,cAAc,QAAQ,KAAK,kBAG5D,KAAK,WAAWpD,EAAU;AAM1B,QAAI8B,GAAcC;AAElB,QAAI,KAAK,YAAY;AAKnB,YAAMuB,KACHvD,EAAmBC,CAAS,EAAE,QAAQD,EAAmBC,CAAS,EAAE,YAAY,KAAKD,EAAmBC,CAAS,EAAE,QACpHD,EAAmBC,CAAS,EAAE,QAAQ,GAClCuD,KACHxD,EAAmBC,CAAS,EAAE,QAAQD,EAAmBC,CAAS,EAAE,aAAa,KAAKD,EAAmBC,CAAS,EAAE,SACrHD,EAAmBC,CAAS,EAAE,SAAS,GAGnCwB,IAAe8B,GACf7B,IAAe8B,GAQfC,IADiB5J,EAAU,aAAaoG,CAAS,EACpB,kBAAkBwB,GAAcC,CAAY,GACzEG,IAAe4B,EAAY,IAC3B3B,IAAe2B,EAAY;AAGjC,MAAA1B,IAAe9B,EAAU,IAAI4B,GAC7BG,IAAe/B,EAAU,IAAI6B;AAAA,IAC/B,OAAO;AAGL,YAAMyB,KACHvD,EAAmBC,CAAS,EAAE,QAAQD,EAAmBC,CAAS,EAAE,YAAY,KAAKD,EAAmBC,CAAS,EAAE,QACpHD,EAAmBC,CAAS,EAAE,QAAQ,GAClCuD,KACHxD,EAAmBC,CAAS,EAAE,QAAQD,EAAmBC,CAAS,EAAE,aAAa,KAAKD,EAAmBC,CAAS,EAAE,SACrHD,EAAmBC,CAAS,EAAE,SAAS,GAGnCwB,IAAe8B,GACf7B,IAAe8B,GAIfC,IADiB5J,EAAU,aAAaoG,CAAS,EACpB,kBAAkBwB,GAAcC,CAAY,GACzEG,IAAe4B,EAAY,IAC3B3B,IAAe2B,EAAY;AAGjC,MAAA1B,IAAe9B,EAAU,IAAI4B,GAC7BG,IAAe/B,EAAU,IAAI6B;AAAA,IAC/B;AAKA,QAAI4B,GAAmBC,GAInBC,IAAoB,GACpBC,IAAoB;AAExB,IAAIT,MAAoB,cAEtBQ,IAAoB5D,EAAmBC,CAAS,EAAE,QAAQ,GAC1D4D,IAAoB7D,EAAmBC,CAAS,EAAE,SAAS,KAClDmD,MAAoB,eAE7BQ,IAAoB,CAAC5D,EAAmBC,CAAS,EAAE,QAAQ,GAC3D4D,IAAoB7D,EAAmBC,CAAS,EAAE,SAAS,KAClDmD,MAAoB,iBAE7BQ,IAAoB5D,EAAmBC,CAAS,EAAE,QAAQ,GAC1D4D,IAAoB,CAAC7D,EAAmBC,CAAS,EAAE,SAAS,KACnDmD,MAAoB,kBAE7BQ,IAAoB,CAAC5D,EAAmBC,CAAS,EAAE,QAAQ,GAC3D4D,IAAoB,CAAC7D,EAAmBC,CAAS,EAAE,SAAS,KACnDmD,MAAoB,iBAE7BQ,IAAoB5D,EAAmBC,CAAS,EAAE,QAAQ,GAC1D4D,IAAoB,KACXT,MAAoB,kBAE7BQ,IAAoB,CAAC5D,EAAmBC,CAAS,EAAE,QAAQ,GAC3D4D,IAAoB,KACXT,MAAoB,gBAE7BQ,IAAoB,GACpBC,IAAoB7D,EAAmBC,CAAS,EAAE,SAAS,KAClDmD,MAAoB,oBAE7BQ,IAAoB,GACpBC,IAAoB,CAAC7D,EAAmBC,CAAS,EAAE,SAAS;AAQ9D,UAAM6D,IAD0BjK,EAAU,aAAaoG,CAAS,EACT,kBAAkB2D,GAAmBC,CAAiB,GACvGE,IAA0BD,EAAuB,IACjDE,IAA0BF,EAAuB;AAEvD,IAAAJ,IAAoB3B,IAAegC,GACnCJ,IAAoB3B,IAAegC;AAQnC,QAAIC,IAAuB,GACvBC,IAAuB;AAE3B,IAAId,MAAoB,cAEtBa,IAAuB,KAAK,cAAc,QAAQ,GAClDC,IAAuB,KAAK,cAAc,SAAS,KAC1Cd,MAAoB,eAE7Ba,IAAuB,CAAC,KAAK,cAAc,QAAQ,GACnDC,IAAuB,KAAK,cAAc,SAAS,KAC1Cd,MAAoB,iBAE7Ba,IAAuB,KAAK,cAAc,QAAQ,GAClDC,IAAuB,CAAC,KAAK,cAAc,SAAS,KAC3Cd,MAAoB,kBAE7Ba,IAAuB,CAAC,KAAK,cAAc,QAAQ,GACnDC,IAAuB,CAAC,KAAK,cAAc,SAAS,KAC3Cd,MAAoB,iBAE7Ba,IAAuB,KAAK,cAAc,QAAQ,GAClDC,IAAuB,KACdd,MAAoB,kBAE7Ba,IAAuB,CAAC,KAAK,cAAc,QAAQ,GACnDC,IAAuB,KACdd,MAAoB,gBAE7Ba,IAAuB,GACvBC,IAAuB,KAAK,cAAc,SAAS,KAC1Cd,MAAoB,oBAE7Ba,IAAuB,GACvBC,IAAuB,CAAC,KAAK,cAAc,SAAS;AAMtD,UAAMC,IADe,IAAItK,EAAU,IAAI,EACQ,kBAAkBoK,GAAsBC,CAAoB,GACrGE,IAA6BD,EAA0B,IACvDE,IAA6BF,EAA0B,IAKvDG,IAAkBZ,IAAoBU,GACtCG,IAAkBZ,IAAoBU;AAG5C,QAAI,KAAK,YAAY;AAGnB,YAAMG,IAAsBvE,EAAU,GAChCwE,IAAsBxE,EAAU,GAGhCyE,IAAoB1E,EAAmBC,CAAS,EAAE,YAAYD,EAAmBC,CAAS,EAAE,OAC5F0E,IAAqB3E,EAAmBC,CAAS,EAAE,aAAaD,EAAmBC,CAAS,EAAE;AAGpG,WAAK,IAAIuE,GACT,KAAK,IAAIC;AAGT,YAAMG,IAAmBF,IAAoB,KAAK,cAAc,OAC1DG,IAAoBF,IAAqB,KAAK,cAAc,QAI5D9C,IAAe2C,IAAsBF,GACrCxC,IAAe2C,IAAsBF,GAIrCO,KADoB,IAAIjL,EAAU,IAAI,EACN,kBAAkBgI,GAAcC,CAAY,GAC5EL,KAAeqD,GAAY,IAC3BpD,KAAeoD,GAAY,IAG3BC,KAAmB,KAAK,cAAc,QAAQ,IAAItD,IAClDuD,KAAmB,KAAK,cAAc,SAAS,IAAItD,IAGnDuD,KAAWF,KAAmB,KAAK,cAAc,QAAQH,IAAmB,GAC5EM,KAAWF,KAAmB,KAAK,cAAc,SAASH,IAAoB;AAIpF,WAAK,cAAc,QAAQI,IAC3B,KAAK,cAAc,QAAQC,IAC3B,KAAK,cAAc,YAAYN,GAC/B,KAAK,cAAc,aAAaC;AAAA,IAClC,OAAO;AAKL,YAAMM,KACH,KAAK,cAAc,QAAQ,KAAK,cAAc,YAAY,KAAK,KAAK,cAAc,QACnF,KAAK,cAAc,QAAQ,GACvBC,KACH,KAAK,cAAc,QAAQ,KAAK,cAAc,aAAa,KAAK,KAAK,cAAc,SACpF,KAAK,cAAc,SAAS,GAGxBC,IAAkBF,GAClBG,IAAkBF,GAIlBG,IADe,IAAI1L,EAAU,IAAI,EACH,kBAAkBwL,GAAiBC,CAAe,GAChFE,IAAkBD,EAAe,IACjCE,IAAkBF,EAAe;AAGvC,WAAK,IAAIjB,IAAkBkB,GAC3B,KAAK,IAAIjB,IAAkBkB;AAAA,IAC7B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoC;AAClC,WAAO,CAAC,YAAY,aAAa,eAAe,cAAc;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAsB;AACpB,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAqB;AACnB,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAuC;AACrC,QAAI,CAAC,KAAK,WAAY,QAAO;AAI7B,UAAMlE,IAAY,KAAK,cAAc,QAAQ,KAAK,cAAc,WAC1DC,IAAa,KAAK,cAAc,SAAS,KAAK,cAAc;AAGlE,WAAO;AAAA,MACL,GAAG,CAACD,IAAY;AAAA,MAChB,GAAG,CAACC,IAAa;AAAA,MACjB,OAAOD;AAAA,MACP,QAAQC;AAAA,IAAA;AAAA,EAEZ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,yBAAyC;AACvC,UAAMkE,IAAU,KAAK,iBAAA;AACrB,QAAI,CAACA,EAAS,QAAO;AAErB,UAAMzM,IAAM,KAAK,IAAIT,EAAc,UAAU,KAAK,QAAQ,CAAC,GACrDU,IAAM,KAAK,IAAIV,EAAc,UAAU,KAAK,QAAQ,CAAC;AAW3D,WARgB;AAAA,MACd,EAAE,GAAGkN,EAAQ,GAAG,GAAGA,EAAQ,EAAA;AAAA;AAAA,MAC3B,EAAE,GAAGA,EAAQ,IAAIA,EAAQ,OAAO,GAAGA,EAAQ,EAAA;AAAA;AAAA,MAC3C,EAAE,GAAGA,EAAQ,GAAG,GAAGA,EAAQ,IAAIA,EAAQ,OAAA;AAAA;AAAA,MACvC,EAAE,GAAGA,EAAQ,IAAIA,EAAQ,OAAO,GAAGA,EAAQ,IAAIA,EAAQ,OAAA;AAAA;AAAA,IAAO,EAIjD,IAAI,CAACC,OAAY;AAAA,MAC9B,GAAG,KAAK,IAAIA,EAAO,IAAI1M,IAAM0M,EAAO,IAAIzM;AAAA,MACxC,GAAG,KAAK,IAAIyM,EAAO,IAAIzM,IAAMyM,EAAO,IAAI1M;AAAA,IAAA,EACxC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAaa,GAAgBC,GAAuB;AAElD,UAAMZ,IAAKW,IAAS,KAAK,GACnBV,IAAKW,IAAS,KAAK,GAGnBd,IAAM,KAAK,IAAIT,EAAc,iBAAiB,KAAK,QAAQ,CAAC,GAC5DU,IAAM,KAAK,IAAIV,EAAc,iBAAiB,KAAK,QAAQ,CAAC;AAElE,QAAIa,IAASF,IAAKF,IAAMG,IAAKF,GACzBI,IAASH,IAAKD,IAAME,IAAKH;AAI7B,UAAM0I,IAAQ,KAAK,cAAc,iBAAiB,KAAK,GACjDC,IAAQ,KAAK,cAAc,eAAe,KAAK;AACrD,WAAAvI,KAAUsI,GACVrI,KAAUsI,GAEH;AAAA,MACL,GAAGvI;AAAA,MACH,GAAGC;AAAA,IAAA;AAAA,EAEP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAaD,GAAgBC,GAAuB;AAElD,UAAMqI,IAAQ,KAAK,cAAc,iBAAiB,KAAK,GACjDC,IAAQ,KAAK,cAAc,eAAe,KAAK;AACrD,QAAIgE,IAAWvM,IAASsI,GACpBkE,IAAWvM,IAASsI;AAGxB,UAAM3I,IAAM,KAAK,IAAIT,EAAc,UAAU,KAAK,QAAQ,CAAC,GACrDU,IAAM,KAAK,IAAIV,EAAc,UAAU,KAAK,QAAQ,CAAC,GAErDsN,IAAWF,IAAW3M,IAAM4M,IAAW3M,GACvC6M,IAAWH,IAAW1M,IAAM2M,IAAW5M;AAG7C,WAAO;AAAA,MACL,GAAG,KAAK,IAAI6M;AAAA,MACZ,GAAG,KAAK,IAAIC;AAAA,IAAA;AAAA,EAEhB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,kBACEjM,GACAC,GACAiM,IAAe,GACqE;AACpF,QAAI,CAAC,KAAK,WAAY,QAAO;AAE7B,UAAMN,IAAU,KAAK,iBAAA;AACrB,QAAI,CAACA,EAAS,QAAO;AAGrB,UAAMzD,IAAQ,KAAK,aAAanI,GAAQC,CAAM,GAIxCkM,IAAW,IAAID,GAGfE,IAA2BxJ,KAA2BuJ,GACtDE,IAAqBxJ,KAAqBsJ,GAC1CG,IAAoBxJ,KAAwBqJ,GAI5CI,IAAU;AAAA,MACd,EAAE,GAAGX,EAAQ,GAAG,GAAGA,EAAQ,GAAG,QAAQ,WAAA;AAAA,MACtC,EAAE,GAAGA,EAAQ,IAAIA,EAAQ,OAAO,GAAGA,EAAQ,GAAG,QAAQ,YAAA;AAAA,MACtD,EAAE,GAAGA,EAAQ,GAAG,GAAGA,EAAQ,IAAIA,EAAQ,QAAQ,QAAQ,cAAA;AAAA,MACvD,EAAE,GAAGA,EAAQ,IAAIA,EAAQ,OAAO,GAAGA,EAAQ,IAAIA,EAAQ,QAAQ,QAAQ,eAAA;AAAA,IAAe;AAGxF,eAAWC,KAAUU,GAAS;AAC5B,YAAMlN,IAAK8I,EAAM,IAAI0D,EAAO,GACtBvM,IAAK6I,EAAM,IAAI0D,EAAO;AAI5B,UAHiB,KAAK,KAAKxM,IAAKA,IAAKC,IAAKA,CAAE,KAG5B8M,GAA0B;AACxC,cAAMI,IAAc,KAAK,aAAaX,EAAO,GAAGA,EAAO,CAAC;AACxD,eAAO,EAAE,MAAM,UAAU,QAAQA,EAAO,QAAQ,QAAQW,EAAY,GAAG,QAAQA,EAAY,EAAA;AAAA,MAC7F;AAAA,IACF;AAGA,UAAMC,IAAQ;AAAA,MACZ,EAAE,GAAGb,EAAQ,IAAIA,EAAQ,QAAQ,GAAG,GAAGA,EAAQ,GAAG,QAAQ,OAAO,aAAa,aAAA;AAAA,MAC9E,EAAE,GAAGA,EAAQ,IAAIA,EAAQ,QAAQ,GAAG,GAAGA,EAAQ,IAAIA,EAAQ,QAAQ,QAAQ,UAAU,aAAa,aAAA;AAAA,MAClG,EAAE,GAAGA,EAAQ,GAAG,GAAGA,EAAQ,IAAIA,EAAQ,SAAS,GAAG,QAAQ,QAAQ,aAAa,WAAA;AAAA,MAChF,EAAE,GAAGA,EAAQ,IAAIA,EAAQ,OAAO,GAAGA,EAAQ,IAAIA,EAAQ,SAAS,GAAG,QAAQ,SAAS,aAAa,WAAA;AAAA,IAAW;AAG9G,eAAWc,KAAQD,GAAO;AACxB,YAAMpN,IAAK8I,EAAM,IAAIuE,EAAK,GACpBpN,IAAK6I,EAAM,IAAIuE,EAAK;AAI1B,UAAItE,GAAWC;AAWf,UAVIqE,EAAK,gBAAgB,gBAEvBtE,IAAYiE,IAAqB,GACjChE,IAAaiE,IAAoB,MAGjClE,IAAYkE,IAAoB,GAChCjE,IAAagE,IAAqB,IAGhC,KAAK,IAAIhN,CAAE,KAAK+I,KAAa,KAAK,IAAI9I,CAAE,KAAK+I,GAAY;AAE3D,cAAMsE,IAAY,KAAK,aAAaD,EAAK,GAAGA,EAAK,CAAC;AAClD,eAAO,EAAE,MAAM,QAAQ,QAAQA,EAAK,QAAQ,QAAQC,EAAU,GAAG,QAAQA,EAAU,EAAA;AAAA,MACrF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe3M,GAAgBC,GAAyB;AACtD,QAAI,CAAC,KAAK,WAAY,QAAO;AAE7B,UAAM2L,IAAU,KAAK,iBAAA;AACrB,QAAI,CAACA,EAAS,QAAO;AAGrB,UAAMzD,IAAQ,KAAK,aAAanI,GAAQC,CAAM;AAE9C,WACEkI,EAAM,KAAKyD,EAAQ,KACnBzD,EAAM,KAAKyD,EAAQ,IAAIA,EAAQ,SAC/BzD,EAAM,KAAKyD,EAAQ,KACnBzD,EAAM,KAAKyD,EAAQ,IAAIA,EAAQ;AAAA,EAEnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,WACEgB,GACAC,GACApF,GACAC,GACAoF,IAA0B,IACpB;AAKN,QAAIC,IAAe,KAAK,IAAI,KAAS,KAAK,IAAI,GAAGtF,CAAS,CAAC,GACvDuF,IAAgB,KAAK,IAAI,KAAS,KAAK,IAAI,GAAGtF,CAAU,CAAC,GAIzDuF,IAAW,KAAK,IAAI,GAAG,KAAK,IAAI,IAAIF,GAAcH,CAAK,CAAC,GACxDM,IAAW,KAAK,IAAI,GAAG,KAAK,IAAI,IAAIF,GAAeH,CAAK,CAAC;AAG7D,IAAII,IAAWF,IAAe,MAC5BA,IAAe,IAAIE,IAEjBC,IAAWF,IAAgB,MAC7BA,IAAgB,IAAIE;AAItB,QAAInF,IAAe,GACfC,IAAe;AAEnB,QAAI8E,GAAgB;AAElB,YAAMK,IACJ,KAAK,cAAc,QAAQ,KAAK,cAAc,QAC7C,KAAK,cAAc,YAAY,KAAK,cAAc,QAAS,GACxDC,IACJ,KAAK,cAAc,QAAQ,KAAK,cAAc,SAC7C,KAAK,cAAc,aAAa,KAAK,cAAc,SAAU,GAG1DC,IAAiBJ,IAAW,KAAK,cAAc,QAASF,IAAe,KAAK,cAAc,QAAS,GACnGO,IAAiBJ,IAAW,KAAK,cAAc,SAAUF,IAAgB,KAAK,cAAc,SAAU,GAGtGrF,IAAe0F,IAAiBF,GAChCvF,IAAe0F,IAAiBF,GAGhCjO,IAAM,KAAK,IAAIT,EAAc,UAAU,KAAK,QAAQ,CAAC,GACrDU,IAAM,KAAK,IAAIV,EAAc,UAAU,KAAK,QAAQ,CAAC;AAC3D,MAAAqJ,IAAeJ,IAAexI,IAAMyI,IAAexI,GACnD4I,IAAeL,IAAevI,IAAMwI,IAAezI;AAAA,IACrD;AAGA,SAAK,cAAc,QAAQ8N,GAC3B,KAAK,cAAc,QAAQC,GAC3B,KAAK,cAAc,YAAYH,GAC/B,KAAK,cAAc,aAAaC,GAG5BF,MACF,KAAK,KAAK/E,GACV,KAAK,KAAKC;AAAA,EAEd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,+BACNkB,GACAqE,GACAC,GAC0B;AAC1B,UAAMC,IAAQF,IAAQ,GAChBG,IAAQF,IAAS;AAiBvB,WAfwE;AAAA,MACtE,YAAY,EAAE,GAAGC,GAAO,GAAGC,EAAA;AAAA;AAAA,MAC3B,aAAa,EAAE,GAAG,CAACD,GAAO,GAAGC,EAAA;AAAA;AAAA,MAC7B,eAAe,EAAE,GAAGD,GAAO,GAAG,CAACC,EAAA;AAAA;AAAA,MAC/B,gBAAgB,EAAE,GAAG,CAACD,GAAO,GAAG,CAACC,EAAA;AAAA;AAAA,MACjC,KAAK,EAAE,GAAG,GAAG,GAAGA,EAAA;AAAA;AAAA,MAChB,QAAQ,EAAE,GAAG,GAAG,GAAG,CAACA,EAAA;AAAA;AAAA,MACpB,MAAM,EAAE,GAAGD,GAAO,GAAG,EAAA;AAAA;AAAA,MACrB,OAAO,EAAE,GAAG,CAACA,GAAO,GAAG,EAAA;AAAA;AAAA,MACvB,cAAc,EAAE,GAAG,GAAG,GAAGC,EAAA;AAAA;AAAA,MACzB,iBAAiB,EAAE,GAAG,GAAG,GAAG,CAACA,EAAA;AAAA;AAAA,MAC7B,eAAe,EAAE,GAAGD,GAAO,GAAG,EAAA;AAAA;AAAA,MAC9B,gBAAgB,EAAE,GAAG,CAACA,GAAO,GAAG,EAAA;AAAA;AAAA,IAAE,EAGbvE,CAAM;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mCAAmC/C,GAAyD;AAClG,UAAMsB,IAAYvB,EAAmBC,CAAS,EAAE,QAAQD,EAAmBC,CAAS,EAAE,WAChFuB,IAAaxB,EAAmBC,CAAS,EAAE,SAASD,EAAmBC,CAAS,EAAE,YAOlFwB,IACJzB,EAAmBC,CAAS,EAAE,QAAQ,KACrCD,EAAmBC,CAAS,EAAE,QAAQD,EAAmBC,CAAS,EAAE,QAAQsB,IAAY,IACrFG,IACJ1B,EAAmBC,CAAS,EAAE,SAAS,KACtCD,EAAmBC,CAAS,EAAE,QAAQD,EAAmBC,CAAS,EAAE,SAASuB,IAAa,IAIvFiC,IADY,IAAI5J,EAAUoG,CAAS,EACX,kBAAkBwB,GAAcC,CAAY;AAG1E,WAAO;AAAA,MACL,GAAGzB,EAAU,IAAIwD,EAAY;AAAA,MAC7B,GAAGxD,EAAU,IAAIwD,EAAY;AAAA,IAAA;AAAA,EAEjC;AAAA;AAAA;AAAA;AAAA,EAKQ,4BAA4BT,GAAsB/C,GAAyD;AAEjH,UAAMwH,IAAmB,KAAK;AAAA,MAC5BzE;AAAA,MACAhD,EAAmBC,CAAS,EAAE;AAAA,MAC9BD,EAAmBC,CAAS,EAAE;AAAA,IAAA,GAI1ByH,IAAmB,KAAK,mCAAmCzH,CAAS,GAIpE6D,IADiB,IAAIjK,EAAUoG,CAAS,EACA,kBAAkBwH,EAAiB,GAAGA,EAAiB,CAAC;AAEtG,WAAO;AAAA,MACL,GAAGC,EAAiB,IAAI5D,EAAuB;AAAA,MAC/C,GAAG4D,EAAiB,IAAI5D,EAAuB;AAAA,IAAA;AAAA,EAEnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gCACN6D,GACA3E,GACA/C,GAC0B;AAE1B,UAAM2H,IAAmB,KAAK,4BAA4B5E,GAAQ/C,CAAS,GAGrE4H,IAAgB7H,EAAmBC,CAAS,EAAE,QAAQ0H,GACtDG,IAAiBD,IAAgB,KAAK,kBAGtCJ,IAAmB,KAAK,+BAA+BzE,GAAQ6E,GAAeC,CAAc,GAS5FhE,IANY,IAAIjK,EAAU;AAAA,MAC9B,GAAG;AAAA;AAAA,MACH,GAAG;AAAA,MACH,UAAUoG,EAAU;AAAA,IAAA,CACrB,EAEwC,kBAAkBwH,EAAiB,GAAGA,EAAiB,CAAC;AAGjG,WAAO;AAAA,MACL,GAAGG,EAAiB,IAAI9D,EAAuB;AAAA,MAC/C,GAAG8D,EAAiB,IAAI9D,EAAuB;AAAA,IAAA;AAAA,EAEnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,0BACN6D,GACA3E,GACA/C,GACyE;AAEzE,UAAM4H,IAAgB7H,EAAmBC,CAAS,EAAE,QAAQ0H,GACtDG,IAAiBD,IAAgB,KAAK,kBAGtCE,IAAiB/H,EAAmBC,CAAS,EAAE,YAAYD,EAAmBC,CAAS,EAAE,OACzF+H,IAAkBhI,EAAmBC,CAAS,EAAE,aAAaD,EAAmBC,CAAS,EAAE,QAG3FgI,IAAgBF,IAAiBF,GACjCK,IAAiBF,IAAkBF,GAInCK,IAAiB,KAAK,gCAAgCR,GAAO3E,GAAQ/C,CAAS,GAG9EmI,IAAkB;AAAA,MACtB,GAAGnI,EAAU;AAAA;AAAA,MACb,GAAGA,EAAU;AAAA,IAAA,GAIT4B,IAAeuG,EAAgB,IAAID,EAAe,GAClDrG,IAAesG,EAAgB,IAAID,EAAe,GASlDrD,IANY,IAAIjL,EAAU;AAAA,MAC9B,GAAGsO,EAAe;AAAA,MAClB,GAAGA,EAAe;AAAA,MAClB,UAAUlI,EAAU;AAAA,IAAA,CACrB,EAE6B,kBAAkB4B,GAAcC,CAAY,GAGpEuG,IAAmBR,IAAgB,IAAI/C,EAAY,IACnDwD,IAAmBR,IAAiB,IAAIhD,EAAY,IAGpD4B,IAAQ2B,IAAmBR,IAAgBI,IAAgB,GAC3DtB,IAAQ2B,IAAmBR,IAAiBI,IAAiB;AAEnE,WAAO;AAAA,MACL,OAAAxB;AAAA,MACA,OAAAC;AAAA,MACA,WAAWsB;AAAA,MACX,YAAYC;AAAA,IAAA;AAAA,EAEhB;AAAA;AAAA;AAAA;AAAA,EAKQ,0BAA0BP,GAAe3E,GAAsB/C,GAAwC;AAI7G,UAAMsI,IAAY,KAAK,0BAA0BZ,GAAO3E,GAAQ/C,CAAS;AAGzE,QAAIsI,EAAU,YAAY,IAAM,QAAWA,EAAU,aAAa,IAAM;AACtE,aAAO;AAIT,UAAMC,IAAWD,EAAU,OACrBE,IAAYF,EAAU,QAAQA,EAAU,WACxCG,IAAUH,EAAU,OACpBI,IAAaJ,EAAU,QAAQA,EAAU;AAM/C,WAFEC,KAAY,SAAYE,KAAW,SAAYD,KAAa,IAAM,QAAWE,KAAc,IAAM;AAAA,EAGrG;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,+BACN3F,GACA/C,GACAkD,GACAyF,GACQ;AAKR,QAAI,KAAK,0BAA0BzF,GAAeH,GAAQ/C,CAAS;AACjE,aAAOkD;AAKT,QAAI0F,IAAK1F,GACL2F,IAAKF;AAET,aAASpT,IAAI,GAAGA,IAAI,IAAgBA,KAAK;AACvC,YAAMuT,KAAaF,IAAKC,KAAM;AAW9B,UATI,KAAK,0BAA0BC,GAAW/F,GAAQ/C,CAAS,IAE7D6I,IAAKC,IAGLF,IAAKE,GAIH,KAAK,IAAID,IAAKD,CAAE,IAAI;AACtB;AAAA,IAEJ;AAEA,WAAOC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB9F,GAAsB/C,GAA6C;AAC7F,QAAImD,IAAkBJ;AACtB,UAAMrB,IAAQ3B,EAAmBC,CAAS,EAAE,gBACtC2B,IAAQ5B,EAAmBC,CAAS,EAAE;AAE5C,WAAI0B,MACEyB,EAAgB,SAAS,MAAM,IACjCA,IAAkBA,EAAgB,QAAQ,QAAQ,OAAO,IAChDA,EAAgB,SAAS,OAAO,MACzCA,IAAkBA,EAAgB,QAAQ,SAAS,MAAM,KAIzDxB,MACEwB,EAAgB,SAAS,KAAK,IAChCA,IAAkBA,EAAgB,QAAQ,OAAO,QAAQ,IAChDA,EAAgB,SAAS,QAAQ,MAC1CA,IAAkBA,EAAgB,QAAQ,UAAU,KAAK,KAItDA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,QAAsB;AACpB,UAAM4F,IAAO,KAAK,OAAA,GAGZ,EAAE,UAAAC,GAAU,GAAGC,EAAA,IAAqBF,GAEpCG,IAAS,IAAIjJ,GAAagJ,CAAgB;AAGhD,WAAAC,EAAO,WAAW,KAAK,UACvBA,EAAO,eAAe,KAAK,cAC3BA,EAAO,cAAc,KAAK,aAC1BA,EAAO,mBAAmB,KAAK,kBAC/BA,EAAO,aAAa,KAAK,YACzBA,EAAO,iBAAiB,KAAK,gBAC7BA,EAAO,QAAQ,KAAK,OACpBA,EAAO,iBAAiB,KAAK,gBAC7BA,EAAO,iBAAiB,KAAK,gBAC7BA,EAAO,qBAAqB,KAAK,oBAE1BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAqG;AAEnG,WAAO;AAAA,MACL,GAFe,MAAM,OAAA;AAAA,MAGrB,IAAI,KAAK;AAAA;AAAA,MACT,MAAM;AAAA,MACN,eAAe;AAAA,MACf,GAAG,KAAK;AAAA;AAAA,MACR,GAAG,KAAK;AAAA;AAAA,MACR,UAAU,KAAK;AAAA;AAAA,MACf,UAAU,KAAK;AAAA,MACf,kBAAkB,KAAK;AAAA,MACvB,oBAAoB,KAAK;AAAA;AAAA,MACzB,eAAe;AAAA,QACb,GAAG,KAAK;AAAA;AAAA;AAAA,QAGR,GAAI,KAAK,cAAc,cAAc,EAAE,aAAa,EAAE,GAAG,KAAK,cAAc,YAAA,MAAkB,CAAA;AAAA,MAAC;AAAA,IACjG;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAgB;AAEd,SAAK,iBAAA,GAGL,KAAK,kBAAA,GAGL,KAAK,iBAAA,GAGL,KAAK,eAAe,MACpB,KAAK,iBAAiB,MACtB,KAAK,iBAAiB;AAAA,EACxB;AACF;AC9oDA,MAAMC,yBAA4B,IAAA;AAClC,IAAIC,KAAoD;AAExD,SAASC,GAAeC,GAAqB;AAC3C,SAAO,IAAI;AAAA,IACT,8CAA8CA,CAAI;AAAA,EAAA;AAKtD;AAEO,SAASC,GAA6BC,GAAcC,GAAqC;AAC9F,EAAAN,GAAsB,IAAIK,GAAMC,CAAO;AACzC;AAEO,SAASC,GAAwBF,GAAoC;AAC1E,QAAMC,IAAUN,GAAsB,IAAIK,CAAI;AAC9C,MAAI,CAACC,EAAS,OAAMJ,GAAe,mBAAmBG,CAAI,GAAG;AAC7D,SAAOC;AACT;AAEO,SAASE,GAA6BF,GAAqC;AAChF,EAAAL,KAAuBK;AACzB;AAEO,SAASG,KAAgD;AAC9D,MAAI,CAACR,GAAsB,OAAMC,GAAe,gBAAgB;AAChE,SAAOD;AACT;ACwRO,MAAMS,KAAqD;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAuRO,MAAMC,EAAS;AAAA,EAGpB,YAAYC,IAAoB,IAAI;AAGlC,SAAK,QACHA,EAAM,SAAS,IACXA,EAAM,IAAI,CAACC,OAAO,EAAE,GAAGA,GAAG,OAAO,EAAE,GAAGA,EAAE,MAAA,EAAM,EAAI,IAClD,CAAC,EAAE,MAAM,IAAI,OAAO,CAAA,GAAI,GAC9B,KAAK,UAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,cAAcC,GAAcC,IAAwB,IAAc;AACvE,WAAO,IAAIJ,EAAS,CAAC,EAAE,MAAAG,GAAM,OAAAC,EAAA,CAAO,CAAC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAkB;AAChB,WAAO,KAAK,MAAM,IAAI,CAACF,MAAMA,EAAE,IAAI,EAAE,KAAK,EAAE;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAoB;AAClB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAWG,GAAmC;AAE5C,QAAI,KAAK,MAAM,WAAW,UAAU,CAAA;AAGpC,QAAIA,IAAY,EAAG,QAAO,KAAK,MAAM,CAAC,EAAE;AAExC,QAAIC,IAAe;AACnB,eAAWC,KAAQ,KAAK,OAAO;AAC7B,UAAIF,IAAYC,IAAeC,EAAK,KAAK;AACvC,eAAOA,EAAK;AAEd,MAAAD,KAAgBC,EAAK,KAAK;AAAA,IAC5B;AAIA,WAAO,KAAK,MAAM,KAAK,MAAM,SAAS,CAAC,EAAE;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAWC,GAAeC,GAAaL,GAA6B;AAClE,QAAII,KAASC,KAAOD,IAAQ,KAAKC,IAAM,KAAK;AAC1C;AAGF,UAAMC,IAAuB,CAAA;AAC7B,QAAIJ,IAAe;AAEnB,eAAWC,KAAQ,KAAK,OAAO;AAC7B,YAAMI,IAAYL,GACZM,IAAUN,IAAeC,EAAK,KAAK;AAGzC,UAAIK,KAAWJ,GAAO;AACpB,QAAAE,EAAS,KAAKH,CAAI,GAClBD,IAAeM;AACf;AAAA,MACF;AAGA,UAAID,KAAaF,GAAK;AACpB,QAAAC,EAAS,KAAKH,CAAI,GAClBD,IAAeM;AACf;AAAA,MACF;AAGA,YAAMC,IAAe,KAAK,IAAIL,GAAOG,CAAS,GACxCG,IAAa,KAAK,IAAIL,GAAKG,CAAO;AAGxC,MAAIC,IAAeF,KACjBD,EAAS,KAAK;AAAA,QACZ,MAAMH,EAAK,KAAK,UAAU,GAAGM,IAAeF,CAAS;AAAA,QACrD,OAAOJ,EAAK;AAAA,MAAA,CACb,GAIHG,EAAS,KAAK;AAAA,QACZ,MAAMH,EAAK,KAAK,UAAUM,IAAeF,GAAWG,IAAaH,CAAS;AAAA,QAC1E,OAAO,EAAE,GAAGJ,EAAK,OAAO,GAAGH,EAAA;AAAA,MAAM,CAClC,GAGGU,IAAaF,KACfF,EAAS,KAAK;AAAA,QACZ,MAAMH,EAAK,KAAK,UAAUO,IAAaH,CAAS;AAAA,QAChD,OAAOJ,EAAK;AAAA,MAAA,CACb,GAGHD,IAAeM;AAAA,IACjB;AAEA,SAAK,QAAQF,GACb,KAAK,UAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAOK,GAAeZ,GAAcC,GAA6B;AAC/D,QAAID,EAAK,WAAW,EAAG;AAEvB,UAAMO,IAAuB,CAAA;AAC7B,QAAIJ,IAAe;AAEnB,eAAWC,KAAQ,KAAK,OAAO;AAC7B,YAAMI,IAAYL,GACZM,IAAUN,IAAeC,EAAK,KAAK;AAQzC,UALIQ,KAASJ,KAAaD,EAAS,WAAW,KAC5CA,EAAS,KAAK,EAAE,MAAAP,GAAM,OAAAC,EAAA,CAAO,GAI3BW,IAAQJ,KAAaI,KAASH,GAAS;AACzC,cAAMI,IAASD,IAAQJ;AAEvB,QAAAD,EAAS,KAAK;AAAA,UACZ,MAAMH,EAAK,KAAK,UAAU,GAAGS,CAAM;AAAA,UACnC,OAAOT,EAAK;AAAA,QAAA,CACb,GACDG,EAAS,KAAK,EAAE,MAAAP,GAAM,OAAAC,EAAA,CAAO,GAC7BM,EAAS,KAAK;AAAA,UACZ,MAAMH,EAAK,KAAK,UAAUS,CAAM;AAAA,UAChC,OAAOT,EAAK;AAAA,QAAA,CACb,GACDD,IAAeM;AACf;AAAA,MACF;AAEA,MAAAF,EAAS,KAAKH,CAAI,GAClBD,IAAeM;AAAA,IACjB;AAGA,IAAIG,KAAS,KAAK,UAAA,KAAe,CAACL,EAAS,KAAK,CAACR,MAAMA,EAAE,SAASC,CAAI,KACpEO,EAAS,KAAK,EAAE,MAAAP,GAAM,OAAAC,EAAA,CAAO,GAG/B,KAAK,QAAQM,GACb,KAAK,UAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAOF,GAAeC,GAAmB;AAEvC,UAAMQ,IAAS,KAAK,UAAA;AAKpB,QAJAT,IAAQ,KAAK,IAAI,GAAG,KAAK,IAAIA,GAAOS,CAAM,CAAC,GAC3CR,IAAM,KAAK,IAAI,GAAG,KAAK,IAAIA,GAAKQ,CAAM,CAAC,GAGnCT,KAASC;AACX;AAGF,UAAMC,IAAuB,CAAA;AAC7B,QAAIJ,IAAe;AAEnB,eAAWC,KAAQ,KAAK,OAAO;AAC7B,YAAMI,IAAYL,GACZM,IAAUN,IAAeC,EAAK,KAAK;AAGzC,UAAIK,KAAWJ,GAAO;AACpB,QAAAE,EAAS,KAAKH,CAAI,GAClBD,IAAeM;AACf;AAAA,MACF;AAGA,UAAID,KAAaF,GAAK;AACpB,QAAAC,EAAS,KAAKH,CAAI,GAClBD,IAAeM;AACf;AAAA,MACF;AAGA,YAAMM,IAAc,KAAK,IAAIV,GAAOG,CAAS,GACvCQ,IAAY,KAAK,IAAIV,GAAKG,CAAO;AAGvC,MAAIM,IAAcP,KAChBD,EAAS,KAAK;AAAA,QACZ,MAAMH,EAAK,KAAK,UAAU,GAAGW,IAAcP,CAAS;AAAA,QACpD,OAAOJ,EAAK;AAAA,MAAA,CACb,GAICY,IAAYP,KACdF,EAAS,KAAK;AAAA,QACZ,MAAMH,EAAK,KAAK,UAAUY,IAAYR,CAAS;AAAA,QAC/C,OAAOJ,EAAK;AAAA,MAAA,CACb,GAGHD,IAAeM;AAAA,IACjB;AAEA,SAAK,QAAQF,EAAS,SAAS,IAAIA,IAAW,CAAC,EAAE,MAAM,IAAI,OAAO,CAAA,EAAC,CAAG,GACtE,KAAK,UAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,YAAkB;AAKxB,QAHA,KAAK,QAAQ,KAAK,MAAM,OAAO,CAACR,MAAMA,EAAE,SAAS,EAAE,GAG/C,KAAK,MAAM,WAAW,GAAG;AAC3B,WAAK,QAAQ,CAAC,EAAE,MAAM,IAAI,OAAO,CAAA,GAAI;AACrC;AAAA,IACF;AAGA,QAAI,KAAK,MAAM,UAAU,EAAG;AAE5B,UAAMkB,IAAqB,CAAA;AAC3B,QAAIC,IAAU,KAAK,MAAM,CAAC;AAE1B,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,YAAMC,IAAO,KAAK,MAAM,CAAC;AAGzB,MAAI,KAAK,YAAYD,EAAQ,OAAOC,EAAK,KAAK,IAE5CD,IAAU;AAAA,QACR,MAAMA,EAAQ,OAAOC,EAAK;AAAA,QAC1B,OAAOD,EAAQ;AAAA,MAAA,KAGjBD,EAAO,KAAKC,CAAO,GACnBA,IAAUC;AAAA,IAEd;AAEA,IAAAF,EAAO,KAAKC,CAAO,GACnB,KAAK,QAAQD;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAYlT,GAAmBuD,GAA4B;AACjE,WACEvD,EAAE,UAAUuD,EAAE,SACdvD,EAAE,eAAeuD,EAAE,cACnBvD,EAAE,aAAauD,EAAE,YACjBvD,EAAE,SAASuD,EAAE,QACbvD,EAAE,WAAWuD,EAAE,UACfvD,EAAE,cAAcuD,EAAE,aAClBvD,EAAE,kBAAkBuD,EAAE,iBACtBvD,EAAE,kBAAkBuD,EAAE;AAAA,EAE1B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAkB;AAChB,WAAO,IAAIuO;AAAA,MACT,KAAK,MAAM,IAAI,CAACO,OAAU;AAAA,QACxB,MAAMA,EAAK;AAAA,QACX,OAAO,EAAE,GAAGA,EAAK,MAAA;AAAA,MAAM,EACvB;AAAA,IAAA;AAAA,EAEN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmBgB,GAAsC;AACvD,eAAWhB,KAAQ,KAAK;AACtB,MAAIA,EAAK,MAAMgB,CAAQ,MAAM,UAC3B,OAAOhB,EAAK,MAAMgB,CAAQ;AAG9B,SAAK,UAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,SAAgC;AAE9B,WAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAACrB,OAAO,EAAE,GAAGA,GAAG,OAAO,EAAE,GAAGA,EAAE,MAAA,EAAM,EAAI,EAAA;AAAA,EACzE;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,SAASjB,GAAuC;AACrD,WAAO,IAAIe,EAASf,EAAK,KAAK;AAAA,EAChC;AACF;AAucO,SAASuC,GAAkBrT,GAAqD;AACrF,SAAOA,EAAK,SAAS;AACvB;AAMO,SAASsT,GAAkBtT,GAAqD;AACrF,SAAOA,EAAK,SAAS;AACvB;AAMO,SAASuT,GAAgBvT,GAAmD;AACjF,SAAOA,EAAK,SAAS;AACvB;AAMO,SAASwT,GAAgBxT,GAAmD;AACjF,SAAOA,EAAK,SAAS;AACvB;AAEO,SAASyT,GAAgBzT,GAAmD;AACjF,SAAOA,EAAK,SAAS;AACvB;AAEO,SAAS0T,GAAiB1T,GAAoD;AACnF,SAAOA,EAAK,SAAS;AACvB;AAEO,SAAS2T,GAAiB3T,GAAoD;AACnF,SAAOA,EAAK,SAAS;AACvB;AAOO,SAAS4T,GAAoB5V,GAA2D;AAC7F,SAAOA,EAAO,kBAAkB,WAAWA,EAAO,kBAAkB;AACtE;AAEO,SAAS6V,GAAqB7V,GAAwD;AAC3F,SAAOA,EAAO,kBAAkB;AAClC;AAEO,SAAS8V,GAAsB9V,GAAyD;AAC7F,SAAOA,EAAO,kBAAkB;AAClC;AAEO,SAAS+V,GAAsB/V,GAAyD;AAC7F,SAAOA,EAAO,kBAAkB;AAClC;AAsBO,SAASgW,GAAqBhW,GAAwD;AAC3F,SAAOA,EAAO,kBAAkB;AAClC;AAEO,SAASiW,GAAqBjW,GAAwD;AAC3F,SAAOA,EAAO,kBAAkB;AAClC;AAEO,SAASkW,GAAoBlW,GAAuD;AACzF,SAAOA,EAAO,kBAAkB;AAClC;AAMO,SAASmW,GAAiBnU,GAAoD;AACnF,SAAOA,EAAK,SAAS;AACvB;AAEO,SAASoU,GAAgBpU,GAAmD;AACjF,SAAOA,EAAK,SAAS;AACvB;AAOO,SAASqU,GAAUrW,GAAmC;AdpgD7D,MAAA0D;AcqgDE,WAAOA,IAAA1D,EAAO,WAAP,gBAAA0D,EAAe,aAAY;AACpC;AAGO,SAAS4S,GAAStW,GAAmC;AAC1D,SAAO,MAAM,QAAQA,EAAO,KAAK,KAAKA,EAAO,MAAM,SAAS;AAC9D;AAGO,SAASuW,GAAWvW,GAAmC;AAC5D,SAAOA,EAAO,cAAc,cAAcA,EAAO,cAAc;AACjE;AAQO,SAASwW,GAAoBC,GAAwE;AAC1G,SAAQ7C,GAA2C,SAAS6C,CAAc;AAC5E;AAQO,SAASC,GAAkB1W,GAAmC;AdliDrE,MAAA0D;AcmiDE,WAAOA,IAAA1D,EAAO,mBAAP,gBAAA0D,EAAuB,aAAY;AAC5C;AChhDO,MAAMiT,UAAoBjU,GAAY;AAAA,EAgC3C,YAAY1C,IAAyC,IAAI;AACvD,UAAMA,CAAM,GAGZ,KAAK,gBAAgBA,EAAO,iBAAiB,UAG7C,KAAK,WAAWA,EAAO,YAAY,IACnC,KAAK,aAAaA,EAAO,cAAc,SACvC,KAAK,QAAQA,EAAO,SAAS,WAC7B,KAAK,OAAOA,EAAO,SAAS,SAAYA,EAAO,OAAO,IACtD,KAAK,SAASA,EAAO,WAAW,SAAYA,EAAO,SAAS,IAC5D,KAAK,YAAYA,EAAO,cAAc,SAAYA,EAAO,YAAY,IACrE,KAAK,gBAAgBA,EAAO,kBAAkB,SAAYA,EAAO,gBAAgB,IACjF,KAAK,YAAYA,EAAO,aAAa,UACrC,KAAK,gBAAgBA,EAAO,eAC5B,KAAK,aAAaA,EAAO,YAGrBA,EAAO,YAELA,EAAO,oBAAoB6T,IAC7B,KAAK,WAAW7T,EAAO,WAGvB,KAAK,WAAW6T,EAAS,SAAS7T,EAAO,QAAQ,GAEnD,KAAK,OAAO,KAAK,SAAS,QAAA,KACjBA,EAAO,SAAS,UAGzB,KAAK,OAAOA,EAAO,MACnB,KAAK,WAAW6T,EAAS,cAAc7T,EAAO,MAAM,EAAE,MAGtD,KAAK,OAAO,QACZ,KAAK,WAAW6T,EAAS,cAAc,QAAQ,CAAA,CAAE,IAInD,KAAK,iBAAiB7T,EAAO,gBAC7B,KAAK,mBAAmBA,EAAO;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAA8B;AAC5B,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAOuD,GAA+B6I,IAAuB,IAAOC,IAAsB,IAAa;AAErG,UAAMuK,IAAgBrT,EAAI;AAC1B,IAAI,KAAK,YAAY,UAAa,KAAK,YAAY,MACjDA,EAAI,cAAc,KAAK,UAGzBkQ,GAAwB,KAAK,aAAa,EAAElQ,GAAK,KAAK,QAAQ,GAG9DA,EAAI,cAAcqT;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAgC;AAE9B,WAAO;AAAA,MACL,GAFe,MAAM,OAAA;AAAA;AAAA,MAIrB,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS,SAAO;AAAA;AAAA,MAEtD,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,YAAY,KAAK;AAAA,MACjB,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,eAAe,KAAK;AAAA;AAAA,MAEpB,GAAI,KAAK,kBAAkB,UAAa,EAAE,eAAe,KAAK,cAAA;AAAA,MAC9D,GAAI,KAAK,eAAe,UAAa,EAAE,YAAY,KAAK,WAAA;AAAA,MACxD,GAAI,KAAK,oBAAoB,EAAE,kBAAkB,EAAE,GAAG,KAAK,mBAAiB;AAAA,MAC5E,GAAI,KAAK,kBAAkB,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAACvR,OAAO,EAAE,GAAGA,EAAA,EAAI,EAAA;AAAA,IAAE;AAAA,EAE5F;AAAA;AAAA;AAAA;AAAA,EAKA,QAAqB;AACnB,UAAMwR,IAAc,KAAK;AACzB,WAAO,IAAIA,EAAY,KAAK,QAAQ;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQC,GAAuB;AAC7B,SAAK,OAAOA,GAEZ,KAAK,WAAWjD,EAAS,cAAciD,GAAS,CAAA,CAAE;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,UAAkB;Af9KpB,QAAApT;Ae+KI,aAAOA,IAAA,KAAK,aAAL,gBAAAA,EAAe,cAAa,KAAK;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAwB;AACtB,WAAK,KAAK,aAER,KAAK,WAAWmQ,EAAS,cAAc,KAAK,MAAM,EAAE,IAE/C,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,YAAYkD,GAA6B;AACvC,SAAK,WAAWA,GAChB,KAAK,OAAOA,EAAY,QAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkC;AAChC,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB,UAAU,KAAK;AAAA,MACf,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,eAAe,KAAK;AAAA,MACpB,GAAI,KAAK,kBAAkB,UAAa,EAAE,eAAe,KAAK,cAAA;AAAA,IAAc;AAAA,EAEhF;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB1C,GAAeC,GAAaL,GAA6B;AACvE,UAAM+C,IAAW,KAAK,YAAA;AACtB,IAAAA,EAAS,WAAW3C,GAAOC,GAAKL,CAAK,GACrC,KAAK,OAAO+C,EAAS,QAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAYC,GAA2B;AACrC,SAAK,WAAW,KAAK,IAAI7P,IAAe,KAAK,IAAIC,IAAe4P,CAAW,CAAC;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAASC,GAAwB;AAC/B,UAAMC,IAAW,KAAK;AAItB,QAHA,KAAK,QAAQD,GAGT,KAAK;AACP,eAAS,IAAI,GAAG,IAAI,KAAK,SAAS,MAAM,QAAQ,KAAK;AACnD,cAAM9C,IAAO,KAAK,SAAS,MAAM,CAAC;AAClC,SAAIA,EAAK,MAAM,UAAU,UAAaA,EAAK,MAAM,UAAU+C,OACzD/C,EAAK,MAAM,QAAQ8C;AAAA,MAEvB;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAOE,GAAuBC,GAAmBC,GAAoBC,GAAgD;AACnH,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMS,wBAA4C;AAEnD,WAAO;AAAA,MACL,GAFe,MAAM,sBAAA;AAAA,MAGrB,UAAU,KAAK;AAAA;AAAA,MAEf,UAAU,KAAK,WAAW,KAAK,SAAS,UAAU;AAAA,IAAA;AAAA,EAEtD;AACF;AClOO,SAASC,GAAiBxD,GAAsB;AACrD,SAAO,MAAM,KAAKA,CAAI,EAAE;AAC1B;AAMO,SAASyD,GAAgBC,GAAkB1D,GAAc2D,GAA2C;AACzG,SAAKA,IACED,IAAWC,IAAgBH,GAAiBxD,CAAI,IAD5B0D;AAE7B;AAqBO,SAASE,GACdrU,GACAyQ,GACA2D,GACU;AACV,QAAME,IAAQ,MAAM,KAAK7D,CAAI,GACvB8D,IAAoB,CAAC,CAAC;AAC5B,MAAIC,IAAS;AACb,WAASzY,IAAI,GAAGA,IAAIuY,EAAM,QAAQvY;AAChC,IAAAyY,KAAUF,EAAMvY,CAAC,GACjBwY,EAAQ,KAAKvU,EAAI,YAAYwU,CAAM,EAAE,QAAQJ,KAAiBrY,IAAI,EAAE;AAEtE,SAAOwY;AACT;AAGA,SAASE,GAAmBzU,GAAuBrF,GAAW+Z,GAA4B;AACxF,UAAQ1U,EAAI,WAAA;AAAA,IACV,KAAK;AACH,aAAOrF,IAAI+Z,IAAa;AAAA,IAC1B,KAAK;AAAA,IACL,KAAK;AACH,aAAO/Z,IAAI+Z;AAAA,IACb;AAEE,aAAO/Z;AAAA,EAAA;AAEb;AAIA,SAASga,GACPC,GACA5U,GACAyQ,GACA9V,GACAuD,GACAkW,GACM;AACN,MAAI,CAACA,KAAiB3D,EAAK,WAAW,GAAG;AAEvC,IAAAzQ,EAAI4U,CAAE,EAAEnE,GAAM9V,GAAGuD,CAAC;AAClB;AAAA,EACF;AAEA,QAAMqW,IAAUF,GAAqBrU,GAAKyQ,GAAM2D,CAAa,GACvDM,IAAaH,EAAQA,EAAQ,SAAS,CAAC,GACvCM,IAASJ,GAAmBzU,GAAKrF,GAAG+Z,CAAU,GAE9CI,IAAgB9U,EAAI;AAC1B,EAAAA,EAAI,YAAY;AAChB,QAAMsU,IAAQ,MAAM,KAAK7D,CAAI;AAC7B,WAAS1U,IAAI,GAAGA,IAAIuY,EAAM,QAAQvY;AAChC,IAAAiE,EAAI4U,CAAE,EAAEN,EAAMvY,CAAC,GAAG8Y,IAASN,EAAQxY,CAAC,GAAGmC,CAAC;AAE1C,EAAA8B,EAAI,YAAY8U;AAClB;AAQO,SAASC,GACd/U,GACAyQ,GACA9V,GACAuD,GACAkW,GACM;AACN,EAAAO,GAAoB,YAAY3U,GAAKyQ,GAAM9V,GAAGuD,GAAGkW,CAAa;AAChE;AAMO,SAASY,GACdhV,GACAyQ,GACA9V,GACAuD,GACAkW,GACM;AACN,EAAAO,GAAoB,cAAc3U,GAAKyQ,GAAM9V,GAAGuD,GAAGkW,CAAa;AAClE;AC5IA,IAAIa,KAA6D;AAOjE,SAASC,KAAwD;AAC/D,MAAI,CAACD;AACH,QAAI,OAAO,WAAa;AACtB,MAAAA,KAAiB,SAAS,cAAc,QAAQ;AAAA,aACvC,OAAO,kBAAoB;AACpC,MAAAA,KAAiB,IAAI,gBAAgB,GAAG,CAAC;AAAA;AAEzC,YAAM,IAAI,MAAM,0CAA0C;AAG9D,SAAOA;AACT;AAUO,SAASE,GACdC,GACAC,GACAC,IAAgB,IAChBC,IAAkB,IACV;AAER,QAAMC,IAAkB,CAAA;AAGxB,SAAID,KACFC,EAAM,KAAK,QAAQ,GAIjBF,KACFE,EAAM,KAAK,MAAM,GAInBA,EAAM,KAAK,GAAGJ,CAAQ,IAAI,GAC1BI,EAAM,KAAKH,CAAU,GAEdG,EAAM,KAAK,GAAG;AACvB;AAeO,SAASC,GACdhF,GACAiF,GACAN,GACAC,GACAC,IAAgB,IAChBC,IAAkB,IAClBI,GACAC,IAAkB,IAClBC,GACU;AAEV,QAAM7V,IADSkV,GAAA,EACI,WAAW,IAAI;AAClC,EAAAlV,EAAI,OAAOmV,GAAgBC,GAAUC,GAAYC,GAAMC,CAAM;AAC7D,QAAMnB,KAAgByB,KAAA,gBAAAA,EAAM,kBAAiB,GAEvCC,IAAU,CAACC,MAAsB7B,GAAgBlU,EAAI,YAAY+V,CAAC,EAAE,OAAOA,GAAG3B,CAAa;AAGjG,MAAI3D,EAAK,OAAO,WAAW;AACzB,WAAO,CAACA,CAAI;AAId,MAAIA,EAAK,SAAS;AAAA,CAAI,GAAG;AACvB,UAAMuF,IAAgBvF,EAAK,MAAM;AAAA,CAAI,GAC/BwF,IAA4B,CAAA;AAElC,aAASla,IAAI,GAAGA,IAAIia,EAAc,QAAQja,KAAK;AAC7C,YAAMma,IAAOF,EAAcja,CAAC,GACtBoa,IAAcV,GAASS,GAAMR,GAAUN,GAAUC,GAAYC,GAAMC,GAAQ,QAAWK,GAAQC,CAAI;AACxG,MAAAI,EAAgB,KAAK,GAAGE,CAAW;AAAA,IACrC;AAEA,WAAOF;AAAA,EACT;AAGA,QAAMG,IAAqB3F,EAAK,MAAM,MAAM,GACtC4F,IAAsB5F,EAAK,MAAM,MAAM,GACvC6F,IAAgBF,IAAqBA,EAAmB,CAAC,IAAI,IAC7DG,IAAiBF,IAAsBA,EAAoB,CAAC,IAAI,IAGhEG,IAFc/F,EAAK,UAAU6F,EAAc,QAAQ7F,EAAK,SAAS8F,EAAe,MAAM,EAElE,MAAM,GAAG;AAGnC,MAAIZ,MAAoB,UAAaA,IAAkB,GAAG;AACxD,QAAIA,MAAoB;AACtB,aAAO,CAACW,IAAgBE,EAAM,KAAK,GAAG,IAAID,CAAc;AAI1D,UAAME,IAAkB,CAAA,GAClBC,IAAe,KAAK,KAAKF,EAAM,SAASb,CAAe;AAE7D,aAAS5Z,IAAI,GAAGA,IAAIya,EAAM,QAAQza,KAAK2a,GAAc;AACnD,YAAMC,IAAYH,EAAM,MAAMza,GAAGA,IAAI2a,CAAY;AACjDD,MAAAA,EAAM,KAAKE,EAAU,KAAK,GAAG,CAAC;AAAA,IAChC;AAGA,WAAIF,EAAM,SAAS,MACjBA,EAAM,CAAC,IAAIH,IAAgBG,EAAM,CAAC,GAClCA,EAAMA,EAAM,SAAS,CAAC,IAAIA,EAAMA,EAAM,SAAS,CAAC,IAAIF,IAG/CE;AAAAA,EACT;AAIA,QAAMG,IAAUJ,EAAM,KAAK,GAAG,GAGxBK,IAAoBP,IAAgBM,IAAUL,GAC9CO,IAAehB,EAAQe,CAAiB,GAMxCE,IAAcrB,IAAW,KAEzBsB,IAAgBpB,IAAS,IAAI,KAAK,IAAI,IAAIF,KADnBE,IAAS,IAAKmB,IAAc,OAAO,KACe;AAG/E,MAAID,KAAgBpB,IAAWsB;AAC7B,WAAO,CAACH,CAAiB;AAI3B,QAAMJ,IAAkB,CAAA;AACxB,MAAIQ,IAAc;AAElB,QAAMC,IAAgBtB,IAAS,IAAI,KAAK,IAAI,IAAIF,KADnBE,IAAS,IAAKmB,IAAc,OAAO,KACe;AAE/E,WAAShb,IAAI,GAAGA,IAAIya,EAAM,QAAQza,KAAK;AACrC,UAAMob,IAAOX,EAAMza,CAAC;AAIpB,QAHkB+Z,EAAQqB,CAAI,IAGdzB,IAAWwB,GAAe;AAExC,MAAID,EAAY,SAAS,MACvBR,EAAM,KAAKQ,CAAW,GACtBA,IAAc;AAIhB,YAAM3C,IAAQ,MAAM,KAAK6C,CAAI;AAC7B,UAAIC,IAAa;AAEjB,iBAAWC,KAAQ/C,GAAO;AACxB,cAAMgD,IAAaF,IAAaC;AAGhC,QAFoBvB,EAAQwB,CAAU,IAEpB5B,IAAWwB,KAAiBE,EAAW,SAAS,KAEhEX,EAAM,KAAKW,CAAU,GACrBA,IAAaC,KAEbD,IAAaE;AAAA,MAEjB;AAGA,MAAAL,IAAcG;AAAA,IAChB,OAAO;AAEL,YAAMG,IAAWN,EAAY,SAAS,IAAIA,IAAc,MAAME,IAAOA;AAGrE,MAFsBrB,EAAQyB,CAAQ,IAElB7B,IAAWwB,KAAiBD,EAAY,SAAS,KACnER,EAAM,KAAKQ,CAAW,GACtBA,IAAcE,KAEdF,IAAcM;AAAA,IAElB;AAAA,EACF;AAQA,MANIN,EAAY,SAAS,KACvBR,EAAM,KAAKQ,CAAW,GAKpBR,EAAM,SAAS,GAAG;AAEpB,QAAIH,EAAc,SAAS,GAAG;AAC5B,YAAMkB,IAAqBlB,IAAgBG,EAAM,CAAC;AAGlD,MAFuBX,EAAQ0B,CAAkB,IAE5B9B,IAAWwB,IAG9BT,EAAM,QAAQH,CAAa,IAG3BG,EAAM,CAAC,IAAIe;AAAA,IAEf;AAGA,QAAIjB,EAAe,SAAS,GAAG;AAC7B,YAAMkB,IAAUhB,EAAM,SAAS,GACzBiB,IAAoBjB,EAAMgB,CAAO,IAAIlB;AAG3C,MAFsBT,EAAQ4B,CAAiB,IAE3BhC,IAAWwB,IAE7BT,EAAM,KAAKF,CAAc,IAGzBE,EAAMgB,CAAO,IAAIC;AAAA,IAErB;AAAA,EACF;AAEA,SAAOjB;AACT;AAYO,SAASkB,GACdlH,GACA2E,GACAC,GACAC,IAAgB,IAChBC,IAAkB,IAClBnB,IAAwB,GAChB;AAER,QAAMpU,IADSkV,GAAA,EACI,WAAW,IAAI;AAClC,SAAAlV,EAAI,OAAOmV,GAAgBC,GAAUC,GAAYC,GAAMC,CAAM,GACtDrB,GAAgBlU,EAAI,YAAYyQ,CAAI,EAAE,OAAOA,GAAM2D,CAAa;AACzE;AAUO,SAASwD,GACdxC,GACAC,GACAC,IAAgB,IAChBC,IAAkB,IACmC;AAErD,QAAMvV,IADSkV,GAAA,EACI,WAAW,IAAI;AAClC,EAAAlV,EAAI,OAAOmV,GAAgBC,GAAUC,GAAYC,GAAMC,CAAM;AAI7D,QAAMsC,IAAU7X,EAAI,YADD,UACuB,GAEpC8X,IAASD,EAAQ,2BAA2BzC,IAAW,KACvD2C,IAAUF,EAAQ,4BAA4BzC,IAAW,KACzDvH,IAASiK,IAASC;AAExB,SAAO,EAAE,QAAAD,GAAQ,SAAAC,GAAS,QAAAlK,EAAA;AAC5B;AA0KO,SAASmK,GACdvH,GACAiF,GACAN,GACAC,GACAC,IAAgB,IAChBC,IAAkB,IAClBI,GACAC,IAAkB,IAClBC,GACoD;AACpD,QAAMzB,KAAgByB,KAAA,gBAAAA,EAAM,kBAAiB,GAEvCY,IAAQhB,GAAShF,GAAMiF,GAAUN,GAAUC,GAAYC,GAAMC,GAAQI,GAAiBC,GAAQC,CAAI,GAKlGoC,IAAsBxH,EAAK,SAAS;AAAA,CAAI,GAKxCyH,IAAoBzB,EAAM,IAAI,CAAC0B,GAAG9G,OAAW;AAAA,IACjD,kBAAkB,CAAC4G,KAAuB5G,MAAU;AAAA,IACpD,gBAAgB,CAAC4G,KAAuB5G,MAAUoF,EAAM,SAAS;AAAA,EAAA,EACjE,GAIIzW,IADSkV,GAAA,EACI,WAAW,IAAI;AAClC,EAAAlV,EAAI,OAAOmV,GAAgBC,GAAUC,GAAYC,GAAMC,CAAM;AAE7D,MAAI6C,IAAe;AACnB,QAAMC,IAAcT,GAAexC,GAAUC,GAAYC,GAAMC,CAAM;AAMrE,MAAI+C,IAAiB;AAErB,EAAA7B,EAAM,QAAQ,CAACP,GAAM7E,MAAU;AAC7B,UAAMkH,IAAmBL,EAAkB7G,CAAK,EAAE,kBAC5CmH,IAAiBN,EAAkB7G,CAAK,EAAE;AAGhD,QAAIoH,IAAcvC;AAGlB,QAAI,CAACqC,GAAkB;AACrB,YAAMnC,IAAqBF,EAAK,MAAM,KAAK,GACrCwC,IAAqBtC,IAAqBA,EAAmB,CAAC,EAAE,SAAS;AAC/E,MAAIsC,IAAqB,MACvBD,IAAcA,EAAY,UAAUC,CAAkB;AAAA,IAE1D;AAGA,QAAI,CAACF,GAAgB;AACnB,YAAMnC,IAAsBoC,EAAY,MAAM,KAAK,GAC7CE,IAAsBtC,IAAsBA,EAAoB,CAAC,EAAE,SAAS;AAClF,MAAIsC,IAAsB,MACxBF,IAAcA,EAAY,UAAU,GAAGA,EAAY,SAASE,CAAmB;AAAA,IAEnF;AAMA,QAHAL,KAGIG,EAAY,SAAS,GAAG;AAI1B,YAAMG,IAAY1E,GAAgBlU,EAAI,YAAYyY,CAAW,EAAE,OAAOA,GAAarE,CAAa;AAChG,MAAAgE,IAAe,KAAK,IAAIA,GAAcQ,CAAS;AAAA,IACjD;AAAA,EACF,CAAC;AAKD,MAAI/K;AAGJ,QAAMgL,KAAahD,KAAA,gBAAAA,EAAM,eAAclS;AACvC,MAAI2U,MAAmB;AACrB,IAAAzK,IAASwK,EAAY;AAAA,WACZC,MAAmB;AAE5B,IAAAzK,IAAS;AAAA,OACJ;AACL,UAAMiL,IAAc1D,IAAWyD;AAC/B,IAAAhL,KAAUyK,IAAiB,KAAKQ,IAAcT,EAAY;AAAA,EAC5D;AAEA,SAAO;AAAA,IACL,OAAOD;AAAA,IACP,QAAAvK;AAAA,IACA,OAAA4I;AAAA,EAAA;AAEJ;AC9jBA,IAAIsC,KAAqC;AAEzC,SAASC,KAAoC;AAC3C,MAAI,CAACD;AACH,QAAI,OAAO,kBAAoB;AAE7B,MAAAA,KADe,IAAI,gBAAgB,GAAG,CAAC,EAClB,WAAW,IAAI;AAAA,aAC3B,OAAO,WAAa;AAE7B,MAAAA,KADe,SAAS,cAAc,QAAQ,EACzB,WAAW,IAAI;AAAA;AAEpC,YAAM,IAAI,MAAM,6BAA6B;AAGjD,SAAOA;AACT;AAUO,SAASE,GACd1I,GACAmF,GACA9Y,GAQkD;AAClD,QAAMoD,IAAMgZ,GAAA,GACNvC,IAA0D,CAAA,GAG1DyC,IAAc,CAACzI,GAAcC,MAAkC;AACnE,UAAM0E,IAAW1E,EAAM,YAAY9T,EAAQ,UACrCyY,IAAa3E,EAAM,cAAc9T,EAAQ,YACzC0Y,IAAO5E,EAAM,SAAS,SAAYA,EAAM,OAAO9T,EAAQ,QAAQ,IAC/D2Y,IAAS7E,EAAM,WAAW,SAAYA,EAAM,SAAS9T,EAAQ,UAAU,IACvEwX,IAAgB1D,EAAM,kBAAkB,SAAYA,EAAM,gBAAgB9T,EAAQ,iBAAiB;AACzG,WAAAoD,EAAI,OAAOmV,GAAgBC,GAAUC,GAAYC,GAAMC,CAAM,GACtDrB,GAAgBlU,EAAI,YAAYyQ,CAAI,EAAE,OAAOA,GAAM2D,CAAa;AAAA,EACzE,GAGM+E,IAAc,CAACC,GAAoBC,MAErCD,EAAG,aAAaC,EAAG,YACnBD,EAAG,eAAeC,EAAG,cACrBD,EAAG,UAAUC,EAAG,SAChBD,EAAG,SAASC,EAAG,QACfD,EAAG,WAAWC,EAAG,UACjBD,EAAG,cAAcC,EAAG,aACpBD,EAAG,kBAAkBC,EAAG,iBACxBD,EAAG,kBAAkBC,EAAG,eAKtBC,IAA8D,CAAA;AACpE,EAAA/I,EAAM,QAAQ,CAACM,MAAS;AACtB,IAAIyI,EAAY,SAAS,KAAKH,EAAYG,EAAYA,EAAY,SAAS,CAAC,EAAE,OAAOzI,EAAK,KAAK,IAC7FyI,EAAYA,EAAY,SAAS,CAAC,EAAE,QAAQzI,EAAK,OAEjDyI,EAAY,KAAK,EAAE,MAAMzI,EAAK,MAAM,OAAOA,EAAK,OAAO;AAAA,EAE3D,CAAC;AAGD,MAAI0I,IAAW;AACf,QAAMC,IAAuB,CAAA;AAC7B,EAAAF,EAAY,QAAQ,CAACzI,GAAM4I,MAAc;AACvC,aAAS1d,IAAI,GAAGA,IAAI8U,EAAK,KAAK,QAAQ9U;AACpC,MAAAwd,KAAY1I,EAAK,KAAK9U,CAAC,GACvByd,EAAW,KAAKC,CAAS;AAAA,EAE7B,CAAC;AAGD,QAAMjD,IAAmE,CAAA;AACzE,MAAIkD,IAAc,IACdC,IAAY;AAEhB,WAAS5d,IAAI,GAAGA,IAAIwd,EAAS,QAAQxd,KAAK;AACxC,UAAMsb,IAAOkC,EAASxd,CAAC;AACvB,IAAIsb,MAAS,OACPqC,EAAY,SAAS,MACvBlD,EAAM,KAAK,EAAE,MAAMkD,GAAa,UAAUC,GAAW,QAAQ5d,IAAI,GAAG,GACpE2d,IAAc,KAGhBlD,EAAM,KAAK,EAAE,MAAM,KAAK,UAAUza,GAAG,QAAQA,GAAG,MAE5C2d,EAAY,WAAW,MACzBC,IAAY5d,IAEd2d,KAAerC;AAAA,EAEnB;AACA,EAAIqC,EAAY,SAAS,KACvBlD,EAAM,KAAK,EAAE,MAAMkD,GAAa,UAAUC,GAAW,QAAQJ,EAAS,SAAS,EAAA,CAAG;AAIpF,MAAItC,IAAyD,CAAA,GACzD2C,IAAmB;AAEvB,QAAMC,IAAa,MAAM;AACvB,IAAI5C,EAAY,SAAS,MACvBR,EAAM,KAAKQ,CAAW,GACtBA,IAAc,CAAA,GACd2C,IAAmB;AAAA,EAEvB;AAEA,SAAApD,EAAM,QAAQ,CAACW,GAAM2C,MAAa;AAEhC,UAAMC,IAA4D,CAAA;AAClE,QAAIC,IAAiBR,EAAWrC,EAAK,QAAQ,GACzC8C,IAAc;AAElB,aAASC,IAAU/C,EAAK,UAAU+C,KAAW/C,EAAK,QAAQ+C,KAAW;AACnE,YAAMC,IAAUX,EAAWU,CAAO;AAClC,MAAIC,MAAYH,KAEdD,EAAU,KAAK,EAAE,MAAME,GAAa,OAAOX,EAAYU,CAAc,EAAE,OAAO,GAC9EC,IAAcV,EAASW,CAAO,GAC9BF,IAAiBG,KAEjBF,KAAeV,EAASW,CAAO;AAAA,IAEnC;AACA,IAAID,EAAY,SAAS,KACvBF,EAAU,KAAK,EAAE,MAAME,GAAa,OAAOX,EAAYU,CAAc,EAAE,OAAO;AAIhF,UAAMI,IAAYL,EAAU,OAAO,CAACM,GAAKxJ,MAASwJ,IAAMnB,EAAYrI,EAAK,MAAMA,EAAK,KAAK,GAAG,CAAC;AAG7F,QAAIsG,EAAK,SAAS,KAAK;AAErB,UAAIF,EAAY,SAAS,GAAG;AAC1B,cAAMqD,IAAWrD,EAAYA,EAAY,SAAS,CAAC;AACnD,QAAIkC,EAAYmB,EAAS,OAAOP,EAAU,CAAC,EAAE,KAAK,IAChDO,EAAS,QAAQ,MAEjBrD,EAAY,KAAK,EAAE,MAAM,KAAK,OAAO8C,EAAU,CAAC,EAAE,OAAO;AAAA,MAE7D;AAEE,QAAA9C,EAAY,KAAK,EAAE,MAAM,KAAK,OAAO8C,EAAU,CAAC,EAAE,OAAO;AAE3D,MAAAH,KAAoBQ;AAAA,IACtB,WAGMA,IAAY1E,GAAU;AAGxB,MAAIuB,EAAY,SAAS,KACvB4C,EAAA;AAIF,iBAAWhJ,KAAQkJ,GAAW;AAC5B,cAAMzF,IAAQ,MAAM,KAAKzD,EAAK,IAAI;AAClC,YAAIuG,IAAa;AAEjB,mBAAWC,KAAQ/C,GAAO;AACxB,gBAAMiG,IAAYrB,EAAY7B,GAAMxG,EAAK,KAAK;AAG9C,cAAI+I,IAAmBW,IAAY7E,KAAYkE,IAAmB,GAAG;AAEnE,gBAAIxC,EAAW,SAAS,GAAG;AACzB,kBAAIH,EAAY,SAAS,GAAG;AAC1B,sBAAMqD,IAAWrD,EAAYA,EAAY,SAAS,CAAC;AACnD,gBAAIkC,EAAYmB,EAAS,OAAOzJ,EAAK,KAAK,IACxCyJ,EAAS,QAAQlD,IAEjBH,EAAY,KAAK,EAAE,MAAMG,GAAY,OAAOvG,EAAK,OAAO;AAAA,cAE5D;AACE,gBAAAoG,EAAY,KAAK,EAAE,MAAMG,GAAY,OAAOvG,EAAK,OAAO;AAE1D,cAAAuG,IAAa;AAAA,YACf;AACA,YAAAyC,EAAA;AAAA,UACF;AAEA,UAAAzC,KAAcC,GACduC,KAAoBW;AAAA,QACtB;AAGA,YAAInD,EAAW,SAAS;AACtB,cAAIH,EAAY,SAAS,GAAG;AAC1B,kBAAMqD,IAAWrD,EAAYA,EAAY,SAAS,CAAC;AACnD,YAAIkC,EAAYmB,EAAS,OAAOzJ,EAAK,KAAK,IACxCyJ,EAAS,QAAQlD,IAEjBH,EAAY,KAAK,EAAE,MAAMG,GAAY,OAAOvG,EAAK,OAAO;AAAA,UAE5D;AACE,YAAAoG,EAAY,KAAK,EAAE,MAAMG,GAAY,OAAOvG,EAAK,OAAO;AAAA,MAG9D;AAAA,IACF;AAEE,MAAI+I,IAAmBQ,IAAY1E,KAAYuB,EAAY,SAAS,KAGlE4C,EAAA,GAIFE,EAAU,QAAQ,CAAClJ,MAAS;AAC1B,YAAIoG,EAAY,SAAS,GAAG;AAC1B,gBAAMqD,IAAWrD,EAAYA,EAAY,SAAS,CAAC;AACnD,UAAIkC,EAAYmB,EAAS,OAAOzJ,EAAK,KAAK,IACxCyJ,EAAS,QAAQzJ,EAAK,OAEtBoG,EAAY,KAAK,EAAE,GAAGpG,GAAM;AAAA,QAEhC;AACE,UAAAoG,EAAY,KAAK,EAAE,GAAGpG,GAAM;AAAA,MAEhC,CAAC,GACD+I,KAAoBQ;AAAA,EAG1B,CAAC,GAEDP,EAAA,GAGIpD,EAAM,WAAW,KACnBA,EAAM,KAAK,EAAE,GAGRA;AACT;AAKO,SAAS+D,GAAuB/G,GAAsE;AAC3G,QAAMgD,IAA0D,CAAA;AAChE,MAAIQ,IAAyD,CAAA;AAE7D,SAAAxD,EAAS,MAAM,QAAQ,CAAC5C,MAAS;AAE/B,UAAM2E,IAAQ3E,EAAK,KAAK,MAAM;AAAA,CAAI;AAElC,IAAA2E,EAAM,QAAQ,CAACiF,GAAM1e,MAAM;AACzB,MAAI0e,EAAK,SAAS,KAEhBxD,EAAY,KAAK,EAAE,MAAMwD,GAAM,OAAO5J,EAAK,OAAO,GAIhD9U,IAAIyZ,EAAM,SAAS,MACrBiB,EAAM,KAAKQ,CAAW,GACtBA,IAAc,CAAA;AAAA,IAElB,CAAC;AAAA,EACH,CAAC,GAGGA,EAAY,SAAS,KACvBR,EAAM,KAAKQ,CAAW,GAIpBR,EAAM,WAAW,KACnBA,EAAM,KAAK,EAAE,GAGRA;AACT;ACxSA,SAASiE,GAAkBjc,GAAmD;AAC5E,SAAO,OAAOA,EAAK,SAAU,WAAWA,EAAK,QAAQ;AACvD;AAGA,SAASkc,GAAkBlc,GAA+BW,GAAqB;AAC7E,EAAAX,EAAK,QAAQW;AACf;AAMO,SAASwb,GAAmBhe,GAAsBW,GAAkBiJ,GAA+B;AAKxG,QAAMqU,IAAqBrU,EAAU,eAC/BsU,IAAsBJ,GAAkBG,CAAkB,GAC1DE,IAAaD,MAAwB,SAAYA,IAAuBtU,EAAU,SAAS,GAC3F0H,IAAQ3Q,IAAWwd,GAOnBC,IAAgBxU,EAAU,YAAY5J,EAAQ,UAC9Cqe,IAAqBD,IAAgB9M;AAC3C,MAAIwF;AAYJ,MAXIuH,IAAqB,KAEvBvH,IAAc,KAAK,MAAMuH,CAAkB,IAG3CvH,IAAc,KAAK,MAAMuH,IAAqB,CAAC,IAAI,GAErDre,EAAQ,YAAY8W,CAAW,GAI3BlN,EAAU,YAAYA,EAAU,oBAAoB8J,KAAY,OAAO1T,EAAQ,eAAgB,YAAY;AAE7G,UAAMse,IADmB1U,EAAU,SACK,MAAA;AAGxC,eAAWqK,KAAQqK,EAAe;AAChC,UAAIrK,EAAK,MAAM,aAAa,QAAW;AACrC,cAAMsK,IAAiBtK,EAAK,MAAM,WAAW3C;AAE7C,QAAIiN,IAAiB,KACnBtK,EAAK,MAAM,WAAW,KAAK,MAAMsK,CAAc,IAE/CtK,EAAK,MAAM,WAAW,KAAK,MAAMsK,IAAiB,CAAC,IAAI;AAAA,MAE3D;AAGF,IAAAve,EAAQ,YAAYse,CAAc;AAAA,EACpC;AAGA,QAAME,IAAoBxe,EAAQ;AAElC,MAD8B8d,GAAkBU,CAAiB,MACnC,UAAaN,MAAwB,QAAW;AAS5E,UAAMO,IAAiBL,IAAgB,IAAItH,IAAcsH,IAAgB9M,GACnEoN,IAAcR,IAAsBO;AAG1C,IAAAV,GAAkBS,GAAmB,KAAK,IADzB,IACuC,KAAK,KAAKE,CAAW,CAAC,CAAC;AAAA,EACjF;AAIF;AAMO,SAASC,GAAiB3e,GAAsB2M,GAAgBhM,GAAkBiJ,GAA+B;AAEtH,MAAI+C,MAAW,iBAAiBA,MAAW;AACzC;AAIF,EAAA3M,EAAQ,WAAW4J,EAAU,YAAY5J,EAAQ;AAGjD,QAAMwe,IAAoBxe,EAAQ;AAElC,MAD8B8d,GAAkBU,CAAiB,MACnC,QAAW;AACvC,IAAAT,GAAkBS,GAAmB,KAAK,IAAIxX,IAAWrG,CAAQ,CAAC;AAGlE,UAAMgC,IAAcR,EAAc,iBAAiBnC,EAAQ,QAAQ,GAC7D4C,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW,GAG1Bsb,IAAqBrU,EAAU,eAE/BgV,IADsBd,GAAkBG,CAAkB,KACxBrU,EAAU,SAAS,GAErDiV,KADoBf,GAAkBU,CAAiB,KAAK,KAC1BI;AAGxC,IAAIjS,MAAW,iBAEb3M,EAAQ,IAAI4J,EAAU,IAAKiV,IAAc,IAAKjc,GAC9C5C,EAAQ,IAAI4J,EAAU,IAAKiV,IAAc,IAAKhc,KACrC8J,MAAW,mBAEpB3M,EAAQ,IAAI4J,EAAU,IAAKiV,IAAc,IAAKjc,GAC9C5C,EAAQ,IAAI4J,EAAU,IAAKiV,IAAc,IAAKhc;AAAA,EAElD;AACF;AAMO,SAASic,GAAe9e,GAAsB2M,GAAgBhM,GAAkBwW,GAAoBvN,GAA+B;AACxI,QAAMmV,IAAiBpS,EAAO,SAAS,KAAK,KAAKA,EAAO,SAAS,QAAQ,GACnEqS,IAAerS,EAAO,SAAS,QAAQ;AAE7C,EAAIoS,IACFf,GAAmBhe,GAASW,GAAUiJ,CAAS,IACtCoV,KACTL,GAAiB3e,GAAS2M,GAAQhM,GAAUiJ,CAAS;AAEzD;AC7IO,MAAMqV,WAAwBzI,EAAY;AAAA;AAAA,EAI/C,YAAY3W,IAAuC,IAAI;AACrD,UAAMA,CAAM,GACZ,KAAK,gBAAgB;AAKrB,UAAMgC,IAAOhC,EAAO,eACdmR,KACJnP,KAAA,gBAAAA,EAAM,WAAU,SACZA,EAAK,QACL,KAAK;AAAA,MACHmF;AAAA,MACA+T,GAAiB,KAAK,MAAM,KAAK,UAAU,KAAK,YAAY,KAAK,MAAM,KAAK,QAAQ,KAAK,iBAAiB,CAAC,IACzGjU,IAAqB;AAAA,IAAA;AAG/B,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,gBAAejF,KAAA,gBAAAA,EAAM,kBAAiB,CAAA;AAAA,MACtC,OAAAmP;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,0BAAkC;AACxC,UAAM6F,IAAW,KAAK,YAAA,GAChBqI,IAAiB,KAAK,cAAc,QAAQpY,IAAqB,GAGjEsS,IAAgBwE,GAAuB/G,CAAQ,GAG/CsI,IAAiE,CAAA;AAevE,QAbA/F,EAAc,QAAQ,CAACgG,MAAc;AAQnC,MAPgB/C,GAAkB+C,GAAWF,GAAgB;AAAA,QAC3D,UAAU,KAAK;AAAA,QACf,YAAY,KAAK;AAAA,QACjB,MAAM,KAAK;AAAA,QACX,QAAQ,KAAK;AAAA,QACb,eAAe,KAAK;AAAA,MAAA,CACrB,EACO,QAAQ,CAACG,MAAU;AACzB,QAAAF,EAAa,KAAKE,CAAK;AAAA,MACzB,CAAC;AAAA,IACH,CAAC,GAEGF,EAAa,WAAW;AAC1B,aAAO;AAKT,QAAIG,IAAgB,GAChBC,IAAY;AAEhB,IAAAJ,EAAa,QAAQ,CAACC,MAAc;AAClC,MAAAA,EAAU,QAAQ,CAACnL,MAAS;AAC1B,cAAMuL,IAAevL,EAAK,MAAM,aAAa,SAAYA,EAAK,MAAM,WAAW,KAAK,UAC9EwL,IAAiBxL,EAAK,MAAM,eAAe,SAAYA,EAAK,MAAM,aAAa,KAAK,YACpFyL,IAAWzL,EAAK,MAAM,SAAS,SAAYA,EAAK,MAAM,OAAO,KAAK,MAClE0L,IAAa1L,EAAK,MAAM,WAAW,SAAYA,EAAK,MAAM,SAAS,KAAK,QAExEgH,IAAUD,GAAewE,GAAcC,GAAgBC,GAAUC,CAAU;AACjF,QAAAL,IAAgB,KAAK,IAAIA,GAAerE,EAAQ,MAAM,GACtDsE,IAAY,KAAK,IAAIA,GAAWtE,EAAQ,MAAM;AAAA,MAChD,CAAC;AAAA,IACH,CAAC;AAGD,UAAMiB,IAAc,KAAK,YAAY,KAAK,cAAcnV;AAQxD,QAAIoY,EAAa,WAAW;AAC1B,aAAOG;AACF;AAEL,YAAMM,IAAiB5E,GAAe,KAAK,UAAU,KAAK,YAAY,KAAK,MAAM,KAAK,MAAM,GACtF6E,KAAkBV,EAAa,SAAS,KAAKjD,IAAc0D,EAAe,QAK1EE,IAAc,KAAK,IAAI,GAAGP,IAAYK,EAAe,MAAM,GAC3DG,IAAe,KAAK,IAAI,GAAIT,IAAgBC,KAAcK,EAAe,SAASA,EAAe,OAAO;AAE9G,aAAOC,IAAiBC,IAAcC;AAAA,IACxC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,4BAAqC;AAC3C,UAAMlJ,IAAW,KAAK,YAAA;AAEtB,eAAW5C,KAAQ4C,EAAS;AAK1B,UAHI5C,EAAK,MAAM,aAAa,UAAaA,EAAK,MAAM,aAAa,KAAK,YAGlEA,EAAK,MAAM,eAAe,UAAaA,EAAK,MAAM,eAAe,KAAK;AACxE,eAAO;AAGX,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAA8B;AAC5B,UAAMiL,IAAiB,KAAK,cAAc,QAAQpY,IAAqB;AAGvE,QAAImK;AACJ,WAAI,KAAK,8BAEPA,IAAS,KAAK,wBAAA,IAcdA,IAXemK;AAAA,MACb,KAAK;AAAA,MACL8D;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA;AAAA,MACA,EAAE,eAAe,KAAK,eAAe,YAAY,KAAK,WAAA;AAAA,IAAW,EAEnD,QAGX;AAAA,MACL,GAAG,KAAK,IAAI,KAAK,cAAc,QAAQ;AAAA,MACvC,GAAG,KAAK,IAAIjO,IAAS;AAAA,MACrB,OAAO,KAAK,cAAc;AAAA,MAC1B,QAAAA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,uBAAoC;AAClC,UAAMiO,IAAiB,KAAK,cAAc,QAAQpY,IAAqB;AAGvE,QAAIkZ;AACJ,IAAI,KAAK,8BAEPA,IAAe,KAAK,wBAAA,IAcpBA,IAXe5E;AAAA,MACb,KAAK;AAAA,MACL8D;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA;AAAA,MACA,EAAE,eAAe,KAAK,eAAe,YAAY,KAAK,WAAA;AAAA,IAAW,EAE7C;AAKxB,UAAMe,IAAc,KAAK,cAAc;AAGvC,WAAO;AAAA,MACL,GAAG,KAAK,IAAIA,IAAc;AAAA,MAC1B,GAAG,KAAK,IAAID,IAAe;AAAA,MAC3B,OAAOC;AAAA,MACP,QAAQD;AAAA,IAAA;AAAA,EAEZ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAA8C;AAC5C,UAAMvd,IAAa,KAAK,qBAAA;AACxB,WAAO;AAAA,MACL,GAAGA,EAAW,IAAIA,EAAW,QAAQ;AAAA,MACrC,GAAGA,EAAW,IAAIA,EAAW,SAAS;AAAA,IAAA;AAAA,EAE1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAOW,GAA+B6I,IAAuB,IAAOC,IAAsB,IAAa;ApBvOzG,QAAA3I;AoB2OI,UAAM2c,IAAiB,KAAK,WAAW,GACjChK,IAAY,CAAC,GAAC3S,IAAA,KAAK,WAAL,QAAAA,EAAa;AAIjC,QAAI2c,IAAiB,KAAKhK,GAAW;AACnC,WAAK,oBAAoB9S,GAAK8c,CAAc;AAC5C;AAAA,IACF;AAGA,UAAMzJ,IAAgBrT,EAAI;AAC1B,IAAI8c,MAAmB,MACrB9c,EAAI,cAAc8c,IAIpB5M,GAAwB,QAAQ,EAAElQ,GAAK,KAAK,QAAQ,GAGpDA,EAAI,cAAcqT;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoBrT,GAA+B8c,GAA8B;ApBtQ3F,QAAA3c;AoBwQI,UAAM4c,OADc5c,IAAA,KAAK,WAAL,gBAAAA,EAAa,UAAS,KACZ,IACxBtD,IAAO,KAAK,qBAAA,GACZmgB,IAAO,KAAK,KAAKngB,EAAK,QAAQkgB,IAAU,CAAC,GACzCE,IAAO,KAAK,KAAKpgB,EAAK,SAASkgB,IAAU,CAAC,GAE1CG,IAAY,SAAS,cAAc,QAAQ;AACjD,IAAAA,EAAU,QAAQF,GAClBE,EAAU,SAASD;AACnB,UAAME,IAASD,EAAU,WAAW,IAAI;AACxC,QAAI,CAACC,GAAQ;AAEX,YAAMC,IAAOpd,EAAI;AACjB,MAAAA,EAAI,cAAc8c,GAClB5M,GAAwB,QAAQ,EAAElQ,GAAK,KAAK,QAAQ,GACpDA,EAAI,cAAcod;AAClB;AAAA,IACF;AAGA,UAAMC,IAAUxgB,EAAK,IAAIA,EAAK,QAAQ,GAChCygB,IAAUzgB,EAAK,IAAIA,EAAK,SAAS;AAEvC,IAAAsgB,EAAO,UAAUH,IAAO,IAAIK,GAASJ,IAAO,IAAIK,CAAO,GAGvDpN,GAAwB,QAAQ,EAAEiN,GAAQ,KAAK,QAAQ,GAGvDnd,EAAI,KAAA,GACJA,EAAI,cAAc8c,GAClB9c,EAAI,UAAUkd,GAAWG,IAAUL,IAAO,GAAGM,IAAUL,IAAO,CAAC,GAC/Djd,EAAI,QAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKS,wBAA4C;AACnD,UAAMwG,IAAY,MAAM,sBAAA,GAKlBsV,IAAiB,KAAK,cAAc,QAAQpY,IAAqB,GACjE+S,IAAQhB,GAAS,KAAK,MAAMqG,GAAgB,KAAK,UAAU,KAAK,YAAY,KAAK,MAAM,KAAK,QAAQ,QAAW,IAAO;AAAA,MAC1H,eAAe,KAAK;AAAA,IAAA,CACrB;AACD,WAAAtV,EAAU,YAAYiQ,EAAM,QAErBjQ;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO+C,GAAsBhM,GAAkBC,GAAmBgJ,GAAqC;AAgBrG,QAZE+C,MAAW,cAAcA,MAAW,eAAeA,MAAW,iBAAiBA,MAAW,mBAI1F,KAAK,mBAAmB,SAI1BmS,GAAe,MAAMnS,GAAQhM,GAAUC,GAAWgJ,CAAS,GAIvD,KAAK,qBAAqB,UAAa,KAAK,mBAAmB,GAAG;AAcpE,YAAM+W,IAZgB5F;AAAAA,QACpB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK,iBAAiB;AAAA,MAAA,IAMS,KAAK,mBAAmBjU,IAAqB;AAG9E,MAAI,KAAK,cAAc,QAAQ6Z,MAC7B,KAAK,cAAc,QAAQ,KAAK,KAAKA,CAAQ;AAAA,IAEjD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQhK,GAAuB;AAC7B,UAAM,QAAQA,CAAO,GAKrB,KAAK,mBAAmB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAAoC;AAClC,WAAO,CAAC,YAAY,aAAa,eAAe,gBAAgB,eAAe,cAAc;AAAA,EAC/F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAyB;AACvB,UAAM7D,IAAS,MAAM,MAAA;AAIrB,WAAI,KAAK,qBAAqB,WAC5BA,EAAO,mBAAmB,KAAK,mBAG1BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAA8B;AAe5B,WAda;AAAA,MACX,GAAG,MAAM,OAAA;AAAA,MACT,eAAe;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA;AAAA,QAEN,eAAe,KAAK,cAAc,cAAc,IAAI,CAAC8N,OAAO,EAAE,GAAGA,EAAA,EAAI;AAAA,QACrE,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAOJ;AACF;ACjZO,SAASC,GAA6BjX,GAAoEkX,GAAkDC,GAAsBC,GAAkB;AACzM,QAAMre,IAAcR,EAAc,UAAU6e,CAAQ,GAC9Cpe,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW;AAGhC,MAAIse,IAAc,GACdC,IAAc;AAElB,EAAIH,MAAiB,cAEnBE,IAAcrX,EAAU,OACxBsX,IAActX,EAAU,UACfmX,MAAiB,eAE1BE,IAAc,GACdC,IAActX,EAAU,UACfmX,MAAiB,iBAE1BE,IAAcrX,EAAU,OACxBsX,IAAc,KACLH,MAAiB,mBAE1BE,IAAc,GACdC,IAAc;AAIhB,QAAMC,IAAcvX,EAAU,KAAKqX,IAAcre,IAAMse,IAAcre,IAC/Due,IAAcxX,EAAU,KAAKqX,IAAcpe,IAAMqe,IAActe;AAGrE,MAAIye,IAAiB,GACjBC,IAAiB;AAErB,EAAIP,MAAiB,cACnBM,IAAiBP,EAAc,OAC/BQ,IAAiBR,EAAc,UACtBC,MAAiB,eAC1BM,IAAiB,GACjBC,IAAiBR,EAAc,UACtBC,MAAiB,iBAC1BM,IAAiBP,EAAc,OAC/BQ,IAAiB,KACRP,MAAiB,mBAC1BM,IAAiB,GACjBC,IAAiB;AAInB,QAAMzgB,IAAOsgB,KAAeE,IAAiBze,IAAM0e,IAAiBze,IAC9D/B,IAAOsgB,KAAeC,IAAiBxe,IAAMye,IAAiB1e;AAEpE,SAAO,EAAE,GAAG/B,GAAM,GAAGC,EAAA;AACvB;AAiBO,SAASygB,GAAgCthB,GAAmB+gB,GAAkBte,GAAuBiN,IAAe,GAAK;AAI9H,QAAM3M,IAAS/C,EAAK,QAAQ,GACtBgD,IAAShD,EAAK,SAAUiG,KAA2ByJ,GAGnDlM,IAASxD,EAAK,IAAI+C,GAClBU,IAASzD,EAAK,IAAIgD,GAGlBN,IAAcR,EAAc,UAAU6e,CAAQ,GAC9Cpe,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW,GAG1BG,IAAKW,IAASf,EAAe,GAC7BK,IAAKW,IAAShB,EAAe,GAE7B+M,IAAW3M,IAAKF,IAAMG,IAAKF,GAC3B6M,IAAW5M,IAAKD,IAAME,IAAKH;AAEjC,SAAO;AAAA,IACL,GAAGF,EAAe,IAAI+M;AAAA,IACtB,GAAG/M,EAAe,IAAIgN;AAAA,EAAA;AAE1B;AAUO,SAAS8R,GAAuBvhB,GAAmB+gB,GAAkBte,GAAuB;AACjG,QAAMC,IAAcR,EAAc,UAAU6e,CAAQ,GAC9Cpe,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW,GAI1B8e,IAAY,CAACze,GAAgBC,MAAmB;AAEpD,UAAMQ,IAASxD,EAAK,IAAI+C,GAClBU,IAASzD,EAAK,IAAIgD,GAGlBH,IAAKW,IAASf,EAAe,GAC7BK,IAAKW,IAAShB,EAAe,GAE7B+M,IAAW3M,IAAKF,IAAMG,IAAKF,GAC3B6M,IAAW5M,IAAKD,IAAME,IAAKH;AAEjC,WAAO;AAAA,MACL,GAAGF,EAAe,IAAI+M;AAAA,MACtB,GAAG/M,EAAe,IAAIgN;AAAA,IAAA;AAAA,EAE1B,GAEM,EAAE,OAAAsB,GAAO,QAAAC,EAAA,IAAWhR,GAGpByhB,IAAe,CAACtO,GAAczG,GAAsB3J,GAAgBC,MAAmB;AAC3F,UAAM0e,IAAWF,EAAUze,GAAQC,CAAM,GACnC2e,IAASC;AAAA,MACbF,EAAS;AAAA,MACTA,EAAS;AAAA,MACTjf,EAAe;AAAA,MACfA,EAAe;AAAA,MACf0Q;AAAA,MACA4N;AAAA,MACArU;AAAA,IAAA;AAEF,WAAO;AAAA,MACL,MAAAyG;AAAA,MACA,QAAAzG;AAAA,MACA,GAAGgV;AAAA,MACH,QAAAC;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AAAA;AAAA,IAELF,EAAa,UAAU,YAAY,GAAG,CAAC;AAAA,IACvCA,EAAa,UAAU,aAAa1Q,GAAO,CAAC;AAAA,IAC5C0Q,EAAa,UAAU,eAAe,GAAGzQ,CAAM;AAAA,IAC/CyQ,EAAa,UAAU,gBAAgB1Q,GAAOC,CAAM;AAAA;AAAA,IAEpDyQ,EAAa,QAAQ,eAAe,GAAGzQ,IAAS,CAAC;AAAA,IACjDyQ,EAAa,QAAQ,gBAAgB1Q,GAAOC,IAAS,CAAC;AAAA,IACtDyQ,EAAa,QAAQ,cAAc1Q,IAAQ,GAAG,CAAC;AAAA,IAC/C0Q,EAAa,QAAQ,iBAAiB1Q,IAAQ,GAAGC,CAAM;AAAA,EAAA;AAE3D;AAKO,SAAS6Q,GAAchiB,GAAYC,GAAYgiB,GAAYC,GAAY1V,GAAyB;AACrG,QAAMxJ,IAAKhD,IAAKiiB,GACVhf,IAAKhD,IAAKiiB;AAChB,SAAOlf,IAAKA,IAAKC,IAAKA,KAAMuJ,IAASA;AACvC;AAKO,SAAS2V,GAAYniB,GAAYC,GAAYmiB,GAAmBlB,GAA2B;AAGhG,QAAMre,IAAcR,EAAc,iBAAiB6e,CAAQ,GACrDpe,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW,GAG1Bwf,IAAcriB,IAAKoiB,EAAK,GACxBE,IAAcriB,IAAKmiB,EAAK,GAGxBlf,IAASmf,IAAcvf,IAAMwf,IAAcvf,GAC3CI,IAASkf,IAActf,IAAMuf,IAAcxf;AAGjD,SAAOI,KAAU,KAAKA,KAAUkf,EAAK,SAASjf,KAAU,KAAKA,KAAUif,EAAK;AAC9E;AAMO,SAASnH,GACdlH,GACA2E,GACAC,IAAqB,SACrBC,IAAgB,IAChBC,IAAkB,IACV;AAIR,QAAMvV,IADS,SAAS,cAAc,QAAQ,EAC3B,WAAW,IAAI,GAC5Bif,IAAS3J,IAAO,SAAS,UACzB5E,IAAQ6E,IAAS,WAAW;AAClC,SAAAvV,EAAI,OAAO,GAAG0Q,CAAK,IAAIuO,CAAM,IAAI7J,CAAQ,MAAMC,CAAU,IAClDrV,EAAI,YAAYyQ,CAAI,EAAE;AAC/B;AAKO,SAASyO,GAAqBriB,GAAmB;AACtD,SAAO;AAAA,IACL,GAAGA,EAAK,IAAIA,EAAK,QAAQ;AAAA,IACzB,GAAGA,EAAK,IAAIA,EAAK,SAAS;AAAA,EAAA;AAE9B;AAKO,SAASsiB,GAAeC,GAAiBC,GAAiB3e,GAAgBC,GAAwB;AACvG,SAAO,KAAK,MAAMA,IAAS0e,GAAS3e,IAAS0e,CAAO;AACtD;AAoEO,SAASE,GAAOC,GAAgBC,GAAgBC,GAA4B;AACjF,SAAO,KAAK,IAAIF,IAASC,CAAM,KAAKC;AACtC;AAwDO,SAAShB,GACdiB,GACAC,GACAC,GACAC,GACAC,GACAlC,IAAmB,GACnBrU,IAAiB,IACT;AAER,QAAM7J,IAAKggB,IAAeE,GACpBjgB,IAAKggB,IAAeE;AAG1B,MAAIE,IAAQ,KAAK,MAAMpgB,GAAID,CAAE,KAAK,MAAM,KAAK;AAK7C,MAFAqgB,KAAUA,IAAQ,MAAO,OAAO,KAE5BD,MAAe,UAAU;AAK3B,QADoB,KAAK,IAAIlC,CAAQ,IAAI,KACxB;AAEf,UAAIrU,MAAW,cAAcA,MAAW;AACtC,eAAO;AACT,UAAWA,MAAW,eAAeA,MAAW;AAC9C,eAAO;AAAA,IAEX;AAQA,UAAMyW,IAAkBD,IAAQ;AAGhC,WAAIC,KAAmB,SAASA,IAAkB,OAEzC,cACEA,KAAmB,QAAQA,IAAkB,OAE/C,gBACEA,KAAmB,QAAQA,IAAkB,QAE/C,cAGA;AAAA,EAEX,OAAO;AAEL,UAAMA,IAAkBD,IAAQ;AAEhC,WAAIC,KAAmB,MAAMA,IAAkB,MAEtC,cAGA;AAAA,EAEX;AACF;AAwBO,SAASC,GAAejgB,GAAuC;AAEpE,MAAI,CAACA,EAAI;AACP,WAAO;AAGT,QAAMqe,IAAYre,EAAI,aAAA;AAGtB,SAAO,KAAK,KAAKqe,EAAU,IAAIA,EAAU,IAAIA,EAAU,IAAIA,EAAU,CAAC;AACxE;AA0DO,SAAS6B,GACdlgB,GACA4N,GACAuS,IAAwB,CAAA,GAClB;AACN,QAAMjS,IAAQ+R,GAAejgB,CAAG;AAChC,EAAAA,EAAI,YAAYkO,IAAQ,IAAIN,IAAQM,IAAQN,GACxCuS,EAAY,SAAS,KACvBngB,EAAI,YAAYkO,IAAQ,IAAIiS,EAAY,IAAI,CAAA/gB,MAASA,IAAQ8O,CAAK,IAAIiS,CAAW;AAErF;AC1hBO,MAAMC,WAAwBhN,EAAY;AAAA,EAG/C,YAAY3W,IAAuC,IAAI;AACrD,UAAMA,CAAM,GACZ,KAAK,gBAAgB;AAIrB,UAAMgC,IAAOhC,EAAO;AACpB,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,SAAQgC,KAAA,gBAAAA,EAAM,WAAU;AAAA,MACxB,QAAOA,KAAA,gBAAAA,EAAM,UAAS;AAAA,MACtB,UAASA,KAAA,gBAAAA,EAAM,YAAW;AAAA,IAAA;AAAA,EAE9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAA8B;AAC5B,UAAM4hB,IAAkB,KAAK,cAAc,SAAS,KAAK,cAAc,OACjEC,IAAWD,IAAkB;AAEnC,WAAO;AAAA,MACL,GAAG,KAAK,IAAIA;AAAA,MACZ,GAAG,KAAK,IAAIA;AAAA,MACZ,OAAOC;AAAA,MACP,QAAQA;AAAA,IAAA;AAAA,EAEZ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,uBAAoC;AAClC,UAAMD,IAAkB,KAAK,cAAc,SAAS,KAAK,cAAc,OACjEE,IAAoB,KAAK,WAAW,KAAK,cAAc,OAIvDC,IADkB7I,GAAiB,KAAK,MAAM4I,GAAmB,KAAK,YAAY,KAAK,MAAM,KAAK,MAAM,IAC3EF,GAI7BI,IAAa,CAAC,KAAK,KAAK,IAAID,IAAW,GACvCE,IAAW,CAAC,KAAK,KAAK,IAAIF,IAAW,GAKrCG,IACJN,IAAkBE,IAAoB,IAAIF,IAAkBE,IAAoB,IAAIF,IAAkB,KAClGO,IAAcP,IAAkBE,IAAoB;AAE1D,QAAIM,IAAO,OACTC,IAAO,QACLC,IAAO,OACTC,IAAO;AAGT,UAAMC,IAAU;AAChB,aAASllB,IAAI,GAAGA,KAAKklB,GAASllB,KAAK;AACjC,YAAMgkB,IAAQU,KAAcC,IAAWD,MAAe1kB,IAAIklB,IAGpDC,IAAS,KAAK,IAAI,KAAK,IAAInB,CAAK,IAAIY,GACpCQ,IAAS,KAAK,IAAI,KAAK,IAAIpB,CAAK,IAAIY,GACpCS,IAAS,KAAK,IAAI,KAAK,IAAIrB,CAAK,IAAIa,GACpCS,IAAS,KAAK,IAAI,KAAK,IAAItB,CAAK,IAAIa;AAE1C,MAAAC,IAAO,KAAK,IAAIA,GAAMK,GAAQE,CAAM,GACpCN,IAAO,KAAK,IAAIA,GAAMI,GAAQE,CAAM,GACpCL,IAAO,KAAK,IAAIA,GAAMI,GAAQE,CAAM,GACpCL,IAAO,KAAK,IAAIA,GAAMG,GAAQE,CAAM;AAAA,IACtC;AAEA,WAAO;AAAA,MACL,GAAGR;AAAA,MACH,GAAGE;AAAA,MACH,OAAOD,IAAOD;AAAA,MACd,QAAQG,IAAOD;AAAA,IAAA;AAAA,EAEnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,OAAOxX,GAAsBhM,GAAkBC,GAAmBgJ,GAAqC;AACrG,UAAM8a,IAAkB9a,EAAU,eAG5B+a,KAAgBhkB,IAAWC,KAAa,GACxCgkB,KAAqBhb,EAAU,QAASA,EAAU,UAAW,GAC7Dib,IAAcF,IAAeC,GAG7BE,IAAWJ,EAAgB,QAAQG,GAGnCE,IAAe,KAAK,IAAI,KAAK,KAAK,IAAI,IAAID,CAAQ,CAAC;AACzD,SAAK,cAAc,QAAQC;AAG3B,UAAM9kB,IAAO,KAAK,eAAA,GACZ+kB,IAAcnE;AAAA,MAClB;AAAA,QACE,GAAGjX,EAAU,IAAI8a,EAAgB,SAASA,EAAgB;AAAA,QAC1D,GAAG9a,EAAU,IAAI8a,EAAgB,SAASA,EAAgB;AAAA,QAC1D,OAAOA,EAAgB,SAAS,IAAIA,EAAgB;AAAA,QACpD,QAAQA,EAAgB,SAAS,IAAIA,EAAgB;AAAA,MAAA;AAAA,MAEvD,EAAE,OAAOzkB,EAAK,OAAO,QAAQA,EAAK,OAAA;AAAA,MAClC0M;AAAA,MACA,KAAK;AAAA,IAAA;AAGP,IAAIqY,MAEF,KAAK,IAAIA,EAAY,IAAI/kB,EAAK,QAAQ,GACtC,KAAK,IAAI+kB,EAAY,IAAI/kB,EAAK,SAAS;AAAA,EAE3C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAAoC;AAClC,WAAO,CAAC,YAAY,aAAa,eAAe,cAAc;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,uBAA+B;AAC7B,WAAO,KAAK,MAAM,KAAK,WAAW,KAAK,cAAc,QAAQ,EAAE,IAAI;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqBglB,GAA0B;AAC7C,QAAI,KAAK,IAAI,KAAK,cAAc,QAAQ,CAAC,IAAI,MAAM;AAEjD,YAAMC,IAAkBD,IAAa,KAAK,cAAc;AACxD,WAAK,YAAYC,CAAe;AAAA,IAClC;AAEE,WAAK,YAAYD,CAAU;AAAA,EAE/B;AAAA;AAAA;AAAA;AAAA,EAKA,SAA8B;AAC5B,WAAO;AAAA,MACL,GAAG,MAAM,OAAA;AAAA,MACT,eAAe;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA,QACN,QAAQ,KAAK,cAAc;AAAA,QAC3B,OAAO,KAAK,cAAc;AAAA,QAC1B,SAAS,KAAK,cAAc;AAAA,MAAA;AAAA,IAC9B;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKS,wBAA4C;AACnD,UAAMhlB,IAAO,KAAK,eAAA;AAClB,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,OAAOA,EAAK;AAAA,MACZ,QAAQA,EAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA,QACN,QAAQ,KAAK,cAAc;AAAA,QAC3B,OAAO,KAAK,cAAc;AAAA,QAC1B,SAAS,KAAK,cAAc;AAAA,MAAA;AAAA,IAC9B;AAAA,EAEJ;AACF;AC9MO,MAAMklB,WAAsB3O,EAAY;AAAA,EAG7C,YAAY3W,IAAqC,IAAI;AACnD,UAAMA,CAAM,GACZ,KAAK,gBAAgB;AAIrB,UAAMgC,IAAOhC,EAAO;AACpB,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,aAAYgC,KAAA,gBAAAA,EAAM,eAAc;AAAA,MAChC,QAAOA,KAAA,gBAAAA,EAAM,UAAS;AAAA,IAAA;AAAA,EAE1B;AAAA,EAEA,iBAA8B;AAC5B,UAAMujB,IAAgB,KAAK,IAAI,KAAK,cAAc,UAAU,GACtDnU,IAAS,KAAK,YAAY,MAAMmU,IAIhCC,IAAU,KAAK,cAAc,aAAa,IAAI,CAACpU,IAAS;AAE9D,WAAO;AAAA,MACL,GAAG,KAAK,IAAI,KAAK,cAAc,QAAQ;AAAA,MACvC,GAAG,KAAK,IAAIoU;AAAA,MACZ,OAAO,KAAK,cAAc;AAAA,MAC1B,QAAApU;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,uBAAoC;AAGlC,UAAMyG,IAAQ,KAAK,KAAK,MAAM,EAAE,GAC1B4N,IAAa5N,EAAM,IAAI,CAAC+C,MAASM,GAAiBN,GAAM,KAAK,UAAU,KAAK,UAAU,CAAC,GACvF3C,IAAawN,EAAW,OAAO,CAAC7H,GAAK8H,MAAM9H,IAAM8H,GAAG,CAAC,GACrDjZ,IAAS,KAAK,cAAc,QAAQ,GAEpCkZ,IAAa,KAIb1Z,IADa,KAAK,WACQ;AAEhC,QAAImY,IAAO,OACPC,IAAO,QACPC,IAAO,OACPC,IAAO,QAGPqB,IAAW,CAAC3N,IAAa;AAC7B,IAAAJ,EAAM,QAAQ,CAACgO,GAAOvmB,MAAM;AAC1B,YAAMwe,IAAY2H,EAAWnmB,CAAC,GACxBqjB,IAAUiD,IAAW9H,IAAY,GACjCgI,IAAcnD,IAAUlW,GAGxBsZ,KAAS,KAAK,IAAID,GAAa,CAAC,IAAI,KAAK,KAAK,cAAc,aAAa,KAAK,UAG9EE,IAAQ,IAAIF,IAAc,KAAK,cAAc;AAWnD,MARgB;AAAA,QACd,EAAE,GAAG,CAAChI,IAAY,GAAG,GAAG,CAAC7R,EAAA;AAAA;AAAA,QACzB,EAAE,GAAG6R,IAAY,GAAG,GAAG,CAAC7R,EAAA;AAAA;AAAA,QACxB,EAAE,GAAG,CAAC6R,IAAY,GAAG,GAAG7R,EAAA;AAAA;AAAA,QACxB,EAAE,GAAG6R,IAAY,GAAG,GAAG7R,EAAA;AAAA;AAAA,MAAW,EAI5B,QAAQ,CAACwD,MAAW;AAE1B,cAAMwW,IAAUxW,EAAO,GACjByW,IAAUzW,EAAO,IAAIuW,IAAQL,IAAalW,EAAO,GAGjD7L,IAAS+e,IAAUsD,GACnBpiB,IAASkiB,IAAQG;AAEvB,QAAA9B,IAAO,KAAK,IAAIA,GAAMxgB,CAAM,GAC5BygB,IAAO,KAAK,IAAIA,GAAMzgB,CAAM,GAC5B0gB,IAAO,KAAK,IAAIA,GAAMzgB,CAAM,GAC5B0gB,IAAO,KAAK,IAAIA,GAAM1gB,CAAM;AAAA,MAC9B,CAAC,GAED+hB,KAAY9H;AAAA,IACd,CAAC;AAGD,UAAMwC,IAAU;AAChB,WAAA8D,KAAQ9D,GACR+D,KAAQ/D,GACRgE,KAAQhE,GACRiE,KAAQjE,GAED;AAAA,MACL,GAAG,KAAK,IAAI8D;AAAA,MACZ,GAAG,KAAK,IAAIE;AAAA,MACZ,OAAOD,IAAOD;AAAA,MACd,QAAQG,IAAOD;AAAA,IAAA;AAAA,EAEnB;AAAA,EAGA,OAAOxX,GAAsBhM,GAAkBwW,GAAoBvN,GAAqC;AACtG,UAAMmV,IAAiBpS,EAAO,SAAS,KAAK,KAAKA,EAAO,SAAS,QAAQ,GACnEqS,IAAerS,EAAO,SAAS,QAAQ,GACvCqZ,IAAgBpc,EAAU;AAEhC,QAAImV,GAAgB;AAUlB,YAAMzN,IAAQ3Q,IAAWiJ,EAAU,OAG7BkN,IAAclN,EAAU,WAAY0H;AAC1C,WAAK,YAAYwF,CAAW;AAG5B,YAAM4H,IAAcsH,EAAc,QAAQ1U;AAC1C,WAAK,cAAc,QAAQ,KAAK,IAAI,IAAIoN,CAAW;AAAA,IAIrD,WAAWM,MAELrS,MAAW,iBAAiBA,MAAW,iBAAgB;AAGzD,WAAK,WAAW/C,EAAU,UAG1B,KAAK,cAAc,QAAQ,KAAK,IAAI,IAAIjJ,CAAQ;AAMhD,YAAMslB,IAAatlB,IAAWiJ,EAAU,OAClCjH,IAAe,KAAK,WAAW,KAAK,KAAM,KAC1CC,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW;AAEhC,UAAIgK,MAAW,eAAe;AAG5B,cAAMuZ,IAAc,CAACD,IAAa;AAClC,aAAK,IAAIrc,EAAU,IAAIsc,IAActjB,GACrC,KAAK,IAAIgH,EAAU,IAAIsc,IAAcrjB;AAAA,MACvC,WAAW8J,MAAW,gBAAgB;AAGpC,cAAMuZ,IAAcD,IAAa;AACjC,aAAK,IAAIrc,EAAU,IAAIsc,IAActjB,GACrC,KAAK,IAAIgH,EAAU,IAAIsc,IAAcrjB;AAAA,MACvC;AAAA,IACF;AAAA,EAIJ;AAAA,EAEA,oBAAoC;AAElC,WAAO,CAAC,YAAY,aAAa,eAAe,cAAc;AAAA,EAChE;AAAA,EAEA,SAA4B;AAC1B,WAAO;AAAA,MACL,GAAG,MAAM,OAAA;AAAA,MACT,eAAe;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AAAA,EAES,wBAA4C;AACnD,UAAM5C,IAAO,KAAK,eAAA;AAClB,WAAO;AAAA,MACL,GAAG,MAAM,sBAAA;AAAA,MACT,OAAOA,EAAK;AAAA,MACZ,QAAQA,EAAK;AAAA,MACb,eAAe;AAAA,QACb,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AACF;AC7MO,MAAMkmB,KAAgB;AAAA,EAC3B,WAAW;AAAA;AAAA,EACX,WAAW;AAAA;AAAA,EACX,OAAO;AACT,GAGaC,KAAgB;AAAA,EAC3B,WAAW;AAAA;AAAA,EACX,WAAW;AAAA;AAAA,EACX,OAAO;AACT,GAGaC,KAAgB;AAAA,EAC3B,YAAY;AAEd,GASaC,KAAgB;AAAA,EAC3B,YAAY;AAEd,GAGaC,KAAkB;AAAA,EAC7B,aAAa;AAEf;AC9BO,MAAMC,WAAsBhQ,EAAY;AAAA,EAG7C,YAAY3W,IAAqC,IAAI;AACnD,UAAMA,CAAM,GACZ,KAAK,gBAAgB;AAGrB,UAAMgC,IAAOhC,EAAO;AACpB,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,YAAWgC,KAAA,gBAAAA,EAAM,cAAaskB,GAAc;AAAA,MAC5C,YAAWtkB,KAAA,gBAAAA,EAAM,cAAaskB,GAAc;AAAA,MAC5C,QAAOtkB,KAAA,gBAAAA,EAAM,UAASskB,GAAc;AAAA,IAAA;AAAA,EAExC;AAAA,EAEA,iBAA8B;AAC5B,UAAMM,IAAe,KAAK,IAAI,KAAK,cAAc,SAAS,GACpDxV,IAAS,KAAK,YAAY,MAAMwV,IAAe;AAErD,WAAO;AAAA,MACL,GAAG,KAAK,IAAI,KAAK,cAAc,QAAQ;AAAA,MACvC,GAAG,KAAK,IAAIxV,IAAS;AAAA,MACrB,OAAO,KAAK,cAAc;AAAA,MAC1B,QAAAA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,uBAAoC;AAElC,UAAMyG,IAAQ,KAAK,KAAK,MAAM,EAAE,GAC1B4N,IAAa5N,EAAM,IAAI,CAAC+C,MAASM,GAAiBN,GAAM,KAAK,UAAU,KAAK,UAAU,CAAC,GACvF3C,IAAawN,EAAW,OAAO,CAAC7H,GAAK8H,MAAM9H,IAAM8H,GAAG,CAAC,GAIrDzZ,IADa,KAAK,WACQ;AAEhC,QAAImY,IAAO,OACPC,IAAO,QACPC,IAAO,OACPC,IAAO,QAGPqB,IAAW,CAAC3N,IAAa;AAC7B,IAAAJ,EAAM,QAAQ,CAACgO,GAAOvmB,MAAM;AAC1B,YAAMwe,IAAY2H,EAAWnmB,CAAC,GACxBqjB,IAAUiD,IAAW9H,IAAY,GACjCgI,IAAcnD,KAAW,KAAK,cAAc,QAAQ,IAGpDoD,IACJ,KAAK,cAAc,YAAY,KAAK,WAAW,KAAK,IAAI,KAAK,cAAc,YAAY,KAAK,KAAKD,CAAW,GAGxGE,IACJ,KAAK,cAAc,YACnB,KAAK,cAAc,YACnB,KAAK,KACL,KAAK,IAAI,KAAK,cAAc,YAAY,KAAK,KAAKF,CAAW;AAW/D,MARgB;AAAA,QACd,EAAE,GAAG,CAAChI,IAAY,GAAG,GAAG,CAAC7R,EAAA;AAAA;AAAA,QACzB,EAAE,GAAG6R,IAAY,GAAG,GAAG,CAAC7R,EAAA;AAAA;AAAA,QACxB,EAAE,GAAG,CAAC6R,IAAY,GAAG,GAAG7R,EAAA;AAAA;AAAA,QACxB,EAAE,GAAG6R,IAAY,GAAG,GAAG7R,EAAA;AAAA;AAAA,MAAW,EAI5B,QAAQ,CAACwD,MAAW;AAE1B,cAAMwW,IAAUxW,EAAO,GACjByW,IAAUzW,EAAO,IAAIuW,IAAQze,KAAmBkI,EAAO,GAGvD7L,IAAS+e,IAAUsD,GACnBpiB,IAASkiB,IAAQG;AAEvB,QAAA9B,IAAO,KAAK,IAAIA,GAAMxgB,CAAM,GAC5BygB,IAAO,KAAK,IAAIA,GAAMzgB,CAAM,GAC5B0gB,IAAO,KAAK,IAAIA,GAAMzgB,CAAM,GAC5B0gB,IAAO,KAAK,IAAIA,GAAM1gB,CAAM;AAAA,MAC9B,CAAC,GAED+hB,KAAY9H;AAAA,IACd,CAAC;AAGD,UAAMwC,IAAU;AAChB,WAAA8D,KAAQ9D,GACR+D,KAAQ/D,GACRgE,KAAQhE,GACRiE,KAAQjE,GAED;AAAA,MACL,GAAG,KAAK,IAAI8D;AAAA,MACZ,GAAG,KAAK,IAAIE;AAAA,MACZ,OAAOD,IAAOD;AAAA,MACd,QAAQG,IAAOD;AAAA,IAAA;AAAA,EAEnB;AAAA,EAGA,OAAOxX,GAAsBhM,GAAkBC,GAAmBgJ,GAAqC;AACrG,IAAAkV,GAAe,MAAMnS,GAAQhM,GAAUC,GAAWgJ,CAAS;AAAA,EAC7D;AAAA,EAEA,oBAAoC;AAElC,WAAO,CAAC,YAAY,aAAa,eAAe,cAAc;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,SAA4B;AAC1B,WAAO;AAAA,MACL,GAAG,MAAM,OAAA;AAAA,MACT,eAAe;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA,QACN,WAAW,KAAK,cAAc;AAAA,QAC9B,WAAW,KAAK,cAAc;AAAA,QAC9B,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AACF;AClIO,MAAM8c,WAAsBlQ,EAAY;AAAA,EAG7C,YAAY3W,IAAqC,IAAI;AACnD,UAAMA,CAAM,GACZ,KAAK,gBAAgB;AAIrB,UAAMgC,IAAOhC,EAAO;AACpB,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,YAAWgC,KAAA,gBAAAA,EAAM,cAAaukB,GAAc;AAAA,MAC5C,YAAWvkB,KAAA,gBAAAA,EAAM,cAAaukB,GAAc;AAAA,MAC5C,QAAOvkB,KAAA,gBAAAA,EAAM,UAASukB,GAAc;AAAA,IAAA;AAAA,EAExC;AAAA,EAEA,iBAA8B;AAC5B,UAAMK,IAAe,KAAK,IAAI,KAAK,cAAc,SAAS,GACpDxV,IAAS,KAAK,YAAY,MAAMwV,IAAe;AAErD,WAAO;AAAA,MACL,GAAG,KAAK,IAAI,KAAK,cAAc,QAAQ;AAAA,MACvC,GAAG,KAAK,IAAIxV,IAAS;AAAA,MACrB,OAAO,KAAK,cAAc;AAAA,MAC1B,QAAAA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,uBAAoC;AAElC,UAAMyG,IAAQ,KAAK,KAAK,MAAM,EAAE,GAC1B4N,IAAa5N,EAAM,IAAI,CAAC+C,MAASM,GAAiBN,GAAM,KAAK,UAAU,KAAK,UAAU,CAAC,GACvF3C,IAAawN,EAAW,OAAO,CAAC7H,GAAK8H,MAAM9H,IAAM8H,GAAG,CAAC,GAErDC,IAAa,KAIb1Z,IADa,KAAK,WACQ;AAEhC,QAAImY,IAAO,OACPC,IAAO,QACPC,IAAO,OACPC,IAAO,QAGPqB,IAAW,CAAC3N,IAAa;AAC7B,IAAAJ,EAAM,QAAQ,CAACgO,GAAOvmB,MAAM;AAC1B,YAAMwe,IAAY2H,EAAWnmB,CAAC,GACxBqjB,IAAUiD,IAAW9H,IAAY,GACjCgI,IAAcnD,KAAW,KAAK,cAAc,QAAQ,IAGpDmE,IAAqB,KAAK,IAAIhB,CAAW,GACzCiB,IAAgB,KAAK,cAAc,YAAYD,GAG/Cf,IAAQgB,IAAgB,KAAK,WAAW,KAAK,IAAI,KAAK,cAAc,YAAY,KAAK,KAAKjB,CAAW,GAGrGkB,IACJD,IACA,KAAK,cAAc,YACnB,KAAK,KACL,KAAK,IAAI,KAAK,cAAc,YAAY,KAAK,KAAKjB,CAAW,GACzDmB,IACJ,KAAK,cAAc,YACnB,KAAK,KAAKnB,CAAW,IACrB,KAAK,IAAI,KAAK,cAAc,YAAY,KAAK,KAAKA,CAAW,GACzDE,IAAQgB,IAAeC;AAW7B,MARgB;AAAA,QACd,EAAE,GAAG,CAACnJ,IAAY,GAAG,GAAG,CAAC7R,EAAA;AAAA;AAAA,QACzB,EAAE,GAAG6R,IAAY,GAAG,GAAG,CAAC7R,EAAA;AAAA;AAAA,QACxB,EAAE,GAAG,CAAC6R,IAAY,GAAG,GAAG7R,EAAA;AAAA;AAAA,QACxB,EAAE,GAAG6R,IAAY,GAAG,GAAG7R,EAAA;AAAA;AAAA,MAAW,EAI5B,QAAQ,CAACwD,MAAW;AAE1B,cAAMwW,IAAUxW,EAAO,GACjByW,IAAUzW,EAAO,IAAIuW,IAAQL,IAAalW,EAAO,GAGjD7L,IAAS+e,IAAUsD,GACnBpiB,IAASkiB,IAAQG;AAEvB,QAAA9B,IAAO,KAAK,IAAIA,GAAMxgB,CAAM,GAC5BygB,IAAO,KAAK,IAAIA,GAAMzgB,CAAM,GAC5B0gB,IAAO,KAAK,IAAIA,GAAMzgB,CAAM,GAC5B0gB,IAAO,KAAK,IAAIA,GAAM1gB,CAAM;AAAA,MAC9B,CAAC,GAED+hB,KAAY9H;AAAA,IACd,CAAC;AAGD,UAAMwC,IAAU;AAChB,WAAA8D,KAAQ9D,GACR+D,KAAQ/D,GACRgE,KAAQhE,GACRiE,KAAQjE,GAED;AAAA,MACL,GAAG,KAAK,IAAI8D;AAAA,MACZ,GAAG,KAAK,IAAIE;AAAA,MACZ,OAAOD,IAAOD;AAAA,MACd,QAAQG,IAAOD;AAAA,IAAA;AAAA,EAEnB;AAAA,EAGA,OAAOxX,GAAsBhM,GAAkBwW,GAAoBvN,GAAqC;AACtG,UAAMmV,IAAiBpS,EAAO,SAAS,KAAK,KAAKA,EAAO,SAAS,QAAQ,GACnEqS,IAAerS,EAAO,SAAS,QAAQ,GACvCoa,IAAgBnd,EAAU;AAEhC,QAAImV,GAAgB;AAElB,YAAMzN,IAAQ3Q,IAAWiJ,EAAU,OAE7BkN,IAAclN,EAAU,WAAY0H;AAC1C,WAAK,YAAYwF,CAAW;AAE5B,YAAM4H,IAAcqI,EAAc,QAAQzV;AAC1C,WAAK,cAAc,QAAQ,KAAK,IAAI,IAAIoN,CAAW;AAAA,IACrD,WAAWM,MAELrS,MAAW,iBAAiBA,MAAW,iBAAgB;AACzD,WAAK,WAAW/C,EAAU,UAC1B,KAAK,cAAc,QAAQ,KAAK,IAAI,IAAIjJ,CAAQ;AAGhD,YAAMslB,IAAatlB,IAAWiJ,EAAU,OAClCjH,IAAe,KAAK,WAAW,KAAK,KAAM,KAC1CC,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW;AAEhC,UAAIgK,MAAW,eAAe;AAC5B,cAAMuZ,IAAc,CAACD,IAAa;AAClC,aAAK,IAAIrc,EAAU,IAAIsc,IAActjB,GACrC,KAAK,IAAIgH,EAAU,IAAIsc,IAAcrjB;AAAA,MACvC,WAAW8J,MAAW,gBAAgB;AACpC,cAAMuZ,IAAcD,IAAa;AACjC,aAAK,IAAIrc,EAAU,IAAIsc,IAActjB,GACrC,KAAK,IAAIgH,EAAU,IAAIsc,IAAcrjB;AAAA,MACvC;AAAA,IACF;AAAA,EAEJ;AAAA,EAEA,oBAAoC;AAElC,WAAO,CAAC,YAAY,aAAa,eAAe,cAAc;AAAA,EAChE;AAAA,EAEA,SAA4B;AAC1B,WAAO;AAAA,MACL,GAAG,MAAM,OAAA;AAAA,MACT,eAAe;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA,QACN,WAAW,KAAK,cAAc;AAAA,QAC9B,WAAW,KAAK,cAAc;AAAA,QAC9B,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AAAA,EAES,wBAA4C;AACnD,UAAM5C,IAAO,KAAK,eAAA;AAClB,WAAO;AAAA,MACL,GAAG,MAAM,sBAAA;AAAA,MACT,OAAOA,EAAK;AAAA,MACZ,QAAQA,EAAK;AAAA,MACb,eAAe;AAAA,QACb,MAAM;AAAA,QACN,WAAW,KAAK,cAAc;AAAA,QAC9B,WAAW,KAAK,cAAc;AAAA,QAC9B,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AACF;AC5LO,MAAM+mB,WAAsBxQ,EAAY;AAAA,EAG7C,YAAY3W,IAAqC,IAAI;AACnD,UAAMA,CAAM,GACZ,KAAK,gBAAgB;AAIrB,UAAMgC,IAAOhC,EAAO;AACpB,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,aAAYgC,KAAA,gBAAAA,EAAM,gBAAeA,KAAA,gBAAAA,EAAwD,gBAAe;AAAA,MACxG,QAAOA,KAAA,gBAAAA,EAAM,UAAS;AAAA,IAAA;AAAA,EAE1B;AAAA,EAEA,iBAA8B;AAC5B,UAAMolB,IAAgB,KAAK,IAAI,KAAK,cAAc,UAAU,GACtDhW,IAAS,KAAK,YAAY,MAAMgW,IAAgB;AAEtD,WAAO;AAAA,MACL,GAAG,KAAK,IAAI,KAAK,cAAc,QAAQ;AAAA,MACvC,GAAG,KAAK,IAAIhW,IAAS;AAAA,MACrB,OAAO,KAAK,cAAc;AAAA,MAC1B,QAAAA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,uBAAoC;AAGlC,UAAMD,IADkB+J,GAAiB,KAAK,MAAM,KAAK,UAAU,KAAK,UAAU,GAE5E9J,IAAS,KAAK,WAAW,KAIzBiW,IAAa,CAAC,KAAK,cAAc,aAAa,KAAK,KAAM,GACzDC,IAAQ,KAAK,IAAID,CAAS,GAG1BlX,IAAU;AAAA,MACd,EAAE,GAAG,CAACgB,IAAQ,GAAG,GAAG,CAACC,IAAS,EAAA;AAAA;AAAA,MAC9B,EAAE,GAAGD,IAAQ,GAAG,GAAG,CAACC,IAAS,EAAA;AAAA;AAAA,MAC7B,EAAE,GAAG,CAACD,IAAQ,GAAG,GAAGC,IAAS,EAAA;AAAA;AAAA,MAC7B,EAAE,GAAGD,IAAQ,GAAG,GAAGC,IAAS,EAAA;AAAA;AAAA,IAAE;AAGhC,QAAIgT,IAAO,OACPC,IAAO,QACPC,IAAO,OACPC,IAAO;AAGX,IAAApU,EAAQ,QAAQ,CAACV,MAAW;AAE1B,YAAMwW,IAAUxW,EAAO,IAAI6X,IAAQ7X,EAAO,GACpCyW,IAAUzW,EAAO;AAEvB,MAAA2U,IAAO,KAAK,IAAIA,GAAM6B,CAAO,GAC7B5B,IAAO,KAAK,IAAIA,GAAM4B,CAAO,GAC7B3B,IAAO,KAAK,IAAIA,GAAM4B,CAAO,GAC7B3B,IAAO,KAAK,IAAIA,GAAM2B,CAAO;AAAA,IAC/B,CAAC;AAGD,UAAM5F,IAAU;AAChB,WAAA8D,KAAQ9D,GACR+D,KAAQ/D,GACRgE,KAAQhE,GACRiE,KAAQjE,GAED;AAAA,MACL,GAAG,KAAK,IAAI8D;AAAA,MACZ,GAAG,KAAK,IAAIE;AAAA,MACZ,OAAOD,IAAOD;AAAA,MACd,QAAQG,IAAOD;AAAA,IAAA;AAAA,EAEnB;AAAA,EAGA,OAAOxX,GAAsBhM,GAAkBwW,GAAoBvN,GAAqC;AACtG,UAAMmV,IAAiBpS,EAAO,SAAS,KAAK,KAAKA,EAAO,SAAS,QAAQ,GACnEqS,IAAerS,EAAO,SAAS,QAAQ,GACvCya,IAAgBxd,EAAU;AAEhC,QAAImV,GAAgB;AAElB,YAAMzN,IAAQ3Q,IAAWiJ,EAAU,OAE7BkN,IAAclN,EAAU,WAAY0H;AAC1C,WAAK,YAAYwF,CAAW;AAE5B,YAAM4H,IAAc0I,EAAc,QAAQ9V;AAC1C,WAAK,cAAc,QAAQ,KAAK,IAAI,IAAIoN,CAAW;AAAA,IACrD,WAAWM,MAELrS,MAAW,iBAAiBA,MAAW,iBAAgB;AACzD,WAAK,WAAW/C,EAAU,UAC1B,KAAK,cAAc,QAAQ,KAAK,IAAI,IAAIjJ,CAAQ;AAEhD,YAAMslB,IAAatlB,IAAWymB,EAAc,OACtCzkB,IAAe,KAAK,WAAW,KAAK,KAAM,KAC1CC,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW;AAEhC,UAAIgK,MAAW,eAAe;AAC5B,cAAMuZ,IAAc,CAACD,IAAa;AAClC,aAAK,IAAIrc,EAAU,IAAIsc,IAActjB,GACrC,KAAK,IAAIgH,EAAU,IAAIsc,IAAcrjB;AAAA,MACvC,WAAW8J,MAAW,gBAAgB;AACpC,cAAMuZ,IAAcD,IAAa;AACjC,aAAK,IAAIrc,EAAU,IAAIsc,IAActjB,GACrC,KAAK,IAAIgH,EAAU,IAAIsc,IAAcrjB;AAAA,MACvC;AAAA,IACF;AAAA,EAEJ;AAAA,EAEA,oBAAoC;AAGlC,WAAO,CAAC,YAAY,aAAa,eAAe,cAAc;AAAA,EAChE;AAAA,EAEA,SAA4B;AAC1B,WAAO;AAAA,MACL,GAAG,MAAM,OAAA;AAAA,MACT,eAAe;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AAAA,EAES,wBAA4C;AACnD,UAAM5C,IAAO,KAAK,eAAA;AAClB,WAAO;AAAA,MACL,GAAG,MAAM,sBAAA;AAAA,MACT,OAAOA,EAAK;AAAA,MACZ,QAAQA,EAAK;AAAA,MACb,eAAe;AAAA,QACb,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AACF;ACvJO,MAAMonB,WAAwB7Q,EAAY;AAAA,EAG/C,YAAY3W,IAAuC,IAAI;AACrD,UAAMA,CAAM,GACZ,KAAK,gBAAgB;AAKrB,UAAMgC,IAAOhC,EAAO;AACpB,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,cAAagC,KAAA,gBAAAA,EAAM,gBAAe;AAAA,MAClC,QAAOA,KAAA,gBAAAA,EAAM,UAAS;AAAA,IAAA;AAAA,EAE1B;AAAA,EAEA,iBAA8B;AAC5B,UAAMylB,IAAY,KAAK,cAAc,cAAc,KAAK,KAAM,KACxDtW,IAAQ,KAAK,cAAc,OAG3BuW,IAAOvW,IAAQ,KAAK,IAAIsW,CAAQ,GAGhCrW,IAAS,KAAK,IAAIsW,CAAI,IAAI,KAAK,WAAW,KAG1ClC,IAAUkC,IAAO,IAAI,CAACtW,IAAS;AAErC,WAAO;AAAA,MACL,GAAG,KAAK,IAAID,IAAQ;AAAA,MACpB,GAAG,KAAK,IAAIqU;AAAA,MACZ,OAAArU;AAAA,MACA,QAAAC;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,uBAAoC;AAGlC,UAAMyG,IAAQ,KAAK,KAAK,MAAM,EAAE,GAC1B4N,IAAa5N,EAAM,IAAI,CAAC+C,MAASM,GAAiBN,GAAM,KAAK,UAAU,KAAK,UAAU,CAAC,GACvF3C,IAAawN,EAAW,OAAO,CAAC7H,GAAK8H,MAAM9H,IAAM8H,GAAG,CAAC,GAErD+B,IAAY,KAAK,cAAc,cAAc,KAAK,KAAM,KACxDzB,IAAQ,KAAK,IAAIyB,CAAQ,GACzB9B,IAAa,GAIb1Z,IADa,KAAK,WACQ;AAEhC,QAAImY,IAAO,OACPC,IAAO,QACPC,IAAO,OACPC,IAAO,QAGPqB,IAAW,CAAC3N,IAAa;AAC7B,IAAAJ,EAAM,QAAQ,CAACgO,GAAOvmB,MAAM;AAC1B,YAAMwe,IAAY2H,EAAWnmB,CAAC,GACxBqjB,IAAUiD,IAAW9H,IAAY,GAGjCiI,IAAQpD,IAAUqD;AAWxB,MARgB;AAAA,QACd,EAAE,GAAG,CAAClI,IAAY,GAAG,GAAG,CAAC7R,EAAA;AAAA;AAAA,QACzB,EAAE,GAAG6R,IAAY,GAAG,GAAG,CAAC7R,EAAA;AAAA;AAAA,QACxB,EAAE,GAAG,CAAC6R,IAAY,GAAG,GAAG7R,EAAA;AAAA;AAAA,QACxB,EAAE,GAAG6R,IAAY,GAAG,GAAG7R,EAAA;AAAA;AAAA,MAAW,EAI5B,QAAQ,CAACwD,MAAW;AAE1B,cAAMwW,IAAUxW,EAAO,GACjByW,IAAUzW,EAAO,IAAIuW,IAAQL,IAAalW,EAAO,GAGjD7L,IAAS+e,IAAUsD,GACnBpiB,IAASkiB,IAAQG;AAEvB,QAAA9B,IAAO,KAAK,IAAIA,GAAMxgB,CAAM,GAC5BygB,IAAO,KAAK,IAAIA,GAAMzgB,CAAM,GAC5B0gB,IAAO,KAAK,IAAIA,GAAMzgB,CAAM,GAC5B0gB,IAAO,KAAK,IAAIA,GAAM1gB,CAAM;AAAA,MAC9B,CAAC,GAED+hB,KAAY9H;AAAA,IACd,CAAC;AAGD,UAAMwC,IAAU;AAChB,WAAA8D,KAAQ9D,GACR+D,KAAQ/D,GACRgE,KAAQhE,GACRiE,KAAQjE,GAED;AAAA,MACL,GAAG,KAAK,IAAI8D;AAAA,MACZ,GAAG,KAAK,IAAIE;AAAA,MACZ,OAAOD,IAAOD;AAAA,MACd,QAAQG,IAAOD;AAAA,IAAA;AAAA,EAEnB;AAAA,EAGA,OAAOxX,GAAsBhM,GAAkBwW,GAAoBvN,GAAqC;AACtG,UAAMmV,IAAiBpS,EAAO,SAAS,KAAK,KAAKA,EAAO,SAAS,QAAQ,GACnEqS,IAAerS,EAAO,SAAS,QAAQ,GACvC6a,IAAkB5d,EAAU;AAElC,QAAImV,GAAgB;AAElB,YAAMzN,IAAQ3Q,IAAWiJ,EAAU,OAG7BkN,IAAclN,EAAU,WAAY0H;AAC1C,WAAK,YAAYwF,CAAW;AAG5B,YAAM4H,IAAc8I,EAAgB,QAAQlW;AAC5C,WAAK,cAAc,QAAQ,KAAK,IAAI,IAAIoN,CAAW;AAAA,IACrD,WAAWM,MAELrS,MAAW,iBAAiBA,MAAW,iBAAgB;AAEzD,WAAK,WAAW/C,EAAU,UAG1B,KAAK,cAAc,QAAQ,KAAK,IAAI,IAAIjJ,CAAQ;AAIhD,YAAMslB,IAAatlB,IAAWiJ,EAAU,OAClCjH,IAAe,KAAK,WAAW,KAAK,KAAM,KAC1CC,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW;AAEhC,UAAIgK,MAAW,eAAe;AAE5B,cAAMuZ,IAAc,CAACD,IAAa;AAClC,aAAK,IAAIrc,EAAU,IAAIsc,IAActjB,GACrC,KAAK,IAAIgH,EAAU,IAAIsc,IAAcrjB;AAAA,MACvC,WAAW8J,MAAW,gBAAgB;AAEpC,cAAMuZ,IAAcD,IAAa;AACjC,aAAK,IAAIrc,EAAU,IAAIsc,IAActjB,GACrC,KAAK,IAAIgH,EAAU,IAAIsc,IAAcrjB;AAAA,MACvC;AAAA,IACF;AAAA,EAEJ;AAAA,EAEA,oBAAoC;AAElC,WAAO,CAAC,YAAY,aAAa,eAAe,cAAc;AAAA,EAChE;AAAA,EAEA,SAA8B;AAC5B,WAAO;AAAA,MACL,GAAG,MAAM,OAAA;AAAA,MACT,eAAe;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA,QACN,aAAa,KAAK,cAAc;AAAA,QAChC,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AAAA,EAES,wBAA4C;AACnD,UAAM5C,IAAO,KAAK,eAAA;AAClB,WAAO;AAAA,MACL,GAAG,MAAM,sBAAA;AAAA,MACT,OAAOA,EAAK;AAAA,MACZ,QAAQA,EAAK;AAAA,MACb,eAAe;AAAA,QACb,MAAM;AAAA,QACN,aAAa,KAAK,cAAc;AAAA,QAChC,OAAO,KAAK,cAAc;AAAA,MAAA;AAAA,IAC5B;AAAA,EAEJ;AACF;ACrEO,MAAMwnB,WAAoB,MAAM;AAAA,EAIrC,YAAYC,GAAuB/f,GAAiB9F,IAAsC,CAAA,GAAI;AAC5F,UAAM,YAAY6lB,CAAI,KAAK/f,CAAO,EAAE,GACpC,KAAK,OAAO,eACZ,KAAK,OAAO+f,GACZ,KAAK,OAAO,EAAE,MAAAA,GAAM,GAAG7lB,EAAA;AAAA,EACzB;AACF;AAEO,SAAS8lB,GAAcC,GAAkC;AAC9D,SAAOA,aAAe,SAASA,EAAI,SAAS,iBAAiB,UAAUA;AACzE;AAIO,SAASC,KAAuB;A7BjJvC,MAAAtkB;A6BkJE,SAAO,OAAOtF,KAAY,SAAeA,IAAAA,GAAQ,QAARA,gBAAAA,EAAa,cAAa;AACrE;ACvHO,MAAM6pB,KAA2B,WAElCC,KAAa,CAAC,SAAS,UAAU,QAAQ;AAQ/C,SAASC,EAAYrgB,GAAiB9F,GAA4C;AAChF,SAAO,IAAI4lB,GAAY,kBAAkB9f,GAAS9F,CAAI;AACxD;AAMO,SAASomB,GAAqBC,GAA0C;AAC7E,MAAI,OAAOA,KAAS,YAAYA,MAAS;AACvC,UAAMF;AAAA,MACJ,oFAAoF,KAAK,UAAUE,CAAI,CAAC;AAAA,MACxG,EAAE,MAAAA,EAAA;AAAA,IAAK;AAGX,QAAM5jB,IAAI4jB;AACV,MAAI5jB,EAAE,SAAS,SAAS;AACtB,QAAI,OAAOA,EAAE,SAAU;AACrB,YAAM0jB,EAAY,4CAA4C,KAAK,UAAU1jB,EAAE,KAAK,CAAC,KAAK,EAAE,MAAA4jB,EAAA,CAAM;AAEpG;AAAA,EACF;AACA,MAAI5jB,EAAE,SAAS,YAAYA,EAAE,SAAS,UAAU;AAC9C,QAAIA,EAAE,SAAS,aAAa,OAAOA,EAAE,SAAU,YAAY,CAAC,OAAO,SAASA,EAAE,KAAK;AACjF,YAAM0jB;AAAA,QACJ,oFAAoF,KAAK,UAAU1jB,EAAE,KAAK,CAAC;AAAA,QAC3G,EAAE,MAAA4jB,EAAA;AAAA,MAAK;AAGX,QAAI,CAAC,MAAM,QAAQ5jB,EAAE,KAAK,KAAKA,EAAE,MAAM,SAAS;AAC9C,YAAM0jB;AAAA,QACJ,GAAG1jB,EAAE,IAAI,qCAAqC,MAAM,QAAQA,EAAE,KAAK,IAAIA,EAAE,MAAM,SAAS,KAAK,UAAUA,EAAE,KAAK,CAAC;AAAA,QAC/G,EAAE,MAAA4jB,EAAA;AAAA,MAAK;AAGX,eAAWC,KAAQ7jB,EAAE,OAAuD;AAC1E,UACE,OAAO6jB,KAAS,YAChBA,MAAS,QACT,OAAOA,EAAK,UAAW,YACvB,CAAC,OAAO,SAASA,EAAK,MAAM,KAC5BA,EAAK,SAAS,KACdA,EAAK,SAAS;AAEd,cAAMH;AAAA,UACJ,+DAA+D,KAAK,UAAUG,CAAI,CAAC;AAAA,UACnF,EAAE,MAAAD,GAAM,MAAAC,EAAA;AAAA,QAAK;AAGjB,UAAI,OAAOA,EAAK,SAAU;AACxB,cAAMH,EAAY,+CAA+C,KAAK,UAAUG,CAAI,CAAC,KAAK,EAAE,MAAAD,GAAM,MAAAC,EAAA,CAAM;AAAA,IAE5G;AACA;AAAA,EACF;AACA,QAAMH;AAAA,IACJ,2BAA2B,KAAK,UAAU1jB,EAAE,IAAI,CAAC,kBAAkB,KAAK,UAAUyjB,EAAU,CAAC;AAAA,IAC7F,EAAE,MAAAG,GAAM,YAAYH,GAAA;AAAA,EAAW;AAEnC;AAGO,SAASK,GAAeF,GAA4B;AACzD,UAAQA,EAAK,MAAA;AAAA,IACX,KAAK;AACH,aAAO,EAAE,MAAM,SAAS,OAAOA,EAAK,MAAA;AAAA,IACtC,KAAK;AACH,aAAO,EAAE,MAAM,UAAU,OAAOA,EAAK,OAAO,OAAOA,EAAK,MAAM,IAAI,CAACtU,OAAO,EAAE,GAAGA,EAAA,EAAI,EAAA;AAAA,IACrF,KAAK;AACH,aAAO,EAAE,MAAM,UAAU,OAAOsU,EAAK,MAAM,IAAI,CAACtU,OAAO,EAAE,GAAGA,EAAA,EAAI,EAAA;AAAA,EAAE;AAExE;AAEA,SAASyU,GAASC,GAA0BC,GAAuC;AACjF,aAAWJ,KAAQI;AAGjB,IAAAD,EAAS,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,GAAGH,EAAK,MAAM,CAAC,GAAGA,EAAK,KAAK;AAEzE,SAAOG;AACT;AAOO,SAASE,GACdplB,GACAqlB,GACyB;AACzB,QAAM,EAAE,MAAAP,MAASO;AACjB,MAAI,CAACP,EAAM,QAAOO,EAAc,aAAaX;AAC7C,MAAII,EAAK,SAAS,QAAS,QAAOA,EAAK;AAEvC,QAAMlX,IAAQyX,EAAc,SAAS,KAC/BxX,IAASwX,EAAc,UAAU;AAEvC,MAAIP,EAAK,SAAS,UAAU;AAC1B,UAAMQ,IAASR,EAAK,QAAQ,KAAK,KAAM,KAGjCplB,IAAK,KAAK,IAAI4lB,CAAK,GACnB3lB,IAAK,CAAC,KAAK,IAAI2lB,CAAK,GAEpBC,KAAc,KAAK,IAAI3X,IAAQlO,CAAE,IAAI,KAAK,IAAImO,IAASlO,CAAE,KAAK;AACpE,WAAOslB;AAAA,MACLjlB,EAAI,qBAAqB,CAACN,IAAK6lB,GAAY,CAAC5lB,IAAK4lB,GAAY7lB,IAAK6lB,GAAY5lB,IAAK4lB,CAAU;AAAA,MAC7FT,EAAK;AAAA,IAAA;AAAA,EAET;AAGA,QAAM5b,IAAS,KAAK,KAAK0E,IAAQA,IAAQC,IAASA,CAAM,IAAI;AAC5D,SAAOoX,GAASjlB,EAAI,qBAAqB,GAAG,GAAG,GAAG,GAAG,GAAGkJ,CAAM,GAAG4b,EAAK,KAAK;AAC7E;AAMO,SAASU,GAAeV,GAA6BW,GAAgC;AAC1F,MAAI,CAACX,EAAM,QAAOW,KAAiBf;AACnC,UAAQI,EAAK,MAAA;AAAA,IACX,KAAK;AACH,aAAOA,EAAK;AAAA,IACd,KAAK;AACH,aAAO,mBAAmBA,EAAK,KAAK,QAAQA,EAAK,MAC9C,IAAI,CAACtU,MAAM,GAAGA,EAAE,KAAK,IAAIA,EAAE,SAAS,GAAG,GAAG,EAC1C,KAAK,IAAI,CAAC;AAAA,IACf,KAAK;AACH,aAAO,2BAA2BsU,EAAK,MAAM,IAAI,CAACtU,MAAM,GAAGA,EAAE,KAAK,IAAIA,EAAE,SAAS,GAAG,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,EAAA;AAEzG;ACxJO,MAAMkV,WAAqBvmB,GAAY;AAAA,EAI5C,YAAY1C,IAAsC,IAAI;A/BvBxD,QAAA0D,GAAAuG,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAwe;A+BwBI,UAAMlpB,CAAM,GACZ,KAAK,gBAAgB;AAKrB,UAAMqoB,KAAO3kB,IAAA1D,EAAO,kBAAP,gBAAA0D,EAAsB;AACnC,IAAI2kB,MAAS,UAAWD,GAAqBC,CAAI;AAGjD,UAAMc,IAA8B;AACpC,SAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,aAAWlf,IAAAjK,EAAO,kBAAP,gBAAAiK,EAAsB,cAAakf;AAAA,MAC9C,SAAOjf,IAAAlK,EAAO,kBAAP,gBAAAkK,EAAsB,UAAS;AAAA,MACtC,UAAQC,IAAAnK,EAAO,kBAAP,gBAAAmK,EAAsB,WAAU;AAAA,MACxC,gBAAcC,IAAApK,EAAO,kBAAP,gBAAAoK,EAAsB,iBAAgB;AAAA;AAAA,MACpD,UAASC,IAAArK,EAAO,kBAAP,gBAAAqK,EAAsB;AAAA,MAC/B,UAASC,IAAAtK,EAAO,kBAAP,gBAAAsK,EAAsB;AAAA,MAC/B,SAAOC,IAAAvK,EAAO,kBAAP,gBAAAuK,EAAsB,UAAS;AAAA,MACtC,UAAQC,IAAAxK,EAAO,kBAAP,gBAAAwK,EAAsB,WAAU;AAAA,MACxC,eAAaC,IAAAzK,EAAO,kBAAP,gBAAAyK,EAAsB,gBAAe;AAAA,MAClD,aAAWC,IAAA1K,EAAO,kBAAP,gBAAA0K,EAAsB,cAAa5F,GAAA;AAAA,MAC9C,eAAaokB,IAAAlpB,EAAO,kBAAP,gBAAAkpB,EAAsB,gBAAe;AAAA;AAAA,MAElD,GAAIb,MAAS,UAAa,EAAE,MAAME,GAAeF,CAAI,EAAA;AAAA,IAAE;AAAA,EAE3D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAA8B;AAC5B,WAAO;AAAA,MACL,GAAG,KAAK,IAAI,KAAK,cAAc,QAAQ;AAAA,MACvC,GAAG,KAAK,IAAI,KAAK,cAAc,SAAS;AAAA,MACxC,OAAO,KAAK,cAAc;AAAA,MAC1B,QAAQ,KAAK,cAAc;AAAA,IAAA;AAAA,EAE/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,uBAAoC;AAClC,UAAM,EAAE,WAAAe,GAAW,OAAAjY,GAAO,QAAAC,EAAA,IAAW,KAAK;AAI1C,QAAIgY,MAAc,UAAU;AAC1B,YAAMhkB,IAAI,KAAK,IAAI+L,GAAOC,CAAM,IAAI;AACpC,aAAO;AAAA,QACL,GAAG,KAAK,IAAIhM;AAAA,QACZ,GAAG,KAAK,IAAIA;AAAA,QACZ,OAAOA,IAAI;AAAA,QACX,QAAQA,IAAI;AAAA,MAAA;AAAA,IAEhB;AAMA,QAAIgkB,MAAc,aAAaA,MAAc,QAAQ;AACnD,YAAMhkB,IAAI,KAAK,IAAI+L,GAAOC,CAAM,IAAI;AACpC,UAAIgT,IAAO,OAAUE,IAAO,OAAUD,IAAO,QAAWE,IAAO;AAE/D,UAAI6E,MAAc,WAAW;AAC3B,cAAMC,IAAQ,KAAK,cAAc,SAAS;AAC1C,iBAAS/pB,IAAI,GAAGA,IAAI+pB,GAAO/pB,KAAK;AAC9B,gBAAMgkB,IAAShkB,IAAI,IAAI,KAAK,KAAM+pB,IAAQ,KAAK,KAAK,GAC9CppB,IAAKmF,IAAI,KAAK,IAAIke,CAAK,GACvBpjB,IAAKkF,IAAI,KAAK,IAAIke,CAAK;AAC7B,UAAIrjB,IAAKmkB,MAAMA,IAAOnkB,IAClBA,IAAKokB,MAAMA,IAAOpkB,IAClBC,IAAKokB,MAAMA,IAAOpkB,IAClBA,IAAKqkB,MAAMA,IAAOrkB;AAAA,QACxB;AAAA,MACF,OAAO;AACL,cAAMopB,IAAS,KAAK,cAAc,UAAU,GACtCnF,IAAc/e,GACd8e,IAAcC,KAAe,KAAK,cAAc,eAAe;AACrE,iBAAS7kB,IAAI,GAAGA,IAAIgqB,IAAS,GAAGhqB,KAAK;AACnC,gBAAMgkB,IAAShkB,IAAI,KAAK,KAAMgqB,IAAS,KAAK,KAAK,GAC3CxlB,IAAMxE,IAAI,MAAM,IAAI6kB,IAAcD,GAClCjkB,IAAK6D,IAAM,KAAK,IAAIwf,CAAK,GACzBpjB,IAAK4D,IAAM,KAAK,IAAIwf,CAAK;AAC/B,UAAIrjB,IAAKmkB,MAAMA,IAAOnkB,IAClBA,IAAKokB,MAAMA,IAAOpkB,IAClBC,IAAKokB,MAAMA,IAAOpkB,IAClBA,IAAKqkB,MAAMA,IAAOrkB;AAAA,QACxB;AAAA,MACF;AAEA,aAAO;AAAA,QACL,GAAG,KAAK,IAAIkkB;AAAA,QACZ,GAAG,KAAK,IAAIE;AAAA,QACZ,OAAOD,IAAOD;AAAA,QACd,QAAQG,IAAOD;AAAA,MAAA;AAAA,IAEnB;AAIA,WAAO,KAAK,eAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA2B;AACzB,WAAO,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO/gB,GAA+B6I,IAAuB,IAAOC,IAAsB,IAAa;A/B3JzG,QAAA3I;A+B4JI,UAAM2c,IAAiB,KAAK,WAAW,GACjChK,IAAY,CAAC,GAAC3S,IAAA,KAAK,WAAL,QAAAA,EAAa;AAKjC,QAAI2c,IAAiB,KAAKhK,GAAW;AACnC,WAAK,oBAAoB9S,GAAK8c,CAAc;AAC5C;AAAA,IACF;AAEA,IAAA9c,EAAI,KAAA,GAGJA,EAAI,UAAU,KAAK,GAAG,KAAK,CAAC,GAC5BA,EAAI,OAAOjB,EAAc,UAAU,KAAK,QAAQ,CAAC,GAKjDiB,EAAI,YAAYolB,GAAqBplB,GAAK,KAAK,aAAa;AAC5D,UAAMgmB,IAAc,KAAK,cAAc,eAAe;AACtD,IAAAhmB,EAAI,cAAc8c,IAAiBkJ,GAGnC,KAAK,YAAYhmB,CAAG,GAEpBA,EAAI,QAAA,GAGA8S,KACF,KAAK,aAAa9S,GAAK8c,CAAc;AAAA,EAEzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoB9c,GAA+B8c,GAA8B;A/BnM3F,QAAA3c;A+BoMI,UAAM,EAAE,OAAAyN,GAAO,QAAAC,EAAA,IAAW,KAAK,eAEzBkP,OADc5c,IAAA,KAAK,WAAL,gBAAAA,EAAa,UAAS,KACZ,GACxB6c,IAAO,KAAK,KAAKpP,IAAQmP,IAAU,CAAC,GACpCE,IAAO,KAAK,KAAKpP,IAASkP,IAAU,CAAC,GAErCG,IAAY,SAAS,cAAc,QAAQ;AACjD,IAAAA,EAAU,QAAQF,GAClBE,EAAU,SAASD;AACnB,UAAME,IAASD,EAAU,WAAW,IAAI;AACxC,QAAI,CAACC,GAAQ;AAEX,WAAK,aAAand,GAAK8c,CAAc;AACrC;AAAA,IACF;AAGA,IAAAK,EAAO,KAAA,GACPA,EAAO,UAAUH,IAAO,GAAGC,IAAO,CAAC,GAGnCE,EAAO,YAAYiI,GAAqBjI,GAAQ,KAAK,aAAa;AAClE,UAAM6I,IAAc,KAAK,cAAc,eAAe;AACtD,IAAA7I,EAAO,cAAc6I,GACrB7I,EAAO,UAAA,GACP,KAAK,eAAeA,CAAM,GAC1BA,EAAO,KAAA,GACPA,EAAO,QAAA,GAGPA,EAAO,KAAA,GACPA,EAAO,UAAUH,IAAO,GAAGC,IAAO,CAAC;AACnC,UAAMgJ,IAAS,KAAK;AACpB,IAAA9I,EAAO,cAAc8I,EAAO,SAAS,WACrC9I,EAAO,YAAY8I,EAAO,SAAS,GACnC9I,EAAO,UAAU8I,EAAO,WAAW,SACnC9I,EAAO,WAAW8I,EAAO,YAAY,SACrC9I,EAAO,cAAc8I,EAAO,WAAW,GACnCA,EAAO,aAAaA,EAAO,UAAU,SAAS,KAChD9I,EAAO,YAAY8I,EAAO,SAAS,GAErC9I,EAAO,UAAA,GACP,KAAK,eAAeA,CAAM,GAC1BA,EAAO,OAAA,GACPA,EAAO,QAAA,GAGPnd,EAAI,KAAA,GACJA,EAAI,UAAU,KAAK,GAAG,KAAK,CAAC,GAC5BA,EAAI,OAAOjB,EAAc,UAAU,KAAK,QAAQ,CAAC,GACjDiB,EAAI,cAAc8c,GAClB9c,EAAI,UAAUkd,GAAW,CAACF,IAAO,GAAG,CAACC,IAAO,CAAC,GAC7Cjd,EAAI,QAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAaA,GAA+B8c,GAA8B;A/B9PpF,QAAA3c;A+B+PI,IAAAH,EAAI,KAAA,GACJA,EAAI,UAAU,KAAK,GAAG,KAAK,CAAC,GAC5BA,EAAI,OAAOjB,EAAc,UAAU,KAAK,QAAQ,CAAC,GACjDiB,EAAI,YAAYolB,GAAqBplB,GAAK,KAAK,aAAa,GAC5DA,EAAI,cAAc8c,KAAkB,KAAK,cAAc,eAAe,IACtE,KAAK,YAAY9c,CAAG,GACpBA,EAAI,QAAA,IAEAG,IAAA,KAAK,WAAL,QAAAA,EAAa,WACf,KAAK,aAAaH,GAAK8c,CAAc;AAAA,EAEzC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe9c,GAAqC;AAC1D,UAAM,EAAE,WAAA6lB,GAAW,OAAAjY,GAAO,QAAAC,EAAA,IAAW,KAAK;AAE1C,YAAQgY,GAAA;AAAA,MACN,KAAK,aAAa;AAChB,cAAM5c,IAAe,KAAK,cAAc,gBAAgB,GAClDtO,IAAI,CAACiT,IAAQ,GACb1P,IAAI,CAAC2P,IAAS;AACpB,YAAI5E,IAAe,GAAG;AACpB,gBAAMC,IAAS,KAAK,IAAKD,IAAe,MAAO,KAAK,IAAI2E,GAAOC,CAAM,GAAGD,IAAQ,GAAGC,IAAS,CAAC;AAC7F,UAAA7N,EAAI,UAAUrF,GAAGuD,GAAG0P,GAAOC,GAAQ3E,CAAM;AAAA,QAC3C;AACE,UAAAlJ,EAAI,KAAKrF,GAAGuD,GAAG0P,GAAOC,CAAM;AAE9B;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,cAAM3E,IAAS,KAAK,IAAI0E,GAAOC,CAAM,IAAI;AACzC,QAAA7N,EAAI,IAAI,GAAG,GAAGkJ,GAAQ,GAAG,KAAK,KAAK,CAAC;AACpC;AAAA,MACF;AAAA,MACA,KAAK;AACH,QAAAlJ,EAAI,QAAQ,GAAG,GAAG4N,IAAQ,GAAGC,IAAS,GAAG,GAAG,GAAG,KAAK,KAAK,CAAC;AAC1D;AAAA,MACF,KAAK,YAAY;AACf,cAAMpF,IAAYmF,IAAQ,GACpBlF,IAAamF,IAAS;AAC5B,QAAA7N,EAAI,OAAO,GAAG,CAAC0I,CAAU,GACzB1I,EAAI,OAAOyI,GAAWC,CAAU,GAChC1I,EAAI,OAAO,CAACyI,GAAWC,CAAU,GACjC1I,EAAI,UAAA;AACJ;AAAA,MACF;AAAA,MACA,KAAK,WAAW;AACd,cAAM8lB,IAAQ,KAAK,cAAc,SAAS,GACpCjkB,IAAI,KAAK,IAAI+L,GAAOC,CAAM,IAAI;AACpC,iBAAS9R,IAAI,GAAGA,IAAI+pB,GAAO/pB,KAAK;AAC9B,gBAAMgkB,IAAShkB,IAAI,IAAI,KAAK,KAAM+pB,IAAQ,KAAK,KAAK,GAC9CppB,IAAKmF,IAAI,KAAK,IAAIke,CAAK,GACvBpjB,IAAKkF,IAAI,KAAK,IAAIke,CAAK;AAC7B,UAAIhkB,MAAM,IAAGiE,EAAI,OAAOtD,GAAIC,CAAE,IACzBqD,EAAI,OAAOtD,GAAIC,CAAE;AAAA,QACxB;AACA,QAAAqD,EAAI,UAAA;AACJ;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AACX,cAAM+lB,IAAS,KAAK,cAAc,UAAU,GACtCnF,IAAc,KAAK,IAAIhT,GAAOC,CAAM,IAAI,GACxC8S,IAAcC,KAAe,KAAK,cAAc,eAAe;AACrE,iBAAS7kB,IAAI,GAAGA,IAAIgqB,IAAS,GAAGhqB,KAAK;AACnC,gBAAMgkB,IAAShkB,IAAI,KAAK,KAAMgqB,IAAS,KAAK,KAAK,GAC3ClkB,IAAI9F,IAAI,MAAM,IAAI6kB,IAAcD,GAChCjkB,IAAKmF,IAAI,KAAK,IAAIke,CAAK,GACvBpjB,IAAKkF,IAAI,KAAK,IAAIke,CAAK;AAC7B,UAAIhkB,MAAM,IAAGiE,EAAI,OAAOtD,GAAIC,CAAE,IACzBqD,EAAI,OAAOtD,GAAIC,CAAE;AAAA,QACxB;AACA,QAAAqD,EAAI,UAAA;AACJ;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AACX,cAAMyI,IAAYmF,IAAQ,GACpBlF,IAAamF,IAAS;AAC5B,QAAA7N,EAAI,KAAK,CAACyI,GAAW,CAACC,GAAYkF,GAAOC,CAAM;AAC/C;AAAA,MACF;AAAA,MACA;AACE,QAAA7N,EAAI,KAAK,CAAC4N,IAAQ,GAAG,CAACC,IAAS,GAAGD,GAAOC,CAAM;AAAA,IAAA;AAAA,EAErD;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa7N,GAA+B8c,GAA8B;AAChF,IAAA9c,EAAI,KAAA,GAEJA,EAAI,UAAU,KAAK,GAAG,KAAK,CAAC,GAC5BA,EAAI,OAAOjB,EAAc,UAAU,KAAK,QAAQ,CAAC;AAEjD,UAAMknB,IAAS,KAAK;AACpB,IAAAjmB,EAAI,cAAcimB,EAAO,SAAS,WAClCjmB,EAAI,YAAYimB,EAAO,SAAS,GAChCjmB,EAAI,UAAUimB,EAAO,WAAW,SAChCjmB,EAAI,WAAWimB,EAAO,YAAY,SAClCjmB,EAAI,cAAc8c,KAAkBmJ,EAAO,WAAW,IAElDA,EAAO,aAAaA,EAAO,UAAU,SAAS,KAChDjmB,EAAI,YAAYimB,EAAO,SAAS;AAIlC,UAAM,EAAE,WAAAJ,GAAW,OAAAjY,GAAO,QAAAC,EAAA,IAAW,KAAK;AAG1C,YAFA7N,EAAI,UAAA,GAEI6lB,GAAA;AAAA,MACN,KAAK,aAAa;AAChB,cAAM5c,IAAe,KAAK,cAAc,gBAAgB,GAClDtO,IAAI,CAACiT,IAAQ,GACb1P,IAAI,CAAC2P,IAAS;AACpB,YAAI5E,IAAe,GAAG;AACpB,gBAAMC,IAAS,KAAK,IAAKD,IAAe,MAAO,KAAK,IAAI2E,GAAOC,CAAM,GAAGD,IAAQ,GAAGC,IAAS,CAAC;AAC7F,UAAA7N,EAAI,UAAUrF,GAAGuD,GAAG0P,GAAOC,GAAQ3E,CAAM;AAAA,QAC3C;AACE,UAAAlJ,EAAI,KAAKrF,GAAGuD,GAAG0P,GAAOC,CAAM;AAE9B;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,cAAM3E,IAAS,KAAK,IAAI0E,GAAOC,CAAM,IAAI;AACzC,QAAA7N,EAAI,IAAI,GAAG,GAAGkJ,GAAQ,GAAG,KAAK,KAAK,CAAC;AACpC;AAAA,MACF;AAAA,MACA,KAAK;AACH,QAAAlJ,EAAI,QAAQ,GAAG,GAAG4N,IAAQ,GAAGC,IAAS,GAAG,GAAG,GAAG,KAAK,KAAK,CAAC;AAC1D;AAAA,MACF,KAAK,YAAY;AACf,cAAMpF,IAAYmF,IAAQ,GACpBlF,IAAamF,IAAS;AAC5B,QAAA7N,EAAI,OAAO,GAAG,CAAC0I,CAAU,GACzB1I,EAAI,OAAOyI,GAAWC,CAAU,GAChC1I,EAAI,OAAO,CAACyI,GAAWC,CAAU,GACjC1I,EAAI,UAAA;AACJ;AAAA,MACF;AAAA,MACA,KAAK,WAAW;AACd,cAAM8lB,IAAQ,KAAK,cAAc,SAAS,GACpC5c,IAAS,KAAK,IAAI0E,GAAOC,CAAM,IAAI;AACzC,iBAAS9R,IAAI,GAAGA,IAAI+pB,GAAO/pB,KAAK;AAC9B,gBAAMgkB,IAAShkB,IAAI,IAAI,KAAK,KAAM+pB,IAAQ,KAAK,KAAK,GAC9CppB,IAAKwM,IAAS,KAAK,IAAI6W,CAAK,GAC5BpjB,IAAKuM,IAAS,KAAK,IAAI6W,CAAK;AAClC,UAAIhkB,MAAM,IAAGiE,EAAI,OAAOtD,GAAIC,CAAE,IACzBqD,EAAI,OAAOtD,GAAIC,CAAE;AAAA,QACxB;AACA,QAAAqD,EAAI,UAAA;AACJ;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AACX,cAAM+lB,IAAS,KAAK,cAAc,UAAU,GACtCnF,IAAc,KAAK,IAAIhT,GAAOC,CAAM,IAAI,GACxC8S,IAAcC,KAAe,KAAK,cAAc,eAAe;AACrE,iBAAS7kB,IAAI,GAAGA,IAAIgqB,IAAS,GAAGhqB,KAAK;AACnC,gBAAMgkB,IAAShkB,IAAI,KAAK,KAAMgqB,IAAS,KAAK,KAAK,GAC3ClkB,IAAI9F,IAAI,MAAM,IAAI6kB,IAAcD,GAChCjkB,IAAKmF,IAAI,KAAK,IAAIke,CAAK,GACvBpjB,IAAKkF,IAAI,KAAK,IAAIke,CAAK;AAC7B,UAAIhkB,MAAM,IAAGiE,EAAI,OAAOtD,GAAIC,CAAE,IACzBqD,EAAI,OAAOtD,GAAIC,CAAE;AAAA,QACxB;AACA,QAAAqD,EAAI,UAAA;AACJ;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AACX,cAAMyI,IAAYmF,IAAQ,GACpBlF,IAAamF,IAAS;AAC5B,QAAA7N,EAAI,KAAK,CAACyI,GAAW,CAACC,GAAYkF,GAAOC,CAAM;AAC/C;AAAA,MACF;AAAA,MACA;AACE,QAAA7N,EAAI,KAAK,CAAC4N,IAAQ,GAAG,CAACC,IAAS,GAAGD,GAAOC,CAAM;AAAA,IAAA;AAGnD,IAAA7N,EAAI,OAAA,GACJA,EAAI,QAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAYA,GAAqC;AACvD,UAAM,EAAE,WAAA6lB,GAAW,OAAAjY,GAAO,QAAAC,EAAA,IAAW,KAAK;AAI1C,YAFA7N,EAAI,UAAA,GAEI6lB,GAAA;AAAA,MACN,KAAK;AACH,aAAK,gBAAgB7lB,GAAK4N,GAAOC,CAAM;AACvC;AAAA,MACF,KAAK;AACH,aAAK,aAAa7N,GAAK,KAAK,IAAI4N,GAAOC,CAAM,IAAI,CAAC;AAClD;AAAA,MACF,KAAK;AACH,aAAK,cAAc7N,GAAK4N,IAAQ,GAAGC,IAAS,CAAC;AAC7C;AAAA,MACF,KAAK;AACH,aAAK,eAAe7N,GAAK4N,GAAOC,CAAM;AACtC;AAAA,MACF,KAAK;AACH,aAAK,cAAc7N,GAAK,KAAK,IAAI4N,GAAOC,CAAM,IAAI,CAAC;AACnD;AAAA,MACF,KAAK;AACH,aAAK,WAAW7N,GAAK,KAAK,IAAI4N,GAAOC,CAAM,IAAI,CAAC;AAChD;AAAA,MACF,KAAK;AACH,aAAK,WAAW7N,GAAK4N,GAAOC,CAAM;AAClC;AAAA;AAAA,MACF;AAEE,aAAK,gBAAgB7N,GAAK4N,GAAOC,CAAM;AAAA,IAAA;AAG3C,IAAA7N,EAAI,KAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgBA,GAA+B4N,GAAeC,GAAsB;AAC1F,UAAM5E,IAAe,KAAK,cAAc,gBAAgB,GAClDtO,IAAI,CAACiT,IAAQ,GACb1P,IAAI,CAAC2P,IAAS;AAEpB,QAAI5E,IAAe,GAAG;AACpB,YAAMC,IAAS,KAAK,IAAKD,IAAe,MAAO,KAAK,IAAI2E,GAAOC,CAAM,GAAGD,IAAQ,GAAGC,IAAS,CAAC;AAC7F,MAAA7N,EAAI,UAAUrF,GAAGuD,GAAG0P,GAAOC,GAAQ3E,CAAM;AAAA,IAC3C;AACE,MAAAlJ,EAAI,KAAKrF,GAAGuD,GAAG0P,GAAOC,CAAM;AAAA,EAEhC;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa7N,GAA+BkJ,GAAsB;AACxE,IAAAlJ,EAAI,IAAI,GAAG,GAAGkJ,GAAQ,GAAG,KAAK,KAAK,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAclJ,GAA+BkmB,GAAiBC,GAAuB;AAC3F,IAAAnmB,EAAI,QAAQ,GAAG,GAAGkmB,GAASC,GAAS,GAAG,GAAG,KAAK,KAAK,CAAC;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAenmB,GAA+B4N,GAAeC,GAAsB;AACzF,UAAMpF,IAAYmF,IAAQ,GACpBlF,IAAamF,IAAS;AAE5B,IAAA7N,EAAI,OAAO,GAAG,CAAC0I,CAAU,GACzB1I,EAAI,OAAOyI,GAAWC,CAAU,GAChC1I,EAAI,OAAO,CAACyI,GAAWC,CAAU,GACjC1I,EAAI,UAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAcA,GAA+BkJ,GAAsB;AACzE,UAAM4c,IAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,cAAc,SAAS,CAAC,CAAC,GAC/DM,IAAa,KAAK,KAAK,IAAKN,GAC5BrF,IAAa,CAAC,KAAK,KAAK;AAE9B,aAAS1kB,IAAI,GAAGA,KAAK+pB,GAAO/pB,KAAK;AAC/B,YAAMgkB,IAAQU,IAAa2F,IAAYrqB,GACjCpB,IAAI,KAAK,IAAIolB,CAAK,IAAI7W,GACtBhL,IAAI,KAAK,IAAI6hB,CAAK,IAAI7W;AAE5B,MAAInN,MAAM,IACRiE,EAAI,OAAOrF,GAAGuD,CAAC,IAEf8B,EAAI,OAAOrF,GAAGuD,CAAC;AAAA,IAEnB;AAEA,IAAA8B,EAAI,UAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAWA,GAA+B4gB,GAA2B;AAC3E,UAAMmF,IAAS,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,cAAc,UAAU,CAAC,CAAC,GACjEM,IAAmB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,cAAc,eAAe,GAAG,CAAC,GACrF1F,IAAcC,IAAcyF,GAC5BD,IAAY,KAAK,KAAKL,GACtBtF,IAAa,CAAC,KAAK,KAAK;AAE9B,aAAS1kB,IAAI,GAAGA,IAAIgqB,IAAS,GAAGhqB,KAAK;AACnC,YAAMgkB,IAAQU,IAAa2F,IAAYrqB,GACjCmN,IAASnN,IAAI,MAAM,IAAI6kB,IAAcD,GACrChmB,IAAI,KAAK,IAAIolB,CAAK,IAAI7W,GACtBhL,IAAI,KAAK,IAAI6hB,CAAK,IAAI7W;AAE5B,MAAInN,MAAM,IACRiE,EAAI,OAAOrF,GAAGuD,CAAC,IAEf8B,EAAI,OAAOrF,GAAGuD,CAAC;AAAA,IAEnB;AAEA,IAAA8B,EAAI,UAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAWA,GAA+B4N,GAAeC,GAAsB;AAErF,UAAMpF,IAAYmF,IAAQ,GACpB0Y,IAAYzY;AAElB,IAAA7N,EAAI,OAAO,CAACyI,GAAW,CAAC,GACxBzI,EAAI,OAAOyI,GAAW,CAAC,GAIvBzI,EAAI,cAAcolB,GAAqBplB,GAAK,KAAK,aAAa,GAC9DA,EAAI,YAAYsmB,GAChBtmB,EAAI,UAAU,SACdA,EAAI,OAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKS,OAAOuJ,GAAsBhM,GAAkBC,GAAmBgJ,GAAwC;AACjH,UAAM,EAAE,WAAAqf,MAAc,KAAK;AAG3B,QAAIA,MAAc,YAAYA,MAAc,aAAaA,MAAc,QAAQ;AAC7E,YAAM3X,IAAQ,KAAK,IAAI3Q,IAAWiJ,EAAU,cAAc,OAAOhJ,IAAYgJ,EAAU,cAAc,MAAM;AAC3G,WAAK,cAAc,QAAQA,EAAU,cAAc,QAAQ0H,GAC3D,KAAK,cAAc,SAAS1H,EAAU,cAAc,SAAS0H;AAAA,IAC/D,MAAA,CAAW2X,MAAc,UAEvB,KAAK,cAAc,QAAQtoB,GAC3B,KAAK,cAAc,SAASiJ,EAAU,cAAc,WAGpD,KAAK,cAAc,QAAQjJ,GAC3B,KAAK,cAAc,SAASC;AAI9B,SAAK,WAAWgJ,EAAU;AAG1B,UAAM+f,IAAc,KAAK,eAAehd,GAAQ/C,CAAS,GAInDggB,IAAiB,KAAK,kBAAkBjd,CAAM,GAC9Ckd,IAAU,KAAK,eAAA,GACfC,IAAoB,KAAK,gBAAgBF,GAAgBC,CAAO;AAEtE,gBAAK,IAAIF,EAAY,IAAIG,EAAkB,GAC3C,KAAK,IAAIH,EAAY,IAAIG,EAAkB,GAEpC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkBnd,GAAoC;AAe5D,WAdsD;AAAA,MACpD,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,IAAA,EAEQA,CAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAeA,GAAsB/C,GAAsC;AACjF,UAAMmgB,IAAY;AAAA,MAChB,GAAGngB,EAAU,IAAIA,EAAU,cAAc,QAAQ;AAAA,MACjD,GAAGA,EAAU,IAAIA,EAAU,cAAc,SAAS;AAAA,MAClD,OAAOA,EAAU,cAAc;AAAA,MAC/B,QAAQA,EAAU,cAAc;AAAA,IAAA;AAGlC,QAAIogB,GAAgBC;AAEpB,YAAQtd,GAAA;AAAA,MACN,KAAK;AACH,QAAAqd,IAASD,EAAU,IAAIA,EAAU,OACjCE,IAASF,EAAU,IAAIA,EAAU;AACjC;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,GACnBE,IAASF,EAAU,IAAIA,EAAU;AACjC;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,IAAIA,EAAU,OACjCE,IAASF,EAAU;AACnB;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,GACnBE,IAASF,EAAU;AACnB;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,IAAIA,EAAU,OACjCE,IAASF,EAAU,IAAIA,EAAU,SAAS;AAC1C;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,GACnBE,IAASF,EAAU,IAAIA,EAAU,SAAS;AAC1C;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,IAAIA,EAAU,QAAQ,GACzCE,IAASF,EAAU,IAAIA,EAAU;AACjC;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,IAAIA,EAAU,QAAQ,GACzCE,IAASF,EAAU;AACnB;AAAA,MACF;AACE,QAAAC,IAASD,EAAU,IAAIA,EAAU,QAAQ,GACzCE,IAASF,EAAU,IAAIA,EAAU,SAAS;AAAA,IAAA;AAG9C,WAAO,EAAE,GAAGC,GAAQ,GAAGC,EAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgBtd,GAAsB1M,GAA0B;AACtE,QAAIwgB,GAAiBC;AAErB,YAAQ/T,GAAA;AAAA,MACN,KAAK;AACH,QAAA8T,IAAU,CAACxgB,EAAK,QAAQ,GACxBygB,IAAU,CAACzgB,EAAK,SAAS;AACzB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAUxgB,EAAK,QAAQ,GACvBygB,IAAU,CAACzgB,EAAK,SAAS;AACzB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAU,CAACxgB,EAAK,QAAQ,GACxBygB,IAAUzgB,EAAK,SAAS;AACxB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAUxgB,EAAK,QAAQ,GACvBygB,IAAUzgB,EAAK,SAAS;AACxB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAU,CAACxgB,EAAK,QAAQ,GACxBygB,IAAU;AACV;AAAA,MACF,KAAK;AACH,QAAAD,IAAUxgB,EAAK,QAAQ,GACvBygB,IAAU;AACV;AAAA,MACF,KAAK;AACH,QAAAD,IAAU,GACVC,IAAU,CAACzgB,EAAK,SAAS;AACzB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAU,GACVC,IAAUzgB,EAAK,SAAS;AACxB;AAAA,MACF;AACE,QAAAwgB,IAAU,GACVC,IAAU;AAAA,IAAA;AAGd,WAAO,EAAE,GAAGD,GAAS,GAAGC,EAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoC;AAClC,UAAM,EAAE,WAAAuI,MAAc,KAAK;AAG3B,WAAIA,MAAc,YAAYA,MAAc,aAAaA,MAAc,SAC9D,CAAC,YAAY,aAAa,eAAe,cAAc,IAI5DA,MAAc,SACT,CAAC,eAAe,cAAc,IAIhC;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,QAAsB;AACpB,WAAO,IAAIH,GAAa,KAAK,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAqG;AAEnG,WAAO;AAAA,MACL,GAFe,MAAM,OAAA;AAAA,MAGrB,IAAI,KAAK;AAAA;AAAA,MACT,MAAM;AAAA,MACN,eAAe;AAAA,MACf,GAAG,KAAK;AAAA;AAAA,MACR,GAAG,KAAK;AAAA;AAAA,MACR,UAAU,KAAK;AAAA;AAAA,MACf,eAAe;AAAA,QACb,GAAG,KAAK;AAAA;AAAA;AAAA,QAGR,GAAI,KAAK,cAAc,QAAQ,EAAE,MAAMV,GAAe,KAAK,cAAc,IAAI,EAAA;AAAA,MAAE;AAAA,IACjF;AAAA,EAEJ;AACF;ACpxBO,MAAM8B,WAAoB3nB,GAAY;AAAA,EAI3C,YAAY1C,IAAqC,IAAI;AhCtBvD,QAAA0D,GAAAuG,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC,GAAAC;AgCuBI,UAAMzK,CAAM,GACZ,KAAK,gBAAgB,QAGrB,KAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,UAAQ0D,IAAA1D,EAAO,kBAAP,gBAAA0D,EAAsB,WAAU,CAAA;AAAA,MACxC,UAAQuG,IAAAjK,EAAO,kBAAP,gBAAAiK,EAAsB,WAAU;AAAA,MACxC,SAAOC,IAAAlK,EAAO,kBAAP,gBAAAkK,EAAsB,UAAS;AAAA,MACtC,UAAQC,IAAAnK,EAAO,kBAAP,gBAAAmK,EAAsB,WAAU;AAAA,MACxC,eAAaC,IAAApK,EAAO,kBAAP,gBAAAoK,EAAsB,gBAAe;AAAA,MAClD,aAAWC,IAAArK,EAAO,kBAAP,gBAAAqK,EAAsB,cAAavF,GAAA;AAAA,MAC9C,eAAawF,IAAAtK,EAAO,kBAAP,gBAAAsK,EAAsB,gBAAe;AAAA,MAClD,iBAAeC,IAAAvK,EAAO,kBAAP,gBAAAuK,EAAsB,kBAAiB;AAAA,MACtD,eAAaC,IAAAxK,EAAO,kBAAP,gBAAAwK,EAAsB,gBAAe;AAAA,MAClD,eAAaC,IAAAzK,EAAO,kBAAP,gBAAAyK,EAAsB,gBAAe;AAAA,IAAA;AAAA,EAKtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAA8B;AAC5B,UAAM6f,IAAc,KAAK,oBAAA;AAGzB,WAAO;AAAA,MACL,GAAG,KAAK,IAAIA,EAAY,QAAQ;AAAA,MAChC,GAAG,KAAK,IAAIA,EAAY,SAAS;AAAA,MACjC,OAAOA,EAAY;AAAA,MACnB,QAAQA,EAAY;AAAA,IAAA;AAAA,EAExB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,uBAAoC;AAClC,WAAO,KAAK,eAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA2B;AAGzB,WAAO;AAAA,MACL,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,IAAA;AAAA,EAEZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,YAAYC,GAAWC,GAAWC,GAAWC,GAAWpR,GAAkB;AAChF,UAAMqR,IAAK,IAAIrR,GACTsR,IAAMD,IAAKA,GACXE,IAAMD,IAAMD,GACZG,IAAKxR,IAAIA,GACTyR,IAAKD,IAAKxR;AAEhB,WAAO;AAAA,MACL,GAAGuR,IAAMN,EAAG,IAAI,IAAIK,IAAMtR,IAAIkR,EAAG,IAAI,IAAIG,IAAKG,IAAKL,EAAG,IAAIM,IAAKL,EAAG;AAAA,MAClE,GAAGG,IAAMN,EAAG,IAAI,IAAIK,IAAMtR,IAAIkR,EAAG,IAAI,IAAIG,IAAKG,IAAKL,EAAG,IAAIM,IAAKL,EAAG;AAAA,IAAA;AAAA,EAEtE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAAmC;AhC5G7C,QAAAhnB,GAAAuG,GAAAC,GAAAC;AgC6GI,UAAM,EAAE,QAAAmf,MAAW,KAAK;AAExB,QAAIA,EAAO,WAAW;AACpB,aAAO,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,GAAG,QAAQ,EAAA;AAGzC,QAAIlF,IAAO,OACPE,IAAO,OACPD,IAAO,QACPE,IAAO;AAGX,aAASjlB,IAAI,GAAGA,IAAIgqB,EAAO,QAAQhqB,KAAK;AACtC,YAAM0rB,IAAe1B,EAAOhqB,CAAC,GACvB2rB,IAAY3B,GAAQhqB,IAAI,KAAKgqB,EAAO,MAAM;AAGhD,UAAIhqB,MAAMgqB,EAAO,SAAS,KAAK,CAAC,KAAK,cAAc,QAAQ;AAEzD,QAAAlF,IAAO,KAAK,IAAIA,GAAM4G,EAAa,CAAC,GACpC1G,IAAO,KAAK,IAAIA,GAAM0G,EAAa,CAAC,GACpC3G,IAAO,KAAK,IAAIA,GAAM2G,EAAa,CAAC,GACpCzG,IAAO,KAAK,IAAIA,GAAMyG,EAAa,CAAC;AACpC;AAAA,MACF;AAWA,UARA5G,IAAO,KAAK,IAAIA,GAAM4G,EAAa,CAAC,GACpC1G,IAAO,KAAK,IAAIA,GAAM0G,EAAa,CAAC,GACpC3G,IAAO,KAAK,IAAIA,GAAM2G,EAAa,CAAC,GACpCzG,IAAO,KAAK,IAAIA,GAAMyG,EAAa,CAAC,GAGnBA,EAAa,aAAaC,EAAU,UAEvC;AAEZ,cAAMV,IAAK,EAAE,GAAGS,EAAa,GAAG,GAAGA,EAAa,EAAA,GAC1CR,IAAK;AAAA,UACT,GAAGQ,EAAa,OAAKtnB,IAAAsnB,EAAa,cAAb,gBAAAtnB,EAAwB,MAAK;AAAA,UAClD,GAAGsnB,EAAa,OAAK/gB,IAAA+gB,EAAa,cAAb,gBAAA/gB,EAAwB,MAAK;AAAA,QAAA,GAE9CwgB,IAAK;AAAA,UACT,GAAGQ,EAAU,OAAK/gB,IAAA+gB,EAAU,aAAV,gBAAA/gB,EAAoB,MAAK;AAAA,UAC3C,GAAG+gB,EAAU,OAAK9gB,IAAA8gB,EAAU,aAAV,gBAAA9gB,EAAoB,MAAK;AAAA,QAAA,GAEvCugB,IAAK,EAAE,GAAGO,EAAU,GAAG,GAAGA,EAAU,EAAA,GAGpCzG,IAAU;AAChB,iBAASlL,IAAI,GAAGA,KAAKkL,GAASlL,KAAK;AACjC,gBAAM4R,IAAI5R,IAAIkL,GACR2G,IAAQ,KAAK,YAAYZ,GAAIC,GAAIC,GAAIC,GAAIQ,CAAC;AAChD,UAAA9G,IAAO,KAAK,IAAIA,GAAM+G,EAAM,CAAC,GAC7B7G,IAAO,KAAK,IAAIA,GAAM6G,EAAM,CAAC,GAC7B9G,IAAO,KAAK,IAAIA,GAAM8G,EAAM,CAAC,GAC7B5G,IAAO,KAAK,IAAIA,GAAM4G,EAAM,CAAC;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAGA,UAAMC,IAAc,KAAK,cAAc,gBAAgB,KAAK,cAAc,eAAe,IAAI,GACvFC,IAAaD,IAAc;AAEjC,WAAO;AAAA,MACL,GAAGhH,IAAOiH;AAAA,MACV,GAAG/G,IAAO+G;AAAA,MACV,OAAOhH,IAAOD,IAAOgH;AAAA,MACrB,QAAQ7G,IAAOD,IAAO8G;AAAA,IAAA;AAAA,EAE1B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,UAAME,IAAS,KAAK,oBAAA;AACpB,SAAK,cAAc,QAAQ,KAAK,IAAI,GAAGA,EAAO,KAAK,GACnD,KAAK,cAAc,SAAS,KAAK,IAAI,GAAGA,EAAO,MAAM;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO/nB,GAA+B6I,IAAuB,IAAOC,IAAsB,IAAa;AACrG,UAAM,EAAE,QAAAid,GAAQ,QAAAiC,GAAQ,aAAAC,GAAa,WAAAC,GAAW,aAAAlC,GAAa,eAAAmC,GAAe,aAAAC,GAAa,aAAAP,EAAA,IACvF,KAAK;AAEP,QAAI9B,EAAO,WAAW;AACpB;AAGF,IAAA/lB,EAAI,KAAA,GAGJA,EAAI,UAAU,KAAK,GAAG,KAAK,CAAC,GAC5BA,EAAI,OAAOjB,EAAc,UAAU,KAAK,QAAQ,CAAC;AAGjD,UAAMgoB,IAAc,KAAK,oBAAA,GACnBsB,IAAgBtB,EAAY,IAAIA,EAAY,QAAQ,GACpDuB,IAAgBvB,EAAY,IAAIA,EAAY,SAAS;AAC3D,IAAA/mB,EAAI,UAAU,CAACqoB,GAAe,CAACC,CAAa,GAG5CtoB,EAAI,UAAA,GACJ,KAAK,WAAWA,CAAG,GAGfioB,KAAeD,KAAUE,MAC3BloB,EAAI,YAAYkoB,GAChBloB,EAAI,cAAcgmB,KAAe,GACjChmB,EAAI,KAAA,IAIFmoB,KAAiBC,KAAeP,MAClC7nB,EAAI,cAAcooB,GAClBpoB,EAAI,YAAY6nB,GAChB7nB,EAAI,UAAU,SACdA,EAAI,WAAW,SACfA,EAAI,cAAc,GAClBA,EAAI,OAAA,IAGNA,EAAI,QAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAWA,GAAqC;AhCjP1D,QAAAG,GAAAuG,GAAAC,GAAAC;AgCkPI,UAAM,EAAE,QAAAmf,GAAQ,QAAAiC,EAAA,IAAW,KAAK;AAEhC,QAAIjC,EAAO,WAAW,EAAG;AAGzB,UAAMwC,IAAaxC,EAAO,CAAC;AAC3B,IAAA/lB,EAAI,OAAOuoB,EAAW,GAAGA,EAAW,CAAC;AAGrC,aAASxsB,IAAI,GAAGA,IAAIgqB,EAAO,QAAQhqB,KAAK;AACtC,YAAM0rB,IAAe1B,EAAOhqB,CAAC,GACvB2rB,IAAY3B,GAAQhqB,IAAI,KAAKgqB,EAAO,MAAM;AAGhD,UAAIhqB,MAAMgqB,EAAO,SAAS,KAAK,CAACiC;AAC9B;AAMF,UAFiBP,EAAa,aAAaC,EAAU,UAEvC;AAEZ,cAAMc,IAAOf,EAAa,OAAKtnB,IAAAsnB,EAAa,cAAb,gBAAAtnB,EAAwB,MAAK,IACtDsoB,IAAOhB,EAAa,OAAK/gB,IAAA+gB,EAAa,cAAb,gBAAA/gB,EAAwB,MAAK,IACtDgiB,IAAOhB,EAAU,OAAK/gB,IAAA+gB,EAAU,aAAV,gBAAA/gB,EAAoB,MAAK,IAC/CgiB,IAAOjB,EAAU,OAAK9gB,IAAA8gB,EAAU,aAAV,gBAAA9gB,EAAoB,MAAK;AAErD,QAAA5G,EAAI,cAAcwoB,GAAMC,GAAMC,GAAMC,GAAMjB,EAAU,GAAGA,EAAU,CAAC;AAAA,MACpE;AAEE,QAAA1nB,EAAI,OAAO0nB,EAAU,GAAGA,EAAU,CAAC;AAAA,IAEvC;AAEA,IAAIM,KACFhoB,EAAI,UAAA;AAAA,EAER;AAAA;AAAA;AAAA;AAAA,EAKS,OAAOuJ,GAAsBhM,GAAkBC,GAAmBgJ,GAAwC;AACjH,UAAMgV,IAAWhV,EAAU,cAAc,OACnCoiB,IAAYpiB,EAAU,cAAc;AAE1C,QAAIgV,MAAa,KAAKoN,MAAc;AAClC,aAAO;AAIT,UAAMC,IAAStrB,IAAWie,GACpBsN,IAAStrB,IAAYorB,GAIrBG,IAAcviB,EAAU,cAAc;AAC5C,QAAIqa,IAAO,OACTE,IAAO,OACPD,IAAO,QACPE,IAAO;AACT,eAAW4G,KAASmB;AAClB,MAAAlI,IAAO,KAAK,IAAIA,GAAM+G,EAAM,CAAC,GAC7B7G,IAAO,KAAK,IAAIA,GAAM6G,EAAM,CAAC,GAC7B9G,IAAO,KAAK,IAAIA,GAAM8G,EAAM,CAAC,GAC7B5G,IAAO,KAAK,IAAIA,GAAM4G,EAAM,CAAC;AAE/B,UAAMxI,KAAWyB,IAAOC,KAAQ,GAC1BzB,KAAW0B,IAAOC,KAAQ;AAGhC,SAAK,cAAc,SAAS+H,EAAY,IAAI,CAACnB,OAAW;AAAA,MACtD,GAAGA;AAAA,MACH,GAAGxI,KAAWwI,EAAM,IAAIxI,KAAWyJ;AAAA,MACnC,GAAGxJ,KAAWuI,EAAM,IAAIvI,KAAWyJ;AAAA,MACnC,UAAUlB,EAAM,WAAW,EAAE,GAAGA,EAAM,SAAS,IAAIiB,GAAQ,GAAGjB,EAAM,SAAS,IAAIkB,MAAW;AAAA,MAC5F,WAAWlB,EAAM,YAAY,EAAE,GAAGA,EAAM,UAAU,IAAIiB,GAAQ,GAAGjB,EAAM,UAAU,IAAIkB,MAAW;AAAA,IAAA,EAChG,GAGF,KAAK,cAAc,QAAQvrB,GAC3B,KAAK,cAAc,SAASC,GAG5B,KAAK,WAAWgJ,EAAU;AAG1B,UAAM+f,IAAc,KAAK,eAAehd,GAAQ/C,CAAS,GAGnDggB,IAAiB,KAAK,kBAAkBjd,CAAM,GAC9Ckd,IAAU,KAAK,eAAA,GACfC,IAAoB,KAAK,gBAAgBF,GAAgBC,CAAO;AAEtE,gBAAK,IAAIF,EAAY,IAAIG,EAAkB,GAC3C,KAAK,IAAIH,EAAY,IAAIG,EAAkB,GAEpC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkBnd,GAAoC;AAe5D,WAdsD;AAAA,MACpD,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,IAAA,EAEQA,CAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAeA,GAAsB/C,GAAsC;AACjF,UAAMmgB,IAAY;AAAA,MAChB,GAAGngB,EAAU,IAAIA,EAAU,cAAc,QAAQ;AAAA,MACjD,GAAGA,EAAU,IAAIA,EAAU,cAAc,SAAS;AAAA,MAClD,OAAOA,EAAU,cAAc;AAAA,MAC/B,QAAQA,EAAU,cAAc;AAAA,IAAA;AAGlC,QAAIogB,GAAgBC;AAEpB,YAAQtd,GAAA;AAAA,MACN,KAAK;AACH,QAAAqd,IAASD,EAAU,IAAIA,EAAU,OACjCE,IAASF,EAAU,IAAIA,EAAU;AACjC;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,GACnBE,IAASF,EAAU,IAAIA,EAAU;AACjC;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,IAAIA,EAAU,OACjCE,IAASF,EAAU;AACnB;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,GACnBE,IAASF,EAAU;AACnB;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,IAAIA,EAAU,OACjCE,IAASF,EAAU,IAAIA,EAAU,SAAS;AAC1C;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,GACnBE,IAASF,EAAU,IAAIA,EAAU,SAAS;AAC1C;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,IAAIA,EAAU,QAAQ,GACzCE,IAASF,EAAU,IAAIA,EAAU;AACjC;AAAA,MACF,KAAK;AACH,QAAAC,IAASD,EAAU,IAAIA,EAAU,QAAQ,GACzCE,IAASF,EAAU;AACnB;AAAA,MACF;AACE,QAAAC,IAASD,EAAU,IAAIA,EAAU,QAAQ,GACzCE,IAASF,EAAU,IAAIA,EAAU,SAAS;AAAA,IAAA;AAG9C,WAAO,EAAE,GAAGC,GAAQ,GAAGC,EAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgBtd,GAAsB1M,GAA0B;AACtE,QAAIwgB,GAAiBC;AAErB,YAAQ/T,GAAA;AAAA,MACN,KAAK;AACH,QAAA8T,IAAU,CAACxgB,EAAK,QAAQ,GACxBygB,IAAU,CAACzgB,EAAK,SAAS;AACzB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAUxgB,EAAK,QAAQ,GACvBygB,IAAU,CAACzgB,EAAK,SAAS;AACzB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAU,CAACxgB,EAAK,QAAQ,GACxBygB,IAAUzgB,EAAK,SAAS;AACxB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAUxgB,EAAK,QAAQ,GACvBygB,IAAUzgB,EAAK,SAAS;AACxB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAU,CAACxgB,EAAK,QAAQ,GACxBygB,IAAU;AACV;AAAA,MACF,KAAK;AACH,QAAAD,IAAUxgB,EAAK,QAAQ,GACvBygB,IAAU;AACV;AAAA,MACF,KAAK;AACH,QAAAD,IAAU,GACVC,IAAU,CAACzgB,EAAK,SAAS;AACzB;AAAA,MACF,KAAK;AACH,QAAAwgB,IAAU,GACVC,IAAUzgB,EAAK,SAAS;AACxB;AAAA,MACF;AACE,QAAAwgB,IAAU,GACVC,IAAU;AAAA,IAAA;AAGd,WAAO,EAAE,GAAGD,GAAS,GAAGC,EAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoC;AAClC,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,QAAqB;AACnB,WAAO,IAAIwJ,GAAY,KAAK,QAAQ;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAmG;AAEjG,WAAO;AAAA,MACL,GAFe,MAAM,OAAA;AAAA,MAGrB,IAAI,KAAK;AAAA;AAAA,MACT,MAAM;AAAA,MACN,eAAe;AAAA,MACf,GAAG,KAAK;AAAA;AAAA,MACR,GAAG,KAAK;AAAA;AAAA,MACR,UAAU,KAAK;AAAA;AAAA,MACf,eAAe;AAAA,QACb,GAAG,KAAK;AAAA;AAAA,QAER,QAAQ,KAAK,cAAc,OAAO,IAAI,CAACtJ,OAAO;AAAA,UAC5C,GAAGA;AAAA,UACH,UAAUA,EAAE,WAAW,EAAE,GAAGA,EAAE,aAAa;AAAA,UAC3C,WAAWA,EAAE,YAAY,EAAE,GAAGA,EAAE,cAAc;AAAA,QAAA,EAC9C;AAAA,MAAA;AAAA,IACJ;AAAA,EAEJ;AACF;ACveO,MAAMwL,KAAyE;AAAA,EACpF,QAAQ;AAAA,IACN;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,IAAA;AAAA,EAChB;AAAA,EAEF,MAAM;AAAA,IACJ;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsB/F,GAAc;AAAA;AAAA,MAEpC,UAAU,CAACgG,OAAuBA,IAAW,KAAK,IAAK;AAAA,MACvD,YAAY,CAACC,MAAoBA,IAAS,MAAO,IAAI;AAAA,MACrD,WAAW,CAACD,MAAqB,IAAIA,IAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,IAAA;AAAA,EACjE;AAAA,EAEF,MAAM;AAAA,IACJ;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsBlG,GAAc;AAAA;AAAA,MAEpC,UAAU,CAACkG,OAAsBA,IAAW,OAAO;AAAA,MACnD,YAAY,CAACC,MAAoBA,IAAS,MAAO;AAAA,MACjD,WAAW,CAACD,MAAqB,IAAIA,IAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,IAAA;AAAA,IAEjE;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsBlG,GAAc;AAAA,MACpC,MAAM;AAAA;AAAA;AAAA,MAEN,UAAU,CAACkG,OAAuBA,IAAW,KAAK,IAAK;AAAA,MACvD,YAAY,CAACC,MAAmB,IAAKA,IAAS,MAAO;AAAA,MACrD,WAAW,CAACD,MAAqBA,EAAS,QAAQ,CAAC;AAAA,IAAA;AAAA,EACrD;AAAA,EAEF,MAAM;AAAA,IACJ;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsBjG,GAAc;AAAA;AAAA,MAEpC,UAAU,CAACiG,OAAsBA,IAAW,OAAO;AAAA,MACnD,YAAY,CAACC,MAAoBA,IAAS,MAAO;AAAA,MACjD,WAAW,CAACD,MAAqB,IAAIA,IAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,IAAA;AAAA,IAEjE;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsBjG,GAAc;AAAA,MACpC,MAAM;AAAA,MACN,UAAU,CAACiG,OAAuBA,IAAW,KAAK,IAAK;AAAA,MACvD,YAAY,CAACC,MAAmB,IAAKA,IAAS,MAAO;AAAA,MACrD,WAAW,CAACD,MAAqBA,EAAS,QAAQ,CAAC;AAAA,IAAA;AAAA,EACrD;AAAA,EAEF,MAAM;AAAA,IACJ;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsB/F,GAAc;AAAA;AAAA;AAAA,MAGpC,UAAU,CAAC+F,OAAsBA,IAAW,OAAO;AAAA,MACnD,YAAY,CAACC,MAAmBA,IAAS,MAAM;AAAA,MAC/C,WAAW,CAACD,MAAqB,IAAIA,IAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,IAAA;AAAA,EACjE;AAAA,EAEF,QAAQ;AAAA,IACN;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsB9F,GAAgB;AAAA;AAAA;AAAA,MAGtC,UAAU,CAAC8F,OAAuB,KAAKA,KAAY,KAAM;AAAA,MACzD,YAAY,CAACC,MAAmB,KAAMA,IAAS,MAAO;AAAA,MACtD,WAAW,CAACD,MAAqB,IAAK,CAACA,IAAW,KAAM,KAAK,QAAQ,CAAC,CAAC;AAAA,IAAA;AAAA,EACzE;AAAA,EAEF,OAAO;AAAA,IACL;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,SAAS;AAAA,QACP,EAAE,OAAO,aAAa,OAAO,YAAA;AAAA,QAC7B,EAAE,OAAO,UAAU,OAAO,SAAA;AAAA,QAC1B,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,QAC3B,EAAE,OAAO,YAAY,OAAO,WAAA;AAAA,QAC5B,EAAE,OAAO,WAAW,OAAO,UAAA;AAAA,QAC3B,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,QACxB,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,MAAO;AAAA,MAEjC,cAAc;AAAA,IAAA;AAAA,IAEhB;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsB;AAAA;AAAA,MAEtB,UAAU,CAACA,MAAsBA,IAAW,KAAM;AAAA,MAClD,YAAY,CAACC,MAAoBA,IAAS,MAAO;AAAA,MACjD,WAAW,CAACD,MAAqB,GAAGA,EAAS,QAAQ,CAAC,CAAC;AAAA,MACvD,aAAa,CAACxqB,MAAkCA,EAAK,cAAc;AAAA,IAAA;AAAA,IAErE;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsB;AAAA,MACtB,MAAM;AAAA;AAAA,MAEN,UAAU,CAACwqB,OAAuBA,IAAW,KAAK,KAAM;AAAA,MACxD,YAAY,CAACC,MAAmB,KAAK,MAAM,IAAKA,IAAS,MAAO,EAAE;AAAA,MAClE,WAAW,CAACD,MAAqBA,EAAS,QAAQ,CAAC;AAAA,MACnD,aAAa,CAACxqB,MAAkCA,EAAK,cAAc;AAAA,IAAA;AAAA,IAErE;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsB;AAAA,MACtB,MAAM;AAAA;AAAA,MAEN,UAAU,CAACwqB,OAAuBA,IAAW,KAAK,KAAM;AAAA,MACxD,YAAY,CAACC,MAAmB,KAAK,MAAM,IAAKA,IAAS,MAAO,EAAE;AAAA,MAClE,WAAW,CAACD,MAAqBA,EAAS,QAAQ,CAAC;AAAA,MACnD,aAAa,CAACxqB,MAAkCA,EAAK,cAAc;AAAA,IAAA;AAAA,IAErE;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsB;AAAA;AAAA,MAEtB,UAAU,CAACwqB,OAAuBA,IAAW,OAAO,MAAO;AAAA,MAC3D,YAAY,CAACC,MAAmB,MAAOA,IAAS,MAAO;AAAA,MACvD,WAAW,CAACD,MAAqB,IAAIA,IAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,MAC/D,aAAa,CAACxqB,MAAkCA,EAAK,cAAc;AAAA,IAAA;AAAA,IAErE;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,sBAAsB;AAAA;AAAA,MAEtB,UAAU,CAACwqB,MAAqBA,IAAW;AAAA,MAC3C,YAAY,CAACC,MAAmBA,IAAS;AAAA,MACzC,WAAW,CAACD,MAAqB,IAAIA,IAAW,KAAK,QAAQ,CAAC,CAAC;AAAA,IAAA;AAAA,EACjE;AAEJ,GAgBaE,KAAkC;AAAA,EAC7C,EAAE,IAAI,UAAU,OAAO,UAAU,WAAWtN,GAAA;AAAA;AAAA;AAAA;AAAA,EAI5C,EAAE,IAAI,UAAU,OAAO,UAAU,WAAWuE,GAAA;AAAA,EAC5C,EAAE,IAAI,QAAQ,OAAO,QAAQ,WAAWwD,GAAA;AAAA,EACxC,EAAE,IAAI,QAAQ,OAAO,QAAQ,WAAW7B,GAAA;AAAA,EACxC,EAAE,IAAI,UAAU,OAAO,UAAU,WAAWkC,GAAA;AAAA,EAC5C,EAAE,IAAI,QAAQ,OAAO,QAAQ,WAAWb,GAAA;AAAA,EACxC,EAAE,IAAI,QAAQ,OAAO,QAAQ,WAAWE,GAAA;AAAA,EACxC,EAAE,IAAI,SAAS,OAAO,SAAS,WAAWoC,GAAA;AAAA,EAC1C,EAAE,IAAI,QAAQ,OAAO,QAAQ,WAAWoB,GAAA;AAAA,EACxC,EAAE,IAAI,SAAS,OAAO,SAAS,WAAWrgB,GAAA;AAAA;AAAA;AAAA;AAAA,EAI1C,EAAE,IAAI,SAAS,OAAO,SAAS,IAAI,YAAY;AAAE,WAAO2iB;AAAA,EAAc,EAAA;AACxE,GAGMC,KAAc,IAAI,IAAIF,GAAgB,IAAI,CAACpT,MAAMA,EAAE,EAAE,CAAC,GAGtDuT,yBAAyB,IAAA;AAaxB,SAASC,GAAkBzqB,GAAY0qB,GAAyB;AACrE,MAAIH,GAAY,IAAIvqB,CAAE;AACpB,UAAM,IAAI;AAAA,MACR,mCAAmCA,CAAE;AAAA,IAAA;AAGzC,MAAIwqB,GAAmB,IAAIxqB,CAAE;AAC3B,UAAM,IAAI;AAAA,MACR,mCAAmCA,CAAE;AAAA,IAAA;AAGzC,EAAAwqB,GAAmB,IAAIxqB,GAAI0qB,CAAG;AAChC;AAOO,SAASC,GAAoB3qB,GAAqB;AACvD,MAAIuqB,GAAY,IAAIvqB,CAAE;AACpB,UAAM,IAAI;AAAA,MACR,qCAAqCA,CAAE;AAAA,IAAA;AAG3C,SAAOwqB,GAAmB,OAAOxqB,CAAE;AACrC;AAYO,SAAS4qB,GAAiB5qB,GAAsC;AACrE,QAAM6qB,IAAUR,GAAgB,KAAK,CAACpT,MAAMA,EAAE,OAAOjX,CAAE;AACvD,SAAI6qB,KACGL,GAAmB,IAAIxqB,CAAE;AAClC;AAKO,SAAS8qB,GAAqB9qB,GAAuC;AAC1E,SAAOkqB,GAAmBlqB,CAAE,KAAK,CAAA;AACnC;ACxPO,MAAMsqB,WAAqBjqB,GAAY;AAAA,EAc5C,YAAY1C,IAAsC,IAAI;AACpD,UAAMA,CAAM,GAXd,KAAA,UAAmB,IAGnB,KAAQ,iBAAyB,GACjC,KAAQ,kBAA0B,GAGlC,KAAQ,mBAA2B,GACnC,KAAQ,mBAA2B,GAIjC,KAAK,gBAAgB;AAGrB,UAAMotB,IAAcptB,EAAO,YAAY,CAAA;AACvC,SAAK,WAAWotB,EAAY,IAAI,CAACC,MAE3BA,aAAiB3qB,KACZ2qB,IAKF,KAAK,0BAA0BA,CAA2C,CAClF,GAED,KAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,IAAA,GAIJ,KAAK,SAAS,SAAS,KACzB,KAAK,yBAAyB,EAAI;AAAA,EAEtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,0BAA0BrtB,GAA2B;AAC3D,UAAMstB,IAAgBttB,EAAO,iBAAiBA,EAAO,MAC/CutB,IAAeN,GAAiBK,CAAa;AAEnD,QAAI,CAACC,KAAgB,CAACA,EAAa;AACjC,YAAM,IAAI,MAAM,2BAA2BD,CAAa,EAAE;AAI5D,WAAO,IAAIC,EAAa,UAAUvtB,CAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAA8B;AAC5B,QAAI,KAAK,SAAS,WAAW;AAC3B,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,KAAK,QAAQ,IAAA;AAG/D,QAAIokB,IAAO,OACPE,IAAO,OACPD,IAAO,QACPE,IAAO;AAEX,gBAAK,SAAS,QAAQ,CAAC8I,MAAU;AAC/B,YAAMjtB,IAAOitB,EAAM,qBAAA;AAGnB,MAFgB,KAAK,mBAAmBA,GAAOjtB,CAAI,EAE3C,QAAQ,CAACqP,MAAW;AAC1B,QAAA2U,IAAO,KAAK,IAAIA,GAAM3U,EAAO,CAAC,GAC9B6U,IAAO,KAAK,IAAIA,GAAM7U,EAAO,CAAC,GAC9B4U,IAAO,KAAK,IAAIA,GAAM5U,EAAO,CAAC,GAC9B8U,IAAO,KAAK,IAAIA,GAAM9U,EAAO,CAAC;AAAA,MAChC,CAAC;AAAA,IACH,CAAC,GAEM;AAAA,MACL,GAAG2U;AAAA,MACH,GAAGE;AAAA,MACH,OAAOD,IAAOD;AAAA,MACd,QAAQG,IAAOD;AAAA,IAAA;AAAA,EAEnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,uBAAoC;AAClC,WAAO,KAAK,uBAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAA2B;AACzB,WAAO;AAAA,MACL,GAAG,KAAK,oBAAoB,KAAK;AAAA,MACjC,GAAG,KAAK,oBAAoB,KAAK;AAAA,IAAA;AAAA,EAErC;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmBnkB,GAAuBC,GAA4B;AAC5E,UAAMwhB,IAAY,IAAIje,EAAUxD,CAAO,GAGjCwiB,IAAUviB,EAAK,IAAIA,EAAK,QAAQ,GAChCwiB,IAAUxiB,EAAK,IAAIA,EAAK,SAAS,GAGjCwgB,IAAU+B,IAAUxiB,EAAQ,GAC5B0gB,IAAU+B,IAAUziB,EAAQ;AAUlC,WAPgB;AAAA,MACd,EAAE,QAAQygB,IAAUxgB,EAAK,QAAQ,GAAG,QAAQygB,IAAUzgB,EAAK,SAAS,EAAA;AAAA,MACpE,EAAE,QAAQwgB,IAAUxgB,EAAK,QAAQ,GAAG,QAAQygB,IAAUzgB,EAAK,SAAS,EAAA;AAAA,MACpE,EAAE,QAAQwgB,IAAUxgB,EAAK,QAAQ,GAAG,QAAQygB,IAAUzgB,EAAK,SAAS,EAAA;AAAA,MACpE,EAAE,QAAQwgB,IAAUxgB,EAAK,QAAQ,GAAG,QAAQygB,IAAUzgB,EAAK,SAAS,EAAA;AAAA,IAAE,EAGzD,IAAI,CAACqP,MAAWmS,EAAU,aAAanS,EAAO,QAAQA,EAAO,MAAM,CAAC;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,yBAAyB+d,IAAiC,IAAa;AACrE,UAAMptB,IAAO,KAAK,eAAA;AAMlB,QALA,KAAK,IAAIA,EAAK,IAAIA,EAAK,QAAQ,GAC/B,KAAK,IAAIA,EAAK,IAAIA,EAAK,SAAS,GAI5BotB,KAAyB,KAAK,mBAAmB,KAAK,KAAK,oBAAoB,GAAG;AACpF,YAAMC,IAAO,KAAK,4BAAA;AAClB,WAAK,iBAAiBA,EAAK,OAC3B,KAAK,kBAAkBA,EAAK,QAE5B,KAAK,mBAAmB,KAAK,GAC7B,KAAK,mBAAmB,KAAK;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,8BAAiE;AACvE,QAAI,KAAK,SAAS,WAAW;AAC3B,aAAO,EAAE,OAAO,KAAK,QAAQ,IAAA;AAK/B,UAAMrtB,IAAO,KAAK,eAAA;AAMlB,WALe;AAAA,MACb,OAAOA,EAAK;AAAA,MACZ,QAAQA,EAAK;AAAA,IAAA;AAAA,EAIjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAOmD,GAA+BmqB,IAAsB,IAAOC,IAAqB,IAAa;AAEnG,UAAMC,IAAkB,KAAK,SAAS,OAAO,CAACP,MAAUA,EAAM,YAAY,EAAK;AAM/E,IAHoBO,EAAgB,KAAK,CAACP,MAAUA,EAAM,UAAU,IAYlE1Z,GAAA,EAA0BpQ,GAAKqqB,CAAe,IAP9CA,EAAgB,QAAQ,CAACP,MAAU;AACjC,MAAAA,EAAM,OAAO9pB,GAAK,EAAK;AAAA,IACzB,CAAC,GASCmqB,IACF,KAAK,oBAAoBnqB,GAAK,CAAG,IACxBoqB,KACT,KAAK,oBAAoBpqB,GAAK,GAAG;AAAA,EAErC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoBA,GAA+BsqB,IAAkB,GAAW;AAEtF,UAAMC,IAAM,KAAK,uBAAA;AAEjB,IAAAvqB,EAAI,KAAA,GACJA,EAAI,cAAcsqB,GAClBtqB,EAAI,cAAcoB,GAAA,GAClBpB,EAAI,YAAY,GAChBA,EAAI,YAAY,CAAC,GAAG,CAAC,CAAC;AAItB,UAAMof,IAAU,KAAK,oBAAoB,KAAK,GACxCC,IAAU,KAAK,oBAAoB,KAAK;AAE9C,IAAI,KAAK,aAAa,MACpBrf,EAAI,UAAUof,GAASC,CAAO,GAC9Brf,EAAI,OAAQ,CAAC,KAAK,WAAW,KAAK,KAAM,GAAG,GAC3CA,EAAI,UAAU,CAACof,GAAS,CAACC,CAAO,IAIlCrf,EAAI,WAAWuqB,EAAI,GAAGA,EAAI,GAAGA,EAAI,OAAOA,EAAI,MAAM,GAClDvqB,EAAI,YAAY,EAAE,GAClBA,EAAI,QAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,yBAAsC;AAC5C,QAAI,KAAK,SAAS,WAAW;AAC3B,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,KAAK,QAAQ,IAAA;AAI/D,QAAI4N,GAAeC;AAEnB,QAAI,KAAK,iBAAiB,KAAK,KAAK,kBAAkB;AAEpD,MAAAD,IAAQ,KAAK,gBACbC,IAAS,KAAK;AAAA,SACT;AAEL,YAAMhR,IAAO,KAAK,eAAA;AAClB,MAAA+Q,IAAQ/Q,EAAK,OACbgR,IAAShR,EAAK,QAGd,KAAK,iBAAiB+Q,GACtB,KAAK,kBAAkBC;AAAA,IACzB;AAGA,UAAMuR,IAAU,KAAK,oBAAoB,KAAK,GACxCC,IAAU,KAAK,oBAAoB,KAAK;AAC9C,WAAO;AAAA,MACL,GAAGD,IAAUxR,IAAQ;AAAA,MACrB,GAAGyR,IAAUxR,IAAS;AAAA,MACtB,OAAAD;AAAA,MACA,QAAAC;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,KAAKnO,GAAYC,GAAkB;AAEjC,SAAK,KAAKD,GACV,KAAK,KAAKC,GAGV,KAAK,oBAAoBD,GACzB,KAAK,oBAAoBC,GAGzB,KAAK,SAAS,QAAQ,CAACmqB,MAAU;AAC/B,MAAAA,EAAM,KAAKpqB,GAAIC,CAAE;AAAA,IACnB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,YAAYO,GAA2B;AACrC,UAAMsqB,IAAgBtqB,IAAc,KAAK;AACzC,SAAK,WAAWA;AAGhB,UAAMkf,IAAU,KAAK,oBAAoB,KAAK,GACxCC,IAAU,KAAK,oBAAoB,KAAK;AAE9C,SAAK,SAAS,QAAQ,CAACyK,MAAU;AAE/B,YAAMW,IAAUrqB,EAAU,wBAAwB0pB,EAAM,GAAGA,EAAM,GAAG1K,GAASC,GAASmL,CAAa;AACnG,MAAAV,EAAM,IAAIW,EAAQ,GAClBX,EAAM,IAAIW,EAAQ,GAGlBX,EAAM,YAAYA,EAAM,WAAWU,CAAa;AAAA,IAClD,CAAC,GAID,KAAK,IAAIpL,GACT,KAAK,IAAIC;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO9V,GAAsBhM,GAAkBC,GAAmBgJ,GAAqC;AACrG,QAAI,CAACA,EAAU,qBAAqB,CAACA,EAAU,SAAS,CAACA,EAAU;AACjE;AAGF,UAAMqiB,IAAStrB,IAAWiJ,EAAU,OAC9BsiB,IAAStrB,IAAYgJ,EAAU,QAC/B0H,KAAS2a,IAASC,KAAU;AAGlC,QAAI4B,GAAqBC;AAEzB,YAAQphB,GAAA;AAAA,MACN,KAAK;AACH,QAAAmhB,IAAclkB,EAAU,QAAQ,GAChCmkB,IAAcnkB,EAAU,SAAS;AACjC;AAAA,MACF,KAAK;AACH,QAAAkkB,IAAc,CAAClkB,EAAU,QAAQ,GACjCmkB,IAAcnkB,EAAU,SAAS;AACjC;AAAA,MACF,KAAK;AACH,QAAAkkB,IAAclkB,EAAU,QAAQ,GAChCmkB,IAAc,CAACnkB,EAAU,SAAS;AAClC;AAAA,MACF,KAAK;AACH,QAAAkkB,IAAc,CAAClkB,EAAU,QAAQ,GACjCmkB,IAAc,CAACnkB,EAAU,SAAS;AAClC;AAAA,MACF;AAEE,QAAAkkB,IAAc,GACdC,IAAc;AAAA,IAAA;AAIlB,UAAMprB,IAAe,CAACiH,EAAU,WAAW,KAAK,KAAM,KAChDhH,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW,GAC1BqnB,IAASpgB,EAAU,KAAKkkB,IAAclrB,IAAMmrB,IAAclrB,IAC1DonB,IAASrgB,EAAU,KAAKkkB,IAAcjrB,IAAMkrB,IAAcnrB;AAEhE,SAAK,SAAS,QAAQ,CAACsqB,GAAOzY,MAAU;AACtC,YAAMuZ,IAAapkB,EAAU,kBAAmB6K,CAAK,GAI/CgM,IAAUuN,EAAW,IAAIhE,GACzBtJ,IAAUsN,EAAW,IAAI/D,GACzBppB,IAAOmpB,IAASvJ,IAAUnP,GAC1BxQ,IAAOmpB,IAASvJ,IAAUpP;AAMhC,UAJA4b,EAAM,IAAIrsB,GACVqsB,EAAM,IAAIpsB,GAGNosB,aAAiBrjB,IAAc;AAEjC,cAAMokB,IAAiBD,EAAW,eAC5BE,IAAgBD,EAAe,QAAQ3c,GACvC6c,IAAiBF,EAAe,SAAS3c;AAC/C,QAAA4b,EAAM,cAAc,QAAQgB,GAC5BhB,EAAM,cAAc,SAASiB;AAAA,MAC/B,WAAWjB,aAAiBV,IAAc;AAExC,cAAM0B,KAAiBF,EAAW,SAAS,KAAK1c,GAC1C6c,KAAkBH,EAAW,UAAU,KAAK1c;AAClD,QAAA4b,EAAM,OAAOvgB,GAAQuhB,GAAeC,GAAgBH,CAAU;AAAA,MAChE,OAAO;AAEL,cAAME,KAAiBF,EAAW,SAAS,KAAK1c,GAC1C6c,KAAkBH,EAAW,UAAU,KAAK1c;AAClD,QAAA4b,EAAM,OAAOvgB,GAAQuhB,GAAeC,GAAgBH,CAAU;AAAA,MAChE;AAAA,IACF,CAAC,GAGD,KAAK,iBAAiBpkB,EAAU,QAAS0H,GACzC,KAAK,kBAAkB1H,EAAU,SAAU0H;AAG3C,UAAMma,IAAgB7hB,EAAU,IAAIogB,GAC9B0B,IAAgB9hB,EAAU,IAAIqgB;AACpC,SAAK,IAAID,IAASyB,IAAgBna,GAClC,KAAK,IAAI2Y,IAASyB,IAAgBpa,GAGlC,KAAK,mBAAmB,KAAK,GAC7B,KAAK,mBAAmB,KAAK;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,wBAA4C;AAG1C,UAAMN,IAAQ,KAAK,iBAAiB,IAAI,KAAK,iBAAiB,KAAK,iBAAiB,OAC9EC,IAAS,KAAK,kBAAkB,IAAI,KAAK,kBAAkB,KAAK,iBAAiB,QAIjFuR,IAAU,KAAK,oBAAoB,KAAK,GACxCC,IAAU,KAAK,oBAAoB,KAAK;AAE9C,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,GAAGD;AAAA,MACH,GAAGC;AAAA,MACH,OAAAzR;AAAA,MACA,QAAAC;AAAA,MACA,UAAU,KAAK;AAAA,MACf,eAAe,KAAK;AAAA,MACpB,mBAAmB,KAAK,SAAS,IAAI,CAACic,MAAUA,EAAM,uBAAuB;AAAA,IAAA;AAAA,EAEjF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQnvB,GAAWuD,GAAoB;AAErC,UAAMqsB,IAAM,KAAK,uBAAA,GACXnL,IAAU,KAAK,oBAAoB,KAAK,GACxCC,IAAU,KAAK,oBAAoB,KAAK;AAG9C,QAAI,KAAK,aAAa;AAIpB,aAHiB1kB,KAAK4vB,EAAI,KAAK5vB,KAAK4vB,EAAI,IAAIA,EAAI,SAASrsB,KAAKqsB,EAAI,KAAKrsB,KAAKqsB,EAAI,IAAIA,EAAI;AAQ1F,UAAMhrB,IAAe,KAAK,WAAW,KAAK,KAAM,KAC1CC,IAAM,KAAK,IAAID,CAAW,GAC1BE,IAAM,KAAK,IAAIF,CAAW,GAG1BG,IAAK/E,IAAIykB,GACTzf,IAAKzB,IAAImhB,GAGTzf,IAASwf,KAAW1f,IAAKF,IAAMG,IAAKF,IACpCI,IAASwf,KAAW3f,IAAKD,IAAME,IAAKH;AAQ1C,WALiBI,KAAU2qB,EAAI,KAAK3qB,KAAU2qB,EAAI,IAAIA,EAAI,SAAS1qB,KAAU0qB,EAAI,KAAK1qB,KAAU0qB,EAAI,IAAIA,EAAI;AAAA,EAM9G;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoC;AAClC,WAAO,CAAC,YAAY,aAAa,eAAe,cAAc;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,SAA6B;AAE3B,WAAO;AAAA,MACL,GAFe,MAAM,OAAA;AAAA,MAGrB,eAAe;AAAA,MACf,eAAe;AAAA,QACb,MAAM;AAAA,MAAA;AAAA,MAER,UAAU,KAAK,SAAS,IAAI,CAACT,MAAUA,EAAM,QAAQ;AAAA,IAAA;AAAA,EAEzD;AAAA;AAAA;AAAA;AAAA,EAKA,QAAsB;AACpB,UAAMkB,IAAiB,KAAK,SAAS,IAAI,CAAClB,MAAUA,EAAM,OAAO,GAC3DmB,IAAc,IAAI7B,GAAa;AAAA,MACnC,IAAI,KAAK;AAAA,MACT,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,UAAU,KAAK;AAAA;AAAA,MAEf,UAAU4B;AAAA,IAAA,CACX;AAED,WAAAC,EAAY,iBAAiB,KAAK,gBAClCA,EAAY,kBAAkB,KAAK,iBAEnCA,EAAY,mBAAmB,KAAK,kBACpCA,EAAY,mBAAmB,KAAK,kBAGpCA,EAAY,UAAU,KAAK,SAG3BA,EAAY,OAAO,KAAK,MACxBA,EAAY,UAAU,KAAK,SAC3BA,EAAY,SAAS,KAAK,QAGtB,KAAK,cAAWA,EAAY,YAAY,KAAK,YAC7C,KAAK,kBAAeA,EAAY,gBAAgB,EAAE,GAAG,KAAK,cAAA,IAC1D,KAAK,WACPA,EAAY,SAAS;AAAA,MACnB,GAAG,KAAK;AAAA,MACR,GAAI,KAAK,OAAO,aAAa,EAAE,WAAW,CAAC,GAAG,KAAK,OAAO,SAAS,EAAA;AAAA,IAAE,IAIrE,KAAK,UACPA,EAAY,QAAQ,KAAK,MAAM,IAAI,CAAChrB,OAAO;AAAA,MACzC,GAAGA;AAAA,MACH,aAAa,gBAAgBA,EAAE,WAAW;AAAA,IAAA,EAC1C,IAEA,KAAK,mBAAgBgrB,EAAY,iBAAiB,EAAE,GAAG,KAAK,eAAA,IAC5D,KAAK,WAAQA,EAAY,SAAS,EAAE,GAAG,KAAK,OAAA,IAEzCA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAASnB,GAA2B;AAClC,SAAK,SAAS,KAAKA,CAAK,GACxB,KAAK,yBAAyB,EAAI;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAYoB,GAA0B;AACpC,UAAM7Z,IAAQ,KAAK,SAAS,UAAU,CAAC8Z,MAAMA,EAAE,OAAOD,CAAO;AAC7D,WAAI7Z,KAAS,KACX,KAAK,SAAS,OAAOA,GAAO,CAAC,GAC7B,KAAK,yBAAyB,EAAI,GAC3B,MAEF;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,cAA8B;AAC5B,WAAO,CAAC,GAAG,KAAK,QAAQ;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa1W,GAAWuD,GAAgC;AAEtD,aAAS,IAAI,KAAK,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAClD,YAAM4rB,IAAQ,KAAK,SAAS,CAAC;AAC7B,UAAIA,EAAM,QAAQnvB,GAAGuD,CAAC;AACpB,eAAO4rB;AAAA,IAEX;AACA,WAAO;AAAA,EACT;AACF;ACnmBO,MAAMsB,EAAa;AAAA,EAKxB,YAAY1sB,GAA2B;AAGrC,QALF,KAAQ,eAAsC,MAG5C,KAAK,2BAAW,IAAA,GAChB,KAAK,QAAQ,CAAA,GACTA;AACF,iBAAW2sB,KAAM3sB;AACf,aAAK,KAAK,IAAI2sB,EAAG,IAAIA,CAAE,GACvB,KAAK,MAAM,KAAKA,EAAG,EAAE;AAAA,EAG3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAIvsB,GAAsC;AACxC,WAAO,KAAK,KAAK,IAAIA,CAAE;AAAA,EACzB;AAAA;AAAA,EAGA,IAAIA,GAAqB;AACvB,WAAO,KAAK,KAAK,IAAIA,CAAE;AAAA,EACzB;AAAA;AAAA,EAGA,SAAyB;AACvB,WAAO,KAAK,MAAM,IAAI,CAACA,MAAO,KAAK,KAAK,IAAIA,CAAE,CAAE;AAAA,EAClD;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA;AAAA,EAGA,WAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,UAAU3C,GAAwC;AAChD,eAAW2C,KAAM,KAAK,OAAO;AAC3B,YAAMusB,IAAK,KAAK,KAAK,IAAIvsB,CAAE;AAC3B,UAAIusB,KAAMA,EAAG,SAASlvB,EAAM,QAAOkvB;AAAA,IACrC;AAAA,EAEF;AAAA;AAAA,EAGA,aAAalvB,GAA8B;AACzC,UAAMmvB,IAAyB,CAAA;AAC/B,eAAWxsB,KAAM,KAAK,OAAO;AAC3B,YAAMusB,IAAK,KAAK,KAAK,IAAIvsB,CAAE;AAC3B,MAAIusB,KAAMA,EAAG,SAASlvB,KAAMmvB,EAAO,KAAKD,CAAE;AAAA,IAC5C;AACA,WAAOC;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO1uB,GAAqC;AAC1C,UAAMgV,IAAO,KAAK,MAAA;AAClB,WAAAA,EAAK,KAAK,IAAIhV,EAAQ,IAAIA,CAAO,GAE5B,KAAK,KAAK,IAAIA,EAAQ,EAAE,KAC3BgV,EAAK,MAAM,KAAKhV,EAAQ,EAAE,GAErBgV;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW9S,GAAYlC,GAAqC;AAC1D,QAAI,CAAC,KAAK,KAAK,IAAIkC,CAAE,EAAG,QAAO;AAC/B,UAAM8S,IAAO,KAAK,MAAA;AAElB,QAAI9S,MAAOlC,EAAQ,IAAI;AACrB,MAAAgV,EAAK,KAAK,OAAO9S,CAAE;AACnB,YAAMysB,IAAM3Z,EAAK,MAAM,QAAQ9S,CAAE;AACjC,MAAIysB,MAAQ,OAAI3Z,EAAK,MAAM2Z,CAAG,IAAI3uB,EAAQ;AAAA,IAC5C;AACA,WAAAgV,EAAK,KAAK,IAAIhV,EAAQ,IAAIA,CAAO,GAC1BgV;AAAA,EACT;AAAA;AAAA,EAGA,IAAIhV,GAAqC;AACvC,UAAMgV,IAAO,KAAK,MAAA;AAClB,WAAAA,EAAK,KAAK,IAAIhV,EAAQ,IAAIA,CAAO,GAC5B,KAAK,KAAK,IAAIA,EAAQ,EAAE,KAC3BgV,EAAK,MAAM,KAAKhV,EAAQ,EAAE,GAErBgV;AAAA,EACT;AAAA;AAAA,EAGA,YAAYhV,GAAuB4uB,GAA+B;AAChE,UAAM5Z,IAAO,KAAK,MAAA;AAClB,IAAAA,EAAK,KAAK,IAAIhV,EAAQ,IAAIA,CAAO;AACjC,UAAM2uB,IAAM3Z,EAAK,MAAM,QAAQ4Z,CAAO;AACtC,WAAID,MAAQ,KACV3Z,EAAK,MAAM,OAAO2Z,IAAM,GAAG,GAAG3uB,EAAQ,EAAE,IAGxCgV,EAAK,MAAM,KAAKhV,EAAQ,EAAE,GAErBgV;AAAA,EACT;AAAA;AAAA,EAGA,OAAO9S,GAA0B;AAC/B,QAAI,CAAC,KAAK,KAAK,IAAIA,CAAE,EAAG,QAAO;AAC/B,UAAM8S,IAAO,KAAK,MAAA;AAClB,WAAAA,EAAK,KAAK,OAAO9S,CAAE,GACnB8S,EAAK,QAAQA,EAAK,MAAM,OAAO,CAAC6Z,MAAQA,MAAQ3sB,CAAE,GAC3C8S;AAAA,EACT;AAAA;AAAA,EAGA,QAAQ9S,GAAY4sB,GAAgC;AAClD,UAAM9Z,IAAO,KAAK,MAAA;AAClB,WAAAA,EAAK,QAAQA,EAAK,MAAM,OAAO,CAAC6Z,MAAQA,MAAQ3sB,CAAE,GAClD8S,EAAK,MAAM,OAAO8Z,GAAU,GAAG5sB,CAAE,GAC1B8S;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAWlT,GAAwC;AACjD,WAAO,IAAI0sB,EAAa1sB,CAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAOitB,GAAwD;AAC7D,UAAMC,IAAW,KAAK,OAAA,EAAS,OAAOD,CAAS;AAC/C,WAAO,IAAIP,EAAaQ,CAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAIC,GAAsD;AACxD,UAAMC,IAAS,KAAK,OAAA,EAAS,IAAID,CAAE;AACnC,WAAO,IAAIT,EAAaU,CAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAASC,GAAkC;AACzC,UAAMna,IAAO,KAAK,MAAA;AAClB,WAAAA,EAAK,QAAQma,EAAS,OAAO,CAACjtB,MAAO8S,EAAK,KAAK,IAAI9S,CAAE,CAAC,GAC/C8S;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAA0B;AACxB,WAAI,KAAK,iBAAiB,SACxB,KAAK,eAAe,KAAK,OAAA,IAEpB,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,OAAO,UAAUlT,GAAwC;AACvD,WAAO,IAAI0sB,EAAa1sB,CAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,QAAsB;AAC5B,UAAMstB,IAAQ,IAAIZ,EAAA;AAClB,WAAAY,EAAM,OAAO,IAAI,IAAI,KAAK,IAAI,GAC9BA,EAAM,QAAQ,CAAC,GAAG,KAAK,KAAK,GACrBA;AAAA,EACT;AACF;ACzNO,MAAMC,EAAoC;AAAA;AAAA;AAAA;AAAA,EAI/C,UAAgB;AACd,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACF;AAKO,MAAMC,WAA6BD,EAAQ;AAAA,EAMhD,YACEnvB,GACAqvB,GACAC,GACAC,GACA;AACA,UAAA,GACA,KAAK,YAAYvvB,GACjB,KAAK,aAAaqvB,IAAaA,EAAW,MAAA,IAAU,MACpD,KAAK,aAAaC,IAAaA,EAAW,MAAA,IAAU,MACpD,KAAK,WAAWC;AAAA,EAClB;AAAA,EAEA,UAAgB;AACd,IAAI,KAAK,cACP,KAAK,SAAS,KAAK,UAAU;AAAA,EAEjC;AAAA,EAEA,OAAa;AACX,IAAI,KAAK,cACP,KAAK,SAAS,KAAK,UAAU;AAAA,EAEjC;AACF;AAKO,MAAMC,WAA0BL,EAAQ;AAAA,EAK7C,YAAYrvB,GAAsB2vB,GAAuCC,GAAgC;AACvG,UAAA,GACA,KAAK,UAAU5vB,EAAQ,MAAA,GACvB,KAAK,QAAQ2vB,GACb,KAAK,WAAWC;AAAA,EAClB;AAAA,EAEA,UAAgB;AACd,SAAK,MAAM,KAAK,OAAO;AAAA,EACzB;AAAA,EAEA,OAAa;AACX,SAAK,SAAS,KAAK,QAAQ,EAAE;AAAA,EAC/B;AACF;AAKO,MAAMC,WAA6BR,EAAQ;AAAA,EAKhD,YAAYrvB,GAAsB2vB,GAAuCC,GAAgC;AACvG,UAAA,GACA,KAAK,UAAU5vB,EAAQ,MAAA,GACvB,KAAK,QAAQ2vB,GACb,KAAK,WAAWC;AAAA,EAClB;AAAA,EAEA,UAAgB;AACd,SAAK,SAAS,KAAK,QAAQ,EAAE;AAAA,EAC/B;AAAA,EAEA,OAAa;AACX,SAAK,MAAM,KAAK,OAAO;AAAA,EACzB;AACF;AAgCO,MAAME,WAAwBT,EAAQ;AAAA,EAG3C,YAAYU,GAAqB;AAC/B,UAAA,GACA,KAAK,WAAWA;AAAA,EAClB;AAAA,EAEA,UAAgB;AACd,SAAK,SAAS,QAAQ,CAACC,MAAQA,EAAI,SAAS;AAAA,EAC9C;AAAA,EAEA,OAAa;AAEX,aAAS7wB,IAAI,KAAK,SAAS,SAAS,GAAGA,KAAK,GAAGA;AAC7C,WAAK,SAASA,CAAC,EAAE,KAAA;AAAA,EAErB;AACF;AAKO,MAAM8wB,GAAe;AAAA,EAK1B,YAAYC,IAAkB,IAAI;AAChC,SAAK,UAAU,CAAA,GACf,KAAK,eAAe,IACpB,KAAK,UAAUA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQC,GAAwB;AAE9B,IAAAA,EAAQ,QAAA,GAGR,KAAK,UAAU,KAAK,QAAQ,MAAM,GAAG,KAAK,eAAe,CAAC,GAG1D,KAAK,QAAQ,KAAKA,CAAO,GACzB,KAAK,gBAGD,KAAK,QAAQ,SAAS,KAAK,YAC7B,KAAK,QAAQ,MAAA,GACb,KAAK;AAAA,EAET;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAaJ,GAA2B;AACtC,QAAIA,EAAS,WAAW;AACtB;AAGF,UAAMK,IAAW,IAAIN,GAAgBC,CAAQ;AAC7C,SAAK,QAAQK,CAAQ;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAgB;AACd,WAAK,KAAK,aAIM,KAAK,QAAQ,KAAK,YAAY,EACtC,KAAA,GACR,KAAK,gBACE,MANE;AAAA,EAOX;AAAA;AAAA;AAAA;AAAA,EAKA,OAAgB;AACd,WAAK,KAAK,aAIV,KAAK,gBACW,KAAK,QAAQ,KAAK,YAAY,EACtC,QAAA,GACD,MANE;AAAA,EAOX;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACjB,WAAO,KAAK,gBAAgB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACjB,WAAO,KAAK,eAAe,KAAK,QAAQ,SAAS;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,UAAU,CAAA,GACf,KAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,WAA8F;AAC5F,WAAO;AAAA,MACL,SAAS,KAAK,QAAA;AAAA,MACd,SAAS,KAAK,QAAA;AAAA,MACd,aAAa,KAAK,QAAQ;AAAA,MAC1B,cAAc,KAAK;AAAA,IAAA;AAAA,EAEvB;AACF;AAKO,MAAMC,WAA8BhB,EAAQ;AAAA,EAIjD,YAAYruB,GAA2BsvB,GAAkC;AACvE,UAAA,GACA,KAAK,WAAWtvB,EAAS,MAAA,GACzB,KAAK,kBAAkBsvB;AAAA,EACzB;AAAA,EAEA,UAAgB;AAEd,UAAMtvB,IAAW,KAAK,SAAS,MAAA;AAC/B,SAAK,gBAAgB,eAAe;AAAA,MAClC,IAAIA,EAAS;AAAA,MACb,MAAMA,EAAS;AAAA,MACf,GAAGA,EAAS;AAAA,MACZ,GAAGA,EAAS;AAAA,MACZ,OAAOA,EAAS;AAAA,MAChB,QAAQA,EAAS;AAAA,MACjB,iBAAiBA,EAAS;AAAA,IAAA,CAC3B;AAAA,EACH;AAAA,EAEA,OAAa;AACX,SAAK,gBAAgB,eAAe,KAAK,SAAS,EAAE;AAAA,EACtD;AACF;AAKO,MAAMuvB,WAA8BlB,EAAQ;AAAA,EAKjD,YAAYruB,GAA2BsvB,GAAkC;AACvE,UAAA,GACA,KAAK,WAAWtvB,EAAS,MAAA,GACzB,KAAK,kBAAkBsvB,GACvB,KAAK,qBAAqBtvB,EAAS,cAAA;AAAA,EACrC;AAAA,EAEA,UAAgB;AACd,SAAK,gBAAgB,eAAe,KAAK,SAAS,EAAE;AAAA,EACtD;AAAA,EAEA,OAAa;AAEX,UAAMA,IAAW,KAAK,SAAS,MAAA;AAC/B,SAAK,gBAAgB,eAAe;AAAA,MAClC,IAAIA,EAAS;AAAA,MACb,MAAMA,EAAS;AAAA,MACf,GAAGA,EAAS;AAAA,MACZ,GAAGA,EAAS;AAAA,MACZ,OAAOA,EAAS;AAAA,MAChB,QAAQA,EAAS;AAAA,MACjB,iBAAiBA,EAAS;AAAA,MAC1B,YAAY,KAAK;AAAA,IAAA,CAClB,GAGD,KAAK,mBAAmB,QAAQ,CAACd,MAAc;AAC7C,WAAK,gBAAgB,qBAAqBA,GAAWc,EAAS,EAAE;AAAA,IAClE,CAAC;AAAA,EACH;AACF;AAKO,MAAMwvB,WAA8BnB,EAAQ;AAAA,EAMjD,YACEpuB,GACAwvB,GACAC,GACAJ,GACA;AACA,UAAA,GACA,KAAK,aAAarvB,GAClB,KAAK,gBAAgB,EAAE,GAAGwvB,EAAA,GAC1B,KAAK,gBAAgB,EAAE,GAAGC,EAAA,GAC1B,KAAK,kBAAkBJ;AAAA,EACzB;AAAA,EAEA,UAAgB;AACd,SAAK,gBAAgB,eAAe,KAAK,YAAY,KAAK,aAAa;AAAA,EACzE;AAAA,EAEA,OAAa;AACX,SAAK,gBAAgB,eAAe,KAAK,YAAY,KAAK,aAAa;AAAA,EACzE;AACF;AAKO,MAAMK,WAA8BtB,EAAQ;AAAA,EAQjD,YACEuB,GACAC,GACAC,GACAC,GACAC,GACA;AACA,UAAA,GACA,KAAK,YAAYJ,GACjB,KAAK,WAAWC,GAChB,KAAK,WAAWC,GAChB,KAAK,WAAW,CAAC,GAAGC,CAAY,GAChC,KAAK,YAAYC,GAGjB,KAAK,WAAW,KAAK,kBAAA;AAAA,EACvB;AAAA,EAEQ,oBAA8B;AACpC,UAAMC,IAAQ,CAAC,GAAG,KAAK,QAAQ,GAGzBC,IAAeD,EAAM,QAAQ,KAAK,SAAS;AACjD,QAAIC,MAAiB,GAAI,QAAOD;AAChC,IAAAA,EAAM,OAAOC,GAAc,CAAC;AAG5B,UAAMC,IAAcF,EAAM,QAAQ,KAAK,QAAQ;AAC/C,QAAIE,MAAgB,GAAI,QAAOF;AAG/B,UAAMG,IAAc,KAAK,aAAa,WAAWD,IAAcA,IAAc;AAC7E,WAAAF,EAAM,OAAOG,GAAa,GAAG,KAAK,SAAS,GAEpCH;AAAA,EACT;AAAA,EAEA,UAAgB;AACd,SAAK,UAAU,KAAK,QAAQ;AAAA,EAC9B;AAAA,EAEA,OAAa;AACX,SAAK,UAAU,KAAK,QAAQ;AAAA,EAC9B;AACF;ACnaA,MAAMnpB,KAASC,GAAa,sBAAsB;AAU3C,MAAMspB,GAAqB;AAAA,EAQhC,YAAYf,GAAkCgB,IAAyB,IAAI;AAF3E,SAAQ,2BAAsG,CAAA,GAG5G,KAAK,gBAAgB,IAAIrB,GAAeqB,CAAc,GACtD,KAAK,wCAAwB,IAAA,GAC7B,KAAK,gBAAgB,MACrB,KAAK,kBAAkBhB,GACvB,KAAK,iBAAiBgB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkBC,GAA0F;AAC1G,gBAAK,yBAAyB,KAAKA,CAAQ,GACpC,MAAM;AACX,YAAM9c,IAAQ,KAAK,yBAAyB,QAAQ8c,CAAQ;AAC5D,MAAI9c,IAAQ,MACV,KAAK,yBAAyB,OAAOA,GAAO,CAAC;AAAA,IAEjD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB0b,GAAkB/c,GAAmBnS,GAA2B;AAC5F,eAAWswB,KAAY,KAAK;AAC1B,UAAI;AACF,QAAAA,EAASpB,GAAS/c,GAAMnS,CAAU;AAAA,MACpC,SAASmH,GAAO;AACd,QAAAN,GAAO,MAAM,uCAAuCM,CAAK;AAAA,MAC3D;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc+nB,GAAwB;AACpC,SAAK,cAAc,QAAQA,CAAO,GAClC,KAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,WAAW,KAAK,IAAA;AAAA,IAAI,GAEtB,KAAK,sBAAsBA,GAAS,QAAQ;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmBJ,GAA2B;AAC5C,QAAIA,EAAS,WAAW;AACtB;AAEF,SAAK,cAAc,aAAaA,CAAQ,GACxC,KAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,WAAW,KAAK,IAAA;AAAA,IAAI;AAGtB,UAAMyB,IAAkB,KAAK,cAAc,QAAQ,KAAK,cAAc,YAAY;AAClF,SAAK,sBAAsBA,GAAiB,QAAQ;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,uBAAuBvwB,GAAoB8uB,GAA2B;AACpE,QAAIA,EAAS,WAAW;AACtB;AAEF,QAAI0B,IAAU,KAAK,kBAAkB,IAAIxwB,CAAU;AACnD,IAAKwwB,MACHA,IAAU,IAAIxB,GAAe,KAAK,cAAc,GAChD,KAAK,kBAAkB,IAAIhvB,GAAYwwB,CAAO,IAEhDA,EAAQ,aAAa1B,CAAQ,GAC7B,KAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,YAAA9uB;AAAA,MACA,WAAW,KAAK,IAAA;AAAA,IAAI;AAEtB,UAAMuwB,IAAkBC,EAAQ,QAAQA,EAAQ,YAAY;AAC5D,SAAK,sBAAsBD,GAAiB,YAAYvwB,CAAU;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkBA,GAAoBkvB,GAAwB;AAE5D,QAAIsB,IAAU,KAAK,kBAAkB,IAAIxwB,CAAU;AACnD,IAAKwwB,MACHA,IAAU,IAAIxB,GAAe,KAAK,cAAc,GAChD,KAAK,kBAAkB,IAAIhvB,GAAYwwB,CAAO,IAGhDA,EAAQ,QAAQtB,CAAO,GACvB,KAAK,gBAAgB;AAAA,MACnB,MAAM;AAAA,MACN,YAAAlvB;AAAA,MACA,WAAW,KAAK,IAAA;AAAA,IAAI,GAEtB,KAAK,sBAAsBkvB,GAAS,YAAYlvB,CAAU;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAaA,GAAoC;AAC/C,UAAMwwB,IAAU,KAAK,kBAAkB,IAAIxwB,CAAU;AACrD,WAAKwwB,IACEA,EAAQ,QAAQA,EAAQ,YAAY,KAAK,OAD3B;AAAA,EAEvB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAgB;AAEd,QAAI,KAAK,eAAe;AACtB,UAAI,KAAK,cAAc,SAAS;AAC9B,eAAO,KAAK,WAAA;AACd,UAAW,KAAK,cAAc;AAC5B,eAAO,KAAK,aAAa,KAAK,cAAc,UAAU;AAAA,IAE1D;AAGA,UAAMC,IAAmB,KAAK,gBAAgB,oBAAA;AAC9C,WAAIA,KAAoB,KAAK,gBAAgBA,CAAgB,IACpD,KAAK,aAAaA,CAAgB,IAGvC,KAAK,kBACA,KAAK,WAAA,IAGP;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAgB;AAEd,QAAI,KAAK,eAAe;AACtB,UAAI,KAAK,cAAc,SAAS;AAC9B,eAAO,KAAK,WAAA;AACd,UAAW,KAAK,cAAc;AAC5B,eAAO,KAAK,aAAa,KAAK,cAAc,UAAU;AAAA,IAE1D;AAGA,UAAMA,IAAmB,KAAK,gBAAgB,oBAAA;AAC9C,WAAIA,KAAoB,KAAK,gBAAgBA,CAAgB,IACpD,KAAK,aAAaA,CAAgB,IAGvC,KAAK,kBACA,KAAK,WAAA,IAGP;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,UAAMhD,IAAS,KAAK,cAAc,KAAA;AAClC,WAAIA,KACF,KAAK,oBAAoB,QAAQ,GAE5BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,UAAMA,IAAS,KAAK,cAAc,KAAA;AAClC,WAAIA,KACF,KAAK,oBAAoB,QAAQ,GAE5BA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAaztB,GAA6B;AACxC,UAAMwwB,IAAU,KAAK,kBAAkB,IAAIxwB,CAAU;AACrD,QAAI,CAACwwB;AACH,aAAO;AAGT,UAAM/C,IAAS+C,EAAQ,KAAA;AACvB,WAAI/C,KACF,KAAK,oBAAoB,YAAYztB,CAAU,GAE1CytB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAaztB,GAA6B;AACxC,UAAMwwB,IAAU,KAAK,kBAAkB,IAAIxwB,CAAU;AACrD,QAAI,CAACwwB;AACH,aAAO;AAGT,UAAM/C,IAAS+C,EAAQ,KAAA;AACvB,WAAI/C,KACF,KAAK,oBAAoB,YAAYztB,CAAU,GAE1CytB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAyB;AACvB,WAAO,KAAK,cAAc,QAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAyB;AACvB,WAAO,KAAK,cAAc,QAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgBztB,GAA6B;AAC3C,UAAMwwB,IAAU,KAAK,kBAAkB,IAAIxwB,CAAU;AACrD,WAAOwwB,IAAUA,EAAQ,QAAA,IAAY;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgBxwB,GAA6B;AAC3C,UAAMwwB,IAAU,KAAK,kBAAkB,IAAIxwB,CAAU;AACrD,WAAOwwB,IAAUA,EAAQ,QAAA,IAAY;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AAEjB,QAAI,KAAK,eAAe;AACtB,UAAI,KAAK,cAAc,SAAS;AAC9B,eAAO,KAAK,cAAA;AACd,UAAW,KAAK,cAAc;AAC5B,eAAO,KAAK,gBAAgB,KAAK,cAAc,UAAU;AAAA,IAE7D;AAGA,UAAMC,IAAmB,KAAK,gBAAgB,oBAAA;AAC9C,WAAIA,KAAoB,KAAK,gBAAgBA,CAAgB,IACpD,KAGF,KAAK,cAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AAEjB,QAAI,KAAK,eAAe;AACtB,UAAI,KAAK,cAAc,SAAS;AAC9B,eAAO,KAAK,cAAA;AACd,UAAW,KAAK,cAAc;AAC5B,eAAO,KAAK,gBAAgB,KAAK,cAAc,UAAU;AAAA,IAE7D;AAGA,UAAMA,IAAmB,KAAK,gBAAgB,oBAAA;AAC9C,WAAIA,KAAoB,KAAK,gBAAgBA,CAAgB,IACpD,KAGF,KAAK,cAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,cAAc,MAAA,GACnB,KAAK,kBAAkB,MAAA,GACvB,KAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqBzwB,GAA0B;ArC1VjD,QAAAsC;AqC2VI,SAAK,kBAAkB,OAAOtC,CAAU,KACpCsC,IAAA,KAAK,kBAAL,gBAAAA,EAAoB,gBAAetC,MACrC,KAAK,gBAAgB;AAAA,EAEzB;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmBA,GAAoC;AACrD,QAAIwwB,IAAU,KAAK,kBAAkB,IAAIxwB,CAAU;AACnD,WAAKwwB,MACHA,IAAU,IAAIxB,GAAe,KAAK,cAAc,GAChD,KAAK,kBAAkB,IAAIhvB,GAAYwwB,CAAO,IAEzCA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmC;AACjC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,WAeE;AACA,UAAME,IAAc,KAAK,cAAc,SAAA,GACjCC,wBAAqB,IAAA;AAE3B,gBAAK,kBAAkB,QAAQ,CAACH,GAASxwB,MAAe;AACtD,YAAM4wB,IAAQJ,EAAQ,SAAA;AACtB,MAAAG,EAAe,IAAI3wB,GAAY;AAAA,QAC7B,SAAS4wB,EAAM;AAAA,QACf,SAASA,EAAM;AAAA,QACf,aAAaA,EAAM;AAAA,MAAA,CACpB;AAAA,IACH,CAAC,GAEM;AAAA,MACL,QAAQ;AAAA,QACN,SAASF,EAAY;AAAA,QACrB,SAASA,EAAY;AAAA,QACrB,aAAaA,EAAY;AAAA,MAAA;AAAA,MAE3B,WAAWC;AAAA,MACX,eAAe,KAAK;AAAA,IAAA;AAAA,EAExB;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoBxe,GAAmBnS,GAA2B;AACxE,SAAK,gBAAgB;AAAA,MACnB,MAAAmS;AAAA,MACA,YAAAnS;AAAA,MACA,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAExB;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC3B,QAAI,KAAK,eAAe;AACtB,UAAI,KAAK,cAAc,SAAS;AAC9B,eAAO;AACT,UAAW,KAAK,cAAc,YAAY;AACxC,cAAMD,IAAW,KAAK,gBAAgB,YAAY,KAAK,cAAc,UAAU;AAC/E,eAAOA,IAAW,YAAYA,EAAS,IAAI,MAAM;AAAA,MACnD;AAAA,IACF;AAEA,UAAM0wB,IAAmB,KAAK,gBAAgB,oBAAA;AAC9C,QAAIA,KAAoB,KAAK,gBAAgBA,CAAgB,GAAG;AAC9D,YAAM1wB,IAAW,KAAK,gBAAgB,YAAY0wB,CAAgB;AAClE,aAAO1wB,IAAW,YAAYA,EAAS,IAAI,MAAM;AAAA,IACnD;AAEA,WAAI,KAAK,kBACA,4BAGF;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC3B,QAAI,KAAK,eAAe;AACtB,UAAI,KAAK,cAAc,SAAS;AAC9B,eAAO;AACT,UAAW,KAAK,cAAc,YAAY;AACxC,cAAMA,IAAW,KAAK,gBAAgB,YAAY,KAAK,cAAc,UAAU;AAC/E,eAAOA,IAAW,YAAYA,EAAS,IAAI,MAAM;AAAA,MACnD;AAAA,IACF;AAEA,UAAM0wB,IAAmB,KAAK,gBAAgB,oBAAA;AAC9C,QAAIA,KAAoB,KAAK,gBAAgBA,CAAgB,GAAG;AAC9D,YAAM1wB,IAAW,KAAK,gBAAgB,YAAY0wB,CAAgB;AAClE,aAAO1wB,IAAW,YAAYA,EAAS,IAAI,MAAM;AAAA,IACnD;AAEA,WAAI,KAAK,kBACA,4BAGF;AAAA,EACT;AACF;ACtaA,MAAM8wB,IAAM/pB,GAAa,cAAc;AAIvC,IAAIgqB,KAAiD;AAErD,eAAeC,KAAa;AAC1B,SAAKD,OACHA,KAAgB,MAAM,OAAO,8BAAS,IAEjCA;AACT;AAQA,MAAME,GAAoB;AAAA,EAA1B,cAAA;AACE,SAAQ,gCAA6C,IAAA,GACrD,KAAQ,kCAA+B,IAAA,GACvC,KAAiB,iBAAiB,MAAO,KAAK,IAC9C,KAAiB,iBAAiB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM1B,kBAAwB;AAC9B,QAAI,KAAK,UAAU,QAAQ,KAAK,eAAgB;AAGhD,QAAIC,IAA2B,MAC3BC,IAAa,KAAK,IAAA;AAEtB,SAAK,UAAU,QAAQ,CAAC1pB,GAAO2pB,MAAQ;AACrC,MAAI3pB,EAAM,YAAY0pB,MACpBA,IAAa1pB,EAAM,WACnBypB,IAAYE;AAAA,IAEhB,CAAC,GAEGF,KACF,KAAK,UAAU,OAAOA,CAAS;AAAA,EAEnC;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAazZ,GAA6B;AAChD,WAAOtR,GAAa,IAAIsR,CAAU;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiBA,GAAoB4J,IAAiB,KAAa;AAGzE,WAAO,4CADiB5J,EAAW,QAAQ,QAAQ,GAAG,CACY,SAAS4J,CAAM;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBAAmB5J,GAAoB4J,IAAiB,KAA6B;AACjG,QAAI;AACF,YAAMgQ,IAAS,KAAK,iBAAiB5Z,GAAY4J,CAAM,GAGjDiQ,IAAM,OADK,MAAM,MAAMD,CAAM,GACR,KAAA,GAIrBE,IAAgB,2BAChBC,IAAY,MAAM,KAAKF,EAAI,SAASC,CAAa,CAAC;AAExD,UAAIC,EAAU,WAAW;AACvB,eAAAV,EAAI,KAAK,4BAA4B,GAC9B;AAMT,UAAIW,IAAyB,MACzBC,IAAY;AAEhB,eAASvzB,IAAI,GAAGA,IAAIqzB,EAAU,QAAQrzB,KAAK;AACzC,cAAMwzB,IAAkBH,EAAUrzB,CAAC,EAAE,CAAC,GAChCyzB,IAAWD,EAAgB,MAAM,+CAA+C,GAChFE,IAAkBF,EAAgB,SAAS,eAAe,GAC1DG,IAAoBH,EAAgB,MAAM,2BAA2B;AAE3E,YAAI,CAACC,EAAU;AAEf,cAAMtqB,IAAMsqB,EAAS,CAAC;AACtB,YAAIG,IAAQ;AAGZ,QAAKF,IAIIC,MAEPC,KADqBD,EAAkB,CAAC,EAClB,MAAM,MAAM,KAAK,CAAA,GAAI,UAL3CC,IAAQ,KASNA,IAAQL,MACVA,IAAYK,GACZN,IAAUnqB;AAAA,MAEd;AAEA,aAAImqB,MAIJX,EAAI,KAAK,gCAAgCrZ,CAAU,GAC5C;AAAA,IACT,SAASrQ,GAAO;AACd,aAAA0pB,EAAI,MAAM,mCAAmC1pB,CAAK,GAC3C;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAASqQ,GAAoB4J,IAAiB,KAAkC;AAEpF,QAAI,KAAK,aAAa5J,CAAU;AAC9B,aAAO;AAGT,UAAMua,IAAW,GAAGva,CAAU,IAAI4J,CAAM;AAGxC,QAAI,KAAK,YAAY,IAAI2Q,CAAQ;AAC/B,aAAO;AAIT,UAAMC,IAAS,KAAK,UAAU,IAAID,CAAQ;AAC1C,QAAIC,KAAU,KAAK,IAAA,IAAQA,EAAO,YAAY,KAAK;AAEjD,aAAAA,EAAO,YAAY,KAAK,IAAA,GACjBA,EAAO;AAGhB,QAAI;AAGF,YAAMC,IAAU,MAAM,KAAK,mBAAmBza,GAAY4J,CAAM;AAChE,UAAI,CAAC6Q;AAEH,oBAAK,YAAY,IAAIF,CAAQ,GACtB;AAKT,YAAMG,IAAW,MAAM,MAAMD,CAAO;AACpC,UAAI,CAACC,EAAS;AACZ,eAAArB,EAAI,MAAM,sBAAsBqB,EAAS,MAAM,IAAIA,EAAS,UAAU,EAAE,GACxE,KAAK,YAAY,IAAIH,CAAQ,GACtB;AAGT,YAAMI,IAAc,MAAMD,EAAS,YAAA,GAG7BE,IAAS,IAAI,WAAWD,CAAW,GAGnCE,KAFU,MAAMtB,GAAA,GAEW,OAAOqB,CAA2B,GAG7DE,IACJ,WAAWD,IACPA,EAAiB,MAAM,CAAC,IACxBA;AAIN,kBAAK,UAAU,IAAIN,GAAU;AAAA,QAC3B,MAAAO;AAAA,QACA,KAAKL;AAAA,QACL,WAAW,KAAK,IAAA;AAAA,MAAI,CACrB,GAGD,KAAK,gBAAA,GAEEK;AAAA,IACT,SAASnrB,GAAO;AACd,aAAA0pB,EAAI,MAAM,sBAAsBrZ,CAAU,KAAKrQ,CAAK,GAEpD,KAAK,YAAY,IAAI4qB,CAAQ,GACtB;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAYva,GAAoB4J,IAAiB,KAAyB;AACxE,UAAM2Q,IAAW,GAAGva,CAAU,IAAI4J,CAAM,IAClC4Q,IAAS,KAAK,UAAU,IAAID,CAAQ;AAE1C,WAAIC,KAAU,KAAK,IAAA,IAAQA,EAAO,YAAY,KAAK,kBAEjDA,EAAO,YAAY,KAAK,IAAA,GACjBA,EAAO,QAGT;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAWxa,GAAqB4J,GAAuB;AACrD,QAAI5J,GAAY;AACd,YAAMua,IAAW,GAAGva,CAAU,IAAI4J,KAAU,GAAG;AAC/C,WAAK,UAAU,OAAO2Q,CAAQ,GAC9B,KAAK,YAAY,OAAOA,CAAQ;AAAA,IAClC;AACE,WAAK,UAAU,MAAA,GACf,KAAK,YAAY,MAAA;AAAA,EAErB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aACJva,GACA4J,IAAiB,KACjBmR,IAAgB,MACW;AtC5S/B,QAAAjwB,GAAAuG;AsC8SI,QAAI,KAAK,aAAa2O,CAAU;AAC9B,aAAO,CAAA;AAGT,UAAM8a,IAAO,MAAM,KAAK,SAAS9a,GAAY4J,CAAM;AACnD,QAAI,CAACkR;AACH,aAAO,CAAA;AAGT,UAAME,IAA2B,CAAA,GAI3BC,IAAY,KAAK,IAAIH,EAAK,WAAWC,CAAK;AAChD,aAASG,IAAU,GAAGA,IAAUD,GAAWC;AACzC,UAAI;AACF,cAAMC,IAAQL,EAAK,SAASI,CAAO;AAInC,YAHI,CAACC,KAAS,CAACA,EAAM,QAGjBA,EAAM,SAAS,aAAaA,EAAM,KAAK,WAAW,OAAO;AAC3D;AAIF,cAAMC,IAAaD,EAAM,cAAc,CAAA;AACvC,YAAIE,IAAUD,EAAW,SAAS,IAAI,OAAO,cAAcA,EAAW,CAAC,CAAC,IAAI;AAI5E,YAAI,CAACC,KAAWA,EAAQ,WAAW,GAAG;AACpC,gBAAMC,IAAgBH,EAAM,KAAK,MAAM,oBAAoB;AAC3D,UAAIG,MACFD,IAAUC,EAAc,CAAC;AAAA,QAE7B;AAGA,YAAIC,IAAeJ,EAAM;AACzB,YAAIE,KAAWA,EAAQ,SAAS,KAAK,KAAK,KAAKA,CAAO;AAEpD,cAAIF,EAAM,KAAK,SAAS,QAAQ;AAC9B,YAAAI,IAAe,GAAGF,CAAO;AAAA,mBAChBF,EAAM,KAAK,MAAM,aAAa,GAAG;AAC1C,kBAAMK,MAAM1wB,IAAAqwB,EAAM,KAAK,MAAM,YAAY,MAA7B,gBAAArwB,EAAiC,OAAM;AACnD,YAAAywB,IAAe,GAAGF,CAAO,aAAaG,IAAM,MAAMA,IAAM,EAAE;AAAA,UAC5D,WAAWL,EAAM,KAAK,MAAM,WAAW,GAAG;AACxC,kBAAMK,KAAMnqB,IAAA8pB,EAAM,KAAK,MAAM,WAAW,MAA5B,gBAAA9pB,EAAgC;AAC5C,YAAAkqB,IAAe,GAAGF,CAAO,kBAAkBG,CAAG;AAAA,UAChD;AAEE,YAAAD,IAAe,GAAGF,CAAO,MAAMF,EAAM,IAAI;AAK7C,YAAIM,IAAuC;AAC3C,cAAMC,IAAYP,EAAM,KAAK,YAAA;AAe7B,YAbIO,EAAU,SAAS,OAAO,KAAKA,EAAU,SAAS,MAAM,IAC1DD,IAAW,UACFN,EAAM,KAAK,MAAM,mCAAmC,KAEpDA,EAAM,KAAK,MAAM,OAAO,IADjCM,IAAW,cAIFC,EAAU,MAAM,WAAW,MACpCD,IAAW,aAKTA,MAAa;AACf;AAIF,QAAIJ,KAAWA,EAAQ,SAAS,KAC9BL,EAAO,KAAK;AAAA,UACV,YAAYG,EAAM;AAAA,UAClB,SAAAE;AAAA,UACA,MAAME;AAAA,UACN,UAAAE;AAAA,UACA,gBAAgB,KAAK,qBAAqBX,GAAMK,EAAM,IAAI,EAAE;AAAA,QAAA,CAC7D;AAAA,MAEL,QAAY;AAAA,MAEZ;AAGF,WAAOH;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAmBhb,GAAoB2b,GAAmB/R,IAAiB,KAAgC;AtC/YnH,QAAA9e,GAAAuG,GAAAC,GAAAC;AsCgZI,UAAMupB,IAAO,MAAM,KAAK,SAAS9a,GAAY4J,CAAM;AACnD,QAAI,CAACkR;AACH,aAAO,CAAA;AAGT,UAAMc,IAA+B,CAAA,GAG/BC,IAAYF,EAAU,YAAY,CAAC;AACzC,QAAIE,MAAc;AAChB,aAAO,CAAA;AAGT,UAAMC,IAAehB,EAAK,kBAAkBe,CAAS;AACrD,IAAIC,KACFF,EAAW,KAAK;AAAA,MACd,YAAYE,EAAa;AAAA,MACzB,SAASH;AAAA,MACT,MAAMG,EAAa,QAAQ;AAAA,MAC3B,UAAU;AAAA,MACV,gBAAgB,KAAK,qBAAqBhB,GAAMgB,EAAa,IAAI,EAAE;AAAA,IAAA,CACpE;AAKH,UAAMC,IAAWD,KAAA,gBAAAA,EAAc;AAG/B,QAAIC,GAAU;AACZ,UAAIC,IAAa;AAGjB,eAASd,IAAU,GAAGA,IAAUJ,EAAK,WAAWI;AAC9C,YAAI;AACF,gBAAMC,IAAQL,EAAK,SAASI,CAAO;AACnC,cAAI,CAACC,KAAS,CAACA,EAAM,KAAM;AAY3B,cARqB;AAAA,YACnB,IAAI,OAAO,IAAIY,CAAQ,uCAAuC,GAAG;AAAA,YACjE,IAAI,OAAO,IAAIA,CAAQ,UAAU,GAAG;AAAA,YACpC,IAAI,OAAO,IAAIA,CAAQ,YAAY,GAAG;AAAA,UAAA,EAGT,KAAK,CAACE,MAAYA,EAAQ,KAAKd,EAAM,IAAI,CAAC,KAExDA,EAAM,OAAOW,EAAa,IAAI;AAC7C,YAAAE;AAGA,gBAAIT,IAAeJ,EAAM;AACzB,gBAAIA,EAAM,KAAK,SAAS,QAAQ;AAC9B,cAAAI,IAAe,GAAGI,CAAS;AAAA,qBAClBR,EAAM,KAAK,MAAM,aAAa,GAAG;AAC1C,oBAAMK,MAAM1wB,IAAAqwB,EAAM,KAAK,MAAM,YAAY,MAA7B,gBAAArwB,EAAiC,OAAM;AACnD,cAAAywB,IAAe,GAAGI,CAAS,aAAaH,IAAM,MAAMA,IAAM,EAAE;AAAA,YAC9D,WAAWL,EAAM,KAAK,MAAM,WAAW,GAAG;AACxC,oBAAMK,KAAMnqB,IAAA8pB,EAAM,KAAK,MAAM,WAAW,MAA5B,gBAAA9pB,EAAgC;AAC5C,cAAAkqB,IAAe,GAAGI,CAAS,kBAAkBH,CAAG;AAAA,YAClD;AAEA,YAAAI,EAAW,KAAK;AAAA,cACd,YAAYT,EAAM;AAAA,cAClB,SAASQ;AAAA,cACT,MAAMJ;AAAA,cACN,UAAU;AAAA,cACV,gBAAgB,KAAK,qBAAqBT,GAAMK,EAAM,IAAI,EAAE;AAAA,YAAA,CAC7D;AAAA,UACH;AAAA,QACF,QAAY;AAAA,QAEZ;AAAA,IAGJ;AAGA,QAAIL,EAAK,MAAM;AACb,YAAMoB,IAAOpB,EAAK;AAGlB,UAAIoB,EAAK;AACP,mBAAWC,KAAWD,EAAK,aAAa;AACtC,gBAAME,IAAMD,EAAQ;AAGpB,cAAK,KAAK,kBAAkBC,CAAG,KAK3BD,EAAQ,WAAWA,EAAQ,QAAQ;AACrC,uBAAWE,KAAeF,EAAQ,QAAQ,mBAAmB;AAC3D,oBAAMG,KAAS/qB,KAAAD,IAAA4qB,EAAK,eAAL,gBAAA5qB,EAAiB,YAAjB,gBAAAC,EAA2B8qB;AAC1C,kBAAKC;AAGL,oBAAIA,EAAO,eAAe;AACxB,6BAAWC,KAAYD,EAAO,aAAa,CAAA,GAAI;AAC7C,0BAAME,IAAWD,EAAS;AAC1B,wBAAI,CAACC,EAAU;AAIf,wBADsB,KAAK,iBAAiBA,GAAUV,EAAc,EAAE,MAChD,IAAI;AACxB,4BAAMW,IAAuB,KAAK,mBAAmBF,GAAqCT,EAAc,EAAE;AAC1G,sBAAIW,MAAyB,QAC3Bb,EAAW,KAAK;AAAA,wBACd,YAAYa;AAAA,wBACZ,SAASd;AAAA,wBACT,MAAM,GAAGA,CAAS,KAAKS,CAAG;AAAA,wBAC1B,UAAU,KAAK,kBAAkBA,CAAG;AAAA,wBACpC,gBAAgB,KAAK,qBAAqBtB,GAAM2B,GAAsB,EAAE;AAAA,sBAAA,CACzE;AAAA,oBAEL;AAAA,kBACF;AAAA,yBAIOH,EAAO,eAAe;AAC7B,6BAAWC,KAAYD,EAAO,aAAa,CAAA,GAAI;AAC7C,0BAAME,IAAWD,EAAS;AAC1B,wBAAI,CAACC,EAAU;AAEf,0BAAME,IAAgB,KAAK,iBAAiBF,GAAUV,EAAc,EAAE,GAChEa,IAAcJ;AACpB,wBAAIG,MAAkB,MAAMC,EAAY,eAAe;AACrD,4BAAMC,IAAeD,EAAY,cAAcD,CAAa;AAC5D,0BAAIE;AACF,mCAAWC,KAAiBD;AAC1B,0BAAAhB,EAAW,KAAK;AAAA,4BACd,YAAYiB;AAAA,4BACZ,SAASlB;AAAA,4BACT,MAAM,GAAGA,CAAS,KAAKS,CAAG;AAAA,4BAC1B,UAAU,KAAK,kBAAkBA,CAAG;AAAA,4BACpC,gBAAgB,KAAK,qBAAqBtB,GAAM+B,GAAe,EAAE;AAAA,0BAAA,CAClE;AAAA,oBAGP;AAAA,kBACF;AAAA;AAAA,YAEJ;AAAA,QAEJ;AAAA,IAEJ;AAEA,WAAOjB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAqB5b,GAAoB4J,IAAiB,KAAwB;AAEtF,UAAMkR,IAAO,MAAM,KAAK,SAAS9a,GAAY4J,CAAM;AACnD,QAAI,CAACkR;AACH,aAAAzB,EAAI,KAAK,QAAQrZ,CAAU,iBAAiB,GACrC,CAAA;AAGT,QAAI;AACF,YAAM8c,IAAqB,CAAA;AAG3B,UAAIhC,EAAK,QAAQA,EAAK,KAAK,aAAa;AACtC,cAAMiC,wBAAqB,IAAA;AAC3B,mBAAWZ,KAAWrB,EAAK,KAAK;AAC9B,UAAIqB,EAAQ,OACVY,EAAe,IAAIZ,EAAQ,GAAG;AAGlC,QAAAW,EAAS,KAAK,GAAG,MAAM,KAAKC,CAAc,CAAC;AAAA,MAC7C;AAGA,UAAIjC,EAAK,QAAQA,EAAK,KAAK,aAAa;AACtC,cAAMiC,wBAAqB,IAAA;AAC3B,mBAAWZ,KAAWrB,EAAK,KAAK;AAC9B,UAAIqB,EAAQ,OACVY,EAAe,IAAIZ,EAAQ,GAAG;AAGlC,QAAAW,EAAS,KAAK,GAAG,MAAM,KAAKC,CAAc,CAAC;AAAA,MAC7C;AAEA,aAAOD;AAAA,IACT,SAASntB,GAAO;AACd,aAAA0pB,EAAI,MAAM,8BAA8BrZ,CAAU,KAAKrQ,CAAK,GACrD,CAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAqBmrB,GAAmBkC,GAAoBC,IAAe,IAAY;AAC7F,UAAM1qB,IAAS,SAAS,cAAc,QAAQ;AAC9C,IAAAA,EAAO,QAAQ0qB,GACf1qB,EAAO,SAAS0qB;AAChB,UAAMtyB,IAAM4H,EAAO,WAAW,IAAI;AAElC,QAAI,CAAC5H;AACH,aAAO;AAGT,QAAI;AACF,YAAMwwB,IAAQL,EAAK,SAASkC,CAAU;AACtC,UAAI,CAAC7B;AACH,eAAO;AAIT,MAAAxwB,EAAI,UAAU,GAAG,GAAGsyB,GAAMA,CAAI;AAG9B,YAAMz1B,IAAO2zB,EAAM;AACnB,UAAI,CAAC3zB;AACH,eAAO;AAGT,YAAM01B,IAAa11B,EAAK,OAAOA,EAAK,MAC9B21B,IAAc31B,EAAK,OAAOA,EAAK;AAErC,UAAI01B,MAAe,KAAKC,MAAgB;AACtC,eAAO;AAKT,YAAMtkB,IAAQ,KAAK,IAAIokB,IAAOC,GAAYD,IAAOE,CAAW,IAD5C,KAKVC,KAAgB51B,EAAK,OAAOA,EAAK,QAAQ,GACzC61B,KAAgB71B,EAAK,OAAOA,EAAK,QAAQ,GAGzC81B,IAAgBL,IAAO,GACvBM,IAAgBN,IAAO,GAGvBO,IAAUrC,EAAM,KAAK,MAAA;AAC3B,UAAI,CAACqC;AACH,eAAO;AAIT,YAAMC,IAAS,IAAI,OAAOD,CAAO;AAGjC,aAAA7yB,EAAI,KAAA,GAGJA,EAAI,UAAU2yB,GAAeC,CAAa,GAG1C5yB,EAAI,MAAMkO,GAAO,CAACA,CAAK,GAGvBlO,EAAI,UAAU,CAACyyB,GAAc,CAACC,CAAY,GAE1C1yB,EAAI,YAAY,WAChBA,EAAI,KAAK8yB,CAAM,GACf9yB,EAAI,QAAA,GAGG4H,EAAO,UAAU,WAAW;AAAA,IACrC,SAAS5C,GAAO;AACd,aAAA0pB,EAAI,MAAM,mCAAmC1pB,CAAK,GAC3C;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkBysB,GAAsB;AAuC9C,WAtCyB;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,EAEsB,SAASA,CAAG;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkBA,GAAyC;AACjE,WAAIA,EAAI,WAAW,MAAM,KAAKA,MAAQ,SAAe,UACjDA,EAAI,WAAW,IAAI,KAAKA,EAAI,WAAW,IAAI,KAAKA,MAAQ,SAAe,cACvEA,MAAQ,UAAUA,MAAQ,SAAe,aACzCA,MAAQ,SAAe,eACpB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiBI,GAAmCQ,GAA4B;AACtF,QAAI,CAACR,EAAU,QAAO;AAGtB,QAAIA,EAAS,WAAW,KAAKA,EAAS;AACpC,aAAQA,EAAS,OAAoB,QAAQQ,CAAU;AAIzD,QAAIR,EAAS,WAAW,KAAKA,EAAS,QAAQ;AAC5C,YAAMkB,IAASlB,EAAS;AACxB,eAAS91B,IAAI,GAAGA,IAAIg3B,EAAO,QAAQh3B,KAAK;AACtC,cAAMi3B,IAAQD,EAAOh3B,CAAC;AACtB,YAAIs2B,KAAcW,EAAM,SAASX,KAAcW,EAAM;AACnD,iBAAOA,EAAM,SAASX,IAAaW,EAAM;AAAA,MAE7C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmBpB,GAAmCS,GAAmC;AAC/F,QAAI,CAACT,EAAU,QAAO;AAGtB,QAAIA,EAAS,gBAAgB,KAAK,OAAOA,EAAS,gBAAiB;AACjE,aAAOS,IAAaT,EAAS;AAI/B,QAAIA,EAAS,gBAAgB,KAAKA,EAAS,YAAY;AACrD,YAAMG,IAAgB,KAAK,iBAAiBH,EAAS,UAAqCS,CAAU,GAC9FY,IAAarB,EAAS;AAC5B,UAAIG,MAAkB,MAAMkB,EAAWlB,CAAa,MAAM;AACxD,eAAOkB,EAAWlB,CAAa;AAAA,IAEnC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,uBAAuB1c,GAAoB4J,IAAiB,KAAsB;AACtF,UAAMkR,IAAO,MAAM,KAAK,SAAS9a,GAAY4J,CAAM;AACnD,QAAI,CAACkR;AACH,aAAO;AAGT,QAAI+C,IAAQ;AAGZ,UAAM5C,IAAY,KAAK,IAAIH,EAAK,WAAW,IAAI;AAC/C,aAASI,IAAU,GAAGA,IAAUD,GAAWC;AACzC,UAAI;AACF,cAAMC,IAAQL,EAAK,SAASI,CAAO;AAInC,YAHI,CAACC,KAAS,CAACA,EAAM,QAGjBA,EAAM,SAAS,aAAaA,EAAM,KAAK,WAAW,OAAO;AAC3D;AAIF,cAAMC,IAAaD,EAAM,cAAc,CAAA;AACvC,YAAIE,IAAUD,EAAW,SAAS,IAAI,OAAO,cAAcA,EAAW,CAAC,CAAC,IAAI;AAG5E,YAAI,CAACC,KAAWA,EAAQ,WAAW,GAAG;AACpC,gBAAMC,IAAgBH,EAAM,KAAK,MAAM,oBAAoB;AAC3D,UAAIG,MACFD,IAAUC,EAAc,CAAC;AAAA,QAE7B;AAGA,YAAIG,IAAW;AACf,cAAMC,IAAYP,EAAM,KAAK,YAAA;AAE7B,QAAIO,EAAU,SAAS,OAAO,KAAKA,EAAU,SAAS,MAAM,IAC1DD,IAAW,UACFN,EAAM,KAAK,MAAM,mCAAmC,KAEpDA,EAAM,KAAK,MAAM,OAAO,IADjCM,IAAW,cAGFC,EAAU,MAAM,WAAW,MACpCD,IAAW,aAITA,MAAa,aAAaJ,KAAWA,EAAQ,SAAS,KACxDwC;AAAA,MAEJ,QAAY;AAAA,MAEZ;AAGF,WAAOA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAqD;AACnD,WAAO;AAAA,MACL,MAAM,KAAK,UAAU;AAAA,MACrB,SAAS,MAAM,KAAK,KAAK,UAAU,MAAM;AAAA,IAAA;AAAA,EAE7C;AACF;AAGO,MAAMC,KAAe,IAAItE,GAAA;","x_google_ignoreList":[0]}